mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-06 10:00:17 +00:00
i2c: spacemit: remove stop function to avoid bus error
[ Upstream commit445522fe7a] 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:
committed by
Greg Kroah-Hartman
parent
b39876f0fe
commit
abc9829e4c
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user