]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
i3c: master: Introduce optional Runtime PM support
authorAdrian Hunter <adrian.hunter@intel.com>
Tue, 13 Jan 2026 07:27:00 +0000 (09:27 +0200)
committerAlexandre Belloni <alexandre.belloni@bootlin.com>
Wed, 14 Jan 2026 16:21:10 +0000 (17:21 +0100)
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 <adrian.hunter@intel.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Link: https://patch.msgid.link/20260113072702.16268-20-adrian.hunter@intel.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
drivers/i3c/device.c
drivers/i3c/internals.h
drivers/i3c/master.c
include/linux/i3c/master.h

index 8a156f5ad6929402eb92b152d2e80754dd5a2387..101eaa77de68607c704afda73cafb83dbee80603 100644 (file)
@@ -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);
index f609e5098137c1b00db1830a176bb44c2802eb6f..0f1f3f766623793f0ecbcd4559caabc625df5d1c 100644 (file)
 #include <linux/i3c/master.h>
 #include <linux/io.h>
 
+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);
 
index 71583cc4d19700d90092f8e5affef4a7e0117d6d..80dda0e855589142ece253ccf6a87150ed8314fb 100644 (file)
@@ -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");
        }
 
index d231c4fbc58b855de8ec86581216f706cc65cbd2..38a82139542672ce603a49b092ca64d8549f02ae 100644 (file)
@@ -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;