[PATCH v6 3/8] i3c: master: i3c mastership request and handover

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


This patch add I3C mastership request and
handover infrasturcture to I3C master subsystem.

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

diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 0ec332e45737..3598856a0b25 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -1217,6 +1217,69 @@ static int i3c_master_getdcr_locked(struct i3c_master_controller *master,
return ret;
}

+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_enable_mr_events(struct i3c_master_controller *master)
+{
+ int ret;
+
+ 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_CCC_EVENT_HJ);
+ i3c_bus_maintenance_unlock(&master->bus);
+
+ return ret;
+}
+
+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 int i3c_master_retrieve_dev_info(struct i3c_dev_desc *dev)
{
struct i3c_master_controller *master = i3c_dev_get_master(dev);
@@ -1612,6 +1675,161 @@ static void i3c_master_detach_free_devs(struct i3c_master_controller *master)
}
}

+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_CCC_EVENT_HJ);
+ 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;
+
+ default:
+ case I3C_MR_FAILED:
+ i3c_master_enable_mr_events(m);
+ m->mr_state = I3C_MR_IDLE;
+ break;
+ }
+}
+
+void
+i3c_master_yield_bus(struct i3c_master_controller *master, u8 sec_mst_dyn_addr)
+{
+ if (master->this && master->this == master->bus.cur_master) {
+ 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);
+ } else {
+ /* If not a current master, we should never come here */
+ WARN_ON(1);
+ }
+}
+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_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;
+ m->bus.cur_master = m->this;
+ 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);
+
+/* This function is expected to be called with normaluse_lock */
+int i3c_master_acquire_bus(struct i3c_master_controller *master)
+{
+ int ret = 0;
+
+ if (!master->this || master->this != master->bus.cur_master) {
+ if (master->mr_state == I3C_MR_IDLE) {
+ master->mr_state = I3C_MR_WAIT_DA;
+ init_completion(&master->mr_comp);
+ queue_work(master->wq, &master->sec_mst_work);
+ /*
+ * Bus acquire procedure may need write lock
+ * so release read lock before yielding
+ * to bus acquire state machine
+ */
+ i3c_bus_normaluse_unlock(&master->bus);
+ wait_for_completion(&master->mr_comp);
+ i3c_bus_normaluse_lock(&master->bus);
+ 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;
+ }
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_master_acquire_bus);
+
/**
* i3c_master_bus_init() - initialize an I3C bus
* @master: main master initializing the bus
@@ -2451,6 +2669,9 @@ int i3c_master_register(struct i3c_master_controller *master,
device_initialize(&master->dev);
dev_set_name(&master->dev, "i3c-%d", i3cbus->id);

+ INIT_WORK(&master->sec_mst_work, i3c_sec_mst_acquire_bus);
+ INIT_WORK(&master->mst_work, i3c_mst_yield_bus);
+
ret = of_populate_i3c_bus(master);
if (ret)
goto err_put_dev;
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index 3dc7eafe811a..c465c7792ccb 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,11 @@ struct i3c_master_controller {
} boardinfo;
struct i3c_bus bus;
struct workqueue_struct *wq;
+ struct work_struct mst_work;
+ struct work_struct sec_mst_work;
+ struct completion mr_comp;
+ enum i3c_mr_state mr_state;
+ u8 mr_addr;
};

/**
@@ -513,6 +544,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