summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharlie Mooney <charliemooney@chromium.org>2012-09-04 15:33:54 -0700
committerCharlie Mooney <charliemooney@chromium.org>2012-09-05 16:01:46 -0700
commitb86efe1e493f3e6f3678f1ef74f1a25faf7a33d3 (patch)
tree71988057d29b77ce3898a960daeaaaaaa07d723a
parent3a8324977272c0165792843d8d4b4f72e5b27e31 (diff)
downloadchrome-ec-b86efe1e493f3e6f3678f1ef74f1a25faf7a33d3.tar.gz
Snow: Add checking for more i2c error cases
There are a number of ways for the i2c to fail, and some are quite rare and have thus been overlooked. It's easy enough to handle these rationally, but we have to check for them. This checks that the i2c peripheral is actually in slave mode when it gets a slave event firing (stopping it from accidentally sending garbage on the tail end of another request) and makes sure a STOP bit is sent in the event that the BUSY signal isn't set at the moment we check it (if we check it at the moment that it is sending a 1, it may not be set). Finally, if the i2c can't send a STOP bit, the peripheral is reset to get it back to a sane state, specifically it needs to not be stuck in master mode forever. BUG=chrome-os-partner:13380 TEST=Boot machine normally, from AP run "while true; do ectool version; done" to start a loop of the long transaction that sends lots of spurious reads too. Then on the EC, run "pmu 10000" and then "battery 1000" to stress the bus from all sides. Once the EC is done, stop the AP's side of the stress test, and make sure the bus is still functioning. Tested the resetting, by making it reset the peripheral every 150 times, and confirmed that the following transfers work just fine. BRANCH=snow Original-Change-Id: I265b3cddd25e1fd6ab4e8cf9c7290c875fad89f8 Signed-off-by: Charlie Mooney <charliemooney@chromium.org> (cherry picked from commit f3bb25cd8ccb22e7aa33e5cdd600912ea5fe1c96) Change-Id: Ifdd76a255d963744c62a4995743a4abf4b9dfc0a Signed-off-by: Charlie Mooney <charliemooney@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/32241 Reviewed-by: Che-Liang Chiou <clchiou@chromium.org>
-rw-r--r--chip/stm32/i2c.c40
1 files changed, 31 insertions, 9 deletions
diff --git a/chip/stm32/i2c.c b/chip/stm32/i2c.c
index 7068b9bce5..d2e1fceff4 100644
--- a/chip/stm32/i2c.c
+++ b/chip/stm32/i2c.c
@@ -282,11 +282,16 @@ static void i2c_process_command(void)
static void i2c_event_handler(int port)
{
-
/* save and clear status */
i2c_sr1[port] = STM32_I2C_SR1(port);
STM32_I2C_SR1(port) = 0;
+ /* Confirm that you are not in master mode */
+ if (STM32_I2C_SR2(port) & (1 << 0)) {
+ CPRINTF("I2C slave ISR triggered in master mode, ignoring.\n");
+ return;
+ }
+
/* transfer matched our slave address */
if (i2c_sr1[port] & (1 << 1)) {
/* If it's a receiver slave */
@@ -385,10 +390,6 @@ static int i2c_init2(void)
/* clear status */
STM32_I2C_SR1(I2C2) = 0;
- /* enable event and error interrupts */
- task_enable_irq(STM32_IRQ_I2C2_EV);
- task_enable_irq(STM32_IRQ_I2C2_ER);
-
board_i2c_post_init(I2C2);
CPUTS("done\n");
@@ -417,10 +418,6 @@ static int i2c_init1(void)
/* clear status */
STM32_I2C_SR1(I2C1) = 0;
- /* enable event and error interrupts */
- task_enable_irq(STM32_IRQ_I2C1_EV);
- task_enable_irq(STM32_IRQ_I2C1_ER);
-
board_i2c_post_init(I2C1);
return EC_SUCCESS;
@@ -435,6 +432,14 @@ static int i2c_init(void)
rc |= i2c_init2();
rc |= i2c_init1();
+ /* enable event and error interrupts */
+ if (!rc) {
+ task_enable_irq(STM32_IRQ_I2C1_EV);
+ task_enable_irq(STM32_IRQ_I2C1_ER);
+ task_enable_irq(STM32_IRQ_I2C2_EV);
+ task_enable_irq(STM32_IRQ_I2C2_ER);
+ }
+
return rc;
}
DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_DEFAULT);
@@ -650,10 +655,27 @@ static void handle_i2c_error(int port, int rv)
r = STM32_I2C_SR2(port);
/* Clear busy state */
t1 = get_time();
+
+ /**
+ * If the BUSY bit is faulty, send a stop bit just to be sure. It
+ * seems that this can be happen very briefly while sending a 1.
+ * We've not actually seen this happen, but we just want to be safe.
+ */
+ if (rv == EC_ERROR_TIMEOUT && !(r & 2)) {
+ CPRINTF("Bad BUSY bit detected.\n");
+ master_stop(port);
+ }
+
+ /* Try to send stop bits until the bus becomes idle */
while (r & 2) {
t2 = get_time();
if (t2.val - t1.val > I2C_TX_TIMEOUT_MASTER) {
dump_i2c_reg(port);
+ /* Reset the i2c periph to get it back to slave mode */
+ if (port == I2C1)
+ i2c_init1();
+ else
+ i2c_init2();
goto cr_cleanup;
}
/* Send stop */