summaryrefslogtreecommitdiff
path: root/power
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2013-10-16 13:23:10 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2013-10-23 20:07:25 +0000
commit8cf03ac0563294fbdeca2dc133d06f0b51c9a546 (patch)
tree6b07c493e7567a3221d8592b4337d2787d6bc531 /power
parent2464d08e4d310a3f63208f22df4502c5250c4b58 (diff)
downloadchrome-ec-8cf03ac0563294fbdeca2dc133d06f0b51c9a546.tar.gz
Move source files to driver/ and power/ subdirs
The common/ subdir was getting cluttered. Move drivers for external components to a new driver/ tree, and move what used to be called chipset_*.c to a new power/ directory. This does not move/rename header files or CONFIG options. That will be done in subsequent steps, since moving and modifying .c files in the same CL is harder to review. BUG=chrome-os-partner:18343 BRANCH=none TEST=build all boards; pass unit tests Change-Id: I67a3003dc8564783a320335cf0e9620a21982d5e Signed-off-by: Randall Spangler <rspangler@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/173601 Reviewed-by: Bill Richardson <wfrichar@chromium.org> Tested-by: Bill Richardson <wfrichar@chromium.org> Reviewed-by: Vic Yang <victoryang@chromium.org>
Diffstat (limited to 'power')
-rw-r--r--power/baytrail.c362
-rw-r--r--power/build.mk15
-rw-r--r--power/gaia.c770
-rw-r--r--power/haswell.c406
-rw-r--r--power/ivybridge.c344
-rw-r--r--power/tegra.c601
-rw-r--r--power/x86_common.c422
7 files changed, 2920 insertions, 0 deletions
diff --git a/power/baytrail.c b/power/baytrail.c
new file mode 100644
index 0000000000..7de5aaac01
--- /dev/null
+++ b/power/baytrail.c
@@ -0,0 +1,362 @@
+/* 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.
+ */
+
+/* X86 chipset power control module for Chrome EC */
+
+#include "chipset.h"
+#include "chipset_x86_common.h"
+#include "common.h"
+#include "console.h"
+#include "ec_commands.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "host_command.h"
+#include "lid_switch.h"
+#include "system.h"
+#include "timer.h"
+#include "util.h"
+#include "wireless.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_CHIPSET, outstr)
+#define CPRINTF(format, args...) cprintf(CC_CHIPSET, format, ## args)
+
+/* Input state flags */
+#define IN_PGOOD_PP5000 X86_SIGNAL_MASK(X86_PGOOD_PP5000)
+#define IN_PGOOD_PP1050 X86_SIGNAL_MASK(X86_PGOOD_PP1050)
+#define IN_PGOOD_S5 X86_SIGNAL_MASK(X86_PGOOD_S5)
+#define IN_PGOOD_VCORE X86_SIGNAL_MASK(X86_PGOOD_VCORE)
+#define IN_PCH_SLP_S3n_DEASSERTED X86_SIGNAL_MASK(X86_PCH_SLP_S3n_DEASSERTED)
+#define IN_PCH_SLP_S4n_DEASSERTED X86_SIGNAL_MASK(X86_PCH_SLP_S4n_DEASSERTED)
+
+/* All always-on supplies */
+#define IN_PGOOD_ALWAYS_ON (IN_PGOOD_S5)
+/* All non-core power rails */
+#define IN_PGOOD_ALL_NONCORE (IN_PGOOD_PP5000)
+/* All core power rails */
+#define IN_PGOOD_ALL_CORE (IN_PGOOD_VCORE)
+/* Rails required for S3 */
+#define IN_PGOOD_S3 (IN_PGOOD_ALWAYS_ON)
+/* Rails required for S0 */
+#define IN_PGOOD_S0 (IN_PGOOD_ALWAYS_ON | IN_PGOOD_ALL_NONCORE)
+
+/* All PM_SLP signals from PCH deasserted */
+#define IN_ALL_PM_SLP_DEASSERTED (IN_PCH_SLP_S3n_DEASSERTED | \
+ IN_PCH_SLP_S4n_DEASSERTED)
+/* All inputs in the right state for S0 */
+#define IN_ALL_S0 (IN_PGOOD_ALWAYS_ON | IN_PGOOD_ALL_NONCORE | \
+ IN_PGOOD_ALL_CORE | IN_ALL_PM_SLP_DEASSERTED)
+
+static int throttle_cpu; /* Throttle CPU? */
+static int pause_in_s5; /* Pause in S5 when shutting down? */
+
+void chipset_force_shutdown(void)
+{
+ CPRINTF("[%T %s()]\n", __func__);
+
+ /*
+ * Force x86 off. This condition will reset once the state machine
+ * transitions to G3.
+ */
+ /* TODO(rspangler): verify this works */
+ gpio_set_level(GPIO_PCH_SYS_PWROK, 0);
+ gpio_set_level(GPIO_PCH_RSMRST_L, 0);
+}
+
+void chipset_reset(int cold_reset)
+{
+ CPRINTF("[%T %s(%d)]\n", __func__, cold_reset);
+ if (cold_reset) {
+ /*
+ * Drop and restore PWROK. This causes the PCH to reboot,
+ * regardless of its after-G3 setting. This type of reboot
+ * causes the PCH to assert PLTRST#, SLP_S3#, and SLP_S5#, so
+ * we actually drop power to the rest of the system (hence, a
+ * "cold" reboot).
+ */
+
+ /* Ignore if PWROK is already low */
+ if (gpio_get_level(GPIO_PCH_SYS_PWROK) == 0)
+ return;
+
+ /* PWROK must deassert for at least 3 RTC clocks = 91 us */
+ gpio_set_level(GPIO_PCH_SYS_PWROK, 0);
+ udelay(100);
+ gpio_set_level(GPIO_PCH_SYS_PWROK, 1);
+
+ } else {
+ /*
+ * Send a reset pulse to the PCH. This just causes it to
+ * assert INIT# to the CPU without dropping power or asserting
+ * PLTRST# to reset the rest of the system. Pulse must be at
+ * least 16 PCI clocks long = 500 ns.
+ */
+ gpio_set_level(GPIO_PCH_RCIN_L, 0);
+ udelay(10);
+ gpio_set_level(GPIO_PCH_RCIN_L, 1);
+ }
+}
+
+void chipset_throttle_cpu(int throttle)
+{
+ if (chipset_in_state(CHIPSET_STATE_ON))
+ gpio_set_level(GPIO_CPU_PROCHOT, throttle);
+}
+
+enum x86_state x86_chipset_init(void)
+{
+ /*
+ * If we're switching between images without rebooting, see if the x86
+ * is already powered on; if so, leave it there instead of cycling
+ * through G3.
+ */
+ if (system_jumped_to_this_image()) {
+ if ((x86_get_signals() & IN_ALL_S0) == IN_ALL_S0) {
+ CPRINTF("[%T x86 already in S0]\n");
+ return X86_S0;
+ } else {
+ /* Force all signals to their G3 states */
+ CPRINTF("[%T x86 forcing G3]\n");
+ gpio_set_level(GPIO_PCH_CORE_PWROK, 0);
+ gpio_set_level(GPIO_VCORE_EN, 0);
+ gpio_set_level(GPIO_SUSP_VR_EN, 0);
+ gpio_set_level(GPIO_PP1350_EN, 0);
+ gpio_set_level(GPIO_PP3300_DX_EN, 0);
+ gpio_set_level(GPIO_PP5000_EN, 0);
+ gpio_set_level(GPIO_PCH_RSMRST_L, 0);
+ gpio_set_level(GPIO_PCH_SYS_PWROK, 0);
+ wireless_enable(0);
+ }
+ }
+
+ return X86_G3;
+}
+
+enum x86_state x86_handle_state(enum x86_state state)
+{
+ switch (state) {
+ case X86_G3:
+ break;
+
+ case X86_S5:
+ if (gpio_get_level(GPIO_PCH_SLP_S4_L) == 1)
+ return X86_S5S3; /* Power up to next state */
+ break;
+
+ case X86_S3:
+ /*
+ * If lid is closed; hold touchscreen in reset to cut power
+ * usage. If lid is open, take touchscreen out of reset so it
+ * can wake the processor. Chipset task is awakened on lid
+ * switch transitions.
+ */
+ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, lid_is_open());
+
+ /* Check for state transitions */
+ if (!x86_has_signals(IN_PGOOD_S3)) {
+ /* Required rail went away */
+ chipset_force_shutdown();
+ return X86_S3S5;
+ } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 1) {
+ /* Power up to next state */
+ return X86_S3S0;
+ } else if (gpio_get_level(GPIO_PCH_SLP_S4_L) == 0) {
+ /* Power down to next state */
+ return X86_S3S5;
+ }
+ break;
+
+ case X86_S0:
+ if (!x86_has_signals(IN_PGOOD_S0)) {
+ /* Required rail went away */
+ chipset_force_shutdown();
+ return X86_S0S3;
+ } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 0) {
+ /* Power down to next state */
+ return X86_S0S3;
+ }
+ break;
+
+ case X86_G3S5:
+ /* TODO(rspangler): temporary hack on Rev.1 boards */
+ gpio_set_level(GPIO_PP5000_EN, 1);
+
+ /*
+ * Wait 10ms after +3VALW good, since that powers VccDSW and
+ * VccSUS.
+ */
+ msleep(10);
+
+ gpio_set_level(GPIO_SUSP_VR_EN, 1);
+ if (x86_wait_signals(IN_PGOOD_S5)) {
+ chipset_force_shutdown();
+ return X86_G3;
+ }
+
+ /* Deassert RSMRST# */
+ gpio_set_level(GPIO_PCH_RSMRST_L, 1);
+
+ /* Wait 10ms for SUSCLK to stabilize */
+ msleep(10);
+ return X86_S5;
+
+ case X86_S5S3:
+ /* Wait for the always-on rails to be good */
+ if (x86_wait_signals(IN_PGOOD_ALWAYS_ON)) {
+ chipset_force_shutdown();
+ return X86_S5G3;
+ }
+
+ /* Turn on power to RAM */
+ gpio_set_level(GPIO_PP1350_EN, 1);
+ if (x86_wait_signals(IN_PGOOD_S3)) {
+ chipset_force_shutdown();
+ return X86_S5G3;
+ }
+
+ /*
+ * Enable touchpad power so it can wake the system from
+ * suspend.
+ */
+ gpio_set_level(GPIO_ENABLE_TOUCHPAD, 1);
+
+ /* Call hooks now that rails are up */
+ hook_notify(HOOK_CHIPSET_STARTUP);
+ return X86_S3;
+
+ case X86_S3S0:
+ /* Turn on power rails */
+ gpio_set_level(GPIO_PP5000_EN, 1);
+ gpio_set_level(GPIO_PP3300_DX_EN, 1);
+
+ /* Enable wireless */
+ wireless_enable(EC_WIRELESS_SWITCH_ALL);
+
+ /*
+ * Make sure touchscreen is out if reset (even if the lid is
+ * still closed); it may have been turned off if the lid was
+ * closed in S3.
+ */
+ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, 1);
+
+ /* Wait for non-core power rails good */
+ if (x86_wait_signals(IN_PGOOD_S0)) {
+ chipset_force_shutdown();
+ wireless_enable(0);
+ gpio_set_level(GPIO_PP3300_DX_EN, 0);
+ /* TODO(rspangler) turn off PP5000 after Rev.1 */
+ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, 0);
+ return X86_S3;
+ }
+
+ /*
+ * Enable +CPU_CORE. The CPU itself will request the supplies
+ * when it's ready.
+ */
+ gpio_set_level(GPIO_VCORE_EN, 1);
+
+ /* Call hooks now that rails are up */
+ hook_notify(HOOK_CHIPSET_RESUME);
+
+ /* Wait 100ms after all voltages good */
+ msleep(100);
+
+ /*
+ * Throttle CPU if necessary. This should only be asserted
+ * when +VCCP is powered (it is by now).
+ */
+ gpio_set_level(GPIO_CPU_PROCHOT, throttle_cpu);
+
+ /* Set SYS and CORE PWROK */
+ gpio_set_level(GPIO_PCH_SYS_PWROK, 1);
+ gpio_set_level(GPIO_PCH_CORE_PWROK, 1);
+ return X86_S0;
+
+ case X86_S0S3:
+ /* Call hooks before we remove power rails */
+ hook_notify(HOOK_CHIPSET_SUSPEND);
+
+ /* Clear SYS and CORE PWROK */
+ gpio_set_level(GPIO_PCH_SYS_PWROK, 0);
+ gpio_set_level(GPIO_PCH_CORE_PWROK, 0);
+
+ /* Wait 40ns */
+ udelay(1);
+
+ /* Disable +CPU_CORE */
+ gpio_set_level(GPIO_VCORE_EN, 0);
+
+ /* Disable wireless */
+ wireless_enable(0);
+
+ /*
+ * Deassert prochot since CPU is off and we're about to drop
+ * +VCCP.
+ */
+ gpio_set_level(GPIO_CPU_PROCHOT, 0);
+
+ /* Turn off power rails */
+ gpio_set_level(GPIO_PP3300_DX_EN, 0);
+ /* TODO(rspangler: turn off PP5000 after rev.1 */
+ return X86_S3;
+
+ case X86_S3S5:
+ /* Call hooks before we remove power rails */
+ hook_notify(HOOK_CHIPSET_SHUTDOWN);
+
+ /* Disable touchpad power */
+ gpio_set_level(GPIO_ENABLE_TOUCHPAD, 0);
+
+ /* Turn off power to RAM */
+ gpio_set_level(GPIO_PP1350_EN, 0);
+
+ /* Start shutting down */
+ return pause_in_s5 ? X86_S5 : X86_S5G3;
+
+ case X86_S5G3:
+ /* Assert RSMRST# */
+ gpio_set_level(GPIO_PCH_RSMRST_L, 0);
+ gpio_set_level(GPIO_SUSP_VR_EN, 0);
+
+ /* TODO(rspangler): temporary hack on rev.1 boards */
+ gpio_set_level(GPIO_PP5000_EN, 0);
+
+ return X86_G3;
+ }
+
+ return state;
+}
+
+static int host_command_gsv(struct host_cmd_handler_args *args)
+{
+ const struct ec_params_get_set_value *p = args->params;
+ struct ec_response_get_set_value *r = args->response;
+
+ if (p->flags & EC_GSV_SET)
+ pause_in_s5 = p->value;
+
+ r->value = pause_in_s5;
+
+ args->response_size = sizeof(*r);
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_GSV_PAUSE_IN_S5,
+ host_command_gsv,
+ EC_VER_MASK(0));
+
+static int console_command_gsv(int argc, char **argv)
+{
+ if (argc > 1 && !parse_bool(argv[1], &pause_in_s5))
+ return EC_ERROR_INVAL;
+
+ ccprintf("pause_in_s5 = %s\n", pause_in_s5 ? "on" : "off");
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(pause_in_s5, console_command_gsv,
+ "[on|off]",
+ "Should the AP pause in S5 during shutdown?",
+ NULL);
+
diff --git a/power/build.mk b/power/build.mk
new file mode 100644
index 0000000000..1c176a3db9
--- /dev/null
+++ b/power/build.mk
@@ -0,0 +1,15 @@
+# -*- makefile -*-
+# 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 management for application processor and peripherals
+#
+
+# TODO(rspangler): rename _CHIPSET to _POWER
+power-$(CONFIG_CHIPSET_BAYTRAIL)+=baytrail.o
+power-$(CONFIG_CHIPSET_GAIA)+=gaia.o
+power-$(CONFIG_CHIPSET_HASWELL)+=haswell.o
+power-$(CONFIG_CHIPSET_IVYBRIDGE)+=ivybridge.o
+power-$(CONFIG_CHIPSET_TEGRA)+=tegra.o
+power-$(CONFIG_CHIPSET_X86)+=x86_common.o
diff --git a/power/gaia.c b/power/gaia.c
new file mode 100644
index 0000000000..fd5d5fb7a6
--- /dev/null
+++ b/power/gaia.c
@@ -0,0 +1,770 @@
+/* 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.
+ */
+
+/*
+ * GAIA SoC power sequencing module for Chrome EC
+ *
+ * This implements the following features:
+ *
+ * - Cold reset powers off the AP
+ *
+ * When powered off:
+ * - Press pwron turns on the AP
+ * - Hold pwron turns on the AP, and then 16s later turns it off and leaves
+ * it off until pwron is released and pressed again
+ *
+ * When powered on:
+ * - The PMIC PWRON signal is released <= 1 second after the power button is
+ * released (we expect that U-Boot as asserted XPSHOLD by then)
+ * - Holding pwron for 8s powers off the AP
+ * - Pressing and releasing pwron within that 8s is ignored
+ * - If XPSHOLD is dropped by the AP, then we power the AP off
+ */
+
+#include "clock.h"
+#include "chipset.h" /* This module implements chipset functions too */
+#include "common.h"
+#include "console.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "lid_switch.h"
+#include "keyboard_scan.h"
+#include "power_led.h"
+#include "pmu_tpschrome.h"
+#include "system.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_CHIPSET, outstr)
+#define CPRINTF(format, args...) cprintf(CC_CHIPSET, format, ## args)
+
+/* Time necessary for the 5V and 3.3V regulator outputs to stabilize */
+#if defined(BOARD_pit) || defined(BOARD_puppy)
+#define DELAY_5V_SETUP (2 * MSEC)
+#define DELAY_3V_SETUP (2 * MSEC)
+#else
+#define DELAY_5V_SETUP MSEC
+#endif
+
+/* Delay between PMIC_PWRON and enabling 3.3V */
+#ifdef BOARD_kirby
+#define DELAY_PRE_3V_ENABLE 16620
+#endif
+
+/* Delay between 1.35v and 3.3v rails startup */
+#define DELAY_RAIL_STAGGERING 100 /* 100us */
+
+/* Long power key press to force shutdown */
+#define DELAY_FORCE_SHUTDOWN (8 * SECOND)
+
+/* Time necessary for pulling down XPSHOLD to shutdown PMIC power */
+#define DELAY_XPSHOLD_PULL (2 * MSEC)
+
+/*
+ * If the power key is pressed to turn on, then held for this long, we
+ * power off.
+ *
+ * The idea here is that behavior for 8s for AP shutdown is unchanged
+ * but power-on is modified to allow enough time U-Boot to be updated
+ * via USB (which takes about 10sec).
+ *
+ * So after power button is pressed:
+
+ * Normal case: User releases power button and chipset_task() goes
+ * into the inner loop, waiting for next event to occur (power button
+ * press or XPSHOLD == 0).
+ *
+ * U-Boot updating: User presses and holds power button. If EC does not
+ * see XPSHOLD, it waits up to 16sec for an event. If no event occurs
+ * within 16sec, EC powers off AP.
+ */
+#define DELAY_SHUTDOWN_ON_POWER_HOLD (8 * SECOND)
+#define DELAY_SHUTDOWN_ON_USB_BOOT (16 * SECOND)
+
+/* Maximum delay after power button press before we deassert GPIO_PMIC_PWRON */
+#define DELAY_RELEASE_PWRON SECOND /* 1s */
+
+/* debounce time to prevent accidental power-on after keyboard power off */
+#define KB_PWR_ON_DEBOUNCE 250 /* 250us */
+
+/* debounce time to prevent accidental power event after lid open/close */
+#define LID_SWITCH_DEBOUNCE 250 /* 250us */
+
+/* PMIC fails to set the LDO2 output */
+#define PMIC_TIMEOUT (100 * MSEC) /* 100ms */
+
+/* Default timeout for input transition */
+#define FAIL_TIMEOUT (500 * MSEC) /* 500ms */
+
+
+/* Application processor power state */
+static int ap_on;
+static int ap_suspended;
+
+/* simulated event state */
+static int force_signal = -1;
+static int force_value;
+
+/* 1 if the power button was pressed last time we checked */
+static char power_button_was_pressed;
+
+/* 1 if lid-open event has been detected */
+static char lid_opened;
+
+/* time where we will power off, if power button still held down */
+static timestamp_t power_off_deadline;
+
+/* force AP power on (used for recovery keypress) */
+static int auto_power_on;
+
+enum power_request_t {
+ POWER_REQ_NONE,
+ POWER_REQ_OFF,
+ POWER_REQ_ON,
+
+ POWER_REQ_COUNT,
+};
+
+static enum power_request_t power_request;
+
+/**
+ * Wait for GPIO "signal" to reach level "value".
+ * Returns EC_ERROR_TIMEOUT if timeout before reaching the desired state.
+ *
+ * @param signal Signal to watch
+ * @param value Value to watch for
+ * @param timeout Timeout in microseconds from now, or -1 to wait forever
+ * @return 0 if signal did change to required value, EC_ERROR_TIMEOUT if we
+ * timed out first.
+ */
+static int wait_in_signal(enum gpio_signal signal, int value, int timeout)
+{
+ timestamp_t deadline;
+ timestamp_t now = get_time();
+
+ deadline.val = now.val + timeout;
+
+ while (((force_signal != signal) || (force_value != value)) &&
+ gpio_get_level(signal) != value) {
+ now = get_time();
+ if (timeout < 0) {
+ task_wait_event(-1);
+ } else if (timestamp_expired(deadline, &now) ||
+ (task_wait_event(deadline.val - now.val) ==
+ TASK_EVENT_TIMER)) {
+ CPRINTF("[%T power timeout waiting for GPIO %d/%s]\n",
+ signal, gpio_get_name(signal));
+ return EC_ERROR_TIMEOUT;
+ }
+ }
+
+ return EC_SUCCESS;
+}
+
+/**
+ * Set the PMIC PWROK signal.
+ *
+ * @param asserted Assert (=1) or deassert (=0) the signal. This is the
+ * logical level of the pin, not the physical level.
+ */
+static void set_pmic_pwrok(int asserted)
+{
+#if defined(BOARD_pit) || defined(BOARD_kirby)
+ /* Signal is active-high */
+ gpio_set_level(GPIO_PMIC_PWRON, asserted);
+#else
+ /* Signal is active-low */
+ gpio_set_level(GPIO_PMIC_PWRON_L, asserted ? 0 : 1);
+#endif
+}
+
+
+/**
+ * Check for some event triggering the shutdown.
+ *
+ * It can be either a long power button press or a shutdown triggered from the
+ * AP and detected by reading XPSHOLD.
+ *
+ * @return 1 if a shutdown should happen, 0 if not
+ */
+static int check_for_power_off_event(void)
+{
+ timestamp_t now;
+ int pressed = 0;
+
+ /* Check for power button press */
+ if (gpio_get_level(GPIO_KB_PWR_ON_L) == 0) {
+ udelay(KB_PWR_ON_DEBOUNCE);
+ if (gpio_get_level(GPIO_KB_PWR_ON_L) == 0)
+ pressed = 1;
+ }
+
+#ifdef HAS_TASK_KEYSCAN
+ /* Dis/Enable keyboard scanning when the power button state changes */
+ if (!pressed || pressed != power_button_was_pressed)
+ keyboard_scan_enable(!pressed);
+#endif
+
+
+ now = get_time();
+ if (pressed) {
+ set_pmic_pwrok(1);
+
+ if (!power_button_was_pressed) {
+ power_off_deadline.val = now.val + DELAY_FORCE_SHUTDOWN;
+ CPRINTF("[%T power waiting for long press %u]\n",
+ power_off_deadline.le.lo);
+ } else if (timestamp_expired(power_off_deadline, &now)) {
+ power_off_deadline.val = 0;
+ CPRINTF("[%T power off after long press now=%u, %u]\n",
+ now.le.lo, power_off_deadline.le.lo);
+ return 2;
+ }
+ } else if (power_button_was_pressed) {
+ CPRINTF("[%T power off cancel]\n");
+ set_pmic_pwrok(0);
+ }
+
+ power_button_was_pressed = pressed;
+
+ /* XPSHOLD released by AP : shutdown immediately */
+ if (gpio_get_level(GPIO_SOC1V8_XPSHOLD) == 0)
+ return 3;
+
+ if (power_request == POWER_REQ_OFF) {
+ power_request = POWER_REQ_NONE;
+ return 4;
+ }
+
+ return 0;
+}
+
+/**
+ * Deferred handling for suspend events
+ *
+ * The suspend event needs to be able to call the suspend and resume hooks.
+ * This cannot be done from interrupt level, since the handlers from those
+ * hooks may need to use mutexes or other functionality not present at
+ * interrupt level. Use a deferred function instead.
+ *
+ * Deferred functions are called from the hook task and not the chipset task,
+ * so that's a slight deviation from the spec in hooks.h, but a minor one.
+ */
+static void gaia_suspend_deferred(void)
+{
+ int new_ap_suspended;
+
+ if (!ap_on) /* power on/off : not a real suspend / resume */
+ return;
+
+ /*
+ * Note: For Snow, suspend state can only be reliably
+ * determined when the AP is on (crosbug.com/p/13200).
+ */
+ new_ap_suspended = !gpio_get_level(GPIO_SUSPEND_L);
+
+ /* We never want to call two suspend or two resumes in a row */
+ if (ap_suspended == new_ap_suspended)
+ return;
+
+ ap_suspended = new_ap_suspended;
+
+ if (ap_suspended) {
+ if (lid_is_open())
+ powerled_set_state(POWERLED_STATE_SUSPEND);
+ else
+ powerled_set_state(POWERLED_STATE_OFF);
+ /* Call hooks here since we don't know it prior to AP suspend */
+ hook_notify(HOOK_CHIPSET_SUSPEND);
+ } else {
+ powerled_set_state(POWERLED_STATE_ON);
+ hook_notify(HOOK_CHIPSET_RESUME);
+ }
+
+}
+DECLARE_DEFERRED(gaia_suspend_deferred);
+
+void power_interrupt(enum gpio_signal signal)
+{
+ if (signal == GPIO_SUSPEND_L) {
+ /* Handle suspend events in the hook task */
+ hook_call_deferred(gaia_suspend_deferred, 0);
+ } else {
+ /* All other events are handled in the chipset task */
+ task_wake(TASK_ID_CHIPSET);
+ }
+}
+
+static void gaia_lid_event(void)
+{
+ /* Power task only cares about lid-open events */
+ if (!lid_is_open())
+ return;
+
+ lid_opened = 1;
+ task_wake(TASK_ID_CHIPSET);
+}
+DECLARE_HOOK(HOOK_LID_CHANGE, gaia_lid_event, HOOK_PRIO_DEFAULT);
+
+static int gaia_power_init(void)
+{
+ /* Enable interrupts for our GPIOs */
+ gpio_enable_interrupt(GPIO_KB_PWR_ON_L);
+ gpio_enable_interrupt(GPIO_SOC1V8_XPSHOLD);
+ gpio_enable_interrupt(GPIO_SUSPEND_L);
+#ifndef BOARD_kirby
+ gpio_enable_interrupt(GPIO_PP1800_LDO2);
+#endif
+
+ /* Leave power off only if requested by reset flags */
+ if (!(system_get_reset_flags() & RESET_FLAG_AP_OFF)) {
+ CPRINTF("[%T auto_power_on is set due to reset_flag 0x%x]\n",
+ system_get_reset_flags());
+ auto_power_on = 1;
+ }
+
+#ifdef BOARD_pit
+ /*
+ * Force the AP into reset unless we're doing a sysjump. Otherwise a
+ * suspended AP may still be in a strange state from the last reboot,
+ * and will hold XPSHOLD for a long time if it's in a low power state.
+ * See crosbug.com/p/22233.
+ */
+ if (!(system_get_reset_flags() & RESET_FLAG_SYSJUMP)) {
+ CPRINTF("[%T not sysjump; forcing AP reset]\n");
+ gpio_set_level(GPIO_AP_RESET_L, 0);
+ udelay(1000);
+ gpio_set_level(GPIO_AP_RESET_L, 1);
+ }
+#endif
+
+ return EC_SUCCESS;
+}
+
+
+/*****************************************************************************/
+/* Chipset interface */
+
+int chipset_in_state(int state_mask)
+{
+ /* If AP is off, match any off state for now */
+ if ((state_mask & CHIPSET_STATE_ANY_OFF) && !ap_on)
+ return 1;
+
+ /* If AP is on, match on state */
+ if ((state_mask & CHIPSET_STATE_ON) && ap_on && !ap_suspended)
+ return 1;
+
+ /* if AP is suspended, match on state */
+ if ((state_mask & CHIPSET_STATE_SUSPEND) && ap_on && ap_suspended)
+ return 1;
+
+ /* In any other case, we don't have a match */
+ return 0;
+}
+
+void chipset_exit_hard_off(void)
+{
+ /* TODO: implement, if/when we take the AP down to a hard-off state */
+}
+
+void chipset_reset(int is_cold)
+{
+ /* TODO: implement cold reset. For now, all resets are warm resets. */
+ CPRINTF("[%T EC triggered warm reboot]\n");
+
+ /*
+ * This is a hack to do an AP warm reboot while still preserving RAM
+ * contents. This is useful for looking at kernel log message contents
+ * from previous boot in cases where the AP/OS is hard hung.
+ */
+#ifdef CONFIG_CHIPSET_HAS_PP5000
+ gpio_set_level(GPIO_EN_PP5000, 0);
+#endif
+ gpio_set_level(GPIO_EN_PP3300, 0);
+
+ power_request = POWER_REQ_ON;
+ task_wake(TASK_ID_CHIPSET);
+}
+
+void chipset_force_shutdown(void)
+{
+#ifdef BOARD_kirby
+ gpio_set_flags(GPIO_SOC1V8_XPSHOLD, GPIO_ODR_LOW);
+ udelay(DELAY_XPSHOLD_PULL);
+ gpio_set_flags(GPIO_SOC1V8_XPSHOLD, GPIO_INT_RISING | GPIO_INPUT);
+#endif
+
+ /* Turn off all rails */
+ gpio_set_level(GPIO_EN_PP3300, 0);
+#ifdef CONFIG_CHIPSET_HAS_PP1350
+ /*
+ * Turn off PP1350 unless we're immediately waking back up. This
+ * works with the hack in chipset_reset() to preserve the contents of
+ * RAM across a reset.
+ */
+ if (power_request != POWER_REQ_ON)
+ gpio_set_level(GPIO_EN_PP1350, 0);
+#endif
+ set_pmic_pwrok(0);
+#ifdef CONFIG_CHIPSET_HAS_PP5000
+ gpio_set_level(GPIO_EN_PP5000, 0);
+#endif
+
+#ifdef BOARD_pit
+ /*
+ * Force the AP into reset. Otherwise it will hold XPSHOLD for a long
+ * time if it's in a low power state. See crosbug.com/p/22233.
+ */
+ gpio_set_level(GPIO_AP_RESET_L, 0);
+ udelay(1000);
+ gpio_set_level(GPIO_AP_RESET_L, 1);
+#endif
+}
+
+/*****************************************************************************/
+
+/**
+ * Check if there has been a power-on event
+ *
+ * This checks all power-on event signals and returns non-zero if any have been
+ * triggered (with debounce taken into account).
+ *
+ * @return non-zero if there has been a power-on event, 0 if not.
+ */
+static int check_for_power_on_event(void)
+{
+ /* the system is already ON */
+ /* TODO: this isn't the right check for pit */
+ if (gpio_get_level(GPIO_EN_PP3300)) {
+ CPRINTF("[%T system is on, thus clear auto_power_on]\n");
+ auto_power_on = 0; /* no need to arrange another power on */
+ return 1;
+ }
+
+ /* power on requested at EC startup for recovery */
+ if (auto_power_on) {
+ auto_power_on = 0;
+ return 2;
+ }
+
+ /* Check lid open */
+ if (lid_opened) {
+ lid_opened = 0;
+ return 3;
+ }
+
+ /* check for power button press */
+ if (gpio_get_level(GPIO_KB_PWR_ON_L) == 0) {
+ udelay(KB_PWR_ON_DEBOUNCE);
+ if (gpio_get_level(GPIO_KB_PWR_ON_L) == 0)
+ return 4;
+ }
+
+ if (power_request == POWER_REQ_ON) {
+ power_request = POWER_REQ_NONE;
+ return 5;
+ }
+
+ return 0;
+}
+
+/**
+ * Power on the AP
+ *
+ * @return 0 if ok, -1 on error (PP1800_LDO2 failed to come on)
+ */
+static int power_on(void)
+{
+#ifdef CONFIG_CHIPSET_HAS_PP5000
+ /* Enable 5v power rail */
+ gpio_set_level(GPIO_EN_PP5000, 1);
+ /* Wait for it to stabilize */
+ usleep(DELAY_5V_SETUP);
+#endif
+
+#if defined(BOARD_pit) || defined(BOARD_puppy)
+ /*
+ * 3.3V rail must come up right after 5V, because it sources power to
+ * various buck supplies.
+ */
+ gpio_set_level(GPIO_EN_PP3300, 1);
+ usleep(DELAY_3V_SETUP);
+#endif
+ if (gpio_get_level(GPIO_SOC1V8_XPSHOLD) == 0) {
+ /* Initialize non-AP components */
+ hook_notify(HOOK_CHIPSET_PRE_INIT);
+
+ /*
+ * Initiate PMIC power-on sequence only if cold booting AP to
+ * avoid accidental reset (crosbug.com/p/12650).
+ */
+ set_pmic_pwrok(1);
+ }
+
+#ifdef BOARD_kirby
+ /*
+ * There is no input signal for PMIC ready for 3.3V power. We can only
+ * for a pre-defined amount of time.
+ */
+ udelay(DELAY_PRE_3V_ENABLE);
+#else
+ /* wait for all PMIC regulators to be ready */
+ wait_in_signal(GPIO_PP1800_LDO2, 1, PMIC_TIMEOUT);
+
+ /*
+ * If PP1800_LDO2 did not come up (e.g. PMIC_TIMEOUT was reached),
+ * turn off 5V rail (and 3.3V, if turned on above) and start over.
+ */
+ if (gpio_get_level(GPIO_PP1800_LDO2) == 0) {
+ gpio_set_level(GPIO_EN_PP5000, 0);
+ gpio_set_level(GPIO_EN_PP3300, 0);
+ usleep(DELAY_5V_SETUP);
+ CPRINTF("[%T power error: PMIC failed to enable]\n");
+ return -1;
+ }
+
+ /* Enable DDR 1.35v power rail */
+ gpio_set_level(GPIO_EN_PP1350, 1);
+ /* Wait to avoid large inrush current */
+ usleep(DELAY_RAIL_STAGGERING);
+#endif
+
+ /* Enable 3.3v power rail, if it's not already on */
+ gpio_set_level(GPIO_EN_PP3300, 1);
+
+ ap_on = 1;
+ disable_sleep(SLEEP_MASK_AP_RUN);
+ powerled_set_state(POWERLED_STATE_ON);
+
+ /* Call hooks now that AP is running */
+ hook_notify(HOOK_CHIPSET_STARTUP);
+
+ CPRINTF("[%T AP running ...]\n");
+ return 0;
+}
+
+/**
+ * Wait for the power button to be released
+ *
+ * @return 0 if ok, -1 if power button failed to release
+ */
+static int wait_for_power_button_release(unsigned int timeout_us)
+{
+ /* wait for Power button release */
+ wait_in_signal(GPIO_KB_PWR_ON_L, 1, timeout_us);
+
+ udelay(KB_PWR_ON_DEBOUNCE);
+ if (gpio_get_level(GPIO_KB_PWR_ON_L) == 0) {
+ CPRINTF("[%T power button not released in time]\n");
+ return -1;
+ }
+ CPRINTF("[%T power button released]\n");
+ return 0;
+}
+
+/**
+ * Wait for the XPSHOLD signal from the AP to be asserted within timeout_us
+ * and if asserted clear the PMIC_PWRON signal
+ *
+ * @return 0 if ok, -1 if power button failed to release
+ */
+static int react_to_xpshold(unsigned int timeout_us)
+{
+ /* wait for Power button release */
+ wait_in_signal(GPIO_SOC1V8_XPSHOLD, 1, timeout_us);
+
+ if (gpio_get_level(GPIO_SOC1V8_XPSHOLD) == 0) {
+ CPRINTF("[%T XPSHOLD not seen in time]\n");
+ return -1;
+ }
+ CPRINTF("[%T XPSHOLD seen]\n");
+ set_pmic_pwrok(0);
+ return 0;
+}
+
+/**
+ * Power off the AP
+ */
+static void power_off(void)
+{
+ /* Call hooks before we drop power rails */
+ hook_notify(HOOK_CHIPSET_SHUTDOWN);
+ /* switch off all rails */
+ chipset_force_shutdown();
+ ap_on = 0;
+ ap_suspended = 0;
+ lid_opened = 0;
+ enable_sleep(SLEEP_MASK_AP_RUN);
+ powerled_set_state(POWERLED_STATE_OFF);
+#ifdef CONFIG_PMU_TPS65090
+ pmu_shutdown();
+#endif
+ CPRINTF("[%T power shutdown complete]\n");
+}
+
+
+/*
+ * Calculates the delay in microseconds to the next time we have to check
+ * for a power event,
+ *
+ *@return delay to next check, or -1 if no future check is needed
+ */
+static int next_pwr_event(void)
+{
+ if (!power_off_deadline.val)
+ return -1;
+
+ return power_off_deadline.val - get_time().val;
+}
+
+
+/*****************************************************************************/
+
+static int wait_for_power_on(void)
+{
+ int value;
+ while (1) {
+ value = check_for_power_on_event();
+ if (!value) {
+ task_wait_event(-1);
+ continue;
+ }
+
+#ifdef HAS_TASK_CHARGER
+ /*
+ * If the system is already on (value == 1), the kernel
+ * would handle low power condition and we should not
+ * shutdown the system from EC.
+ */
+ if (value != 1 && charge_keep_power_off()) {
+ CPRINTF("[%T power on ignored due to low battery]\n");
+ continue;
+ }
+#endif
+
+ CPRINTF("[%T power on %d]\n", value);
+ return value;
+ }
+}
+
+void chipset_task(void)
+{
+ int value;
+
+ gaia_power_init();
+ ap_on = 0;
+
+ while (1) {
+ /* Wait until we need to power on, then power on */
+ wait_for_power_on();
+
+ if (!power_on()) {
+ int continue_power = 0;
+
+ if (!react_to_xpshold(DELAY_RELEASE_PWRON)) {
+ /* AP looks good */
+ if (!wait_for_power_button_release(
+ DELAY_SHUTDOWN_ON_POWER_HOLD))
+ continue_power = 1;
+ } else {
+ /* AP is possibly in bad shape */
+ /* allow USB boot in 16 secs */
+ if (!wait_for_power_button_release(
+ DELAY_SHUTDOWN_ON_USB_BOOT))
+ continue_power = 1;
+ }
+ if (continue_power) {
+ power_button_was_pressed = 0;
+ while (!(value = check_for_power_off_event()))
+ task_wait_event(next_pwr_event());
+ CPRINTF("[%T power ending loop %d]\n", value);
+ }
+ }
+ power_off();
+ wait_for_power_button_release(-1);
+ }
+}
+
+/*****************************************************************************/
+/* Console debug command */
+
+static int command_force_power(int argc, char **argv)
+{
+ /* simulate power button pressed */
+ force_signal = GPIO_KB_PWR_ON_L;
+ force_value = 1;
+ /* Wake up the task */
+ task_wake(TASK_ID_CHIPSET);
+ /* Wait 100 ms */
+ msleep(100);
+ /* Release power button */
+ force_signal = -1;
+ force_value = 0;
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(forcepower, command_force_power,
+ NULL,
+ "Force power on",
+ NULL);
+
+static const char *power_req_name[POWER_REQ_COUNT] = {
+ "none",
+ "off",
+ "on",
+};
+
+/* Power states that we can report */
+enum power_state_t {
+ PSTATE_UNKNOWN,
+ PSTATE_OFF,
+ PSTATE_SUSPEND,
+ PSTATE_ON,
+
+ PSTATE_COUNT,
+};
+
+static const char * const state_name[] = {
+ "unknown",
+ "off",
+ "suspend",
+ "on",
+};
+
+static int command_power(int argc, char **argv)
+{
+ int v;
+
+ if (argc < 2) {
+ enum power_state_t state;
+
+ state = PSTATE_UNKNOWN;
+ if (chipset_in_state(CHIPSET_STATE_ANY_OFF))
+ state = PSTATE_OFF;
+ if (chipset_in_state(CHIPSET_STATE_SUSPEND))
+ state = PSTATE_SUSPEND;
+ if (chipset_in_state(CHIPSET_STATE_ON))
+ state = PSTATE_ON;
+ ccprintf("%s\n", state_name[state]);
+
+ return EC_SUCCESS;
+ }
+
+ if (!parse_bool(argv[1], &v))
+ return EC_ERROR_PARAM1;
+
+ power_request = v ? POWER_REQ_ON : POWER_REQ_OFF;
+ ccprintf("Requesting power %s\n", power_req_name[power_request]);
+ task_wake(TASK_ID_CHIPSET);
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(power, command_power,
+ "on/off",
+ "Turn AP power on/off",
+ NULL);
diff --git a/power/haswell.c b/power/haswell.c
new file mode 100644
index 0000000000..95823ae418
--- /dev/null
+++ b/power/haswell.c
@@ -0,0 +1,406 @@
+/* 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.
+ */
+
+/* X86 chipset power control module for Chrome EC */
+
+#include "chipset.h"
+#include "chipset_x86_common.h"
+#include "common.h"
+#include "console.h"
+#include "ec_commands.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "host_command.h"
+#include "lid_switch.h"
+#include "system.h"
+#include "timer.h"
+#include "util.h"
+#include "wireless.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_CHIPSET, outstr)
+#define CPRINTF(format, args...) cprintf(CC_CHIPSET, format, ## args)
+
+/* Input state flags */
+#define IN_PGOOD_PP5000 X86_SIGNAL_MASK(X86_PGOOD_PP5000)
+#define IN_PGOOD_PP1350 X86_SIGNAL_MASK(X86_PGOOD_PP1350)
+#define IN_PGOOD_PP1050 X86_SIGNAL_MASK(X86_PGOOD_PP1050)
+#define IN_PGOOD_VCORE X86_SIGNAL_MASK(X86_PGOOD_VCORE)
+#define IN_PCH_SLP_S0n_DEASSERTED X86_SIGNAL_MASK(X86_PCH_SLP_S0n_DEASSERTED)
+#define IN_PCH_SLP_S3n_DEASSERTED X86_SIGNAL_MASK(X86_PCH_SLP_S3n_DEASSERTED)
+#define IN_PCH_SLP_S5n_DEASSERTED X86_SIGNAL_MASK(X86_PCH_SLP_S5n_DEASSERTED)
+#define IN_PCH_SLP_SUSn_DEASSERTED X86_SIGNAL_MASK(X86_PCH_SLP_SUSn_DEASSERTED)
+
+/* All always-on supplies */
+#define IN_PGOOD_ALWAYS_ON (IN_PGOOD_PP5000)
+/* All non-core power rails */
+#define IN_PGOOD_ALL_NONCORE (IN_PGOOD_PP1350 | IN_PGOOD_PP1050)
+/* All core power rails */
+#define IN_PGOOD_ALL_CORE (IN_PGOOD_VCORE)
+/* Rails required for S3 */
+#define IN_PGOOD_S3 (IN_PGOOD_ALWAYS_ON | IN_PGOOD_PP1350)
+/* Rails required for S0 */
+#define IN_PGOOD_S0 (IN_PGOOD_ALWAYS_ON | IN_PGOOD_ALL_NONCORE)
+
+/* All PM_SLP signals from PCH deasserted */
+#define IN_ALL_PM_SLP_DEASSERTED (IN_PCH_SLP_S3n_DEASSERTED | \
+ IN_PCH_SLP_S5n_DEASSERTED)
+/* All inputs in the right state for S0 */
+#define IN_ALL_S0 (IN_PGOOD_ALWAYS_ON | IN_PGOOD_ALL_NONCORE | \
+ IN_PGOOD_ALL_CORE | IN_ALL_PM_SLP_DEASSERTED)
+
+static int throttle_cpu; /* Throttle CPU? */
+static int pause_in_s5; /* Pause in S5 when shutting down? */
+
+void chipset_force_shutdown(void)
+{
+ CPRINTF("[%T %s()]\n", __func__);
+
+ /*
+ * Force x86 off. This condition will reset once the state machine
+ * transitions to G3.
+ */
+ gpio_set_level(GPIO_PCH_DPWROK, 0);
+ gpio_set_level(GPIO_PCH_RSMRST_L, 0);
+}
+
+void chipset_reset(int cold_reset)
+{
+ CPRINTF("[%T %s(%d)]\n", __func__, cold_reset);
+ if (cold_reset) {
+ /*
+ * Drop and restore PWROK. This causes the PCH to reboot,
+ * regardless of its after-G3 setting. This type of reboot
+ * causes the PCH to assert PLTRST#, SLP_S3#, and SLP_S5#, so
+ * we actually drop power to the rest of the system (hence, a
+ * "cold" reboot).
+ */
+
+ /* Ignore if PWROK is already low */
+ if (gpio_get_level(GPIO_PCH_PWROK) == 0)
+ return;
+
+ /* PWROK must deassert for at least 3 RTC clocks = 91 us */
+ gpio_set_level(GPIO_PCH_PWROK, 0);
+ udelay(100);
+ gpio_set_level(GPIO_PCH_PWROK, 1);
+
+ } else {
+ /*
+ * Send a RCIN# pulse to the PCH. This just causes it to
+ * assert INIT# to the CPU without dropping power or asserting
+ * PLTRST# to reset the rest of the system. Pulse must be at
+ * least 16 PCI clocks long = 500 ns.
+ */
+
+ /*
+ * The gpio pin used by the EC (PL6) does not behave in the
+ * correct manner when configured as open drain. In order to
+ * mimic open drain, the pin is initially configured as an
+ * input. When it is needed to drive low, the flags are
+ * updated which changes the pin to an output and drives the
+ * pin low. Note that this logic will work fine even on boards
+ * where RCIN# has been moved to a different pin, so there's no
+ * need to #ifdef this behavior. See crosbug.com/p/20173.
+ */
+ gpio_set_flags(GPIO_PCH_RCIN_L, GPIO_OUT_LOW);
+ udelay(10);
+ gpio_set_flags(GPIO_PCH_RCIN_L, GPIO_INPUT);
+ }
+}
+
+void chipset_throttle_cpu(int throttle)
+{
+ if (chipset_in_state(CHIPSET_STATE_ON))
+ gpio_set_level(GPIO_CPU_PROCHOT, throttle);
+}
+
+enum x86_state x86_chipset_init(void)
+{
+ /* Enable interrupts for our GPIOs */
+ gpio_enable_interrupt(GPIO_PCH_EDP_VDD_EN);
+
+ /*
+ * If we're switching between images without rebooting, see if the x86
+ * is already powered on; if so, leave it there instead of cycling
+ * through G3.
+ */
+ if (system_jumped_to_this_image()) {
+ if ((x86_get_signals() & IN_ALL_S0) == IN_ALL_S0) {
+ CPRINTF("[%T x86 already in S0]\n");
+ return X86_S0;
+ } else {
+ /* Force all signals to their G3 states */
+ CPRINTF("[%T x86 forcing G3]\n");
+ gpio_set_level(GPIO_PCH_PWROK, 0);
+ gpio_set_level(GPIO_VCORE_EN, 0);
+ gpio_set_level(GPIO_SUSP_VR_EN, 0);
+ gpio_set_level(GPIO_PP1350_EN, 0);
+ gpio_set_level(GPIO_EC_EDP_VDD_EN, 0);
+ gpio_set_level(GPIO_PP3300_DX_EN, 0);
+ gpio_set_level(GPIO_PP5000_EN, 0);
+ gpio_set_level(GPIO_PCH_RSMRST_L, 0);
+ gpio_set_level(GPIO_PCH_DPWROK, 0);
+ wireless_enable(0);
+ }
+ }
+
+ return X86_G3;
+}
+
+enum x86_state x86_handle_state(enum x86_state state)
+{
+ switch (state) {
+ case X86_G3:
+ break;
+
+ case X86_S5:
+ if (gpio_get_level(GPIO_PCH_SLP_S5_L) == 1)
+ return X86_S5S3; /* Power up to next state */
+ break;
+
+ case X86_S3:
+ /*
+ * If lid is closed; hold touchscreen in reset to cut power
+ * usage. If lid is open, take touchscreen out of reset so it
+ * can wake the processor. Chipset task is awakened on lid
+ * switch transitions.
+ */
+ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, lid_is_open());
+
+ /* Check for state transitions */
+ if (!x86_has_signals(IN_PGOOD_S3)) {
+ /* Required rail went away */
+ chipset_force_shutdown();
+ return X86_S3S5;
+ } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 1) {
+ /* Power up to next state */
+ return X86_S3S0;
+ } else if (gpio_get_level(GPIO_PCH_SLP_S5_L) == 0) {
+ /* Power down to next state */
+ return X86_S3S5;
+ }
+ break;
+
+ case X86_S0:
+ if (!x86_has_signals(IN_PGOOD_S0)) {
+ /* Required rail went away */
+ chipset_force_shutdown();
+ return X86_S0S3;
+ } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 0) {
+ /* Power down to next state */
+ return X86_S0S3;
+ }
+ break;
+
+ case X86_G3S5:
+ /*
+ * Wait 10ms after +3VALW good, since that powers VccDSW and
+ * VccSUS.
+ */
+ msleep(10);
+
+ /* Assert DPWROK */
+ gpio_set_level(GPIO_PCH_DPWROK, 1);
+ if (x86_wait_signals(IN_PCH_SLP_SUSn_DEASSERTED)) {
+ chipset_force_shutdown();
+ return X86_G3;
+ }
+
+ gpio_set_level(GPIO_SUSP_VR_EN, 1);
+ if (x86_wait_signals(IN_PGOOD_PP1050)) {
+ chipset_force_shutdown();
+ return X86_G3;
+ }
+
+ /* Deassert RSMRST# */
+ gpio_set_level(GPIO_PCH_RSMRST_L, 1);
+
+ /* Wait 5ms for SUSCLK to stabilize */
+ msleep(5);
+ return X86_S5;
+
+ case X86_S5S3:
+ /* Enable PP5000 (5V) rail. */
+ gpio_set_level(GPIO_PP5000_EN, 1);
+ if (x86_wait_signals(IN_PGOOD_PP5000)) {
+ chipset_force_shutdown();
+ return X86_G3;
+ }
+
+ /* Wait for the always-on rails to be good */
+ if (x86_wait_signals(IN_PGOOD_ALWAYS_ON)) {
+ chipset_force_shutdown();
+ return X86_S5G3;
+ }
+
+ /* Turn on power to RAM */
+ gpio_set_level(GPIO_PP1350_EN, 1);
+ if (x86_wait_signals(IN_PGOOD_S3)) {
+ chipset_force_shutdown();
+ return X86_S5G3;
+ }
+
+ /*
+ * Enable touchpad power so it can wake the system from
+ * suspend.
+ */
+ gpio_set_level(GPIO_ENABLE_TOUCHPAD, 1);
+
+ /* Call hooks now that rails are up */
+ hook_notify(HOOK_CHIPSET_STARTUP);
+ return X86_S3;
+
+ case X86_S3S0:
+ /* Turn on power rails */
+ gpio_set_level(GPIO_PP3300_DX_EN, 1);
+
+ /* Enable wireless */
+ wireless_enable(EC_WIRELESS_SWITCH_ALL);
+
+ /*
+ * Make sure touchscreen is out if reset (even if the lid is
+ * still closed); it may have been turned off if the lid was
+ * closed in S3.
+ */
+ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, 1);
+
+ /* Wait for non-core power rails good */
+ if (x86_wait_signals(IN_PGOOD_S0)) {
+ chipset_force_shutdown();
+ wireless_enable(0);
+ gpio_set_level(GPIO_EC_EDP_VDD_EN, 0);
+ gpio_set_level(GPIO_PP3300_DX_EN, 0);
+ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, 0);
+ return X86_S3;
+ }
+
+ /*
+ * Enable +CPU_CORE. The CPU itself will request the supplies
+ * when it's ready.
+ */
+ gpio_set_level(GPIO_VCORE_EN, 1);
+
+ /* Call hooks now that rails are up */
+ hook_notify(HOOK_CHIPSET_RESUME);
+
+ /*
+ * Disable idle task deep sleep. This means that the low
+ * power idle task will not go into deep sleep while in S0.
+ */
+ disable_sleep(SLEEP_MASK_AP_RUN);
+
+ /* Wait 99ms after all voltages good */
+ msleep(99);
+
+ /*
+ * Throttle CPU if necessary. This should only be asserted
+ * when +VCCP is powered (it is by now).
+ */
+ gpio_set_level(GPIO_CPU_PROCHOT, throttle_cpu);
+
+ /* Set PCH_PWROK */
+ gpio_set_level(GPIO_PCH_PWROK, 1);
+ gpio_set_level(GPIO_SYS_PWROK, 1);
+ return X86_S0;
+
+ case X86_S0S3:
+ /* Call hooks before we remove power rails */
+ hook_notify(HOOK_CHIPSET_SUSPEND);
+
+ /* Clear PCH_PWROK */
+ gpio_set_level(GPIO_SYS_PWROK, 0);
+ gpio_set_level(GPIO_PCH_PWROK, 0);
+
+ /* Wait 40ns */
+ udelay(1);
+
+ /* Disable +CPU_CORE */
+ gpio_set_level(GPIO_VCORE_EN, 0);
+
+ /* Disable wireless */
+ wireless_enable(0);
+
+ /*
+ * Enable idle task deep sleep. Allow the low power idle task
+ * to go into deep sleep in S3 or lower.
+ */
+ enable_sleep(SLEEP_MASK_AP_RUN);
+
+ /*
+ * Deassert prochot since CPU is off and we're about to drop
+ * +VCCP.
+ */
+ gpio_set_level(GPIO_CPU_PROCHOT, 0);
+
+ /* Turn off power rails */
+ gpio_set_level(GPIO_EC_EDP_VDD_EN, 0);
+ gpio_set_level(GPIO_PP3300_DX_EN, 0);
+ return X86_S3;
+
+ case X86_S3S5:
+ /* Call hooks before we remove power rails */
+ hook_notify(HOOK_CHIPSET_SHUTDOWN);
+
+ /* Disable touchpad power */
+ gpio_set_level(GPIO_ENABLE_TOUCHPAD, 0);
+
+ /* Turn off power to RAM */
+ gpio_set_level(GPIO_PP1350_EN, 0);
+
+ /* Disable PP5000 (5V) rail. */
+ gpio_set_level(GPIO_PP5000_EN, 0);
+
+ /* Start shutting down */
+ return pause_in_s5 ? X86_S5 : X86_S5G3;
+
+ case X86_S5G3:
+ /* Deassert DPWROK, assert RSMRST# */
+ gpio_set_level(GPIO_PCH_DPWROK, 0);
+ gpio_set_level(GPIO_PCH_RSMRST_L, 0);
+ gpio_set_level(GPIO_SUSP_VR_EN, 0);
+ return X86_G3;
+ }
+
+ return state;
+}
+
+void power_interrupt(enum gpio_signal signal)
+{
+ /* Pass through eDP VDD enable from PCH */
+ gpio_set_level(GPIO_EC_EDP_VDD_EN, gpio_get_level(GPIO_PCH_EDP_VDD_EN));
+}
+
+static int host_command_gsv(struct host_cmd_handler_args *args)
+{
+ const struct ec_params_get_set_value *p = args->params;
+ struct ec_response_get_set_value *r = args->response;
+
+ if (p->flags & EC_GSV_SET)
+ pause_in_s5 = p->value;
+
+ r->value = pause_in_s5;
+
+ args->response_size = sizeof(*r);
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_GSV_PAUSE_IN_S5,
+ host_command_gsv,
+ EC_VER_MASK(0));
+
+static int console_command_gsv(int argc, char **argv)
+{
+ if (argc > 1 && !parse_bool(argv[1], &pause_in_s5))
+ return EC_ERROR_INVAL;
+
+ ccprintf("pause_in_s5 = %s\n", pause_in_s5 ? "on" : "off");
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(pause_in_s5, console_command_gsv,
+ "[on|off]",
+ "Should the AP pause in S5 during shutdown?",
+ NULL);
+
diff --git a/power/ivybridge.c b/power/ivybridge.c
new file mode 100644
index 0000000000..7a8dfd12aa
--- /dev/null
+++ b/power/ivybridge.c
@@ -0,0 +1,344 @@
+/* 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.
+ */
+
+/* X86 chipset power control module for Chrome EC */
+
+#include "chipset.h"
+#include "chipset_x86_common.h"
+#include "common.h"
+#include "console.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "lid_switch.h"
+#include "system.h"
+#include "timer.h"
+#include "util.h"
+#include "wireless.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_CHIPSET, outstr)
+#define CPRINTF(format, args...) cprintf(CC_CHIPSET, format, ## args)
+
+/* Input state flags */
+#define IN_PGOOD_5VALW X86_SIGNAL_MASK(X86_PGOOD_5VALW)
+#define IN_PGOOD_1_5V_DDR X86_SIGNAL_MASK(X86_PGOOD_1_5V_DDR)
+#define IN_PGOOD_1_5V_PCH X86_SIGNAL_MASK(X86_PGOOD_1_5V_PCH)
+#define IN_PGOOD_1_8VS X86_SIGNAL_MASK(X86_PGOOD_1_8VS)
+#define IN_PGOOD_VCCP X86_SIGNAL_MASK(X86_PGOOD_VCCP)
+#define IN_PGOOD_VCCSA X86_SIGNAL_MASK(X86_PGOOD_VCCSA)
+#define IN_PGOOD_CPU_CORE X86_SIGNAL_MASK(X86_PGOOD_CPU_CORE)
+#define IN_PGOOD_VGFX_CORE X86_SIGNAL_MASK(X86_PGOOD_VGFX_CORE)
+#define IN_PCH_SLP_S3n_DEASSERTED X86_SIGNAL_MASK(X86_PCH_SLP_S3n_DEASSERTED)
+#define IN_PCH_SLP_S4n_DEASSERTED X86_SIGNAL_MASK(X86_PCH_SLP_S4n_DEASSERTED)
+#define IN_PCH_SLP_S5n_DEASSERTED X86_SIGNAL_MASK(X86_PCH_SLP_S5n_DEASSERTED)
+#define IN_PCH_SLP_An_DEASSERTED X86_SIGNAL_MASK(X86_PCH_SLP_An_DEASSERTED)
+#define IN_PCH_SLP_SUSn_DEASSERTED X86_SIGNAL_MASK(X86_PCH_SLP_SUSn_DEASSERTED)
+#define IN_PCH_SLP_MEn_DEASSERTED X86_SIGNAL_MASK(X86_PCH_SLP_MEn_DEASSERTED)
+
+/* All always-on supplies */
+#define IN_PGOOD_ALWAYS_ON (IN_PGOOD_5VALW)
+/* All non-core power rails */
+#define IN_PGOOD_ALL_NONCORE (IN_PGOOD_1_5V_DDR | IN_PGOOD_1_5V_PCH | \
+ IN_PGOOD_1_8VS | IN_PGOOD_VCCP | IN_PGOOD_VCCSA)
+/* All core power rails */
+#define IN_PGOOD_ALL_CORE (IN_PGOOD_CPU_CORE | IN_PGOOD_VGFX_CORE)
+/* Rails required for S3 */
+#define IN_PGOOD_S3 (IN_PGOOD_ALWAYS_ON | IN_PGOOD_1_5V_DDR)
+/* Rails required for S0 */
+#define IN_PGOOD_S0 (IN_PGOOD_ALWAYS_ON | IN_PGOOD_ALL_NONCORE)
+
+/* All PM_SLP signals from PCH deasserted */
+#define IN_ALL_PM_SLP_DEASSERTED (IN_PCH_SLP_S3n_DEASSERTED | \
+ IN_PCH_SLP_S4n_DEASSERTED | \
+ IN_PCH_SLP_S5n_DEASSERTED | \
+ IN_PCH_SLP_An_DEASSERTED)
+/* All inputs in the right state for S0 */
+#define IN_ALL_S0 (IN_PGOOD_ALWAYS_ON | IN_PGOOD_ALL_NONCORE | \
+ IN_PGOOD_CPU_CORE | IN_ALL_PM_SLP_DEASSERTED)
+
+static int throttle_cpu; /* Throttle CPU? */
+
+void chipset_force_shutdown(void)
+{
+ CPRINTF("[%T chipset force shutdown]\n");
+
+ /*
+ * Force x86 off. This condition will reset once the state machine
+ * transitions to G3.
+ */
+ gpio_set_level(GPIO_PCH_DPWROK, 0);
+ gpio_set_level(GPIO_PCH_RSMRST_L, 0);
+}
+
+void chipset_reset(int cold_reset)
+{
+ if (cold_reset) {
+ /*
+ * Drop and restore PWROK. This causes the PCH to reboot,
+ * regardless of its after-G3 setting. This type of reboot
+ * causes the PCH to assert PLTRST#, SLP_S3#, and SLP_S5#, so
+ * we actually drop power to the rest of the system (hence, a
+ * "cold" reboot).
+ */
+
+ /* Ignore if PWROK is already low */
+ if (gpio_get_level(GPIO_PCH_PWROK) == 0)
+ return;
+
+ /* PWROK must deassert for at least 3 RTC clocks = 91 us */
+ gpio_set_level(GPIO_PCH_PWROK, 0);
+ udelay(100);
+ gpio_set_level(GPIO_PCH_PWROK, 1);
+
+ } else {
+ /*
+ * Send a RCIN# pulse to the PCH. This just causes it to
+ * assert INIT# to the CPU without dropping power or asserting
+ * PLTRST# to reset the rest of the system.
+ */
+
+ /* Pulse must be at least 16 PCI clocks long = 500 ns */
+ gpio_set_level(GPIO_PCH_RCIN_L, 0);
+ udelay(10);
+ gpio_set_level(GPIO_PCH_RCIN_L, 1);
+ }
+}
+
+void chipset_throttle_cpu(int throttle)
+{
+ throttle_cpu = throttle;
+
+ /* Immediately set throttling if CPU is on */
+ if (chipset_in_state(CHIPSET_STATE_ON))
+ gpio_set_level(GPIO_CPU_PROCHOT, throttle);
+}
+
+enum x86_state x86_chipset_init(void)
+{
+ /*
+ * If we're switching between images without rebooting, see if the x86
+ * is already powered on; if so, leave it there instead of cycling
+ * through G3.
+ */
+ if (system_jumped_to_this_image()) {
+ if ((x86_get_signals() & IN_ALL_S0) == IN_ALL_S0) {
+ CPRINTF("[%T x86 already in S0]\n");
+ return X86_S0;
+ } else {
+ /* Force all signals to their G3 states */
+ CPRINTF("[%T x86 forcing G3]\n");
+ gpio_set_level(GPIO_PCH_PWROK, 0);
+ gpio_set_level(GPIO_ENABLE_VCORE, 0);
+ gpio_set_level(GPIO_ENABLE_VS, 0);
+ gpio_set_level(GPIO_ENABLE_TOUCHPAD, 0);
+ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, 0);
+ gpio_set_level(GPIO_ENABLE_1_5V_DDR, 0);
+ gpio_set_level(GPIO_PCH_RSMRST_L, 0);
+ gpio_set_level(GPIO_PCH_DPWROK, 0);
+ }
+ }
+
+ return X86_G3;
+}
+
+enum x86_state x86_handle_state(enum x86_state state)
+{
+ switch (state) {
+ case X86_G3:
+ break;
+
+ case X86_S5:
+ if (gpio_get_level(GPIO_PCH_SLP_S5_L) == 1) {
+ /* Power up to next state */
+ return X86_S5S3;
+ }
+ break;
+
+ case X86_S3:
+ /*
+ * If lid is closed; hold touchscreen in reset to cut power
+ * usage. If lid is open, take touchscreen out of reset so it
+ * can wake the processor.
+ */
+ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, lid_is_open());
+
+ /* Check for state transitions */
+ if (!x86_has_signals(IN_PGOOD_S3)) {
+ /* Required rail went away */
+ chipset_force_shutdown();
+ return X86_S3S5;
+ } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 1) {
+ /* Power up to next state */
+ return X86_S3S0;
+ } else if (gpio_get_level(GPIO_PCH_SLP_S5_L) == 0) {
+ /* Power down to next state */
+ return X86_S3S5;
+ }
+ break;
+
+ case X86_S0:
+ if (!x86_has_signals(IN_PGOOD_S0)) {
+ /* Required rail went away */
+ chipset_force_shutdown();
+ return X86_S0S3;
+ } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 0) {
+ /* Power down to next state */
+ return X86_S0S3;
+ }
+ break;
+
+ case X86_G3S5:
+ /*
+ * Wait 10ms after +3VALW good, since that powers VccDSW and
+ * VccSUS.
+ */
+ msleep(10);
+
+ /* Assert DPWROK, deassert RSMRST# */
+ gpio_set_level(GPIO_PCH_DPWROK, 1);
+ gpio_set_level(GPIO_PCH_RSMRST_L, 1);
+
+ /* Wait 5ms for SUSCLK to stabilize */
+ msleep(5);
+ return X86_S5;
+
+ case X86_S5S3:
+ /* Wait for the always-on rails to be good */
+ if (x86_wait_signals(IN_PGOOD_ALWAYS_ON)) {
+ chipset_force_shutdown();
+ return X86_S5;
+ }
+
+ /*
+ * Take lightbar out of reset, now that +5VALW is available and
+ * we won't leak +3VALW through the reset line.
+ */
+ gpio_set_level(GPIO_LIGHTBAR_RESET_L, 1);
+
+ /* Turn on power to RAM */
+ gpio_set_level(GPIO_ENABLE_1_5V_DDR, 1);
+ if (x86_wait_signals(IN_PGOOD_S3)) {
+ chipset_force_shutdown();
+ return X86_S5;
+ }
+
+ /*
+ * Enable touchpad power so it can wake the system from
+ * suspend.
+ */
+ gpio_set_level(GPIO_ENABLE_TOUCHPAD, 1);
+
+ /* Call hooks now that rails are up */
+ hook_notify(HOOK_CHIPSET_STARTUP);
+ return X86_S3;
+
+ case X86_S3S0:
+ /* Turn on power rails */
+ gpio_set_level(GPIO_ENABLE_VS, 1);
+
+ /* Enable wireless */
+ wireless_enable(EC_WIRELESS_SWITCH_ALL);
+
+ /*
+ * Make sure touchscreen is out if reset (even if the lid is
+ * still closed); it may have been turned off if the lid was
+ * closed in S3.
+ */
+ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, 1);
+
+ /* Wait for non-core power rails good */
+ if (x86_wait_signals(IN_PGOOD_S0)) {
+ chipset_force_shutdown();
+ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, 0);
+ wireless_enable(0);
+ gpio_set_level(GPIO_ENABLE_VS, 0);
+ return X86_S3;
+ }
+
+ /*
+ * Enable +CPU_CORE and +VGFX_CORE regulator. The CPU itself
+ * will request the supplies when it's ready.
+ */
+ gpio_set_level(GPIO_ENABLE_VCORE, 1);
+
+ /* Call hooks now that rails are up */
+ hook_notify(HOOK_CHIPSET_RESUME);
+
+ /* Wait 99ms after all voltages good */
+ msleep(99);
+
+ /*
+ * Throttle CPU if necessary. This should only be asserted
+ * when +VCCP is powered (it is by now).
+ */
+ gpio_set_level(GPIO_CPU_PROCHOT, throttle_cpu);
+
+ /* Set PCH_PWROK */
+ gpio_set_level(GPIO_PCH_PWROK, 1);
+ return X86_S0;
+
+ case X86_S0S3:
+ /* Call hooks before we remove power rails */
+ hook_notify(HOOK_CHIPSET_SUSPEND);
+
+ /* Clear PCH_PWROK */
+ gpio_set_level(GPIO_PCH_PWROK, 0);
+
+ /* Wait 40ns */
+ udelay(1);
+
+ /* Disable +CPU_CORE and +VGFX_CORE */
+ gpio_set_level(GPIO_ENABLE_VCORE, 0);
+
+ /* Disable wireless */
+ wireless_enable(0);
+
+ /*
+ * Deassert prochot since CPU is off and we're about to drop
+ * +VCCP.
+ */
+ gpio_set_level(GPIO_CPU_PROCHOT, 0);
+
+ /* Turn off power rails */
+ gpio_set_level(GPIO_ENABLE_VS, 0);
+ return X86_S3;
+
+ case X86_S3S5:
+ /* Call hooks before we remove power rails */
+ hook_notify(HOOK_CHIPSET_SHUTDOWN);
+
+ /* Disable touchpad power */
+ gpio_set_level(GPIO_ENABLE_TOUCHPAD, 0);
+
+ /* Turn off power to RAM */
+ gpio_set_level(GPIO_ENABLE_1_5V_DDR, 0);
+
+ /*
+ * Put touchscreen and lightbar in reset, so we won't leak
+ * +3VALW through the reset line to chips powered by +5VALW.
+ *
+ * (Note that we're no longer powering down +5VALW due to
+ * crosbug.com/p/16600, but to minimize side effects of that
+ * change we'll still reset these components in S5.)
+ */
+ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, 0);
+ gpio_set_level(GPIO_LIGHTBAR_RESET_L, 0);
+ return X86_S5;
+
+ case X86_S5G3:
+ /* Deassert DPWROK, assert RSMRST# */
+ gpio_set_level(GPIO_PCH_DPWROK, 0);
+ gpio_set_level(GPIO_PCH_RSMRST_L, 0);
+ return X86_G3;
+ }
+
+ return state;
+}
+
+void power_interrupt(enum gpio_signal signal)
+{
+ /* Route SUSWARN# back to SUSACK# */
+ gpio_set_level(GPIO_PCH_SUSACK_L, gpio_get_level(GPIO_PCH_SUSWARN_L));
+}
diff --git a/power/tegra.c b/power/tegra.c
new file mode 100644
index 0000000000..3484a266ae
--- /dev/null
+++ b/power/tegra.c
@@ -0,0 +1,601 @@
+/* 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.
+ */
+
+/*
+ * TEGRA SoC power sequencing module for Chrome EC
+ *
+ * This implements the following features:
+ *
+ * - Cold reset powers on the AP
+ *
+ * When powered off:
+ * - Press pwron turns on the AP
+ * - Hold pwron turns on the AP, and then 9s later turns it off and leaves
+ * it off until pwron is released and pressed again
+ *
+ * When powered on:
+ * - The PMIC PWRON signal is released <= 1 second after the power button is
+ * released
+ * - Holding pwron for 9s powers off the AP
+ * - Pressing and releasing pwron within that 9s is ignored
+ * - If XPSHOLD is dropped by the AP, then we power the AP off
+ */
+
+#include "clock.h"
+#include "chipset.h" /* This module implements chipset functions too */
+#include "common.h"
+#include "console.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "lid_switch.h"
+#include "keyboard_scan.h"
+#include "power_led.h"
+#include "pmu_tpschrome.h"
+#include "system.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_CHIPSET, outstr)
+#define CPRINTF(format, args...) cprintf(CC_CHIPSET, format, ## args)
+
+/* Long power key press to force shutdown */
+#define DELAY_FORCE_SHUTDOWN (9 * SECOND)
+
+/*
+ * If the power key is pressed to turn on, then held for this long, we
+ * power off.
+ *
+ * Normal case: User releases power button and chipset_task() goes
+ * into the inner loop, waiting for next event to occur (power button
+ * press or XPSHOLD == 0).
+ */
+#define DELAY_SHUTDOWN_ON_POWER_HOLD (9 * SECOND)
+
+/* Maximum delay after power button press before we deassert GPIO_PMIC_PWRON */
+#define DELAY_RELEASE_PWRON SECOND /* 1s */
+
+/* debounce time to prevent accidental power-on after keyboard power off */
+#define KB_PWR_ON_DEBOUNCE 250 /* 250us */
+
+/*
+ * nyan's GPIO_SOC1V8_XPSHOLD will go low for ~20ms after initial high.
+ * XPSHOLD_DEBOUNCE is used to wait this long, then check the signal again.
+ */
+#define XPSHOLD_DEBOUNCE (30 * 1000) /* 30 ms */
+
+/* Application processor power state */
+static int ap_on;
+static int ap_suspended;
+
+/* simulated event state */
+static int force_signal = -1;
+static int force_value;
+
+/* 1 if the power button was pressed last time we checked */
+static char power_button_was_pressed;
+
+/* 1 if lid-open event has been detected */
+static char lid_opened;
+
+/* time where we will power off, if power button still held down */
+static timestamp_t power_off_deadline;
+
+/* force AP power on (used for recovery keypress) */
+static int auto_power_on;
+
+enum power_request_t {
+ POWER_REQ_NONE,
+ POWER_REQ_OFF,
+ POWER_REQ_ON,
+
+ POWER_REQ_COUNT,
+};
+
+static enum power_request_t power_request;
+
+/**
+ * Wait for GPIO "signal" to reach level "value".
+ * Returns EC_ERROR_TIMEOUT if timeout before reaching the desired state.
+ *
+ * @param signal Signal to watch
+ * @param value Value to watch for
+ * @param timeout Timeout in microseconds from now, or -1 to wait forever
+ * @return 0 if signal did change to required value, EC_ERROR_TIMEOUT if we
+ * timed out first.
+ */
+static int wait_in_signal(enum gpio_signal signal, int value, int timeout)
+{
+ timestamp_t deadline;
+ timestamp_t now = get_time();
+
+ deadline.val = now.val + timeout;
+
+ while (((force_signal != signal) || (force_value != value)) &&
+ gpio_get_level(signal) != value) {
+ now = get_time();
+ if (timeout < 0) {
+ task_wait_event(-1);
+ } else if (timestamp_expired(deadline, &now) ||
+ (task_wait_event(deadline.val - now.val) ==
+ TASK_EVENT_TIMER)) {
+ CPRINTF("[%T power timeout waiting for GPIO %d/%s]\n",
+ signal, gpio_get_name(signal));
+ return EC_ERROR_TIMEOUT;
+ }
+ }
+
+ return EC_SUCCESS;
+}
+
+/**
+ * Set the PMIC PWROK signal.
+ *
+ * @param asserted Assert (=1) or deassert (=0) the signal. This is the
+ * logical level of the pin, not the physical level.
+ */
+static void set_pmic_pwrok(int asserted)
+{
+ /* Signal is active-low */
+ gpio_set_level(GPIO_PMIC_PWRON_L, asserted ? 0 : 1);
+}
+
+/**
+ * Check for some event triggering the shutdown.
+ *
+ * It can be either a long power button press or a shutdown triggered from the
+ * AP and detected by reading XPSHOLD.
+ *
+ * @return non-zero if a shutdown should happen, 0 if not
+ */
+static int check_for_power_off_event(void)
+{
+ timestamp_t now;
+ int pressed = 0;
+
+ /*
+ * Check for power button press.
+ *
+ * If power_request is POWER_REQ_OFF, simulate the requst as power
+ * button is pressed. This will casue GPIO_PMIC_PWRON_L to be driven
+ * low (by set_pmic_pwrok(1)) until PMIC automatically turns off power.
+ * (PMIC turns off power if GPIO_PMIC_PWRON_L is low for >=9 seconds.)
+ */
+ if (gpio_get_level(GPIO_KB_PWR_ON_L) == 0) {
+ udelay(KB_PWR_ON_DEBOUNCE);
+ if (gpio_get_level(GPIO_KB_PWR_ON_L) == 0)
+ pressed = 1;
+ } else if (power_request == POWER_REQ_OFF) {
+ pressed = 1;
+ power_request = POWER_REQ_NONE;
+ }
+
+#ifdef HAS_TASK_KEYSCAN
+ /* Dis/Enable keyboard scanning when the power button state changes */
+ if (!pressed || pressed != power_button_was_pressed)
+ keyboard_scan_enable(!pressed);
+#endif
+
+ now = get_time();
+ if (pressed) {
+ set_pmic_pwrok(1);
+
+ if (!power_button_was_pressed) {
+ power_off_deadline.val = now.val + DELAY_FORCE_SHUTDOWN;
+ CPRINTF("[%T power waiting for long press %u]\n",
+ power_off_deadline.le.lo);
+ } else if (timestamp_expired(power_off_deadline, &now)) {
+ power_off_deadline.val = 0;
+ CPRINTF("[%T power off after long press now=%u, %u]\n",
+ now.le.lo, power_off_deadline.le.lo);
+ return 2;
+ }
+ } else if (power_button_was_pressed) {
+ CPRINTF("[%T power off cancel]\n");
+ set_pmic_pwrok(0);
+ }
+
+ power_button_was_pressed = pressed;
+
+ /* XPSHOLD released by AP : shutdown immediately */
+ if (gpio_get_level(GPIO_SOC1V8_XPSHOLD) == 0)
+ return 3;
+
+ return 0;
+}
+
+/**
+ * Deferred handling for suspend events
+ *
+ * The suspend event needs to be able to call the suspend and resume hooks.
+ * This cannot be done from interrupt level, since the handlers from those
+ * hooks may need to use mutexes or other functionality not present at
+ * interrupt level. Use a deferred function instead.
+ *
+ * Deferred functions are called from the hook task and not the chipset task,
+ * so that's a slight deviation from the spec in hooks.h, but a minor one.
+ */
+static void tegra_suspend_deferred(void)
+{
+ int new_ap_suspended;
+
+ if (!ap_on) /* power on/off : not a real suspend / resume */
+ return;
+
+ new_ap_suspended = !gpio_get_level(GPIO_SUSPEND_L);
+
+ /* We never want to call two suspend or two resumes in a row */
+ if (ap_suspended == new_ap_suspended)
+ return;
+
+ ap_suspended = new_ap_suspended;
+
+ if (ap_suspended) {
+ if (lid_is_open())
+ powerled_set_state(POWERLED_STATE_SUSPEND);
+ else
+ powerled_set_state(POWERLED_STATE_OFF);
+ /* Call hooks here since we don't know it prior to AP suspend */
+ hook_notify(HOOK_CHIPSET_SUSPEND);
+ } else {
+ powerled_set_state(POWERLED_STATE_ON);
+ hook_notify(HOOK_CHIPSET_RESUME);
+ }
+}
+DECLARE_DEFERRED(tegra_suspend_deferred);
+
+void power_interrupt(enum gpio_signal signal)
+{
+ if (signal == GPIO_SUSPEND_L) {
+ /* Handle suspend events in the hook task */
+ hook_call_deferred(tegra_suspend_deferred, 0);
+ } else {
+ /* All other events are handled in the chipset task */
+ task_wake(TASK_ID_CHIPSET);
+ }
+}
+
+static void tegra_lid_event(void)
+{
+ /* Power task only cares about lid-open events */
+ if (!lid_is_open())
+ return;
+
+ lid_opened = 1;
+ task_wake(TASK_ID_CHIPSET);
+}
+DECLARE_HOOK(HOOK_LID_CHANGE, tegra_lid_event, HOOK_PRIO_DEFAULT);
+
+static int tegra_power_init(void)
+{
+ /* Enable interrupts for our GPIOs */
+ gpio_enable_interrupt(GPIO_KB_PWR_ON_L);
+ gpio_enable_interrupt(GPIO_SOC1V8_XPSHOLD);
+ gpio_enable_interrupt(GPIO_SUSPEND_L);
+
+ /* Leave power off only if requested by reset flags */
+ if (!(system_get_reset_flags() & RESET_FLAG_AP_OFF)) {
+ CPRINTF("[%T auto_power_on is set due to reset_flag 0x%x]\n",
+ system_get_reset_flags());
+ auto_power_on = 1;
+ }
+
+ return EC_SUCCESS;
+}
+
+/*****************************************************************************/
+/* Chipset interface */
+
+int chipset_in_state(int state_mask)
+{
+ /* If AP is off, match any off state for now */
+ if ((state_mask & CHIPSET_STATE_ANY_OFF) && !ap_on)
+ return 1;
+
+ /* If AP is on, match on state */
+ if ((state_mask & CHIPSET_STATE_ON) && ap_on && !ap_suspended)
+ return 1;
+
+ /* if AP is suspended, match on state */
+ if ((state_mask & CHIPSET_STATE_SUSPEND) && ap_on && ap_suspended)
+ return 1;
+
+ /* In any other case, we don't have a match */
+ return 0;
+}
+
+void chipset_exit_hard_off(void)
+{
+ /* TODO: implement, if/when we take the AP down to a hard-off state */
+}
+
+void chipset_reset(int is_cold)
+{
+ /* TODO: implement cold reset. For now, all resets are warm resets. */
+ CPRINTF("[%T EC triggered warm reboot]\n");
+
+ /*
+ * This is a hack to do an AP warm reboot while still preserving RAM
+ * contents. This is useful for looking at kernel log message contents
+ * from previous boot in cases where the AP/OS is hard hung.
+ */
+ power_request = POWER_REQ_ON;
+ task_wake(TASK_ID_CHIPSET);
+}
+
+void chipset_force_shutdown(void)
+{
+ set_pmic_pwrok(0);
+}
+
+/*****************************************************************************/
+
+/**
+ * Check if there has been a power-on event
+ *
+ * This checks all power-on event signals and returns non-zero if any have been
+ * triggered (with debounce taken into account).
+ *
+ * @return non-zero if there has been a power-on event, 0 if not.
+ */
+static int check_for_power_on_event(void)
+{
+ /* check if system is already ON */
+ if (gpio_get_level(GPIO_SOC1V8_XPSHOLD)) {
+ CPRINTF("[%T system is on, thus clear auto_power_on]\n");
+ auto_power_on = 0; /* no need to arrange another power on */
+ return 1;
+ }
+
+ /* power on requested at EC startup for recovery */
+ if (auto_power_on) {
+ auto_power_on = 0;
+ return 2;
+ }
+
+ /* Check lid open */
+ if (lid_opened) {
+ lid_opened = 0;
+ return 3;
+ }
+
+ /* check for power button press */
+ if (gpio_get_level(GPIO_KB_PWR_ON_L) == 0) {
+ udelay(KB_PWR_ON_DEBOUNCE);
+ if (gpio_get_level(GPIO_KB_PWR_ON_L) == 0)
+ return 4;
+ }
+
+ if (power_request == POWER_REQ_ON) {
+ power_request = POWER_REQ_NONE;
+ return 5;
+ }
+
+ return 0;
+}
+
+/**
+ * Power on the AP
+ *
+ * @return 0 if ok, -1 on error (PP1800_LDO2 failed to come on)
+ */
+static int power_on(void)
+{
+ gpio_set_level(GPIO_AP_RESET_L, 1);
+ set_pmic_pwrok(1);
+
+ if (gpio_get_level(GPIO_SOC1V8_XPSHOLD) == 0)
+ /* Initialize non-AP components */
+ hook_notify(HOOK_CHIPSET_PRE_INIT);
+
+ ap_on = 1;
+ disable_sleep(SLEEP_MASK_AP_RUN);
+ powerled_set_state(POWERLED_STATE_ON);
+
+ /* Call hooks now that AP is running */
+ hook_notify(HOOK_CHIPSET_STARTUP);
+
+ CPRINTF("[%T AP running ...]\n");
+ return 0;
+}
+
+/**
+ * Wait for the power button to be released
+ *
+ * @return 0 if ok, -1 if power button failed to release
+ */
+static int wait_for_power_button_release(unsigned int timeout_us)
+{
+ /* wait for Power button release */
+ wait_in_signal(GPIO_KB_PWR_ON_L, 1, timeout_us);
+
+ udelay(KB_PWR_ON_DEBOUNCE);
+ if (gpio_get_level(GPIO_KB_PWR_ON_L) == 0) {
+ CPRINTF("[%T power button not released in time]\n");
+ return -1;
+ }
+ CPRINTF("[%T power button released]\n");
+ return 0;
+}
+
+/**
+ * Wait for the XPSHOLD signal from the AP to be asserted within timeout_us
+ * and if asserted clear the PMIC_PWRON signal
+ *
+ * @return 0 if ok, -1 if power button failed to release
+ */
+static int react_to_xpshold(unsigned int timeout_us)
+{
+ /* wait for Power button release */
+ wait_in_signal(GPIO_SOC1V8_XPSHOLD, 1, timeout_us);
+
+#ifdef BOARD_nyan
+ /*
+ * nyan's GPIO_SOC1V8_XPSHOLD will go low for about 20ms after initial
+ * high. Wait XPSHOLD_DEBOUNCE time, then check the signal again.
+ */
+ udelay(XPSHOLD_DEBOUNCE);
+#endif
+
+ if (gpio_get_level(GPIO_SOC1V8_XPSHOLD) == 0) {
+ CPRINTF("[%T XPSHOLD not seen in time]\n");
+ return -1;
+ }
+
+ CPRINTF("[%T XPSHOLD seen]\n");
+ return 0;
+}
+
+/**
+ * Power off the AP
+ */
+static void power_off(void)
+{
+ /* Call hooks before we drop power rails */
+ hook_notify(HOOK_CHIPSET_SHUTDOWN);
+ /* switch off all rails */
+ chipset_force_shutdown();
+ ap_on = 0;
+ ap_suspended = 0;
+ lid_opened = 0;
+ enable_sleep(SLEEP_MASK_AP_RUN);
+ powerled_set_state(POWERLED_STATE_OFF);
+ CPRINTF("[%T power shutdown complete]\n");
+}
+
+/*
+ * Calculates the delay in microseconds to the next time we have to check
+ * for a power event,
+ *
+ *@return delay to next check, or -1 if no future check is needed
+ */
+static int next_pwr_event(void)
+{
+ if (!power_off_deadline.val)
+ return -1;
+
+ return power_off_deadline.val - get_time().val;
+}
+
+/*****************************************************************************/
+static int wait_for_power_on(void)
+{
+ int value;
+ while (1) {
+ value = check_for_power_on_event();
+ if (!value) {
+ task_wait_event(-1);
+ continue;
+ }
+
+#ifdef HAS_TASK_CHARGER
+ /*
+ * If the system is already on (value == 1), the kernel
+ * would handle low power condition and we should not
+ * shutdown the system from EC.
+ */
+ if (value != 1 && charge_keep_power_off()) {
+ CPRINTF("[%T power on ignored due to low battery]\n");
+ continue;
+ }
+#endif
+
+ CPRINTF("[%T power on %d]\n", value);
+ return value;
+ }
+}
+
+void chipset_task(void)
+{
+ int value;
+
+ tegra_power_init();
+ ap_on = 0;
+
+ while (1) {
+ /* Wait until we need to power on, then power on */
+ wait_for_power_on();
+
+ if (!power_on()) {
+ int continue_power = 0;
+
+ if (!react_to_xpshold(DELAY_RELEASE_PWRON)) {
+ /* AP looks good */
+ if (!wait_for_power_button_release(
+ DELAY_SHUTDOWN_ON_POWER_HOLD))
+ continue_power = 1;
+ }
+ set_pmic_pwrok(0);
+ if (continue_power) {
+ power_button_was_pressed = 0;
+ while (!(value = check_for_power_off_event()))
+ task_wait_event(next_pwr_event());
+ CPRINTF("[%T power ending loop %d]\n", value);
+ }
+ }
+ power_off();
+ wait_for_power_button_release(-1);
+ }
+}
+
+/*****************************************************************************/
+/* Console debug command */
+
+static const char *power_req_name[POWER_REQ_COUNT] = {
+ "none",
+ "off",
+ "on",
+};
+
+/* Power states that we can report */
+enum power_state_t {
+ PSTATE_UNKNOWN,
+ PSTATE_OFF,
+ PSTATE_SUSPEND,
+ PSTATE_ON,
+
+ PSTATE_COUNT,
+};
+
+static const char * const state_name[] = {
+ "unknown",
+ "off",
+ "suspend",
+ "on",
+};
+
+static int command_power(int argc, char **argv)
+{
+ int v;
+
+ if (argc < 2) {
+ enum power_state_t state;
+
+ state = PSTATE_UNKNOWN;
+ if (chipset_in_state(CHIPSET_STATE_ANY_OFF))
+ state = PSTATE_OFF;
+ if (chipset_in_state(CHIPSET_STATE_SUSPEND))
+ state = PSTATE_SUSPEND;
+ if (chipset_in_state(CHIPSET_STATE_ON))
+ state = PSTATE_ON;
+ ccprintf("%s\n", state_name[state]);
+
+ return EC_SUCCESS;
+ }
+
+ if (!parse_bool(argv[1], &v))
+ return EC_ERROR_PARAM1;
+
+ power_request = v ? POWER_REQ_ON : POWER_REQ_OFF;
+ ccprintf("Requesting power %s\n", power_req_name[power_request]);
+ task_wake(TASK_ID_CHIPSET);
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(power, command_power,
+ "on/off",
+ "Turn AP power on/off",
+ NULL);
diff --git a/power/x86_common.c b/power/x86_common.c
new file mode 100644
index 0000000000..add033906e
--- /dev/null
+++ b/power/x86_common.c
@@ -0,0 +1,422 @@
+/* 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.
+ */
+
+/* Common functionality across x86 chipsets */
+
+#include "chipset.h"
+#include "chipset_x86_common.h"
+#include "common.h"
+#include "console.h"
+#include "extpower.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "system.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_CHIPSET, outstr)
+#define CPRINTF(format, args...) cprintf(CC_CHIPSET, format, ## args)
+
+/*
+ * Default timeout in us; if we've been waiting this long for an input
+ * transition, just jump to the next state.
+ */
+#define DEFAULT_TIMEOUT SECOND
+
+/* Timeout for dropping back from S5 to G3 */
+#define S5_INACTIVITY_TIMEOUT (10 * SECOND)
+
+static const char * const state_names[] = {
+ "G3",
+ "S5",
+ "S3",
+ "S0",
+ "G3->S5",
+ "S5->S3",
+ "S3->S0",
+ "S0->S3",
+ "S3->S5",
+ "S5->G3",
+};
+
+static uint32_t in_signals; /* Current input signal states (IN_PGOOD_*) */
+static uint32_t in_want; /* Input signal state we're waiting for */
+static uint32_t in_debug; /* Signal values which print debug output */
+
+static enum x86_state state = X86_G3; /* Current state */
+static int want_g3_exit; /* Should we exit the G3 state? */
+static uint64_t last_shutdown_time; /* When did we enter G3? */
+
+/* Delay before hibernating, in seconds */
+static uint32_t hibernate_delay = 3600;
+
+/**
+ * Update input signals mask
+ */
+static void x86_update_signals(void)
+{
+ uint32_t inew = 0;
+ const struct x86_signal_info *s = x86_signal_list;
+ int i;
+
+ for (i = 0; i < X86_SIGNAL_COUNT; i++, s++) {
+ if (gpio_get_level(s->gpio) == s->level)
+ inew |= 1 << i;
+ }
+
+ if ((in_signals & in_debug) != (inew & in_debug))
+ CPRINTF("[%T x86 in 0x%04x]\n", inew);
+
+ in_signals = inew;
+}
+
+uint32_t x86_get_signals(void)
+{
+ return in_signals;
+}
+
+int x86_has_signals(uint32_t want)
+{
+ if ((in_signals & want) == want)
+ return 1;
+
+ CPRINTF("[%T x86 power lost input; wanted 0x%04x, got 0x%04x]\n",
+ want, in_signals & want);
+
+ return 0;
+}
+
+int x86_wait_signals(uint32_t want)
+{
+ in_want = want;
+ if (!want)
+ return EC_SUCCESS;
+
+ while ((in_signals & in_want) != in_want) {
+ if (task_wait_event(DEFAULT_TIMEOUT) == TASK_EVENT_TIMER) {
+ x86_update_signals();
+ CPRINTF("[%T x86 power timeout on input; "
+ "wanted 0x%04x, got 0x%04x]\n",
+ in_want, in_signals & in_want);
+ return EC_ERROR_TIMEOUT;
+ }
+ /*
+ * TODO: should really shrink the remaining timeout if we woke
+ * up but didn't have all the signals we wanted. Also need to
+ * handle aborts if we're no longer in the same state we were
+ * when we started waiting.
+ */
+ }
+ return EC_SUCCESS;
+}
+
+/**
+ * Set the low-level x86 chipset state.
+ *
+ * @param new_state New chipset state.
+ */
+void x86_set_state(enum x86_state new_state)
+{
+ /* Record the time we go into G3 */
+ if (new_state == X86_G3)
+ last_shutdown_time = get_time().val;
+
+ state = new_state;
+}
+
+/**
+ * Common handler for x86 steady states
+ *
+ * @param state Current x86 state
+ * @return Updated x86 state
+ */
+static enum x86_state x86_common_state(enum x86_state state)
+{
+ switch (state) {
+ case X86_G3:
+ if (want_g3_exit) {
+ want_g3_exit = 0;
+ return X86_G3S5;
+ }
+
+ in_want = 0;
+ if (extpower_is_present())
+ task_wait_event(-1);
+ else {
+ uint64_t target_time = last_shutdown_time +
+ hibernate_delay * 1000000ull;
+ uint64_t time_now = get_time().val;
+ if (time_now > target_time) {
+ /*
+ * Time's up. Hibernate until wake pin
+ * asserted.
+ */
+ CPRINTF("[%T x86 hibernating]\n");
+ system_hibernate(0, 0);
+ } else {
+ uint64_t wait = target_time - time_now;
+ if (wait > TASK_MAX_WAIT_US)
+ wait = TASK_MAX_WAIT_US;
+
+ /* Wait for a message */
+ task_wait_event(wait);
+ }
+ }
+ break;
+
+ case X86_S5:
+ /* Wait for inactivity timeout */
+ x86_wait_signals(0);
+ if (task_wait_event(S5_INACTIVITY_TIMEOUT) ==
+ TASK_EVENT_TIMER) {
+ /* Drop to G3; wake not requested yet */
+ want_g3_exit = 0;
+ return X86_S5G3;
+ }
+ break;
+
+ case X86_S3:
+ /* Wait for a message */
+ x86_wait_signals(0);
+ task_wait_event(-1);
+ break;
+
+ case X86_S0:
+ /* Wait for a message */
+ x86_wait_signals(0);
+ task_wait_event(-1);
+ break;
+
+ default:
+ /* No common functionality for transition states */
+ break;
+ }
+
+ return state;
+}
+
+/*****************************************************************************/
+/* Chipset interface */
+
+int chipset_in_state(int state_mask)
+{
+ int need_mask = 0;
+
+ /*
+ * TODO: what to do about state transitions? If the caller wants
+ * HARD_OFF|SOFT_OFF and we're in G3S5, we could still return
+ * non-zero.
+ */
+ switch (state) {
+ case X86_G3:
+ need_mask = CHIPSET_STATE_HARD_OFF;
+ break;
+ case X86_G3S5:
+ case X86_S5G3:
+ /*
+ * In between hard and soft off states. Match only if caller
+ * will accept both.
+ */
+ need_mask = CHIPSET_STATE_HARD_OFF | CHIPSET_STATE_SOFT_OFF;
+ break;
+ case X86_S5:
+ need_mask = CHIPSET_STATE_SOFT_OFF;
+ break;
+ case X86_S5S3:
+ case X86_S3S5:
+ need_mask = CHIPSET_STATE_SOFT_OFF | CHIPSET_STATE_SUSPEND;
+ break;
+ case X86_S3:
+ need_mask = CHIPSET_STATE_SUSPEND;
+ break;
+ case X86_S3S0:
+ case X86_S0S3:
+ need_mask = CHIPSET_STATE_SUSPEND | CHIPSET_STATE_ON;
+ break;
+ case X86_S0:
+ need_mask = CHIPSET_STATE_ON;
+ break;
+ }
+
+ /* Return non-zero if all needed bits are present */
+ return (state_mask & need_mask) == need_mask;
+}
+
+void chipset_exit_hard_off(void)
+{
+ /* If not in the hard-off state nor headed there, nothing to do */
+ if (state != X86_G3 && state != X86_S5G3)
+ return;
+
+ /* Set a flag to leave G3, then wake the task */
+ want_g3_exit = 1;
+
+ if (task_start_called())
+ task_wake(TASK_ID_CHIPSET);
+}
+
+/*****************************************************************************/
+/* Task function */
+
+void chipset_task(void)
+{
+ enum x86_state new_state;
+
+ while (1) {
+ CPRINTF("[%T x86 power state %d = %s, in 0x%04x]\n",
+ state, state_names[state], in_signals);
+
+ /* Always let the specific chipset handle the state first */
+ new_state = x86_handle_state(state);
+
+ /*
+ * If the state hasn't changed, run common steady-state
+ * handler.
+ */
+ if (new_state == state)
+ new_state = x86_common_state(state);
+
+ /* Handle state changes */
+ if (new_state != state)
+ x86_set_state(new_state);
+ }
+}
+
+/*****************************************************************************/
+/* Hooks */
+
+static void x86_common_init(void)
+{
+ const struct x86_signal_info *s = x86_signal_list;
+ int i;
+
+ /* Update input state */
+ x86_update_signals();
+
+ /* Call chipset-specific init to set initial state */
+ x86_set_state(x86_chipset_init());
+
+ /* Enable interrupts for input signals */
+ for (i = 0; i < X86_SIGNAL_COUNT; i++, s++)
+ gpio_enable_interrupt(s->gpio);
+}
+DECLARE_HOOK(HOOK_INIT, x86_common_init, HOOK_PRIO_INIT_CHIPSET);
+
+static void x86_lid_change(void)
+{
+ /* Wake up the task to update power state */
+ task_wake(TASK_ID_CHIPSET);
+}
+DECLARE_HOOK(HOOK_LID_CHANGE, x86_lid_change, HOOK_PRIO_DEFAULT);
+
+static void x86_ac_change(void)
+{
+ if (extpower_is_present()) {
+ CPRINTF("[%T x86 AC on]\n");
+ } else {
+ CPRINTF("[%T x86 AC off]\n");
+
+ if (state == X86_G3) {
+ last_shutdown_time = get_time().val;
+ task_wake(TASK_ID_CHIPSET);
+ }
+ }
+}
+DECLARE_HOOK(HOOK_AC_CHANGE, x86_ac_change, HOOK_PRIO_DEFAULT);
+
+/*****************************************************************************/
+/* Interrupts */
+
+void x86_interrupt(enum gpio_signal signal)
+{
+ /* Shadow signals and compare with our desired signal state. */
+ x86_update_signals();
+
+ /* Wake up the task */
+ task_wake(TASK_ID_CHIPSET);
+}
+
+/*****************************************************************************/
+/* Console commands */
+
+static int command_powerinfo(int argc, char **argv)
+{
+ /*
+ * Print x86 power state in same format as state machine. This is
+ * used by FAFT tests, so must match exactly.
+ */
+ ccprintf("[%T x86 power state %d = %s, in 0x%04x]\n",
+ state, state_names[state], in_signals);
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(powerinfo, command_powerinfo,
+ NULL,
+ "Show current x86 power state",
+ NULL);
+
+static int command_x86indebug(int argc, char **argv)
+{
+ const struct x86_signal_info *s = x86_signal_list;
+ int i;
+ char *e;
+
+ /* If one arg, set the mask */
+ if (argc == 2) {
+ int m = strtoi(argv[1], &e, 0);
+ if (*e)
+ return EC_ERROR_PARAM1;
+
+ in_debug = m;
+ }
+
+ /* Print the mask */
+ ccprintf("x86 in: 0x%04x\n", in_signals);
+ ccprintf("debug mask: 0x%04x\n", in_debug);
+
+ /* Print the decode */
+
+ ccprintf("bit meanings:\n");
+ for (i = 0; i < X86_SIGNAL_COUNT; i++, s++) {
+ int mask = 1 << i;
+ ccprintf(" 0x%04x %d %s\n",
+ mask, in_signals & mask ? 1 : 0, s->name);
+ }
+
+ return EC_SUCCESS;
+};
+DECLARE_CONSOLE_COMMAND(x86indebug, command_x86indebug,
+ "[mask]",
+ "Get/set x86 input debug mask",
+ NULL);
+
+static int command_hibernation_delay(int argc, char **argv)
+{
+ char *e;
+ uint32_t time_g3 = ((uint32_t)(get_time().val - last_shutdown_time))
+ / SECOND;
+
+ if (argc >= 2) {
+ uint32_t s = strtoi(argv[1], &e, 0);
+ if (*e)
+ return EC_ERROR_PARAM1;
+
+ hibernate_delay = s;
+ }
+
+ /* Print the current setting */
+ ccprintf("Hibernation delay: %d s\n", hibernate_delay);
+ if (state == X86_G3 && !extpower_is_present()) {
+ ccprintf("Time G3: %d s\n", time_g3);
+ ccprintf("Time left: %d s\n", hibernate_delay - time_g3);
+ }
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(hibdelay, command_hibernation_delay,
+ "[sec]",
+ "Set the delay before going into hibernation",
+ NULL);