From 8a53f9102a0d3eeb8784999f925028acf339c276 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 17 Jun 2026 23:01:38 +0800 Subject: [PATCH 01/70] i3c: master: adi: initialize the lock before enabling interrupts adi_i3c_master_probe() requests the IRQ and unmasks REG_IRQ_PENDING_CMDR before the controller's IBI state, transfer queue list and transfer queue lock are initialized. A pending CMDR interrupt can therefore run adi_i3c_master_irq() and take master->xferqueue.lock before the dynamic lock has been initialized. This issue was found by our static analysis tool and then manually reviewed against the current tree. The grounded PoC kept the probe ordering and the IRQ path adi_i3c_master_probe() -> adi_i3c_master_irq() -> xferqueue.lock, with a pending CMDR interrupt arriving after REG_IRQ_PENDING_CMDR is unmasked. Lockdep reported: INFO: trying to register non-static key. you didn't initialize this object before use? lock_acquire+0xbb/0x290 _raw_spin_lock_irqsave+0x36/0x60 adi_i3c_master_irq+0x32/0x56 [vuln_msv] adi_i3c_master_probe+0x5a/0xf47 [vuln_msv] Initialize the transfer queue and IBI state before requesting and unmasking the IRQ. Fixes: a79ac2cdc91d ("i3c: master: Add driver for Analog Devices I3C Controller IP") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Reviewed-by: Frank Li Link: https://patch.msgid.link/20260617150138.628578-1-runyu.xiao@seu.edu.cn Signed-off-by: Alexandre Belloni --- drivers/i3c/master/adi-i3c-master.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c index 047081c9f064..e29aac286957 100644 --- a/drivers/i3c/master/adi-i3c-master.c +++ b/drivers/i3c/master/adi-i3c-master.c @@ -964,17 +964,9 @@ static int adi_i3c_master_probe(struct platform_device *pdev) writel(0x00, master->regs + REG_ENABLE); writel(0x00, master->regs + REG_IRQ_MASK); - ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0, - dev_name(&pdev->dev), master); - if (ret) - return ret; - platform_set_drvdata(pdev, master); master->free_rr_slots = GENMASK(ADI_MAX_DEVS, 1); - - writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK); - spin_lock_init(&master->ibi.lock); master->ibi.num_slots = 15; master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots, @@ -986,6 +978,13 @@ static int adi_i3c_master_probe(struct platform_device *pdev) spin_lock_init(&master->xferqueue.lock); INIT_LIST_HEAD(&master->xferqueue.list); + ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0, + dev_name(&pdev->dev), master); + if (ret) + return ret; + + writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK); + return i3c_master_register(&master->base, &pdev->dev, &adi_i3c_master_ops, false); } From f479d3033e5abc8f2daad275505a041166482063 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 23 Jun 2026 14:08:21 +0800 Subject: [PATCH 02/70] i3c: master: svc: report timeout waiting for STOP idle svc_i3c_master_xfer() emits STOP or force-exit for the final transfer and then waits for the controller state to become idle, but ignores readl_poll_timeout(). The function can therefore return success while the controller is still not idle. Return the idle-wait error through the existing warning/FIFO cleanup path so the caller observes the failed transfer without emitting a second STOP after the final STOP or force-exit has already been sent. Signed-off-by: Pengpeng Hou Reviewed-by: Frank Li Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260623060821.23238-1-pengpeng@iscas.ac.cn Signed-off-by: Alexandre Belloni --- drivers/i3c/master/svc-i3c-master.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index 93805df8a940..ae38ceee5abf 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -1488,8 +1488,11 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master, svc_i3c_master_emit_force_exit(master); /* Wait idle if stop is sent. */ - readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg, - SVC_I3C_MSTATUS_STATE_IDLE(reg), 0, 1000); + ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg, + SVC_I3C_MSTATUS_STATE_IDLE(reg), + 0, 1000); + if (ret) + goto cleanup; } return 0; @@ -1500,6 +1503,7 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master, else svc_i3c_master_emit_force_exit(master); +cleanup: svc_i3c_master_clear_merrwarn(master); svc_i3c_master_flush_fifo(master); From e2bda39d7f9f285ec803e200b5c1f17143d0b483 Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Wed, 24 Jun 2026 13:04:33 +0800 Subject: [PATCH 03/70] i3c: master: svc: bound IBI payload to the requested max_payload_len svc_i3c_master_handle_ibi() reads the IBI payload from the RX FIFO into the IBI slot. The loop is bounded by the hardware FIFO size (SVC_I3C_FIFO_SIZE), not by the slot size. slot->data points into the IBI pool, which i3c_generic_ibi_alloc_pool() sizes at max_payload_len per slot. svc_i3c_master_request_ibi() only rejects a max_payload_len larger than SVC_I3C_FIFO_SIZE, so a driver can request a smaller one. mctp-i3c requests 1. Each readsb() then copies the controller RXCOUNT bytes (up to 31) with no check against the slot size. A device that sends more bytes than the slot holds writes past slot->data, an out-of-bounds write into the IBI pool. Bound the loop by dev->ibi->max_payload_len and clamp each read to the space left in the slot, the same way dw-i3c does. A device can still send more than the requested payload. Flush the leftover bytes from the RX FIFO so they do not leak into the next transfer. Fixes: dd3c52846d59 ("i3c: master: svc: Add Silvaco I3C master driver") Cc: stable@vger.kernel.org Co-developed-by: Kaixuan Li Signed-off-by: Kaixuan Li Signed-off-by: Maoyi Xie Reviewed-by: Frank Li Link: https://patch.msgid.link/178227747353.2931373.15868718612134648277@maoyixie.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/svc-i3c-master.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index ae38ceee5abf..486c22840ea9 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -455,14 +455,22 @@ static int svc_i3c_master_handle_ibi(struct svc_i3c_master *master, buf = slot->data; while (SVC_I3C_MSTATUS_RXPEND(readl(master->regs + SVC_I3C_MSTATUS)) && - slot->len < SVC_I3C_FIFO_SIZE) { + slot->len < dev->ibi->max_payload_len) { mdatactrl = readl(master->regs + SVC_I3C_MDATACTRL); count = SVC_I3C_MDATACTRL_RXCOUNT(mdatactrl); + count = min(count, dev->ibi->max_payload_len - slot->len); readsb(master->regs + SVC_I3C_MRDATAB, buf, count); slot->len += count; buf += count; } + /* + * The device may have sent more than the requested payload. Drop the + * extra bytes so they do not leak into the next transfer. + */ + if (SVC_I3C_MSTATUS_RXPEND(readl(master->regs + SVC_I3C_MSTATUS))) + writel(SVC_I3C_MDATACTRL_FLUSHRB, master->regs + SVC_I3C_MDATACTRL); + master->ibi.tbq_slot = slot; return 0; From 038cf48b3170af26a70bf2dee4f8c3ac910f5176 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 30 Jun 2026 10:29:04 -0700 Subject: [PATCH 04/70] i3c: dw: avoid shift-out-of-bounds when DAA assigns no devices On an empty bus ENTDAA assigns nothing, so cmd->rx_len (the count of addresses left unassigned) equals master->maxdevs. The GENMASK() index master->maxdevs - cmd->rx_len - 1 then becomes -1, which trips up UBSAN. This happens every time on boot on a Gigabyte/AMD server: UBSAN: shift-out-of-bounds in drivers/i3c/master/dw-i3c-master.c:905:12 shift exponent 64 is too large for 64-bit type 'long unsigned int' CPU: 7 UID: 0 PID: 963 Comm: (udev-worker) Not tainted 7.0.11-200.fc44.x86_64 #1 PREEMPT(lazy) Hardware name: Giga Computing E163-Z34-AAH1-000/MZ33-DC1-000, BIOS R32_F45 04/01/2026 Call Trace: dump_stack_lvl+0x5d/0x80 ubsan_epilogue+0x5/0x2b __ubsan_handle_shift_out_of_bounds.cold+0xd7/0x1ab dw_i3c_master_daa.cold+0x1b/0x96 [dw_i3c_master] i3c_master_do_daa_ext.part.0+0x3e/0xf0 [i3c] Skip the mask when no new device was assigned. Fixes: 1dd728f5d4d4 ("i3c: master: Add driver for Synopsys DesignWare IP") Signed-off-by: Jakub Kicinski Reviewed-by: Frank Li Link: https://patch.msgid.link/20260630172904.2662160-1-kuba@kernel.org Signed-off-by: Alexandre Belloni --- drivers/i3c/master/dw-i3c-master.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 2f8c0c4683e0..dc3b74822f8e 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -888,7 +888,15 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m) if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT)) dw_i3c_master_dequeue_xfer(master, xfer); - newdevs = GENMASK(master->maxdevs - cmd->rx_len - 1, 0); + /* + * cmd->rx_len holds the number of addresses ENTDAA left unassigned. + * On an empty bus rx_len == maxdevs, so avoid GENMASK(-1, 0). + */ + if (cmd->rx_len >= master->maxdevs) + newdevs = 0; + else + newdevs = GENMASK(master->maxdevs - cmd->rx_len - 1, 0); + newdevs &= ~olddevs; for (pos = 0; pos < master->maxdevs; pos++) { From 74be657d98a8d684c0475f3cbd450ef2a30ffc73 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Thu, 2 Jul 2026 21:36:44 +0300 Subject: [PATCH 05/70] i3c: master: Fix device_register() error path When device_register() fails in i3c_master_register_new_i3c_devs(), put_device() is called to drop the reference taken by device_register(). That drops the last reference, so the device's release callback i3c_device_release() runs and frees the i3c_device. Two problems follow from that: i3c_device_release() does WARN_ON(i3cdev->desc), so it warns because desc->dev->desc still points back at the descriptor. Clear it before calling put_device(). After put_device() frees the i3c_device, desc->dev is left pointing at freed memory, so clear desc->dev as well. That prevents, for example, i3c_master_unregister_i3c_devs() seeing desc->dev as non-NULL and dereferencing it. Reported-by: sashiko-bot@kernel.org Link: https://lore.kernel.org/linux-i3c/20260701203053.8F3971F000E9@smtp.kernel.org/ Fixes: cab63f6488761 ("i3c: Fix potential refcount leak in i3c_master_register_new_i3c_devs") Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260702183644.60827-1-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index f1be38a640ca..ac408086ddcc 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1934,7 +1934,9 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) if (ret) { dev_err(&master->dev, "Failed to add I3C device (err = %d)\n", ret); + desc->dev->desc = NULL; put_device(&desc->dev->dev); + desc->dev = NULL; } } } From cae22bd965bbee732993f3df1337c9b4167f943d Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Wed, 8 Jul 2026 00:17:37 -0700 Subject: [PATCH 06/70] i3c: ccc: Add actual_len to struct i3c_ccc_cmd_payload Add actual_len to struct i3c_ccc_cmd_payload so drivers can report how many bytes were received on a GET CCC without overwriting the requested buffer length in len. Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Tze Yee Ng Reviewed-by: Alexandre Mergnat Reviewed-by: Frank Li Tested-by: Tommaso Merciai Tested-by: Claudiu Beznea # on RZ/G3S Link: https://patch.msgid.link/e452777c3a9be734a97e20b9822d8a4264ceadba.1783493868.git.tze.yee.ng@altera.com Signed-off-by: Alexandre Belloni --- include/linux/i3c/ccc.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/linux/i3c/ccc.h b/include/linux/i3c/ccc.h index ad59a4ae60d1..d8052949e57e 100644 --- a/include/linux/i3c/ccc.h +++ b/include/linux/i3c/ccc.h @@ -343,11 +343,13 @@ struct i3c_ccc_getxtime { /** * struct i3c_ccc_cmd_payload - CCC payload * - * @len: payload length + * @len: requested payload length + * @actual_len: number of bytes received on a GET CCC (filled by the driver) * @data: payload data. This buffer must be DMA-able */ struct i3c_ccc_cmd_payload { u16 len; + u16 actual_len; void *data; }; From cb92910153f4ea6d721779f72ae165cdbe7222f0 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Wed, 8 Jul 2026 00:17:38 -0700 Subject: [PATCH 07/70] i3c: master: Report actual GET CCC payload length on success Set dests[].payload.actual_len on successful GET CCC transfers in I3C master drivers so the core can distinguish requested and received buffer lengths. Switch core GET helpers to use actual_len instead of len when interpreting GET CCC results. Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Tze Yee Ng Reviewed-by: Alexandre Mergnat Reviewed-by: Frank Li Tested-by: Tommaso Merciai Tested-by: Claudiu Beznea # on RZ/G3S Link: https://patch.msgid.link/6c66571ae7166aa4b87616d900e6d643631ac355.1783493868.git.tze.yee.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 11 ++++++----- drivers/i3c/master/adi-i3c-master.c | 3 +++ drivers/i3c/master/dw-i3c-master.c | 5 ++++- drivers/i3c/master/i3c-master-cdns.c | 3 +++ drivers/i3c/master/mipi-i3c-hci/core.c | 5 +++-- drivers/i3c/master/renesas-i3c.c | 14 +++++++++++--- drivers/i3c/master/svc-i3c-master.c | 4 ++-- 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index ac408086ddcc..84cda266d8e3 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -952,6 +952,7 @@ static void *i3c_ccc_cmd_dest_init(struct i3c_ccc_cmd_dest *dest, u8 addr, { dest->addr = addr; dest->payload.len = payloadlen; + dest->payload.actual_len = 0; if (payloadlen) dest->payload.data = kzalloc(payloadlen, GFP_KERNEL); else @@ -1374,7 +1375,7 @@ static int i3c_master_getmrl_locked(struct i3c_master_controller *master, if (ret) goto out; - switch (dest.payload.len) { + switch (dest.payload.actual_len) { case 3: info->max_ibi_len = mrl->ibi_len; fallthrough; @@ -1409,7 +1410,7 @@ static int i3c_master_getmwl_locked(struct i3c_master_controller *master, if (ret) goto out; - if (dest.payload.len != sizeof(*mwl)) { + if (dest.payload.actual_len != sizeof(*mwl)) { ret = -EIO; goto out; } @@ -1448,14 +1449,14 @@ static int i3c_master_getmxds_locked(struct i3c_master_controller *master, goto out; } - if (dest.payload.len != 2 && dest.payload.len != 5) { + if (dest.payload.actual_len != 2 && dest.payload.actual_len != 5) { ret = -EIO; goto out; } info->max_read_ds = getmaxds->maxrd; info->max_write_ds = getmaxds->maxwr; - if (dest.payload.len == 5) + if (dest.payload.actual_len == 5) info->max_read_turnaround = getmaxds->maxrdturn[0] | ((u32)getmaxds->maxrdturn[1] << 8) | ((u32)getmaxds->maxrdturn[2] << 16); @@ -1484,7 +1485,7 @@ static int i3c_master_gethdrcap_locked(struct i3c_master_controller *master, if (ret) goto out; - if (dest.payload.len != 1) { + if (dest.payload.actual_len != 1) { ret = -EIO; goto out; } diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c index e29aac286957..cbf33f640c18 100644 --- a/drivers/i3c/master/adi-i3c-master.c +++ b/drivers/i3c/master/adi-i3c-master.c @@ -246,6 +246,7 @@ static void adi_i3c_master_end_xfer_locked(struct adi_i3c_master *master, if (cmd->cmd0 & REG_CMD_FIFO_0_RNW) { rx_len = min_t(u32, REG_CMDR_FIFO_XFER_BYTES(cmdr), cmd->rx_len); adi_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len); + cmd->rx_len = rx_len; } cmd->error = REG_CMDR_FIFO_ERROR(cmdr); } @@ -360,6 +361,8 @@ static int adi_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, adi_i3c_master_unqueue_xfer(master, xfer); cmd->err = adi_i3c_cmd_get_err(&xfer->cmds[0]); + if (!xfer->ret && cmd->rnw) + cmd->dests[0].payload.actual_len = ccmd->rx_len; return xfer->ret; } diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index dc3b74822f8e..97b8f4d8564c 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -780,7 +780,10 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc) dw_i3c_master_dequeue_xfer(master, xfer); ret = xfer->ret; - if (xfer->cmds[0].error == RESPONSE_ERROR_IBA_NACK) + cmd = &xfer->cmds[0]; + if (!ret) + ccc->dests[0].payload.actual_len = cmd->rx_len; + if (cmd->error == RESPONSE_ERROR_IBA_NACK) ccc->err = I3C_ERROR_M2; return ret; diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c index 6d221596ea35..2d98c1ce9b12 100644 --- a/drivers/i3c/master/i3c-master-cdns.c +++ b/drivers/i3c/master/i3c-master-cdns.c @@ -573,6 +573,7 @@ static void cdns_i3c_master_end_xfer_locked(struct cdns_i3c_master *master, cmd = &xfer->cmds[CMDR_CMDID(cmdr)]; rx_len = min_t(u32, CMDR_XFER_BYTES(cmdr), cmd->rx_len); cdns_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len); + cmd->rx_len = rx_len; cmd->error = CMDR_ERROR(cmdr); } @@ -714,6 +715,8 @@ static int cdns_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, ret = xfer->ret; cmd->err = cdns_i3c_cmd_get_err(&xfer->cmds[0]); + if (!ret && cmd->rnw) + cmd->dests[0].payload.actual_len = ccmd->rx_len; cdns_i3c_master_free_xfer(xfer); return ret; diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index e80aa1f5722e..cfe9b5390b56 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -375,7 +375,7 @@ static int i3c_hci_send_ccc_cmd(struct i3c_master_controller *m, goto out; for (i = prefixed; i < nxfers; i++) { if (ccc->rnw) - ccc->dests[i - prefixed].payload.len = + ccc->dests[i - prefixed].payload.actual_len = RESP_DATA_LENGTH(xfer[i].response); switch (RESP_STATUS(xfer[i].response)) { case RESP_SUCCESS: @@ -392,7 +392,8 @@ static int i3c_hci_send_ccc_cmd(struct i3c_master_controller *m, if (ccc->rnw) dev_dbg(&hci->master.dev, "got: %*ph", - ccc->dests[0].payload.len, ccc->dests[0].payload.data); + ccc->dests[0].payload.actual_len, + ccc->dests[0].payload.data); out: hci_free_xfer(xfer, nxfers); diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index f39c449922ca..25a2b2ed618e 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -805,6 +805,8 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m, ret = xfer->ret; if (ret) ccc->err = I3C_ERROR_M2; + else if (ccc->rnw) + ccc->dests[0].payload.actual_len = cmd->rx_count; return ret; } @@ -1072,10 +1074,16 @@ static irqreturn_t renesas_i3c_resp_isr(int irq, void *data) break; case I3C_INTERNAL_STATE_CONTROLLER_READ: case I3C_INTERNAL_STATE_CONTROLLER_COMMAND_READ: - if (NDBSTLV0_RDBLV(renesas_readl(i3c->regs, NDBSTLV0)) && !cmd->err) - bytes_remaining = data_len - cmd->rx_count; + if (!cmd->err) { + u32 rx_count = min(cmd->rx_count, data_len); - i3c_readl_fifo(i3c->regs + NTDTBP0, cmd->rx_buf, bytes_remaining); + bytes_remaining = data_len - rx_count; + if (bytes_remaining) + i3c_readl_fifo(i3c->regs + NTDTBP0, + cmd->rx_buf + rx_count, + bytes_remaining); + cmd->rx_count = data_len; + } renesas_clear_bit(i3c->regs, NTIE, NTIE_RDBFIE0); break; default: diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c index 486c22840ea9..e700392f0801 100644 --- a/drivers/i3c/master/svc-i3c-master.c +++ b/drivers/i3c/master/svc-i3c-master.c @@ -1725,8 +1725,8 @@ static int svc_i3c_master_send_direct_ccc_cmd(struct svc_i3c_master *master, svc_i3c_master_dequeue_xfer(master, xfer); mutex_unlock(&master->lock); - if (cmd->actual_len != xfer_len) - ccc->dests[0].payload.len = cmd->actual_len; + if (ccc->rnw) + ccc->dests[0].payload.actual_len = cmd->actual_len; ret = xfer->ret; svc_i3c_master_free_xfer(xfer); From 790354f7c4f70cd4be2bd53a62d87ebd53343846 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Wed, 8 Jul 2026 00:17:39 -0700 Subject: [PATCH 08/70] i3c: master: dw: Map CCC hardware errors to I3C M0/M2 Map DesignWare I3C master CCC hardware errors to I3C M0/M2 error codes. I3C_ERROR_M2 is reported only for broadcast address-header NACK (RESPONSE_ERROR_IBA_NACK). Target address NACK remains -EIO. Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Tze Yee Ng Reviewed-by: Alexandre Mergnat Reviewed-by: Frank Li Tested-by: Tommaso Merciai Tested-by: Claudiu Beznea # on RZ/G3S Link: https://patch.msgid.link/05994f0479041f3d8d199babe2a4535d6dcba3bf.1783493868.git.tze.yee.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/dw-i3c-master.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 97b8f4d8564c..84ffcb189338 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -493,6 +493,7 @@ static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr) break; case RESPONSE_ERROR_PARITY: case RESPONSE_ERROR_IBA_NACK: + case RESPONSE_ERROR_ADDRESS_NACK: case RESPONSE_ERROR_TRANSF_ABORT: case RESPONSE_ERROR_CRC: case RESPONSE_ERROR_FRAME: @@ -502,7 +503,6 @@ static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr) ret = -ENOSPC; break; case RESPONSE_ERROR_I2C_W_NACK_ERR: - case RESPONSE_ERROR_ADDRESS_NACK: default: ret = -EINVAL; break; @@ -708,12 +708,29 @@ static void dw_i3c_master_bus_cleanup(struct i3c_master_controller *m) dw_i3c_master_disable(master); } +static enum i3c_error_code dw_i3c_ccc_map_err(u8 dw_err) +{ + switch (dw_err) { + case RESPONSE_ERROR_IBA_NACK: + return I3C_ERROR_M2; + case RESPONSE_ERROR_CRC: + case RESPONSE_ERROR_PARITY: + case RESPONSE_ERROR_FRAME: + case RESPONSE_ERROR_TRANSF_ABORT: + return I3C_ERROR_M0; + default: + return I3C_ERROR_UNKNOWN; + } +} + static int dw_i3c_ccc_set(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc) { struct dw_i3c_cmd *cmd; int ret, pos = 0; + ccc->err = I3C_ERROR_UNKNOWN; + if (ccc->id & I3C_CCC_DIRECT) { pos = dw_i3c_master_get_addr_pos(master, ccc->dests[0].addr); if (pos < 0) @@ -742,8 +759,8 @@ static int dw_i3c_ccc_set(struct dw_i3c_master *master, dw_i3c_master_dequeue_xfer(master, xfer); ret = xfer->ret; - if (xfer->cmds[0].error == RESPONSE_ERROR_IBA_NACK) - ccc->err = I3C_ERROR_M2; + cmd = &xfer->cmds[0]; + ccc->err = dw_i3c_ccc_map_err(cmd->error); return ret; } @@ -753,6 +770,8 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc) struct dw_i3c_cmd *cmd; int ret, pos; + ccc->err = I3C_ERROR_UNKNOWN; + pos = dw_i3c_master_get_addr_pos(master, ccc->dests[0].addr); if (pos < 0) return pos; @@ -781,10 +800,9 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc) ret = xfer->ret; cmd = &xfer->cmds[0]; + ccc->err = dw_i3c_ccc_map_err(cmd->error); if (!ret) ccc->dests[0].payload.actual_len = cmd->rx_len; - if (cmd->error == RESPONSE_ERROR_IBA_NACK) - ccc->err = I3C_ERROR_M2; return ret; } From 09361ed979e68622977751f6274f280d7aec5cb6 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Wed, 8 Jul 2026 00:17:40 -0700 Subject: [PATCH 09/70] i3c: master: Validate GET CCC payload length and retry Direct GET once Add retries to struct i3c_ccc_cmd. Validate GET payload length in i3c_master_send_ccc_cmd_locked() after a successful transfer. Retry failed Direct GET CCCs up to cmd->retries times when the driver reports failure or an I3C error; validation failures are not retried. SET CCCs are not retried by default. Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Tze Yee Ng Reviewed-by: Alexandre Mergnat Reviewed-by: Frank Li Tested-by: Tommaso Merciai Tested-by: Claudiu Beznea # on RZ/G3S Link: https://patch.msgid.link/b467f01edfaaa0710f30e719ce7f2753b06c1a3f.1783493868.git.tze.yee.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 59 ++++++++++++++++++++++++++++++++++++++--- include/linux/i3c/ccc.h | 5 ++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 84cda266d8e3..9c32fd0c5fef 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -966,17 +966,46 @@ static void i3c_ccc_cmd_dest_cleanup(struct i3c_ccc_cmd_dest *dest) kfree(dest->payload.data); } -static void i3c_ccc_cmd_init(struct i3c_ccc_cmd *cmd, bool rnw, u8 id, - struct i3c_ccc_cmd_dest *dests, - unsigned int ndests) +static void i3c_ccc_cmd_init_retries(struct i3c_ccc_cmd *cmd, bool rnw, u8 id, + struct i3c_ccc_cmd_dest *dests, + unsigned int ndests, unsigned int retries) { cmd->rnw = rnw ? 1 : 0; cmd->id = id; cmd->dests = dests; cmd->ndests = ndests; + cmd->retries = retries; cmd->err = I3C_ERROR_UNKNOWN; } +static void i3c_ccc_cmd_init(struct i3c_ccc_cmd *cmd, bool rnw, u8 id, + struct i3c_ccc_cmd_dest *dests, + unsigned int ndests) +{ + i3c_ccc_cmd_init_retries(cmd, rnw, id, dests, ndests, + rnw ? I3C_CCC_RETRIES : 0); +} + +static int i3c_ccc_validate_payload_len(struct i3c_ccc_cmd *cmd) +{ + unsigned int i; + + if (!cmd->rnw) + return 0; + + for (i = 0; i < cmd->ndests; i++) { + struct i3c_ccc_cmd_payload *p = &cmd->dests[i].payload; + + if (p->actual_len > p->len) + return -EIO; + + if (p->len && p->actual_len != p->len) + return -EIO; + } + + return 0; +} + /** * i3c_master_send_ccc_cmd_locked() - send a CCC (Common Command Codes) * @master: master used to send frames on the bus @@ -988,6 +1017,9 @@ static void i3c_ccc_cmd_init(struct i3c_ccc_cmd *cmd, bool rnw, u8 id, static int i3c_master_send_ccc_cmd_locked(struct i3c_master_controller *master, struct i3c_ccc_cmd *cmd) { + unsigned int attempt, max_attempts; + int ret; + if (!cmd || !master) return -EINVAL; @@ -1005,7 +1037,25 @@ static int i3c_master_send_ccc_cmd_locked(struct i3c_master_controller *master, !master->ops->supports_ccc_cmd(master, cmd)) return -EOPNOTSUPP; - return master->ops->send_ccc_cmd(master, cmd); + max_attempts = cmd->retries + 1; + ret = -EIO; + for (attempt = 0; attempt < max_attempts; attempt++) { + unsigned int i; + + if (cmd->rnw) + for (i = 0; i < cmd->ndests; i++) + cmd->dests[i].payload.actual_len = 0; + + cmd->err = I3C_ERROR_UNKNOWN; + ret = master->ops->send_ccc_cmd(master, cmd); + if (!ret && cmd->err == I3C_ERROR_UNKNOWN) + break; + } + + if (!ret) + ret = i3c_ccc_validate_payload_len(cmd); + + return ret; } static struct i2c_dev_desc * @@ -1444,6 +1494,7 @@ static int i3c_master_getmxds_locked(struct i3c_master_controller *master, * while expecting shorter length from this CCC command. */ dest.payload.len -= 3; + i3c_ccc_cmd_init(&cmd, true, I3C_CCC_GETMXDS, &dest, 1); ret = i3c_master_send_ccc_cmd_locked(master, &cmd); if (ret) goto out; diff --git a/include/linux/i3c/ccc.h b/include/linux/i3c/ccc.h index d8052949e57e..2506d83b8255 100644 --- a/include/linux/i3c/ccc.h +++ b/include/linux/i3c/ccc.h @@ -12,6 +12,8 @@ #include /* I3C CCC (Common Command Codes) related definitions */ +#define I3C_CCC_RETRIES 1 + #define I3C_CCC_DIRECT BIT(7) #define I3C_CCC_ID(id, broadcast) \ @@ -374,12 +376,15 @@ struct i3c_ccc_cmd_dest { * @ndests: number of destinations. Should always be one for broadcast commands * @dests: array of destinations and associated payload for this CCC. Most of * the time, only one destination is provided + * @retries: number of times to retry a failed Direct GET CCC (see + * &I3C_CCC_RETRIES) * @err: I3C error code */ struct i3c_ccc_cmd { u8 rnw; u8 id; unsigned int ndests; + unsigned int retries; struct i3c_ccc_cmd_dest *dests; enum i3c_error_code err; }; From b32f4ed0cc069206cb7b6baa0654b14410a91440 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Wed, 8 Jul 2026 00:17:41 -0700 Subject: [PATCH 10/70] i3c: master: Add optional_bytes for variable-length GET CCC validation Add optional_bytes to struct i3c_ccc_cmd_payload so callers describe variable-length GET CCC responses. GETMRL and GETMXDS set optional_bytes at the call site. Extend i3c_ccc_validate_payload_len() to honour it. Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Tze Yee Ng Reviewed-by: Alexandre Mergnat Reviewed-by: Frank Li Tested-by: Tommaso Merciai Tested-by: Claudiu Beznea # on RZ/G3S Link: https://patch.msgid.link/2e07dc944eab1c4358be1da87fa5000e711ac8bd.1783493868.git.tze.yee.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 29 +++++++++++++++++++++++------ include/linux/i3c/ccc.h | 2 ++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 9c32fd0c5fef..fd3e79d10c84 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -953,6 +953,7 @@ static void *i3c_ccc_cmd_dest_init(struct i3c_ccc_cmd_dest *dest, u8 addr, dest->addr = addr; dest->payload.len = payloadlen; dest->payload.actual_len = 0; + dest->payload.optional_bytes = 0; if (payloadlen) dest->payload.data = kzalloc(payloadlen, GFP_KERNEL); else @@ -995,11 +996,19 @@ static int i3c_ccc_validate_payload_len(struct i3c_ccc_cmd *cmd) for (i = 0; i < cmd->ndests; i++) { struct i3c_ccc_cmd_payload *p = &cmd->dests[i].payload; + u16 min_len; + + if (p->optional_bytes > p->len) + return -EINVAL; if (p->actual_len > p->len) return -EIO; - if (p->len && p->actual_len != p->len) + if (!p->len) + continue; + + min_len = p->len - p->optional_bytes; + if (p->actual_len < min_len) return -EIO; } @@ -1414,10 +1423,14 @@ static int i3c_master_getmrl_locked(struct i3c_master_controller *master, return -ENOMEM; /* - * When the device does not have IBI payload GETMRL only returns 2 - * bytes of data. + * GETMRL returns 2 bytes (max read length) when the device does not + * advertise IBI payload, or 2 or 3 bytes when it does (the optional + * third byte is max IBI length). Use optional_bytes to allow either + * length when IBI payload is supported. */ - if (!(info->bcr & I3C_BCR_IBI_PAYLOAD)) + if (info->bcr & I3C_BCR_IBI_PAYLOAD) + dest.payload.optional_bytes = 1; + else dest.payload.len -= 1; i3c_ccc_cmd_init(&cmd, true, I3C_CCC_GETMRL, &dest, 1); @@ -1486,14 +1499,18 @@ static int i3c_master_getmxds_locked(struct i3c_master_controller *master, if (!getmaxds) return -ENOMEM; + dest.payload.optional_bytes = 3; + i3c_ccc_cmd_init(&cmd, true, I3C_CCC_GETMXDS, &dest, 1); ret = i3c_master_send_ccc_cmd_locked(master, &cmd); if (ret) { /* - * Retry when the device does not support max read turnaround - * while expecting shorter length from this CCC command. + * optional_bytes = 3 accepts a 2-byte response on the first + * attempt, so this fallback runs only when the 5-byte request + * fails rather than returning a short read. */ dest.payload.len -= 3; + dest.payload.optional_bytes = 0; i3c_ccc_cmd_init(&cmd, true, I3C_CCC_GETMXDS, &dest, 1); ret = i3c_master_send_ccc_cmd_locked(master, &cmd); if (ret) diff --git a/include/linux/i3c/ccc.h b/include/linux/i3c/ccc.h index 2506d83b8255..7ad677baf761 100644 --- a/include/linux/i3c/ccc.h +++ b/include/linux/i3c/ccc.h @@ -347,11 +347,13 @@ struct i3c_ccc_getxtime { * * @len: requested payload length * @actual_len: number of bytes received on a GET CCC (filled by the driver) + * @optional_bytes: GET CCCs may return up to this many fewer bytes than @len * @data: payload data. This buffer must be DMA-able */ struct i3c_ccc_cmd_payload { u16 len; u16 actual_len; + u16 optional_bytes; void *data; }; From 9ddb4d35817706c379d8d82c892895958c1571c4 Mon Sep 17 00:00:00 2001 From: Manikanta Guntupalli Date: Thu, 9 Jul 2026 12:12:32 +0530 Subject: [PATCH 11/70] dt-bindings: i3c: Add AMD I3C master controller support Add device tree binding documentation for the AMD I3C master controller version 1.0. Signed-off-by: Manikanta Guntupalli Co-developed-by: Shubham Patil Signed-off-by: Shubham Patil Reviewed-by: Rob Herring (Arm) Reviewed-by: Radhey Shyam Pandey Link: https://patch.msgid.link/20260709064233.1451482-2-shubhamsanjay.patil@amd.com Signed-off-by: Alexandre Belloni --- .../bindings/i3c/xlnx,axi-i3c-1.0.yaml | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml diff --git a/Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml b/Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml new file mode 100644 index 000000000000..2caa245a8656 --- /dev/null +++ b/Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i3c/xlnx,axi-i3c-1.0.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: AMD I3C master + +maintainers: + - Shubhrajyoti Datta + - Shubham Patil + +description: + The AXI-I3C IP is an I3C Controller with an AXI4-Lite interface, compatible + with the MIPI I3C Specification v1.1.1. The design includes bidirectional I/O + buffers that implement open collector drivers for the SDA and SCL signals. + External pull-up resistors are required to properly hold the bus at a Logic-1 + level when the drivers are released. + + For more details, please see https://docs.amd.com/r/en-US/pg439-axi-i3c + +properties: + compatible: + const: xlnx,axi-i3c-1.0 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - clocks + +allOf: + - $ref: i3c.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + + i3c@80000000 { + compatible = "xlnx,axi-i3c-1.0"; + reg = <0x80000000 0x10000>; + clocks = <&zynqmp_clk 71>; + interrupt-parent = <&imux>; + interrupts = ; + #address-cells = <3>; + #size-cells = <0>; + }; +... From 629a6ddd1d9a0f7cc609f11822fbd5fac819a475 Mon Sep 17 00:00:00 2001 From: Manikanta Guntupalli Date: Thu, 9 Jul 2026 12:12:33 +0530 Subject: [PATCH 12/70] i3c: master: Add driver for AMD AXI I3C master controller Add an I3C master driver and maintainers fragment for the AMD I3C bus controller. The driver currently supports the I3C bus operating in SDR mode, with features including Dynamic Address Assignment, private data transfers, and CCC transfers in both broadcast and direct modes. It also supports operation in I2C mode. The controller's data FIFOs are accessed big-endian; the driver performs this conversion locally using ioread32be()/iowrite32be() with the helpers, so it does not depend on any core FIFO-endianness helpers. Signed-off-by: Manikanta Guntupalli Co-developed-by: Shubhrajyoti Datta Signed-off-by: Shubhrajyoti Datta Co-developed-by: Shubham Patil Signed-off-by: Shubham Patil Reviewed-by: Frank Li Link: https://patch.msgid.link/20260709064233.1451482-3-shubhamsanjay.patil@amd.com Signed-off-by: Alexandre Belloni --- MAINTAINERS | 8 + drivers/i3c/master/Kconfig | 15 + drivers/i3c/master/Makefile | 1 + drivers/i3c/master/amd-i3c-master.c | 1124 +++++++++++++++++++++++++++ 4 files changed, 1148 insertions(+) create mode 100644 drivers/i3c/master/amd-i3c-master.c diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..81a9a02c919d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1044,6 +1044,14 @@ L: linux-sound@vger.kernel.org S: Supported F: sound/soc/amd/ +AMD AXI I3C MASTER DRIVER +M: Shubhrajyoti Datta +M: Shubham Patil +L: linux-i3c@lists.infradead.org +S: Maintained +F: Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml +F: drivers/i3c/master/amd-i3c-master.c + AMD AXI W1 DRIVER M: Kris Chaplin R: Thomas Delev diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig index 2609f2b18e0a..da96d2aaa399 100644 --- a/drivers/i3c/master/Kconfig +++ b/drivers/i3c/master/Kconfig @@ -86,3 +86,18 @@ config RENESAS_I3C This driver can also be built as a module. If so, the module will be called renesas-i3c. + +config AMD_AXI_I3C_MASTER + tristate "AMD AXI I3C Master driver" + depends on HAS_IOMEM + help + Support for the AMD AXI I3C master controller, a soft IP used on + AMD (Xilinx) FPGAs and adaptive SoCs with ARM or MicroBlaze + processors. + + The controller currently supports Standard Data Rate (SDR) mode. + Features include Dynamic Address Assignment, private transfers, + and CCC transfers in both broadcast and direct modes. + + This driver can also be built as a module. If so, the module + will be called amd-i3c-master. diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile index 816a227b6f7a..8d82196dcf83 100644 --- a/drivers/i3c/master/Makefile +++ b/drivers/i3c/master/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_AST2600_I3C_MASTER) += ast2600-i3c-master.o obj-$(CONFIG_SVC_I3C_MASTER) += svc-i3c-master.o obj-$(CONFIG_MIPI_I3C_HCI) += mipi-i3c-hci/ obj-$(CONFIG_RENESAS_I3C) += renesas-i3c.o +obj-$(CONFIG_AMD_AXI_I3C_MASTER) += amd-i3c-master.o diff --git a/drivers/i3c/master/amd-i3c-master.c b/drivers/i3c/master/amd-i3c-master.c new file mode 100644 index 000000000000..ef5ad5abb788 --- /dev/null +++ b/drivers/i3c/master/amd-i3c-master.c @@ -0,0 +1,1124 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * I3C master driver for the AMD I3C controller. + * + * Copyright (C) 2026, Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define XI3C_VERSION_OFFSET 0x00 /* Version Register */ +#define XI3C_RESET_OFFSET 0x04 /* Soft Reset Register */ +#define XI3C_CR_OFFSET 0x08 /* Control Register */ +#define XI3C_ADDRESS_OFFSET 0x0C /* Target Address Register */ +#define XI3C_SR_OFFSET 0x10 /* Status Register */ +#define XI3C_CMD_FIFO_OFFSET 0x20 /* I3C Command FIFO Register */ +#define XI3C_WR_FIFO_OFFSET 0x24 /* I3C Write Data FIFO Register */ +#define XI3C_RD_FIFO_OFFSET 0x28 /* I3C Read Data FIFO Register */ +#define XI3C_RESP_STATUS_FIFO_OFFSET 0x2C /* I3C Response status FIFO Register */ +#define XI3C_FIFO_LVL_STATUS_OFFSET 0x30 /* CMD slots free | WR-FIFO free (words) */ +#define XI3C_FIFO_LVL_STATUS_1_OFFSET 0x34 /* RESP fill | RD-FIFO fill level (words) */ +#define XI3C_SCL_HIGH_TIME_OFFSET 0x38 /* I3C SCL HIGH Register */ +#define XI3C_SCL_LOW_TIME_OFFSET 0x3C /* I3C SCL LOW Register */ +#define XI3C_SDA_HOLD_TIME_OFFSET 0x40 /* I3C SDA HOLD Register */ +#define XI3C_TSU_START_OFFSET 0x48 /* I3C START SETUP Register */ +#define XI3C_THD_START_OFFSET 0x4C /* I3C START HOLD Register */ +#define XI3C_TSU_STOP_OFFSET 0x50 /* I3C STOP Setup Register */ +#define XI3C_OD_SCL_HIGH_TIME_OFFSET 0x54 /* I3C OD SCL HIGH Register */ +#define XI3C_OD_SCL_LOW_TIME_OFFSET 0x58 /* I3C OD SCL LOW Register */ +#define XI3C_PID0_OFFSET 0x6C /* LSB 4 bytes of the PID */ +#define XI3C_PID1_BCR_DCR 0x70 /* MSB 2 bytes of the PID, BCR and DCR */ + +#define XI3C_CR_EN_MASK BIT(0) /* Core Enable */ +#define XI3C_CR_RESUME_MASK BIT(2) /* Core Resume */ +#define XI3C_SR_RESP_NOT_EMPTY_MASK BIT(4) /* Resp Fifo not empty status mask */ +#define XI3C_RD_FIFO_NOT_EMPTY_MASK BIT(15) /* Read Fifo not empty status mask */ + +#define XI3C_BCR_MASK GENMASK(23, 16) +#define XI3C_DCR_MASK GENMASK(31, 24) +#define XI3C_PID_MASK GENMASK_ULL(63, 16) +#define XI3C_TIMING_MASK GENMASK(17, 0) +#define XI3C_REV_NUM_MASK GENMASK(15, 8) +#define XI3C_PID1_MASK GENMASK(15, 0) +#define XI3C_FIFO_LEVEL_MASK GENMASK(15, 0) +#define XI3C_RESP_CODE_MASK GENMASK(8, 5) + +/* Controller response codes; PG439 page 34, Table 46 */ +#define XI3C_RESP_CODE_SUCCESS 0 /* Transfer completed OK */ +#define XI3C_RESP_CODE_NO_TARGET 2 /* 7E NACK: no target on bus */ +#define XI3C_RESP_CODE_NACK 3 /* Target NACK / DAA end */ +#define XI3C_RESP_CODE_READ_EARLY_TERM 5 /* Target ended read early (T-bit), short read */ + +/* xi3c_get_response() private return: read ended early with valid data; resume needed */ +#define XI3C_XFER_SHORT_READ 1 + +#define XI3C_RESP_BYTES_MASK GENMASK(20, 9) /* NUM_BYTES processed */ +#define XI3C_ADDR_MASK GENMASK(6, 0) +#define XI3C_FIFOS_RST_MASK GENMASK(4, 1) + +/* Command FIFO word layout (bit ranges encoded in the GENMASK/BIT args) */ +#define XI3C_CMD_TYPE GENMASK(3, 0) /* command type */ +#define XI3C_CMD_TERMINATE BIT(4) /* terminate (last cmd of xfer) */ +#define XI3C_CMD_ADDR GENMASK(15, 8) /* target address << 1 | RnW */ +#define XI3C_CMD_LEN GENMASK(27, 16) /* payload length in bytes */ +#define XI3C_CMD_TID GENMASK(31, 28) /* transfer ID */ + +/* tLOW_OD open-drain SCL low; MIPI I3C v1.1.1 Table 74 min 200 ns, 500 ns chosen conservatively */ +#define XI3C_OD_TLOW_NS 500 +/* Open-drain SCL high (tHIGH) max; MIPI I3C v1.1.1 Table 74 (41 ns) */ +#define XI3C_OD_THIGH_NS 41 +/* + * tSU_STA/tHD_STA/tSU_STO min, 400 kHz/Fm; MIPI I3C v1.1.1 Table 73 + * (mixed bus with legacy I2C device) + */ +#define XI3C_I2C_TCASMIN_NS 600 +/* tSU_STA/tHD_STA/tSU_STO min, 1 MHz/Fm+; MIPI I3C v1.1.1 Table 73 (pure I3C bus) */ +#define XI3C_TCASMIN_NS 260 +/* Max payload per transfer: 12-bit CMD length field (XI3C_CMD_LEN); PG439 page 32, Table 42 */ +#define XI3C_MAXDATA_LENGTH 4095 +/* Max enumerated devices; PG439 page 27, AXI_I3C_IBI_TARGET_ADDR register detail */ +#define XI3C_MAX_DEVS 128 +/* DAA target response = 48-bit PID + BCR + DCR = 8 bytes; PG439 page 28 */ +#define XI3C_DAA_SLAVEINFO_READ_BYTECOUNT 8 + +/* + * Min SDA hold cycles, rev 0 IP. Revision-specific value, PG439 page 24, + * AXI_I3C_SDA_HOLD_TIME register detail + */ +#define XI3C_THOLD_MIN_REV0 5 +/* + * Min SDA hold cycles, rev >= 1 IP. Revision-specific value, PG439 page 24, + * AXI_I3C_SDA_HOLD_TIME register detail + */ +#define XI3C_THOLD_MIN_REV1 6 +/* + * SCL/SDA pre-bias to account for the HW pipeline. PG439 page 24, + * AXI_I3C_SDA_HOLD_TIME register detail + */ +#define XI3C_CYCLE_ADJUST 2 +/* Short settling delay so the FIFO reset assert/de-assert takes effect before the FIFOs are used */ +#define XI3C_FIFO_RESET_DELAY_US 10 +/* + * Poll/sleep slice for FIFO and response waits: small enough to stay + * responsive, avoids busy-waiting + */ +#define XI3C_POLL_INTERVAL_US 10 + +#define XI3C_I2C_MODE 0 +#define XI3C_I2C_TID 0 +#define XI3C_SDR_MODE 1 +#define XI3C_SDR_TID 1 + +#define XI3C_WORD_LEN 4 + +/* Software guard: 500 ms (us, for readl_poll_timeout) to bail out if no response word arrives */ +#define XI3C_RESP_TIMEOUT_US 500000 +/* Software guard: 1 s (ms, for msecs_to_jiffies) to bail out if a transfer never completes */ +#define XI3C_XFER_TIMEOUT_MS 1000 + +struct xi3c_cmd { + const void *tx_buf; + void *rx_buf; + u16 tx_len; + u16 rx_len; + u16 rx_actual; + u8 addr; + u8 type; + u8 tid; + bool rnw; + bool is_daa; + bool continued; + enum i3c_error_code err; +}; + +struct xi3c_xfer { + unsigned int ncmds; + unsigned int nissued; + struct xi3c_cmd cmds[] __counted_by(ncmds); +}; + +/** + * struct xi3c_master - I3C master controller state. + * @base: I3C master controller embedded by the framework. + * @dev: Pointer to the backing device structure. + * @membase: Memory base of the HW registers. + * @pclk: Input clock driving the controller. + * @lock: Serializes transfers and CCC submission. + * @daa: ENTDAA enumeration state. + * @daa.addrs: Dynamic addresses assigned in enumeration order. + * @daa.index: Number of responders enumerated so far. + */ +struct xi3c_master { + struct i3c_master_controller base; + struct device *dev; + void __iomem *membase; + struct clk *pclk; + struct mutex lock; /* serializes transfers and CCC submission */ + struct { + u8 addrs[XI3C_MAX_DEVS]; + u8 index; + } daa; +}; + +static inline struct xi3c_master * +to_xi3c_master(struct i3c_master_controller *master) +{ + return container_of(master, struct xi3c_master, base); +} + +static inline u8 xi3c_get_revision_number(struct xi3c_master *master) +{ + return FIELD_GET(XI3C_REV_NUM_MASK, + ioread32(master->membase + XI3C_VERSION_OFFSET)); +} + +static inline u16 xi3c_wr_fifo_level(struct xi3c_master *master) +{ + return ioread32(master->membase + XI3C_FIFO_LVL_STATUS_OFFSET) & + XI3C_FIFO_LEVEL_MASK; +} + +static inline u16 xi3c_rd_fifo_level(struct xi3c_master *master) +{ + return ioread32(master->membase + XI3C_FIFO_LVL_STATUS_1_OFFSET) & + XI3C_FIFO_LEVEL_MASK; +} + +static inline bool xi3c_is_resp_available(struct xi3c_master *master) +{ + return FIELD_GET(XI3C_SR_RESP_NOT_EMPTY_MASK, + ioread32(master->membase + XI3C_SR_OFFSET)); +} + +static int xi3c_get_response(struct xi3c_master *master, struct xi3c_cmd *cmd) +{ + u32 response_data; + u32 resp_reg; + u8 code; + int ret; + + ret = readl_poll_timeout(master->membase + XI3C_SR_OFFSET, + resp_reg, + resp_reg & XI3C_SR_RESP_NOT_EMPTY_MASK, + XI3C_POLL_INTERVAL_US, XI3C_RESP_TIMEOUT_US); + if (ret) { + dev_err(master->dev, "XI3C response timeout\n"); + return ret; + } + + response_data = ioread32(master->membase + XI3C_RESP_STATUS_FIFO_OFFSET); + code = FIELD_GET(XI3C_RESP_CODE_MASK, response_data); + + switch (code) { + case XI3C_RESP_CODE_SUCCESS: + cmd->err = I3C_ERROR_UNKNOWN; + cmd->rx_actual = FIELD_GET(XI3C_RESP_BYTES_MASK, response_data); + return 0; + case XI3C_RESP_CODE_READ_EARLY_TERM: + /* Short read: valid data, but controller parked in STOP and must be resumed */ + cmd->err = I3C_ERROR_UNKNOWN; + cmd->rx_actual = FIELD_GET(XI3C_RESP_BYTES_MASK, response_data); + return XI3C_XFER_SHORT_READ; + case XI3C_RESP_CODE_NO_TARGET: + case XI3C_RESP_CODE_NACK: + cmd->err = I3C_ERROR_M2; + return cmd->is_daa ? -ENODEV : -EIO; + default: + cmd->err = I3C_ERROR_M0; + dev_err(master->dev, "XI3C transfer error, response code %u\n", + code); + return -EIO; + } +} + +static inline void xi3c_writesl_be(void __iomem *addr, const void *buffer, + unsigned int count) +{ + const u32 *buf = buffer; + + while (count--) + iowrite32be(get_unaligned(buf++), addr); +} + +static inline void xi3c_readsl_be(const void __iomem *addr, void *buffer, + unsigned int count) +{ + u32 *buf = buffer; + + while (count--) + put_unaligned(ioread32be(addr), buf++); +} + +static inline void xi3c_writel_fifo(void __iomem *addr, const void *buf, + int nbytes) +{ + xi3c_writesl_be(addr, buf, nbytes / 4); + if (nbytes & 3) { + u32 tmp = 0; + + memcpy(&tmp, (const u8 *)buf + (nbytes & ~3), nbytes & 3); + xi3c_writesl_be(addr, &tmp, 1); + } +} + +static inline void xi3c_readl_fifo(const void __iomem *addr, void *buf, + int nbytes) +{ + xi3c_readsl_be(addr, buf, nbytes / 4); + if (nbytes & 3) { + u32 tmp; + + xi3c_readsl_be(addr, &tmp, 1); + memcpy((u8 *)buf + (nbytes & ~3), &tmp, nbytes & 3); + } +} + +static void xi3c_master_write_to_cmdfifo(struct xi3c_master *master, + struct xi3c_cmd *cmd, u16 len) +{ + u32 transfer_cmd; + u8 addr; + + addr = ((cmd->addr & XI3C_ADDR_MASK) << 1) | (u8)cmd->rnw; + + transfer_cmd = FIELD_PREP(XI3C_CMD_TYPE, cmd->type); + transfer_cmd |= FIELD_PREP(XI3C_CMD_TERMINATE, !cmd->continued); + transfer_cmd |= FIELD_PREP(XI3C_CMD_ADDR, addr); + transfer_cmd |= FIELD_PREP(XI3C_CMD_TID, cmd->tid); + + /* + * For dynamic addressing, an additional 1-byte length must be added + * to the command FIFO to account for the address present in the TX FIFO + */ + if (cmd->is_daa) { + xi3c_writel_fifo(master->membase + XI3C_WR_FIFO_OFFSET, + cmd->tx_buf, cmd->tx_len); + + len++; + } + + transfer_cmd |= FIELD_PREP(XI3C_CMD_LEN, len); + iowrite32(transfer_cmd, master->membase + XI3C_CMD_FIFO_OFFSET); +} + +static inline void xi3c_master_enable(struct xi3c_master *master) +{ + iowrite32(ioread32(master->membase + XI3C_CR_OFFSET) | XI3C_CR_EN_MASK, + master->membase + XI3C_CR_OFFSET); +} + +static inline void xi3c_master_disable(struct xi3c_master *master) +{ + iowrite32(ioread32(master->membase + XI3C_CR_OFFSET) & ~XI3C_CR_EN_MASK, + master->membase + XI3C_CR_OFFSET); +} + +static inline void xi3c_master_resume(struct xi3c_master *master) +{ + iowrite32(ioread32(master->membase + XI3C_CR_OFFSET) | + XI3C_CR_RESUME_MASK, master->membase + XI3C_CR_OFFSET); +} + +static void xi3c_master_reset_fifos(struct xi3c_master *master) +{ + u32 data; + + /* Assert FIFO reset. */ + data = ioread32(master->membase + XI3C_RESET_OFFSET); + data |= XI3C_FIFOS_RST_MASK; + iowrite32(data, master->membase + XI3C_RESET_OFFSET); + /* Read-back flushes the posted write before the settling delay below. */ + ioread32(master->membase + XI3C_RESET_OFFSET); + fsleep(XI3C_FIFO_RESET_DELAY_US); + + /* De-assert FIFO reset, then wait for the FIFOs to come back up. */ + data &= ~XI3C_FIFOS_RST_MASK; + iowrite32(data, master->membase + XI3C_RESET_OFFSET); + ioread32(master->membase + XI3C_RESET_OFFSET); + fsleep(XI3C_FIFO_RESET_DELAY_US); +} + +static inline void xi3c_master_init(struct xi3c_master *master) +{ + /* Reset fifos */ + xi3c_master_reset_fifos(master); + + /* Enable controller */ + xi3c_master_enable(master); +} + +static inline void xi3c_master_reinit(struct xi3c_master *master) +{ + /* Reset fifos */ + xi3c_master_reset_fifos(master); + + /* Resume controller */ + xi3c_master_resume(master); +} + +static struct xi3c_xfer *xi3c_master_alloc_xfer(unsigned int ncmds) +{ + struct xi3c_xfer *xfer; + + xfer = kzalloc_flex(*xfer, cmds, ncmds, GFP_KERNEL); + if (!xfer) + return NULL; + + xfer->ncmds = ncmds; + + return xfer; +} + +static void xi3c_master_rd_from_rx_fifo(struct xi3c_master *master, + struct xi3c_cmd *cmd) +{ + u16 rx_data_available; + u16 copy_len; + u16 len; + + rx_data_available = xi3c_rd_fifo_level(master); + len = rx_data_available * XI3C_WORD_LEN; + + if (!len) + return; + + copy_len = min(len, cmd->rx_len); + xi3c_readl_fifo(master->membase + XI3C_RD_FIFO_OFFSET, + (u8 *)cmd->rx_buf, copy_len); + + cmd->rx_buf = (u8 *)cmd->rx_buf + copy_len; + cmd->rx_len -= copy_len; +} + +static int xi3c_master_read(struct xi3c_master *master, struct xi3c_cmd *cmd) +{ + unsigned long timeout; + u32 status_reg; + int ret; + + if (cmd->rx_len > XI3C_MAXDATA_LENGTH) + return -EINVAL; + /* + * Zero-length probes (e.g. i2cdetect) legitimately pass a NULL + * buffer; only a non-zero length requires one. + */ + if (cmd->rx_len && !cmd->rx_buf) + return -EINVAL; + + /* Fill command fifo */ + xi3c_master_write_to_cmdfifo(master, cmd, cmd->rx_len); + + if (!cmd->rx_len) + return 0; + + ret = readl_poll_timeout(master->membase + XI3C_SR_OFFSET, + status_reg, + status_reg & (XI3C_RD_FIFO_NOT_EMPTY_MASK | + XI3C_SR_RESP_NOT_EMPTY_MASK), + XI3C_POLL_INTERVAL_US, XI3C_RESP_TIMEOUT_US); + if (ret) { + dev_err(master->dev, "XI3C read timeout\n"); + return ret; + } + + if (!(status_reg & XI3C_RD_FIFO_NOT_EMPTY_MASK)) + return 0; + + timeout = jiffies + msecs_to_jiffies(XI3C_XFER_TIMEOUT_MS); + + /* Read data from rx fifo */ + while (cmd->rx_len > 0 && !xi3c_is_resp_available(master)) { + if (time_after(jiffies, timeout)) { + dev_err(master->dev, "XI3C read timeout\n"); + return -EIO; + } + xi3c_master_rd_from_rx_fifo(master, cmd); + usleep_range(XI3C_POLL_INTERVAL_US, 2 * XI3C_POLL_INTERVAL_US); + } + + /* Read remaining data */ + xi3c_master_rd_from_rx_fifo(master, cmd); + + return 0; +} + +static void xi3c_master_wr_to_tx_fifo(struct xi3c_master *master, + struct xi3c_cmd *cmd) +{ + u16 wrfifo_space; + u16 len; + + wrfifo_space = xi3c_wr_fifo_level(master); + if (cmd->tx_len > wrfifo_space * XI3C_WORD_LEN) + len = wrfifo_space * XI3C_WORD_LEN; + else + len = cmd->tx_len; + + if (len) { + xi3c_writel_fifo(master->membase + XI3C_WR_FIFO_OFFSET, cmd->tx_buf, + len); + + cmd->tx_buf = (const u8 *)cmd->tx_buf + len; + cmd->tx_len -= len; + } +} + +static int xi3c_master_write(struct xi3c_master *master, struct xi3c_cmd *cmd) +{ + unsigned long timeout; + u16 cmd_len; + + if (cmd->tx_len > XI3C_MAXDATA_LENGTH) + return -EINVAL; + /* + * Zero-length probes (e.g. i2cdetect) legitimately pass a NULL + * buffer; only a non-zero length requires one. + */ + if (cmd->tx_len && !cmd->tx_buf) + return -EINVAL; + + cmd_len = cmd->tx_len; + + /* Fill Tx fifo */ + xi3c_master_wr_to_tx_fifo(master, cmd); + + /* Write to command fifo */ + xi3c_master_write_to_cmdfifo(master, cmd, cmd_len); + + timeout = jiffies + msecs_to_jiffies(XI3C_XFER_TIMEOUT_MS); + /* Fill if any remaining data to tx fifo */ + while (cmd->tx_len > 0 && !xi3c_is_resp_available(master)) { + if (time_after(jiffies, timeout)) { + dev_err(master->dev, "XI3C write timeout\n"); + return -EIO; + } + + xi3c_master_wr_to_tx_fifo(master, cmd); + usleep_range(XI3C_POLL_INTERVAL_US, 2 * XI3C_POLL_INTERVAL_US); + } + + return 0; +} + +static int xi3c_master_xfer(struct xi3c_master *master, struct xi3c_cmd *cmd) +{ + int ret; + + if (cmd->rnw) + ret = xi3c_master_read(master, cmd); + else + ret = xi3c_master_write(master, cmd); + + if (ret) + goto err_xfer_out; + + ret = xi3c_get_response(master, cmd); + if (ret < 0) + goto err_xfer_out; + + /* Short read leaves the controller parked in STOP; resume it for the next command */ + if (ret == XI3C_XFER_SHORT_READ) + xi3c_master_resume(master); + + return 0; + +err_xfer_out: + xi3c_master_reinit(master); + return ret; +} + +static int xi3c_master_common_xfer(struct xi3c_master *master, + struct xi3c_xfer *xfer) +{ + unsigned int i; + int ret; + + guard(mutex)(&master->lock); + + for (i = 0; i < xfer->ncmds; i++) { + ret = xi3c_master_xfer(master, &xfer->cmds[i]); + if (ret) { + /* Count commands sent on the bus; the rest never ran */ + xfer->nissued = i + 1; + return ret; + } + } + + xfer->nissued = xfer->ncmds; + + return 0; +} + +static int xi3c_master_do_daa(struct i3c_master_controller *m) +{ + u8 (*pid_bufs)[XI3C_DAA_SLAVEINFO_READ_BYTECOUNT]; + struct xi3c_master *master = to_xi3c_master(m); + struct xi3c_cmd *daa_cmd; + struct xi3c_xfer *xfer; + int addr, ret, i; + u8 last_addr = 0; + u8 *pid_buf; + u8 ccc_id; + + xfer = xi3c_master_alloc_xfer(1); + if (!xfer) + return -ENOMEM; + + pid_bufs = kcalloc(XI3C_MAX_DEVS, XI3C_DAA_SLAVEINFO_READ_BYTECOUNT, + GFP_KERNEL); + if (!pid_bufs) { + ret = -ENOMEM; + goto out; + } + + /* Fill ENTDAA CCC */ + ccc_id = I3C_CCC_ENTDAA; + daa_cmd = &xfer->cmds[0]; + daa_cmd->addr = I3C_BROADCAST_ADDR; + daa_cmd->rnw = false; + daa_cmd->tx_buf = &ccc_id; + daa_cmd->tx_len = 1; + daa_cmd->type = XI3C_SDR_MODE; + daa_cmd->tid = XI3C_SDR_TID; + daa_cmd->continued = true; + + ret = xi3c_master_common_xfer(master, xfer); + if (ret) { + /* NACK on ENTDAA broadcast means no devices to enumerate */ + if (daa_cmd->err == I3C_ERROR_M2) + ret = 0; + goto err_daa; + } + + master->daa.index = 0; + + while (true) { + struct xi3c_cmd *cmd = &xfer->cmds[0]; + u8 daa_byte; + + /* Out of device slots; stop and keep what was enumerated */ + if (master->daa.index >= XI3C_MAX_DEVS) { + dev_warn(master->dev, + "DAA: reached %d devices, stopping enumeration\n", + XI3C_MAX_DEVS); + xi3c_master_reinit(master); + break; + } + + addr = i3c_master_get_free_addr(m, last_addr + 1); + if (addr < 0) { + dev_warn(master->dev, + "DAA: no free dynamic address, stopping enumeration\n"); + xi3c_master_reinit(master); + break; + } + + pid_buf = pid_bufs[master->daa.index]; + + daa_byte = (addr << 1) | (parity8(addr) ^ 1); + + cmd->tx_buf = &daa_byte; + cmd->tx_len = 1; + cmd->addr = I3C_BROADCAST_ADDR; + cmd->rnw = true; + cmd->rx_buf = pid_buf; + cmd->rx_len = XI3C_DAA_SLAVEINFO_READ_BYTECOUNT; + cmd->is_daa = true; + cmd->type = XI3C_SDR_MODE; + cmd->tid = XI3C_SDR_TID; + cmd->continued = true; + + ret = xi3c_master_common_xfer(master, xfer); + + /* -ENODEV: no more responders, enumeration complete */ + if (ret == -ENODEV) { + ret = 0; + break; + } + if (ret) + goto err_daa; + + master->daa.addrs[master->daa.index] = addr; + last_addr = addr; + master->daa.index++; + } + + for (i = 0; i < master->daa.index; i++) { + u64 pid; + + /* Ignore per-device add errors so one failure doesn't abort the rest */ + i3c_master_add_i3c_dev_locked(m, master->daa.addrs[i]); + + pid = FIELD_GET(XI3C_PID_MASK, + get_unaligned_be64(pid_bufs[i])); + dev_dbg(master->dev, "Client %d: PID: 0x%llx\n", i, pid); + } + + ret = 0; + goto out; + +err_daa: + xi3c_master_reinit(master); +out: + kfree(pid_bufs); + kfree(xfer); + return ret; +} + +static bool +xi3c_master_supports_ccc_cmd(struct i3c_master_controller *master, + const struct i3c_ccc_cmd *cmd) +{ + if (cmd->ndests > 1) + return false; + + switch (cmd->id) { + case I3C_CCC_ENEC(true): + case I3C_CCC_ENEC(false): + case I3C_CCC_DISEC(true): + case I3C_CCC_DISEC(false): + case I3C_CCC_ENTAS(0, true): + case I3C_CCC_ENTAS(0, false): + case I3C_CCC_RSTDAA(true): + case I3C_CCC_RSTDAA(false): + case I3C_CCC_ENTDAA: + case I3C_CCC_SETMWL(true): + case I3C_CCC_SETMWL(false): + case I3C_CCC_SETMRL(true): + case I3C_CCC_SETMRL(false): + case I3C_CCC_SETDASA: + case I3C_CCC_SETNEWDA: + case I3C_CCC_GETMWL: + case I3C_CCC_GETMRL: + case I3C_CCC_GETPID: + case I3C_CCC_GETBCR: + case I3C_CCC_GETDCR: + case I3C_CCC_GETSTATUS: + case I3C_CCC_GETMXDS: + return true; + default: + return false; + } +} + +static int xi3c_master_send_bdcast_ccc_cmd(struct xi3c_master *master, + struct i3c_ccc_cmd *ccc) +{ + struct xi3c_xfer *xfer __free(kfree) = NULL; + u8 *buf __free(kfree) = NULL; + struct xi3c_cmd *cmd; + u16 xfer_len; + int ret; + + if (ccc->dests[0].payload.len >= XI3C_MAXDATA_LENGTH) + return -EINVAL; + + xfer_len = ccc->dests[0].payload.len + 1; + + xfer = xi3c_master_alloc_xfer(1); + if (!xfer) + return -ENOMEM; + + buf = kmalloc_objs(*buf, xfer_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = ccc->id; + memcpy(&buf[1], ccc->dests[0].payload.data, ccc->dests[0].payload.len); + + cmd = &xfer->cmds[0]; + cmd->addr = ccc->dests[0].addr; + cmd->rnw = ccc->rnw; + cmd->tx_buf = buf; + cmd->tx_len = xfer_len; + cmd->type = XI3C_SDR_MODE; + cmd->tid = XI3C_SDR_TID; + cmd->continued = false; + + ret = xi3c_master_common_xfer(master, xfer); + ccc->err = cmd->err; + + return ret; +} + +static int xi3c_master_send_direct_ccc_cmd(struct xi3c_master *master, + struct i3c_ccc_cmd *ccc) +{ + struct xi3c_xfer *xfer __free(kfree) = NULL; + struct xi3c_cmd *cmd; + int ret; + + if (ccc->dests[0].payload.len > XI3C_MAXDATA_LENGTH) + return -EINVAL; + + xfer = xi3c_master_alloc_xfer(2); + if (!xfer) + return -ENOMEM; + + /* Broadcasted message */ + cmd = &xfer->cmds[0]; + cmd->addr = I3C_BROADCAST_ADDR; + cmd->rnw = false; + cmd->tx_buf = &ccc->id; + cmd->tx_len = 1; + cmd->type = XI3C_SDR_MODE; + cmd->tid = XI3C_SDR_TID; + cmd->continued = true; + + /* Directed message */ + cmd = &xfer->cmds[1]; + cmd->addr = ccc->dests[0].addr; + cmd->rnw = ccc->rnw; + if (cmd->rnw) { + cmd->rx_buf = ccc->dests[0].payload.data; + cmd->rx_len = ccc->dests[0].payload.len; + } else { + cmd->tx_buf = ccc->dests[0].payload.data; + cmd->tx_len = ccc->dests[0].payload.len; + } + cmd->type = XI3C_SDR_MODE; + cmd->tid = XI3C_SDR_TID; + cmd->continued = false; + + ret = xi3c_master_common_xfer(master, xfer); + + /* + * Report broadcast error if any, else the directed one, so either + * NACK reaches the caller + */ + ccc->err = xfer->cmds[0].err ? xfer->cmds[0].err : xfer->cmds[1].err; + + /* Report actual byte count so the core sees the right length on short reads */ + if (!ret && ccc->rnw) + ccc->dests[0].payload.len = min(xfer->cmds[1].rx_actual, + ccc->dests[0].payload.len); + + return ret; +} + +static int xi3c_master_send_ccc_cmd(struct i3c_master_controller *m, + struct i3c_ccc_cmd *cmd) +{ + struct xi3c_master *master = to_xi3c_master(m); + + if (cmd->id & I3C_CCC_DIRECT) + return xi3c_master_send_direct_ccc_cmd(master, cmd); + + return xi3c_master_send_bdcast_ccc_cmd(master, cmd); +} + +static int xi3c_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 xi3c_master *master = to_xi3c_master(m); + struct xi3c_xfer *xfer __free(kfree) = NULL; + int i, ret; + + if (!nxfers) + return 0; + + if (mode != I3C_SDR) + return -EOPNOTSUPP; + + for (i = 0; i < nxfers; i++) + if (xfers[i].len > XI3C_MAXDATA_LENGTH) + return -EINVAL; + + xfer = xi3c_master_alloc_xfer(nxfers); + if (!xfer) + return -ENOMEM; + + for (i = 0; i < nxfers; i++) { + struct xi3c_cmd *cmd = &xfer->cmds[i]; + + cmd->addr = dev->info.dyn_addr; + cmd->rnw = xfers[i].rnw; + + if (cmd->rnw) { + cmd->rx_buf = xfers[i].data.in; + cmd->rx_len = xfers[i].len; + } else { + cmd->tx_buf = xfers[i].data.out; + cmd->tx_len = xfers[i].len; + } + + cmd->type = XI3C_SDR_MODE; + cmd->tid = XI3C_SDR_TID; + cmd->continued = (i + 1) < nxfers; + } + + ret = xi3c_master_common_xfer(master, xfer); + + for (i = 0; i < xfer->nissued; i++) { + xfers[i].err = xfer->cmds[i].err; + if (xfers[i].rnw) + xfers[i].actual_len = min(xfer->cmds[i].rx_actual, + xfers[i].len); + } + + return ret; +} + +static int xi3c_master_i2c_xfers(struct i2c_dev_desc *dev, + struct i2c_msg *xfers, + int nxfers) +{ + struct i3c_master_controller *m = i2c_dev_get_master(dev); + struct xi3c_master *master = to_xi3c_master(m); + struct xi3c_xfer *xfer __free(kfree) = NULL; + int i; + + if (!nxfers) + return 0; + + for (i = 0; i < nxfers; i++) + if (xfers[i].len > XI3C_MAXDATA_LENGTH) + return -EINVAL; + + xfer = xi3c_master_alloc_xfer(nxfers); + if (!xfer) + return -ENOMEM; + + for (i = 0; i < nxfers; i++) { + struct xi3c_cmd *cmd = &xfer->cmds[i]; + + cmd->addr = xfers[i].addr & XI3C_ADDR_MASK; + cmd->rnw = !!(xfers[i].flags & I2C_M_RD); + + if (cmd->rnw) { + cmd->rx_buf = xfers[i].buf; + cmd->rx_len = xfers[i].len; + } else { + cmd->tx_buf = xfers[i].buf; + cmd->tx_len = xfers[i].len; + } + + cmd->type = XI3C_I2C_MODE; + cmd->tid = XI3C_I2C_TID; + cmd->continued = (i + 1) < nxfers; + } + + return xi3c_master_common_xfer(master, xfer); +} + +static int xi3c_clk_cfg(struct xi3c_master *master, unsigned long sclhz, u8 mode) +{ + unsigned long core_rate, core_periodns; + u32 tcasmin, tsustart, tsustop, thdstart; + u32 thigh, tlow, thold; + u32 odthigh, odtlow; + + core_rate = clk_get_rate(master->pclk); + if (!core_rate) + return -EINVAL; + + if (!sclhz) + return -EINVAL; + + core_periodns = DIV_ROUND_UP(NSEC_PER_SEC, core_rate); + + thigh = DIV_ROUND_UP(core_rate, sclhz) >> 1; + tlow = thigh; + + /* Reject rates whose timing exceeds the 18-bit registers (would wrap) */ + if (thigh <= XI3C_CYCLE_ADJUST || + (thigh - XI3C_CYCLE_ADJUST) > XI3C_TIMING_MASK) + return -EINVAL; + + /* Hold time : 40% of tlow time */ + thold = (tlow * 4) / 10; + + if (xi3c_get_revision_number(master) == 0) + thold = max_t(u32, thold, XI3C_THOLD_MIN_REV0); + else + thold = max_t(u32, thold, XI3C_THOLD_MIN_REV1); + + iowrite32((thigh - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK, + master->membase + XI3C_SCL_HIGH_TIME_OFFSET); + iowrite32((tlow - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK, + master->membase + XI3C_SCL_LOW_TIME_OFFSET); + iowrite32((thold - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK, + master->membase + XI3C_SDA_HOLD_TIME_OFFSET); + + if (mode == XI3C_I2C_MODE) { + iowrite32((thigh - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK, + master->membase + XI3C_OD_SCL_HIGH_TIME_OFFSET); + iowrite32((tlow - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK, + master->membase + XI3C_OD_SCL_LOW_TIME_OFFSET); + + tcasmin = DIV_ROUND_UP(XI3C_I2C_TCASMIN_NS, core_periodns); + } else { + odtlow = DIV_ROUND_UP(XI3C_OD_TLOW_NS, core_periodns); + odthigh = DIV_ROUND_UP(XI3C_OD_THIGH_NS, core_periodns); + + odtlow = max(tlow, odtlow); + odthigh = min(thigh, odthigh); + + if (odthigh <= XI3C_CYCLE_ADJUST) + return -EINVAL; + + iowrite32((odthigh - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK, + master->membase + XI3C_OD_SCL_HIGH_TIME_OFFSET); + iowrite32((odtlow - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK, + master->membase + XI3C_OD_SCL_LOW_TIME_OFFSET); + + tcasmin = DIV_ROUND_UP(XI3C_TCASMIN_NS, core_periodns); + } + + thdstart = max(thigh, tcasmin); + tsustart = max(tlow, tcasmin); + tsustop = max(tlow, tcasmin); + + iowrite32((tsustart - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK, + master->membase + XI3C_TSU_START_OFFSET); + iowrite32((thdstart - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK, + master->membase + XI3C_THD_START_OFFSET); + iowrite32((tsustop - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK, + master->membase + XI3C_TSU_STOP_OFFSET); + + return 0; +} + +static int xi3c_master_bus_init(struct i3c_master_controller *m) +{ + struct xi3c_master *master = to_xi3c_master(m); + struct i3c_bus *bus = i3c_master_get_bus(m); + struct i3c_device_info info = {}; + unsigned long sclhz; + u32 pid1_bcr_dcr; + u8 mode; + int ret; + + switch (bus->mode) { + case I3C_BUS_MODE_MIXED_FAST: + case I3C_BUS_MODE_MIXED_LIMITED: + case I3C_BUS_MODE_MIXED_SLOW: + mode = XI3C_I2C_MODE; + sclhz = bus->scl_rate.i2c; + break; + case I3C_BUS_MODE_PURE: + mode = XI3C_SDR_MODE; + sclhz = bus->scl_rate.i3c; + break; + default: + return -EINVAL; + } + + ret = xi3c_clk_cfg(master, sclhz, mode); + if (ret) + return ret; + + xi3c_master_init(master); + + /* Get an address for the master. */ + ret = i3c_master_get_free_addr(m, 0); + if (ret < 0) + return ret; + + info.dyn_addr = ret; + + /* Write the dynamic address value to the address register. */ + iowrite32(info.dyn_addr, master->membase + XI3C_ADDRESS_OFFSET); + + /* Read PID, BCR and DCR values, and assign to i3c device info. */ + pid1_bcr_dcr = ioread32(master->membase + XI3C_PID1_BCR_DCR); + info.pid = ((u64)FIELD_GET(XI3C_PID1_MASK, pid1_bcr_dcr) << 32) | + ioread32(master->membase + XI3C_PID0_OFFSET); + info.bcr = FIELD_GET(XI3C_BCR_MASK, pid1_bcr_dcr); + info.dcr = FIELD_GET(XI3C_DCR_MASK, pid1_bcr_dcr); + + return i3c_master_set_info(&master->base, &info); +} + +static void xi3c_master_bus_cleanup(struct i3c_master_controller *m) +{ + struct xi3c_master *master = to_xi3c_master(m); + + xi3c_master_disable(master); +} + +static const struct i3c_master_controller_ops xi3c_master_ops = { + .bus_init = xi3c_master_bus_init, + .bus_cleanup = xi3c_master_bus_cleanup, + .do_daa = xi3c_master_do_daa, + .supports_ccc_cmd = xi3c_master_supports_ccc_cmd, + .send_ccc_cmd = xi3c_master_send_ccc_cmd, + .i3c_xfers = xi3c_master_i3c_xfers, + .i2c_xfers = xi3c_master_i2c_xfers, +}; + +static int xi3c_master_probe(struct platform_device *pdev) +{ + struct xi3c_master *master; + int ret; + + master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); + if (!master) + return -ENOMEM; + + master->dev = &pdev->dev; + + master->membase = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(master->membase)) + return dev_err_probe(master->dev, PTR_ERR(master->membase), + "Failed to map registers\n"); + + master->pclk = devm_clk_get_enabled(master->dev, NULL); + if (IS_ERR(master->pclk)) + return dev_err_probe(master->dev, PTR_ERR(master->pclk), + "Failed to get and enable clock\n"); + + ret = devm_mutex_init(master->dev, &master->lock); + if (ret) + return ret; + + platform_set_drvdata(pdev, master); + + return i3c_master_register(&master->base, master->dev, + &xi3c_master_ops, false); +} + +static void xi3c_master_remove(struct platform_device *pdev) +{ + struct xi3c_master *master = platform_get_drvdata(pdev); + + i3c_master_unregister(&master->base); +} + +static const struct of_device_id xi3c_master_of_ids[] = { + { .compatible = "xlnx,axi-i3c-1.0" }, + { }, +}; +MODULE_DEVICE_TABLE(of, xi3c_master_of_ids); + +static struct platform_driver xi3c_master_driver = { + .probe = xi3c_master_probe, + .remove = xi3c_master_remove, + .driver = { + .name = "axi-i3c-master", + .of_match_table = xi3c_master_of_ids, + }, +}; +module_platform_driver(xi3c_master_driver); + +MODULE_AUTHOR("Manikanta Guntupalli "); +MODULE_DESCRIPTION("AMD AXI I3C master driver"); +MODULE_LICENSE("GPL"); From 5f1a76ecfe90544a28d657306c9b3caa66ba0e63 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:29 +0300 Subject: [PATCH 13/70] i3c: renesas: Check that the transfer is valid before accessing it The Renesas I3C driver uses an asynchronous model to transfer data. It prepares a struct renesas_i3c_xfer, enqueues it, and waits for completion. The interrupt handler dequeues the transfer, updates/uses it, and signals the waiting thread. If the completion times out, the waiting thread dequeues the transfer and free it. If an interrupt fires after that, the handler may access freed memory, leading to crashes. Check that the transfer is still valid before accessing it in the interrupt handler. With it clear any status flags and disable all the interrupts to avoid triggering the same interrupts again. Fixes: d028219a9f14 ("i3c: master: Add basic driver for the Renesas I3C controller") Cc: stable@vger.kernel.org Signed-off-by: Claudiu Beznea Reviewed-by: Frank Li Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-2-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 52 +++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 25a2b2ed618e..676ba863661f 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -433,6 +433,21 @@ static void renesas_i3c_enqueue_xfer(struct renesas_i3c *i3c, struct renesas_i3c } } +static void renesas_i3c_irqs_mask_and_clear_locked(struct renesas_i3c *i3c) +{ + /* Disable all the interrupts. */ + renesas_writel(i3c->regs, BIE, 0); + renesas_writel(i3c->regs, NTIE, 0); + + /* Clear normal transfer status flags. */ + renesas_writel(i3c->regs, NTST, 0); + + /* Clear bus status flags. */ + renesas_writel(i3c->regs, BST, 0); + /* Read back registers to confirm writes have fully propagated. */ + renesas_readl(i3c->regs, BST); +} + static void renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xfer *xfer) { unsigned long time_left; @@ -1016,6 +1031,11 @@ static irqreturn_t renesas_i3c_tx_isr(int irq, void *data) scoped_guard(spinlock, &i3c->xferqueue.lock) { xfer = i3c->xferqueue.cur; + if (!xfer) { + renesas_i3c_irqs_mask_and_clear_locked(i3c); + return IRQ_HANDLED; + } + cmd = xfer->cmds; if (xfer->is_i2c_xfer) { @@ -1056,6 +1076,11 @@ static irqreturn_t renesas_i3c_resp_isr(int irq, void *data) scoped_guard(spinlock, &i3c->xferqueue.lock) { xfer = i3c->xferqueue.cur; + if (!xfer) { + renesas_i3c_irqs_mask_and_clear_locked(i3c); + return IRQ_HANDLED; + } + cmd = xfer->cmds; /* Clear the Respone Queue Full status flag*/ @@ -1146,6 +1171,11 @@ static irqreturn_t renesas_i3c_tend_isr(int irq, void *data) scoped_guard(spinlock, &i3c->xferqueue.lock) { xfer = i3c->xferqueue.cur; + if (!xfer) { + renesas_i3c_irqs_mask_and_clear_locked(i3c); + return IRQ_HANDLED; + } + cmd = xfer->cmds; if (xfer->is_i2c_xfer) { @@ -1192,6 +1222,11 @@ static irqreturn_t renesas_i3c_rx_isr(int irq, void *data) scoped_guard(spinlock, &i3c->xferqueue.lock) { xfer = i3c->xferqueue.cur; + if (!xfer) { + renesas_i3c_irqs_mask_and_clear_locked(i3c); + return IRQ_HANDLED; + } + cmd = xfer->cmds; if (xfer->is_i2c_xfer) { @@ -1242,15 +1277,13 @@ static irqreturn_t renesas_i3c_stop_isr(int irq, void *data) struct renesas_i3c_xfer *xfer; scoped_guard(spinlock, &i3c->xferqueue.lock) { - xfer = i3c->xferqueue.cur; - - /* read back registers to confirm writes have fully propagated */ - renesas_writel(i3c->regs, BST, 0); - renesas_readl(i3c->regs, BST); - renesas_writel(i3c->regs, BIE, 0); - renesas_clear_bit(i3c->regs, NTST, NTST_TDBEF0 | NTST_RDBFF0); + renesas_i3c_irqs_mask_and_clear_locked(i3c); renesas_clear_bit(i3c->regs, SCSTRCTL, SCSTRCTL_RWE); + xfer = i3c->xferqueue.cur; + if (!xfer) + return IRQ_HANDLED; + xfer->ret = 0; complete(&xfer->comp); } @@ -1267,6 +1300,11 @@ static irqreturn_t renesas_i3c_start_isr(int irq, void *data) scoped_guard(spinlock, &i3c->xferqueue.lock) { xfer = i3c->xferqueue.cur; + if (!xfer) { + renesas_i3c_irqs_mask_and_clear_locked(i3c); + return IRQ_HANDLED; + } + cmd = xfer->cmds; if (xfer->is_i2c_xfer) { From 21cded44e69c630799e65baadb72ad123d1347c8 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:30 +0300 Subject: [PATCH 14/70] i3c: renesas: Restore STDBR and EXTBR registers on resume The Renesas RZ/G3S supports a power saving state where power to the most SoC componentes (including I3C) is lost. The STDBR and EXTBR are configured in initialization phase though the struct i3c_master_controller_ops::bus_init. Set them on resume function as well to keep the same state of the controller after a suspend with power loss and a similar initialization sequence as in bus_init. Fixes: e7218986319b ("i3c: renesas: Add suspend/resume support") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-3-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 676ba863661f..acfc38b18896 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -260,6 +260,7 @@ struct renesas_i3c { u32 dyn_addr; u32 i2c_STDBR; u32 i3c_STDBR; + u32 extbr; unsigned long rate; u8 addrs[RENESAS_I3C_MAX_DEVS]; struct renesas_i3c_xferqueue xferqueue; @@ -622,10 +623,9 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR); /* Extended Bit Rate setting */ - renesas_writel(i3c->regs, EXTBR, EXTBR_EBRLO(od_low_ticks) | - EXTBR_EBRHO(od_high_ticks) | - EXTBR_EBRLP(pp_low_ticks) | - EXTBR_EBRHP(pp_high_ticks)); + i3c->extbr = EXTBR_EBRLO(od_low_ticks) | EXTBR_EBRHO(od_high_ticks) | + EXTBR_EBRLP(pp_low_ticks) | EXTBR_EBRHP(pp_high_ticks); + renesas_writel(i3c->regs, EXTBR, i3c->extbr); renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks)); i3c->refclk_div = cks; @@ -1476,6 +1476,8 @@ static int renesas_i3c_resume_noirq(struct device *dev) goto err_tresetn; /* Re-store I3C registers value. */ + renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR); + renesas_writel(i3c->regs, EXTBR, i3c->extbr); renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(i3c->refclk_div)); renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYADV | From 7b15ca2615ec41fb428eb9af71bf3ebc95021201 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:31 +0300 Subject: [PATCH 15/70] i3c: renesas: Follow the reset deassert order used in probe Use the same reset deassert order in the resume and probe paths to avoid potential failures due to ordering differences. Fixes: e7218986319b ("i3c: renesas: Add suspend/resume support") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-4-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index acfc38b18896..45de42c40fbb 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -1463,17 +1463,17 @@ static int renesas_i3c_resume_noirq(struct device *dev) struct renesas_i3c *i3c = dev_get_drvdata(dev); int i, ret; - ret = reset_control_deassert(i3c->presetn); + ret = reset_control_deassert(i3c->tresetn); if (ret) return ret; - ret = reset_control_deassert(i3c->tresetn); + ret = reset_control_deassert(i3c->presetn); if (ret) - goto err_presetn; + goto err_tresetn; ret = clk_bulk_enable(i3c->num_clks, i3c->clks); if (ret) - goto err_tresetn; + goto err_presetn; /* Re-store I3C registers value. */ renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR); @@ -1494,10 +1494,10 @@ static int renesas_i3c_resume_noirq(struct device *dev) return 0; -err_tresetn: - reset_control_assert(i3c->tresetn); err_presetn: reset_control_assert(i3c->presetn); +err_tresetn: + reset_control_assert(i3c->tresetn); return ret; } From 1364afd3e2e76e007a2c07ec95704d56980226f0 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:32 +0300 Subject: [PATCH 16/70] i3c: renesas: Reconfigure the DATBAS register on re-attach During re-attach, the device may change its position in the i3c->addrs[] array. As a result, it may use a different Device Address Table Basic Register (DATBAS), which needs to be reconfigured. Reconfigure the DATBAS register on re-attach. Along with it update software caches. Fixes: d028219a9f14 ("i3c: master: Add basic driver for the Renesas I3C controller") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-5-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 45de42c40fbb..ba500714fe06 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -909,10 +909,26 @@ static int renesas_i3c_reattach_i3c_dev(struct i3c_dev_desc *dev, struct i3c_master_controller *m = i3c_dev_get_master(dev); struct renesas_i3c *i3c = to_renesas_i3c(m); struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); + int pos; + + pos = renesas_i3c_get_free_pos(i3c); + + if (data->index > pos && pos >= 0) { + renesas_writel(i3c->regs, DATBAS(data->index), 0); + i3c->addrs[data->index] = 0; + i3c->free_pos |= BIT(data->index); + + data->index = pos; + i3c->free_pos &= ~BIT(data->index); + } i3c->addrs[data->index] = dev->info.dyn_addr ? dev->info.dyn_addr : dev->info.static_addr; + renesas_writel(i3c->regs, DATBAS(data->index), + DATBAS_DVSTAD(dev->info.static_addr) | + datbas_dvdyad_with_parity(i3c->addrs[data->index])); + return 0; } From 27cf0ad162f1a4526a86fc2ec24d84a4a98ac1dd Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:33 +0300 Subject: [PATCH 17/70] i3c: renesas: Reset the controller on resume Reset the controller on resume after enabling the clocks to follow the same sequence as in probe and avoid potential ordering related failures. With it, renesas_i3c_reset() was updated to use read_poll_timeout_atomic(), as the driver's resume callback is executed during the noirq phase of resume, where interrupts are disabled. Fixes: e7218986319b ("i3c: renesas: Add suspend/resume support") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-6-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index ba500714fe06..fa8045b49fca 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -495,8 +495,8 @@ static int renesas_i3c_reset(struct renesas_i3c *i3c) renesas_writel(i3c->regs, BCTL, 0); renesas_set_bit(i3c->regs, RSTCTL, RSTCTL_RI3CRST); - return read_poll_timeout(renesas_readl, val, !(val & RSTCTL_RI3CRST), - 0, 1000, false, i3c->regs, RSTCTL); + return read_poll_timeout_atomic(renesas_readl, val, !(val & RSTCTL_RI3CRST), + 0, 1000, false, i3c->regs, RSTCTL); } static void renesas_i3c_hw_init(struct renesas_i3c *i3c) @@ -1491,6 +1491,10 @@ static int renesas_i3c_resume_noirq(struct device *dev) if (ret) goto err_presetn; + ret = renesas_i3c_reset(i3c); + if (ret) + goto err_clks_disable; + /* Re-store I3C registers value. */ renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR); renesas_writel(i3c->regs, EXTBR, i3c->extbr); @@ -1510,6 +1514,8 @@ static int renesas_i3c_resume_noirq(struct device *dev) return 0; +err_clks_disable: + clk_bulk_disable(i3c->num_clks, i3c->clks); err_presetn: reset_control_assert(i3c->presetn); err_tresetn: From fbf26154c47953d08c60fb402ab19c5c56c3779f Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:34 +0300 Subject: [PATCH 18/70] i3c: renesas: Perform Dynamic Address Assignment on resume The Renesas RZ/G3S SoC supports a power saving mode where power to most SoC components, including I3C, is turned off. On systems where the I3C devices also loses power during suspend (e.g. NXP P3T1085UK-ARD connected to the PMOD1_6A connector of the RZ SMARC Carrier 2 + Renesas RZ/G3S SMARC SOM), the devices becomes unreachable after resume. Running DAA in the controller resume path restores communication. However, DAA relies on interrupts for TX/RX, which are not available in the noirq suspend/resume phase (unless they are wakeup interrupts). For this, the suspend/resume callbacks were moved out of the noirq phase. Currently, there is no identified use case on either the Renesas RZ/G3S or Renesas RZ/G3E SoCs that requires the controller suspend/resume hooks to be part of the noirq suspend/resume phase. Since renesas_i3c_reset() is not called anymore in atomic context update it to use read_poll_timeout(). Along with this, struct renesas_i3c::DATBASn and its usage were removed, as they are no longer needed. Fixes: e7218986319b ("i3c: renesas: Add suspend/resume support") Cc: stable@vger.kernel.org Signed-off-by: Claudiu Beznea Reviewed-by: Frank Li Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-7-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 38 +++++++++++++------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index fa8045b49fca..0b302021e07b 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -265,7 +265,6 @@ struct renesas_i3c { u8 addrs[RENESAS_I3C_MAX_DEVS]; struct renesas_i3c_xferqueue xferqueue; void __iomem *regs; - u32 *DATBASn; struct clk_bulk_data *clks; struct reset_control *presetn; struct reset_control *tresetn; @@ -495,8 +494,8 @@ static int renesas_i3c_reset(struct renesas_i3c *i3c) renesas_writel(i3c->regs, BCTL, 0); renesas_set_bit(i3c->regs, RSTCTL, RSTCTL_RI3CRST); - return read_poll_timeout_atomic(renesas_readl, val, !(val & RSTCTL_RI3CRST), - 0, 1000, false, i3c->regs, RSTCTL); + return read_poll_timeout(renesas_readl, val, !(val & RSTCTL_RI3CRST), + 0, 1000, false, i3c->regs, RSTCTL); } static void renesas_i3c_hw_init(struct renesas_i3c *i3c) @@ -1427,12 +1426,6 @@ static int renesas_i3c_probe(struct platform_device *pdev) i3c->maxdevs = RENESAS_I3C_MAX_DEVS; i3c->free_pos = GENMASK(i3c->maxdevs - 1, 0); - /* Allocate dynamic Device Address Table backup. */ - i3c->DATBASn = devm_kzalloc(&pdev->dev, sizeof(u32) * i3c->maxdevs, - GFP_KERNEL); - if (!i3c->DATBASn) - return -ENOMEM; - return i3c_master_register(&i3c->base, &pdev->dev, &renesas_i3c_ops, false); } @@ -1443,17 +1436,13 @@ static void renesas_i3c_remove(struct platform_device *pdev) i3c_master_unregister(&i3c->base); } -static int renesas_i3c_suspend_noirq(struct device *dev) +static int renesas_i3c_suspend(struct device *dev) { struct renesas_i3c *i3c = dev_get_drvdata(dev); - int i, ret; + int ret; i2c_mark_adapter_suspended(&i3c->base.i2c); - /* Store Device Address Table values. */ - for (i = 0; i < i3c->maxdevs; i++) - i3c->DATBASn[i] = renesas_readl(i3c->regs, DATBAS(i)); - ret = reset_control_assert(i3c->presetn); if (ret) goto err_mark_resumed; @@ -1474,10 +1463,10 @@ static int renesas_i3c_suspend_noirq(struct device *dev) return ret; } -static int renesas_i3c_resume_noirq(struct device *dev) +static int renesas_i3c_resume(struct device *dev) { struct renesas_i3c *i3c = dev_get_drvdata(dev); - int i, ret; + int ret; ret = reset_control_deassert(i3c->tresetn); if (ret) @@ -1503,15 +1492,19 @@ static int renesas_i3c_resume_noirq(struct device *dev) renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYADV | MSDVAD_MDYAD(i3c->dyn_addr)); - /* Restore Device Address Table values. */ - for (i = 0; i < i3c->maxdevs; i++) - renesas_writel(i3c->regs, DATBAS(i), i3c->DATBASn[i]); - /* I3C hw init. */ renesas_i3c_hw_init(i3c); + ret = i3c_master_do_daa_ext(&i3c->base, true); + if (ret) + dev_err(dev, "DAA failed on resume, ret=%d", ret); + i2c_mark_adapter_resumed(&i3c->base.i2c); + /* + * I3C devices may have retained their dynamic address anyway. Do not + * fail the resume because of DAA error. + */ return 0; err_clks_disable: @@ -1524,8 +1517,7 @@ static int renesas_i3c_resume_noirq(struct device *dev) } static const struct dev_pm_ops renesas_i3c_pm_ops = { - NOIRQ_SYSTEM_SLEEP_PM_OPS(renesas_i3c_suspend_noirq, - renesas_i3c_resume_noirq) + SYSTEM_SLEEP_PM_OPS(renesas_i3c_suspend, renesas_i3c_resume) }; static const struct of_device_id renesas_i3c_of_ids[] = { From 797ed83c0cd495be4b345750c59e0363bf4d6207 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:35 +0300 Subject: [PATCH 19/70] i3c: renesas: Clean DATBAS register on detach The controller uses DATBAS registers on TX/RX logic. Clean the DATBAS register for the detached I3C device to avoid issues. Fixes: d028219a9f14 ("i3c: master: Add basic driver for the Renesas I3C controller") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-8-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 0b302021e07b..216c8b969ec8 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -937,6 +937,8 @@ static void renesas_i3c_detach_i3c_dev(struct i3c_dev_desc *dev) struct i3c_master_controller *m = i3c_dev_get_master(dev); struct renesas_i3c *i3c = to_renesas_i3c(m); + renesas_writel(i3c->regs, DATBAS(data->index), 0); + i3c_dev_set_master_data(dev, NULL); i3c->addrs[data->index] = 0; i3c->free_pos |= BIT(data->index); From 50dec95c9d1f1f82819bc898ef2b60fa83fd4ccd Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:36 +0300 Subject: [PATCH 20/70] i3c: renesas: Fix out-of-bounds access for newdevs mask When software initiates DAA (Dynamic Address Assignment), the controller reports the result via the NRSPQP (Normal Response Queue Port Register). The data length field of the response descriptor, which is accessible through the NRSPQP register, indicates the number of devices remaining after DAA. Consequently, when the bus is empty, this field contains the maximum number of devices supported by the controller (8 for the Renesas I3C controller). Adjust the condition that computes the newly discovered devices bitmask to prevent an out-of-bounds when the I3C bus is empty. Fixes: e7218986319b ("i3c: renesas: Add suspend/resume support") Cc: stable@vger.kernel.org Signed-off-by: Claudiu Beznea Reviewed-by: Frank Li Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-9-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 216c8b969ec8..6518a18e019b 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -703,7 +703,11 @@ static int renesas_i3c_daa(struct i3c_master_controller *m) renesas_i3c_wait_xfer(i3c, xfer); - newdevs = GENMASK(i3c->maxdevs - cmd->rx_count - 1, 0); + if (cmd->rx_count >= i3c->maxdevs) + newdevs = 0; + else + newdevs = GENMASK(i3c->maxdevs - cmd->rx_count - 1, 0); + newdevs &= ~olddevs; for (pos = 0; pos < i3c->maxdevs; pos++) { From 35e9bb2be1a20561e2cc19ca79c88c009f65c5f4 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:37 +0300 Subject: [PATCH 21/70] i3c: renesas: Use reset_control_bulk_{assert, deassert}() Use reset_control_bulk_assert() and reset_control_bulk_deassert() in the suspend and resume paths to simplify the code. Signed-off-by: Claudiu Beznea Reviewed-by: Frank Li Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-10-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 6518a18e019b..18f1154f5566 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -1445,24 +1445,22 @@ static void renesas_i3c_remove(struct platform_device *pdev) static int renesas_i3c_suspend(struct device *dev) { struct renesas_i3c *i3c = dev_get_drvdata(dev); + struct reset_control_bulk_data resets[] = { + { .rstc = i3c->presetn }, + { .rstc = i3c->tresetn }, + }; int ret; i2c_mark_adapter_suspended(&i3c->base.i2c); - ret = reset_control_assert(i3c->presetn); + ret = reset_control_bulk_assert(ARRAY_SIZE(resets), resets); if (ret) goto err_mark_resumed; - ret = reset_control_assert(i3c->tresetn); - if (ret) - goto err_presetn; - clk_bulk_disable(i3c->num_clks, i3c->clks); return 0; -err_presetn: - reset_control_deassert(i3c->presetn); err_mark_resumed: i2c_mark_adapter_resumed(&i3c->base.i2c); @@ -1472,19 +1470,19 @@ static int renesas_i3c_suspend(struct device *dev) static int renesas_i3c_resume(struct device *dev) { struct renesas_i3c *i3c = dev_get_drvdata(dev); + struct reset_control_bulk_data resets[] = { + { .rstc = i3c->presetn }, + { .rstc = i3c->tresetn }, + }; int ret; - ret = reset_control_deassert(i3c->tresetn); + ret = reset_control_bulk_deassert(ARRAY_SIZE(resets), resets); if (ret) return ret; - ret = reset_control_deassert(i3c->presetn); - if (ret) - goto err_tresetn; - ret = clk_bulk_enable(i3c->num_clks, i3c->clks); if (ret) - goto err_presetn; + goto err_resets_asserted; ret = renesas_i3c_reset(i3c); if (ret) @@ -1515,10 +1513,8 @@ static int renesas_i3c_resume(struct device *dev) err_clks_disable: clk_bulk_disable(i3c->num_clks, i3c->clks); -err_presetn: - reset_control_assert(i3c->presetn); -err_tresetn: - reset_control_assert(i3c->tresetn); +err_resets_asserted: + reset_control_bulk_assert(ARRAY_SIZE(resets), resets); return ret; } From 33b5ecc5a16e270c8e0dd9835e7d9d2522f64129 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:38 +0300 Subject: [PATCH 22/70] i3c: renesas: Return immediately if there is no transfer There is no need to allocate a transfer structure when i2c_nxfers is zero. Return immediately instead of unnecessarily allocating memory. Signed-off-by: Claudiu Beznea Reviewed-by: Frank Li Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-11-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 18f1154f5566..1f534b933358 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -959,13 +959,13 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev, u8 start_bit = CNDCTL_STCND; int i; + if (!i2c_nxfers) + return 0; + struct renesas_i3c_xfer *xfer __free(kfree) = renesas_i3c_alloc_xfer(i3c, 1); if (!xfer) return -ENOMEM; - if (!i2c_nxfers) - return 0; - renesas_i3c_bus_enable(m, false); init_completion(&xfer->comp); From 234a26e6febcf789d9f6779d67c01e3d0c1df5bc Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:39 +0300 Subject: [PATCH 23/70] i3c: renesas: Follow a unified pattern for transfer and command initialization Follow a unified pattern for transfer and command initialization across the driver. This keeps the code cleaner and easier to follow. Also, in some cases the I3C device was enabled before the transfer data structure was even allocated. Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-12-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 1f534b933358..d7305408371a 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -663,6 +663,10 @@ static int renesas_i3c_daa(struct i3c_master_controller *m) if (!xfer) return -ENOMEM; + init_completion(&xfer->comp); + cmd = xfer->cmds; + cmd->rx_count = 0; + /* Enable I3C bus. */ renesas_i3c_bus_enable(m, true); @@ -684,10 +688,6 @@ static int renesas_i3c_daa(struct i3c_master_controller *m) renesas_writel(i3c->regs, DATBAS(pos), datbas_dvdyad_with_parity(ret)); } - init_completion(&xfer->comp); - cmd = xfer->cmds; - cmd->rx_count = 0; - ret = renesas_i3c_get_free_pos(i3c); if (ret < 0) return ret; @@ -779,13 +779,13 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m, if (!xfer) return -ENOMEM; - renesas_i3c_bus_enable(m, true); - init_completion(&xfer->comp); cmd = xfer->cmds; cmd->rnw = ccc->rnw; cmd->cmd0 = 0; + renesas_i3c_bus_enable(m, true); + /* Calculate the command descriptor. */ switch (ccc->id) { case I3C_CCC_SETDASA: @@ -837,15 +837,15 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_ struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); int i; - /* Enable I3C bus. */ - renesas_i3c_bus_enable(m, true); - struct renesas_i3c_xfer *xfer __free(kfree) = renesas_i3c_alloc_xfer(i3c, 1); if (!xfer) return -ENOMEM; init_completion(&xfer->comp); + /* Enable I3C bus. */ + renesas_i3c_bus_enable(m, true); + for (i = 0; i < i3c_nxfers; i++) { struct renesas_i3c_cmd *cmd = xfer->cmds; @@ -966,12 +966,12 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev, if (!xfer) return -ENOMEM; - renesas_i3c_bus_enable(m, false); - init_completion(&xfer->comp); xfer->is_i2c_xfer = true; cmd = xfer->cmds; + renesas_i3c_bus_enable(m, false); + if (!(renesas_readl(i3c->regs, BCST) & BCST_BFREF)) { cmd->err = -EBUSY; return cmd->err; From 5e0b4ae8017782c5495f96f924b28fa447682a36 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:40 +0300 Subject: [PATCH 24/70] i3c: renesas: Drop the explicit memset() call Drop the explicit memset() call on struct i3c_device_info object, as it is already initialized at declaration through compiler initialization. Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-13-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index d7305408371a..50a94d130a0c 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -639,7 +639,6 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) i3c->dyn_addr = ret; renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYAD(ret) | MSDVAD_MDYADV); - memset(&info, 0, sizeof(info)); info.dyn_addr = ret; return i3c_master_set_info(&i3c->base, &info); } From 826a7d3d5ff2296d021a6a4521544b852609047a Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:41 +0300 Subject: [PATCH 25/70] i3c: renesas: Update HW registers after SW computations are done renesas_i3c_bus_init() performs a number of computations and software cache updates, interleaving them with hardware register writes. While this works today, it makes it harder to minimize the time the controller must remain powered when runtime PM is introduced. Perform all software computations and cache updates first, then update the hardware registers. This prepares for future runtime PM support. Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-14-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 50a94d130a0c..995ace3d2b5a 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -565,10 +565,6 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) if (!i3c->rate) return -EINVAL; - ret = renesas_i3c_reset(i3c); - if (ret) - return ret; - i2c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i2c); i3c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i3c); @@ -619,27 +615,31 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) STDBR_SBRHO(double_SBR, od_high_ticks) | STDBR_SBRLP(pp_low_ticks) | STDBR_SBRHP(pp_high_ticks); - renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR); /* Extended Bit Rate setting */ i3c->extbr = EXTBR_EBRLO(od_low_ticks) | EXTBR_EBRHO(od_high_ticks) | EXTBR_EBRLP(pp_low_ticks) | EXTBR_EBRHP(pp_high_ticks); - renesas_writel(i3c->regs, EXTBR, i3c->extbr); - - renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks)); - i3c->refclk_div = cks; - - /* I3C hw init*/ - renesas_i3c_hw_init(i3c); ret = i3c_master_get_free_addr(m, 0); if (ret < 0) return ret; - i3c->dyn_addr = ret; - renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYAD(ret) | MSDVAD_MDYADV); - info.dyn_addr = ret; + i3c->dyn_addr = ret; + i3c->refclk_div = cks; + + ret = renesas_i3c_reset(i3c); + if (ret) + return ret; + + renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR); + renesas_writel(i3c->regs, EXTBR, i3c->extbr); + renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks)); + renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYAD(i3c->dyn_addr) | MSDVAD_MDYADV); + + /* I3C hw init*/ + renesas_i3c_hw_init(i3c); + return i3c_master_set_info(&i3c->base, &info); } From 42a6f531c950c8b8009d938b3e51fed3533a1a3c Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:42 +0300 Subject: [PATCH 26/70] i3c: renesas: Organize structures to avoid unnecessary padding Reorder structure members to reduce padding and improve memory layout. Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-15-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 995ace3d2b5a..0ee842dd11f8 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -221,19 +221,19 @@ enum renesas_i3c_event { }; struct renesas_i3c_cmd { + const void *tx_buf; + void *rx_buf; + /* i2c xfer */ + u8 *i2c_buf; + const struct i2c_msg *msg; + int i2c_bytes_left; + int i2c_is_last; u32 cmd0; u32 len; - const void *tx_buf; u32 tx_count; - void *rx_buf; u32 rx_count; u32 err; u8 rnw; - /* i2c xfer */ - int i2c_bytes_left; - int i2c_is_last; - u8 *i2c_buf; - const struct i2c_msg *msg; }; struct renesas_i3c_xfer { @@ -253,21 +253,22 @@ struct renesas_i3c_xferqueue { }; struct renesas_i3c { + void __iomem *regs; + struct clk_bulk_data *clks; + struct reset_control *presetn; + struct reset_control *tresetn; + struct renesas_i3c_xferqueue xferqueue; struct i3c_master_controller base; + u8 addrs[RENESAS_I3C_MAX_DEVS]; + unsigned long rate; enum i3c_internal_state internal_state; - u16 maxdevs; + bool resuming; u32 free_pos; u32 dyn_addr; u32 i2c_STDBR; u32 i3c_STDBR; u32 extbr; - unsigned long rate; - u8 addrs[RENESAS_I3C_MAX_DEVS]; - struct renesas_i3c_xferqueue xferqueue; - void __iomem *regs; - struct clk_bulk_data *clks; - struct reset_control *presetn; - struct reset_control *tresetn; + u16 maxdevs; u8 num_clks; u8 refclk_div; }; From 0c863015fcd6d58843f0f012b69ae9e22c00d9dc Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:43 +0300 Subject: [PATCH 27/70] i3c: renesas: Use the "dev_name:irq_name" format for the interrupt name Use the "dev_name:irq_name" format for the interrupt names. This makes it easier to identify interrupts in systems where multiple devices may request interrupts with the same name. Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-16-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 0ee842dd11f8..18ce95e69898 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -1417,12 +1417,19 @@ static int renesas_i3c_probe(struct platform_device *pdev) return ret; for (i = 0; i < ARRAY_SIZE(renesas_i3c_irqs); i++) { + const char *irqname; + ret = platform_get_irq_byname(pdev, renesas_i3c_irqs[i].name); if (ret < 0) return ret; + irqname = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s:%s", dev_name(&pdev->dev), + renesas_i3c_irqs[i].desc); + if (!irqname) + return -ENOMEM; + ret = devm_request_irq(&pdev->dev, ret, renesas_i3c_irqs[i].isr, - 0, renesas_i3c_irqs[i].desc, i3c); + 0, irqname, i3c); if (ret) return ret; } From 46fa00a6c42251870551802b724027a10119d788 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:44 +0300 Subject: [PATCH 28/70] i3c: renesas: Drop unnecessary tab Remove an unnecessary tab to make the code cleaner. Reviewed-by: Frank Li Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-17-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 18ce95e69898..a665cd237a07 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -109,7 +109,7 @@ #define NCMDQP_DATA_LENGTH(x) FIELD_PREP(GENMASK(31, 16), x) #define NRSPQP 0x154 /* Normal Respone Queue */ -#define NRSPQP_NO_ERROR 0 +#define NRSPQP_NO_ERROR 0 #define NRSPQP_ERROR_CRC 1 #define NRSPQP_ERROR_PARITY 2 #define NRSPQP_ERROR_FRAME 3 From 4af1aacc580c1633be5d7b37b5847ba91c6ad97f Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Mon, 13 Jul 2026 16:05:45 +0300 Subject: [PATCH 29/70] i3c: renesas: Add runtime PM support On the SoCs where the Renesas I3C driver is enabled (RZ/G3S and RZ/G3E), the clocks of the IP are managed through a clock PM domain. To keep the I3C code simpler, the explicit clock handling was dropped along with the addition of runtime PM support, in favor of the runtime PM APIs. Only the code for getting tclk was preserved, as it is necessary to compute the I3C clock rate. All the APIs provided to the I3C subsystem through struct i3c_master_controller_ops are guarded with runtime PM APIs to enable/disable the controller at runtime. As the Renesas I3C driver implements an asynchronous transmit model by preparing a transfer and waiting for its completion through the ISR, renesas_i3c_abort_xfer() was added to disable interrupts and clear any pending IRQ status bits when there is no completion in the defined timeout. Along with this, renesas_i3c_wait_xfer() return type was changed to unsigned long. Add runtime PM support for the Renesas I3C driver. Signed-off-by: Claudiu Beznea Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260713130545.568657-18-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 140 ++++++++++++++++++++++++------- 1 file changed, 112 insertions(+), 28 deletions(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index a665cd237a07..2b501f31e874 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include "../internals.h" @@ -199,8 +200,6 @@ #define RENESAS_I3C_MAX_DEVS 8 #define I2C_INIT_MSG -1 -#define RENESAS_I3C_TCLK_IDX 1 - enum i3c_internal_state { I3C_INTERNAL_STATE_DISABLED, I3C_INTERNAL_STATE_CONTROLLER_IDLE, @@ -254,9 +253,10 @@ struct renesas_i3c_xferqueue { struct renesas_i3c { void __iomem *regs; - struct clk_bulk_data *clks; + struct clk *tclk; struct reset_control *presetn; struct reset_control *tresetn; + struct device *dev; struct renesas_i3c_xferqueue xferqueue; struct i3c_master_controller base; u8 addrs[RENESAS_I3C_MAX_DEVS]; @@ -269,7 +269,6 @@ struct renesas_i3c { u32 i3c_STDBR; u32 extbr; u16 maxdevs; - u8 num_clks; u8 refclk_div; }; @@ -449,7 +448,14 @@ static void renesas_i3c_irqs_mask_and_clear_locked(struct renesas_i3c *i3c) renesas_readl(i3c->regs, BST); } -static void renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xfer *xfer) +static void renesas_i3c_irqs_mask_and_clear(struct renesas_i3c *i3c) +{ + guard(spinlock_irqsave)(&i3c->xferqueue.lock); + + renesas_i3c_irqs_mask_and_clear_locked(i3c); +} + +static unsigned long renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xfer *xfer) { unsigned long time_left; @@ -458,6 +464,8 @@ static void renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xf time_left = wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000)); if (!time_left) renesas_i3c_dequeue_xfer(i3c, xfer); + + return time_left; } static void renesas_i3c_set_prts(struct renesas_i3c *i3c, u32 val) @@ -491,6 +499,12 @@ static void renesas_i3c_bus_enable(struct i3c_master_controller *m, bool i3c_mod static int renesas_i3c_reset(struct renesas_i3c *i3c) { u32 val; + int ret; + + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; renesas_writel(i3c->regs, BCTL, 0); renesas_set_bit(i3c->regs, RSTCTL, RSTCTL_RI3CRST); @@ -562,7 +576,7 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) int od_high_ticks, od_low_ticks, i2c_total_ticks; int ret; - i3c->rate = clk_get_rate(i3c->clks[RENESAS_I3C_TCLK_IDX].clk); + i3c->rate = clk_get_rate(i3c->tclk); if (!i3c->rate) return -EINVAL; @@ -633,6 +647,11 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m) if (ret) return ret; + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR); renesas_writel(i3c->regs, EXTBR, i3c->extbr); renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks)); @@ -655,6 +674,7 @@ static int renesas_i3c_daa(struct i3c_master_controller *m) { struct renesas_i3c *i3c = to_renesas_i3c(m); struct renesas_i3c_cmd *cmd; + unsigned long time_left; u32 olddevs, newdevs; u8 last_addr = 0, pos; int ret; @@ -667,6 +687,11 @@ static int renesas_i3c_daa(struct i3c_master_controller *m) cmd = xfer->cmds; cmd->rx_count = 0; + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + /* Enable I3C bus. */ renesas_i3c_bus_enable(m, true); @@ -701,7 +726,9 @@ static int renesas_i3c_daa(struct i3c_master_controller *m) NCMDQP_CMD(I3C_CCC_ENTDAA) | NCMDQP_DEV_INDEX(ret) | NCMDQP_DEV_COUNT(i3c->maxdevs - ret) | NCMDQP_TOC; - renesas_i3c_wait_xfer(i3c, xfer); + time_left = renesas_i3c_wait_xfer(i3c, xfer); + if (!time_left) + renesas_i3c_irqs_mask_and_clear(i3c); if (cmd->rx_count >= i3c->maxdevs) newdevs = 0; @@ -767,6 +794,7 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m, { struct renesas_i3c *i3c = to_renesas_i3c(m); struct renesas_i3c_cmd *cmd; + unsigned long time_left; int ret, pos = 0; if (ccc->id & I3C_CCC_DIRECT) { @@ -784,6 +812,11 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m, cmd->rnw = ccc->rnw; cmd->cmd0 = 0; + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + renesas_i3c_bus_enable(m, true); /* Calculate the command descriptor. */ @@ -818,7 +851,9 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m, } } - renesas_i3c_wait_xfer(i3c, xfer); + time_left = renesas_i3c_wait_xfer(i3c, xfer); + if (!time_left) + renesas_i3c_irqs_mask_and_clear(i3c); ret = xfer->ret; if (ret) @@ -835,7 +870,9 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_ struct i3c_master_controller *m = i3c_dev_get_master(dev); struct renesas_i3c *i3c = to_renesas_i3c(m); struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); - int i; + unsigned long time_left; + bool xfer_failed = false; + int i, ret; struct renesas_i3c_xfer *xfer __free(kfree) = renesas_i3c_alloc_xfer(i3c, 1); if (!xfer) @@ -843,6 +880,11 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_ init_completion(&xfer->comp); + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + /* Enable I3C bus. */ renesas_i3c_bus_enable(m, true); @@ -874,9 +916,14 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_ renesas_set_bit(i3c->regs, NTIE, NTIE_TDBEIE0); } - renesas_i3c_wait_xfer(i3c, xfer); + time_left = renesas_i3c_wait_xfer(i3c, xfer); + if (!time_left) + xfer_failed = true; } + if (xfer_failed) + renesas_i3c_irqs_mask_and_clear(i3c); + return 0; } @@ -885,12 +932,17 @@ static int renesas_i3c_attach_i3c_dev(struct i3c_dev_desc *dev) struct i3c_master_controller *m = i3c_dev_get_master(dev); struct renesas_i3c *i3c = to_renesas_i3c(m); struct renesas_i3c_i2c_dev_data *data; - int pos; + int pos, ret; pos = renesas_i3c_get_free_pos(i3c); if (pos < 0) return pos; + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + data = kzalloc_obj(*data); if (!data) return -ENOMEM; @@ -912,7 +964,12 @@ static int renesas_i3c_reattach_i3c_dev(struct i3c_dev_desc *dev, struct i3c_master_controller *m = i3c_dev_get_master(dev); struct renesas_i3c *i3c = to_renesas_i3c(m); struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); - int pos; + int pos, ret; + + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; pos = renesas_i3c_get_free_pos(i3c); @@ -940,8 +997,12 @@ static void renesas_i3c_detach_i3c_dev(struct i3c_dev_desc *dev) struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev); struct i3c_master_controller *m = i3c_dev_get_master(dev); struct renesas_i3c *i3c = to_renesas_i3c(m); + int ret; - renesas_writel(i3c->regs, DATBAS(data->index), 0); + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (!ret) + renesas_writel(i3c->regs, DATBAS(data->index), 0); i3c_dev_set_master_data(dev, NULL); i3c->addrs[data->index] = 0; @@ -957,7 +1018,9 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev, struct renesas_i3c *i3c = to_renesas_i3c(m); struct renesas_i3c_cmd *cmd; u8 start_bit = CNDCTL_STCND; - int i; + unsigned long time_left; + bool xfer_failed = false; + int i, ret; if (!i2c_nxfers) return 0; @@ -970,6 +1033,11 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev, xfer->is_i2c_xfer = true; cmd = xfer->cmds; + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + renesas_i3c_bus_enable(m, false); if (!(renesas_readl(i3c->regs, BCST) & BCST_BFREF)) { @@ -996,7 +1064,9 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev, renesas_set_bit(i3c->regs, NTSTE, NTSTE_TDBEE0); - wait_for_completion_timeout(&xfer->comp, m->i2c.timeout); + time_left = wait_for_completion_timeout(&xfer->comp, m->i2c.timeout); + if (!time_left) + xfer_failed = true; if (cmd->err) break; @@ -1005,6 +1075,10 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev, } renesas_i3c_dequeue_xfer(i3c, xfer); + + if (xfer_failed) + renesas_i3c_irqs_mask_and_clear(i3c); + return cmd->err; } @@ -1392,12 +1466,16 @@ static int renesas_i3c_probe(struct platform_device *pdev) if (IS_ERR(i3c->regs)) return PTR_ERR(i3c->regs); - ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &i3c->clks); - if (ret <= RENESAS_I3C_TCLK_IDX) - return dev_err_probe(&pdev->dev, ret < 0 ? ret : -EINVAL, - "Failed to get clocks (need > %d, got %d)\n", - RENESAS_I3C_TCLK_IDX, ret); - i3c->num_clks = ret; + i3c->tclk = devm_clk_get(&pdev->dev, "tclk"); + if (IS_ERR(i3c->tclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(i3c->tclk), "Failed to get tclk"); + + i3c->dev = &pdev->dev; + pm_runtime_set_autosuspend_delay(&pdev->dev, 300); + pm_runtime_use_autosuspend(&pdev->dev); + ret = devm_pm_runtime_enable(&pdev->dev); + if (ret) + return ret; i3c->tresetn = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn"); if (IS_ERR(i3c->tresetn)) @@ -1464,8 +1542,6 @@ static int renesas_i3c_suspend(struct device *dev) if (ret) goto err_mark_resumed; - clk_bulk_disable(i3c->num_clks, i3c->clks); - return 0; err_mark_resumed: @@ -1487,13 +1563,13 @@ static int renesas_i3c_resume(struct device *dev) if (ret) return ret; - ret = clk_bulk_enable(i3c->num_clks, i3c->clks); + ret = renesas_i3c_reset(i3c); if (ret) goto err_resets_asserted; - ret = renesas_i3c_reset(i3c); + ret = pm_runtime_resume_and_get(dev); if (ret) - goto err_clks_disable; + goto err_resets_asserted; /* Re-store I3C registers value. */ renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR); @@ -1512,15 +1588,23 @@ static int renesas_i3c_resume(struct device *dev) i2c_mark_adapter_resumed(&i3c->base.i2c); + pm_runtime_put_autosuspend(dev); + /* * I3C devices may have retained their dynamic address anyway. Do not * fail the resume because of DAA error. */ return 0; -err_clks_disable: - clk_bulk_disable(i3c->num_clks, i3c->clks); err_resets_asserted: + /* + * If this happens, there is no way to recover from this state without + * reloading the driver. We want to avoid keeping the reset line + * deasserted unnecessarily. The runtime paths will still work correctly + * even if the IP registers are accessed while reset is asserted (e.g. + * if a runtime path is triggered after a failed resume). Checked on + * RZ/G3S. + */ reset_control_bulk_assert(ARRAY_SIZE(resets), resets); return ret; } From bc0bc485c873743fb212f0d92d45686d5da8077a Mon Sep 17 00:00:00 2001 From: Swark Yang Date: Thu, 16 Jul 2026 20:51:11 -0700 Subject: [PATCH 30/70] dt-bindings: i3c: cdns: add Axiado AX3005 I3C variant Add binding for Axiado AX3005 I3C master. So far, no changes are known, so it can fall back to the cdns,i3c-master compatible. Signed-off-by: Swark Yang Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260716-upstream-axiado-ax3005-upstream-v3-4-c429095143ec@axiado.com Signed-off-by: Alexandre Belloni --- Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml b/Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml index 6fa3078074d0..280f0ada3c43 100644 --- a/Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml +++ b/Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml @@ -19,6 +19,7 @@ properties: - items: - enum: - axiado,ax3000-i3c + - axiado,ax3005-i3c - const: cdns,i3c-master reg: From a733069a1943f30922b90bde5eef3cb25b010f8b Mon Sep 17 00:00:00 2001 From: Can Peng Date: Wed, 15 Jul 2026 09:29:49 +0800 Subject: [PATCH 31/70] i3c: master: adi: add OF module alias for autoloading The Analog Devices I3C master driver can be built as a module and uses adi_i3c_master_of_match as its OF match table, but the table is not exported for module alias generation. Add the MODULE_DEVICE_TABLE(of, ...) entry so modpost can generate OF module aliases for OF based module autoloading. Fixes: a79ac2cdc91d ("i3c: master: Add driver for Analog Devices I3C Controller IP") Signed-off-by: Can Peng Reviewed-by: Frank Li Link: https://patch.msgid.link/20260715012949.180245-1-pengcan@kylinos.cn Signed-off-by: Alexandre Belloni --- drivers/i3c/master/adi-i3c-master.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c index cbf33f640c18..b35386260350 100644 --- a/drivers/i3c/master/adi-i3c-master.c +++ b/drivers/i3c/master/adi-i3c-master.c @@ -932,6 +932,7 @@ static const struct of_device_id adi_i3c_master_of_match[] = { { .compatible = "adi,i3c-master-v1" }, {} }; +MODULE_DEVICE_TABLE(of, adi_i3c_master_of_match); static int adi_i3c_master_probe(struct platform_device *pdev) { From dce830367d5f8b56520993a5f121cfd35773cabc Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Mon, 20 Jul 2026 13:05:09 +0530 Subject: [PATCH 32/70] dt-bindings: i3c: dw: Document missing optional core reset The DesignWare I3C master IP supports a core reset input, but the binding is missing the corresponding reset description. Document the optional reset property. Keep it optional because the reset line is integration-specific. Signed-off-by: Shubham Patil Reviewed-by: Rob Herring (Arm) Reviewed-by: Frank Li Reviewed-by: Radhey Shyam Pandey Link: https://patch.msgid.link/20260720073510.1869623-2-shubhamsanjay.patil@amd.com Signed-off-by: Alexandre Belloni --- Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml b/Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml index e803457d3f55..dc7ee38001d6 100644 --- a/Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml +++ b/Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml @@ -38,6 +38,9 @@ properties: interrupts: maxItems: 1 + resets: + maxItems: 1 + power-domains: maxItems: 1 From 52f6f5f43260d4118e9b137a61a3f2aa4a5b8adf Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Mon, 20 Jul 2026 13:05:10 +0530 Subject: [PATCH 33/70] i3c: master: dw: Drop redundant core reset name The DesignWare I3C master has a single reset line, so a dedicated reset name is redundant. Look up the reset by index by passing NULL instead of the "core_rst" name. Signed-off-by: Shubham Patil Reviewed-by: Frank Li Reviewed-by: Radhey Shyam Pandey Link: https://patch.msgid.link/20260720073510.1869623-3-shubhamsanjay.patil@amd.com Signed-off-by: Alexandre Belloni --- 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 84ffcb189338..33148e199281 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1613,7 +1613,7 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, return PTR_ERR(master->pclk); master->core_rst = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, - "core_rst"); + NULL); if (IS_ERR(master->core_rst)) return PTR_ERR(master->core_rst); From d2c743efd2d1ee64e94324664808f623dd865872 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Thu, 23 Jul 2026 10:57:47 +0300 Subject: [PATCH 34/70] i3c: master: Fix info leak and UAF in device unregister path i3c_master_unregister_i3c_devs() clears i3cdev->dev->desc before calling device_unregister(). During device_unregister(), device_del() emits a KOBJ_REMOVE uevent and unbinds the driver while the device descriptor is still expected to be valid. As a result, i3c_device_uevent() and a racing modalias_show() can observe a NULL desc and fall back to an uninitialized stack struct i3c_device_info, leaking kernel stack contents in the generated modalias. Driver .remove() callbacks may also encounter an unexpected NULL desc during unbind. Keep desc valid until device_unregister() has completed. Since device_unregister() drops the device reference and may free the device, take an extra reference with get_device() before unregistering. Clear desc afterwards and release the extra reference with put_device(). This preserves the release-time invariant that desc must be NULL while avoiding both the information leak and a potential use-after-free from writing desc after the device has been released. Reported-by: sashiko-bot@kernel.org Link: https://lore.kernel.org/linux-i3c/20260702190003.8BF741F000E9@smtp.kernel.org/ Fixes: 3a379bbcea0a ("i3c: Add core I3C infrastructure") Cc: stable@vger.kernel.org Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260723075747.34049-1-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index fd3e79d10c84..b66729c7c3ec 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -3048,11 +3048,12 @@ static void i3c_master_unregister_i3c_devs(struct i3c_master_controller *master) if (!i3cdev->dev) continue; - i3cdev->dev->desc = NULL; - if (device_is_registered(&i3cdev->dev->dev)) + if (device_is_registered(&i3cdev->dev->dev)) { + get_device(&i3cdev->dev->dev); device_unregister(&i3cdev->dev->dev); - else - put_device(&i3cdev->dev->dev); + } + i3cdev->dev->desc = NULL; + put_device(&i3cdev->dev->dev); i3cdev->dev = NULL; } } From 92f95300474b9f72a12c5896bea767d011cd847e Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Thu, 23 Jul 2026 17:23:10 +0800 Subject: [PATCH 35/70] i3c: master: require dests for read CCC commands Read CCC commands (rnw set) always need a destination array to store payload data. Extend the existing direct-CCC validation so cmd->dests is guaranteed non-NULL when cmd->rnw is set. Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202607222347.TXH6r6ie-lkp@intel.com/ Signed-off-by: Adrian Ng Ho Yin Reviewed-by: Frank Li Acked-by: Mukesh Kumar Savaliya Link: https://patch.msgid.link/153608c2ff6504fe29f5f727a23c53c41768a44a.1784796086.git.adrian.ho.yin.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index b66729c7c3ec..16c47df67e3a 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1039,7 +1039,8 @@ static int i3c_master_send_ccc_cmd_locked(struct i3c_master_controller *master, if (!master->ops->send_ccc_cmd) return -EOPNOTSUPP; - if ((cmd->id & I3C_CCC_DIRECT) && (!cmd->dests || !cmd->ndests)) + if ((cmd->rnw || (cmd->id & I3C_CCC_DIRECT)) && + (!cmd->dests || !cmd->ndests)) return -EINVAL; if (master->ops->supports_ccc_cmd && From 81e7c27b0d5cb3029fc01374c3a96019d3a9e673 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:43 +0000 Subject: [PATCH 36/70] 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 --- .../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 ee170021bee17124c13f42813607bca553a6b48a Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:44 +0000 Subject: [PATCH 37/70] 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 --- 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 16c47df67e3a..9f95d59078b1 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -13,10 +13,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -491,7 +493,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; @@ -501,8 +503,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) { @@ -837,7 +839,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 = { @@ -1105,7 +1107,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); } @@ -1998,7 +2000,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) { @@ -2692,8 +2694,8 @@ EXPORT_SYMBOL_GPL(i3c_master_do_daa); #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; @@ -2703,9 +2705,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 @@ -2721,14 +2727,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; @@ -2751,7 +2757,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; @@ -2768,14 +2774,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; @@ -2783,7 +2789,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; @@ -2792,25 +2798,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; } @@ -2820,10 +2826,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; @@ -2878,7 +2884,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; @@ -2997,7 +3003,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; @@ -3006,7 +3013,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); @@ -3308,7 +3317,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; @@ -3327,13 +3336,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 4d2a68793324..a16deb04b2e1 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 b46a4b3c5d1e312e74e5364a04c283a5c88e0916 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:45 +0000 Subject: [PATCH 38/70] 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 --- 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 9f95d59078b1..819235440445 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -5,6 +5,7 @@ * Author: Boris Brezillon */ +#include #include #include #include @@ -2693,6 +2694,55 @@ EXPORT_SYMBOL_GPL(i3c_master_do_daa); #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) @@ -2709,6 +2759,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; } @@ -2723,9 +2782,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); @@ -2780,8 +2836,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; @@ -2805,6 +2861,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; @@ -2816,7 +2940,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; } @@ -2884,8 +3014,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 bbaf8733b84846897d2d3b997ce650dd2d2539a4 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:46 +0000 Subject: [PATCH 39/70] 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 --- 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 819235440445..16260269926e 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -5,6 +5,7 @@ * Author: Boris Brezillon */ +#include #include #include #include @@ -1163,6 +1164,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 @@ -1939,8 +1985,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; @@ -1948,6 +1996,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) @@ -1970,6 +2034,16 @@ static int i3c_master_early_i3c_dev_add(struct i3c_master_controller *master, 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; } @@ -2344,6 +2418,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; @@ -2795,7 +2882,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) @@ -2813,7 +2900,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; @@ -2823,6 +2922,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)) || @@ -3458,6 +3565,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 7ad677baf761..c6947dcb0f57 100644 --- a/include/linux/i3c/ccc.h +++ b/include/linux/i3c/ccc.h @@ -34,6 +34,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 a16deb04b2e1..2dc139a217bf 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; }; @@ -517,6 +526,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 @@ -552,6 +566,7 @@ struct i3c_master_controller { struct list_head i2c; } boardinfo; struct i3c_bus bus; + u8 addr_method; struct workqueue_struct *wq; struct work_struct hj_work; struct work_struct reg_work; From a1dd42fb82fa71bf4cb6462b4803b55d844c1286 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:47 +0000 Subject: [PATCH 40/70] 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 --- 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 16260269926e..a8d3eb54ecbf 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2071,8 +2071,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); @@ -2473,8 +2482,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; @@ -2488,8 +2507,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; } @@ -2932,9 +2955,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); @@ -2957,10 +2987,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 3456baa2110c1fa31e7609ace50117346b99c3d4 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:48 +0000 Subject: [PATCH 41/70] 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 --- 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 a8d3eb54ecbf..f485b98805cf 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -345,15 +346,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 fc6963aad560aebeb926e677aa26746c42c6223b Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:49 +0000 Subject: [PATCH 42/70] 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 --- 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 33148e199281..a6d3fa548144 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -309,6 +309,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 0fd9549975b5b8186a78b654e02518113cc2ac93 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:50 +0000 Subject: [PATCH 43/70] 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 --- 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 a6d3fa548144..e2ab9f199eb3 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; @@ -556,13 +557,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; @@ -615,7 +631,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; @@ -1602,14 +1618,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); @@ -1665,15 +1695,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 97d7cfb62a215ed5c64c6e7772545be4afab167f Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:51 +0000 Subject: [PATCH 44/70] 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 --- 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 e2ab9f199eb3..3816a50a52cc 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1884,11 +1884,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, @@ -1897,7 +1898,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 7bf5a11dde2c957bf6751f6ba28fef66e848ac6d Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:52 +0000 Subject: [PATCH 45/70] 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 --- 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 cc40661cab21..6ba37a719300 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 */ @@ -348,12 +344,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; @@ -473,15 +464,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); @@ -519,8 +501,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; @@ -537,8 +518,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; @@ -680,69 +659,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 9cd3db0cda4741befa6de8af94eb371b9ec6d057 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:53 +0000 Subject: [PATCH 46/70] 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 --- 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 5c2d3ff5fce8..c4bf5475fcb3 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2354,12 +2354,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 6ba37a719300..9724cf70b61d 100644 --- a/drivers/hwmon/spd5118.c +++ b/drivers/hwmon/spd5118.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -464,6 +465,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); @@ -701,7 +723,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 "); From 2cdb9c2138bef5f767dd786431fd6ca7581177aa Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Tue, 21 Jul 2026 22:16:18 +0300 Subject: [PATCH 47/70] i3c: renesas: Drop unused structure member 'resuming' The struct renesas_i3c::resuming is a leftover from a rebase. It is not used anywhere within the driver. Drop it. Signed-off-by: Claudiu Beznea Reviewed-by: Frank Li Link: https://patch.msgid.link/20260721191618.1850795-1-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index 2b501f31e874..ff1a243a802e 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -262,7 +262,6 @@ struct renesas_i3c { u8 addrs[RENESAS_I3C_MAX_DEVS]; unsigned long rate; enum i3c_internal_state internal_state; - bool resuming; u32 free_pos; u32 dyn_addr; u32 i2c_STDBR; From 456f832e5fc26fbfd3b8200fd4553eee520cc377 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:25 +0300 Subject: [PATCH 48/70] i3c: master: Fix recursive locking during device registration i3c_master_register_new_i3c_devs() registers newly discovered devices while holding i3c_bus_normaluse_lock(), a down_read(). device_register() can immediately probe the device, and probe callbacks typically invoke I3C helpers that take i3c_bus_normaluse_lock() again, leading to a recursive acquisition of the same rwsem. rwsems do not support recursive read locking and can deadlock when a writer is waiting. See the "Recursive read locks" section of Documentation/locking/lockdep-design.rst. For example, with Intel LPSS I3C, LOCKDEP generates a WARNING like: # echo intel-lpss-i3c.0 > /sys/bus/platform/drivers/mipi-i3c-hci/unbind # echo intel-lpss-i3c.0 > /sys/bus/platform/drivers/mipi-i3c-hci/bind WARNING: possible recursive locking detected kworker/5:1/94 is trying to acquire lock: ffff88811c810d78 (&i3cbus->lock){++++}-{4:4}, at: i3c_device_match_id+0x45/0x370 but task is already holding lock: ffff88811c810d78 (&i3cbus->lock){++++}-{4:4}, at: i3c_master_reg_work_fn+0x21/0x5f0 Fix this by separating device creation from device registration. Populate desc->dev under the maintenance lock, collect the devices that still need registration into a local list, then release the lock before calling device_register(). Finally retake the lock and clean up any devices that failed to register. Use the maintenance lock rather than the normal-use lock while adding device objects. A write-side maintenance lock prevents readers from observing a partially initialized desc->dev during initial device population, or desc->dev disappearing if registration fails. The local list requires a list node, so add a list node member to struct i3c_device. Fixes: 3a379bbcea0a ("i3c: Add core I3C infrastructure") Cc: stable@vger.kernel.org Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-2-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 45 ++++++++++++++++++++++++++++---------- include/linux/i3c/master.h | 3 +++ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index f485b98805cf..d2fb1a110521 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2069,12 +2069,21 @@ static int i3c_master_early_i3c_dev_add(struct i3c_master_controller *master, static void i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) { + struct i3c_device *i3cdev, *tmp; struct i3c_dev_desc *desc; + LIST_HEAD(i3c_unreg_devs); int ret; if (!master->init_done) return; + i3c_bus_maintenance_lock(&master->bus); + + if (master->shutting_down) { + i3c_bus_maintenance_unlock(&master->bus); + return; + } + i3c_bus_for_each_i3cdev(&master->bus, desc) { if (desc->dev || !desc->info.dyn_addr || desc == master->this) continue; @@ -2104,25 +2113,37 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) if (desc->boardinfo) device_set_node(&desc->dev->dev, desc->boardinfo->fwnode); - ret = device_register(&desc->dev->dev); - if (ret) { - dev_err(&master->dev, - "Failed to add I3C device (err = %d)\n", ret); - desc->dev->desc = NULL; - put_device(&desc->dev->dev); - desc->dev = NULL; - } + list_add_tail(&desc->dev->node, &i3c_unreg_devs); } + + i3c_bus_maintenance_unlock(&master->bus); + + list_for_each_entry_safe(i3cdev, tmp, &i3c_unreg_devs, node) { + ret = device_register(&i3cdev->dev); + if (ret) + dev_err(&master->dev, "Failed to add I3C device (err = %d)\n", ret); + else + list_del_init(&i3cdev->node); + } + + i3c_bus_maintenance_lock(&master->bus); + + list_for_each_entry_safe(i3cdev, tmp, &i3c_unreg_devs, node) { + list_del(&i3cdev->node); + desc = i3cdev->desc; + i3cdev->desc = NULL; + put_device(&i3cdev->dev); + desc->dev = NULL; + } + + i3c_bus_maintenance_unlock(&master->bus); } static void i3c_master_reg_work_fn(struct work_struct *work) { struct i3c_master_controller *master = container_of(work, typeof(*master), reg_work); - i3c_bus_normaluse_lock(&master->bus); - if (!master->shutting_down) - i3c_master_register_new_i3c_devs(master); - i3c_bus_normaluse_unlock(&master->bus); + i3c_master_register_new_i3c_devs(master); } /** diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 2dc139a217bf..26535beb1e77 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -238,6 +238,8 @@ struct i3c_dev_desc { * every time the I3C device is rediscovered with a different dynamic * address assigned * @bus: I3C bus this device is attached to + * @node: unregistered device list node, only for use by + * i3c_master_register_new_i3c_devs(), it is not protected by a lock * * I3C device object exposed to I3C device drivers. The takes care of linking * this object to the relevant &struct_i3c_dev_desc one. @@ -248,6 +250,7 @@ struct i3c_device { struct device dev; struct i3c_dev_desc *desc; struct i3c_bus *bus; + struct list_head node; }; /* From 8bed7f4fa710914b7f05fd59998316bfb4d43385 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:26 +0300 Subject: [PATCH 49/70] i3c: Fix unlocked dereference of dev->desc in i3c_device_get_supported_xfer_mode() i3c_device_get_supported_xfer_mode() uses dev->desc to obtain the master controller. However, dev->desc must not be dereferenced unless bus->lock is held, and this function does not take that lock. The function only needs access to the master controller associated with the device's bus. Use dev->bus instead, which is always valid for the lifetime of the device and does not require dereferencing dev->desc. Fixes: 256a21743d91 ("i3c: Add HDR API support") Cc: stable@vger.kernel.org Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-3-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/device.c | 2 +- drivers/i3c/internals.h | 5 +++++ drivers/i3c/master.c | 6 ------ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c index 101eaa77de68..a3778282e84c 100644 --- a/drivers/i3c/device.c +++ b/drivers/i3c/device.c @@ -309,7 +309,7 @@ EXPORT_SYMBOL_GPL(i3c_device_match_id); */ 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); + return i3c_bus_to_i3c_master(dev->bus)->this->info.hdr_cap | BIT(I3C_SDR); } EXPORT_SYMBOL_GPL(i3c_device_get_supported_xfer_mode); diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h index 0f1f3f766623..86a36b951e0d 100644 --- a/drivers/i3c/internals.h +++ b/drivers/i3c/internals.h @@ -72,4 +72,9 @@ static inline void i3c_readl_fifo(const void __iomem *addr, void *buf, } } +static inline struct i3c_master_controller *i3c_bus_to_i3c_master(struct i3c_bus *i3cbus) +{ + return container_of(i3cbus, struct i3c_master_controller, bus); +} + #endif /* I3C_INTERNAL_H */ diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index d2fb1a110521..c7bb52b71d88 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -102,12 +102,6 @@ void i3c_bus_normaluse_unlock(struct i3c_bus *bus) up_read(&bus->lock); } -static struct i3c_master_controller * -i3c_bus_to_i3c_master(struct i3c_bus *i3cbus) -{ - return container_of(i3cbus, struct i3c_master_controller, bus); -} - static struct i3c_master_controller *dev_to_i3cmaster(struct device *dev) { return container_of(dev, struct i3c_master_controller, dev); From 4dc1b3eeba7991905a5b5b8129ebea51be7d87b7 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:27 +0300 Subject: [PATCH 50/70] i3c: master: Do not treat master device as a duplicate target i3c_master_search_i3c_dev_duplicate() searches the bus for another I3C device with the same PID as the reference device. The search can match master->this, causing the controller itself to be returned as a duplicate. Since the controller is not a target device, it cannot be a duplicate of one. Exclude master->this from matching so that the function only returns real duplicate target devices. Fixes: 3a379bbcea0a ("i3c: Add core I3C infrastructure") Cc: stable@vger.kernel.org Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Acked-by: Mukesh Savaliya Link: https://patch.msgid.link/20260807145638.168865-4-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index c7bb52b71d88..abb582645a2e 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2545,7 +2545,8 @@ i3c_master_search_i3c_dev_duplicate(struct i3c_dev_desc *refdev) i3c_bus_for_each_i3cdev(&master->bus, i3cdev) { if (i3cdev != refdev && i3cdev->info.pid && - i3cdev->info.pid == refdev->info.pid) + i3cdev->info.pid == refdev->info.pid && + i3cdev != master->this) return i3cdev; } From feb0ed76601f3c2f91f08688c5a7d8b9d382f720 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:28 +0300 Subject: [PATCH 51/70] i3c: master: Fix use-after-free of master->this sysfs attribute callbacks for the master controller device dereference master->this. However, master->this is freed in i3c_master_detach_free_devs() before the master device itself is released. As a result, sysfs accesses can dereference a freed master->this pointer, leading to a use-after-free. Keep master->this alive until i3c_masterdev_release(), which is called after the master device and its sysfs state are being torn down. Do not free master->this as part of the normal device detach path. On the error path in i3c_master_set_info(), reset master->this and bus.cur_master to NULL before freeing the allocated device. Fixes: 3a379bbcea0a ("i3c: Add core I3C infrastructure") Cc: stable@vger.kernel.org Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-5-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index abb582645a2e..2357874bb9d6 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -842,6 +842,11 @@ static struct attribute *i3c_masterdev_attrs[] = { }; ATTRIBUTE_GROUPS(i3c_masterdev); +static void i3c_master_free_i3c_dev(struct i3c_dev_desc *dev) +{ + kfree(dev); +} + static void i3c_masterdev_release(struct device *dev) { struct i3c_master_controller *master = dev_to_i3cmaster(dev); @@ -854,6 +859,8 @@ static void i3c_masterdev_release(struct device *dev) i3c_bus_cleanup(bus); fwnode_handle_put(dev->fwnode); + + i3c_master_free_i3c_dev(master->this); } static const struct device_type i3c_masterdev_type = { @@ -1125,11 +1132,6 @@ static void i3c_device_release(struct device *dev) kfree(i3cdev); } -static void i3c_master_free_i3c_dev(struct i3c_dev_desc *dev) -{ - kfree(dev); -} - static struct i3c_dev_desc * i3c_master_alloc_i3c_dev(struct i3c_master_controller *master, const struct i3c_device_info *info) @@ -2266,6 +2268,8 @@ int i3c_master_set_info(struct i3c_master_controller *master, return 0; err_free_dev: + master->bus.cur_master = NULL; + master->this = NULL; i3c_master_free_i3c_dev(i3cdev); return ret; @@ -2286,7 +2290,8 @@ static void i3c_master_detach_free_devs(struct i3c_master_controller *master) i3cdev->boardinfo->init_dyn_addr, I3C_ADDR_SLOT_FREE); - i3c_master_free_i3c_dev(i3cdev); + if (i3cdev != master->this) + i3c_master_free_i3c_dev(i3cdev); } list_for_each_entry_safe(i2cdev, i2ctmp, &master->bus.devs.i2c, From 4083d192f6f39130981cdb49615faade9162695d Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:29 +0300 Subject: [PATCH 52/70] i3c: Make dev->desc locking assumptions explicit i3c_device_get_info() takes the bus normal-use lock before accessing dev->desc. Under that lock, the descriptor pointer is guaranteed to be valid for the duration of the access. Remove the unnecessary NULL check on dev->desc so the code more clearly reflects the locking rules and expected descriptor lifetime. Signed-off-by: Adrian Hunter Acked-by: Mukesh Savaliya Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-6-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/device.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c index a3778282e84c..5e6df6de0283 100644 --- a/drivers/i3c/device.c +++ b/drivers/i3c/device.c @@ -101,8 +101,7 @@ void i3c_device_get_info(const struct i3c_device *dev, return; i3c_bus_normaluse_lock(dev->bus); - if (dev->desc) - *info = dev->desc->info; + *info = dev->desc->info; i3c_bus_normaluse_unlock(dev->bus); } EXPORT_SYMBOL_GPL(i3c_device_get_info); From e5e8dd2e959f470524c16ca444d001c90d6bb3ad Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:30 +0300 Subject: [PATCH 53/70] i3c: master: Fix potential UAF in i3c_device_uevent() i3c_device_uevent() dereferences i3cdev->desc without holding the bus normal-use lock. Since the descriptor pointer can be replaced concurrently, including when a uevent is generated from sysfs, this can result in dereferencing a stale descriptor and lead to a use-after-free. Use i3c_device_get_info() instead, which protects access to the descriptor with the normal-use lock. Commit 6cf7b65f7029 ("i3c: Use i3cdev->desc->info instead of calling i3c_device_get_info() to avoid deadlock") replaced the accessor with a direct descriptor dereference because i3c_device_get_info() would recursively acquire bus->lock during device registration. This change depends on "i3c: master: Fix recursive locking during device registration", which moves device registration out from under bus->lock and removes the possibility of that deadlock. Without that change, restoring the i3c_device_get_info() call would reintroduce the deadlock. Fixes: 6cf7b65f7029 ("i3c: Use i3cdev->desc->info instead of calling i3c_device_get_info() to avoid deadlock") Cc: stable@vger.kernel.org # requires "i3c: master: Fix recursive locking during device registration" Signed-off-by: Adrian Hunter Acked-by: Mukesh Savaliya Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-7-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- 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 2357874bb9d6..ee771ac0b203 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -316,8 +316,7 @@ static int i3c_device_uevent(const struct device *dev, struct kobj_uevent_env *e struct i3c_device_info devinfo; u16 manuf, part, ext; - if (i3cdev->desc) - devinfo = i3cdev->desc->info; + i3c_device_get_info(i3cdev, &devinfo); manuf = I3C_PID_MANUF_ID(devinfo.pid); part = I3C_PID_PART_ID(devinfo.pid); ext = I3C_PID_EXTRA_INFO(devinfo.pid); From f44d3b15326c498b7a285ee56d4fd8fbc64a8c15 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:31 +0300 Subject: [PATCH 54/70] i3c: master: Fix potential UAF in i3c_device_match() i3c_device_match() dereferences i3cdev->desc without holding the bus normal-use lock. Since the descriptor pointer can be replaced concurrently, the dereference can race with descriptor replacement and result in a use-after-free. Protect access to i3cdev->desc with the normal-use lock. While the lock is held, the descriptor is guaranteed to remain valid, so the NULL check is also unnecessary and can be removed. This change depends on "i3c: master: Fix recursive locking during device registration". Prior to that change, taking the normal-use lock in i3c_device_match() could recurse on bus->lock during device registration. Fixes: 3456baa2110c ("i3c: master: match I3C device through DT and ACPI") Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-8-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index ee771ac0b203..83e04a2c202a 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -347,8 +347,10 @@ static int i3c_device_match(struct device *dev, const struct device_driver *drv) i3cdev = dev_to_i3cdev(dev); i3cdrv = drv_to_i3cdrv(drv); - if (i3cdev->desc && i3cdev->desc->boardinfo) + i3c_bus_normaluse_lock(i3cdev->bus); + if (i3cdev->desc->boardinfo) static_addr_method = i3cdev->desc->boardinfo->static_addr_method; + i3c_bus_normaluse_unlock(i3cdev->bus); /* * SETAASA-based devices need not always have a matching ID since From 9fd18a865591d981c0081a3e663a65dbe4a64eb9 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:32 +0300 Subject: [PATCH 55/70] i3c: master: Support IBI-based wakeup capability An I3C controller acts as a bus controller for one or more I3C devices. If the controller can wake the system in response to an In-Band Interrupt (IBI), then any device on that bus that is capable of generating IBIs can potentially be used as a wakeup source. Add an ibi_wakeup flag to struct i3c_master_controller so controller drivers can advertise support for IBI-based wakeup. If set, mark IBI-capable I3C devices as wakeup capable when they are registered, allowing wakeup management through the standard device wakeup framework. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Acked-by: Mukesh Savaliya Link: https://patch.msgid.link/20260807145638.168865-9-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 4 ++++ include/linux/i3c/master.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 83e04a2c202a..2616354dbe54 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2110,6 +2110,10 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) if (desc->boardinfo) device_set_node(&desc->dev->dev, desc->boardinfo->fwnode); + /* If the device has IBI capability, set as wakeup capable */ + if (master->ibi_wakeup && (desc->info.bcr & I3C_BCR_IBI_REQ_CAP)) + device_set_wakeup_capable(&desc->dev->dev, true); + list_add_tail(&desc->dev->node, &i3c_unreg_devs); } diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 26535beb1e77..9d675d01522c 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -524,6 +524,7 @@ struct i3c_master_controller_ops { * @hotjoin: true if the master support hotjoin * @rpm_allowed: true if Runtime PM allowed * @rpm_ibi_allowed: true if IBI and Hot-Join allowed while runtime suspended + * @ibi_wakeup: IBI can wakeup the system * @shutting_down: set to true when master begins shutdown or unregister * @boardinfo.i3c: list of I3C boardinfo objects * @boardinfo.i2c: list of I2C boardinfo objects @@ -563,6 +564,7 @@ struct i3c_master_controller { unsigned int hotjoin: 1; unsigned int rpm_allowed: 1; unsigned int rpm_ibi_allowed: 1; + unsigned int ibi_wakeup: 1; bool shutting_down; struct { struct list_head i3c; From ff2b20f8f1ff9680ca1fce315e1d5eddd687266d Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:33 +0300 Subject: [PATCH 56/70] i3c: master: Report wakeup events for IBIs An I3C device configured as a wakeup source can wake the system by generating an In-Band Interrupt (IBI). When an IBI is queued for processing, record a wakeup event for the device if wakeup is enabled. Use a 100 ms processing interval to give the I3C device driver time to process the IBI. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-10-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 2616354dbe54..6c5341491944 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -3384,6 +3384,9 @@ static void i3c_master_unregister_i3c_devs(struct i3c_master_controller *master) } } +/* Approximate time for IBI handler to run */ +#define I3C_WAKEUP_PROCESSING_TIME_MS 100 + /** * i3c_master_queue_ibi() - Queue an IBI * @dev: the device this IBI is coming from @@ -3397,6 +3400,9 @@ void i3c_master_queue_ibi(struct i3c_dev_desc *dev, struct i3c_ibi_slot *slot) if (!dev->ibi || !slot) return; + if (device_may_wakeup(&dev->dev->dev)) + pm_wakeup_event(&dev->dev->dev, I3C_WAKEUP_PROCESSING_TIME_MS); + atomic_inc(&dev->ibi->pending_ibis); queue_work(dev->ibi->wq, &slot->work); } From 60ff731f06909f9b54af27d70f00a41bd84c6246 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:34 +0300 Subject: [PATCH 57/70] i3c: master: Add helper to query bus wakeup requirements Add i3c_master_has_wakeup_enabled_devs(), which iterates over the devices on an I3C bus and reports whether any of them are enabled for system wakeup and have IBI enabled. Controller drivers can use this helper to determine whether wakeup support must remain available while the system is suspended. Acked-by : Mukesh Savaliya Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-11-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 35 +++++++++++++++++++++++++++++++++++ include/linux/i3c/master.h | 1 + 2 files changed, 36 insertions(+) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 6c5341491944..afcd7a21a3e6 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2147,6 +2147,41 @@ static void i3c_master_reg_work_fn(struct work_struct *work) i3c_master_register_new_i3c_devs(master); } +/** + * i3c_master_has_wakeup_enabled_devs() - check if any device can wake the system + * @master: I3C master controller + * + * Iterate over devices on the bus and return true if any device has + * system wakeup enabled and IBI enabled. + * + * Whether a device is enabled for system wakeup is user space policy, + * settable at any time through the device's power/wakeup sysfs attribute, + * so the answer is only stable once user space is frozen. Call this from + * a system suspend callback. + * + * Return: true if any device may wake the system via IBI, false otherwise. + */ +bool i3c_master_has_wakeup_enabled_devs(struct i3c_master_controller *master) +{ + struct i3c_dev_desc *desc; + bool wakeup = false; + + i3c_bus_normaluse_lock(&master->bus); + i3c_bus_for_each_i3cdev(&master->bus, desc) { + if (!desc->dev || desc == master->this || !device_may_wakeup(&desc->dev->dev)) + continue; + guard(mutex)(&desc->ibi_lock); + if (desc->ibi && desc->ibi->enabled) { + wakeup = true; + break; + } + } + i3c_bus_normaluse_unlock(&master->bus); + + return wakeup; +} +EXPORT_SYMBOL_GPL(i3c_master_has_wakeup_enabled_devs); + /** * i3c_master_dma_map_single() - Map buffer for single DMA transfer * @dev: device object of a device doing DMA diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 9d675d01522c..82d9886e7f12 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -765,6 +765,7 @@ void i3c_generic_ibi_recycle_slot(struct i3c_generic_ibi_pool *pool, struct i3c_ibi_slot *slot); void i3c_master_queue_ibi(struct i3c_dev_desc *dev, struct i3c_ibi_slot *slot); +bool i3c_master_has_wakeup_enabled_devs(struct i3c_master_controller *master); struct i3c_ibi_slot *i3c_master_get_free_ibi_slot(struct i3c_dev_desc *dev); From 087efc8e88e2955ac2e8895402bd0e7fc1cfc314 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:35 +0300 Subject: [PATCH 58/70] i3c: master: Reject IBI requests from non-IBI-capable devices i3c_device_request_ibi() does not verify that a device advertises IBI support before attempting to set up IBI handling. Add a check for I3C_BCR_IBI_REQ_CAP and fail with -EOPNOTSUPP when IBI support is not reported by the device. This keeps IBI setup consistent with other IBI-related functionality, such as exposing wakeup capability only for IBI-capable devices. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-12-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/device.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c index 5e6df6de0283..f1ba363b22a1 100644 --- a/drivers/i3c/device.c +++ b/drivers/i3c/device.c @@ -204,12 +204,14 @@ int i3c_device_request_ibi(struct i3c_device *dev, return ret; i3c_bus_normaluse_lock(dev->bus); - if (dev->desc) { + if (!dev->desc) { + ret = -ENOENT; + } else if (!(dev->desc->info.bcr & I3C_BCR_IBI_REQ_CAP)) { + ret = -EOPNOTSUPP; + } else { 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); From 94da8edf849bc769988609205750166fef415573 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:36 +0300 Subject: [PATCH 59/70] i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI Keep the PCI wakeup state aligned with the wakeup requirements of the devices served by the controller(s). The PCI function is the wakeup source for HCI instances exposed beneath it. However, wakeup is only needed when at least one attached I3C device is enabled as a wakeup source. During suspend, check whether any HCI instance has a wakeup-enabled I3C device and enable wakeup for the PCI function only in that case. Otherwise leave PCI wakeup disabled. Note, the suspend callback is used for both system and runtime suspend. Although this change may update the PCI wakeup state during runtime suspend, it does so only when the required wakeup state changes. Moreover, PCI wakeup-capable devices already have PME wakeup armed for runtime suspend, so changing the wakeup-enabled state does not affect runtime PM wakeup behavior. Note also, since the PCI wakeup state is derived from the wakeup configuration of the attached I3C devices, the PCI device power/wakeup sysfs attribute no longer provides independent wakeup control. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-13-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- .../master/mipi-i3c-hci/mipi-i3c-hci-pci.c | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c index 5a9e2a43eff8..42c172e82622 100644 --- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c +++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c @@ -265,6 +265,8 @@ static bool mipi_i3c_hci_pci_is_operational(struct device *dev, bool update) struct mipi_i3c_hci_pci_pm_data { struct device *dev[INST_MAX]; int dev_cnt; + bool can_wakeup; + bool may_wakeup; }; static bool mipi_i3c_hci_pci_is_mfd(struct device *dev) @@ -272,6 +274,13 @@ static bool mipi_i3c_hci_pci_is_mfd(struct device *dev) return dev_is_platform(dev) && mfd_get_cell(to_platform_device(dev)); } +static bool mipi_i3c_hci_pci_any_wakeup_enabled(struct device *dev) +{ + struct i3c_hci *hci = dev_get_drvdata(dev); + + return i3c_master_has_wakeup_enabled_devs(&hci->master); +} + static int mipi_i3c_hci_pci_suspend_instance(struct device *dev, void *data) { struct mipi_i3c_hci_pci_pm_data *pm_data = data; @@ -287,6 +296,9 @@ static int mipi_i3c_hci_pci_suspend_instance(struct device *dev, void *data) pm_data->dev[pm_data->dev_cnt++] = dev; + if (pm_data->can_wakeup && mipi_i3c_hci_pci_any_wakeup_enabled(dev)) + pm_data->may_wakeup = true; + return 0; } @@ -317,12 +329,19 @@ static int mipi_i3c_hci_pci_suspend(struct device *dev) if (!hci->info->control_instance_pm) return 0; + pm_data.can_wakeup = device_can_wakeup(dev); + ret = device_for_each_child_reverse(dev, &pm_data, mipi_i3c_hci_pci_suspend_instance); - if (ret) + if (ret) { for (int i = 0; i < pm_data.dev_cnt; i++) i3c_hci_rpm_resume(pm_data.dev[i]); + return ret; + } - return ret; + if (device_may_wakeup(dev) != pm_data.may_wakeup) + device_set_wakeup_enable(dev, pm_data.may_wakeup); + + return 0; } static int mipi_i3c_hci_pci_resume(struct device *dev) From 842e46925ef97d6bf9cc3760305184d53de67beb Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:37 +0300 Subject: [PATCH 60/70] i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev() The MIPI I3C HCI driver needs to identify the underlying system device used for DMA mapping and PM operations. The logic for determining that device is currently embedded in the DMA implementation. Factor this code out into i3c_hci_sysdev() so it can be shared by other parts of the driver and keep the device-selection logic in one place. The explanatory comment moves with the code, reworked as kernel-doc now that it documents a function rather than an inline block. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Acked-by: Mukesh Savaliya Link: https://patch.msgid.link/20260807145638.168865-14-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 16 ++++++++++++++++ drivers/i3c/master/mipi-i3c-hci/dma.c | 15 +-------------- drivers/i3c/master/mipi-i3c-hci/hci.h | 2 ++ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index cfe9b5390b56..f95da427d7a9 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -117,6 +118,21 @@ static inline struct i3c_hci *to_i3c_hci(struct i3c_master_controller *m) return container_of(m, struct i3c_hci, master); } +/** + * i3c_hci_sysdev() - Get the device to use for DMA + * @dev: Device the HCI controller is bound to + * + * When an IOMMU is enabled, DMA API calls must use the device that IOMMU + * setup was done for. Under PCI enumeration that is the PCI device, not + * the "mipi-i3c-hci" platform device below it. + * + * Return: @dev's parent if it is a PCI device, otherwise @dev. + */ +struct device *i3c_hci_sysdev(struct device *dev) +{ + return dev->parent && dev_is_pci(dev->parent) ? dev->parent : dev; +} + static void i3c_hci_set_master_dyn_addr(struct i3c_hci *hci) { reg_write(MASTER_DEVICE_ADDR, diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c index 0672ed1132f8..7c2b20474130 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dma.c +++ b/drivers/i3c/master/mipi-i3c-hci/dma.c @@ -15,7 +15,6 @@ #include #include #include -#include #include "hci.h" #include "cmd.h" @@ -301,23 +300,11 @@ static int hci_dma_init(struct i3c_hci *hci) { struct hci_rings_data *rings; struct hci_rh_data *rh; - struct device *sysdev; u32 regval; unsigned int i, nr_rings, xfers_sz, resps_sz; unsigned int ibi_status_ring_sz, ibi_data_ring_sz; int ret; - /* - * Set pointer to a physical device that does DMA and has IOMMU setup - * done for it in case of enabled IOMMU and use it with the DMA API. - * Here such device is either - * "mipi-i3c-hci" platform device (OF/ACPI enumeration) parent or - * grandparent (PCI enumeration). - */ - sysdev = hci->master.dev.parent; - if (sysdev->parent && dev_is_pci(sysdev->parent)) - sysdev = sysdev->parent; - regval = rhs_reg_read(CONTROL); nr_rings = FIELD_GET(MAX_HEADER_COUNT_CAP, regval); dev_dbg(&hci->master.dev, "%d DMA rings available\n", nr_rings); @@ -332,7 +319,7 @@ static int hci_dma_init(struct i3c_hci *hci) return -ENOMEM; hci->io_data = rings; rings->total = nr_rings; - rings->sysdev = sysdev; + rings->sysdev = i3c_hci_sysdev(hci->master.dev.parent); for (i = 0; i < rings->total; i++) { u32 offset = rhs_reg_read(RHn_OFFSET(i)); diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h index b3d9803b1968..b8d2a3d680f8 100644 --- a/drivers/i3c/master/mipi-i3c-hci/hci.h +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h @@ -184,6 +184,8 @@ void amd_set_resp_buf_thld(struct i3c_hci *hci); void i3c_hci_sync_irq_inactive(struct i3c_hci *hci); int i3c_hci_process_xfer(struct i3c_hci *hci, struct hci_xfer *xfer, int n); +struct device *i3c_hci_sysdev(struct device *dev); + #define DEFAULT_AUTOSUSPEND_DELAY_MS 1000 int i3c_hci_rpm_suspend(struct device *dev); From 1421e948a32a8dfe08ff80f54dff95445d8cc51f Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:38 +0300 Subject: [PATCH 61/70] i3c: mipi-i3c-hci: Advertise IBI wakeup capability Set master->ibi_wakeup during probe when the associated system device advertises wakeup capability, allowing the I3C core to mark IBI-capable I3C devices as wakeup capable. Tweak the comment for i3c_hci_sysdev() to mention the new usage. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-15-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index f95da427d7a9..06acf6cf5a0d 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -119,12 +119,13 @@ static inline struct i3c_hci *to_i3c_hci(struct i3c_master_controller *m) } /** - * i3c_hci_sysdev() - Get the device to use for DMA + * i3c_hci_sysdev() - Get the device to use for DMA and system PM * @dev: Device the HCI controller is bound to * * When an IOMMU is enabled, DMA API calls must use the device that IOMMU * setup was done for. Under PCI enumeration that is the PCI device, not - * the "mipi-i3c-hci" platform device below it. + * the "mipi-i3c-hci" platform device below it. The same device owns + * system PM and wakeup configuration. * * Return: @dev's parent if it is a PCI device, otherwise @dev. */ @@ -1180,6 +1181,9 @@ static int i3c_hci_probe(struct platform_device *pdev) if (hci->quirks & HCI_QUIRK_RPM_IBI_ALLOWED) hci->master.rpm_ibi_allowed = true; + if (device_can_wakeup(i3c_hci_sysdev(&pdev->dev))) + hci->master.ibi_wakeup = true; + return i3c_master_register(&hci->master, &pdev->dev, &i3c_hci_ops, false); } From 7315aad228c29508ec77ab64a7c75377981e8c4f Mon Sep 17 00:00:00 2001 From: Tze Yee Ng Date: Fri, 31 Jul 2026 01:01:39 -0700 Subject: [PATCH 62/70] i3c: master: dw-i3c-master: fix OD timing for first broadcast Implement ->set_speed() so the I3C core can switch open-drain timing for the first broadcast address per spec: I3C_OPEN_DRAIN_SLOW_SPEED programs tHIGH_INIT (200 ns) before RSTDAA, and I3C_OPEN_DRAIN_NORMAL_SPEED restores normal OD timing afterward. Cache the normal OD register value during bus init and use a separate od_hcnt for the slow path so SDR extended timing remains derived from the normal PP hcnt. For AMD_I3C_OD_PP_TIMING, cache AMD_I3C_OD_TIMING as the normal OD baseline and stop rewriting OD timing in send_ccc_cmd()/runtime resume so I3C_OPEN_DRAIN_SLOW_SPEED is preserved through RSTDAA. Use PM_RUNTIME_ACQUIRE_AUTOSUSPEND() in set_speed(). Compute od_hcnt with DIV_ROUND_UP_ULL() for 32-bit safety and clamp it to U8_MAX to match the 8-bit I3C_OD_HCNT field. Fixes I2C devices with spike filters not being detected on mixed buses. Signed-off-by: Tze Yee Ng Reviewed-by: Frank Li Link: https://patch.msgid.link/d789219ca0418898a1ef2bf9295b4f96ca7b4209.1785484707.git.tze.yee.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/dw-i3c-master.c | 82 +++++++++++++++++++++++------- drivers/i3c/master/dw-i3c-master.h | 1 + include/linux/i3c/master.h | 1 + 3 files changed, 67 insertions(+), 17 deletions(-) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 3816a50a52cc..405089f181c4 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -572,6 +572,13 @@ static unsigned long dw_i3c_master_get_core_rate(struct dw_i3c_master *master) return core_rate_prop; } +static void amd_configure_od_pp_quirk(struct dw_i3c_master *master) +{ + master->i3c_od_timing = AMD_I3C_OD_TIMING; + master->i3c_od_timing_normal = AMD_I3C_OD_TIMING; + master->i3c_pp_timing = AMD_I3C_PP_TIMING; +} + static int dw_i3c_clk_cfg(struct dw_i3c_master *master) { unsigned long core_rate, core_period; @@ -610,6 +617,18 @@ static int dw_i3c_clk_cfg(struct dw_i3c_master *master) scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt); writel(scl_timing, master->regs + SCL_I3C_OD_TIMING); master->i3c_od_timing = scl_timing; + master->i3c_od_timing_normal = scl_timing; + + /* + * AMD legacy platforms need fixed OD/PP timings. Cache them as the + * normal OD baseline so set_speed(NORMAL) restores AMD values, and + * set_speed(SLOW) can stretch HCNT while keeping the AMD LCNT. + */ + if (master->quirks & AMD_I3C_OD_PP_TIMING) { + amd_configure_od_pp_quirk(master); + writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING); + writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING); + } lcnt = DIV_ROUND_UP(core_rate, I3C_BUS_SDR1_SCL_RATE) - hcnt; scl_timing = SCL_EXT_LCNT_1(lcnt); @@ -825,12 +844,6 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc) return ret; } -static void amd_configure_od_pp_quirk(struct dw_i3c_master *master) -{ - master->i3c_od_timing = AMD_I3C_OD_TIMING; - master->i3c_pp_timing = AMD_I3C_PP_TIMING; -} - static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, struct i3c_ccc_cmd *ccc) { @@ -840,13 +853,6 @@ static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, if (ccc->id == I3C_CCC_ENTDAA) return -EINVAL; - /* AMD platform specific OD and PP timings */ - if (master->quirks & AMD_I3C_OD_PP_TIMING) { - amd_configure_od_pp_quirk(master); - writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING); - writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING); - } - ret = pm_runtime_resume_and_get(master->dev); if (ret < 0) { dev_err(master->dev, @@ -1531,6 +1537,50 @@ static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } +static int dw_i3c_master_set_speed(struct i3c_master_controller *m, + enum i3c_open_drain_speed speed) +{ + struct dw_i3c_master *master = to_dw_i3c_master(m); + unsigned long core_rate; + u32 scl_timing, od_hcnt; + u8 lcnt; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(master->dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + switch (speed) { + case I3C_OPEN_DRAIN_SLOW_SPEED: + core_rate = dw_i3c_master_get_core_rate(master); + if (!core_rate) + return -EINVAL; + + lcnt = SCL_I3C_TIMING_LCNT(master->i3c_od_timing_normal); + od_hcnt = DIV_ROUND_UP_ULL((u64)I3C_BUS_THIGH_INIT_OD_MIN_NS * + core_rate, NSEC_PER_SEC) - 1; + if (od_hcnt < SCL_I3C_TIMING_CNT_MIN) + od_hcnt = SCL_I3C_TIMING_CNT_MIN; + else if (od_hcnt > U8_MAX) + od_hcnt = U8_MAX; + scl_timing = SCL_I3C_TIMING_HCNT(od_hcnt) | + SCL_I3C_TIMING_LCNT(lcnt); + writel(scl_timing, master->regs + SCL_I3C_OD_TIMING); + master->i3c_od_timing = scl_timing; + break; + + case I3C_OPEN_DRAIN_NORMAL_SPEED: + writel(master->i3c_od_timing_normal, + master->regs + SCL_I3C_OD_TIMING); + master->i3c_od_timing = master->i3c_od_timing_normal; + break; + + default: + return -EINVAL; + } + + return 0; +} + static int dw_i3c_master_set_dev_nack_retry(struct i3c_master_controller *m, unsigned int dev_nack_retry_cnt) { @@ -1585,6 +1635,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_speed = dw_i3c_master_set_speed, .set_dev_nack_retry = dw_i3c_master_set_dev_nack_retry, }; @@ -1780,10 +1831,7 @@ static void dw_i3c_master_restore_addrs(struct dw_i3c_master *master) static void dw_i3c_master_restore_timing_regs(struct dw_i3c_master *master) { - /* AMD platform specific OD and PP timings */ - if (master->quirks & AMD_I3C_OD_PP_TIMING) - amd_configure_od_pp_quirk(master); - + /* Preserve cached OD timing; it may be the SLOW setting from set_speed(). */ writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING); writel(master->bus_free_timing, master->regs + BUS_FREE_TIMING); writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING); diff --git a/drivers/i3c/master/dw-i3c-master.h b/drivers/i3c/master/dw-i3c-master.h index 28e9348f2153..17ad817d1f8e 100644 --- a/drivers/i3c/master/dw-i3c-master.h +++ b/drivers/i3c/master/dw-i3c-master.h @@ -46,6 +46,7 @@ struct dw_i3c_master { u32 dev_addr; u32 i3c_pp_timing; u32 i3c_od_timing; + u32 i3c_od_timing_normal; u32 ext_lcnt_timing; u32 bus_free_timing; u32 i2c_fm_timing; diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 82d9886e7f12..f7ceec2b4477 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -272,6 +272,7 @@ struct i3c_device { #define I3C_BUS_THIGH_MIXED_MAX_NS 41 #define I3C_BUS_TIDLE_MIN_NS 200000 #define I3C_BUS_TLOW_OD_MIN_NS 200 +#define I3C_BUS_THIGH_INIT_OD_MIN_NS 200 /** * enum i3c_bus_mode - I3C bus mode From b6e56fd8c8a8ac7992868a28702192372ab04015 Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Fri, 31 Jul 2026 09:01:46 +0200 Subject: [PATCH 63/70] i3c: renesas: Don't register devices when ENTDAA times out renesas_i3c_daa() derives the number of newly assigned dynamic addresses from cmd->rx_count, which the response ISR sets to the number of address slots ENTDAA left unassigned. It starts out as zero, which already means "every address was assigned", so a timed out transfer leaves that value in place and it gets used as a result. On a bus with no target connected the ENTDAA times out and the driver registers RENESAS_I3C_MAX_DEVS devices that are not there, each costing the core two seconds on a GETPID that can only time out: i3c i3c-0: Failed to add I3C device at address 9, error -110 ... i3c i3c-0: Failed to add I3C device at address 16, error -110 Start from maxdevs instead: no address is assigned before ENTDAA runs, and the existing rx_count >= maxdevs check then reports an empty bus. Fixes: d028219a9f14 ("i3c: master: Add basic driver for the Renesas I3C controller") Signed-off-by: Tommaso Merciai Reviewed-by: Claudiu Beznea Tested-by: Claudiu Beznea # on RZ/G3S Reviewed-by: Frank Li Link: https://patch.msgid.link/20260731070150.2519825-1-tommaso.merciai.xr@bp.renesas.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/renesas-i3c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c index ff1a243a802e..28c0927a0179 100644 --- a/drivers/i3c/master/renesas-i3c.c +++ b/drivers/i3c/master/renesas-i3c.c @@ -684,7 +684,7 @@ static int renesas_i3c_daa(struct i3c_master_controller *m) init_completion(&xfer->comp); cmd = xfer->cmds; - cmd->rx_count = 0; + cmd->rx_count = i3c->maxdevs; PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm); ret = PM_RUNTIME_ACQUIRE_ERR(&pm); From 942e9a5b676ee54717d891e42135e8cc75b86b58 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 13 Aug 2026 12:42:25 +0800 Subject: [PATCH 64/70] i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding Use the well defined COMMAND_PORT_TRANSFER_ARG macro instead of hardcoding '1'. Signed-off-by: Jisheng Zhang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260813044225.22237-1-jszhang@kernel.org Signed-off-by: Alexandre Belloni --- 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 405089f181c4..7038aa3bf4c7 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -921,7 +921,7 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m) goto rpm_out; } cmd = &xfer->cmds[0]; - cmd->cmd_hi = 0x1; + cmd->cmd_hi = COMMAND_PORT_TRANSFER_ARG; cmd->cmd_lo = COMMAND_PORT_DEV_COUNT(master->maxdevs - pos) | COMMAND_PORT_DEV_INDEX(pos) | COMMAND_PORT_CMD(I3C_CCC_ENTDAA) | From 9939e4cf593ddd23f5b4719bbfd7c894d64a3b7e Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 13 Aug 2026 12:46:03 +0800 Subject: [PATCH 65/70] i3c: dw: make struct dw_i3c_cmd smaller The dw_i3c_cmd is dynamically allocated, make it smaller. For example on 64bit platforms, we reduce the size from 48 bytes to 32 bytes. Signed-off-by: Jisheng Zhang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260813044603.22425-1-jszhang@kernel.org Signed-off-by: Alexandre Belloni --- 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 7038aa3bf4c7..7c88ed03f0af 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -248,10 +248,10 @@ struct dw_i3c_cmd { u32 cmd_lo; u32 cmd_hi; u16 tx_len; - const void *tx_buf; u16 rx_len; - void *rx_buf; u8 error; + const void *tx_buf; + void *rx_buf; }; struct dw_i3c_xfer { From ff2eee2b8686fc9826b3e360435fb25460953da9 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Fri, 14 Aug 2026 12:03:45 +0800 Subject: [PATCH 66/70] i3c: dw: rename "pclk" to "apb" to match dt-binding Change clock name "pclk" to "apb" to match dt-binding doc. No upstream device tree sources currently use the "pclk" clock name, so no any backward compatibility issues. Fixes: a0d48ebf39ce ("i3c: dw: Add optional apb clock") Signed-off-by: Jisheng Zhang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260814040345.23033-1-jszhang@kernel.org Signed-off-by: Alexandre Belloni --- 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 7c88ed03f0af..0a3799a4ce87 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1691,7 +1691,7 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, return -EINVAL; } - master->pclk = devm_clk_get_optional_enabled(&pdev->dev, "pclk"); + master->pclk = devm_clk_get_optional_enabled(&pdev->dev, "apb"); if (IS_ERR(master->pclk)) return PTR_ERR(master->pclk); From 308ecb824db329a8e22dd0b10f2a3411a6fbbde3 Mon Sep 17 00:00:00 2001 From: Jian-Ming Liao Date: Tue, 18 Aug 2026 18:41:04 +0800 Subject: [PATCH 67/70] i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode In PIO mode initialization, STAT_IBI_STATUS_THLD was missing from pio->enabled_irqs. As a result, the host controller interrupt signal for IBI threshold was never enabled when transfer starts, preventing IBI status descriptors from being properly processed in PIO mode. Include STAT_IBI_STATUS_THLD in pio->enabled_irqs so that the IBI threshold interrupt is activated alongside error interrupts upon the first transfer. Fixes: 9ad9a52cce28 ("i3c/master: introduce the mipi-i3c-hci driver") Co-developed-by: Patrick Yen Signed-off-by: Patrick Yen Signed-off-by: Jian-Ming Liao Reviewed-by: Frank Li Link: https://patch.msgid.link/20260818104106.763772-2-Jm_Liao@asmedia.com.tw Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/pio.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c index ff2657ee220b..a1341d66bc65 100644 --- a/drivers/i3c/master/mipi-i3c-hci/pio.c +++ b/drivers/i3c/master/mipi-i3c-hci/pio.c @@ -185,8 +185,11 @@ static void __hci_pio_init(struct i3c_hci *hci, u32 *size_val_ptr) pio_reg_write(INTR_SIGNAL_ENABLE, 0x0); pio_reg_write(INTR_STATUS_ENABLE, 0xffffffff); - /* Always accept error interrupts (will be activated on first xfer) */ - pio->enabled_irqs = STAT_ALL_ERRORS; + /* + * Always accept error interrupts and IBI threshold interrupt + * (will be activated on first xfer). + */ + pio->enabled_irqs = STAT_ALL_ERRORS | STAT_IBI_STATUS_THLD; } static void hci_pio_suspend(struct i3c_hci *hci) From ee53e1787fb09d6345995c19ae49f088e2e6ba26 Mon Sep 17 00:00:00 2001 From: Jian-Ming Liao Date: Tue, 18 Aug 2026 18:41:05 +0800 Subject: [PATCH 68/70] i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2 Support explicit enablement and starting of PIO queues as required by HCI v1.2. Handle alternate PIO queue sizes via ALT_QUEUE_SIZE register. Implement explicit PIO queue stopping/disabling and restart logic after errors. Co-developed-by: Patrick Yen Signed-off-by: Patrick Yen Signed-off-by: Jian-Ming Liao Reviewed-by: Frank Li Link: https://patch.msgid.link/20260818104106.763772-3-Jm_Liao@asmedia.com.tw Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 1 + drivers/i3c/master/mipi-i3c-hci/hci.h | 5 ++ drivers/i3c/master/mipi-i3c-hci/pio.c | 67 ++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 06acf6cf5a0d..84b78ac6c2e4 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -1049,6 +1049,7 @@ static int i3c_hci_init(struct i3c_hci *hci) switch (regval & ~0xf) { case 0x100: /* version 1.0 */ case 0x110: /* version 1.1 */ + case 0x120: /* version 1.2 */ case 0x200: /* version 2.0 */ break; default: diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h index b8d2a3d680f8..ee73f6e6756a 100644 --- a/drivers/i3c/master/mipi-i3c-hci/hci.h +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h @@ -30,6 +30,11 @@ #define reg_set(r, v) reg_write(r, reg_read(r) | (v)) #define reg_clear(r, v) reg_write(r, reg_read(r) & ~(v)) +/* helper macro for HCI version check */ +#define hci_version_at_least(hci, maj, min) \ + ((hci)->version_major > (maj) || \ + ((hci)->version_major == (maj) && (hci)->version_minor >= (min))) + struct hci_cmd_ops; struct dat_words { diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c index a1341d66bc65..439578a6eb54 100644 --- a/drivers/i3c/master/mipi-i3c-hci/pio.c +++ b/drivers/i3c/master/mipi-i3c-hci/pio.c @@ -45,6 +45,11 @@ #define IBI_STATUS_SIZE GENMASK(15, 8) #define CR_QUEUE_SIZE GENMASK(7, 0) +#define PIO_ALT_QUEUE_SIZE 0x1C +#define EXT_IBI_QUEUE_EN BIT(28) +#define ALT_RESP_QUEUE_EN BIT(24) +#define ALT_RESP_QUEUE_SIZE GENMASK(7, 0) + #define PIO_INTR_STATUS 0x20 #define PIO_INTR_STATUS_ENABLE 0x24 #define PIO_INTR_SIGNAL_ENABLE 0x28 @@ -72,6 +77,11 @@ #define STAT_RX_THLD BIT(1) #define STAT_TX_THLD BIT(0) +#define PIO_CONTROL 0x30 +#define PIO_CONTROL_ABORT BIT(2) +#define PIO_CONTROL_RS BIT(1) +#define PIO_CONTROL_ENABLE BIT(0) + #define PIO_QUEUE_CUR_STATUS 0x38 #define CUR_IBI_Q_LEVEL GENMASK(28, 20) #define CUR_RESP_Q_LEVEL GENMASK(18, 10) @@ -173,6 +183,14 @@ static void __hci_pio_init(struct i3c_hci *hci, u32 *size_val_ptr) * IBI queue size within allowed bounds. */ ibi_val = FIELD_GET(IBI_STATUS_SIZE, size_val); + /* Adjust actual IBI queue size based on v1.2 ALT_QUEUE_SIZE */ + if (hci_version_at_least(hci, 1, 2)) { + u32 alt_val = pio_reg_read(ALT_QUEUE_SIZE); + + if (alt_val & EXT_IBI_QUEUE_EN) + ibi_val *= 8; + } + pio->max_ibi_thresh = clamp_val(ibi_val/2, 1, 63); val = FIELD_PREP(QUEUE_IBI_STATUS_THLD, 1) | FIELD_PREP(QUEUE_IBI_DATA_THLD, pio->max_ibi_thresh) | @@ -190,6 +208,17 @@ static void __hci_pio_init(struct i3c_hci *hci, u32 *size_val_ptr) * (will be activated on first xfer). */ pio->enabled_irqs = STAT_ALL_ERRORS | STAT_IBI_STATUS_THLD; + + /* MIPI I3C HCI v1.2 requires explicitly enabling and starting PIO queues */ + if (hci_version_at_least(hci, 1, 2)) { + u32 ctl_val = pio_reg_read(CONTROL); + + if (!(ctl_val & PIO_CONTROL_ENABLE)) { + ctl_val |= PIO_CONTROL_ENABLE; + pio_reg_write(CONTROL, ctl_val); + } + pio_reg_write(CONTROL, ctl_val | PIO_CONTROL_RS); + } } static void hci_pio_suspend(struct i3c_hci *hci) @@ -208,6 +237,7 @@ static int hci_pio_init(struct i3c_hci *hci) { struct hci_pio_data *pio; u32 size_val; + u32 cmd_sz, resp_sz, ibi_val; pio = devm_kzalloc(hci->master.dev.parent, sizeof(*pio), GFP_KERNEL); if (!pio) @@ -217,10 +247,24 @@ static int hci_pio_init(struct i3c_hci *hci) __hci_pio_init(hci, &size_val); - dev_dbg(&hci->master.dev, "CMD/RESP FIFO = %ld entries\n", - FIELD_GET(CR_QUEUE_SIZE, size_val)); - dev_dbg(&hci->master.dev, "IBI FIFO = %ld bytes\n", - 4 * FIELD_GET(IBI_STATUS_SIZE, size_val)); + cmd_sz = FIELD_GET(CR_QUEUE_SIZE, size_val); + resp_sz = cmd_sz; + ibi_val = FIELD_GET(IBI_STATUS_SIZE, size_val); + + /* MIPI I3C HCI v1.2 supports alternate RESP/IBI queue size */ + if (hci_version_at_least(hci, 1, 2)) { + u32 alt_val = pio_reg_read(ALT_QUEUE_SIZE); + + if (alt_val & ALT_RESP_QUEUE_EN) + resp_sz = FIELD_GET(ALT_RESP_QUEUE_SIZE, alt_val); + if (alt_val & EXT_IBI_QUEUE_EN) + ibi_val *= 8; + } + + dev_dbg(&hci->master.dev, "CMD FIFO = %u, RESP FIFO = %u entries\n", + cmd_sz, resp_sz); + dev_dbg(&hci->master.dev, "IBI FIFO = %u bytes\n", + 4 * ibi_val); dev_dbg(&hci->master.dev, "RX data FIFO = %d bytes\n", 4 * (2 << FIELD_GET(RX_DATA_BUFFER_SIZE, size_val))); dev_dbg(&hci->master.dev, "TX data FIFO = %d bytes\n", @@ -244,6 +288,9 @@ static void hci_pio_cleanup(struct i3c_hci *hci) BUG_ON(pio->curr_rx); BUG_ON(pio->curr_tx); BUG_ON(pio->curr_resp); + /* MIPI I3C HCI v1.2 requires explicitly stopping and disabling PIO queues */ + if (hci_version_at_least(hci, 1, 2)) + pio_reg_write(CONTROL, 0x0); } } @@ -764,6 +811,18 @@ static void hci_pio_err(struct i3c_hci *hci, struct hci_pio_data *pio, hci_pio_dequeue_xfer_common(hci, pio, pio->curr_tx, 1); /* then reset the hardware */ mipi_i3c_hci_pio_reset(hci); + + /* MIPI I3C HCI v1.2 requires explicitly restarting PIO queues after error/abort */ + if (hci_version_at_least(hci, 1, 2)) { + u32 ctl_val = pio_reg_read(CONTROL); + + if (!(ctl_val & PIO_CONTROL_ENABLE)) { + ctl_val |= PIO_CONTROL_ENABLE; + pio_reg_write(CONTROL, ctl_val); + } + pio_reg_write(CONTROL, ctl_val | PIO_CONTROL_RS); + } + mipi_i3c_hci_resume(hci); dev_dbg(&hci->master.dev, "status=%#x/%#x", From 455b454c87bb01ffcebe0e0c09303d6af685bd2a Mon Sep 17 00:00:00 2001 From: Jian-Ming Liao Date: Tue, 18 Aug 2026 18:41:06 +0800 Subject: [PATCH 69/70] i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller Add support for the AMD_PT I3C controller by introducing the following changes: - Add AMD_PT I3C controller platform device ID in core.c. - Register AMD_PT I3C controller PCI ID in mipi-i3c-hci-pci.c. Co-developed-by: Patrick Yen Signed-off-by: Patrick Yen Signed-off-by: Jian-Ming Liao Reviewed-by: Frank Li Link: https://patch.msgid.link/20260818104106.763772-4-Jm_Liao@asmedia.com.tw Signed-off-by: Alexandre Belloni --- drivers/i3c/master/mipi-i3c-hci/core.c | 4 ++++ drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index 84b78ac6c2e4..dadf049bd4b5 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -1219,6 +1219,10 @@ static const struct platform_device_id i3c_hci_driver_ids[] = { HCI_QUIRK_DMA_ABORT_REQUIRES_PIO_RESET | HCI_QUIRK_DMA_REQUIRES_HC_ABORT, }, + { + .name = "amd-pt-i3c-hci", + .driver_data = HCI_QUIRK_RPM_ALLOWED, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, i3c_hci_driver_ids); diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c index 42c172e82622..ab595661db1e 100644 --- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c +++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c @@ -223,6 +223,13 @@ static const struct mipi_i3c_hci_pci_info intel_si_2_info = { .control_instance_pm = true, }; +static const struct mipi_i3c_hci_pci_info amd_pt_info = { + .name = "amd-pt-i3c-hci", + .id = {0}, + .instance_offset = {0}, + .instance_count = 1, +}; + static int mipi_i3c_hci_pci_find_instance(struct mipi_i3c_hci_pci *hci, struct device *dev) { for (int i = 0; i < INST_MAX; i++) { @@ -494,6 +501,8 @@ static const struct pci_device_id mipi_i3c_hci_pci_devices[] = { /* Nova Lake-H */ { PCI_VDEVICE(INTEL, 0xd37c), .driver_data = (kernel_ulong_t)&intel_mi_1_info }, { PCI_VDEVICE(INTEL, 0xd36f), .driver_data = (kernel_ulong_t)&intel_mi_2_info }, + /* AMD_PT */ + { PCI_VDEVICE(AMD, 0x444c), .driver_data = (kernel_ulong_t)&amd_pt_info }, { } }; MODULE_DEVICE_TABLE(pci, mipi_i3c_hci_pci_devices); From cab40cfc9e116acd4d60f95b4b1264cab78f3803 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Wed, 19 Aug 2026 12:48:33 +0800 Subject: [PATCH 70/70] i3c: dw: reduce do_daa time if there's no client dw_i3c_master_daa() derives the number of newly assigned dynamic addresses from cmd->rx_len, the ISR sets it to the number of address slots ENTDAA left unassigned. It starts out as zero, which already means "every address was assigned", so a timed out transfer leaves that value in place and it gets used as a result. If there's no client connected, the addr assign cmd times out, then the driver calls i3c_master_add_i3c_dev_locked() to add devices that are not there, each costing about 1s, thus adds non necessary boot time up to (maxdev * 1)s. Start from maxdevs instead: no address is assigned before ENTDAA runs, and the existing rx_count >= maxdevs check then reports an empty bus. Signed-off-by: Jisheng Zhang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260819044833.32611-1-jszhang@kernel.org Signed-off-by: Alexandre Belloni --- drivers/i3c/master/dw-i3c-master.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 0a3799a4ce87..4563d8761ba0 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -921,6 +921,7 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m) goto rpm_out; } cmd = &xfer->cmds[0]; + cmd->rx_len = master->maxdevs; cmd->cmd_hi = COMMAND_PORT_TRANSFER_ARG; cmd->cmd_lo = COMMAND_PORT_DEV_COUNT(master->maxdevs - pos) | COMMAND_PORT_DEV_INDEX(pos) |