diff options
author | Ting Shen <phoenixshen@google.com> | 2021-11-16 16:06:37 +0800 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2022-01-07 04:31:36 +0000 |
commit | afc427853f629e12e238dce780b7b13eabb3b299 (patch) | |
tree | 8e6687315010443a0c3a1a22bcd5f04124119054 /power | |
parent | 229723e49cef20809c0ca0fca520d63c0cfd0e24 (diff) | |
download | chrome-ec-afc427853f629e12e238dce780b7b13eabb3b299.tar.gz |
power: add mt8186 power sequence
MT8186 power sequence is a simplified version of 8192/95. EC does not
lie between AP and PMIC, so there's no need to forward the signals.
Other logics are almost the same.
BUG=b:206338930
TEST=Test following items on krabby CL:3233784
* Cold reset:
$ dut-control cold_reset:on sleep:0.2 cold_reset:off
Result: G3 -> S0
* Long power press to shutdown:
$ dut-control 'ec_uart_cmd:powerbtn 8200'
Result: S0 -> S5 -> G3
* Long power press to power-on but then shutdown:
$ dut-control 'ec_uart_cmd:powerbtn 8200'
Result: G3 -> S0 -> S5 -> G3
* Short power press to power-on:
$ dut-control 'ec_uart_cmd:powerbtn 200'
Result: G3 -> S0
* Console command: apreset
Result: S0 -> S5 -> S0, AP reboots
* Console command: apshutdown
Result: S0 -> S5 -> G3
* Lid open to power-on:
$ dut-control lid_open:no sleep:0.2 lid_open:yes
Result: G3 -> S0
BRANCH=none
Cq-Depend: chromium:3366102
Signed-off-by: Ting Shen <phoenixshen@google.com>
Change-Id: Iebfe77c8f6d127ee4d0685903b67afd215ca6682
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3233783
Reviewed-by: Eric Yilun Lin <yllin@google.com>
Tested-by: Ting Shen <phoenixshen@chromium.org>
Commit-Queue: Ting Shen <phoenixshen@chromium.org>
Diffstat (limited to 'power')
-rw-r--r-- | power/build.mk | 1 | ||||
-rw-r--r-- | power/mt8186.c | 410 |
2 files changed, 411 insertions, 0 deletions
diff --git a/power/build.mk b/power/build.mk index 3e47167f0a..b04526c45f 100644 --- a/power/build.mk +++ b/power/build.mk @@ -17,6 +17,7 @@ power-$(CONFIG_CHIPSET_ECDRIVEN)+=ec_driven.o power-$(CONFIG_CHIPSET_ICELAKE)+=icelake.o intel_x86.o power-$(CONFIG_CHIPSET_MT817X)+=mt817x.o power-$(CONFIG_CHIPSET_MT8183)+=mt8183.o +power-$(CONFIG_CHIPSET_MT8186)+=mt8186.o power-$(CONFIG_CHIPSET_MT8192)+=mt8192.o power-$(CONFIG_CHIPSET_CEZANNE)+=amd_x86.o power-$(CONFIG_CHIPSET_RK3288)+=rk3288.o diff --git a/power/mt8186.c b/power/mt8186.c new file mode 100644 index 0000000000..75ab6fd170 --- /dev/null +++ b/power/mt8186.c @@ -0,0 +1,410 @@ +/* Copyright 2022 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. + */ + +/* + * MT8186 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 power within that 8s is ignored + */ + +#include "assert.h" +#include "battery.h" +#include "chipset.h" +#include "common.h" +#include "gpio.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) +#define IN_AP_RST POWER_SIGNAL_MASK(AP_IN_RST) + +/* 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. */ +#define POWERBTN_BOOT_DELAY (10 * MSEC) +#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) + +/* 30 ms for hard reset, we hold it longer to prevent TPM false alarm. */ +#define SYS_RST_PULSE_LENGTH (50 * MSEC) + +#ifndef CONFIG_ZEPHYR +/* power signal list. Must match order of enum power_signal. */ +const struct power_signal_info power_signal_list[] = { + {GPIO_AP_EC_SYSRST_ODL, POWER_SIGNAL_ACTIVE_LOW, "AP_IN_RST"}, + {GPIO_AP_IN_SLEEP_L, POWER_SIGNAL_ACTIVE_LOW, "AP_IN_S3"}, + {GPIO_AP_EC_WDTRST_L, POWER_SIGNAL_ACTIVE_LOW, "AP_WDT_ASSERTED"}, + {GPIO_AP_EC_WARM_RST_REQ, POWER_SIGNAL_ACTIVE_HIGH, "AP_WARM_RST_REQ"}, +}; +BUILD_ASSERT(ARRAY_SIZE(power_signal_list) == POWER_SIGNAL_COUNT); +#endif /* CONFIG_ZEPHYR */ + +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); +} + +static void release_power_button(void) +{ + CPRINTS("release power button after 8 seconds."); + GPIO_SET_LEVEL(GPIO_EC_PMIC_EN_ODL, 1); +} +DECLARE_DEFERRED(release_power_button); + +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. + */ + GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 0); + CPRINTS("Forcing pmic off with long press."); + GPIO_SET_LEVEL(GPIO_EC_PMIC_EN_ODL, 0); + hook_call_deferred(&release_power_button_data, + FORCED_SHUTDOWN_DELAY + SECOND); + + 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) +{ + /* + * release power button in case we are in the 8 seconds long hold + * period + */ + hook_call_deferred(&release_power_button_data, -1); + release_power_button(); + /* Power up from off */ + chipset_exit_hard_off(); +} +DECLARE_DEFERRED(chipset_exit_hard_off_button); + +void chipset_reset(enum chipset_shutdown_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); +} + +#ifdef CONFIG_POWER_TRACK_HOST_SLEEP_STATE +static void power_reset_host_sleep_state(void) +{ + power_set_host_sleep_state(HOST_SLEEP_EVENT_DEFAULT_RESET); + sleep_reset_tracking(); + power_chipset_handle_host_sleep_event(HOST_SLEEP_EVENT_DEFAULT_RESET, + NULL); +} + +static void handle_chipset_reset(void) +{ + if (chipset_in_state(CHIPSET_STATE_SUSPEND)) { + CPRINTS("Chipset reset: exit s3"); + power_reset_host_sleep_state(); + task_wake(TASK_ID_CHIPSET); + } +} +DECLARE_HOOK(HOOK_CHIPSET_RESET, handle_chipset_reset, HOOK_PRIO_FIRST); +#endif /* CONFIG_POWER_TRACK_HOST_SLEEP_STATE */ + +/* + * Power state is determined from the following table: + * + * | IN_AP_RST | IN_SUSPEND_ASSERTED | + * ---------------------------------------------- + * S0 | 0 | 0 | + * S3 | 0 | 1 | + * G3 | 1 | x | + * + * S5 is only used when exit from G3 in power_common_state(). + */ +static enum power_state power_get_signal_state(void) +{ + if (power_get_signals() & IN_AP_RST) + return POWER_G3; + if (power_get_signals() & IN_SUSPEND_ASSERTED) + return POWER_S3; + return POWER_S0; +} + +enum power_state power_chipset_init(void) +{ + int exit_hard_off = 1; + enum power_state init_state = power_get_signal_state(); + + /* Enable reboot / sleep control inputs from AP */ + gpio_enable_interrupt(GPIO_AP_EC_WARM_RST_REQ); + gpio_enable_interrupt(GPIO_AP_IN_SLEEP_L); + gpio_enable_interrupt(GPIO_AP_EC_SYSRST_ODL); + gpio_enable_interrupt(GPIO_AP_EC_WDTRST_L); + + if (system_jumped_late()) { + if (init_state == POWER_S0) { + disable_sleep(SLEEP_MASK_AP_RUN); + CPRINTS("already in S0"); + } + } else if (system_get_reset_flags() & EC_RESET_FLAG_AP_OFF) { + exit_hard_off = 0; + } else if ((system_get_reset_flags() & EC_RESET_FLAG_HIBERNATE) && + gpio_get_level(GPIO_AC_PRESENT)) { + /* + * If AC present, assume this is a wake-up by AC insert. + * Boot EC only. + * + * Note that extpower module is not initialized at this point, + * the only way is to ask GPIO_AC_PRESENT directly. + */ + exit_hard_off = 0; + } + + 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 (exit_hard_off) + /* Auto-power on */ + chipset_exit_hard_off(); + + if (init_state != POWER_G3 && !exit_hard_off) + /* Force shutdown from S5 if the PMIC is already up. */ + chipset_force_shutdown(CHIPSET_SHUTDOWN_INIT); + + return init_state; +} + +enum power_state power_handle_state(enum power_state state) +{ + enum power_state next_state = power_get_signal_state(); + + switch (state) { + case POWER_G3: + if (next_state != POWER_G3) + return POWER_G3S5; + break; + + case POWER_S5: + return POWER_S5S3; + + case POWER_S3: + if (next_state == POWER_G3) + return POWER_S3S5; + else if (next_state == POWER_S0) + return POWER_S3S0; + break; + + case POWER_S0: + if (next_state != POWER_S0) + return POWER_S0S3; + + break; + + case POWER_G3S5: + return POWER_S5; + + case POWER_S5S3: + hook_notify(HOOK_CHIPSET_PRE_INIT); + + GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 1); + 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); + + if (power_wait_mask_signals_timeout(0, IN_AP_RST, + PMIC_EN_TIMEOUT)) + /* Give up, go back to G3. */ + return POWER_S5G3; + + msleep(500); + /* Call hooks now that rails are up */ + hook_notify(HOOK_CHIPSET_STARTUP); + /* + * Clearing the sleep failure detection tracking on the path + * to S0 to handle any reset conditions. + */ +#ifdef CONFIG_POWER_SLEEP_FAILURE_DETECTION + power_reset_host_sleep_state(); +#endif /* CONFIG_POWER_SLEEP_FAILURE_DETECTION */ + /* Power up to next state */ + return POWER_S3; + + case POWER_S3S0: + if (power_wait_mask_signals_timeout(0, IN_AP_RST, SECOND)) { + chipset_force_shutdown(CHIPSET_SHUTDOWN_WAIT); + return POWER_S0S3; + } + + /* Call hooks now that rails are up */ + hook_notify(HOOK_CHIPSET_RESUME); + +#ifdef CONFIG_POWER_SLEEP_FAILURE_DETECTION + sleep_resume_transition(); +#endif /* CONFIG_POWER_SLEEP_FAILURE_DETECTION */ + + /* + * 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); + +#ifdef CONFIG_POWER_SLEEP_FAILURE_DETECTION + sleep_suspend_transition(); +#endif /* CONFIG_POWER_SLEEP_FAILURE_DETECTION */ + + /* + * 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()) + hook_call_deferred(&chipset_force_shutdown_button_data, + -1); + + return POWER_S3; + + case POWER_S3S5: + /* Call hooks before we remove power rails */ + hook_notify(HOOK_CHIPSET_SHUTDOWN); + hook_notify(HOOK_CHIPSET_SHUTDOWN_COMPLETE); + + /* Skip S5 */ + return POWER_S5G3; + + case POWER_S5G3: + return POWER_G3; + default: + CPRINTS("Unexpected power state %d", state); + break; + } + + 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_POWER_TRACK_HOST_SLEEP_STATE +static void suspend_hang_detected(void) +{ + CPRINTS("Warning: Detected sleep hang! Waking host up!"); + host_set_single_event(EC_HOST_EVENT_HANG_DETECT); +} + +__override void power_chipset_handle_host_sleep_event( + enum host_sleep_event state, + struct host_sleep_event_context *ctx) +{ + CPRINTS("Handle sleep: %d", state); + + if (state == HOST_SLEEP_EVENT_S3_SUSPEND) { + /* + * Indicate to power state machine that a new host event for + * S3 suspend has been received and so chipset suspend + * notification needs to be sent to listeners. + */ + sleep_set_notify(SLEEP_NOTIFY_SUSPEND); + sleep_start_suspend(ctx, suspend_hang_detected); + + } else if (state == HOST_SLEEP_EVENT_S3_RESUME) { + /* + * Wake up chipset task and indicate to power state machine that + * listeners need to be notified of chipset resume. + */ + sleep_set_notify(SLEEP_NOTIFY_RESUME); + task_wake(TASK_ID_CHIPSET); + sleep_complete_resume(ctx); + + } +} +#endif /* CONFIG_POWER_TRACK_HOST_SLEEP_STATE */ + +#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 |