From 990c149c61ee45da4fb6372e6b2fdd9808414e7a Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:27:00 +0200 Subject: [PATCH] i3c: master: Introduce optional Runtime PM support Master drivers currently manage Runtime PM individually, but all require runtime resume for bus operations. This can be centralized in common code. Add optional Runtime PM support to ensure the parent device is runtime resumed before bus operations and auto-suspended afterward. Notably, do not call ->bus_cleanup() if runtime resume fails. Master drivers that opt-in to core runtime PM support must take that into account. Also provide an option to allow IBIs and hot-joins while runtime suspended. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-20-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/device.c | 46 +++++++++++++++++-- drivers/i3c/internals.h | 4 ++ drivers/i3c/master.c | 93 +++++++++++++++++++++++++++++++++++--- include/linux/i3c/master.h | 4 ++ 4 files changed, 138 insertions(+), 9 deletions(-) diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c index 8a156f5ad6929..101eaa77de686 100644 --- a/drivers/i3c/device.c +++ b/drivers/i3c/device.c @@ -46,10 +46,16 @@ int i3c_device_do_xfers(struct i3c_device *dev, struct i3c_xfer *xfers, return -EINVAL; } + ret = i3c_bus_rpm_get(dev->bus); + if (ret) + return ret; + i3c_bus_normaluse_lock(dev->bus); ret = i3c_dev_do_xfers_locked(dev->desc, xfers, nxfers, mode); i3c_bus_normaluse_unlock(dev->bus); + i3c_bus_rpm_put(dev->bus); + return ret; } EXPORT_SYMBOL_GPL(i3c_device_do_xfers); @@ -66,10 +72,16 @@ int i3c_device_do_setdasa(struct i3c_device *dev) { int ret; + ret = i3c_bus_rpm_get(dev->bus); + if (ret) + return ret; + i3c_bus_normaluse_lock(dev->bus); ret = i3c_dev_setdasa_locked(dev->desc); i3c_bus_normaluse_unlock(dev->bus); + i3c_bus_rpm_put(dev->bus); + return ret; } EXPORT_SYMBOL_GPL(i3c_device_do_setdasa); @@ -106,16 +118,27 @@ EXPORT_SYMBOL_GPL(i3c_device_get_info); */ int i3c_device_disable_ibi(struct i3c_device *dev) { - int ret = -ENOENT; + int ret; + + if (i3c_bus_rpm_ibi_allowed(dev->bus)) { + ret = i3c_bus_rpm_get(dev->bus); + if (ret) + return ret; + } i3c_bus_normaluse_lock(dev->bus); if (dev->desc) { mutex_lock(&dev->desc->ibi_lock); ret = i3c_dev_disable_ibi_locked(dev->desc); mutex_unlock(&dev->desc->ibi_lock); + } else { + ret = -ENOENT; } i3c_bus_normaluse_unlock(dev->bus); + if (!ret || i3c_bus_rpm_ibi_allowed(dev->bus)) + i3c_bus_rpm_put(dev->bus); + return ret; } EXPORT_SYMBOL_GPL(i3c_device_disable_ibi); @@ -135,16 +158,25 @@ EXPORT_SYMBOL_GPL(i3c_device_disable_ibi); */ int i3c_device_enable_ibi(struct i3c_device *dev) { - int ret = -ENOENT; + int ret; + + ret = i3c_bus_rpm_get(dev->bus); + if (ret) + return ret; i3c_bus_normaluse_lock(dev->bus); if (dev->desc) { mutex_lock(&dev->desc->ibi_lock); ret = i3c_dev_enable_ibi_locked(dev->desc); mutex_unlock(&dev->desc->ibi_lock); + } else { + ret = -ENOENT; } i3c_bus_normaluse_unlock(dev->bus); + if (ret || i3c_bus_rpm_ibi_allowed(dev->bus)) + i3c_bus_rpm_put(dev->bus); + return ret; } EXPORT_SYMBOL_GPL(i3c_device_enable_ibi); @@ -163,19 +195,27 @@ EXPORT_SYMBOL_GPL(i3c_device_enable_ibi); int i3c_device_request_ibi(struct i3c_device *dev, const struct i3c_ibi_setup *req) { - int ret = -ENOENT; + int ret; if (!req->handler || !req->num_slots) return -EINVAL; + ret = i3c_bus_rpm_get(dev->bus); + if (ret) + return ret; + i3c_bus_normaluse_lock(dev->bus); if (dev->desc) { mutex_lock(&dev->desc->ibi_lock); ret = i3c_dev_request_ibi_locked(dev->desc, req); mutex_unlock(&dev->desc->ibi_lock); + } else { + ret = -ENOENT; } i3c_bus_normaluse_unlock(dev->bus); + i3c_bus_rpm_put(dev->bus); + return ret; } EXPORT_SYMBOL_GPL(i3c_device_request_ibi); diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h index f609e5098137c..0f1f3f7666237 100644 --- a/drivers/i3c/internals.h +++ b/drivers/i3c/internals.h @@ -11,6 +11,10 @@ #include #include +int __must_check i3c_bus_rpm_get(struct i3c_bus *bus); +void i3c_bus_rpm_put(struct i3c_bus *bus); +bool i3c_bus_rpm_ibi_allowed(struct i3c_bus *bus); + void i3c_bus_normaluse_lock(struct i3c_bus *bus); void i3c_bus_normaluse_unlock(struct i3c_bus *bus); diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 71583cc4d1970..80dda0e855589 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -106,6 +106,38 @@ static struct i3c_master_controller *dev_to_i3cmaster(struct device *dev) return container_of(dev, struct i3c_master_controller, dev); } +static int __must_check i3c_master_rpm_get(struct i3c_master_controller *master) +{ + int ret = master->rpm_allowed ? pm_runtime_resume_and_get(master->dev.parent) : 0; + + if (ret < 0) { + dev_err(master->dev.parent, "runtime resume failed, error %d\n", ret); + return ret; + } + return 0; +} + +static void i3c_master_rpm_put(struct i3c_master_controller *master) +{ + if (master->rpm_allowed) + pm_runtime_put_autosuspend(master->dev.parent); +} + +int i3c_bus_rpm_get(struct i3c_bus *bus) +{ + return i3c_master_rpm_get(i3c_bus_to_i3c_master(bus)); +} + +void i3c_bus_rpm_put(struct i3c_bus *bus) +{ + i3c_master_rpm_put(i3c_bus_to_i3c_master(bus)); +} + +bool i3c_bus_rpm_ibi_allowed(struct i3c_bus *bus) +{ + return i3c_bus_to_i3c_master(bus)->rpm_ibi_allowed; +} + static const struct device_type i3c_device_type; static struct i3c_bus *dev_to_i3cbus(struct device *dev) @@ -611,6 +643,12 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable) if (!master->ops->enable_hotjoin || !master->ops->disable_hotjoin) return -EINVAL; + if (enable || master->rpm_ibi_allowed) { + ret = i3c_master_rpm_get(master); + if (ret) + return ret; + } + i3c_bus_normaluse_lock(&master->bus); if (enable) @@ -623,6 +661,9 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable) i3c_bus_normaluse_unlock(&master->bus); + if ((enable && ret) || (!enable && !ret) || master->rpm_ibi_allowed) + i3c_master_rpm_put(master); + return ret; } @@ -1745,18 +1786,23 @@ int i3c_master_do_daa(struct i3c_master_controller *master) { int ret; + ret = i3c_master_rpm_get(master); + if (ret) + return ret; + i3c_bus_maintenance_lock(&master->bus); ret = master->ops->do_daa(master); i3c_bus_maintenance_unlock(&master->bus); if (ret) - return ret; + goto out; i3c_bus_normaluse_lock(&master->bus); i3c_master_register_new_i3c_devs(master); i3c_bus_normaluse_unlock(&master->bus); - - return 0; +out: + i3c_master_rpm_put(master); + return ret; } EXPORT_SYMBOL_GPL(i3c_master_do_daa); @@ -2098,8 +2144,17 @@ err_detach_devs: static void i3c_master_bus_cleanup(struct i3c_master_controller *master) { - if (master->ops->bus_cleanup) - master->ops->bus_cleanup(master); + if (master->ops->bus_cleanup) { + int ret = i3c_master_rpm_get(master); + + if (ret) { + dev_err(&master->dev, + "runtime resume error: master bus_cleanup() not done\n"); + } else { + master->ops->bus_cleanup(master); + i3c_master_rpm_put(master); + } + } i3c_master_detach_free_devs(master); } @@ -2451,6 +2506,10 @@ static int i3c_master_i2c_adapter_xfer(struct i2c_adapter *adap, return -EOPNOTSUPP; } + ret = i3c_master_rpm_get(master); + if (ret) + return ret; + i3c_bus_normaluse_lock(&master->bus); dev = i3c_master_find_i2c_dev_by_addr(master, addr); if (!dev) @@ -2459,6 +2518,8 @@ static int i3c_master_i2c_adapter_xfer(struct i2c_adapter *adap, ret = master->ops->i2c_xfers(dev, xfers, nxfers); i3c_bus_normaluse_unlock(&master->bus); + i3c_master_rpm_put(master); + return ret ? ret : nxfers; } @@ -2561,6 +2622,10 @@ static int i3c_i2c_notifier_call(struct notifier_block *nb, unsigned long action master = i2c_adapter_to_i3c_master(adap); + ret = i3c_master_rpm_get(master); + if (ret) + return ret; + i3c_bus_maintenance_lock(&master->bus); switch (action) { case BUS_NOTIFY_ADD_DEVICE: @@ -2574,6 +2639,8 @@ static int i3c_i2c_notifier_call(struct notifier_block *nb, unsigned long action } i3c_bus_maintenance_unlock(&master->bus); + i3c_master_rpm_put(master); + return ret; } @@ -2911,6 +2978,10 @@ int i3c_master_register(struct i3c_master_controller *master, INIT_LIST_HEAD(&master->boardinfo.i2c); INIT_LIST_HEAD(&master->boardinfo.i3c); + ret = i3c_master_rpm_get(master); + if (ret) + return ret; + device_initialize(&master->dev); master->dev.dma_mask = parent->dma_mask; @@ -2994,6 +3065,8 @@ int i3c_master_register(struct i3c_master_controller *master, if (master->ops->set_dev_nack_retry) device_create_file(&master->dev, &dev_attr_dev_nack_retry_count); + i3c_master_rpm_put(master); + return 0; err_del_dev: @@ -3003,6 +3076,7 @@ err_cleanup_bus: i3c_master_bus_cleanup(master); err_put_dev: + i3c_master_rpm_put(master); put_device(&master->dev); return ret; @@ -3151,8 +3225,15 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev) return; if (dev->ibi->enabled) { + int ret; + dev_err(&master->dev, "Freeing IBI that is still enabled\n"); - if (i3c_dev_disable_ibi_locked(dev)) + ret = i3c_master_rpm_get(master); + if (!ret) { + ret = i3c_dev_disable_ibi_locked(dev); + i3c_master_rpm_put(master); + } + if (ret) dev_err(&master->dev, "Failed to disable IBI before freeing\n"); } diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index d231c4fbc58b8..38a8213954267 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -509,6 +509,8 @@ struct i3c_master_controller_ops { * @secondary: true if the master is a secondary master * @init_done: true when the bus initialization is done * @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 * @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 @@ -533,6 +535,8 @@ struct i3c_master_controller { unsigned int secondary : 1; unsigned int init_done : 1; unsigned int hotjoin: 1; + unsigned int rpm_allowed: 1; + unsigned int rpm_ibi_allowed: 1; struct { struct list_head i3c; struct list_head i2c; -- 2.47.3