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
This commit is contained in:
Hung-ying Tyan
2013-08-16 16:19:20 +08:00
committed by Caroline Tice
parent 58d8a739b2
commit f3525ca990

View File

@@ -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);