diff options
author | Randall Spangler <rspangler@chromium.org> | 2013-08-26 16:14:34 -0700 |
---|---|---|
committer | chrome-internal-fetch <chrome-internal-fetch@google.com> | 2013-08-27 23:21:09 +0000 |
commit | bad620183488eeb3fa97e435e66400dd754c8e3e (patch) | |
tree | 1d8484c44f1c80e5d143fa3a4dd396ef3ede5923 /chip/stm32/i2c-stm32l.c | |
parent | e294f8086692cf86785ecb5aede4bedff86600a9 (diff) | |
download | chrome-ec-bad620183488eeb3fa97e435e66400dd754c8e3e.tar.gz |
stm32l: Wait for stop condition to complete after i2c transfer
Currently, the STM32L I2C driver queues the stop condition, but
doesn't actually wait for it to take effect before returning. If
another back-to-back transfer is started, this may attempt to send a
start condition before the stop condition completes. If this happens
after a slave read, this can look to the slave like just another pulse
on SCL, causing it to clock out another bit - potentially driving SDA
low and hanging the bus.
Instead, wait for the bus to go idle, then wait another clock period
(10 us) to give the slaves plenty of time to detect bus-idle before
the next start condition.
BUG=chrome-os-partner:22093
BRANCH=pit
TEST=repeatedly run the battery i2c command from the EC console while
running 'ectool i2cxfer 0 0x48 1' from a root shell. Should not hang
the I2C bus.
Change-Id: I5e65ee242537dbc801fba4ae57847a5af5104186
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/66997
Reviewed-by: Douglas Anderson <dianders@chromium.org>
Commit-Queue: Douglas Anderson <dianders@chromium.org>
Diffstat (limited to 'chip/stm32/i2c-stm32l.c')
-rw-r--r-- | chip/stm32/i2c-stm32l.c | 24 |
1 files changed, 20 insertions, 4 deletions
diff --git a/chip/stm32/i2c-stm32l.c b/chip/stm32/i2c-stm32l.c index 6f09cc340b..cd5ebf8000 100644 --- a/chip/stm32/i2c-stm32l.c +++ b/chip/stm32/i2c-stm32l.c @@ -135,13 +135,11 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_bytes, dump_i2c_reg(port, "xfer start"); - /* Clear status */ /* + * Clear status + * * TODO: should check for any leftover error status, and reset the * port if present. - * - * Also, may need to wait a bit if a previous STOP hasn't finished - * sending yet. */ STM32_I2C_SR1(port) = 0; @@ -252,10 +250,28 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_bytes, xfer_exit: /* On error, queue a stop condition */ if (rv) { + flags |= I2C_XFER_STOP; STM32_I2C_CR1(port) |= STM32_I2C_CR1_STOP; dump_i2c_reg(port, "stop after error"); } + /* If a stop condition is queued, wait for it to take effect */ + if (flags & I2C_XFER_STOP) { + /* Wait up to 100 us for bus idle */ + for (i = 0; i < 10; i++) { + if (!(STM32_I2C_SR2(port) & STM32_I2C_SR2_BUSY)) + break; + udelay(10); + } + + /* + * Allow bus to idle for at least one 100KHz clock = 10 us. + * This allows slaves on the bus to detect bus-idle before + * the next start condition. + */ + udelay(10); + } + return rv; } |