summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chip/stm32/i2c.c153
1 files changed, 149 insertions, 4 deletions
diff --git a/chip/stm32/i2c.c b/chip/stm32/i2c.c
index d2e1fceff4..160186a4a1 100644
--- a/chip/stm32/i2c.c
+++ b/chip/stm32/i2c.c
@@ -52,6 +52,15 @@
#define I2C_TX_TIMEOUT_SLAVE 100000 /* us */
#define I2C_TX_TIMEOUT_MASTER 10000 /* us */
+/*
+ * We delay 5us in bitbang mode. That gives us 5us low and 5us high or
+ * a frequency of 100kHz.
+ *
+ * Note that the code takes a little time to run so we don't actually get
+ * 100kHz, but that's OK.
+ */
+#define I2C_BITBANG_DELAY_US 5
+
#define NUM_PORTS 2
#define I2C1 STM32_I2C1_PORT
#define I2C2 STM32_I2C2_PORT
@@ -365,10 +374,138 @@ 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);
+
+ 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.
+ *
+ * We manually set the level first in addition to using GPIO_HIGH
+ * since gpio_set_flags() behaves strangely in the case of a warm boot.
+ */
+ gpio_set_level(scl, 1);
+ gpio_set_level(sda, 1);
+ gpio_set_flags(scl, GPIO_OUTPUT | GPIO_OPEN_DRAIN | GPIO_HIGH);
+ gpio_set_flags(sda, GPIO_OUTPUT | GPIO_OPEN_DRAIN | GPIO_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 int i2c_init2(void)
{
- /* enable I2C2 clock */
- STM32_RCC_APB1ENR |= 1 << 22;
+ if (!(STM32_RCC_APB1ENR & (1 << 22))) {
+ /* Only unwedge the bus if the clock is off */
+ if (board_i2c_claim(I2C2) == EC_SUCCESS) {
+ unwedge_i2c_bus(I2C2);
+ board_i2c_release(I2C2);
+ }
+
+ /* enable I2C2 clock */
+ STM32_RCC_APB1ENR |= 1 << 22;
+ }
/* force reset if the bus is stuck in BUSY state */
if (STM32_I2C_SR2(I2C2) & 0x2) {
@@ -398,8 +535,16 @@ static int i2c_init2(void)
static int i2c_init1(void)
{
- /* enable clock */
- STM32_RCC_APB1ENR |= 1 << 21;
+ if (!(STM32_RCC_APB1ENR & (1 << 21))) {
+ /* Only unwedge the bus if the clock is off */
+ if (board_i2c_claim(I2C1) == EC_SUCCESS) {
+ unwedge_i2c_bus(I2C1);
+ board_i2c_release(I2C1);
+ }
+
+ /* enable clock */
+ STM32_RCC_APB1ENR |= 1 << 21;
+ }
/* force reset if the bus is stuck in BUSY state */
if (STM32_I2C_SR2(I2C1) & 0x2) {