diff --git a/board/cr50/ap_state.c b/board/cr50/ap_state.c new file mode 100644 index 0000000000..4e95d1c1c4 --- /dev/null +++ b/board/cr50/ap_state.c @@ -0,0 +1,211 @@ +/* Copyright 2017 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * AP state machine + */ +#include "common.h" +#include "console.h" +#include "gpio.h" +#include "hooks.h" +#include "system.h" + +#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args) + +static enum device_state state = DEVICE_STATE_INIT; + +void print_ap_state(void) +{ + ccprintf("AP: %s\n", device_state_name(state)); +} + +int ap_is_on(void) +{ + /* Debouncing and on are both still on */ + return (state == DEVICE_STATE_DEBOUNCING || state == DEVICE_STATE_ON); +} + +/** + * Set the AP state. + * + * Done as a function to make it easier to debug state transitions. Note that + * this ONLY sets the state (and possibly prints debug info), and doesn't do + * all the additional transition work that set_ap_on(), etc. do. + * + * @param new_state State to set. + */ +static void set_state(enum device_state new_state) +{ +#ifdef CR50_DEBUG_AP_STATE + /* Print all state transitions. May spam the console. */ + if (state != new_state) + CPRINTS("AP %s -> %s", + device_state_name(state), device_state_name(new_state)); +#endif + state = new_state; +} + +/** + * Set AP to the off state + */ +static void set_ap_off(void) +{ + CPRINTS("AP off"); + set_state(DEVICE_STATE_OFF); + + /* + * If TPM is configured then the INT_AP_L signal is used as a low pulse + * trigger to sync transactions with the host. By default Cr50 is + * driving this line high, but when the AP powers off, the 1.8V rail + * that it's pulled up to will be off and cause excessive power to be + * consumed by the Cr50. Set INT_AP_L as an input while the AP is + * powered off. + */ + gpio_set_flags(GPIO_INT_AP_L, GPIO_INPUT); + + disable_ccd_uart(UART_AP); + + /* + * We don't enable deep sleep on ARM devices yet, as its processing + * there will require more support on the AP side than is available + * now. + * + * Note: Presence of platform reset is a poor indicator of deep sleep + * support. It happens to be correlated with ARM vs x86 at present. + */ + if (board_deep_sleep_allowed()) + enable_deep_sleep(); +} + +/** + * Move the AP to the ON state + */ +static void set_ap_on(void) +{ + CPRINTS("AP on"); + set_state(DEVICE_STATE_ON); + + /* + * AP is powering up, set the host sync signal to output and set it + * high which is the default level. + */ + gpio_set_flags(GPIO_INT_AP_L, GPIO_OUT_HIGH); + gpio_set_level(GPIO_INT_AP_L, 1); + + enable_ccd_uart(UART_AP); + + if (board_deep_sleep_allowed()) + disable_deep_sleep(); +} + +/** + * Handle moving the AP to the OFF state from a deferred interrupt handler. + * + * Needs to make additional state checks to avoid double-on in case ap_detect() + * has run in the meantime. + */ +void set_ap_on_deferred(void) +{ + /* If we were debouncing ON->OFF, cancel it because we're still on */ + if (state == DEVICE_STATE_DEBOUNCING) + set_state(DEVICE_STATE_ON); + + /* If AP isn't already on, make it so */ + if (state != DEVICE_STATE_ON) + set_ap_on(); +} +DECLARE_DEFERRED(set_ap_on_deferred); + +/** + * Interrupt handler for AP detect asserted + */ +void ap_detect_asserted(enum gpio_signal signal) +{ + gpio_disable_interrupt(GPIO_DETECT_AP); + hook_call_deferred(&set_ap_on_deferred_data, 0); +} + +/** + * Detect state machine + */ +static void ap_detect(void) +{ + int detect; + + if (board_detect_ap_with_tpm_rst()) { + /* AP is detected if platform reset is deasserted */ + detect = gpio_get_level(GPIO_TPM_RST_L); + } else { + /* Disable interrupts if we had them on for debouncing */ + gpio_disable_interrupt(GPIO_DETECT_AP); + + /* AP is detected if it's driving its UART TX signal */ + detect = gpio_get_level(GPIO_DETECT_AP); + } + + /* Handle detecting device */ + if (detect) { + /* + * If we were debouncing ON->OFF, cancel debouncing and go back + * to the ON state. + */ + if (state == DEVICE_STATE_DEBOUNCING) + set_state(DEVICE_STATE_ON); + + /* If we're already ON, done */ + if (state == DEVICE_STATE_ON) + return; + + if (board_detect_ap_with_tpm_rst()) { + /* + * The platform reset handler has not run yet; + * otherwise, it would have already turned the AP on + * and we wouldn't get here. + * + * This can happen if the hook task calls ap_detect() + * before deferred_tpm_rst_isr(). In this case, the + * deferred handler is already pending so calling the + * ISR has no effect. + * + * But we may actually have missed the edge. In that + * case, calling the ISR makes sure we don't miss the + * reset. It will call set_ap_on_deferred() to move + * the AP to the ON state. + */ + CPRINTS("AP detect calling tpm_rst_deasserted()"); + tpm_rst_deasserted(GPIO_TPM_RST_L); + } else { + /* We're responsible for setting the AP state to ON */ + set_ap_on(); + } + + return; + } + + /* AP wasn't detected. If we're already off, done. */ + if (state == DEVICE_STATE_OFF) + return; + + /* If we were debouncing, we're now sure we're off */ + if (state == DEVICE_STATE_DEBOUNCING || + state == DEVICE_STATE_INIT_DEBOUNCING) { + set_ap_off(); + return; + } + + /* + * Otherwise, we were on before and haven't detected the AP. But we + * don't know if that's because the AP is actually off, or because the + * AP UART is sending a 0-bit or temporarily asserting platform reset. + * So start debouncing. + */ + if (state == DEVICE_STATE_INIT) + set_state(DEVICE_STATE_INIT_DEBOUNCING); + else + set_state(DEVICE_STATE_DEBOUNCING); + + /* If we're using AP UART RX for detect, enable its interrupt */ + if (!board_detect_ap_with_tpm_rst()) + gpio_enable_interrupt(GPIO_DETECT_AP); +} +DECLARE_HOOK(HOOK_SECOND, ap_detect, HOOK_PRIO_DEFAULT); diff --git a/board/cr50/board.c b/board/cr50/board.c index e72a88a8c1..27ec67fbd2 100644 --- a/board/cr50/board.c +++ b/board/cr50/board.c @@ -546,11 +546,6 @@ void board_configure_deep_sleep_wakepins(void) static void deferred_tpm_rst_isr(void); DECLARE_DEFERRED(deferred_tpm_rst_isr); -int ap_is_on(void) -{ - return device_get_state(DEVICE_AP) == DEVICE_STATE_ON; -} - static void configure_board_specific_gpios(void) { /* Add a pullup to sys_rst_l */ @@ -569,15 +564,9 @@ static void configure_board_specific_gpios(void) * plt_rst_l is on diom3, and sys_rst_l is on diom0. */ if (board_use_plt_rst()) { - /* Use plt_rst_l for device detect purposes. */ - device_states[DEVICE_AP].detect = GPIO_TPM_RST_L; - /* Use plt_rst_l as the tpm reset signal. */ GWRITE(PINMUX, GPIO1_GPIO0_SEL, GC_PINMUX_DIOM3_SEL); - /* No interrupts from AP UART TX state change are needed. */ - gpio_disable_interrupt(GPIO_DETECT_AP); - /* Enable the input */ GWRITE_FIELD(PINMUX, DIOM3_CTL, IE, 1); @@ -597,9 +586,6 @@ static void configure_board_specific_gpios(void) /* Enable powerdown exit on DIOM3 */ GWRITE_FIELD(PINMUX, EXITEN0, DIOM3, 1); } else { - /* Use AP UART TX for device detect purposes. */ - device_states[DEVICE_AP].detect = GPIO_DETECT_AP; - /* Use sys_rst_l as the tpm reset signal. */ GWRITE(PINMUX, GPIO1_GPIO0_SEL, GC_PINMUX_DIOM0_SEL); /* Enable the input */ @@ -731,7 +717,6 @@ static void board_init(void) /* Enable GPIO interrupts for device state machines */ gpio_enable_interrupt(GPIO_TPM_RST_L); - gpio_enable_interrupt(GPIO_DETECT_AP); gpio_enable_interrupt(GPIO_DETECT_SERVO); /* @@ -826,18 +811,21 @@ int flash_regions_to_enable(struct g_flash_region *regions, return 3; } +/** + * Deferred TPM reset interrupt handling + * + * This is always called from the HOOK task. + */ static void deferred_tpm_rst_isr(void) { CPRINTS("%s", __func__); /* - * If the board has platform reset, move the AP into DEVICE_STATE_ON. - * If we're transitioning the AP from OFF or UNKNOWN, also trigger the - * resume handler. + * If the board uses TPM reset to detect the AP, connect AP. This is + * the only way those boards connect; they don't examine AP UART TX. */ - if (board_detect_ap_with_tpm_rst() && - device_state_changed(DEVICE_AP, DEVICE_STATE_ON)) - hook_notify(HOOK_CHIPSET_RESUME); + if (board_detect_ap_with_tpm_rst()) + set_ap_on_deferred(); /* * If no reboot request is posted, OR if the other RW's header is not @@ -863,7 +851,12 @@ static void deferred_tpm_rst_isr(void) system_reset(SYSTEM_RESET_MANUALLY_TRIGGERED | SYSTEM_RESET_HARD); } -/* This is the interrupt handler to react to TPM_RST_L */ +/** + * Handle TPM_RST_L deasserting + * + * This can also be called explicitly from AP detection, if it thinks the + * interrupt handler missed the rising edge. + */ void tpm_rst_deasserted(enum gpio_signal signal) { hook_call_deferred(&deferred_tpm_rst_isr_data, 0); @@ -1104,22 +1097,6 @@ static void servo_deferred(void) } DECLARE_DEFERRED(servo_deferred); -/** - * Deferred handler for debouncing AP presence detect falling. - * - * This is called if DETECT_AP has been low long enough. - */ -static void ap_deferred(void) -{ - /* - * If the AP was still in DEVICE_STATE_UNKNOWN, move it to - * DEVICE_STATE_OFF and trigger the shutdown hook. - */ - if (device_powered_off(DEVICE_AP)) - hook_notify(HOOK_CHIPSET_SHUTDOWN); -} -DECLARE_DEFERRED(ap_deferred); - /* Note: this must EXACTLY match enum device_type! */ struct device_config device_states[] = { [DEVICE_SERVO] = { @@ -1128,11 +1105,6 @@ struct device_config device_states[] = { .detect = GPIO_DETECT_SERVO, .name = "Servo" }, - [DEVICE_AP] = { - .state = DEVICE_STATE_UNKNOWN, - .deferred = &ap_deferred_data, - .name = "AP" - }, }; BUILD_ASSERT(ARRAY_SIZE(device_states) == DEVICE_COUNT); @@ -1169,47 +1141,9 @@ static void servo_attached(void) */ void device_state_on(enum gpio_signal signal) { - /* - * On boards with plt_rst_l the ap state is detected with tpm_rst_l. - * Make sure we don't disable the tpm reset interrupt - * tpm_rst_deasserted(), which is what actually handles that interrupt. - */ - if (signal != GPIO_TPM_RST_L) - gpio_disable_interrupt(signal); + gpio_disable_interrupt(signal); switch (signal) { - case GPIO_TPM_RST_L: - /* - * Boards using tpm_rst_l have no AP state interrupt that will - * trigger device_state_on, so this will only get called when - * we poll the AP state and see that the detect signal is high, - * but the device state is not 'on'. - * - * Boards using tpm_rst_l to detect the AP state use the tpm - * reset handler to set the AP state to 'on'. If we managed to - * get to this point, the tpm reset handler has not run yet. - * This should only happen if there is a race between the board - * state polling and a scheduled call to - * deferred_tpm_rst_isr_data, but it may be because we missed - * the rising edge. Notify the handler again just in case we - * missed the edge to make sure we reset the tpm and update the - * state. If there is already a pending call, then this call - * won't affect it, because subsequent calls to to - * hook_call_deferred just change the delay for the call, and - * we are setting the delay to asap. - */ - CPRINTS("%s: tpm_rst_isr hasn't set the AP state to 'on'.", - __func__); - hook_call_deferred(&deferred_tpm_rst_isr_data, 0); - break; - case GPIO_DETECT_AP: - /* - * Turn the AP device on. If it was previously unknown or - * off, notify the resume hook. - */ - if (device_state_changed(DEVICE_AP, DEVICE_STATE_ON)) - hook_notify(HOOK_CHIPSET_RESUME); - break; case GPIO_DETECT_SERVO: servo_attached(); break; @@ -1274,19 +1208,8 @@ void board_update_device_state(enum device_type device) */ device_set_state(device, DEVICE_STATE_UNKNOWN); - /* - * The possible devices at this point are AP, EC, and SERVO. - * - * If the device is AP, it may use the TPM interrupt line as - * presence detect. In that case, we don't want to mess with - * the interrupt enable; it should already be enabled. - * - * EC and SERVO use UART lines muxed to GPIOs for their detect - * signals; we own those GPIOs, so need to enable their - * interrupts explicitly. - */ - if ((device != DEVICE_AP) || !board_detect_ap_with_tpm_rst()) - gpio_enable_interrupt(device_states[device].detect); + /* Enable servo detect interrupt */ + gpio_enable_interrupt(device_states[device].detect); /* * The signal is low now, but this could be just a (EC, AP, or @@ -1303,57 +1226,6 @@ void board_update_device_state(enum device_type device) } } -/** - * AP shutdown handler - * - * This is triggered by the deferred debounce handler for AP_DETECT when we're - * sure the AP has actually shut down. - */ -static void ap_shutdown(void) -{ - /* - * If I2C TPM is configured then the INT_AP_L signal is used as - * a low pulse trigger to sync I2C transactions with the - * host. By default Cr50 is driving this line high, but when the - * AP powers off, the 1.8V rail that it's pulled up to will be - * off and cause exessive power to be consumed by the Cr50. Set - * INT_AP_L as an input while the AP is powered off. - */ - gpio_set_flags(GPIO_INT_AP_L, GPIO_INPUT); - - disable_ccd_uart(UART_AP); - - /* - * We don't enable deep sleep on ARM devices yet, as its processing - * there will require more support on the AP side than is available - * now. - */ - if (board_deep_sleep_allowed()) - enable_deep_sleep(); -} -DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, ap_shutdown, HOOK_PRIO_DEFAULT); - -/** - * AP resume handler - * - * This is triggered by AP_DETECT interrupt handler (which may be a GPIO - * attached to the UART RX line from the AP, or the TPM interrupt signal). - */ -static void ap_resume(void) -{ - /* - * AP is powering up, set the I2C host sync signal to output and set - * it high which is the default level. - */ - gpio_set_flags(GPIO_INT_AP_L, GPIO_OUT_HIGH); - gpio_set_level(GPIO_INT_AP_L, 1); - - enable_ccd_uart(UART_AP); - - disable_deep_sleep(); -} -DECLARE_HOOK(HOOK_CHIPSET_RESUME, ap_resume, HOOK_PRIO_DEFAULT); - /* * This function duplicates some of the functionality in chip/g/gpio.c in order * to configure a given strap pin to be either a low gpio output, a gpio input diff --git a/board/cr50/board.h b/board/cr50/board.h index 0f59e48d56..6a350d0e28 100644 --- a/board/cr50/board.h +++ b/board/cr50/board.h @@ -173,8 +173,7 @@ enum usb_strings { /* Device indexes for devices that require debouncing */ enum device_type { - DEVICE_AP = 0, - DEVICE_SERVO, + DEVICE_SERVO = 0, DEVICE_COUNT }; @@ -246,6 +245,7 @@ enum nvmem_vars { void board_configure_deep_sleep_wakepins(void); /* Interrupt handler */ void tpm_rst_deasserted(enum gpio_signal signal); +void ap_detect_asserted(enum gpio_signal signal); void ec_detect_asserted(enum gpio_signal signal); void device_state_on(enum gpio_signal signal); void post_reboot_request(void); @@ -281,6 +281,7 @@ int board_is_first_factory_boot(void); void enable_ccd_uart(int uart); void disable_ccd_uart(int uart); +void print_ap_state(void); void print_ec_state(void); int ap_is_on(void); @@ -288,6 +289,8 @@ int ec_is_on(void); int rdd_is_connected(void); int servo_is_connected(void); +void set_ap_on_deferred(void); + /* Returns True if chip is brought up in a factory test harness. */ int chip_factory_mode(void); diff --git a/board/cr50/build.mk b/board/cr50/build.mk index dd99f31380..68bf4a0d03 100644 --- a/board/cr50/build.mk +++ b/board/cr50/build.mk @@ -29,7 +29,7 @@ dirs-y += chip/$(CHIP)/dcrypto dirs-y += $(BDIR)/tpm2 # Objects that we need to build -board-y = board.o ec_state.o +board-y = board.o ap_state.o ec_state.o board-${CONFIG_RDD} += rdd.o board-${CONFIG_USB_SPI} += usb_spi.o board-${CONFIG_USB_I2C} += usb_i2c.o diff --git a/board/cr50/gpio.inc b/board/cr50/gpio.inc index 0bd90555af..dcf5b38561 100644 --- a/board/cr50/gpio.inc +++ b/board/cr50/gpio.inc @@ -56,7 +56,7 @@ * system resets. */ GPIO_INT(TPM_RST_L, PIN(1, 0), GPIO_INT_RISING, tpm_rst_deasserted) -GPIO_INT(DETECT_AP, PIN(1, 1), GPIO_INT_HIGH, device_state_on) +GPIO_INT(DETECT_AP, PIN(1, 1), GPIO_INT_HIGH, ap_detect_asserted) GPIO_INT(DETECT_EC, PIN(1, 2), GPIO_INT_HIGH, ec_detect_asserted) /* * DETECT_SERVO and EC_TX_CR50_RX pins must NOT be changed without also changing diff --git a/board/cr50/rdd.c b/board/cr50/rdd.c index a0ad76cb22..03b1785d90 100644 --- a/board/cr50/rdd.c +++ b/board/cr50/rdd.c @@ -185,6 +185,7 @@ static int command_ccd(int argc, char **argv) return EC_ERROR_PARAM1; } + print_ap_state(); print_ec_state(); print_rdd_state();