summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Yilun Lin <yllin@chromium.org>2021-07-23 13:42:28 +0800
committerCommit Bot <commit-bot@chromium.org>2021-11-05 09:16:09 +0000
commite43da54b185221ed19b246f9e07bc6fbefb688cc (patch)
tree9c5249881e90f8aa737d226a8639747879a4c039
parent72878ab78497403542f1f50e740e03f11ac91f03 (diff)
downloadchrome-ec-e43da54b185221ed19b246f9e07bc6fbefb688cc.tar.gz
power: support falcon-lite power sequencing
Copy and modified from power/mt8192.c There's no PMIC in falcon-lite, and EC has to control the rails. Besides PGOOD signals, and power enable pins, the important rails are: - AP_EC_WARM_RST_REQ: AP issues a warm reset request and EC toggles SYS_RST_ODL for a S0->S0 transition. - AP_EC_SHUTDOWN_REQ_L: This pin is repurposed from AP_IN_SLEEP_L. AP issues a shutdown request and EC forces it in to off state. - AP_EC_WATCHDOG_L: This pin is unused since AP is able to reset itself when WDT happened. There's no S3 signals for falcon-lite since AP_IN_SLEEP_L is repurposed, so we used host power info for S3/S0 transition. BUG=b:185846337 TEST=Tried the following cases: * Cold reset: $ dut-control cold_reset:on sleep:0.2 cold_reset:off Result: G3 -> S0 * Long power press to shutdown: $ dut-control pwr_button:press sleep:8.2 pwr_button:release Result: S0 -> S5 -> G3 * Long power press to power-on but then shutdown: $ dut-control pwr_button:press sleep:8.2 pwr_button:release Result: G3 -> S0 -> S5 -> G3 * Short power press to power-on: $ dut-control pwr_button:press sleep:0.2 pwr_button:release Result: G3 -> S0 * Console command: apreset Result: 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=main Change-Id: I239975513286b062f80184f91e565d2bd6fa01ac Signed-off-by: Eric Yilun Lin <yllin@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3045436 Tested-by: Eric Yilun Lin <yllin@google.com> Auto-Submit: Eric Yilun Lin <yllin@google.com> Reviewed-by: Ting Shen <phoenixshen@chromium.org> Commit-Queue: Ting Shen <phoenixshen@chromium.org>
-rw-r--r--include/config.h1
-rw-r--r--include/power/falconlite.h20
-rw-r--r--power/build.mk1
-rw-r--r--power/falconlite.c508
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