Skip to content

i2c: set hold time of SDA during transmit to an appropriate value #273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 6, 2021
Merged
21 changes: 21 additions & 0 deletions src/rp2_common/hardware_i2c/i2c.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,24 @@ uint i2c_set_baudrate(i2c_inst_t *i2c, uint baudrate) {
invalid_params_if(I2C, hcnt < 8);
invalid_params_if(I2C, lcnt < 8);

// Per I2C-bus specification a device in standard or fast mode must
// internally provide a hold time of at least 300ns for the SDA signal to
// bridge the undefined region of the falling edge of SCL. A smaller hold
// time of 120ns is used for fast mode plus.
uint sda_tx_hold_count;
if (baudrate < 1000000) {
// sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns)
// Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint.
// Add 1 to avoid division truncation.
sda_tx_hold_count = ((freq_in * 3) / 10000000) + 1;
} else {
// sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns)
// Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint.
// Add 1 to avoid division truncation.
sda_tx_hold_count = ((freq_in * 3) / 25000000) + 1;
}
assert(sda_tx_hold_count <= lcnt - 2);

i2c->hw->enable = 0;
// Always use "fast" mode (<= 400 kHz, works fine for standard mode too)
hw_write_masked(&i2c->hw->con,
Expand All @@ -84,6 +102,9 @@ uint i2c_set_baudrate(i2c_inst_t *i2c, uint baudrate) {
i2c->hw->fs_scl_hcnt = hcnt;
i2c->hw->fs_scl_lcnt = lcnt;
i2c->hw->fs_spklen = lcnt < 16 ? 1 : lcnt / 16;
hw_write_masked(&i2c->hw->sda_hold,
sda_tx_hold_count << I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD_LSB,
I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD_BITS);

i2c->hw->enable = 1;
return freq_in / period;
Expand Down