Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sw/device/examples/i2c.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
123 changes: 97 additions & 26 deletions sw/device/lib/hal/i2c.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
24 changes: 23 additions & 1 deletion sw/device/lib/hal/i2c.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion sw/device/tests/i2c/smoketest.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down