in master/svc-i3c-master.c [476:577]
static int svc_i3c_master_bus_init(struct i3c_master_controller *m)
{
struct svc_i3c_master *master = to_svc_i3c_master(m);
struct i3c_bus *bus = i3c_master_get_bus(m);
struct i3c_device_info info = {};
unsigned long fclk_rate, fclk_period_ns;
unsigned int high_period_ns, od_low_period_ns;
u32 ppbaud, pplow, odhpp, odbaud, odstop, i2cbaud, reg;
int ret;
ret = pm_runtime_resume_and_get(master->dev);
if (ret < 0) {
dev_err(master->dev,
"<%s> cannot resume i3c bus master, err: %d\n",
__func__, ret);
return ret;
}
/* Timings derivation */
fclk_rate = clk_get_rate(master->fclk);
if (!fclk_rate) {
ret = -EINVAL;
goto rpm_out;
}
fclk_period_ns = DIV_ROUND_UP(1000000000, fclk_rate);
/*
* Using I3C Push-Pull mode, target is 12.5MHz/80ns period.
* Simplest configuration is using a 50% duty-cycle of 40ns.
*/
ppbaud = DIV_ROUND_UP(40, fclk_period_ns) - 1;
pplow = 0;
/*
* Using I3C Open-Drain mode, target is 4.17MHz/240ns with a
* duty-cycle tuned so that high levels are filetered out by
* the 50ns filter (target being 40ns).
*/
odhpp = 1;
high_period_ns = (ppbaud + 1) * fclk_period_ns;
odbaud = DIV_ROUND_UP(240 - high_period_ns, high_period_ns) - 1;
od_low_period_ns = (odbaud + 1) * high_period_ns;
switch (bus->mode) {
case I3C_BUS_MODE_PURE:
i2cbaud = 0;
odstop = 0;
break;
case I3C_BUS_MODE_MIXED_FAST:
case I3C_BUS_MODE_MIXED_LIMITED:
/*
* Using I2C Fm+ mode, target is 1MHz/1000ns, the difference
* between the high and low period does not really matter.
*/
i2cbaud = DIV_ROUND_UP(1000, od_low_period_ns) - 2;
odstop = 1;
break;
case I3C_BUS_MODE_MIXED_SLOW:
/*
* Using I2C Fm mode, target is 0.4MHz/2500ns, with the same
* constraints as the FM+ mode.
*/
i2cbaud = DIV_ROUND_UP(2500, od_low_period_ns) - 2;
odstop = 1;
break;
default:
goto rpm_out;
}
reg = SVC_I3C_MCONFIG_MASTER_EN |
SVC_I3C_MCONFIG_DISTO(0) |
SVC_I3C_MCONFIG_HKEEP(0) |
SVC_I3C_MCONFIG_ODSTOP(odstop) |
SVC_I3C_MCONFIG_PPBAUD(ppbaud) |
SVC_I3C_MCONFIG_PPLOW(pplow) |
SVC_I3C_MCONFIG_ODBAUD(odbaud) |
SVC_I3C_MCONFIG_ODHPP(odhpp) |
SVC_I3C_MCONFIG_SKEW(0) |
SVC_I3C_MCONFIG_I2CBAUD(i2cbaud);
writel(reg, master->regs + SVC_I3C_MCONFIG);
/* Master core's registration */
ret = i3c_master_get_free_addr(m, 0);
if (ret < 0)
goto rpm_out;
info.dyn_addr = ret;
writel(SVC_MDYNADDR_VALID | SVC_MDYNADDR_ADDR(info.dyn_addr),
master->regs + SVC_I3C_MDYNADDR);
ret = i3c_master_set_info(&master->base, &info);
if (ret)
goto rpm_out;
rpm_out:
pm_runtime_mark_last_busy(master->dev);
pm_runtime_put_autosuspend(master->dev);
return ret;
}