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 4069fb6aa..c4a44efe9 100644 --- a/sw/device/lib/hal/i2c.c +++ b/sw/device/lib/hal/i2c.c @@ -22,36 +22,107 @@ static uint16_t rnd_up_div(uint32_t a, uint32_t b) return (uint16_t)result; } -void i2c_init(i2c_t i2c) +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_high_cycles_min) { - // -- 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_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 // - // 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); + // Even though SCL_low_cycles and SCL_high_cycles have minimum allowable values, increase in + // 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 = + (scl_high_cycles > scl_high_cycles_min) ? scl_high_cycles : scl_high_cycles_min; + + return scl_high_cycles; +} + +// 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) +{ + 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);