summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--board/nyan/board.c2
-rw-r--r--board/pit/board.c2
-rw-r--r--board/snow/board.c2
-rw-r--r--board/spring/board.c2
-rw-r--r--chip/lm4/i2c.c71
-rw-r--r--chip/mec1322/i2c.c75
-rw-r--r--chip/stm32/i2c-stm32f.c165
-rw-r--r--chip/stm32/i2c-stm32l.c144
-rw-r--r--common/i2c.c215
-rw-r--r--include/i2c.h79
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]