diff options
-rw-r--r-- | board/nyan/board.c | 2 | ||||
-rw-r--r-- | board/pit/board.c | 2 | ||||
-rw-r--r-- | board/snow/board.c | 2 | ||||
-rw-r--r-- | board/spring/board.c | 2 | ||||
-rw-r--r-- | chip/lm4/i2c.c | 71 | ||||
-rw-r--r-- | chip/mec1322/i2c.c | 75 | ||||
-rw-r--r-- | chip/stm32/i2c-stm32f.c | 165 | ||||
-rw-r--r-- | chip/stm32/i2c-stm32l.c | 144 | ||||
-rw-r--r-- | common/i2c.c | 215 | ||||
-rw-r--r-- | include/i2c.h | 79 |
10 files changed, 489 insertions, 268 deletions
diff --git a/board/nyan/board.c b/board/nyan/board.c index 7aabae2491..e093c74a6b 100644 --- a/board/nyan/board.c +++ b/board/nyan/board.c @@ -109,7 +109,7 @@ BUILD_ASSERT(ARRAY_SIZE(power_signal_list) == POWER_SIGNAL_COUNT); /* I2C ports */ const struct i2c_port_t i2c_ports[] = { - {"master", I2C_PORT_MASTER, 100}, + {"master", I2C_PORT_MASTER, 100, GPIO_I2C1_SCL, GPIO_I2C1_SDA}, }; const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports); diff --git a/board/pit/board.c b/board/pit/board.c index 8977a86d4e..4bd4fa3665 100644 --- a/board/pit/board.c +++ b/board/pit/board.c @@ -114,7 +114,7 @@ const struct battery_info *battery_get_info(void) /* I2C ports */ const struct i2c_port_t i2c_ports[] = { - {"master", I2C_PORT_MASTER, 100}, + {"master", I2C_PORT_MASTER, 100, GPIO_I2C1_SCL, GPIO_I2C1_SDA}, }; const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports); diff --git a/board/snow/board.c b/board/snow/board.c index bf89ebc467..0be82cbaed 100644 --- a/board/snow/board.c +++ b/board/snow/board.c @@ -127,7 +127,7 @@ const struct battery_info *battery_get_info(void) /* I2C ports */ const struct i2c_port_t i2c_ports[] = { - {"master", I2C_PORT_MASTER, 100}, + {"master", I2C_PORT_MASTER, 100, GPIO_I2C1_SCL, GPIO_I2C1_SDA}, }; const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports); diff --git a/board/spring/board.c b/board/spring/board.c index 965da81283..066f188019 100644 --- a/board/spring/board.c +++ b/board/spring/board.c @@ -132,7 +132,7 @@ BUILD_ASSERT(ARRAY_SIZE(pwm_channels) == PWM_CH_COUNT); /* I2C ports */ const struct i2c_port_t i2c_ports[] = { - {"master", I2C_PORT_MASTER, 100}, + {"master", I2C_PORT_MASTER, 100, GPIO_I2C1_SCL, GPIO_I2C1_SDA}, }; const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports); diff --git a/chip/lm4/i2c.c b/chip/lm4/i2c.c index 510b8aaa1d..e888b31361 100644 --- a/chip/lm4/i2c.c +++ b/chip/lm4/i2c.c @@ -161,12 +161,6 @@ int i2c_do_work(int port) return 0; } -int i2c_get_line_levels(int port) -{ - /* Conveniently, MBMON bit (1 << 1) is SDA and (1 << 0) is SCL. */ - return LM4_I2C_MBMON(port) & 0x03; -} - int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_size, uint8_t *in, int in_size, int flags) { @@ -189,10 +183,17 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_size, /* Make sure we're in a good state to start */ if ((flags & I2C_XFER_START) && - (reg_mcs & (LM4_I2C_MCS_CLKTO | LM4_I2C_MCS_ARBLST))) { + ((reg_mcs & (LM4_I2C_MCS_CLKTO | LM4_I2C_MCS_ARBLST)) || + (i2c_get_line_levels(port) != I2C_LINE_IDLE))) { uint32_t tpr = LM4_I2C_MTPR(port); - CPRINTF("[%T I2C%d bad status 0x%02x]\n", port, reg_mcs); + CPRINTF("[%T I2C%d bad status 0x%02x, SCL=%d, SDA=%d]\n", port, + reg_mcs, + i2c_get_line_levels(port) & I2C_LINE_SCL_HIGH, + i2c_get_line_levels(port) & I2C_LINE_SDA_HIGH); + + /* Attempt to unwedge the port. */ + i2c_unwedge(port); /* Clock timeout or arbitration lost. Reset port to clear. */ LM4_SYSTEM_SRI2C |= (1 << port); @@ -265,6 +266,60 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_size, return pd->err; } +int i2c_raw_get_scl(int port) +{ + enum gpio_signal g; + int ret; + + /* If no SCL pin defined for this port, then return 1 to appear idle. */ + if (get_scl_from_i2c_port(port, &g) != EC_SUCCESS) + return 1; + + /* If we are driving the pin low, it must be low. */ + if (gpio_get_level(g) == 0) + return 0; + + /* + * Otherwise, we need to toggle it to an input to read the true pin + * state. + */ + gpio_set_flags(g, GPIO_INPUT); + ret = gpio_get_level(g); + gpio_set_flags(g, GPIO_ODR_HIGH); + + return ret; +} + +int i2c_raw_get_sda(int port) +{ + enum gpio_signal g; + int ret; + + /* If no SDA pin defined for this port, then return 1 to appear idle. */ + if (get_sda_from_i2c_port(port, &g) != EC_SUCCESS) + return 1; + + /* If we are driving the pin low, it must be low. */ + if (gpio_get_level(g) == 0) + return 0; + + /* + * Otherwise, we need to toggle it to an input to read the true pin + * state. + */ + gpio_set_flags(g, GPIO_INPUT); + ret = gpio_get_level(g); + gpio_set_flags(g, GPIO_ODR_HIGH); + + return ret; +} + +int i2c_get_line_levels(int port) +{ + /* Conveniently, MBMON bit (1 << 1) is SDA and (1 << 0) is SCL. */ + return LM4_I2C_MBMON(port) & 0x03; +} + int i2c_read_string(int port, int slave_addr, int offset, uint8_t *data, int len) { diff --git a/chip/mec1322/i2c.c b/chip/mec1322/i2c.c index 98dd28e170..feaade3813 100644 --- a/chip/mec1322/i2c.c +++ b/chip/mec1322/i2c.c @@ -197,8 +197,15 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_size, reg_sts = MEC1322_I2C_STATUS(port); if (!started && - ((reg_sts & (STS_BER | STS_LAB)) || !(reg_sts & STS_NBB))) { - CPRINTF("[%T I2C%d bad status 0x%02x]\n", port, reg_sts); + (((reg_sts & (STS_BER | STS_LAB)) || !(reg_sts & STS_NBB)) || + (i2c_get_line_levels(port) != I2C_LINE_IDLE))) { + CPRINTF("[%T I2C%d bad status 0x%02x, SCL=%d, SDA=%d]\n", port, + reg_sts, + i2c_get_line_levels(port) & I2C_LINE_SCL_HIGH, + i2c_get_line_levels(port) & I2C_LINE_SDA_HIGH); + + /* Attempt to unwedge the port. */ + i2c_unwedge(port); /* Bus error, bus busy, or arbitration lost. Reset port. */ reset_port(port); @@ -296,6 +303,70 @@ err_i2c_xfer: return EC_ERROR_UNKNOWN; } +int i2c_raw_get_scl(int port) +{ + enum gpio_signal g; + int ret; + + /* If no SCL pin defined for this port, then return 1 to appear idle. */ + if (get_scl_from_i2c_port(port, &g) != EC_SUCCESS) + return 1; + + /* + * TODO(crosbug.com/p/26483): The following code assumes the worst case, + * that since the pin is an output, gpio_get_level() will return the + * state that we are trying to drive the output to, instead of the + * actual state of the pin. Need to determine if this is the case, and + * if not, we can optimize. + */ + + /* If we are driving the pin low, it must be low. */ + if (gpio_get_level(g) == 0) + return 0; + + /* + * Otherwise, we need to toggle it to an input to read the true pin + * state. + */ + gpio_set_flags(g, GPIO_INPUT); + ret = gpio_get_level(g); + gpio_set_flags(g, GPIO_ODR_HIGH); + + return ret; +} + +int i2c_raw_get_sda(int port) +{ + enum gpio_signal g; + int ret; + + /* If no SDA pin defined for this port, then return 1 to appear idle. */ + if (get_sda_from_i2c_port(port, &g) != EC_SUCCESS) + return 1; + + /* + * TODO(crosbug.com/p/26483): The following code assumes the worst case, + * that since the pin is an output, gpio_get_level() will return the + * state that we are trying to drive the output to, instead of the + * actual state of the pin. Need to determine if this is the case, and + * if not, we can optimize. + */ + + /* If we are driving the pin low, it must be low. */ + if (gpio_get_level(g) == 0) + return 0; + + /* + * Otherwise, we need to toggle it to an input to read the true pin + * state. + */ + gpio_set_flags(g, GPIO_INPUT); + ret = gpio_get_level(g); + gpio_set_flags(g, GPIO_ODR_HIGH); + + return ret; +} + int i2c_get_line_levels(int port) { return (MEC1322_I2C_BB_CTRL(port) >> 5) & 0x3; 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, diff --git a/chip/stm32/i2c-stm32l.c b/chip/stm32/i2c-stm32l.c index c05ddd980f..74643a76c3 100644 --- a/chip/stm32/i2c-stm32l.c +++ b/chip/stm32/i2c-stm32l.c @@ -144,113 +144,15 @@ static void i2c_set_freq_port(const struct i2c_port_t *p) STM32_I2C_CR1(port) |= STM32_I2C_CR1_PE; } -/* - * Try to pull up SCL. If clock is stretched, we will wait for a few cycles - * for the slave to get ready. - * - * @param scl the SCL gpio pin - * @return 0 when success; -1 if SCL is still low - */ -static int try_pull_up_scl(enum gpio_signal scl) -{ - int i; - for (i = 0; i < 3; ++i) { - gpio_set_level(scl, 1); - if (gpio_get_level(scl)) - return 0; - udelay(I2C_BITBANG_HALF_CYCLE_US); - } - CPRINTF("[%T I2C clock stretched too long?]\n"); - return -1; -} - -/* - * Try to unwedge the bus. - * - * The implementation is based on unwedge_i2c_bus() in i2c-stm32f.c. - * Or refer to https://chromium-review.googlesource.com/#/c/32168 for details. - * - * @param port I2C port - * @param force_unwedge perform unwedge without checking if wedged - */ -static void i2c_try_unwedge(int port, int force_unwedge) -{ - enum gpio_signal scl, sda; - int i; - - /* - * 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; - } - - if (!force_unwedge) { - if (gpio_get_level(scl) && gpio_get_level(sda)) - /* Everything seems ok; no need to unwedge */ - return; - CPRINTF("[%T I2C wedge detected; fixing]\n"); - } - - gpio_set_flags(scl, GPIO_ODR_HIGH); - gpio_set_flags(sda, GPIO_ODR_HIGH); - - if (!gpio_get_level(scl)) { - /* - * Clock is low, wait for a while in case of clock stretched - * by a slave. - */ - if (try_pull_up_scl(scl)) - return; - } - - /* - * SCL is high. No matter whether SDA is 0 or 1, we generate at most - * 9 clocks with SDA released and then send a STOP. If a slave is in the - * middle of writing, one of the cycles should be a NACK. - * If it's in reading, then this should finish the transaction. - */ - udelay(I2C_BITBANG_HALF_CYCLE_US); - for (i = 0; i < 9; ++i) { - if (try_pull_up_scl(scl)) - return; - udelay(I2C_BITBANG_HALF_CYCLE_US); - gpio_set_level(scl, 0); - udelay(I2C_BITBANG_HALF_CYCLE_US); - if (gpio_get_level(sda)) - break; - } - - /* Issue a STOP */ - gpio_set_level(sda, 0); - udelay(I2C_BITBANG_HALF_CYCLE_US); - if (try_pull_up_scl(scl)) - return; - udelay(I2C_BITBANG_HALF_CYCLE_US); - gpio_set_level(sda, 1); - if (gpio_get_level(sda) == 0) - CPRINTF("[%T sda is still low]\n"); - udelay(I2C_BITBANG_HALF_CYCLE_US); -} - /** * Initialize on the specified I2C port. * * @param p the I2c port - * @param force_unwedge perform unwedge without checking if wedged */ -static void i2c_init_port(const struct i2c_port_t *p, int force_unwedge) +static void i2c_init_port(const struct i2c_port_t *p) { int port = p->port; - /* Unwedge the bus if it seems wedged */ - i2c_try_unwedge(port, force_unwedge); - /* Enable clocks to I2C modules if necessary */ if (!(STM32_RCC_APB1ENR & (1 << (21 + port)))) STM32_RCC_APB1ENR |= 1 << (21 + port); @@ -403,14 +305,16 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_bytes, if (rv == I2C_ERROR_FAILED_START) { const struct i2c_port_t *p = i2c_ports; CPRINTF("[%T i2c_xfer start error; " - "try resetting i2c%d to unwedge.\n", port); + "unwedging and resetting i2c %d.\n", port); + + i2c_unwedge(port); + for (i = 0; i < i2c_ports_used; i++, p++) { if (p->port == port) { - i2c_init_port(p, 1); /* force unwedge */ + i2c_init_port(p); break; } } - CPRINTF("[%T I2C done resetting.\n"); } } @@ -434,22 +338,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 SCL 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, @@ -526,7 +440,7 @@ static void i2c_init(void) int i; for (i = 0; i < i2c_ports_used; i++, p++) - i2c_init_port(p, 0); /* do not force unwedged */ + i2c_init_port(p); } DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_DEFAULT); diff --git a/common/i2c.c b/common/i2c.c index f5ac9ff7f8..f5ce577aa2 100644 --- a/common/i2c.c +++ b/common/i2c.c @@ -8,12 +8,20 @@ #include "clock.h" #include "console.h" #include "host_command.h" +#include "gpio.h" #include "i2c.h" #include "system.h" #include "task.h" #include "util.h" #include "watchdog.h" +/* Delay for bitbanging i2c corresponds roughly to 100kHz. */ +#define I2C_BITBANG_DELAY_US 5 + +/* Number of attempts to unwedge each pin. */ +#define UNWEDGE_SCL_ATTEMPTS 10 +#define UNWEDGE_SDA_ATTEMPTS 3 + #define CPUTS(outstr) cputs(CC_I2C, outstr) #define CPRINTF(format, args...) cprintf(CC_I2C, format, ## args) @@ -111,6 +119,213 @@ int i2c_write8(int port, int slave_addr, int offset, int data) return rv; } +int get_sda_from_i2c_port(int port, enum gpio_signal *sda) +{ + int i; + + /* Find the matching port in i2c_ports[] table. */ + for (i = 0; i < i2c_ports_used; i++) { + if (i2c_ports[i].port == port) + break; + } + + /* Crash if the port given is not in the i2c_ports[] table. */ + ASSERT(i != i2c_ports_used); + + /* Check if the SCL and SDA pins have been defined for this port. */ + if (i2c_ports[i].scl == 0 && i2c_ports[i].sda == 0) + return EC_ERROR_INVAL; + + *sda = i2c_ports[i].sda; + return EC_SUCCESS; +} + +int get_scl_from_i2c_port(int port, enum gpio_signal *scl) +{ + int i; + + /* Find the matching port in i2c_ports[] table. */ + for (i = 0; i < i2c_ports_used; i++) { + if (i2c_ports[i].port == port) + break; + } + + /* Crash if the port given is not in the i2c_ports[] table. */ + ASSERT(i != i2c_ports_used); + + /* Check if the SCL and SDA pins have been defined for this port. */ + if (i2c_ports[i].scl == 0 && i2c_ports[i].sda == 0) + return EC_ERROR_INVAL; + + *scl = i2c_ports[i].scl; + return EC_SUCCESS; +} + +void i2c_raw_set_scl(int port, int level) +{ + enum gpio_signal g; + + if (get_scl_from_i2c_port(port, &g) == EC_SUCCESS) + gpio_set_level(g, level); +} + +void i2c_raw_set_sda(int port, int level) +{ + enum gpio_signal g; + + if (get_sda_from_i2c_port(port, &g) == EC_SUCCESS) + gpio_set_level(g, level); +} + +int i2c_raw_mode(int port, int enable) +{ + enum gpio_signal sda, scl; + + /* Get the SDA and SCL pins for this port. If none, then return. */ + if (get_sda_from_i2c_port(port, &sda) != EC_SUCCESS) + return EC_ERROR_INVAL; + if (get_scl_from_i2c_port(port, &scl) != EC_SUCCESS) + return EC_ERROR_INVAL; + + if (enable) { + /* + * To enable raw mode, take out of alternate function mode and + * set the flags to open drain output. + */ + gpio_set_alternate_function(gpio_list[sda].port, + gpio_list[sda].mask, -1); + gpio_set_alternate_function(gpio_list[scl].port, + gpio_list[scl].mask, -1); + + gpio_set_flags(scl, GPIO_ODR_HIGH); + gpio_set_flags(sda, GPIO_ODR_HIGH); + } else { + /* + * TODO(crosbug.com/p/26485): Note that this will return *all* + * I2C ports to normal mode. If two I2C ports are both in raw + * mode, whichever one finishes first will yank raw mode away + * from the other one. + */ + + /* To disable raw mode, configure the I2C pins. */ + gpio_config_module(MODULE_I2C, 1); + } + + return EC_SUCCESS; +} + + +/* + * 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 part way 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. + * - 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). + * + * We attempt to unwedge the bus by doing: + * - If SCL is being held low, then a slave is clock extending. The only + * thing we can do is try to wait until the slave stops clock extending. + * - Otherwise, we will toggle the clock until the slave releases the SDA line. + * Once the SDA line is released, try to send a STOP bit. Rinse and repeat + * until either the bus is normal, or we run out of attempts. + * + * Note this should work for most devices, but depending on the slaves i2c + * state machine, it may not be possible to unwedge the bus. + */ +int i2c_unwedge(int port) +{ + int i, j; + int ret = EC_SUCCESS; + + /* Try to put port in to raw bit bang mode. */ + if (i2c_raw_mode(port, 1) != EC_SUCCESS) + return EC_ERROR_UNKNOWN; + + /* + * If clock is low, wait for a while in case of clock stretched + * by a slave. + */ + if (!i2c_raw_get_scl(port)) { + for (i = 0; i < UNWEDGE_SCL_ATTEMPTS; i++) { + udelay(I2C_BITBANG_DELAY_US); + if (i2c_raw_get_scl(port)) + break; + } + + /* + * If we get here, a slave is holding the clock low and there + * is nothing we can do. + */ + CPRINTF("[%T I2C unwedge failed, SCL is being held low.]\n"); + ret = EC_ERROR_UNKNOWN; + goto unwedge_done; + } + + if (i2c_raw_get_sda(port)) + goto unwedge_done; + + CPRINTF("[%T I2C unwedge called with SDA held low.]\n"); + + /* Keep trying to unwedge the SDA line until we run out of attempts. */ + for (i = 0; i < UNWEDGE_SDA_ATTEMPTS; i++) { + /* Drive the clock high. */ + i2c_raw_set_scl(port, 1); + udelay(I2C_BITBANG_DELAY_US); + + /* + * Clock through the problem by clocking out 9 bits. If slave + * releases the SDA line, then we can stop clocking bits and + * send a STOP. + */ + for (j = 0; j < 9; j++) { + if (i2c_raw_get_sda(port)) + break; + + i2c_raw_set_scl(port, 0); + udelay(I2C_BITBANG_DELAY_US); + i2c_raw_set_scl(port, 1); + udelay(I2C_BITBANG_DELAY_US); + } + + /* Take control of SDA line and issue a STOP command. */ + i2c_raw_set_sda(port, 0); + udelay(I2C_BITBANG_DELAY_US); + i2c_raw_set_sda(port, 1); + udelay(I2C_BITBANG_DELAY_US); + + /* Check if the bus is unwedged. */ + if (i2c_raw_get_sda(port) && i2c_raw_get_scl(port)) + break; + } + + if (!i2c_raw_get_sda(port)) { + CPRINTF("[%T I2C unwedge failed, SDA still low]\n"); + ret = EC_ERROR_UNKNOWN; + } + if (!i2c_raw_get_scl(port)) { + CPRINTF("[%T I2C unwedge failed, SCL still low]\n"); + ret = EC_ERROR_UNKNOWN; + } + +unwedge_done: + /* Take port out of raw bit bang mode. */ + i2c_raw_mode(port, 0); + + return ret; +} + /*****************************************************************************/ /* Host commands */ diff --git a/include/i2c.h b/include/i2c.h index 7d93eed7ef..ac572960df 100644 --- a/include/i2c.h +++ b/include/i2c.h @@ -15,9 +15,11 @@ /* Data structure to define I2C port configuration. */ struct i2c_port_t { - const char *name; /* Port name */ - int port; /* Port */ - int kbps; /* Speed in kbps */ + const char *name; /* Port name */ + int port; /* Port */ + int kbps; /* Speed in kbps */ + enum gpio_signal scl; /* Port SCL GPIO line */ + enum gpio_signal sda; /* Port SDA GPIO line */ }; extern const struct i2c_port_t i2c_ports[]; @@ -51,13 +53,73 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_size, #define I2C_LINE_IDLE (I2C_LINE_SCL_HIGH | I2C_LINE_SDA_HIGH) /** - * Return raw I/O line levels (I2C_LINE_*) for a port. + * Return raw I/O line levels (I2C_LINE_*) for a port when port is in alternate + * function mode. * * @param port Port to check */ int i2c_get_line_levels(int port); /** + * Get GPIO pin for I2C SCL from the i2c port number + * + * @param port I2C port number + * @param sda Pointer to gpio signal to store the SCL gpio at + * @return EC_SUCCESS if a valid GPIO point is found, EC_ERROR_INVAL if not + */ +int get_scl_from_i2c_port(int port, enum gpio_signal *scl); + +/** + * Get GPIO pin for I2C SDA from the i2c port number + * + * @param port I2C port number + * @param sda Pointer to gpio signal to store the SDA gpio at + * @return EC_SUCCESS if a valid GPIO point is found, EC_ERROR_INVAL if not + */ +int get_sda_from_i2c_port(int port, enum gpio_signal *sda); + +/** + * Get the state of the SCL pin when port is not in alternate function mode. + * + * @param port I2C port of interest + * @return State of SCL pin + */ +int i2c_raw_get_scl(int port); + +/** + * Get the state of the SDA pin when port is not in alternate function mode. + * + * @param port I2C port of interest + * @return State of SDA pin + */ +int i2c_raw_get_sda(int port); + +/** + * Set the state of the SCL pin. + * + * @param port I2C port of interest + * @param level State to set SCL pin to + */ +void i2c_raw_set_scl(int port, int level); + +/** + * Set the state of the SDA pin. + * + * @param port I2C port of interest + * @param level State to set SDA pin to + */ +void i2c_raw_set_sda(int port, int level); + +/** + * Toggle the I2C pins into or out of raw / big-bang mode. + * + * @param port I2C port of interest + * @param enable Flag to enable raw mode or disable it + * @return EC_SUCCESS if successful + */ +int i2c_raw_mode(int port, int enable); + +/** * Lock / unlock an I2C port. * @param port Port to lock * @param lock 1 to lock, 0 to unlock @@ -80,6 +142,15 @@ int i2c_read8(int port, int slave_addr, int offset, int *data); * the specified 8-bit <offset> in the slave's address space. */ int i2c_write8(int port, int slave_addr, int offset, int data); +/** + * Attempt to unwedge an I2C bus. + * + * @param port I2C port + * + * @return EC_SUCCESS or EC_ERROR_UNKNOWN + */ +int i2c_unwedge(int port); + /* Read ascii string using smbus read block protocol. * Read bytestream from <slaveaddr>:<offset> with format: * [length_N] [byte_0] [byte_1] ... [byte_N-1] |