From b58f47eb392680d4c6626c8b3b1fcf6412a0a02c Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Fri, 12 Dec 2025 17:02:55 +0800 Subject: [PATCH 01/52] i3c: add sysfs entry and attribute for Device NACK Retry count Document sysfs attribute dev_nack_retry_cnt that controls the number of automatic retries performed by the I3C controller when a target device returns a NACK Add a `dev_nack_retry_count` sysfs attribute to allow reading and updating the device NACK retry count. A new `dev_nack_retry_count` field and an optional `set_dev_nack_retry()` callback are added to i3c_master_controller. The attribute is created only when the callback is implemented. Updates are applied under the I3C bus maintenance lock to ensure safe hardware reconfiguration. Signed-off-by: Adrian Ng Ho Yin Reviewed-by: Frank Li Link: https://patch.msgid.link/3c4b5082bde64024fc383c44bebeef89ad3c7ed3.1765529948.git.adrianhoyin.ng@altera.com Signed-off-by: Alexandre Belloni --- Documentation/ABI/testing/sysfs-bus-i3c | 11 +++++++ drivers/i3c/master.c | 39 +++++++++++++++++++++++++ include/linux/i3c/master.h | 6 ++++ 3 files changed, 56 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-i3c b/Documentation/ABI/testing/sysfs-bus-i3c index c812ab180ff4..c1e048957a01 100644 --- a/Documentation/ABI/testing/sysfs-bus-i3c +++ b/Documentation/ABI/testing/sysfs-bus-i3c @@ -161,3 +161,14 @@ Contact: linux-i3c@vger.kernel.org Description: These directories are just symbolic links to /sys/bus/i3c/devices/i3c-/-. + +What: /sys/bus/i3c/devices/i3c-/-/dev_nack_retry_count +KernelVersion: 6.18 +Contact: linux-i3c@vger.kernel.org +Description: + Expose the dev_nak_retry_count which controls the number of + automatic retries that will be performed by the controller when + the target device returns a NACK response. A value of 0 disables + the automatic retries. Exist only when I3C constroller supports + this retry on nack feature. + diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 7f606c871648..5e37ccc758e4 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -683,6 +683,39 @@ static ssize_t hotjoin_show(struct device *dev, struct device_attribute *da, cha static DEVICE_ATTR_RW(hotjoin); +static ssize_t dev_nack_retry_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", dev_to_i3cmaster(dev)->dev_nack_retry_count); +} + +static ssize_t dev_nack_retry_count_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i3c_bus *i3cbus = dev_to_i3cbus(dev); + struct i3c_master_controller *master = dev_to_i3cmaster(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 0, &val); + if (ret) + return ret; + + i3c_bus_maintenance_lock(i3cbus); + ret = master->ops->set_dev_nack_retry(master, val); + i3c_bus_maintenance_unlock(i3cbus); + + if (ret) + return ret; + + master->dev_nack_retry_count = val; + + return count; +} + +static DEVICE_ATTR_RW(dev_nack_retry_count); + static struct attribute *i3c_masterdev_attrs[] = { &dev_attr_mode.attr, &dev_attr_current_master.attr, @@ -2959,6 +2992,9 @@ int i3c_master_register(struct i3c_master_controller *master, i3c_master_register_new_i3c_devs(master); i3c_bus_normaluse_unlock(&master->bus); + if (master->ops->set_dev_nack_retry) + device_create_file(&master->dev, &dev_attr_dev_nack_retry_count); + return 0; err_del_dev: @@ -2984,6 +3020,9 @@ void i3c_master_unregister(struct i3c_master_controller *master) { i3c_bus_notify(&master->bus, I3C_NOTIFY_BUS_REMOVE); + if (master->ops->set_dev_nack_retry) + device_remove_file(&master->dev, &dev_attr_dev_nack_retry_count); + i3c_master_i2c_adapter_cleanup(master); i3c_master_unregister_i3c_devs(master); i3c_master_bus_cleanup(master); diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 58d01ed4cce7..d231c4fbc58b 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -462,6 +462,8 @@ struct i3c_bus { * @enable_hotjoin: enable hot join event detect. * @disable_hotjoin: disable hot join event detect. * @set_speed: adjust I3C open drain mode timing. + * @set_dev_nack_retry: configure device NACK retry count for the master + * controller. */ struct i3c_master_controller_ops { int (*bus_init)(struct i3c_master_controller *master); @@ -491,6 +493,8 @@ struct i3c_master_controller_ops { int (*enable_hotjoin)(struct i3c_master_controller *master); int (*disable_hotjoin)(struct i3c_master_controller *master); int (*set_speed)(struct i3c_master_controller *master, enum i3c_open_drain_speed speed); + int (*set_dev_nack_retry)(struct i3c_master_controller *master, + unsigned long dev_nack_retry_cnt); }; /** @@ -514,6 +518,7 @@ 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 + * @dev_nack_retry_count: retry count when slave device nack * * A &struct i3c_master_controller has to be registered to the I3C subsystem * through i3c_master_register(). None of &struct i3c_master_controller fields @@ -534,6 +539,7 @@ struct i3c_master_controller { } boardinfo; struct i3c_bus bus; struct workqueue_struct *wq; + unsigned int dev_nack_retry_count; }; /** From ec17f14309481318df4af2a0c7f2aa7da9e7ebcb Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Fri, 12 Dec 2025 17:02:56 +0800 Subject: [PATCH 02/52] i3c: dw: Add support for Device NACK Retry configuration The DesignWare I3C controller supports automatically retrying transactions when a device NACKs. This is useful for slave devices that may be temporarily busy and not ready to respond immediately. Add new ops to configure all active DAT entry with dev_nack_retry during runtime. Returns error when value exceeds hw specified limit. Signed-off-by: Adrian Ng Ho Yin Reviewed-by: Frank Li Link: https://patch.msgid.link/f09ee67e61d31f0a12a0bf48f01e9057ca9e2fb7.1765529948.git.adrianhoyin.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/dw-i3c-master.c | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 889e2ed5bc83..843861ba62bd 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -5,6 +5,7 @@ * Author: Vitor Soares */ +#include #include #include #include @@ -204,8 +205,12 @@ #define EXTENDED_CAPABILITY 0xe8 #define SLAVE_CONFIG 0xec +#define DW_I3C_DEV_NACK_RETRY_CNT_MAX 0x3 +#define DEV_ADDR_TABLE_DEV_NACK_RETRY_MASK GENMASK(30, 29) #define DEV_ADDR_TABLE_IBI_MDB BIT(12) #define DEV_ADDR_TABLE_SIR_REJECT BIT(13) +#define DEV_ADDR_TABLE_DEV_NACK_RETRY_CNT(x) \ + FIELD_PREP(DEV_ADDR_TABLE_DEV_NACK_RETRY_MASK, (x)) #define DEV_ADDR_TABLE_LEGACY_I2C_DEV BIT(31) #define DEV_ADDR_TABLE_DYNAMIC_ADDR(x) (((x) << 16) & GENMASK(23, 16)) #define DEV_ADDR_TABLE_STATIC_ADDR(x) ((x) & GENMASK(6, 0)) @@ -1489,6 +1494,40 @@ static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } +static int dw_i3c_master_set_dev_nack_retry(struct i3c_master_controller *m, + unsigned long dev_nack_retry_cnt) +{ + struct dw_i3c_master *master = to_dw_i3c_master(m); + u32 reg; + int i; + + if (dev_nack_retry_cnt > DW_I3C_DEV_NACK_RETRY_CNT_MAX) { + dev_err(&master->base.dev, + "Value %ld exceeds maximum %d\n", + dev_nack_retry_cnt, DW_I3C_DEV_NACK_RETRY_CNT_MAX); + return -ERANGE; + } + + /* + * Update DAT entries for all currently attached devices. + * We directly iterate through the master's device array. + */ + for (i = 0; i < master->maxdevs; i++) { + /* Skip free/empty slots */ + if (master->free_pos & BIT(i)) + continue; + + reg = readl(master->regs + + DEV_ADDR_TABLE_LOC(master->datstartaddr, i)); + reg &= ~DEV_ADDR_TABLE_DEV_NACK_RETRY_MASK; + reg |= DEV_ADDR_TABLE_DEV_NACK_RETRY_CNT(dev_nack_retry_cnt); + writel(reg, master->regs + + DEV_ADDR_TABLE_LOC(master->datstartaddr, i)); + } + + return 0; +} + static const struct i3c_master_controller_ops dw_mipi_i3c_ops = { .bus_init = dw_i3c_master_bus_init, .bus_cleanup = dw_i3c_master_bus_cleanup, @@ -1509,6 +1548,7 @@ static const struct i3c_master_controller_ops dw_mipi_i3c_ops = { .recycle_ibi_slot = dw_i3c_master_recycle_ibi_slot, .enable_hotjoin = dw_i3c_master_enable_hotjoin, .disable_hotjoin = dw_i3c_master_disable_hotjoin, + .set_dev_nack_retry = dw_i3c_master_set_dev_nack_retry, }; /* default platform ops implementations */ From 4cd9d2bf0b56f98347ca1046e4d8acea95bd7ffa Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Fri, 12 Dec 2025 17:02:57 +0800 Subject: [PATCH 03/52] i3c: dw: use FIELD_PREP for device address table macros Add DEV_ADDR_TABLE_DYNAMIC_MASK / DEV_ADDR_TABLE_DYNAMIC_ADDR(x) for dynamic device addresses and DEV_ADDR_TABLE_STATIC_MASK / DEV_ADDR_TABLE_STATIC_ADDR(x) for static device addresses in the I3C address table. Replace manual shift-and-mask with FIELD_PREP() for both dynamic and static addresses for clarity and maintainability. Signed-off-by: Adrian Ng Ho Yin Reviewed-by: Frank Li Link: https://patch.msgid.link/d72896e510db1870d26a794f131f600c7e42cf00.1765529948.git.adrianhoyin.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/dw-i3c-master.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 843861ba62bd..791a8387d4e0 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -207,13 +207,15 @@ #define DW_I3C_DEV_NACK_RETRY_CNT_MAX 0x3 #define DEV_ADDR_TABLE_DEV_NACK_RETRY_MASK GENMASK(30, 29) +#define DEV_ADDR_TABLE_DYNAMIC_MASK GENMASK(23, 16) +#define DEV_ADDR_TABLE_STATIC_MASK GENMASK(6, 0) #define DEV_ADDR_TABLE_IBI_MDB BIT(12) #define DEV_ADDR_TABLE_SIR_REJECT BIT(13) #define DEV_ADDR_TABLE_DEV_NACK_RETRY_CNT(x) \ FIELD_PREP(DEV_ADDR_TABLE_DEV_NACK_RETRY_MASK, (x)) #define DEV_ADDR_TABLE_LEGACY_I2C_DEV BIT(31) -#define DEV_ADDR_TABLE_DYNAMIC_ADDR(x) (((x) << 16) & GENMASK(23, 16)) -#define DEV_ADDR_TABLE_STATIC_ADDR(x) ((x) & GENMASK(6, 0)) +#define DEV_ADDR_TABLE_DYNAMIC_ADDR(x) FIELD_PREP(DEV_ADDR_TABLE_DYNAMIC_MASK, x) +#define DEV_ADDR_TABLE_STATIC_ADDR(x) FIELD_PREP(DEV_ADDR_TABLE_STATIC_MASK, x) #define DEV_ADDR_TABLE_LOC(start, idx) ((start) + ((idx) << 2)) #define I3C_BUS_SDR1_SCL_RATE 8000000 From de28e002df2e2d44138174a158883e703e216a06 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Fri, 12 Dec 2025 17:02:58 +0800 Subject: [PATCH 04/52] i3c: dw: Preserve DAT entry bits when restoring addresses Update dw_i3c_master_restore_addrs() to preserve existing bits in each Device Address Table (DAT) entry when restoring addresses. This prevents overwriting configuration bits during PM runtime resumes. Signed-off-by: Adrian Ng Ho Yin Reviewed-by: Frank Li Link: https://patch.msgid.link/46112c0da44110f46709cb0e7a4595e312b95c10.1765529948.git.adrianhoyin.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/dw-i3c-master.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 791a8387d4e0..48af00659e19 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1718,11 +1718,16 @@ static void dw_i3c_master_restore_addrs(struct dw_i3c_master *master) if (master->free_pos & BIT(pos)) continue; - if (master->devs[pos].is_i2c_addr) - reg_val = DEV_ADDR_TABLE_LEGACY_I2C_DEV | + reg_val = readl(master->regs + DEV_ADDR_TABLE_LOC(master->datstartaddr, pos)); + + if (master->devs[pos].is_i2c_addr) { + reg_val &= ~DEV_ADDR_TABLE_STATIC_MASK; + reg_val |= DEV_ADDR_TABLE_LEGACY_I2C_DEV | DEV_ADDR_TABLE_STATIC_ADDR(master->devs[pos].addr); - else - reg_val = DEV_ADDR_TABLE_DYNAMIC_ADDR(master->devs[pos].addr); + } else { + reg_val &= ~DEV_ADDR_TABLE_DYNAMIC_MASK; + reg_val |= DEV_ADDR_TABLE_DYNAMIC_ADDR(master->devs[pos].addr); + } writel(reg_val, master->regs + DEV_ADDR_TABLE_LOC(master->datstartaddr, pos)); } From 9904232ae30bc65d7822f50c885987a7876f0beb Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 15 Dec 2025 12:24:04 -0500 Subject: [PATCH 05/52] i3c: drop i3c_priv_xfer and i3c_device_do_priv_xfers() Drop i3c_priv_xfer and i3c_device_do_priv_xfers() after all driver switch to use new API. Signed-off-by: Frank Li Link: https://patch.msgid.link/20251215172405.2982801-1-Frank.Li@nxp.com Signed-off-by: Alexandre Belloni --- include/linux/i3c/device.h | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h index 9fcb6410a584..39a1ff180871 100644 --- a/include/linux/i3c/device.h +++ b/include/linux/i3c/device.h @@ -25,7 +25,7 @@ * @I3C_ERROR_M2: M2 error * * These are the standard error codes as defined by the I3C specification. - * When -EIO is returned by the i3c_device_do_priv_xfers() or + * When -EIO is returned by the i3c_device_do_i3c_xfers() or * i3c_device_send_hdr_cmds() one can check the error code in * &struct_i3c_xfer.err or &struct i3c_hdr_cmd.err to get a better idea of * what went wrong. @@ -79,9 +79,6 @@ struct i3c_xfer { enum i3c_error_code err; }; -/* keep back compatible */ -#define i3c_priv_xfer i3c_xfer - /** * enum i3c_dcr - I3C DCR values * @I3C_DCR_GENERIC_DEVICE: generic I3C device @@ -311,13 +308,6 @@ static __always_inline void i3c_i2c_driver_unregister(struct i3c_driver *i3cdrv, int i3c_device_do_xfers(struct i3c_device *dev, struct i3c_xfer *xfers, int nxfers, enum i3c_xfer_mode mode); -static inline int i3c_device_do_priv_xfers(struct i3c_device *dev, - struct i3c_xfer *xfers, - int nxfers) -{ - return i3c_device_do_xfers(dev, xfers, nxfers, I3C_SDR); -} - int i3c_device_do_setdasa(struct i3c_device *dev); void i3c_device_get_info(const struct i3c_device *dev, struct i3c_device_info *info); From 3c9ffb4db787428a5851d5865823ab23842d5103 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 15 Dec 2025 15:08:51 -0500 Subject: [PATCH 06/52] i3c: master: svc: Initialize 'dev' to NULL in svc_i3c_master_ibi_isr() Initialize the 'dev' pointer to NULL in svc_i3c_master_ibi_isr() and add a NULL check in the error path. Reported-by: kernel test robot Closes: https://lore.kernel.org/r/202512131016.YCKIsDXM-lkp@intel.com/ Signed-off-by: Frank Li Link: https://patch.msgid.link/20251215200852.3079073-1-Frank.Li@nxp.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/svc-i3c-master.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index a62f22ff8b57..857504d36e18 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -533,8 +533,8 @@ static int svc_i3c_master_handle_ibi_won(struct svc_i3c_master *master, u32 msta static void svc_i3c_master_ibi_isr(struct svc_i3c_master *master) { struct svc_i3c_i2c_dev_data *data; + struct i3c_dev_desc *dev = NULL; unsigned int ibitype, ibiaddr; - struct i3c_dev_desc *dev; u32 status, val; int ret; @@ -627,7 +627,7 @@ static void svc_i3c_master_ibi_isr(struct svc_i3c_master *master) * for the slave to interrupt again. */ if (svc_i3c_master_error(master)) { - if (master->ibi.tbq_slot) { + if (master->ibi.tbq_slot && dev) { data = i3c_dev_get_master_data(dev); i3c_generic_ibi_recycle_slot(data->ibi_pool, master->ibi.tbq_slot); From ceff3bc1518a6f3c897e6187ae6e1213d53a611a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 24 Dec 2025 13:45:52 +0100 Subject: [PATCH 07/52] i3c: master: Simplify with scoped for each OF child loop Use scoped for-each loop when iterating over device nodes to make code a bit simpler. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Frank Li Link: https://patch.msgid.link/20251224124551.208778-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 5e37ccc758e4..e551b1de5d7b 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2403,19 +2403,16 @@ static int of_populate_i3c_bus(struct i3c_master_controller *master) { struct device *dev = &master->dev; struct device_node *i3cbus_np = dev->of_node; - struct device_node *node; int ret; u32 val; if (!i3cbus_np) return 0; - for_each_available_child_of_node(i3cbus_np, node) { + for_each_available_child_of_node_scoped(i3cbus_np, node) { ret = of_i3c_master_add_dev(master, node); - if (ret) { - of_node_put(node); + if (ret) return ret; - } } /* From 8564f88df2020357430280e3e1d8e8da5d1b19e1 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 30 Dec 2025 09:57:18 -0500 Subject: [PATCH 08/52] i3c: Add stub functions when I3C support is disabled When I3C is disabled, unused functions are removed by the linker because the driver relies on regmap and no I3C devices are registered, so normal I3C paths are never called. However, some drivers may still call low-level I3C transfer helpers. Provide stub implementations to avoid adding conditional ifdefs everywhere. Add stubs for i3c_device_do_xfers() and i3c_device_get_supported_xfer_mode() only. Other stubs will be introduced when they are actually needed. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202512230418.nu3V6Yua-lkp@intel.com/ Signed-off-by: Frank Li Link: https://patch.msgid.link/20251230145718.4088694-1-Frank.Li@nxp.com Signed-off-by: Alexandre Belloni --- include/linux/i3c/device.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h index 39a1ff180871..971d53349b6f 100644 --- a/include/linux/i3c/device.h +++ b/include/linux/i3c/device.h @@ -305,8 +305,23 @@ static __always_inline void i3c_i2c_driver_unregister(struct i3c_driver *i3cdrv, i3c_i2c_driver_unregister, \ __i2cdrv) +#if IS_ENABLED(CONFIG_I3C) int i3c_device_do_xfers(struct i3c_device *dev, struct i3c_xfer *xfers, int nxfers, enum i3c_xfer_mode mode); +u32 i3c_device_get_supported_xfer_mode(struct i3c_device *dev); +#else +static inline int +i3c_device_do_xfers(struct i3c_device *dev, struct i3c_xfer *xfers, + int nxfers, enum i3c_xfer_mode mode) +{ + return -EOPNOTSUPP; +} + +static inline u32 i3c_device_get_supported_xfer_mode(struct i3c_device *dev) +{ + return 0; +} +#endif int i3c_device_do_setdasa(struct i3c_device *dev); @@ -348,6 +363,5 @@ int i3c_device_request_ibi(struct i3c_device *dev, void i3c_device_free_ibi(struct i3c_device *dev); int i3c_device_enable_ibi(struct i3c_device *dev); int i3c_device_disable_ibi(struct i3c_device *dev); -u32 i3c_device_get_supported_xfer_mode(struct i3c_device *dev); #endif /* I3C_DEV_H */ From 840688d8e65cc6b0d1ac1aa01b9374ae56ff3dfc Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 6 Jan 2026 18:44:06 +0200 Subject: [PATCH 09/52] i3c: mipi-i3c-hci: Remove duplicate blank lines Remove duplicate blank lines from mipi-i3c-hci code. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260106164416.67074-2-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/cmd_v1.c | 2 -- drivers/i3c/master/mipi-i3c-hci/cmd_v2.c | 2 -- drivers/i3c/master/mipi-i3c-hci/core.c | 2 -- drivers/i3c/master/mipi-i3c-hci/dat_v1.c | 1 - drivers/i3c/master/mipi-i3c-hci/dma.c | 2 -- drivers/i3c/master/mipi-i3c-hci/ext_caps.c | 1 - drivers/i3c/master/mipi-i3c-hci/ext_caps.h | 1 - drivers/i3c/master/mipi-i3c-hci/hci.h | 5 ----- drivers/i3c/master/mipi-i3c-hci/pio.c | 1 - 9 files changed, 17 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c b/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c index eb8a3ae2990d..fe260461e7e6 100644 --- a/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c +++ b/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c @@ -15,7 +15,6 @@ #include "dat.h" #include "dct.h" - /* * Address Assignment Command */ @@ -100,7 +99,6 @@ #define CMD_M0_VENDOR_INFO_PRESENT W0_BIT_( 7) #define CMD_M0_TID(v) FIELD_PREP(W0_MASK( 6, 3), v) - /* Data Transfer Speed and Mode */ enum hci_cmd_mode { MODE_I3C_SDR0 = 0x0, diff --git a/drivers/i3c/master/mipi-i3c-hci/cmd_v2.c b/drivers/i3c/master/mipi-i3c-hci/cmd_v2.c index efb4326a25b7..3729e6419581 100644 --- a/drivers/i3c/master/mipi-i3c-hci/cmd_v2.c +++ b/drivers/i3c/master/mipi-i3c-hci/cmd_v2.c @@ -16,7 +16,6 @@ #include "cmd.h" #include "xfer_mode_rate.h" - /* * Unified Data Transfer Command */ @@ -62,7 +61,6 @@ #define CMD_A0_ASSIGN_ADDRESS(v) FIELD_PREP(W0_MASK( 14, 8), v) #define CMD_A0_TID(v) FIELD_PREP(W0_MASK( 6, 3), v) - static unsigned int get_i3c_rate_idx(struct i3c_hci *hci) { struct i3c_bus *bus = i3c_master_get_bus(&hci->master); diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 607d77ab0e54..211321f73e02 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -21,7 +21,6 @@ #include "cmd.h" #include "dat.h" - /* * Host Controller Capabilities and Operation Registers */ @@ -109,7 +108,6 @@ #define DEV_CTX_BASE_LO 0x60 #define DEV_CTX_BASE_HI 0x64 - static inline struct i3c_hci *to_i3c_hci(struct i3c_master_controller *m) { return container_of(m, struct i3c_hci, master); diff --git a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c index 85c4916972e4..cc5d2deb23ab 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c +++ b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c @@ -15,7 +15,6 @@ #include "hci.h" #include "dat.h" - /* * Device Address Table Structure */ diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c index c401a9425cdc..f20db2899989 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dma.c +++ b/drivers/i3c/master/mipi-i3c-hci/dma.c @@ -20,7 +20,6 @@ #include "cmd.h" #include "ibi.h" - /* * Software Parameter Values (somewhat arb itrary for now). * Some of them could be determined at run time eventually. @@ -124,7 +123,6 @@ #define DATA_BUF_IOC BIT(30) /* Interrupt on Completion */ #define DATA_BUF_BLOCK_SIZE GENMASK(15, 0) - struct hci_rh_data { void __iomem *regs; void *xfer, *resp, *ibi_status, *ibi_data; diff --git a/drivers/i3c/master/mipi-i3c-hci/ext_caps.c b/drivers/i3c/master/mipi-i3c-hci/ext_caps.c index 7714f00ea9cc..40939af0b0e3 100644 --- a/drivers/i3c/master/mipi-i3c-hci/ext_caps.c +++ b/drivers/i3c/master/mipi-i3c-hci/ext_caps.c @@ -16,7 +16,6 @@ #include "ext_caps.h" #include "xfer_mode_rate.h" - /* Extended Capability Header */ #define CAP_HEADER_LENGTH GENMASK(23, 8) #define CAP_HEADER_ID GENMASK(7, 0) diff --git a/drivers/i3c/master/mipi-i3c-hci/ext_caps.h b/drivers/i3c/master/mipi-i3c-hci/ext_caps.h index 9df17822fdb4..b15e629951f0 100644 --- a/drivers/i3c/master/mipi-i3c-hci/ext_caps.h +++ b/drivers/i3c/master/mipi-i3c-hci/ext_caps.h @@ -13,7 +13,6 @@ /* MIPI vendor IDs */ #define MIPI_VENDOR_NXP 0x11b - int i3c_hci_parse_ext_caps(struct i3c_hci *hci); #endif diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h index 249ccb13c909..3f88b67bc5cc 100644 --- a/drivers/i3c/master/mipi-i3c-hci/hci.h +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h @@ -62,7 +62,6 @@ struct i3c_hci { void *vendor_data; }; - /* * Structure to represent a master initiated transfer. * The rnw, data and data_len fields must be initialized before calling any @@ -108,7 +107,6 @@ static inline void hci_free_xfer(struct hci_xfer *xfer, unsigned int n) kfree(xfer); } - /* This abstracts PIO vs DMA operations */ struct hci_io_ops { bool (*irq_handler)(struct i3c_hci *hci); @@ -126,21 +124,18 @@ struct hci_io_ops { extern const struct hci_io_ops mipi_i3c_hci_pio; extern const struct hci_io_ops mipi_i3c_hci_dma; - /* Our per device master private data */ struct i3c_hci_dev_data { int dat_idx; void *ibi_data; }; - /* list of quirks */ #define HCI_QUIRK_RAW_CCC BIT(1) /* CCC framing must be explicit */ #define HCI_QUIRK_PIO_MODE BIT(2) /* Set PIO mode for AMD platforms */ #define HCI_QUIRK_OD_PP_TIMING BIT(3) /* Set OD and PP timings for AMD platforms */ #define HCI_QUIRK_RESP_BUF_THLD BIT(4) /* Set resp buf thld to 0 for AMD platforms */ - /* global functions */ void mipi_i3c_hci_resume(struct i3c_hci *hci); void mipi_i3c_hci_pio_reset(struct i3c_hci *hci); diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c index 710faa46a00f..142f3f79415b 100644 --- a/drivers/i3c/master/mipi-i3c-hci/pio.c +++ b/drivers/i3c/master/mipi-i3c-hci/pio.c @@ -15,7 +15,6 @@ #include "cmd.h" #include "ibi.h" - /* * PIO Access Area */ From 0818e4aa8fdeeed5973e0a8faeddc9da599fc897 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 6 Jan 2026 18:44:07 +0200 Subject: [PATCH 10/52] i3c: mipi-i3c-hci: Stop reading Extended Capabilities if capability ID is 0 Extended Capability ID value 0 is special. It signifies the end of the list. Stop reading Extended Capabilities if capability ID is 0. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260106164416.67074-3-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/ext_caps.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/ext_caps.c b/drivers/i3c/master/mipi-i3c-hci/ext_caps.c index 40939af0b0e3..024bccf23fd0 100644 --- a/drivers/i3c/master/mipi-i3c-hci/ext_caps.c +++ b/drivers/i3c/master/mipi-i3c-hci/ext_caps.c @@ -271,7 +271,7 @@ int i3c_hci_parse_ext_caps(struct i3c_hci *hci) cap_length = FIELD_GET(CAP_HEADER_LENGTH, cap_header); dev_dbg(&hci->master.dev, "id=0x%02x length=%d", cap_id, cap_length); - if (!cap_length) + if (!cap_id || !cap_length) break; if (curr_cap + cap_length * 4 >= end) { dev_err(&hci->master.dev, From 581d5b7953b8f24d2f379c8c56ceaa7d163488ce Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 6 Jan 2026 18:44:08 +0200 Subject: [PATCH 11/52] i3c: mipi-i3c-hci: Quieten initialization messages The copious initialization messages are at most useful only for debugging. Change them from dev_info() or dev_notice() to dev_dbg(). Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260106164416.67074-4-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 18 +++---- drivers/i3c/master/mipi-i3c-hci/dma.c | 4 +- drivers/i3c/master/mipi-i3c-hci/ext_caps.c | 55 ++++++++++------------ drivers/i3c/master/mipi-i3c-hci/pio.c | 16 +++---- 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 211321f73e02..07fb91a12593 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -597,8 +597,8 @@ static int i3c_hci_init(struct i3c_hci *hci) hci->DAT_entry_size = FIELD_GET(DAT_ENTRY_SIZE, regval) ? 0 : 8; if (size_in_dwords) hci->DAT_entries = 4 * hci->DAT_entries / hci->DAT_entry_size; - dev_info(&hci->master.dev, "DAT: %u %u-bytes entries at offset %#x\n", - hci->DAT_entries, hci->DAT_entry_size, offset); + dev_dbg(&hci->master.dev, "DAT: %u %u-bytes entries at offset %#x\n", + hci->DAT_entries, hci->DAT_entry_size, offset); regval = reg_read(DCT_SECTION); offset = FIELD_GET(DCT_TABLE_OFFSET, regval); @@ -607,23 +607,23 @@ static int i3c_hci_init(struct i3c_hci *hci) hci->DCT_entry_size = FIELD_GET(DCT_ENTRY_SIZE, regval) ? 0 : 16; if (size_in_dwords) hci->DCT_entries = 4 * hci->DCT_entries / hci->DCT_entry_size; - dev_info(&hci->master.dev, "DCT: %u %u-bytes entries at offset %#x\n", - hci->DCT_entries, hci->DCT_entry_size, offset); + dev_dbg(&hci->master.dev, "DCT: %u %u-bytes entries at offset %#x\n", + hci->DCT_entries, hci->DCT_entry_size, offset); regval = reg_read(RING_HEADERS_SECTION); offset = FIELD_GET(RING_HEADERS_OFFSET, regval); hci->RHS_regs = offset ? hci->base_regs + offset : NULL; - dev_info(&hci->master.dev, "Ring Headers at offset %#x\n", offset); + dev_dbg(&hci->master.dev, "Ring Headers at offset %#x\n", offset); regval = reg_read(PIO_SECTION); offset = FIELD_GET(PIO_REGS_OFFSET, regval); hci->PIO_regs = offset ? hci->base_regs + offset : NULL; - dev_info(&hci->master.dev, "PIO section at offset %#x\n", offset); + dev_dbg(&hci->master.dev, "PIO section at offset %#x\n", offset); regval = reg_read(EXT_CAPS_SECTION); offset = FIELD_GET(EXT_CAPS_OFFSET, regval); hci->EXTCAPS_regs = offset ? hci->base_regs + offset : NULL; - dev_info(&hci->master.dev, "Extended Caps at offset %#x\n", offset); + dev_dbg(&hci->master.dev, "Extended Caps at offset %#x\n", offset); ret = i3c_hci_parse_ext_caps(hci); if (ret) @@ -705,7 +705,7 @@ static int i3c_hci_init(struct i3c_hci *hci) ret = -EIO; } else { hci->io = &mipi_i3c_hci_dma; - dev_info(&hci->master.dev, "Using DMA\n"); + dev_dbg(&hci->master.dev, "Using DMA\n"); } } @@ -717,7 +717,7 @@ static int i3c_hci_init(struct i3c_hci *hci) ret = -EIO; } else { hci->io = &mipi_i3c_hci_pio; - dev_info(&hci->master.dev, "Using PIO\n"); + dev_dbg(&hci->master.dev, "Using PIO\n"); } } diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c index f20db2899989..0f6bbe184e85 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dma.c +++ b/drivers/i3c/master/mipi-i3c-hci/dma.c @@ -212,7 +212,7 @@ static int hci_dma_init(struct i3c_hci *hci) regval = rhs_reg_read(CONTROL); nr_rings = FIELD_GET(MAX_HEADER_COUNT_CAP, regval); - dev_info(&hci->master.dev, "%d DMA rings available\n", nr_rings); + dev_dbg(&hci->master.dev, "%d DMA rings available\n", nr_rings); if (unlikely(nr_rings > 8)) { dev_err(&hci->master.dev, "number of rings should be <= 8\n"); nr_rings = 8; @@ -232,7 +232,7 @@ static int hci_dma_init(struct i3c_hci *hci) for (i = 0; i < rings->total; i++) { u32 offset = rhs_reg_read(RHn_OFFSET(i)); - dev_info(&hci->master.dev, "Ring %d at offset %#x\n", i, offset); + dev_dbg(&hci->master.dev, "Ring %d at offset %#x\n", i, offset); ret = -EINVAL; if (!offset) goto err_out; diff --git a/drivers/i3c/master/mipi-i3c-hci/ext_caps.c b/drivers/i3c/master/mipi-i3c-hci/ext_caps.c index 024bccf23fd0..77840fd4aa51 100644 --- a/drivers/i3c/master/mipi-i3c-hci/ext_caps.c +++ b/drivers/i3c/master/mipi-i3c-hci/ext_caps.c @@ -26,9 +26,9 @@ static int hci_extcap_hardware_id(struct i3c_hci *hci, void __iomem *base) hci->vendor_version_id = readl(base + 0x08); hci->vendor_product_id = readl(base + 0x0c); - dev_info(&hci->master.dev, "vendor MIPI ID: %#x\n", hci->vendor_mipi_id); - dev_info(&hci->master.dev, "vendor version ID: %#x\n", hci->vendor_version_id); - dev_info(&hci->master.dev, "vendor product ID: %#x\n", hci->vendor_product_id); + dev_dbg(&hci->master.dev, "vendor MIPI ID: %#x\n", hci->vendor_mipi_id); + dev_dbg(&hci->master.dev, "vendor version ID: %#x\n", hci->vendor_version_id); + dev_dbg(&hci->master.dev, "vendor product ID: %#x\n", hci->vendor_product_id); /* ought to go in a table if this grows too much */ switch (hci->vendor_mipi_id) { @@ -48,7 +48,7 @@ static int hci_extcap_master_config(struct i3c_hci *hci, void __iomem *base) static const char * const functionality[] = { "(unknown)", "master only", "target only", "primary/secondary master" }; - dev_info(&hci->master.dev, "operation mode: %s\n", functionality[operation_mode]); + dev_dbg(&hci->master.dev, "operation mode: %s\n", functionality[operation_mode]); if (operation_mode & 0x1) return 0; dev_err(&hci->master.dev, "only master mode is currently supported\n"); @@ -60,7 +60,7 @@ static int hci_extcap_multi_bus(struct i3c_hci *hci, void __iomem *base) u32 bus_instance = readl(base + 0x04); unsigned int count = FIELD_GET(GENMASK(3, 0), bus_instance); - dev_info(&hci->master.dev, "%d bus instances\n", count); + dev_dbg(&hci->master.dev, "%d bus instances\n", count); return 0; } @@ -70,8 +70,7 @@ static int hci_extcap_xfer_modes(struct i3c_hci *hci, void __iomem *base) u32 entries = FIELD_GET(CAP_HEADER_LENGTH, header) - 1; unsigned int index; - dev_info(&hci->master.dev, "transfer mode table has %d entries\n", - entries); + dev_dbg(&hci->master.dev, "transfer mode table has %d entries\n", entries); base += 4; /* skip header */ for (index = 0; index < entries; index++) { u32 mode_entry = readl(base); @@ -94,7 +93,7 @@ static int hci_extcap_xfer_rates(struct i3c_hci *hci, void __iomem *base) base += 4; /* skip header */ - dev_info(&hci->master.dev, "available data rates:\n"); + dev_dbg(&hci->master.dev, "available data rates:\n"); for (index = 0; index < entries; index++) { rate_entry = readl(base); dev_dbg(&hci->master.dev, "entry %d: 0x%08x", @@ -102,12 +101,12 @@ static int hci_extcap_xfer_rates(struct i3c_hci *hci, void __iomem *base) rate = FIELD_GET(XFERRATE_ACTUAL_RATE_KHZ, rate_entry); rate_id = FIELD_GET(XFERRATE_RATE_ID, rate_entry); mode_id = FIELD_GET(XFERRATE_MODE_ID, rate_entry); - dev_info(&hci->master.dev, "rate %d for %s = %d kHz\n", - rate_id, - mode_id == XFERRATE_MODE_I3C ? "I3C" : - mode_id == XFERRATE_MODE_I2C ? "I2C" : - "unknown mode", - rate); + dev_dbg(&hci->master.dev, "rate %d for %s = %d kHz\n", + rate_id, + mode_id == XFERRATE_MODE_I3C ? "I3C" : + mode_id == XFERRATE_MODE_I2C ? "I2C" : + "unknown mode", + rate); base += 4; } @@ -121,8 +120,8 @@ static int hci_extcap_auto_command(struct i3c_hci *hci, void __iomem *base) u32 autocmd_ext_config = readl(base + 0x08); unsigned int count = FIELD_GET(GENMASK(3, 0), autocmd_ext_config); - dev_info(&hci->master.dev, "%d/%d active auto-command entries\n", - count, max_count); + dev_dbg(&hci->master.dev, "%d/%d active auto-command entries\n", + count, max_count); /* remember auto-command register location for later use */ hci->AUTOCMD_regs = base; return 0; @@ -130,46 +129,46 @@ static int hci_extcap_auto_command(struct i3c_hci *hci, void __iomem *base) static int hci_extcap_debug(struct i3c_hci *hci, void __iomem *base) { - dev_info(&hci->master.dev, "debug registers present\n"); + dev_dbg(&hci->master.dev, "debug registers present\n"); hci->DEBUG_regs = base; return 0; } static int hci_extcap_scheduled_cmd(struct i3c_hci *hci, void __iomem *base) { - dev_info(&hci->master.dev, "scheduled commands available\n"); + dev_dbg(&hci->master.dev, "scheduled commands available\n"); /* hci->schedcmd_regs = base; */ return 0; } static int hci_extcap_non_curr_master(struct i3c_hci *hci, void __iomem *base) { - dev_info(&hci->master.dev, "Non-Current Master support available\n"); + dev_dbg(&hci->master.dev, "Non-Current Master support available\n"); /* hci->NCM_regs = base; */ return 0; } static int hci_extcap_ccc_resp_conf(struct i3c_hci *hci, void __iomem *base) { - dev_info(&hci->master.dev, "CCC Response Configuration available\n"); + dev_dbg(&hci->master.dev, "CCC Response Configuration available\n"); return 0; } static int hci_extcap_global_DAT(struct i3c_hci *hci, void __iomem *base) { - dev_info(&hci->master.dev, "Global DAT available\n"); + dev_dbg(&hci->master.dev, "Global DAT available\n"); return 0; } static int hci_extcap_multilane(struct i3c_hci *hci, void __iomem *base) { - dev_info(&hci->master.dev, "Master Multi-Lane support available\n"); + dev_dbg(&hci->master.dev, "Master Multi-Lane support available\n"); return 0; } static int hci_extcap_ncm_multilane(struct i3c_hci *hci, void __iomem *base) { - dev_info(&hci->master.dev, "NCM Multi-Lane support available\n"); + dev_dbg(&hci->master.dev, "NCM Multi-Lane support available\n"); return 0; } @@ -202,7 +201,7 @@ static const struct hci_ext_caps ext_capabilities[] = { static int hci_extcap_vendor_NXP(struct i3c_hci *hci, void __iomem *base) { hci->vendor_data = (__force void *)base; - dev_info(&hci->master.dev, "Build Date Info = %#x\n", readl(base + 1*4)); + dev_dbg(&hci->master.dev, "Build Date Info = %#x\n", readl(base + 1 * 4)); /* reset the FPGA */ writel(0xdeadbeef, base + 1*4); return 0; @@ -240,9 +239,8 @@ static int hci_extcap_vendor_specific(struct i3c_hci *hci, void __iomem *base, } if (!vendor_cap_entry) { - dev_notice(&hci->master.dev, - "unknown ext_cap 0x%02x for vendor 0x%02x\n", - cap_id, hci->vendor_mipi_id); + dev_dbg(&hci->master.dev, "unknown ext_cap 0x%02x for vendor 0x%02x\n", + cap_id, hci->vendor_mipi_id); return 0; } if (cap_length < vendor_cap_entry->min_length) { @@ -295,8 +293,7 @@ int i3c_hci_parse_ext_caps(struct i3c_hci *hci) } } if (!cap_entry) { - dev_notice(&hci->master.dev, - "unknown ext_cap 0x%02x\n", cap_id); + dev_dbg(&hci->master.dev, "unknown ext_cap 0x%02x\n", cap_id); } else if (cap_length < cap_entry->min_length) { dev_err(&hci->master.dev, "ext_cap 0x%02x has size %d (expecting >= %d)\n", diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c index 142f3f79415b..109c6c5d83d6 100644 --- a/drivers/i3c/master/mipi-i3c-hci/pio.c +++ b/drivers/i3c/master/mipi-i3c-hci/pio.c @@ -148,14 +148,14 @@ static int hci_pio_init(struct i3c_hci *hci) spin_lock_init(&pio->lock); size_val = pio_reg_read(QUEUE_SIZE); - dev_info(&hci->master.dev, "CMD/RESP FIFO = %ld entries\n", - FIELD_GET(CR_QUEUE_SIZE, size_val)); - dev_info(&hci->master.dev, "IBI FIFO = %ld bytes\n", - 4 * FIELD_GET(IBI_STATUS_SIZE, size_val)); - dev_info(&hci->master.dev, "RX data FIFO = %d bytes\n", - 4 * (2 << FIELD_GET(RX_DATA_BUFFER_SIZE, size_val))); - dev_info(&hci->master.dev, "TX data FIFO = %d bytes\n", - 4 * (2 << FIELD_GET(TX_DATA_BUFFER_SIZE, size_val))); + dev_dbg(&hci->master.dev, "CMD/RESP FIFO = %ld entries\n", + FIELD_GET(CR_QUEUE_SIZE, size_val)); + dev_dbg(&hci->master.dev, "IBI FIFO = %ld bytes\n", + 4 * FIELD_GET(IBI_STATUS_SIZE, size_val)); + dev_dbg(&hci->master.dev, "RX data FIFO = %d bytes\n", + 4 * (2 << FIELD_GET(RX_DATA_BUFFER_SIZE, size_val))); + dev_dbg(&hci->master.dev, "TX data FIFO = %d bytes\n", + 4 * (2 << FIELD_GET(TX_DATA_BUFFER_SIZE, size_val))); /* * Let's initialize data thresholds to half of the actual FIFO size. From d540d090be8fd2be2dc2b1e0b2818a4a48abcc3e Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 6 Jan 2026 18:44:09 +0200 Subject: [PATCH 12/52] i3c: mipi-i3c-hci-pci: Do not repeatedly check for NULL driver_data All entries in the id_table have driver_data. Do not repeatedly check for NULL driver_data. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260106164416.67074-5-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c index dc8ede0f8ad8..8ade911e3835 100644 --- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c +++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c @@ -229,7 +229,7 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, goto err; hci->info = (const struct mipi_i3c_hci_pci_info *)id->driver_data; - if (hci->info && hci->info->init) { + if (hci->info->init) { ret = hci->info->init(hci); if (ret) goto err; @@ -244,7 +244,7 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, return 0; err_exit: - if (hci->info && hci->info->exit) + if (hci->info->exit) hci->info->exit(hci); err: platform_device_put(hci->pdev); @@ -258,7 +258,7 @@ static void mipi_i3c_hci_pci_remove(struct pci_dev *pci) struct platform_device *pdev = hci->pdev; int dev_id = pdev->id; - if (hci->info && hci->info->exit) + if (hci->info->exit) hci->info->exit(hci); platform_device_unregister(pdev); From b43181b724e8b98f1c42d44db8f3c132a932773e Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 6 Jan 2026 18:44:10 +0200 Subject: [PATCH 13/52] i3c: mipi-i3c-hci-pci: Enable MSI support Enable MSI support by using pci_alloc_irq_vectors() to request all supported IRQ types. Do not call pci_free_irq_vectors() because for resource-managed devices (those initialized with pcim_enable_device()), IRQ vector allocation is automatically managed. See pci_setup_msi_context() and pcim_setup_msi_release() for details. Note: The current documentation for pci_alloc_irq_vectors() does not mention this behavior. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260106164416.67074-6-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c index 8ade911e3835..0fd3587671e1 100644 --- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c +++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c @@ -203,6 +203,10 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, pci_set_master(pci); + ret = pci_alloc_irq_vectors(pci, 1, 1, PCI_IRQ_ALL_TYPES); + if (ret < 0) + return ret; + memset(&res, 0, sizeof(res)); res[0].flags = IORESOURCE_MEM; @@ -210,8 +214,8 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, res[0].end = pci_resource_end(pci, 0); res[1].flags = IORESOURCE_IRQ; - res[1].start = pci->irq; - res[1].end = pci->irq; + res[1].start = pci_irq_vector(hci->pci, 0); + res[1].end = res[1].start; dev_id = ida_alloc(&mipi_i3c_hci_pci_ida, GFP_KERNEL); if (dev_id < 0) From 35c0bfe8fd1066c61420b076e7bcd3cfa54b9d92 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 6 Jan 2026 18:44:11 +0200 Subject: [PATCH 14/52] i3c: mipi-i3c-hci-pci: Assign unique device names and IDs for Intel LPSS I3C Simplify the code and ensure names and IDs align with device documentation. Use explicit device names and IDs for Intel LPSS I3C controllers instead of dynamically allocated values. Add "intel-lpss-i3c" to the platform_device_id table in the mipi-i3c-hci driver and use the same name for Intel I3C controllers in the mipi_i3c_hci_pci driver. Assign hard-coded IDs to reflect the hardware layout. Intel SoCs include two I3C PCI devices in the Low Power Subsystem (LPSS), each supporting up to two I3C buses. The second PCI device is assigned ID 2 (not 1) to match this topology. Additional IDs will be introduced when Multi-Bus Instance support is implemented. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260106164416.67074-7-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 7 +++ .../master/mipi-i3c-hci/mipi-i3c-hci-pci.c | 43 ++++++++++--------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 07fb91a12593..3d6544a64188 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -790,9 +790,16 @@ static const struct acpi_device_id i3c_hci_acpi_match[] = { }; MODULE_DEVICE_TABLE(acpi, i3c_hci_acpi_match); +static const struct platform_device_id i3c_hci_driver_ids[] = { + { .name = "intel-lpss-i3c" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, i3c_hci_driver_ids); + static struct platform_driver i3c_hci_driver = { .probe = i3c_hci_probe, .remove = i3c_hci_remove, + .id_table = i3c_hci_driver_ids, .driver = { .name = "mipi-i3c-hci", .of_match_table = of_match_ptr(i3c_hci_of_match), diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c index 0fd3587671e1..3b319fbf18ce 100644 --- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c +++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c @@ -27,10 +27,10 @@ struct mipi_i3c_hci_pci { struct mipi_i3c_hci_pci_info { int (*init)(struct mipi_i3c_hci_pci *hci); void (*exit)(struct mipi_i3c_hci_pci *hci); + const char *name; + int id; }; -static DEFINE_IDA(mipi_i3c_hci_pci_ida); - #define INTEL_PRIV_OFFSET 0x2b0 #define INTEL_PRIV_SIZE 0x28 #define INTEL_RESETS 0x04 @@ -179,9 +179,18 @@ static void intel_i3c_exit(struct mipi_i3c_hci_pci *hci) intel_ltr_hide(&hci->pci->dev); } -static const struct mipi_i3c_hci_pci_info intel_info = { +static const struct mipi_i3c_hci_pci_info intel_1_info = { .init = intel_i3c_init, .exit = intel_i3c_exit, + .name = "intel-lpss-i3c", + .id = 0, +}; + +static const struct mipi_i3c_hci_pci_info intel_2_info = { + .init = intel_i3c_init, + .exit = intel_i3c_exit, + .name = "intel-lpss-i3c", + .id = 2, }; static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, @@ -189,7 +198,7 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, { struct mipi_i3c_hci_pci *hci; struct resource res[2]; - int dev_id, ret; + int ret; hci = devm_kzalloc(&pci->dev, sizeof(*hci), GFP_KERNEL); if (!hci) @@ -217,11 +226,9 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, res[1].start = pci_irq_vector(hci->pci, 0); res[1].end = res[1].start; - dev_id = ida_alloc(&mipi_i3c_hci_pci_ida, GFP_KERNEL); - if (dev_id < 0) - return dev_id; + hci->info = (const struct mipi_i3c_hci_pci_info *)id->driver_data; - hci->pdev = platform_device_alloc("mipi-i3c-hci", dev_id); + hci->pdev = platform_device_alloc(hci->info->name, hci->info->id); if (!hci->pdev) return -ENOMEM; @@ -232,7 +239,6 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, if (ret) goto err; - hci->info = (const struct mipi_i3c_hci_pci_info *)id->driver_data; if (hci->info->init) { ret = hci->info->init(hci); if (ret) @@ -252,7 +258,6 @@ err_exit: hci->info->exit(hci); err: platform_device_put(hci->pdev); - ida_free(&mipi_i3c_hci_pci_ida, dev_id); return ret; } @@ -260,28 +265,26 @@ static void mipi_i3c_hci_pci_remove(struct pci_dev *pci) { struct mipi_i3c_hci_pci *hci = pci_get_drvdata(pci); struct platform_device *pdev = hci->pdev; - int dev_id = pdev->id; if (hci->info->exit) hci->info->exit(hci); platform_device_unregister(pdev); - ida_free(&mipi_i3c_hci_pci_ida, dev_id); } static const struct pci_device_id mipi_i3c_hci_pci_devices[] = { /* Wildcat Lake-U */ - { PCI_VDEVICE(INTEL, 0x4d7c), (kernel_ulong_t)&intel_info}, - { PCI_VDEVICE(INTEL, 0x4d6f), (kernel_ulong_t)&intel_info}, + { PCI_VDEVICE(INTEL, 0x4d7c), (kernel_ulong_t)&intel_1_info}, + { PCI_VDEVICE(INTEL, 0x4d6f), (kernel_ulong_t)&intel_2_info}, /* Panther Lake-H */ - { PCI_VDEVICE(INTEL, 0xe37c), (kernel_ulong_t)&intel_info}, - { PCI_VDEVICE(INTEL, 0xe36f), (kernel_ulong_t)&intel_info}, + { PCI_VDEVICE(INTEL, 0xe37c), (kernel_ulong_t)&intel_1_info}, + { PCI_VDEVICE(INTEL, 0xe36f), (kernel_ulong_t)&intel_2_info}, /* Panther Lake-P */ - { PCI_VDEVICE(INTEL, 0xe47c), (kernel_ulong_t)&intel_info}, - { PCI_VDEVICE(INTEL, 0xe46f), (kernel_ulong_t)&intel_info}, + { PCI_VDEVICE(INTEL, 0xe47c), (kernel_ulong_t)&intel_1_info}, + { PCI_VDEVICE(INTEL, 0xe46f), (kernel_ulong_t)&intel_2_info}, /* Nova Lake-S */ - { PCI_VDEVICE(INTEL, 0x6e2c), (kernel_ulong_t)&intel_info}, - { PCI_VDEVICE(INTEL, 0x6e2d), (kernel_ulong_t)&intel_info}, + { PCI_VDEVICE(INTEL, 0x6e2c), (kernel_ulong_t)&intel_1_info}, + { PCI_VDEVICE(INTEL, 0x6e2d), (kernel_ulong_t)&intel_2_info}, { }, }; MODULE_DEVICE_TABLE(pci, mipi_i3c_hci_pci_devices); From b8460480f62e16751876a1f367dc14fb62867463 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 6 Jan 2026 18:44:12 +0200 Subject: [PATCH 15/52] i3c: mipi-i3c-hci: Allow for Multi-Bus Instances Add support for MIPI I3C Host Controllers with the Multi-Bus Instance capability. These controllers can host multiple I3C buses (up to 15) within a single hardware function (e.g., PCIe B/D/F), providing one indepedent HCI register set and corresponding I3C bus controller logic per bus. A separate platform device will represent each instance, but it is necessary to allow for shared resources. Multi-bus instances share the same MMIO address space, but the ranges are not guaranteed to be contiguous. To avoid overlapping mappings, pass base_regs from the parent mapping to child devices. Allow the IRQ to be shared among instances. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260106164416.67074-8-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 21 +++++++++++++++++---- include/linux/platform_data/mipi-i3c-hci.h | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 include/linux/platform_data/mipi-i3c-hci.h diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 3d6544a64188..6da5daf18166 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "hci.h" @@ -737,15 +738,27 @@ static int i3c_hci_init(struct i3c_hci *hci) static int i3c_hci_probe(struct platform_device *pdev) { + const struct mipi_i3c_hci_platform_data *pdata = pdev->dev.platform_data; struct i3c_hci *hci; int irq, ret; hci = devm_kzalloc(&pdev->dev, sizeof(*hci), GFP_KERNEL); if (!hci) return -ENOMEM; - hci->base_regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(hci->base_regs)) - return PTR_ERR(hci->base_regs); + + /* + * Multi-bus instances share the same MMIO address range, but not + * necessarily in separate contiguous sub-ranges. To avoid overlapping + * mappings, provide base_regs from the parent mapping. + */ + if (pdata) + hci->base_regs = pdata->base_regs; + + if (!hci->base_regs) { + hci->base_regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(hci->base_regs)) + return PTR_ERR(hci->base_regs); + } platform_set_drvdata(pdev, hci); /* temporary for dev_printk's, to be replaced in i3c_master_register */ @@ -759,7 +772,7 @@ static int i3c_hci_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); ret = devm_request_irq(&pdev->dev, irq, i3c_hci_irq_handler, - 0, NULL, hci); + IRQF_SHARED, NULL, hci); if (ret) return ret; diff --git a/include/linux/platform_data/mipi-i3c-hci.h b/include/linux/platform_data/mipi-i3c-hci.h new file mode 100644 index 000000000000..ab7395f455f9 --- /dev/null +++ b/include/linux/platform_data/mipi-i3c-hci.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef INCLUDE_PLATFORM_DATA_MIPI_I3C_HCI_H +#define INCLUDE_PLATFORM_DATA_MIPI_I3C_HCI_H + +#include + +/** + * struct mipi_i3c_hci_platform_data - Platform-dependent data for mipi_i3c_hci + * @base_regs: Register set base address (to support multi-bus instances) + */ +struct mipi_i3c_hci_platform_data { + void __iomem *base_regs; +}; + +#endif From 9a4d56b42ff019bbff1e3d0940ebfd3e292326fa Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 6 Jan 2026 18:44:13 +0200 Subject: [PATCH 16/52] i3c: mipi-i3c-hci-pci: Pass base regs as platform data to i3c core device Use the parent's MMIO mapping for multi-bus instances to avoid overlapping regions. These instances share the same MMIO address space, but the ranges are not guaranteed to be contiguous. By passing base_regs from the parent mapping, child devices can access their registers without creating conflicting mappings. Prepare for multi-bus instance support by passing base_regs to child devices. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260106164416.67074-9-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- .../master/mipi-i3c-hci/mipi-i3c-hci-pci.c | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c index 3b319fbf18ce..ca562a5634e8 100644 --- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c +++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c @@ -14,12 +14,14 @@ #include #include #include +#include #include #include struct mipi_i3c_hci_pci { struct pci_dev *pci; struct platform_device *pdev; + void __iomem *base; const struct mipi_i3c_hci_pci_info *info; void *private; }; @@ -32,7 +34,6 @@ struct mipi_i3c_hci_pci_info { }; #define INTEL_PRIV_OFFSET 0x2b0 -#define INTEL_PRIV_SIZE 0x28 #define INTEL_RESETS 0x04 #define INTEL_RESETS_RESET BIT(0) #define INTEL_RESETS_RESET_DONE BIT(1) @@ -143,19 +144,12 @@ static void intel_reset(void __iomem *priv) writel(INTEL_RESETS_RESET, priv + INTEL_RESETS); } -static void __iomem *intel_priv(struct pci_dev *pci) -{ - resource_size_t base = pci_resource_start(pci, 0); - - return devm_ioremap(&pci->dev, base + INTEL_PRIV_OFFSET, INTEL_PRIV_SIZE); -} - static int intel_i3c_init(struct mipi_i3c_hci_pci *hci) { struct intel_host *host = devm_kzalloc(&hci->pci->dev, sizeof(*host), GFP_KERNEL); - void __iomem *priv = intel_priv(hci->pci); + void __iomem *priv = hci->base + INTEL_PRIV_OFFSET; - if (!host || !priv) + if (!host) return -ENOMEM; dma_set_mask_and_coherent(&hci->pci->dev, DMA_BIT_MASK(64)); @@ -196,8 +190,9 @@ static const struct mipi_i3c_hci_pci_info intel_2_info = { static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) { + struct mipi_i3c_hci_platform_data pdata = {}; struct mipi_i3c_hci_pci *hci; - struct resource res[2]; + struct resource res; int ret; hci = devm_kzalloc(&pci->dev, sizeof(*hci), GFP_KERNEL); @@ -212,19 +207,19 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, pci_set_master(pci); + hci->base = pcim_iomap_region(pci, 0, pci_name(pci)); + if (IS_ERR(hci->base)) + return PTR_ERR(hci->base); + ret = pci_alloc_irq_vectors(pci, 1, 1, PCI_IRQ_ALL_TYPES); if (ret < 0) return ret; memset(&res, 0, sizeof(res)); - res[0].flags = IORESOURCE_MEM; - res[0].start = pci_resource_start(pci, 0); - res[0].end = pci_resource_end(pci, 0); - - res[1].flags = IORESOURCE_IRQ; - res[1].start = pci_irq_vector(hci->pci, 0); - res[1].end = res[1].start; + res.flags = IORESOURCE_IRQ; + res.start = pci_irq_vector(hci->pci, 0); + res.end = res.start; hci->info = (const struct mipi_i3c_hci_pci_info *)id->driver_data; @@ -235,7 +230,13 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, hci->pdev->dev.parent = &pci->dev; device_set_node(&hci->pdev->dev, dev_fwnode(&pci->dev)); - ret = platform_device_add_resources(hci->pdev, res, ARRAY_SIZE(res)); + ret = platform_device_add_resources(hci->pdev, &res, 1); + if (ret) + goto err; + + pdata.base_regs = hci->base; + + ret = platform_device_add_data(hci->pdev, &pdata, sizeof(pdata)); if (ret) goto err; From 0590fe32f9040bccb5481915b32bba1595946b16 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 6 Jan 2026 18:44:14 +0200 Subject: [PATCH 17/52] i3c: mipi-i3c-hci-pci: Convert to MFD driver Prepare for Multi-Bus instance support. Convert to MFD driver but still support only 1 instance. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260106164416.67074-10-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/Kconfig | 1 + .../master/mipi-i3c-hci/mipi-i3c-hci-pci.c | 75 ++++++++++--------- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig index 82cf330778d5..2609f2b18e0a 100644 --- a/drivers/i3c/master/Kconfig +++ b/drivers/i3c/master/Kconfig @@ -69,6 +69,7 @@ config MIPI_I3C_HCI_PCI tristate "MIPI I3C Host Controller Interface PCI support" depends on MIPI_I3C_HCI depends on PCI + select MFD_CORE help Support for MIPI I3C Host Controller Interface compatible hardware on the PCI bus. diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c index ca562a5634e8..7ef17255c312 100644 --- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c +++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -20,7 +21,6 @@ struct mipi_i3c_hci_pci { struct pci_dev *pci; - struct platform_device *pdev; void __iomem *base; const struct mipi_i3c_hci_pci_info *info; void *private; @@ -187,12 +187,45 @@ static const struct mipi_i3c_hci_pci_info intel_2_info = { .id = 2, }; +struct mipi_i3c_hci_pci_cell_data { + struct mipi_i3c_hci_platform_data pdata; + struct resource res; +}; + +static void mipi_i3c_hci_pci_setup_cell(struct mipi_i3c_hci_pci *hci, + struct mipi_i3c_hci_pci_cell_data *data, + struct mfd_cell *cell) +{ + data->pdata.base_regs = hci->base; + + data->res = DEFINE_RES_IRQ(0); + + cell->name = hci->info->name; + cell->id = hci->info->id; + cell->platform_data = &data->pdata; + cell->pdata_size = sizeof(data->pdata); + cell->num_resources = 1; + cell->resources = &data->res; +} + +static int mipi_i3c_hci_pci_add_instances(struct mipi_i3c_hci_pci *hci) +{ + struct mipi_i3c_hci_pci_cell_data *data __free(kfree) = kzalloc(sizeof(*data), GFP_KERNEL); + struct mfd_cell *cells __free(kfree) = kzalloc(sizeof(*cells), GFP_KERNEL); + int irq = pci_irq_vector(hci->pci, 0); + + if (!cells || !data) + return -ENOMEM; + + mipi_i3c_hci_pci_setup_cell(hci, data, cells); + + return mfd_add_devices(&hci->pci->dev, 0, cells, 1, NULL, irq, NULL); +} + static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) { - struct mipi_i3c_hci_platform_data pdata = {}; struct mipi_i3c_hci_pci *hci; - struct resource res; int ret; hci = devm_kzalloc(&pci->dev, sizeof(*hci), GFP_KERNEL); @@ -215,38 +248,13 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, if (ret < 0) return ret; - memset(&res, 0, sizeof(res)); - - res.flags = IORESOURCE_IRQ; - res.start = pci_irq_vector(hci->pci, 0); - res.end = res.start; - hci->info = (const struct mipi_i3c_hci_pci_info *)id->driver_data; - hci->pdev = platform_device_alloc(hci->info->name, hci->info->id); - if (!hci->pdev) - return -ENOMEM; - - hci->pdev->dev.parent = &pci->dev; - device_set_node(&hci->pdev->dev, dev_fwnode(&pci->dev)); - - ret = platform_device_add_resources(hci->pdev, &res, 1); + ret = hci->info->init ? hci->info->init(hci) : 0; if (ret) - goto err; + return ret; - pdata.base_regs = hci->base; - - ret = platform_device_add_data(hci->pdev, &pdata, sizeof(pdata)); - if (ret) - goto err; - - if (hci->info->init) { - ret = hci->info->init(hci); - if (ret) - goto err; - } - - ret = platform_device_add(hci->pdev); + ret = mipi_i3c_hci_pci_add_instances(hci); if (ret) goto err_exit; @@ -257,20 +265,17 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, err_exit: if (hci->info->exit) hci->info->exit(hci); -err: - platform_device_put(hci->pdev); return ret; } static void mipi_i3c_hci_pci_remove(struct pci_dev *pci) { struct mipi_i3c_hci_pci *hci = pci_get_drvdata(pci); - struct platform_device *pdev = hci->pdev; if (hci->info->exit) hci->info->exit(hci); - platform_device_unregister(pdev); + mfd_remove_devices(&pci->dev); } static const struct pci_device_id mipi_i3c_hci_pci_devices[] = { From 9b1679028e760259eaf817b8ceecad9b03a60118 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 6 Jan 2026 18:44:15 +0200 Subject: [PATCH 18/52] i3c: mipi-i3c-hci-pci: Add support for Multi-Bus Instances Add support for MIPI I3C Host Controllers with the Multi-Bus Instance capability. These controllers can host multiple I3C buses (up to 15) within a single hardware function (e.g., PCIe B/D/F), providing one indepedent HCI register set and corresponding I3C bus controller logic per bus. Create an MFD cell for each instance and use platform_data to pass the starting address of the instance's register set. The MIPI I3C HCI specification defines an Extended Capability that holds the offset of each instance register set. Parsing this information is relatively complex, so include the offsets in driver data for now. Driver data for additional instances beyond instance 0 will be added in a subsequent patch. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260106164416.67074-11-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- .../master/mipi-i3c-hci/mipi-i3c-hci-pci.c | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c index 7ef17255c312..782f46989423 100644 --- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c +++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c @@ -19,6 +19,12 @@ #include #include +/* + * There can up to 15 instances, but implementations have at most 2 at this + * time. + */ +#define INST_MAX 2 + struct mipi_i3c_hci_pci { struct pci_dev *pci; void __iomem *base; @@ -30,7 +36,9 @@ struct mipi_i3c_hci_pci_info { int (*init)(struct mipi_i3c_hci_pci *hci); void (*exit)(struct mipi_i3c_hci_pci *hci); const char *name; - int id; + int id[INST_MAX]; + u32 instance_offset[INST_MAX]; + int instance_count; }; #define INTEL_PRIV_OFFSET 0x2b0 @@ -177,14 +185,18 @@ static const struct mipi_i3c_hci_pci_info intel_1_info = { .init = intel_i3c_init, .exit = intel_i3c_exit, .name = "intel-lpss-i3c", - .id = 0, + .id = {0}, + .instance_offset = {0}, + .instance_count = 1, }; static const struct mipi_i3c_hci_pci_info intel_2_info = { .init = intel_i3c_init, .exit = intel_i3c_exit, .name = "intel-lpss-i3c", - .id = 2, + .id = {2}, + .instance_offset = {0}, + .instance_count = 1, }; struct mipi_i3c_hci_pci_cell_data { @@ -192,34 +204,38 @@ struct mipi_i3c_hci_pci_cell_data { struct resource res; }; -static void mipi_i3c_hci_pci_setup_cell(struct mipi_i3c_hci_pci *hci, +static void mipi_i3c_hci_pci_setup_cell(struct mipi_i3c_hci_pci *hci, int idx, struct mipi_i3c_hci_pci_cell_data *data, struct mfd_cell *cell) { - data->pdata.base_regs = hci->base; + data->pdata.base_regs = hci->base + hci->info->instance_offset[idx]; data->res = DEFINE_RES_IRQ(0); cell->name = hci->info->name; - cell->id = hci->info->id; + cell->id = hci->info->id[idx]; cell->platform_data = &data->pdata; cell->pdata_size = sizeof(data->pdata); cell->num_resources = 1; cell->resources = &data->res; } +#define mipi_i3c_hci_pci_alloc(h, x) kcalloc((h)->info->instance_count, sizeof(*(x)), GFP_KERNEL) + static int mipi_i3c_hci_pci_add_instances(struct mipi_i3c_hci_pci *hci) { - struct mipi_i3c_hci_pci_cell_data *data __free(kfree) = kzalloc(sizeof(*data), GFP_KERNEL); - struct mfd_cell *cells __free(kfree) = kzalloc(sizeof(*cells), GFP_KERNEL); + struct mipi_i3c_hci_pci_cell_data *data __free(kfree) = mipi_i3c_hci_pci_alloc(hci, data); + struct mfd_cell *cells __free(kfree) = mipi_i3c_hci_pci_alloc(hci, cells); int irq = pci_irq_vector(hci->pci, 0); + int nr = hci->info->instance_count; if (!cells || !data) return -ENOMEM; - mipi_i3c_hci_pci_setup_cell(hci, data, cells); + for (int i = 0; i < nr; i++) + mipi_i3c_hci_pci_setup_cell(hci, i, data + i, cells + i); - return mfd_add_devices(&hci->pci->dev, 0, cells, 1, NULL, irq, NULL); + return mfd_add_devices(&hci->pci->dev, 0, cells, nr, NULL, irq, NULL); } static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, From 540a55a5bafd0ddbeb87672fe569c15954b47038 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 6 Jan 2026 18:44:16 +0200 Subject: [PATCH 19/52] i3c: mipi-i3c-hci-pci: Define Multi-Bus instances for supported controllers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Define Multi-Bus Instances at offset 0x400 for Intel controllers. Intel SoCs include two I3C PCI devices in the Low Power Subsystem (LPSS), each capable of hosting two I3C buses. Panther Lake and Wildcat Lake support three buses in total (IDs 0–2), while Nova Lake supports four (IDs 0–3). Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260106164416.67074-12-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- .../master/mipi-i3c-hci/mipi-i3c-hci-pci.c | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c index 782f46989423..458f871a2e61 100644 --- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c +++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c @@ -181,16 +181,25 @@ static void intel_i3c_exit(struct mipi_i3c_hci_pci *hci) intel_ltr_hide(&hci->pci->dev); } -static const struct mipi_i3c_hci_pci_info intel_1_info = { +static const struct mipi_i3c_hci_pci_info intel_mi_1_info = { .init = intel_i3c_init, .exit = intel_i3c_exit, .name = "intel-lpss-i3c", - .id = {0}, - .instance_offset = {0}, - .instance_count = 1, + .id = {0, 1}, + .instance_offset = {0, 0x400}, + .instance_count = 2, }; -static const struct mipi_i3c_hci_pci_info intel_2_info = { +static const struct mipi_i3c_hci_pci_info intel_mi_2_info = { + .init = intel_i3c_init, + .exit = intel_i3c_exit, + .name = "intel-lpss-i3c", + .id = {2, 3}, + .instance_offset = {0, 0x400}, + .instance_count = 2, +}; + +static const struct mipi_i3c_hci_pci_info intel_si_2_info = { .init = intel_i3c_init, .exit = intel_i3c_exit, .name = "intel-lpss-i3c", @@ -296,17 +305,17 @@ static void mipi_i3c_hci_pci_remove(struct pci_dev *pci) static const struct pci_device_id mipi_i3c_hci_pci_devices[] = { /* Wildcat Lake-U */ - { PCI_VDEVICE(INTEL, 0x4d7c), (kernel_ulong_t)&intel_1_info}, - { PCI_VDEVICE(INTEL, 0x4d6f), (kernel_ulong_t)&intel_2_info}, + { PCI_VDEVICE(INTEL, 0x4d7c), (kernel_ulong_t)&intel_mi_1_info}, + { PCI_VDEVICE(INTEL, 0x4d6f), (kernel_ulong_t)&intel_si_2_info}, /* Panther Lake-H */ - { PCI_VDEVICE(INTEL, 0xe37c), (kernel_ulong_t)&intel_1_info}, - { PCI_VDEVICE(INTEL, 0xe36f), (kernel_ulong_t)&intel_2_info}, + { PCI_VDEVICE(INTEL, 0xe37c), (kernel_ulong_t)&intel_mi_1_info}, + { PCI_VDEVICE(INTEL, 0xe36f), (kernel_ulong_t)&intel_si_2_info}, /* Panther Lake-P */ - { PCI_VDEVICE(INTEL, 0xe47c), (kernel_ulong_t)&intel_1_info}, - { PCI_VDEVICE(INTEL, 0xe46f), (kernel_ulong_t)&intel_2_info}, + { PCI_VDEVICE(INTEL, 0xe47c), (kernel_ulong_t)&intel_mi_1_info}, + { PCI_VDEVICE(INTEL, 0xe46f), (kernel_ulong_t)&intel_si_2_info}, /* Nova Lake-S */ - { PCI_VDEVICE(INTEL, 0x6e2c), (kernel_ulong_t)&intel_1_info}, - { PCI_VDEVICE(INTEL, 0x6e2d), (kernel_ulong_t)&intel_2_info}, + { PCI_VDEVICE(INTEL, 0x6e2c), (kernel_ulong_t)&intel_mi_1_info}, + { PCI_VDEVICE(INTEL, 0x6e2d), (kernel_ulong_t)&intel_mi_2_info}, { }, }; MODULE_DEVICE_TABLE(pci, mipi_i3c_hci_pci_devices); From 579c7255922a3e0ba432eb608deb7d0fed896052 Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Wed, 7 Jan 2026 11:33:47 +0100 Subject: [PATCH 20/52] i3c: renesas: Switch to clk_bulk API and store clocks in private data Replace individual devm_clk_get_enabled() calls with the clk_bulk API and store the clock handles in the driver's private data structure. All clocks required by the controller are now acquired and enabled using devm_clk_bulk_get_all_enabled(), removing the need for per-SoC clock handling and the renesas_i3c_config data. The TCLK is accessed via a fixed index in the bulk clock array. Simplify the code and prepare the driver for upcoming suspend/resume support. No functional change intended. Reviewed-by: Biju Das Signed-off-by: Tommaso Merciai Link: https://patch.msgid.link/1286f8600b542da55facf9920fed7c06b2b0e4d5.1767781092.git.tommaso.merciai.xr@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 46 +++++++++----------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 426a418f29b6..bb1d11693ec9 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -198,6 +198,8 @@ #define RENESAS_I3C_MAX_DEVS 8 #define I2C_INIT_MSG -1 +#define RENESAS_I3C_TCLK_IDX 1 + enum i3c_internal_state { I3C_INTERNAL_STATE_DISABLED, I3C_INTERNAL_STATE_CONTROLLER_IDLE, @@ -259,7 +261,8 @@ struct renesas_i3c { u8 addrs[RENESAS_I3C_MAX_DEVS]; struct renesas_i3c_xferqueue xferqueue; void __iomem *regs; - struct clk *tclk; + struct clk_bulk_data *clks; + u8 num_clks; }; struct renesas_i3c_i2c_dev_data { @@ -272,10 +275,6 @@ struct renesas_i3c_irq_desc { const char *desc; }; -struct renesas_i3c_config { - unsigned int has_pclkrw:1; -}; - static inline void renesas_i3c_reg_update(void __iomem *reg, u32 mask, u32 val) { u32 data = readl(reg); @@ -489,7 +488,7 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) int od_high_ticks, od_low_ticks, i2c_total_ticks; int ret; - rate = clk_get_rate(i3c->tclk); + rate = clk_get_rate(i3c->clks[RENESAS_I3C_TCLK_IDX].clk); if (!rate) return -EINVAL; @@ -1302,13 +1301,8 @@ static int renesas_i3c_probe(struct platform_device *pdev) { struct renesas_i3c *i3c; struct reset_control *reset; - struct clk *clk; - const struct renesas_i3c_config *config = of_device_get_match_data(&pdev->dev); int ret, i; - if (!config) - return -ENODATA; - i3c = devm_kzalloc(&pdev->dev, sizeof(*i3c), GFP_KERNEL); if (!i3c) return -ENOMEM; @@ -1317,19 +1311,12 @@ static int renesas_i3c_probe(struct platform_device *pdev) if (IS_ERR(i3c->regs)) return PTR_ERR(i3c->regs); - clk = devm_clk_get_enabled(&pdev->dev, "pclk"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - if (config->has_pclkrw) { - clk = devm_clk_get_enabled(&pdev->dev, "pclkrw"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - } - - i3c->tclk = devm_clk_get_enabled(&pdev->dev, "tclk"); - if (IS_ERR(i3c->tclk)) - return PTR_ERR(i3c->tclk); + ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &i3c->clks); + if (ret <= RENESAS_I3C_TCLK_IDX) + return dev_err_probe(&pdev->dev, ret < 0 ? ret : -EINVAL, + "Failed to get clocks (need > %d, got %d)\n", + RENESAS_I3C_TCLK_IDX, ret); + i3c->num_clks = ret; reset = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn"); if (IS_ERR(reset)) @@ -1374,16 +1361,9 @@ static void renesas_i3c_remove(struct platform_device *pdev) i3c_master_unregister(&i3c->base); } -static const struct renesas_i3c_config empty_i3c_config = { -}; - -static const struct renesas_i3c_config r9a09g047_i3c_config = { - .has_pclkrw = 1, -}; - static const struct of_device_id renesas_i3c_of_ids[] = { - { .compatible = "renesas,r9a08g045-i3c", .data = &empty_i3c_config }, - { .compatible = "renesas,r9a09g047-i3c", .data = &r9a09g047_i3c_config }, + { .compatible = "renesas,r9a08g045-i3c" }, + { .compatible = "renesas,r9a09g047-i3c" }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, renesas_i3c_of_ids); From ff4e4f03f0089635b3476d68f5b8833ea67adad6 Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Wed, 7 Jan 2026 11:33:48 +0100 Subject: [PATCH 21/52] i3c: renesas: Store clock rate and reset controls in struct renesas_i3c Update the struct renesas_i3c to store the clock rate, presetn and tresetn handlers. Replace local usage of the clock rate and reset controls with these structure fields. Simplify the code and prepare the driver for upcoming suspend/resume support. No functional change intended. Reviewed-by: Frank Li Signed-off-by: Tommaso Merciai Link: https://patch.msgid.link/9e1da95dd9137590c752ecd9429925afcbeb918b.1767781092.git.tommaso.merciai.xr@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 39 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index bb1d11693ec9..c1ab4edb3ab3 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -258,10 +258,13 @@ struct renesas_i3c { u32 free_pos; u32 i2c_STDBR; u32 i3c_STDBR; + unsigned long rate; u8 addrs[RENESAS_I3C_MAX_DEVS]; struct renesas_i3c_xferqueue xferqueue; void __iomem *regs; struct clk_bulk_data *clks; + struct reset_control *presetn; + struct reset_control *tresetn; u8 num_clks; }; @@ -482,22 +485,21 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) struct i3c_bus *bus = i3c_master_get_bus(m); struct i3c_device_info info = {}; struct i2c_timings t; - unsigned long rate; u32 double_SBR, val; int cks, pp_high_ticks, pp_low_ticks, i3c_total_ticks; int od_high_ticks, od_low_ticks, i2c_total_ticks; int ret; - rate = clk_get_rate(i3c->clks[RENESAS_I3C_TCLK_IDX].clk); - if (!rate) + i3c->rate = clk_get_rate(i3c->clks[RENESAS_I3C_TCLK_IDX].clk); + if (!i3c->rate) return -EINVAL; ret = renesas_i3c_reset(i3c); if (ret) return ret; - i2c_total_ticks = DIV_ROUND_UP(rate, bus->scl_rate.i2c); - i3c_total_ticks = DIV_ROUND_UP(rate, bus->scl_rate.i3c); + i2c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i2c); + i3c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i3c); i2c_parse_fw_timings(&m->dev, &t, true); @@ -510,7 +512,7 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) pp_high_ticks = ((i3c_total_ticks * 5) / 10); else pp_high_ticks = DIV_ROUND_UP(I3C_BUS_THIGH_MIXED_MAX_NS, - NSEC_PER_SEC / rate); + NSEC_PER_SEC / i3c->rate); pp_low_ticks = i3c_total_ticks - pp_high_ticks; if ((od_low_ticks / 2) <= 0xFF && pp_low_ticks < 0x3F) @@ -518,7 +520,7 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) i2c_total_ticks /= 2; i3c_total_ticks /= 2; - rate /= 2; + i3c->rate /= 2; } /* SCL clock period calculation in Open-drain mode */ @@ -539,8 +541,8 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) STDBR_SBRLP(pp_low_ticks) | STDBR_SBRHP(pp_high_ticks); - od_low_ticks -= t.scl_fall_ns / (NSEC_PER_SEC / rate) + 1; - od_high_ticks -= t.scl_rise_ns / (NSEC_PER_SEC / rate) + 1; + od_low_ticks -= t.scl_fall_ns / (NSEC_PER_SEC / i3c->rate) + 1; + od_high_ticks -= t.scl_rise_ns / (NSEC_PER_SEC / i3c->rate) + 1; i3c->i2c_STDBR = (double_SBR ? STDBR_DSBRPO : 0) | STDBR_SBRLO(double_SBR, od_low_ticks) | STDBR_SBRHO(double_SBR, od_high_ticks) | @@ -591,13 +593,13 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) renesas_set_bit(i3c->regs, SCSTRCTL, SCSTRCTL_ACKTWE); /* Bus condition timing */ - val = DIV_ROUND_UP(I3C_BUS_TBUF_MIXED_FM_MIN_NS, NSEC_PER_SEC / rate); + val = DIV_ROUND_UP(I3C_BUS_TBUF_MIXED_FM_MIN_NS, NSEC_PER_SEC / i3c->rate); renesas_writel(i3c->regs, BFRECDT, BFRECDT_FRECYC(val)); - val = DIV_ROUND_UP(I3C_BUS_TAVAL_MIN_NS, NSEC_PER_SEC / rate); + val = DIV_ROUND_UP(I3C_BUS_TAVAL_MIN_NS, NSEC_PER_SEC / i3c->rate); renesas_writel(i3c->regs, BAVLCDT, BAVLCDT_AVLCYC(val)); - val = DIV_ROUND_UP(I3C_BUS_TIDLE_MIN_NS, NSEC_PER_SEC / rate); + val = DIV_ROUND_UP(I3C_BUS_TIDLE_MIN_NS, NSEC_PER_SEC / i3c->rate); renesas_writel(i3c->regs, BIDLCDT, BIDLCDT_IDLCYC(val)); ret = i3c_master_get_free_addr(m, 0); @@ -1300,7 +1302,6 @@ static const struct renesas_i3c_irq_desc renesas_i3c_irqs[] = { static int renesas_i3c_probe(struct platform_device *pdev) { struct renesas_i3c *i3c; - struct reset_control *reset; int ret, i; i3c = devm_kzalloc(&pdev->dev, sizeof(*i3c), GFP_KERNEL); @@ -1318,14 +1319,14 @@ static int renesas_i3c_probe(struct platform_device *pdev) RENESAS_I3C_TCLK_IDX, ret); i3c->num_clks = ret; - reset = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn"); - if (IS_ERR(reset)) - return dev_err_probe(&pdev->dev, PTR_ERR(reset), + i3c->tresetn = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn"); + if (IS_ERR(i3c->tresetn)) + return dev_err_probe(&pdev->dev, PTR_ERR(i3c->tresetn), "Error: missing tresetn ctrl\n"); - reset = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "presetn"); - if (IS_ERR(reset)) - return dev_err_probe(&pdev->dev, PTR_ERR(reset), + i3c->presetn = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "presetn"); + if (IS_ERR(i3c->presetn)) + return dev_err_probe(&pdev->dev, PTR_ERR(i3c->presetn), "Error: missing presetn ctrl\n"); spin_lock_init(&i3c->xferqueue.lock); From 5eb3e8763e076a47ab145c5fafcf39f1ac7c7aee Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Wed, 7 Jan 2026 11:33:49 +0100 Subject: [PATCH 22/52] i3c: renesas: Factor out hardware initialization to separate function Move the hardware initialization sequence in renesas_i3c_bus_init() into a dedicated renesas_i3c_hw_init() helper. Simplify the code and prepare the driver for upcoming suspend/resume support. No functional change intended. Reviewed-by: Frank Li Signed-off-by: Tommaso Merciai Link: https://patch.msgid.link/795327270a6ceb23e15513a2619a19ae4876cfba.1767781092.git.tommaso.merciai.xr@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 99 ++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index c1ab4edb3ab3..008b6ee60e26 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -479,13 +479,65 @@ static int renesas_i3c_reset(struct renesas_i3c *i3c) 0, 1000, false, i3c->regs, RSTCTL); } +static void renesas_i3c_hw_init(struct renesas_i3c *i3c) +{ + u32 val; + + /* Disable Slave Mode */ + renesas_writel(i3c->regs, SVCTL, 0); + + /* Initialize Queue/Buffer threshold */ + renesas_writel(i3c->regs, NQTHCTL, NQTHCTL_IBIDSSZ(6) | + NQTHCTL_CMDQTH(1)); + + /* The only supported configuration is two entries*/ + renesas_writel(i3c->regs, NTBTHCTL0, 0); + /* Interrupt when there is one entry in the queue */ + renesas_writel(i3c->regs, NRQTHCTL, 0); + + /* Enable all Bus/Transfer Status Flags */ + renesas_writel(i3c->regs, BSTE, BSTE_ALL_FLAG); + renesas_writel(i3c->regs, NTSTE, NTSTE_ALL_FLAG); + + /* Interrupt enable settings */ + renesas_writel(i3c->regs, BIE, BIE_NACKDIE | BIE_TENDIE); + renesas_writel(i3c->regs, NTIE, 0); + + /* Clear Status register */ + renesas_writel(i3c->regs, NTST, 0); + renesas_writel(i3c->regs, INST, 0); + renesas_writel(i3c->regs, BST, 0); + + /* Hot-Join Acknowlege setting. */ + renesas_set_bit(i3c->regs, BCTL, BCTL_HJACKCTL); + + renesas_writel(i3c->regs, IBINCTL, IBINCTL_NRHJCTL | IBINCTL_NRMRCTL | + IBINCTL_NRSIRCTL); + + renesas_writel(i3c->regs, SCSTLCTL, 0); + renesas_set_bit(i3c->regs, SCSTRCTL, SCSTRCTL_ACKTWE); + + /* Bus condition timing */ + val = DIV_ROUND_UP(I3C_BUS_TBUF_MIXED_FM_MIN_NS, + NSEC_PER_SEC / i3c->rate); + renesas_writel(i3c->regs, BFRECDT, BFRECDT_FRECYC(val)); + + val = DIV_ROUND_UP(I3C_BUS_TAVAL_MIN_NS, + NSEC_PER_SEC / i3c->rate); + renesas_writel(i3c->regs, BAVLCDT, BAVLCDT_AVLCYC(val)); + + val = DIV_ROUND_UP(I3C_BUS_TIDLE_MIN_NS, + NSEC_PER_SEC / i3c->rate); + renesas_writel(i3c->regs, BIDLCDT, BIDLCDT_IDLCYC(val)); +} + static int renesas_i3c_bus_init(struct i3c_master_controller *m) { struct renesas_i3c *i3c = to_renesas_i3c(m); struct i3c_bus *bus = i3c_master_get_bus(m); struct i3c_device_info info = {}; struct i2c_timings t; - u32 double_SBR, val; + u32 double_SBR; int cks, pp_high_ticks, pp_low_ticks, i3c_total_ticks; int od_high_ticks, od_low_ticks, i2c_total_ticks; int ret; @@ -558,49 +610,8 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks)); - /* Disable Slave Mode */ - renesas_writel(i3c->regs, SVCTL, 0); - - /* Initialize Queue/Buffer threshold */ - renesas_writel(i3c->regs, NQTHCTL, NQTHCTL_IBIDSSZ(6) | - NQTHCTL_CMDQTH(1)); - - /* The only supported configuration is two entries*/ - renesas_writel(i3c->regs, NTBTHCTL0, 0); - /* Interrupt when there is one entry in the queue */ - renesas_writel(i3c->regs, NRQTHCTL, 0); - - /* Enable all Bus/Transfer Status Flags */ - renesas_writel(i3c->regs, BSTE, BSTE_ALL_FLAG); - renesas_writel(i3c->regs, NTSTE, NTSTE_ALL_FLAG); - - /* Interrupt enable settings */ - renesas_writel(i3c->regs, BIE, BIE_NACKDIE | BIE_TENDIE); - renesas_writel(i3c->regs, NTIE, 0); - - /* Clear Status register */ - renesas_writel(i3c->regs, NTST, 0); - renesas_writel(i3c->regs, INST, 0); - renesas_writel(i3c->regs, BST, 0); - - /* Hot-Join Acknowlege setting. */ - renesas_set_bit(i3c->regs, BCTL, BCTL_HJACKCTL); - - renesas_writel(i3c->regs, IBINCTL, IBINCTL_NRHJCTL | IBINCTL_NRMRCTL | - IBINCTL_NRSIRCTL); - - renesas_writel(i3c->regs, SCSTLCTL, 0); - renesas_set_bit(i3c->regs, SCSTRCTL, SCSTRCTL_ACKTWE); - - /* Bus condition timing */ - val = DIV_ROUND_UP(I3C_BUS_TBUF_MIXED_FM_MIN_NS, NSEC_PER_SEC / i3c->rate); - renesas_writel(i3c->regs, BFRECDT, BFRECDT_FRECYC(val)); - - val = DIV_ROUND_UP(I3C_BUS_TAVAL_MIN_NS, NSEC_PER_SEC / i3c->rate); - renesas_writel(i3c->regs, BAVLCDT, BAVLCDT_AVLCYC(val)); - - val = DIV_ROUND_UP(I3C_BUS_TIDLE_MIN_NS, NSEC_PER_SEC / i3c->rate); - renesas_writel(i3c->regs, BIDLCDT, BIDLCDT_IDLCYC(val)); + /* I3C hw init*/ + renesas_i3c_hw_init(i3c); ret = i3c_master_get_free_addr(m, 0); if (ret < 0) From e7218986319b4e903dfb4642dcbd1087cf16aaec Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Wed, 7 Jan 2026 11:33:50 +0100 Subject: [PATCH 23/52] i3c: renesas: Add suspend/resume support The Renesas I3C controller does not retain its register state across system suspend, requiring the driver to explicitly save and restore hardware configuration. Add suspend and resume NOIRQ callbacks to handle system sleep transitions. During suspend, save the Device Address Table (DAT) entries, assert reset lines, and disable all related clocks to allow the controller to enter a low-power state. On resume, re-enable clocks and reset lines in the proper order. Restore the REFCKCTL register, master dynamic address, and all DAT entries, then reinitialize the controller. Store the REFCLK divider value, and the master dynamic address to restore timing and addressing configuration after resume. Reviewed-by: Frank Li Signed-off-by: Tommaso Merciai Link: https://patch.msgid.link/c469ef89e0156d37746a85bfc314232847d1185a.1767781092.git.tommaso.merciai.xr@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 89 ++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 008b6ee60e26..528bccc2c68e 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -256,16 +256,19 @@ struct renesas_i3c { enum i3c_internal_state internal_state; u16 maxdevs; u32 free_pos; + u32 dyn_addr; u32 i2c_STDBR; u32 i3c_STDBR; unsigned long rate; u8 addrs[RENESAS_I3C_MAX_DEVS]; struct renesas_i3c_xferqueue xferqueue; void __iomem *regs; + u32 *DATBASn; struct clk_bulk_data *clks; struct reset_control *presetn; struct reset_control *tresetn; u8 num_clks; + u8 refclk_div; }; struct renesas_i3c_i2c_dev_data { @@ -609,6 +612,7 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) EXTBR_EBRHP(pp_high_ticks)); renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks)); + i3c->refclk_div = cks; /* I3C hw init*/ renesas_i3c_hw_init(i3c); @@ -617,6 +621,7 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) if (ret < 0) return ret; + i3c->dyn_addr = ret; renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYAD(ret) | MSDVAD_MDYADV); memset(&info, 0, sizeof(info)); @@ -1363,6 +1368,12 @@ static int renesas_i3c_probe(struct platform_device *pdev) i3c->maxdevs = RENESAS_I3C_MAX_DEVS; i3c->free_pos = GENMASK(i3c->maxdevs - 1, 0); + /* Allocate dynamic Device Address Table backup. */ + i3c->DATBASn = devm_kzalloc(&pdev->dev, sizeof(u32) * i3c->maxdevs, + GFP_KERNEL); + if (!i3c->DATBASn) + return -ENOMEM; + return i3c_master_register(&i3c->base, &pdev->dev, &renesas_i3c_ops, false); } @@ -1373,6 +1384,83 @@ static void renesas_i3c_remove(struct platform_device *pdev) i3c_master_unregister(&i3c->base); } +static int renesas_i3c_suspend_noirq(struct device *dev) +{ + struct renesas_i3c *i3c = dev_get_drvdata(dev); + int i, ret; + + i2c_mark_adapter_suspended(&i3c->base.i2c); + + /* Store Device Address Table values. */ + for (i = 0; i < i3c->maxdevs; i++) + i3c->DATBASn[i] = renesas_readl(i3c->regs, DATBAS(i)); + + ret = reset_control_assert(i3c->presetn); + if (ret) + goto err_mark_resumed; + + ret = reset_control_assert(i3c->tresetn); + if (ret) + goto err_presetn; + + clk_bulk_disable(i3c->num_clks, i3c->clks); + + return 0; + +err_presetn: + reset_control_deassert(i3c->presetn); +err_mark_resumed: + i2c_mark_adapter_resumed(&i3c->base.i2c); + + return ret; +} + +static int renesas_i3c_resume_noirq(struct device *dev) +{ + struct renesas_i3c *i3c = dev_get_drvdata(dev); + int i, ret; + + ret = reset_control_deassert(i3c->presetn); + if (ret) + return ret; + + ret = reset_control_deassert(i3c->tresetn); + if (ret) + goto err_presetn; + + ret = clk_bulk_enable(i3c->num_clks, i3c->clks); + if (ret) + goto err_tresetn; + + /* Re-store I3C registers value. */ + renesas_writel(i3c->regs, REFCKCTL, + REFCKCTL_IREFCKS(i3c->refclk_div)); + renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYADV | + MSDVAD_MDYAD(i3c->dyn_addr)); + + /* Restore Device Address Table values. */ + for (i = 0; i < i3c->maxdevs; i++) + renesas_writel(i3c->regs, DATBAS(i), i3c->DATBASn[i]); + + /* I3C hw init. */ + renesas_i3c_hw_init(i3c); + + i2c_mark_adapter_resumed(&i3c->base.i2c); + + return 0; + +err_tresetn: + reset_control_assert(i3c->tresetn); +err_presetn: + reset_control_assert(i3c->presetn); + return ret; +} + +static const struct dev_pm_ops renesas_i3c_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(renesas_i3c_suspend_noirq, + renesas_i3c_resume_noirq) +}; + static const struct of_device_id renesas_i3c_of_ids[] = { { .compatible = "renesas,r9a08g045-i3c" }, { .compatible = "renesas,r9a09g047-i3c" }, @@ -1386,6 +1474,7 @@ static struct platform_driver renesas_i3c = { .driver = { .name = "renesas-i3c", .of_match_table = renesas_i3c_of_ids, + .pm = pm_sleep_ptr(&renesas_i3c_pm_ops), }, }; module_platform_driver(renesas_i3c); From 3502cea99c7ceb331458cbd34ef6792c83144687 Mon Sep 17 00:00:00 2001 From: Billy Tsai Date: Mon, 12 Jan 2026 14:07:22 +0800 Subject: [PATCH 24/52] i3c: Move device name assignment after i3c_bus_init Move device name initialization to occur after i3c_bus_init() so that i3cbus->id is guaranteed to be assigned before it is used. Fixes: 9d4f219807d5 ("i3c: fix refcount inconsistency in i3c_master_register") Signed-off-by: Billy Tsai Reviewed-by: Frank Li Link: https://patch.msgid.link/20260112-upstream_i3c_fix-v1-1-cbbf2cb71809@aspeedtech.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index e551b1de5d7b..3bc8b4abf8af 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2911,7 +2911,6 @@ int i3c_master_register(struct i3c_master_controller *master, INIT_LIST_HEAD(&master->boardinfo.i3c); device_initialize(&master->dev); - dev_set_name(&master->dev, "i3c-%d", i3cbus->id); master->dev.dma_mask = parent->dma_mask; master->dev.coherent_dma_mask = parent->coherent_dma_mask; @@ -2921,6 +2920,8 @@ int i3c_master_register(struct i3c_master_controller *master, if (ret) goto err_put_dev; + dev_set_name(&master->dev, "i3c-%d", i3cbus->id); + ret = of_populate_i3c_bus(master); if (ret) goto err_put_dev; From 78f63ae4a82db173f93adca462e63d11ba06b126 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:42 +0200 Subject: [PATCH 25/52] i3c: mipi-i3c-hci: Reset RING_OPERATION1 fields during init The MIPI I3C HCI specification does not define reset values for RING_OPERATION1 fields, and some controllers (e.g., Intel) do not clear them during a software reset. Ensure the ring pointers are explicitly set to zero during bus initialization to avoid inconsistent state. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-2-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/dma.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c index 0f6bbe184e85..5515ed740ca4 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dma.c +++ b/drivers/i3c/master/mipi-i3c-hci/dma.c @@ -340,6 +340,14 @@ static int hci_dma_init(struct i3c_hci *hci) rh_reg_write(INTR_SIGNAL_ENABLE, regval); ring_ready: + /* + * The MIPI I3C HCI specification does not document reset values for + * RING_OPERATION1 fields and some controllers (e.g. Intel controllers) + * do not reset the values, so ensure the ring pointers are set to zero + * here. + */ + rh_reg_write(RING_OPERATION1, 0); + rh_reg_write(RING_CONTROL, RING_CTRL_ENABLE | RING_CTRL_RUN_STOP); } From 8bb96575883d3b201ce37046b3903ea1d2d50bbc Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:43 +0200 Subject: [PATCH 26/52] i3c: mipi-i3c-hci: Ensure proper bus clean-up Wait for the bus to fully disable before proceeding, ensuring that no operations are still in progress. Synchronize the IRQ handler only after interrupt signals have been disabled. This approach also handles cases where bus disable might fail, preventing race conditions and ensuring a consistent shutdown sequence. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-3-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 32 +++++++++++++++++++++++--- drivers/i3c/master/mipi-i3c-hci/dma.c | 7 ++++++ drivers/i3c/master/mipi-i3c-hci/hci.h | 1 + drivers/i3c/master/mipi-i3c-hci/pio.c | 2 ++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 6da5daf18166..0d3ec674878d 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -151,13 +151,39 @@ static int i3c_hci_bus_init(struct i3c_master_controller *m) return 0; } +/* Bus disable should never fail, so be generous with the timeout */ +#define BUS_DISABLE_TIMEOUT_US (500 * USEC_PER_MSEC) + +static int i3c_hci_bus_disable(struct i3c_hci *hci) +{ + u32 regval; + int ret; + + reg_clear(HC_CONTROL, HC_CONTROL_BUS_ENABLE); + + /* Ensure controller is disabled */ + ret = readx_poll_timeout(reg_read, HC_CONTROL, regval, + !(regval & HC_CONTROL_BUS_ENABLE), 0, BUS_DISABLE_TIMEOUT_US); + if (ret) + dev_err(&hci->master.dev, "%s: Failed to disable bus\n", __func__); + + return ret; +} + +void i3c_hci_sync_irq_inactive(struct i3c_hci *hci) +{ + struct platform_device *pdev = to_platform_device(hci->master.dev.parent); + int irq = platform_get_irq(pdev, 0); + + reg_write(INTR_SIGNAL_ENABLE, 0x0); + synchronize_irq(irq); +} + static void i3c_hci_bus_cleanup(struct i3c_master_controller *m) { struct i3c_hci *hci = to_i3c_hci(m); - struct platform_device *pdev = to_platform_device(m->dev.parent); - reg_clear(HC_CONTROL, HC_CONTROL_BUS_ENABLE); - synchronize_irq(platform_get_irq(pdev, 0)); + i3c_hci_bus_disable(hci); hci->io->cleanup(hci); if (hci->cmd == &mipi_i3c_hci_cmd_v1) mipi_i3c_hci_dat_v1.cleanup(hci); diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c index 5515ed740ca4..54849aa98fad 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dma.c +++ b/drivers/i3c/master/mipi-i3c-hci/dma.c @@ -160,6 +160,13 @@ static void hci_dma_cleanup(struct i3c_hci *hci) rh_reg_write(INTR_SIGNAL_ENABLE, 0); rh_reg_write(RING_CONTROL, 0); + } + + i3c_hci_sync_irq_inactive(hci); + + for (i = 0; i < rings->total; i++) { + rh = &rings->headers[i]; + rh_reg_write(CR_SETUP, 0); rh_reg_write(IBI_SETUP, 0); diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h index 3f88b67bc5cc..fd08b701d094 100644 --- a/drivers/i3c/master/mipi-i3c-hci/hci.h +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h @@ -142,5 +142,6 @@ void mipi_i3c_hci_pio_reset(struct i3c_hci *hci); void mipi_i3c_hci_dct_index_reset(struct i3c_hci *hci); void amd_set_od_pp_timing(struct i3c_hci *hci); void amd_set_resp_buf_thld(struct i3c_hci *hci); +void i3c_hci_sync_irq_inactive(struct i3c_hci *hci); #endif diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c index 109c6c5d83d6..90dca56fc0c5 100644 --- a/drivers/i3c/master/mipi-i3c-hci/pio.c +++ b/drivers/i3c/master/mipi-i3c-hci/pio.c @@ -211,6 +211,8 @@ static void hci_pio_cleanup(struct i3c_hci *hci) pio_reg_write(INTR_SIGNAL_ENABLE, 0x0); + i3c_hci_sync_irq_inactive(hci); + if (pio) { dev_dbg(&hci->master.dev, "status = %#x/%#x", pio_reg_read(INTR_STATUS), pio_reg_read(INTR_SIGNAL_ENABLE)); From f0775157b9f9a28ae3eabc8d05b0bc52e8056c80 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:44 +0200 Subject: [PATCH 27/52] i3c: master: Update hot-join flag only on success To prevent inconsistent state when an error occurs, ensure the hot-join flag is updated only when enabling or disabling hot-join succeeds. Fixes: 317bacf960a48 ("i3c: master: add enable(disable) hot join in sys entry") Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-4-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 3bc8b4abf8af..d684a6b79960 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -618,7 +618,8 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable) else ret = master->ops->disable_hotjoin(master); - master->hotjoin = enable; + if (!ret) + master->hotjoin = enable; i3c_bus_normaluse_unlock(&master->bus); From 471895799c2f46688792e175ced936ffeb6cdf01 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:45 +0200 Subject: [PATCH 28/52] i3c: master: Replace WARN_ON() with dev_err() in i3c_dev_free_ibi_locked() IBI disable failures are not indicative of a software bug, so using WARN_ON() is not appropriate. Replace these warnings with dev_err(). Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-5-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index d684a6b79960..71583cc4d197 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -3150,8 +3150,11 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev) if (!dev->ibi) return; - if (WARN_ON(dev->ibi->enabled)) - WARN_ON(i3c_dev_disable_ibi_locked(dev)); + if (dev->ibi->enabled) { + dev_err(&master->dev, "Freeing IBI that is still enabled\n"); + if (i3c_dev_disable_ibi_locked(dev)) + dev_err(&master->dev, "Failed to disable IBI before freeing\n"); + } master->ops->free_ibi(dev); From f64c1a46ea7c40bbab57a0cb1665a1c1e11da6af Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:46 +0200 Subject: [PATCH 29/52] i3c: mipi-i3c-hci: Switch DAT bitmap allocation to devm_bitmap_zalloc() The driver already uses managed resources, so convert the Device Address Table (DAT) bitmap allocation to use devm_bitmap_zalloc(). Remove the manual cleanup routine. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-6-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 2 -- drivers/i3c/master/mipi-i3c-hci/dat.h | 1 - drivers/i3c/master/mipi-i3c-hci/dat_v1.c | 11 +++-------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 0d3ec674878d..c4b249fde764 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -185,8 +185,6 @@ static void i3c_hci_bus_cleanup(struct i3c_master_controller *m) i3c_hci_bus_disable(hci); hci->io->cleanup(hci); - if (hci->cmd == &mipi_i3c_hci_cmd_v1) - mipi_i3c_hci_dat_v1.cleanup(hci); } void mipi_i3c_hci_resume(struct i3c_hci *hci) diff --git a/drivers/i3c/master/mipi-i3c-hci/dat.h b/drivers/i3c/master/mipi-i3c-hci/dat.h index 1f0f345c3daf..5277c65fc601 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dat.h +++ b/drivers/i3c/master/mipi-i3c-hci/dat.h @@ -17,7 +17,6 @@ struct hci_dat_ops { int (*init)(struct i3c_hci *hci); - void (*cleanup)(struct i3c_hci *hci); int (*alloc_entry)(struct i3c_hci *hci); void (*free_entry)(struct i3c_hci *hci, unsigned int dat_idx); void (*set_dynamic_addr)(struct i3c_hci *hci, unsigned int dat_idx, u8 addr); diff --git a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c index cc5d2deb23ab..c60ef5d77ca3 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c +++ b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c @@ -55,8 +55,10 @@ static int hci_dat_v1_init(struct i3c_hci *hci) } if (!hci->DAT_data) { + struct device *dev = hci->master.dev.parent; + /* use a bitmap for faster free slot search */ - hci->DAT_data = bitmap_zalloc(hci->DAT_entries, GFP_KERNEL); + hci->DAT_data = devm_bitmap_zalloc(dev, hci->DAT_entries, GFP_KERNEL); if (!hci->DAT_data) return -ENOMEM; @@ -70,12 +72,6 @@ static int hci_dat_v1_init(struct i3c_hci *hci) return 0; } -static void hci_dat_v1_cleanup(struct i3c_hci *hci) -{ - bitmap_free(hci->DAT_data); - hci->DAT_data = NULL; -} - static int hci_dat_v1_alloc_entry(struct i3c_hci *hci) { unsigned int dat_idx; @@ -170,7 +166,6 @@ static int hci_dat_v1_get_index(struct i3c_hci *hci, u8 dev_addr) const struct hci_dat_ops mipi_i3c_hci_dat_v1 = { .init = hci_dat_v1_init, - .cleanup = hci_dat_v1_cleanup, .alloc_entry = hci_dat_v1_alloc_entry, .free_entry = hci_dat_v1_free_entry, .set_dynamic_addr = hci_dat_v1_set_dynamic_addr, From 11d17c2855bfc04550557017eae02e92f3eeab1c Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:47 +0200 Subject: [PATCH 30/52] i3c: mipi-i3c-hci: Switch PIO data allocation to devm_kzalloc() The driver already uses managed resources, so convert the PIO data structure allocation to devm_zalloc(). Remove the manual kfree(). Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-7-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/pio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c index 90dca56fc0c5..3d633abf6099 100644 --- a/drivers/i3c/master/mipi-i3c-hci/pio.c +++ b/drivers/i3c/master/mipi-i3c-hci/pio.c @@ -140,7 +140,7 @@ static int hci_pio_init(struct i3c_hci *hci) struct hci_pio_data *pio; u32 val, size_val, rx_thresh, tx_thresh, ibi_val; - pio = kzalloc(sizeof(*pio), GFP_KERNEL); + pio = devm_kzalloc(hci->master.dev.parent, sizeof(*pio), GFP_KERNEL); if (!pio) return -ENOMEM; @@ -220,8 +220,6 @@ static void hci_pio_cleanup(struct i3c_hci *hci) BUG_ON(pio->curr_rx); BUG_ON(pio->curr_tx); BUG_ON(pio->curr_resp); - kfree(pio); - hci->io_data = NULL; } } From 29bf98a6346ad7a80136ba58116c2a0f8a3cb03f Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:48 +0200 Subject: [PATCH 31/52] i3c: mipi-i3c-hci: Manage DMA deallocation via devres action The driver already uses devres for resource management, but the standard resource-managed DMA allocation helpers cannot be used because they assume the DMA device matches the managed device. To address this, factor out the deallocation logic from hci_dma_cleanup() into a new helper, hci_dma_free(), and register it as a devres action. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-8-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/dma.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c index 54849aa98fad..4999cf3d9674 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dma.c +++ b/drivers/i3c/master/mipi-i3c-hci/dma.c @@ -169,6 +169,22 @@ static void hci_dma_cleanup(struct i3c_hci *hci) rh_reg_write(CR_SETUP, 0); rh_reg_write(IBI_SETUP, 0); + } + + rhs_reg_write(CONTROL, 0); +} + +static void hci_dma_free(void *data) +{ + struct i3c_hci *hci = data; + struct hci_rings_data *rings = hci->io_data; + struct hci_rh_data *rh; + + if (!rings) + return; + + for (int i = 0; i < rings->total; i++) { + rh = &rings->headers[i]; if (rh->xfer) dma_free_coherent(rings->sysdev, @@ -190,8 +206,6 @@ static void hci_dma_cleanup(struct i3c_hci *hci) kfree(rh->ibi_data); } - rhs_reg_write(CONTROL, 0); - kfree(rings); hci->io_data = NULL; } @@ -359,10 +373,15 @@ ring_ready: RING_CTRL_RUN_STOP); } + ret = devm_add_action(hci->master.dev.parent, hci_dma_free, hci); + if (ret) + goto err_out; + return 0; err_out: hci_dma_cleanup(hci); + hci_dma_free(hci); return ret; } From a372cfac056abb555d6c70c08af548aea0ab5cde Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:49 +0200 Subject: [PATCH 32/52] i3c: mipi-i3c-hci: Cache DAT in memory for Runtime PM restore Prepare for Runtime PM support, which requires restoring the Device Address Table (DAT) registers after resume. Maintain a copy of DAT in memory so it can be reprogrammed when the controller is powered back up. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-9-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/dat_v1.c | 29 +++++++++++++++++++----- drivers/i3c/master/mipi-i3c-hci/hci.h | 6 +++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c index c60ef5d77ca3..644ab939be1c 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c +++ b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c @@ -34,13 +34,26 @@ /* DAT_0_IBI_PAYLOAD W0_BIT_(12) */ #define DAT_0_STATIC_ADDRESS W0_MASK(6, 0) -#define dat_w0_read(i) readl(hci->DAT_regs + (i) * 8) -#define dat_w1_read(i) readl(hci->DAT_regs + (i) * 8 + 4) -#define dat_w0_write(i, v) writel(v, hci->DAT_regs + (i) * 8) -#define dat_w1_write(i, v) writel(v, hci->DAT_regs + (i) * 8 + 4) +#define dat_w0_read(i) hci->DAT[i].w0 +#define dat_w1_read(i) hci->DAT[i].w1 +#define dat_w0_write(i, v) hci_dat_w0_write(hci, i, v) +#define dat_w1_write(i, v) hci_dat_w1_write(hci, i, v) + +static inline void hci_dat_w0_write(struct i3c_hci *hci, int i, u32 v) +{ + hci->DAT[i].w0 = v; + writel(v, hci->DAT_regs + i * 8); +} + +static inline void hci_dat_w1_write(struct i3c_hci *hci, int i, u32 v) +{ + hci->DAT[i].w1 = v; + writel(v, hci->DAT_regs + i * 8 + 4); +} static int hci_dat_v1_init(struct i3c_hci *hci) { + struct device *dev = hci->master.dev.parent; unsigned int dat_idx; if (!hci->DAT_regs) { @@ -54,9 +67,13 @@ static int hci_dat_v1_init(struct i3c_hci *hci) return -EOPNOTSUPP; } - if (!hci->DAT_data) { - struct device *dev = hci->master.dev.parent; + if (!hci->DAT) { + hci->DAT = devm_kcalloc(dev, hci->DAT_entries, hci->DAT_entry_size, GFP_KERNEL); + if (!hci->DAT) + return -ENOMEM; + } + if (!hci->DAT_data) { /* use a bitmap for faster free slot search */ hci->DAT_data = devm_bitmap_zalloc(dev, hci->DAT_entries, GFP_KERNEL); if (!hci->DAT_data) diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h index fd08b701d094..aa8a03594e64 100644 --- a/drivers/i3c/master/mipi-i3c-hci/hci.h +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h @@ -31,6 +31,11 @@ struct hci_cmd_ops; +struct dat_words { + u32 w0; + u32 w1; +}; + /* Our main structure */ struct i3c_hci { struct i3c_master_controller master; @@ -51,6 +56,7 @@ struct i3c_hci { unsigned int DAT_entries; unsigned int DAT_entry_size; void *DAT_data; + struct dat_words *DAT; unsigned int DCT_entries; unsigned int DCT_entry_size; u8 version_major; From f180524a4877329bca4285e36f1fad63b70577ea Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:50 +0200 Subject: [PATCH 33/52] i3c: mipi-i3c-hci: Introduce helper to restore DAT Add a dedicated function to restore the Device Address Table (DAT) in preparation for Runtime PM support. This will allow reprogramming the DAT after the controller resumes from a low-power state. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-10-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/dat.h | 1 + drivers/i3c/master/mipi-i3c-hci/dat_v1.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/drivers/i3c/master/mipi-i3c-hci/dat.h b/drivers/i3c/master/mipi-i3c-hci/dat.h index 5277c65fc601..6881f19da77f 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dat.h +++ b/drivers/i3c/master/mipi-i3c-hci/dat.h @@ -24,6 +24,7 @@ struct hci_dat_ops { void (*set_flags)(struct i3c_hci *hci, unsigned int dat_idx, u32 w0, u32 w1); void (*clear_flags)(struct i3c_hci *hci, unsigned int dat_idx, u32 w0, u32 w1); int (*get_index)(struct i3c_hci *hci, u8 address); + void (*restore)(struct i3c_hci *hci); }; extern const struct hci_dat_ops mipi_i3c_hci_dat_v1; diff --git a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c index 644ab939be1c..852966aa20d9 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c +++ b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c @@ -181,6 +181,14 @@ static int hci_dat_v1_get_index(struct i3c_hci *hci, u8 dev_addr) return -ENODEV; } +static void hci_dat_v1_restore(struct i3c_hci *hci) +{ + for (int i = 0; i < hci->DAT_entries; i++) { + writel(hci->DAT[i].w0, hci->DAT_regs + i * 8); + writel(hci->DAT[i].w1, hci->DAT_regs + i * 8 + 4); + } +} + const struct hci_dat_ops mipi_i3c_hci_dat_v1 = { .init = hci_dat_v1_init, .alloc_entry = hci_dat_v1_alloc_entry, @@ -190,4 +198,5 @@ const struct hci_dat_ops mipi_i3c_hci_dat_v1 = { .set_flags = hci_dat_v1_set_flags, .clear_flags = hci_dat_v1_clear_flags, .get_index = hci_dat_v1_get_index, + .restore = hci_dat_v1_restore, }; From f5401c973e7f08cdd921c62079ac2514a9b69397 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:51 +0200 Subject: [PATCH 34/52] i3c: mipi-i3c-hci: Extract ring initialization from hci_dma_init() Split the ring setup logic out of hci_dma_init() into a new helper hci_dma_init_rings(). This refactoring prepares for Runtime PM support by allowing DMA rings to be reinitialized independently after resume. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-11-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/dma.c | 118 +++++++++++++++----------- 1 file changed, 68 insertions(+), 50 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c index 4999cf3d9674..9ed69da52977 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dma.c +++ b/drivers/i3c/master/mipi-i3c-hci/dma.c @@ -210,6 +210,71 @@ static void hci_dma_free(void *data) hci->io_data = NULL; } +static void hci_dma_init_rh(struct i3c_hci *hci, struct hci_rh_data *rh, int i) +{ + u32 regval; + + rh_reg_write(CMD_RING_BASE_LO, lower_32_bits(rh->xfer_dma)); + rh_reg_write(CMD_RING_BASE_HI, upper_32_bits(rh->xfer_dma)); + rh_reg_write(RESP_RING_BASE_LO, lower_32_bits(rh->resp_dma)); + rh_reg_write(RESP_RING_BASE_HI, upper_32_bits(rh->resp_dma)); + + regval = FIELD_PREP(CR_RING_SIZE, rh->xfer_entries); + rh_reg_write(CR_SETUP, regval); + + rh_reg_write(INTR_STATUS_ENABLE, 0xffffffff); + rh_reg_write(INTR_SIGNAL_ENABLE, INTR_IBI_READY | + INTR_TRANSFER_COMPLETION | + INTR_RING_OP | + INTR_TRANSFER_ERR | + INTR_IBI_RING_FULL | + INTR_TRANSFER_ABORT); + + if (i >= IBI_RINGS) + goto ring_ready; + + rh_reg_write(IBI_STATUS_RING_BASE_LO, lower_32_bits(rh->ibi_status_dma)); + rh_reg_write(IBI_STATUS_RING_BASE_HI, upper_32_bits(rh->ibi_status_dma)); + rh_reg_write(IBI_DATA_RING_BASE_LO, lower_32_bits(rh->ibi_data_dma)); + rh_reg_write(IBI_DATA_RING_BASE_HI, upper_32_bits(rh->ibi_data_dma)); + + regval = FIELD_PREP(IBI_STATUS_RING_SIZE, rh->ibi_status_entries) | + FIELD_PREP(IBI_DATA_CHUNK_SIZE, ilog2(rh->ibi_chunk_sz) - 2) | + FIELD_PREP(IBI_DATA_CHUNK_COUNT, rh->ibi_chunks_total); + rh_reg_write(IBI_SETUP, regval); + + regval = rh_reg_read(INTR_SIGNAL_ENABLE); + regval |= INTR_IBI_READY; + rh_reg_write(INTR_SIGNAL_ENABLE, regval); + +ring_ready: + /* + * The MIPI I3C HCI specification does not document reset values for + * RING_OPERATION1 fields and some controllers (e.g. Intel controllers) + * do not reset the values, so ensure the ring pointers are set to zero + * here. + */ + rh_reg_write(RING_OPERATION1, 0); + + rh_reg_write(RING_CONTROL, RING_CTRL_ENABLE); + rh_reg_write(RING_CONTROL, RING_CTRL_ENABLE | RING_CTRL_RUN_STOP); + + rh->done_ptr = 0; + rh->ibi_chunk_ptr = 0; +} + +static void hci_dma_init_rings(struct i3c_hci *hci) +{ + struct hci_rings_data *rings = hci->io_data; + u32 regval; + + regval = FIELD_PREP(MAX_HEADER_COUNT, rings->total); + rhs_reg_write(CONTROL, regval); + + for (int i = 0; i < rings->total; i++) + hci_dma_init_rh(hci, &rings->headers[i], i); +} + static int hci_dma_init(struct i3c_hci *hci) { struct hci_rings_data *rings; @@ -247,9 +312,6 @@ static int hci_dma_init(struct i3c_hci *hci) rings->total = nr_rings; rings->sysdev = sysdev; - regval = FIELD_PREP(MAX_HEADER_COUNT, rings->total); - rhs_reg_write(CONTROL, regval); - for (i = 0; i < rings->total; i++) { u32 offset = rhs_reg_read(RHn_OFFSET(i)); @@ -284,26 +346,10 @@ static int hci_dma_init(struct i3c_hci *hci) if (!rh->xfer || !rh->resp || !rh->src_xfers) goto err_out; - rh_reg_write(CMD_RING_BASE_LO, lower_32_bits(rh->xfer_dma)); - rh_reg_write(CMD_RING_BASE_HI, upper_32_bits(rh->xfer_dma)); - rh_reg_write(RESP_RING_BASE_LO, lower_32_bits(rh->resp_dma)); - rh_reg_write(RESP_RING_BASE_HI, upper_32_bits(rh->resp_dma)); - - regval = FIELD_PREP(CR_RING_SIZE, rh->xfer_entries); - rh_reg_write(CR_SETUP, regval); - - rh_reg_write(INTR_STATUS_ENABLE, 0xffffffff); - rh_reg_write(INTR_SIGNAL_ENABLE, INTR_IBI_READY | - INTR_TRANSFER_COMPLETION | - INTR_RING_OP | - INTR_TRANSFER_ERR | - INTR_IBI_RING_FULL | - INTR_TRANSFER_ABORT); - /* IBIs */ if (i >= IBI_RINGS) - goto ring_ready; + continue; regval = rh_reg_read(IBI_SETUP); rh->ibi_status_sz = FIELD_GET(IBI_STATUS_STRUCT_SIZE, regval); @@ -342,45 +388,17 @@ static int hci_dma_init(struct i3c_hci *hci) ret = -ENOMEM; goto err_out; } - - rh_reg_write(IBI_STATUS_RING_BASE_LO, lower_32_bits(rh->ibi_status_dma)); - rh_reg_write(IBI_STATUS_RING_BASE_HI, upper_32_bits(rh->ibi_status_dma)); - rh_reg_write(IBI_DATA_RING_BASE_LO, lower_32_bits(rh->ibi_data_dma)); - rh_reg_write(IBI_DATA_RING_BASE_HI, upper_32_bits(rh->ibi_data_dma)); - - regval = FIELD_PREP(IBI_STATUS_RING_SIZE, - rh->ibi_status_entries) | - FIELD_PREP(IBI_DATA_CHUNK_SIZE, - ilog2(rh->ibi_chunk_sz) - 2) | - FIELD_PREP(IBI_DATA_CHUNK_COUNT, - rh->ibi_chunks_total); - rh_reg_write(IBI_SETUP, regval); - - regval = rh_reg_read(INTR_SIGNAL_ENABLE); - regval |= INTR_IBI_READY; - rh_reg_write(INTR_SIGNAL_ENABLE, regval); - -ring_ready: - /* - * The MIPI I3C HCI specification does not document reset values for - * RING_OPERATION1 fields and some controllers (e.g. Intel controllers) - * do not reset the values, so ensure the ring pointers are set to zero - * here. - */ - rh_reg_write(RING_OPERATION1, 0); - - rh_reg_write(RING_CONTROL, RING_CTRL_ENABLE | - RING_CTRL_RUN_STOP); } ret = devm_add_action(hci->master.dev.parent, hci_dma_free, hci); if (ret) goto err_out; + hci_dma_init_rings(hci); + return 0; err_out: - hci_dma_cleanup(hci); hci_dma_free(hci); return ret; } From 8169587204431ce23df9522c2e859b5238934c42 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:52 +0200 Subject: [PATCH 35/52] i3c: mipi-i3c-hci: Add DMA suspend and resume support Introduce helper functions to suspend and resume DMA operations. These are required to prepare for upcoming Runtime PM support, ensuring that DMA state is properly managed during power transitions. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-12-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/dma.c | 25 +++++++++++++++++++++++++ drivers/i3c/master/mipi-i3c-hci/hci.h | 2 ++ 2 files changed, 27 insertions(+) diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c index 9ed69da52977..2e5b4974d431 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dma.c +++ b/drivers/i3c/master/mipi-i3c-hci/dma.c @@ -275,6 +275,29 @@ static void hci_dma_init_rings(struct i3c_hci *hci) hci_dma_init_rh(hci, &rings->headers[i], i); } +static void hci_dma_suspend(struct i3c_hci *hci) +{ + struct hci_rings_data *rings = hci->io_data; + int n = rings ? rings->total : 0; + + for (int i = 0; i < n; i++) { + struct hci_rh_data *rh = &rings->headers[i]; + + rh_reg_write(INTR_SIGNAL_ENABLE, 0); + rh_reg_write(RING_CONTROL, 0); + } + + i3c_hci_sync_irq_inactive(hci); +} + +static void hci_dma_resume(struct i3c_hci *hci) +{ + struct hci_rings_data *rings = hci->io_data; + + if (rings) + hci_dma_init_rings(hci); +} + static int hci_dma_init(struct i3c_hci *hci) { struct hci_rings_data *rings; @@ -865,4 +888,6 @@ const struct hci_io_ops mipi_i3c_hci_dma = { .request_ibi = hci_dma_request_ibi, .free_ibi = hci_dma_free_ibi, .recycle_ibi_slot = hci_dma_recycle_ibi_slot, + .suspend = hci_dma_suspend, + .resume = hci_dma_resume, }; diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h index aa8a03594e64..38f927685d3b 100644 --- a/drivers/i3c/master/mipi-i3c-hci/hci.h +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h @@ -125,6 +125,8 @@ struct hci_io_ops { struct i3c_ibi_slot *slot); int (*init)(struct i3c_hci *hci); void (*cleanup)(struct i3c_hci *hci); + void (*suspend)(struct i3c_hci *hci); + void (*resume)(struct i3c_hci *hci); }; extern const struct hci_io_ops mipi_i3c_hci_pio; From ca4d4682d353bf4e7e5db7b025e9ecd80bc67b27 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:53 +0200 Subject: [PATCH 36/52] i3c: mipi-i3c-hci: Refactor PIO register initialization Move the PIO register setup logic out of hci_pio_init() into a new helper, __hci_pio_init(). This refactoring prepares for Runtime PM support by allowing PIO registers to be reinitialized independently after resume. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-13-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/pio.c | 45 +++++++++++++++++---------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c index 3d633abf6099..52d9f01d9ca9 100644 --- a/drivers/i3c/master/mipi-i3c-hci/pio.c +++ b/drivers/i3c/master/mipi-i3c-hci/pio.c @@ -135,27 +135,14 @@ struct hci_pio_data { u32 enabled_irqs; }; -static int hci_pio_init(struct i3c_hci *hci) +static void __hci_pio_init(struct i3c_hci *hci, u32 *size_val_ptr) { - struct hci_pio_data *pio; u32 val, size_val, rx_thresh, tx_thresh, ibi_val; - - pio = devm_kzalloc(hci->master.dev.parent, sizeof(*pio), GFP_KERNEL); - if (!pio) - return -ENOMEM; - - hci->io_data = pio; - spin_lock_init(&pio->lock); + struct hci_pio_data *pio = hci->io_data; size_val = pio_reg_read(QUEUE_SIZE); - dev_dbg(&hci->master.dev, "CMD/RESP FIFO = %ld entries\n", - FIELD_GET(CR_QUEUE_SIZE, size_val)); - dev_dbg(&hci->master.dev, "IBI FIFO = %ld bytes\n", - 4 * FIELD_GET(IBI_STATUS_SIZE, size_val)); - dev_dbg(&hci->master.dev, "RX data FIFO = %d bytes\n", - 4 * (2 << FIELD_GET(RX_DATA_BUFFER_SIZE, size_val))); - dev_dbg(&hci->master.dev, "TX data FIFO = %d bytes\n", - 4 * (2 << FIELD_GET(TX_DATA_BUFFER_SIZE, size_val))); + if (size_val_ptr) + *size_val_ptr = size_val; /* * Let's initialize data thresholds to half of the actual FIFO size. @@ -201,6 +188,30 @@ static int hci_pio_init(struct i3c_hci *hci) /* Always accept error interrupts (will be activated on first xfer) */ pio->enabled_irqs = STAT_ALL_ERRORS; +} + +static int hci_pio_init(struct i3c_hci *hci) +{ + struct hci_pio_data *pio; + u32 size_val; + + pio = devm_kzalloc(hci->master.dev.parent, sizeof(*pio), GFP_KERNEL); + if (!pio) + return -ENOMEM; + + hci->io_data = pio; + spin_lock_init(&pio->lock); + + __hci_pio_init(hci, &size_val); + + dev_dbg(&hci->master.dev, "CMD/RESP FIFO = %ld entries\n", + FIELD_GET(CR_QUEUE_SIZE, size_val)); + dev_dbg(&hci->master.dev, "IBI FIFO = %ld bytes\n", + 4 * FIELD_GET(IBI_STATUS_SIZE, size_val)); + dev_dbg(&hci->master.dev, "RX data FIFO = %d bytes\n", + 4 * (2 << FIELD_GET(RX_DATA_BUFFER_SIZE, size_val))); + dev_dbg(&hci->master.dev, "TX data FIFO = %d bytes\n", + 4 * (2 << FIELD_GET(TX_DATA_BUFFER_SIZE, size_val))); return 0; } From 8afa0dd83b608a344b967dc1ef1b4f282662416d Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:54 +0200 Subject: [PATCH 37/52] i3c: mipi-i3c-hci: Add PIO suspend and resume support Introduce helper functions to suspend and resume PIO operations. These are required to prepare for upcoming Runtime PM support, ensuring that PIO state is properly managed during power transitions. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-14-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/pio.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c index 52d9f01d9ca9..8e868e81acda 100644 --- a/drivers/i3c/master/mipi-i3c-hci/pio.c +++ b/drivers/i3c/master/mipi-i3c-hci/pio.c @@ -190,6 +190,18 @@ static void __hci_pio_init(struct i3c_hci *hci, u32 *size_val_ptr) pio->enabled_irqs = STAT_ALL_ERRORS; } +static void hci_pio_suspend(struct i3c_hci *hci) +{ + pio_reg_write(INTR_SIGNAL_ENABLE, 0); + + i3c_hci_sync_irq_inactive(hci); +} + +static void hci_pio_resume(struct i3c_hci *hci) +{ + __hci_pio_init(hci, NULL); +} + static int hci_pio_init(struct i3c_hci *hci) { struct hci_pio_data *pio; @@ -1059,4 +1071,6 @@ const struct hci_io_ops mipi_i3c_hci_pio = { .request_ibi = hci_pio_request_ibi, .free_ibi = hci_pio_free_ibi, .recycle_ibi_slot = hci_pio_recycle_ibi_slot, + .suspend = hci_pio_suspend, + .resume = hci_pio_resume, }; From 57a2f976ac18b909e43cca51a63be26cbd62ab88 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:55 +0200 Subject: [PATCH 38/52] i3c: mipi-i3c-hci: Factor out software reset into helper Prepare for future reuse of the reset sequence in other contexts, such as power management. Move the software reset logic from i3c_hci_init() into a dedicated helper function, i3c_hci_software_reset(). Software reset should never fail. Print an error message if it does. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-15-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 41 ++++++++++++++++++-------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index c4b249fde764..3b0609cb7da7 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -585,6 +585,34 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id) return result; } +static int i3c_hci_software_reset(struct i3c_hci *hci) +{ + u32 regval; + int ret; + + /* + * SOFT_RST must be clear before we write to it. + * Then we must wait until it clears again. + */ + ret = readx_poll_timeout(reg_read, RESET_CONTROL, regval, + !(regval & SOFT_RST), 0, 10 * USEC_PER_MSEC); + if (ret) { + dev_err(&hci->master.dev, "%s: Software reset stuck\n", __func__); + return ret; + } + + reg_write(RESET_CONTROL, SOFT_RST); + + ret = readx_poll_timeout(reg_read, RESET_CONTROL, regval, + !(regval & SOFT_RST), 0, 10 * USEC_PER_MSEC); + if (ret) { + dev_err(&hci->master.dev, "%s: Software reset failed\n", __func__); + return ret; + } + + return 0; +} + static int i3c_hci_init(struct i3c_hci *hci) { bool size_in_dwords, mode_selector; @@ -654,18 +682,7 @@ static int i3c_hci_init(struct i3c_hci *hci) if (ret) return ret; - /* - * Now let's reset the hardware. - * SOFT_RST must be clear before we write to it. - * Then we must wait until it clears again. - */ - ret = readx_poll_timeout(reg_read, RESET_CONTROL, regval, - !(regval & SOFT_RST), 1, 10000); - if (ret) - return -ENXIO; - reg_write(RESET_CONTROL, SOFT_RST); - ret = readx_poll_timeout(reg_read, RESET_CONTROL, regval, - !(regval & SOFT_RST), 1, 10000); + ret = i3c_hci_software_reset(hci); if (ret) return -ENXIO; From e4269df518d62527ff6a8f3cd4740d754c4257cd Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:56 +0200 Subject: [PATCH 39/52] i3c: mipi-i3c-hci: Factor out IO mode setting into helper Prepare for future reuse. Move the IO mode setting logic from i3c_hci_init() into a dedicated helper function, i3c_hci_set_io_mode(). Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-16-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 45 ++++++++++++++++++-------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 3b0609cb7da7..569c8584045a 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -613,9 +613,35 @@ static int i3c_hci_software_reset(struct i3c_hci *hci) return 0; } +static inline bool is_version_1_1_or_newer(struct i3c_hci *hci) +{ + return hci->version_major > 1 || (hci->version_major == 1 && hci->version_minor > 0); +} + +static int i3c_hci_set_io_mode(struct i3c_hci *hci, bool dma) +{ + bool pio_mode; + + if (dma) + reg_clear(HC_CONTROL, HC_CONTROL_PIO_MODE); + else + reg_set(HC_CONTROL, HC_CONTROL_PIO_MODE); + + if (!is_version_1_1_or_newer(hci)) + return 0; + + pio_mode = reg_read(HC_CONTROL) & HC_CONTROL_PIO_MODE; + if ((dma && pio_mode) || (!dma && !pio_mode)) { + dev_err(&hci->master.dev, "%s mode is stuck\n", pio_mode ? "PIO" : "DMA"); + return -EIO; + } + + return 0; +} + static int i3c_hci_init(struct i3c_hci *hci) { - bool size_in_dwords, mode_selector; + bool size_in_dwords; u32 regval, offset; int ret; @@ -732,20 +758,14 @@ static int i3c_hci_init(struct i3c_hci *hci) return -EINVAL; } - mode_selector = hci->version_major > 1 || - (hci->version_major == 1 && hci->version_minor > 0); - /* Quirk for HCI_QUIRK_PIO_MODE on AMD platforms */ if (hci->quirks & HCI_QUIRK_PIO_MODE) hci->RHS_regs = NULL; /* Try activating DMA operations first */ if (hci->RHS_regs) { - reg_clear(HC_CONTROL, HC_CONTROL_PIO_MODE); - if (mode_selector && (reg_read(HC_CONTROL) & HC_CONTROL_PIO_MODE)) { - dev_err(&hci->master.dev, "PIO mode is stuck\n"); - ret = -EIO; - } else { + ret = i3c_hci_set_io_mode(hci, true); + if (!ret) { hci->io = &mipi_i3c_hci_dma; dev_dbg(&hci->master.dev, "Using DMA\n"); } @@ -753,11 +773,8 @@ static int i3c_hci_init(struct i3c_hci *hci) /* If no DMA, try PIO */ if (!hci->io && hci->PIO_regs) { - reg_set(HC_CONTROL, HC_CONTROL_PIO_MODE); - if (mode_selector && !(reg_read(HC_CONTROL) & HC_CONTROL_PIO_MODE)) { - dev_err(&hci->master.dev, "DMA mode is stuck\n"); - ret = -EIO; - } else { + ret = i3c_hci_set_io_mode(hci, false); + if (!ret) { hci->io = &mipi_i3c_hci_pio; dev_dbg(&hci->master.dev, "Using PIO\n"); } From 7f91e0e6aa3f1e6e461dd5f95b5bcc3567abfa51 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:57 +0200 Subject: [PATCH 40/52] i3c: mipi-i3c-hci: Factor out core initialization into helper Prepare for future reuse. Move core initialization logic from i3c_hci_init() into a dedicated helper function, i3c_hci_reset_and_init(). Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-17-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 142 +++++++++++++------------ 1 file changed, 75 insertions(+), 67 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 569c8584045a..8b8e3952d41d 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -639,6 +639,80 @@ static int i3c_hci_set_io_mode(struct i3c_hci *hci, bool dma) return 0; } +static int i3c_hci_reset_and_init(struct i3c_hci *hci) +{ + u32 regval; + int ret; + + ret = i3c_hci_software_reset(hci); + if (ret) + return -ENXIO; + + /* Disable all interrupts */ + reg_write(INTR_SIGNAL_ENABLE, 0x0); + /* + * Only allow bit 31:10 signal updates because + * Bit 0:9 are reserved in IP version >= 0.8 + * Bit 0:5 are defined in IP version < 0.8 but not handled by PIO code + */ + reg_write(INTR_STATUS_ENABLE, GENMASK(31, 10)); + + /* Make sure our data ordering fits the host's */ + regval = reg_read(HC_CONTROL); + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) { + if (!(regval & HC_CONTROL_DATA_BIG_ENDIAN)) { + regval |= HC_CONTROL_DATA_BIG_ENDIAN; + reg_write(HC_CONTROL, regval); + regval = reg_read(HC_CONTROL); + if (!(regval & HC_CONTROL_DATA_BIG_ENDIAN)) { + dev_err(&hci->master.dev, "cannot set BE mode\n"); + return -EOPNOTSUPP; + } + } + } else { + if (regval & HC_CONTROL_DATA_BIG_ENDIAN) { + regval &= ~HC_CONTROL_DATA_BIG_ENDIAN; + reg_write(HC_CONTROL, regval); + regval = reg_read(HC_CONTROL); + if (regval & HC_CONTROL_DATA_BIG_ENDIAN) { + dev_err(&hci->master.dev, "cannot clear BE mode\n"); + return -EOPNOTSUPP; + } + } + } + + /* Try activating DMA operations first */ + if (hci->RHS_regs) { + ret = i3c_hci_set_io_mode(hci, true); + if (!ret) { + hci->io = &mipi_i3c_hci_dma; + dev_dbg(&hci->master.dev, "Using DMA\n"); + } + } + + /* If no DMA, try PIO */ + if (!hci->io && hci->PIO_regs) { + ret = i3c_hci_set_io_mode(hci, false); + if (!ret) { + hci->io = &mipi_i3c_hci_pio; + dev_dbg(&hci->master.dev, "Using PIO\n"); + } + } + + if (!hci->io) { + dev_err(&hci->master.dev, "neither DMA nor PIO can be used\n"); + ret = ret ?: -EINVAL; + } + if (ret) + return ret; + + /* Configure OD and PP timings for AMD platforms */ + if (hci->quirks & HCI_QUIRK_OD_PP_TIMING) + amd_set_od_pp_timing(hci); + + return 0; +} + static int i3c_hci_init(struct i3c_hci *hci) { bool size_in_dwords; @@ -708,43 +782,6 @@ static int i3c_hci_init(struct i3c_hci *hci) if (ret) return ret; - ret = i3c_hci_software_reset(hci); - if (ret) - return -ENXIO; - - /* Disable all interrupts */ - reg_write(INTR_SIGNAL_ENABLE, 0x0); - /* - * Only allow bit 31:10 signal updates because - * Bit 0:9 are reserved in IP version >= 0.8 - * Bit 0:5 are defined in IP version < 0.8 but not handled by PIO code - */ - reg_write(INTR_STATUS_ENABLE, GENMASK(31, 10)); - - /* Make sure our data ordering fits the host's */ - regval = reg_read(HC_CONTROL); - if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) { - if (!(regval & HC_CONTROL_DATA_BIG_ENDIAN)) { - regval |= HC_CONTROL_DATA_BIG_ENDIAN; - reg_write(HC_CONTROL, regval); - regval = reg_read(HC_CONTROL); - if (!(regval & HC_CONTROL_DATA_BIG_ENDIAN)) { - dev_err(&hci->master.dev, "cannot set BE mode\n"); - return -EOPNOTSUPP; - } - } - } else { - if (regval & HC_CONTROL_DATA_BIG_ENDIAN) { - regval &= ~HC_CONTROL_DATA_BIG_ENDIAN; - reg_write(HC_CONTROL, regval); - regval = reg_read(HC_CONTROL); - if (regval & HC_CONTROL_DATA_BIG_ENDIAN) { - dev_err(&hci->master.dev, "cannot clear BE mode\n"); - return -EOPNOTSUPP; - } - } - } - /* Select our command descriptor model */ switch (FIELD_GET(HC_CAP_CMD_SIZE, hci->caps)) { case 0: @@ -762,36 +799,7 @@ static int i3c_hci_init(struct i3c_hci *hci) if (hci->quirks & HCI_QUIRK_PIO_MODE) hci->RHS_regs = NULL; - /* Try activating DMA operations first */ - if (hci->RHS_regs) { - ret = i3c_hci_set_io_mode(hci, true); - if (!ret) { - hci->io = &mipi_i3c_hci_dma; - dev_dbg(&hci->master.dev, "Using DMA\n"); - } - } - - /* If no DMA, try PIO */ - if (!hci->io && hci->PIO_regs) { - ret = i3c_hci_set_io_mode(hci, false); - if (!ret) { - hci->io = &mipi_i3c_hci_pio; - dev_dbg(&hci->master.dev, "Using PIO\n"); - } - } - - if (!hci->io) { - dev_err(&hci->master.dev, "neither DMA nor PIO can be used\n"); - if (!ret) - ret = -EINVAL; - return ret; - } - - /* Configure OD and PP timings for AMD platforms */ - if (hci->quirks & HCI_QUIRK_OD_PP_TIMING) - amd_set_od_pp_timing(hci); - - return 0; + return i3c_hci_reset_and_init(hci); } static int i3c_hci_probe(struct platform_device *pdev) From f2b5d43c93e0a642f7bba970dbc5a24c9605ecd3 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:58 +0200 Subject: [PATCH 41/52] i3c: mipi-i3c-hci: Allow core re-initialization for Runtime PM support Prepare i3c_hci_reset_and_init() to support runtime resume. Update it to handle the case where the I/O mode has already been selected. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-18-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 38 ++++++++++++++------------ 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 8b8e3952d41d..fc0a47a36961 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -681,27 +681,31 @@ static int i3c_hci_reset_and_init(struct i3c_hci *hci) } } - /* Try activating DMA operations first */ - if (hci->RHS_regs) { - ret = i3c_hci_set_io_mode(hci, true); - if (!ret) { - hci->io = &mipi_i3c_hci_dma; - dev_dbg(&hci->master.dev, "Using DMA\n"); + if (hci->io) { + ret = i3c_hci_set_io_mode(hci, hci->io == &mipi_i3c_hci_dma); + } else { + /* Try activating DMA operations first */ + if (hci->RHS_regs) { + ret = i3c_hci_set_io_mode(hci, true); + if (!ret) { + hci->io = &mipi_i3c_hci_dma; + dev_dbg(&hci->master.dev, "Using DMA\n"); + } } - } - /* If no DMA, try PIO */ - if (!hci->io && hci->PIO_regs) { - ret = i3c_hci_set_io_mode(hci, false); - if (!ret) { - hci->io = &mipi_i3c_hci_pio; - dev_dbg(&hci->master.dev, "Using PIO\n"); + /* If no DMA, try PIO */ + if (!hci->io && hci->PIO_regs) { + ret = i3c_hci_set_io_mode(hci, false); + if (!ret) { + hci->io = &mipi_i3c_hci_pio; + dev_dbg(&hci->master.dev, "Using PIO\n"); + } } - } - if (!hci->io) { - dev_err(&hci->master.dev, "neither DMA nor PIO can be used\n"); - ret = ret ?: -EINVAL; + if (!hci->io) { + dev_err(&hci->master.dev, "neither DMA nor PIO can be used\n"); + ret = ret ?: -EINVAL; + } } if (ret) return ret; From 3c3de6803a7d90faba0387ba248ac71e627ca827 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:59 +0200 Subject: [PATCH 42/52] i3c: mipi-i3c-hci: Factor out master dynamic address setting into helper Prepare for future reuse. Move master dynamic address setting logic from i3c_hci_bus_init() into a dedicated helper function, i3c_hci_set_master_dyn_addr(). Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-19-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 12 +++++++++--- drivers/i3c/master/mipi-i3c-hci/hci.h | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index fc0a47a36961..ec5425f07635 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -114,6 +114,12 @@ static inline struct i3c_hci *to_i3c_hci(struct i3c_master_controller *m) return container_of(m, struct i3c_hci, master); } +static void i3c_hci_set_master_dyn_addr(struct i3c_hci *hci) +{ + reg_write(MASTER_DEVICE_ADDR, + MASTER_DYNAMIC_ADDR(hci->dyn_addr) | MASTER_DYNAMIC_ADDR_VALID); +} + static int i3c_hci_bus_init(struct i3c_master_controller *m) { struct i3c_hci *hci = to_i3c_hci(m); @@ -129,10 +135,10 @@ static int i3c_hci_bus_init(struct i3c_master_controller *m) ret = i3c_master_get_free_addr(m, 0); if (ret < 0) return ret; - reg_write(MASTER_DEVICE_ADDR, - MASTER_DYNAMIC_ADDR(ret) | MASTER_DYNAMIC_ADDR_VALID); + hci->dyn_addr = ret; + i3c_hci_set_master_dyn_addr(hci); memset(&info, 0, sizeof(info)); - info.dyn_addr = ret; + info.dyn_addr = hci->dyn_addr; ret = i3c_master_set_info(m, &info); if (ret) return ret; diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h index 38f927685d3b..ed89228ea971 100644 --- a/drivers/i3c/master/mipi-i3c-hci/hci.h +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h @@ -62,6 +62,7 @@ struct i3c_hci { u8 version_major; u8 version_minor; u8 revision; + u8 dyn_addr; u32 vendor_mipi_id; u32 vendor_version_id; u32 vendor_product_id; From 990c149c61ee45da4fb6372e6b2fdd9808414e7a Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:27:00 +0200 Subject: [PATCH 43/52] 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 8a156f5ad692..101eaa77de68 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 f609e5098137..0f1f3f766623 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 71583cc4d197..80dda0e85558 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 d231c4fbc58b..38a821395426 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; From b9a15012a14520b2b006ecb770f32eb9a57d4b8b Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:27:01 +0200 Subject: [PATCH 44/52] i3c: mipi-i3c-hci: Add optional Runtime PM support Implement optional Runtime PM support for the MIPI I3C HCI driver. Introduce runtime suspend and resume callbacks to manage bus state and restore hardware configuration after resume. Optionally enable autosuspend with a default delay of 1 second, and add helper functions to control Runtime PM during probe and remove. Read quirks from i3c_hci_driver_ids[] and set new quirk HCI_QUIRK_RPM_ALLOWED for intel-lpss-i3c devices to enable runtime PM for them. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-21-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 76 ++++++++++++++++++++++++-- drivers/i3c/master/mipi-i3c-hci/hci.h | 2 + 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index ec5425f07635..02c5a133e329 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "hci.h" #include "ext_caps.h" @@ -182,6 +183,7 @@ void i3c_hci_sync_irq_inactive(struct i3c_hci *hci) int irq = platform_get_irq(pdev, 0); reg_write(INTR_SIGNAL_ENABLE, 0x0); + hci->irq_inactive = true; synchronize_irq(irq); } @@ -564,6 +566,14 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id) irqreturn_t result = IRQ_NONE; u32 val; + /* + * The IRQ can be shared, so the handler may be called when the IRQ is + * due to a different device. That could happen when runtime suspended, + * so exit immediately if IRQs are not expected for this device. + */ + if (hci->irq_inactive) + return IRQ_NONE; + val = reg_read(INTR_STATUS); reg_write(INTR_STATUS, val); dev_dbg(&hci->master.dev, "INTR_STATUS %#x", val); @@ -723,6 +733,55 @@ static int i3c_hci_reset_and_init(struct i3c_hci *hci) return 0; } +static int i3c_hci_runtime_suspend(struct device *dev) +{ + struct i3c_hci *hci = dev_get_drvdata(dev); + int ret; + + ret = i3c_hci_bus_disable(hci); + if (ret) + return ret; + + hci->io->suspend(hci); + + return 0; +} + +static int i3c_hci_runtime_resume(struct device *dev) +{ + struct i3c_hci *hci = dev_get_drvdata(dev); + int ret; + + ret = i3c_hci_reset_and_init(hci); + if (ret) + return -EIO; + + i3c_hci_set_master_dyn_addr(hci); + + mipi_i3c_hci_dat_v1.restore(hci); + + hci->irq_inactive = false; + + hci->io->resume(hci); + + reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE); + + return 0; +} + +#define DEFAULT_AUTOSUSPEND_DELAY_MS 1000 + +static void i3c_hci_rpm_enable(struct device *dev) +{ + struct i3c_hci *hci = dev_get_drvdata(dev); + + pm_runtime_set_autosuspend_delay(dev, DEFAULT_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + devm_pm_runtime_set_active_enabled(dev); + + hci->master.rpm_allowed = true; +} + static int i3c_hci_init(struct i3c_hci *hci) { bool size_in_dwords; @@ -841,6 +900,8 @@ static int i3c_hci_probe(struct platform_device *pdev) hci->master.dev.init_name = dev_name(&pdev->dev); hci->quirks = (unsigned long)device_get_match_data(&pdev->dev); + if (!hci->quirks && platform_get_device_id(pdev)) + hci->quirks = platform_get_device_id(pdev)->driver_data; ret = i3c_hci_init(hci); if (ret) @@ -852,12 +913,10 @@ static int i3c_hci_probe(struct platform_device *pdev) if (ret) return ret; - ret = i3c_master_register(&hci->master, &pdev->dev, - &i3c_hci_ops, false); - if (ret) - return ret; + if (hci->quirks & HCI_QUIRK_RPM_ALLOWED) + i3c_hci_rpm_enable(&pdev->dev); - return 0; + return i3c_master_register(&hci->master, &pdev->dev, &i3c_hci_ops, false); } static void i3c_hci_remove(struct platform_device *pdev) @@ -880,11 +939,15 @@ static const struct acpi_device_id i3c_hci_acpi_match[] = { MODULE_DEVICE_TABLE(acpi, i3c_hci_acpi_match); static const struct platform_device_id i3c_hci_driver_ids[] = { - { .name = "intel-lpss-i3c" }, + { .name = "intel-lpss-i3c", HCI_QUIRK_RPM_ALLOWED }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, i3c_hci_driver_ids); +static const struct dev_pm_ops i3c_hci_pm_ops = { + RUNTIME_PM_OPS(i3c_hci_runtime_suspend, i3c_hci_runtime_resume, NULL) +}; + static struct platform_driver i3c_hci_driver = { .probe = i3c_hci_probe, .remove = i3c_hci_remove, @@ -893,6 +956,7 @@ static struct platform_driver i3c_hci_driver = { .name = "mipi-i3c-hci", .of_match_table = of_match_ptr(i3c_hci_of_match), .acpi_match_table = i3c_hci_acpi_match, + .pm = pm_ptr(&i3c_hci_pm_ops), }, }; module_platform_driver(i3c_hci_driver); diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h index ed89228ea971..6035f74212db 100644 --- a/drivers/i3c/master/mipi-i3c-hci/hci.h +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h @@ -51,6 +51,7 @@ struct i3c_hci { void *io_data; const struct hci_cmd_ops *cmd; atomic_t next_cmd_tid; + bool irq_inactive; u32 caps; unsigned int quirks; unsigned int DAT_entries; @@ -144,6 +145,7 @@ struct i3c_hci_dev_data { #define HCI_QUIRK_PIO_MODE BIT(2) /* Set PIO mode for AMD platforms */ #define HCI_QUIRK_OD_PP_TIMING BIT(3) /* Set OD and PP timings for AMD platforms */ #define HCI_QUIRK_RESP_BUF_THLD BIT(4) /* Set resp buf thld to 0 for AMD platforms */ +#define HCI_QUIRK_RPM_ALLOWED BIT(5) /* Runtime PM allowed */ /* global functions */ void mipi_i3c_hci_resume(struct i3c_hci *hci); From 95cb1935168ab8f637bd0bf64b9ec6f5667d1d8e Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 13 Jan 2026 09:27:02 +0200 Subject: [PATCH 45/52] i3c: mipi-i3c-hci-pci: Add Runtime PM support Enable Runtime PM for the mipi_i3c_hci_pci driver. Introduce helpers to allow and forbid Runtime PM during probe and remove, using pm_runtime APIs. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260113072702.16268-22-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- .../i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c index 458f871a2e61..1b38771667e5 100644 --- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c +++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c @@ -18,6 +18,7 @@ #include #include #include +#include /* * There can up to 15 instances, but implementations have at most 2 at this @@ -208,6 +209,18 @@ static const struct mipi_i3c_hci_pci_info intel_si_2_info = { .instance_count = 1, }; +static void mipi_i3c_hci_pci_rpm_allow(struct device *dev) +{ + pm_runtime_put(dev); + pm_runtime_allow(dev); +} + +static void mipi_i3c_hci_pci_rpm_forbid(struct device *dev) +{ + pm_runtime_forbid(dev); + pm_runtime_get_sync(dev); +} + struct mipi_i3c_hci_pci_cell_data { struct mipi_i3c_hci_platform_data pdata; struct resource res; @@ -285,6 +298,8 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, pci_set_drvdata(pci, hci); + mipi_i3c_hci_pci_rpm_allow(&pci->dev); + return 0; err_exit: @@ -300,6 +315,8 @@ static void mipi_i3c_hci_pci_remove(struct pci_dev *pci) if (hci->info->exit) hci->info->exit(hci); + mipi_i3c_hci_pci_rpm_forbid(&pci->dev); + mfd_remove_devices(&pci->dev); } From b58eaa4761ab02fc38c39d674a6bcdd55e00f388 Mon Sep 17 00:00:00 2001 From: Fredrik Markstrom Date: Fri, 16 Jan 2026 15:29:42 +0100 Subject: [PATCH 46/52] i3c: dw: Initialize spinlock to avoid upsetting lockdep The devs_lock spinlock introduced when adding support for ibi:s was never initialized. Fixes: e389b1d72a624 ("i3c: dw: Add support for in-band interrupts") Suggested-by: Jani Nurminen Signed-off-by: Fredrik Markstrom Reviewed-by: Ivar Holmqvist Link: https://patch.msgid.link/20260116-i3c_dw_initialize_spinlock-v3-1-cf707b6ed75f@est.tech Signed-off-by: Alexandre Belloni --- drivers/i3c/master/dw-i3c-master.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 48af00659e19..e9b2c23ed171 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1612,6 +1612,8 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, spin_lock_init(&master->xferqueue.lock); INIT_LIST_HEAD(&master->xferqueue.list); + spin_lock_init(&master->devs_lock); + writel(INTR_ALL, master->regs + INTR_STATUS); irq = platform_get_irq(pdev, 0); ret = devm_request_irq(&pdev->dev, irq, From c481ef12e713fb7c292d04f53b3532ac0804ab3d Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 23 Jan 2026 08:33:23 +0200 Subject: [PATCH 47/52] i3c: master: Add i3c_master_do_daa_ext() for post-hibernation address recovery After system hibernation, I3C Dynamic Addresses may be reassigned at boot and no longer match the values recorded before suspend. Introduce i3c_master_do_daa_ext() to handle this situation. The restore procedure is straightforward: issue a Reset Dynamic Address Assignment (RSTDAA), then run the standard DAA sequence. The existing DAA logic already supports detecting and updating devices whose dynamic addresses differ from previously known values. Refactor the DAA path by introducing a shared helper used by both the normal i3c_master_do_daa() path and the new extended restore function, and correct the kernel-doc in the process. Export i3c_master_do_daa_ext() so that master drivers can invoke it from their PM restore callbacks. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260123063325.8210-2-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 49 +++++++++++++++++++++++++++++--------- include/linux/i3c/master.h | 1 + 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 80dda0e85558..0eae19b3823d 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1768,22 +1768,24 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) } /** - * i3c_master_do_daa() - do a DAA (Dynamic Address Assignment) - * @master: master doing the DAA + * i3c_master_do_daa_ext() - Dynamic Address Assignment (extended version) + * @master: controller + * @rstdaa: whether to first perform Reset of Dynamic Addresses (RSTDAA) * - * This function is instantiating an I3C device object and adding it to the - * I3C device list. All device information are automatically retrieved using - * standard CCC commands. + * Perform Dynamic Address Assignment with optional support for System + * Hibernation (@rstdaa is true). * - * The I3C device object is returned in case the master wants to attach - * private data to it using i3c_dev_set_master_data(). - * - * This function must be called with the bus lock held in write mode. + * After System Hibernation, Dynamic Addresses can have been reassigned at boot + * time to different values. A simple strategy is followed to handle that. + * Perform a Reset of Dynamic Addresses (RSTDAA) followed by the normal DAA + * procedure which has provision for reassigning addresses that differ from the + * previously recorded addresses. * * Return: a 0 in case of success, an negative error code otherwise. */ -int i3c_master_do_daa(struct i3c_master_controller *master) +int i3c_master_do_daa_ext(struct i3c_master_controller *master, bool rstdaa) { + int rstret = 0; int ret; ret = i3c_master_rpm_get(master); @@ -1791,7 +1793,15 @@ int i3c_master_do_daa(struct i3c_master_controller *master) return ret; i3c_bus_maintenance_lock(&master->bus); + + if (rstdaa) { + rstret = i3c_master_rstdaa_locked(master, I3C_BROADCAST_ADDR); + if (rstret == I3C_ERROR_M2) + rstret = 0; + } + ret = master->ops->do_daa(master); + i3c_bus_maintenance_unlock(&master->bus); if (ret) @@ -1802,7 +1812,24 @@ int i3c_master_do_daa(struct i3c_master_controller *master) i3c_bus_normaluse_unlock(&master->bus); out: i3c_master_rpm_put(master); - return ret; + + return rstret ?: ret; +} +EXPORT_SYMBOL_GPL(i3c_master_do_daa_ext); + +/** + * i3c_master_do_daa() - do a DAA (Dynamic Address Assignment) + * @master: master doing the DAA + * + * This function instantiates I3C device objects and adds them to the + * I3C device list. All device information is automatically retrieved using + * standard CCC commands. + * + * Return: a 0 in case of success, an negative error code otherwise. + */ +int i3c_master_do_daa(struct i3c_master_controller *master) +{ + return i3c_master_do_daa_ext(master, false); } EXPORT_SYMBOL_GPL(i3c_master_do_daa); diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 38a821395426..592b646f6134 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -605,6 +605,7 @@ int i3c_master_get_free_addr(struct i3c_master_controller *master, int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master, u8 addr); int i3c_master_do_daa(struct i3c_master_controller *master); +int i3c_master_do_daa_ext(struct i3c_master_controller *master, bool rstdaa); struct i3c_dma *i3c_master_dma_map_single(struct device *dev, void *ptr, size_t len, bool force_bounce, enum dma_data_direction dir); From c3357bdd9be9cd9e34e46fe100e1d425503d4acf Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 23 Jan 2026 08:33:24 +0200 Subject: [PATCH 48/52] i3c: mipi-i3c-hci: Add optional System Suspend support Add system suspend callbacks. Implement them by forcing runtime PM. Consequently bail out if Runtime PM is not allowed. On resume from System Suspend (suspend to RAM), rerun Dynamic Address Assignment to restore addresses for devices that may have lost power. On resume from System Hibernation (suspend to disk), use the new i3c_master_do_daa_ext() helper with 'rstdaa' set to true, which additionally handles the case where devices are assigned different dynamic addresses after a hibernation boot. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260123063325.8210-3-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 02c5a133e329..e925584113d1 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -769,6 +769,49 @@ static int i3c_hci_runtime_resume(struct device *dev) return 0; } +static int i3c_hci_suspend(struct device *dev) +{ + struct i3c_hci *hci = dev_get_drvdata(dev); + + if (!(hci->quirks & HCI_QUIRK_RPM_ALLOWED)) + return 0; + + return pm_runtime_force_suspend(dev); +} + +static int i3c_hci_resume_common(struct device *dev, bool rstdaa) +{ + struct i3c_hci *hci = dev_get_drvdata(dev); + int ret; + + if (!(hci->quirks & HCI_QUIRK_RPM_ALLOWED)) + return 0; + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + ret = i3c_master_do_daa_ext(&hci->master, rstdaa); + if (ret) + dev_err(dev, "Dynamic Address Assignment failed on resume, error %d\n", ret); + + /* + * I3C devices may have retained their dynamic address anyway. Do not + * fail the resume because of DAA error. + */ + return 0; +} + +static int i3c_hci_resume(struct device *dev) +{ + return i3c_hci_resume_common(dev, false); +} + +static int i3c_hci_restore(struct device *dev) +{ + return i3c_hci_resume_common(dev, true); +} + #define DEFAULT_AUTOSUSPEND_DELAY_MS 1000 static void i3c_hci_rpm_enable(struct device *dev) @@ -945,6 +988,12 @@ static const struct platform_device_id i3c_hci_driver_ids[] = { MODULE_DEVICE_TABLE(platform, i3c_hci_driver_ids); static const struct dev_pm_ops i3c_hci_pm_ops = { + .suspend = pm_sleep_ptr(i3c_hci_suspend), + .resume = pm_sleep_ptr(i3c_hci_resume), + .freeze = pm_sleep_ptr(i3c_hci_suspend), + .thaw = pm_sleep_ptr(i3c_hci_resume), + .poweroff = pm_sleep_ptr(i3c_hci_suspend), + .restore = pm_sleep_ptr(i3c_hci_restore), RUNTIME_PM_OPS(i3c_hci_runtime_suspend, i3c_hci_runtime_resume, NULL) }; From 4280197d154cae1d1d5acb54484da26da04eac32 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 23 Jan 2026 08:33:25 +0200 Subject: [PATCH 49/52] i3c: mipi-i3c-hci-pci: Add System Suspend support Assign the driver PM operations pointer, which is necessary for the PCI subsystem to put the device into a low power state. Refer to pci_pm_suspend_noirq() which bails out if the pointer is NULL, before it has the opportunity to call pci_prepare_to_sleep(). No other actions are necessary as the mipi-i3c-hci driver takes care of controller state. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260123063325.8210-4-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c index 1b38771667e5..0f05a15c14c7 100644 --- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c +++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c @@ -320,6 +320,10 @@ static void mipi_i3c_hci_pci_remove(struct pci_dev *pci) mfd_remove_devices(&pci->dev); } +/* PM ops must exist for PCI to put a device to a low power state */ +static const struct dev_pm_ops mipi_i3c_hci_pci_pm_ops = { +}; + static const struct pci_device_id mipi_i3c_hci_pci_devices[] = { /* Wildcat Lake-U */ { PCI_VDEVICE(INTEL, 0x4d7c), (kernel_ulong_t)&intel_mi_1_info}, @@ -342,6 +346,9 @@ static struct pci_driver mipi_i3c_hci_pci_driver = { .id_table = mipi_i3c_hci_pci_devices, .probe = mipi_i3c_hci_pci_probe, .remove = mipi_i3c_hci_pci_remove, + .driver = { + .pm = pm_ptr(&mipi_i3c_hci_pci_pm_ops) + }, }; module_pci_driver(mipi_i3c_hci_pci_driver); From 2537089413514caaa9a5fdeeac3a34d45100f747 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Mon, 26 Jan 2026 08:11:21 +0000 Subject: [PATCH 50/52] i3c: dw: Fix memory leak in dw_i3c_master_i2c_xfers() The dw_i3c_master_i2c_xfers() function allocates memory for the xfer structure using dw_i3c_master_alloc_xfer(). If pm_runtime_resume_and_get() fails, the function returns without freeing the allocated xfer, resulting in a memory leak. Add a dw_i3c_master_free_xfer() call to the error path to ensure the allocated memory is properly freed. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: 62fe9d06f570 ("i3c: dw: Add power management support") Signed-off-by: Zilin Guan Reviewed-by: Frank Li Link: https://patch.msgid.link/20260126081121.644099-1-zilin@seu.edu.cn Signed-off-by: Alexandre Belloni --- drivers/i3c/master/dw-i3c-master.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index e9b2c23ed171..cfc15aaf8c7b 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1106,6 +1106,7 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, dev_err(master->dev, "<%s> cannot resume i3c bus master, err: %d\n", __func__, ret); + dw_i3c_master_free_xfer(xfer); return ret; } From c7311aa4a71ebbf6e86e3173e04afbad233a3937 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Tue, 27 Jan 2026 10:05:06 +0800 Subject: [PATCH 51/52] i3c: dw-i3c-master: convert spinlock usage to scoped guards Convert dw-i3c-master to use scoped spinlock guards in place of open-coded spin_lock_irqsave()/spin_unlock_irqrestore() pairs to ensure locks are always safely released on scope exit. Signed-off-by: Adrian Ng Ho Yin Reviewed-by: Frank Li Link: https://patch.msgid.link/79020c006c15dda9d057946530f16cfb4650d450.1769479330.git.adrianhoyin.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/dw-i3c-master.c | 34 +++++++----------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index cfc15aaf8c7b..d7df81c56667 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -416,17 +416,14 @@ static void dw_i3c_master_start_xfer_locked(struct dw_i3c_master *master) static void dw_i3c_master_enqueue_xfer(struct dw_i3c_master *master, struct dw_i3c_xfer *xfer) { - unsigned long flags; - init_completion(&xfer->comp); - spin_lock_irqsave(&master->xferqueue.lock, flags); + guard(spinlock_irqsave)(&master->xferqueue.lock); if (master->xferqueue.cur) { list_add_tail(&xfer->node, &master->xferqueue.list); } else { master->xferqueue.cur = xfer; dw_i3c_master_start_xfer_locked(master); } - spin_unlock_irqrestore(&master->xferqueue.lock, flags); } static void dw_i3c_master_dequeue_xfer_locked(struct dw_i3c_master *master, @@ -451,11 +448,8 @@ static void dw_i3c_master_dequeue_xfer_locked(struct dw_i3c_master *master, static void dw_i3c_master_dequeue_xfer(struct dw_i3c_master *master, struct dw_i3c_xfer *xfer) { - unsigned long flags; - - spin_lock_irqsave(&master->xferqueue.lock, flags); + guard(spinlock_irqsave)(&master->xferqueue.lock); dw_i3c_master_dequeue_xfer_locked(master, xfer); - spin_unlock_irqrestore(&master->xferqueue.lock, flags); } static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr) @@ -1195,15 +1189,13 @@ static int dw_i3c_master_request_ibi(struct i3c_dev_desc *dev, struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); struct i3c_master_controller *m = i3c_dev_get_master(dev); struct dw_i3c_master *master = to_dw_i3c_master(m); - unsigned long flags; data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req); if (IS_ERR(data->ibi_pool)) return PTR_ERR(data->ibi_pool); - spin_lock_irqsave(&master->devs_lock, flags); + guard(spinlock_irqsave)(&master->devs_lock); master->devs[data->index].ibi_dev = dev; - spin_unlock_irqrestore(&master->devs_lock, flags); return 0; } @@ -1213,11 +1205,10 @@ static void dw_i3c_master_free_ibi(struct i3c_dev_desc *dev) struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); struct i3c_master_controller *m = i3c_dev_get_master(dev); struct dw_i3c_master *master = to_dw_i3c_master(m); - unsigned long flags; - spin_lock_irqsave(&master->devs_lock, flags); - master->devs[data->index].ibi_dev = NULL; - spin_unlock_irqrestore(&master->devs_lock, flags); + scoped_guard(spinlock_irqsave, &master->devs_lock) { + master->devs[data->index].ibi_dev = NULL; + } i3c_generic_ibi_free_pool(data->ibi_pool); data->ibi_pool = NULL; @@ -1244,13 +1235,12 @@ static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master, struct i3c_dev_desc *dev, u8 idx, bool enable) { - unsigned long flags; u32 dat_entry, reg; bool global; dat_entry = DEV_ADDR_TABLE_LOC(master->datstartaddr, idx); - spin_lock_irqsave(&master->devs_lock, flags); + guard(spinlock_irqsave)(&master->devs_lock); reg = readl(master->regs + dat_entry); if (enable) { reg &= ~DEV_ADDR_TABLE_SIR_REJECT; @@ -1275,9 +1265,6 @@ static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master, if (global) dw_i3c_master_enable_sir_signal(master, enable); - - - spin_unlock_irqrestore(&master->devs_lock, flags); } static int dw_i3c_master_enable_hotjoin(struct i3c_master_controller *m) @@ -1378,7 +1365,6 @@ static void dw_i3c_master_handle_ibi_sir(struct dw_i3c_master *master, struct dw_i3c_i2c_dev_data *data; struct i3c_ibi_slot *slot; struct i3c_dev_desc *dev; - unsigned long flags; u8 addr, len; int idx; @@ -1396,7 +1382,7 @@ static void dw_i3c_master_handle_ibi_sir(struct dw_i3c_master *master, * a new platform op to validate it. */ - spin_lock_irqsave(&master->devs_lock, flags); + guard(spinlock_irqsave)(&master->devs_lock); idx = dw_i3c_master_get_addr_pos(master, addr); if (idx < 0) { dev_dbg_ratelimited(&master->base.dev, @@ -1432,14 +1418,10 @@ static void dw_i3c_master_handle_ibi_sir(struct dw_i3c_master *master, } i3c_master_queue_ibi(dev, slot); - spin_unlock_irqrestore(&master->devs_lock, flags); - return; err_drain: dw_i3c_master_drain_ibi_queue(master, len); - - spin_unlock_irqrestore(&master->devs_lock, flags); } /* "ibis": referring to In-Band Interrupts, and not From ed318b3fb4ab317c533d38d160326fa5d7569497 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Tue, 27 Jan 2026 10:05:07 +0800 Subject: [PATCH 52/52] i3c: dw-i3c-master: fix SIR reject bit mapping for dynamic addresses The IBI_SIR_REQ_REJECT register is a 32-bit bitmap indexed by the dynamic address of each I3C slave. The DesignWare controller derives the bit index by folding the 7-bit dynamic address into a 5-bit value, using the sum of the lower 5 bits and the upper 2 bits, modulo 32. The current implementation incorrectly uses the device table index when updating the SIR reject mask, which can result in rejecting or accepting IBIs for the wrong device. Compute the SIR reject bit index directly from the dynamic address, as defined by the controller specification, and use it consistently when updating the reject mask. Signed-off-by: Adrian Ng Ho Yin Reviewed-by: Frank Li Link: https://patch.msgid.link/d4ad8161e604156c60327060ad3d339ebf18fe4f.1769479330.git.adrianhoyin.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/dw-i3c-master.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index d7df81c56667..7eb09ad10171 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -205,6 +205,10 @@ #define EXTENDED_CAPABILITY 0xe8 #define SLAVE_CONFIG 0xec +#define DYN_ADDR_LO_MASK GENMASK(4, 0) +#define DYN_ADDR_HI_MASK GENMASK(6, 5) +#define IBI_SIR_BIT_MOD 32 /* 32-bit vector */ + #define DW_I3C_DEV_NACK_RETRY_CNT_MAX 0x3 #define DEV_ADDR_TABLE_DEV_NACK_RETRY_MASK GENMASK(30, 29) #define DEV_ADDR_TABLE_DYNAMIC_MASK GENMASK(23, 16) @@ -217,6 +221,7 @@ #define DEV_ADDR_TABLE_DYNAMIC_ADDR(x) FIELD_PREP(DEV_ADDR_TABLE_DYNAMIC_MASK, x) #define DEV_ADDR_TABLE_STATIC_ADDR(x) FIELD_PREP(DEV_ADDR_TABLE_STATIC_MASK, x) #define DEV_ADDR_TABLE_LOC(start, idx) ((start) + ((idx) << 2)) +#define DEV_ADDR_TABLE_GET_DYNAMIC_ADDR(x) FIELD_GET(DEV_ADDR_TABLE_DYNAMIC_MASK, x) #define I3C_BUS_SDR1_SCL_RATE 8000000 #define I3C_BUS_SDR2_SCL_RATE 6000000 @@ -264,6 +269,14 @@ struct dw_i3c_drvdata { u32 flags; }; +static inline u32 get_ibi_sir_bit_index(u8 addr) +{ + u32 lo = FIELD_GET(DYN_ADDR_LO_MASK, addr); + u32 hi = FIELD_GET(DYN_ADDR_HI_MASK, addr); + + return (lo + hi) % IBI_SIR_BIT_MOD; +} + static bool dw_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m, const struct i3c_ccc_cmd *cmd) { @@ -1237,11 +1250,19 @@ static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master, { u32 dat_entry, reg; bool global; + u8 dynamic_addr; dat_entry = DEV_ADDR_TABLE_LOC(master->datstartaddr, idx); guard(spinlock_irqsave)(&master->devs_lock); reg = readl(master->regs + dat_entry); + dynamic_addr = DEV_ADDR_TABLE_GET_DYNAMIC_ADDR(reg); + + if (!dynamic_addr) + dev_warn(master->dev, + "<%s> unassigned slave device, dynamic addr:%x\n", + __func__, dynamic_addr); + if (enable) { reg &= ~DEV_ADDR_TABLE_SIR_REJECT; if (dev->info.bcr & I3C_BCR_IBI_PAYLOAD) @@ -1254,11 +1275,11 @@ static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master, if (enable) { global = (master->sir_rej_mask == IBI_REQ_REJECT_ALL); - master->sir_rej_mask &= ~BIT(idx); + master->sir_rej_mask &= ~BIT(get_ibi_sir_bit_index(dynamic_addr)); } else { bool hj_rejected = !!(readl(master->regs + DEVICE_CTRL) & DEV_CTRL_HOT_JOIN_NACK); - master->sir_rej_mask |= BIT(idx); + master->sir_rej_mask |= BIT(get_ibi_sir_bit_index(dynamic_addr)); global = (master->sir_rej_mask == IBI_REQ_REJECT_ALL) && hj_rejected; } writel(master->sir_rej_mask, master->regs + IBI_SIR_REQ_REJECT);