diff mbox series

[v6,4/8] i3c: master: defslvs processing

Message ID 1587140518-30782-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:21 p.m. UTC
This patch add DEFSLVS processing code
to I3C master subsystem.

Signed-off-by: Parshuram Thombare <pthombar@cadence.com>
---
 drivers/i3c/master.c       | 200 +++++++++++++++++++++++++++++++++++++
 include/linux/i3c/master.h |   6 ++
 2 files changed, 206 insertions(+)
diff mbox series

Patch

diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 3598856a0b25..2690910d724c 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -1830,6 +1830,206 @@  int i3c_master_acquire_bus(struct i3c_master_controller *master)
 }
 EXPORT_SYMBOL_GPL(i3c_master_acquire_bus);
 
+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 void
+i3c_master_copy_olddev(struct i3c_master_controller *master,
+		       struct i3c_dev_desc *newdev,
+		       struct i3c_dev_desc *olddev)
+{
+	struct i3c_ibi_setup ibireq = { };
+	bool enable_ibi = false;
+	int ret;
+
+	newdev->dev = olddev->dev;
+	if (newdev->dev)
+		newdev->dev->desc = newdev;
+
+	mutex_lock(&olddev->ibi_lock);
+	if (olddev->ibi) {
+		ibireq.handler = olddev->ibi->handler;
+		ibireq.max_payload_len = olddev->ibi->max_payload_len;
+		ibireq.num_slots = olddev->ibi->num_slots;
+
+		if (olddev->ibi->enabled) {
+			enable_ibi = true;
+			i3c_dev_disable_ibi_locked(olddev);
+		}
+
+		i3c_dev_free_ibi_locked(olddev);
+	}
+	mutex_unlock(&olddev->ibi_lock);
+
+	i3c_master_detach_i3c_dev(olddev);
+	i3c_master_free_i3c_dev(olddev);
+
+	if (ibireq.handler) {
+		mutex_lock(&newdev->ibi_lock);
+		ret = i3c_dev_request_ibi_locked(newdev, &ibireq);
+		if (ret) {
+			dev_err(&master->dev,
+				"Failed to request IBI on device %d-%llx",
+				master->bus.id, newdev->info.pid);
+		} else if (enable_ibi) {
+			ret = i3c_dev_enable_ibi_locked(newdev);
+			if (ret)
+				dev_err(&master->dev,
+					"Failed to re-enable IBI on device %d-%llx",
+					master->bus.id, newdev->info.pid);
+		}
+		mutex_unlock(&newdev->ibi_lock);
+	}
+}
+
+static int i3c_master_populate_bus(struct i3c_master_controller *master)
+{
+	struct i3c_ccc_dev_desc *desc;
+	struct i2c_dev_desc *i2cdev;
+	struct i2c_dev_boardinfo *info;
+	struct i3c_dev_desc *i3cdev, *olddev, *i3ctmp;
+	struct i3c_bus *i3cbus;
+	struct list_head i3c_old;
+	int slot, ret;
+
+	INIT_LIST_HEAD(&i3c_old);
+	i3cbus = i3c_master_get_bus(master);
+	list_add(&i3c_old, &i3cbus->devs.i3c);
+	list_del(&i3cbus->devs.i3c);
+	INIT_LIST_HEAD(&i3cbus->devs.i3c);
+	desc = master->defslvs_data.devs;
+	for (slot = 1; slot <= master->defslvs_data.ndevs; slot++, desc++) {
+		if (desc->static_addr && list_empty(&master->bus.devs.i2c)) {
+			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)
+				goto err_detach_devs;
+
+			ret = i3c_master_retrieve_dev_info(i3cdev);
+			if (ret)
+				goto err_detach_devs;
+
+			list_for_each_entry(olddev, &i3c_old, common.node) {
+				if (i3cdev != olddev &&
+				    i3cdev->info.pid == olddev->info.pid)
+					i3c_master_copy_olddev(master, i3cdev,
+							       olddev);
+			}
+		}
+	}
+
+	list_for_each_entry_safe(i3cdev, i3ctmp, &i3c_old,
+				 common.node) {
+		i3c_master_detach_i3c_dev(i3cdev);
+		i3c_master_free_i3c_dev(i3cdev);
+	}
+
+	return 0;
+
+err_detach_devs:
+	if (!master->init_done) {
+		i3c_master_detach_free_devs(master);
+	} else {
+		INIT_LIST_HEAD(&i3cbus->devs.i3c);
+		list_add(&i3cbus->devs.i3c, &i3c_old);
+		list_del(&i3c_old);
+	}
+
+	return ret;
+}
+
+/* This function may sleep, so should not be called from atomic context */
+int i3c_master_process_defslvs(struct i3c_master_controller *master)
+{
+	struct i3c_bus *i3cbus;
+	int ret;
+
+	i3cbus = i3c_master_get_bus(master);
+
+	i3c_bus_normaluse_lock(&master->bus);
+	ret = i3c_master_acquire_bus(master);
+	i3c_bus_normaluse_unlock(&master->bus);
+	if (ret)
+		return ret;
+
+	/* 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)
+		return ret;
+
+	ret = master->ops->bus_init(master);
+	if (ret)
+		goto err_cleanup_bus;
+
+	if (!ret) {
+		master->ops->master_set_info(master);
+		i3c_bus_maintenance_lock(&master->bus);
+		ret = i3c_master_populate_bus(master);
+		if (ret) {
+			i3c_bus_maintenance_unlock(&master->bus);
+			goto err_cleanup_bus;
+		}
+		i3c_master_register_new_i3c_devs(master);
+		i3c_bus_maintenance_unlock(&master->bus);
+	}
+
+	if (master->init_done)
+		i3c_master_enable_mr_events(master);
+	return 0;
+
+err_cleanup_bus:
+	i3c_master_enable_mr_events(master);
+	if (master->ops->bus_cleanup)
+		master->ops->bus_cleanup(master);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_master_process_defslvs);
+
 /**
  * i3c_master_bus_init() - initialize an I3C bus
  * @master: main master initializing the bus
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index c465c7792ccb..cc482934803b 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -520,6 +520,11 @@  struct i3c_master_controller {
 	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;
 };
 
 /**
@@ -544,6 +549,7 @@  struct i3c_master_controller {
 #define i3c_bus_for_each_i3cdev(bus, dev)				\
 	list_for_each_entry(dev, &(bus)->devs.i3c, common.node)
 
+int i3c_master_process_defslvs(struct i3c_master_controller *master);
 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);