summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlec Berg <alecaberg@chromium.org>2014-02-28 15:10:26 -0800
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2014-03-06 02:42:49 +0000
commit8a9817a5c7be54b6efc0e116ae607fb9955af247 (patch)
tree6afc3294ab5c27e46ba9211dcde516b4898e9f54
parent362cf0864a46b8ae6a66093cceba6859c801b443 (diff)
downloadchrome-ec-8a9817a5c7be54b6efc0e116ae607fb9955af247.tar.gz
cleanup: Combined i2c unwedge code into one common function
Refactored the i2c unwedge code to place it in the common directory so that any EC chip can use it. Added to the STM32F and LM4 boards, code to automatically detect and unwedge the i2c bus at the start of an i2c transaction. Note that STM32L already had this ability. To enable unwedging of the i2c port though, the gpio pins for SDA and SCL must be defined in the i2c_ports[] array in the board.c file. This allows the i2c module to bit bang the unwedging for the given port. If SDA and SCL are not defined for the port, then the unwedge code will not run. BUG=chrome-os-partner:26315, chrome-os-partner:23802 BRANCH=none TEST=Manual testing on machines with different EC chips. Testing made extensive use of https://chromium-review.googlesource.com/66389 in order to force wedging of the i2c bus so that we can attempt to unwedge it. Note that you can easily test if the bus is wedged by running i2cscan. On pit and spring: On pit, after each of the following, I verified that the bus was automatically unwedged. On spring, the unwedge only runs at reboot, so, for the non-reboot wedge commands, I manually ran console command unwedge, and verified that the bus became unwedged. (1) Bit bang a transaction but only read part of the response. Command to wedge: i2cwedge 0x90 0 2 2 (2) Bit bang a transaction to do a "write" and stop while the other side is acking. Command to wedge: i2cwedge 0x90 0 1 (3) Same as (1) but do a reboot instead of returning and see that the unwedge works at init time w/ no cancelled transactions. Command to wedge: i2cwedge 0x90 0 6 2 (4) Same as (2) but do a reboot instead of returning and see that the unwedge works at init time w/ no cancelled transactions. Command to wedge: i2cwedge 0x90 0 5 On glimmer: Added code to call i2c_unwedge in accel_init(). Then tested unwedging the accelerometer with the following. One extra difficulty testing this with the accelerometer is that sometimes the bit you stop on is high, which means it won't be wedged at all, the next start transaction will reset the bus. So, sometimes running i2cwedge won't wedge the bus and sometimes it will depending on the acceleration data. (1) Big bang transaction to do a "read" of accelerometer and stop partway: i2cwedge 0x1c 0x0f 2 2 i2cscan to make sure bus is actually wedged i2cunwedge i2cscan to make sure bus is now unwedged. (2) Bit bang transaction to do a "read" and stop partway, then reboot: i2cwedge 0x1c 0x0f 6 2. i2cscan to verify that the bus is working after the reboot. Change-Id: Ie3328e843ffb40f5001c96626fea131c0f9ad9b1 Signed-off-by: Alec Berg <alecaberg@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/188422 Reviewed-by: Randall Spangler <rspangler@chromium.org>
-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]