| From aa42421b22922a7340603c9d8ed702816188e5ab Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Thu, 25 Sep 2025 10:02:26 +0800 |
| Subject: i2c: spacemit: remove stop function to avoid bus error |
| |
| 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: |
| -- |
| 2.51.0 |
| |