summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--board/cr50/board.c28
-rw-r--r--board/cr50/board.h7
-rw-r--r--board/cr50/gpio.inc12
-rw-r--r--chip/g/i2cs.c38
4 files changed, 70 insertions, 15 deletions
diff --git a/board/cr50/board.c b/board/cr50/board.c
index 9ec4edcbd6..606e8ffb82 100644
--- a/board/cr50/board.c
+++ b/board/cr50/board.c
@@ -1690,3 +1690,31 @@ void board_start_ite_sync(void)
/* Let the usb reply to make it to the host. */
hook_call_deferred(&deferred_ite_sync_reset_data, 10 * MSEC);
}
+
+void board_unwedge_i2cs(void)
+{
+ /*
+ * Create connection between i2cs_scl and the 'unwedge_scl' GPIO, and
+ * generate the i2c stop sequence which will reset the i2cs FSM.
+ *
+ * First, disconnect the external pin from the i2cs_scl input.
+ */
+ GWRITE(PINMUX, DIOA9_SEL, 0);
+
+ /* Connect the 'unwedge' GPIO to the i2cs_scl input. */
+ GWRITE(PINMUX, GPIO1_GPIO5_SEL, GC_PINMUX_I2CS0_SCL_SEL);
+
+ /* Generate a 'stop' condition. */
+ gpio_set_level(GPIO_UNWEDGE_I2CS_SCL, 1);
+ usleep(2);
+ GWRITE_FIELD(I2CS, CTRL_SDA_VAL, READ0_S, 1);
+ usleep(2);
+ GWRITE_FIELD(I2CS, CTRL_SDA_VAL, READ0_S, 0);
+ usleep(2);
+
+ /* Disconnect the 'unwedge' mode SCL. */
+ GWRITE(PINMUX, GPIO1_GPIO5_SEL, 0);
+
+ /* Restore external pin connection to the i2cs_scl. */
+ GWRITE(PINMUX, DIOA9_SEL, GC_PINMUX_I2CS0_SCL_SEL);
+}
diff --git a/board/cr50/board.h b/board/cr50/board.h
index a16bb89b1f..5a0dc1e330 100644
--- a/board/cr50/board.h
+++ b/board/cr50/board.h
@@ -373,6 +373,13 @@ int chip_factory_mode(void);
*/
void board_start_ite_sync(void);
+/*
+ * Board specific function (needs information about pinmux settings) which
+ * allows to take the i2cs controller out of the 'wedged' state where the
+ * master stopped i2c access mid transaction and the slave is holding SDA low,
+ */
+void board_unwedge_i2cs(void);
+
#endif /* !__ASSEMBLER__ */
/* USB interface indexes (use define rather than enum to expand them) */
diff --git a/board/cr50/gpio.inc b/board/cr50/gpio.inc
index 60cc1de97f..f899272166 100644
--- a/board/cr50/gpio.inc
+++ b/board/cr50/gpio.inc
@@ -67,6 +67,8 @@
* GPIO1.2 detect_ec_uart
* GPIO1.3 detect_servo
* GPIO1.4 detect_tpm_rst_asserted
+ * GPIO1.5 unwedge_i2cs_scl
+ * GPIO1.6 monitor_i2cs_sda
* GPIO1.11 ec_tx_cr50_rx_in
* GPIO1.12 strap_a0
* GPIO1.13 strap_a1
@@ -153,6 +155,14 @@ GPIO(STRAP_A1, PIN(1, 13), GPIO_INPUT)
GPIO(STRAP_B0, PIN(1, 14), GPIO_INPUT)
GPIO(STRAP_B1, PIN(1, 15), GPIO_INPUT)
+GPIO(UNWEDGE_I2CS_SCL, PIN(1, 5), GPIO_OUT_HIGH)
+
+/*
+ * A GPIO to sample the current state of the I2CS SDA line, allowing to detect
+ * the 'wedged I2C bus' condition.
+ */
+GPIO(MONITOR_I2CS_SDA, PIN(1, 6), GPIO_INPUT)
+
/*
* If you change the names of EN_PP3300_INA_L, I2C_SCL_INA, or I2C_SDA_INA,
* you also need to update the usage in closed_source_set1.c
@@ -198,6 +208,8 @@ PINMUX(GPIO(INT_AP_L), A5, DIO_INPUT) /* DIOB7 is p_digitial_od */
/* We can't pull it up */
PINMUX(GPIO(EC_FLASH_SELECT), B2, DIO_INPUT)
PINMUX(GPIO(AP_FLASH_SELECT), B3, DIO_INPUT)
+PINMUX(GPIO(MONITOR_I2CS_SDA), A1, GPIO_INPUT)
+
/*
* Update closed_source_set1.c if pinmux for EN_PP3300_INA_L is changed or
* removed.
diff --git a/chip/g/i2cs.c b/chip/g/i2cs.c
index f7411db5f6..1992784c0b 100644
--- a/chip/g/i2cs.c
+++ b/chip/g/i2cs.c
@@ -104,8 +104,21 @@ static uint16_t last_read_pointer;
static uint16_t i2cs_read_recovery_count;
static uint16_t i2cs_sda_low_count;
+static void check_i2cs_state(void)
+{
+ if (gpio_get_level(GPIO_MONITOR_I2CS_SDA))
+ return;
+
+ /*
+ * The bus might be stuck;
+ * Generate a stop sequence to unwedge.
+ */
+ board_unwedge_i2cs();
+}
+
static void i2cs_init(void)
{
+
/* First decide if i2c is even needed for this platform. */
/* if (i2cs is not needed) return; */
if (!board_tpm_uses_i2c())
@@ -113,26 +126,21 @@ static void i2cs_init(void)
pmu_clock_en(PERIPH_I2CS);
- /*
- * Toggle the reset register to make sure i2cs interface is in the
- * initial state even if it is mid transaction at this time.
- */
- GWRITE_FIELD(PMU, RST0, DI2CS0, 1);
-
- /*
- * This initialization is guraranteed to take way more than enough
- * time for the reset to kick in.
- */
memset(i2cs_buffer, 0, sizeof(i2cs_buffer));
+
+ i2cs_set_pinmux();
+
+ check_i2cs_state();
+
+ /* Reset read and write pointers. */
last_write_pointer = 0;
last_read_pointer = 0;
i2cs_sda_low_count = 0;
+ GWRITE(I2CS, READ_PTR, 0);
+ GWRITE(I2CS, WRITE_PTR, 0);
- GWRITE_FIELD(PMU, RST0, DI2CS0, 0);
-
-
- /* Set pinmux registers for I2CS interface */
- i2cs_set_pinmux();
+ /* Just in case we were wedged and the master starts with a read. */
+ *GREG32_ADDR(I2CS, READ_BUFFER0) = ~0;
/* Enable I2CS interrupt */
GWRITE_FIELD(I2CS, INT_ENABLE, INTR_WRITE_COMPLETE, 1);