summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVadim Bendebury <vbendeb@chromium.org>2019-07-31 13:25:36 -0700
committerCommit Bot <commit-bot@chromium.org>2019-09-05 17:40:58 +0000
commitfa6f77cd0da5b2f17494201e0770d1c8d550b7f4 (patch)
tree3fc8ce190205e75b573b4242e3613e5fd6676d3b
parent73bb9370b182e4f46d37529365cb4eee78bf7c6f (diff)
downloadchrome-ec-fa6f77cd0da5b2f17494201e0770d1c8d550b7f4.tar.gz
g: allow I2CS operate without hardware resets
It is not always possible to rely on PMU for resetting the I2CS controller. Most of the AP firmware versions deploy the 'I2C unwedge' cycle when coming out of reset, but not all of them, this is why Cr50 needs to be able to recover on its own in case there was a crash and the I2C bus was left mid transaction with the H1 holding down the SDA line. A GPIO is dedicated to monitor the I2CS_SDA line during reset. If the line is kept low, it could be a sign of a 'wedged' controller. The g I2CS FSM will reset any time the I2C 'stop' condition is detected. The create the 'stop' condition the I2C_SCL input is disconnected from the bus and connected to an internal GPIO, then I2C_SCL level is set to 'high' and register inverting the I2C_SDA value is toggled, which looks like a transition from zero to one to the controller. thus creating the 'stop' condition. BRANCH=cr50, cr50-mp BUG=b:135772657 TEST=the test was ran on a Pyro device, which uses I2C for communication with H1 and which AP firmware does not deploy the 'I2C unwedge' cycle. Test instrumentation involved setting a Chrome OS startup file such that once booted, the AP starts continuously polling TPM for value of an NVMEM index, creating I2C traffic. The host workstation sends the 'apreset cold' command to the EC within a few seconds of Chrome OS coming up. First run a special Cr50 image which is not resetting I2CS using PMU on TPM restarts, is was not trying to unwedge the stuck I2C bus. On five experiments, it takes on average 32 reboots for until I2C bus is locked up and the DUT falls into recovery. Then loaded the Cr50 image with this patch and ran the test again, it survived for 150 cycles without a problem. Change-Id: Iffec33f97557e3acfd1cd5fb76ba158f8c23b608 Signed-off-by: Vadim Bendebury <vbendeb@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/1730143 Reviewed-by: Mary Ruthven <mruthven@chromium.org>
-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);