diff options
-rw-r--r-- | board/cr50/board.c | 6 | ||||
-rw-r--r-- | board/cr50/gpio.inc | 11 | ||||
-rw-r--r-- | chip/g/i2cs.c | 75 | ||||
-rw-r--r-- | chip/g/i2cs.h | 11 | ||||
-rw-r--r-- | common/i2cs_tpm.c | 8 |
5 files changed, 111 insertions, 0 deletions
diff --git a/board/cr50/board.c b/board/cr50/board.c index 4537f2aab9..8cc0970586 100644 --- a/board/cr50/board.c +++ b/board/cr50/board.c @@ -1335,6 +1335,12 @@ void i2cs_set_pinmux(void) GWRITE_FIELD(PINMUX, DIOA1_CTL, IE, 1); /* I2CS_SDA */ GWRITE_FIELD(PINMUX, DIOA9_CTL, IE, 1); /* I2CS_SCL */ + /* + * Provide access to the SDA line to be able to detect 'hosed i2c + * slave' condition. + */ + GWRITE(PINMUX, GPIO0_GPIO14_SEL, GC_PINMUX_DIOA1_SEL); + /* Allow I2CS_SCL to wake from sleep */ GWRITE_FIELD(PINMUX, EXITEDGE0, DIOA9, 1); /* edge sensitive */ GWRITE_FIELD(PINMUX, EXITINV0, DIOA9, 1); /* wake on low */ diff --git a/board/cr50/gpio.inc b/board/cr50/gpio.inc index faa6ab09be..39686a1943 100644 --- a/board/cr50/gpio.inc +++ b/board/cr50/gpio.inc @@ -100,6 +100,17 @@ GPIO(EN_PP3300_INA_L, PIN(0, 11), GPIO_ODR_HIGH) GPIO(I2C_SCL_INA, PIN(0, 12), GPIO_INPUT) GPIO(I2C_SDA_INA, PIN(0, 13), GPIO_INPUT) +/* + * Use this to poll the state of the I2CS SDA line. Note that this is not + * necessary if SPI interface is used to communicate with the AP, if needed, + * this GPIO could be reclaimed in that case. + * + * Actual attachment of this GPIO to the SDA line happens in board.c only when + * I2CS interface is required. Should this GPIO ever change, the code setting + * up the pinmux in board.c will have to change as well. + */ +GPIO(I2CS_SDA, PIN(0, 14), GPIO_INPUT) + /* Unimplemented signals which we need to emulate for now */ /* TODO(wfrichar): Half the boards don't use this signal. Take it out. */ UNIMPLEMENTED(ENTERING_RW) diff --git a/chip/g/i2cs.c b/chip/g/i2cs.c index 6efc891bdd..17183c7b7f 100644 --- a/chip/g/i2cs.c +++ b/chip/g/i2cs.c @@ -63,7 +63,10 @@ */ #include "common.h" +#include "board.h" #include "console.h" +#include "device_state.h" +#include "gpio.h" #include "hooks.h" #include "i2cs.h" #include "pmu.h" @@ -96,6 +99,13 @@ static uint16_t last_write_pointer; */ static uint16_t last_read_pointer; +/* + * Keep track of i2c interrupts and the number of times the "hosed slave" + * condition was encountered. + */ +static uint16_t i2cs_read_irq_count; +static uint16_t i2cs_read_recovery_count; + static void i2cs_init(void) { /* First decide if i2c is even needed for this platform. */ @@ -118,6 +128,7 @@ static void i2cs_init(void) memset(i2cs_buffer, 0, sizeof(i2cs_buffer)); last_write_pointer = 0; last_read_pointer = 0; + i2cs_read_irq_count = 0; GWRITE_FIELD(PMU, RST0, DI2CS0, 0); @@ -132,6 +143,56 @@ static void i2cs_init(void) GWRITE(I2CS, SLAVE_DEVADDRVAL, 0x50); } +/* Forward declaration of the hook function. */ +static void poll_read_state(void); +DECLARE_DEFERRED(poll_read_state); + +/* Poll SDA line to detect the "hosed" condition. */ +#define READ_STATUS_CHECK_INTERVAL (500 * MSEC) + +/* + * Check for receive problems, if found - reinitialize the i2c slave + * interface. + */ +static void poll_read_state(void) +{ + /* + * Make sure there is no accidental match between + * last_i2cs_read_irq_count and i2cs_read_irq_count if the first run + * of this function happens when SDA is low. + */ + static uint16_t last_i2cs_read_irq_count = ~0; + + if (device_get_state(DEVICE_AP) == DEVICE_STATE_ON) { + if (!gpio_get_level(GPIO_I2CS_SDA)) { + if (last_i2cs_read_irq_count == i2cs_read_irq_count) { + /* + * SDA line is low and number of RX interrupts + * has not changed since last poll when it was + * low, it must be hosed. Reinitialize the i2c + * interface (which will also restart this + * polling function). + */ + last_i2cs_read_irq_count = ~0; + i2cs_read_recovery_count++; + i2cs_register_write_complete_handler + (write_complete_handler_); + return; + } + last_i2cs_read_irq_count = i2cs_read_irq_count; + } + } else { + /* + * AP is off, let's make sure that in case this function + * happens to run right after AP wakes up and i2c is active, + * there is no false positive 'hosed' condition detection. + */ + if (last_i2cs_read_irq_count == i2cs_read_irq_count) + last_i2cs_read_irq_count -= 1; + } + hook_call_deferred(&poll_read_state_data, READ_STATUS_CHECK_INTERVAL); +} + /* Process the 'end of a write cycle' interrupt. */ static void _i2cs_write_complete_int(void) { @@ -185,6 +246,9 @@ static void _i2cs_write_complete_int(void) /* Invoke the callback to process the message. */ write_complete_handler_(i2cs_buffer, bytes_processed); + + if (bytes_processed == 1) + i2cs_read_irq_count++; } /* @@ -287,6 +351,12 @@ int i2cs_register_write_complete_handler(wr_complete_handler_f wc_handler) write_complete_handler_ = wc_handler; task_enable_irq(GC_IRQNUM_I2CS0_INTR_WRITE_COMPLETE_INT); + /* + * Start a self perpetuating polling function to check for 'hosed' + * condition periodically. + */ + hook_call_deferred(&poll_read_state_data, READ_STATUS_CHECK_INTERVAL); + return 0; } @@ -315,3 +385,8 @@ size_t i2cs_zero_read_fifo_buffer_depth(void) */ return depth; } + +void i2cs_get_status(struct i2cs_status *status) +{ + status->read_recovery_count = i2cs_read_recovery_count; +} diff --git a/chip/g/i2cs.h b/chip/g/i2cs.h index 809e4127a0..8fbc28187f 100644 --- a/chip/g/i2cs.h +++ b/chip/g/i2cs.h @@ -50,4 +50,15 @@ size_t i2cs_zero_read_fifo_buffer_depth(void); */ void i2cs_post_read_fill_fifo(uint8_t *buffer, size_t len); +/* + * Provide upper layers with information with the I2CS interface + * status/statistics. The only piece of information currently provided is the + * counter of "hosed" i2c interface occurences, where i2c clocking stopped + * while slave was transmitting a zero. + */ +struct i2cs_status { + uint16_t read_recovery_count; +}; +void i2cs_get_status(struct i2cs_status *status); + #endif /* ! __CHIP_G_I2CS_H */ diff --git a/common/i2cs_tpm.c b/common/i2cs_tpm.c index e649be1675..42089681dd 100644 --- a/common/i2cs_tpm.c +++ b/common/i2cs_tpm.c @@ -229,14 +229,22 @@ DECLARE_HOOK(HOOK_INIT, i2cs_if_register, HOOK_PRIO_LAST); static int command_i2cs(int argc, char **argv) { + static uint16_t base_read_recovery_count; + struct i2cs_status status; + + i2cs_get_status(&status); + ccprintf("rd fifo adjust cnt = %d\n", i2cs_fifo_adjust_count); ccprintf("wr mismatch cnt = %d\n", i2cs_write_error_count); + ccprintf("read recovered cnt = %d\n", status.read_recovery_count + - base_read_recovery_count); if (argc < 2) return EC_SUCCESS; if (!strcasecmp(argv[1], "reset")) { i2cs_fifo_adjust_count = 0; i2cs_write_error_count = 0; + base_read_recovery_count = status.read_recovery_count; ccprintf("i2cs error counts reset\n"); } else return EC_ERROR_PARAM1; |