[PATCH v2] i2c: xiic: Rework dynamic-mode RX FIFO drain in xiic_read_rx

From: Vasantha Likitha T

Date: Mon Sep 28 2026 - 03:34:45 EST


From: Shubhrajyoti Datta <shubhrajyoti.datta@xxxxxxx>

Programming the RFD (RX FIFO depth) threshold at runtime causes a
spurious SCL clock pulse which results in the STOP condition being
missed.

Fix this by programming RFD once, when the receive transfer starts, and
handling the final-byte sequencing in software: select how many bytes to
drain from the RX FIFO on each RX_FULL interrupt instead of
reprogramming RFD after every read.

RX_FULL is then always raised at the same threshold. When fewer bytes
remain to be received after the current FIFO contents are accounted for,
drain only that remaining count and retain the other bytes in the FIFO.
The retained bytes together with the bytes still arriving restore the
FIFO occupancy to the fixed threshold. Otherwise, drain the FIFO
completely.

Fixes: e1d5b6598cdc ("i2c: Add support for Xilinx XPS IIC Bus Interface")
Signed-off-by: Shubhrajyoti Datta <shubhrajyoti.datta@xxxxxxx>
Co-developed-by: Vasantha Likitha T <vasanthalikitha.tandra@xxxxxxx>
Signed-off-by: Vasantha Likitha T <vasanthalikitha.tandra@xxxxxxx>
---
Changes in v2:
- Updated commit message and Fixes tag
- Removed dead code
- Reused cached rx_space
- Improved comment wording
---
drivers/i2c/busses/i2c-xiic.c | 39 ++++++++++++++++++++++-------------
1 file changed, 25 insertions(+), 14 deletions(-)

diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c
index 3e7735e1dae0..474540d4d611 100644
--- a/drivers/i2c/busses/i2c-xiic.c
+++ b/drivers/i2c/busses/i2c-xiic.c
@@ -578,7 +578,8 @@ static void xiic_smbus_block_read_setup(struct xiic_i2c *i2c)
static void xiic_read_rx(struct xiic_i2c *i2c)
{
u8 bytes_in_fifo, cr = 0, bytes_to_read = 0;
- u32 bytes_rem = 0;
+ u32 bytes_rem;
+ u32 rx_space;
int i;

bytes_in_fifo = xiic_getreg8(i2c, XIIC_RFO_REG_OFFSET) + 1;
@@ -590,13 +591,32 @@ static void xiic_read_rx(struct xiic_i2c *i2c)
xiic_getreg8(i2c, XIIC_SR_REG_OFFSET),
xiic_getreg8(i2c, XIIC_CR_REG_OFFSET));

- if (bytes_in_fifo > xiic_rx_space(i2c))
- bytes_in_fifo = xiic_rx_space(i2c);
+ rx_space = xiic_rx_space(i2c);
+
+ /* Do not read more bytes than are still expected for this message */
+ if (bytes_in_fifo > rx_space)
+ bytes_in_fifo = rx_space;

bytes_to_read = bytes_in_fifo;

- if (!i2c->dynamic) {
- bytes_rem = xiic_rx_space(i2c) - bytes_in_fifo;
+ if (i2c->dynamic) {
+ /*
+ * RFD is set by xiic_start_recv() and must not be changed during
+ * a transfer, because reprogramming it may generate a spurious
+ * SCL pulse and cause the STOP condition to be missed.
+ */
+ bytes_rem = rx_space - bytes_in_fifo;
+
+ /*
+ * If fewer bytes remain to arrive than are currently in the FIFO,
+ * drain only that remaining count. The retained FIFO bytes together
+ * with the bytes still arriving restore the FIFO occupancy to the
+ * fixed threshold. Otherwise, drain the FIFO completely.
+ */
+ if (bytes_rem && bytes_rem < bytes_in_fifo)
+ bytes_to_read = bytes_rem;
+ } else {
+ bytes_rem = rx_space - bytes_in_fifo;

/* Set msg length if smbus_block_read */
if (i2c->rx_msg->flags & I2C_M_RECV_LEN) {
@@ -636,15 +656,6 @@ static void xiic_read_rx(struct xiic_i2c *i2c)
i2c->rx_msg->buf[i2c->rx_pos++] =
xiic_getreg8(i2c, XIIC_DRR_REG_OFFSET);
}
-
- if (i2c->dynamic) {
- u8 bytes;
-
- /* Receive remaining bytes if less than fifo depth */
- bytes = min_t(u8, xiic_rx_space(i2c), IIC_RX_FIFO_DEPTH);
- bytes--;
- xiic_setreg8(i2c, XIIC_RFD_REG_OFFSET, bytes);
- }
}

static bool xiic_error_check(struct xiic_i2c *i2c)
--
2.44.4