]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
i3c: master: svc: Fix npcm845 FIFO_EMPTY quirk
authorStanley Chu <yschu@nuvoton.com>
Wed, 30 Jul 2025 00:37:19 +0000 (08:37 +0800)
committerAlexandre Belloni <alexandre.belloni@bootlin.com>
Wed, 30 Jul 2025 23:36:16 +0000 (01:36 +0200)
In a private write transfer, the driver pre-fills the FIFO to work around
the FIFO_EMPTY quirk. However, if an IBIWON event occurs, the hardware
emits a NACK and the driver initiates a retry. During the retry, driver
attempts to pre-fill the FIFO again if there is remaining data, but since
the FIFO is already full, this leads to data loss.

Check available space in FIFO to prevent overflow.

Fixes: 4008a74e0f9b ("i3c: master: svc: Fix npcm845 FIFO empty issue")
Signed-off-by: Stanley Chu <yschu@nuvoton.com>
Link: https://lore.kernel.org/r/20250730003719.1825593-1-yschu@nuvoton.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
drivers/i3c/master/svc-i3c-master.c

index 6d0eea80ea34885f6b740a158a7d60ab7666b368..b2b5db3ed5bb8753b2f6cdd30fe892307e1d4602 100644 (file)
 #define   SVC_I3C_MDATACTRL_TXTRIG_FIFO_NOT_FULL GENMASK(5, 4)
 #define   SVC_I3C_MDATACTRL_RXTRIG_FIFO_NOT_EMPTY 0
 #define   SVC_I3C_MDATACTRL_RXCOUNT(x) FIELD_GET(GENMASK(28, 24), (x))
+#define   SVC_I3C_MDATACTRL_TXCOUNT(x) FIELD_GET(GENMASK(20, 16), (x))
 #define   SVC_I3C_MDATACTRL_TXFULL BIT(30)
 #define   SVC_I3C_MDATACTRL_RXEMPTY BIT(31)
 
@@ -1304,14 +1305,19 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
                 * FIFO start filling as soon as possible after EmitStartAddr.
                 */
                if (svc_has_quirk(master, SVC_I3C_QUIRK_FIFO_EMPTY) && !rnw && xfer_len) {
-                       u32 end = xfer_len > SVC_I3C_FIFO_SIZE ? 0 : SVC_I3C_MWDATAB_END;
-                       u32 len = min_t(u32, xfer_len, SVC_I3C_FIFO_SIZE);
-
-                       writesb(master->regs + SVC_I3C_MWDATAB1, out, len - 1);
-                       /* Mark END bit if this is the last byte */
-                       writel(out[len - 1] | end, master->regs + SVC_I3C_MWDATAB);
-                       xfer_len -= len;
-                       out += len;
+                       u32 space, end, len;
+
+                       reg = readl(master->regs + SVC_I3C_MDATACTRL);
+                       space = SVC_I3C_FIFO_SIZE - SVC_I3C_MDATACTRL_TXCOUNT(reg);
+                       if (space) {
+                               end = xfer_len > space ? 0 : SVC_I3C_MWDATAB_END;
+                               len = min_t(u32, xfer_len, space);
+                               writesb(master->regs + SVC_I3C_MWDATAB1, out, len - 1);
+                               /* Mark END bit if this is the last byte */
+                               writel(out[len - 1] | end, master->regs + SVC_I3C_MWDATAB);
+                               xfer_len -= len;
+                               out += len;
+                       }
                }
 
                ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,