diff options
Diffstat (limited to 'chip/stm32/i2c-stm32f.c')
-rw-r--r-- | chip/stm32/i2c-stm32f.c | 165 |
1 files changed, 30 insertions, 135 deletions
diff --git a/chip/stm32/i2c-stm32f.c b/chip/stm32/i2c-stm32f.c index 2e049757e4..c75660b07e 100644 --- a/chip/stm32/i2c-stm32f.c +++ b/chip/stm32/i2c-stm32f.c @@ -329,136 +329,13 @@ 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); - - /* - * TODO(crosbug.com/p/23802): This requires defining GPIOs for both - * ports even if the board only supports one port. - */ - 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. - */ - gpio_set_flags(scl, GPIO_ODR_HIGH); - gpio_set_flags(sda, GPIO_ODR_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 void i2c_init_port(unsigned int port) { const int i2c_clock_bit[] = {21, 22}; - ASSERT(port == I2C1 || port == I2C2); - ASSERT(port < 2); - if (!(STM32_RCC_APB1ENR & (1 << i2c_clock_bit[port]))) { /* Only unwedge the bus if the clock is off */ if (i2c_claim(port) == EC_SUCCESS) { - unwedge_i2c_bus(port); i2c_release(port); } @@ -834,6 +711,14 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_bytes, if (i2c_claim(port)) return EC_ERROR_BUSY; + /* If the port appears to be wedged, then try to unwedge it. */ + if (!i2c_raw_get_scl(port) || !i2c_raw_get_sda(port)) { + i2c_unwedge(port); + + /* Reset the i2c port. */ + i2c_init_port(port); + } + disable_i2c_interrupt(port); rv = i2c_master_transmit(port, slave_addr, out, out_bytes, @@ -849,22 +734,32 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_bytes, return rv; } -int i2c_get_line_levels(int port) +int i2c_raw_get_scl(int port) { - enum gpio_signal sda, scl; + enum gpio_signal g; - ASSERT(port == I2C1 || port == I2C2); + if (get_scl_from_i2c_port(port, &g) == EC_SUCCESS) + return gpio_get_level(g); - if (port == I2C1) { - sda = GPIO_I2C1_SDA; - scl = GPIO_I2C1_SCL; - } else { - sda = GPIO_I2C2_SDA; - scl = GPIO_I2C2_SCL; - } + /* If no SCL pin defined for this port, then return 1 to appear idle. */ + return 1; +} + +int i2c_raw_get_sda(int port) +{ + enum gpio_signal g; - return (gpio_get_level(sda) ? I2C_LINE_SDA_HIGH : 0) | - (gpio_get_level(scl) ? I2C_LINE_SCL_HIGH : 0); + if (get_sda_from_i2c_port(port, &g) == EC_SUCCESS) + return gpio_get_level(g); + + /* If no SDA pin defined for this port, then return 1 to appear idle. */ + return 1; +} + +int i2c_get_line_levels(int port) +{ + return (i2c_raw_get_sda(port) ? I2C_LINE_SDA_HIGH : 0) | + (i2c_raw_get_scl(port) ? I2C_LINE_SCL_HIGH : 0); } int i2c_read_string(int port, int slave_addr, int offset, uint8_t *data, |