diff options
-rw-r--r-- | board/cr50/board.c | 28 | ||||
-rw-r--r-- | board/cr50/board.h | 7 | ||||
-rw-r--r-- | board/cr50/gpio.inc | 12 | ||||
-rw-r--r-- | chip/g/i2cs.c | 38 |
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); |