From 26bf98b14b42891470237f58106cd7c8d7ba882c Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:02:03 -0400 Subject: [PATCH 01/88] ACPICA: Add LVR to acrestyp.h JIRA: https://issues.redhat.com/browse/RHEL-143335 commit f0ee0b0927f78adfd9ba4f57b42a07645b1526a3 Author: Akhil R Date: Wed, 27 May 2026 19:55:57 +0200 ACPICA: Add LVR to acrestyp.h Add a new field called lvr to struct acpi_resource_i2c_serialbus. Link: https://github.com/acpica/acpica/commit/e62e74baf7e0 Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2354060.iZASKD2KPV@rafael.j.wysocki Signed-off-by: Jennifer Berringer --- include/acpi/acrestyp.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/acpi/acrestyp.h b/include/acpi/acrestyp.h index 842f932e2c2b..38a19b1d19ac 100644 --- a/include/acpi/acrestyp.h +++ b/include/acpi/acrestyp.h @@ -423,6 +423,7 @@ struct acpi_resource_i2c_serialbus { ACPI_RESOURCE_SERIAL_COMMON u8 access_mode; u16 slave_address; u32 connection_speed; + u8 lvr; }; /* Values for access_mode field above */ From 0aa784aa4ec75ba4d6e3d2d6a37bfb7a7d7dc45b Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:00:16 -0400 Subject: [PATCH 02/88] ACPICA: Fetch LVR I2C resource descriptor JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 468adc6b1ff83431644cf002a1850010d7ff32dd Author: Akhil R Date: Wed, 27 May 2026 19:56:38 +0200 ACPICA: Fetch LVR I2C resource descriptor Add LVR I2C resource entry to acpi_rs_convert_i2c_serial_bus[]. Link: https://github.com/acpica/acpica/commit/c40411823510 Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/23121545.EfDdHjke4D@rafael.j.wysocki Signed-off-by: Jennifer Berringer --- drivers/acpi/acpica/rsserial.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/acpica/rsserial.c b/drivers/acpi/acpica/rsserial.c index 279bfa27da94..3e4a1fe81ef6 100644 --- a/drivers/acpi/acpica/rsserial.c +++ b/drivers/acpi/acpica/rsserial.c @@ -315,7 +315,7 @@ struct acpi_rsconvert_info acpi_rs_convert_csi2_serial_bus[14] = { * ******************************************************************************/ -struct acpi_rsconvert_info acpi_rs_convert_i2c_serial_bus[17] = { +struct acpi_rsconvert_info acpi_rs_convert_i2c_serial_bus[18] = { {ACPI_RSC_INITGET, ACPI_RESOURCE_TYPE_SERIAL_BUS, ACPI_RS_SIZE(struct acpi_resource_i2c_serialbus), ACPI_RSC_TABLE_SIZE(acpi_rs_convert_i2c_serial_bus)}, @@ -391,6 +391,10 @@ struct acpi_rsconvert_info acpi_rs_convert_i2c_serial_bus[17] = { AML_OFFSET(i2c_serial_bus.type_specific_flags), 0}, + {ACPI_RSC_1BITFLAG, ACPI_RS_OFFSET(data.i2c_serial_bus.lvr), + AML_OFFSET(i2c_serial_bus.type_specific_flags) + 1, + 0}, + {ACPI_RSC_MOVE32, ACPI_RS_OFFSET(data.i2c_serial_bus.connection_speed), AML_OFFSET(i2c_serial_bus.connection_speed), 1}, From 5e204972e8a8e1af5059fc60286e5c31d41ddbef Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:00:31 -0400 Subject: [PATCH 03/88] ACPICA: Change LVR to 8 bit value JIRA: https://issues.redhat.com/browse/RHEL-143335 commit d364d76f3d0ccba6c8b0e3b0df348b4e86a0a72b Author: Akhil R Date: Wed, 27 May 2026 19:57:13 +0200 ACPICA: Change LVR to 8 bit value In the LVR I2C resource entry to acpi_rs_convert_i2c_serial_bus[]. Link: https://github.com/acpica/acpica/commit/7650d4a889ea Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3952474.kQq0lBPeGt@rafael.j.wysocki Signed-off-by: Jennifer Berringer --- drivers/acpi/acpica/rsserial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/acpica/rsserial.c b/drivers/acpi/acpica/rsserial.c index 3e4a1fe81ef6..1119c64795a7 100644 --- a/drivers/acpi/acpica/rsserial.c +++ b/drivers/acpi/acpica/rsserial.c @@ -391,7 +391,7 @@ struct acpi_rsconvert_info acpi_rs_convert_i2c_serial_bus[18] = { AML_OFFSET(i2c_serial_bus.type_specific_flags), 0}, - {ACPI_RSC_1BITFLAG, ACPI_RS_OFFSET(data.i2c_serial_bus.lvr), + {ACPI_RSC_MOVE8, ACPI_RS_OFFSET(data.i2c_serial_bus.lvr), AML_OFFSET(i2c_serial_bus.type_specific_flags) + 1, 0}, From 76969782e4bc959df77c6c6e63d47366ffdcfcb9 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:00:44 -0400 Subject: [PATCH 04/88] ACPICA: Mention the LVR bits JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 53a3a7723c9eac56c47003291b52f106734eb438 Author: Akhil R Date: Wed, 27 May 2026 19:57:52 +0200 ACPICA: Mention the LVR bits Add a comment mentioning the LVR byte position in the type_specific_flag. Link: https://github.com/acpica/acpica/commit/014fa9f2dbcc Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/9627007.CDJkKcVGEf@rafael.j.wysocki Signed-off-by: Jennifer Berringer --- drivers/acpi/acpica/rsserial.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/acpi/acpica/rsserial.c b/drivers/acpi/acpica/rsserial.c index 1119c64795a7..7d7ee3af7272 100644 --- a/drivers/acpi/acpica/rsserial.c +++ b/drivers/acpi/acpica/rsserial.c @@ -391,6 +391,7 @@ struct acpi_rsconvert_info acpi_rs_convert_i2c_serial_bus[18] = { AML_OFFSET(i2c_serial_bus.type_specific_flags), 0}, + /* Read LVR from Type Specific Flags, bits[15:8] */ {ACPI_RSC_MOVE8, ACPI_RS_OFFSET(data.i2c_serial_bus.lvr), AML_OFFSET(i2c_serial_bus.type_specific_flags) + 1, 0}, From 70ec860b6ca1d7a5dc569e559fee0f316562be07 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:00:55 -0400 Subject: [PATCH 05/88] ACPICA: fix I2C LVR item count in the conversion table JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 2543fbb21642f740288e3c292cab03cd611f35a4 Author: Akhil R Date: Wed, 27 May 2026 19:58:29 +0200 ACPICA: fix I2C LVR item count in the conversion table For ACPI_RSC_MOVE8, the 'Value' field in struct acpi_rsconvert_info is the item count count and not a bit position like for the bitflags. Set 'Value' as '1' to fix this. Conversion still works coincidentally with '0' because item_count is not reset between table entries, and the previous count value was taking effect. Link: https://github.com/acpica/acpica/commit/70082dc8fc84 Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/6164740.MhkbZ0Pkbq@rafael.j.wysocki Signed-off-by: Jennifer Berringer --- drivers/acpi/acpica/rsserial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/acpica/rsserial.c b/drivers/acpi/acpica/rsserial.c index 7d7ee3af7272..5ab41e9b9039 100644 --- a/drivers/acpi/acpica/rsserial.c +++ b/drivers/acpi/acpica/rsserial.c @@ -394,7 +394,7 @@ struct acpi_rsconvert_info acpi_rs_convert_i2c_serial_bus[18] = { /* Read LVR from Type Specific Flags, bits[15:8] */ {ACPI_RSC_MOVE8, ACPI_RS_OFFSET(data.i2c_serial_bus.lvr), AML_OFFSET(i2c_serial_bus.type_specific_flags) + 1, - 0}, + 1}, {ACPI_RSC_MOVE32, ACPI_RS_OFFSET(data.i2c_serial_bus.connection_speed), AML_OFFSET(i2c_serial_bus.connection_speed), From 3ff9fb3c857fa9585f00add60a9fa66161294b52 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:19:25 -0400 Subject: [PATCH 06/88] i3c: master: Replace hard code 2 with macro I3C_ADDR_SLOT_STATUS_BITS JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 16aed0a6520ba01b7d22c32e193fc1ec674f92d4 Author: Frank Li Date: Mon, 21 Oct 2024 11:45:06 -0400 i3c: master: Replace hard code 2 with macro I3C_ADDR_SLOT_STATUS_BITS Replace the hardcoded value 2, which indicates 2 bits for I3C address status, with the predefined macro I3C_ADDR_SLOT_STATUS_BITS. Improve maintainability and extensibility of the code. Reviewed-by: Miquel Raynal Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20241021-i3c_dts_assign-v8-1-4098b8bde01e@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 4 ++-- include/linux/i3c/master.h | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 8bef96085792..60ea9ae3eade 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -348,7 +348,7 @@ static enum i3c_addr_slot_status i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr) { unsigned long status; - int bitpos = addr * 2; + int bitpos = addr * I3C_ADDR_SLOT_STATUS_BITS; if (addr > I2C_MAX_ADDR) return I3C_ADDR_SLOT_RSVD; @@ -362,7 +362,7 @@ i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr) static void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr, enum i3c_addr_slot_status status) { - int bitpos = addr * 2; + int bitpos = addr * I3C_ADDR_SLOT_STATUS_BITS; unsigned long *ptr; if (addr > I2C_MAX_ADDR) diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 2a1ed05d5782..2100547b2d8d 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -313,6 +313,8 @@ enum i3c_addr_slot_status { I3C_ADDR_SLOT_STATUS_MASK = 3, }; +#define I3C_ADDR_SLOT_STATUS_BITS 2 + /** * struct i3c_bus - I3C bus object * @cur_master: I3C master currently driving the bus. Since I3C is multi-master @@ -354,7 +356,7 @@ enum i3c_addr_slot_status { struct i3c_bus { struct i3c_dev_desc *cur_master; int id; - unsigned long addrslots[((I2C_MAX_ADDR + 1) * 2) / BITS_PER_LONG]; + unsigned long addrslots[((I2C_MAX_ADDR + 1) * I3C_ADDR_SLOT_STATUS_BITS) / BITS_PER_LONG]; enum i3c_bus_mode mode; struct { unsigned long i3c; From d259614a7ca1ebf2c08510516918df7c5682e8a2 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:20:46 -0400 Subject: [PATCH 07/88] i3c: master: Extend address status bit to 4 and add I3C_ADDR_SLOT_EXT_DESIRED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 2f552fa280590e61bd3dbe66a7b54b99caa642a4 Author: Frank Li Date: Mon, 21 Oct 2024 11:45:07 -0400 i3c: master: Extend address status bit to 4 and add I3C_ADDR_SLOT_EXT_DESIRED Extend the address status bit to 4 and introduce the I3C_ADDR_SLOT_EXT_DESIRED macro to indicate that a device prefers a specific address. This is generally set by the 'assigned-address' in the device tree source (dts) file. ┌────┬─────────────┬───┬─────────┬───┐ │S/Sr│ 7'h7E RnW=0 │ACK│ ENTDAA │ T ├────┐ └────┴─────────────┴───┴─────────┴───┘ │ ┌─────────────────────────────────────────┘ │ ┌──┬─────────────┬───┬─────────────────┬────────────────┬───┬─────────┐ └─►│Sr│7'h7E RnW=1 │ACK│48bit UID BCR DCR│Assign 7bit Addr│PAR│ ACK/NACK│ └──┴─────────────┴───┴─────────────────┴────────────────┴───┴─────────┘ Some master controllers (such as HCI) need to prepare the entire above transaction before sending it out to the I3C bus. This means that a 7-bit dynamic address needs to be allocated before knowing the target device's UID information. However, some I3C targets may request specific addresses (called as "init_dyn_addr"), which is typically specified by the DT-'s assigned-address property. Lower addresses having higher IBI priority. If it is available, i3c_bus_get_free_addr() preferably return a free address that is not in the list of desired addresses (called as "init_dyn_addr"). This allows the device with the "init_dyn_addr" to switch to its "init_dyn_addr" when it hot-joins the I3C bus. Otherwise, if the "init_dyn_addr" is already in use by another I3C device, the target device will not be able to switch to its desired address. If the previous step fails, fallback returning one of the remaining unassigned address, regardless of its state in the desired list. Reviewed-by: Miquel Raynal Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20241021-i3c_dts_assign-v8-2-4098b8bde01e@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 65 +++++++++++++++++++++++++++++++------- include/linux/i3c/master.h | 7 ++-- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 60ea9ae3eade..396be110ab06 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -345,7 +345,7 @@ const struct bus_type i3c_bus_type = { EXPORT_SYMBOL_GPL(i3c_bus_type); static enum i3c_addr_slot_status -i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr) +i3c_bus_get_addr_slot_status_mask(struct i3c_bus *bus, u16 addr, u32 mask) { unsigned long status; int bitpos = addr * I3C_ADDR_SLOT_STATUS_BITS; @@ -356,11 +356,17 @@ i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr) status = bus->addrslots[bitpos / BITS_PER_LONG]; status >>= bitpos % BITS_PER_LONG; - return status & I3C_ADDR_SLOT_STATUS_MASK; + return status & mask; } -static void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr, - enum i3c_addr_slot_status status) +static enum i3c_addr_slot_status +i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr) +{ + return i3c_bus_get_addr_slot_status_mask(bus, addr, I3C_ADDR_SLOT_STATUS_MASK); +} + +static void i3c_bus_set_addr_slot_status_mask(struct i3c_bus *bus, u16 addr, + enum i3c_addr_slot_status status, u32 mask) { int bitpos = addr * I3C_ADDR_SLOT_STATUS_BITS; unsigned long *ptr; @@ -369,9 +375,14 @@ static void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr, return; ptr = bus->addrslots + (bitpos / BITS_PER_LONG); - *ptr &= ~((unsigned long)I3C_ADDR_SLOT_STATUS_MASK << - (bitpos % BITS_PER_LONG)); - *ptr |= (unsigned long)status << (bitpos % BITS_PER_LONG); + *ptr &= ~((unsigned long)mask << (bitpos % BITS_PER_LONG)); + *ptr |= ((unsigned long)status & mask) << (bitpos % BITS_PER_LONG); +} + +static void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr, + enum i3c_addr_slot_status status) +{ + i3c_bus_set_addr_slot_status_mask(bus, addr, status, I3C_ADDR_SLOT_STATUS_MASK); } static bool i3c_bus_dev_addr_is_avail(struct i3c_bus *bus, u8 addr) @@ -383,13 +394,44 @@ static bool i3c_bus_dev_addr_is_avail(struct i3c_bus *bus, u8 addr) return status == I3C_ADDR_SLOT_FREE; } +/* + * ┌────┬─────────────┬───┬─────────┬───┐ + * │S/Sr│ 7'h7E RnW=0 │ACK│ ENTDAA │ T ├────┐ + * └────┴─────────────┴───┴─────────┴───┘ │ + * ┌─────────────────────────────────────────┘ + * │ ┌──┬─────────────┬───┬─────────────────┬────────────────┬───┬─────────┐ + * └─►│Sr│7'h7E RnW=1 │ACK│48bit UID BCR DCR│Assign 7bit Addr│PAR│ ACK/NACK│ + * └──┴─────────────┴───┴─────────────────┴────────────────┴───┴─────────┘ + * Some master controllers (such as HCI) need to prepare the entire above transaction before + * sending it out to the I3C bus. This means that a 7-bit dynamic address needs to be allocated + * before knowing the target device's UID information. + * + * However, some I3C targets may request specific addresses (called as "init_dyn_addr"), which is + * typically specified by the DT-'s assigned-address property. Lower addresses having higher IBI + * priority. If it is available, i3c_bus_get_free_addr() preferably return a free address that is + * not in the list of desired addresses (called as "init_dyn_addr"). This allows the device with + * the "init_dyn_addr" to switch to its "init_dyn_addr" when it hot-joins the I3C bus. Otherwise, + * if the "init_dyn_addr" is already in use by another I3C device, the target device will not be + * able to switch to its desired address. + * + * If the previous step fails, fallback returning one of the remaining unassigned address, + * regardless of its state in the desired list. + */ static int i3c_bus_get_free_addr(struct i3c_bus *bus, u8 start_addr) { enum i3c_addr_slot_status status; u8 addr; for (addr = start_addr; addr < I3C_MAX_ADDR; addr++) { - status = i3c_bus_get_addr_slot_status(bus, addr); + status = i3c_bus_get_addr_slot_status_mask(bus, addr, + I3C_ADDR_SLOT_EXT_STATUS_MASK); + if (status == I3C_ADDR_SLOT_FREE) + return addr; + } + + for (addr = start_addr; addr < I3C_MAX_ADDR; addr++) { + status = i3c_bus_get_addr_slot_status_mask(bus, addr, + I3C_ADDR_SLOT_STATUS_MASK); if (status == I3C_ADDR_SLOT_FREE) return addr; } @@ -1918,9 +1960,10 @@ static int i3c_master_bus_init(struct i3c_master_controller *master) goto err_rstdaa; } - i3c_bus_set_addr_slot_status(&master->bus, - i3cboardinfo->init_dyn_addr, - I3C_ADDR_SLOT_I3C_DEV); + i3c_bus_set_addr_slot_status_mask(&master->bus, + i3cboardinfo->init_dyn_addr, + I3C_ADDR_SLOT_I3C_DEV | I3C_ADDR_SLOT_EXT_DESIRED, + I3C_ADDR_SLOT_EXT_STATUS_MASK); /* * Only try to create/attach devices that have a static diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 2100547b2d8d..6e5328c6c6af 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -298,7 +298,8 @@ enum i3c_open_drain_speed { * @I3C_ADDR_SLOT_I2C_DEV: address is assigned to an I2C device * @I3C_ADDR_SLOT_I3C_DEV: address is assigned to an I3C device * @I3C_ADDR_SLOT_STATUS_MASK: address slot mask - * + * @I3C_ADDR_SLOT_EXT_DESIRED: the bitmask represents addresses that are preferred by some devices, + * such as the "assigned-address" property in a device tree source. * On an I3C bus, addresses are assigned dynamically, and we need to know which * addresses are free to use and which ones are already assigned. * @@ -311,9 +312,11 @@ enum i3c_addr_slot_status { I3C_ADDR_SLOT_I2C_DEV, I3C_ADDR_SLOT_I3C_DEV, I3C_ADDR_SLOT_STATUS_MASK = 3, + I3C_ADDR_SLOT_EXT_STATUS_MASK = 7, + I3C_ADDR_SLOT_EXT_DESIRED = BIT(2), }; -#define I3C_ADDR_SLOT_STATUS_BITS 2 +#define I3C_ADDR_SLOT_STATUS_BITS 4 /** * struct i3c_bus - I3C bus object From ab952bf3acfa8b4140bf00076424bca80fc34661 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:22:12 -0400 Subject: [PATCH 08/88] i3c: master: Fix dynamic address leak when 'assigned-address' is present JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 851bd21cdb55e727ab29280bc9f6b678164f802a Author: Frank Li Date: Mon, 21 Oct 2024 11:45:08 -0400 i3c: master: Fix dynamic address leak when 'assigned-address' is present If the DTS contains 'assigned-address', a dynamic address leak occurs during hotjoin events. Assume a device have assigned-address 0xb. - Device issue Hotjoin - Call i3c_master_do_daa() - Call driver xxx_do_daa() - Call i3c_master_get_free_addr() to get dynamic address 0x9 - i3c_master_add_i3c_dev_locked(0x9) - expected_dyn_addr = newdev->boardinfo->init_dyn_addr (0xb); - i3c_master_reattach_i3c_dev(newdev(0xb), old_dyn_addr(0x9)); - if (dev->info.dyn_addr != old_dyn_addr && ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 0xb != 0x9 -> TRUE (!dev->boardinfo || ^^^^^^^^^^^^^^^ -> FALSE dev->info.dyn_addr != dev->boardinfo->init_dyn_addr)) { ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 0xb != 0xb -> FALSE ... i3c_bus_set_addr_slot_status(&master->bus, old_dyn_addr, I3C_ADDR_SLOT_FREE); ^^^ This will be skipped. So old_dyn_addr never free } - i3c_master_get_free_addr() will return increased sequence number. Remove dev->info.dyn_addr != dev->boardinfo->init_dyn_addr condition check. dev->info.dyn_addr should be checked before calling this function because i3c_master_setnewda_locked() has already been called and the target device has already accepted dyn_addr. It is too late to check if dyn_addr is free in i3c_master_reattach_i3c_dev(). Add check to ensure expected_dyn_addr is free before i3c_master_setnewda_locked(). Fixes: cc3a392d69b6 ("i3c: master: fix for SETDASA and DAA process") Cc: stable@kernel.org Reviewed-by: Miquel Raynal Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20241021-i3c_dts_assign-v8-3-4098b8bde01e@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 396be110ab06..54817324bb8e 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1548,16 +1548,9 @@ static int i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 old_dyn_addr) { struct i3c_master_controller *master = i3c_dev_get_master(dev); - enum i3c_addr_slot_status status; int ret; - if (dev->info.dyn_addr != old_dyn_addr && - (!dev->boardinfo || - dev->info.dyn_addr != dev->boardinfo->init_dyn_addr)) { - status = i3c_bus_get_addr_slot_status(&master->bus, - dev->info.dyn_addr); - if (status != I3C_ADDR_SLOT_FREE) - return -EBUSY; + if (dev->info.dyn_addr != old_dyn_addr) { i3c_bus_set_addr_slot_status(&master->bus, dev->info.dyn_addr, I3C_ADDR_SLOT_I3C_DEV); @@ -1960,9 +1953,10 @@ static int i3c_master_bus_init(struct i3c_master_controller *master) goto err_rstdaa; } + /* Do not mark as occupied until real device exist in bus */ i3c_bus_set_addr_slot_status_mask(&master->bus, i3cboardinfo->init_dyn_addr, - I3C_ADDR_SLOT_I3C_DEV | I3C_ADDR_SLOT_EXT_DESIRED, + I3C_ADDR_SLOT_EXT_DESIRED, I3C_ADDR_SLOT_EXT_STATUS_MASK); /* @@ -2126,7 +2120,8 @@ int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master, else expected_dyn_addr = newdev->info.dyn_addr; - if (newdev->info.dyn_addr != expected_dyn_addr) { + if (newdev->info.dyn_addr != expected_dyn_addr && + i3c_bus_get_addr_slot_status(&master->bus, expected_dyn_addr) == I3C_ADDR_SLOT_FREE) { /* * Try to apply the expected dynamic address. If it fails, keep * the address assigned by the master. From 9d4d8c1b174cb8b05417ca3280dbc6eeb6337c32 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:24:45 -0400 Subject: [PATCH 09/88] i3c: master: Remove i3c_dev_disable_ibi_locked(olddev) on device hotjoin JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 36faa04ce3d9c962b4b29d285ad07ca29e2988e4 Author: Frank Li Date: Tue, 1 Oct 2024 12:22:32 -0400 i3c: master: Remove i3c_dev_disable_ibi_locked(olddev) on device hotjoin When a new device hotjoins, a new dynamic address is assigned. i3c_master_add_i3c_dev_locked() identifies that the device was previously attached to the bus and locates the olddev. i3c_master_add_i3c_dev_locked() { ... olddev = i3c_master_search_i3c_dev_duplicate(newdev); ... if (olddev) { ... i3c_dev_disable_ibi_locked(olddev); ^^^^^^ The olddev should not receive any commands on the i3c bus as it does not exist and has been assigned a new address. This will result in NACK or timeout. So remove it. } i3c_dev_free_ibi_locked(olddev); ^^^^^^^^ This function internally calls i3c_dev_disable_ibi_locked() function causing to send DISEC command with old Address. The olddev should not receive any commands on the i3c bus as it does not exist and has been assigned a new address. This will result in NACK or timeout. So, update the olddev->ibi->enabled flag to false to avoid DISEC with OldAddr. } Include part of Ravindra Yashvant Shinde's work: https://lore.kernel.org/linux-i3c/20240820151917.3904956-1-ravindra.yashvant.shinde@nxp.com/T/#u Fixes: 317bacf960a4 ("i3c: master: add enable(disable) hot join in sys entry") Co-developed-by: Ravindra Yashvant Shinde Signed-off-by: Ravindra Yashvant Shinde Reviewed-by: Miquel Raynal Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20241001162232.223724-1-Frank.Li@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 54817324bb8e..af353fbfbe83 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2088,11 +2088,16 @@ int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master, ibireq.max_payload_len = olddev->ibi->max_payload_len; ibireq.num_slots = olddev->ibi->num_slots; - if (olddev->ibi->enabled) { + if (olddev->ibi->enabled) enable_ibi = true; - i3c_dev_disable_ibi_locked(olddev); - } - + /* + * The olddev should not receive any commands on the + * i3c bus as it does not exist and has been assigned + * a new address. This will result in NACK or timeout. + * So, update the olddev->ibi->enabled flag to false + * to avoid DISEC with OldAddr. + */ + olddev->ibi->enabled = false; i3c_dev_free_ibi_locked(olddev); } mutex_unlock(&olddev->ibi_lock); From d3639089547b21b07c4721e583b3e3c0c632efaf Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:25:52 -0400 Subject: [PATCH 10/88] i3c: master: Fix miss free init_dyn_addr at i3c_master_put_i3c_addrs() JIRA: https://issues.redhat.com/browse/RHEL-143335 CVE: CVE-2024-56562 commit 3082990592f7c6d7510a9133afa46e31bbe26533 Author: Frank Li Date: Tue, 1 Oct 2024 12:26:08 -0400 i3c: master: Fix miss free init_dyn_addr at i3c_master_put_i3c_addrs() if (dev->boardinfo && dev->boardinfo->init_dyn_addr) ^^^ here check "init_dyn_addr" i3c_bus_set_addr_slot_status(&master->bus, dev->info.dyn_addr, ...) ^^^^ free "dyn_addr" Fix copy/paste error "dyn_addr" by replacing it with "init_dyn_addr". Cc: stable@kernel.org Fixes: 3a379bbcea0a ("i3c: Add core I3C infrastructure") Reviewed-by: Miquel Raynal Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20241001162608.224039-1-Frank.Li@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index af353fbfbe83..c8c7b8c1824a 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1459,7 +1459,7 @@ static void i3c_master_put_i3c_addrs(struct i3c_dev_desc *dev) I3C_ADDR_SLOT_FREE); if (dev->boardinfo && dev->boardinfo->init_dyn_addr) - i3c_bus_set_addr_slot_status(&master->bus, dev->info.dyn_addr, + i3c_bus_set_addr_slot_status(&master->bus, dev->boardinfo->init_dyn_addr, I3C_ADDR_SLOT_FREE); } From 0f616c1129f66086b4fd5b1afc4be3cb9503f7cc Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:00:33 -0400 Subject: [PATCH 11/88] i3c: Document I3C_ADDR_SLOT_EXT_STATUS_MASK JIRA: https://issues.redhat.com/browse/RHEL-143335 commit f6ca73063754950bf3fbad753e3a9557e3aa85e3 Author: Alexandre Belloni Date: Sat, 2 Nov 2024 14:28:41 +0100 i3c: Document I3C_ADDR_SLOT_EXT_STATUS_MASK As the mask is part of the enum, document it. Reported-by: Stephen Rothwell Link: https://lore.kernel.org/r/20241102132841.2446176-1-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- include/linux/i3c/master.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 6e5328c6c6af..12d532b012c5 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -298,6 +298,7 @@ enum i3c_open_drain_speed { * @I3C_ADDR_SLOT_I2C_DEV: address is assigned to an I2C device * @I3C_ADDR_SLOT_I3C_DEV: address is assigned to an I3C device * @I3C_ADDR_SLOT_STATUS_MASK: address slot mask + * @I3C_ADDR_SLOT_EXT_STATUS_MASK: address slot mask with extended information * @I3C_ADDR_SLOT_EXT_DESIRED: the bitmask represents addresses that are preferred by some devices, * such as the "assigned-address" property in a device tree source. * On an I3C bus, addresses are assigned dynamically, and we need to know which From 996830163e5f614d2fe1bd6abf4d3b73c30a8958 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:29:09 -0400 Subject: [PATCH 12/88] i3c: Use i3cdev->desc->info instead of calling i3c_device_get_info() to avoid deadlock JIRA: https://issues.redhat.com/browse/RHEL-143335 CVE: CVE-2024-43098 commit 6cf7b65f7029914dc0cd7db86fac9ee5159008c6 Author: Defa Li Date: Thu, 7 Nov 2024 21:25:39 +0800 i3c: Use i3cdev->desc->info instead of calling i3c_device_get_info() to avoid deadlock A deadlock may happen since the i3c_master_register() acquires &i3cbus->lock twice. See the log below. Use i3cdev->desc->info instead of calling i3c_device_info() to avoid acquiring the lock twice. v2: - Modified the title and commit message ============================================ WARNING: possible recursive locking detected 6.11.0-mainline -------------------------------------------- init/1 is trying to acquire lock: f1ffff80a6a40dc0 (&i3cbus->lock){++++}-{3:3}, at: i3c_bus_normaluse_lock but task is already holding lock: f1ffff80a6a40dc0 (&i3cbus->lock){++++}-{3:3}, at: i3c_master_register other info that might help us debug this: Possible unsafe locking scenario: CPU0 ---- lock(&i3cbus->lock); lock(&i3cbus->lock); *** DEADLOCK *** May be due to missing lock nesting notation 2 locks held by init/1: #0: fcffff809b6798f8 (&dev->mutex){....}-{3:3}, at: __driver_attach #1: f1ffff80a6a40dc0 (&i3cbus->lock){++++}-{3:3}, at: i3c_master_register stack backtrace: CPU: 6 UID: 0 PID: 1 Comm: init Call trace: dump_backtrace+0xfc/0x17c show_stack+0x18/0x28 dump_stack_lvl+0x40/0xc0 dump_stack+0x18/0x24 print_deadlock_bug+0x388/0x390 __lock_acquire+0x18bc/0x32ec lock_acquire+0x134/0x2b0 down_read+0x50/0x19c i3c_bus_normaluse_lock+0x14/0x24 i3c_device_get_info+0x24/0x58 i3c_device_uevent+0x34/0xa4 dev_uevent+0x310/0x384 kobject_uevent_env+0x244/0x414 kobject_uevent+0x14/0x20 device_add+0x278/0x460 device_register+0x20/0x34 i3c_master_register_new_i3c_devs+0x78/0x154 i3c_master_register+0x6a0/0x6d4 mtk_i3c_master_probe+0x3b8/0x4d8 platform_probe+0xa0/0xe0 really_probe+0x114/0x454 __driver_probe_device+0xa0/0x15c driver_probe_device+0x3c/0x1ac __driver_attach+0xc4/0x1f0 bus_for_each_dev+0x104/0x160 driver_attach+0x24/0x34 bus_add_driver+0x14c/0x294 driver_register+0x68/0x104 __platform_driver_register+0x20/0x30 init_module+0x20/0xfe4 do_one_initcall+0x184/0x464 do_init_module+0x58/0x1ec load_module+0xefc/0x10c8 __arm64_sys_finit_module+0x238/0x33c invoke_syscall+0x58/0x10c el0_svc_common+0xa8/0xdc do_el0_svc+0x1c/0x28 el0_svc+0x50/0xac el0t_64_sync_handler+0x70/0xbc el0t_64_sync+0x1a8/0x1ac Signed-off-by: Defa Li Link: https://lore.kernel.org/r/20241107132549.25439-1-defa.li@mediatek.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- 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 c8c7b8c1824a..ffcb1617b9fe 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -282,7 +282,8 @@ static int i3c_device_uevent(const struct device *dev, struct kobj_uevent_env *e struct i3c_device_info devinfo; u16 manuf, part, ext; - i3c_device_get_info(i3cdev, &devinfo); + if (i3cdev->desc) + devinfo = i3cdev->desc->info; manuf = I3C_PID_MANUF_ID(devinfo.pid); part = I3C_PID_PART_ID(devinfo.pid); ext = I3C_PID_EXTRA_INFO(devinfo.pid); From a772a483bf839391ef1bc0d23ac54304bc61e45d Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:29:44 -0400 Subject: [PATCH 13/88] i3c: master: Fix missing 'ret' assignment in set_speed() JIRA: https://issues.redhat.com/browse/RHEL-143335 commit b266e0d4dac00eecdfaf50ec3f708fd0c3b39637 Author: Frank Li Date: Wed, 8 Jan 2025 17:55:33 -0500 i3c: master: Fix missing 'ret' assignment in set_speed() Fix a probe failure in the i3c master driver that occurs when no i3c devices are connected to the bus. The issue arises in `i3c_master_bus_init()` where the `ret` value is not updated after calling `master->ops->set_speed()`. If no devices are present, `ret` remains set to `I3C_ERROR_M2`, causing the code to incorrectly proceed to `err_bus_cleanup`. Cc: stable@vger.kernel.org Fixes: aef79e189ba2 ("i3c: master: support to adjust first broadcast address speed") Signed-off-by: Frank Li Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Acked-by: Mukesh Kumar Savaliya Reviewed-by: Miquel Raynal Link: https://lore.kernel.org/r/20250108225533.915334-1-Frank.Li@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index ffcb1617b9fe..695f5332d7e2 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1919,7 +1919,7 @@ static int i3c_master_bus_init(struct i3c_master_controller *master) goto err_bus_cleanup; if (master->ops->set_speed) { - master->ops->set_speed(master, I3C_OPEN_DRAIN_NORMAL_SPEED); + ret = master->ops->set_speed(master, I3C_OPEN_DRAIN_NORMAL_SPEED); if (ret) goto err_bus_cleanup; } From 6dd26ffbd87f686ad0fb40afbaeb6bdae797515a Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:02:29 -0400 Subject: [PATCH 14/88] i3c: Remove the const qualifier from i2c_msg pointer in i2c_xfers API JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 6866c91f8c2327546d850d5a85025fb81e2089bf Author: Billy Tsai Date: Tue, 4 Feb 2025 17:17:01 +0800 i3c: Remove the const qualifier from i2c_msg pointer in i2c_xfers API The change is necessary to enable the use of the `i2c_get_dma_safe_msg_buf()` API, which requires a non-const `struct i2c_msg *` to operate. The `i2c_get_dma_safe_msg_buf()` function ensures safe handling of I2C messages when using DMA, making it essential for scenarios where DMA transfers are involved. By removing the `const` qualifier, this patch allows drivers to prepare and manage DMA-safe buffers directly. Signed-off-by: Billy Tsai Reviewed-by: Frank Li Reviewed-by: Wolfram Sang Acked-by: Mukesh Kumar Savaliya Link: https://lore.kernel.org/r/20250204091702.4014466-1-billy_tsai@aspeedtech.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 2 +- drivers/i3c/master/i3c-master-cdns.c | 2 +- drivers/i3c/master/mipi-i3c-hci/core.c | 2 +- drivers/i3c/master/svc-i3c-master.c | 2 +- include/linux/i3c/master.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 57b76b0a522f..87f8fe6af709 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1092,7 +1092,7 @@ static void dw_i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev) } static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, - const struct i2c_msg *i2c_xfers, + struct i2c_msg *i2c_xfers, int i2c_nxfers) { struct dw_i3c_i2c_dev_data *data = i2c_dev_get_master_data(dev); diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c index fe4d59833ad5..ce2933c912d0 100644 --- a/drivers/i3c/master/i3c-master-cdns.c +++ b/drivers/i3c/master/i3c-master-cdns.c @@ -813,7 +813,7 @@ static int cdns_i3c_master_priv_xfers(struct i3c_dev_desc *dev, } static int cdns_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, - const struct i2c_msg *xfers, int nxfers) + struct i2c_msg *xfers, int nxfers) { struct i3c_master_controller *m = i2c_dev_get_master(dev); struct cdns_i3c_master *master = to_cdns_i3c_master(m); diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index a82c47c9986d..5bc22d9635eb 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -369,7 +369,7 @@ out: } static int i3c_hci_i2c_xfers(struct i2c_dev_desc *dev, - const struct i2c_msg *i2c_xfers, int nxfers) + struct i2c_msg *i2c_xfers, int nxfers) { struct i3c_master_controller *m = i2c_dev_get_master(dev); struct i3c_hci *hci = to_i3c_hci(m); diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index a7bfc678153e..93a4ae7ad062 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -1520,7 +1520,7 @@ static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev, } static int svc_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, - const struct i2c_msg *xfers, + struct i2c_msg *xfers, int nxfers) { struct i3c_master_controller *m = i2c_dev_get_master(dev); diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 12d532b012c5..c67922ece617 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -475,7 +475,7 @@ struct i3c_master_controller_ops { int (*attach_i2c_dev)(struct i2c_dev_desc *dev); void (*detach_i2c_dev)(struct i2c_dev_desc *dev); int (*i2c_xfers)(struct i2c_dev_desc *dev, - const struct i2c_msg *xfers, int nxfers); + struct i2c_msg *xfers, int nxfers); int (*request_ibi)(struct i3c_dev_desc *dev, const struct i3c_ibi_setup *req); void (*free_ibi)(struct i3c_dev_desc *dev); From a88317e57cb6175e2f3db20eadcde85efd0f4150 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 09:17:56 -0400 Subject: [PATCH 15/88] i3c: mipi-i3c-hci: Use I2C DMA-safe api JIRA: https://issues.redhat.com/browse/RHEL-143335 commit effed5dac8f8b3db006c4971cdd592504afd1c5d Author: Billy Tsai Date: Tue, 4 Feb 2025 17:17:02 +0800 i3c: mipi-i3c-hci: Use I2C DMA-safe api Use the i2c_get/put_dma_safe_msg_buf for I2C transfers instead of using the I3C-specific API. Signed-off-by: Billy Tsai Acked-by: Mukesh Kumar Savaliya Reviewed-by: Jarkko Nikula Link: https://lore.kernel.org/r/20250204091702.4014466-2-billy_tsai@aspeedtech.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/mipi-i3c-hci/core.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 5bc22d9635eb..b189aa3622e0 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -384,14 +384,11 @@ static int i3c_hci_i2c_xfers(struct i2c_dev_desc *dev, return -ENOMEM; for (i = 0; i < nxfers; i++) { - xfer[i].data = i2c_xfers[i].buf; + xfer[i].data = i2c_get_dma_safe_msg_buf(&i2c_xfers[i], 1); xfer[i].data_len = i2c_xfers[i].len; xfer[i].rnw = i2c_xfers[i].flags & I2C_M_RD; hci->cmd->prep_i2c_xfer(hci, dev, &xfer[i]); xfer[i].cmd_desc[0] |= CMD_0_ROC; - ret = i3c_hci_alloc_safe_xfer_buf(hci, &xfer[i]); - if (ret) - goto out; } last = i - 1; xfer[last].cmd_desc[0] |= CMD_0_TOC; @@ -414,7 +411,8 @@ static int i3c_hci_i2c_xfers(struct i2c_dev_desc *dev, out: for (i = 0; i < nxfers; i++) - i3c_hci_free_safe_xfer_buf(hci, &xfer[i]); + i2c_put_dma_safe_msg_buf(xfer[i].data, &i2c_xfers[i], + ret ? false : true); hci_free_xfer(xfer, nxfers); return ret; From ca744ae220097cb7ab3d52110e3b76dffb660e2e Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:32:41 -0400 Subject: [PATCH 16/88] i3c: Add NULL pointer check in i3c_master_queue_ibi() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-143335 CVE: CVE-2025-23147 commit bd496a44f041da9ef3afe14d1d6193d460424e91 Author: Manjunatha Venkatesh Date: Wed, 26 Mar 2025 18:00:46 +0530 i3c: Add NULL pointer check in i3c_master_queue_ibi() The I3C master driver may receive an IBI from a target device that has not been probed yet. In such cases, the master calls `i3c_master_queue_ibi()` to queue an IBI work task, leading to "Unable to handle kernel read from unreadable memory" and resulting in a kernel panic. Typical IBI handling flow: 1. The I3C master scans target devices and probes their respective drivers. 2. The target device driver calls `i3c_device_request_ibi()` to enable IBI and assigns `dev->ibi = ibi`. 3. The I3C master receives an IBI from the target device and calls `i3c_master_queue_ibi()` to queue the target device driver’s IBI handler task. However, since target device events are asynchronous to the I3C probe sequence, step 3 may occur before step 2, causing `dev->ibi` to be `NULL`, leading to a kernel panic. Add a NULL pointer check in `i3c_master_queue_ibi()` to prevent accessing an uninitialized `dev->ibi`, ensuring stability. Fixes: 3a379bbcea0af ("i3c: Add core I3C infrastructure") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/lkml/Z9gjGYudiYyl3bSe@lizhi-Precision-Tower-5810/ Signed-off-by: Manjunatha Venkatesh Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250326123047.2797946-1-manjunatha.venkatesh@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 695f5332d7e2..e179957c7e9e 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2552,6 +2552,9 @@ static void i3c_master_unregister_i3c_devs(struct i3c_master_controller *master) */ void i3c_master_queue_ibi(struct i3c_dev_desc *dev, struct i3c_ibi_slot *slot) { + if (!dev->ibi || !slot) + return; + atomic_inc(&dev->ibi->pending_ibis); queue_work(dev->ibi->wq, &slot->work); } From b198493627b28626499169135f6ba1de7455235b Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:05:35 -0400 Subject: [PATCH 17/88] i3c: dw: use adapter timeout value for I2C transfers JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 1b84691e7870bc5b6a66a1e81abe0eae8359dfce Author: Wolfram Sang Date: Wed, 4 Jun 2025 12:18:27 +0200 i3c: dw: use adapter timeout value for I2C transfers I2C adapters have their own timeout value which can be changed by userspace if desired. Use it for I2C transfers. The default is 1Hz, so the default behaviour is unchanged. Signed-off-by: Wolfram Sang Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250604101831.56585-2-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 87f8fe6af709..c66d76ca38b2 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1155,7 +1155,7 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, } dw_i3c_master_enqueue_xfer(master, xfer); - if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT)) + if (!wait_for_completion_timeout(&xfer->comp, m->i2c.timeout)) dw_i3c_master_dequeue_xfer(master, xfer); ret = xfer->ret; From 76680b75e60c858cbb1a873a110413eb33ee6a38 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:33:53 -0400 Subject: [PATCH 18/88] i3c: master: Initialize ret in i3c_i2c_notifier_call() JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 290ce8b2d0745e45a3155268184523a8c75996f1 Author: Jorge Marques Date: Sun, 22 Jun 2025 12:11:07 +0200 i3c: master: Initialize ret in i3c_i2c_notifier_call() Set ret to -EINVAL if i3c_i2c_notifier_call() receives an invalid action, resolving uninitialized warning. Signed-off-by: Jorge Marques Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250622-i3c-master-ret-uninitialized-v1-1-aabb5625c932@analog.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index e179957c7e9e..b71ab319bbf2 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2464,6 +2464,8 @@ static int i3c_i2c_notifier_call(struct notifier_block *nb, unsigned long action case BUS_NOTIFY_DEL_DEVICE: ret = i3c_master_i2c_detach(adap, client); break; + default: + ret = -EINVAL; } i3c_bus_maintenance_unlock(&master->bus); From a754b1dc793717b618d48be56446e0e57e326e49 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:34:36 -0400 Subject: [PATCH 19/88] i3c: don't fail if GETHDRCAP is unsupported JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 447270cdb41b1c8c3621bb14b93a6749f942556e Author: Wolfram Sang Date: Fri, 4 Jul 2025 22:44:32 +0200 i3c: don't fail if GETHDRCAP is unsupported 'I3C_BCR_HDR_CAP' is still spec v1.0 and has been renamed to 'advanced capabilities' in v1.1 onwards. The ST pressure sensor LPS22DF does not have HDR, but has the 'advanced cap' bit set. The core still wants to get additional information using the CCC 'GETHDRCAP' (or GETCAPS in v1.1 onwards). Not all controllers support this CCC and will notify the upper layers about it. For instantiating the device, we can ignore this unsupported CCC as standard communication will work. Without this patch, the device will not be instantiated at all. Signed-off-by: Wolfram Sang Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250704204524.6124-1-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index b71ab319bbf2..339a74aeaf28 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1439,7 +1439,7 @@ static int i3c_master_retrieve_dev_info(struct i3c_dev_desc *dev) if (dev->info.bcr & I3C_BCR_HDR_CAP) { ret = i3c_master_gethdrcap_locked(master, &dev->info); - if (ret) + if (ret && ret != -ENOTSUPP) return ret; } From 0dd1a1419d7459fcf7748ea93330a4e3c54587e2 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:34:57 -0400 Subject: [PATCH 20/88] i3c: master: replace ENOTSUPP with SUSV4-compliant EOPNOTSUPP JIRA: https://issues.redhat.com/browse/RHEL-143335 commit d10a4c323883c41cf1b652309e61c48bce248e35 Author: Wolfram Sang Date: Sat, 28 Jun 2025 21:20:28 +0200 i3c: master: replace ENOTSUPP with SUSV4-compliant EOPNOTSUPP Replace non-standard ENOTSUPP with the SUSV4-defined error code EOPNOTSUPP to fix below checkpatch warning: "ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP" Signed-off-by: Wolfram Sang Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250628192027.3932-6-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 339a74aeaf28..db214bc8431a 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -837,14 +837,14 @@ static int i3c_master_send_ccc_cmd_locked(struct i3c_master_controller *master, return -EINVAL; if (!master->ops->send_ccc_cmd) - return -ENOTSUPP; + return -EOPNOTSUPP; if ((cmd->id & I3C_CCC_DIRECT) && (!cmd->dests || !cmd->ndests)) return -EINVAL; if (master->ops->supports_ccc_cmd && !master->ops->supports_ccc_cmd(master, cmd)) - return -ENOTSUPP; + return -EOPNOTSUPP; ret = master->ops->send_ccc_cmd(master, cmd); if (ret) { @@ -1439,7 +1439,7 @@ static int i3c_master_retrieve_dev_info(struct i3c_dev_desc *dev) if (dev->info.bcr & I3C_BCR_HDR_CAP) { ret = i3c_master_gethdrcap_locked(master, &dev->info); - if (ret && ret != -ENOTSUPP) + if (ret && ret != -EOPNOTSUPP) return ret; } @@ -2210,7 +2210,7 @@ of_i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, */ if (boardinfo->base.flags & I2C_CLIENT_TEN) { dev_err(dev, "I2C device with 10 bit address not supported."); - return -ENOTSUPP; + return -EOPNOTSUPP; } /* LVR is encoded in reg[2]. */ @@ -2337,13 +2337,13 @@ static int i3c_master_i2c_adapter_xfer(struct i2c_adapter *adap, return -EINVAL; if (!master->ops->i2c_xfers) - return -ENOTSUPP; + return -EOPNOTSUPP; /* Doing transfers to different devices is not supported. */ addr = xfers[0].addr; for (i = 1; i < nxfers; i++) { if (addr != xfers[i].addr) - return -ENOTSUPP; + return -EOPNOTSUPP; } i3c_bus_normaluse_lock(&master->bus); @@ -2765,7 +2765,7 @@ static int i3c_master_check_ops(const struct i3c_master_controller_ops *ops) * controller) * @ops: the master controller operations * @secondary: true if you are registering a secondary master. Will return - * -ENOTSUPP if set to true since secondary masters are not yet + * -EOPNOTSUPP if set to true since secondary masters are not yet * supported * * This function takes care of everything for you: @@ -2792,7 +2792,7 @@ int i3c_master_register(struct i3c_master_controller *master, /* We do not support secondary masters yet. */ if (secondary) - return -ENOTSUPP; + return -EOPNOTSUPP; ret = i3c_master_check_ops(ops); if (ret) @@ -2954,7 +2954,7 @@ int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev, return -EINVAL; if (!master->ops->priv_xfers) - return -ENOTSUPP; + return -EOPNOTSUPP; return master->ops->priv_xfers(dev, xfers, nxfers); } @@ -3004,7 +3004,7 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, int ret; if (!master->ops->request_ibi) - return -ENOTSUPP; + return -EOPNOTSUPP; if (dev->ibi) return -EBUSY; From 92887866538428607fd85c723ce1d0daea3a83e1 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:35:55 -0400 Subject: [PATCH 21/88] i3c: dw: replace ENOTSUPP with SUSV4-compliant EOPNOTSUPP JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 566aebedee37789644bcc976fd6d98ccf8de375b Author: Wolfram Sang Date: Sat, 28 Jun 2025 21:20:29 +0200 i3c: dw: replace ENOTSUPP with SUSV4-compliant EOPNOTSUPP Replace non-standard ENOTSUPP with the SUSV4-defined error code EOPNOTSUPP to fix below checkpatch warning: "ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP" Signed-off-by: Wolfram Sang Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250628192027.3932-7-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index c66d76ca38b2..212b268e8d41 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -945,7 +945,7 @@ static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev, return 0; if (i3c_nxfers > master->caps.cmdfifodepth) - return -ENOTSUPP; + return -EOPNOTSUPP; for (i = 0; i < i3c_nxfers; i++) { if (i3c_xfers[i].rnw) @@ -956,7 +956,7 @@ static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev, if (ntxwords > master->caps.datafifodepth || nrxwords > master->caps.datafifodepth) - return -ENOTSUPP; + return -EOPNOTSUPP; xfer = dw_i3c_master_alloc_xfer(master, i3c_nxfers); if (!xfer) @@ -1106,7 +1106,7 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, return 0; if (i2c_nxfers > master->caps.cmdfifodepth) - return -ENOTSUPP; + return -EOPNOTSUPP; for (i = 0; i < i2c_nxfers; i++) { if (i2c_xfers[i].flags & I2C_M_RD) @@ -1117,7 +1117,7 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, if (ntxwords > master->caps.datafifodepth || nrxwords > master->caps.datafifodepth) - return -ENOTSUPP; + return -EOPNOTSUPP; xfer = dw_i3c_master_alloc_xfer(master, i2c_nxfers); if (!xfer) From 0a6b4defd88178dc9db5d31d62fe096627a1f654 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:37:16 -0400 Subject: [PATCH 22/88] i3c: prefix hexadecimal entries in sysfs JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 12aa3e0cb0c6f8d406be00bc9f5d89bfbee7b9d9 Author: Wolfram Sang Date: Mon, 7 Jul 2025 13:54:08 +0200 i3c: prefix hexadecimal entries in sysfs Hexadecimal values in sysfs should be prefixed with '0x' like e.g. PCI and SCSI already do it. Also ensure the two digit length since BCR and DCR are a byte in size. Signed-off-by: Wolfram Sang Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250707115409.73545-2-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index db214bc8431a..82a45fa223fe 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -141,7 +141,7 @@ static ssize_t bcr_show(struct device *dev, i3c_bus_normaluse_lock(bus); desc = dev_to_i3cdesc(dev); - ret = sprintf(buf, "%x\n", desc->info.bcr); + ret = sprintf(buf, "0x%02x\n", desc->info.bcr); i3c_bus_normaluse_unlock(bus); return ret; @@ -158,7 +158,7 @@ static ssize_t dcr_show(struct device *dev, i3c_bus_normaluse_lock(bus); desc = dev_to_i3cdesc(dev); - ret = sprintf(buf, "%x\n", desc->info.dcr); + ret = sprintf(buf, "0x%02x\n", desc->info.dcr); i3c_bus_normaluse_unlock(bus); return ret; From fefc1842987b88772d6b192c024ed3b5df7d69c8 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:09:25 -0400 Subject: [PATCH 23/88] i3c: master: Add inline i3c_readl_fifo() and i3c_writel_fifo() JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 733b439375b494e8a6950ab47d18a4b615b73cb3 Author: Jorge Marques Date: Tue, 24 Jun 2025 11:06:04 +0200 i3c: master: Add inline i3c_readl_fifo() and i3c_writel_fifo() The I3C abstraction expects u8 buffers, but some controllers operate with a 32-bit bus width FIFO and cannot flag valid bytes individually. To avoid reading or writing outside the buffer bounds, use 32-bit accesses where possible and apply memcpy for any remaining bytes Signed-off-by: Jorge Marques Suggested-by: Wolfram Sang Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250624-i3c-writesl-readsl-v3-1-63ccf0870f01@analog.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/internals.h | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h index 433f6088b7ce..6a11437fee47 100644 --- a/drivers/i3c/internals.h +++ b/drivers/i3c/internals.h @@ -22,4 +22,41 @@ int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev); int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, const struct i3c_ibi_setup *req); void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev); + +/** + * i3c_writel_fifo - Write data buffer to 32bit FIFO + * @addr: FIFO Address to write to + * @buf: Pointer to the data bytes to write + * @nbytes: Number of bytes to write + */ +static inline void i3c_writel_fifo(void __iomem *addr, const void *buf, + int nbytes) +{ + writesl(addr, buf, nbytes / 4); + if (nbytes & 3) { + u32 tmp = 0; + + memcpy(&tmp, buf + (nbytes & ~3), nbytes & 3); + writel(tmp, addr); + } +} + +/** + * i3c_readl_fifo - Read data buffer from 32bit FIFO + * @addr: FIFO Address to read from + * @buf: Pointer to the buffer to store read bytes + * @nbytes: Number of bytes to read + */ +static inline void i3c_readl_fifo(const void __iomem *addr, void *buf, + int nbytes) +{ + readsl(addr, buf, nbytes / 4); + if (nbytes & 3) { + u32 tmp; + + tmp = readl(addr); + memcpy(buf + (nbytes & ~3), &tmp, nbytes & 3); + } +} + #endif /* I3C_INTERNAL_H */ From a28c085b33ae7ac4b0abbc960bb96b3ad8d9eb4e Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:09:46 -0400 Subject: [PATCH 24/88] i3c: master: dw: Use i3c_writel_fifo() and i3c_readl_fifo() JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 6e055b1fb2fc72ad937fc75ac109fe904ce56003 Author: Jorge Marques Date: Tue, 24 Jun 2025 11:06:06 +0200 i3c: master: dw: Use i3c_writel_fifo() and i3c_readl_fifo() Use common inline i3c_writel_fifo()/i3c_readl_fifo() methods to simplify code since the FIFO of controller is a 32bit width. Signed-off-by: Jorge Marques Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250624-i3c-writesl-readsl-v3-3-63ccf0870f01@analog.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 212b268e8d41..0cf596e05da6 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -23,6 +23,7 @@ #include #include +#include "../internals.h" #include "dw-i3c-master.h" #define DEVICE_CTRL 0x0 @@ -349,37 +350,19 @@ static int dw_i3c_master_get_free_pos(struct dw_i3c_master *master) static void dw_i3c_master_wr_tx_fifo(struct dw_i3c_master *master, const u8 *bytes, int nbytes) { - writesl(master->regs + RX_TX_DATA_PORT, bytes, nbytes / 4); - if (nbytes & 3) { - u32 tmp = 0; - - memcpy(&tmp, bytes + (nbytes & ~3), nbytes & 3); - writesl(master->regs + RX_TX_DATA_PORT, &tmp, 1); - } -} - -static void dw_i3c_master_read_fifo(struct dw_i3c_master *master, - int reg, u8 *bytes, int nbytes) -{ - readsl(master->regs + reg, bytes, nbytes / 4); - if (nbytes & 3) { - u32 tmp; - - readsl(master->regs + reg, &tmp, 1); - memcpy(bytes + (nbytes & ~3), &tmp, nbytes & 3); - } + i3c_writel_fifo(master->regs + RX_TX_DATA_PORT, bytes, nbytes); } static void dw_i3c_master_read_rx_fifo(struct dw_i3c_master *master, u8 *bytes, int nbytes) { - return dw_i3c_master_read_fifo(master, RX_TX_DATA_PORT, bytes, nbytes); + i3c_readl_fifo(master->regs + RX_TX_DATA_PORT, bytes, nbytes); } static void dw_i3c_master_read_ibi_fifo(struct dw_i3c_master *master, u8 *bytes, int nbytes) { - return dw_i3c_master_read_fifo(master, IBI_QUEUE_STATUS, bytes, nbytes); + i3c_readl_fifo(master->regs + IBI_QUEUE_STATUS, bytes, nbytes); } static struct dw_i3c_xfer * From 35cfdc9dbe2a5458b18e77ec488ebaccc934424a Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:10:39 -0400 Subject: [PATCH 25/88] i3c: master: cdns: Use i3c_writel_fifo() and i3c_readl_fifo() JIRA: https://issues.redhat.com/browse/RHEL-143335 commit c20d3fa7049144f519b21616e6020e6939822145 Author: Jorge Marques Date: Tue, 24 Jun 2025 11:06:05 +0200 i3c: master: cdns: Use i3c_writel_fifo() and i3c_readl_fifo() Use common inline i3c_writel_fifo()/i3c_readl_fifo() methods to simplify code since the FIFO of controller is a 32bit width. Signed-off-by: Jorge Marques Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250624-i3c-writesl-readsl-v3-2-63ccf0870f01@analog.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/i3c-master-cdns.c | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c index ce2933c912d0..55d527ed95d2 100644 --- a/drivers/i3c/master/i3c-master-cdns.c +++ b/drivers/i3c/master/i3c-master-cdns.c @@ -23,6 +23,8 @@ #include #include +#include "../internals.h" + #define DEV_ID 0x0 #define DEV_ID_I3C_MASTER 0x5034 @@ -427,25 +429,13 @@ to_cdns_i3c_master(struct i3c_master_controller *master) static void cdns_i3c_master_wr_to_tx_fifo(struct cdns_i3c_master *master, const u8 *bytes, int nbytes) { - writesl(master->regs + TX_FIFO, bytes, nbytes / 4); - if (nbytes & 3) { - u32 tmp = 0; - - memcpy(&tmp, bytes + (nbytes & ~3), nbytes & 3); - writesl(master->regs + TX_FIFO, &tmp, 1); - } + i3c_writel_fifo(master->regs + TX_FIFO, bytes, nbytes); } static void cdns_i3c_master_rd_from_rx_fifo(struct cdns_i3c_master *master, u8 *bytes, int nbytes) { - readsl(master->regs + RX_FIFO, bytes, nbytes / 4); - if (nbytes & 3) { - u32 tmp; - - readsl(master->regs + RX_FIFO, &tmp, 1); - memcpy(bytes + (nbytes & ~3), &tmp, nbytes & 3); - } + i3c_readl_fifo(master->regs + RX_FIFO, bytes, nbytes); } static bool cdns_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m, @@ -1331,12 +1321,7 @@ static void cdns_i3c_master_handle_ibi(struct cdns_i3c_master *master, buf = slot->data; nbytes = IBIR_XFER_BYTES(ibir); - readsl(master->regs + IBI_DATA_FIFO, buf, nbytes / 4); - if (nbytes % 3) { - u32 tmp = __raw_readl(master->regs + IBI_DATA_FIFO); - - memcpy(buf + (nbytes & ~3), &tmp, nbytes & 3); - } + i3c_readl_fifo(master->regs + IBI_DATA_FIFO, buf, nbytes); slot->len = min_t(unsigned int, IBIR_XFER_BYTES(ibir), dev->ibi->max_payload_len); From ca45939003dd0cc9e2583d2cfe343ab9f7f09178 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:44:26 -0400 Subject: [PATCH 26/88] i3c: Standardize defines for specification parameters JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 9c0609d685b27a0bb392390680207baa820ed118 Author: Wolfram Sang Date: Thu, 24 Jul 2025 11:41:40 +0200 i3c: Standardize defines for specification parameters Align existing defines to follow the consistent pattern: I3C_BUS___. Prepare the codebase for adding new parameters and help avoid duplication. Signed-off-by: Wolfram Sang Tested-by: Tommaso Merciai Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250724094146.6443-2-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 12 ++++++------ drivers/i3c/master/dw-i3c-master.c | 4 ++-- include/linux/i3c/master.h | 9 +++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 82a45fa223fe..91b49af79522 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -727,12 +727,12 @@ static int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode, switch (i3cbus->mode) { case I3C_BUS_MODE_PURE: if (!i3cbus->scl_rate.i3c) - i3cbus->scl_rate.i3c = I3C_BUS_TYP_I3C_SCL_RATE; + i3cbus->scl_rate.i3c = I3C_BUS_I3C_SCL_TYP_RATE; break; case I3C_BUS_MODE_MIXED_FAST: case I3C_BUS_MODE_MIXED_LIMITED: if (!i3cbus->scl_rate.i3c) - i3cbus->scl_rate.i3c = I3C_BUS_TYP_I3C_SCL_RATE; + i3cbus->scl_rate.i3c = I3C_BUS_I3C_SCL_TYP_RATE; if (!i3cbus->scl_rate.i2c) i3cbus->scl_rate.i2c = max_i2c_scl_rate; break; @@ -754,8 +754,8 @@ static int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode, * I3C/I2C frequency may have been overridden, check that user-provided * values are not exceeding max possible frequency. */ - if (i3cbus->scl_rate.i3c > I3C_BUS_MAX_I3C_SCL_RATE || - i3cbus->scl_rate.i2c > I3C_BUS_I2C_FM_PLUS_SCL_RATE) + if (i3cbus->scl_rate.i3c > I3C_BUS_I3C_SCL_MAX_RATE || + i3cbus->scl_rate.i2c > I3C_BUS_I2C_FM_PLUS_SCL_MAX_RATE) return -EINVAL; return 0; @@ -2784,7 +2784,7 @@ int i3c_master_register(struct i3c_master_controller *master, const struct i3c_master_controller_ops *ops, bool secondary) { - unsigned long i2c_scl_rate = I3C_BUS_I2C_FM_PLUS_SCL_RATE; + unsigned long i2c_scl_rate = I3C_BUS_I2C_FM_PLUS_SCL_MAX_RATE; struct i3c_bus *i3cbus = i3c_master_get_bus(master); enum i3c_bus_mode mode = I3C_BUS_MODE_PURE; struct i2c_dev_boardinfo *i2cbi; @@ -2844,7 +2844,7 @@ int i3c_master_register(struct i3c_master_controller *master, } if (i2cbi->lvr & I3C_LVR_I2C_FM_MODE) - i2c_scl_rate = I3C_BUS_I2C_FM_SCL_RATE; + i2c_scl_rate = I3C_BUS_I2C_FM_SCL_MAX_RATE; } ret = i3c_bus_set_mode(i3cbus, mode, i2c_scl_rate); diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 0cf596e05da6..43535b851969 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -618,14 +618,14 @@ static int dw_i2c_clk_cfg(struct dw_i3c_master *master) core_period = DIV_ROUND_UP(1000000000, core_rate); lcnt = DIV_ROUND_UP(I3C_BUS_I2C_FMP_TLOW_MIN_NS, core_period); - hcnt = DIV_ROUND_UP(core_rate, I3C_BUS_I2C_FM_PLUS_SCL_RATE) - lcnt; + hcnt = DIV_ROUND_UP(core_rate, I3C_BUS_I2C_FM_PLUS_SCL_MAX_RATE) - lcnt; scl_timing = SCL_I2C_FMP_TIMING_HCNT(hcnt) | SCL_I2C_FMP_TIMING_LCNT(lcnt); writel(scl_timing, master->regs + SCL_I2C_FMP_TIMING); master->i2c_fmp_timing = scl_timing; lcnt = DIV_ROUND_UP(I3C_BUS_I2C_FM_TLOW_MIN_NS, core_period); - hcnt = DIV_ROUND_UP(core_rate, I3C_BUS_I2C_FM_SCL_RATE) - lcnt; + hcnt = DIV_ROUND_UP(core_rate, I3C_BUS_I2C_FM_SCL_MAX_RATE) - lcnt; scl_timing = SCL_I2C_FM_TIMING_HCNT(hcnt) | SCL_I2C_FM_TIMING_LCNT(lcnt); writel(scl_timing, master->regs + SCL_I2C_FM_TIMING); diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index c67922ece617..7dfcbe530515 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -249,10 +249,11 @@ struct i3c_device { */ #define I3C_BUS_MAX_DEVS 11 -#define I3C_BUS_MAX_I3C_SCL_RATE 12900000 -#define I3C_BUS_TYP_I3C_SCL_RATE 12500000 -#define I3C_BUS_I2C_FM_PLUS_SCL_RATE 1000000 -#define I3C_BUS_I2C_FM_SCL_RATE 400000 +/* Taken from the I3C Spec V1.1.1, chapter 6.2. "Timing specification" */ +#define I3C_BUS_I2C_FM_PLUS_SCL_MAX_RATE 1000000 +#define I3C_BUS_I2C_FM_SCL_MAX_RATE 400000 +#define I3C_BUS_I3C_SCL_MAX_RATE 12900000 +#define I3C_BUS_I3C_SCL_TYP_RATE 12500000 #define I3C_BUS_TLOW_OD_MIN_NS 200 /** From 8138873233e4f0bd5bbb00800a1524aaf51b4eb8 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:46:30 -0400 Subject: [PATCH 27/88] i3c: Add more parameters for controllers to the header JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 8acf1f3bae1ea48949458b67d68a72a95c3244a4 Author: Wolfram Sang Date: Thu, 24 Jul 2025 11:41:41 +0200 i3c: Add more parameters for controllers to the header Add standard timing value definition from specification. Signed-off-by: Wolfram Sang Tested-by: Tommaso Merciai Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250724094146.6443-3-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- include/linux/i3c/master.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 7dfcbe530515..043f5c7ff398 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -254,6 +254,10 @@ struct i3c_device { #define I3C_BUS_I2C_FM_SCL_MAX_RATE 400000 #define I3C_BUS_I3C_SCL_MAX_RATE 12900000 #define I3C_BUS_I3C_SCL_TYP_RATE 12500000 +#define I3C_BUS_TAVAL_MIN_NS 1000 +#define I3C_BUS_TBUF_MIXED_FM_MIN_NS 1300 +#define I3C_BUS_THIGH_MIXED_MAX_NS 41 +#define I3C_BUS_TIDLE_MIN_NS 200000 #define I3C_BUS_TLOW_OD_MIN_NS 200 /** From b9bef4130c1089bf1ba5d6c199856d48d609bde3 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:17:22 -0400 Subject: [PATCH 28/88] i3c: dw: Remove redundant pm_runtime_mark_last_busy() calls JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 5fa62d4ec49a26c5ce747d6b0c205d6b30396bbc Author: Sakari Ailus Date: Fri, 4 Jul 2025 10:54:17 +0300 i3c: dw: Remove redundant pm_runtime_mark_last_busy() calls pm_runtime_put_autosuspend(), pm_runtime_put_sync_autosuspend(), pm_runtime_autosuspend() and pm_request_autosuspend() now include a call to pm_runtime_mark_last_busy(). Remove the now-reduntant explicit call to pm_runtime_mark_last_busy(). Signed-off-by: Sakari Ailus Link: https://lore.kernel.org/r/20250704075417.3218742-1-sakari.ailus@linux.intel.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 43535b851969..b6de0312d0e6 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -695,7 +695,6 @@ static int dw_i3c_master_bus_init(struct i3c_master_controller *m) dw_i3c_master_enable(master); rpm_out: - pm_runtime_mark_last_busy(master->dev); pm_runtime_put_autosuspend(master->dev); return ret; } @@ -825,7 +824,6 @@ static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, else ret = dw_i3c_ccc_set(master, ccc); - pm_runtime_mark_last_busy(master->dev); pm_runtime_put_autosuspend(master->dev); return ret; } @@ -908,7 +906,6 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m) dw_i3c_master_free_xfer(xfer); rpm_out: - pm_runtime_mark_last_busy(master->dev); pm_runtime_put_autosuspend(master->dev); return ret; } @@ -994,7 +991,6 @@ static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev, ret = xfer->ret; dw_i3c_master_free_xfer(xfer); - pm_runtime_mark_last_busy(master->dev); pm_runtime_put_autosuspend(master->dev); return ret; } @@ -1144,7 +1140,6 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, ret = xfer->ret; dw_i3c_master_free_xfer(xfer); - pm_runtime_mark_last_busy(master->dev); pm_runtime_put_autosuspend(master->dev); return ret; } @@ -1312,7 +1307,6 @@ static int dw_i3c_master_disable_hotjoin(struct i3c_master_controller *m) writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_HOT_JOIN_NACK, master->regs + DEVICE_CTRL); - pm_runtime_mark_last_busy(master->dev); pm_runtime_put_autosuspend(master->dev); return 0; } @@ -1338,7 +1332,6 @@ static int dw_i3c_master_enable_ibi(struct i3c_dev_desc *dev) if (rc) { dw_i3c_master_set_sir_enabled(master, dev, data->index, false); - pm_runtime_mark_last_busy(master->dev); pm_runtime_put_autosuspend(master->dev); } @@ -1358,7 +1351,6 @@ static int dw_i3c_master_disable_ibi(struct i3c_dev_desc *dev) dw_i3c_master_set_sir_enabled(master, dev, data->index, false); - pm_runtime_mark_last_busy(master->dev); pm_runtime_put_autosuspend(master->dev); return 0; } From dbc541491bdd1c380502638a24b57d9d8a7325e3 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:49:41 -0400 Subject: [PATCH 29/88] i3c: add missing include to internal header JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 3b661ca549b9e5bb11d0bc97ada6110aac3282d2 Author: Wolfram Sang Date: Thu, 17 Jul 2025 14:00:47 +0200 i3c: add missing include to internal header LKP found a random config which failed to build because IO accessors were not defined: In file included from drivers/i3c/master.c:21: drivers/i3c/internals.h: In function 'i3c_writel_fifo': >> drivers/i3c/internals.h:35:9: error: implicit declaration of function 'writesl' [-Werror=implicit-function-declaration] Add the proper header to where the IO accessors are used. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202507150208.BZDzzJ5E-lkp@intel.com/ Signed-off-by: Wolfram Sang Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250717120046.9022-2-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/internals.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h index 6a11437fee47..0d857cc68cc5 100644 --- a/drivers/i3c/internals.h +++ b/drivers/i3c/internals.h @@ -9,6 +9,7 @@ #define I3C_INTERNALS_H #include +#include void i3c_bus_normaluse_lock(struct i3c_bus *bus); void i3c_bus_normaluse_unlock(struct i3c_bus *bus); From 19b1d48c735ecd4ba306d9a4d926306ebbc7ed4c Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:47:24 -0400 Subject: [PATCH 30/88] i3c: master: Add helpers for DMA mapping and bounce buffer handling JIRA: https://issues.redhat.com/browse/RHEL-143335 commit f8d9e56aeb87ce82ce8636cd176cc59b69aa0e41 Author: Jarkko Nikula Date: Fri, 22 Aug 2025 13:56:27 +0300 i3c: master: Add helpers for DMA mapping and bounce buffer handling Some I3C controllers such as MIPI I3C HCI may pad the last DWORD (32-bit) with stale data from the RX FIFO in DMA transfers if the receive length is not DWORD aligned and when the device DMA is IOMMU mapped. In such a case, a properly sized bounce buffer is required in order to avoid possible data corruption. In a review discussion, proposal was to have a common helpers in I3C core for DMA mapping and bounce buffer handling. Drivers may use the helper i3c_master_dma_map_single() to map a buffer for a DMA transfer. It internally allocates a bounce buffer if buffer is not DMA'able or when the driver requires it for a transfer. Helper i3c_master_dma_unmap_single() does the needed cleanups and data copying from the bounce buffer. Signed-off-by: Jarkko Nikula Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250822105630.2820009-2-jarkko.nikula@linux.intel.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 74 ++++++++++++++++++++++++++++++++++++++ include/linux/i3c/master.h | 26 ++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 91b49af79522..41a972be5b6f 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -1727,6 +1728,79 @@ int i3c_master_do_daa(struct i3c_master_controller *master) } EXPORT_SYMBOL_GPL(i3c_master_do_daa); +/** + * i3c_master_dma_map_single() - Map buffer for single DMA transfer + * @dev: device object of a device doing DMA + * @buf: destination/source buffer for DMA + * @len: length of transfer + * @force_bounce: true, force to use a bounce buffer, + * false, function will auto check is a bounce buffer required + * @dir: DMA direction + * + * Map buffer for a DMA transfer and allocate a bounce buffer if required. + * + * Return: I3C DMA transfer descriptor or NULL in case of error. + */ +struct i3c_dma *i3c_master_dma_map_single(struct device *dev, void *buf, + size_t len, bool force_bounce, enum dma_data_direction dir) +{ + struct i3c_dma *dma_xfer __free(kfree) = NULL; + void *bounce __free(kfree) = NULL; + void *dma_buf = buf; + + dma_xfer = kzalloc(sizeof(*dma_xfer), GFP_KERNEL); + if (!dma_xfer) + return NULL; + + dma_xfer->dev = dev; + dma_xfer->buf = buf; + dma_xfer->dir = dir; + dma_xfer->len = len; + dma_xfer->map_len = len; + + if (is_vmalloc_addr(buf)) + force_bounce = true; + + if (force_bounce) { + dma_xfer->map_len = ALIGN(len, cache_line_size()); + if (dir == DMA_FROM_DEVICE) + bounce = kzalloc(dma_xfer->map_len, GFP_KERNEL); + else + bounce = kmemdup(buf, dma_xfer->map_len, GFP_KERNEL); + if (!bounce) + return NULL; + dma_buf = bounce; + } + + dma_xfer->addr = dma_map_single(dev, dma_buf, dma_xfer->map_len, dir); + if (dma_mapping_error(dev, dma_xfer->addr)) + return NULL; + + dma_xfer->bounce_buf = no_free_ptr(bounce); + return no_free_ptr(dma_xfer); +} +EXPORT_SYMBOL_GPL(i3c_master_dma_map_single); + +/** + * i3c_master_dma_unmap_single() - Unmap buffer after DMA + * @dma_xfer: DMA transfer and mapping descriptor + * + * Unmap buffer and cleanup DMA transfer descriptor. + */ +void i3c_master_dma_unmap_single(struct i3c_dma *dma_xfer) +{ + dma_unmap_single(dma_xfer->dev, dma_xfer->addr, + dma_xfer->map_len, dma_xfer->dir); + if (dma_xfer->bounce_buf) { + if (dma_xfer->dir == DMA_FROM_DEVICE) + memcpy(dma_xfer->buf, dma_xfer->bounce_buf, + dma_xfer->len); + kfree(dma_xfer->bounce_buf); + } + kfree(dma_xfer); +} +EXPORT_SYMBOL_GPL(i3c_master_dma_unmap_single); + /** * i3c_master_set_info() - set master device information * @master: master used to send frames on the bus diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 043f5c7ff398..c52a82dd79a6 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -558,6 +558,26 @@ struct i3c_master_controller { #define i3c_bus_for_each_i3cdev(bus, dev) \ list_for_each_entry(dev, &(bus)->devs.i3c, common.node) +/** + * struct i3c_dma - DMA transfer and mapping descriptor + * @dev: device object of a device doing DMA + * @buf: destination/source buffer for DMA + * @len: length of transfer + * @map_len: length of DMA mapping + * @addr: mapped DMA address for a Host Controller Driver + * @dir: DMA direction + * @bounce_buf: an allocated bounce buffer if transfer needs it or NULL + */ +struct i3c_dma { + struct device *dev; + void *buf; + size_t len; + size_t map_len; + dma_addr_t addr; + enum dma_data_direction dir; + void *bounce_buf; +}; + int i3c_master_do_i2c_xfers(struct i3c_master_controller *master, const struct i2c_msg *xfers, int nxfers); @@ -575,6 +595,12 @@ 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); +struct i3c_dma *i3c_master_dma_map_single(struct device *dev, void *ptr, + size_t len, bool force_bounce, + enum dma_data_direction dir); +void i3c_master_dma_unmap_single(struct i3c_dma *dma_xfer); +DEFINE_FREE(i3c_master_dma_unmap_single, void *, + if (_T) i3c_master_dma_unmap_single(_T)) int i3c_master_set_info(struct i3c_master_controller *master, const struct i3c_device_info *info); From 2138b45e55430ec79b77392cb7bbd4ceafc8714f Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:49:50 -0400 Subject: [PATCH 31/88] i3c: Fix default I2C adapter timeout value JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 9395b3c412933401a34845d5326afe4011bbd40f Author: Jarkko Nikula Date: Fri, 5 Sep 2025 13:03:20 +0300 i3c: Fix default I2C adapter timeout value Commit 3a379bbcea0a ("i3c: Add core I3C infrastructure") set the default adapter timeout for I2C transfers as 1000 (ms). However that parameter is defined in jiffies not in milliseconds. With mipi-i3c-hci driver this wasn't visible until commit c0a90eb55a69 ("i3c: mipi-i3c-hci: use adapter timeout value for I2C transfers"). Fix this by setting the default timeout as HZ (CONFIG_HZ) not 1000. Fixes: 1b84691e7870 ("i3c: dw: use adapter timeout value for I2C transfers") Fixes: be27ed672878 ("i3c: master: cdns: use adapter timeout value for I2C transfers") Fixes: c0a90eb55a69 ("i3c: mipi-i3c-hci: use adapter timeout value for I2C transfers") Fixes: a747e01adad2 ("i3c: master: svc: use adapter timeout value for I2C transfers") Fixes: d028219a9f14 ("i3c: master: Add basic driver for the Renesas I3C controller") Fixes: 3a379bbcea0a ("i3c: Add core I3C infrastructure") Cc: stable@vger.kernel.org # 6.17 Signed-off-by: Jarkko Nikula Reviewed-by: Frank Li Reviewed-by: Wolfram Sang Link: https://lore.kernel.org/r/20250905100320.954536-1-jarkko.nikula@linux.intel.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 41a972be5b6f..a7ebfc815b9e 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2563,7 +2563,7 @@ static int i3c_master_i2c_adapter_init(struct i3c_master_controller *master) strscpy(adap->name, dev_name(master->dev.parent), sizeof(adap->name)); /* FIXME: Should we allow i3c masters to override these values? */ - adap->timeout = 1000; + adap->timeout = HZ; adap->retries = 3; id = of_alias_get_id(master->dev.of_node, "i2c"); From e6e9361f741b2b18ffc588c1c403099d17f22b6a Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:52:29 -0400 Subject: [PATCH 32/88] i3c: Remove superfluous FIXME JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 649764145b70a0328fc020fe31fc80594761a707 Author: Wolfram Sang Date: Tue, 23 Sep 2025 22:35:58 +0200 i3c: Remove superfluous FIXME I2C adapters can already change timeout and retry parameters via IOCTL. This allows for better tuning to workloads compared to per-adapter defaults. So, the FIXME is not needed. Signed-off-by: Wolfram Sang Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250923203557.18298-2-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index a7ebfc815b9e..1033f9eb6bf8 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2561,8 +2561,6 @@ static int i3c_master_i2c_adapter_init(struct i3c_master_controller *master) adap->owner = master->dev.parent->driver->owner; adap->algo = &i3c_master_i2c_algo; strscpy(adap->name, dev_name(master->dev.parent), sizeof(adap->name)); - - /* FIXME: Should we allow i3c masters to override these values? */ adap->timeout = HZ; adap->retries = 3; From 4e8be6e41c7b0dec4d3eb1c24841cd769c9efc5f Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:49:59 -0400 Subject: [PATCH 33/88] i3c: fix big-endian FIFO transfers JIRA: https://issues.redhat.com/browse/RHEL-143335 commit d6ddd9beb1a5c32acb9b80f5c2cd8b17f41371d1 Author: Arnd Bergmann Date: Wed, 24 Sep 2025 22:18:33 +0200 i3c: fix big-endian FIFO transfers Short MMIO transfers that are not a multiple of four bytes in size need a special case for the final bytes, however the existing implementation is not endian-safe and introduces an incorrect byteswap on big-endian kernels. This usually does not cause problems because most systems are little-endian and most transfers are multiple of four bytes long, but still needs to be fixed to avoid the extra byteswap. Change the special case for both i3c_writel_fifo() and i3c_readl_fifo() to use non-byteswapping writesl() and readsl() with a single element instead of the byteswapping writel()/readl() that are meant for individual MMIO registers. As data is copied between a FIFO and a memory buffer, the writesl()/readsl() loops are typically based on __raw_readl()/ __raw_writel(), resulting in the order of bytes in the FIFO to match the order in the buffer, regardless of the CPU endianess. The earlier versions in the dw-i3c and i3c-master-cdns had a correct implementation, but the generic version that was recently added broke it. Fixes: 733b439375b4 ("i3c: master: Add inline i3c_readl_fifo() and i3c_writel_fifo()") Cc: Manikanta Guntupalli Signed-off-by: Arnd Bergmann Reviewed-by: Jorge Marques Link: https://lore.kernel.org/r/20250924201837.3691486-1-arnd@kernel.org Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/internals.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h index 0d857cc68cc5..79ceaa5f5afd 100644 --- a/drivers/i3c/internals.h +++ b/drivers/i3c/internals.h @@ -38,7 +38,11 @@ static inline void i3c_writel_fifo(void __iomem *addr, const void *buf, u32 tmp = 0; memcpy(&tmp, buf + (nbytes & ~3), nbytes & 3); - writel(tmp, addr); + /* + * writesl() instead of writel() to keep FIFO + * byteorder on big-endian targets + */ + writesl(addr, &tmp, 1); } } @@ -55,7 +59,11 @@ static inline void i3c_readl_fifo(const void __iomem *addr, void *buf, if (nbytes & 3) { u32 tmp; - tmp = readl(addr); + /* + * readsl() instead of readl() to keep FIFO + * byteorder on big-endian targets + */ + readsl(addr, &tmp, 1); memcpy(buf + (nbytes & ~3), &tmp, nbytes & 3); } } From f7e703158476c8c0232add88ba0480fdda17c8be Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:52:55 -0400 Subject: [PATCH 34/88] i3c: master: Remove i3c_device_free_ibi from i3c_device_remove JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 5b9481a4157198b93cd6a5e7ad2603682202149d Author: Jorge Marques Date: Wed, 12 Nov 2025 22:30:00 +0100 i3c: master: Remove i3c_device_free_ibi from i3c_device_remove i3c_device_disable_ibi should be called before i3c_device_free_ibi, however, a driver using devm actions cannot yield the call before the bus_type.remove(), requiring to use a .remove method that is usually discouraged for drivers that uses resources already manage. Since the only consumer mctp-i3c.c of this method calls both i3c_device_disable_ibi then i3c_device_free_ibi, remove the call from the i3c_device_remove (bus_type.remove()). Signed-off-by: Jorge Marques Link: https://patch.msgid.link/20251112-ibi-unsafe-v1-1-d8454db22613@analog.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 1033f9eb6bf8..c1a15405a47e 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -334,8 +334,6 @@ static void i3c_device_remove(struct device *dev) if (driver->remove) driver->remove(i3cdev); - - i3c_device_free_ibi(i3cdev); } const struct bus_type i3c_bus_type = { From 480d79be68d4023a11a8d8b224de20831084ae14 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:54:21 -0400 Subject: [PATCH 35/88] i3c: master: add WQ_PERCPU to alloc_workqueue users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-143335 commit de53ad6ca49e5d73bba72d24b49ec5d40f33ee01 Author: Marco Crivellari Date: Fri, 7 Nov 2025 14:29:49 +0100 i3c: master: add WQ_PERCPU to alloc_workqueue users Currently if a user enqueues a work item using schedule_delayed_work() the used wq is "system_wq" (per-cpu wq) while queue_delayed_work() use WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to schedule_work() that is using system_wq and queue_work(), that makes use again of WORK_CPU_UNBOUND. This lack of consistency cannot be addressed without refactoring the API. alloc_workqueue() treats all queues as per-CPU by default, while unbound workqueues must opt-in via WQ_UNBOUND. This default is suboptimal: most workloads benefit from unbound queues, allowing the scheduler to place worker threads where they’re needed and reducing noise when CPUs are isolated. This continues the effort to refactor workqueue APIs, which began with the introduction of new workqueues and a new alloc_workqueue flag in: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") This change adds a new WQ_PERCPU flag to explicitly request alloc_workqueue() to be per-cpu when WQ_UNBOUND has not been specified. With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND must now use WQ_PERCPU. Once migration is complete, WQ_UNBOUND can be removed and unbound will become the implicit default. Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Link: https://patch.msgid.link/20251107132949.184944-1-marco.crivellari@suse.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index c1a15405a47e..4672e7a212d2 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2921,7 +2921,7 @@ int i3c_master_register(struct i3c_master_controller *master, if (ret) goto err_put_dev; - master->wq = alloc_workqueue("%s", 0, 0, dev_name(parent)); + master->wq = alloc_workqueue("%s", WQ_PERCPU, 0, dev_name(parent)); if (!master->wq) { ret = -ENOMEM; goto err_put_dev; From ad8ed3a2761a2892684d1b69ed20ce611a794678 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 04:39:55 -0400 Subject: [PATCH 36/88] i3c: master: svc: skip address resend on repeat START JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 12cbd157344848b1bd726ecadce27532d79b785a Author: Frank Li Date: Tue, 29 Apr 2025 01:42:34 -0400 i3c: master: svc: skip address resend on repeat START According to the I3C specification, address arbitration only happens during the START. Repeated START do not initiate arbitration, and In-Band Interrupts (IBIs) cannot occur at this stage. Resending the address upon a NACK in a repeat START is therefore redundant and unnecessary. Avoid redundant retries, improving efficiency and ensuring protocol compliance. Signed-off-by: Frank Li Reviewed-by: Miquel Raynal Link: https://lore.kernel.org/r/20250429054234.4013929-1-Frank.Li@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/svc-i3c-master.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index 93a4ae7ad062..e640453a7d8a 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -1139,9 +1139,9 @@ static int svc_i3c_master_write(struct svc_i3c_master *master, static int svc_i3c_master_xfer(struct svc_i3c_master *master, bool rnw, unsigned int xfer_type, u8 addr, u8 *in, const u8 *out, unsigned int xfer_len, - unsigned int *actual_len, bool continued) + unsigned int *actual_len, bool continued, bool repeat_start) { - int retry = 2; + int retry = repeat_start ? 1 : 2; u32 reg; int ret; @@ -1305,7 +1305,7 @@ static void svc_i3c_master_start_xfer_locked(struct svc_i3c_master *master) ret = svc_i3c_master_xfer(master, cmd->rnw, xfer->type, cmd->addr, cmd->in, cmd->out, cmd->len, &cmd->actual_len, - cmd->continued); + cmd->continued, i > 0); /* cmd->xfer is NULL if I2C or CCC transfer */ if (cmd->xfer) cmd->xfer->actual_len = cmd->actual_len; From 7e15281cc1a9fc9550a929bf31958a09fa08b176 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 04:20:43 -0400 Subject: [PATCH 37/88] i3c: mipi-i3c-hci: Use core helpers for DMA mapping and bounce buffering JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 1c46bfc4f75e7d43261af65b64e7d3dc4a30daa9 Author: Jarkko Nikula Date: Fri, 22 Aug 2025 13:56:28 +0300 i3c: mipi-i3c-hci: Use core helpers for DMA mapping and bounce buffering So far only I3C private and I2C transfers have required a bounce buffer for DMA transfers when buffer is not DMA'able. It was observed that when the device DMA is IOMMU mapped and the receive length is not a multiple of DWORDs (32-bit), the last DWORD is padded with stale data from the RX FIFO, corrupting 1-3 bytes beyond the expected data. A similar issue, though less severe, occurs when an I3C target returns less data than requested. In this case, the padding does not exceed the requested number of bytes, assuming the device DMA is not IOMMU mapped. Therefore, all I3C private transfer, CCC command payload and I2C transfer receive buffers must be properly sized for the DMA being IOMMU mapped. Even if those buffers are already DMA safe, their size may not be DWORD aligned. To prepare for the device DMA being IOMMU mapped and to address the above issue, use helpers from I3C core for DMA mapping and bounce buffering for all DMA transfers. For now, require bounce buffer only when the buffer is in the vmalloc() area to avoid unnecessary copying with CCC commands and DMA-safe I2C transfers. Signed-off-by: Jarkko Nikula Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250822105630.2820009-3-jarkko.nikula@linux.intel.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/mipi-i3c-hci/core.c | 34 -------------------------- drivers/i3c/master/mipi-i3c-hci/dma.c | 27 +++++++++----------- drivers/i3c/master/mipi-i3c-hci/hci.h | 3 +-- 3 files changed, 12 insertions(+), 52 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index b189aa3622e0..ca6a785e12af 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -274,34 +274,6 @@ static int i3c_hci_daa(struct i3c_master_controller *m) return hci->cmd->perform_daa(hci); } -static int i3c_hci_alloc_safe_xfer_buf(struct i3c_hci *hci, - struct hci_xfer *xfer) -{ - if (hci->io != &mipi_i3c_hci_dma || - xfer->data == NULL || !is_vmalloc_addr(xfer->data)) - return 0; - - if (xfer->rnw) - xfer->bounce_buf = kzalloc(xfer->data_len, GFP_KERNEL); - else - xfer->bounce_buf = kmemdup(xfer->data, - xfer->data_len, GFP_KERNEL); - - return xfer->bounce_buf == NULL ? -ENOMEM : 0; -} - -static void i3c_hci_free_safe_xfer_buf(struct i3c_hci *hci, - struct hci_xfer *xfer) -{ - if (hci->io != &mipi_i3c_hci_dma || xfer->bounce_buf == NULL) - return; - - if (xfer->rnw) - memcpy(xfer->data, xfer->bounce_buf, xfer->data_len); - - kfree(xfer->bounce_buf); -} - static int i3c_hci_priv_xfers(struct i3c_dev_desc *dev, struct i3c_priv_xfer *i3c_xfers, int nxfers) @@ -335,9 +307,6 @@ static int i3c_hci_priv_xfers(struct i3c_dev_desc *dev, } hci->cmd->prep_i3c_xfer(hci, dev, &xfer[i]); xfer[i].cmd_desc[0] |= CMD_0_ROC; - ret = i3c_hci_alloc_safe_xfer_buf(hci, &xfer[i]); - if (ret) - goto out; } last = i - 1; xfer[last].cmd_desc[0] |= CMD_0_TOC; @@ -361,9 +330,6 @@ static int i3c_hci_priv_xfers(struct i3c_dev_desc *dev, } out: - for (i = 0; i < nxfers; i++) - i3c_hci_free_safe_xfer_buf(hci, &xfer[i]); - hci_free_xfer(xfer, nxfers); return ret; } diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c index a918e96b21fd..179c121bb2b9 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dma.c +++ b/drivers/i3c/master/mipi-i3c-hci/dma.c @@ -349,9 +349,7 @@ static void hci_dma_unmap_xfer(struct i3c_hci *hci, xfer = xfer_list + i; if (!xfer->data) continue; - dma_unmap_single(&hci->master.dev, - xfer->data_dma, xfer->data_len, - xfer->rnw ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + i3c_master_dma_unmap_single(xfer->dma); } } @@ -362,7 +360,6 @@ static int hci_dma_queue_xfer(struct i3c_hci *hci, struct hci_rh_data *rh; unsigned int i, ring, enqueue_ptr; u32 op1_val, op2_val; - void *buf; /* For now we only use ring 0 */ ring = 0; @@ -373,6 +370,8 @@ static int hci_dma_queue_xfer(struct i3c_hci *hci, for (i = 0; i < n; i++) { struct hci_xfer *xfer = xfer_list + i; u32 *ring_data = rh->xfer + rh->xfer_struct_sz * enqueue_ptr; + enum dma_data_direction dir = xfer->rnw ? DMA_FROM_DEVICE : + DMA_TO_DEVICE; /* store cmd descriptor */ *ring_data++ = xfer->cmd_desc[0]; @@ -391,21 +390,17 @@ static int hci_dma_queue_xfer(struct i3c_hci *hci, /* 2nd and 3rd words of Data Buffer Descriptor Structure */ if (xfer->data) { - buf = xfer->bounce_buf ? xfer->bounce_buf : xfer->data; - xfer->data_dma = - dma_map_single(&hci->master.dev, - buf, - xfer->data_len, - xfer->rnw ? - DMA_FROM_DEVICE : - DMA_TO_DEVICE); - if (dma_mapping_error(&hci->master.dev, - xfer->data_dma)) { + xfer->dma = i3c_master_dma_map_single(&hci->master.dev, + xfer->data, + xfer->data_len, + false, + dir); + if (!xfer->dma) { hci_dma_unmap_xfer(hci, xfer_list, i); return -ENOMEM; } - *ring_data++ = lower_32_bits(xfer->data_dma); - *ring_data++ = upper_32_bits(xfer->data_dma); + *ring_data++ = lower_32_bits(xfer->dma->addr); + *ring_data++ = upper_32_bits(xfer->dma->addr); } else { *ring_data++ = 0; *ring_data++ = 0; diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h index aaa47ac47381..aefeeb0871ac 100644 --- a/drivers/i3c/master/mipi-i3c-hci/hci.h +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h @@ -94,8 +94,7 @@ struct hci_xfer { }; struct { /* DMA specific */ - dma_addr_t data_dma; - void *bounce_buf; + struct i3c_dma *dma; int ring_number; int ring_entry; }; From df610a9b2cd63d3e53ef56105a7fb5acbb45ac23 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 11:56:35 -0400 Subject: [PATCH 38/88] i3c: Add HDR API support JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 256a21743d911f94ce92fe28f793cd586f3860b2 Author: Frank Li Date: Thu, 6 Nov 2025 12:36:00 -0500 i3c: Add HDR API support Rename struct i3c_priv_xfer to struct i3c_xfer, since private xfer in the I3C spec refers only to SDR transfers. Ref: i3c spec ver1.2, section 3, Technical Overview. i3c_xfer will be used for both SDR and HDR. Rename enum i3c_hdr_mode to i3c_xfer_mode. Previous definition need match CCC GET_CAP1 bit position. Use 31 as SDR transfer mode. Add i3c_device_do_xfers() with an xfer mode argument, while keeping i3c_device_do_priv_xfers() as a wrapper that calls i3c_device_do_xfers() with I3C_SDR for backward compatibility. Introduce a 'cmd' field in struct i3c_xfer as an anonymous union with 'rnw', since HDR mode uses read/write commands instead of the SDR address bit. Add .i3c_xfers() callback for master controllers. If not implemented, fall back to SDR with .priv_xfers(). The .priv_xfers() API can be removed once all controllers switch to .i3c_xfers(). Add 'mode_mask' bitmask to advertise controller capability. Signed-off-by: Frank Li Link: https://patch.msgid.link/20251106-i3c_ddr-v11-1-33a6a66ed095@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/device.c | 27 ++++++++++++++++++------- drivers/i3c/internals.h | 6 +++--- drivers/i3c/master.c | 19 +++++++++++++----- include/linux/i3c/device.h | 40 +++++++++++++++++++++++++++----------- include/linux/i3c/master.h | 4 ++++ 5 files changed, 70 insertions(+), 26 deletions(-) diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c index e80e48756914..0a919d137db6 100644 --- a/drivers/i3c/device.c +++ b/drivers/i3c/device.c @@ -15,12 +15,12 @@ #include "internals.h" /** - * i3c_device_do_priv_xfers() - do I3C SDR private transfers directed to a - * specific device + * i3c_device_do_xfers() - do I3C transfers directed to a specific device * * @dev: device with which the transfers should be done * @xfers: array of transfers * @nxfers: number of transfers + * @mode: transfer mode * * Initiate one or several private SDR transfers with @dev. * @@ -32,9 +32,8 @@ * driver needs to resend the 'xfers' some time later. * See I3C spec ver 1.1.1 09-Jun-2021. Section: 5.1.2.2.3. */ -int i3c_device_do_priv_xfers(struct i3c_device *dev, - struct i3c_priv_xfer *xfers, - int nxfers) +int i3c_device_do_xfers(struct i3c_device *dev, struct i3c_xfer *xfers, + int nxfers, enum i3c_xfer_mode mode) { int ret, i; @@ -47,12 +46,12 @@ int i3c_device_do_priv_xfers(struct i3c_device *dev, } i3c_bus_normaluse_lock(dev->bus); - ret = i3c_dev_do_priv_xfers_locked(dev->desc, xfers, nxfers); + ret = i3c_dev_do_xfers_locked(dev->desc, xfers, nxfers, mode); i3c_bus_normaluse_unlock(dev->bus); return ret; } -EXPORT_SYMBOL_GPL(i3c_device_do_priv_xfers); +EXPORT_SYMBOL_GPL(i3c_device_do_xfers); /** * i3c_device_do_setdasa() - do I3C dynamic address assignement with @@ -259,6 +258,20 @@ i3c_device_match_id(struct i3c_device *i3cdev, } EXPORT_SYMBOL_GPL(i3c_device_match_id); +/** + * i3c_device_get_supported_xfer_mode - Returns the supported transfer mode by + * connected master controller. + * @dev: I3C device + * + * Return: a bit mask, which supported transfer mode, bit position is defined at + * enum i3c_hdr_mode + */ +u32 i3c_device_get_supported_xfer_mode(struct i3c_device *dev) +{ + return i3c_dev_get_master(dev->desc)->this->info.hdr_cap | BIT(I3C_SDR); +} +EXPORT_SYMBOL_GPL(i3c_device_get_supported_xfer_mode); + /** * i3c_driver_register_with_owner() - register an I3C device driver * diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h index 79ceaa5f5afd..f609e5098137 100644 --- a/drivers/i3c/internals.h +++ b/drivers/i3c/internals.h @@ -15,9 +15,9 @@ void i3c_bus_normaluse_lock(struct i3c_bus *bus); void i3c_bus_normaluse_unlock(struct i3c_bus *bus); int i3c_dev_setdasa_locked(struct i3c_dev_desc *dev); -int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev, - struct i3c_priv_xfer *xfers, - int nxfers); +int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, + struct i3c_xfer *xfers, + int nxfers, enum i3c_xfer_mode mode); int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev); int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev); int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 4672e7a212d2..3814c9b76d8d 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2816,10 +2816,14 @@ EXPORT_SYMBOL_GPL(i3c_generic_ibi_recycle_slot); static int i3c_master_check_ops(const struct i3c_master_controller_ops *ops) { - if (!ops || !ops->bus_init || !ops->priv_xfers || + if (!ops || !ops->bus_init || !ops->send_ccc_cmd || !ops->do_daa || !ops->i2c_xfers) return -EINVAL; + /* Must provide one of priv_xfers (SDR only) or i3c_xfers (all modes) */ + if (!ops->priv_xfers && !ops->i3c_xfers) + return -EINVAL; + if (ops->request_ibi && (!ops->enable_ibi || !ops->disable_ibi || !ops->free_ibi || !ops->recycle_ibi_slot)) @@ -3010,9 +3014,8 @@ int i3c_dev_setdasa_locked(struct i3c_dev_desc *dev) dev->boardinfo->init_dyn_addr); } -int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev, - struct i3c_priv_xfer *xfers, - int nxfers) +int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, struct i3c_xfer *xfers, + int nxfers, enum i3c_xfer_mode mode) { struct i3c_master_controller *master; @@ -3023,9 +3026,15 @@ int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev, if (!master || !xfers) return -EINVAL; - if (!master->ops->priv_xfers) + if (mode != I3C_SDR && !(master->this->info.hdr_cap & BIT(mode))) return -EOPNOTSUPP; + if (master->ops->i3c_xfers) + return master->ops->i3c_xfers(dev, xfers, nxfers, mode); + + if (mode != I3C_SDR) + return -EINVAL; + return master->ops->priv_xfers(dev, xfers, nxfers); } diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h index 0a8a44ac2f02..2daa0878257d 100644 --- a/include/linux/i3c/device.h +++ b/include/linux/i3c/device.h @@ -39,20 +39,25 @@ enum i3c_error_code { }; /** - * enum i3c_hdr_mode - HDR mode ids + * enum i3c_xfer_mode - I3C xfer mode ids * @I3C_HDR_DDR: DDR mode * @I3C_HDR_TSP: TSP mode * @I3C_HDR_TSL: TSL mode + * @I3C_SDR: SDR mode (NOT HDR mode) */ -enum i3c_hdr_mode { - I3C_HDR_DDR, - I3C_HDR_TSP, - I3C_HDR_TSL, +enum i3c_xfer_mode { + /* The below 3 value (I3C_HDR*) must match GETCAP1 Byte bit position */ + I3C_HDR_DDR = 0, + I3C_HDR_TSP = 1, + I3C_HDR_TSL = 2, + /* Use for default SDR transfer mode */ + I3C_SDR = 0x31, }; /** - * struct i3c_priv_xfer - I3C SDR private transfer + * struct i3c_xfer - I3C data transfer * @rnw: encodes the transfer direction. true for a read, false for a write + * @cmd: Read/Write command in HDR mode, read: 0x80 - 0xff, write: 0x00 - 0x7f * @len: transfer length in bytes of the transfer * @actual_len: actual length in bytes are transferred by the controller * @data: input/output buffer @@ -60,8 +65,11 @@ enum i3c_hdr_mode { * @data.out: output buffer. Must point to a DMA-able buffer * @err: I3C error code */ -struct i3c_priv_xfer { - u8 rnw; +struct i3c_xfer { + union { + u8 rnw; + u8 cmd; + }; u16 len; u16 actual_len; union { @@ -71,6 +79,9 @@ struct i3c_priv_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 @@ -297,9 +308,15 @@ static inline void i3c_i2c_driver_unregister(struct i3c_driver *i3cdrv, i3c_i2c_driver_unregister, \ __i2cdrv) -int i3c_device_do_priv_xfers(struct i3c_device *dev, - struct i3c_priv_xfer *xfers, - int nxfers); +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_priv_xfer *xfers, + int nxfers) +{ + return i3c_device_do_xfers(dev, xfers, nxfers, I3C_SDR); +} int i3c_device_do_setdasa(struct i3c_device *dev); @@ -341,5 +358,6 @@ 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 */ diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index c52a82dd79a6..d0d5b3a9049f 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -474,9 +474,13 @@ struct i3c_master_controller_ops { const struct i3c_ccc_cmd *cmd); int (*send_ccc_cmd)(struct i3c_master_controller *master, struct i3c_ccc_cmd *cmd); + /* Deprecated, please use i3c_xfers() */ int (*priv_xfers)(struct i3c_dev_desc *dev, struct i3c_priv_xfer *xfers, int nxfers); + int (*i3c_xfers)(struct i3c_dev_desc *dev, + struct i3c_xfer *xfers, + int nxfers, enum i3c_xfer_mode mode); int (*attach_i2c_dev)(struct i2c_dev_desc *dev); void (*detach_i2c_dev)(struct i2c_dev_desc *dev); int (*i2c_xfers)(struct i2c_dev_desc *dev, From 04f984e2329f3cb6b8ccf67becec393d588200f3 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 04:47:47 -0400 Subject: [PATCH 39/88] i3c: Switch to use new i3c_xfer from i3c_priv_xfer JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 9280b6ebbf08e53734d34f3bb325c37cddc1422d Author: Frank Li Date: Thu, 6 Nov 2025 12:36:01 -0500 i3c: Switch to use new i3c_xfer from i3c_priv_xfer Switch to use i3c_xfer instead of i3c_priv_xfer because framework update to support HDR mode. i3c_priv_xfer is now an alias of i3c_xfer. Signed-off-by: Frank Li Link: https://patch.msgid.link/20251106-i3c_ddr-v11-2-33a6a66ed095@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- include/linux/i3c/device.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h index 2daa0878257d..c6daac1bb154 100644 --- a/include/linux/i3c/device.h +++ b/include/linux/i3c/device.h @@ -27,7 +27,7 @@ * These are the standard error codes as defined by the I3C specification. * When -EIO is returned by the i3c_device_do_priv_xfers() or * i3c_device_send_hdr_cmds() one can check the error code in - * &struct_i3c_priv_xfer.err or &struct i3c_hdr_cmd.err to get a better idea of + * &struct_i3c_xfer.err or &struct i3c_hdr_cmd.err to get a better idea of * what went wrong. * */ @@ -312,7 +312,7 @@ 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_priv_xfer *xfers, + struct i3c_xfer *xfers, int nxfers) { return i3c_device_do_xfers(dev, xfers, nxfers, I3C_SDR); From 4f70f149a4ff4ae15b091379d9f9d6bb476ea086 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 04:40:03 -0400 Subject: [PATCH 40/88] i3c: master: svc: Replace bool rnw with union for HDR support JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 108420fe2100b0c9cfb2d3598681eab3724247b9 Author: Frank Li Date: Thu, 6 Nov 2025 12:36:02 -0500 i3c: master: svc: Replace bool rnw with union for HDR support Replace the bool rnw field with a union in preparation for adding HDR support. HDR uses a cmd field instead of the rnw bit to indicate read or write direction. Add helper function svc_cmd_is_read() to check transfer direction. Add a local variable 'rnw' in svc_i3c_master_priv_xfers() to avoid repeatedly accessing xfers[i].rnw. No functional change. Signed-off-by: Frank Li Link: https://patch.msgid.link/20251106-i3c_ddr-v11-3-33a6a66ed095@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/svc-i3c-master.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index e640453a7d8a..ff000e48b2ba 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -135,7 +135,11 @@ struct svc_i3c_cmd { u8 addr; - bool rnw; + union { + bool rnw; + u8 cmd; + u32 rnw_cmd; + }; u8 *in; const void *out; unsigned int len; @@ -337,6 +341,11 @@ svc_i3c_master_dev_from_addr(struct svc_i3c_master *master, return master->descs[i]; } +static bool svc_cmd_is_read(u32 rnw_cmd, u32 type) +{ + return rnw_cmd; +} + static void svc_i3c_master_emit_stop(struct svc_i3c_master *master) { writel(SVC_I3C_MCTRL_REQUEST_STOP, master->regs + SVC_I3C_MCTRL); @@ -1137,10 +1146,11 @@ static int svc_i3c_master_write(struct svc_i3c_master *master, } static int svc_i3c_master_xfer(struct svc_i3c_master *master, - bool rnw, unsigned int xfer_type, u8 addr, + u32 rnw_cmd, unsigned int xfer_type, u8 addr, u8 *in, const u8 *out, unsigned int xfer_len, unsigned int *actual_len, bool continued, bool repeat_start) { + bool rnw = svc_cmd_is_read(rnw_cmd, xfer_type); int retry = repeat_start ? 1 : 2; u32 reg; int ret; @@ -1302,7 +1312,7 @@ static void svc_i3c_master_start_xfer_locked(struct svc_i3c_master *master) for (i = 0; i < xfer->ncmds; i++) { struct svc_i3c_cmd *cmd = &xfer->cmds[i]; - ret = svc_i3c_master_xfer(master, cmd->rnw, xfer->type, + ret = svc_i3c_master_xfer(master, cmd->rnw_cmd, xfer->type, cmd->addr, cmd->in, cmd->out, cmd->len, &cmd->actual_len, cmd->continued, i > 0); @@ -1496,14 +1506,15 @@ static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev, for (i = 0; i < nxfers; i++) { struct svc_i3c_cmd *cmd = &xfer->cmds[i]; + bool rnw = xfers[i].rnw; cmd->xfer = &xfers[i]; cmd->addr = master->addrs[data->index]; - cmd->rnw = xfers[i].rnw; - cmd->in = xfers[i].rnw ? xfers[i].data.in : NULL; - cmd->out = xfers[i].rnw ? NULL : xfers[i].data.out; + cmd->rnw = rnw; + cmd->in = rnw ? xfers[i].data.in : NULL; + cmd->out = rnw ? NULL : xfers[i].data.out; cmd->len = xfers[i].len; - cmd->actual_len = xfers[i].rnw ? xfers[i].len : 0; + cmd->actual_len = rnw ? xfers[i].len : 0; cmd->continued = (i + 1) < nxfers; } From c8c9499dbd30637cbc29e81d347111e971d6c92c Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 04:40:12 -0400 Subject: [PATCH 41/88] i3c: master: svc: Add basic HDR mode support JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 4e7263b87ca362825beeac669dcbe24aae2c6257 Author: Frank Li Date: Thu, 6 Nov 2025 12:36:03 -0500 i3c: master: svc: Add basic HDR mode support Add basic HDR mode support for the svs I3C master driver. Only support for private transfers and does not support sending CCC commands in HDR mode. Key differences: - HDR uses commands (0x00-0x7F for write, 0x80-0xFF for read) to distinguish transfer direction. - HDR read/write commands must be written to FIFO before issuing the I3C address command. The hardware automatically sends the standard CCC command to enter HDR mode. - HDR exit pattern must be sent instead of send a stop after transfer completion. - Read/write data size must be an even number. Co-developed-by: Carlos Song Signed-off-by: Carlos Song Signed-off-by: Frank Li Link: https://patch.msgid.link/20251106-i3c_ddr-v11-4-33a6a66ed095@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/svc-i3c-master.c | 96 +++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 13 deletions(-) diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index ff000e48b2ba..1a7a4e7e52ac 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -39,11 +39,13 @@ #define SVC_I3C_MCTRL_REQUEST_NONE 0 #define SVC_I3C_MCTRL_REQUEST_START_ADDR 1 #define SVC_I3C_MCTRL_REQUEST_STOP 2 +#define SVC_I3C_MCTRL_REQUEST_FORCE_EXIT 6 #define SVC_I3C_MCTRL_REQUEST_IBI_ACKNACK 3 #define SVC_I3C_MCTRL_REQUEST_PROC_DAA 4 #define SVC_I3C_MCTRL_REQUEST_AUTO_IBI 7 #define SVC_I3C_MCTRL_TYPE_I3C 0 #define SVC_I3C_MCTRL_TYPE_I2C BIT(4) +#define SVC_I3C_MCTRL_TYPE_DDR BIT(5) #define SVC_I3C_MCTRL_IBIRESP_AUTO 0 #define SVC_I3C_MCTRL_IBIRESP_ACK_WITHOUT_BYTE 0 #define SVC_I3C_MCTRL_IBIRESP_ACK_WITH_BYTE BIT(7) @@ -93,6 +95,7 @@ #define SVC_I3C_MINTMASKED 0x098 #define SVC_I3C_MERRWARN 0x09C #define SVC_I3C_MERRWARN_NACK BIT(2) +#define SVC_I3C_MERRWARN_CRC BIT(10) #define SVC_I3C_MERRWARN_TIMEOUT BIT(20) #define SVC_I3C_MDMACTRL 0x0A0 #define SVC_I3C_MDATACTRL 0x0AC @@ -144,7 +147,7 @@ struct svc_i3c_cmd { const void *out; unsigned int len; unsigned int actual_len; - struct i3c_priv_xfer *xfer; + struct i3c_xfer *xfer; bool continued; }; @@ -343,7 +346,32 @@ svc_i3c_master_dev_from_addr(struct svc_i3c_master *master, static bool svc_cmd_is_read(u32 rnw_cmd, u32 type) { - return rnw_cmd; + return (type == SVC_I3C_MCTRL_TYPE_DDR) ? (rnw_cmd & 0x80) : rnw_cmd; +} + +static void svc_i3c_master_emit_force_exit(struct svc_i3c_master *master) +{ + u32 reg; + + writel(SVC_I3C_MCTRL_REQUEST_FORCE_EXIT, master->regs + SVC_I3C_MCTRL); + + /* + * Not need check error here because it is never happen at hardware. + * IP just wait for few fclk cycle to complete DDR exit pattern. Even + * though fclk stop, timeout happen here, the whole data actually + * already finish transfer. The next command will be timeout because + * wrong hardware state. + */ + readl_poll_timeout_atomic(master->regs + SVC_I3C_MSTATUS, reg, + SVC_I3C_MSTATUS_MCTRLDONE(reg), 0, 1000); + + /* + * This delay is necessary after the emission of a stop, otherwise eg. + * repeating IBIs do not get detected. There is a note in the manual + * about it, stating that the stop condition might not be settled + * correctly if a start condition follows too rapidly. + */ + udelay(1); } static void svc_i3c_master_emit_stop(struct svc_i3c_master *master) @@ -435,7 +463,7 @@ static void svc_i3c_master_ibi_work(struct work_struct *work) * cycle, leading to missed client IBI handlers. * * A typical scenario is when IBIWON occurs and bus arbitration is lost - * at svc_i3c_master_priv_xfers(). + * at svc_i3c_master_i3c_xfers(). * * Clear SVC_I3C_MINT_IBIWON before sending SVC_I3C_MCTRL_REQUEST_AUTO_IBI. */ @@ -691,6 +719,8 @@ static int svc_i3c_master_bus_init(struct i3c_master_controller *m) info.dyn_addr = ret; + info.hdr_cap = I3C_CCC_HDR_MODE(I3C_HDR_DDR); + writel(SVC_MDYNADDR_VALID | SVC_MDYNADDR_ADDR(info.dyn_addr), master->regs + SVC_I3C_MDYNADDR); @@ -1158,6 +1188,16 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master, /* clean SVC_I3C_MINT_IBIWON w1c bits */ writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS); + if (xfer_type == SVC_I3C_MCTRL_TYPE_DDR) { + /* DDR command need prefill into FIFO */ + writel(rnw_cmd, master->regs + SVC_I3C_MWDATAB); + if (!rnw) { + /* write data also need prefill into FIFO */ + ret = svc_i3c_master_write(master, out, xfer_len); + if (ret) + goto emit_stop; + } + } while (retry--) { writel(SVC_I3C_MCTRL_REQUEST_START_ADDR | @@ -1226,7 +1266,7 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master, if (rnw) ret = svc_i3c_master_read(master, in, xfer_len); - else + else if (xfer_type != SVC_I3C_MCTRL_TYPE_DDR) ret = svc_i3c_master_write(master, out, xfer_len); if (ret < 0) goto emit_stop; @@ -1239,10 +1279,19 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master, if (ret) goto emit_stop; + if (xfer_type == SVC_I3C_MCTRL_TYPE_DDR && + (readl(master->regs + SVC_I3C_MERRWARN) & SVC_I3C_MERRWARN_CRC)) { + ret = -ENXIO; + goto emit_stop; + } + writel(SVC_I3C_MINT_COMPLETE, master->regs + SVC_I3C_MSTATUS); if (!continued) { - svc_i3c_master_emit_stop(master); + if (xfer_type != SVC_I3C_MCTRL_TYPE_DDR) + svc_i3c_master_emit_stop(master); + else + svc_i3c_master_emit_force_exit(master); /* Wait idle if stop is sent. */ readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg, @@ -1252,7 +1301,11 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master, return 0; emit_stop: - svc_i3c_master_emit_stop(master); + if (xfer_type != SVC_I3C_MCTRL_TYPE_DDR) + svc_i3c_master_emit_stop(master); + else + svc_i3c_master_emit_force_exit(master); + svc_i3c_master_clear_merrwarn(master); return ret; @@ -1298,6 +1351,11 @@ static void svc_i3c_master_dequeue_xfer(struct svc_i3c_master *master, spin_unlock_irqrestore(&master->xferqueue.lock, flags); } +static int i3c_mode_to_svc_type(enum i3c_xfer_mode mode) +{ + return (mode == I3C_SDR) ? SVC_I3C_MCTRL_TYPE_I3C : SVC_I3C_MCTRL_TYPE_DDR; +} + static void svc_i3c_master_start_xfer_locked(struct svc_i3c_master *master) { struct svc_i3c_xfer *xfer = master->xferqueue.cur; @@ -1488,9 +1546,8 @@ static int svc_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, return ret; } -static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev, - struct i3c_priv_xfer *xfers, - int nxfers) +static int svc_i3c_master_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *xfers, + int nxfers, enum i3c_xfer_mode mode) { struct i3c_master_controller *m = i3c_dev_get_master(dev); struct svc_i3c_master *master = to_svc_i3c_master(m); @@ -1498,19 +1555,32 @@ static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev, struct svc_i3c_xfer *xfer; int ret, i; + if (mode != I3C_SDR) { + /* + * Only support data size less than FIFO SIZE when using DDR + * mode. First entry is cmd in FIFO, so actual available FIFO + * for data is SVC_I3C_FIFO_SIZE - 2 since DDR only supports + * even length. + */ + for (i = 0; i < nxfers; i++) + if (xfers[i].len > SVC_I3C_FIFO_SIZE - 2) + return -EINVAL; + } + xfer = svc_i3c_master_alloc_xfer(master, nxfers); if (!xfer) return -ENOMEM; - xfer->type = SVC_I3C_MCTRL_TYPE_I3C; + xfer->type = i3c_mode_to_svc_type(mode); for (i = 0; i < nxfers; i++) { + u32 rnw_cmd = (mode == I3C_SDR) ? xfers[i].rnw : xfers[i].cmd; + bool rnw = svc_cmd_is_read(rnw_cmd, xfer->type); struct svc_i3c_cmd *cmd = &xfer->cmds[i]; - bool rnw = xfers[i].rnw; cmd->xfer = &xfers[i]; cmd->addr = master->addrs[data->index]; - cmd->rnw = rnw; + cmd->rnw_cmd = rnw_cmd; cmd->in = rnw ? xfers[i].data.in : NULL; cmd->out = rnw ? NULL : xfers[i].data.out; cmd->len = xfers[i].len; @@ -1711,7 +1781,7 @@ static const struct i3c_master_controller_ops svc_i3c_master_ops = { .do_daa = svc_i3c_master_do_daa, .supports_ccc_cmd = svc_i3c_master_supports_ccc_cmd, .send_ccc_cmd = svc_i3c_master_send_ccc_cmd, - .priv_xfers = svc_i3c_master_priv_xfers, + .i3c_xfers = svc_i3c_master_i3c_xfers, .i2c_xfers = svc_i3c_master_i2c_xfers, .request_ibi = svc_i3c_master_request_ibi, .free_ibi = svc_i3c_master_free_ibi, From 31a4ced7575251c37379436d302ec0fb07485b09 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 13:05:49 -0400 Subject: [PATCH 42/88] i3c: document i3c_xfers JIRA: https://issues.redhat.com/browse/RHEL-143335 commit e01a8baf60af43f6f87a5850dee29cf31377ec25 Author: Alexandre Belloni Date: Tue, 2 Dec 2025 16:38:03 +0100 i3c: document i3c_xfers i3c_xfers was left undocumented, document it. Reported-by: Stephen Rothwell Link: https://patch.msgid.link/20251202153804.2640623-2-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- include/linux/i3c/master.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index d0d5b3a9049f..2fd850f4678b 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -418,7 +418,11 @@ struct i3c_bus { * @send_ccc_cmd: send a CCC command * This method is mandatory. * @priv_xfers: do one or several private I3C SDR transfers - * This method is mandatory. + * This method is mandatory when i3c_xfers is not implemented. It + * is deprecated. + * @i3c_xfers: do one or several I3C SDR or HDR transfers + * This method is mandatory when priv_xfers is not implemented but + * should be implemented instead of priv_xfers. * @attach_i2c_dev: called every time an I2C device is attached to the bus. * This is a good place to attach master controller specific * data to I2C devices. From 702b603e183912c6ddae3d638198c80b084bcfeb Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 04:21:41 -0400 Subject: [PATCH 43/88] i3c: master: switch to use new callback .i3c_xfers() from .priv_xfers() JIRA: https://issues.redhat.com/browse/RHEL-143335 Conflicts: i3c/master/adi-i3c-master.c i3c/master/renesas-i3c.c Dropped hunks for these files which do not exist in this tree. commit eaa0870387f9ecbd2992ca58e73f3c7768815e8b Author: Frank Li Date: Wed, 3 Dec 2025 15:45:50 -0500 i3c: master: switch to use new callback .i3c_xfers() from .priv_xfers() Switch to use the new .i3c_xfers() callback, which supports all I3C transfer modes (SDR and HDR). Also replace struct i3c_priv_xfer with the new struct i3c_xfer, as i3c_priv_xfer is now an alias of i3c_xfer. No functional changes. Signed-off-by: Frank Li Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20251203-i3c_xfer_cleanup_master-v2-1-7dd94d04ee2d@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 8 ++++---- drivers/i3c/master/i3c-master-cdns.c | 8 ++++---- drivers/i3c/master/mipi-i3c-hci/core.c | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index b6de0312d0e6..5b55174aa8e2 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -910,9 +910,9 @@ rpm_out: return ret; } -static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev, - struct i3c_priv_xfer *i3c_xfers, - int i3c_nxfers) +static int dw_i3c_master_i3c_xfers(struct i3c_dev_desc *dev, + struct i3c_xfer *i3c_xfers, + int i3c_nxfers, enum i3c_xfer_mode mode) { struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); struct i3c_master_controller *m = i3c_dev_get_master(dev); @@ -1506,7 +1506,7 @@ static const struct i3c_master_controller_ops dw_mipi_i3c_ops = { .do_daa = dw_i3c_master_daa, .supports_ccc_cmd = dw_i3c_master_supports_ccc_cmd, .send_ccc_cmd = dw_i3c_master_send_ccc_cmd, - .priv_xfers = dw_i3c_master_priv_xfers, + .i3c_xfers = dw_i3c_master_i3c_xfers, .attach_i2c_dev = dw_i3c_master_attach_i2c_dev, .detach_i2c_dev = dw_i3c_master_detach_i2c_dev, .i2c_xfers = dw_i3c_master_i2c_xfers, diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c index 55d527ed95d2..3993019f5bc4 100644 --- a/drivers/i3c/master/i3c-master-cdns.c +++ b/drivers/i3c/master/i3c-master-cdns.c @@ -721,9 +721,9 @@ static int cdns_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, return ret; } -static int cdns_i3c_master_priv_xfers(struct i3c_dev_desc *dev, - struct i3c_priv_xfer *xfers, - int nxfers) +static int cdns_i3c_master_i3c_xfers(struct i3c_dev_desc *dev, + struct i3c_xfer *xfers, + int nxfers, enum i3c_xfer_mode mode) { struct i3c_master_controller *m = i3c_dev_get_master(dev); struct cdns_i3c_master *master = to_cdns_i3c_master(m); @@ -1521,7 +1521,7 @@ static const struct i3c_master_controller_ops cdns_i3c_master_ops = { .detach_i2c_dev = cdns_i3c_master_detach_i2c_dev, .supports_ccc_cmd = cdns_i3c_master_supports_ccc_cmd, .send_ccc_cmd = cdns_i3c_master_send_ccc_cmd, - .priv_xfers = cdns_i3c_master_priv_xfers, + .i3c_xfers = cdns_i3c_master_i3c_xfers, .i2c_xfers = cdns_i3c_master_i2c_xfers, .enable_ibi = cdns_i3c_master_enable_ibi, .disable_ibi = cdns_i3c_master_disable_ibi, diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index ca6a785e12af..c9b5482a2605 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -274,9 +274,9 @@ static int i3c_hci_daa(struct i3c_master_controller *m) return hci->cmd->perform_daa(hci); } -static int i3c_hci_priv_xfers(struct i3c_dev_desc *dev, - struct i3c_priv_xfer *i3c_xfers, - int nxfers) +static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev, + struct i3c_xfer *i3c_xfers, int nxfers, + enum i3c_xfer_mode mode) { struct i3c_master_controller *m = i3c_dev_get_master(dev); struct i3c_hci *hci = to_i3c_hci(m); @@ -536,7 +536,7 @@ static const struct i3c_master_controller_ops i3c_hci_ops = { .bus_cleanup = i3c_hci_bus_cleanup, .do_daa = i3c_hci_daa, .send_ccc_cmd = i3c_hci_send_ccc_cmd, - .priv_xfers = i3c_hci_priv_xfers, + .i3c_xfers = i3c_hci_i3c_xfers, .i2c_xfers = i3c_hci_i2c_xfers, .attach_i3c_dev = i3c_hci_attach_i3c_dev, .reattach_i3c_dev = i3c_hci_reattach_i3c_dev, From 29fd4b13f14bcd237e964976e611c71660a2fbf6 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 13:06:25 -0400 Subject: [PATCH 44/88] i3c: master: cleanup callback .priv_xfers() JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 41b80d43d9a00a302b5559baa7ebafc28dd54793 Author: Frank Li Date: Wed, 3 Dec 2025 15:45:51 -0500 i3c: master: cleanup callback .priv_xfers() Remove the .priv_xfers() callback from the framework after all master controller drivers have switched to use the new .i3c_xfers() callback. Signed-off-by: Frank Li Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20251203-i3c_xfer_cleanup_master-v2-2-7dd94d04ee2d@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 14 ++------------ include/linux/i3c/master.h | 12 ++---------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 3814c9b76d8d..715687f9e957 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2816,14 +2816,10 @@ EXPORT_SYMBOL_GPL(i3c_generic_ibi_recycle_slot); static int i3c_master_check_ops(const struct i3c_master_controller_ops *ops) { - if (!ops || !ops->bus_init || + if (!ops || !ops->bus_init || !ops->i3c_xfers || !ops->send_ccc_cmd || !ops->do_daa || !ops->i2c_xfers) return -EINVAL; - /* Must provide one of priv_xfers (SDR only) or i3c_xfers (all modes) */ - if (!ops->priv_xfers && !ops->i3c_xfers) - return -EINVAL; - if (ops->request_ibi && (!ops->enable_ibi || !ops->disable_ibi || !ops->free_ibi || !ops->recycle_ibi_slot)) @@ -3029,13 +3025,7 @@ int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, struct i3c_xfer *xfers, if (mode != I3C_SDR && !(master->this->info.hdr_cap & BIT(mode))) return -EOPNOTSUPP; - if (master->ops->i3c_xfers) - return master->ops->i3c_xfers(dev, xfers, nxfers, mode); - - if (mode != I3C_SDR) - return -EINVAL; - - return master->ops->priv_xfers(dev, xfers, nxfers); + return master->ops->i3c_xfers(dev, xfers, nxfers, mode); } int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev) diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 2fd850f4678b..58d01ed4cce7 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -417,12 +417,8 @@ struct i3c_bus { * all CCC commands are supported. * @send_ccc_cmd: send a CCC command * This method is mandatory. - * @priv_xfers: do one or several private I3C SDR transfers - * This method is mandatory when i3c_xfers is not implemented. It - * is deprecated. - * @i3c_xfers: do one or several I3C SDR or HDR transfers - * This method is mandatory when priv_xfers is not implemented but - * should be implemented instead of priv_xfers. + * @i3c_xfers: do one or several I3C SDR or HDR transfers. + * This method is mandatory. * @attach_i2c_dev: called every time an I2C device is attached to the bus. * This is a good place to attach master controller specific * data to I2C devices. @@ -478,10 +474,6 @@ struct i3c_master_controller_ops { const struct i3c_ccc_cmd *cmd); int (*send_ccc_cmd)(struct i3c_master_controller *master, struct i3c_ccc_cmd *cmd); - /* Deprecated, please use i3c_xfers() */ - int (*priv_xfers)(struct i3c_dev_desc *dev, - struct i3c_priv_xfer *xfers, - int nxfers); int (*i3c_xfers)(struct i3c_dev_desc *dev, struct i3c_xfer *xfers, int nxfers, enum i3c_xfer_mode mode); From 50bd4f5a6c1bdc89b9aaef08d6b838a1a36f2baf Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 13:07:55 -0400 Subject: [PATCH 45/88] i3c: master: Fix confusing cleanup.h syntax JIRA: https://issues.redhat.com/browse/RHEL-143335 commit cc3b18f9fedec517e35b973d14670a37290f133c Author: Krzysztof Kozlowski Date: Mon, 8 Dec 2025 03:07:51 +0100 i3c: master: Fix confusing cleanup.h syntax Initializing automatic __free variables to NULL without need (e.g. branches with different allocations), followed by actual allocation is in contrary to explicit coding rules guiding cleanup.h: "Given that the "__free(...) = NULL" pattern for variables defined at the top of the function poses this potential interdependency problem the recommendation is to always define and assign variables in one statement and not group variable definitions at the top of the function when __free() is used." Code does not have a bug, but is less readable and uses discouraged coding practice, so fix that by moving declaration to the place of assignment. Not that other existing usage of __free() in this context is a corret exception initialized to NULL, because the actual allocation is branched in if(). Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251208020750.4727-3-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 715687f9e957..5cc0a84e985d 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1742,11 +1742,10 @@ EXPORT_SYMBOL_GPL(i3c_master_do_daa); struct i3c_dma *i3c_master_dma_map_single(struct device *dev, void *buf, size_t len, bool force_bounce, enum dma_data_direction dir) { - struct i3c_dma *dma_xfer __free(kfree) = NULL; void *bounce __free(kfree) = NULL; void *dma_buf = buf; - dma_xfer = kzalloc(sizeof(*dma_xfer), GFP_KERNEL); + struct i3c_dma *dma_xfer __free(kfree) = kzalloc(sizeof(*dma_xfer), GFP_KERNEL); if (!dma_xfer) return NULL; From fb0d99a9eae9a56cfa650c67e9265bd55390fd86 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 13:08:19 -0400 Subject: [PATCH 46/88] i3c: add sysfs entry and attribute for Device NACK Retry count JIRA: https://issues.redhat.com/browse/RHEL-143335 commit b58f47eb392680d4c6626c8b3b1fcf6412a0a02c Author: Adrian Ng Ho Yin Date: Fri, 12 Dec 2025 17:02:55 +0800 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 Signed-off-by: Jennifer Berringer --- 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 5cc0a84e985d..3bc8b4abf8af 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, @@ -2957,6 +2990,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: @@ -2982,6 +3018,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 c242064720e97915c99814ee3b2d87ba4b983582 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:58:22 -0400 Subject: [PATCH 47/88] i3c: dw: Add support for Device NACK Retry configuration JIRA: https://issues.redhat.com/browse/RHEL-143335 commit ec17f14309481318df4af2a0c7f2aa7da9e7ebcb Author: Adrian Ng Ho Yin Date: Fri, 12 Dec 2025 17:02:56 +0800 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 Signed-off-by: Jennifer Berringer --- 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 5b55174aa8e2..8bbe57350bb7 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)) @@ -1497,6 +1502,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, @@ -1517,6 +1556,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 c982ae7de5ed971cc839950fae4621411e91891d Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:19:30 -0400 Subject: [PATCH 48/88] i3c: dw: use FIELD_PREP for device address table macros JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 4cd9d2bf0b56f98347ca1046e4d8acea95bd7ffa Author: Adrian Ng Ho Yin Date: Fri, 12 Dec 2025 17:02:57 +0800 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 Signed-off-by: Jennifer Berringer --- 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 8bbe57350bb7..6f0ebd4a7089 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 a175010574d71731a5e12b82e246e2902ad2f2ae Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:20:26 -0400 Subject: [PATCH 49/88] i3c: dw: Preserve DAT entry bits when restoring addresses JIRA: https://issues.redhat.com/browse/RHEL-143335 commit de28e002df2e2d44138174a158883e703e216a06 Author: Adrian Ng Ho Yin Date: Fri, 12 Dec 2025 17:02:58 +0800 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 Signed-off-by: Jennifer Berringer --- 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 6f0ebd4a7089..6bd9de67d976 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1728,11 +1728,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 864bdacd8e320e2f9a052244eea7291a05f0e925 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 13:09:23 -0400 Subject: [PATCH 50/88] i3c: master: Update hot-join flag only on success JIRA: https://issues.redhat.com/browse/RHEL-143335 commit f0775157b9f9a28ae3eabc8d05b0bc52e8056c80 Author: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:44 +0200 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 Signed-off-by: Jennifer Berringer --- 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 70668ca0ac128c9075c4e30e8539178c2564b713 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 13:10:28 -0400 Subject: [PATCH 51/88] i3c: master: Replace WARN_ON() with dev_err() in i3c_dev_free_ibi_locked() JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 471895799c2f46688792e175ced936ffeb6cdf01 Author: Adrian Hunter Date: Tue, 13 Jan 2026 09:26:45 +0200 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 Signed-off-by: Jennifer Berringer --- 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 bc2a536e8d983b48689779a9b8c22e19fe2d7001 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:08:34 -0400 Subject: [PATCH 52/88] i3c: master: Introduce optional Runtime PM support JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 990c149c61ee45da4fb6372e6b2fdd9808414e7a Author: Adrian Hunter Date: Tue, 13 Jan 2026 09:27:00 +0200 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 Signed-off-by: Jennifer Berringer --- 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 0a919d137db6..b57d2bf71a7b 100644 --- a/drivers/i3c/device.c +++ b/drivers/i3c/device.c @@ -45,10 +45,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); @@ -65,10 +71,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); @@ -105,16 +117,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); @@ -134,16 +157,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); @@ -162,19 +194,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 c773476873c4b0184ffefecc7190a433e182aca9 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:21:02 -0400 Subject: [PATCH 53/88] i3c: dw: Initialize spinlock to avoid upsetting lockdep JIRA: https://issues.redhat.com/browse/RHEL-143335 commit b58eaa4761ab02fc38c39d674a6bcdd55e00f388 Author: Fredrik Markstrom Date: Fri, 16 Jan 2026 15:29:42 +0100 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 Signed-off-by: Jennifer Berringer --- 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 6bd9de67d976..69fbdae0fcdf 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1620,6 +1620,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 b78d21f05755dfa5ba0536897fa78f89125fc55a Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:10:29 -0400 Subject: [PATCH 54/88] i3c: master: Add i3c_master_do_daa_ext() for post-hibernation address recovery JIRA: https://issues.redhat.com/browse/RHEL-143335 commit c481ef12e713fb7c292d04f53b3532ac0804ab3d Author: Adrian Hunter Date: Fri, 23 Jan 2026 08:33:23 +0200 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 Signed-off-by: Jennifer Berringer --- 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 ffb1df8fae2a3d0557aa7ccb5a7eae1b6117de5e Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:21:58 -0400 Subject: [PATCH 55/88] i3c: dw: Fix memory leak in dw_i3c_master_i2c_xfers() JIRA: https://issues.redhat.com/browse/RHEL-143335 CVE: CVE-2026-45863 commit 2537089413514caaa9a5fdeeac3a34d45100f747 Author: Zilin Guan Date: Mon, 26 Jan 2026 08:11:21 +0000 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 Signed-off-by: Jennifer Berringer --- 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 69fbdae0fcdf..4d301a792be3 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1114,6 +1114,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 57affae68b54aff6520451902f3a3669957285a5 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:22:16 -0400 Subject: [PATCH 56/88] i3c: dw-i3c-master: convert spinlock usage to scoped guards JIRA: https://issues.redhat.com/browse/RHEL-143335 commit c7311aa4a71ebbf6e86e3173e04afbad233a3937 Author: Adrian Ng Ho Yin Date: Tue, 27 Jan 2026 10:05:06 +0800 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 Signed-off-by: Jennifer Berringer --- 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 4d301a792be3..a67516be0813 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -424,17 +424,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, @@ -459,11 +456,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) @@ -1203,15 +1197,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; } @@ -1221,11 +1213,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; @@ -1252,13 +1243,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; @@ -1283,9 +1273,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) @@ -1386,7 +1373,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; @@ -1404,7 +1390,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, @@ -1440,14 +1426,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 09b9f6923530ba2cb8225713ac8a5898419a9008 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:24:04 -0400 Subject: [PATCH 57/88] i3c: dw-i3c-master: fix SIR reject bit mapping for dynamic addresses JIRA: https://issues.redhat.com/browse/RHEL-143335 commit ed318b3fb4ab317c533d38d160326fa5d7569497 Author: Adrian Ng Ho Yin Date: Tue, 27 Jan 2026 10:05:07 +0800 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 Signed-off-by: Jennifer Berringer --- 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 a67516be0813..00665d01c75d 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 @@ -272,6 +277,14 @@ static u8 even_parity(u8 p) return (0x9669 >> p) & 1; } +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) { @@ -1245,11 +1258,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) @@ -1262,11 +1283,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); From e2b9a5e7728b2ad9b874a8a9d9405643cad98eac Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:27:04 -0400 Subject: [PATCH 58/88] i3c: master: Replace kmalloc with kmalloc_obj for non-scalar types JIRA: https://issues.redhat.com/browse/RHEL-143335 The upstream commit is treewide but this is filtered to only include the hunks affecting i3c master. commit 69050f8d6d075dc01af7a5f2f550a8067510366f Author: Kees Cook Date: Fri Feb 20 23:49:23 2026 -0800 treewide: Replace kmalloc with kmalloc_obj for non-scalar types This is the result of running the Coccinelle script from scripts/coccinelle/api/kmalloc_objs.cocci. The script is designed to avoid scalar types (which need careful case-by-case checking), and instead replace kmalloc-family calls that allocate struct or union object instances: Single allocations: kmalloc(sizeof(TYPE), ...) are replaced with: kmalloc_obj(TYPE, ...) Array allocations: kmalloc_array(COUNT, sizeof(TYPE), ...) are replaced with: kmalloc_objs(TYPE, COUNT, ...) Flex array allocations: kmalloc(struct_size(PTR, FAM, COUNT), ...) are replaced with: kmalloc_flex(*PTR, FAM, COUNT, ...) (where TYPE may also be *VAR) The resulting allocations no longer return "void *", instead returning "TYPE *". Signed-off-by: Kees Cook Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 15 ++++++++------- drivers/i3c/master/dw-i3c-master.c | 6 +++--- drivers/i3c/master/i3c-master-cdns.c | 6 +++--- drivers/i3c/master/svc-i3c-master.c | 6 +++--- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 0eae19b3823d..161eab16d17e 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -858,7 +858,7 @@ i3c_master_alloc_i2c_dev(struct i3c_master_controller *master, { struct i2c_dev_desc *dev; - dev = kzalloc(sizeof(*dev), GFP_KERNEL); + dev = kzalloc_obj(*dev, GFP_KERNEL); if (!dev) return ERR_PTR(-ENOMEM); @@ -983,7 +983,7 @@ i3c_master_alloc_i3c_dev(struct i3c_master_controller *master, { struct i3c_dev_desc *dev; - dev = kzalloc(sizeof(*dev), GFP_KERNEL); + dev = kzalloc_obj(*dev, GFP_KERNEL); if (!dev) return ERR_PTR(-ENOMEM); @@ -1742,7 +1742,7 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) if (desc->dev || !desc->info.dyn_addr || desc == master->this) continue; - desc->dev = kzalloc(sizeof(*desc->dev), GFP_KERNEL); + desc->dev = kzalloc_obj(*desc->dev, GFP_KERNEL); if (!desc->dev) continue; @@ -1852,7 +1852,8 @@ struct i3c_dma *i3c_master_dma_map_single(struct device *dev, void *buf, void *bounce __free(kfree) = NULL; void *dma_buf = buf; - struct i3c_dma *dma_xfer __free(kfree) = kzalloc(sizeof(*dma_xfer), GFP_KERNEL); + struct i3c_dma *dma_xfer __free(kfree) = kzalloc_obj(*dma_xfer, + GFP_KERNEL); if (!dma_xfer) return NULL; @@ -2847,7 +2848,7 @@ i3c_generic_ibi_alloc_pool(struct i3c_dev_desc *dev, unsigned int i; int ret; - pool = kzalloc(sizeof(*pool), GFP_KERNEL); + pool = kzalloc_obj(*pool, GFP_KERNEL); if (!pool) return ERR_PTR(-ENOMEM); @@ -2855,7 +2856,7 @@ i3c_generic_ibi_alloc_pool(struct i3c_dev_desc *dev, INIT_LIST_HEAD(&pool->free_slots); INIT_LIST_HEAD(&pool->pending); - pool->slots = kcalloc(req->num_slots, sizeof(*slot), GFP_KERNEL); + pool->slots = kzalloc_objs(*slot, req->num_slots, GFP_KERNEL); if (!pool->slots) { ret = -ENOMEM; goto err_free_pool; @@ -3218,7 +3219,7 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, if (dev->ibi) return -EBUSY; - ibi = kzalloc(sizeof(*ibi), GFP_KERNEL); + ibi = kzalloc_obj(*ibi, GFP_KERNEL); if (!ibi) return -ENOMEM; diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 00665d01c75d..291f37cfb05b 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -390,7 +390,7 @@ dw_i3c_master_alloc_xfer(struct dw_i3c_master *master, unsigned int ncmds) { struct dw_i3c_xfer *xfer; - xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL); + xfer = kzalloc_flex(*xfer, cmds, ncmds, GFP_KERNEL); if (!xfer) return NULL; @@ -1052,7 +1052,7 @@ static int dw_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev) if (pos < 0) return pos; - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = kzalloc_obj(*data, GFP_KERNEL); if (!data) return -ENOMEM; @@ -1170,7 +1170,7 @@ static int dw_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev) if (pos < 0) return pos; - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = kzalloc_obj(*data, GFP_KERNEL); if (!data) return -ENOMEM; diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c index 3993019f5bc4..3bcb2862c278 100644 --- a/drivers/i3c/master/i3c-master-cdns.c +++ b/drivers/i3c/master/i3c-master-cdns.c @@ -499,7 +499,7 @@ cdns_i3c_master_alloc_xfer(struct cdns_i3c_master *master, unsigned int ncmds) { struct cdns_i3c_xfer *xfer; - xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL); + xfer = kzalloc_flex(*xfer, cmds, ncmds, GFP_KERNEL); if (!xfer) return NULL; @@ -942,7 +942,7 @@ static int cdns_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev) struct cdns_i3c_i2c_dev_data *data; int slot; - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = kzalloc_obj(*data, GFP_KERNEL); if (!data) return -ENOMEM; @@ -993,7 +993,7 @@ static int cdns_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev) if (slot < 0) return slot; - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = kzalloc_obj(*data, GFP_KERNEL); if (!data) return -ENOMEM; diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index 1a7a4e7e52ac..e100afbf4d98 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -786,7 +786,7 @@ static int svc_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev) if (slot < 0) return slot; - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = kzalloc_obj(*data, GFP_KERNEL); if (!data) { svc_i3c_master_release_slot(master, slot); return -ENOMEM; @@ -839,7 +839,7 @@ static int svc_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev) if (slot < 0) return slot; - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = kzalloc_obj(*data, GFP_KERNEL); if (!data) { svc_i3c_master_release_slot(master, slot); return -ENOMEM; @@ -1316,7 +1316,7 @@ svc_i3c_master_alloc_xfer(struct svc_i3c_master *master, unsigned int ncmds) { struct svc_i3c_xfer *xfer; - xfer = kzalloc(struct_size(xfer, cmds, ncmds), GFP_KERNEL); + xfer = kzalloc_flex(*xfer, cmds, ncmds, GFP_KERNEL); if (!xfer) return NULL; From 830222a7f9bb74e29c965c6dd44ac3b97cac825f Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:32:21 -0400 Subject: [PATCH 59/88] i3c: master: Convert 'alloc_obj' family to use the new default GFP_KERNEL argument JIRA: https://issues.redhat.com/browse/RHEL-143335 Filter the upstream treewide commit to only include the hunks affecting i3c master. commit bf4afc53b77aeaa48b5409da5c8da6bb4eff7f43 Author: Linus Torvalds Date: Sat Feb 21 16:37:42 2026 -0800 Convert 'alloc_obj' family to use the new default GFP_KERNEL argument This was done entirely with mindless brute force, using git grep -l '\ Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 12 ++++++------ drivers/i3c/master/dw-i3c-master.c | 4 ++-- drivers/i3c/master/i3c-master-cdns.c | 4 ++-- drivers/i3c/master/svc-i3c-master.c | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 161eab16d17e..9446a56d692f 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -858,7 +858,7 @@ i3c_master_alloc_i2c_dev(struct i3c_master_controller *master, { struct i2c_dev_desc *dev; - dev = kzalloc_obj(*dev, GFP_KERNEL); + dev = kzalloc_obj(*dev); if (!dev) return ERR_PTR(-ENOMEM); @@ -983,7 +983,7 @@ i3c_master_alloc_i3c_dev(struct i3c_master_controller *master, { struct i3c_dev_desc *dev; - dev = kzalloc_obj(*dev, GFP_KERNEL); + dev = kzalloc_obj(*dev); if (!dev) return ERR_PTR(-ENOMEM); @@ -1742,7 +1742,7 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) if (desc->dev || !desc->info.dyn_addr || desc == master->this) continue; - desc->dev = kzalloc_obj(*desc->dev, GFP_KERNEL); + desc->dev = kzalloc_obj(*desc->dev); if (!desc->dev) continue; @@ -2848,7 +2848,7 @@ i3c_generic_ibi_alloc_pool(struct i3c_dev_desc *dev, unsigned int i; int ret; - pool = kzalloc_obj(*pool, GFP_KERNEL); + pool = kzalloc_obj(*pool); if (!pool) return ERR_PTR(-ENOMEM); @@ -2856,7 +2856,7 @@ i3c_generic_ibi_alloc_pool(struct i3c_dev_desc *dev, INIT_LIST_HEAD(&pool->free_slots); INIT_LIST_HEAD(&pool->pending); - pool->slots = kzalloc_objs(*slot, req->num_slots, GFP_KERNEL); + pool->slots = kzalloc_objs(*slot, req->num_slots); if (!pool->slots) { ret = -ENOMEM; goto err_free_pool; @@ -3219,7 +3219,7 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, if (dev->ibi) return -EBUSY; - ibi = kzalloc_obj(*ibi, GFP_KERNEL); + ibi = kzalloc_obj(*ibi); if (!ibi) return -ENOMEM; diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 291f37cfb05b..f43c70a5b3f7 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1052,7 +1052,7 @@ static int dw_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev) if (pos < 0) return pos; - data = kzalloc_obj(*data, GFP_KERNEL); + data = kzalloc_obj(*data); if (!data) return -ENOMEM; @@ -1170,7 +1170,7 @@ static int dw_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev) if (pos < 0) return pos; - data = kzalloc_obj(*data, GFP_KERNEL); + data = kzalloc_obj(*data); if (!data) return -ENOMEM; diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c index 3bcb2862c278..8ed47399d2a1 100644 --- a/drivers/i3c/master/i3c-master-cdns.c +++ b/drivers/i3c/master/i3c-master-cdns.c @@ -942,7 +942,7 @@ static int cdns_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev) struct cdns_i3c_i2c_dev_data *data; int slot; - data = kzalloc_obj(*data, GFP_KERNEL); + data = kzalloc_obj(*data); if (!data) return -ENOMEM; @@ -993,7 +993,7 @@ static int cdns_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev) if (slot < 0) return slot; - data = kzalloc_obj(*data, GFP_KERNEL); + data = kzalloc_obj(*data); if (!data) return -ENOMEM; diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index e100afbf4d98..f04364affbd3 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -786,7 +786,7 @@ static int svc_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev) if (slot < 0) return slot; - data = kzalloc_obj(*data, GFP_KERNEL); + data = kzalloc_obj(*data); if (!data) { svc_i3c_master_release_slot(master, slot); return -ENOMEM; @@ -839,7 +839,7 @@ static int svc_i3c_master_attach_i2c_dev(struct i2c_dev_desc *dev) if (slot < 0) return slot; - data = kzalloc_obj(*data, GFP_KERNEL); + data = kzalloc_obj(*data); if (!data) { svc_i3c_master_release_slot(master, slot); return -ENOMEM; From 8cf75580b6df5d5635c59c92f2611ce621ad425c Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:36:04 -0400 Subject: [PATCH 60/88] i3c: master: Convert more 'alloc_obj' cases to default GFP_KERNEL arguments JIRA: https://issues.redhat.com/browse/RHEL-143335 Only the hunk affecting i3c/master.c is included. commit 32a92f8c89326985e05dce8b22d3f0aa07a3e1bd Author: Linus Torvalds Date: Sat Feb 21 20:03:00 2026 -0800 Convert more 'alloc_obj' cases to default GFP_KERNEL arguments This converts some of the visually simpler cases that have been split over multiple lines. I only did the ones that are easy to verify the resulting diff by having just that final GFP_KERNEL argument on the next line. Somebody should probably do a proper coccinelle script for this, but for me the trivial script actually resulted in an assertion failure in the middle of the script. I probably had made it a bit _too_ trivial. So after fighting that far a while I decided to just do some of the syntactically simpler cases with variations of the previous 'sed' scripts. The more syntactically complex multi-line cases would mostly really want whitespace cleanup anyway. Signed-off-by: Linus Torvalds Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 9446a56d692f..9e6be49bebb2 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1852,8 +1852,7 @@ struct i3c_dma *i3c_master_dma_map_single(struct device *dev, void *buf, void *bounce __free(kfree) = NULL; void *dma_buf = buf; - struct i3c_dma *dma_xfer __free(kfree) = kzalloc_obj(*dma_xfer, - GFP_KERNEL); + struct i3c_dma *dma_xfer __free(kfree) = kzalloc_obj(*dma_xfer); if (!dma_xfer) return NULL; From 60426be6a02c6f49c95f6b21ae6e64a45fe568b1 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:25:01 -0400 Subject: [PATCH 61/88] i3c: master: dw-i3c: Fix missing of_node for virtual I2C adapter JIRA: https://issues.redhat.com/browse/RHEL-143335 commit f26ecaa0f0abfe5db173416214098a00d3b7db79 Author: Peter Yin Date: Mon, 2 Mar 2026 15:56:42 +0800 i3c: master: dw-i3c: Fix missing of_node for virtual I2C adapter The DesignWare I3C master driver creates a virtual I2C adapter to provide backward compatibility with I2C devices. However, the current implementation does not associate this virtual adapter with any Device Tree node. Propagate the of_node from the I3C master platform device to the virtual I2C adapter's device structure. This ensures that standard I2C aliases are correctly resolved and bus numbering remains consistent. Signed-off-by: Peter Yin Reviewed-by: Frank Li Link: https://patch.msgid.link/20260302075645.1492766-1-peteryin.openbmc@gmail.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- 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 f43c70a5b3f7..f3f3e00abb25 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1667,6 +1667,8 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, pm_runtime_get_noresume(&pdev->dev); INIT_WORK(&master->hj_work, dw_i3c_hj_work); + + device_set_of_node_from_dev(&master->base.i2c.dev, &pdev->dev); ret = i3c_master_register(&master->base, &pdev->dev, &dw_mipi_i3c_ops, false); if (ret) From 4108bb5d7b08876edfc2d35423acaee0d4a896c7 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:25:32 -0400 Subject: [PATCH 62/88] i3c: dw-i3c-master: Set SIR_REJECT in DAT on device attach and reattach JIRA: https://issues.redhat.com/browse/RHEL-143335 commit f311a05784634febd299f03476b80f3f18489767 Author: Adrian Ng Ho Yin Date: Fri, 13 Feb 2026 14:00:48 +0800 i3c: dw-i3c-master: Set SIR_REJECT in DAT on device attach and reattach The DesignWare I3C master controller ACKs IBIs as soon as a valid Device Address Table (DAT) entry is present. This can create a race between device attachment (after DAA) and the point where the client driver enables IBIs via i3c_device_enable_ibi(). Set DEV_ADDR_TABLE_SIR_REJECT in the DAT entry during attach_i3c_dev() and reattach_i3c_dev() so that IBIs are rejected by default. The bit is managed thereafter by the existing dw_i3c_master_set_sir_enabled() function, which clears it in enable_ibi() after ENEC is issued, and restores it in disable_ibi() after DISEC. Fixes: 1dd728f5d4d4 ("i3c: master: Add driver for Synopsys DesignWare IP") Signed-off-by: Adrian Ng Ho Yin Reviewed-by: Frank Li Link: https://patch.msgid.link/53f5b8cbdd8af789ec38b95b02873f32f9182dd6.1770962368.git.adrianhoyin.ng@altera.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index f3f3e00abb25..471e0ed65c11 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1032,7 +1032,7 @@ static int dw_i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev, master->free_pos &= ~BIT(pos); } - writel(DEV_ADDR_TABLE_DYNAMIC_ADDR(dev->info.dyn_addr), + writel(DEV_ADDR_TABLE_DYNAMIC_ADDR(dev->info.dyn_addr) | DEV_ADDR_TABLE_SIR_REJECT, master->regs + DEV_ADDR_TABLE_LOC(master->datstartaddr, data->index)); @@ -1061,7 +1061,7 @@ static int dw_i3c_master_attach_i3c_dev(struct i3c_dev_desc *dev) master->free_pos &= ~BIT(pos); i3c_dev_set_master_data(dev, data); - writel(DEV_ADDR_TABLE_DYNAMIC_ADDR(master->devs[pos].addr), + writel(DEV_ADDR_TABLE_DYNAMIC_ADDR(master->devs[pos].addr) | DEV_ADDR_TABLE_SIR_REJECT, master->regs + DEV_ADDR_TABLE_LOC(master->datstartaddr, data->index)); From 80a35325e93163ecc490829f0c3c265603901a14 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:39:22 -0400 Subject: [PATCH 63/88] i3c: master: use kzalloc_flex JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 7f53c556c207600a9cd26798687f2df1c1c1dce2 Author: Rosen Penev Date: Wed, 11 Mar 2026 17:15:34 -0700 i3c: master: use kzalloc_flex Simplifies allocations by using a flexible array member in this struct. Add __counted_by to get extra runtime analysis. Signed-off-by: Rosen Penev Reviewed-by: Frank Li Link: https://patch.msgid.link/20260312001534.24423-1-rosenp@gmail.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 9e6be49bebb2..b62cb3ee69c1 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2793,10 +2793,10 @@ struct i3c_generic_ibi_slot { struct i3c_generic_ibi_pool { spinlock_t lock; unsigned int num_slots; - struct i3c_generic_ibi_slot *slots; void *payload_buf; struct list_head free_slots; struct list_head pending; + struct i3c_generic_ibi_slot slots[] __counted_by(num_slots); }; /** @@ -2824,7 +2824,6 @@ void i3c_generic_ibi_free_pool(struct i3c_generic_ibi_pool *pool) WARN_ON(nslots != pool->num_slots); kfree(pool->payload_buf); - kfree(pool->slots); kfree(pool); } EXPORT_SYMBOL_GPL(i3c_generic_ibi_free_pool); @@ -2847,20 +2846,16 @@ i3c_generic_ibi_alloc_pool(struct i3c_dev_desc *dev, unsigned int i; int ret; - pool = kzalloc_obj(*pool); + pool = kzalloc_flex(*pool, slots, req->num_slots); if (!pool) return ERR_PTR(-ENOMEM); + pool->num_slots = req->num_slots; + spin_lock_init(&pool->lock); INIT_LIST_HEAD(&pool->free_slots); INIT_LIST_HEAD(&pool->pending); - pool->slots = kzalloc_objs(*slot, req->num_slots); - if (!pool->slots) { - ret = -ENOMEM; - goto err_free_pool; - } - if (req->max_payload_len) { pool->payload_buf = kcalloc(req->num_slots, req->max_payload_len, GFP_KERNEL); @@ -2879,7 +2874,6 @@ i3c_generic_ibi_alloc_pool(struct i3c_dev_desc *dev, (i * req->max_payload_len); list_add_tail(&slot->node, &pool->free_slots); - pool->num_slots++; } return pool; From 29457622b821abd5496e1f508ed8d290878c4c9f Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:14:05 -0400 Subject: [PATCH 64/88] i3c: master: Add sysfs option to rescan bus via entdaa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 8ea0b60bc00d86b5ce33837487f4d16ae212f70a Author: David Nyström Date: Thu, 19 Feb 2026 21:58:03 +0100 i3c: master: Add sysfs option to rescan bus via entdaa Allow userspace to request dynamic address assignment, which is useful for i3cdev devices with broken hot-join support. This will assign dynamic addresses to all devices on the I3C bus which are currently unassigned. Signed-off-by: David Nyström Reviewed-by: Frank Li Reviewed-by: Meagan Lloyd Link: https://patch.msgid.link/20260219-i3c_rescan-v6-1-b81d6cc3cb30@est.tech Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- Documentation/ABI/testing/sysfs-bus-i3c | 20 ++++++++++++++++++ drivers/i3c/master.c | 27 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-i3c b/Documentation/ABI/testing/sysfs-bus-i3c index c1e048957a01..19f5cf8b1b11 100644 --- a/Documentation/ABI/testing/sysfs-bus-i3c +++ b/Documentation/ABI/testing/sysfs-bus-i3c @@ -172,3 +172,23 @@ Description: the automatic retries. Exist only when I3C constroller supports this retry on nack feature. +What: /sys/bus/i3c/devices/i3c-/do_daa +KernelVersion: 7.0 +Contact: linux-i3c@vger.kernel.org +Description: + Write-only attribute that triggers a Dynamic Address Assignment + (DAA) procedure which discovers new I3C devices on the bus. + Writing a boolean true value (1, y, yes, true, on) to this + attribute causes the master controller to perform DAA, which + includes broadcasting an ENTDAA (Enter Dynamic Address Assignment) + Common Command Code (CCC) on the bus. Writing a false value + returns -EINVAL. + + This is useful for discovering I3C devices that were not present + during initial bus initialization and are unable to issue + Hot-Join. Only devices without a currently assigned dynamic address + will respond to the ENTDAA broadcast and be assigned addresses. + + Note that this mechanism is distinct from Hot-Join, since this is + controller-initiated discovery, while Hot-Join is device-initiated + method to provoke controller discovery procedure. diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index b62cb3ee69c1..c32847bc4d0d 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -758,6 +758,32 @@ static ssize_t dev_nack_retry_count_store(struct device *dev, static DEVICE_ATTR_RW(dev_nack_retry_count); +static ssize_t do_daa_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i3c_master_controller *master = dev_to_i3cmaster(dev); + bool val; + int ret; + + if (kstrtobool(buf, &val)) + return -EINVAL; + + if (!val) + return -EINVAL; + + if (!master->init_done) + return -EAGAIN; + + ret = i3c_master_do_daa(master); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR_WO(do_daa); + static struct attribute *i3c_masterdev_attrs[] = { &dev_attr_mode.attr, &dev_attr_current_master.attr, @@ -769,6 +795,7 @@ static struct attribute *i3c_masterdev_attrs[] = { &dev_attr_dynamic_address.attr, &dev_attr_hdrcap.attr, &dev_attr_hotjoin.attr, + &dev_attr_do_daa.attr, NULL, }; ATTRIBUTE_GROUPS(i3c_masterdev); From 1edb2c8fecc2eb8d1b7573e82510c17a5e8666fe Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:39:48 -0400 Subject: [PATCH 65/88] i3c: fix missing newline in dev_err messages JIRA: https://issues.redhat.com/browse/RHEL-143335 commit acfcdff920dcab2b71536f04684e92c7933f7f40 Author: haoyu.lu Date: Tue, 17 Mar 2026 11:40:15 +0800 i3c: fix missing newline in dev_err messages Add missing newline to dev_err messages in: - drivers/i3c/master.c - drivers/i3c/master/svc-i3c-master.c Signed-off-by: haoyu.lu Reviewed-by: Frank Li Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260317034015.638-1-hechushiguitu666@gmail.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 2 +- drivers/i3c/master/svc-i3c-master.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index c32847bc4d0d..6dd3c0fd7823 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2423,7 +2423,7 @@ of_i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, * DEFSLVS command. */ if (boardinfo->base.flags & I2C_CLIENT_TEN) { - dev_err(dev, "I2C device with 10 bit address not supported."); + dev_err(dev, "I2C device with 10 bit address not supported.\n"); return -EOPNOTSUPP; } diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index f04364affbd3..a11d5f45ce86 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -1105,7 +1105,7 @@ static int svc_i3c_master_do_daa(struct i3c_master_controller *m) /* Configure IBI auto-rules */ ret = svc_i3c_update_ibirules(master); if (ret) - dev_err(master->dev, "Cannot handle such a list of devices"); + dev_err(master->dev, "Cannot handle such a list of devices\n"); rpm_out: pm_runtime_mark_last_busy(master->dev); From 4cf061f58f80d3fb7d70608a6782ecad821bb427 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:27:10 -0400 Subject: [PATCH 66/88] i3c: master: dw-i3c: Fix missing reset assertion in remove() callback JIRA: https://issues.redhat.com/browse/RHEL-143335 commit bef1eef667186cedb0bc6d152464acb3c97d5f72 Author: Felix Gu Date: Fri, 20 Mar 2026 22:18:02 +0800 i3c: master: dw-i3c: Fix missing reset assertion in remove() callback The reset line acquired during probe is currently left deasserted when the driver is unbound. Switch to devm_reset_control_get_optional_exclusive_deasserted() to ensure the reset is automatically re-asserted by the devres core when the driver is removed. Fixes: 62fe9d06f570 ("i3c: dw: Add power management support") Reviewed-by: Philipp Zabel Signed-off-by: Felix Gu Reviewed-by: Frank Li Link: https://patch.msgid.link/20260320-dw-i3c-v3-1-477040c2e3f5@gmail.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 471e0ed65c11..4b17429be5c8 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1614,13 +1614,11 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, if (IS_ERR(master->pclk)) return PTR_ERR(master->pclk); - master->core_rst = devm_reset_control_get_optional_exclusive(&pdev->dev, - "core_rst"); + master->core_rst = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, + "core_rst"); if (IS_ERR(master->core_rst)) return PTR_ERR(master->core_rst); - reset_control_deassert(master->core_rst); - spin_lock_init(&master->xferqueue.lock); INIT_LIST_HEAD(&master->xferqueue.list); @@ -1632,7 +1630,7 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, dw_i3c_master_irq_handler, 0, dev_name(&pdev->dev), master); if (ret) - goto err_assert_rst; + return ret; platform_set_drvdata(pdev, master); @@ -1683,9 +1681,6 @@ err_disable_pm: pm_runtime_set_suspended(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); -err_assert_rst: - reset_control_assert(master->core_rst); - return ret; } EXPORT_SYMBOL_GPL(dw_i3c_common_probe); From dbd3aed69a5dd3f84a81b886ed7e345116f3cbbc Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:27:51 -0400 Subject: [PATCH 67/88] i3c: dw: Fix memory leak in dw_i3c_master_i3c_xfers() JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 256cc1f1305a8e5dcadf8ca208d04a3acadd26f1 Author: Felix Gu Date: Sat, 4 Apr 2026 18:32:30 +0800 i3c: dw: Fix memory leak in dw_i3c_master_i3c_xfers() The dw_i3c_master_i3c_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. Since dw_i3c_master_free_xfer() is a thin wrapper around kfree(), use the __free(kfree) cleanup attribute to handle the free automatically on all exit paths. Fixes: 62fe9d06f570 ("i3c: dw: Add power management support") Signed-off-by: Felix Gu Reviewed-by: Frank Li Link: https://patch.msgid.link/20260404-dw-i3c-2-v3-1-8f7d146549c1@gmail.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 4b17429be5c8..7cc50b657de1 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -932,7 +933,6 @@ static int dw_i3c_master_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_master_controller *m = i3c_dev_get_master(dev); struct dw_i3c_master *master = to_dw_i3c_master(m); unsigned int nrxwords = 0, ntxwords = 0; - struct dw_i3c_xfer *xfer; int i, ret = 0; if (!i3c_nxfers) @@ -952,7 +952,7 @@ static int dw_i3c_master_i3c_xfers(struct i3c_dev_desc *dev, nrxwords > master->caps.datafifodepth) return -EOPNOTSUPP; - xfer = dw_i3c_master_alloc_xfer(master, i3c_nxfers); + struct dw_i3c_xfer *xfer __free(kfree) = dw_i3c_master_alloc_xfer(master, i3c_nxfers); if (!xfer) return -ENOMEM; @@ -1003,7 +1003,6 @@ static int dw_i3c_master_i3c_xfers(struct i3c_dev_desc *dev, } ret = xfer->ret; - dw_i3c_master_free_xfer(xfer); pm_runtime_put_autosuspend(master->dev); return ret; From bfafafa82d3b553bb5e95665edf11eed2c265a00 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:28:17 -0400 Subject: [PATCH 68/88] i3c: dw: Simplify xfer cleanup with __free(kfree) JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 6105f49196158f3e27143444651c9ca9439ac8d4 Author: Felix Gu Date: Sat, 4 Apr 2026 18:32:31 +0800 i3c: dw: Simplify xfer cleanup with __free(kfree) Convert dw-i3c-master to use __free(kfree) guards for struct dw_i3c_xfer allocations. This frees xfer objects automatically on scope exit, and removes the now-unused dw_i3c_master_free_xfer() helper. Signed-off-by: Felix Gu Reviewed-by: Frank Li Link: https://patch.msgid.link/20260404-dw-i3c-2-v3-2-8f7d146549c1@gmail.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 33 +++++++----------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 7cc50b657de1..6e877301565f 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -402,11 +402,6 @@ dw_i3c_master_alloc_xfer(struct dw_i3c_master *master, unsigned int ncmds) return xfer; } -static void dw_i3c_master_free_xfer(struct dw_i3c_xfer *xfer) -{ - kfree(xfer); -} - static void dw_i3c_master_start_xfer_locked(struct dw_i3c_master *master) { struct dw_i3c_xfer *xfer = master->xferqueue.cur; @@ -724,7 +719,6 @@ static void dw_i3c_master_bus_cleanup(struct i3c_master_controller *m) static int dw_i3c_ccc_set(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc) { - struct dw_i3c_xfer *xfer; struct dw_i3c_cmd *cmd; int ret, pos = 0; @@ -734,7 +728,7 @@ static int dw_i3c_ccc_set(struct dw_i3c_master *master, return pos; } - xfer = dw_i3c_master_alloc_xfer(master, 1); + struct dw_i3c_xfer *xfer __free(kfree) = dw_i3c_master_alloc_xfer(master, 1); if (!xfer) return -ENOMEM; @@ -759,14 +753,11 @@ static int dw_i3c_ccc_set(struct dw_i3c_master *master, if (xfer->cmds[0].error == RESPONSE_ERROR_IBA_NACK) ccc->err = I3C_ERROR_M2; - dw_i3c_master_free_xfer(xfer); - return ret; } static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc) { - struct dw_i3c_xfer *xfer; struct dw_i3c_cmd *cmd; int ret, pos; @@ -774,7 +765,7 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc) if (pos < 0) return pos; - xfer = dw_i3c_master_alloc_xfer(master, 1); + struct dw_i3c_xfer *xfer __free(kfree) = dw_i3c_master_alloc_xfer(master, 1); if (!xfer) return -ENOMEM; @@ -799,7 +790,6 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc) ret = xfer->ret; if (xfer->cmds[0].error == RESPONSE_ERROR_IBA_NACK) ccc->err = I3C_ERROR_M2; - dw_i3c_master_free_xfer(xfer); return ret; } @@ -846,12 +836,15 @@ static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, static int dw_i3c_master_daa(struct i3c_master_controller *m) { struct dw_i3c_master *master = to_dw_i3c_master(m); - struct dw_i3c_xfer *xfer; struct dw_i3c_cmd *cmd; u32 olddevs, newdevs; u8 p, last_addr = 0; int ret, pos; + struct dw_i3c_xfer *xfer __free(kfree) = dw_i3c_master_alloc_xfer(master, 1); + if (!xfer) + return -ENOMEM; + ret = pm_runtime_resume_and_get(master->dev); if (ret < 0) { dev_err(master->dev, @@ -885,15 +878,8 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m) ret = 0; } - xfer = dw_i3c_master_alloc_xfer(master, 1); - if (!xfer) { - ret = -ENOMEM; - goto rpm_out; - } - pos = dw_i3c_master_get_free_pos(master); if (pos < 0) { - dw_i3c_master_free_xfer(xfer); ret = pos; goto rpm_out; } @@ -918,8 +904,6 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m) i3c_master_add_i3c_dev_locked(m, master->devs[pos].addr); } - dw_i3c_master_free_xfer(xfer); - rpm_out: pm_runtime_put_autosuspend(master->dev); return ret; @@ -1091,7 +1075,6 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, struct i3c_master_controller *m = i2c_dev_get_master(dev); struct dw_i3c_master *master = to_dw_i3c_master(m); unsigned int nrxwords = 0, ntxwords = 0; - struct dw_i3c_xfer *xfer; int i, ret = 0; if (!i2c_nxfers) @@ -1111,7 +1094,7 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, nrxwords > master->caps.datafifodepth) return -EOPNOTSUPP; - xfer = dw_i3c_master_alloc_xfer(master, i2c_nxfers); + struct dw_i3c_xfer *xfer __free(kfree) = dw_i3c_master_alloc_xfer(master, i2c_nxfers); if (!xfer) return -ENOMEM; @@ -1120,7 +1103,6 @@ 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; } @@ -1152,7 +1134,6 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev, dw_i3c_master_dequeue_xfer(master, xfer); ret = xfer->ret; - dw_i3c_master_free_xfer(xfer); pm_runtime_put_autosuspend(master->dev); return ret; From ece7a87e29b80bb4f87756de85f137d0e672ab11 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 03:29:32 -0400 Subject: [PATCH 69/88] i3c: dw-i3c-master: Fix IBI count register selection for versalnet JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 1d78a8fc97c133b8aee54993a83f86b68ed2fdb8 Author: Shubhrajyoti Datta Date: Wed, 1 Apr 2026 14:14:30 +0530 i3c: dw-i3c-master: Fix IBI count register selection for versalnet On DesignWare I3C controllers where IC_HAS_IBI_DATA=0 (such as versalnet), the IBI_STS_CNT field (bits [28:24] of QUEUE_STATUS_LEVEL) is hardwired to 0. The IBI status entry count is instead reported via IBI_BUF_BLR (bits [23:16] of the same register). irq_handle_ibis() was unconditionally reading IBI_STS_CNT, causing it to always see 0 pending IBIs on versalnet and return early without draining the IBI buffer. Since INTR_IBI_THLD_STAT is level-triggered against the buffer fill level, this left the interrupt permanently asserted. Detect IBI data capability at probe time by writing the IBI data threshold field in QUEUE_THLD_CTRL and reading it back. Use the result to select the correct register field in irq_handle_ibis(). Signed-off-by: Shubhrajyoti Datta Link: https://patch.msgid.link/20260401084430.436059-1-shubhrajyoti.datta@amd.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 21 ++++++++++++++++++++- drivers/i3c/master/dw-i3c-master.h | 1 + 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 6e877301565f..7c6fd356016a 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1443,7 +1443,11 @@ static void dw_i3c_master_irq_handle_ibis(struct dw_i3c_master *master) u32 reg; reg = readl(master->regs + QUEUE_STATUS_LEVEL); - n_ibis = QUEUE_STATUS_IBI_STATUS_CNT(reg); + if (master->has_ibi_data) + n_ibis = QUEUE_STATUS_IBI_STATUS_CNT(reg); + else + n_ibis = QUEUE_STATUS_IBI_BUF_BLR(reg); + if (!n_ibis) return; @@ -1574,6 +1578,7 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, struct platform_device *pdev) { int ret, irq; + u32 thld_ctrl; const struct dw_i3c_drvdata *drvdata; unsigned long quirks = 0; @@ -1631,6 +1636,20 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, master->maxdevs = ret >> 16; master->free_pos = GENMASK(master->maxdevs - 1, 0); + /* + * Detect IBI data capability (IC_HAS_IBI_DATA): write a non-zero value + * to IBI_DATA_THLD and read back. On controllers like Versalnet + * the field is hardwired to 0 and the write is ignored. Restore the + * original register value after detection. + */ + thld_ctrl = readl(master->regs + QUEUE_THLD_CTRL); + ret = thld_ctrl | QUEUE_THLD_CTRL_IBI_DATA(2); + writel(ret, master->regs + QUEUE_THLD_CTRL); + ret = readl(master->regs + QUEUE_THLD_CTRL); + if (ret & QUEUE_THLD_CTRL_IBI_DATA_MASK) + master->has_ibi_data = true; + writel(thld_ctrl, master->regs + QUEUE_THLD_CTRL); + if (has_acpi_companion(&pdev->dev)) { quirks = (unsigned long)device_get_match_data(&pdev->dev); } else if (pdev->dev.of_node) { diff --git a/drivers/i3c/master/dw-i3c-master.h b/drivers/i3c/master/dw-i3c-master.h index c5cb695c16ab..306e25a08937 100644 --- a/drivers/i3c/master/dw-i3c-master.h +++ b/drivers/i3c/master/dw-i3c-master.h @@ -51,6 +51,7 @@ struct dw_i3c_master { u32 i2c_fm_timing; u32 i2c_fmp_timing; u32 quirks; + bool has_ibi_data; /* * Per-device hardware data, used to manage the device address table * (DAT) From 65532db16da71a2fb3479888754010212f801da1 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:45:38 -0400 Subject: [PATCH 70/88] i3c: master: Make hot-join workqueue freezable to block hot-join during suspend JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 527756cb9ebb277dca12fff00af9fbb3b9ec8cc8 Author: Adrian Hunter Date: Mon, 8 Jun 2026 08:43:05 +0300 i3c: master: Make hot-join workqueue freezable to block hot-join during suspend The I3C master workqueue (master->wq) is used to defer work that needs thread context and the bus maintenance lock, most notably Hot Join processing (which calls i3c_master_do_daa() to assign dynamic addresses to newly joined devices). Currently the workqueue keeps running across system suspend, which can race with the suspend path: - do_daa() may execute after the controller has been suspended, issuing bus transactions on a powered-down or otherwise unusable controller. - New I3C devices can be enumerated and added to the bus mid-suspend, registering driver model objects at a point where the I3C subsystem and its consumers are not prepared to handle them. Mark the workqueue WQ_FREEZABLE so its workers are frozen for the duration of system suspend/hibernate and resumed afterwards. This naturally defers any pending or newly queued Hot Join work until the system (and the controller) is fully resumed, closing both races without adding explicit suspend/resume synchronization in the master drivers. Update the kerneldoc for struct i3c_master_controller::wq to reflect that the workqueue is freezable. Fixes: 3a379bbcea0af ("i3c: Add core I3C infrastructure") Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260608054312.10604-2-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 2 +- include/linux/i3c/master.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 6dd3c0fd7823..6d523c0d326b 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -3073,7 +3073,7 @@ int i3c_master_register(struct i3c_master_controller *master, if (ret) goto err_put_dev; - master->wq = alloc_workqueue("%s", WQ_PERCPU, 0, dev_name(parent)); + master->wq = alloc_workqueue("%s", WQ_PERCPU | WQ_FREEZABLE, 0, dev_name(parent)); if (!master->wq) { ret = -ENOMEM; goto err_put_dev; diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 592b646f6134..e6112e5f6608 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -515,7 +515,7 @@ struct i3c_master_controller_ops { * @boardinfo.i2c: list of I2C boardinfo objects * @boardinfo: board-level information attached to devices connected on the bus * @bus: I3C bus exposed by this master - * @wq: workqueue which can be used by master + * @wq: freezable workqueue which can be used by master * drivers if they need to postpone operations that need to take place * in a thread context. Typical examples are Hot Join processing which * requires taking the bus lock in maintenance, which in turn, can only From e04b9e077da3c77cad84f2ab5b51f8c51a8131b5 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:49:12 -0400 Subject: [PATCH 71/88] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 5b130aadc36b6a935b90937dcd67b8ed4ba57831 Author: Adrian Hunter Date: Mon, 8 Jun 2026 08:43:06 +0300 i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock i3c_set_hotjoin() dispatches the controller's enable_hotjoin() or disable_hotjoin() op and updates master->hotjoin under i3c_bus_normaluse_lock(). That lock is a read-side acquisition of bus->lock (down_read()), so it does not exclude concurrent callers. The hotjoin sysfs attribute can be opened multiple times, and writes through different opens are not serialized. Two concurrent writers to "hotjoin" can therefore race in i3c_set_hotjoin(), with the controller op and the master->hotjoin store from one call interleaving with the other. The hardware enable/disable state and the value reported by hotjoin_show() can end up out of sync. Take i3c_bus_maintenance_lock() instead. Toggling Hot Join enable changes bus state and is conceptually a maintenance operation, so the write-side acquisition of bus->lock is the appropriate lock and serializes concurrent callers against each other and against other maintenance operations. Fixes: 317bacf960a48 ("i3c: master: add enable(disable) hot join in sys entry") Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260608054312.10604-3-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 6d523c0d326b..8ff81e1ee451 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -649,7 +649,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable) return ret; } - i3c_bus_normaluse_lock(&master->bus); + i3c_bus_maintenance_lock(&master->bus); if (enable) ret = master->ops->enable_hotjoin(master); @@ -659,7 +659,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable) if (!ret) master->hotjoin = enable; - i3c_bus_normaluse_unlock(&master->bus); + i3c_bus_maintenance_unlock(&master->bus); if ((enable && ret) || (!enable && !ret) || master->rpm_ibi_allowed) i3c_master_rpm_put(master); From 0a42f8ced209c5c3c2fa80f70049eae182885ad4 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:54:33 -0400 Subject: [PATCH 72/88] i3c: master: Prevent reuse of dynamic address on device add failure JIRA: https://issues.redhat.com/browse/RHEL-143335 commit b3ba8383da4d0cff15810e32ea785eceb0a80813 Author: Adrian Hunter Date: Fri, 12 Jun 2026 11:01:03 +0300 i3c: master: Prevent reuse of dynamic address on device add failure i3c_master_add_i3c_dev_locked() is called after a device has already been assigned a dynamic address. If the function fails, the address remains marked as free and may be reallocated to another device, leading to address conflicts on the bus. Ensure the address is not marked as free on failure, by updating the address slot state to prevent the address from being re-used. Emit an error message to inform of the failure. Opportunistically remove the !master check because it is impossible. Note, directly resetting the device's dynamic address is no longer an option, since Direct RSTDAA was deprecated from I3C starting from version 1.1 and v1.1 (or later) target devices are meant to NACK it. Fixes: 3a379bbcea0af ("i3c: Add core I3C infrastructure") Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260612080107.11606-4-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 8ff81e1ee451..0554a4b069fb 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2268,12 +2268,11 @@ int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master, bool enable_ibi = false; int ret; - if (!master) - return -EINVAL; - newdev = i3c_master_alloc_i3c_dev(master, &info); - if (IS_ERR(newdev)) - return PTR_ERR(newdev); + if (IS_ERR(newdev)) { + ret = PTR_ERR(newdev); + goto err_prevent_addr_reuse; + } ret = i3c_master_attach_i3c_dev(master, newdev); if (ret) @@ -2395,6 +2394,16 @@ err_detach_dev: err_free_dev: i3c_master_free_i3c_dev(newdev); +err_prevent_addr_reuse: + /* + * Although the device has not been added, the address has been + * assigned. Prevent the address from being used again. + */ + if (i3c_bus_get_addr_slot_status(&master->bus, addr) == I3C_ADDR_SLOT_FREE) + i3c_bus_set_addr_slot_status(&master->bus, addr, I3C_ADDR_SLOT_I3C_DEV); + + dev_err(&master->dev, "Failed to add I3C device at address %u, error %d\n", addr, ret); + return ret; } EXPORT_SYMBOL_GPL(i3c_master_add_i3c_dev_locked); From ac9673b446a7cf071a70821c4ec30c13f8b78429 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:54:59 -0400 Subject: [PATCH 73/88] i3c: master: Update dev_nack_retry_count under maintenance lock JIRA: https://issues.redhat.com/browse/RHEL-143335 commit ab5f9c5cb527c03790a92142ad368881a9100aaf Author: Adrian Hunter Date: Tue, 16 Jun 2026 14:37:50 +0300 i3c: master: Update dev_nack_retry_count under maintenance lock Protect master->dev_nack_retry_count against concurrent sysfs updates by updating it while holding the bus maintenance lock. Consequently, combine adjacent return statements into one. For consistency, read dev_nack_retry_count while holding the bus normaluse lock. Fixes: b58f47eb39268 ("i3c: add sysfs entry and attribute for Device NACK Retry count") Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260616113752.196140-2-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 0554a4b069fb..ad5b24a8742e 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -728,7 +728,14 @@ 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); + struct i3c_bus *i3cbus = dev_to_i3cbus(dev); + ssize_t ret; + + i3c_bus_normaluse_lock(i3cbus); + ret = sysfs_emit(buf, "%u\n", dev_to_i3cmaster(dev)->dev_nack_retry_count); + i3c_bus_normaluse_unlock(i3cbus); + + return ret; } static ssize_t dev_nack_retry_count_store(struct device *dev, @@ -746,14 +753,11 @@ static ssize_t dev_nack_retry_count_store(struct device *dev, i3c_bus_maintenance_lock(i3cbus); ret = master->ops->set_dev_nack_retry(master, val); + if (!ret) + master->dev_nack_retry_count = val; i3c_bus_maintenance_unlock(i3cbus); - if (ret) - return ret; - - master->dev_nack_retry_count = val; - - return count; + return ret ?: count; } static DEVICE_ATTR_RW(dev_nack_retry_count); From 0e802b930b4b09ebde4a1fafec0a5536f533fa74 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:55:19 -0400 Subject: [PATCH 74/88] i3c: master: Add missing runtime PM get in dev_nack_retry_count_store() JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 79ce29e100ab3de0cad66eb48d32a7de4043e2ae Author: Adrian Hunter Date: Tue, 16 Jun 2026 14:37:51 +0300 i3c: master: Add missing runtime PM get in dev_nack_retry_count_store() Ensure the device is runtime resumed while updating the retry configuration to avoid accessing the controller while suspended. Call i3c_master_rpm_get() before accessing the controller in dev_nack_retry_count_store() and release it with i3c_master_rpm_put() afterwards. Fixes: 990c149c61ee4 ("i3c: master: Introduce optional Runtime PM support") Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260616113752.196140-3-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index ad5b24a8742e..bf59fd0c87d2 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -751,12 +751,18 @@ static ssize_t dev_nack_retry_count_store(struct device *dev, if (ret) return ret; + ret = i3c_master_rpm_get(master); + if (ret) + return ret; + i3c_bus_maintenance_lock(i3cbus); ret = master->ops->set_dev_nack_retry(master, val); if (!ret) master->dev_nack_retry_count = val; i3c_bus_maintenance_unlock(i3cbus); + i3c_master_rpm_put(master); + return ret ?: count; } From 7d7b4a4d126508520fa459a27a339f2d8f051618 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 02:58:32 -0400 Subject: [PATCH 75/88] i3c: master: Use unsigned int for dev_nack_retry_count consistently JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 225b76e2a711dc061ec337befba49dd3ee75e534 Author: Adrian Hunter Date: Tue, 16 Jun 2026 14:37:52 +0300 i3c: master: Use unsigned int for dev_nack_retry_count consistently Use unsigned int for dev_nack_retry_count across the core and controller drivers to match the type of master->dev_nack_retry_count. Update the sysfs store path to use kstrtouint() and adjust the ->set_dev_nack_retry() callback prototype and callers accordingly. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260616113752.196140-4-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 4 ++-- drivers/i3c/master/dw-i3c-master.c | 4 ++-- include/linux/i3c/master.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index bf59fd0c87d2..d256b381836a 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -744,10 +744,10 @@ static ssize_t dev_nack_retry_count_store(struct device *dev, { struct i3c_bus *i3cbus = dev_to_i3cbus(dev); struct i3c_master_controller *master = dev_to_i3cmaster(dev); - unsigned long val; + unsigned int val; int ret; - ret = kstrtoul(buf, 0, &val); + ret = kstrtouint(buf, 0, &val); if (ret) return ret; diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 7c6fd356016a..83991c92b8cf 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1493,7 +1493,7 @@ static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id) } static int dw_i3c_master_set_dev_nack_retry(struct i3c_master_controller *m, - unsigned long dev_nack_retry_cnt) + unsigned int dev_nack_retry_cnt) { struct dw_i3c_master *master = to_dw_i3c_master(m); u32 reg; @@ -1501,7 +1501,7 @@ static int dw_i3c_master_set_dev_nack_retry(struct i3c_master_controller *m, if (dev_nack_retry_cnt > DW_I3C_DEV_NACK_RETRY_CNT_MAX) { dev_err(&master->base.dev, - "Value %ld exceeds maximum %d\n", + "Value %u exceeds maximum %d\n", dev_nack_retry_cnt, DW_I3C_DEV_NACK_RETRY_CNT_MAX); return -ERANGE; } diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index e6112e5f6608..7085fde5dad3 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -494,7 +494,7 @@ struct i3c_master_controller_ops { 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); + unsigned int dev_nack_retry_cnt); }; /** From 38e324428805c5f5df5a2daec9463d4ea64920c9 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 04:01:21 -0400 Subject: [PATCH 76/88] i3c: master: rename i3c_master_reattach_i3c_dev() to *_locked JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 79c41666b397df9218b6e7555663b298a88e2b56 Author: Lakshay Piplani Date: Fri, 12 Jun 2026 16:48:08 +0530 i3c: master: rename i3c_master_reattach_i3c_dev() to *_locked Rename i3c_master_reattach_i3c_dev() to *_locked() to make the locking requirement explicit and consistent with other I3C core helpers that require the bus lock to be held by the caller. Signed-off-by: Lakshay Piplani Reviewed-by: Frank Li Link: https://patch.msgid.link/20260612111816.3688240-2-lakshay.piplani@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index d256b381836a..fb3e26562a2a 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1656,8 +1656,8 @@ static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master, return 0; } -static int i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev, - u8 old_dyn_addr) +static int i3c_master_reattach_i3c_dev_locked(struct i3c_dev_desc *dev, + u8 old_dyn_addr) { struct i3c_master_controller *master = i3c_dev_get_master(dev); int ret; @@ -1746,7 +1746,7 @@ static int i3c_master_early_i3c_dev_add(struct i3c_master_controller *master, goto err_detach_dev; i3cdev->info.dyn_addr = i3cdev->boardinfo->init_dyn_addr; - ret = i3c_master_reattach_i3c_dev(i3cdev, 0); + ret = i3c_master_reattach_i3c_dev_locked(i3cdev, 0); if (ret) goto err_rstdaa; @@ -2361,7 +2361,7 @@ int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master, if (!ret) { old_dyn_addr = newdev->info.dyn_addr; newdev->info.dyn_addr = expected_dyn_addr; - i3c_master_reattach_i3c_dev(newdev, old_dyn_addr); + i3c_master_reattach_i3c_dev_locked(newdev, old_dyn_addr); } else { dev_err(&master->dev, "Failed to assign reserved/old address to device %d%llx", From c08289dcab72e3229339fe34cd88cf261c3e417e Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Fri, 14 Aug 2026 04:02:20 -0400 Subject: [PATCH 77/88] i3c: master: Expose the APIs to support I3C hub JIRA: https://issues.redhat.com/browse/RHEL-143335 commit 8d8afa428318a623aa674c3f90550475ad3e6ccd Author: Aman Kumar Pandey Date: Fri, 12 Jun 2026 16:48:09 +0530 i3c: master: Expose the APIs to support I3C hub Change the below internal static functions to APIs to allow new I3C hub driver to use them 1) i3c_dev_enable_ibi_locked() 2) i3c_dev_disable_ibi_locked() 3) i3c_dev_request_ibi_locked() 4) i3c_dev_free_ibi_locked() 5) i3c_master_reattach_i3c_dev_locked() Signed-off-by: Aman Kumar Pandey Signed-off-by: Lakshay Piplani Reviewed-by: Frank Li Link: https://patch.msgid.link/20260612111816.3688240-3-lakshay.piplani@nxp.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 70 ++++++++++++++++++++++++++++++++++++-- include/linux/i3c/master.h | 2 ++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index fb3e26562a2a..f4459de23b73 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1656,8 +1656,23 @@ static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master, return 0; } -static int i3c_master_reattach_i3c_dev_locked(struct i3c_dev_desc *dev, - u8 old_dyn_addr) +/** + * i3c_master_reattach_i3c_dev_locked() - reattach an I3C device with a new address + * @dev: I3C device descriptor to reattach + * @old_dyn_addr: previous dynamic address of the device + * + * This function reattaches an existing I3C device to the bus when its dynamic + * address has changed. It updates the bus address slot status accordingly: + * - Marks the new dynamic address as occupied by an I3C device. + * - Frees the old dynamic address slot if applicable. + * + * This function must be called with the bus lock held in write mode. + * + * Return: 0 on success, or a negative error code if reattachment fails + * (e.g. -EBUSY if the new address slot is not free). + */ +int i3c_master_reattach_i3c_dev_locked(struct i3c_dev_desc *dev, + u8 old_dyn_addr) { struct i3c_master_controller *master = i3c_dev_get_master(dev); int ret; @@ -1681,6 +1696,7 @@ static int i3c_master_reattach_i3c_dev_locked(struct i3c_dev_desc *dev, return 0; } +EXPORT_SYMBOL_GPL(i3c_master_reattach_i3c_dev_locked); static void i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev) { @@ -3208,6 +3224,16 @@ int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, struct i3c_xfer *xfers, return master->ops->i3c_xfers(dev, xfers, nxfers, mode); } +/** + * i3c_dev_disable_ibi_locked() - Disable IBIs coming from a specific device + * @dev: device on which IBIs should be disabled + * + * This function disable IBIs coming from a specific device and wait for + * all pending IBIs to be processed. + * + * Context: Must be called with mutex_lock(&dev->desc->ibi_lock) held. + * Return: 0 in case of success, a negative error core otherwise. + */ int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev) { struct i3c_master_controller *master; @@ -3229,7 +3255,22 @@ int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev) return 0; } +EXPORT_SYMBOL_GPL(i3c_dev_disable_ibi_locked); +/** + * i3c_dev_enable_ibi_locked() - Enable IBIs from a specific device (lock held) + * @dev: device on which IBIs should be enabled + * + * This function enable IBIs coming from a specific device and wait for + * all pending IBIs to be processed. This should be called on a device + * where i3c_device_request_ibi() has succeeded. + * + * Note that IBIs from this device might be received before this function + * returns to its caller. + * + * Context: Must be called with mutex_lock(&dev->desc->ibi_lock) held. + * Return: 0 on success, or a negative error code on failure. + */ int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev) { struct i3c_master_controller *master = i3c_dev_get_master(dev); @@ -3244,7 +3285,20 @@ int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev) return ret; } +EXPORT_SYMBOL_GPL(i3c_dev_enable_ibi_locked); +/** + * i3c_dev_request_ibi_locked() - Request an IBI + * @dev: device for which we should enable IBIs + * @req: setup requested for this IBI + * + * This function is responsible for pre-allocating all resources needed to + * process IBIs coming from @dev. When this function returns, the IBI is not + * enabled until i3c_device_enable_ibi() is called. + * + * Context: Must be called with mutex_lock(&dev->desc->ibi_lock) held. + * Return: 0 in case of success, a negative error core otherwise. + */ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, const struct i3c_ibi_setup *req) { @@ -3283,7 +3337,18 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, return ret; } +EXPORT_SYMBOL_GPL(i3c_dev_request_ibi_locked); +/** + * i3c_dev_free_ibi_locked() - Free all resources needed for IBI handling + * @dev: device on which you want to release IBI resources + * + * This function is responsible for de-allocating resources previously + * allocated by i3c_device_request_ibi(). It should be called after disabling + * IBIs with i3c_device_disable_ibi(). + * + * Context: Must be called with mutex_lock(&dev->desc->ibi_lock) held. + */ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev) { struct i3c_master_controller *master = i3c_dev_get_master(dev); @@ -3314,6 +3379,7 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev) kfree(dev->ibi); dev->ibi = NULL; } +EXPORT_SYMBOL_GPL(i3c_dev_free_ibi_locked); static int __init i3c_init(void) { diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 7085fde5dad3..5d0286f0e076 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -613,6 +613,8 @@ void i3c_master_dma_unmap_single(struct i3c_dma *dma_xfer); DEFINE_FREE(i3c_master_dma_unmap_single, void *, if (_T) i3c_master_dma_unmap_single(_T)) +int i3c_master_reattach_i3c_dev_locked(struct i3c_dev_desc *dev, + u8 old_dyn_addr); int i3c_master_set_info(struct i3c_master_controller *master, const struct i3c_device_info *info); From 7ec4b6402866e76b0c92782bed81fec3e0de1bad Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 10:31:34 -0400 Subject: [PATCH 78/88] dt-bindings: i3c: Add mipi-i3c-static-method to support SETAASA JIRA: https://issues.redhat.com/browse/RHEL-143335 Upstream Status: https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git commit 81e7c27b0d5cb3029fc01374c3a96019d3a9e673 Author: Akhil R Date: Tue, 28 Jul 2026 06:59:43 +0000 dt-bindings: i3c: Add mipi-i3c-static-method to support SETAASA Add the 'mipi-i3c-static-method' property mentioned in the MIPI I3C Discovery and Configuration Specification [1] to specify which discovery method an I3C device supports during bus initialization. The property is a bitmap, where a bit value of 1 indicates support for that method, and 0 indicates lack of support. Bit 0: SETDASA CCC (Direct) Bit 1: SETAASA CCC (Broadcast) Bit 2: Other CCC (vendor / standards extension) All other bits are reserved. It is specifically needed when an I3C device requires SETAASA for the address assignment. SETDASA will be supported by default if this property is absent, which means for now the property just serves as a flag to enable SETAASA, but keep the property as a bitmap to align with the specifications. [1] https://www.mipi.org/mipi-disco-for-i3c-download Reviewed-by: Frank Li Reviewed-by: Rob Herring (Arm) Signed-off-by: Akhil R Link: https://patch.msgid.link/20260728065955.809445-2-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- .../devicetree/bindings/i3c/i3c.yaml | 36 ++++++++++++++++--- include/dt-bindings/i3c/i3c.h | 4 +++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/i3c/i3c.yaml b/Documentation/devicetree/bindings/i3c/i3c.yaml index e25fa72fd785..5603f2e7807d 100644 --- a/Documentation/devicetree/bindings/i3c/i3c.yaml +++ b/Documentation/devicetree/bindings/i3c/i3c.yaml @@ -31,10 +31,12 @@ properties: described in the device tree, which in turn means we have to describe I3C devices. - Another use case for describing an I3C device in the device tree is when - this I3C device has a static I2C address and we want to assign it a - specific I3C dynamic address before the DAA takes place (so that other - devices on the bus can't take this dynamic address). + Other use-cases for describing an I3C device in the device tree are: + - When the I3C device has a static I2C address and we want to assign + it a specific I3C dynamic address before the DAA takes place (so + that other devices on the bus can't take this dynamic address). + - When the I3C device requires SETAASA for its discovery and uses a + pre-defined static address. "#size-cells": const: 0 @@ -145,7 +147,31 @@ patternProperties: Dynamic address to be assigned to this device. In case static address is present (first cell of the reg property != 0), this address is assigned through SETDASA. If static address is not present, this address is assigned - through SETNEWDA after assigning a temporary address via ENTDAA. + through SETNEWDA after assigning a temporary address via ENTDAA. If + SETAASA is used, this property is not used, and the static address itself + becomes the dynamic address. + + mipi-i3c-static-method: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0x1 + maximum: 0x7 + default: 1 + description: | + Bitmap describing which methods of Dynamic Address Assignment from a + static address are supported by this I3C Target. For each defined bit + position, a set bit indicates support for that method and a cleared + bit indicates lack of support. + + Bit 0: SETDASA CCC (Direct) + Bit 1: SETAASA CCC (Broadcast) + Bit 2: Other CCC (vendor / standards extension) + All other bits are reserved. + + This property follows the MIPI I3C specification. The primary use + of this property is to indicate support for SETAASA, i.e Bit 1, but + will allow other values mentioned in the specification so that it + mirrors the specification. SETDASA will remain as the default method + even if this property is not present. required: - reg diff --git a/include/dt-bindings/i3c/i3c.h b/include/dt-bindings/i3c/i3c.h index 373439218bba..78b8c634aad8 100644 --- a/include/dt-bindings/i3c/i3c.h +++ b/include/dt-bindings/i3c/i3c.h @@ -13,4 +13,8 @@ #define I2C_NO_FILTER_HIGH_FREQUENCY (1 << 5) #define I2C_NO_FILTER_LOW_FREQUENCY (2 << 5) +#define I3C_ADDR_METHOD_SETDASA (1 << 0) +#define I3C_ADDR_METHOD_SETAASA (1 << 1) +#define I3C_ADDR_METHOD_VENDOR (1 << 2) + #endif From aaf658672fc1de0fede9146ea0082dda0a3ce648 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 10:33:00 -0400 Subject: [PATCH 79/88] i3c: master: Use unified device property interface JIRA: https://issues.redhat.com/browse/RHEL-143335 Upstream Status: https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git commit ee170021bee17124c13f42813607bca553a6b48a Author: Akhil R Date: Tue, 28 Jul 2026 06:59:44 +0000 i3c: master: Use unified device property interface Replace all OF-specific functions with unified device property functions as a prerequisite to support both ACPI and device tree. Reviewed-by: Frank Li Signed-off-by: Akhil R Link: https://patch.msgid.link/20260728065955.809445-3-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 77 +++++++++++++++++++++----------------- include/linux/i3c/master.h | 5 ++- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index f4459de23b73..051161e917be 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -11,10 +11,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -497,7 +499,7 @@ static void i3c_bus_cleanup(struct i3c_bus *i3cbus) mutex_unlock(&i3c_core_lock); } -static int i3c_bus_init(struct i3c_bus *i3cbus, struct device_node *np) +static int i3c_bus_init(struct i3c_bus *i3cbus, struct fwnode_handle *fwnode) { int ret, start, end, id = -1; @@ -507,8 +509,8 @@ static int i3c_bus_init(struct i3c_bus *i3cbus, struct device_node *np) i3c_bus_init_addrslots(i3cbus); i3cbus->mode = I3C_BUS_MODE_PURE; - if (np) - id = of_alias_get_id(np, "i3c"); + if (fwnode && is_of_node(fwnode)) + id = of_alias_get_id(to_of_node(fwnode), "i3c"); mutex_lock(&i3c_core_lock); if (id >= 0) { @@ -821,7 +823,7 @@ static void i3c_masterdev_release(struct device *dev) WARN_ON(!list_empty(&bus->devs.i2c) || !list_empty(&bus->devs.i3c)); i3c_bus_cleanup(bus); - of_node_put(dev->of_node); + fwnode_handle_put(dev->fwnode); } static const struct device_type i3c_masterdev_type = { @@ -1005,7 +1007,7 @@ static void i3c_device_release(struct device *dev) WARN_ON(i3cdev->desc); - of_node_put(i3cdev->dev.of_node); + fwnode_handle_put(dev->fwnode); kfree(i3cdev); } @@ -1809,7 +1811,7 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) desc->info.pid); if (desc->boardinfo) - desc->dev->dev.of_node = desc->boardinfo->of_node; + device_set_node(&desc->dev->dev, desc->boardinfo->fwnode); ret = device_register(&desc->dev->dev); if (ret) { @@ -2437,8 +2439,8 @@ EXPORT_SYMBOL_GPL(i3c_master_add_i3c_dev_locked); #define OF_I3C_REG1_IS_I2C_DEV BIT(31) static int -of_i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, - struct device_node *node, u32 *reg) +i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, + struct fwnode_handle *fwnode, u32 *reg) { struct i2c_dev_boardinfo *boardinfo; struct device *dev = &master->dev; @@ -2448,9 +2450,13 @@ of_i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, if (!boardinfo) return -ENOMEM; - ret = of_i2c_get_board_info(dev, node, &boardinfo->base); - if (ret) - return ret; + if (is_of_node(fwnode)) { + ret = of_i2c_get_board_info(dev, to_of_node(fwnode), &boardinfo->base); + if (ret) + return ret; + } else { + return -EINVAL; + } /* * The I3C Specification does not clearly say I2C devices with 10-bit @@ -2466,14 +2472,14 @@ of_i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, boardinfo->lvr = reg[2]; list_add_tail(&boardinfo->node, &master->boardinfo.i2c); - of_node_get(node); + fwnode_handle_get(fwnode); return 0; } static int -of_i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, - struct device_node *node, u32 *reg) +i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, + struct fwnode_handle *fwnode, u32 *reg) { struct i3c_dev_boardinfo *boardinfo; struct device *dev = &master->dev; @@ -2496,7 +2502,7 @@ of_i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, boardinfo->static_addr = reg[0]; - if (!of_property_read_u32(node, "assigned-address", &init_dyn_addr)) { + if (!fwnode_property_read_u32(fwnode, "assigned-address", &init_dyn_addr)) { if (init_dyn_addr > I3C_MAX_ADDR) return -EINVAL; @@ -2513,14 +2519,14 @@ of_i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, return -EINVAL; boardinfo->init_dyn_addr = init_dyn_addr; - boardinfo->of_node = of_node_get(node); + boardinfo->fwnode = fwnode_handle_get(fwnode); list_add_tail(&boardinfo->node, &master->boardinfo.i3c); return 0; } -static int of_i3c_master_add_dev(struct i3c_master_controller *master, - struct device_node *node) +static int i3c_master_add_dev(struct i3c_master_controller *master, + struct fwnode_handle *fwnode) { u32 reg[3]; int ret; @@ -2528,7 +2534,7 @@ static int of_i3c_master_add_dev(struct i3c_master_controller *master, if (!master) return -EINVAL; - ret = of_property_read_u32_array(node, "reg", reg, ARRAY_SIZE(reg)); + ret = fwnode_property_read_u32_array(fwnode, "reg", reg, ARRAY_SIZE(reg)); if (ret) return ret; @@ -2537,25 +2543,25 @@ static int of_i3c_master_add_dev(struct i3c_master_controller *master, * dealing with an I2C device. */ if (!reg[1]) - ret = of_i3c_master_add_i2c_boardinfo(master, node, reg); + ret = i3c_master_add_i2c_boardinfo(master, fwnode, reg); else - ret = of_i3c_master_add_i3c_boardinfo(master, node, reg); + ret = i3c_master_add_i3c_boardinfo(master, fwnode, reg); return ret; } -static int of_populate_i3c_bus(struct i3c_master_controller *master) +static int fwnode_populate_i3c_bus(struct i3c_master_controller *master) { struct device *dev = &master->dev; - struct device_node *i3cbus_np = dev->of_node; + struct fwnode_handle *fwnode = dev_fwnode(dev); int ret; u32 val; - if (!i3cbus_np) + if (!fwnode) return 0; - for_each_available_child_of_node_scoped(i3cbus_np, node) { - ret = of_i3c_master_add_dev(master, node); + fwnode_for_each_available_child_node_scoped(fwnode, child) { + ret = i3c_master_add_dev(master, child); if (ret) return ret; } @@ -2565,10 +2571,10 @@ static int of_populate_i3c_bus(struct i3c_master_controller *master) * on the bus are not supporting typical rates, or if the bus topology * prevents it from using max possible rate. */ - if (!of_property_read_u32(i3cbus_np, "i2c-scl-hz", &val)) + if (!device_property_read_u32(dev, "i2c-scl-hz", &val)) master->bus.scl_rate.i2c = val; - if (!of_property_read_u32(i3cbus_np, "i3c-scl-hz", &val)) + if (!device_property_read_u32(dev, "i3c-scl-hz", &val)) master->bus.scl_rate.i3c = val; return 0; @@ -2623,7 +2629,7 @@ static u8 i3c_master_i2c_get_lvr(struct i2c_client *client) u8 lvr = I3C_LVR_I2C_INDEX(2) | I3C_LVR_I2C_FM_MODE; u32 reg[3]; - if (!of_property_read_u32_array(client->dev.of_node, "reg", reg, ARRAY_SIZE(reg))) + if (!fwnode_property_read_u32_array(client->dev.fwnode, "reg", reg, ARRAY_SIZE(reg))) lvr = reg[2]; return lvr; @@ -2742,7 +2748,8 @@ static int i3c_master_i2c_adapter_init(struct i3c_master_controller *master) struct i2c_adapter *adap = i3c_master_to_i2c_adapter(master); struct i2c_dev_desc *i2cdev; struct i2c_dev_boardinfo *i2cboardinfo; - int ret, id; + struct fwnode_handle *fwnode = dev_fwnode(&master->dev); + int ret, id = -1; adap->dev.parent = master->dev.parent; adap->owner = master->dev.parent->driver->owner; @@ -2751,7 +2758,9 @@ static int i3c_master_i2c_adapter_init(struct i3c_master_controller *master) adap->timeout = HZ; adap->retries = 3; - id = of_alias_get_id(master->dev.of_node, "i2c"); + if (fwnode && is_of_node(fwnode)) + id = of_alias_get_id(to_of_node(fwnode), "i2c"); + if (id >= 0) { adap->nr = id; ret = i2c_add_numbered_adapter(adap); @@ -3052,7 +3061,7 @@ int i3c_master_register(struct i3c_master_controller *master, return ret; master->dev.parent = parent; - master->dev.of_node = of_node_get(parent->of_node); + device_set_node(&master->dev, fwnode_handle_get(dev_fwnode(parent))); master->dev.bus = &i3c_bus_type; master->dev.type = &i3c_masterdev_type; master->dev.release = i3c_masterdev_release; @@ -3071,13 +3080,13 @@ int i3c_master_register(struct i3c_master_controller *master, master->dev.coherent_dma_mask = parent->coherent_dma_mask; master->dev.dma_parms = parent->dma_parms; - ret = i3c_bus_init(i3cbus, master->dev.of_node); + ret = i3c_bus_init(i3cbus, dev_fwnode(&master->dev)); if (ret) goto err_put_dev; dev_set_name(&master->dev, "i3c-%d", i3cbus->id); - ret = of_populate_i3c_bus(master); + ret = fwnode_populate_i3c_bus(master); if (ret) goto err_put_dev; diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 5d0286f0e076..978e7a37bd7d 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -177,7 +177,8 @@ struct i3c_device_ibi_info { * @pid: I3C Provisioned ID exposed by the device. This is a unique identifier * that may be used to attach boardinfo to i3c_dev_desc when the device * does not have a static address - * @of_node: optional DT node in case the device has been described in the DT + * @fwnode: Firmware node (DT or ACPI) in case the device has been + * described in firmware * * This structure is used to attach board-level information to an I3C device. * Not all I3C devices connected on the bus will have a boardinfo. It's only @@ -189,7 +190,7 @@ struct i3c_dev_boardinfo { u8 init_dyn_addr; u8 static_addr; u64 pid; - struct device_node *of_node; + struct fwnode_handle *fwnode; }; /** From a6c09fcb564f6ad843c03c4a294daa407ed50930 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 10:33:28 -0400 Subject: [PATCH 80/88] i3c: master: Support ACPI enumeration of child devices JIRA: https://issues.redhat.com/browse/RHEL-143335 Upstream Status: https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git commit b46a4b3c5d1e312e74e5364a04c283a5c88e0916 Author: Akhil R Date: Tue, 28 Jul 2026 06:59:45 +0000 i3c: master: Support ACPI enumeration of child devices Although the existing subsystem allows host controllers to register through the ACPI table, it was not possible to describe I3C or I2C devices when using ACPI. This is because the driver relied on the reg property to retrieve the PID, static address, etc., whereas ACPI uses _ADR or serial resources to describe such devices. Read _ADR and LVR from ACPI resources and extract the data as per the ACPI specification for an I3C bus. Also read mipi-i3c-static-address as per the MIPI DISCO specifications [1] to get the static address to be used. Enable describing I3C or I2C devices in the ACPI table. This is required if the device uses a static address or if it needs device-specific properties. [1] https://www.mipi.org/mipi-disco-for-i3c-download Reviewed-by: Frank Li Signed-off-by: Akhil R Link: https://patch.msgid.link/20260728065955.809445-4-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 151 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 143 insertions(+), 8 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 051161e917be..f1aa35ab1e76 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -5,6 +5,7 @@ * Author: Boris Brezillon */ +#include #include #include #include @@ -2438,6 +2439,55 @@ EXPORT_SYMBOL_GPL(i3c_master_add_i3c_dev_locked); #define OF_I3C_REG1_IS_I2C_DEV BIT(31) +#ifdef CONFIG_ACPI +static int i3c_acpi_get_i2c_resource(struct acpi_resource *ares, void *data) +{ + struct i2c_dev_boardinfo *boardinfo = data; + struct acpi_resource_i2c_serialbus *sb; + + if (boardinfo->base.addr || !i2c_acpi_get_i2c_resource(ares, &sb)) + return 1; + + boardinfo->base.addr = sb->slave_address; + if (sb->access_mode == ACPI_I2C_10BIT_MODE) + boardinfo->base.flags |= I2C_CLIENT_TEN; + + boardinfo->lvr = sb->lvr; + + return 1; +} + +static int i3c_acpi_add_i2c_boardinfo(struct i2c_dev_boardinfo *boardinfo, + struct fwnode_handle *fwnode) +{ + struct acpi_device *adev = to_acpi_device_node(fwnode); + LIST_HEAD(resources); + int ret; + + boardinfo->base.fwnode = acpi_fwnode_handle(adev); + acpi_set_modalias(adev, dev_name(&adev->dev), boardinfo->base.type, + sizeof(boardinfo->base.type)); + + ret = acpi_dev_get_resources(adev, &resources, + i3c_acpi_get_i2c_resource, boardinfo); + if (ret < 0) + return ret; + + acpi_dev_free_resource_list(&resources); + + if (!boardinfo->base.addr) + return -ENODEV; + + return 0; +} +#else +static inline int i3c_acpi_add_i2c_boardinfo(struct i2c_dev_boardinfo *boardinfo, + struct fwnode_handle *fwnode) +{ + return -ENODEV; +} +#endif + static int i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, struct fwnode_handle *fwnode, u32 *reg) @@ -2454,6 +2504,15 @@ i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, ret = of_i2c_get_board_info(dev, to_of_node(fwnode), &boardinfo->base); if (ret) return ret; + + /* LVR is encoded in reg[2] for Device Tree. */ + boardinfo->lvr = reg[2]; + } else if (is_acpi_device_node(fwnode)) { + ret = i3c_acpi_add_i2c_boardinfo(boardinfo, fwnode); + if (ret) { + devm_kfree(dev, boardinfo); + return ret; + } } else { return -EINVAL; } @@ -2468,9 +2527,6 @@ i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, return -EOPNOTSUPP; } - /* LVR is encoded in reg[2]. */ - boardinfo->lvr = reg[2]; - list_add_tail(&boardinfo->node, &master->boardinfo.i2c); fwnode_handle_get(fwnode); @@ -2525,8 +2581,8 @@ i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, return 0; } -static int i3c_master_add_dev(struct i3c_master_controller *master, - struct fwnode_handle *fwnode) +static int i3c_master_add_of_dev(struct i3c_master_controller *master, + struct fwnode_handle *fwnode) { u32 reg[3]; int ret; @@ -2550,6 +2606,74 @@ static int i3c_master_add_dev(struct i3c_master_controller *master, return ret; } +#ifdef CONFIG_ACPI +static int i3c_master_add_acpi_dev(struct i3c_master_controller *master, + struct fwnode_handle *fwnode) +{ + struct acpi_device *adev = to_acpi_device_node(fwnode); + acpi_bus_address adr; + u32 reg[3] = { 0 }; + int ret; + + /* + * If the ACPI table entry has _ADR method, it's an I3C device. + * Otherwise it may be an I2C device described by an I2cSerialBus + * resource. If no I2cSerialBus resource is found, ignore the entry. + */ + if (!acpi_has_method(adev->handle, "_ADR")) { + ret = i3c_master_add_i2c_boardinfo(master, fwnode, reg); + if (ret == -ENODEV) + return 0; + + return ret; + } + + adr = acpi_device_adr(adev); + + /* For I3C devices, _ADR will have the 48 bit PID of the device */ + reg[1] = upper_32_bits(adr); + reg[2] = lower_32_bits(adr); + + fwnode_property_read_u32(fwnode, "mipi-i3c-static-address", ®[0]); + + return i3c_master_add_i3c_boardinfo(master, fwnode, reg); +} + +static u8 i3c_acpi_i2c_get_lvr(struct i2c_client *client) +{ + struct acpi_device *adev = to_acpi_device_node(client->dev.fwnode); + struct i2c_dev_boardinfo boardinfo = {}; + LIST_HEAD(resources); + int ret; + u8 lvr; + + lvr = I3C_LVR_I2C_INDEX(2) | I3C_LVR_I2C_FM_MODE; + + ret = acpi_dev_get_resources(adev, &resources, + i3c_acpi_get_i2c_resource, &boardinfo); + if (ret < 0) + return lvr; + + if (boardinfo.base.addr) + lvr = boardinfo.lvr; + + acpi_dev_free_resource_list(&resources); + + return lvr; +} +#else +static inline int i3c_master_add_acpi_dev(struct i3c_master_controller *master, + struct fwnode_handle *fwnode) +{ + return -ENODEV; +} + +static inline u8 i3c_acpi_i2c_get_lvr(struct i2c_client *client) +{ + return I3C_LVR_I2C_INDEX(2) | I3C_LVR_I2C_FM_MODE; +} +#endif + static int fwnode_populate_i3c_bus(struct i3c_master_controller *master) { struct device *dev = &master->dev; @@ -2561,7 +2685,13 @@ static int fwnode_populate_i3c_bus(struct i3c_master_controller *master) return 0; fwnode_for_each_available_child_node_scoped(fwnode, child) { - ret = i3c_master_add_dev(master, child); + if (is_of_node(child)) + ret = i3c_master_add_of_dev(master, child); + else if (is_acpi_device_node(child)) + ret = i3c_master_add_acpi_dev(master, child); + else + continue; + if (ret) return ret; } @@ -2629,8 +2759,13 @@ static u8 i3c_master_i2c_get_lvr(struct i2c_client *client) u8 lvr = I3C_LVR_I2C_INDEX(2) | I3C_LVR_I2C_FM_MODE; u32 reg[3]; - if (!fwnode_property_read_u32_array(client->dev.fwnode, "reg", reg, ARRAY_SIZE(reg))) - lvr = reg[2]; + if (is_of_node(client->dev.fwnode)) { + if (!fwnode_property_read_u32_array(client->dev.fwnode, "reg", + reg, ARRAY_SIZE(reg))) + lvr = reg[2]; + } else if (is_acpi_device_node(client->dev.fwnode)) { + lvr = i3c_acpi_i2c_get_lvr(client); + } return lvr; } From 1489e051e4ed2ff3dde29c8ae15cab02dfbadbfc Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 10:49:34 -0400 Subject: [PATCH 81/88] i3c: master: Add support for devices using SETAASA JIRA: https://issues.redhat.com/browse/RHEL-143335 Upstream Status: https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git commit bbaf8733b84846897d2d3b997ce650dd2d2539a4 Author: Akhil R Date: Tue, 28 Jul 2026 06:59:46 +0000 i3c: master: Add support for devices using SETAASA Add support for devices using SETAASA, such as SPD5118 and SPD5108 attached to DDR5 memory modules that do not support ENTDAA. Follow the guidelines proposed by the MIPI Discovery and Configuration Specification [1] for discovering such devices. SETAASA (Set All Addresses to Static Address) differs from standard I3C address assignment that uses ENTDAA or SETDASA to assign dynamic addresses. Devices using SETAASA assign their pre-defined static addresses as their dynamic addresses during DAA, and it is not mandatory for these devices to implement standard CCC commands like GETPID, GETDCR, or GETBCR. For such devices, it is generally recommended to issue SETHID (specified by JEDEC JESD300) as a prerequisite for SETAASA to stop HID bit flipping. [1] https://www.mipi.org/mipi-disco-for-i3c-download Signed-off-by: Akhil R Link: https://www.mipi.org/mipi-disco-for-i3c-download Link: https://patch.msgid.link/20260728065955.809445-5-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 114 ++++++++++++++++++++++++++++++++++++- include/linux/i3c/ccc.h | 1 + include/linux/i3c/master.h | 15 +++++ 3 files changed, 127 insertions(+), 3 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index f1aa35ab1e76..a7118b84b31e 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -5,6 +5,7 @@ * Author: Boris Brezillon */ +#include #include #include #include @@ -1059,6 +1060,51 @@ static int i3c_master_rstdaa_locked(struct i3c_master_controller *master, return ret; } +/** + * i3c_master_setaasa_locked() - start a SETAASA procedure (Set All Addresses to Static Address) + * @master: I3C master object + * + * Send a SETAASA CCC command to set all attached I3C devices' dynamic addresses to + * their static address. + * + * This function must be called with the bus lock held in write mode. + * + * First, the SETHID CCC command is sent, followed by the SETAASA CCC. + * + * Return: 0 in case of success, a positive I3C error code if the error is + * one of the official Mx error codes, and a negative error code otherwise. + */ +static int i3c_master_setaasa_locked(struct i3c_master_controller *master) +{ + struct i3c_ccc_cmd_dest dest; + struct i3c_ccc_cmd cmd; + int ret; + + /* + * Send SETHID CCC command. Though it is a standard CCC command specified + * in JESD300-5, we are not defining a separate macro to be explicit that + * the value falls under the vendor specific range. + */ + i3c_ccc_cmd_dest_init(&dest, I3C_BROADCAST_ADDR, 0); + i3c_ccc_cmd_init(&cmd, false, I3C_CCC_VENDOR(0, true), &dest, 1); + ret = i3c_master_send_ccc_cmd_locked(master, &cmd); + i3c_ccc_cmd_dest_cleanup(&dest); + if (ret && cmd.err == I3C_ERROR_M2) + ret = 0; + if (ret) + return ret; + + /* Send SETAASA CCC command */ + i3c_ccc_cmd_dest_init(&dest, I3C_BROADCAST_ADDR, 0); + i3c_ccc_cmd_init(&cmd, false, I3C_CCC_SETAASA, &dest, 1); + ret = i3c_master_send_ccc_cmd_locked(master, &cmd); + i3c_ccc_cmd_dest_cleanup(&dest); + if (ret && cmd.err == I3C_ERROR_M2) + ret = 0; + + return ret; +} + /** * i3c_master_entdaa_locked() - start a DAA (Dynamic Address Assignment) * procedure @@ -1750,8 +1796,10 @@ static int i3c_master_early_i3c_dev_add(struct i3c_master_controller *master, int ret; i3cdev = i3c_master_alloc_i3c_dev(master, &info); - if (IS_ERR(i3cdev)) - return -ENOMEM; + if (IS_ERR(i3cdev)) { + ret = -ENOMEM; + goto err_reserve_addr; + } i3cdev->boardinfo = boardinfo; @@ -1759,6 +1807,22 @@ static int i3c_master_early_i3c_dev_add(struct i3c_master_controller *master, if (ret) goto err_free_dev; + /* + * For devices using SETAASA instead of ENTDAA, the address is statically + * assigned. Update the dynamic address to the provided static address. + * Reattach the I3C device after updating the dynamic address with the same + * static address. It is not mandatory for such devices to implement CCC + * commands like GETPID, GETDCR etc. Hence, we can return after reattaching. + */ + if (i3cdev->boardinfo->static_addr_method & I3C_ADDR_METHOD_SETAASA) { + i3cdev->info.dyn_addr = i3cdev->boardinfo->static_addr; + ret = i3c_master_reattach_i3c_dev_locked(i3cdev, 0); + if (ret) + goto err_detach_dev; + + return 0; + } + ret = i3c_master_setdasa_locked(master, i3cdev->info.static_addr, i3cdev->boardinfo->init_dyn_addr); if (ret) @@ -1781,6 +1845,16 @@ err_detach_dev: i3c_master_detach_i3c_dev(i3cdev); err_free_dev: i3c_master_free_i3c_dev(i3cdev); +err_reserve_addr: + /* + * A target using SETAASA may still get the static address on the + * SETAASA broadcast even if attach fails here. Keep the address + * reserved so that it is not assigned to another device during DAA. + */ + if (boardinfo->static_addr_method & I3C_ADDR_METHOD_SETAASA) + i3c_bus_set_addr_slot_status(&master->bus, + boardinfo->static_addr, + I3C_ADDR_SLOT_RSVD); return ret; } @@ -2206,6 +2280,19 @@ static int i3c_master_bus_init(struct i3c_master_controller *master) i3c_master_early_i3c_dev_add(master, i3cboardinfo); } + /* + * SETAASA is a broadcast CCC. Issue it after SETDASA so that devices + * configured for SETDASA (or supporting both methods) are assigned + * first, matching MIPI DISCO guidance to prefer SETDASA when both are + * available. Targets that already have a dynamic address ignore the + * later SETAASA broadcast. + */ + if (master->addr_method & I3C_ADDR_METHOD_SETAASA) { + ret = i3c_master_setaasa_locked(master); + if (ret) + goto err_rstdaa; + } + ret = i3c_master_do_daa(master); if (ret) goto err_rstdaa; @@ -2540,7 +2627,7 @@ i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, struct i3c_dev_boardinfo *boardinfo; struct device *dev = &master->dev; enum i3c_addr_slot_status addrstatus; - u32 init_dyn_addr = 0; + u32 init_dyn_addr = 0, static_addr_method = 0; boardinfo = devm_kzalloc(dev, sizeof(*boardinfo), GFP_KERNEL); if (!boardinfo) @@ -2558,7 +2645,19 @@ i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, boardinfo->static_addr = reg[0]; + if (!fwnode_property_read_u32(fwnode, "mipi-i3c-static-method", &static_addr_method)) + boardinfo->static_addr_method = static_addr_method & + (I3C_ADDR_METHOD_SETDASA | I3C_ADDR_METHOD_SETAASA); + if (!fwnode_property_read_u32(fwnode, "assigned-address", &init_dyn_addr)) { + /* + * When a device advertises both SETDASA and SETAASA, an explicit + * dynamic address selects SETDASA (MIPI DISCO prefers it); drop + * SETAASA so it is not used for this device. + */ + if (boardinfo->static_addr_method & I3C_ADDR_METHOD_SETDASA) + boardinfo->static_addr_method &= ~I3C_ADDR_METHOD_SETAASA; + if (init_dyn_addr > I3C_MAX_ADDR) return -EINVAL; @@ -2568,6 +2667,14 @@ i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, return -EINVAL; } + if (boardinfo->static_addr_method & I3C_ADDR_METHOD_SETAASA) { + /* For SETAASA, static address is taken as the dynamic address. */ + init_dyn_addr = boardinfo->static_addr; + } + + /* Update the address methods required for device discovery */ + master->addr_method |= boardinfo->static_addr_method; + boardinfo->pid = ((u64)reg[1] << 32) | reg[2]; if ((boardinfo->pid & GENMASK_ULL(63, 48)) || @@ -3202,6 +3309,7 @@ int i3c_master_register(struct i3c_master_controller *master, master->dev.release = i3c_masterdev_release; master->ops = ops; master->secondary = secondary; + master->addr_method = I3C_ADDR_METHOD_SETDASA; INIT_LIST_HEAD(&master->boardinfo.i2c); INIT_LIST_HEAD(&master->boardinfo.i3c); diff --git a/include/linux/i3c/ccc.h b/include/linux/i3c/ccc.h index ad59a4ae60d1..a145d766ab6f 100644 --- a/include/linux/i3c/ccc.h +++ b/include/linux/i3c/ccc.h @@ -32,6 +32,7 @@ #define I3C_CCC_DEFSLVS I3C_CCC_ID(0x8, true) #define I3C_CCC_ENTTM I3C_CCC_ID(0xb, true) #define I3C_CCC_ENTHDR(x) I3C_CCC_ID(0x20 + (x), true) +#define I3C_CCC_SETAASA I3C_CCC_ID(0x29, true) /* Unicast-only commands */ #define I3C_CCC_SETDASA I3C_CCC_ID(0x7, false) diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 978e7a37bd7d..449ee2df51a5 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -174,6 +174,14 @@ struct i3c_device_ibi_info { * assigned a dynamic address by the master. Will be used during * bus initialization to assign it a specific dynamic address * before starting DAA (Dynamic Address Assignment) + * @static_addr_method: Bitmap describing which methods of Dynamic Address + * Assignment from a Static Address are supported by this I3C Target. + * A value of 1 in a bit position indicates that the I3C target + * supports that method, and a value of 0 indicates that the I3C + * target does not support that method. + * Bit 0: SETDASA + * Bit 1: SETAASA + * All other bits are reserved. * @pid: I3C Provisioned ID exposed by the device. This is a unique identifier * that may be used to attach boardinfo to i3c_dev_desc when the device * does not have a static address @@ -189,6 +197,7 @@ struct i3c_dev_boardinfo { struct list_head node; u8 init_dyn_addr; u8 static_addr; + u8 static_addr_method; u64 pid; struct fwnode_handle *fwnode; }; @@ -516,6 +525,11 @@ struct i3c_master_controller_ops { * @boardinfo.i2c: list of I2C boardinfo objects * @boardinfo: board-level information attached to devices connected on the bus * @bus: I3C bus exposed by this master + * @addr_method: Bitmap describing which methods of Address Assignment required + * to be run for discovering all the devices on the bus. + * Bit 0: SETDASA + * Bit 1: SETAASA + * All other bits are reserved. * @wq: freezable workqueue which can be used by master * drivers if they need to postpone operations that need to take place * in a thread context. Typical examples are Hot Join processing which @@ -543,6 +557,7 @@ struct i3c_master_controller { struct list_head i2c; } boardinfo; struct i3c_bus bus; + u8 addr_method; struct workqueue_struct *wq; unsigned int dev_nack_retry_count; }; From 9f1efe5d60ff8276dfaa3a6510ab2ee6a01d4e04 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 10:50:02 -0400 Subject: [PATCH 82/88] i3c: master: Add support for devices without PID JIRA: https://issues.redhat.com/browse/RHEL-143335 Upstream Status: https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git commit a1dd42fb82fa71bf4cb6462b4803b55d844c1286 Author: Akhil R Date: Tue, 28 Jul 2026 06:59:47 +0000 i3c: master: Add support for devices without PID Devices using SETAASA for address assignment are not required to have a 48-bit PID according to the I3C specification. Allow such devices to register and use the static address where PID was required. Reviewed-by: Frank Li Signed-off-by: Akhil R Link: https://patch.msgid.link/20260728065955.809445-6-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 52 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index a7118b84b31e..7c802f957a83 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1882,8 +1882,17 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) desc->dev->dev.type = &i3c_device_type; desc->dev->dev.bus = &i3c_bus_type; desc->dev->dev.release = i3c_device_release; - dev_set_name(&desc->dev->dev, "%d-%llx", master->bus.id, - desc->info.pid); + + /* + * For devices without PID (e.g., SETAASA devices), use + * static address for naming instead. + */ + if (desc->info.pid) + dev_set_name(&desc->dev->dev, "%d-%llx", master->bus.id, + desc->info.pid); + else + dev_set_name(&desc->dev->dev, "%d-%02x", master->bus.id, + desc->info.static_addr); if (desc->boardinfo) device_set_node(&desc->dev->dev, desc->boardinfo->fwnode); @@ -2335,8 +2344,18 @@ static void i3c_master_attach_boardinfo(struct i3c_dev_desc *i3cdev) struct i3c_dev_boardinfo *i3cboardinfo; list_for_each_entry(i3cboardinfo, &master->boardinfo.i3c, node) { - if (i3cdev->info.pid != i3cboardinfo->pid) - continue; + /* + * For devices without PID (e.g., SETAASA devices), match by + * static address. For devices with PID, match by PID. + */ + if (i3cboardinfo->pid) { + if (i3cdev->info.pid != i3cboardinfo->pid) + continue; + } else { + if (!i3cboardinfo->static_addr || + i3cdev->info.static_addr != i3cboardinfo->static_addr) + continue; + } i3cdev->boardinfo = i3cboardinfo; i3cdev->info.static_addr = i3cboardinfo->static_addr; @@ -2350,8 +2369,12 @@ i3c_master_search_i3c_dev_duplicate(struct i3c_dev_desc *refdev) struct i3c_master_controller *master = i3c_dev_get_master(refdev); struct i3c_dev_desc *i3cdev; + if (!refdev->info.pid) + return NULL; + i3c_bus_for_each_i3cdev(&master->bus, i3cdev) { - if (i3cdev != refdev && i3cdev->info.pid == refdev->info.pid) + if (i3cdev != refdev && i3cdev->info.pid && + i3cdev->info.pid == refdev->info.pid) return i3cdev; } @@ -2677,9 +2700,16 @@ i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, boardinfo->pid = ((u64)reg[1] << 32) | reg[2]; - if ((boardinfo->pid & GENMASK_ULL(63, 48)) || - I3C_PID_RND_LOWER_32BITS(boardinfo->pid)) - return -EINVAL; + /* For SETAASA devices, validate the static address instead of PID */ + if (boardinfo->static_addr_method & I3C_ADDR_METHOD_SETAASA) { + if (!boardinfo->static_addr) + return -EINVAL; + } else { + if (!I3C_PID_MANUF_ID(boardinfo->pid) || + (boardinfo->pid & GENMASK_ULL(63, 48)) || + I3C_PID_RND_LOWER_32BITS(boardinfo->pid)) + return -EINVAL; + } boardinfo->init_dyn_addr = init_dyn_addr; boardinfo->fwnode = fwnode_handle_get(fwnode); @@ -2702,10 +2732,10 @@ static int i3c_master_add_of_dev(struct i3c_master_controller *master, return ret; /* - * The manufacturer ID can't be 0. If reg[1] == 0 that means we're - * dealing with an I2C device. + * I3C device should have either the manufacturer ID specified or the + * address discovery method specified. Else treat it as an I2C device. */ - if (!reg[1]) + if (!reg[1] && !fwnode_property_present(fwnode, "mipi-i3c-static-method")) ret = i3c_master_add_i2c_boardinfo(master, fwnode, reg); else ret = i3c_master_add_i3c_boardinfo(master, fwnode, reg); From 0f0debfd0c10e7b4f90d558521cd824ddf0985dc Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 10:50:19 -0400 Subject: [PATCH 83/88] i3c: master: match I3C device through DT and ACPI JIRA: https://issues.redhat.com/browse/RHEL-143335 Upstream Status: https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git commit 3456baa2110c1fa31e7609ace50117346b99c3d4 Author: Akhil R Date: Tue, 28 Jul 2026 06:59:48 +0000 i3c: master: match I3C device through DT and ACPI SETAASA-based devices cannot always be identified by PID or DCR; the standard I3C id_table matching may not be applicable. Allow such devices to match through Device Tree or ACPI. Reviewed-by: Frank Li Signed-off-by: Akhil R Link: https://patch.msgid.link/20260728065955.809445-7-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 7c802f957a83..2d724b9c0901 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -343,15 +344,32 @@ static int i3c_device_match(struct device *dev, const struct device_driver *drv) { struct i3c_device *i3cdev; const struct i3c_driver *i3cdrv; + u8 static_addr_method = 0; if (dev->type != &i3c_device_type) return 0; i3cdev = dev_to_i3cdev(dev); i3cdrv = drv_to_i3cdrv(drv); - if (i3c_device_match_id(i3cdev, i3cdrv->id_table)) + + if (i3cdev->desc && i3cdev->desc->boardinfo) + static_addr_method = i3cdev->desc->boardinfo->static_addr_method; + + /* + * SETAASA-based devices need not always have a matching ID since + * it is not mandatory for such devices to implement deviceinfo + * CCC commands. Allow them to register through DT or ACPI. + */ + if (i3cdrv->id_table && i3c_device_match_id(i3cdev, i3cdrv->id_table)) return 1; + if (static_addr_method & I3C_ADDR_METHOD_SETAASA) { + if (of_driver_match_device(dev, drv)) + return 1; + if (acpi_driver_match_device(dev, drv)) + return 1; + } + return 0; } From 2ad09a58c99251c9d644cc86a6662e1c4934deda Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 10:50:34 -0400 Subject: [PATCH 84/88] i3c: dw-i3c-master: Add SETAASA as supported CCC JIRA: https://issues.redhat.com/browse/RHEL-143335 Upstream Status: https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git commit fc6963aad560aebeb926e677aa26746c42c6223b Author: Akhil R Date: Tue, 28 Jul 2026 06:59:49 +0000 i3c: dw-i3c-master: Add SETAASA as supported CCC Add SETAASA and SETHID to the supported list of CCC commands for DesignWare I3C host controller. SETAASA is a broadcast command that assigns predefined static addresses to all I3C devices on the bus. SETHID is to stop HID bit flipping by the SPD Hub to which the SPD devices are connected. It is a prerequisite command to be sent before SETAASA as recommended by JESD300-5 and JESD403 sideband bus specifications. Reviewed-by: Frank Li Signed-off-by: Akhil R Link: https://patch.msgid.link/20260728065955.809445-8-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- 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 83991c92b8cf..9cd8e6c9c225 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -317,6 +317,8 @@ static bool dw_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m, case I3C_CCC_GETSTATUS: case I3C_CCC_GETMXDS: case I3C_CCC_GETHDRCAP: + case I3C_CCC_SETAASA: + case I3C_CCC_VENDOR(0, true): /* SETHID */ return true; default: return false; From ceabcd2bceea7b783b97248782d7648df9b4e657 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 10:54:03 -0400 Subject: [PATCH 85/88] i3c: dw-i3c-master: Add ACPI core clock frequency quirk JIRA: https://issues.redhat.com/browse/RHEL-143335 Upstream Status: https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git commit 0fd9549975b5b8186a78b654e02518113cc2ac93 Author: Akhil R Date: Tue, 28 Jul 2026 06:59:50 +0000 i3c: dw-i3c-master: Add ACPI core clock frequency quirk Some ACPI-enumerated devices like Tegra410 do not expose the controller core clock through the clk framework. Unlike device tree, ACPI on Arm does not model clock providers. The hardware is expected to have its clocks enabled by firmware before the OS takes over. Make the core clock optional and allow selected ACPI devices to provide the core clock rate through the "clock-frequency" _DSD property when the core clock is absent. Resolve device quirks before acquiring the core clock so platforms without the ACPI skip-clock quirk still fail probe immediately when the clock is missing, before any MMIO access. Signed-off-by: Akhil R Link: https://patch.msgid.link/20260728065955.809445-9-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 45 ++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 9cd8e6c9c225..06bb88921d97 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -242,6 +242,7 @@ /* List of quirks */ #define AMD_I3C_OD_PP_TIMING BIT(1) #define DW_I3C_DISABLE_RUNTIME_PM_QUIRK BIT(2) +#define DW_I3C_ACPI_SKIP_CLK_RST BIT(3) struct dw_i3c_cmd { u32 cmd_lo; @@ -564,13 +565,28 @@ static void dw_i3c_master_set_intr_regs(struct dw_i3c_master *master) writel(IBI_REQ_REJECT_ALL, master->regs + IBI_MR_REQ_REJECT); } +static unsigned long dw_i3c_master_get_core_rate(struct dw_i3c_master *master) +{ + unsigned int core_rate_prop; + + if (master->core_clk) + return clk_get_rate(master->core_clk); + + if (device_property_read_u32(master->dev, "clock-frequency", &core_rate_prop)) { + dev_err(master->dev, "missing clock-frequency property\n"); + return 0; + } + + return core_rate_prop; +} + static int dw_i3c_clk_cfg(struct dw_i3c_master *master) { unsigned long core_rate, core_period; u32 scl_timing; u8 hcnt, lcnt; - core_rate = clk_get_rate(master->core_clk); + core_rate = dw_i3c_master_get_core_rate(master); if (!core_rate) return -EINVAL; @@ -623,7 +639,7 @@ static int dw_i2c_clk_cfg(struct dw_i3c_master *master) u16 hcnt, lcnt; u32 scl_timing; - core_rate = clk_get_rate(master->core_clk); + core_rate = dw_i3c_master_get_core_rate(master); if (!core_rate) return -EINVAL; @@ -1589,14 +1605,28 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, master->dev = &pdev->dev; + if (has_acpi_companion(&pdev->dev)) { + quirks = (unsigned long)device_get_match_data(&pdev->dev); + } else if (pdev->dev.of_node) { + drvdata = device_get_match_data(&pdev->dev); + if (drvdata) + quirks = drvdata->flags; + } + master->quirks = quirks; + master->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(master->regs)) return PTR_ERR(master->regs); - master->core_clk = devm_clk_get_enabled(&pdev->dev, NULL); + master->core_clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); if (IS_ERR(master->core_clk)) return PTR_ERR(master->core_clk); + if (!master->core_clk && !(master->quirks & DW_I3C_ACPI_SKIP_CLK_RST)) { + dev_err(&pdev->dev, "missing core clock\n"); + return -EINVAL; + } + master->pclk = devm_clk_get_optional_enabled(&pdev->dev, "pclk"); if (IS_ERR(master->pclk)) return PTR_ERR(master->pclk); @@ -1652,15 +1682,6 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, master->has_ibi_data = true; writel(thld_ctrl, master->regs + QUEUE_THLD_CTRL); - if (has_acpi_companion(&pdev->dev)) { - quirks = (unsigned long)device_get_match_data(&pdev->dev); - } else if (pdev->dev.of_node) { - drvdata = device_get_match_data(&pdev->dev); - if (drvdata) - quirks = drvdata->flags; - } - master->quirks = quirks; - /* Keep controller enabled by preventing runtime suspend */ if (master->quirks & DW_I3C_DISABLE_RUNTIME_PM_QUIRK) pm_runtime_get_noresume(&pdev->dev); From ee21e7ef71e4b36b20beaaaa21cfed29b7ed9177 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 10:54:16 -0400 Subject: [PATCH 86/88] i3c: dw-i3c-master: Add ACPI ID for Tegra410 JIRA: https://issues.redhat.com/browse/RHEL-143335 Upstream Status: https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git commit 97d7cfb62a215ed5c64c6e7772545be4afab167f Author: Akhil R Date: Tue, 28 Jul 2026 06:59:51 +0000 i3c: dw-i3c-master: Add ACPI ID for Tegra410 Update variable names to generic names and add Tegra410 ACPI ID to support the I3C controller in Tegra410, which is a DesignWare I3C host controller. Reviewed-by: Frank Li Signed-off-by: Akhil R Link: https://patch.msgid.link/20260728065955.809445-10-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/i3c/master/dw-i3c-master.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 06bb88921d97..3e082382e008 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1876,11 +1876,12 @@ static const struct of_device_id dw_i3c_master_of_match[] = { }; MODULE_DEVICE_TABLE(of, dw_i3c_master_of_match); -static const struct acpi_device_id amd_i3c_device_match[] = { +static const struct acpi_device_id dw_i3c_master_acpi_match[] = { { "AMDI0015", AMD_I3C_OD_PP_TIMING }, + { "NVDA2018", DW_I3C_ACPI_SKIP_CLK_RST }, { } }; -MODULE_DEVICE_TABLE(acpi, amd_i3c_device_match); +MODULE_DEVICE_TABLE(acpi, dw_i3c_master_acpi_match); static struct platform_driver dw_i3c_driver = { .probe = dw_i3c_probe, @@ -1889,7 +1890,7 @@ static struct platform_driver dw_i3c_driver = { .driver = { .name = "dw-i3c-master", .of_match_table = dw_i3c_master_of_match, - .acpi_match_table = amd_i3c_device_match, + .acpi_match_table = dw_i3c_master_acpi_match, .pm = &dw_i3c_pm_ops, }, }; From 58a66d6af1302c8f68d02a2e86bb0c212817632b Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 10:54:24 -0400 Subject: [PATCH 87/88] hwmon: spd5118: Remove 16-bit addressing JIRA: https://issues.redhat.com/browse/RHEL-143335 Upstream Status: https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git commit 7bf5a11dde2c957bf6751f6ba28fef66e848ac6d Author: Akhil R Date: Tue, 28 Jul 2026 06:59:52 +0000 hwmon: spd5118: Remove 16-bit addressing The intent of introducing 16-bit addressing was to support I3C, but it turns out that I3C does not require reading the Legacy Mode register, nor any specific encoding for page translation. The testing of 16-bit code was limited and there are no known users for this feature. Remove the sections that support 16-bit addressing and prepare the driver to support I3C appropriately. Suggested-by: Guenter Roeck Acked-by: Guenter Roeck Signed-off-by: Akhil R Link: https://patch.msgid.link/20260728065955.809445-11-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/hwmon/spd5118.c | 79 +++-------------------------------------- 1 file changed, 5 insertions(+), 74 deletions(-) diff --git a/drivers/hwmon/spd5118.c b/drivers/hwmon/spd5118.c index 6e4d46a7e269..7201941452aa 100644 --- a/drivers/hwmon/spd5118.c +++ b/drivers/hwmon/spd5118.c @@ -66,9 +66,6 @@ static const unsigned short normal_i2c[] = { #define SPD5118_EEPROM_BASE 0x80 #define SPD5118_EEPROM_SIZE (SPD5118_PAGE_SIZE * SPD5118_NUM_PAGES) -#define PAGE_ADDR0(page) (((page) & BIT(0)) << 6) -#define PAGE_ADDR1_4(page) (((page) & GENMASK(4, 1)) >> 1) - /* Temperature unit in millicelsius */ #define SPD5118_TEMP_UNIT (MILLIDEGREE_PER_DEGREE / 4) /* Representable temperature range in millicelsius */ @@ -78,7 +75,6 @@ static const unsigned short normal_i2c[] = { struct spd5118_data { struct regmap *regmap; struct mutex nvmem_lock; - bool is_16bit; }; /* hwmon */ @@ -354,12 +350,7 @@ static ssize_t spd5118_nvmem_read_page(struct spd5118_data *data, char *buf, if (offset + count > SPD5118_PAGE_SIZE) count = SPD5118_PAGE_SIZE - offset; - if (data->is_16bit) { - addr = SPD5118_EEPROM_BASE | PAGE_ADDR0(page) | - (PAGE_ADDR1_4(page) << 8); - } else { - addr = page * 0x100 + SPD5118_EEPROM_BASE; - } + addr = page * 0x100 + SPD5118_EEPROM_BASE; err = regmap_bulk_read(regmap, addr + offset, buf, count); if (err) return err; @@ -479,15 +470,6 @@ static const struct regmap_config spd5118_regmap8_config = { .num_ranges = ARRAY_SIZE(spd5118_i2c_regmap_range_cfg), }; -static const struct regmap_config spd5118_regmap16_config = { - .reg_bits = 16, - .val_bits = 8, - .max_register = 0x7ff, - .writeable_reg = spd5118_writeable_reg, - .volatile_reg = spd5118_volatile_reg, - .cache_type = REGCACHE_MAPLE, -}; - static int spd5118_suspend(struct device *dev) { struct spd5118_data *data = dev_get_drvdata(dev); @@ -525,8 +507,7 @@ static int spd5118_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(spd5118_pm_ops, spd5118_suspend, spd5118_resume); -static int spd5118_common_probe(struct device *dev, struct regmap *regmap, - bool is_16bit) +static int spd5118_common_probe(struct device *dev, struct regmap *regmap) { unsigned int capability, revision, vendor, bank; struct spd5118_data *data; @@ -543,8 +524,6 @@ static int spd5118_common_probe(struct device *dev, struct regmap *regmap, if (!(capability & SPD5118_CAP_TS_SUPPORT)) return -ENODEV; - data->is_16bit = is_16bit; - err = regmap_read(regmap, SPD5118_REG_REVISION, &revision); if (err) return err; @@ -686,69 +665,21 @@ static int spd5118_i2c_init(struct i2c_client *client) return 0; } -/* - * 16-bit addressing note: - * - * If I2C_FUNC_I2C is not supported by an I2C adapter driver, regmap uses - * SMBus operations as alternative. To simulate a read operation with a 16-bit - * address, it writes the address using i2c_smbus_write_byte_data(), followed - * by one or more calls to i2c_smbus_read_byte() to read the data. - * Per spd5118 standard, a read operation after writing the address must start - * with (Repeat Start). However, a SMBus read byte operation starts with - * (Start). This resets the register address in the spd5118 chip. As result, - * i2c_smbus_read_byte() always returns data from register address 0x00. - * - * A working alternative to access chips with 16-bit register addresses in the - * absence of I2C_FUNC_I2C support is not known. - * - * For this reason, 16-bit addressing can only be supported with I2C if the - * adapter supports I2C_FUNC_I2C. - * - * For I2C, the addressing mode selected by the BIOS must not be changed. - * Experiments show that at least some PC BIOS versions will not change the - * addressing mode on a soft reboot and end up in setup, claiming that some - * configuration change happened. This will happen again after a power cycle, - * which does reset the addressing mode. To prevent this from happening, - * detect if 16-bit addressing is enabled and always use the currently - * configured addressing mode. - */ - static int spd5118_i2c_probe(struct i2c_client *client) { - const struct regmap_config *config; struct device *dev = &client->dev; struct regmap *regmap; - int err, mode; - bool is_16bit; + int err; err = spd5118_i2c_init(client); if (err) return err; - mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE); - if (mode < 0) - return mode; - - is_16bit = mode & SPD5118_LEGACY_MODE_ADDR; - if (is_16bit) { - /* - * See 16-bit addressing note above explaining why it is - * necessary to check for I2C_FUNC_I2C support here. - */ - if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { - dev_err(dev, "Adapter does not support 16-bit register addresses\n"); - return -ENODEV; - } - config = &spd5118_regmap16_config; - } else { - config = &spd5118_regmap8_config; - } - - regmap = devm_regmap_init_i2c(client, config); + regmap = devm_regmap_init_i2c(client, &spd5118_regmap8_config); if (IS_ERR(regmap)) return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n"); - return spd5118_common_probe(dev, regmap, is_16bit); + return spd5118_common_probe(dev, regmap); } static const struct i2c_device_id spd5118_i2c_id[] = { From b8fcbdf74296b8d42eca81242a4b00fa25c7b2c5 Mon Sep 17 00:00:00 2001 From: Jennifer Berringer Date: Thu, 6 Aug 2026 10:54:49 -0400 Subject: [PATCH 88/88] hwmon: spd5118: Add I3C support JIRA: https://issues.redhat.com/browse/RHEL-143335 Upstream Status: https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git commit 9cd3db0cda4741befa6de8af94eb371b9ec6d057 Author: Akhil R Date: Tue, 28 Jul 2026 06:59:53 +0000 hwmon: spd5118: Add I3C support Add a regmap config and a probe function to support I3C-based communication with SPD5118 devices. On an I3C bus, SPD5118 devices are enumerated via SETAASA and always require an ACPI or device tree entry. Device matching is hence through the OF match tables only and does not need an I3C class match table. The device identity is verified in the type registers before proceeding to the common probe function. Acked-by: Guenter Roeck Signed-off-by: Akhil R Link: https://patch.msgid.link/20260728065955.809445-12-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni Signed-off-by: Jennifer Berringer --- drivers/hwmon/Kconfig | 9 ++++--- drivers/hwmon/spd5118.c | 56 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 769b7a64b6a1..9766f3ec99ce 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2201,12 +2201,15 @@ config SENSORS_INA3221 config SENSORS_SPD5118 tristate "SPD5118 Compliant Temperature Sensors" - depends on I2C + depends on I3C_OR_I2C select REGMAP_I2C + select REGMAP_I3C if I3C help If you say yes here you get support for SPD5118 (JEDEC JESD300) - compliant temperature sensors. Such sensors are found on DDR5 memory - modules. + compliant temperature sensors using I2C or I3C bus interface. + Such sensors are found on DDR5 memory modules. + + This driver supports both I2C and I3C interfaces. This driver can also be built as a module. If so, the module will be called spd5118. diff --git a/drivers/hwmon/spd5118.c b/drivers/hwmon/spd5118.c index 7201941452aa..4e2dfbc41b37 100644 --- a/drivers/hwmon/spd5118.c +++ b/drivers/hwmon/spd5118.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -470,6 +471,27 @@ static const struct regmap_config spd5118_regmap8_config = { .num_ranges = ARRAY_SIZE(spd5118_i2c_regmap_range_cfg), }; +/* + * SPD5118 2-byte register address format (JESD300-5, Tables 7 & 20): + * Byte 1 (on wire first): MemReg | BlkAddr[0] | Address[5:0] + * Byte 2 (on wire second): 0000 | BlkAddr[4:1] + * + * The address byte (with MemReg and lower address bits) must be sent first, + * followed by the upper block address byte. With regmap 16-bit register + * format, this maps to little-endian: the low byte of the 16-bit value is + * transmitted first. No range config is needed since I3C does not use MR11 + * page switching. + */ +static const struct regmap_config spd5118_regmap_i3c_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x7ff, + .reg_format_endian = REGMAP_ENDIAN_LITTLE, + .writeable_reg = spd5118_writeable_reg, + .volatile_reg = spd5118_volatile_reg, + .cache_type = REGCACHE_MAPLE, +}; + static int spd5118_suspend(struct device *dev) { struct spd5118_data *data = dev_get_drvdata(dev); @@ -707,7 +729,39 @@ static struct i2c_driver spd5118_i2c_driver = { .address_list = IS_ENABLED(CONFIG_SENSORS_SPD5118_DETECT) ? normal_i2c : NULL, }; -module_i2c_driver(spd5118_i2c_driver); +/* I3C */ + +static int spd5118_i3c_probe(struct i3c_device *i3cdev) +{ + struct device *dev = i3cdev_to_dev(i3cdev); + struct regmap *regmap; + u8 regval[2]; + int err; + + regmap = devm_regmap_init_i3c(i3cdev, &spd5118_regmap_i3c_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n"); + + err = regmap_bulk_read(regmap, SPD5118_REG_TYPE, regval, 2); + if (err) + return dev_err_probe(dev, err, "failed to read device type\n"); + + if (regval[0] != 0x51 || regval[1] != 0x18) + return -ENODEV; + + return spd5118_common_probe(dev, regmap); +} + +static struct i3c_driver spd5118_i3c_driver = { + .driver = { + .name = "spd5118_i3c", + .of_match_table = spd5118_of_ids, + .pm = pm_sleep_ptr(&spd5118_pm_ops), + }, + .probe = spd5118_i3c_probe, +}; + +module_i3c_i2c_driver(spd5118_i3c_driver, &spd5118_i2c_driver); MODULE_AUTHOR("René Rebe "); MODULE_AUTHOR("Guenter Roeck ");