mirror of
https://github.com/google/pebble.git
synced 2025-06-06 10:13:12 +00:00
141 lines
5.4 KiB
C
141 lines
5.4 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "i2c_timingr.h"
|
|
|
|
#include "util/attributes.h"
|
|
#include "util/math.h"
|
|
#include "util/units.h"
|
|
|
|
static const struct {
|
|
uint32_t clock_speed_max;
|
|
uint32_t min_scl_low_ps;
|
|
uint32_t min_scl_high_ps;
|
|
uint32_t min_sda_setup_ps;
|
|
} s_timing_data[] = {
|
|
[I2CBusMode_Standard] = {
|
|
.clock_speed_max = 100000,
|
|
.min_scl_low_ps = 4700000,
|
|
.min_scl_high_ps = 4000000,
|
|
.min_sda_setup_ps = 250000,
|
|
},
|
|
[I2CBusMode_FastMode] = {
|
|
.clock_speed_max = 400000,
|
|
.min_scl_low_ps = 1300000,
|
|
.min_scl_high_ps = 600000,
|
|
.min_sda_setup_ps = 100000,
|
|
},
|
|
};
|
|
|
|
// Per the STM32F7 reference manual, the I2C peripheral adds 2-3 cycles to sync SCL with I2CCLK. In
|
|
// practice, 3 has been observed.
|
|
#define I2C_SYNC_CYCLES (3)
|
|
|
|
#define TIMINGR_MAX_SCLL (0x100) // 8 bits storing (SCLL - 1)
|
|
#define TIMINGR_MAX_SCLH (0x100) // 8 bits storing (SCLH - 1)
|
|
#define TIMINGR_MAX_SCLDEL (0x10) // 4 bits storing (SCLDEL - 1)
|
|
#define TIMINGR_MAX_PRESC (0x10) // 4 bits storing (PRESC - 1)
|
|
|
|
typedef union PACKED TIMINGR {
|
|
struct {
|
|
uint32_t SCLL:8;
|
|
uint32_t SCLH:8;
|
|
uint32_t SDADEL:4;
|
|
uint32_t SCLDEL:4;
|
|
uint32_t reserved:4;
|
|
uint32_t PRESC:4;
|
|
};
|
|
uint32_t reg;
|
|
} TIMINGR;
|
|
_Static_assert(sizeof(TIMINGR) == sizeof(uint32_t), "Invalid TIMINGR size");
|
|
|
|
uint32_t i2c_timingr_calculate(uint32_t i2c_clk_frequency,
|
|
I2CBusMode bus_mode,
|
|
uint32_t target_bus_frequency,
|
|
uint32_t rise_time_ns,
|
|
uint32_t fall_time_ns) {
|
|
if (bus_mode != I2CBusMode_Standard && bus_mode != I2CBusMode_FastMode) {
|
|
// This is FM+ (or higher) and is not currently supported.
|
|
return I2C_TIMINGR_INVALID_VALUE;
|
|
}
|
|
if (target_bus_frequency > s_timing_data[bus_mode].clock_speed_max) {
|
|
return I2C_TIMINGR_INVALID_VALUE;
|
|
}
|
|
|
|
uint32_t min_scl_low_ps = s_timing_data[bus_mode].min_scl_low_ps;
|
|
uint32_t min_scl_high_ps = s_timing_data[bus_mode].min_scl_high_ps;
|
|
|
|
for (uint32_t prescaler = 1; prescaler <= TIMINGR_MAX_PRESC; ++prescaler) {
|
|
const uint32_t base_frequency = i2c_clk_frequency / prescaler;
|
|
const uint64_t base_period_ps = PS_PER_S / base_frequency;
|
|
|
|
// Calculate what the total SCL period should be in terms of base clock cycles and then
|
|
// recalculate the target frequency based on that value. The result will be the highest
|
|
// target frequency we can obtain without going over.
|
|
uint32_t total_scl_cycles = base_frequency / target_bus_frequency;
|
|
|
|
// Calculate the number of overhead cycles. This includes the rise time, fall time, and sync
|
|
// cycles for both SCLL and SCLH.
|
|
const uint32_t overhead_i2cclk_cycles =
|
|
DIVIDE_CEIL((rise_time_ns + fall_time_ns) * PS_PER_NS,
|
|
PS_PER_S / i2c_clk_frequency) +
|
|
I2C_SYNC_CYCLES * 2;
|
|
const uint32_t overhead_cycles = DIVIDE_CEIL(overhead_i2cclk_cycles, prescaler);
|
|
|
|
// Figure out how many base clock cycles the minimum SCL periods correspond to.
|
|
uint32_t scl_low = DIVIDE_CEIL(min_scl_low_ps, base_period_ps);
|
|
uint32_t scl_high = DIVIDE_CEIL(min_scl_high_ps, base_period_ps);
|
|
|
|
// Calculate the number of extra cycles we have.
|
|
const int32_t extra_cycles = total_scl_cycles - scl_low - scl_high - overhead_cycles;
|
|
if (extra_cycles < 0) {
|
|
// The base frequency is too slow to satisfy the target frequency, and continuing will only
|
|
// get slower, so give up.
|
|
return I2C_TIMINGR_INVALID_VALUE;
|
|
}
|
|
|
|
// Split up the extra cycles evenly between the low and high periods. If necessary, give the
|
|
// extra one to the high period arbitrarily.
|
|
scl_low += extra_cycles / 2;
|
|
scl_high += extra_cycles - (extra_cycles / 2);
|
|
|
|
// Calculate the SDA setup time delay, which is confusingly referred to as SCLDEL.
|
|
uint32_t scl_delay = DIVIDE_CEIL(
|
|
(rise_time_ns * PS_PER_NS) + s_timing_data[bus_mode].min_sda_setup_ps,
|
|
base_period_ps);
|
|
|
|
// Check if the computed values are valid. If they aren't valid, we'll increase the prescaler
|
|
// try again with a higher prescaler.
|
|
// NOTE: We could end up in a situation where it is not valid, but could be if we split up the
|
|
// extra cycles differently. We're not going to worry about this because it doesn't currently
|
|
// happen in practice, and if it does, the next prescaler value will give us a valid (although
|
|
// slightly sub-optimal) result.
|
|
if ((scl_low <= TIMINGR_MAX_SCLL) && (scl_high <= TIMINGR_MAX_SCLH) &&
|
|
(scl_delay <= TIMINGR_MAX_SCLDEL)) {
|
|
// 1 less than the SCLL / SCLH / SCLDEL / PRESC values should be stored in the register.
|
|
const TIMINGR result = (TIMINGR) {
|
|
.SCLL = scl_low - 1,
|
|
.SCLH = scl_high - 1,
|
|
.SCLDEL = scl_delay - 1,
|
|
.PRESC = prescaler - 1,
|
|
};
|
|
return result.reg;
|
|
}
|
|
}
|
|
|
|
// We tried every possible prescaler and couldn't find valid TIMINGR values.
|
|
return I2C_TIMINGR_INVALID_VALUE;
|
|
}
|