Skip to content

Commit

Permalink
drivers: i2c: stm32 driver V2 new timing calculation
Browse files Browse the repository at this point in the history
This PR is implementing a new formula to calculate the I2C timing
value from the I2C clock source and the bit rate.

Signed-off-by: Francois Ramu <[email protected]>
  • Loading branch information
FRASTM committed Jun 28, 2023
1 parent 3bef10f commit 6b4c5da
Showing 1 changed file with 271 additions and 60 deletions.
331 changes: 271 additions & 60 deletions drivers/i2c/i2c_ll_stm32_v2.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,91 @@ LOG_MODULE_REGISTER(i2c_ll_stm32_v2);

#define STM32_I2C_TRANSFER_TIMEOUT_MSEC 500

#ifndef STM32_I2C_VALID_TIMING_NBR
#define STM32_I2C_VALID_TIMING_NBR 128U
#endif
#define STM32_I2C_SPEED_FREQ_STANDARD 0U /* 100 kHz */
#define STM32_I2C_SPEED_FREQ_FAST 1U /* 400 kHz */
#define STM32_I2C_SPEED_FREQ_FAST_PLUS 2U /* 1 MHz */
#define STM32_I2C_ANALOG_FILTER_DELAY_MIN 50U /* ns */
#define STM32_I2C_ANALOG_FILTER_DELAY_MAX 260U /* ns */
#define STM32_I2C_USE_ANALOG_FILTER 1U
#define STM32_I2C_DIGITAL_FILTER_COEF 0U
#define STM32_I2C_PRESC_MAX 16U
#define STM32_I2C_SCLDEL_MAX 16U
#define STM32_I2C_SDADEL_MAX 16U
#define STM32_I2C_SCLH_MAX 256U
#define STM32_I2C_SCLL_MAX 256U

/* I2C_DEVICE_Private_Types */
struct {
uint32_t freq; /* Frequency in Hz */
uint32_t freq_min; /* Minimum frequency in Hz */
uint32_t freq_max; /* Maximum frequency in Hz */
uint32_t hddat_min; /* Minimum data hold time in ns */
uint32_t vddat_max; /* Maximum data valid time in ns */
uint32_t sudat_min; /* Minimum data setup time in ns */
uint32_t lscl_min; /* Minimum low period of the SCL clock in ns */
uint32_t hscl_min; /* Minimum high period of SCL clock in ns */
uint32_t trise; /* Rise time in ns */
uint32_t tfall; /* Fall time in ns */
uint32_t dnf; /* Digital noise filter coefficient */
} stm32_i2c_charac_t;

struct {
uint32_t presc; /* Timing prescaler */
uint32_t tscldel; /* SCL delay */
uint32_t tsdadel; /* SDA delay */
uint32_t sclh; /* SCL high period */
uint32_t scll; /* SCL low period */
} stm32_i2c_timings_t;

/* I2C_DEVICE Private Constants */
static const stm32_i2c_charac_t stm32_i2c_charac[] = {
[STM32_I2C_SPEED_FREQ_STANDARD] = {
.freq = 100000,
.freq_min = 80000,
.freq_max = 120000,
.hddat_min = 0,
.vddat_max = 3450,
.sudat_min = 250,
.lscl_min = 4700,
.hscl_min = 4000,
.trise = 640,
.tfall = 20,
.dnf = STM32_I2C_DIGITAL_FILTER_COEF,
},
[STM32_I2C_SPEED_FREQ_FAST] = {
.freq = 400000,
.freq_min = 320000,
.freq_max = 480000,
.hddat_min = 0,
.vddat_max = 900,
.sudat_min = 100,
.lscl_min = 1300,
.hscl_min = 600,
.trise = 250,
.tfall = 100,
.dnf = STM32_I2C_DIGITAL_FILTER_COEF,
},
[STM32_I2C_SPEED_FREQ_FAST_PLUS] = {
.freq = 1000000,
.freq_min = 800000,
.freq_max = 1200000,
.hddat_min = 0,
.vddat_max = 450,
.sudat_min = 50,
.lscl_min = 500,
.hscl_min = 260,
.trise = 60,
.tfall = 100,
.dnf = STM32_I2C_DIGITAL_FILTER_COEF,
},
};

static stm32_i2c_timings_t i2c_valid_timing[STM32_I2C_VALID_TIMING_NBR];
static uint32_t i2c_valid_timing_nbr;

static inline void msg_init(const struct device *dev, struct i2c_msg *msg,
uint8_t *next_msg_flags, uint16_t slave,
uint32_t transfer)
Expand Down Expand Up @@ -664,74 +749,200 @@ static int stm32_i2c_msg_read(const struct device *dev, struct i2c_msg *msg,
}
#endif

int stm32_i2c_configure_timing(const struct device *dev, uint32_t clock)
/*
* @brief Calculate SCLL and SCLH and find best configuration.
* @param clock_src_freq I2C source clock in Hz.
* @param i2c_speed I2C frequency (index).
* @retval config index (0 to I2C_VALID_TIMING_NBR], 0xFFFFFFFF for no valid config.
*/
uint32_t i2c_compute_scll_sclh(uint32_t clock_src_freq, uint32_t i2c_speed)
{
const struct i2c_stm32_config *cfg = dev->config;
struct i2c_stm32_data *data = dev->data;
I2C_TypeDef *i2c = cfg->i2c;
uint32_t i2c_hold_time_min, i2c_setup_time_min;
uint32_t i2c_h_min_time, i2c_l_min_time;
uint32_t presc = 1U;
uint32_t timing = 0U;

/* Look for an adequate preset timing value */
for (uint32_t i = 0; i < cfg->n_timings; i++) {
const struct i2c_config_timing *preset = &cfg->timings[i];
uint32_t speed = i2c_map_dt_bitrate(preset->i2c_speed);

if ((I2C_SPEED_GET(speed) == I2C_SPEED_GET(data->dev_config))
&& (preset->periph_clock == clock)) {
/* Found a matching periph clock and i2c speed */
LL_I2C_SetTiming(i2c, preset->timing_setting);
return 0;
uint32_t ret = 0xFFFFFFFFU;
uint32_t ti2cclk;
uint32_t ti2cspeed;
uint32_t prev_error;
uint32_t dnf_delay;
uint32_t clk_min, clk_max;
uint32_t scll, sclh;
uint32_t tafdel_min;

ti2cclk = (NSEC_PER_SEC + (clock_src_freq / 2U)) / clock_src_freq;
ti2cspeed = (NSEC_PER_SEC + (stm32_i2c_charac[i2c_speed].freq / 2U)) /
stm32_i2c_charac[i2c_speed].freq;

tafdel_min = (STM32_I2C_USE_ANALOG_FILTER == 1U) ?
STM32_I2C_ANALOG_FILTER_DELAY_MIN :
0U;

/* tDNF = DNF x tI2CCLK */
dnf_delay = stm32_i2c_charac[i2c_speed].dnf * ti2cclk;

clk_max = NSEC_PER_SEC / stm32_i2c_charac[i2c_speed].freq_min;
clk_min = NSEC_PER_SEC / stm32_i2c_charac[i2c_speed].freq_max;

prev_error = ti2cspeed;

for (uint32_t count = 0; count < stm32_i2c_valid_timing_nbr; count++) {
/* tPRESC = (PRESC+1) x tI2CCLK*/
uint32_t tpresc = (stm32_i2c_valid_timing[count].presc + 1U) * ti2cclk;

for (scll = 0; scll < STM32_I2C_SCLL_MAX; scll++) {
/* tLOW(min) <= tAF(min) + tDNF + 2 x tI2CCLK + [(SCLL+1) x tPRESC ] */
uint32_t tscl_l = tafdel_min + dnf_delay +
(2U * ti2cclk) + ((scll + 1U) * tpresc);


/*
* The I2CCLK period tI2CCLK must respect the following conditions:
* tI2CCLK < (tLOW - tfilters) / 4 and tI2CCLK < tHIGH
*/
if ((tscl_l > stm32_i2c_charac[i2c_speed].lscl_min) &&
(ti2cclk < ((tscl_l - tafdel_min - dnf_delay) / 4U))) {
for (sclh = 0; sclh < STM32_I2C_SCLH_MAX; sclh++) {
/* tHIGH(min) <= tAF(min) + tDNF + 2 x tI2CCLK + [(SCLH+1) x tPRESC] */

Check warning on line 802 in drivers/i2c/i2c_ll_stm32_v2.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LONG_LINE_COMMENT

drivers/i2c/i2c_ll_stm32_v2.c:802 line length of 103 exceeds 100 columns
uint32_t tscl_h = tafdel_min + dnf_delay +
(2U * ti2cclk) + ((sclh + 1U) * tpresc);

/* tSCL = tf + tLOW + tr + tHIGH */
uint32_t tscl = tscl_l +
tscl_h + stm32_i2c_charac[i2c_speed].trise +
stm32_i2c_charac[i2c_speed].tfall;

if ((tscl >= clk_min) &&
(tscl <= clk_max) &&
(tscl_h >= stm32_i2c_charac[i2c_speed].hscl_min) &&
(ti2cclk < tscl_h)) {
int32_t error = (int32_t)tscl - (int32_t)ti2cspeed;

if (error < 0) {

Check warning on line 817 in drivers/i2c/i2c_ll_stm32_v2.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

DEEP_INDENTATION

drivers/i2c/i2c_ll_stm32_v2.c:817 Too many leading tabs - consider code refactoring
error = -error;
}

/* look for the timings with the lowest clock error */

Check warning on line 821 in drivers/i2c/i2c_ll_stm32_v2.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LONG_LINE_COMMENT

drivers/i2c/i2c_ll_stm32_v2.c:821 line length of 102 exceeds 100 columns
if ((uint32_t)error < prev_error) {

Check warning on line 822 in drivers/i2c/i2c_ll_stm32_v2.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

DEEP_INDENTATION

drivers/i2c/i2c_ll_stm32_v2.c:822 Too many leading tabs - consider code refactoring
prev_error = (uint32_t)error;
i2c_valid_timing[count].scll = scll;
i2c_valid_timing[count].sclh = sclh;
ret = count;
}
}
}
}
}
}

/* No preset timing was provided, let's dynamically configure */
switch (I2C_SPEED_GET(data->dev_config)) {
case I2C_SPEED_STANDARD:
i2c_h_min_time = 4000U;
i2c_l_min_time = 4700U;
i2c_hold_time_min = 500U;
i2c_setup_time_min = 1250U;
break;
case I2C_SPEED_FAST:
i2c_h_min_time = 600U;
i2c_l_min_time = 1300U;
i2c_hold_time_min = 375U;
i2c_setup_time_min = 500U;
break;
default:
return -EINVAL;
}

/* Calculate period until prescaler matches */
do {
uint32_t t_presc = clock / presc;
uint32_t ns_presc = NSEC_PER_SEC / t_presc;
uint32_t sclh = i2c_h_min_time / ns_presc;
uint32_t scll = i2c_l_min_time / ns_presc;
uint32_t sdadel = i2c_hold_time_min / ns_presc;
uint32_t scldel = i2c_setup_time_min / ns_presc;
return ret;
}

if ((sclh - 1) > 255 || (scll - 1) > 255) {
++presc;
continue;
/*
* @brief Compute PRESC, SCLDEL and SDADEL.
* @param clock_src_freq I2C source clock in Hz.
* @param i2c_speed I2C frequency (index).
* @retval None.
*/
void i2c_compute_presc_scldel_sdadel(uint32_t clock_src_freq, uint32_t i2c_speed)
{
uint32_t prev_presc = STM32_I2C_PRESC_MAX;
uint32_t ti2cclk;
int32_t tsdadel_min, tsdadel_max;
int32_t tscldel_min;
uint32_t presc, scldel, sdadel;
uint32_t tafdel_min, tafdel_max;

ti2cclk = (NSEC_PER_SEC + (clock_src_freq / 2U)) / clock_src_freq;

tafdel_min = (STM32_I2C_USE_ANALOG_FILTER == 1U) ?
STM32_I2C_ANALOG_FILTER_DELAY_MIN : 0U;
tafdel_max = (STM32_I2C_USE_ANALOG_FILTER == 1U) ?
STM32_I2C_ANALOG_FILTER_DELAY_MAX : 0U;
/*
* tDNF = DNF x tI2CCLK
* tPRESC = (PRESC+1) x tI2CCLK
* SDADEL >= {tf +tHD;DAT(min) - tAF(min) - tDNF - [3 x tI2CCLK]} / {tPRESC}
* SDADEL <= {tVD;DAT(max) - tr - tAF(max) - tDNF- [4 x tI2CCLK]} / {tPRESC}
*/
tsdadel_min = (int32_t)stm32_i2c_charac[i2c_speed].tfall +
(int32_t)stm32_i2c_charac[i2c_speed].hddat_min -
(int32_t)tafdel_min -
(int32_t)(((int32_t)stm32_i2c_charac[i2c_speed].dnf + 3) *
(int32_t)ti2cclk);

tsdadel_max = (int32_t)stm32_i2c_charac[i2c_speed].vddat_max -
(int32_t)stm32_i2c_charac[i2c_speed].trise -
(int32_t)tafdel_max -
(int32_t)(((int32_t)stm32_i2c_charac[i2c_speed].dnf + 4) *
(int32_t)ti2cclk);

/* {[tr+ tSU;DAT(min)] / [tPRESC]} - 1 <= SCLDEL */
tscldel_min = (int32_t)stm32_i2c_charac[i2c_speed].trise +
(int32_t)stm32_i2c_charac[i2c_speed].sudat_min;

if (tsdadel_min <= 0) {
tsdadel_min = 0;
}

if (tsdadel_max <= 0) {
tsdadel_max = 0;
}

for (presc = 0; presc < STM32_I2C_PRESC_MAX; presc++) {
for (scldel = 0; scldel < STM32_I2C_SCLDEL_MAX; scldel++) {
/* TSCLDEL = (SCLDEL+1) * (PRESC+1) * TI2CCLK */
uint32_t tscldel = (scldel + 1U) * (presc + 1U) * ti2cclk;

if (tscldel >= (uint32_t)tscldel_min) {
for (sdadel = 0; sdadel < STM32_I2C_SDADEL_MAX; sdadel++) {
/* TSDADEL = SDADEL * (PRESC+1) * TI2CCLK */
uint32_t tsdadel = (sdadel * (presc + 1U)) * ti2cclk;

if ((tsdadel >= (uint32_t)tsdadel_min) &&
(tsdadel <= (uint32_t)tsdadel_max)) {
if (presc != prev_presc) {

Check warning on line 900 in drivers/i2c/i2c_ll_stm32_v2.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

DEEP_INDENTATION

drivers/i2c/i2c_ll_stm32_v2.c:900 Too many leading tabs - consider code refactoring
i2c_valid_timing[i2c_valid_timing_nbr].presc = presc;

Check warning on line 901 in drivers/i2c/i2c_ll_stm32_v2.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LONG_LINE

drivers/i2c/i2c_ll_stm32_v2.c:901 line length of 109 exceeds 100 columns
i2c_valid_timing[i2c_valid_timing_nbr].tscldel = scldel;

Check warning on line 902 in drivers/i2c/i2c_ll_stm32_v2.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LONG_LINE

drivers/i2c/i2c_ll_stm32_v2.c:902 line length of 112 exceeds 100 columns
i2c_valid_timing[i2c_valid_timing_nbr].tsdadel = sdadel;

Check warning on line 903 in drivers/i2c/i2c_ll_stm32_v2.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LONG_LINE

drivers/i2c/i2c_ll_stm32_v2.c:903 line length of 112 exceeds 100 columns
prev_presc = presc;
i2c_valid_timing_nbr++;
if (i2c_valid_timing_nbr >= STM32_I2C_VALID_TIMING_NBR) {

Check warning on line 906 in drivers/i2c/i2c_ll_stm32_v2.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LONG_LINE

drivers/i2c/i2c_ll_stm32_v2.c:906 line length of 113 exceeds 100 columns

Check warning on line 906 in drivers/i2c/i2c_ll_stm32_v2.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

DEEP_INDENTATION

drivers/i2c/i2c_ll_stm32_v2.c:906 Too many leading tabs - consider code refactoring
return;
}
}
}
}
}
}
}
}

if (sdadel > 15 || (scldel - 1) > 15) {
++presc;
continue;
int stm32_i2c_configure_timing(const struct device *dev, uint32_t clock)
{
const struct i2c_stm32_config *cfg = dev->config;
I2C_TypeDef *i2c = cfg->i2c;
uint32_t timing = 0U;
uint32_t idx;
uint32_t speed = 0U;
uint32_t i2c_freq = cfg->bitrate;

/* Reset valid timing count at the beginning of each new computation */
stm32_i2c_valid_timing_nbr = 0;

if ((clock != 0U) && (i2c_freq != 0U)) {
for (speed = 0 ; speed <= (uint32_t)STM32_I2C_SPEED_FREQ_FAST_PLUS ; speed++) {
if ((i2c_freq >= stm32_i2c_charac[speed].freq_min) &&
(i2c_freq <= stm32_i2c_charac[speed].freq_max)) {
i2c_compute_presc_scldel_sdadel(clock, speed);
idx = i2c_compute_scll_sclh(clock, speed);
if (idx < STM32_I2C_VALID_TIMING_NBR) {
timing = ((stm32_i2c_valid_timing[idx].presc &
0x0FU) << 28) |
((i2c_valid_timing[idx].tscldel & 0x0FU) << 20) |
((i2c_valid_timing[idx].tsdadel & 0x0FU) << 16) |
((i2c_valid_timing[idx].sclh & 0xFFU) << 8) |
((i2c_valid_timing[idx].scll & 0xFFU) << 0);
}
break;
}
}

timing = __LL_I2C_CONVERT_TIMINGS(presc - 1,
scldel - 1, sdadel, sclh - 1, scll - 1);
break;
} while (presc < 16);

if (presc >= 16U) {
LOG_DBG("I2C:failed to find prescaler value");
return -EINVAL;
}

LL_I2C_SetTiming(i2c, timing);
Expand Down

0 comments on commit 6b4c5da

Please sign in to comment.