summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Barnes <robbarnes@google.com>2020-12-09 13:17:41 -0700
committerCommit Bot <commit-bot@chromium.org>2021-02-12 16:49:07 +0000
commit914a3266dc25ff00e2ca171827f1e94cb5e8c5e9 (patch)
treeffee77507a3300d4cc53a5c25a1fee39650760dd
parenteb0d724fdf3e7ce15ac2b7c13403aefc355f718f (diff)
downloadchrome-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>
-rw-r--r--baseboard/guybrush/baseboard.h3
-rw-r--r--power/amd_x86.c220
2 files changed, 223 insertions, 0 deletions
diff --git a/baseboard/guybrush/baseboard.h b/baseboard/guybrush/baseboard.h
index d9d305d01f..d4999acd88 100644
--- a/baseboard/guybrush/baseboard.h
+++ b/baseboard/guybrush/baseboard.h
@@ -29,7 +29,10 @@
#define CONFIG_POWER_BUTTON_TO_PCH_CUSTOM
#define CONFIG_POWER_BUTTON_X86
#define CONFIG_POWER_COMMON
+#define CONFIG_POWER_S0IX
+#define CONFIG_POWER_SLEEP_FAILURE_DETECTION
#define CONFIG_POWER_SHUTDOWN_PAUSE_IN_S5
+#define CONFIG_POWER_TRACK_HOST_SLEEP_STATE
#define G3_TO_PWRBTN_DELAY_MS 80
#define GPIO_AC_PRESENT GPIO_ACOK_OD
#define GPIO_EN_PWR_A GPIO_EN_PWR_Z1
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;
}