diff options
author | Randall Spangler <rspangler@chromium.org> | 2013-10-16 13:23:10 -0700 |
---|---|---|
committer | chrome-internal-fetch <chrome-internal-fetch@google.com> | 2013-10-23 20:07:25 +0000 |
commit | 8cf03ac0563294fbdeca2dc133d06f0b51c9a546 (patch) | |
tree | 6b07c493e7567a3221d8592b4337d2787d6bc531 /power | |
parent | 2464d08e4d310a3f63208f22df4502c5250c4b58 (diff) | |
download | chrome-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.c | 362 | ||||
-rw-r--r-- | power/build.mk | 15 | ||||
-rw-r--r-- | power/gaia.c | 770 | ||||
-rw-r--r-- | power/haswell.c | 406 | ||||
-rw-r--r-- | power/ivybridge.c | 344 | ||||
-rw-r--r-- | power/tegra.c | 601 | ||||
-rw-r--r-- | power/x86_common.c | 422 |
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); |