]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
i3c: mipi-i3c-hci: Add Hot-Join support
authorAdrian Hunter <adrian.hunter@intel.com>
Mon, 8 Jun 2026 05:43:12 +0000 (08:43 +0300)
committerAlexandre Belloni <alexandre.belloni@bootlin.com>
Sun, 14 Jun 2026 19:40:10 +0000 (21:40 +0200)
Wire the MIPI I3C HCI driver into the I3C core Hot-Join framework to
allow targets to dynamically join the bus after initial DAA.

HCI hardware ACKs or NACKs Hot-Join requests based on
HC_CONTROL.HOT_JOIN_CTRL.  This was previously left in the
NACK-and-DISEC state, effectively preventing Hot-Join.  Implement
the ->enable_hotjoin() and ->disable_hotjoin() master operations
so the core and user space can control this policy at runtime.

Also issue broadcast ENEC HJ when enabling Hot-Join.  This is required
because the controller may have previously DISEC'ed the Hot-Join
event, causing targets that were NACKed once to never retry.

Acknowledged Hot-Join requests are delivered as IBIs on the reserved
address 0x02.  Update both the DMA and PIO IBI paths to recognise this
address and forward the event to i3c_master_queue_hotjoin().

To make Hot-Join usable by default, enable it once after the initial
DAA.  This is gated by rpm_ibi_allowed, since otherwise keeping Hot-Join
enabled prevents runtime suspend.  A new hj_init_done flag ensures this
one-time enablement is not repeated on subsequent DAAs.

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Link: https://patch.msgid.link/20260608054312.10604-9-adrian.hunter@intel.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
drivers/i3c/master/mipi-i3c-hci/core.c
drivers/i3c/master/mipi-i3c-hci/dma.c
drivers/i3c/master/mipi-i3c-hci/hci.h
drivers/i3c/master/mipi-i3c-hci/pio.c

index c6edbbedfdd7f6e737e8a7a5f13684cc40df53ef..53797841b63f9132ffc4bf7d5e4c5d4de9c0a153 100644 (file)
@@ -392,11 +392,52 @@ out:
        return ret;
 }
 
+static int i3c_hci_enable_hotjoin(struct i3c_master_controller *m)
+{
+       struct i3c_hci *hci = to_i3c_hci(m);
+       int ret;
+
+       reg_clear(HC_CONTROL, HC_CONTROL_HOT_JOIN_CTRL);
+
+       /*
+        * Broadcast Hot_join enable, so that an I3C device that has previously
+        * had its Hot-Join request NACK'ed knows to try again.
+        */
+       ret = i3c_master_enec_disec_locked(m, I3C_BROADCAST_ADDR, true, I3C_CCC_EVENT_HJ, true);
+       if (ret) {
+               reg_set(HC_CONTROL, HC_CONTROL_HOT_JOIN_CTRL);
+               dev_err(&hci->master.dev, "Hot-Join ENEC CCC failed\n");
+       }
+
+       return ret;
+}
+
+static int i3c_hci_disable_hotjoin(struct i3c_master_controller *m)
+{
+       struct i3c_hci *hci = to_i3c_hci(m);
+
+       reg_set(HC_CONTROL, HC_CONTROL_HOT_JOIN_CTRL);
+       return 0;
+}
+
 static int i3c_hci_daa(struct i3c_master_controller *m)
 {
        struct i3c_hci *hci = to_i3c_hci(m);
+       int ret;
 
-       return hci->cmd->perform_daa(hci);
+       ret = hci->cmd->perform_daa(hci);
+
+       if (!hci->hj_init_done) {
+               hci->hj_init_done = true;
+               /*
+                * Enable Hot-Join by default after initial DAA if it does not
+                * prevent runtime suspend.
+                */
+               if (m->rpm_ibi_allowed && !ret)
+                       m->hotjoin = !i3c_hci_enable_hotjoin(m);
+       }
+
+       return ret;
 }
 
 static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev,
@@ -652,6 +693,8 @@ static const struct i3c_master_controller_ops i3c_hci_ops = {
        .enable_ibi             = i3c_hci_enable_ibi,
        .disable_ibi            = i3c_hci_disable_ibi,
        .recycle_ibi_slot       = i3c_hci_recycle_ibi_slot,
+       .enable_hotjoin         = i3c_hci_enable_hotjoin,
+       .disable_hotjoin        = i3c_hci_disable_hotjoin,
 };
 
 static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
@@ -833,8 +876,9 @@ static int i3c_hci_do_reset_and_restore(struct i3c_hci *hci)
        scoped_guard(spinlock_irqsave, &hci->lock)
                hci->irq_inactive = false;
 
-       /* Enable bus with Hot-Join disabled */
-       reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE | HC_CONTROL_HOT_JOIN_CTRL);
+       /* Enable bus, restoring hot-join state */
+       reg_set(HC_CONTROL,
+               HC_CONTROL_BUS_ENABLE | (hci->master.hotjoin ? 0 : HC_CONTROL_HOT_JOIN_CTRL));
 
        return 0;
 }
index 5c6ae2055618450af45685200eb03358ec9543c2..87622d6f817e8e0d568a62af2ac1396c5e7f015a 100644 (file)
@@ -960,6 +960,11 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh)
        }
 
        /* determine who this is for */
+       if (ibi_addr == I3C_HOT_JOIN_ADDR) {
+               i3c_master_queue_hotjoin(&hci->master);
+               goto done;
+       }
+
        dev = i3c_hci_addr_to_dev(hci, ibi_addr);
        if (!dev) {
                dev_err(&hci->master.dev,
index 30297823ca8550450aacba04c19fc86ac4239c37..41d31a53abd370cf5e1d635ecc9af0a0d0cecafe 100644 (file)
@@ -57,6 +57,7 @@ struct i3c_hci {
        bool irq_inactive;
        bool enqueue_blocked;
        bool recovery_needed;
+       bool hj_init_done;
        wait_queue_head_t enqueue_wait_queue;
        u32 caps;
        unsigned int quirks;
index 6b8cc5f2b4d2e561983d58b228a28fa44ed603e9..b5ae1cfaa9e03d8238ff09d14796b5320690e81b 100644 (file)
@@ -862,6 +862,11 @@ static bool hci_pio_prep_new_ibi(struct i3c_hci *hci, struct hci_pio_data *pio)
        ibi->seg_len = FIELD_GET(IBI_DATA_LENGTH, ibi_status);
        ibi->seg_cnt = ibi->seg_len;
 
+       if (ibi->addr == I3C_HOT_JOIN_ADDR) {
+               i3c_master_queue_hotjoin(&hci->master);
+               return true;
+       }
+
        dev = i3c_hci_addr_to_dev(hci, ibi->addr);
        if (!dev) {
                dev_err(&hci->master.dev,