summaryrefslogtreecommitdiff
path: root/board/cr50/ap_state.c
diff options
context:
space:
mode:
Diffstat (limited to 'board/cr50/ap_state.c')
-rw-r--r--board/cr50/ap_state.c211
1 files changed, 211 insertions, 0 deletions
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);