6.17-stable review patch. If anyone has any objections, please let me know.
------------------
From: Troy Mitchell troy.mitchell@linux.spacemit.com
[ Upstream commit 445522fe7aad6131b2747ae8c76f77266054cd84 ]
Previously, STOP handling was split into two separate steps: 1) clear TB/STOP/START/ACK bits 2) issue STOP by calling spacemit_i2c_stop()
This left a small window where the control register was updated twice, which can confuse the controller. While this race has not been observed with interrupt-driven transfers, it reliably causes bus errors in PIO mode.
Inline the STOP sequence into the IRQ handler and ensure that control register bits are updated atomically in a single writel().
Fixes: 5ea558473fa31 ("i2c: spacemit: add support for SpacemiT K1 SoC") Signed-off-by: Troy Mitchell troy.mitchell@linux.spacemit.com Reviewed-by: Aurelien Jarno aurelien@aurel32.net Signed-off-by: Wolfram Sang wsa+renesas@sang-engineering.com Signed-off-by: Sasha Levin sashal@kernel.org --- drivers/i2c/busses/i2c-k1.c | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-)
diff --git a/drivers/i2c/busses/i2c-k1.c b/drivers/i2c/busses/i2c-k1.c index ee08811f4087c..84f132d0504dc 100644 --- a/drivers/i2c/busses/i2c-k1.c +++ b/drivers/i2c/busses/i2c-k1.c @@ -267,19 +267,6 @@ static void spacemit_i2c_start(struct spacemit_i2c_dev *i2c) writel(val, i2c->base + SPACEMIT_ICR); }
-static void spacemit_i2c_stop(struct spacemit_i2c_dev *i2c) -{ - u32 val; - - val = readl(i2c->base + SPACEMIT_ICR); - val |= SPACEMIT_CR_STOP | SPACEMIT_CR_ALDIE | SPACEMIT_CR_TB; - - if (i2c->read) - val |= SPACEMIT_CR_ACKNAK; - - writel(val, i2c->base + SPACEMIT_ICR); -} - static int spacemit_i2c_xfer_msg(struct spacemit_i2c_dev *i2c) { unsigned long time_left; @@ -412,7 +399,6 @@ static irqreturn_t spacemit_i2c_irq_handler(int irq, void *devid)
val = readl(i2c->base + SPACEMIT_ICR); val &= ~(SPACEMIT_CR_TB | SPACEMIT_CR_ACKNAK | SPACEMIT_CR_STOP | SPACEMIT_CR_START); - writel(val, i2c->base + SPACEMIT_ICR);
switch (i2c->state) { case SPACEMIT_STATE_START: @@ -429,14 +415,16 @@ static irqreturn_t spacemit_i2c_irq_handler(int irq, void *devid) }
if (i2c->state != SPACEMIT_STATE_IDLE) { + val |= SPACEMIT_CR_TB | SPACEMIT_CR_ALDIE; + if (spacemit_i2c_is_last_msg(i2c)) { /* trigger next byte with stop */ - spacemit_i2c_stop(i2c); - } else { - /* trigger next byte */ - val |= SPACEMIT_CR_ALDIE | SPACEMIT_CR_TB; - writel(val, i2c->base + SPACEMIT_ICR); + val |= SPACEMIT_CR_STOP; + + if (i2c->read) + val |= SPACEMIT_CR_ACKNAK; } + writel(val, i2c->base + SPACEMIT_ICR); }
err_out: