diff options
Diffstat (limited to 'chip')
-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) { |