summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2013-08-26 16:14:34 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2013-08-27 23:21:09 +0000
commitbad620183488eeb3fa97e435e66400dd754c8e3e (patch)
tree1d8484c44f1c80e5d143fa3a4dd396ef3ede5923
parente294f8086692cf86785ecb5aede4bedff86600a9 (diff)
downloadchrome-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>
-rw-r--r--chip/stm32/i2c-stm32l.c24
-rw-r--r--chip/stm32/registers.h2
2 files changed, 22 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;
}
diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h
index 3a4934ea71..2d75a0d249 100644
--- a/chip/stm32/registers.h
+++ b/chip/stm32/registers.h
@@ -304,6 +304,8 @@ typedef volatile struct timer_ctlr timer_ctlr_t;
#define STM32_I2C_SR1_AF (1 << 10)
#define STM32_I2C_SR2(n) REG16(stm32_i2c_reg(n, 0x18))
+#define STM32_I2C_SR2_BUSY (1 << 1)
+
#define STM32_I2C_CCR(n) REG16(stm32_i2c_reg(n, 0x1C))
#define STM32_I2C_TRISE(n) REG16(stm32_i2c_reg(n, 0x20))