[PATCH v6 8/8] i3c: master: add mastership handover support to cdns i3c master driver

From: Parshuram Thombare
Date: Fri Apr 17 2020 - 12:24:46 EST


This patch add secondary master support to
Cadence's I3C master controller driver.

Signed-off-by: Parshuram Thombare <pthombar@xxxxxxxxxxx>
---
drivers/i3c/master.c | 32 ++-
drivers/i3c/master/i3c-master-cdns.c | 365 +++++++++++++++++++++++++--
2 files changed, 362 insertions(+), 35 deletions(-)

diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index c0b6a0c658f0..c716c3461f7e 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -481,7 +481,7 @@ static int i3c_master_enable_mr_events(struct i3c_master_controller *master)
}

/* This function is expected to be called with normaluse_lock */
-int i3c_master_acquire_bus(struct i3c_master_controller *master)
+static int i3c_master_acquire_bus(struct i3c_master_controller *master)
{
int ret = 0;

@@ -512,7 +512,6 @@ int i3c_master_acquire_bus(struct i3c_master_controller *master)

return ret;
}
-EXPORT_SYMBOL_GPL(i3c_master_acquire_bus);

static ssize_t mode_show(struct device *dev,
struct device_attribute *da,
@@ -2526,12 +2525,19 @@ static int i3c_master_i2c_adapter_xfer(struct i2c_adapter *adap,
}

i3c_bus_normaluse_lock(&master->bus);
- dev = i3c_master_find_i2c_dev_by_addr(master, addr);
- if (!dev)
- ret = -ENOENT;
- else
- ret = master->ops->i2c_xfers(dev, xfers, nxfers);
- i3c_bus_normaluse_unlock(&master->bus);
+ if (master->ops->check_event_set(master, I3C_SLV_DEFSLVS_CCC) &&
+ !i3c_master_acquire_bus(master)) {
+ dev = i3c_master_find_i2c_dev_by_addr(master, addr);
+ if (!dev)
+ ret = -ENOENT;
+ else
+ ret = master->ops->i2c_xfers(dev, xfers, nxfers);
+ i3c_bus_normaluse_unlock(&master->bus);
+ i3c_master_enable_mr_events(master);
+ } else {
+ i3c_bus_normaluse_unlock(&master->bus);
+ ret = -EAGAIN;
+ }

return ret ? ret : nxfers;
}
@@ -3065,6 +3071,7 @@ int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev,
int nxfers)
{
struct i3c_master_controller *master;
+ int ret;

if (!dev)
return -ENOENT;
@@ -3076,7 +3083,14 @@ int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev,
if (!master->ops->priv_xfers)
return -ENOTSUPP;

- return master->ops->priv_xfers(dev, xfers, nxfers);
+ if (master->ops->check_event_set(master, I3C_SLV_DEFSLVS_CCC) &&
+ !i3c_master_acquire_bus(master)) {
+ ret = master->ops->priv_xfers(dev, xfers, nxfers);
+ i3c_master_enable_mr_events(master);
+ return ret;
+ } else {
+ return -EAGAIN;
+ }
}

int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev)
diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c
index c2d1631a9e38..1bc27f0de8ba 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)
@@ -390,7 +391,14 @@ struct cdns_i3c_xfer {

struct cdns_i3c_master {
struct work_struct hj_work;
+ struct work_struct defslvs_work;
+ bool defslvs_processed;
+ bool mr_done;
struct i3c_master_controller base;
+ struct {
+ struct work_struct work;
+ u32 ibir;
+ } events;
u32 free_rr_slots;
unsigned int maxdevs;
struct {
@@ -936,6 +944,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 +984,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 +1031,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 +1048,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 +1228,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;
}

@@ -1287,8 +1324,8 @@ static int cdns_i3c_master_set_info(struct i3c_master_controller *m)
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);

@@ -1296,7 +1333,6 @@ static int cdns_i3c_master_set_info(struct i3c_master_controller *m)
if (ret)
return ret;

-
return 0;
}

@@ -1356,6 +1392,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 +1414,120 @@ 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, val, rr, slot;
+
+ desc = master->base.defslvs_data.devs;
+ master->base.defslvs_data.ndevs = 0;
+ 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;
+
+ master->base.defslvs_data.ndevs++;
+ 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);
+
+ if (events & SLV_INT_MR_DONE) {
+ writel(FLUSH_RX_FIFO | FLUSH_TX_FIFO | FLUSH_CMD_FIFO |
+ FLUSH_CMD_RESP,
+ master->regs + FLUSH_CTRL);
+ master->mr_done = true;
+ }
+
+ if (events & SLV_INT_DEFSLVS) {
+ master->defslvs_processed = false;
+ queue_work(master->base.wq, &master->defslvs_work);
+ }
+}
+
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;
+ writel(status, master->regs + SLV_ICR);
+
+ cdns_i3c_handle_slv_events(master, status);
+
+ } 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);
+ if (status & MST_INT_MR_DONE) {
+ writel(MST_INT_MR_DONE, master->regs + MST_ICR);
+ writel(FLUSH_RX_FIFO | FLUSH_TX_FIFO | FLUSH_CMD_FIFO |
+ FLUSH_CMD_RESP, master->regs + FLUSH_CTRL);
+ }
+ }

return IRQ_HANDLED;
}
@@ -1521,6 +1651,161 @@ 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;
+
+ master->mr_done = false;
+ 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;
+
+ 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:
+ ret = master->defslvs_processed;
+ break;
+
+ case I3C_SLV_MR_DIS:
+ if (readl(master->regs + SLV_STATUS1) & SLV_STATUS1_MR_DIS)
+ ret = true;
+ break;
+
+ case I3C_SLV_MR_DONE:
+ ret = master->mr_done;
+ 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 +1826,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)
@@ -1552,10 +1841,24 @@ static void cdns_i3c_master_hj(struct work_struct *work)
i3c_master_do_daa(&master->base);
}

+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);
+
+ cdns_i3c_process_defslvs(master);
+ if (i3c_master_process_defslvs(&master->base))
+ queue_work(master->base.wq, work);
+ else
+ master->defslvs_processed = true;
+}
+
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 +1910,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 +1931,21 @@ 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);
+ master->defslvs_processed = false;
+ } else {
+ master->defslvs_processed = true;
+ }
+
+ writel(SLV_INT_EVENT_UP | SLV_INT_DEFSLVS | SLV_INT_MR_DONE,
+ master->regs + SLV_IER);
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, false);
+ &cdns_i3c_master_ops, secondary);
if (ret)
goto err_disable_sysclk;

--
2.17.1