/* Copyright 2021 The ChromiumOS Authors * 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 "builtin/assert.h" #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_AP_WATCHDOG] = { GPIO_AP_EC_WATCHDOG_L, POWER_SIGNAL_ACTIVE_LOW, "AP_WDT" }, [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. In the meantime, the WDT should not be asserted, * or this is a WDT reset, which will be handled by AP. */ if (gpio_get_level(GPIO_AP_EC_WATCHDOG_L) && !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_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); } /** * 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; default: CPRINTS("Unexpected power state %d", state); ASSERT(0); 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 __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