diff options
-rw-r--r-- | include/config.h | 1 | ||||
-rw-r--r-- | include/power/falconlite.h | 20 | ||||
-rw-r--r-- | power/build.mk | 1 | ||||
-rw-r--r-- | power/falconlite.c | 508 |
4 files changed, 530 insertions, 0 deletions
diff --git a/include/config.h b/include/config.h index 8ad208c5f3..372b236d10 100644 --- a/include/config.h +++ b/include/config.h @@ -1272,6 +1272,7 @@ * discrete EC control */ #undef CONFIG_CHIPSET_ECDRIVEN /* Mock power module */ +#undef CONFIG_CHIPSET_FALCONLITE /* Falcon-lite*/ #undef CONFIG_CHIPSET_GEMINILAKE /* Intel Geminilake (x86) */ #undef CONFIG_CHIPSET_ICELAKE /* Intel Icelake (x86) */ #undef CONFIG_CHIPSET_JASPERLAKE /* Intel Jasperlake (x86) */ diff --git a/include/power/falconlite.h b/include/power/falconlite.h new file mode 100644 index 0000000000..a2ad54bdd1 --- /dev/null +++ b/include/power/falconlite.h @@ -0,0 +1,20 @@ +/* Copyright 2021 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. + */ + +#ifndef __CROS_EC_POWER_FALCONLITE_H_ +#define __CROS_EC_POWER_FALCONLITE_H_ + +enum power_signal_falconlite { + FCL_AP_WARM_RST_REQ, + FCL_AP_SHUTDOWN_REQ, + FCL_PG_S5, + FCL_PG_VDD1_VDD2, + FCL_PG_VDD_MEDIA_ML, + FCL_PG_VDD_SOC, + FCL_PG_VDD_DDR_OD, + POWER_SIGNAL_COUNT, +}; + +#endif /* __CROS_EC_POWER_FALCONLITE_H_ */ diff --git a/power/build.mk b/power/build.mk index e2b86a055e..3e47167f0a 100644 --- a/power/build.mk +++ b/power/build.mk @@ -26,5 +26,6 @@ power-$(CONFIG_CHIPSET_SC7280)+=qcom.o power-$(CONFIG_CHIPSET_SDM845)+=sdm845.o power-$(CONFIG_CHIPSET_SKYLAKE)+=skylake.o intel_x86.o power-$(CONFIG_CHIPSET_STONEY)+=amd_x86.o +power-$(CONFIG_CHIPSET_FALCONLITE)+=falconlite.o power-$(CONFIG_POWER_COMMON)+=common.o power-$(CONFIG_POWER_TRACK_HOST_SLEEP_STATE)+=host_sleep.o diff --git a/power/falconlite.c b/power/falconlite.c new file mode 100644 index 0000000000..f401d1ff50 --- /dev/null +++ b/power/falconlite.c @@ -0,0 +1,508 @@ +/* Copyright 2021 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. + */ + +/* FalconLite chipset power control module for Chrome EC */ + +#include "charge_state.h" +#include "chipset.h" +#include "common.h" +#include "console.h" +#include "ec_commands.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" +#include "util.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 CPUTS(outstr) cputs(CC_CHIPSET, outstr) +#define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ## args) + +/* 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 SYS_RST_PULSE_LENGTH (30 * MSEC) + +/* Masks for power signals */ +#define IN_PG_S5 POWER_SIGNAL_MASK(FCL_PG_S5) +#define IN_PGOOD (POWER_SIGNAL_MASK(FCL_PG_VDD1_VDD2) | \ + POWER_SIGNAL_MASK(FCL_PG_VDD_MEDIA_ML) | \ + POWER_SIGNAL_MASK(FCL_PG_VDD_SOC) | \ + POWER_SIGNAL_MASK(FCL_PG_VDD_DDR_OD) | \ + POWER_SIGNAL_MASK(FCL_PG_S5)) + +#define IN_ALL_S0 IN_PGOOD +#define IN_ALL_S3 IN_PGOOD + +/* Power signal list. Must match order of enum power_signal. */ +const struct power_signal_info power_signal_list[] = { + [FCL_AP_WARM_RST_REQ] = { + GPIO_AP_EC_WARM_RST_REQ, + POWER_SIGNAL_ACTIVE_HIGH, + "AP_WARM_RST_REQ" + }, + [FCL_AP_SHUTDOWN_REQ] = { + GPIO_AP_EC_SHUTDOWN_REQ_L, + POWER_SIGNAL_ACTIVE_LOW, + "AP_SHUTDOWN_REQ" + }, + [FCL_PG_S5] = { + GPIO_PG_S5_PWR_OD, + POWER_SIGNAL_ACTIVE_HIGH, + "PG_S5" + }, + [FCL_PG_VDD1_VDD2] = { + GPIO_PG_VDD1_VDD2_OD, + POWER_SIGNAL_ACTIVE_HIGH, + "PG_VDD1_VDD2" + }, + [FCL_PG_VDD_MEDIA_ML] = { + GPIO_PG_VDD_MEDIA_ML_OD, + POWER_SIGNAL_ACTIVE_HIGH, + "PG_VDD_MEDIA_ML" + }, + [FCL_PG_VDD_SOC] = { + GPIO_PG_VDD_SOC_OD, + POWER_SIGNAL_ACTIVE_HIGH, + "PG_VDD_SOC" + }, + [FCL_PG_VDD_DDR_OD] = { + GPIO_PG_VDD_DDR_OD, + POWER_SIGNAL_ACTIVE_HIGH, + "PG_VDD_DDR" + }, +}; + + +/* Data structure for a GPIO operation for power sequencing */ +struct power_seq_op { + enum gpio_signal signal; + uint8_t level; + /* Number of milliseconds to delay after setting signal to level */ + uint32_t delay; +}; + +/* + * The entries in the table are handled sequentially from the top + * to the bottom. + */ + +/* The power sequence for POWER_S3S5 */ +static const struct power_seq_op s3s5_power_seq[] = { + {GPIO_EN_VDD_CPU, 0, 0}, + {GPIO_EN_VDD_GPU, 0, 0}, + {GPIO_EN_VDD_MEDIA_ML, 0, 4}, + + {GPIO_EN_VDDQ_VR_D, 0, 4}, /* LPDDR */ + + {GPIO_EN_VDD1_VDD2_VR, 0, 4}, /* LPDDR */ + + {GPIO_EN_VDD_DDR, 0, 4}, + + {GPIO_EN_PP3300A_IO_X, 0, 0}, + {GPIO_EN_PP3300_S3, 0, 4}, + + {GPIO_EN_PP1820A_IO_X, 0, 0}, + {GPIO_EN_PP1800_S3, 0, 0}, +}; + +/* The power sequence for POWER_G3S5 */ +static const struct power_seq_op g3s5_power_seq[] = { + /* delay 10ms as PP1800_S5 uses PP1800_S5 as alaternative supply */ + {GPIO_EN_PP5000_S5, 1, 10}, + + {GPIO_EN_PP1800_S5, 1, 0}, + + {GPIO_EN_PP1800_VDDIO_PMC_X, 1, 4}, + + {GPIO_EN_PP0800_VDD_PMC_X, 1, 0}, + {GPIO_EN_VDD_SOC, 1, 4}, + + {GPIO_EN_PP1800_VDD33_PMC_X, 1, 0}, +}; + +/* This is the power sequence for POWER_S5S3. */ +static const struct power_seq_op s5s3_power_seq[] = { + {GPIO_EN_PP1800_S3, 1, 0}, + {GPIO_EN_PP1820A_IO_X, 1, 4}, + + {GPIO_EN_PP3300_S3, 1, 0}, + {GPIO_EN_PP3300A_IO_X, 1, 4}, + + {GPIO_EN_VDD_DDR, 1, 4}, + + {GPIO_EN_VDD1_VDD2_VR, 1, 4}, /* LPDDR */ + + {GPIO_EN_VDDQ_VR_D, 1, 4}, /* LPDDR */ + + {GPIO_EN_VDD_MEDIA_ML, 1, 0}, + {GPIO_EN_VDD_GPU, 1, 0}, + {GPIO_EN_VDD_CPU, 1, 0}, +}; + +/* The power sequence for POWER_S5G3 */ +static const struct power_seq_op s5g3_power_seq[] = { + {GPIO_EN_PP1800_VDD33_PMC_X, 0, 4}, + + {GPIO_EN_VDD_SOC, 0, 0}, + + {GPIO_EN_PP0800_VDD_PMC_X, 0, 4}, + + {GPIO_EN_PP1800_VDDIO_PMC_X, 0, 4}, + + {GPIO_EN_PP1800_S5, 0, 4}, + + {GPIO_EN_PP5000_S5, 0, 4}, +}; + +/* most recently received sleep event */ +static enum host_sleep_event ap_sleep_event; +/* indicator for shutdown AP */ +static char ap_shutdown; +/* indicator for boot AP from off state */ +static char boot_from_off; + +static void reset_request_interrupt_deferred(void) +{ + chipset_reset(CHIPSET_RESET_AP_REQ); +} +DECLARE_DEFERRED(reset_request_interrupt_deferred); + +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. + */ + ap_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 */ + ap_shutdown = 0; + boot_from_off = 1; + CPRINTS("PWRON:BTN"); + chipset_exit_hard_off(); +} +DECLARE_DEFERRED(chipset_exit_hard_off_button); + +void chipset_reset_request_interrupt(enum gpio_signal signal) +{ + /* + * indicator for the following reset is a reboot or a AP requested + * shutdown. + */ + static char want_reboot; + + if (signal == GPIO_AP_EC_WARM_RST_REQ) { + CPRINTS("AP wants reboot"); + hook_call_deferred(&reset_request_interrupt_deferred_data, 0); + want_reboot = 1; + } else if (signal == GPIO_AP_EC_SHUTDOWN_REQ_L) { + /* + * When AP_SHUTDOWN_REQ_L is asserted, we have to check if + * there is a AP_EC_WARM_RST_REQ interrupt prior to this one, + * and that would be a reboot request, rather than a + * shutdown. + */ + if (!gpio_get_level(signal) && !want_reboot) { + CPRINTS("AP wants shutdown"); + ap_shutdown = 1; + } + want_reboot = 0; + } + power_signal_interrupt(signal); +} + +enum power_state power_chipset_init(void) +{ + uint32_t reset_flags = system_get_reset_flags(); + int exit_hard_off = 1; + + /* Enable reboot / sleep control inputs from AP */ + gpio_enable_interrupt(GPIO_AP_EC_WARM_RST_REQ); + gpio_enable_interrupt(GPIO_AP_EC_SHUTDOWN_REQ_L); + + if (reset_flags & EC_RESET_FLAG_SYSJUMP) { + if ((power_get_signals() & IN_ALL_S0) == IN_ALL_S0) { + disable_sleep(SLEEP_MASK_AP_RUN); + CPRINTS("already in S0"); + return POWER_S0; + } + } else if (reset_flags & EC_RESET_FLAG_AP_OFF) { + exit_hard_off = 0; + } else if ((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) { + CPRINTS("PWRON:0x%x", reset_flags); + ap_shutdown = 0; + boot_from_off = 1; + /* Auto-power on */ + chipset_exit_hard_off(); + } + + /* Start from S5 if the rail is already up. */ + if (power_get_signals() & IN_PG_S5) { + /* Force shutdown from S5 if the rails is already up. */ + if (!exit_hard_off) + ap_shutdown = 1; + return POWER_S5; + } + + return POWER_G3; +} + +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); +} +/** + * Step through the power sequence table and do corresponding GPIO operations. + * + * @param power_seq_ops The pointer to the power sequence table. + * @param op_count The number of entries of power_seq_ops. + */ +static void power_seq_run(const struct power_seq_op *power_seq_ops, + int op_count) +{ + int i; + + for (i = 0; i < op_count; i++) { + GPIO_SET_LEVEL(power_seq_ops[i].signal, + power_seq_ops[i].level); + if (!power_seq_ops[i].delay) + continue; + msleep(power_seq_ops[i].delay); + } +} + +enum power_state power_handle_state(enum power_state state) +{ + /* Retry S5->S3 transition, if not zero. */ + static int s5s3_retry; + + switch (state) { + case POWER_G3: + break; + + case POWER_S5: + if (boot_from_off) { + s5s3_retry = 1; + return POWER_S5S3; + } + + /* + * 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) || ap_shutdown) + return POWER_S3S5; + else if (ap_sleep_event == HOST_SLEEP_EVENT_S3_RESUME || + boot_from_off) + return POWER_S3S0; + break; + + case POWER_S0: + if (ap_sleep_event == HOST_SLEEP_EVENT_S3_SUSPEND || + !power_has_signals(IN_ALL_S0) || ap_shutdown) + return POWER_S0S3; + break; + + case POWER_G3S5: + ap_shutdown = 0; + power_seq_run(g3s5_power_seq, ARRAY_SIZE(g3s5_power_seq)); + + /* Power up to next state, or go back */ + if (power_get_signals() & IN_PG_S5) + return POWER_S5; + else + return POWER_G3; + break; + + case POWER_S5S3: + hook_notify(HOOK_CHIPSET_PRE_INIT); + + power_seq_run(s5s3_power_seq, ARRAY_SIZE(s5s3_power_seq)); + + /* + * Wait for rails up. Retry if it fails + * (it may take 2 attempts on restart after we use + * force reset). + */ + if (!power_has_signals(IN_ALL_S3)) { + if (s5s3_retry) { + s5s3_retry = 0; + return POWER_S5S3; + } + boot_from_off = 0; + /* Give up, go back to G3. */ + return POWER_S5G3; + } + + GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 1); + + /* Call hooks now that rails are up */ + hook_notify(HOOK_CHIPSET_STARTUP); + + /* Power up to next state */ + return POWER_S3; + + case POWER_S3S5: + /* Call hooks before we remove power rails */ + hook_notify(HOOK_CHIPSET_SHUTDOWN); + + GPIO_SET_LEVEL(GPIO_SYS_RST_ODL, 0); + power_seq_run(s3s5_power_seq, ARRAY_SIZE(s3s5_power_seq)); + + /* Call hooks after we remove power rails */ + hook_notify(HOOK_CHIPSET_SHUTDOWN_COMPLETE); + + /* Start shutting down */ + return POWER_S5; + + 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()) { + ap_shutdown = 1; + hook_call_deferred(&chipset_force_shutdown_button_data, + -1); + } + + return POWER_S3; + + case POWER_S3S0: + if (power_wait_signals(IN_ALL_S0)) { + chipset_force_shutdown(CHIPSET_SHUTDOWN_WAIT); + return POWER_S0S3; + } + boot_from_off = 0; + + /* 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_S5G3: + power_seq_run(s5g3_power_seq, ARRAY_SIZE(s5g3_power_seq)); + 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_POWER_TRACK_HOST_SLEEP_STATE +__override void power_chipset_handle_host_sleep_event( + enum host_sleep_event state, + struct host_sleep_event_context *ctx) +{ + CPRINTS("Handle sleep: %d", state); + + ap_sleep_event = state; + + if (state == HOST_SLEEP_EVENT_S3_RESUME || + state == HOST_SLEEP_EVENT_S3_SUSPEND) + task_wake(TASK_ID_CHIPSET); +} +#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)) { + CPRINTS("PWRON:LIDOPEN"); + ap_shutdown = 0; + boot_from_off = 1; + chipset_exit_hard_off(); + } +} +DECLARE_HOOK(HOOK_LID_CHANGE, lid_changed, HOOK_PRIO_DEFAULT); +#endif |