in busses/i2c-stm32f7.c [440:631]
static int stm32f7_i2c_compute_timing(struct stm32f7_i2c_dev *i2c_dev,
struct stm32f7_i2c_setup *setup,
struct stm32f7_i2c_timings *output)
{
struct stm32f7_i2c_spec *specs;
u32 p_prev = STM32F7_PRESC_MAX;
u32 i2cclk = DIV_ROUND_CLOSEST(NSEC_PER_SEC,
setup->clock_src);
u32 i2cbus = DIV_ROUND_CLOSEST(NSEC_PER_SEC,
setup->speed_freq);
u32 clk_error_prev = i2cbus;
u32 tsync;
u32 af_delay_min, af_delay_max;
u32 dnf_delay;
u32 clk_min, clk_max;
int sdadel_min, sdadel_max;
int scldel_min;
struct stm32f7_i2c_timings *v, *_v, *s;
struct list_head solutions;
u16 p, l, a, h;
int ret = 0;
specs = stm32f7_get_specs(setup->speed_freq);
if (specs == ERR_PTR(-EINVAL)) {
dev_err(i2c_dev->dev, "speed out of bound {%d}\n",
setup->speed_freq);
return -EINVAL;
}
if ((setup->rise_time > specs->rise_max) ||
(setup->fall_time > specs->fall_max)) {
dev_err(i2c_dev->dev,
"timings out of bound Rise{%d>%d}/Fall{%d>%d}\n",
setup->rise_time, specs->rise_max,
setup->fall_time, specs->fall_max);
return -EINVAL;
}
i2c_dev->dnf = DIV_ROUND_CLOSEST(i2c_dev->dnf_dt, i2cclk);
if (i2c_dev->dnf > STM32F7_I2C_DNF_MAX) {
dev_err(i2c_dev->dev,
"DNF out of bound %d/%d\n",
i2c_dev->dnf * i2cclk, STM32F7_I2C_DNF_MAX * i2cclk);
return -EINVAL;
}
/* Analog and Digital Filters */
af_delay_min =
(i2c_dev->analog_filter ?
STM32F7_I2C_ANALOG_FILTER_DELAY_MIN : 0);
af_delay_max =
(i2c_dev->analog_filter ?
STM32F7_I2C_ANALOG_FILTER_DELAY_MAX : 0);
dnf_delay = i2c_dev->dnf * i2cclk;
sdadel_min = specs->hddat_min + setup->fall_time -
af_delay_min - (i2c_dev->dnf + 3) * i2cclk;
sdadel_max = specs->vddat_max - setup->rise_time -
af_delay_max - (i2c_dev->dnf + 4) * i2cclk;
scldel_min = setup->rise_time + specs->sudat_min;
if (sdadel_min < 0)
sdadel_min = 0;
if (sdadel_max < 0)
sdadel_max = 0;
dev_dbg(i2c_dev->dev, "SDADEL(min/max): %i/%i, SCLDEL(Min): %i\n",
sdadel_min, sdadel_max, scldel_min);
INIT_LIST_HEAD(&solutions);
/* Compute possible values for PRESC, SCLDEL and SDADEL */
for (p = 0; p < STM32F7_PRESC_MAX; p++) {
for (l = 0; l < STM32F7_SCLDEL_MAX; l++) {
u32 scldel = (l + 1) * (p + 1) * i2cclk;
if (scldel < scldel_min)
continue;
for (a = 0; a < STM32F7_SDADEL_MAX; a++) {
u32 sdadel = (a * (p + 1) + 1) * i2cclk;
if (((sdadel >= sdadel_min) &&
(sdadel <= sdadel_max)) &&
(p != p_prev)) {
v = kmalloc(sizeof(*v), GFP_KERNEL);
if (!v) {
ret = -ENOMEM;
goto exit;
}
v->presc = p;
v->scldel = l;
v->sdadel = a;
p_prev = p;
list_add_tail(&v->node,
&solutions);
break;
}
}
if (p_prev == p)
break;
}
}
if (list_empty(&solutions)) {
dev_err(i2c_dev->dev, "no Prescaler solution\n");
ret = -EPERM;
goto exit;
}
tsync = af_delay_min + dnf_delay + (2 * i2cclk);
s = NULL;
clk_max = NSEC_PER_SEC / RATE_MIN(setup->speed_freq);
clk_min = NSEC_PER_SEC / setup->speed_freq;
/*
* Among Prescaler possibilities discovered above figures out SCL Low
* and High Period. Provided:
* - SCL Low Period has to be higher than SCL Clock Low Period
* defined by I2C Specification. I2C Clock has to be lower than
* (SCL Low Period - Analog/Digital filters) / 4.
* - SCL High Period has to be lower than SCL Clock High Period
* defined by I2C Specification
* - I2C Clock has to be lower than SCL High Period
*/
list_for_each_entry(v, &solutions, node) {
u32 prescaler = (v->presc + 1) * i2cclk;
for (l = 0; l < STM32F7_SCLL_MAX; l++) {
u32 tscl_l = (l + 1) * prescaler + tsync;
if ((tscl_l < specs->l_min) ||
(i2cclk >=
((tscl_l - af_delay_min - dnf_delay) / 4))) {
continue;
}
for (h = 0; h < STM32F7_SCLH_MAX; h++) {
u32 tscl_h = (h + 1) * prescaler + tsync;
u32 tscl = tscl_l + tscl_h +
setup->rise_time + setup->fall_time;
if ((tscl >= clk_min) && (tscl <= clk_max) &&
(tscl_h >= specs->h_min) &&
(i2cclk < tscl_h)) {
int clk_error = tscl - i2cbus;
if (clk_error < 0)
clk_error = -clk_error;
if (clk_error < clk_error_prev) {
clk_error_prev = clk_error;
v->scll = l;
v->sclh = h;
s = v;
}
}
}
}
}
if (!s) {
dev_err(i2c_dev->dev, "no solution at all\n");
ret = -EPERM;
goto exit;
}
output->presc = s->presc;
output->scldel = s->scldel;
output->sdadel = s->sdadel;
output->scll = s->scll;
output->sclh = s->sclh;
dev_dbg(i2c_dev->dev,
"Presc: %i, scldel: %i, sdadel: %i, scll: %i, sclh: %i\n",
output->presc,
output->scldel, output->sdadel,
output->scll, output->sclh);
exit:
/* Release list and memory */
list_for_each_entry_safe(v, _v, &solutions, node) {
list_del(&v->node);
kfree(v);
}
return ret;
}