diff options
author | Yen Lin <yelin@nvidia.com> | 2013-09-03 12:04:55 -0700 |
---|---|---|
committer | chrome-internal-fetch <chrome-internal-fetch@google.com> | 2013-10-17 22:52:12 +0000 |
commit | 140404ffdfb54faa432128435e87aacd9a5761a6 (patch) | |
tree | 1c5f958d30632c3c1b5443d4117df64eea886bef /common | |
parent | 99472d5cb577681b6b711e001f5a50e6abc2e356 (diff) | |
download | chrome-ec-140404ffdfb54faa432128435e87aacd9a5761a6.tar.gz |
ec: add nyan board
This is to add nyan board support:
- new files in board/nyan folder, including battery.c
- new common/chipset_tegra.c, which is mostly based on
chipset_gaia.c
- new include/tegra_power.h
- modified build.mk and flash_ec for nyan
BUG=none
BRANCH=nyan
TEST=tested on Venice 2 board
Change-Id: I36895f34f2f4d144a9440aff358c8274797ebbd6
Signed-off-by: Yen Lin <yelin@nvidia.com>
Reviewed-on: https://chromium-review.googlesource.com/168078
Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
Reviewed-by: Randall Spangler <rspangler@chromium.org>
Diffstat (limited to 'common')
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | common/chipset_tegra.c | 602 |
2 files changed, 603 insertions, 0 deletions
diff --git a/common/build.mk b/common/build.mk index c3e2fcf453..02d841e9ee 100644 --- a/common/build.mk +++ b/common/build.mk @@ -25,6 +25,7 @@ common-$(CONFIG_CHARGER_BQ24738)+=charger_bq24738.o common-$(CONFIG_CHARGER_TPS65090)+=pmu_tps65090_charger.o common-$(CONFIG_CHIPSET_BAYTRAIL)+=chipset_baytrail.o common-$(CONFIG_CHIPSET_GAIA)+=chipset_gaia.o +common-$(CONFIG_CHIPSET_TEGRA)+=chipset_tegra.o common-$(CONFIG_CHIPSET_HASWELL)+=chipset_haswell.o common-$(CONFIG_CHIPSET_IVYBRIDGE)+=chipset_ivybridge.o common-$(CONFIG_CHIPSET_X86)+=chipset_x86_common.o diff --git a/common/chipset_tegra.c b/common/chipset_tegra.c new file mode 100644 index 0000000000..7032f969cb --- /dev/null +++ b/common/chipset_tegra.c @@ -0,0 +1,602 @@ +/* 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 "tegra_power.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 tegra_suspend_event(enum gpio_signal signal) +{ + hook_call_deferred(tegra_suspend_deferred, 0); +} + +void tegra_power_event(enum gpio_signal signal) +{ + /* Wake up the 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); |