[v7,5/7] i3c: master: add defslvs processing
diff mbox series

Message ID 1589202928-9912-1-git-send-email-pthombar@cadence.com
State New
Headers show
Series
  • I3C mastership handover support
Related show

Commit Message

Parshuram Thombare May 11, 2020, 1:15 p.m. UTC
Added defslvs processing code to the I3C master subsystem.

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

Patch
diff mbox series

diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 9c8250a6a2b0..ea53fadeed99 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -2639,7 +2639,8 @@  static int i3c_master_check_ops(const struct i3c_master_controller_ops *ops)
 		return -EINVAL;
 
 	if (ops->request_mastership &&
-	    (!ops->enable_mr_events || !ops->check_event_set))
+	    (!ops->enable_mr_events || !ops->check_event_set ||
+	     !ops->sec_mst_dyn_addr))
 		return -EINVAL;
 
 	return 0;
@@ -2818,12 +2819,20 @@  int i3c_secondary_master_register(struct i3c_master_controller *master,
 				  struct device *parent,
 				  const struct i3c_master_controller_ops *ops)
 {
-	int ret;
+	int ret, sz;
 
 	ret = i3c_master_init(master, parent, ops, true);
 	if (ret)
 		return ret;
 
+	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) {
+		ret = -ENOMEM;
+		goto err_cleanup_bus;
+	}
+
 	ret = device_add(&master->dev);
 	if (ret)
 		goto err_cleanup_bus;
@@ -2856,6 +2865,135 @@  int i3c_secondary_master_register(struct i3c_master_controller *master,
 }
 EXPORT_SYMBOL_GPL(i3c_secondary_master_register);
 
+static int i3c_master_populate_bus(struct i3c_master_controller *master)
+{
+	struct i3c_dev_desc *i3cdev, *olddev, *tmp;
+	struct i3c_ccc_dev_desc *desc;
+	struct list_head i3c_old;
+	struct i3c_bus *i3cbus;
+	int slot, dyn_addr, ret;
+
+	i3cbus = i3c_master_get_bus(master);
+
+	INIT_LIST_HEAD(&i3c_old);
+	list_for_each_entry_safe(olddev, tmp, &i3cbus->devs.i3c, common.node) {
+		i3c_master_put_i3c_addrs(olddev);
+		list_del(&olddev->common.node);
+		list_add(&olddev->common.node, &i3c_old);
+	}
+
+	dyn_addr = master->ops->sec_mst_dyn_addr(master);
+	master->this->info.dyn_addr = dyn_addr;
+	i3c_master_get_i3c_addrs(master->this);
+	list_del(&master->this->common.node);
+	list_add(&master->this->common.node, &i3cbus->devs.i3c);
+
+	desc = master->defslvs_data.devs;
+	for (slot = 0; slot < master->defslvs_data.ndevs; slot++, desc++) {
+		struct i3c_device_info info = {
+			.dyn_addr = desc->dyn_addr
+		};
+
+		if (dyn_addr == info.dyn_addr)
+			continue;
+
+		i3cdev = i3c_master_alloc_i3c_dev(master, &info);
+		if (IS_ERR(i3cdev)) {
+			ret = PTR_ERR(i3cdev);
+			goto populate_bus_fail;
+		}
+
+		i3c_master_get_i3c_addrs(i3cdev);
+		ret = i3c_master_retrieve_dev_info(i3cdev);
+		i3c_master_put_i3c_addrs(i3cdev);
+		if (ret) {
+			i3c_master_free_i3c_dev(i3cdev);
+			goto populate_bus_fail;
+		}
+
+		list_for_each_entry_safe(olddev, tmp, &i3c_old, common.node) {
+			if (olddev->info.pid == i3cdev->info.pid) {
+				olddev->info.dyn_addr = info.dyn_addr;
+				i3c_master_get_i3c_addrs(olddev);
+				list_del(&olddev->common.node);
+				list_add(&olddev->common.node,
+					 &i3cbus->devs.i3c);
+				i3c_master_free_i3c_dev(i3cdev);
+				i3cdev = NULL;
+				break;
+			}
+		}
+
+		if (i3cdev) {
+			ret = i3c_master_attach_i3c_dev(master, i3cdev);
+			if (ret) {
+				i3c_master_free_i3c_dev(i3cdev);
+				goto populate_bus_fail;
+			}
+		}
+	}
+
+	list_for_each_entry_safe(olddev, tmp, &i3c_old, common.node) {
+		if (olddev->dev) {
+			olddev->dev->desc = NULL;
+			if (device_is_registered(&olddev->dev->dev))
+				device_unregister(&olddev->dev->dev);
+			else
+				put_device(&olddev->dev->dev);
+			kfree(olddev->dev);
+		}
+		list_del(&olddev->common.node);
+		i3c_master_free_i3c_dev(olddev);
+	}
+
+	return 0;
+
+populate_bus_fail:
+	/*
+	 * Try to restore i3cbus->devs.i3c list, so far no i3c
+	 * device is deleted, only moved or added to the original
+	 * i3c list. Move rest of the i3c devices from old list,
+	 * to correctly process defslvs in rety.
+	 */
+	list_for_each_entry_safe(olddev, tmp, &i3c_old, common.node) {
+		list_del(&olddev->common.node);
+		list_add(&olddev->common.node, &i3cbus->devs.i3c);
+	}
+
+	return ret;
+}
+
+/**
+ * i3c_master_process_defslvs() - process I3C device list received in
+ * DEFSLVS for device plug/unplug and address change.
+ * @m: I3C master object
+ *
+ * This function may sleep, so should not be called in the atomic context.
+ */
+int i3c_master_process_defslvs(struct i3c_master_controller *m)
+{
+	int ret;
+
+	i3c_bus_normaluse_lock(&m->bus);
+	ret = i3c_master_acquire_bus(m);
+	i3c_bus_normaluse_unlock(&m->bus);
+	if (ret)
+		return ret;
+
+	i3c_bus_maintenance_lock(&m->bus);
+	ret = i3c_master_populate_bus(m);
+	i3c_bus_maintenance_unlock(&m->bus);
+	if (!ret) {
+		i3c_bus_normaluse_lock(&m->bus);
+		i3c_master_register_new_i3c_devs(m);
+		i3c_bus_normaluse_unlock(&m->bus);
+	}
+	i3c_master_enable_mr_events(m);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_master_process_defslvs);
+
 /**
  * i3c_master_unregister() - unregister an I3C master
  * @master: master used to send frames on the bus
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index dd67497ad8b1..688487c4a62a 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -488,6 +488,8 @@  struct i3c_master_controller_ops {
  *	in a thread context. Typical examples are Hot Join processing which
  *	requires taking the bus lock in maintenance, which in turn, can only
  *	be done from a sleep-able context
+ * @defslvs_data: list used to pass i3c device list received in DEFSLVS message,
+ *	from DEFSLVS controller driver to I3C core
  *
  * A &struct i3c_master_controller has to be registered to the I3C subsystem
  * through i3c_master_register(). None of &struct i3c_master_controller fields
@@ -507,6 +509,10 @@  struct i3c_master_controller {
 	} boardinfo;
 	struct i3c_bus bus;
 	struct workqueue_struct *wq;
+	struct {
+		u32 ndevs;
+		struct i3c_ccc_dev_desc *devs;
+	} defslvs_data;
 };
 
 /**
@@ -533,6 +539,7 @@  struct i3c_master_controller {
 
 void i3c_master_yield_bus(struct i3c_master_controller *m,
 			  u8 sec_mst_dyn_addr);
+int i3c_master_process_defslvs(struct i3c_master_controller *m);
 int i3c_master_do_i2c_xfers(struct i3c_master_controller *master,
 			    const struct i2c_msg *xfers,
 			    int nxfers);