[PATCH v7 7/7] i3c: master: mastership handover, defslvs processing in cdns controller driver

From: Parshuram Thombare
Date: Mon May 11 2020 - 09:17:50 EST


Added I3C bus mastership handover and DEFSLVS message handling
code to Cadence's I3C master controller driver.

Signed-off-by: Parshuram Thombare <pthombar@xxxxxxxxxxx>
---
drivers/i3c/master/i3c-master-cdns.c | 377 +++++++++++++++++++++++++--
1 file changed, 354 insertions(+), 23 deletions(-)

diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c
index ed4f43807f9e..1155aa327404 100644
--- a/drivers/i3c/master/i3c-master-cdns.c
+++ b/drivers/i3c/master/i3c-master-cdns.c
@@ -157,6 +157,7 @@
#define SLV_IMR 0x48
#define SLV_ICR 0x4c
#define SLV_ISR 0x50
+#define SLV_INT_DEFSLVS BIT(21)
#define SLV_INT_TM BIT(20)
#define SLV_INT_ERROR BIT(19)
#define SLV_INT_EVENT_UP BIT(18)
@@ -189,7 +190,7 @@
#define SLV_STATUS1_HJ_DIS BIT(18)
#define SLV_STATUS1_MR_DIS BIT(17)
#define SLV_STATUS1_PROT_ERR BIT(16)
-#define SLV_STATUS1_DA(x) (((s) & GENMASK(15, 9)) >> 9)
+#define SLV_STATUS1_DA(s) (((s) & GENMASK(15, 9)) >> 9)
#define SLV_STATUS1_HAS_DA BIT(8)
#define SLV_STATUS1_DDR_RX_FULL BIT(7)
#define SLV_STATUS1_DDR_TX_FULL BIT(6)
@@ -390,6 +391,8 @@ struct cdns_i3c_xfer {

struct cdns_i3c_master {
struct work_struct hj_work;
+ struct work_struct mr_yield_work;
+ struct work_struct defslvs_work;
struct i3c_master_controller base;
u32 free_rr_slots;
unsigned int maxdevs;
@@ -408,6 +411,7 @@ struct cdns_i3c_master {
struct clk *pclk;
struct cdns_i3c_master_caps caps;
unsigned long i3c_scl_lim;
+ u8 mr_addr;
};

static inline struct cdns_i3c_master *
@@ -1187,10 +1191,6 @@ static int cdns_i3c_master_do_daa(struct i3c_master_controller *m)

cdns_i3c_master_upd_i3c_scl_lim(master);

- /* Unmask Hot-Join and Mastership request interrupts. */
- i3c_master_enec_locked(m, I3C_BROADCAST_ADDR,
- I3C_CCC_EVENT_HJ | I3C_CCC_EVENT_MR);
-
return 0;
}

@@ -1199,21 +1199,21 @@ 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;
+ u32 ctrl, prescl0, prescl1, pres, low, bus_mode;
struct i3c_device_info info = { };
int ret, ncycles;

switch (bus->mode) {
case I3C_BUS_MODE_PURE:
- ctrl = CTRL_PURE_BUS_MODE;
+ bus_mode = CTRL_PURE_BUS_MODE;
break;

case I3C_BUS_MODE_MIXED_FAST:
- ctrl = CTRL_MIXED_FAST_BUS_MODE;
+ bus_mode = CTRL_MIXED_FAST_BUS_MODE;
break;

case I3C_BUS_MODE_MIXED_SLOW:
- ctrl = CTRL_MIXED_SLOW_BUS_MODE;
+ bus_mode = CTRL_MIXED_SLOW_BUS_MODE;
break;

default:
@@ -1244,7 +1244,6 @@ static int cdns_i3c_master_bus_init(struct i3c_master_controller *m)
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);
@@ -1252,7 +1251,6 @@ static int cdns_i3c_master_bus_init(struct i3c_master_controller *m)
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);
@@ -1270,13 +1268,21 @@ static int cdns_i3c_master_bus_init(struct i3c_master_controller *m)
if (ret)
return ret;

+ ctrl = readl(master->regs + CTRL);
+ if (ctrl & CTRL_DEV_EN)
+ cdns_i3c_master_disable(master);
+ writel(prescl0, master->regs + PRESCL_CTRL0);
+ writel(prescl1, master->regs + PRESCL_CTRL1);
+ ctrl &= ~CTRL_BUS_MODE_MASK;
+ ctrl |= bus_mode | CTRL_HALT_EN | CTRL_MCS_EN;
/*
* 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;
+ if (!m->secondary)
+ ctrl |= CTRL_HJ_ACK | CTRL_HJ_DISEC;
writel(ctrl, master->regs + CTRL);

cdns_i3c_master_enable(master);
@@ -1340,6 +1346,7 @@ static void cdns_i3c_master_handle_ibi(struct cdns_i3c_master *master,

static void cnds_i3c_master_demux_ibis(struct cdns_i3c_master *master)
{
+ struct i3c_dev_desc *dev;
u32 status0;

writel(MST_INT_IBIR_THR, master->regs + MST_ICR);
@@ -1361,6 +1368,14 @@ static void cnds_i3c_master_demux_ibis(struct cdns_i3c_master *master)

case IBIR_TYPE_MR:
WARN_ON(IBIR_XFER_BYTES(ibir) || (ibir & IBIR_ERROR));
+ if (ibir & IBIR_ACKED) {
+ dev = master->ibi.slots[IBIR_SLVID(ibir)];
+ master->mr_addr = dev->info.dyn_addr;
+ queue_work(master->base.wq,
+ &master->mr_yield_work);
+ }
+ break;
+
default:
break;
}
@@ -1372,16 +1387,40 @@ static irqreturn_t cdns_i3c_master_interrupt(int irq, void *data)
struct cdns_i3c_master *master = data;
u32 status;

- status = readl(master->regs + MST_ISR);
- if (!(status & readl(master->regs + MST_IMR)))
- return IRQ_NONE;
+ if (master->base.this &&
+ master->base.this == master->base.bus.cur_master) {
+ status = readl(master->regs + MST_ISR);
+ if (!(status & readl(master->regs + MST_IMR)))
+ return IRQ_NONE;
+
+ spin_lock(&master->xferqueue.lock);
+ cdns_i3c_master_end_xfer_locked(master, status);
+ spin_unlock(&master->xferqueue.lock);
+
+ if (status & MST_INT_IBIR_THR)
+ cnds_i3c_master_demux_ibis(master);
+
+ if (status & MST_INT_MR_DONE) {
+ writel(FLUSH_RX_FIFO | FLUSH_TX_FIFO,
+ master->regs + FLUSH_CTRL);
+ writel(MST_INT_MR_DONE, master->regs + MST_ICR);
+ }
+ } else {
+ status = (readl(master->regs + SLV_ISR) &
+ readl(master->regs + SLV_IMR));
+
+ if (!status)
+ return IRQ_NONE;
+
+ if (status & SLV_INT_MR_DONE)
+ writel(FLUSH_RX_FIFO | FLUSH_TX_FIFO,
+ master->regs + FLUSH_CTRL);

- spin_lock(&master->xferqueue.lock);
- cdns_i3c_master_end_xfer_locked(master, status);
- spin_unlock(&master->xferqueue.lock);
+ if (status & SLV_INT_DEFSLVS)
+ queue_work(master->base.wq, &master->defslvs_work);

- if (status & MST_INT_IBIR_THR)
- cnds_i3c_master_demux_ibis(master);
+ writel(status, master->regs + SLV_ICR);
+ }

return IRQ_HANDLED;
}
@@ -1505,6 +1544,138 @@ static void cdns_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
i3c_generic_ibi_recycle_slot(data->ibi_pool, slot);
}

+static int cdns_i3c_master_find_ibi_slot(struct cdns_i3c_master *master,
+ struct i3c_dev_desc *dev,
+ s16 *slot)
+{
+ unsigned long flags;
+ unsigned int i;
+ int ret = -ENOENT;
+
+ spin_lock_irqsave(&master->ibi.lock, flags);
+ for (i = 0; i < master->ibi.num_slots; i++) {
+ if (master->ibi.slots[i] == dev) {
+ *slot = i;
+ ret = 0;
+ break;
+ }
+ }
+
+ if (ret) {
+ for (i = 0; i < master->ibi.num_slots; i++) {
+ if (!master->ibi.slots[i]) {
+ master->ibi.slots[i] = dev;
+ *slot = i;
+ ret = 0;
+ break;
+ }
+ }
+ }
+ spin_unlock_irqrestore(&master->ibi.lock, flags);
+
+ return ret;
+}
+
+static int cdns_i3c_request_mastership(struct i3c_master_controller *m)
+{
+ struct cdns_i3c_master *master = to_cdns_i3c_master(m);
+ int status;
+
+ status = readl(master->regs + MST_STATUS0);
+
+ if (status & MST_STATUS0_MASTER_MODE)
+ return 0;
+
+ status = readl(master->regs + SLV_STATUS1);
+
+ if (status & SLV_STATUS1_MR_DIS)
+ return -EACCES;
+
+ writel(readl(master->regs + CTRL) | CTRL_MST_INIT | CTRL_MST_ACK,
+ master->regs + CTRL);
+
+ return 0;
+}
+
+static void
+cdns_i3c_master_enable_mastership_events(struct i3c_master_controller *m)
+{
+ struct cdns_i3c_master *master = to_cdns_i3c_master(m);
+ struct cdns_i3c_i2c_dev_data *data;
+ struct i3c_dev_desc *i3cdev;
+ unsigned long flags;
+ u32 sircfg, sirmap;
+ int ret;
+
+ i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
+ if (I3C_BCR_DEVICE_ROLE(i3cdev->info.bcr) !=
+ I3C_BCR_I3C_MASTER ||
+ m->this == i3cdev)
+ continue;
+
+ data = i3c_dev_get_master_data(i3cdev);
+ if (!data)
+ continue;
+
+ ret = cdns_i3c_master_find_ibi_slot(master, i3cdev, &data->ibi);
+ if (ret)
+ continue;
+
+ spin_lock_irqsave(&master->ibi.lock, flags);
+ sirmap = readl(master->regs + SIR_MAP_DEV_REG(data->ibi));
+ sirmap &= ~SIR_MAP_DEV_CONF_MASK(data->ibi);
+ sircfg = SIR_MAP_DEV_ROLE(i3cdev->info.bcr >> 6) |
+ SIR_MAP_DEV_DA(i3cdev->info.dyn_addr) |
+ SIR_MAP_DEV_PL(i3cdev->info.max_ibi_len) |
+ SIR_MAP_DEV_ACK;
+
+ if (i3cdev->info.bcr & I3C_BCR_MAX_DATA_SPEED_LIM)
+ sircfg |= SIR_MAP_DEV_SLOW;
+
+ sirmap |= SIR_MAP_DEV_CONF(data->ibi, sircfg);
+ writel(sirmap, master->regs + SIR_MAP_DEV_REG(data->ibi));
+ spin_unlock_irqrestore(&master->ibi.lock, flags);
+ }
+}
+
+static bool
+cdns_i3c_master_check_event_set(struct i3c_master_controller *m,
+ enum i3c_event event)
+{
+ struct cdns_i3c_master *master = to_cdns_i3c_master(m);
+ bool ret = false;
+
+ switch (event) {
+ case I3C_SLV_DA_UPDATE:
+ if (readl(master->regs + SLV_STATUS1) & SLV_STATUS1_HAS_DA)
+ ret = true;
+ break;
+
+ case I3C_SLV_MR_DONE:
+ if (readl(master->regs + MST_STATUS0) & MST_STATUS0_MASTER_MODE)
+ ret = true;
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+int cdns_i3c_sec_master_dyn_addr(struct i3c_master_controller *m)
+{
+ struct cdns_i3c_master *master = to_cdns_i3c_master(m);
+ int dyn_addr = -1;
+ u32 status;
+
+ status = readl(master->regs + SLV_STATUS1);
+ if (status & SLV_STATUS1_HAS_DA)
+ dyn_addr = SLV_STATUS1_DA(status);
+
+ return dyn_addr;
+}
+
static const struct i3c_master_controller_ops cdns_i3c_master_ops = {
.bus_init = cdns_i3c_master_bus_init,
.bus_cleanup = cdns_i3c_master_bus_cleanup,
@@ -1524,6 +1695,10 @@ static const struct i3c_master_controller_ops cdns_i3c_master_ops = {
.request_ibi = cdns_i3c_master_request_ibi,
.free_ibi = cdns_i3c_master_free_ibi,
.recycle_ibi_slot = cdns_i3c_master_recycle_ibi_slot,
+ .request_mastership = cdns_i3c_request_mastership,
+ .enable_mr_events = cdns_i3c_master_enable_mastership_events,
+ .check_event_set = cdns_i3c_master_check_event_set,
+ .sec_mst_dyn_addr = cdns_i3c_sec_master_dyn_addr,
};

static void cdns_i3c_master_hj(struct work_struct *work)
@@ -1535,10 +1710,152 @@ static void cdns_i3c_master_hj(struct work_struct *work)
i3c_master_do_daa(&master->base);
}

+static void cdns_i3c_master_yield(struct work_struct *work)
+{
+ struct cdns_i3c_master *master = container_of(work,
+ struct cdns_i3c_master,
+ mr_yield_work);
+
+ i3c_master_yield_bus(&master->base, master->mr_addr);
+}
+
+static void cdns_i3c_sec_master_defslvs(struct work_struct *work)
+{
+ struct cdns_i3c_master *master = container_of(work,
+ struct cdns_i3c_master,
+ defslvs_work);
+ struct i3c_master_controller *m = &master->base;
+ struct i3c_ccc_dev_desc *desc;
+ struct cdns_i3c_i2c_dev_data *data;
+ struct i2c_dev_desc *i2cdev;
+ struct i3c_dev_desc *i3cdev;
+ u32 devs, val, rr, slot;
+ u32 r0, r1, r2;
+ u8 saddr;
+
+ devs = readl(master->regs + DEVS_CTRL) & DEVS_CTRL_DEVS_ACTIVE_MASK;
+ master->free_rr_slots = GENMASK(master->maxdevs, 1) & ~devs;
+
+ /*
+ * We chose to ignore I2C devices received from
+ * main master and use I2C device info from boardinfo.
+ * Since I2C device from boardinfo are already
+ * registered during bus_init, we just use same slot
+ * and if any I3C device is received in DEFSLVS in that
+ * place, just move that I3C device to other free slot.
+ * If there is no free slot, then such I3C devices
+ * are ignored.
+ * Master controller driver can chose how to handle I2C
+ * devices in DEFSLVS and pass only I3C devices list to
+ * I3C core DEFSVLS processing to handle hotplug and
+ * I3C device address changes.
+ */
+ for (slot = 0; slot < master->maxdevs; slot++) {
+ if (!(devs & BIT(slot)))
+ continue;
+
+ val = readl(master->regs + DEV_ID_RR0(slot));
+ if (!(val & DEV_ID_RR0_IS_I3C)) {
+ writel(readl(master->regs + DEVS_CTRL) |
+ DEVS_CTRL_DEV_CLR(slot),
+ master->regs + DEVS_CTRL);
+ master->free_rr_slots |= BIT(slot);
+ }
+ }
+
+ val = 0;
+ devs = readl(master->regs + DEVS_CTRL) & DEVS_CTRL_DEVS_ACTIVE_MASK;
+ i3c_bus_for_each_i2cdev(&m->bus, i2cdev) {
+ data = i2c_dev_get_master_data(i2cdev);
+ if (devs & BIT(data->id)) {
+ rr = readl(master->regs + DEV_ID_RR0(data->id));
+ saddr = DEV_ID_RR0_GET_DEV_ADDR(rr);
+ if (saddr != i2cdev->boardinfo->base.addr) {
+ r0 = readl(master->regs + DEV_ID_RR0(data->id));
+ r1 = readl(master->regs + DEV_ID_RR1(data->id));
+ r2 = readl(master->regs + DEV_ID_RR2(data->id));
+ slot = ffs(master->free_rr_slots) - 1;
+ if (slot > 0) {
+ writel(r0,
+ master->regs + DEV_ID_RR0(slot));
+ writel(r1,
+ master->regs + DEV_ID_RR1(slot));
+ writel(r2,
+ master->regs + DEV_ID_RR2(slot));
+ writel(readl(master->regs + DEVS_CTRL) |
+ DEVS_CTRL_DEV_ACTIVE(slot),
+ master->regs + DEVS_CTRL);
+ master->free_rr_slots &= ~BIT(slot);
+ }
+ } else {
+ continue;
+ }
+ }
+ writel(readl(master->regs + DEVS_CTRL) |
+ DEVS_CTRL_DEV_CLR(data->id),
+ master->regs + DEVS_CTRL);
+ writel(readl(master->regs + DEVS_CTRL) |
+ DEVS_CTRL_DEV_ACTIVE(data->id),
+ master->regs + DEVS_CTRL);
+ writel(prepare_rr0_dev_address(i2cdev->boardinfo->base.addr) |
+ (i2cdev->boardinfo->base.flags & I2C_CLIENT_TEN ?
+ DEV_ID_RR0_LVR_EXT_ADDR : 0),
+ master->regs + DEV_ID_RR0(data->id));
+ writel(i2cdev->boardinfo->lvr,
+ master->regs + DEV_ID_RR2(data->id));
+ master->free_rr_slots &= ~BIT(data->id);
+ }
+
+ master->base.defslvs_data.ndevs = 0;
+ desc = master->base.defslvs_data.devs;
+ devs = readl(master->regs + DEVS_CTRL) & DEVS_CTRL_DEVS_ACTIVE_MASK;
+
+ for (slot = 0; slot < master->maxdevs; slot++) {
+ if (!(devs & BIT(slot)))
+ continue;
+
+ val = readl(master->regs + DEV_ID_RR0(slot));
+ if (val & DEV_ID_RR0_IS_I3C) {
+ memset(desc, 0, sizeof(struct i3c_ccc_dev_desc));
+ rr = readl(master->regs + DEV_ID_RR0(slot));
+ desc->dyn_addr = DEV_ID_RR0_GET_DEV_ADDR(rr);
+ rr = readl(master->regs + DEV_ID_RR2(slot));
+ desc->dcr = rr;
+ desc->bcr = rr >> 8;
+ master->base.defslvs_data.ndevs++;
+ desc++;
+ }
+ }
+
+ if (i3c_master_process_defslvs(m)) {
+ queue_work(master->base.wq, work);
+ return;
+ }
+
+ /*
+ * Fix data->id for any changes due to mismatch in number
+ * of I2C devices on main and secondary master, causing
+ * I3C devices received in DEFSLVS in a slot which was used
+ * for I2C device on sec master to be moved to other free
+ * slot. And then if any I3C device get unplugged
+ * next DEFSLVS processing would I3C devices in original
+ * I2C slot to be moved to different slot than it
+ * was moved at the first DEFSLVS processing.
+ */
+ i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
+ data = i3c_dev_get_master_data(i3cdev);
+ if (!data)
+ continue;
+ data->id = cdns_i3c_master_get_rr_slot(master,
+ i3cdev->info.dyn_addr);
+ }
+}
+
static int cdns_i3c_master_probe(struct platform_device *pdev)
{
struct cdns_i3c_master *master;
struct resource *res;
+ bool secondary;
int ret, irq;
u32 val;

@@ -1579,6 +1896,7 @@ static int cdns_i3c_master_probe(struct platform_device *pdev)
spin_lock_init(&master->xferqueue.lock);
INIT_LIST_HEAD(&master->xferqueue.list);

+ INIT_WORK(&master->mr_yield_work, cdns_i3c_master_yield);
INIT_WORK(&master->hj_work, cdns_i3c_master_hj);
writel(0xffffffff, master->regs + MST_IDR);
writel(0xffffffff, master->regs + SLV_IDR);
@@ -1590,6 +1908,7 @@ static int cdns_i3c_master_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, master);

val = readl(master->regs + CONF_STATUS0);
+ secondary = (val & CONF_STATUS0_SEC_MASTER) ? true : false;

/* Device ID0 is reserved to describe this master. */
master->maxdevs = CONF_STATUS0_DEVS_NUM(val);
@@ -1610,12 +1929,24 @@ static int cdns_i3c_master_probe(struct platform_device *pdev)
if (!master->ibi.slots)
goto err_disable_sysclk;

+ if (secondary)
+ INIT_WORK(&master->defslvs_work, cdns_i3c_sec_master_defslvs);
+
writel(IBIR_THR(1), master->regs + CMD_IBI_THR_CTRL);
- writel(MST_INT_IBIR_THR, master->regs + MST_IER);
+ writel(MST_INT_IBIR_THR | MST_INT_MR_DONE, master->regs + MST_IER);
writel(DEVS_CTRL_DEV_CLR_ALL, master->regs + DEVS_CTRL);

- ret = i3c_master_register(&master->base, &pdev->dev,
- &cdns_i3c_master_ops);
+ if (secondary) {
+ ret = i3c_secondary_master_register(&master->base, &pdev->dev,
+ &cdns_i3c_master_ops);
+ if (!ret)
+ writel(SLV_INT_DEFSLVS | SLV_INT_MR_DONE,
+ master->regs + SLV_IER);
+ } else {
+ ret = i3c_master_register(&master->base, &pdev->dev,
+ &cdns_i3c_master_ops);
+ }
+
if (ret)
goto err_disable_sysclk;

--
2.17.1