@@ -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,6 +391,10 @@ struct cdns_i3c_xfer {
struct cdns_i3c_master {
struct work_struct hj_work;
+ struct {
+ struct work_struct handover_work;
+ u32 ibir;
+ } mastership;
struct i3c_master_controller base;
u32 free_rr_slots;
unsigned int maxdevs;
@@ -664,6 +669,89 @@ static void cdns_i3c_master_unqueue_xfer(struct cdns_i3c_master *master,
spin_unlock_irqrestore(&master->xferqueue.lock, flags);
}
+static void
+cdns_i3c_master_i3c_dev_rr_to_info(struct cdns_i3c_master *master,
+ unsigned int slot,
+ struct i3c_device_info *info)
+{
+ u32 rr;
+
+ memset(info, 0, sizeof(*info));
+ rr = readl(master->regs + DEV_ID_RR0(slot));
+ info->dyn_addr = DEV_ID_RR0_GET_DEV_ADDR(rr);
+ rr = readl(master->regs + DEV_ID_RR2(slot));
+ info->dcr = rr;
+ info->bcr = rr >> 8;
+ info->pid = rr >> 16;
+ info->pid |= (u64)readl(master->regs + DEV_ID_RR1(slot)) << 16;
+}
+
+static void
+cdns_i3c_master_i2c_dev_rr_to_boardinfo(struct cdns_i3c_master *master,
+ unsigned int slot,
+ struct i2c_dev_boardinfo *info)
+{
+ u32 rr;
+
+ memset(info, 0, sizeof(*info));
+ rr = readl(master->regs + DEV_ID_RR0(slot));
+ info->base.addr = DEV_ID_RR0_GET_DEV_ADDR(rr);
+ rr = readl(master->regs + DEV_ID_RR2(slot));
+ info->lvr = rr;
+}
+
+static
+int cdns_i3c_sec_master_request_mastership(struct i3c_master_controller *m)
+{
+ struct cdns_i3c_master *master = to_cdns_i3c_master(m);
+ u32 status;
+ int ret;
+
+ status = readl(master->regs + MST_STATUS0);
+ if (status & MST_STATUS0_MASTER_MODE)
+ return -EEXIST;
+
+ status = readl(master->regs + SLV_STATUS1);
+ if (status & SLV_STATUS1_MR_DIS)
+ return -EBUSY;
+
+ writel(readl(master->regs + CTRL) | CTRL_MST_INIT | CTRL_MST_ACK,
+ master->regs + CTRL);
+
+ writel(SLV_INT_MR_DONE, master->regs + SLV_IER);
+
+ ret = readl_poll_timeout(master->regs + MST_STATUS0, status,
+ status & MST_STATUS0_MASTER_MODE, 100,
+ 1000000);
+
+ return ret;
+}
+
+static void cdns_i3c_master_update_devs(struct i3c_master_controller *m)
+{
+ struct cdns_i3c_master *master = to_cdns_i3c_master(m);
+ u32 val, newdevs;
+ int slot;
+ struct i3c_device_info i3c_info;
+ struct i2c_dev_boardinfo i2c_info;
+
+ newdevs = readl(master->regs + DEVS_CTRL) & DEVS_CTRL_DEVS_ACTIVE_MASK;
+ for (slot = 1; slot <= master->maxdevs; slot++) {
+ val = readl(master->regs + DEV_ID_RR0(slot));
+
+ if ((newdevs & BIT(slot)) && (val & DEV_ID_RR0_IS_I3C)) {
+ cdns_i3c_master_i3c_dev_rr_to_info(master, slot,
+ &i3c_info);
+ i3c_master_add_i3c_dev_locked(m, i3c_info.dyn_addr);
+ } else if ((newdevs & BIT(slot)) &&
+ !(val & DEV_ID_RR0_IS_I3C)) {
+ cdns_i3c_master_i2c_dev_rr_to_boardinfo(master, slot,
+ &i2c_info);
+ i3c_master_add_i2c_dev(m, &i2c_info);
+ }
+ }
+}
+
static enum i3c_error_code cdns_i3c_cmd_get_err(struct cdns_i3c_cmd *cmd)
{
switch (cmd->error) {
@@ -945,6 +1033,8 @@ static int cdns_i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev,
return 0;
}
+static int cdns_i3c_master_enable_mastership(struct i3c_dev_desc *dev);
+
static int cdns_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev)
{
struct i3c_master_controller *m = i3c_dev_get_master(dev);
@@ -1045,22 +1135,6 @@ static void cdns_i3c_master_bus_cleanup(struct i3c_master_controller *m)
cdns_i3c_master_disable(master);
}
-static void cdns_i3c_master_dev_rr_to_info(struct cdns_i3c_master *master,
- unsigned int slot,
- struct i3c_device_info *info)
-{
- u32 rr;
-
- memset(info, 0, sizeof(*info));
- rr = readl(master->regs + DEV_ID_RR0(slot));
- info->dyn_addr = DEV_ID_RR0_GET_DEV_ADDR(rr);
- rr = readl(master->regs + DEV_ID_RR2(slot));
- info->dcr = rr;
- info->bcr = rr >> 8;
- info->pid = rr >> 16;
- info->pid |= (u64)readl(master->regs + DEV_ID_RR1(slot)) << 16;
-}
-
static void cdns_i3c_master_upd_i3c_scl_lim(struct cdns_i3c_master *master)
{
struct i3c_master_controller *m = &master->base;
@@ -1259,15 +1333,17 @@ static int cdns_i3c_master_bus_init(struct i3c_master_controller *m)
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;
+ if (!m->secondary) {
+ /* 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));
+ 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);
+ cdns_i3c_master_i3c_dev_rr_to_info(master, 0, &info);
if (info.bcr & I3C_BCR_HDR_CAP)
info.hdr_cap = I3C_CCC_HDR_MODE(I3C_HDR_DDR);
@@ -1289,6 +1365,23 @@ static int cdns_i3c_master_bus_init(struct i3c_master_controller *m)
return 0;
}
+static
+struct i3c_dev_desc *cdns_i3c_get_ibi_device(struct cdns_i3c_master *master,
+ u32 ibir)
+{
+ struct i3c_dev_desc *dev;
+ u32 id = IBIR_SLVID(ibir);
+
+ if (id >= master->ibi.num_slots || (ibir & IBIR_ERROR))
+ return NULL;
+
+ dev = master->ibi.slots[id];
+ if (!dev)
+ return NULL;
+
+ return dev;
+}
+
static void cdns_i3c_master_handle_ibi(struct cdns_i3c_master *master,
u32 ibir)
{
@@ -1366,27 +1459,79 @@ 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));
+ master->mastership.ibir = ibir;
+ queue_work(master->base.wq,
+ &master->mastership.handover_work);
+ break;
default:
break;
}
}
}
+static void cdns_i3c_master_bus_handoff(struct cdns_i3c_master *master)
+{
+ struct i3c_dev_desc *dev;
+
+ dev = cdns_i3c_get_ibi_device(master, master->mastership.ibir);
+
+ writel(MST_INT_MR_DONE, master->regs + MST_ICR);
+ i3c_master_switch_operation_mode(&master->base, dev, I3C_SLAVE_MODE);
+}
+
+static void cdns_i3c_sec_master_takeover(struct cdns_i3c_master *master)
+{
+ writel(SLV_INT_MR_DONE, master->regs + SLV_ICR);
+ i3c_master_switch_operation_mode(&master->base,
+ master->base.this, I3C_MASTER_MODE);
+}
+
+static void cdns_i3c_sec_master_event_up(struct cdns_i3c_master *master)
+{
+ u32 status;
+ u8 evts = 0;
+
+ writel(SLV_INT_EVENT_UP, master->regs + SLV_ICR);
+ status = readl(master->regs + SLV_STATUS1);
+ if (!(status & SLV_STATUS1_MR_DIS))
+ evts |= I3C_CCC_EVENT_MR;
+ if (!(status & SLV_STATUS1_HJ_DIS))
+ evts |= I3C_CCC_EVENT_HJ;
+
+ i3c_secondary_master_events_enabled(&master->base, evts);
+}
+
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.op_mode == I3C_SLAVE_MODE) {
+ status = readl(master->regs + SLV_ISR);
- spin_lock(&master->xferqueue.lock);
- cdns_i3c_master_end_xfer_locked(master, status);
- spin_unlock(&master->xferqueue.lock);
+ if (!(status & readl(master->regs + SLV_IMR)))
+ return IRQ_NONE;
- if (status & MST_INT_IBIR_THR)
- cnds_i3c_master_demux_ibis(master);
+ if (status & SLV_INT_MR_DONE)
+ cdns_i3c_sec_master_takeover(master);
+
+ if (status & SLV_INT_EVENT_UP)
+ cdns_i3c_sec_master_event_up(master);
+ } 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);
+
+ if (status & MST_INT_IBIR_THR)
+ cnds_i3c_master_demux_ibis(master);
+
+ if (status & MST_INT_MR_DONE)
+ cdns_i3c_master_bus_handoff(master);
+ }
return IRQ_HANDLED;
}
@@ -1455,30 +1600,46 @@ static int cdns_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
return ret;
}
-static int cdns_i3c_master_request_ibi(struct i3c_dev_desc *dev,
- const struct i3c_ibi_setup *req)
+static int cdns_i3c_master_find_ibi_slot(struct cdns_i3c_master *master,
+ struct i3c_dev_desc *dev,
+ s16 *slot)
{
- struct i3c_master_controller *m = i3c_dev_get_master(dev);
- struct cdns_i3c_master *master = to_cdns_i3c_master(m);
- struct cdns_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
unsigned long flags;
unsigned int i;
-
- data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req);
- if (IS_ERR(data->ibi_pool))
- return PTR_ERR(data->ibi_pool);
+ int ret = -ENOENT;
spin_lock_irqsave(&master->ibi.lock, flags);
for (i = 0; i < master->ibi.num_slots; i++) {
- if (!master->ibi.slots[i]) {
- data->ibi = i;
+ /*
+ * We only need 'SIR' slots to describe IBI-capable devices.
+ * This slot may be used by mastership request interrupt,
+ * We can ruse the same 'SIR' map entry.
+ */
+ if (!master->ibi.slots[i] ||
+ master->ibi.slots[i] == dev) {
master->ibi.slots[i] = dev;
+ *slot = i;
+ ret = 0;
break;
}
}
spin_unlock_irqrestore(&master->ibi.lock, flags);
- if (i < master->ibi.num_slots)
+ return ret;
+}
+
+static int cdns_i3c_master_request_ibi(struct i3c_dev_desc *dev,
+ const struct i3c_ibi_setup *req)
+{
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct cdns_i3c_master *master = to_cdns_i3c_master(m);
+ struct cdns_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
+
+ data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req);
+ if (IS_ERR(data->ibi_pool))
+ return PTR_ERR(data->ibi_pool);
+
+ if (cdns_i3c_master_find_ibi_slot(master, dev, &data->ibi) == 0)
return 0;
i3c_generic_ibi_free_pool(data->ibi_pool);
@@ -1487,6 +1648,37 @@ static int cdns_i3c_master_request_ibi(struct i3c_dev_desc *dev,
return -ENOSPC;
}
+static int cdns_i3c_master_enable_mastership(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct cdns_i3c_master *master = to_cdns_i3c_master(m);
+ struct cdns_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
+ unsigned long flags;
+ u32 sircfg, sirmap;
+ int ret;
+
+ ret = cdns_i3c_master_find_ibi_slot(master, dev, &data->ibi);
+ if (ret)
+ return -ENOSPC;
+
+ 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(dev->info.bcr >> 6) |
+ SIR_MAP_DEV_DA(dev->info.dyn_addr) |
+ SIR_MAP_DEV_PL(dev->info.max_ibi_len) |
+ SIR_MAP_DEV_ACK;
+
+ if (dev->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);
+
+ return 0;
+}
+
static void cdns_i3c_master_free_ibi(struct i3c_dev_desc *dev)
{
struct i3c_master_controller *m = i3c_dev_get_master(dev);
@@ -1529,6 +1721,9 @@ 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_sec_master_request_mastership,
+ .update_devs = cdns_i3c_master_update_devs,
+ .enable_mastership = cdns_i3c_master_enable_mastership
};
static void cdns_i3c_master_hj(struct work_struct *work)
@@ -1537,13 +1732,31 @@ static void cdns_i3c_master_hj(struct work_struct *work)
struct cdns_i3c_master,
hj_work);
- i3c_master_do_daa(&master->base);
+ if (!master->base.secondary)
+ i3c_master_do_daa(&master->base);
+}
+
+static void cdns_i3c_master_mastership_handover(struct work_struct *work)
+{
+ struct cdns_i3c_master *master = container_of(work,
+ struct cdns_i3c_master,
+ mastership.handover_work);
+ struct i3c_dev_desc *dev;
+ u32 ibir = master->mastership.ibir;
+
+ dev = cdns_i3c_get_ibi_device(master, ibir);
+ if (!dev)
+ return;
+
+ if (i3c_master_mastership_ack(&master->base, &dev->info))
+ dev_err(&master->base.dev, "Mastership handover failed\n");
}
static int cdns_i3c_master_probe(struct platform_device *pdev)
{
struct cdns_i3c_master *master;
struct resource *res;
+ bool secondary = false;
int ret, irq;
u32 val;
@@ -1585,15 +1798,22 @@ static int cdns_i3c_master_probe(struct platform_device *pdev)
INIT_LIST_HEAD(&master->xferqueue.list);
INIT_WORK(&master->hj_work, cdns_i3c_master_hj);
+ INIT_WORK(&master->mastership.handover_work,
+ cdns_i3c_master_mastership_handover);
writel(0xffffffff, master->regs + MST_IDR);
writel(0xffffffff, master->regs + SLV_IDR);
ret = devm_request_irq(&pdev->dev, irq, cdns_i3c_master_interrupt, 0,
dev_name(&pdev->dev), master);
+
if (ret)
goto err_disable_sysclk;
platform_set_drvdata(pdev, master);
+ val = readl(master->regs + MST_STATUS0);
+ if (!(val & MST_STATUS0_MASTER_MODE))
+ secondary = true;
+
val = readl(master->regs + CONF_STATUS0);
/* Device ID0 is reserved to describe this master. */
@@ -1609,6 +1829,7 @@ static int cdns_i3c_master_probe(struct platform_device *pdev)
spin_lock_init(&master->ibi.lock);
master->ibi.num_slots = CONF_STATUS1_IBI_HW_RES(val);
+
master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots,
sizeof(*master->ibi.slots),
GFP_KERNEL);
@@ -1620,10 +1841,14 @@ static int cdns_i3c_master_probe(struct platform_device *pdev)
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;
+ if (secondary)
+ writel(SLV_INT_EVENT_UP,
+ master->regs + SLV_IER);
+
return 0;
err_disable_sysclk:
@@ -1666,6 +1891,7 @@ static struct platform_driver cdns_i3c_master = {
module_platform_driver(cdns_i3c_master);
MODULE_AUTHOR("Boris Brezillon <boris.brezillon@bootlin.com>");
+MODULE_AUTHOR("Przemyslaw Gaj <pgaj@cadence.com>");
MODULE_DESCRIPTION("Cadence I3C master driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:cdns-i3c-master");
This patch adds support for mastership request to Cadence I3C master driver. Signed-off-by: Przemyslaw Gaj <pgaj@cadence.com> --- drivers/i3c/master/i3c-master-cdns.c | 316 ++++++++++++++++++++++++++++++----- 1 file changed, 271 insertions(+), 45 deletions(-)