From e7f21914549c9eec0742caeecf4f0d5f3b1a0df6 Mon Sep 17 00:00:00 2001 From: Kinza Qamar Date: Sun, 17 May 2026 12:59:55 +0100 Subject: [PATCH 1/2] [i2c, sw, hal] Added a function to calculate SCL high cycles It takes care that the resultant SCL high cycles must satisfy below equations: ** scl_high_cycles >= 4 (to support correct function in clock streching) ** scl_high_cycles = scl_period_cycles - (rise_cycles + fall_cycles + scl_low_cycles) Signed-off-by: Kinza Qamar --- sw/device/lib/hal/i2c.c | 50 ++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/sw/device/lib/hal/i2c.c b/sw/device/lib/hal/i2c.c index 4069fb6aa..5741d9255 100644 --- a/sw/device/lib/hal/i2c.c +++ b/sw/device/lib/hal/i2c.c @@ -22,6 +22,33 @@ static uint16_t rnd_up_div(uint32_t a, uint32_t b) return (uint16_t)result; } +uint16_t calc_scl_high_cycles(uint16_t rise_cycles, uint16_t fall_cycles, + uint16_t scl_period_cycles, uint16_t scl_low_cycles) +{ + // Calculate the minimum allowable value for SCL high time + uint16_t scl_high_cycles_min = rnd_up_div(4000, SYSCLK_NS); + + // scl_high_time should be atleast 4 cycles to aid correct clock streching + scl_high_cycles_min = (scl_high_cycles_min < 4u) ? 4u : scl_high_cycles_min; + + // An SCL period duration is divided into 4 segments: + // 1) Rise time + // 2) Fall time + // 3) High time + // 4) Low time + // Hence an SCL period must satisfy the equation below: + // scl_period = rise_time + fall_time + high_time + low_time + // + // Even though SCL_low_cycles and SCL_high_cycles have minimum allowable values, increase in + // rise time and fall time influences the SCL_period. + uint16_t scl_high_cycles = scl_period_cycles - (scl_low_cycles + rise_cycles + fall_cycles); + + scl_high_cycles = + (scl_high_cycles > scl_high_cycles_min) ? scl_high_cycles : scl_high_cycles_min; + + return scl_high_cycles; +} + void i2c_init(i2c_t i2c) { // -- Set timing parameters -- @@ -34,17 +61,18 @@ void i2c_init(i2c_t i2c) // SCL high cycles calculation adapted from OpenTitan sw/device/lib/dif/dif_i2c.c // Calculate the timing paramters - uint32_t rise_cycles = rnd_up_div(I2C_RISE_NS, SYSCLK_NS); - uint32_t fall_cycles = rnd_up_div(I2C_FALL_NS, SYSCLK_NS); - uint32_t scl_period_cycles = rnd_up_div((10000 /* 10000 ns -> 100 kHz */), SYSCLK_NS); - uint32_t scl_low_cycles = rnd_up_div(4700, SYSCLK_NS); - uint32_t scl_high_cycles = scl_period_cycles - scl_low_cycles - rise_cycles - fall_cycles; - uint32_t setup_start_cycles = rnd_up_div(4700, SYSCLK_NS); - uint32_t hold_start_cycles = rnd_up_div(4000, SYSCLK_NS); - uint32_t setup_data_cycles = rnd_up_div(250, SYSCLK_NS); - uint32_t hold_data_cycles = 1u; - uint32_t setup_stop_cycles = rnd_up_div(4000, SYSCLK_NS); - uint32_t bus_free_time_cycles = rnd_up_div(4700, SYSCLK_NS); + uint16_t rise_cycles = rnd_up_div(I2C_RISE_NS, SYSCLK_NS); + uint16_t fall_cycles = rnd_up_div(I2C_FALL_NS, SYSCLK_NS); + uint16_t scl_period_cycles = rnd_up_div((10000 /* 10000 ns -> 100 kHz */), SYSCLK_NS); + uint16_t scl_low_cycles = rnd_up_div(4700, SYSCLK_NS); + uint16_t scl_high_cycles = + calc_scl_high_cycles(rise_cycles, fall_cycles, scl_period_cycles, scl_low_cycles); + uint16_t setup_start_cycles = rnd_up_div(4700, SYSCLK_NS); + uint16_t hold_start_cycles = rnd_up_div(4000, SYSCLK_NS); + uint16_t setup_data_cycles = rnd_up_div(250, SYSCLK_NS); + uint16_t hold_data_cycles = 1u; + uint16_t setup_stop_cycles = rnd_up_div(4000, SYSCLK_NS); + uint16_t bus_free_time_cycles = rnd_up_div(4700, SYSCLK_NS); // Declare timing registers i2c_timing0 t0_reg = { .tlow = scl_low_cycles, .thigh = scl_high_cycles }; From c00e4a9fa58fdd113c049c6d278d9b6fee897d3b Mon Sep 17 00:00:00 2001 From: Kinza Qamar Date: Tue, 19 May 2026 15:23:01 +0100 Subject: [PATCH 2/2] [i2c, sw] Function added to calculate timing params based on speed OT's I2C block supports Standard, Fast and Fast Plus modes. Signed-off-by: Kinza Qamar --- sw/device/examples/i2c.c | 2 +- sw/device/lib/hal/i2c.c | 109 ++++++++++++++++++++++---------- sw/device/lib/hal/i2c.h | 24 ++++++- sw/device/tests/i2c/smoketest.c | 2 +- 4 files changed, 101 insertions(+), 36 deletions(-) diff --git a/sw/device/examples/i2c.c b/sw/device/examples/i2c.c index 26c15ee1c..176919194 100644 --- a/sw/device/examples/i2c.c +++ b/sw/device/examples/i2c.c @@ -15,7 +15,7 @@ int main(void) i2c_t i2c = mocha_system_i2c(); uart_t uart = mocha_system_uart(); timer_t timer = mocha_system_timer(); - i2c_init(i2c); + i2c_init(i2c, i2c_speed_mode_standard); uart_init(uart); timer_init(timer); timer_enable_write(timer, true); diff --git a/sw/device/lib/hal/i2c.c b/sw/device/lib/hal/i2c.c index 5741d9255..c4a44efe9 100644 --- a/sw/device/lib/hal/i2c.c +++ b/sw/device/lib/hal/i2c.c @@ -23,11 +23,9 @@ static uint16_t rnd_up_div(uint32_t a, uint32_t b) } uint16_t calc_scl_high_cycles(uint16_t rise_cycles, uint16_t fall_cycles, - uint16_t scl_period_cycles, uint16_t scl_low_cycles) + uint16_t scl_period_cycles, uint16_t scl_low_cycles, + uint16_t scl_high_cycles_min) { - // Calculate the minimum allowable value for SCL high time - uint16_t scl_high_cycles_min = rnd_up_div(4000, SYSCLK_NS); - // scl_high_time should be atleast 4 cycles to aid correct clock streching scl_high_cycles_min = (scl_high_cycles_min < 4u) ? 4u : scl_high_cycles_min; @@ -40,7 +38,7 @@ uint16_t calc_scl_high_cycles(uint16_t rise_cycles, uint16_t fall_cycles, // scl_period = rise_time + fall_time + high_time + low_time // // Even though SCL_low_cycles and SCL_high_cycles have minimum allowable values, increase in - // rise time and fall time influences the SCL_period. + // rise time and fall times influence the SCL_period. uint16_t scl_high_cycles = scl_period_cycles - (scl_low_cycles + rise_cycles + fall_cycles); scl_high_cycles = @@ -49,37 +47,82 @@ uint16_t calc_scl_high_cycles(uint16_t rise_cycles, uint16_t fall_cycles, return scl_high_cycles; } -void i2c_init(i2c_t i2c) +// Calculate the minimum allowable value for each timing parameter taken from the NXP I^2C +// specification "UM10204" Table 10 (rev. 6) / Table 11 (rev. 7). +// +// The values for Rise and Fall times for Fast mode are taken as spec minimum. For Fast plus mode, +// the values are taken from OT's i2c_host_tx_rx_test.c test. +i2c_timing_params_cycles_t compute_minimum_timing_paramaters(i2c_speed_mode_t speed) { - // -- Set timing parameters -- - // - // Using Standard-mode (100 kbits/s) constants, taken from the NXP I^2C specification - // "UM10204" Table 10 (rev. 6) / Table 11 (rev. 7). - // Faster modes will require different/adjustable constants and checking that SCL high/low - // cycles calculated are sufficient to still allow the clock stretching logic to function. - // - // SCL high cycles calculation adapted from OpenTitan sw/device/lib/dif/dif_i2c.c - - // Calculate the timing paramters - uint16_t rise_cycles = rnd_up_div(I2C_RISE_NS, SYSCLK_NS); - uint16_t fall_cycles = rnd_up_div(I2C_FALL_NS, SYSCLK_NS); - uint16_t scl_period_cycles = rnd_up_div((10000 /* 10000 ns -> 100 kHz */), SYSCLK_NS); - uint16_t scl_low_cycles = rnd_up_div(4700, SYSCLK_NS); - uint16_t scl_high_cycles = - calc_scl_high_cycles(rise_cycles, fall_cycles, scl_period_cycles, scl_low_cycles); - uint16_t setup_start_cycles = rnd_up_div(4700, SYSCLK_NS); - uint16_t hold_start_cycles = rnd_up_div(4000, SYSCLK_NS); - uint16_t setup_data_cycles = rnd_up_div(250, SYSCLK_NS); - uint16_t hold_data_cycles = 1u; - uint16_t setup_stop_cycles = rnd_up_div(4000, SYSCLK_NS); - uint16_t bus_free_time_cycles = rnd_up_div(4700, SYSCLK_NS); + switch (speed) { + case i2c_speed_mode_standard: + return (i2c_timing_params_cycles_t){ + .rise_cycles = rnd_up_div(I2C_RISE_NS, SYSCLK_NS), + .fall_cycles = rnd_up_div(I2C_FALL_NS, SYSCLK_NS), + .scl_low_cycles = rnd_up_div(4700, SYSCLK_NS), + .scl_high_cycles = rnd_up_div(4000, SYSCLK_NS), + .scl_period_cycles = rnd_up_div(10000u, SYSCLK_NS), + .setup_start_cycles = rnd_up_div(4700u, SYSCLK_NS), + .hold_start_cycles = rnd_up_div(4000u, SYSCLK_NS), + .setup_data_cycles = rnd_up_div(250u, SYSCLK_NS), + .hold_data_cycles = 1u, + .setup_stop_cycles = rnd_up_div(4000u, SYSCLK_NS), + .bus_free_time_cycles = rnd_up_div(4700u, SYSCLK_NS) + }; + case i2c_speed_mode_fast: + return (i2c_timing_params_cycles_t){ + .rise_cycles = rnd_up_div(20u, SYSCLK_NS), + .fall_cycles = rnd_up_div(20u, SYSCLK_NS), + .scl_low_cycles = rnd_up_div(1300u, SYSCLK_NS), + .scl_high_cycles = rnd_up_div(600, SYSCLK_NS), + .scl_period_cycles = rnd_up_div(2500u, SYSCLK_NS), + .setup_start_cycles = rnd_up_div(600u, SYSCLK_NS), + .hold_start_cycles = rnd_up_div(600u, SYSCLK_NS), + .setup_data_cycles = rnd_up_div(100u, SYSCLK_NS), + .hold_data_cycles = 1u, + .setup_stop_cycles = rnd_up_div(600u, SYSCLK_NS), + .bus_free_time_cycles = rnd_up_div(1300u, SYSCLK_NS) + }; + case i2c_speed_mode_fast_plus: + return (i2c_timing_params_cycles_t){ + .rise_cycles = rnd_up_div(10u, SYSCLK_NS), + .fall_cycles = rnd_up_div(10u, SYSCLK_NS), + .scl_low_cycles = rnd_up_div(500u, SYSCLK_NS), + .scl_high_cycles = rnd_up_div(260, SYSCLK_NS), + .scl_period_cycles = rnd_up_div(1000u, SYSCLK_NS), + .setup_start_cycles = rnd_up_div(260u, SYSCLK_NS), + .hold_start_cycles = rnd_up_div(260u, SYSCLK_NS), + .setup_data_cycles = rnd_up_div(50, SYSCLK_NS), + .hold_data_cycles = 1u, + .setup_stop_cycles = rnd_up_div(260u, SYSCLK_NS), + .bus_free_time_cycles = rnd_up_div(500u, SYSCLK_NS) + }; + default: + return (i2c_timing_params_cycles_t){ 0 }; + } +} + +void i2c_init(i2c_t i2c, i2c_speed_mode_t speed_mode) +{ + i2c_timing_params_cycles_t timing_params_cycles = compute_minimum_timing_paramaters(speed_mode); + + timing_params_cycles.scl_high_cycles = + calc_scl_high_cycles(timing_params_cycles.rise_cycles, timing_params_cycles.fall_cycles, + timing_params_cycles.scl_period_cycles, + timing_params_cycles.scl_low_cycles, + timing_params_cycles.scl_high_cycles); // Declare timing registers - i2c_timing0 t0_reg = { .tlow = scl_low_cycles, .thigh = scl_high_cycles }; - i2c_timing1 t1_reg = { .t_r = rise_cycles, .t_f = fall_cycles }; - i2c_timing2 t2_reg = { .tsu_sta = setup_start_cycles, .thd_sta = hold_start_cycles }; - i2c_timing3 t3_reg = { .tsu_dat = setup_data_cycles, .thd_dat = hold_data_cycles }; - i2c_timing4 t4_reg = { .tsu_sto = setup_stop_cycles, .t_buf = bus_free_time_cycles }; + i2c_timing0 t0_reg = { .tlow = timing_params_cycles.scl_low_cycles, + .thigh = timing_params_cycles.scl_high_cycles }; + i2c_timing1 t1_reg = { .t_r = timing_params_cycles.rise_cycles, + .t_f = timing_params_cycles.fall_cycles }; + i2c_timing2 t2_reg = { .tsu_sta = timing_params_cycles.setup_start_cycles, + .thd_sta = timing_params_cycles.hold_start_cycles }; + i2c_timing3 t3_reg = { .tsu_dat = timing_params_cycles.setup_data_cycles, + .thd_dat = timing_params_cycles.hold_data_cycles }; + i2c_timing4 t4_reg = { .tsu_sto = timing_params_cycles.setup_stop_cycles, + .t_buf = timing_params_cycles.bus_free_time_cycles }; VOLATILE_WRITE(i2c->timing0, t0_reg); VOLATILE_WRITE(i2c->timing1, t1_reg); diff --git a/sw/device/lib/hal/i2c.h b/sw/device/lib/hal/i2c.h index 73857c40d..9d31668cd 100644 --- a/sw/device/lib/hal/i2c.h +++ b/sw/device/lib/hal/i2c.h @@ -88,7 +88,29 @@ #define I2C_RISE_NS (450) #define I2C_FALL_NS (120) -void i2c_init(i2c_t i2c); +// All the speed modes supported by OT's I2C block +typedef enum { + i2c_speed_mode_standard, + i2c_speed_mode_fast, + i2c_speed_mode_fast_plus +} i2c_speed_mode_t; + +// All the timing parameters used by I2C block converted into cycles +typedef struct { + uint16_t rise_cycles; + uint16_t fall_cycles; + uint16_t scl_low_cycles; + uint16_t scl_high_cycles; + uint16_t scl_period_cycles; + uint16_t setup_start_cycles; + uint16_t hold_start_cycles; + uint16_t setup_data_cycles; + uint16_t hold_data_cycles; + uint16_t setup_stop_cycles; + uint16_t bus_free_time_cycles; +} i2c_timing_params_cycles_t; + +void i2c_init(i2c_t i2c, i2c_speed_mode_t speed_mode); bool i2c_write_byte(i2c_t i2c, uint8_t addr, uint8_t data); uint8_t i2c_read_byte(i2c_t i2c, uint8_t addr); diff --git a/sw/device/tests/i2c/smoketest.c b/sw/device/tests/i2c/smoketest.c index 8d82e0a34..f2d09bfa6 100644 --- a/sw/device/tests/i2c/smoketest.c +++ b/sw/device/tests/i2c/smoketest.c @@ -29,7 +29,7 @@ static bool as6212_test(i2c_t i2c) bool test_main() { i2c_t i2c = mocha_system_i2c(); - i2c_init(i2c); + i2c_init(i2c, i2c_speed_mode_standard); // -- Configure IP for Controller mode -- enable_controller_mode(i2c);