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

Signed-off-by: Parshuram Thombare <pthom...@cadence.com>
---
 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

Reply via email to