* [PATCH V3 1/8] i3c: master: Make hot-join workqueue freezable to block hot-join during suspend
2026-06-08 5:43 [PATCH V3 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
@ 2026-06-08 5:43 ` Adrian Hunter
2026-06-08 5:43 ` [PATCH V3 2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock Adrian Hunter
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Adrian Hunter @ 2026-06-08 5:43 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, david.nystrom, linux-i3c, linux-kernel
The I3C master workqueue (master->wq) is used to defer work that needs
thread context and the bus maintenance lock, most notably Hot Join
processing (which calls i3c_master_do_daa() to assign dynamic addresses
to newly joined devices).
Currently the workqueue keeps running across system suspend, which can
race with the suspend path:
- do_daa() may execute after the controller has been suspended,
issuing bus transactions on a powered-down or otherwise unusable
controller.
- New I3C devices can be enumerated and added to the bus mid-suspend,
registering driver model objects at a point where the I3C subsystem
and its consumers are not prepared to handle them.
Mark the workqueue WQ_FREEZABLE so its workers are frozen for the
duration of system suspend/hibernate and resumed afterwards. This
naturally defers any pending or newly queued Hot Join work until the
system (and the controller) is fully resumed, closing both races
without adding explicit suspend/resume synchronization in the master
drivers.
Update the kerneldoc for struct i3c_master_controller::wq to reflect
that the workqueue is freezable.
Fixes: 3a379bbcea0af ("i3c: Add core I3C infrastructure")
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
Changes in V3:
None
Changes in V2:
Add Fixes tag
Add Frank's Rev'd by
drivers/i3c/master.c | 2 +-
include/linux/i3c/master.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 5cd4e5da2233..ab11e2d79aab 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -3079,7 +3079,7 @@ int i3c_master_register(struct i3c_master_controller *master,
if (ret)
goto err_put_dev;
- master->wq = alloc_workqueue("%s", WQ_PERCPU, 0, dev_name(parent));
+ master->wq = alloc_workqueue("%s", WQ_PERCPU | WQ_FREEZABLE, 0, dev_name(parent));
if (!master->wq) {
ret = -ENOMEM;
goto err_put_dev;
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index 592b646f6134..e6112e5f6608 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -515,7 +515,7 @@ struct i3c_master_controller_ops {
* @boardinfo.i2c: list of I2C boardinfo objects
* @boardinfo: board-level information attached to devices connected on the bus
* @bus: I3C bus exposed by this master
- * @wq: workqueue which can be used by master
+ * @wq: freezable workqueue which can be used by master
* drivers if they need to postpone operations that need to take place
* in a thread context. Typical examples are Hot Join processing which
* requires taking the bus lock in maintenance, which in turn, can only
--
2.51.0
--
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH V3 2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock
2026-06-08 5:43 [PATCH V3 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
2026-06-08 5:43 ` [PATCH V3 1/8] i3c: master: Make hot-join workqueue freezable to block hot-join during suspend Adrian Hunter
@ 2026-06-08 5:43 ` Adrian Hunter
2026-06-08 5:43 ` [PATCH V3 3/8] i3c: master: Consolidate Hot-Join DAA work in the core Adrian Hunter
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Adrian Hunter @ 2026-06-08 5:43 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, david.nystrom, linux-i3c, linux-kernel
i3c_set_hotjoin() dispatches the controller's enable_hotjoin() or
disable_hotjoin() op and updates master->hotjoin under
i3c_bus_normaluse_lock(). That lock is a read-side acquisition of
bus->lock (down_read()), so it does not exclude concurrent callers.
The hotjoin sysfs attribute can be opened multiple times, and writes
through different opens are not serialized. Two concurrent writers
to "hotjoin" can therefore race in i3c_set_hotjoin(), with the
controller op and the master->hotjoin store from one call interleaving
with the other. The hardware enable/disable state and the value reported
by hotjoin_show() can end up out of sync.
Take i3c_bus_maintenance_lock() instead. Toggling Hot Join enable
changes bus state and is conceptually a maintenance operation, so the
write-side acquisition of bus->lock is the appropriate lock and
serializes concurrent callers against each other and against other
maintenance operations.
Fixes: 317bacf960a48 ("i3c: master: add enable(disable) hot join in sys entry")
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
Changes in V3:
Add Frank's Rev'd by
Changes in V2:
Add Fixes tag
drivers/i3c/master.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index ab11e2d79aab..38ffc8713167 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -649,7 +649,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
return ret;
}
- i3c_bus_normaluse_lock(&master->bus);
+ i3c_bus_maintenance_lock(&master->bus);
if (enable)
ret = master->ops->enable_hotjoin(master);
@@ -659,7 +659,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
if (!ret)
master->hotjoin = enable;
- i3c_bus_normaluse_unlock(&master->bus);
+ i3c_bus_maintenance_unlock(&master->bus);
if ((enable && ret) || (!enable && !ret) || master->rpm_ibi_allowed)
i3c_master_rpm_put(master);
--
2.51.0
--
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH V3 3/8] i3c: master: Consolidate Hot-Join DAA work in the core
2026-06-08 5:43 [PATCH V3 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
2026-06-08 5:43 ` [PATCH V3 1/8] i3c: master: Make hot-join workqueue freezable to block hot-join during suspend Adrian Hunter
2026-06-08 5:43 ` [PATCH V3 2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock Adrian Hunter
@ 2026-06-08 5:43 ` Adrian Hunter
2026-06-08 5:43 ` [PATCH V3 4/8] i3c: master: Ensure Hot-Join operations are stopped on shutdown Adrian Hunter
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Adrian Hunter @ 2026-06-08 5:43 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, david.nystrom, linux-i3c, linux-kernel
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>
---
Changes in V3:
None
Changes in V2:
Add Frank's Rev'd by
drivers/i3c/master.c | 21 +++++++++++++++++++++
drivers/i3c/master/dw-i3c-master.c | 15 ++-------------
drivers/i3c/master/dw-i3c-master.h | 2 --
drivers/i3c/master/i3c-master-cdns.c | 14 +-------------
drivers/i3c/master/svc-i3c-master.c | 14 +-------------
include/linux/i3c/master.h | 4 ++++
6 files changed, 29 insertions(+), 41 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 38ffc8713167..cdb5cb2aa65d 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 655693a2187e..eb9a13a73684 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -1445,7 +1445,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,
@@ -1554,14 +1554,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)
{
@@ -1636,8 +1628,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);
@@ -1659,7 +1649,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() */
@@ -1804,7 +1793,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 c5cb695c16ab..2f029bd36232 100644
--- a/drivers/i3c/master/dw-i3c-master.h
+++ b/drivers/i3c/master/dw-i3c-master.h
@@ -68,8 +68,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 5cfec6761494..6d221596ea35 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 e2d99a3ac07d..62e5666798c8 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);
@@ -2022,7 +2012,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,
@@ -2081,7 +2070,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 e6112e5f6608..eb5c51608bd7 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
--
2.51.0
--
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH V3 4/8] i3c: master: Ensure Hot-Join operations are stopped on shutdown
2026-06-08 5:43 [PATCH V3 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
` (2 preceding siblings ...)
2026-06-08 5:43 ` [PATCH V3 3/8] i3c: master: Consolidate Hot-Join DAA work in the core Adrian Hunter
@ 2026-06-08 5:43 ` Adrian Hunter
2026-06-08 5:43 ` [PATCH V3 5/8] i3c: dw: Drop redundant Hot-Join cancel_work_sync() in shutdown Adrian Hunter
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Adrian Hunter @ 2026-06-08 5:43 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, david.nystrom, linux-i3c, linux-kernel
System shutdown invokes each device's bus shutdown callback to quiesce
hardware, but the I3C bus type does not currently implement one. As a
result, on shutdown the controller's Hot-Join work and any in-flight
i3c_master_do_daa() can keep running (or be newly triggered) while the
rest of the system is being torn down.
A similar window exists at i3c_master_unregister() time: cancel_work_sync()
on hj_work prevents queued work from completing, but does not stop a
fresh Hot-Join IBI from re-queueing the worker, nor a concurrent sysfs
writer from toggling Hot-Join via i3c_set_hotjoin().
Introduce a single "shutting down" gate in the I3C core, set under the
bus maintenance lock so it is observed by any in-progress DAA path
before pending work is cancelled. Install an i3c_bus_type shutdown
callback that engages this gate for master devices during system
shutdown, and use the same gate in i3c_master_unregister() so both
paths get identical guarantees.
Once the gate is engaged, the Hot-Join worker, i3c_master_do_daa_ext()
and i3c_set_hotjoin() all bail out cleanly, so Hot-Join IBIs that race
with shutdown become no-ops, direct DAA callers see -ENODEV, and sysfs
writers can no longer re-enable Hot-Join through ops->enable_hotjoin()
while the controller is going away.
No functional change for the steady-state runtime path; the new checks
only take effect once the controller has been marked as shutting down.
Note, this patch depends on patch "i3c: master: Consolidate Hot-Join DAA
work in the core".
Fixes: 3a379bbcea0af ("i3c: Add core I3C infrastructure")
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
Changes in V3:
None
Changes in V2:
Add dependency note to commit message
Add Fixes tag
Add Frank's Rev'd by
drivers/i3c/master.c | 52 +++++++++++++++++++++++++++-----------
include/linux/i3c/master.h | 2 ++
2 files changed, 39 insertions(+), 15 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index cdb5cb2aa65d..a59c4b744b36 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -368,14 +368,6 @@ static void i3c_device_remove(struct device *dev)
driver->remove(i3cdev);
}
-const struct bus_type i3c_bus_type = {
- .name = "i3c",
- .match = i3c_device_match,
- .probe = i3c_device_probe,
- .remove = i3c_device_remove,
-};
-EXPORT_SYMBOL_GPL(i3c_bus_type);
-
static enum i3c_addr_slot_status
i3c_bus_get_addr_slot_status_mask(struct i3c_bus *bus, u16 addr, u32 mask)
{
@@ -637,7 +629,8 @@ 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);
+ if (!master->shutting_down)
+ i3c_master_do_daa(master);
}
static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
@@ -658,7 +651,9 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
i3c_bus_maintenance_lock(&master->bus);
- if (enable)
+ if (master->shutting_down)
+ ret = -ENODEV;
+ else if (enable)
ret = master->ops->enable_hotjoin(master);
else
ret = master->ops->disable_hotjoin(master);
@@ -837,6 +832,30 @@ static const struct device_type i3c_masterdev_type = {
.groups = i3c_masterdev_groups,
};
+static void i3c_master_shutdown(struct i3c_master_controller *master)
+{
+ i3c_bus_maintenance_lock(&master->bus);
+ master->shutting_down = true;
+ i3c_bus_maintenance_unlock(&master->bus);
+
+ cancel_work_sync(&master->hj_work);
+}
+
+static void i3c_device_shutdown(struct device *dev)
+{
+ if (dev->type == &i3c_masterdev_type)
+ i3c_master_shutdown(dev_to_i3cmaster(dev));
+}
+
+const struct bus_type i3c_bus_type = {
+ .name = "i3c",
+ .match = i3c_device_match,
+ .probe = i3c_device_probe,
+ .remove = i3c_device_remove,
+ .shutdown = i3c_device_shutdown,
+};
+EXPORT_SYMBOL_GPL(i3c_bus_type);
+
static int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode,
unsigned long max_i2c_scl_rate)
{
@@ -1846,10 +1865,13 @@ int i3c_master_do_daa_ext(struct i3c_master_controller *master, bool rstdaa)
i3c_bus_maintenance_lock(&master->bus);
- if (rstdaa)
- rstret = i3c_master_rstdaa_locked(master, I3C_BROADCAST_ADDR);
-
- ret = master->ops->do_daa(master);
+ if (master->shutting_down) {
+ ret = -ENODEV;
+ } else {
+ if (rstdaa)
+ rstret = i3c_master_rstdaa_locked(master, I3C_BROADCAST_ADDR);
+ ret = master->ops->do_daa(master);
+ }
i3c_bus_maintenance_unlock(&master->bus);
@@ -3166,7 +3188,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);
+ i3c_master_shutdown(master);
if (master->ops->set_dev_nack_retry)
device_remove_file(&master->dev, &dev_attr_dev_nack_retry_count);
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index eb5c51608bd7..77e63082b06e 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -511,6 +511,7 @@ struct i3c_master_controller_ops {
* @hotjoin: true if the master support hotjoin
* @rpm_allowed: true if Runtime PM allowed
* @rpm_ibi_allowed: true if IBI and Hot-Join allowed while runtime suspended
+ * @shutting_down: set to true when master begins shutdown or unregister
* @boardinfo.i3c: list of I3C boardinfo objects
* @boardinfo.i2c: list of I2C boardinfo objects
* @boardinfo: board-level information attached to devices connected on the bus
@@ -539,6 +540,7 @@ struct i3c_master_controller {
unsigned int hotjoin: 1;
unsigned int rpm_allowed: 1;
unsigned int rpm_ibi_allowed: 1;
+ bool shutting_down;
struct {
struct list_head i3c;
struct list_head i2c;
--
2.51.0
--
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH V3 5/8] i3c: dw: Drop redundant Hot-Join cancel_work_sync() in shutdown
2026-06-08 5:43 [PATCH V3 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
` (3 preceding siblings ...)
2026-06-08 5:43 ` [PATCH V3 4/8] i3c: master: Ensure Hot-Join operations are stopped on shutdown Adrian Hunter
@ 2026-06-08 5:43 ` Adrian Hunter
2026-06-08 5:43 ` [PATCH V3 6/8] i3c: master: Defer new-device registration out of DAA caller context Adrian Hunter
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Adrian Hunter @ 2026-06-08 5:43 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, david.nystrom, linux-i3c, linux-kernel
The I3C core now installs an i3c_bus_type shutdown callback that
flushes master->hj_work (via i3c_master_shutdown()) before any driver's
platform shutdown hook runs. The explicit cancel_work_sync() in
dw_i3c_shutdown() is therefore redundant: by the time it executes, the
Hot-Join worker has already been cancelled, and the shutting_down gate
makes a new worker a no-op.
Remove the now-unneeded call. No functional change.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
Changes in V3:
None
Changes in V2:
Add Frank's Rev'd by
drivers/i3c/master/dw-i3c-master.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
index eb9a13a73684..c7030d0cd8a6 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -1793,8 +1793,6 @@ static void dw_i3c_shutdown(struct platform_device *pdev)
return;
}
- cancel_work_sync(&master->base.hj_work);
-
/* Disable interrupts */
writel((u32)~INTR_ALL, master->regs + INTR_STATUS_EN);
writel((u32)~INTR_ALL, master->regs + INTR_SIGNAL_EN);
--
2.51.0
--
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH V3 6/8] i3c: master: Defer new-device registration out of DAA caller context
2026-06-08 5:43 [PATCH V3 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
` (4 preceding siblings ...)
2026-06-08 5:43 ` [PATCH V3 5/8] i3c: dw: Drop redundant Hot-Join cancel_work_sync() in shutdown Adrian Hunter
@ 2026-06-08 5:43 ` Adrian Hunter
2026-06-08 5:43 ` [PATCH V3 7/8] i3c: master: Export i3c_master_enec_disec_locked() Adrian Hunter
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Adrian Hunter @ 2026-06-08 5:43 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, david.nystrom, linux-i3c, linux-kernel
Master drivers may invoke i3c_master_do_daa_ext() during resume to
re-run Dynamic Address Assignment. As well as assigning addresses to
any newly arrived devices, this restores the dynamic address of devices
that lost it across system suspend, so it has to run as part of the
controller's resume path.
A side effect of i3c_master_do_daa_ext() today is that it also
registers any newly discovered I3C devices with the driver model
inline, via i3c_master_register_new_i3c_devs(). Doing that from the
resume path is problematic: a hot-join-capable device may join the bus
during this same DAA, and registering it immediately would push driver
model work (probing, sysfs, etc.) into the controller's resume context,
where the rest of the system is not yet fully resumed and the
controller driver is still partway through its own resume sequence.
Decouple discovery from registration: add a reg_work work item to
struct i3c_master_controller and have i3c_master_do_daa_ext() queue it
on master->wq (the freezable workqueue) instead of calling
i3c_master_register_new_i3c_devs() directly. The worker performs the
registration only when the controller is not shutting_down, and is
cancelled alongside hj_work in i3c_master_shutdown(). Because wq is
freezable, any newly observed devices end up being registered after
the system has finished resuming.
i3c_master_register() also routes its initial post-bus-init registration
through reg_work, using flush_work() to keep probe-time behavior
synchronous. This keeps a single registration code path and ensures the
worker is the only writer of desc->dev.
Fixes: 3a379bbcea0af ("i3c: Add core I3C infrastructure")
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
Changes in V3:
Add Frank's Rev'd by
Changes in V2:
Add comment about reg_work use
Add Fixes tag
drivers/i3c/master.c | 27 ++++++++++++++++++++-------
include/linux/i3c/master.h | 6 ++++++
2 files changed, 26 insertions(+), 7 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index a59c4b744b36..c9685379e868 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -839,6 +839,7 @@ static void i3c_master_shutdown(struct i3c_master_controller *master)
i3c_bus_maintenance_unlock(&master->bus);
cancel_work_sync(&master->hj_work);
+ cancel_work_sync(&master->reg_work);
}
static void i3c_device_shutdown(struct device *dev)
@@ -1838,6 +1839,16 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master)
}
}
+static void i3c_master_reg_work_fn(struct work_struct *work)
+{
+ struct i3c_master_controller *master = container_of(work, typeof(*master), reg_work);
+
+ i3c_bus_normaluse_lock(&master->bus);
+ if (!master->shutting_down)
+ i3c_master_register_new_i3c_devs(master);
+ i3c_bus_normaluse_unlock(&master->bus);
+}
+
/**
* i3c_master_do_daa_ext() - Dynamic Address Assignment (extended version)
* @master: controller
@@ -1878,9 +1889,7 @@ int i3c_master_do_daa_ext(struct i3c_master_controller *master, bool rstdaa)
if (ret)
goto out;
- i3c_bus_normaluse_lock(&master->bus);
- i3c_master_register_new_i3c_devs(master);
- i3c_bus_normaluse_unlock(&master->bus);
+ queue_work(master->wq, &master->reg_work);
out:
i3c_master_rpm_put(master);
@@ -3126,6 +3135,7 @@ int i3c_master_register(struct i3c_master_controller *master,
goto err_put_dev;
}
INIT_WORK(&master->hj_work, i3c_master_hj_work_fn);
+ INIT_WORK(&master->reg_work, i3c_master_reg_work_fn);
ret = i3c_master_bus_init(master);
if (ret)
@@ -3151,12 +3161,15 @@ int i3c_master_register(struct i3c_master_controller *master,
/*
* We're done initializing the bus and the controller, we can now
- * register I3C devices discovered during the initial DAA.
+ * register I3C devices discovered during the initial DAA. Device
+ * registration is done via reg_work because that keeps a single
+ * registration code path and ensures the worker is the only writer
+ * of desc->dev. Flush the work to preserve synchronous probe-time
+ * behavior.
*/
master->init_done = true;
- i3c_bus_normaluse_lock(&master->bus);
- i3c_master_register_new_i3c_devs(master);
- i3c_bus_normaluse_unlock(&master->bus);
+ queue_work(master->wq, &master->reg_work);
+ flush_work(&master->reg_work);
if (master->ops->set_dev_nack_retry)
device_create_file(&master->dev, &dev_attr_dev_nack_retry_count);
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index 77e63082b06e..8cdd7be505d3 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -523,6 +523,11 @@ struct i3c_master_controller_ops {
* 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()
+ * @reg_work: work item used to register newly discovered I3C devices with
+ * the driver model. Queued to @wq by i3c_master_do_daa_ext() so
+ * that device registration is deferred out of the DAA caller's
+ * context (notably the resume path), and is skipped if the
+ * controller is shutting down
* @dev_nack_retry_count: retry count when slave device nack
*
* A &struct i3c_master_controller has to be registered to the I3C subsystem
@@ -548,6 +553,7 @@ struct i3c_master_controller {
struct i3c_bus bus;
struct workqueue_struct *wq;
struct work_struct hj_work;
+ struct work_struct reg_work;
unsigned int dev_nack_retry_count;
};
--
2.51.0
--
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH V3 7/8] i3c: master: Export i3c_master_enec_disec_locked()
2026-06-08 5:43 [PATCH V3 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
` (5 preceding siblings ...)
2026-06-08 5:43 ` [PATCH V3 6/8] i3c: master: Defer new-device registration out of DAA caller context Adrian Hunter
@ 2026-06-08 5:43 ` Adrian Hunter
2026-06-08 5:43 ` [PATCH V3 8/8] i3c: mipi-i3c-hci: Add Hot-Join support Adrian Hunter
2026-06-14 20:20 ` [PATCH V3 0/8] i3c: Hot-Join improvements and MIPI HCI " Alexandre Belloni
8 siblings, 0 replies; 10+ messages in thread
From: Adrian Hunter @ 2026-06-08 5:43 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, david.nystrom, linux-i3c, linux-kernel
The existing i3c_master_enec_locked() wrapper always treats a NACKed
ENEC CCC as a failure (M2 error). However, broadcasting ENEC to enable
Hot-Join is legitimately useful even when no I3C devices are currently
present on the bus, in which case the broadcast will be NACKed and
should not be reported as an error.
The underlying helper i3c_master_enec_disec_locked() already accepts a
suppress_m2 flag that lets callers ignore such NACKs. Expose it so that
a subsequent patch enabling Hot-Join events can issue ENEC with M2
suppression.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
Changes in V3:
None
Changes in V2:
Add Frank's Rev'd by
drivers/i3c/master.c | 27 ++++++++++++++++++++++++---
include/linux/i3c/master.h | 2 ++
2 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index c9685379e868..f87bf0099d3c 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -1121,9 +1121,29 @@ int i3c_master_entdaa_locked(struct i3c_master_controller *master)
}
EXPORT_SYMBOL_GPL(i3c_master_entdaa_locked);
-static int i3c_master_enec_disec_locked(struct i3c_master_controller *master,
- u8 addr, bool enable, u8 evts,
- bool suppress_m2)
+/**
+ * i3c_master_enec_disec_locked() - send an ENEC or DISEC CCC command
+ * @master: master used to send frames on the bus
+ * @addr: a valid I3C slave address or %I3C_BROADCAST_ADDR
+ * @enable: true to send ENEC, false to send DISEC
+ * @evts: events to enable or disable
+ * @suppress_m2: if true, treat an M2 (NACK) error from the CCC as success
+ *
+ * Send an ENEC or DISEC CCC command to enable or disable some or all events
+ * coming from a specific slave, or all devices if @addr is
+ * %I3C_BROADCAST_ADDR.
+ *
+ * When @suppress_m2 is true, a NACK of the broadcast (which can happen when
+ * no devices are present on the bus) is not reported as an error. This is
+ * useful for callers that want to configure event reporting unconditionally,
+ * regardless of whether any devices are currently on the bus.
+ *
+ * This function must be called with the bus lock held in write mode.
+ *
+ * Return: 0 in case of success, or a negative error code otherwise.
+ */
+int i3c_master_enec_disec_locked(struct i3c_master_controller *master, u8 addr,
+ bool enable, u8 evts, bool suppress_m2)
{
struct i3c_ccc_events *events;
struct i3c_ccc_cmd_dest dest;
@@ -1148,6 +1168,7 @@ static int i3c_master_enec_disec_locked(struct i3c_master_controller *master,
return ret;
}
+EXPORT_SYMBOL_GPL(i3c_master_enec_disec_locked);
/**
* i3c_master_disec_locked() - send a DISEC CCC command
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index 8cdd7be505d3..e2c831fb5354 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -607,6 +607,8 @@ int i3c_master_disec_locked(struct i3c_master_controller *master, u8 addr,
u8 evts);
int i3c_master_enec_locked(struct i3c_master_controller *master, u8 addr,
u8 evts);
+int i3c_master_enec_disec_locked(struct i3c_master_controller *master, u8 addr,
+ bool enable, u8 evts, bool suppress_m2);
int i3c_master_entdaa_locked(struct i3c_master_controller *master);
int i3c_master_defslvs_locked(struct i3c_master_controller *master);
--
2.51.0
--
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH V3 8/8] i3c: mipi-i3c-hci: Add Hot-Join support
2026-06-08 5:43 [PATCH V3 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
` (6 preceding siblings ...)
2026-06-08 5:43 ` [PATCH V3 7/8] i3c: master: Export i3c_master_enec_disec_locked() Adrian Hunter
@ 2026-06-08 5:43 ` Adrian Hunter
2026-06-14 20:20 ` [PATCH V3 0/8] i3c: Hot-Join improvements and MIPI HCI " Alexandre Belloni
8 siblings, 0 replies; 10+ messages in thread
From: Adrian Hunter @ 2026-06-08 5:43 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, david.nystrom, linux-i3c, linux-kernel
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>
---
Changes in V3:
None
Changes in V2:
Add Frank's Rev'd by
drivers/i3c/master/mipi-i3c-hci/core.c | 50 ++++++++++++++++++++++++--
drivers/i3c/master/mipi-i3c-hci/dma.c | 5 +++
drivers/i3c/master/mipi-i3c-hci/hci.h | 1 +
drivers/i3c/master/mipi-i3c-hci/pio.c | 5 +++
4 files changed, 58 insertions(+), 3 deletions(-)
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index c6edbbedfdd7..53797841b63f 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -392,11 +392,52 @@ static int i3c_hci_send_ccc_cmd(struct i3c_master_controller *m,
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;
}
diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c
index 5c6ae2055618..87622d6f817e 100644
--- a/drivers/i3c/master/mipi-i3c-hci/dma.c
+++ b/drivers/i3c/master/mipi-i3c-hci/dma.c
@@ -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,
diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h
index 30297823ca85..41d31a53abd3 100644
--- a/drivers/i3c/master/mipi-i3c-hci/hci.h
+++ b/drivers/i3c/master/mipi-i3c-hci/hci.h
@@ -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;
diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c
index 6b8cc5f2b4d2..b5ae1cfaa9e0 100644
--- a/drivers/i3c/master/mipi-i3c-hci/pio.c
+++ b/drivers/i3c/master/mipi-i3c-hci/pio.c
@@ -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,
--
2.51.0
--
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH V3 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support
2026-06-08 5:43 [PATCH V3 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
` (7 preceding siblings ...)
2026-06-08 5:43 ` [PATCH V3 8/8] i3c: mipi-i3c-hci: Add Hot-Join support Adrian Hunter
@ 2026-06-14 20:20 ` Alexandre Belloni
8 siblings, 0 replies; 10+ messages in thread
From: Alexandre Belloni @ 2026-06-14 20:20 UTC (permalink / raw)
To: Adrian Hunter; +Cc: Frank.Li, david.nystrom, linux-i3c, linux-kernel
On Mon, 08 Jun 2026 08:43:04 +0300, Adrian Hunter wrote:
> This series tightens the I3C core's handling of Hot-Join across system
> suspend, shutdown and unregister, consolidates the per-driver Hot-Join
> worker into the core, and finally wires the MIPI I3C HCI driver into the
> new Hot-Join framework.
>
>
> Note all patches have now been reviewed by Frank.
>
> [...]
Applied, thanks!
[1/8] i3c: master: Make hot-join workqueue freezable to block hot-join during suspend
https://git.kernel.org/i3c/c/527756cb9ebb
[2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock
https://git.kernel.org/i3c/c/5b130aadc36b
[3/8] i3c: master: Consolidate Hot-Join DAA work in the core
https://git.kernel.org/i3c/c/828c6130235d
[4/8] i3c: master: Ensure Hot-Join operations are stopped on shutdown
https://git.kernel.org/i3c/c/8323e783dc39
[5/8] i3c: dw: Drop redundant Hot-Join cancel_work_sync() in shutdown
https://git.kernel.org/i3c/c/0cbefeafd2cb
[6/8] i3c: master: Defer new-device registration out of DAA caller context
https://git.kernel.org/i3c/c/3f79dac3ea1c
[7/8] i3c: master: Export i3c_master_enec_disec_locked()
https://git.kernel.org/i3c/c/4bfba7b360da
[8/8] i3c: mipi-i3c-hci: Add Hot-Join support
https://git.kernel.org/i3c/c/32fcf0039814
Best regards,
--
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
--
linux-i3c mailing list
linux-i3c@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-i3c
^ permalink raw reply [flat|nested] 10+ messages in thread