[PATCH v1 3/3] i3c: master: add mastership handover support to cdns i3c master driver

From: Parshuram Thombare
Date: Mon Apr 06 2020 - 18:52:40 EST


This patch adds mastership handover support to the Cadence
I3C controller driver.

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

diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c
index c2d1631a9e38..672391d845bb 100644
--- a/drivers/i3c/master/i3c-master-cdns.c
+++ b/drivers/i3c/master/i3c-master-cdns.c
@@ -391,6 +391,10 @@ struct cdns_i3c_xfer {
struct cdns_i3c_master {
struct work_struct hj_work;
struct i3c_master_controller base;
+ struct {
+ struct work_struct work;
+ u32 ibir;
+ } events;
u32 free_rr_slots;
unsigned int maxdevs;
struct {
@@ -936,6 +940,27 @@ static int cdns_i3c_master_get_rr_slot(struct cdns_i3c_master *master,
return -EINVAL;
}

+static int cdns_i3c_master_find_rr_slot(struct cdns_i3c_master *master,
+ u8 addr)
+{
+ u32 activedevs, rr;
+ int i;
+
+ activedevs = readl(master->regs + DEVS_CTRL) &
+ DEVS_CTRL_DEVS_ACTIVE_MASK;
+
+ for (i = 1; i <= master->maxdevs; i++) {
+ if (!(BIT(i) & activedevs))
+ continue;
+
+ rr = readl(master->regs + DEV_ID_RR0(i));
+ if (DEV_ID_RR0_GET_DEV_ADDR(rr) == addr)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
static int cdns_i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev,
u8 old_dyn_addr)
{
@@ -955,7 +980,11 @@ static int cdns_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev)
if (!data)
return -ENOMEM;

- slot = cdns_i3c_master_get_rr_slot(master, dev->info.dyn_addr);
+ if (m->secondary)
+ slot = cdns_i3c_master_find_rr_slot(master, dev->info.dyn_addr);
+ else
+ slot = cdns_i3c_master_get_rr_slot(master, dev->info.dyn_addr);
+
if (slot < 0) {
kfree(data);
return slot;
@@ -998,7 +1027,12 @@ static int cdns_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev)
struct cdns_i3c_i2c_dev_data *data;
int slot;

- slot = cdns_i3c_master_get_rr_slot(master, 0);
+ if (m->secondary)
+ slot = cdns_i3c_master_find_rr_slot(master,
+ dev->boardinfo->base.addr);
+ else
+ slot = cdns_i3c_master_get_rr_slot(master, 0);
+
if (slot < 0)
return slot;

@@ -1010,14 +1044,17 @@ static int cdns_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev)
master->free_rr_slots &= ~BIT(slot);
i2c_dev_set_master_data(dev, data);

- writel(prepare_rr0_dev_address(dev->boardinfo->base.addr) |
- (dev->boardinfo->base.flags & I2C_CLIENT_TEN ?
- DEV_ID_RR0_LVR_EXT_ADDR : 0),
- master->regs + DEV_ID_RR0(data->id));
- writel(dev->boardinfo->lvr, master->regs + DEV_ID_RR2(data->id));
- writel(readl(master->regs + DEVS_CTRL) |
- DEVS_CTRL_DEV_ACTIVE(data->id),
- master->regs + DEVS_CTRL);
+ if (!m->secondary) {
+ writel(prepare_rr0_dev_address(dev->boardinfo->base.addr) |
+ (dev->boardinfo->base.flags & I2C_CLIENT_TEN ?
+ DEV_ID_RR0_LVR_EXT_ADDR : 0),
+ master->regs + DEV_ID_RR0(data->id));
+ writel(dev->boardinfo->lvr,
+ master->regs + DEV_ID_RR2(data->id));
+ writel(readl(master->regs + DEVS_CTRL) |
+ DEVS_CTRL_DEV_ACTIVE(data->id),
+ master->regs + DEVS_CTRL);
+ }

return 0;
}
@@ -1187,10 +1224,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;
}

@@ -1356,6 +1389,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);
@@ -1377,27 +1411,102 @@ 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)];
+ i3c_master_yield_bus(&master->base,
+ dev->info.dyn_addr);
+ }
+ break;
+
default:
break;
}
}
}

+static void cdns_i3c_process_defslvs(struct cdns_i3c_master *master)
+{
+ enum i3c_bus_mode mode = I3C_BUS_MODE_PURE;
+ struct i3c_ccc_dev_desc *desc;
+ u32 devs, ndevs, val, rr, slot;
+
+ desc = master->base.defslvs_data.devs;
+ ndevs = readl(master->regs + CONF_STATUS0);
+ ndevs = CONF_STATUS0_DEVS_NUM(ndevs);
+ master->base.defslvs_data.ndevs = ndevs;
+ devs = readl(master->regs + DEVS_CTRL) & DEVS_CTRL_DEVS_ACTIVE_MASK;
+ for (slot = 1; slot < I3C_BUS_MAX_DEVS; slot++) {
+ if (!(devs & BIT(slot)))
+ continue;
+
+ memset(desc, 0, sizeof(struct i3c_ccc_dev_desc));
+ val = readl(master->regs + DEV_ID_RR0(slot));
+ if (val & DEV_ID_RR0_IS_I3C) {
+ 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;
+ } else {
+ rr = readl(master->regs + DEV_ID_RR0(slot));
+ desc->static_addr = DEV_ID_RR0_GET_DEV_ADDR(rr);
+ rr = readl(master->regs + DEV_ID_RR2(slot));
+ desc->lvr = rr;
+ switch (desc->lvr & I3C_LVR_I2C_INDEX_MASK) {
+ case I3C_LVR_I2C_INDEX(0):
+ if (mode < I3C_BUS_MODE_MIXED_FAST)
+ mode = I3C_BUS_MODE_MIXED_FAST;
+ break;
+ case I3C_LVR_I2C_INDEX(1):
+ case I3C_LVR_I2C_INDEX(2):
+ if (mode < I3C_BUS_MODE_MIXED_SLOW)
+ mode = I3C_BUS_MODE_MIXED_SLOW;
+ break;
+ default:
+ break;
+ }
+ }
+ desc++;
+ }
+ master->base.defslvs_data.bus_mode = mode;
+}
+
+void cdns_i3c_handle_slv_events(struct cdns_i3c_master *master, u32 events)
+{
+ u32 status1;
+
+ status1 = readl(master->regs + SLV_STATUS1);
+
+ if (events & SLV_INT_EVENT_UP && status1 & SLV_STATUS1_MR_DIS)
+ i3c_sec_mst_mr_dis_event(&master->base);
+}
+
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 + SLV_ISR) &
+ readl(master->regs + SLV_IMR));
+ if (!status)
+ return IRQ_NONE;
+ cdns_i3c_handle_slv_events(master, status);
+
+ writel(status, master->regs + SLV_ICR);
+ } else {
+ 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);
+ 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_IBIR_THR)
+ cnds_i3c_master_demux_ibis(master);
+ }

return IRQ_HANDLED;
}
@@ -1521,6 +1630,166 @@ 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 + 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_disable_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 sirmap;
+
+ 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 (i3cdev->ibi && i3cdev->ibi->handler)
+ 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);
+ sirmap |= SIR_MAP_DEV_CONF(data->ibi,
+ SIR_MAP_DEV_DA(I3C_BROADCAST_ADDR));
+ writel(sirmap, master->regs + SIR_MAP_DEV_REG(data->ibi));
+ spin_unlock_irqrestore(&master->ibi.lock, flags);
+
+ cdns_i3c_master_free_ibi(i3cdev);
+ }
+}
+
+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;
+ u32 status;
+
+ switch (event) {
+ case I3C_SLV_DA_UPDATE:
+ if (readl(master->regs + SLV_STATUS1) & SLV_STATUS1_HAS_DA)
+ ret = true;
+ break;
+
+ case I3C_SLV_DEFSLVS_CCC:
+ status = readl(master->regs + CONF_STATUS0);
+ if (CONF_STATUS0_DEVS_NUM(status) > 1) {
+ cdns_i3c_process_defslvs(master);
+ ret = true;
+ }
+ break;
+
+ case I3C_SLV_MR_DIS:
+ if (readl(master->regs + SLV_STATUS1) & SLV_STATUS1_MR_DIS)
+ 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;
+}
+
static const struct i3c_master_controller_ops cdns_i3c_master_ops = {
.bus_init = cdns_i3c_master_bus_init,
.master_set_info = cdns_i3c_master_set_info,
@@ -1541,6 +1810,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,
+ .disable_mr_events = cdns_i3c_master_disable_mastership_events,
+ .check_event_set = cdns_i3c_master_check_event_set,
};

static void cdns_i3c_master_hj(struct work_struct *work)
@@ -1556,6 +1829,7 @@ 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;

@@ -1607,6 +1881,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);
@@ -1627,12 +1902,13 @@ static int cdns_i3c_master_probe(struct platform_device *pdev)
if (!master->ibi.slots)
goto err_disable_sysclk;

+ writel(SLV_INT_EVENT_UP, master->regs + SLV_IER);
writel(IBIR_THR(1), master->regs + CMD_IBI_THR_CTRL);
writel(MST_INT_IBIR_THR, 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, false);
+ &cdns_i3c_master_ops, secondary);
if (ret)
goto err_disable_sysclk;

--
2.17.1