mirror of
https://github.com/google/pebble.git
synced 2025-06-07 10:43:12 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
141
src/fw/drivers/stm32f7/i2c_timingr.c
Normal file
141
src/fw/drivers/stm32f7/i2c_timingr.c
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue