summaryrefslogtreecommitdiff
path: root/common/power_button_x86.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/power_button_x86.c')
-rw-r--r--common/power_button_x86.c575
1 files changed, 0 insertions, 575 deletions
diff --git a/common/power_button_x86.c b/common/power_button_x86.c
deleted file mode 100644
index 661f9b2a3d..0000000000
--- a/common/power_button_x86.c
+++ /dev/null
@@ -1,575 +0,0 @@
-/* Copyright 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 CPRINTS(format, args...) cprints(CC_SWITCH, format, ## args)
-
-/*
- * x86 chipsets have a hardware timer on the power button input which causes
- * them to reset when the button is pressed for more than 4 seconds. This is
- * problematic for Chrome OS, which needs more time than that to transition
- * through the lock and logout screens. So when the system is on, we need to
- * stretch the power button signal so that the chipset will hard-reboot after 8
- * seconds instead of 4.
- *
- * When the button is pressed, we initially send a short pulse (t0); this
- * allows the chipset to process its initial power button interrupt and do
- * things like wake from suspend. We then deassert the power button signal to
- * the chipset for (t1 = 4 sec - t0), which keeps the chipset from starting its
- * hard reset timer. If the power button is still pressed after this period,
- * we again assert the power button signal for the remainder of the press
- * duration. Since (t0+t1) causes a 4-second offset, the hard reset timeout in
- * the chipset triggers after 8 seconds as desired.
- *
- * PWRBTN# --- ----
- * to EC |______________________|
- *
- *
- * PWRBTN# --- --------- ----
- * to PCH |__| |___________|
- * t0 t1 held down
- *
- * scan code | |
- * to host v v
- * @S0 make code break code
- */
-#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;
-
-/*
- * Record the time when power button task starts. It can be used by any code
- * path that needs to compare the current time with power button task start time
- * to identify any timeouts e.g. PB state machine checks current time to
- * identify if it should wait more for charger and battery to be initialized. In
- * case of recovery using buttons (where the user could be holding the buttons
- * for >30seconds), it is not right to compare current time with the time when
- * EC was reset since the tasks would not have started. Hence, this variable is
- * being added to record the time at which power button task starts.
- */
-static uint64_t tpb_task_start;
-
-/*
- * Determines whether to execute power button pulse (t0 stage)
- */
-static int power_button_pulse_enabled = 1;
-
-static void set_pwrbtn_to_pch(int high, int init)
-{
- /*
- * If the battery is discharging and low enough we'd shut down the
- * system, don't press the power button. Also, don't press the
- * power button if the battery is charging but the battery level
- * is too low.
- */
-#ifdef CONFIG_CHARGER
- if (chipset_in_state(CHIPSET_STATE_ANY_OFF) && !high &&
- (charge_want_shutdown() || charge_prevent_power_on(!init))) {
- CPRINTS("PB PCH pwrbtn ignored due to battery level");
- high = 1;
- }
-#endif
- CPRINTS("PB PCH pwrbtn=%s", high ? "HIGH" : "LOW");
- if (IS_ENABLED(CONFIG_POWER_BUTTON_TO_PCH_CUSTOM))
- board_pwrbtn_to_pch(high);
- else
- gpio_set_level(GPIO_PCH_PWRBTN_L, high);
-}
-
-void power_button_pch_press(void)
-{
- CPRINTS("PB PCH force press");
-
- /* Assert power button signal to PCH */
- if (!power_button_is_pressed())
- set_pwrbtn_to_pch(0, 0);
-}
-
-void power_button_pch_release(void)
-{
- CPRINTS("PB PCH force release");
-
- /* Deassert power button signal to PCH */
- set_pwrbtn_to_pch(1, 0);
-
- /*
- * If power button is actually pressed, eat the next release so we
- * don't send an extra release.
- */
- if (power_button_is_pressed())
- pwrbtn_state = PWRBTN_STATE_EAT_RELEASE;
- else
- pwrbtn_state = PWRBTN_STATE_IDLE;
-}
-
-void power_button_pch_pulse(void)
-{
- CPRINTS("PB PCH pulse");
-
- chipset_exit_hard_off();
- set_pwrbtn_to_pch(0, 0);
- pwrbtn_state = PWRBTN_STATE_LID_OPEN;
- tnext_state = get_time().val + PWRBTN_INITIAL_US;
- task_wake(TASK_ID_POWERBTN);
-}
-
-/**
- * Handle debounced power button down.
- */
-static void power_button_pressed(uint64_t tnow)
-{
- CPRINTS("PB pressed");
- pwrbtn_state = PWRBTN_STATE_PRESSED;
- tnext_state = tnow;
-}
-
-/**
- * Handle debounced power button up.
- */
-static void power_button_released(uint64_t tnow)
-{
- CPRINTS("PB released");
- 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 unless power
- * button pulse is disabled. If power button SMI pulse is
- * enabled, then it should be honored, else setting power
- * button to PCH could lead to x86 platform shutting down. If
- * power button is still held by the time control reaches
- * state_machine(), it would take the appropriate action there.
- */
- if (power_button_is_pressed() && power_button_pulse_enabled) {
- CPRINTS("PB init-jumped-held");
- set_pwrbtn_to_pch(0, 0);
- } else {
- CPRINTS("PB init-jumped");
- }
- return;
- } else if ((reset_flags & EC_RESET_FLAG_AP_OFF) ||
- (keyboard_scan_get_boot_keys() == BOOT_KEY_DOWN_ARROW)) {
- /* Clear AP_OFF so that it won't be carried over to RW. */
- system_clear_reset_flags(EC_RESET_FLAG_AP_OFF);
- /*
- * 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.
- */
- CPRINTS("PB init-off");
- power_button_pch_release();
- return;
- } else if (reset_flags & EC_RESET_FLAG_AP_IDLE) {
- system_clear_reset_flags(EC_RESET_FLAG_AP_IDLE);
- pwrbtn_state = PWRBTN_STATE_IDLE;
- CPRINTS("PB idle");
- return;
- }
-
-#ifdef CONFIG_BRINGUP
- pwrbtn_state = PWRBTN_STATE_IDLE;
-#else
- pwrbtn_state = PWRBTN_STATE_INIT_ON;
-#endif
- CPRINTS("PB %s",
- pwrbtn_state == PWRBTN_STATE_INIT_ON ? "init-on" : "idle");
-}
-
-/**
- * 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;
- set_pwrbtn_to_pch(0, 0);
- } else {
- if (power_button_pulse_enabled) {
- /* 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, 0);
- } else {
- tnext_state = tnow + PWRBTN_DELAY_T1;
- pwrbtn_state = PWRBTN_STATE_T1;
- }
- }
- break;
- case PWRBTN_STATE_T0:
- tnext_state = tnow + PWRBTN_DELAY_T1;
- pwrbtn_state = PWRBTN_STATE_T1;
- set_pwrbtn_to_pch(1, 0);
- 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))
- CPRINTS("PB chipset already off");
- else
- set_pwrbtn_to_pch(0, 0);
- pwrbtn_state = PWRBTN_STATE_HELD;
- break;
- case PWRBTN_STATE_RELEASED:
- case PWRBTN_STATE_LID_OPEN:
- set_pwrbtn_to_pch(1, 0);
- pwrbtn_state = PWRBTN_STATE_IDLE;
- break;
- case PWRBTN_STATE_INIT_ON:
-
- /*
- * Before attempting to power the system on, we need to allow
- * time for charger, battery and USB-C PD initialization to be
- * ready to supply sufficient power. Check every 100
- * milliseconds, and give up CONFIG_POWER_BUTTON_INIT_TIMEOUT
- * seconds after the PB task was started. Here, it is
- * important to check the current time against PB task start
- * time to prevent unnecessary timeouts happening in recovery
- * case where the tasks could start as late as 30 seconds
- * after EC reset.
- */
-
- if (!IS_ENABLED(CONFIG_CHARGER) || charge_prevent_power_on(0)) {
- if (tnow >
- (tpb_task_start +
- CONFIG_POWER_BUTTON_INIT_TIMEOUT * SECOND)) {
- pwrbtn_state = PWRBTN_STATE_IDLE;
- break;
- }
-
- if (IS_ENABLED(CONFIG_CHARGER)) {
- tnext_state = tnow + 100 * MSEC;
- break;
- }
- }
-
- /*
- * Power the system on if possible. Gating due to insufficient
- * battery is handled inside set_pwrbtn_to_pch().
- */
- chipset_exit_hard_off();
-#ifdef CONFIG_DELAY_DSW_PWROK_TO_PWRBTN
- /* Check if power button is ready. If not, we'll come back. */
- if (get_time().val - get_time_dsw_pwrok() <
- CONFIG_DSW_PWROK_TO_PWRBTN_US) {
- tnext_state = get_time_dsw_pwrok() +
- CONFIG_DSW_PWROK_TO_PWRBTN_US;
- break;
- }
-#endif
-
- set_pwrbtn_to_pch(0, 1);
- tnext_state = get_time().val + PWRBTN_INITIAL_US;
- pwrbtn_state = PWRBTN_STATE_BOOT_KB_RESET;
- 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, 0);
- 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 *u)
-{
- uint64_t t;
- uint64_t tsleep;
-
- /*
- * Record the time when the task starts so that the state machine can
- * use this to identify any timeouts.
- */
- tpb_task_start = get_time().val;
-
- while (1) {
- t = get_time().val;
-
- /* Update state machine */
- CPRINTS("PB task %d = %s", 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.)
- */
- CPRINTS("PB task %d = %s, wait %d", 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);
-
-#ifdef CONFIG_LID_SWITCH
-/**
- * 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)
- && pwrbtn_state != PWRBTN_STATE_INIT_ON)
- power_button_pch_pulse();
-}
-DECLARE_HOOK(HOOK_LID_CHANGE, powerbtn_x86_lid_change, HOOK_PRIO_DEFAULT);
-#endif
-
-/**
- * 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 */
- CPRINTS("PB ignoring change");
- 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.
- */
- CPRINTS("PB ignoring release");
- 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);
-
-/**
- * Handle configuring the power button behavior through a host command
- */
-static enum ec_status hc_config_powerbtn_x86(struct host_cmd_handler_args *args)
-{
- const struct ec_params_config_power_button *p = args->params;
-
- power_button_pulse_enabled =
- !!(p->flags & EC_POWER_BUTTON_ENABLE_PULSE);
-
- return EC_SUCCESS;
-}
-DECLARE_HOST_COMMAND(EC_CMD_CONFIG_POWER_BUTTON, hc_config_powerbtn_x86,
- EC_VER_MASK(0));
-
-
-/*
- * Currently, the only reason why we disable power button pulse is to allow
- * detachable menu on AP to use power button for selection purpose without
- * triggering SMI. Thus, re-enable the pulse any time there is a chipset
- * state transition event.
- */
-static void power_button_pulse_setting_reset(void)
-{
- power_button_pulse_enabled = 1;
-}
-
-DECLARE_HOOK(HOOK_CHIPSET_STARTUP, power_button_pulse_setting_reset,
- HOOK_PRIO_DEFAULT);
-DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, power_button_pulse_setting_reset,
- HOOK_PRIO_DEFAULT);
-DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, power_button_pulse_setting_reset,
- HOOK_PRIO_DEFAULT);
-DECLARE_HOOK(HOOK_CHIPSET_RESUME, power_button_pulse_setting_reset,
- HOOK_PRIO_DEFAULT);
-
-#define POWER_BUTTON_SYSJUMP_TAG 0x5042 /* PB */
-#define POWER_BUTTON_HOOK_VERSION 1
-
-static void power_button_pulse_setting_restore_state(void)
-{
- const int *state;
- int version, size;
-
- state = (const int *)system_get_jump_tag(POWER_BUTTON_SYSJUMP_TAG,
- &version, &size);
-
- if (state && (version == POWER_BUTTON_HOOK_VERSION) &&
- (size == sizeof(power_button_pulse_enabled)))
- power_button_pulse_enabled = *state;
-}
-DECLARE_HOOK(HOOK_INIT, power_button_pulse_setting_restore_state,
- HOOK_PRIO_INIT_POWER_BUTTON + 1);
-
-static void power_button_pulse_setting_preserve_state(void)
-{
- system_add_jump_tag(POWER_BUTTON_SYSJUMP_TAG,
- POWER_BUTTON_HOOK_VERSION,
- sizeof(power_button_pulse_enabled),
- &power_button_pulse_enabled);
-}
-DECLARE_HOOK(HOOK_SYSJUMP, power_button_pulse_setting_preserve_state,
- HOOK_PRIO_DEFAULT);