From: Adrian Hunter Date: Mon, 8 Jun 2026 05:43:07 +0000 (+0300) Subject: i3c: master: Consolidate Hot-Join DAA work in the core X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=828c6130235db8144f4810b329b61390dc82719b;p=thirdparty%2Flinux.git i3c: master: Consolidate Hot-Join DAA work in the core 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 Reviewed-by: Frank Li Link: https://patch.msgid.link/20260608054312.10604-4-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index a3a790a142863..deb5cf851940d 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -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); diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index a7593d6efac5a..a60158331a8f5 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -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); diff --git a/drivers/i3c/master/dw-i3c-master.h b/drivers/i3c/master/dw-i3c-master.h index 306e25a089371..28e9348f21531 100644 --- a/drivers/i3c/master/dw-i3c-master.h +++ b/drivers/i3c/master/dw-i3c-master.h @@ -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 { diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c index 5cfec6761494d..6d221596ea357 100644 --- a/drivers/i3c/master/i3c-master-cdns.c +++ b/drivers/i3c/master/i3c-master-cdns.c @@ -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); } diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index 57d63d299e1ef..93805df8a9406 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -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); diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index e6112e5f6608d..eb5c51608bd70 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -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