diff options
author | Rob Barnes <robbarnes@google.com> | 2020-12-09 13:17:41 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2021-02-12 16:49:07 +0000 |
commit | 914a3266dc25ff00e2ca171827f1e94cb5e8c5e9 (patch) | |
tree | ffee77507a3300d4cc53a5c25a1fee39650760dd /power | |
parent | eb0d724fdf3e7ce15ac2b7c13403aefc355f718f (diff) | |
download | chrome-ec-914a3266dc25ff00e2ca171827f1e94cb5e8c5e9.tar.gz |
guybrush: Enable S0ix for amd_x86
Enable S0ix for amd_x86. This closely follows the
intel_x86.c implementation. b/179294969 tracks merging
intel_x86.c and amd_x86.c
BUG=b:175234270
BRANCH=None
TEST=Build for Guybrush
Boot Zork, enter and leave suspend.
Note, Zork does not support S0ix
Change-Id: I874d2e9019fcc162c7ebfb6091b179ba482a4e47
Signed-off-by: Rob Barnes <robbarnes@google.com>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2673905
Reviewed-by: Diana Z <dzigterman@chromium.org>
Diffstat (limited to 'power')
-rw-r--r-- | power/amd_x86.c | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/power/amd_x86.c b/power/amd_x86.c index 7924d6f361..e7e706c4cc 100644 --- a/power/amd_x86.c +++ b/power/amd_x86.c @@ -12,6 +12,7 @@ #include "gpio.h" #include "hooks.h" #include "lid_switch.h" +#include "lpc.h" #include "power.h" #include "power_button.h" #include "system.h" @@ -161,6 +162,153 @@ static void handle_pass_through(enum gpio_signal pin_in, CPRINTS("Pass through %s: %d", gpio_get_name(pin_in), in_level); } +#ifdef CONFIG_POWER_S0IX +/* + * Backup copies of SCI and SMI mask to preserve across S0ix suspend/resume + * cycle. If the host uses S0ix, BIOS is not involved during suspend and resume + * operations and hence SCI/SMI masks are programmed only once during boot-up. + * + * These backup variables are set whenever host expresses its interest to + * enter S0ix and then lpc_host_event_mask for SCI and SMI are cleared. When + * host resumes from S0ix, masks from backup variables are copied over to + * lpc_host_event_mask for SCI and SMI. + */ +static host_event_t backup_sci_mask; +static host_event_t backup_smi_mask; + +/* + * Clear host event masks for SMI and SCI when host is entering S0ix. This is + * done to prevent any SCI/SMI interrupts when the host is in suspend. Since + * BIOS is not involved in the suspend path, EC needs to take care of clearing + * these masks. + */ +static void lpc_s0ix_suspend_clear_masks(void) +{ + backup_sci_mask = lpc_get_host_event_mask(LPC_HOST_EVENT_SCI); + backup_smi_mask = lpc_get_host_event_mask(LPC_HOST_EVENT_SMI); + + lpc_set_host_event_mask(LPC_HOST_EVENT_SCI, 0); + lpc_set_host_event_mask(LPC_HOST_EVENT_SMI, 0); +} + +/* + * Restore host event masks for SMI and SCI when host exits S0ix. This is done + * because BIOS is not involved in the resume path and so EC needs to restore + * the masks from backup variables. + */ +static void lpc_s0ix_resume_restore_masks(void) +{ + /* + * No need to restore SCI/SMI masks if both backup_sci_mask and + * backup_smi_mask are zero. This indicates that there was a failure to + * enter S0ix(SLP_S0# assertion) and hence SCI/SMI masks were never + * backed up. + */ + if (!backup_sci_mask && !backup_smi_mask) + return; + + lpc_set_host_event_mask(LPC_HOST_EVENT_SCI, backup_sci_mask); + lpc_set_host_event_mask(LPC_HOST_EVENT_SMI, backup_smi_mask); + + backup_sci_mask = backup_smi_mask = 0; +} + +static void lpc_s0ix_hang_detected(void) +{ + /* + * Wake up the AP so they don't just chill in a non-suspended state and + * burn power. Overload a vaguely related event bit since event bits are + * at a premium. If the system never entered S0ix, then manually set the + * wake mask to pretend it did, so that the hang detect event wakes the + * system. + */ + if (power_get_state() == POWER_S0) { + host_event_t sleep_wake_mask; + + get_lazy_wake_mask(POWER_S0ix, &sleep_wake_mask); + lpc_set_host_event_mask(LPC_HOST_EVENT_WAKE, sleep_wake_mask); + } + + CPRINTS("Warning: Detected sleep hang! Waking host up!"); + host_set_single_event(EC_HOST_EVENT_HANG_DETECT); +} + +static void handle_chipset_suspend(void) +{ + /* Clear masks before any hooks are run for suspend. */ + lpc_s0ix_suspend_clear_masks(); +} +DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, handle_chipset_suspend, HOOK_PRIO_FIRST); + +static void handle_chipset_reset(void) +{ + if (chipset_in_state(CHIPSET_STATE_STANDBY)) { + CPRINTS("chipset reset: exit s0ix"); + power_reset_host_sleep_state(); + task_wake(TASK_ID_CHIPSET); + } +} +DECLARE_HOOK(HOOK_CHIPSET_RESET, handle_chipset_reset, HOOK_PRIO_FIRST); + +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); +} + +#endif /* CONFIG_POWER_S0IX */ + +#ifdef CONFIG_POWER_TRACK_HOST_SLEEP_STATE + +__overridable void power_board_handle_host_sleep_event( + enum host_sleep_event state) +{ + /* Default weak implementation -- no action required. */ +} + +__override void power_chipset_handle_host_sleep_event( + enum host_sleep_event state, + struct host_sleep_event_context *ctx) +{ + power_board_handle_host_sleep_event(state); + +#ifdef CONFIG_POWER_S0IX + if (state == HOST_SLEEP_EVENT_S0IX_SUSPEND) { + /* + * Indicate to power state machine that a new host event for + * s0ix/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, lpc_s0ix_hang_detected); + power_signal_enable_interrupt(GPIO_PCH_SLP_S0_L); + } else if (state == HOST_SLEEP_EVENT_S0IX_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); + lpc_s0ix_resume_restore_masks(); + power_signal_disable_interrupt(GPIO_PCH_SLP_S0_L); + sleep_complete_resume(ctx); + /* + * If the sleep signal timed out and never transitioned, then + * the wake mask was modified to its suspend state (S0ix), so + * that the event wakes the system. Explicitly restore the wake + * mask to its S0 state now. + */ + power_update_wake_mask(); + } else if (state == HOST_SLEEP_EVENT_DEFAULT_RESET) { + power_signal_disable_interrupt(GPIO_PCH_SLP_S0_L); + } +#endif /* CONFIG_POWER_S0IX */ +} +#endif /* CONFIG_POWER_TRACK_HOST_SLEEP_STATE */ + enum power_state power_handle_state(enum power_state state) { handle_pass_through(GPIO_S5_PGOOD, GPIO_PCH_RSMRST_L); @@ -216,6 +364,13 @@ enum power_state power_handle_state(enum power_state state) /* Call hooks now that rails are up */ hook_notify(HOOK_CHIPSET_STARTUP); +#ifdef CONFIG_POWER_S0IX + /* + * Clearing the S0ix flag on the path to S0 + * to handle any reset conditions. + */ + power_reset_host_sleep_state(); +#endif return POWER_S3; case POWER_S3: @@ -240,6 +395,8 @@ enum power_state power_handle_state(enum power_state state) /* Enable wireless */ wireless_set_state(WIRELESS_ON); + lpc_s3_resume_clear_masks(); + /* Call hooks now that rails are up */ hook_notify(HOOK_CHIPSET_RESUME); @@ -259,6 +416,26 @@ enum power_state power_handle_state(enum power_state state) /* Power down to next state */ return POWER_S0S3; } +#ifdef CONFIG_POWER_S0IX + /* + * SLP_S0 may assert in system idle scenario without a kernel + * freeze call. This may cause interrupt storm since there is + * no freeze/unfreeze of threads/process in the idle scenario. + * Ignore the SLP_S0 assertions in idle scenario by checking + * the host sleep state. + */ + else if (power_get_host_sleep_state() + == HOST_SLEEP_EVENT_S0IX_SUSPEND && + gpio_get_level(GPIO_PCH_SLP_S0_L) == 0) { + return POWER_S0S0ix; + } + /* + * Call hooks only if we haven't notified listeners of S0ix + * resume. + */ + sleep_notify_transition(SLEEP_NOTIFY_RESUME, + HOOK_CHIPSET_RESUME); +#endif break; case POWER_S0S3: @@ -274,6 +451,10 @@ enum power_state power_handle_state(enum power_state state) */ enable_sleep(SLEEP_MASK_AP_RUN); +#ifdef CONFIG_POWER_S0IX + /* re-init S0ix flag */ + power_reset_host_sleep_state(); +#endif return POWER_S3; case POWER_S3S5: @@ -293,6 +474,45 @@ enum power_state power_handle_state(enum power_state state) return POWER_G3; +#ifdef CONFIG_POWER_S0IX + case POWER_S0ix: + /* System in S0 only if SLP_S0 and SLP_S3 are de-asserted */ + if ((gpio_get_level(GPIO_PCH_SLP_S0_L) == 1) && + (gpio_get_level(GPIO_PCH_SLP_S3_L) == 1)) { + return POWER_S0ixS0; + } else if (!power_has_signals(IN_S5_PGOOD)) { + /* Lost power, start transition to G3 */ + return POWER_S0; + } + + break; + + case POWER_S0S0ix: + /* + * Call hooks only if we haven't notified listeners of S0ix + * suspend. + */ + sleep_notify_transition(SLEEP_NOTIFY_SUSPEND, + HOOK_CHIPSET_SUSPEND); + sleep_suspend_transition(); + + /* + * Enable idle task deep sleep. Allow the low power idle task + * to go into deep sleep in S0ix. + */ + enable_sleep(SLEEP_MASK_AP_RUN); + return POWER_S0ix; + + case POWER_S0ixS0: + /* + * 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); + + sleep_resume_transition(); + return POWER_S0; +#endif /* CONFIG_POWER_S0IX */ default: break; } |