in busses/i2c-img-scb.c [1153:1327]
static int img_i2c_init(struct img_i2c *i2c)
{
unsigned int clk_khz, bitrate_khz, clk_period, tckh, tckl, tsdh;
unsigned int i, data, prescale, inc, int_bitrate, filt;
struct img_i2c_timings timing;
u32 rev;
int ret;
ret = pm_runtime_resume_and_get(i2c->adap.dev.parent);
if (ret < 0)
return ret;
rev = img_i2c_readl(i2c, SCB_CORE_REV_REG);
if ((rev & 0x00ffffff) < 0x00020200) {
dev_info(i2c->adap.dev.parent,
"Unknown hardware revision (%d.%d.%d.%d)\n",
(rev >> 24) & 0xff, (rev >> 16) & 0xff,
(rev >> 8) & 0xff, rev & 0xff);
pm_runtime_mark_last_busy(i2c->adap.dev.parent);
pm_runtime_put_autosuspend(i2c->adap.dev.parent);
return -EINVAL;
}
/* Fencing enabled by default. */
i2c->need_wr_rd_fence = true;
/* Determine what mode we're in from the bitrate */
timing = timings[0];
for (i = 0; i < ARRAY_SIZE(timings); i++) {
if (i2c->bitrate <= timings[i].max_bitrate) {
timing = timings[i];
break;
}
}
if (i2c->bitrate > timings[ARRAY_SIZE(timings) - 1].max_bitrate) {
dev_warn(i2c->adap.dev.parent,
"requested bitrate (%u) is higher than the max bitrate supported (%u)\n",
i2c->bitrate,
timings[ARRAY_SIZE(timings) - 1].max_bitrate);
timing = timings[ARRAY_SIZE(timings) - 1];
i2c->bitrate = timing.max_bitrate;
}
bitrate_khz = i2c->bitrate / 1000;
clk_khz = clk_get_rate(i2c->scb_clk) / 1000;
/* Find the prescale that would give us that inc (approx delay = 0) */
prescale = SCB_OPT_INC * clk_khz / (256 * 16 * bitrate_khz);
prescale = clamp_t(unsigned int, prescale, 1, 8);
clk_khz /= prescale;
/* Setup the clock increment value */
inc = (256 * 16 * bitrate_khz) / clk_khz;
/*
* The clock generation logic allows to filter glitches on the bus.
* This filter is able to remove bus glitches shorter than 50ns.
* If the clock enable rate is greater than 20 MHz, no filtering
* is required, so we need to disable it.
* If it's between the 20-40 MHz range, there's no need to divide
* the clock to get a filter.
*/
if (clk_khz < 20000) {
filt = SCB_FILT_DISABLE;
} else if (clk_khz < 40000) {
filt = SCB_FILT_BYPASS;
} else {
/* Calculate filter clock */
filt = (64000 / ((clk_khz / 1000) * SCB_FILT_GLITCH));
/* Scale up if needed */
if (64000 % ((clk_khz / 1000) * SCB_FILT_GLITCH))
inc++;
if (filt > SCB_FILT_INC_MASK)
filt = SCB_FILT_INC_MASK;
filt = (filt & SCB_FILT_INC_MASK) << SCB_FILT_INC_SHIFT;
}
data = filt | ((inc & SCB_INC_MASK) << SCB_INC_SHIFT) | (prescale - 1);
img_i2c_writel(i2c, SCB_CLK_SET_REG, data);
/* Obtain the clock period of the fx16 clock in ns */
clk_period = (256 * 1000000) / (clk_khz * inc);
/* Calculate the bitrate in terms of internal clock pulses */
int_bitrate = 1000000 / (bitrate_khz * clk_period);
if ((1000000 % (bitrate_khz * clk_period)) >=
((bitrate_khz * clk_period) / 2))
int_bitrate++;
/*
* Setup clock duty cycle, start with 50% and adjust TCKH and TCKL
* values from there if they don't meet minimum timing requirements
*/
tckh = int_bitrate / 2;
tckl = int_bitrate - tckh;
/* Adjust TCKH and TCKL values */
data = DIV_ROUND_UP(timing.tckl, clk_period);
if (tckl < data) {
tckl = data;
tckh = int_bitrate - tckl;
}
if (tckh > 0)
--tckh;
if (tckl > 0)
--tckl;
img_i2c_writel(i2c, SCB_TIME_TCKH_REG, tckh);
img_i2c_writel(i2c, SCB_TIME_TCKL_REG, tckl);
/* Setup TSDH value */
tsdh = DIV_ROUND_UP(timing.tsdh, clk_period);
if (tsdh > 1)
data = tsdh - 1;
else
data = 0x01;
img_i2c_writel(i2c, SCB_TIME_TSDH_REG, data);
/* This value is used later */
tsdh = data;
/* Setup TPL value */
data = timing.tpl / clk_period;
if (data > 0)
--data;
img_i2c_writel(i2c, SCB_TIME_TPL_REG, data);
/* Setup TPH value */
data = timing.tph / clk_period;
if (data > 0)
--data;
img_i2c_writel(i2c, SCB_TIME_TPH_REG, data);
/* Setup TSDL value to TPL + TSDH + 2 */
img_i2c_writel(i2c, SCB_TIME_TSDL_REG, data + tsdh + 2);
/* Setup TP2S value */
data = timing.tp2s / clk_period;
if (data > 0)
--data;
img_i2c_writel(i2c, SCB_TIME_TP2S_REG, data);
img_i2c_writel(i2c, SCB_TIME_TBI_REG, TIMEOUT_TBI);
img_i2c_writel(i2c, SCB_TIME_TSL_REG, TIMEOUT_TSL);
img_i2c_writel(i2c, SCB_TIME_TDL_REG, TIMEOUT_TDL);
/* Take module out of soft reset and enable clocks */
img_i2c_soft_reset(i2c);
/* Disable all interrupts */
img_i2c_writel(i2c, SCB_INT_MASK_REG, 0);
/* Clear all interrupts */
img_i2c_writel(i2c, SCB_INT_CLEAR_REG, ~0);
/* Clear the scb_line_status events */
img_i2c_writel(i2c, SCB_CLEAR_REG, ~0);
/* Enable interrupts */
img_i2c_writel(i2c, SCB_INT_MASK_REG, i2c->int_enable);
/* Perform a synchronous sequence to reset the bus */
ret = img_i2c_reset_bus(i2c);
pm_runtime_mark_last_busy(i2c->adap.dev.parent);
pm_runtime_put_autosuspend(i2c->adap.dev.parent);
return ret;
}