Linux-i3c Archive on lore.kernel.org
 help / color / Atom feed
* [PATCH v1 0/3] I3C mastership handover support
@ 2020-04-06 22:20 Parshuram Thombare
  2020-04-06 22:21 ` [PATCH v1 1/3] i3c: master: split bus_init callback into bus_init and master_set_info Parshuram Thombare
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Parshuram Thombare @ 2020-04-06 22:20 UTC (permalink / raw)
  To: bbrezillon, vitor.soares
  Cc: mparab, pgaj, Parshuram Thombare, linux-i3c, linux-kernel

Hi,

This patch series is to enable mastership handover feature in I3C
master subsystem and Cadence's I3C controller driver.


[PATCH 1/3]
i3c: master: split bus_init callback into bus_init and master_set_info
There are 2 reasons for this split
Currently bus_init callback is involved in
a. Controller specific settings such as clock prescalar, enabling
   different functionalities and finally enabling the controller.
b. Allocating address to the main master and calling 
   i3c_master_set_info, which basically create a I3C device for master
   and add it to system. This is fine for main master, but for
   secondary master this should be deferred until controller actually
   owns the bus/mastership.
   
Another reason is, to support secondary master initialization without
board info, controller specific bus mode setting need to be done twice
First with pure bus mode and second time when actual bus mode is known
based on data received through DEFSLVS, whereas master set info is
supposed to be done only once.
	      
[PATCH 2/3]
i3c: add mastership handover support to i3c master subsystem
This patch add mastership handover support as per MIPI I3C
spec v1.0. Main master and secondary masters starts in pure
bus mode to allow enumeration (DAA) to happen in same bus mode.
Secondary masters are not required to have information about
other devices connected on the bus through board info, this
information is derived from DEFSLVS received from main
master. While acquiring mastership, requesting master always
make sure that it has active dynamic address, and received
DEFSLVS at least once. Mastership request state machine
make sure that any pending DEFSLVS is processed, so that
when device become master it always have correct view
of the bus.

[PATCH 3/3]
i3c: master: add mastership handover support to cdns i3c master driver
This patch adds mastership handover support to the Cadence
I3C controller driver. Basically, this include necessary
callbacks for mastership request.

Regards,
Parshuram Thombare

Parshuram Thombare (3):
  i3c: master: split bus_init callback into bus_init and master_set_info
  i3c: add mastership handover support to i3c master subsystem
  i3c: master: add mastership handover support to cdns i3c master driver

 drivers/i3c/master.c                 | 490 ++++++++++++++++++++++++---
 drivers/i3c/master/dw-i3c-master.c   |  29 +-
 drivers/i3c/master/i3c-master-cdns.c | 385 ++++++++++++++++++---
 include/linux/i3c/master.h           |  47 ++-
 4 files changed, 854 insertions(+), 97 deletions(-)

-- 
2.17.1


_______________________________________________
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH v1 1/3] i3c: master: split bus_init callback into bus_init and master_set_info
  2020-04-06 22:20 [PATCH v1 0/3] I3C mastership handover support Parshuram Thombare
@ 2020-04-06 22:21 ` Parshuram Thombare
  2020-04-06 22:21 ` [PATCH v1 2/3] i3c: add mastership handover support to i3c master subsystem Parshuram Thombare
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Parshuram Thombare @ 2020-04-06 22:21 UTC (permalink / raw)
  To: bbrezillon, vitor.soares
  Cc: mparab, pgaj, Parshuram Thombare, linux-i3c, linux-kernel

To support mastership handover procedure, this patch splits the
bus_init callback into bus_init and master_set_info callbacks

Signed-off-by: Parshuram Thombare <pthombar@cadence.com>
---
 drivers/i3c/master.c                 | 10 +++--
 drivers/i3c/master/dw-i3c-master.c   | 29 ++++++++-----
 drivers/i3c/master/i3c-master-cdns.c | 63 ++++++++++++++++++----------
 include/linux/i3c/master.h           |  7 +++-
 4 files changed, 71 insertions(+), 38 deletions(-)

diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 5f4bd52121fe..0ec332e45737 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -1716,6 +1716,10 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
 	if (ret)
 		goto err_detach_devs;
 
+	ret = master->ops->master_set_info(master);
+	if (ret)
+		goto err_detach_devs;
+
 	/*
 	 * The master device should have been instantiated in ->bus_init(),
 	 * complain if this was not the case.
@@ -2378,9 +2382,9 @@ EXPORT_SYMBOL_GPL(i3c_generic_ibi_recycle_slot);
 
 static int i3c_master_check_ops(const struct i3c_master_controller_ops *ops)
 {
-	if (!ops || !ops->bus_init || !ops->priv_xfers ||
-	    !ops->send_ccc_cmd || !ops->do_daa || !ops->i2c_xfers ||
-	    !ops->i2c_funcs)
+	if (!ops || !ops->bus_init || !ops->master_set_info ||
+	    !ops->priv_xfers || !ops->send_ccc_cmd || !ops->do_daa ||
+	    !ops->i2c_xfers || !ops->i2c_funcs)
 		return -EINVAL;
 
 	if (ops->request_ibi &&
diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
index 1d83c97431c7..5c9a72d68fb8 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -593,7 +593,6 @@ static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
 {
 	struct dw_i3c_master *master = to_dw_i3c_master(m);
 	struct i3c_bus *bus = i3c_master_get_bus(m);
-	struct i3c_device_info info = { };
 	u32 thld_ctrl;
 	int ret;
 
@@ -624,6 +623,24 @@ static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
 	writel(INTR_MASTER_MASK, master->regs + INTR_STATUS_EN);
 	writel(INTR_MASTER_MASK, master->regs + INTR_SIGNAL_EN);
 
+	writel(IBI_REQ_REJECT_ALL, master->regs + IBI_SIR_REQ_REJECT);
+	writel(IBI_REQ_REJECT_ALL, master->regs + IBI_MR_REQ_REJECT);
+
+	/* For now don't support Hot-Join */
+	writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_HOT_JOIN_NACK,
+	       master->regs + DEVICE_CTRL);
+
+	dw_i3c_master_enable(master);
+
+	return 0;
+}
+
+static int dw_i3c_master_set_info(struct i3c_master_controller *m)
+{
+	struct dw_i3c_master *master = to_dw_i3c_master(m);
+	struct i3c_device_info info = { };
+	int ret;
+
 	ret = i3c_master_get_free_addr(m, 0);
 	if (ret < 0)
 		return ret;
@@ -638,15 +655,6 @@ static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
 	if (ret)
 		return ret;
 
-	writel(IBI_REQ_REJECT_ALL, master->regs + IBI_SIR_REQ_REJECT);
-	writel(IBI_REQ_REJECT_ALL, master->regs + IBI_MR_REQ_REJECT);
-
-	/* For now don't support Hot-Join */
-	writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_HOT_JOIN_NACK,
-	       master->regs + DEVICE_CTRL);
-
-	dw_i3c_master_enable(master);
-
 	return 0;
 }
 
@@ -1088,6 +1096,7 @@ static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id)
 
 static const struct i3c_master_controller_ops dw_mipi_i3c_ops = {
 	.bus_init = dw_i3c_master_bus_init,
+	.master_set_info = dw_i3c_master_set_info,
 	.bus_cleanup = dw_i3c_master_bus_cleanup,
 	.attach_i3c_dev = dw_i3c_master_attach_i3c_dev,
 	.reattach_i3c_dev = dw_i3c_master_reattach_i3c_dev,
diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c
index 8889a4fdb454..c2d1631a9e38 100644
--- a/drivers/i3c/master/i3c-master-cdns.c
+++ b/drivers/i3c/master/i3c-master-cdns.c
@@ -1199,21 +1199,20 @@ 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;
-	struct i3c_device_info info = { };
-	int ret, ncycles;
+	u32 ctrl, prescl0, prescl1, pres, low, bus_mode;
+	int 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 +1243,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,15 +1250,43 @@ static int cdns_i3c_master_bus_init(struct i3c_master_controller *m)
 	if (ncycles < 0)
 		ncycles = 0;
 	prescl1 = PRESCL_CTRL1_OD_LOW(ncycles);
+
+	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.
+	 */
+	if (!m->secondary)
+		ctrl |= CTRL_HJ_ACK | CTRL_HJ_DISEC;
+	writel(ctrl, master->regs + CTRL);
+	cdns_i3c_master_enable(master);
 
-	/* Get an address for the master. */
-	ret = i3c_master_get_free_addr(m, 0);
-	if (ret < 0)
-		return ret;
+	return 0;
+}
 
-	writel(prepare_rr0_dev_address(ret) | DEV_ID_RR0_IS_I3C,
-	       master->regs + DEV_ID_RR0(0));
+static int cdns_i3c_master_set_info(struct i3c_master_controller *m)
+{
+	struct cdns_i3c_master *master = to_cdns_i3c_master(m);
+	struct i3c_device_info info = { };
+	int 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));
+	}
 
 	cdns_i3c_master_dev_rr_to_info(master, 0, &info);
 	if (info.bcr & I3C_BCR_HDR_CAP)
@@ -1270,16 +1296,6 @@ static int cdns_i3c_master_bus_init(struct i3c_master_controller *m)
 	if (ret)
 		return ret;
 
-	/*
-	 * 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;
-	writel(ctrl, master->regs + CTRL);
-
-	cdns_i3c_master_enable(master);
 
 	return 0;
 }
@@ -1507,6 +1523,7 @@ static void cdns_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
 
 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,
 	.bus_cleanup = cdns_i3c_master_bus_cleanup,
 	.do_daa = cdns_i3c_master_do_daa,
 	.attach_i3c_dev = cdns_i3c_master_attach_i3c_dev,
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index f13fd8b1dd79..3dc7eafe811a 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -337,10 +337,12 @@ struct i3c_bus {
 
 /**
  * struct i3c_master_controller_ops - I3C master methods
- * @bus_init: hook responsible for the I3C bus initialization. You should at
- *	      least call master_set_info() from there and set the bus mode.
+ * @bus_init: hook responsible for the I3C bus initialization.
  *	      You can also put controller specific initialization in there.
  *	      This method is mandatory.
+ * @master_set_info: hook responsible for assigning address to main master.
+ *			You should call i3c_master_set_info from here.
+ *			This method is mandatory.
  * @bus_cleanup: cleanup everything done in
  *		 &i3c_master_controller_ops->bus_init().
  *		 This method is optional.
@@ -421,6 +423,7 @@ struct i3c_bus {
  */
 struct i3c_master_controller_ops {
 	int (*bus_init)(struct i3c_master_controller *master);
+	int (*master_set_info)(struct i3c_master_controller *m);
 	void (*bus_cleanup)(struct i3c_master_controller *master);
 	int (*attach_i3c_dev)(struct i3c_dev_desc *dev);
 	int (*reattach_i3c_dev)(struct i3c_dev_desc *dev, u8 old_dyn_addr);
-- 
2.17.1


_______________________________________________
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH v1 2/3] i3c: add mastership handover support to i3c master subsystem
  2020-04-06 22:20 [PATCH v1 0/3] I3C mastership handover support Parshuram Thombare
  2020-04-06 22:21 ` [PATCH v1 1/3] i3c: master: split bus_init callback into bus_init and master_set_info Parshuram Thombare
@ 2020-04-06 22:21 ` Parshuram Thombare
  2020-04-06 22:21 ` [PATCH v1 3/3] i3c: master: add mastership handover support to cdns i3c master driver Parshuram Thombare
  2020-04-08 10:04 ` [PATCH v1 0/3] I3C mastership handover support Boris Brezillon
  3 siblings, 0 replies; 6+ messages in thread
From: Parshuram Thombare @ 2020-04-06 22:21 UTC (permalink / raw)
  To: bbrezillon, vitor.soares
  Cc: mparab, pgaj, Parshuram Thombare, linux-i3c, linux-kernel

This patch adds mastership handover support to I3C master
subsystem as per MIPI I3C v1.0 specification.

Signed-off-by: Parshuram Thombare <pthombar@cadence.com>
---
 drivers/i3c/master.c       | 486 ++++++++++++++++++++++++++++++++++---
 include/linux/i3c/master.h |  40 +++
 2 files changed, 487 insertions(+), 39 deletions(-)

diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 0ec332e45737..42b6385fc3c8 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -963,6 +963,33 @@ int i3c_master_defslvs_locked(struct i3c_master_controller *master)
 }
 EXPORT_SYMBOL_GPL(i3c_master_defslvs_locked);
 
+static int i3c_master_get_accmst_locked(struct i3c_master_controller *master,
+					u8 addr)
+{
+	struct i3c_ccc_getaccmst *accmst;
+	struct i3c_ccc_cmd_dest dest;
+	struct i3c_ccc_cmd cmd;
+	int ret;
+
+	accmst = i3c_ccc_cmd_dest_init(&dest, addr, sizeof(*accmst));
+	if (!accmst)
+		return -ENOMEM;
+
+	i3c_ccc_cmd_init(&cmd, true, I3C_CCC_GETACCMST, &dest, 1);
+
+	ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
+	if (ret)
+		goto out;
+
+	if (dest.payload.len != sizeof(*accmst))
+		ret = -EIO;
+
+out:
+	i3c_ccc_cmd_dest_cleanup(&dest);
+
+	return ret;
+}
+
 static int i3c_master_setda_locked(struct i3c_master_controller *master,
 				   u8 oldaddr, u8 newaddr, bool setdasa)
 {
@@ -1525,6 +1552,185 @@ int i3c_master_do_daa(struct i3c_master_controller *master)
 }
 EXPORT_SYMBOL_GPL(i3c_master_do_daa);
 
+static int i3c_master_getaccmst(struct i3c_master_controller *master)
+{
+	int ret;
+
+	i3c_bus_maintenance_lock(&master->bus);
+	ret = i3c_master_get_accmst_locked(master, master->mr_addr);
+	i3c_bus_maintenance_unlock(&master->bus);
+
+	return ret;
+}
+
+static int i3c_master_request_mastership(struct i3c_master_controller *master)
+{
+	int ret;
+
+	/* request_mastership callback should handle EN/DIS EC MR.*/
+	i3c_bus_maintenance_lock(&master->bus);
+	ret = master->ops->request_mastership(master);
+	i3c_bus_maintenance_unlock(&master->bus);
+
+	return ret;
+}
+
+static void i3c_mst_yield_bus(struct work_struct *work)
+{
+	struct i3c_master_controller *m;
+	struct i3c_dev_desc *i3cdev;
+	int ret;
+
+	m = container_of(work, struct i3c_master_controller, mst_work);
+	switch (m->mr_state) {
+	case I3C_MR_DISEC_MR:
+		/*
+		 * Disable MR on all but the secondary master first
+		 * reaching here.
+		 */
+		i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
+			if (I3C_BCR_DEVICE_ROLE(i3cdev->info.bcr) !=
+			    I3C_BCR_I3C_MASTER ||
+			    i3cdev->info.dyn_addr == m->mr_addr ||
+			    m->this == i3cdev)
+				continue;
+			i3c_bus_maintenance_lock(&m->bus);
+			i3c_master_disec_locked(m, i3cdev->info.dyn_addr,
+						I3C_CCC_EVENT_MR);
+			i3c_bus_maintenance_unlock(&m->bus);
+		}
+		m->mr_state = I3C_MR_GETACCMST;
+		queue_work(m->wq, &m->mst_work);
+		break;
+
+	case I3C_MR_GETACCMST:
+		ret = i3c_master_getaccmst(m);
+		if (!ret)
+			m->mr_state = I3C_MR_DONE;
+		else
+			m->mr_state = I3C_MR_FAILED;
+		queue_work(m->wq, &m->mst_work);
+		break;
+
+	case I3C_MR_DONE:
+		i3c_bus_for_each_i3cdev(&m->bus, i3cdev) {
+			if (m->mr_addr == i3cdev->info.dyn_addr) {
+				m->bus.cur_master = i3cdev;
+				break;
+			}
+		}
+		m->mr_state = I3C_MR_IDLE;
+		break;
+
+	case I3C_MR_FAILED:
+		m->ops->enable_mr_events(m);
+		i3c_bus_maintenance_lock(&m->bus);
+		i3c_master_enec_locked(m, I3C_BROADCAST_ADDR,
+				       I3C_CCC_EVENT_MR);
+		i3c_bus_maintenance_unlock(&m->bus);
+		m->mr_state = I3C_MR_IDLE;
+		break;
+
+	default:
+		break;
+	}
+}
+
+void
+i3c_master_yield_bus(struct i3c_master_controller *master, u8 sec_mst_dyn_addr)
+{
+	master->ops->disable_mr_events(master);
+	master->mr_addr = sec_mst_dyn_addr;
+	master->mr_state = I3C_MR_DISEC_MR;
+	queue_work(master->wq, &master->mst_work);
+}
+EXPORT_SYMBOL_GPL(i3c_master_yield_bus);
+
+static void i3c_sec_mst_acquire_bus(struct work_struct *work)
+{
+	struct i3c_master_controller *m;
+	struct i3c_bus *i3cbus;
+	int ret;
+
+	m = container_of(work, struct i3c_master_controller, sec_mst_work);
+	i3cbus = i3c_master_get_bus(m);
+	switch (m->mr_state) {
+	case I3C_MR_WAIT_DA:
+		/* Wait until this master have dynamic address */
+		if (m->ops->check_event_set(m, I3C_SLV_DA_UPDATE))
+			m->mr_state = I3C_MR_REQUEST;
+		queue_work(m->wq, &m->sec_mst_work);
+		break;
+
+	case I3C_MR_REQUEST:
+		/* Wait until we can send MR */
+		ret = i3c_master_request_mastership(m);
+		if (!ret)
+			m->mr_state = I3C_MR_WAIT_DEFSLVS;
+		queue_work(m->wq, &m->sec_mst_work);
+		break;
+
+	case I3C_MR_WAIT_DEFSLVS:
+		/*
+		 * Wait for DEFSLVS, this event check should make sure
+		 * that DEFSLVS list is read into m->defslvs_data
+		 */
+		if (m->ops->check_event_set(m, I3C_SLV_DEFSLVS_CCC))
+			m->mr_state = I3C_MR_WAIT_MR_DONE;
+		queue_work(m->wq, &m->sec_mst_work);
+		break;
+
+	case I3C_MR_WAIT_MR_DONE:
+		if (m->ops->check_event_set(m, I3C_SLV_MR_DONE)) {
+			m->mr_state = I3C_MR_DONE;
+			complete(&m->mr_comp);
+		} else {
+			queue_work(m->wq, &m->sec_mst_work);
+		}
+		break;
+
+	default:
+		m->mr_state = I3C_MR_FAILED;
+		complete(&m->mr_comp);
+		break;
+	}
+}
+
+void i3c_sec_mst_mr_dis_event(struct i3c_master_controller *m)
+{
+	if (m->mr_state != I3C_MR_IDLE)
+		m->mr_state = I3C_MR_WAIT_DA;
+}
+EXPORT_SYMBOL_GPL(i3c_sec_mst_mr_dis_event);
+
+static int i3c_master_acquire_bus(struct i3c_master_controller *master)
+{
+	int ret = 0;
+
+	if (!master->this || master->this != master->bus.cur_master) {
+		i3c_bus_normaluse_lock(&master->bus);
+		if (master->mr_state == I3C_MR_IDLE) {
+			master->mr_state = I3C_MR_WAIT_DA;
+			i3c_bus_normaluse_unlock(&master->bus);
+			init_completion(&master->mr_comp);
+			queue_work(master->wq, &master->sec_mst_work);
+			wait_for_completion(&master->mr_comp);
+			if (master->mr_state != I3C_MR_DONE)
+				ret = -EAGAIN;
+			master->mr_state = I3C_MR_IDLE;
+		} else {
+			/*
+			 * MR request is already in process for
+			 * this master
+			 */
+			ret = -EAGAIN;
+			i3c_bus_normaluse_unlock(&master->bus);
+		}
+	}
+
+	return ret;
+}
+
 /**
  * i3c_master_set_info() - set master device information
  * @master: master used to send frames on the bus
@@ -1558,10 +1764,6 @@ int i3c_master_set_info(struct i3c_master_controller *master,
 	if (!i3c_bus_dev_addr_is_avail(&master->bus, info->dyn_addr))
 		return -EINVAL;
 
-	if (I3C_BCR_DEVICE_ROLE(info->bcr) == I3C_BCR_I3C_MASTER &&
-	    master->secondary)
-		return -EINVAL;
-
 	if (master->this)
 		return -EINVAL;
 
@@ -1622,9 +1824,10 @@ static void i3c_master_detach_free_devs(struct i3c_master_controller *master)
  * 1. Attach I2C and statically defined I3C devs to the master so that the
  *    master can fill its internal device table appropriately
  *
- * 2. Call &i3c_master_controller_ops->bus_init() method to initialize
- *    the master controller. That's usually where the bus mode is selected
- *    (pure bus or mixed fast/slow bus)
+ * 2. Should have called &i3c_master_controller_ops->bus_init()
+ *    method with pure bus mode to initialize the master controller.
+ *    That's usually where the bus mode is selected (pure bus or
+ *    mixed fast/slow bus)
  *
  * 3. Instruct all devices on the bus to drop their dynamic address. This is
  *    particularly important when the bus was previously configured by someone
@@ -1708,14 +1911,6 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
 		}
 	}
 
-	/*
-	 * Now execute the controller specific ->bus_init() routine, which
-	 * might configure its internal logic to match the bus limitations.
-	 */
-	ret = master->ops->bus_init(master);
-	if (ret)
-		goto err_detach_devs;
-
 	ret = master->ops->master_set_info(master);
 	if (ret)
 		goto err_detach_devs;
@@ -1728,7 +1923,7 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
 		dev_err(&master->dev,
 			"master_set_info() was not called in ->bus_init()\n");
 		ret = -EINVAL;
-		goto err_bus_cleanup;
+		goto err_detach_devs;
 	}
 
 	/*
@@ -1737,14 +1932,14 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
 	 */
 	ret = i3c_master_rstdaa_locked(master, I3C_BROADCAST_ADDR);
 	if (ret && ret != I3C_ERROR_M2)
-		goto err_bus_cleanup;
+		goto err_detach_devs;
 
 	/* Disable all slave events before starting DAA. */
 	ret = i3c_master_disec_locked(master, I3C_BROADCAST_ADDR,
 				      I3C_CCC_EVENT_SIR | I3C_CCC_EVENT_MR |
 				      I3C_CCC_EVENT_HJ);
 	if (ret && ret != I3C_ERROR_M2)
-		goto err_bus_cleanup;
+		goto err_detach_devs;
 
 	/*
 	 * Pre-assign dynamic address and retrieve device information if
@@ -1762,10 +1957,6 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
 err_rstdaa:
 	i3c_master_rstdaa_locked(master, I3C_BROADCAST_ADDR);
 
-err_bus_cleanup:
-	if (master->ops->bus_cleanup)
-		master->ops->bus_cleanup(master);
-
 err_detach_devs:
 	i3c_master_detach_free_devs(master);
 
@@ -2249,6 +2440,9 @@ void i3c_generic_ibi_free_pool(struct i3c_generic_ibi_pool *pool)
 	struct i3c_generic_ibi_slot *slot;
 	unsigned int nslots = 0;
 
+	if (!pool)
+		return;
+
 	while (!list_empty(&pool->free_slots)) {
 		slot = list_first_entry(&pool->free_slots,
 					struct i3c_generic_ibi_slot, node);
@@ -2392,9 +2586,179 @@ static int i3c_master_check_ops(const struct i3c_master_controller_ops *ops)
 	     !ops->recycle_ibi_slot))
 		return -EINVAL;
 
+	if (ops->request_mastership &&
+	    (!ops->enable_mr_events || !ops->disable_mr_events ||
+	     !ops->check_event_set))
+		return -EINVAL;
+
 	return 0;
 }
 
+static struct i2c_dev_boardinfo *
+i3c_master_alloc_i2c_boardinfo(struct i3c_master_controller *master,
+			       u16 addr, u8 lvr)
+{
+	struct i2c_dev_boardinfo *i2cboardinfo;
+
+	i2cboardinfo = kzalloc(sizeof(*i2cboardinfo), GFP_KERNEL);
+	if (!i2cboardinfo)
+		return ERR_PTR(-ENOMEM);
+
+	i2cboardinfo->base.addr = addr;
+	i2cboardinfo->lvr = lvr;
+
+	return i2cboardinfo;
+}
+
+static int i3c_secondary_master_bus_init(struct i3c_master_controller *master)
+{
+	struct i3c_ccc_dev_desc *desc;
+	struct i3c_dev_desc *i3cdev;
+	struct i2c_dev_desc *i2cdev;
+	struct i2c_dev_boardinfo *info;
+	int ret, slot;
+
+	desc = master->defslvs_data.devs;
+	for (slot = 1; slot <= master->defslvs_data.ndevs; slot++, desc++) {
+		if (desc->static_addr) {
+			i3c_bus_set_addr_slot_status(&master->bus,
+						     desc->static_addr,
+						     I3C_ADDR_SLOT_I2C_DEV);
+			info = i3c_master_alloc_i2c_boardinfo(master,
+							      desc->static_addr,
+							      desc->lvr);
+			if (IS_ERR(info)) {
+				ret = PTR_ERR(info);
+				goto err_detach_devs;
+			}
+
+			i2cdev = i3c_master_alloc_i2c_dev(master, info);
+			if (IS_ERR(i2cdev)) {
+				ret = PTR_ERR(i2cdev);
+				goto err_detach_devs;
+			}
+
+			ret = i3c_master_attach_i2c_dev(master, i2cdev);
+			if (ret) {
+				i3c_master_free_i2c_dev(i2cdev);
+				goto err_detach_devs;
+			}
+		} else {
+			struct i3c_device_info info = {
+				.dyn_addr = desc->dyn_addr,
+			};
+
+			i3cdev = i3c_master_alloc_i3c_dev(master, &info);
+			if (IS_ERR(i3cdev)) {
+				ret = PTR_ERR(i3cdev);
+				goto err_detach_devs;
+			}
+
+			ret = i3c_master_attach_i3c_dev(master, i3cdev);
+			if (ret) {
+				i3c_master_free_i3c_dev(i3cdev);
+				goto err_detach_devs;
+			}
+		}
+	}
+
+	ret = master->ops->master_set_info(master);
+	if (ret)
+		goto err_bus_cleanup;
+
+	i3c_bus_for_each_i3cdev(&master->bus, i3cdev) {
+		if (master->this != i3cdev) {
+			ret = i3c_master_retrieve_dev_info(i3cdev);
+			if (ret)
+				goto err_bus_cleanup;
+		}
+	}
+
+	if (!master->this) {
+		dev_err(&master->dev,
+			"master_set_info() was not called in ->bus_init()\n");
+		ret = -EINVAL;
+		goto err_bus_cleanup;
+	}
+
+	return 0;
+
+err_bus_cleanup:
+	if (master->ops->bus_cleanup)
+		master->ops->bus_cleanup(master);
+
+err_detach_devs:
+	i3c_master_detach_free_devs(master);
+
+	return ret;
+}
+
+static void i3c_secondary_master_register(struct work_struct *work)
+{
+	struct i3c_master_controller *master;
+	struct i3c_bus *i3cbus;
+	int ret;
+
+	master = container_of(work, struct i3c_master_controller,
+			      sec_mst_register_work);
+	i3cbus = i3c_master_get_bus(master);
+
+	ret = i3c_master_acquire_bus(master);
+	if (ret)
+		goto err_cleanup_bus;
+
+	/* Again bus_init to bus_mode, based on data received in DEFSLVS */
+	ret = i3c_bus_set_mode(i3cbus, master->defslvs_data.bus_mode);
+	if (ret)
+		goto err_cleanup_bus;
+
+	ret = master->ops->bus_init(master);
+	if (ret)
+		goto err_cleanup_bus;
+
+	ret = i3c_secondary_master_bus_init(master);
+	if (ret)
+		goto err_cleanup_bus;
+
+	ret = device_add(&master->dev);
+	if (ret)
+		goto err_cleanup_bus;
+
+	/*
+	 * Expose our I3C bus as an I2C adapter so that I2C devices are exposed
+	 * through the I2C subsystem.
+	 */
+	ret = i3c_master_i2c_adapter_init(master);
+	if (ret)
+		goto err_del_dev;
+
+	/*
+	 * We're done initializing the bus and the controller, we can now
+	 * register I3C devices from defslvs list.
+	 */
+	master->init_done = true;
+	i3c_bus_normaluse_lock(&master->bus);
+	i3c_master_register_new_i3c_devs(master);
+	i3c_bus_normaluse_unlock(&master->bus);
+
+	master->ops->enable_mr_events(master);
+	i3c_bus_maintenance_lock(&master->bus);
+	ret = i3c_master_enec_locked(master, I3C_BROADCAST_ADDR,
+				     I3C_CCC_EVENT_MR);
+	i3c_bus_maintenance_unlock(&master->bus);
+
+	return;
+
+err_del_dev:
+	device_del(&master->dev);
+
+err_cleanup_bus:
+	if (master->ops->bus_cleanup)
+		master->ops->bus_cleanup(master);
+
+	put_device(&master->dev);
+}
+
 /**
  * i3c_master_register() - register an I3C master
  * @master: master used to send frames on the bus
@@ -2424,10 +2788,10 @@ int i3c_master_register(struct i3c_master_controller *master,
 	struct i3c_bus *i3cbus = i3c_master_get_bus(master);
 	enum i3c_bus_mode mode = I3C_BUS_MODE_PURE;
 	struct i2c_dev_boardinfo *i2cbi;
-	int ret;
+	int ret, sz;
 
-	/* We do not support secondary masters yet. */
-	if (secondary)
+	/*Check if controller driver supports secondary masters. */
+	if (secondary && !ops->request_mastership)
 		return -ENOTSUPP;
 
 	ret = i3c_master_check_ops(ops);
@@ -2451,10 +2815,45 @@ int i3c_master_register(struct i3c_master_controller *master,
 	device_initialize(&master->dev);
 	dev_set_name(&master->dev, "i3c-%d", i3cbus->id);
 
-	ret = of_populate_i3c_bus(master);
+	master->wq = alloc_workqueue("%s", 0, 0, dev_name(parent));
+	if (!master->wq) {
+		ret = -ENOMEM;
+		goto err_put_dev;
+	}
+
+	master->mr_state = I3C_MR_IDLE;
+	INIT_WORK(&master->sec_mst_work, i3c_sec_mst_acquire_bus);
+	INIT_WORK(&master->mst_work, i3c_mst_yield_bus);
+
+	ret = i3c_bus_set_mode(i3cbus, mode);
+	if (ret)
+		goto err_put_dev;
+
+	/*
+	 * Now execute the controller specific ->bus_init() routine, which
+	 * might configure its internal logic to match the bus limitations.
+	 */
+	ret = master->ops->bus_init(master);
 	if (ret)
 		goto err_put_dev;
 
+	if (secondary) {
+		sz = sizeof(struct i3c_ccc_dev_desc) * I3C_BUS_MAX_DEVS;
+		master->defslvs_data.devs = devm_kzalloc(&master->dev, sz,
+							 GFP_KERNEL);
+		if (!master->defslvs_data.devs)
+			goto err_put_dev;
+
+		INIT_WORK(&master->sec_mst_register_work,
+			  i3c_secondary_master_register);
+		queue_work(master->wq, &master->sec_mst_register_work);
+		return 0;
+	}
+
+	ret = of_populate_i3c_bus(master);
+	if (ret)
+		goto err_cleanup_bus;
+
 	list_for_each_entry(i2cbi, &master->boardinfo.i2c, node) {
 		switch (i2cbi->lvr & I3C_LVR_I2C_INDEX_MASK) {
 		case I3C_LVR_I2C_INDEX(0):
@@ -2468,23 +2867,13 @@ int i3c_master_register(struct i3c_master_controller *master,
 			break;
 		default:
 			ret = -EINVAL;
-			goto err_put_dev;
+			goto err_cleanup_bus;
 		}
 	}
 
-	ret = i3c_bus_set_mode(i3cbus, mode);
-	if (ret)
-		goto err_put_dev;
-
-	master->wq = alloc_workqueue("%s", 0, 0, dev_name(parent));
-	if (!master->wq) {
-		ret = -ENOMEM;
-		goto err_put_dev;
-	}
-
 	ret = i3c_master_bus_init(master);
 	if (ret)
-		goto err_put_dev;
+		goto err_cleanup_bus;
 
 	ret = device_add(&master->dev);
 	if (ret)
@@ -2507,6 +2896,22 @@ int i3c_master_register(struct i3c_master_controller *master,
 	i3c_master_register_new_i3c_devs(master);
 	i3c_bus_normaluse_unlock(&master->bus);
 
+	if (ops->request_mastership) {
+		master->ops->enable_mr_events(master);
+		i3c_bus_maintenance_lock(&master->bus);
+		i3c_master_enec_locked(master, I3C_BROADCAST_ADDR,
+				       I3C_CCC_EVENT_MR);
+		i3c_bus_maintenance_unlock(&master->bus);
+	}
+
+	ret = i3c_bus_set_mode(i3cbus, mode);
+	if (ret)
+		goto err_del_dev;
+
+	ret = master->ops->bus_init(master);
+	if (ret)
+		goto err_del_dev;
+
 	return 0;
 
 err_del_dev:
@@ -2557,7 +2962,10 @@ 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 (!i3c_master_acquire_bus(master))
+		return master->ops->priv_xfers(dev, xfers, nxfers);
+	else
+		return -EAGAIN;
 }
 
 int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev)
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index 3dc7eafe811a..c6427c62ed83 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -259,6 +259,27 @@ enum i3c_bus_mode {
 	I3C_BUS_MODE_MIXED_SLOW,
 };
 
+enum i3c_mr_state {
+	I3C_MR_IDLE,
+	I3C_MR_DISEC_MR,
+	I3C_MR_SEND_DEFSLVS,
+	I3C_MR_GETACCMST,
+	I3C_MR_WAIT_DA,
+	I3C_MR_CHECK_STATE,
+	I3C_MR_REQUEST,
+	I3C_MR_WAIT_DEFSLVS,
+	I3C_MR_WAIT_MR_DONE,
+	I3C_MR_DONE,
+	I3C_MR_FAILED,
+};
+
+enum i3c_event {
+	I3C_SLV_DA_UPDATE,
+	I3C_SLV_DEFSLVS_CCC,
+	I3C_SLV_MR_DIS,
+	I3C_SLV_MR_DONE,
+};
+
 /**
  * enum i3c_addr_slot_status - I3C address slot status
  * @I3C_ADDR_SLOT_FREE: address is free
@@ -448,6 +469,11 @@ struct i3c_master_controller_ops {
 	int (*disable_ibi)(struct i3c_dev_desc *dev);
 	void (*recycle_ibi_slot)(struct i3c_dev_desc *dev,
 				 struct i3c_ibi_slot *slot);
+	int (*request_mastership)(struct i3c_master_controller *master);
+	void (*enable_mr_events)(struct i3c_master_controller *m);
+	void (*disable_mr_events)(struct i3c_master_controller *m);
+	bool (*check_event_set)(struct i3c_master_controller *m,
+				enum i3c_event);
 };
 
 /**
@@ -489,6 +515,17 @@ struct i3c_master_controller {
 	} boardinfo;
 	struct i3c_bus bus;
 	struct workqueue_struct *wq;
+	struct work_struct sec_mst_register_work;
+	struct work_struct mst_work;
+	struct work_struct sec_mst_work;
+	struct completion mr_comp;
+	enum i3c_mr_state mr_state;
+	u8 mr_addr;
+	struct {
+		u32 ndevs;
+		enum i3c_bus_mode bus_mode;
+		struct i3c_ccc_dev_desc *devs;
+	} defslvs_data;
 };
 
 /**
@@ -513,6 +550,9 @@ struct i3c_master_controller {
 #define i3c_bus_for_each_i3cdev(bus, dev)				\
 	list_for_each_entry(dev, &(bus)->devs.i3c, common.node)
 
+void i3c_master_yield_bus(struct i3c_master_controller *master,
+			  u8 slv_dyn_addr);
+void i3c_sec_mst_mr_dis_event(struct i3c_master_controller *m);
 int i3c_master_do_i2c_xfers(struct i3c_master_controller *master,
 			    const struct i2c_msg *xfers,
 			    int nxfers);
-- 
2.17.1


_______________________________________________
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH v1 3/3] i3c: master: add mastership handover support to cdns i3c master driver
  2020-04-06 22:20 [PATCH v1 0/3] I3C mastership handover support Parshuram Thombare
  2020-04-06 22:21 ` [PATCH v1 1/3] i3c: master: split bus_init callback into bus_init and master_set_info Parshuram Thombare
  2020-04-06 22:21 ` [PATCH v1 2/3] i3c: add mastership handover support to i3c master subsystem Parshuram Thombare
@ 2020-04-06 22:21 ` Parshuram Thombare
  2020-04-08 10:04 ` [PATCH v1 0/3] I3C mastership handover support Boris Brezillon
  3 siblings, 0 replies; 6+ messages in thread
From: Parshuram Thombare @ 2020-04-06 22:21 UTC (permalink / raw)
  To: bbrezillon, vitor.soares
  Cc: mparab, pgaj, Parshuram Thombare, linux-i3c, linux-kernel

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

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


_______________________________________________
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v1 0/3] I3C mastership handover support
  2020-04-06 22:20 [PATCH v1 0/3] I3C mastership handover support Parshuram Thombare
                   ` (2 preceding siblings ...)
  2020-04-06 22:21 ` [PATCH v1 3/3] i3c: master: add mastership handover support to cdns i3c master driver Parshuram Thombare
@ 2020-04-08 10:04 ` Boris Brezillon
  2020-04-08 15:06   ` Parshuram Raju Thombare
  3 siblings, 1 reply; 6+ messages in thread
From: Boris Brezillon @ 2020-04-08 10:04 UTC (permalink / raw)
  To: Parshuram Thombare
  Cc: mparab, bbrezillon, linux-kernel, vitor.soares, pgaj, linux-i3c

Hi Parshuram,

On Tue, 7 Apr 2020 00:20:45 +0200
Parshuram Thombare <pthombar@cadence.com> wrote:

> Hi,
> 
> This patch series is to enable mastership handover feature in I3C
> master subsystem and Cadence's I3C controller driver.

It's definitely not the first version (as implied in the subject), and
I'd like a proper changelog detailing what has changed since the last
version (the one sent by Przemek).

Thanks,

Boris

> 
> 
> [PATCH 1/3]
> i3c: master: split bus_init callback into bus_init and master_set_info
> There are 2 reasons for this split
> Currently bus_init callback is involved in
> a. Controller specific settings such as clock prescalar, enabling
>    different functionalities and finally enabling the controller.
> b. Allocating address to the main master and calling 
>    i3c_master_set_info, which basically create a I3C device for master
>    and add it to system. This is fine for main master, but for
>    secondary master this should be deferred until controller actually
>    owns the bus/mastership.
>    
> Another reason is, to support secondary master initialization without
> board info, controller specific bus mode setting need to be done twice
> First with pure bus mode and second time when actual bus mode is known
> based on data received through DEFSLVS, whereas master set info is
> supposed to be done only once.
> 	      
> [PATCH 2/3]
> i3c: add mastership handover support to i3c master subsystem
> This patch add mastership handover support as per MIPI I3C
> spec v1.0. Main master and secondary masters starts in pure
> bus mode to allow enumeration (DAA) to happen in same bus mode.
> Secondary masters are not required to have information about
> other devices connected on the bus through board info, this
> information is derived from DEFSLVS received from main
> master. While acquiring mastership, requesting master always
> make sure that it has active dynamic address, and received
> DEFSLVS at least once. Mastership request state machine
> make sure that any pending DEFSLVS is processed, so that
> when device become master it always have correct view
> of the bus.
> 
> [PATCH 3/3]
> i3c: master: add mastership handover support to cdns i3c master driver
> This patch adds mastership handover support to the Cadence
> I3C controller driver. Basically, this include necessary
> callbacks for mastership request.
> 
> Regards,
> Parshuram Thombare
> 
> Parshuram Thombare (3):
>   i3c: master: split bus_init callback into bus_init and master_set_info
>   i3c: add mastership handover support to i3c master subsystem
>   i3c: master: add mastership handover support to cdns i3c master driver
> 
>  drivers/i3c/master.c                 | 490 ++++++++++++++++++++++++---
>  drivers/i3c/master/dw-i3c-master.c   |  29 +-
>  drivers/i3c/master/i3c-master-cdns.c | 385 ++++++++++++++++++---
>  include/linux/i3c/master.h           |  47 ++-
>  4 files changed, 854 insertions(+), 97 deletions(-)
> 


_______________________________________________
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c

^ permalink raw reply	[flat|nested] 6+ messages in thread

* RE: [PATCH v1 0/3] I3C mastership handover support
  2020-04-08 10:04 ` [PATCH v1 0/3] I3C mastership handover support Boris Brezillon
@ 2020-04-08 15:06   ` Parshuram Raju Thombare
  0 siblings, 0 replies; 6+ messages in thread
From: Parshuram Raju Thombare @ 2020-04-08 15:06 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Milind Parab, bbrezillon, linux-kernel, vitor.soares,
	Przemyslaw Gaj, linux-i3c

Hi Boris,

>It's definitely not the first version (as implied in the subject), and
>I'd like a proper changelog detailing what has changed since the last
>version (the one sent by Przemek).

Sure, I will resend patches with updated version and change log.

But just to summarize, main changes are
1. Secondary master deferring initialization i.e. registering devices
     representing other device as well as master itself, until bus
     ownership is achieved.
2. Moved bus request from slave and bus handover from current 
    Master to separate state machines. This is to assist any further
    changes in mastership request/handover procedures. e.g. MIPI
    v1.1 specify additional features likes bus yield at the will of current
    master to sec master selected by current master, group address 
    functionality, multi lane support etc which requires additional
    steps in handover procedure. This structure will help to extend 
    the functionality further.
3. We don't really need secondary master to be aware other devices
    on the bus through mechanism like device tree, since main master
    broadcast this information through DEFSLVS. And receiving this
    information does not require sec. master driver to be loaded, at 
    least in case of CDNS I3C controller .DEFSLVS information is stored
    by HW inside a table which is later accessed by controller driver,
    to be passed to I3C master subsystem. Sec master initialization 
    state machine make sure it has active dynamic address (this may
    seems repetitive, but it is to handle case of RSTDAA CCC), and 
    DEFSLVS is received at least once. And IMO we don't really need
    to process DEFSLVS for a sec master until it want to become current
    master.
4. Another important change is setting main master and sec master 
    In pure bus mode during enumeration (DAA), this is to avoid need
    of sec. master having device information through device tree, and at  
    the same time allowing enumeration to happened successfully.
    Both main master and sec master change bus mode once enumeration
    is done.

Regards,
Parshuram Thombare



_______________________________________________
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, back to index

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-04-06 22:20 [PATCH v1 0/3] I3C mastership handover support Parshuram Thombare
2020-04-06 22:21 ` [PATCH v1 1/3] i3c: master: split bus_init callback into bus_init and master_set_info Parshuram Thombare
2020-04-06 22:21 ` [PATCH v1 2/3] i3c: add mastership handover support to i3c master subsystem Parshuram Thombare
2020-04-06 22:21 ` [PATCH v1 3/3] i3c: master: add mastership handover support to cdns i3c master driver Parshuram Thombare
2020-04-08 10:04 ` [PATCH v1 0/3] I3C mastership handover support Boris Brezillon
2020-04-08 15:06   ` Parshuram Raju Thombare

Linux-i3c Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-i3c/0 linux-i3c/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-i3c linux-i3c/ https://lore.kernel.org/linux-i3c \
		linux-i3c@lists.infradead.org
	public-inbox-index linux-i3c

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.infradead.lists.linux-i3c


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git