summaryrefslogtreecommitdiff
path: root/common/power_button_x86.c
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2013-07-12 14:07:25 -0700
committerChromeBot <chrome-bot@google.com>2013-07-17 10:49:48 -0700
commit672057cb7e242574079b05e3028c0e337aa450e5 (patch)
treed5d76796ca1ff024fe3aa4bdcffca5d5cc6d2ae8 /common/power_button_x86.c
parentd48828757de2274938cf8ec862689c935adaae33 (diff)
downloadchrome-ec-672057cb7e242574079b05e3028c0e337aa450e5.tar.gz
Split x86 power button logic out of switch.c
Power button logic is common across all platforms and is not LM4-specific, so move it to its own module. Switch.c will eventually be moving to common/ and will common across all platforms (not just x86), and splitting out the x86 power button logic is needed before that too. BUG=chrome-os-partner:18343 BRANCH=none TEST=manual 1) power on system with both lid and power button. 2) power+refresh -> reboots 3) power+refresh+esc -> recovery mode 4) power+refresh+downarrow -> reboots, AP stays off 5) toggling recovery GPIO via servo should generate SW debug output showing bit 0x10 toggling Change-Id: I07714e2c035dceece66f90407983397d2697e7d5 Signed-off-by: Randall Spangler <rspangler@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/61780
Diffstat (limited to 'common/power_button_x86.c')
-rw-r--r--common/power_button_x86.c399
1 files changed, 399 insertions, 0 deletions
diff --git a/common/power_button_x86.c b/common/power_button_x86.c
new file mode 100644
index 0000000000..2d353b74ed
--- /dev/null
+++ b/common/power_button_x86.c
@@ -0,0 +1,399 @@
+/* Copyright (c) 2013 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.
+ */
+
+/* Power button state machine for x86 platforms */
+
+#include "charge_state.h"
+#include "chipset.h"
+#include "common.h"
+#include "console.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "host_command.h"
+#include "keyboard_scan.h"
+#include "lid_switch.h"
+#include "power_button.h"
+#include "switch.h"
+#include "system.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_SWITCH, outstr)
+#define CPRINTF(format, args...) cprintf(CC_SWITCH, format, ## args)
+
+/*
+ * When chipset is on, we stretch the power button signal to it so chipset
+ * hard-reset is triggered at ~8 sec, not ~4 sec:
+ *
+ * PWRBTN# --- ----
+ * to EC |______________________|
+ *
+ *
+ * PWRBTN# --- --------- ----
+ * to PCH |__| |___________|
+ * t0 t1 held down
+ *
+ * scan code | |
+ * to host v v
+ * @S0 make code break code
+ */
+/* TODO: link to full power button / lid switch state machine description. */
+#define PWRBTN_DELAY_T0 (32 * MSEC) /* 32ms (PCH requires >16ms) */
+#define PWRBTN_DELAY_T1 (4 * SECOND - PWRBTN_DELAY_T0) /* 4 secs - t0 */
+/*
+ * Length of time to stretch initial power button press to give chipset a
+ * chance to wake up (~100ms) and react to the press (~16ms). Also used as
+ * pulse length for simulated power button presses when the system is off.
+ */
+#define PWRBTN_INITIAL_US (200 * MSEC)
+
+enum power_button_state {
+ /* Button up; state machine idle */
+ PWRBTN_STATE_IDLE = 0,
+ /* Button pressed; debouncing done */
+ PWRBTN_STATE_PRESSED,
+ /* Button down, chipset on; sending initial short pulse */
+ PWRBTN_STATE_T0,
+ /* Button down, chipset on; delaying until we should reassert signal */
+ PWRBTN_STATE_T1,
+ /* Button down, signal asserted to chipset */
+ PWRBTN_STATE_HELD,
+ /* Force pulse due to lid-open event */
+ PWRBTN_STATE_LID_OPEN,
+ /* Button released; debouncing done */
+ PWRBTN_STATE_RELEASED,
+ /* Ignore next button release */
+ PWRBTN_STATE_EAT_RELEASE,
+ /*
+ * Need to power on system after init, but waiting to find out if
+ * sufficient battery power.
+ */
+ PWRBTN_STATE_INIT_ON,
+ /* Forced pulse at EC boot due to keyboard controlled reset */
+ PWRBTN_STATE_BOOT_KB_RESET,
+ /* Power button pressed when chipset was off; stretching pulse */
+ PWRBTN_STATE_WAS_OFF,
+};
+static enum power_button_state pwrbtn_state = PWRBTN_STATE_IDLE;
+
+static const char * const state_names[] = {
+ "idle",
+ "pressed",
+ "t0",
+ "t1",
+ "held",
+ "lid-open",
+ "released",
+ "eat-release",
+ "init-on",
+ "recovery",
+ "was-off",
+};
+
+/*
+ * Time for next state transition of power button state machine, or 0 if the
+ * state doesn't have a timeout.
+ */
+static uint64_t tnext_state;
+
+static void set_pwrbtn_to_pch(int high)
+{
+ /*
+ * If the battery is discharging and low enough we'd shut down the
+ * system, don't press the power button.
+ */
+ if (!high && charge_want_shutdown()) {
+ CPRINTF("[%T PB PCH pwrbtn ignored due to battery level\n");
+ high = 1;
+ }
+
+ CPRINTF("[%T PB PCH pwrbtn=%s]\n", high ? "HIGH" : "LOW");
+ gpio_set_level(GPIO_PCH_PWRBTN_L, high);
+}
+
+/**
+ * Handle debounced power button down.
+ */
+static void power_button_pressed(uint64_t tnow)
+{
+ CPRINTF("[%T PB pressed]\n");
+ pwrbtn_state = PWRBTN_STATE_PRESSED;
+ tnext_state = tnow;
+}
+
+/**
+ * Handle debounced power button up.
+ */
+static void power_button_released(uint64_t tnow)
+{
+ CPRINTF("[%T PB released]\n");
+ pwrbtn_state = PWRBTN_STATE_RELEASED;
+ tnext_state = tnow;
+}
+
+/**
+ * Set initial power button state.
+ */
+static void set_initial_pwrbtn_state(void)
+{
+ uint32_t reset_flags = system_get_reset_flags();
+
+ if (system_jumped_to_this_image() &&
+ chipset_in_state(CHIPSET_STATE_ON)) {
+ /*
+ * Jumped to this image while the chipset was already on, so
+ * simply reflect the actual power button state.
+ */
+ if (power_button_is_pressed()) {
+ CPRINTF("[%T PB init-jumped-held]\n");
+ set_pwrbtn_to_pch(0);
+ } else {
+ CPRINTF("[%T PB init-jumped]\n");
+ }
+ } else if ((reset_flags & RESET_FLAG_AP_OFF) ||
+ (keyboard_scan_get_boot_key() == BOOT_KEY_DOWN_ARROW)) {
+ /*
+ * Reset triggered by keyboard-controlled reset, and down-arrow
+ * was held down. Or reset flags request AP off.
+ *
+ * Leave the main processor off. This is a fail-safe
+ * combination for debugging failures booting the main
+ * processor.
+ *
+ * Don't let the PCH see that the power button was pressed.
+ * Otherwise, it might power on.
+ */
+ CPRINTF("[%T PB init-off]\n");
+ set_pwrbtn_to_pch(1);
+ if (power_button_is_pressed())
+ pwrbtn_state = PWRBTN_STATE_EAT_RELEASE;
+ else
+ pwrbtn_state = PWRBTN_STATE_IDLE;
+ } else {
+ /*
+ * All other EC reset conditions power on the main processor so
+ * it can verify the EC.
+ */
+ CPRINTF("[%T PB init-on]\n");
+ pwrbtn_state = PWRBTN_STATE_INIT_ON;
+ }
+}
+
+/**
+ * Power button state machine.
+ *
+ * @param tnow Current time from usec counter
+ */
+static void state_machine(uint64_t tnow)
+{
+ /* Not the time to move onto next state */
+ if (tnow < tnext_state)
+ return;
+
+ /* States last forever unless otherwise specified */
+ tnext_state = 0;
+
+ switch (pwrbtn_state) {
+ case PWRBTN_STATE_PRESSED:
+ if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) {
+ /*
+ * Chipset is off, so wake the chipset and send it a
+ * long enough pulse to wake up. After that we'll
+ * reflect the true power button state. If we don't
+ * stretch the pulse here, the user may release the
+ * power button before the chipset finishes waking from
+ * hard off state.
+ */
+ chipset_exit_hard_off();
+ tnext_state = tnow + PWRBTN_INITIAL_US;
+ pwrbtn_state = PWRBTN_STATE_WAS_OFF;
+ } else {
+ /* Chipset is on, so send the chipset a pulse */
+ tnext_state = tnow + PWRBTN_DELAY_T0;
+ pwrbtn_state = PWRBTN_STATE_T0;
+ }
+ set_pwrbtn_to_pch(0);
+ break;
+ case PWRBTN_STATE_T0:
+ tnext_state = tnow + PWRBTN_DELAY_T1;
+ pwrbtn_state = PWRBTN_STATE_T1;
+ set_pwrbtn_to_pch(1);
+ break;
+ case PWRBTN_STATE_T1:
+ /*
+ * If the chipset is already off, don't tell it the power
+ * button is down; it'll just cause the chipset to turn on
+ * again.
+ */
+ if (chipset_in_state(CHIPSET_STATE_ANY_OFF))
+ CPRINTF("[%T PB chipset already off]\n");
+ else
+ set_pwrbtn_to_pch(0);
+ pwrbtn_state = PWRBTN_STATE_HELD;
+ break;
+ case PWRBTN_STATE_RELEASED:
+ case PWRBTN_STATE_LID_OPEN:
+ set_pwrbtn_to_pch(1);
+ pwrbtn_state = PWRBTN_STATE_IDLE;
+ break;
+ case PWRBTN_STATE_INIT_ON:
+ /*
+ * Don't do anything until the charger knows the battery level.
+ * Otherwise we could power on the AP only to shut it right
+ * back down due to insufficient battery.
+ */
+#ifdef HAS_TASK_CHARGER
+ if (charge_get_state() == PWR_STATE_INIT)
+ break;
+#endif
+
+ /*
+ * Power the system on if possible. Gating due to insufficient
+ * battery is handled inside set_pwrbtn_to_pch().
+ */
+ chipset_exit_hard_off();
+ set_pwrbtn_to_pch(0);
+ tnext_state = get_time().val + PWRBTN_INITIAL_US;
+
+ if (power_button_is_pressed()) {
+ if (system_get_reset_flags() & RESET_FLAG_RESET_PIN)
+ pwrbtn_state = PWRBTN_STATE_BOOT_KB_RESET;
+ else
+ pwrbtn_state = PWRBTN_STATE_WAS_OFF;
+ } else {
+ pwrbtn_state = PWRBTN_STATE_RELEASED;
+ }
+
+ break;
+
+ case PWRBTN_STATE_BOOT_KB_RESET:
+ /* Initial forced pulse is done. Ignore the actual power
+ * button until it's released, so that holding down the
+ * recovery combination doesn't cause the chipset to shut back
+ * down. */
+ set_pwrbtn_to_pch(1);
+ if (power_button_is_pressed())
+ pwrbtn_state = PWRBTN_STATE_EAT_RELEASE;
+ else
+ pwrbtn_state = PWRBTN_STATE_IDLE;
+ break;
+ case PWRBTN_STATE_WAS_OFF:
+ /* Done stretching initial power button signal, so show the
+ * true power button state to the PCH. */
+ if (power_button_is_pressed()) {
+ /* User is still holding the power button */
+ pwrbtn_state = PWRBTN_STATE_HELD;
+ } else {
+ /* Stop stretching the power button press */
+ power_button_released(tnow);
+ }
+ break;
+ case PWRBTN_STATE_IDLE:
+ case PWRBTN_STATE_HELD:
+ case PWRBTN_STATE_EAT_RELEASE:
+ /* Do nothing */
+ break;
+ }
+}
+
+void power_button_task(void)
+{
+ uint64_t t;
+ uint64_t tsleep;
+
+ while (1) {
+ t = get_time().val;
+
+ /* Update state machine */
+ CPRINTF("[%T PB task %d = %s]\n", pwrbtn_state,
+ state_names[pwrbtn_state]);
+
+ state_machine(t);
+
+ /* Sleep until our next timeout */
+ tsleep = -1;
+ if (tnext_state && tnext_state < tsleep)
+ tsleep = tnext_state;
+ t = get_time().val;
+ if (tsleep > t) {
+ unsigned d = tsleep == -1 ? -1 : (unsigned)(tsleep - t);
+ /*
+ * (Yes, the conversion from uint64_t to unsigned could
+ * theoretically overflow if we wanted to sleep for
+ * more than 2^32 us, but our timeouts are small enough
+ * that can't happen - and even if it did, we'd just go
+ * back to sleep after deciding that we woke up too
+ * early.)
+ */
+ CPRINTF("[%T PB task %d = %s, wait %d]\n", pwrbtn_state,
+ state_names[pwrbtn_state], d);
+ task_wait_event(d);
+ }
+ }
+}
+
+/*****************************************************************************/
+/* Hooks */
+
+static void powerbtn_x86_init(void)
+{
+ set_initial_pwrbtn_state();
+}
+DECLARE_HOOK(HOOK_INIT, powerbtn_x86_init, HOOK_PRIO_DEFAULT);
+
+/**
+ * Handle switch changes based on lid event.
+ */
+static void powerbtn_x86_lid_change(void)
+{
+ /* If chipset is off, pulse the power button on lid open to wake it. */
+ if (lid_is_open() && chipset_in_state(CHIPSET_STATE_ANY_OFF)) {
+ chipset_exit_hard_off();
+ set_pwrbtn_to_pch(0);
+ pwrbtn_state = PWRBTN_STATE_LID_OPEN;
+ tnext_state = get_time().val + PWRBTN_INITIAL_US;
+ task_wake(TASK_ID_POWERBTN);
+ }
+}
+DECLARE_HOOK(HOOK_LID_CHANGE, powerbtn_x86_lid_change, HOOK_PRIO_DEFAULT);
+
+/**
+ * Handle debounced power button changing state.
+ */
+static void powerbtn_x86_changed(void)
+{
+ if (pwrbtn_state == PWRBTN_STATE_BOOT_KB_RESET ||
+ pwrbtn_state == PWRBTN_STATE_INIT_ON ||
+ pwrbtn_state == PWRBTN_STATE_LID_OPEN ||
+ pwrbtn_state == PWRBTN_STATE_WAS_OFF) {
+ /* Ignore all power button changes during an initial pulse */
+ CPRINTF("[%T PB ignoring change]\n");
+ return;
+ }
+
+ if (power_button_is_pressed()) {
+ /* Power button pressed */
+ power_button_pressed(get_time().val);
+ } else {
+ /* Power button released */
+ if (pwrbtn_state == PWRBTN_STATE_EAT_RELEASE) {
+ /*
+ * Ignore the first power button release if we already
+ * told the PCH the power button was released.
+ */
+ CPRINTF("[%T PB ignoring release]\n");
+ pwrbtn_state = PWRBTN_STATE_IDLE;
+ return;
+ }
+
+ power_button_released(get_time().val);
+ }
+
+ /* Wake the power button task */
+ task_wake(TASK_ID_POWERBTN);
+}
+DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, powerbtn_x86_changed, HOOK_PRIO_DEFAULT);