diff options
author | Randall Spangler <rspangler@chromium.org> | 2017-08-25 12:39:16 -0700 |
---|---|---|
committer | ChromeOS Commit Bot <chromeos-commit-bot@chromium.org> | 2017-09-07 22:34:33 +0000 |
commit | ef272881641cbceb22764cde489b922c434a3769 (patch) | |
tree | 4cd889cf092d1732676592ac54432b17f0474057 /board | |
parent | d52c9191331603dbfb66f69052e88fe9f4cd0c1b (diff) | |
download | chrome-ec-ef272881641cbceb22764cde489b922c434a3769.tar.gz |
cr50: Split servo state machine into its own file
This is the last state machine which used common/device_state.c. But
servo is more complex than that, because it needs to differentiate
state-isn't-known (debouncing) from state-isn't-knowable (Cr50 driving
EC TX), so it's cleaner to split it out the way we did AP and EC state
machines in previous CLs.
BUG=b:35587387
BRANCH=cr50
TEST=manual with CR50_DEV=1 build
// Test detect at boot, even with CCD connected
Pull CCD_MODE_L low
Pull DETECT_SERVO high
Pull DETECT_EC high
reboot -> 'Servo connect'
// CCD is not driving EC UART TX
ccd -> EC on, Servo connected, CCD enabled, EC UART RX
// When servo disconnects CCD can drive EC TX
Pull DETECT_SERVO low --> 'Servo disconnect'
ccd -> EC on, Servo undetectable, CCD enabled, EC UART RX+TX
// Can't detect servo reconnecting if we're driving EC TX
Pull DETECT_SERVO high --> (no change)
ccd -> EC on, Servo undetectable, CCD enabled, EC UART RX+TX
// When we stop driving EC TX, can redetect servo
Pull EC_DETECT low --> See 'EC off', 'Servo connected'
ccd -> EC off, Servo connected, CCD enabled, EC UART disabled
// Test debouncing at boot
Pull DETECT_EC high
Pull DETECT_SERVO low
Pull CCD_MODE_L high
reboot
Within 1 sec, pull DETECT_SERVO high --> 'Servo connected'
// Test debouncing after boot
Pull DETECT_SERVO low then high < 1 sec --> (no message)
Change-Id: I964bd36c35f52c8ef7b3ea3793b6e0764e93587c
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/636047
Reviewed-by: Mary Ruthven <mruthven@chromium.org>
(cherry picked from commit fe0a3b99ff8303113c52e4573361325b44574b5c)
Reviewed-on: https://chromium-review.googlesource.com/656417
Commit-Queue: Vadim Bendebury <vbendeb@chromium.org>
Tested-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
Diffstat (limited to 'board')
-rw-r--r-- | board/cr50/board.c | 253 | ||||
-rw-r--r-- | board/cr50/board.h | 19 | ||||
-rw-r--r-- | board/cr50/build.mk | 2 | ||||
-rw-r--r-- | board/cr50/ec_state.c | 1 | ||||
-rw-r--r-- | board/cr50/gpio.inc | 2 | ||||
-rw-r--r-- | board/cr50/rdd.c | 7 | ||||
-rw-r--r-- | board/cr50/servo_state.c | 227 |
7 files changed, 248 insertions, 263 deletions
diff --git a/board/cr50/board.c b/board/cr50/board.c index 27ec67fbd2..f496ecd8ee 100644 --- a/board/cr50/board.c +++ b/board/cr50/board.c @@ -8,7 +8,6 @@ #include "common.h" #include "console.h" #include "dcrypto/dcrypto.h" -#include "device_state.h" #include "ec_version.h" #include "endian.h" #include "extension.h" @@ -84,9 +83,6 @@ uint32_t nvmem_user_sizes[NVMEM_NUM_USERS] = { NVMEM_CR50_SIZE }; -static int device_state_changed(enum device_type device, - enum device_state state); - /* Board specific configuration settings */ static uint32_t board_properties; /* Mainly used as a cache for strap config. */ static uint8_t reboot_request_posted; @@ -700,9 +696,6 @@ static void board_init(void) /* Load case-closed debugging config. Must be after initvars(). */ ccd_config_init(ccd_init_state); - /* Initialize write protect. Must be after CCD config init. */ - init_wp_state(); - system_update_rollback_mask_with_both_imgs(); /* Indication that firmware is running, for debug purposes. */ @@ -715,9 +708,8 @@ static void board_init(void) check_board_id_mismatch(); check_board_id_mismatch(); - /* Enable GPIO interrupts for device state machines */ + /* Enable TPM reset GPIO interrupt */ gpio_enable_interrupt(GPIO_TPM_RST_L); - gpio_enable_interrupt(GPIO_DETECT_SERVO); /* * Start monitoring AC detect to wake Cr50 from deep sleep. This is @@ -726,6 +718,17 @@ static void board_init(void) */ init_ac_detect(); init_rdd_state(); + + /* Initialize write protect. Must be after CCD config init. */ + init_wp_state(); + + /* + * Note that the AP, EC, and servo state machines do not have explicit + * init_xxx_state() functions, because they don't need to configure + * registers prior to starting their state machines. Their state + * machines run in HOOK_SECOND, which first triggers right after + * HOOK_INIT, not at +1.0 seconds. + */ } DECLARE_HOOK(HOOK_INIT, board_init, HOOK_PRIO_DEFAULT); @@ -924,49 +927,6 @@ int is_ec_rst_asserted(void) return GREAD(RBOX, ASSERT_EC_RST); } -/** - * Update device state with a change that has already happened - * - * Cancels any deferred call pending for this device. - * - * @param device Device to change - * @param state New state - * @return Passthru from device_set_state(). That is, non-zero if the last - * known device state has changed to a known and different value. - */ -static int device_state_changed(enum device_type device, - enum device_state state) -{ - /* Cancel any deferred call for this device */ - hook_call_deferred(device_states[device].deferred, -1); - - return device_set_state(device, state); -} - -/** - * Change the servo device to UNKNOWN if we can't tell if it's connected. - * - * @return 1 if we can't tell if servo is connected, 0 if we can tell. - */ -static int servo_state_unknowable(void) -{ - /* - * If we are driving the UART transmit line to the EC, then we can't - * check to see if servo is also doing so. If so, set the state of the - * servo device to DEVICE_STATE_UNKNOWN. - * - * We also need to check if we're bit-banging the EC UART, because in - * that case, the UART transmit line is directly controlled as a GPIO - * and can be high even if UART TX is disconnected. - */ - if (uart_tx_is_connected(UART_EC) || uart_bitbang_is_enabled(UART_EC)) { - device_set_state(DEVICE_SERVO, DEVICE_STATE_UNKNOWN); - return 1; - } - - return 0; -} - void enable_ccd_uart(int uart) { /* @@ -1037,195 +997,6 @@ static void board_ccd_change_hook(void) } DECLARE_HOOK(HOOK_CCD_CHANGE, board_ccd_change_hook, HOOK_PRIO_DEFAULT); -/** - * Move a device from DEVICE_STATE_UNKNOWN to DEVICE_STATE_OFF. - * - * @return 1 if the device moved from UNKNOWN->OFF, or 0 (without changing - * state) for any other requested state transition. - */ -static int device_powered_off(enum device_type device) -{ - /* - * If the device state is on, then leave it there. - * - * When does this happen? If there's a race condition between a - * state transition to ON in an interrupt handler and a transition to - * OFF in a hook task level function? - * - * If that's the intent, this is *still* race conditiony, because the - * call to device_state_changed() below can still happen after the - * interrupt handler turns the device on. - */ - if (device_get_state(device) == DEVICE_STATE_ON) - return 0; - - /* - * The device state is either unknown or off. - * - * If it's already off, then device_state_changed() will return 0, - * and we'll exit with error unknown. - * - * If it was unknown, we'll change it to off and return success. - */ - if (!device_state_changed(device, DEVICE_STATE_OFF)) - return 0; - - return 1; -} - -/** - * Deferred handler for servo removal debouncing. - * - * This will be called if servo has been detached for long enough, as - * indicated by the UART transmit line to the EC (EC RX) being low. - */ -static void servo_deferred(void) -{ - /* - * If we're already driving the transmit line, we don't know if servo - * has actually been detached. - */ - if (servo_state_unknowable()) - return; - - /* - * Otherwise, we're done debouncing servo removal. If we're still in - * the UNKNOWN state, move it to the OFF state. - */ - if (device_powered_off(DEVICE_SERVO)) - uartn_tx_connect(UART_AP); -} -DECLARE_DEFERRED(servo_deferred); - -/* Note: this must EXACTLY match enum device_type! */ -struct device_config device_states[] = { - [DEVICE_SERVO] = { - .state = DEVICE_STATE_UNKNOWN, - .deferred = &servo_deferred_data, - .detect = GPIO_DETECT_SERVO, - .name = "Servo" - }, -}; -BUILD_ASSERT(ARRAY_SIZE(device_states) == DEVICE_COUNT); - -static void servo_attached(void) -{ - /* - * If we're not actually sure servo is attached because we're driving - * the EC UART's transmit line, then return. - */ - if (servo_state_unknowable()) - return; - - /* Update the device state */ - device_state_changed(DEVICE_SERVO, DEVICE_STATE_ON); - - /* - * Disable UART bit banging. Note this must be done before - * uartn_tx_disconnect() below, because this call will currently call - * uartn_tx_connect()! - */ - uart_bitbang_disable(bitbang_config.uart); - - /* Disconnect AP and EC UART when servo is attached */ - uartn_tx_disconnect(UART_AP); - uartn_tx_disconnect(UART_EC); - - /* Disconnect i2cm interface to ina */ - usb_i2c_board_disable(); -} - -/* - * Note: This is both an interrupt handler AND called from hook_task, and can - * be re-entrant. - */ -void device_state_on(enum gpio_signal signal) -{ - gpio_disable_interrupt(signal); - - switch (signal) { - case GPIO_DETECT_SERVO: - servo_attached(); - break; - default: - CPRINTS("Device %d not supported", signal); - return; - } -} - -/* Note: This is called for every device once a second from the HOOK task */ -void board_update_device_state(enum device_type device) -{ - if (device == DEVICE_SERVO) { - /* - * If we're driving the transmit line to the EC UART, then we - * can't tell if servo is connected. This check will also move - * the servo device to DEVICE_STATE_UNKNOWN in that case. - */ - if (servo_state_unknowable()) - return; - - /* Otherwise, continue to the checks below */ - } - - /* - * If the device is currently on set its state immediately. If it - * thinks the device is powered off debounce the signal. - */ - if (gpio_get_level(device_states[device].detect)) { - /* - * If the device is already on, return now. - * - * Seems like we could rely on a 0 return value from - * device_set_state() to tell us that, rather than making an - * explicit check here. - */ - if (device_get_state(device) == DEVICE_STATE_ON) - return; - - /* - * Otherwise, immediately call the interrupt handler for the - * pin to move the device to DEVICE_STATE_ON. - * - * Note this may be a race condition. - */ - device_state_on(device_states[device].detect); - } else { - /* - * If the device is already off, return now. - * - * Seems like we could rely on a 0 return value from - * device_set_state() to tell us that, rather than making an - * explicit check here. - */ - if (device_get_state(device) == DEVICE_STATE_OFF) - return; - - /* - * Otherwise, move the device to DEVICE_STATE_UNKNOWN. - * - * This is also likely a race condition. - */ - device_set_state(device, DEVICE_STATE_UNKNOWN); - - /* 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 - * Servo) UART transmitting 0-bits or PLT_RST_L pulsing. Let's - * wait long enough to debounce in both cases, picking duration - * slightly shorter than the (one second) device polling - * interval. - * - * Interrupts from the appropriate source (platform dependent) - * will cancel the deferred function if the signal is - * deasserted within the deferral interval. - */ - hook_call_deferred(device_states[device].deferred, 900 * MSEC); - } -} - /* * 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 6a350d0e28..b900768115 100644 --- a/board/cr50/board.h +++ b/board/cr50/board.h @@ -76,9 +76,6 @@ /* Allow multiple concurrent memory allocations. */ #define CONFIG_MALLOC -/* Detect the states of other devices */ -#define CONFIG_DEVICE_STATE - /* Enable debug cable detection */ #define CONFIG_RDD @@ -171,13 +168,6 @@ enum usb_strings { USB_STR_COUNT }; -/* Device indexes for devices that require debouncing */ -enum device_type { - DEVICE_SERVO = 0, - - DEVICE_COUNT -}; - /* * Device states * @@ -243,13 +233,13 @@ 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); void ec_tx_cr50_rx(enum gpio_signal signal); +void servo_detect_asserted(enum gpio_signal signal); +void tpm_rst_deasserted(enum gpio_signal signal); + +void post_reboot_request(void); /* Special controls over EC and AP */ void assert_sys_rst(void); @@ -283,6 +273,7 @@ void disable_ccd_uart(int uart); void print_ap_state(void); void print_ec_state(void); +void print_servo_state(void); int ap_is_on(void); int ec_is_on(void); diff --git a/board/cr50/build.mk b/board/cr50/build.mk index 68bf4a0d03..d6b40c294e 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 ap_state.o ec_state.o +board-y = board.o ap_state.o ec_state.o servo_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/ec_state.c b/board/cr50/ec_state.c index fb8087ab54..a324bdf772 100644 --- a/board/cr50/ec_state.c +++ b/board/cr50/ec_state.c @@ -66,6 +66,7 @@ static void set_ec_on(void) CPRINTS("EC RX only"); if (!uart_bitbang_is_enabled(UART_EC)) uartn_enable(UART_EC); + set_state(DEVICE_STATE_INIT_RX_ONLY); return; } diff --git a/board/cr50/gpio.inc b/board/cr50/gpio.inc index dcf5b38561..8288735a3f 100644 --- a/board/cr50/gpio.inc +++ b/board/cr50/gpio.inc @@ -64,7 +64,7 @@ GPIO_INT(DETECT_EC, PIN(1, 2), GPIO_INT_HIGH, ec_detect_asserted) * in the config assume the pin definitions here. */ GPIO_INT(DETECT_SERVO, PIN(1, 3), GPIO_INT_HIGH | GPIO_PULL_DOWN, - device_state_on) + servo_detect_asserted) GPIO_INT(EC_TX_CR50_RX, PIN(1, 4), GPIO_INT_FALLING, ec_tx_cr50_rx) /* Pull this low to interrupt the AP */ diff --git a/board/cr50/rdd.c b/board/cr50/rdd.c index 03b1785d90..db38fbcd52 100644 --- a/board/cr50/rdd.c +++ b/board/cr50/rdd.c @@ -5,7 +5,6 @@ #include "case_closed_debug.h" #include "console.h" -#include "device_state.h" #include "gpio.h" #include "hooks.h" #include "i2c.h" @@ -51,11 +50,6 @@ static void uart_select_tx(int uart, int signal) } } -int servo_is_connected(void) -{ - return device_get_state(DEVICE_SERVO) == DEVICE_STATE_ON; -} - void uartn_tx_connect(int uart) { /* @@ -188,6 +182,7 @@ static int command_ccd(int argc, char **argv) print_ap_state(); print_ec_state(); print_rdd_state(); + print_servo_state(); ccprintf("CCD: %s\n", rdd_is_connected() ? "enabled" : "disabled"); ccprintf("AP UART: %s\n", diff --git a/board/cr50/servo_state.c b/board/cr50/servo_state.c new file mode 100644 index 0000000000..5192549ec6 --- /dev/null +++ b/board/cr50/servo_state.c @@ -0,0 +1,227 @@ +/* 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. + * + * Servo state machine. + */ +#include "common.h" +#include "console.h" +#include "gpio.h" +#include "hooks.h" +#include "uart_bitbang.h" +#include "uartn.h" +#include "usb_i2c.h" + +#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args) + +static enum device_state state = DEVICE_STATE_INIT; + +void print_servo_state(void) +{ + ccprintf("Servo: %s\n", device_state_name(state)); +} + +int servo_is_connected(void) +{ + /* + * If we're connected, we definitely know we are. If we're debouncing, + * then we were connected and might still be. If we haven't + * initialized yet, we'd bettter assume we're connected until we prove + * otherwise. In any of these cases, it's not safe to allow ports to + * be connected because that would block detecting servo. + */ + return (state == DEVICE_STATE_CONNECTED || + state == DEVICE_STATE_DEBOUNCING || + state == DEVICE_STATE_INIT || + state == DEVICE_STATE_INIT_DEBOUNCING); +} + +/** + * Set the servo 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 servo_disconnect(), etc. do. + * + * @param new_state State to set. + */ +static void set_state(enum device_state new_state) +{ +#ifdef CR50_DEBUG_SERVO_STATE + /* Print all state transitions. May spam the console. */ + if (state != new_state) + CPRINTS("Servo %s -> %s", + device_state_name(state), device_state_name(new_state)); +#endif + state = new_state; +} + +/** + * Check if we can tell servo is connected. + * + * @return 1 if we can tell if servo is connected, 0 if we can't tell. + */ +static int servo_detectable(void) +{ + /* + * If we are driving the UART transmit line to the EC, then we can't + * check to see if servo is also doing so. + * + * We also need to check if we're bit-banging the EC UART, because in + * that case, the UART transmit line is directly controlled as a GPIO + * and can be high even if UART TX is disconnected. + */ + return !(uart_tx_is_connected(UART_EC) || + uart_bitbang_is_enabled(UART_EC)); +} + +/** + * Handle servo being disconnected + */ +static void servo_disconnect(void) +{ + if (!servo_is_connected()) + return; + + CPRINTS("Servo disconnect"); + set_state(DEVICE_STATE_DISCONNECTED); + + /* Reconnect AP and EC UART TX if needed */ + uartn_tx_connect(UART_AP); + uartn_tx_connect(UART_EC); +} + +/** + * Handle servo being connected. + * + * This can be called directly by servo_detect(), or as a deferred function. + * Both are in the HOOK task, so can't preempt each other. + */ +static void servo_connect(void) +{ + /* + * If we were debouncing disconnect, go back to connected. We never + * finished disconnecting, so nothing else is necessary. + */ + if (state == DEVICE_STATE_DEBOUNCING) + set_state(DEVICE_STATE_CONNECTED); + + /* If we're already connected, nothing else needs to be done */ + if (state == DEVICE_STATE_CONNECTED) + return; + + /* + * If we're still here, this is a real transition from a disconnected + * state, so we need to configure ports. + */ + CPRINTS("Servo connect"); + set_state(DEVICE_STATE_CONNECTED); + + /* + * Disable UART bit banging. Note this must be done before + * uartn_tx_disconnect() below, because this call will currently call + * uartn_tx_connect()! + */ + uart_bitbang_disable(bitbang_config.uart); + + /* + * Disconnect AP and EC UART TX when servo is attached. It's ok to + * leave RX enabled, because only TX interferes with TX from servo; + * that's why we don't call disable_ccd_uart() here. + */ + uartn_tx_disconnect(UART_AP); + uartn_tx_disconnect(UART_EC); + + /* Disconnect i2cm interface to ina */ + usb_i2c_board_disable(); +} +DECLARE_DEFERRED(servo_connect); + +/** + * Servo state machine + */ +static void servo_detect(void) +{ + /* Disable interrupts if we had them on for debouncing */ + gpio_disable_interrupt(GPIO_DETECT_SERVO); + + /* If we're driving EC UART TX, we can't detect servo */ + if (!servo_detectable()) { + /* We're driving one port; might as well drive them all */ + servo_disconnect(); + + set_state(DEVICE_STATE_UNDETECTABLE); + return; + } + + /* Handle detecting servo */ + if (gpio_get_level(GPIO_DETECT_SERVO)) { + servo_connect(); + return; + } + + /* + * If servo has become detectable but wasn't detected above, assume + * it's disconnected. + * + * We know we were driving EC UART TX, so we want to give priority to + * our ability to drive it again. If we went to the debouncing state + * here, then we'd need to wait a second before we could drive it. + * + * This is similar to how if servo was driving EC UART TX, we go to the + * debouncing state below, because we want to give priority to servo + * being able to drive it again. + */ + if (state == DEVICE_STATE_UNDETECTABLE) + set_state(DEVICE_STATE_DISCONNECTED); + + /* Servo wasn't detected. If we're already disconnected, done. */ + if (state == DEVICE_STATE_DISCONNECTED) + return; + + /* If we were debouncing, we're now sure we're disconnected */ + if (state == DEVICE_STATE_DEBOUNCING || + state == DEVICE_STATE_INIT_DEBOUNCING) { + servo_disconnect(); + return; + } + + /* + * Otherwise, we were connected or initializing, and we're not sure if + * we're now disconnected or just sending a 0-bit. So start + * debouncing. + * + * During debouncing, servo_is_connected() will still return true, so + * that if both CCD and servo cables are connected, we won't start + * driving EC UART TX and become unable to determine the servo connect + * state. + */ + if (state == DEVICE_STATE_INIT) + set_state(DEVICE_STATE_INIT_DEBOUNCING); + else + set_state(DEVICE_STATE_DEBOUNCING); + gpio_enable_interrupt(GPIO_DETECT_SERVO); +} +/* + * Do this at slightly elevated priority so it runs before rdd_check_pin() and + * ec_detect(). This increases the odds that we'll detect servo before + * detecting the EC. If ec_detect() ran first, it could turn on TX to the EC + * UART before we had a chance to detect servo. This is still a little bit of + * a race condition. + */ +DECLARE_HOOK(HOOK_SECOND, servo_detect, HOOK_PRIO_DEFAULT - 1); + +/** + * Interrupt handler for servo detect asserted + */ +void servo_detect_asserted(enum gpio_signal signal) +{ + gpio_disable_interrupt(GPIO_DETECT_SERVO); + + /* + * If this interrupt is because servo is actually detectable (vs. we're + * driving the detect pin now), queue a transition back to connected. + */ + if (servo_detectable()) + hook_call_deferred(&servo_connect_data, 0); +} |