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

From: Parshuram Thombare
Date: Fri Apr 17 2020 - 12:22:17 EST


This patch add DEFSLVS processing code
to I3C master subsystem.

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

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);
--
2.17.1