diff mbox series

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

Message ID 1587140662-32408-1-git-send-email-pthombar@cadence.com (mailing list archive)
State Superseded
Headers show
Series I3C mastership handover support | expand

Commit Message

Parshuram Raju Thombare April 17, 2020, 4:24 p.m. UTC
This patch add secondary master support to
Cadence's I3C master controller driver.

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

Patch

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;