/* Copyright 2022 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "console.h" #include "extpower.h" #include "hooks.h" #include "host_command.h" #include "system.h" #include "util.h" #include #include #include #include LOG_MODULE_DECLARE(ap_pwrseq, CONFIG_AP_PWRSEQ_LOG_LEVEL); /* * Hibernate processing. * * When enabled, the system will be put into an extreme low * power state after the AP is in G3 for a configurable period of time, * and there is no external power connected (i.e on battery). * * The delay has a configurable default, and may be set dynamically * via a host command, or an EC console command. A typical delay * may be 1 hour (3600 seconds). * * AP events such as AP_POWER_HARD_OFF are listened for, and * a timer is used to detect when the AP has been off for the * selected delay time. If the AP is started again, the timer is canceled. * Once the timer expires, the system_hibernate() function is called, * and this will suspend the EC until a wake signal is received. */ static uint32_t hibernate_delay = CONFIG_HIBERNATE_DELAY_SEC; /* * Return true if conditions are right for hibernation. */ static inline bool ready_to_hibernate(void) { return ap_power_in_or_transitioning_to_state(AP_POWER_STATE_HARD_OFF) && !extpower_is_present(); } /* * The AP has been off for the delay period, so hibernate the system, * if ready. Called from system work queue. */ static void hibernate_handler(struct k_work *unused) { if (ready_to_hibernate()) { LOG_INF("System hibernating due to %d seconds AP off", hibernate_delay); system_hibernate(0, 0); } } K_WORK_DEFINE(hibernate_work, hibernate_handler); /* * Hibernate timer handler. * Called when timer has expired. * Schedule hibernate_handler to run via system work queue. */ static void timer_handler(struct k_timer *timer) { k_work_submit(&hibernate_work); } K_TIMER_DEFINE(hibernate_timer, timer_handler, NULL); /* * A change has been detected in either the AP state or the * external power supply. */ static void change_detected(void) { if (ready_to_hibernate()) { /* * AP is off, and there is no external power. * Start the timer if it is not already running. */ if (k_timer_remaining_get(&hibernate_timer) == 0) { k_timer_start(&hibernate_timer, K_SECONDS(hibernate_delay), K_NO_WAIT); } } else { /* * AP is either on, or external power is on. * Either way, no hibernation is done. * Make sure the timer is not running. */ k_timer_stop(&hibernate_timer); } } static void ap_change(struct ap_power_ev_callback *callback, struct ap_power_ev_data data) { change_detected(); } /* * Hook to listen for external power supply changes. */ DECLARE_HOOK(HOOK_AC_CHANGE, change_detected, HOOK_PRIO_DEFAULT); /* * EC Console command to get/set the hibernation delay */ static int command_hibernation_delay(int argc, const char **argv) { char *e; uint32_t remaining; if (argc >= 2) { uint32_t s = strtoi(argv[1], &e, 0); if (*e) return EC_ERROR_PARAM1; hibernate_delay = s; } /* Print the current setting */ ccprintf("Hibernation delay: %d s\n", hibernate_delay); remaining = k_timer_remaining_get(&hibernate_timer); if (remaining == 0) { ccprintf("Timer not running\n"); } else { ccprintf("Time remaining: %d s\n", remaining / 1000); } return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(hibdelay, command_hibernation_delay, "[sec]", "Set the delay before going into hibernation"); /* * Host command to set the hibernation delay */ static enum ec_status host_command_hibernation_delay(struct host_cmd_handler_args *args) { const struct ec_params_hibernation_delay *p = args->params; struct ec_response_hibernation_delay *r = args->response; /* Only change the hibernation delay if seconds is non-zero. */ if (p->seconds) hibernate_delay = p->seconds; r->hibernate_delay = hibernate_delay; /* * It makes no sense to try and set these values since * they are only valid when the AP is in G3 (so this * host command will never be called at that point). */ r->time_g3 = 0; r->time_remaining = 0; args->response_size = sizeof(struct ec_response_hibernation_delay); return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_HIBERNATION_DELAY, host_command_hibernation_delay, EC_VER_MASK(0)); static int hibernate_init(void) { static struct ap_power_ev_callback cb; ap_power_ev_init_callback(&cb, ap_change, AP_POWER_INITIALIZED | AP_POWER_HARD_OFF | AP_POWER_STARTUP); ap_power_ev_add_callback(&cb); return 0; } SYS_INIT(hibernate_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);