i2c: spacemit: remove stop function to avoid bus error

[ Upstream commit 445522fe7a ]

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: 5ea558473f ("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>
This commit is contained in:
Troy Mitchell
2025-09-25 10:02:26 +08:00
committed by Greg Kroah-Hartman
parent b39876f0fe
commit abc9829e4c

View File

@@ -267,19 +267,6 @@ static void spacemit_i2c_start(struct spacemit_i2c_dev *i2c)
writel(val, i2c->base + SPACEMIT_ICR); 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) static int spacemit_i2c_xfer_msg(struct spacemit_i2c_dev *i2c)
{ {
unsigned long time_left; 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 = readl(i2c->base + SPACEMIT_ICR);
val &= ~(SPACEMIT_CR_TB | SPACEMIT_CR_ACKNAK | SPACEMIT_CR_STOP | SPACEMIT_CR_START); val &= ~(SPACEMIT_CR_TB | SPACEMIT_CR_ACKNAK | SPACEMIT_CR_STOP | SPACEMIT_CR_START);
writel(val, i2c->base + SPACEMIT_ICR);
switch (i2c->state) { switch (i2c->state) {
case SPACEMIT_STATE_START: 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) { if (i2c->state != SPACEMIT_STATE_IDLE) {
val |= SPACEMIT_CR_TB | SPACEMIT_CR_ALDIE;
if (spacemit_i2c_is_last_msg(i2c)) { if (spacemit_i2c_is_last_msg(i2c)) {
/* trigger next byte with stop */ /* trigger next byte with stop */
spacemit_i2c_stop(i2c); val |= SPACEMIT_CR_STOP;
} else {
/* trigger next byte */ if (i2c->read)
val |= SPACEMIT_CR_ALDIE | SPACEMIT_CR_TB; val |= SPACEMIT_CR_ACKNAK;
writel(val, i2c->base + SPACEMIT_ICR);
} }
writel(val, i2c->base + SPACEMIT_ICR);
} }
err_out: err_out: