Skip to content
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

drivers: i2c: stm32 driver V2 new timing calculation #59815

Merged
merged 4 commits into from
Jun 12, 2024
Merged
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
6 changes: 6 additions & 0 deletions drivers/i2c/Kconfig.stm32
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,10 @@ config I2C_STM32_BUS_RECOVERY
help
Enable STM32 driver bus recovery support via GPIO bitbanging.

config I2C_STM32_V2_TIMING
bool "compute the I2C V2 bus timing"
depends on I2C_STM32_V2
help
Enable STM32 driver bus to calculate the Timing.

endif # I2C_STM32
20 changes: 20 additions & 0 deletions drivers/i2c/i2c_ll_stm32.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,26 @@ int i2c_stm32_get_config(const struct device *dev, uint32_t *config)

*config = data->dev_config;

#if CONFIG_I2C_STM32_V2_TIMING
/* Print the timing parameter of device data */
LOG_INF("I2C timing value, report to the DTS :");

/* I2C BIT RATE */
if (data->current_timing.i2c_speed == 100000) {
LOG_INF("timings = <%d I2C_BITRATE_STANDARD 0x%X>;",
data->current_timing.periph_clock,
data->current_timing.timing_setting);
} else if (data->current_timing.i2c_speed == 400000) {
LOG_INF("timings = <%d I2C_BITRATE_FAST 0x%X>;",
data->current_timing.periph_clock,
data->current_timing.timing_setting);
} else if (data->current_timing.i2c_speed == 1000000) {
LOG_INF("timings = <%d I2C_SPEED_FAST_PLUS 0x%X>;",
data->current_timing.periph_clock,
data->current_timing.timing_setting);
}
#endif /* CONFIG_I2C_STM32_V2_TIMING */

return 0;
}

Expand Down
4 changes: 4 additions & 0 deletions drivers/i2c/i2c_ll_stm32.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ struct i2c_stm32_data {
#endif
struct k_sem bus_mutex;
uint32_t dev_config;
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_i2c_v2)
/* Store the current timing structure set by runtime config */
struct i2c_config_timing current_timing;
#endif
#ifdef CONFIG_I2C_STM32_V1
uint16_t slave_address;
#endif
Expand Down
324 changes: 324 additions & 0 deletions drivers/i2c/i2c_ll_stm32_v2.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,94 @@ LOG_MODULE_REGISTER(i2c_ll_stm32_v2);

#define STM32_I2C_TRANSFER_TIMEOUT_MSEC 500

#ifdef CONFIG_I2C_STM32_V2_TIMING
/* Use the algorithm to calcuate the I2C timing */
#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 stm32_i2c_charac_t {
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 */
};

struct stm32_i2c_timings_t {
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 */
};

/* I2C_DEVICE Private Constants */
static const struct 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 struct stm32_i2c_timings_t i2c_valid_timing[STM32_I2C_VALID_TIMING_NBR];
erwango marked this conversation as resolved.
Show resolved Hide resolved
static uint32_t i2c_valid_timing_nbr;
erwango marked this conversation as resolved.
Show resolved Hide resolved
#endif /* CONFIG_I2C_STM32_V2_TIMING */

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 @@ -725,6 +813,240 @@ static int stm32_i2c_msg_read(const struct device *dev, struct i2c_msg *msg,
}
#endif

#ifdef CONFIG_I2C_STM32_V2_TIMING
/*
* Macro used to fix the compliance check warning :
* "DEEP_INDENTATION: Too many leading tabs - consider code refactoring
* in the i2c_compute_scll_sclh() function below
*/
#define I2C_LOOP_SCLH(); \
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) { \
error = -error; \
} \
\
if ((uint32_t)error < prev_error) { \
prev_error = (uint32_t)error; \
i2c_valid_timing[count].scll = scll; \
i2c_valid_timing[count].sclh = sclh; \
ret = count; \
} \
}

/*
* @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)
{
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 = (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]
*/
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;

/* get timings with the lowest clock error */
I2C_LOOP_SCLH();
}
}
}
}

return ret;
}

/*
* Macro used to fix the compliance check warning :
* "DEEP_INDENTATION: Too many leading tabs - consider code refactoring
* in the i2c_compute_presc_scldel_sdadel() function below
*/
#define I2C_LOOP_SDADEL(); \
\
if ((tsdadel >= (uint32_t)tsdadel_min) && \
(tsdadel <= (uint32_t)tsdadel_max)) { \
if (presc != prev_presc) { \
i2c_valid_timing[i2c_valid_timing_nbr].presc = presc; \
i2c_valid_timing[i2c_valid_timing_nbr].tscldel = scldel; \
i2c_valid_timing[i2c_valid_timing_nbr].tsdadel = sdadel; \
prev_presc = presc; \
i2c_valid_timing_nbr++; \
\
if (i2c_valid_timing_nbr >= STM32_I2C_VALID_TIMING_NBR) { \
break; \
} \
} \
}

/*
* @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;

I2C_LOOP_SDADEL();
}

if (i2c_valid_timing_nbr >= STM32_I2C_VALID_TIMING_NBR) {
return;
erwango marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
}

int stm32_i2c_configure_timing(const struct device *dev, uint32_t clock)
{
const struct i2c_stm32_config *cfg = dev->config;
struct i2c_stm32_data *data = dev->data;
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 */
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 = ((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;
}
}
}

/* Fill the current timing value in data structure at runtime */
data->current_timing.periph_clock = clock;
data->current_timing.i2c_speed = i2c_freq;
data->current_timing.timing_setting = timing;

LL_I2C_SetTiming(i2c, timing);

return 0;
}
#else/* CONFIG_I2C_STM32_V2_TIMING */

int stm32_i2c_configure_timing(const struct device *dev, uint32_t clock)
{
const struct i2c_stm32_config *cfg = dev->config;
Expand Down Expand Up @@ -797,10 +1119,12 @@ int stm32_i2c_configure_timing(const struct device *dev, uint32_t clock)
return -EINVAL;
}

LOG_INF("I2C TIMING = 0x%x\n", timing);
LL_I2C_SetTiming(i2c, timing);

return 0;
}
#endif /* CONFIG_I2C_STM32_V2_TIMING */

int stm32_i2c_transaction(const struct device *dev,
struct i2c_msg msg, uint8_t *next_msg_flags,
Expand Down
Loading
Loading