diff options
author | David Hendricks <dhendrix@chromium.org> | 2012-08-30 20:29:37 -0700 |
---|---|---|
committer | Gerrit <chrome-bot@google.com> | 2012-09-05 14:38:13 -0700 |
commit | e54ac8da053cd525946d9c7410405fe1686c5400 (patch) | |
tree | 619f7352c99906b24e05e7d9e0afd546999c978f | |
parent | 67170283788b9265f69c9768f7a5179157bc4074 (diff) | |
download | chrome-ec-e54ac8da053cd525946d9c7410405fe1686c5400.tar.gz |
snow: i2c: Reset i2c busses at bootup to unwedge them
If the EC got reset while some device on the bus was midway
through a transaction, the bus may we wedged and all of our i2c
transactions will fail. Try our best to unwedge the bus at
bootup. Do this even if the bus doens't look wedeged because
some device on the bus may be in a quiescent state at the moment
but be waiting to pounce on the bus when it sees the clock start
running.
BUG=chrome-os-partner:13378
TEST=Capture scope trace in normal bootup
TEST=Capture scope trace in failure bootup with an extra print
statement in the code when scl/sda were not high at bootup. Forced
this case by looping i2c transactions to tpschrome and rebooting
midway through.
BRANCH=snow
Signed-off-by: Doug Anderson <dianders@chromium.org>
Signed-off-by: David Hendricks <dhendrix@chromium.org>
(Note: Credit for this patch goes to Doug, I just uploaded the
initial work-in-progress version to gerrit --dhendrix)
Change-Id: I8da69b5294160048f91461159c039f8f2093e971
Reviewed-on: https://gerrit.chromium.org/gerrit/32168
Commit-Ready: David Hendricks <dhendrix@chromium.org>
Tested-by: David Hendricks <dhendrix@chromium.org>
Reviewed-by: Doug Anderson <dianders@chromium.org>
-rw-r--r-- | chip/stm32/i2c.c | 153 |
1 files changed, 149 insertions, 4 deletions
diff --git a/chip/stm32/i2c.c b/chip/stm32/i2c.c index d2e1fceff4..160186a4a1 100644 --- a/chip/stm32/i2c.c +++ b/chip/stm32/i2c.c @@ -52,6 +52,15 @@ #define I2C_TX_TIMEOUT_SLAVE 100000 /* us */ #define I2C_TX_TIMEOUT_MASTER 10000 /* us */ +/* + * We delay 5us in bitbang mode. That gives us 5us low and 5us high or + * a frequency of 100kHz. + * + * Note that the code takes a little time to run so we don't actually get + * 100kHz, but that's OK. + */ +#define I2C_BITBANG_DELAY_US 5 + #define NUM_PORTS 2 #define I2C1 STM32_I2C1_PORT #define I2C2 STM32_I2C2_PORT @@ -365,10 +374,138 @@ void __board_i2c_post_init(int port) void board_i2c_post_init(int port) __attribute__((weak, alias("__board_i2c_post_init"))); +/* + * Unwedge the i2c bus for the given port. + * + * Some devices on our i2c busses keep power even if we get a reset. That + * means that they could be partway through a transaction and could be + * driving the bus in a way that makes it hard for us to talk on the bus. + * ...or they might listen to the next transaction and interpret it in a + * weird way. + * + * Note that devices could be in one of several states: + * - If a device got interrupted in a write transaction it will be watching + * for additional data to finish its write. It will probably be looking to + * ack the data (drive the data line low) after it gets everything. Ideally + * we'd like to abort right away so we don't write bogus data. + * - If a device got interrupted while responding to a register read, it will + * be watching for clocks and will drive data out when it sees clocks. At + * the moment it might be trying to send out a 1 (so both clock and data + * may be high) or it might be trying to send out a 0 (so it's driving data + * low). Ideally we want to finish reading the current byte and then nak to + * abort everything. + * + * We attempt to unwedge the bus by doing: + * - If possible, send a pseudo-"stop" bit. We can only do this if nobody + * else is driving the clock or data lines, since that's the only way we + * have enough control. The idea here is to abort any writes that might + * be in progress. Note that a real "stop" bit would actually be a "low to + * high transition of SDA while SCL is high". ...but both must be high for + * us to be in control of the bus. Thus we _first_ drive SDA low so we can + * transition it high. This first transition looks like a start bit. In any + * case, the hope here is that it will look enough like an error condition + * that slaves will abort. + * - If we failed to send the pseudo-stop bit, try one clock and try again. + * I've seen a reset happen while the device was waiting for us to clock out + * its ack of the address. That should be the only time that the other side + * is driving things in the case of a write, so only 1 clock is enough. + * - Try to clock 9 times, if we can. This should finish reading out any data + * and then should nak. + * - Send one last pseudo-stop bit, just for good measure. + * + * @param port The i2c port to unwedge. + */ +static void unwedge_i2c_bus(int port) +{ + enum gpio_signal sda, scl; + int i; + + ASSERT(port == I2C1 || port == I2C2); + + if (port == I2C1) { + sda = GPIO_I2C1_SDA; + scl = GPIO_I2C1_SCL; + } else { + sda = GPIO_I2C2_SDA; + scl = GPIO_I2C2_SCL; + } + + /* + * Reconfigure ports as general purpose open-drain outputs, initted + * to high. + * + * We manually set the level first in addition to using GPIO_HIGH + * since gpio_set_flags() behaves strangely in the case of a warm boot. + */ + gpio_set_level(scl, 1); + gpio_set_level(sda, 1); + gpio_set_flags(scl, GPIO_OUTPUT | GPIO_OPEN_DRAIN | GPIO_HIGH); + gpio_set_flags(sda, GPIO_OUTPUT | GPIO_OPEN_DRAIN | GPIO_HIGH); + + /* Try to send out pseudo-stop bit. See function description */ + if (gpio_get_level(scl) && gpio_get_level(sda)) { + gpio_set_level(sda, 0); + udelay(I2C_BITBANG_DELAY_US); + gpio_set_level(sda, 1); + udelay(I2C_BITBANG_DELAY_US); + } else { + /* One more clock in case it was trying to ack its address */ + gpio_set_level(scl, 0); + udelay(I2C_BITBANG_DELAY_US); + gpio_set_level(scl, 1); + udelay(I2C_BITBANG_DELAY_US); + + if (gpio_get_level(scl) && gpio_get_level(sda)) { + gpio_set_level(sda, 0); + udelay(I2C_BITBANG_DELAY_US); + gpio_set_level(sda, 1); + udelay(I2C_BITBANG_DELAY_US); + } + } + + /* + * Now clock 9 to read pending data; one of these will be a NAK. + * + * Don't bother even checking if scl is high--we can't do anything about + * it anyway. + */ + for (i = 0; i < 9; i++) { + gpio_set_level(scl, 0); + udelay(I2C_BITBANG_DELAY_US); + gpio_set_level(scl, 1); + udelay(I2C_BITBANG_DELAY_US); + } + + /* One last try at a pseudo-stop bit */ + if (gpio_get_level(scl) && gpio_get_level(sda)) { + gpio_set_level(sda, 0); + udelay(I2C_BITBANG_DELAY_US); + gpio_set_level(sda, 1); + udelay(I2C_BITBANG_DELAY_US); + } + + /* + * Set things back to quiescent. + * + * We rely on board_i2c_post_init() to actually reconfigure pins to + * be special function. + */ + gpio_set_level(scl, 1); + gpio_set_level(sda, 1); +} + static int i2c_init2(void) { - /* enable I2C2 clock */ - STM32_RCC_APB1ENR |= 1 << 22; + if (!(STM32_RCC_APB1ENR & (1 << 22))) { + /* Only unwedge the bus if the clock is off */ + if (board_i2c_claim(I2C2) == EC_SUCCESS) { + unwedge_i2c_bus(I2C2); + board_i2c_release(I2C2); + } + + /* enable I2C2 clock */ + STM32_RCC_APB1ENR |= 1 << 22; + } /* force reset if the bus is stuck in BUSY state */ if (STM32_I2C_SR2(I2C2) & 0x2) { @@ -398,8 +535,16 @@ static int i2c_init2(void) static int i2c_init1(void) { - /* enable clock */ - STM32_RCC_APB1ENR |= 1 << 21; + if (!(STM32_RCC_APB1ENR & (1 << 21))) { + /* Only unwedge the bus if the clock is off */ + if (board_i2c_claim(I2C1) == EC_SUCCESS) { + unwedge_i2c_bus(I2C1); + board_i2c_release(I2C1); + } + + /* enable clock */ + STM32_RCC_APB1ENR |= 1 << 21; + } /* force reset if the bus is stuck in BUSY state */ if (STM32_I2C_SR2(I2C1) & 0x2) { |