i3c: master: svc: add hot join support
authorFrank Li <Frank.Li@nxp.com>
Fri, 1 Dec 2023 22:25:28 +0000 (17:25 -0500)
committerAlexandre Belloni <alexandre.belloni@bootlin.com>
Sun, 7 Jan 2024 23:48:10 +0000 (00:48 +0100)
Add hot join support for svc master controller. Disable hot join by
default.
User can use sysfs entry to enable hot join.

Signed-off-by: Frank Li <Frank.Li@nxp.com>
Link: https://lore.kernel.org/r/20231201222532.2431484-3-Frank.Li@nxp.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
drivers/i3c/master/svc-i3c-master.c

index cf703c0..a993ccf 100644 (file)
 /* This parameter depends on the implementation and may be tuned */
 #define SVC_I3C_FIFO_SIZE 16
 
+#define SVC_I3C_EVENT_IBI      BIT(0)
+#define SVC_I3C_EVENT_HOTJOIN  BIT(1)
+
 struct svc_i3c_cmd {
        u8 addr;
        bool rnw;
@@ -177,6 +180,7 @@ struct svc_i3c_regs_save {
  * @ibi.tbq_slot: To be queued IBI slot
  * @ibi.lock: IBI lock
  * @lock: Transfer lock, protect between IBI work thread and callbacks from master
+ * @enabled_events: Bit masks for enable events (IBI, HotJoin).
  */
 struct svc_i3c_master {
        struct i3c_master_controller base;
@@ -206,6 +210,7 @@ struct svc_i3c_master {
                spinlock_t lock;
        } ibi;
        struct mutex lock;
+       int enabled_events;
 };
 
 /**
@@ -220,6 +225,11 @@ struct svc_i3c_i2c_dev_data {
        struct i3c_generic_ibi_pool *ibi_pool;
 };
 
+static inline bool is_events_enabled(struct svc_i3c_master *master, u32 mask)
+{
+       return !!(master->enabled_events & mask);
+}
+
 static bool svc_i3c_master_error(struct svc_i3c_master *master)
 {
        u32 mstatus, merrwarn;
@@ -429,13 +439,16 @@ static void svc_i3c_master_ibi_work(struct work_struct *work)
        switch (ibitype) {
        case SVC_I3C_MSTATUS_IBITYPE_IBI:
                dev = svc_i3c_master_dev_from_addr(master, ibiaddr);
-               if (!dev)
+               if (!dev || !is_events_enabled(master, SVC_I3C_EVENT_IBI))
                        svc_i3c_master_nack_ibi(master);
                else
                        svc_i3c_master_handle_ibi(master, dev);
                break;
        case SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN:
-               svc_i3c_master_ack_ibi(master, false);
+               if (is_events_enabled(master, SVC_I3C_EVENT_HOTJOIN))
+                       svc_i3c_master_ack_ibi(master, false);
+               else
+                       svc_i3c_master_nack_ibi(master);
                break;
        case SVC_I3C_MSTATUS_IBITYPE_MASTER_REQUEST:
                svc_i3c_master_nack_ibi(master);
@@ -472,7 +485,9 @@ static void svc_i3c_master_ibi_work(struct work_struct *work)
                svc_i3c_master_emit_stop(master);
                break;
        case SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN:
-               queue_work(master->base.wq, &master->hj_work);
+               svc_i3c_master_emit_stop(master);
+               if (is_events_enabled(master, SVC_I3C_EVENT_HOTJOIN))
+                       queue_work(master->base.wq, &master->hj_work);
                break;
        case SVC_I3C_MSTATUS_IBITYPE_MASTER_REQUEST:
        default:
@@ -1472,6 +1487,7 @@ static int svc_i3c_master_enable_ibi(struct i3c_dev_desc *dev)
                return ret;
        }
 
+       master->enabled_events |= SVC_I3C_EVENT_IBI;
        svc_i3c_master_enable_interrupts(master, SVC_I3C_MINT_SLVSTART);
 
        return i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
@@ -1483,7 +1499,9 @@ static int svc_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
        struct svc_i3c_master *master = to_svc_i3c_master(m);
        int ret;
 
-       svc_i3c_master_disable_interrupts(master);
+       master->enabled_events &= ~SVC_I3C_EVENT_IBI;
+       if (!master->enabled_events)
+               svc_i3c_master_disable_interrupts(master);
 
        ret = i3c_master_disec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
 
@@ -1493,6 +1511,39 @@ static int svc_i3c_master_disable_ibi(struct i3c_dev_desc *dev)
        return ret;
 }
 
+static int svc_i3c_master_enable_hotjoin(struct i3c_master_controller *m)
+{
+       struct svc_i3c_master *master = to_svc_i3c_master(m);
+       int ret;
+
+       ret = pm_runtime_resume_and_get(master->dev);
+       if (ret < 0) {
+               dev_err(master->dev, "<%s> Cannot get runtime PM.\n", __func__);
+               return ret;
+       }
+
+       master->enabled_events |= SVC_I3C_EVENT_HOTJOIN;
+
+       svc_i3c_master_enable_interrupts(master, SVC_I3C_MINT_SLVSTART);
+
+       return 0;
+}
+
+static int svc_i3c_master_disable_hotjoin(struct i3c_master_controller *m)
+{
+       struct svc_i3c_master *master = to_svc_i3c_master(m);
+
+       master->enabled_events &= ~SVC_I3C_EVENT_HOTJOIN;
+
+       if (!master->enabled_events)
+               svc_i3c_master_disable_interrupts(master);
+
+       pm_runtime_mark_last_busy(master->dev);
+       pm_runtime_put_autosuspend(master->dev);
+
+       return 0;
+}
+
 static void svc_i3c_master_recycle_ibi_slot(struct i3c_dev_desc *dev,
                                            struct i3c_ibi_slot *slot)
 {
@@ -1519,6 +1570,8 @@ static const struct i3c_master_controller_ops svc_i3c_master_ops = {
        .recycle_ibi_slot = svc_i3c_master_recycle_ibi_slot,
        .enable_ibi = svc_i3c_master_enable_ibi,
        .disable_ibi = svc_i3c_master_disable_ibi,
+       .enable_hotjoin = svc_i3c_master_enable_hotjoin,
+       .disable_hotjoin = svc_i3c_master_disable_hotjoin,
 };
 
 static int svc_i3c_master_prepare_clks(struct svc_i3c_master *master)