in master/i3c-master-cdns.c [1206:1303]
static int cdns_i3c_master_bus_init(struct i3c_master_controller *m)
{
struct cdns_i3c_master *master = to_cdns_i3c_master(m);
unsigned long pres_step, sysclk_rate, max_i2cfreq;
struct i3c_bus *bus = i3c_master_get_bus(m);
u32 ctrl, prescl0, prescl1, pres, low;
struct i3c_device_info info = { };
int ret, ncycles;
switch (bus->mode) {
case I3C_BUS_MODE_PURE:
ctrl = CTRL_PURE_BUS_MODE;
break;
case I3C_BUS_MODE_MIXED_FAST:
ctrl = CTRL_MIXED_FAST_BUS_MODE;
break;
case I3C_BUS_MODE_MIXED_SLOW:
ctrl = CTRL_MIXED_SLOW_BUS_MODE;
break;
default:
return -EINVAL;
}
sysclk_rate = clk_get_rate(master->sysclk);
if (!sysclk_rate)
return -EINVAL;
pres = DIV_ROUND_UP(sysclk_rate, (bus->scl_rate.i3c * 4)) - 1;
if (pres > PRESCL_CTRL0_MAX)
return -ERANGE;
bus->scl_rate.i3c = sysclk_rate / ((pres + 1) * 4);
prescl0 = PRESCL_CTRL0_I3C(pres);
low = ((I3C_BUS_TLOW_OD_MIN_NS * sysclk_rate) / (pres + 1)) - 2;
prescl1 = PRESCL_CTRL1_OD_LOW(low);
max_i2cfreq = bus->scl_rate.i2c;
pres = (sysclk_rate / (max_i2cfreq * 5)) - 1;
if (pres > PRESCL_CTRL0_MAX)
return -ERANGE;
bus->scl_rate.i2c = sysclk_rate / ((pres + 1) * 5);
prescl0 |= PRESCL_CTRL0_I2C(pres);
writel(prescl0, master->regs + PRESCL_CTRL0);
/* Calculate OD and PP low. */
pres_step = 1000000000 / (bus->scl_rate.i3c * 4);
ncycles = DIV_ROUND_UP(I3C_BUS_TLOW_OD_MIN_NS, pres_step) - 2;
if (ncycles < 0)
ncycles = 0;
prescl1 = PRESCL_CTRL1_OD_LOW(ncycles);
writel(prescl1, master->regs + PRESCL_CTRL1);
/* Get an address for the master. */
ret = i3c_master_get_free_addr(m, 0);
if (ret < 0)
return ret;
writel(prepare_rr0_dev_address(ret) | DEV_ID_RR0_IS_I3C,
master->regs + DEV_ID_RR0(0));
cdns_i3c_master_dev_rr_to_info(master, 0, &info);
if (info.bcr & I3C_BCR_HDR_CAP)
info.hdr_cap = I3C_CCC_HDR_MODE(I3C_HDR_DDR);
ret = i3c_master_set_info(&master->base, &info);
if (ret)
return ret;
/*
* Enable Hot-Join, and, when a Hot-Join request happens, disable all
* events coming from this device.
*
* We will issue ENTDAA afterwards from the threaded IRQ handler.
*/
ctrl |= CTRL_HJ_ACK | CTRL_HJ_DISEC | CTRL_HALT_EN | CTRL_MCS_EN;
/*
* Configure data hold delay based on device-specific data.
*
* MIPI I3C Specification 1.0 defines non-zero minimal tHD_PP timing on
* master output. This setting allows to meet this timing on master's
* SoC outputs, regardless of PCB balancing.
*/
ctrl |= CTRL_THD_DELAY(cdns_i3c_master_calculate_thd_delay(master));
writel(ctrl, master->regs + CTRL);
cdns_i3c_master_enable(master);
return 0;
}