]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
i3c: master: Consolidate Hot-Join DAA work in the core
authorAdrian Hunter <adrian.hunter@intel.com>
Mon, 8 Jun 2026 05:43:07 +0000 (08:43 +0300)
committerAlexandre Belloni <alexandre.belloni@bootlin.com>
Sun, 14 Jun 2026 19:40:10 +0000 (21:40 +0200)
Three master drivers (dw-i3c-master, i3c-master-cdns, svc-i3c-master)
each carry an essentially identical Hot-Join handler: a struct
work_struct embedded in their private state, a work function that just
calls i3c_master_do_daa() on the embedded i3c_master_controller, plus
matching INIT_WORK()/cancel_work_sync() boilerplate in probe/remove (and
shutdown for dw-i3c). The IBI/ISR paths then queue that work onto
master->wq, which already lives in the core.

Move this pattern into the I3C core:

 - Add struct work_struct hj_work to struct i3c_master_controller and
   initialise it in i3c_master_register() with a core-provided handler
   i3c_master_hj_work_fn() that performs i3c_master_do_daa().
 - Cancel the work in i3c_master_unregister() so all controllers get
   correct teardown ordering against the workqueue for free.
 - Export i3c_master_queue_hotjoin() as the single entry point drivers
   call from their Hot-Join IBI handler.

Convert the three existing users to the new API: drop their private
hj_work fields, work functions, INIT_WORK() and cancel_work_sync()
calls, and replace the queue_work(master->wq, &drv->hj_work) call sites
with i3c_master_queue_hotjoin(&drv->base). The dw-i3c shutdown path
still needs to flush pending Hot-Join work before tearing down the
hardware, so it is updated to cancel master->base.hj_work directly.

No functional change intended: the work is still queued on the same
master->wq, runs the same i3c_master_do_daa(), and is cancelled at
controller teardown.  Future Hot-Join improvements now only need to
be made in one place.

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Link: https://patch.msgid.link/20260608054312.10604-4-adrian.hunter@intel.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
drivers/i3c/master.c
drivers/i3c/master/dw-i3c-master.c
drivers/i3c/master/dw-i3c-master.h
drivers/i3c/master/i3c-master-cdns.c
drivers/i3c/master/svc-i3c-master.c
include/linux/i3c/master.h

index a3a790a142863799bf08480e0fd9bdb36a8f3bf1..deb5cf851940da5f247234667c3211926ee15ac7 100644 (file)
@@ -633,6 +633,13 @@ static ssize_t i2c_scl_frequency_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(i2c_scl_frequency);
 
+static void i3c_master_hj_work_fn(struct work_struct *work)
+{
+       struct i3c_master_controller *master = container_of(work, typeof(*master), hj_work);
+
+       i3c_master_do_daa(master);
+}
+
 static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
 {
        int ret;
@@ -711,6 +718,18 @@ int i3c_master_disable_hotjoin(struct i3c_master_controller *master)
 }
 EXPORT_SYMBOL_GPL(i3c_master_disable_hotjoin);
 
+/**
+ * i3c_master_queue_hotjoin - Queue DAA processing after a Hot-Join event
+ * @master: I3C master object
+ *
+ * Queue the hot-join worker on the master's workqueue.
+ */
+void i3c_master_queue_hotjoin(struct i3c_master_controller *master)
+{
+       queue_work(master->wq, &master->hj_work);
+}
+EXPORT_SYMBOL_GPL(i3c_master_queue_hotjoin);
+
 static ssize_t hotjoin_show(struct device *dev, struct device_attribute *da, char *buf)
 {
        struct i3c_bus *i3cbus = dev_to_i3cbus(dev);
@@ -3084,6 +3103,7 @@ int i3c_master_register(struct i3c_master_controller *master,
                ret = -ENOMEM;
                goto err_put_dev;
        }
+       INIT_WORK(&master->hj_work, i3c_master_hj_work_fn);
 
        ret = i3c_master_bus_init(master);
        if (ret)
@@ -3146,6 +3166,7 @@ EXPORT_SYMBOL_GPL(i3c_master_register);
 void i3c_master_unregister(struct i3c_master_controller *master)
 {
        i3c_bus_notify(&master->bus, I3C_NOTIFY_BUS_REMOVE);
+       cancel_work_sync(&master->hj_work);
 
        if (master->ops->set_dev_nack_retry)
                device_remove_file(&master->dev, &dev_attr_dev_nack_retry_count);
index a7593d6efac5a81bf38dec689ea1d23357c801a8..a60158331a8f5753cc723f8647a7b690fd56d573 100644 (file)
@@ -1449,7 +1449,7 @@ static void dw_i3c_master_irq_handle_ibis(struct dw_i3c_master *master)
                if (IBI_TYPE_SIRQ(reg)) {
                        dw_i3c_master_handle_ibi_sir(master, reg);
                } else if (IBI_TYPE_HJ(reg)) {
-                       queue_work(master->base.wq, &master->hj_work);
+                       i3c_master_queue_hotjoin(&master->base);
                } else {
                        len = IBI_QUEUE_STATUS_DATA_LEN(reg);
                        dev_info(&master->base.dev,
@@ -1558,14 +1558,6 @@ static const struct dw_i3c_platform_ops dw_i3c_platform_ops_default = {
        .set_dat_ibi = dw_i3c_platform_set_dat_ibi_nop,
 };
 
-static void dw_i3c_hj_work(struct work_struct *work)
-{
-       struct dw_i3c_master *master =
-               container_of(work, typeof(*master), hj_work);
-
-       i3c_master_do_daa(&master->base);
-}
-
 int dw_i3c_common_probe(struct dw_i3c_master *master,
                        struct platform_device *pdev)
 {
@@ -1655,8 +1647,6 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
        if (master->quirks & DW_I3C_DISABLE_RUNTIME_PM_QUIRK)
                pm_runtime_get_noresume(&pdev->dev);
 
-       INIT_WORK(&master->hj_work, dw_i3c_hj_work);
-
        device_set_of_node_from_dev(&master->base.i2c.dev, &pdev->dev);
        ret = i3c_master_register(&master->base, &pdev->dev,
                                  &dw_mipi_i3c_ops, false);
@@ -1678,7 +1668,6 @@ EXPORT_SYMBOL_GPL(dw_i3c_common_probe);
 
 void dw_i3c_common_remove(struct dw_i3c_master *master)
 {
-       cancel_work_sync(&master->hj_work);
        i3c_master_unregister(&master->base);
 
        /* Balance pm_runtime_get_noresume() from probe() */
@@ -1823,7 +1812,7 @@ static void dw_i3c_shutdown(struct platform_device *pdev)
                return;
        }
 
-       cancel_work_sync(&master->hj_work);
+       cancel_work_sync(&master->base.hj_work);
 
        /* Disable interrupts */
        writel((u32)~INTR_ALL, master->regs + INTR_STATUS_EN);
index 306e25a089371ee1eeea4d1a498558fd80a53349..28e9348f21531eb22f779781047bae7cc2f115f0 100644 (file)
@@ -69,8 +69,6 @@ struct dw_i3c_master {
 
        /* platform-specific data */
        const struct dw_i3c_platform_ops *platform_ops;
-
-       struct work_struct hj_work;
 };
 
 struct dw_i3c_platform_ops {
index 5cfec6761494dc681a9f2ebf9da153150caa2c24..6d221596ea35779b079c2b2e55d41a38aeaf9eaf 100644 (file)
@@ -398,7 +398,6 @@ struct cdns_i3c_data {
 };
 
 struct cdns_i3c_master {
-       struct work_struct hj_work;
        struct i3c_master_controller base;
        u32 free_rr_slots;
        unsigned int maxdevs;
@@ -1357,7 +1356,7 @@ static void cnds_i3c_master_demux_ibis(struct cdns_i3c_master *master)
 
                case IBIR_TYPE_HJ:
                        WARN_ON(IBIR_XFER_BYTES(ibir) || (ibir & IBIR_ERROR));
-                       queue_work(master->base.wq, &master->hj_work);
+                       i3c_master_queue_hotjoin(&master->base);
                        break;
 
                case IBIR_TYPE_MR:
@@ -1528,15 +1527,6 @@ static const struct i3c_master_controller_ops cdns_i3c_master_ops = {
        .recycle_ibi_slot = cdns_i3c_master_recycle_ibi_slot,
 };
 
-static void cdns_i3c_master_hj(struct work_struct *work)
-{
-       struct cdns_i3c_master *master = container_of(work,
-                                                     struct cdns_i3c_master,
-                                                     hj_work);
-
-       i3c_master_do_daa(&master->base);
-}
-
 static struct cdns_i3c_data cdns_i3c_devdata = {
        .thd_delay_ns = 10,
 };
@@ -1584,7 +1574,6 @@ static int cdns_i3c_master_probe(struct platform_device *pdev)
        spin_lock_init(&master->xferqueue.lock);
        INIT_LIST_HEAD(&master->xferqueue.list);
 
-       INIT_WORK(&master->hj_work, cdns_i3c_master_hj);
        writel(0xffffffff, master->regs + MST_IDR);
        writel(0xffffffff, master->regs + SLV_IDR);
        ret = devm_request_irq(&pdev->dev, irq, cdns_i3c_master_interrupt, 0,
@@ -1627,7 +1616,6 @@ static void cdns_i3c_master_remove(struct platform_device *pdev)
 {
        struct cdns_i3c_master *master = platform_get_drvdata(pdev);
 
-       cancel_work_sync(&master->hj_work);
        i3c_master_unregister(&master->base);
 }
 
index 57d63d299e1efc888d6d5bdf288202196ef414cf..93805df8a9406e2d0a80eef7ac53fb00cbede9cd 100644 (file)
@@ -208,7 +208,6 @@ struct svc_i3c_drvdata {
  * @free_slots: Bit array of available slots
  * @addrs: Array containing the dynamic addresses of each attached device
  * @descs: Array of descriptors, one per attached device
- * @hj_work: Hot-join work
  * @irq: Main interrupt
  * @num_clks: I3C clock number
  * @fclk: Fast clock (bus)
@@ -235,7 +234,6 @@ struct svc_i3c_master {
        u32 free_slots;
        u8 addrs[SVC_I3C_MAX_DEVS];
        struct i3c_dev_desc *descs[SVC_I3C_MAX_DEVS];
-       struct work_struct hj_work;
        int irq;
        int num_clks;
        struct clk *fclk;
@@ -366,14 +364,6 @@ to_svc_i3c_master(struct i3c_master_controller *master)
        return container_of(master, struct svc_i3c_master, base);
 }
 
-static void svc_i3c_master_hj_work(struct work_struct *work)
-{
-       struct svc_i3c_master *master;
-
-       master = container_of(work, struct svc_i3c_master, hj_work);
-       i3c_master_do_daa(&master->base);
-}
-
 static struct i3c_dev_desc *
 svc_i3c_master_dev_from_addr(struct svc_i3c_master *master,
                             unsigned int ibiaddr)
@@ -651,7 +641,7 @@ static void svc_i3c_master_ibi_isr(struct svc_i3c_master *master)
        case SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN:
                svc_i3c_master_emit_stop(master);
                if (is_events_enabled(master, SVC_I3C_EVENT_HOTJOIN))
-                       queue_work(master->base.wq, &master->hj_work);
+                       i3c_master_queue_hotjoin(&master->base);
                break;
        case SVC_I3C_MSTATUS_IBITYPE_MASTER_REQUEST:
                svc_i3c_master_emit_stop(master);
@@ -2039,7 +2029,6 @@ static int svc_i3c_master_probe(struct platform_device *pdev)
        if (ret)
                return dev_err_probe(dev, ret, "can't enable I3C clocks\n");
 
-       INIT_WORK(&master->hj_work, svc_i3c_master_hj_work);
        mutex_init(&master->lock);
 
        ret = devm_request_irq(dev, master->irq, svc_i3c_master_irq_handler,
@@ -2098,7 +2087,6 @@ static void svc_i3c_master_remove(struct platform_device *pdev)
 {
        struct svc_i3c_master *master = platform_get_drvdata(pdev);
 
-       cancel_work_sync(&master->hj_work);
        i3c_master_unregister(&master->base);
 
        pm_runtime_dont_use_autosuspend(&pdev->dev);
index e6112e5f6608de0ab0163310cfcf907563c7033a..eb5c51608bd70efd16014112d4ac473acda7a01d 100644 (file)
@@ -520,6 +520,8 @@ struct i3c_master_controller_ops {
  *     in a thread context. Typical examples are Hot Join processing which
  *     requires taking the bus lock in maintenance, which in turn, can only
  *     be done from a sleep-able context
+ * @hj_work: work item used to run DAA after a Hot-Join event is detected.
+ *           Queued to @wq by i3c_master_queue_hotjoin()
  * @dev_nack_retry_count: retry count when slave device nack
  *
  * A &struct i3c_master_controller has to be registered to the I3C subsystem
@@ -543,6 +545,7 @@ struct i3c_master_controller {
        } boardinfo;
        struct i3c_bus bus;
        struct workqueue_struct *wq;
+       struct work_struct hj_work;
        unsigned int dev_nack_retry_count;
 };
 
@@ -623,6 +626,7 @@ int i3c_master_register(struct i3c_master_controller *master,
 void i3c_master_unregister(struct i3c_master_controller *master);
 int i3c_master_enable_hotjoin(struct i3c_master_controller *master);
 int i3c_master_disable_hotjoin(struct i3c_master_controller *master);
+void i3c_master_queue_hotjoin(struct i3c_master_controller *master);
 
 /**
  * i3c_dev_get_master_data() - get master private data attached to an I3C