diff options
Diffstat (limited to 'power/mt8192.c')
-rw-r--r-- | power/mt8192.c | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/power/mt8192.c b/power/mt8192.c new file mode 100644 index 0000000000..749424b2c9 --- /dev/null +++ b/power/mt8192.c @@ -0,0 +1,454 @@ +/* Copyright 2020 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. + */ + +/* + * MT8192 SoC power sequencing module for Chrome EC + * + * This implements the following features: + * + * - Cold reset powers on the AP + * + * When powered off: + * - Press power button turns on the AP + * - Hold power button turns on the AP, and then 8s later turns it off and + * leaves it off until pwron is released and press again. + * - Lid open turns on the AP + * + * When powered on: + * - Holding power button for 8s powers off the AP + * - Pressing and releaseing pwron within that 8s is ignored + */ + +#include "battery.h" +#include "chipset.h" +#include "common.h" +#include "hooks.h" +#include "lid_switch.h" +#include "power.h" +#include "power_button.h" +#include "system.h" +#include "task.h" +#include "timer.h" + +#ifdef CONFIG_BRINGUP +#define GPIO_SET_LEVEL(signal, value) \ + gpio_set_level_verbose(CC_CHIPSET, signal, value) +#else +#define GPIO_SET_LEVEL(signal, value) gpio_set_level(signal, value) +#endif + +/* Console output macros */ +#define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ##args) + +/* Input state flags */ +#define IN_SUSPEND_ASSERTED POWER_SIGNAL_MASK(AP_IN_S3_L) +#define IN_PGOOD_PMIC POWER_SIGNAL_MASK(PMIC_PWR_GOOD) +#define IN_AP_WDT_ASSERTED POWER_SIGNAL_MASK(AP_WDT_ASSERTED) + +/* Rails required for S3 and S0 */ +#define IN_PGOOD_S0 (IN_PGOOD_PMIC) +#define IN_PGOOD_S3 (IN_PGOOD_PMIC) + +/* All inputs in the right state for S0 */ +#define IN_ALL_S0 (IN_PGOOD_S0 & ~IN_SUSPEND_ASSERTED) + +/* Long power key press to force shutdown in S0. go/crosdebug */ +#define FORCED_SHUTDOWN_DELAY (8 * SECOND) + +/* Long power key press to boot from S5/G3 state. */ +#ifndef POWERBTN_BOOT_DELAY +#define POWERBTN_BOOT_DELAY (10 * MSEC) +#endif +#define PMIC_EN_PULSE_MS 50 + +/* Maximum time it should for PMIC to turn on after toggling PMIC_EN_ODL. */ +#define PMIC_EN_TIMEOUT (300 * MSEC) + +/* Time delay in G3 to deassert EN_PP1800_S5_L */ +#define EN_PP1800_S5_L_DEASSERT_TIME (20 * MSEC) + +/* + * Time delay for AP on/off the AP_EC_WDT when received SYS_RST_ODL. + * Generally it can be done within 3 ms. + */ +#define AP_EC_WDT_TIMEOUT (100 * MSEC) + +/* 30 ms for hard reset, we hold it longer to prevent TPM false alarm. */ +#define SYS_RST_PULSE_LENGTH (50 * MSEC) + +static int forcing_shutdown; + +static void watchdog_interrupt_deferred(void) +{ + chipset_reset(CHIPSET_RESET_AP_WATCHDOG); +} +DECLARE_DEFERRED(watchdog_interrupt_deferred); + +static void reset_request_interrupt_deferred(void) +{ + chipset_reset(CHIPSET_RESET_AP_REQ); +} +DECLARE_DEFERRED(reset_request_interrupt_deferred); + +void chipset_reset_request_interrupt(enum gpio_signal signal) +{ + hook_call_deferred(&reset_request_interrupt_deferred_data, 0); +} + +/* + * Triggers on falling edge of AP watchdog line only. The falling edge can + * happen in these 3 cases: + * - AP asserts watchdog while the AP is on: this is a real AP-initiated reset. + * - EC asserted GPIO_SYS_RST_ODL, so the AP is in reset and AP watchdog falls + * as well. This is _not_ a watchdog reset. We mask these cases by disabling + * the interrupt just before shutting down the AP, and re-enabling it just + * after starting the AP. + * - PMIC has shut down (e.g. the AP powered off by itself), this is not a + * watchdog reset either. This should be covered by the case above if the + * EC reacts quickly enough, but we mask those cases as well by testing if + * the PMIC is still on when the watchdog line falls. + */ +void chipset_watchdog_interrupt(enum gpio_signal signal) +{ + /* Pass AP_EC_WATCHDOG_L signal to PMIC */ + GPIO_SET_LEVEL(GPIO_EC_PMIC_WATCHDOG_L, gpio_get_level(signal)); + + /* Update power signals */ + power_signal_interrupt(signal); + + /* + * case 1: PMIC is good, WDT asserts, and EC is not asserting + * SYS_RST_ODL. This is AP initiated real WDT. + */ + if (gpio_get_level(GPIO_SYS_RST_ODL) && + power_get_signals() & IN_PGOOD_PMIC && + power_get_signals() & IN_AP_WDT_ASSERTED) + hook_call_deferred(&watchdog_interrupt_deferred_data, 0); + + /* + * case 2&3: Fall through. The chipset_reset should have been + * invoked. + */ +} + +void chipset_force_shutdown(enum chipset_shutdown_reason reason) +{ + CPRINTS("%s(%d)", __func__, reason); + report_ap_reset(reason); + + /* + * Force power off. This condition will reset once the state machine + * transitions to G3. + */ + forcing_shutdown = 1; + task_wake(TASK_ID_CHIPSET); +} + +void chipset_force_shutdown_button(void) +{ + chipset_force_shutdown(CHIPSET_SHUTDOWN_BUTTON); +} +DECLARE_DEFERRED(chipset_force_shutdown_button); + +void chipset_exit_hard_off_button(void) +{ + /* Power up from off */ + forcing_shutdown = 0; + chipset_exit_hard_off(); +} +DECLARE_DEFERRED(chipset_exit_hard_off_button); + +void chipset_reset(enum chipset_reset_reason reason) +{ + CPRINTS("%s: %d", __func__, reason); + report_ap_reset(reason); + + GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 0); + usleep(SYS_RST_PULSE_LENGTH); + GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 1); +} + +enum power_state power_chipset_init(void) +{ + /* Enable reboot / sleep control inputs from AP */ + gpio_enable_interrupt(GPIO_AP_EC_WARM_RST_REQ); + gpio_enable_interrupt(GPIO_AP_IN_SLEEP_L); + + if (system_get_reset_flags() & EC_RESET_FLAG_SYSJUMP) { + if ((power_get_signals() & IN_ALL_S0) == IN_ALL_S0) { + disable_sleep(SLEEP_MASK_AP_RUN); + power_signal_enable_interrupt(GPIO_AP_EC_WATCHDOG_L); + CPRINTS("already in S0"); + return POWER_S0; + } + } else if (system_get_reset_flags() & EC_RESET_FLAG_AP_OFF) { + /* Force shutdown from S5 if the PMIC is already up. */ + if (power_get_signals() & IN_PGOOD_PMIC) { + forcing_shutdown = 1; + return POWER_S5; + } + } + + if (battery_is_present() == BP_YES) + /* + * (crosbug.com/p/28289): Wait battery stable. + * Some batteries use clock stretching feature, which requires + * more time to be stable. + */ + battery_wait_for_stable(); + + if (!(system_get_reset_flags() & EC_RESET_FLAG_AP_OFF)) + /* Auto-power on */ + chipset_exit_hard_off(); + + /* Start from S5 if the PMIC is already up. */ + if (power_get_signals() & IN_PGOOD_PMIC) + return POWER_S5; + + return POWER_G3; +} + +enum power_state power_handle_state(enum power_state state) +{ + /* Retry S5->S3 transition, if not zero. */ + static int s5s3_retry; + + /* + * PMIC power went away (AP most likely decided to shut down): + * transition to S5, G3. + */ + static int ap_shutdown; + + switch (state) { + case POWER_G3: + + /* Go back to S5->G3 if the PMIC unexpectedly starts again. */ + if (power_get_signals() & IN_PGOOD_PMIC) + return POWER_S5G3; + break; + + case POWER_S5: + /* + * If AP initiated shutdown, PMIC is off, and we can transition + * to G3 immediately. + */ + if (ap_shutdown) { + ap_shutdown = 0; + return POWER_S5G3; + } else if (!forcing_shutdown) { + /* Powering up. */ + s5s3_retry = 1; + return POWER_S5S3; + } + + /* Forcing shutdown */ + + /* Long press has worked, transition to G3. */ + if (!(power_get_signals() & IN_PGOOD_PMIC)) + return POWER_S5G3; + + /* + * Try to force PMIC shutdown with a long press. This takes 8s, + * shorter than the common code S5->G3 timeout (10s). + * + * Note: We might run twice at this line because we + * deasserts SYS_RST_ODL in S5->S3 and then WDT interrupt + * handler sets the wake event for chipset_task. This should be + * no harm, but to prevent misunderstanding in the console, we + * check EC_PMIC_EN_ODL before set. + */ + if (gpio_get_level(GPIO_EC_PMIC_EN_ODL)) { + CPRINTS("Forcing shutdown with long press."); + GPIO_SET_LEVEL(GPIO_EC_PMIC_EN_ODL, 0); + } + + /* + * Stay in S5, common code will drop to G3 after timeout + * if the long press does not work. + */ + return POWER_S5; + + case POWER_S3: + if (!power_has_signals(IN_PGOOD_S3) || forcing_shutdown) + return POWER_S3S5; + else if (!(power_get_signals() & IN_SUSPEND_ASSERTED)) + return POWER_S3S0; + break; + + case POWER_S0: + if (!power_has_signals(IN_PGOOD_S0) || forcing_shutdown || + power_get_signals() & IN_SUSPEND_ASSERTED) + return POWER_S0S3; + + break; + + case POWER_G3S5: + forcing_shutdown = 0; + + GPIO_SET_LEVEL(GPIO_EN_PP5000_A, 1); + /* Power up to next state */ + return POWER_S5; + + case POWER_S5S3: + hook_notify(HOOK_CHIPSET_PRE_INIT); + + /* + * Release power button in case it was pressed by force shutdown + * sequence. + */ + GPIO_SET_LEVEL(GPIO_EC_PMIC_EN_ODL, 1); + + /* If PMIC is off, switch it on by pulsing PMIC enable. */ + if (!(power_get_signals() & IN_PGOOD_PMIC)) { + msleep(PMIC_EN_PULSE_MS); + GPIO_SET_LEVEL(GPIO_EC_PMIC_EN_ODL, 0); + msleep(PMIC_EN_PULSE_MS); + GPIO_SET_LEVEL(GPIO_EC_PMIC_EN_ODL, 1); + } + + /* + * Wait for PMIC to bring up rails. Retry if it fails + * (it may take 2 attempts on restart after we use + * force reset). + */ + if (power_wait_signals_timeout(IN_PGOOD_PMIC, + PMIC_EN_TIMEOUT)) { + if (s5s3_retry) { + s5s3_retry = 0; + return POWER_S5S3; + } + /* Give up, go back to G3. */ + return POWER_S5G3; + } + + GPIO_SET_LEVEL(GPIO_EN_PP1800_U, 1); + GPIO_SET_LEVEL(GPIO_EN_PP3300_U, 1); + + /* Release AP reset and waits for AP pulling WDT up. */ + power_signal_enable_interrupt(GPIO_AP_EC_WATCHDOG_L); + GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 1); + if (power_wait_mask_signals_timeout(0, IN_AP_WDT_ASSERTED, + AP_EC_WDT_TIMEOUT)) { + if (s5s3_retry) { + s5s3_retry = 0; + return POWER_S5S3; + } + /* Give up, go back to G3. */ + return POWER_S5G3; + } + + /* Call hooks now that rails are up */ + hook_notify(HOOK_CHIPSET_STARTUP); + + /* Power up to next state */ + return POWER_S3; + + case POWER_S3S0: + if (power_wait_signals(IN_PGOOD_S0)) { + chipset_force_shutdown(CHIPSET_SHUTDOWN_WAIT); + return POWER_S0S3; + } + + /* 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); + + /* Power up to next state */ + return POWER_S0; + + case POWER_S0S3: + /* Call hooks before we remove power rails */ + hook_notify(HOOK_CHIPSET_SUSPEND); + + /* + * 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); + + /* + * In case the power button is held awaiting power-off timeout, + * power off immediately now that we're entering S3. + */ + if (power_button_is_pressed()) { + forcing_shutdown = 1; + hook_call_deferred(&chipset_force_shutdown_button_data, + -1); + } + + return POWER_S3; + + case POWER_S3S5: + /* PMIC has shutdown, transition to G3. */ + if (!(power_get_signals() & IN_PGOOD_PMIC)) + ap_shutdown = 1; + + /* Call hooks before we remove power rails */ + hook_notify(HOOK_CHIPSET_SHUTDOWN); + + /* + * Assert SYS_RST_ODL, and waits for AP finishing epilogue and + * asserting WDT. + */ + GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 0); + if (EC_ERROR_TIMEOUT == + power_wait_signals_timeout(IN_AP_WDT_ASSERTED, + AP_EC_WDT_TIMEOUT)) { + CPRINTS("Timeout waitting AP watchdog, force if off"); + GPIO_SET_LEVEL(GPIO_EC_PMIC_WATCHDOG_L, 0); + } + power_signal_disable_interrupt(GPIO_AP_EC_WATCHDOG_L); + + GPIO_SET_LEVEL(GPIO_EN_PP1800_U, 0); + GPIO_SET_LEVEL(GPIO_EN_PP3300_U, 0); + + /* Start shutting down */ + return POWER_S5; + + case POWER_S5G3: + /* Release the power button, in case it was long pressed. */ + if (forcing_shutdown) + GPIO_SET_LEVEL(GPIO_EC_PMIC_EN_ODL, 1); + + GPIO_SET_LEVEL(GPIO_EN_PP5000_A, 0); + + return POWER_G3; + } + + return state; +} + +static void power_button_changed(void) +{ + if (power_button_is_pressed()) { + if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) + hook_call_deferred(&chipset_exit_hard_off_button_data, + POWERBTN_BOOT_DELAY); + + /* Delayed power down from S0/S3, cancel on PB release */ + hook_call_deferred(&chipset_force_shutdown_button_data, + FORCED_SHUTDOWN_DELAY); + } else { + /* Power button released, cancel deferred shutdown/boot */ + hook_call_deferred(&chipset_exit_hard_off_button_data, -1); + hook_call_deferred(&chipset_force_shutdown_button_data, -1); + } +} +DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, power_button_changed, HOOK_PRIO_DEFAULT); + +#ifdef CONFIG_LID_SWITCH +static void lid_changed(void) +{ + /* Power-up from off on lid open */ + if (lid_is_open() && chipset_in_state(CHIPSET_STATE_ANY_OFF)) + chipset_exit_hard_off(); +} +DECLARE_HOOK(HOOK_LID_CHANGE, lid_changed, HOOK_PRIO_DEFAULT); +#endif |