summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHung-ying Tyan <tyanh@chromium.org>2013-08-16 16:19:20 +0800
committerCaroline Tice <cmtice@chromium.org>2013-08-29 22:55:10 +0000
commitf3525ca9908ccda3a251f1878f676893dd3f16bf (patch)
treee1fb3c106c792ff1ab3d89a4212a01089f1d72cb
parent58d8a739b2f55be8c0c9a5cf38265733048e0742 (diff)
downloadchrome-ec-f3525ca9908ccda3a251f1878f676893dd3f16bf.tar.gz
pit: i2c: try unwedging the bus when i2c_xfer fails at sending START
and when the bus seems wedged at i2c_init(). BUG=chrome-os-partner:19286 TEST=Manual test on peach pit. Tried the following wedged cases: (1) Bit bang a transaction but only read part of the response. (Refer to https://chromium-review.googlesource.com/#/c/66389) 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. (Refer to https://chromium-review.googlesource.com/#/c/66389) 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. (Refer to https://chromium-review.googlesource.com/#/c/66389) 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. (Refer to https://chromium-review.googlesource.com/#/c/66389) Command to wedge: i2cwedge 0x90 0 5 (5) Manually pull down on SCL. (Refer to https://chromium-review.googlesource.com/#/c/66063) All five cases successfully wedged the bus and were recovered by this change. BRANCH=pit [dianders: made sure we don't change SCL after SCL high, misc other bits] Change-Id: I23f16fcaa2a76ea37025f8204ab1cdb27e9ef6d1 Signed-off-by: Hung-ying Tyan <tyanh@chromium.org> Signed-off-by: Doug Anderson <dianders@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/66915
-rw-r--r--chip/stm32/i2c-stm32l.c200
1 files changed, 164 insertions, 36 deletions
diff --git a/chip/stm32/i2c-stm32l.c b/chip/stm32/i2c-stm32l.c
index d51ab20955..69394832c4 100644
--- a/chip/stm32/i2c-stm32l.c
+++ b/chip/stm32/i2c-stm32l.c
@@ -28,6 +28,8 @@
/* Maximum transfer of a SMBUS block transfer */
#define SMBUS_MAX_BLOCK 32
+#define I2C_ERROR_FAILED_START EC_ERROR_INTERNAL_FIRST
+
/*
* Transmit timeout in microseconds
*
@@ -38,6 +40,12 @@
*/
#define I2C_TX_TIMEOUT_MASTER (10 * MSEC)
+/*
+ * Delay 5us in bitbang mode. That gives us roughly 5us low and 5us high or
+ * a frequency of 100kHz.
+ */
+#define I2C_BITBANG_HALF_CYCLE_US 5
+
#ifdef CONFIG_I2C_DEBUG
static void dump_i2c_reg(int port, const char *what)
{
@@ -82,8 +90,6 @@ static int wait_sr1(int port, int mask)
usleep(100);
}
- /* TODO: on error or timeout, reset port */
-
return EC_ERROR_TIMEOUT;
}
@@ -104,7 +110,7 @@ static int send_start(int port, int slave_addr)
dump_i2c_reg(port, "sent start");
rv = wait_sr1(port, STM32_I2C_SR1_SB);
if (rv)
- return rv;
+ return I2C_ERROR_FAILED_START;
/* Write slave address */
STM32_I2C_DR(port) = slave_addr & 0xff;
@@ -120,6 +126,140 @@ static int send_start(int port, int slave_addr)
return EC_SUCCESS;
}
+static void i2c_set_freq_port(const struct i2c_port_t *p)
+{
+ int port = p->port;
+ int freq = clock_get_freq();
+
+ /* Force peripheral reset and disable port */
+ STM32_I2C_CR1(port) = STM32_I2C_CR1_SWRST;
+ STM32_I2C_CR1(port) = 0;
+
+ /* Set clock frequency */
+ STM32_I2C_CCR(port) = freq / (2 * MSEC * p->kbps);
+ STM32_I2C_CR2(port) = freq / SECOND;
+ STM32_I2C_TRISE(port) = freq / SECOND + 1;
+
+ /* Enable port */
+ 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://gerrit.chromium.org/gerrit/#/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;
+
+ 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)
+{
+ 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);
+
+ /* Configure GPIOs */
+ gpio_config_module(MODULE_I2C, 1);
+
+ /* Set up initial bus frequencies */
+ i2c_set_freq_port(p);
+
+ /* TODO: enable interrupts using I2C_CR2 bits 8,9 */
+}
+
/*****************************************************************************/
/* Interface */
@@ -253,6 +393,23 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_bytes,
flags |= I2C_XFER_STOP;
STM32_I2C_CR1(port) |= STM32_I2C_CR1_STOP;
dump_i2c_reg(port, "stop after error");
+
+ /*
+ * If failed at sending start, try resetting the port
+ * to unwedge the bus.
+ */
+ 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);
+ for (i = 0; i < I2C_PORTS_USED; i++, p++) {
+ if (p->port == port) {
+ i2c_init_port(p, 1); /* force unwedge */
+ break;
+ }
+ }
+ CPRINTF("[%T I2C done resetting.\n");
+ }
}
/* If a stop condition is queued, wait for it to take effect */
@@ -333,24 +490,10 @@ int i2c_read_string(int port, int slave_addr, int offset, uint8_t *data,
static void i2c_freq_change(void)
{
const struct i2c_port_t *p = i2c_ports;
- int freq = clock_get_freq();
int i;
- for (i = 0; i < I2C_PORTS_USED; i++, p++) {
- int port = p->port;
-
- /* Force peripheral reset and disable port */
- STM32_I2C_CR1(port) = STM32_I2C_CR1_SWRST;
- STM32_I2C_CR1(port) = 0;
-
- /* Set clock frequency */
- STM32_I2C_CCR(port) = freq / (2 * MSEC * p->kbps);
- STM32_I2C_CR2(port) = freq / SECOND;
- STM32_I2C_TRISE(port) = freq / SECOND + 1;
-
- /* Enable port */
- STM32_I2C_CR1(port) |= STM32_I2C_CR1_PE;
- }
+ for (i = 0; i < I2C_PORTS_USED; i++, p++)
+ i2c_set_freq_port(p);
}
static void i2c_pre_freq_change_hook(void)
@@ -381,23 +524,8 @@ static void i2c_init(void)
const struct i2c_port_t *p = i2c_ports;
int i;
- for (i = 0; i < I2C_PORTS_USED; i++, p++) {
- int port = p->port;
-
- /* Enable clocks to I2C modules if necessary */
- if (!(STM32_RCC_APB1ENR & (1 << (21 + port)))) {
- /* TODO: unwedge bus if necessary */
- STM32_RCC_APB1ENR |= 1 << (21 + port);
- }
- }
-
- /* Configure GPIOs */
- gpio_config_module(MODULE_I2C, 1);
-
- /* Set up initial bus frequencies */
- i2c_freq_change();
-
- /* TODO: enable interrupts using I2C_CR2 bits 8,9 */
+ for (i = 0; i < I2C_PORTS_USED; i++, p++)
+ i2c_init_port(p, 0); /* do not force unwedged */
}
DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_DEFAULT);