diff options
author | Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com> | 2019-10-09 17:20:34 -0400 |
---|---|---|
committer | Wolfram Sang <wsa@the-dreams.de> | 2019-10-21 08:09:10 -0400 |
commit | 1f0d9cbeec9bb0a1c2013342836f2c9754d6502b (patch) | |
tree | 2bfd5df8e6b06ac4b045407ecc94b688553e4dc2 | |
parent | 7d194c2100ad2a6dded545887d02754948ca5241 (diff) |
i2c: aspeed: fix master pending state handling
In case of master pending state, it should not trigger a master
command, otherwise data could be corrupted because this H/W shares
the same data buffer for slave and master operations. It also means
that H/W command queue handling is unreliable because of the buffer
sharing issue. To fix this issue, it clears command queue if a
master command is queued in pending state to use S/W solution
instead of H/W command queue handling. Also, it refines restarting
mechanism of the pending master command.
Fixes: 2e57b7cebb98 ("i2c: aspeed: Add multi-master use case support")
Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
Acked-by: Joel Stanley <joel@jms.id.au>
Tested-by: Tao Ren <taoren@fb.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
-rw-r--r-- | drivers/i2c/busses/i2c-aspeed.c | 54 |
1 files changed, 34 insertions, 20 deletions
diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c index fa66951b05d0..7b098ff5f5dd 100644 --- a/drivers/i2c/busses/i2c-aspeed.c +++ b/drivers/i2c/busses/i2c-aspeed.c | |||
@@ -108,6 +108,12 @@ | |||
108 | #define ASPEED_I2CD_S_TX_CMD BIT(2) | 108 | #define ASPEED_I2CD_S_TX_CMD BIT(2) |
109 | #define ASPEED_I2CD_M_TX_CMD BIT(1) | 109 | #define ASPEED_I2CD_M_TX_CMD BIT(1) |
110 | #define ASPEED_I2CD_M_START_CMD BIT(0) | 110 | #define ASPEED_I2CD_M_START_CMD BIT(0) |
111 | #define ASPEED_I2CD_MASTER_CMDS_MASK \ | ||
112 | (ASPEED_I2CD_M_STOP_CMD | \ | ||
113 | ASPEED_I2CD_M_S_RX_CMD_LAST | \ | ||
114 | ASPEED_I2CD_M_RX_CMD | \ | ||
115 | ASPEED_I2CD_M_TX_CMD | \ | ||
116 | ASPEED_I2CD_M_START_CMD) | ||
111 | 117 | ||
112 | /* 0x18 : I2CD Slave Device Address Register */ | 118 | /* 0x18 : I2CD Slave Device Address Register */ |
113 | #define ASPEED_I2CD_DEV_ADDR_MASK GENMASK(6, 0) | 119 | #define ASPEED_I2CD_DEV_ADDR_MASK GENMASK(6, 0) |
@@ -336,18 +342,19 @@ static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus) | |||
336 | struct i2c_msg *msg = &bus->msgs[bus->msgs_index]; | 342 | struct i2c_msg *msg = &bus->msgs[bus->msgs_index]; |
337 | u8 slave_addr = i2c_8bit_addr_from_msg(msg); | 343 | u8 slave_addr = i2c_8bit_addr_from_msg(msg); |
338 | 344 | ||
339 | bus->master_state = ASPEED_I2C_MASTER_START; | ||
340 | |||
341 | #if IS_ENABLED(CONFIG_I2C_SLAVE) | 345 | #if IS_ENABLED(CONFIG_I2C_SLAVE) |
342 | /* | 346 | /* |
343 | * If it's requested in the middle of a slave session, set the master | 347 | * If it's requested in the middle of a slave session, set the master |
344 | * state to 'pending' then H/W will continue handling this master | 348 | * state to 'pending' then H/W will continue handling this master |
345 | * command when the bus comes back to the idle state. | 349 | * command when the bus comes back to the idle state. |
346 | */ | 350 | */ |
347 | if (bus->slave_state != ASPEED_I2C_SLAVE_INACTIVE) | 351 | if (bus->slave_state != ASPEED_I2C_SLAVE_INACTIVE) { |
348 | bus->master_state = ASPEED_I2C_MASTER_PENDING; | 352 | bus->master_state = ASPEED_I2C_MASTER_PENDING; |
353 | return; | ||
354 | } | ||
349 | #endif /* CONFIG_I2C_SLAVE */ | 355 | #endif /* CONFIG_I2C_SLAVE */ |
350 | 356 | ||
357 | bus->master_state = ASPEED_I2C_MASTER_START; | ||
351 | bus->buf_index = 0; | 358 | bus->buf_index = 0; |
352 | 359 | ||
353 | if (msg->flags & I2C_M_RD) { | 360 | if (msg->flags & I2C_M_RD) { |
@@ -422,20 +429,6 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) | |||
422 | } | 429 | } |
423 | } | 430 | } |
424 | 431 | ||
425 | #if IS_ENABLED(CONFIG_I2C_SLAVE) | ||
426 | /* | ||
427 | * A pending master command will be started by H/W when the bus comes | ||
428 | * back to idle state after completing a slave operation so change the | ||
429 | * master state from 'pending' to 'start' at here if slave is inactive. | ||
430 | */ | ||
431 | if (bus->master_state == ASPEED_I2C_MASTER_PENDING) { | ||
432 | if (bus->slave_state != ASPEED_I2C_SLAVE_INACTIVE) | ||
433 | goto out_no_complete; | ||
434 | |||
435 | bus->master_state = ASPEED_I2C_MASTER_START; | ||
436 | } | ||
437 | #endif /* CONFIG_I2C_SLAVE */ | ||
438 | |||
439 | /* Master is not currently active, irq was for someone else. */ | 432 | /* Master is not currently active, irq was for someone else. */ |
440 | if (bus->master_state == ASPEED_I2C_MASTER_INACTIVE || | 433 | if (bus->master_state == ASPEED_I2C_MASTER_INACTIVE || |
441 | bus->master_state == ASPEED_I2C_MASTER_PENDING) | 434 | bus->master_state == ASPEED_I2C_MASTER_PENDING) |
@@ -462,11 +455,15 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) | |||
462 | #if IS_ENABLED(CONFIG_I2C_SLAVE) | 455 | #if IS_ENABLED(CONFIG_I2C_SLAVE) |
463 | /* | 456 | /* |
464 | * If a peer master starts a xfer immediately after it queues a | 457 | * If a peer master starts a xfer immediately after it queues a |
465 | * master command, change its state to 'pending' then H/W will | 458 | * master command, clear the queued master command and change |
466 | * continue the queued master xfer just after completing the | 459 | * its state to 'pending'. To simplify handling of pending |
467 | * slave mode session. | 460 | * cases, it uses S/W solution instead of H/W command queue |
461 | * handling. | ||
468 | */ | 462 | */ |
469 | if (unlikely(irq_status & ASPEED_I2CD_INTR_SLAVE_MATCH)) { | 463 | if (unlikely(irq_status & ASPEED_I2CD_INTR_SLAVE_MATCH)) { |
464 | writel(readl(bus->base + ASPEED_I2C_CMD_REG) & | ||
465 | ~ASPEED_I2CD_MASTER_CMDS_MASK, | ||
466 | bus->base + ASPEED_I2C_CMD_REG); | ||
470 | bus->master_state = ASPEED_I2C_MASTER_PENDING; | 467 | bus->master_state = ASPEED_I2C_MASTER_PENDING; |
471 | dev_dbg(bus->dev, | 468 | dev_dbg(bus->dev, |
472 | "master goes pending due to a slave start\n"); | 469 | "master goes pending due to a slave start\n"); |
@@ -629,6 +626,14 @@ static irqreturn_t aspeed_i2c_bus_irq(int irq, void *dev_id) | |||
629 | irq_handled |= aspeed_i2c_master_irq(bus, | 626 | irq_handled |= aspeed_i2c_master_irq(bus, |
630 | irq_remaining); | 627 | irq_remaining); |
631 | } | 628 | } |
629 | |||
630 | /* | ||
631 | * Start a pending master command at here if a slave operation is | ||
632 | * completed. | ||
633 | */ | ||
634 | if (bus->master_state == ASPEED_I2C_MASTER_PENDING && | ||
635 | bus->slave_state == ASPEED_I2C_SLAVE_INACTIVE) | ||
636 | aspeed_i2c_do_start(bus); | ||
632 | #else | 637 | #else |
633 | irq_handled = aspeed_i2c_master_irq(bus, irq_remaining); | 638 | irq_handled = aspeed_i2c_master_irq(bus, irq_remaining); |
634 | #endif /* CONFIG_I2C_SLAVE */ | 639 | #endif /* CONFIG_I2C_SLAVE */ |
@@ -691,6 +696,15 @@ static int aspeed_i2c_master_xfer(struct i2c_adapter *adap, | |||
691 | ASPEED_I2CD_BUS_BUSY_STS)) | 696 | ASPEED_I2CD_BUS_BUSY_STS)) |
692 | aspeed_i2c_recover_bus(bus); | 697 | aspeed_i2c_recover_bus(bus); |
693 | 698 | ||
699 | /* | ||
700 | * If timed out and the state is still pending, drop the pending | ||
701 | * master command. | ||
702 | */ | ||
703 | spin_lock_irqsave(&bus->lock, flags); | ||
704 | if (bus->master_state == ASPEED_I2C_MASTER_PENDING) | ||
705 | bus->master_state = ASPEED_I2C_MASTER_INACTIVE; | ||
706 | spin_unlock_irqrestore(&bus->lock, flags); | ||
707 | |||
694 | return -ETIMEDOUT; | 708 | return -ETIMEDOUT; |
695 | } | 709 | } |
696 | 710 | ||