Skip to content

Commit

Permalink
rp2040: i2c: fix speed calculation
Browse files Browse the repository at this point in the history
The former speed calculation where way off the requested i2c speeds.
This fix, which is based on the official pico-sdk implementation fixes
the calculation for the high and low times:

 | Speed | Before | After |
 | ------| ------ | ----- |
 | 100k  | 126k   | 94k   |
 | 400k  | 660k   | 346k  |
 | 1000k | - (*)  | 760k  |

(*) Failed to start any transaction in my test setup.
  • Loading branch information
KarlK90 committed Nov 23, 2023
1 parent fb67e50 commit 7d9212d
Showing 1 changed file with 35 additions and 29 deletions.
64 changes: 35 additions & 29 deletions os/hal/ports/RP/LLD/I2Cv1/hal_i2c_lld.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,45 +175,51 @@ void i2c_lld_handle_errors(I2CDriver *i2cp) {
*/
static void i2c_lld_setup_frequency(I2CDriver *i2cp) {
I2C_TypeDef *dp = i2cp->i2c;
/* [us] SS FS FS+
* MIN_SCL_LOWtime_FS: 4.7 1.3 0.5
* MIN_SCL_HIGHtime_FS: 4.0 0.6 0.26
* max tSP: - 0.05 0.05
* min tSU:DAT: 0.25 0.1 0.05
* tHD:DAT: 0 0 0
*/

uint32_t minLowfs, minHighfs;
uint32_t baudrate = i2cp->config->baudrate;

halfreq_t freq_in = halClockGetPointX(clk_sys);

const uint32_t baudrate = i2cp->config->baudrate;
const uint32_t period = (freq_in + baudrate / 2U) / baudrate;

uint32_t lcnt, hcnt, sda_tx_hold_count;

if (baudrate <= 100000U) {
// ns
minLowfs = 4700U;
minHighfs = 4000U;
/* Standard Mode: H: 4000ns, L: 4700ns ~ 0.54 */
lcnt = period * 54U / 100U;
} else if (baudrate <= 400000U) {
minLowfs = 1300U;
minHighfs = 600U;
/* Fast Mode: H: 600ns, L: 1300ns ~ 0.68 */
lcnt = period * 68U / 100U;
} else {
minLowfs = 500U;
minHighfs = 260U;
/* Fast Mode Plus: H: 500ns, L: 760ns ~ 0.60 */
lcnt = period * 60U / 100U;
}
hcnt = period - lcnt;

halfreq_t sys_clk = halClockGetPointX(clk_sys) / 100000;
uint32_t hcntfs = (minHighfs * sys_clk) / 10000U + 1U;
uint32_t lcntfs = (minLowfs * hcntfs) / ((10000000U / baudrate) * 100U - minLowfs) + 1U;
osalDbgAssert((lcnt >= 8) &&
(lcnt <= I2C_IC_FS_SCL_LCNT) &&
(hcnt >= 8) &&
(hcnt <= I2C_IC_FS_SCL_HCNT),
"Invalid I2C clock parameters.");

uint32_t sdahd = 0U;
if (baudrate < 1000000U) {
sdahd = (sys_clk * 3U) / 100U + 1U;
/* Standard and Fast Mode: */
/* 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 u32.
* Add 1 to avoid division truncation. */
sda_tx_hold_count = ((freq_in * 3U) / 10000000U) + 1U;
} else {
sdahd = (sys_clk * 3U) / 250U + 1U;
/* Fast Mode Plus: */
/* 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 u32.
* Add 1 to avoid division truncation. */
sda_tx_hold_count = ((freq_in * 3U) / 25000000U) + 1U;
}
osalDbgCheck(sda_tx_hold_count <= lcnt - 2U);

/* Always Fast Mode */
dp->FSSCLHCNT = hcntfs - 7U;
dp->FSSCLLCNT = lcntfs - 1U;
dp->FSSPKLEN = 2U;

dp->SDAHOLD |= sdahd & I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD;
dp->FSSCLHCNT = hcnt & I2C_IC_FS_SCL_HCNT;
dp->FSSCLLCNT = lcnt & I2C_IC_FS_SCL_LCNT;
dp->FSSPKLEN = (lcnt < 16 ? 1 : lcnt / 16) & I2C_IC_FS_SPKLEN_IC_FS_SPKLEN;
dp->SDAHOLD = sda_tx_hold_count & I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD;
}

/**
Expand Down

0 comments on commit 7d9212d

Please sign in to comment.