summaryrefslogtreecommitdiff
path: root/chip/stm32/i2c-stm32f.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/stm32/i2c-stm32f.c')
-rw-r--r--chip/stm32/i2c-stm32f.c165
1 files changed, 30 insertions, 135 deletions
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,