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 <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>
This commit is contained in:
Adrian Hunter 2026-06-08 08:43:07 +03:00 committed by Alexandre Belloni
parent 5b130aadc3
commit 828c613023
6 changed files with 29 additions and 41 deletions

View 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);

View 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);

View 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 {

View 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);
}

View 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);

View 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