summaryrefslogtreecommitdiff
path: root/zephyr/subsys/ap_pwrseq/x86_non_dsx_common_pwrseq_host_sleep.c
diff options
context:
space:
mode:
Diffstat (limited to 'zephyr/subsys/ap_pwrseq/x86_non_dsx_common_pwrseq_host_sleep.c')
-rw-r--r--zephyr/subsys/ap_pwrseq/x86_non_dsx_common_pwrseq_host_sleep.c179
1 files changed, 179 insertions, 0 deletions
diff --git a/zephyr/subsys/ap_pwrseq/x86_non_dsx_common_pwrseq_host_sleep.c b/zephyr/subsys/ap_pwrseq/x86_non_dsx_common_pwrseq_host_sleep.c
new file mode 100644
index 0000000000..015cecb502
--- /dev/null
+++ b/zephyr/subsys/ap_pwrseq/x86_non_dsx_common_pwrseq_host_sleep.c
@@ -0,0 +1,179 @@
+/* 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 <ap_power_host_sleep.h>
+#include <x86_non_dsx_common_pwrseq_sm_handler.h>
+
+static uint16_t sleep_signal_timeout;
+static uint16_t host_sleep_timeout_default = CONFIG_SLEEP_TIMEOUT_MS;
+static uint32_t sleep_signal_transitions;
+static enum sleep_hang_type timeout_hang_type;
+
+static void sleep_transition_timeout(struct k_work *work);
+
+static K_WORK_DELAYABLE_DEFINE(sleep_transition_timeout_data,
+ sleep_transition_timeout);
+
+/**
+ * Type of sleep hang detected
+ */
+enum sleep_hang_type {
+ SLEEP_HANG_NONE,
+ SLEEP_HANG_S0IX_SUSPEND,
+ SLEEP_HANG_S0IX_RESUME
+};
+
+void power_chipset_handle_sleep_hang(enum sleep_hang_type hang_type)
+{
+ /*
+ * 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 (pwr_sm_get_state() == SYS_POWER_STATE_S0) {
+ host_event_t sleep_wake_mask;
+
+ ap_power_get_lazy_wake_mask(SYS_POWER_STATE_S0ix,
+ &sleep_wake_mask);
+ lpc_set_host_event_mask(LPC_HOST_EVENT_WAKE, sleep_wake_mask);
+ }
+
+ ccprintf("Warning: Detected sleep hang! Waking host up!");
+ host_set_single_event(EC_HOST_EVENT_HANG_DETECT);
+}
+
+static void sleep_transition_timeout(struct k_work *work)
+{
+ /* Mark the timeout. */
+ sleep_signal_transitions |= EC_HOST_RESUME_SLEEP_TIMEOUT;
+ k_work_cancel_delayable(&sleep_transition_timeout_data);
+
+ if (timeout_hang_type != SLEEP_HANG_NONE) {
+ power_chipset_handle_sleep_hang(timeout_hang_type);
+ }
+}
+
+static void sleep_increment_transition(void)
+{
+ if ((sleep_signal_transitions & EC_HOST_RESUME_SLEEP_TRANSITIONS_MASK) <
+ EC_HOST_RESUME_SLEEP_TRANSITIONS_MASK)
+ sleep_signal_transitions += 1;
+}
+
+void sleep_suspend_transition(void)
+{
+ sleep_increment_transition();
+ k_work_cancel_delayable(&sleep_transition_timeout_data);
+}
+
+void sleep_resume_transition(void)
+{
+ sleep_increment_transition();
+
+ /*
+ * Start the timer again to ensure the AP doesn't get itself stuck in
+ * a state where it's no longer in a sleep state (S0ix/S3), but from
+ * the Linux perspective is still suspended. Perhaps a bug in the SoC-
+ * internal periodic housekeeping code might result in a situation
+ * like this.
+ */
+ if (sleep_signal_timeout) {
+ timeout_hang_type = SLEEP_HANG_S0IX_RESUME;
+ k_work_schedule(&sleep_transition_timeout_data,
+ K_MSEC(sleep_signal_timeout));
+ }
+}
+
+void sleep_start_suspend(void)
+{
+ uint16_t timeout = host_get_sleep_timeout();
+
+ sleep_signal_transitions = 0;
+
+ /* Use 0xFFFF to disable the timeout */
+ if (timeout == EC_HOST_SLEEP_TIMEOUT_INFINITE) {
+ sleep_signal_timeout = 0;
+ return;
+ }
+
+ /* Use zero internally to indicate host doesn't set timeout value;
+ * we will use default timeout.
+ */
+ if (timeout == EC_HOST_SLEEP_TIMEOUT_DEFAULT) {
+ timeout = host_sleep_timeout_default;
+ }
+
+ sleep_signal_timeout = timeout;
+ timeout_hang_type = SLEEP_HANG_S0IX_SUSPEND;
+ k_work_schedule(&sleep_transition_timeout_data, K_MSEC(timeout));
+}
+
+void sleep_complete_resume(void)
+{
+ /*
+ * Ensure we don't schedule another sleep_transition_timeout
+ * if the the HOST_SLEEP_EVENT_S0IX_RESUME message arrives before
+ * the CHIPSET task transitions to the POWER_S0ixS0 state.
+ */
+ sleep_signal_timeout = 0;
+ k_work_cancel_delayable(&sleep_transition_timeout_data);
+ host_set_sleep_transitions(sleep_signal_transitions);
+}
+
+void sleep_reset_tracking(void)
+{
+ sleep_signal_transitions = 0;
+ sleep_signal_timeout = 0;
+ timeout_hang_type = SLEEP_HANG_NONE;
+}
+
+/*
+ * s0ix event handler.
+ */
+static void ap_power_sleep_event_handler(struct ap_power_ev_callback *cb,
+ struct ap_power_ev_data data)
+{
+ switch (data.event) {
+ case AP_POWER_S0IX_SUSPEND_START:
+ sleep_start_suspend();
+ break;
+ case AP_POWER_S0IX_SUSPEND:
+ sleep_suspend_transition();
+ break;
+ case AP_POWER_S0IX_RESUME:
+ sleep_resume_transition();
+ break;
+ case AP_POWER_S0IX_RESUME_COMPLETE:
+ sleep_complete_resume();
+ break;
+ case AP_POWER_S0IX_RESET_TRACKING:
+ sleep_reset_tracking();
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Registers callback for s0ix events.
+ */
+static int ap_power_sleep_s0ix_event(const struct device *unused)
+{
+ static struct ap_power_ev_callback cb;
+
+ /*
+ * Register for all events.
+ */
+ ap_power_ev_init_callback(
+ &cb, ap_power_sleep_event_handler,
+ AP_POWER_S0IX_SUSPEND_START | AP_POWER_S0IX_SUSPEND |
+ AP_POWER_S0IX_RESUME | AP_POWER_S0IX_RESUME_COMPLETE |
+ AP_POWER_S0IX_RESET_TRACKING);
+ ap_power_ev_add_callback(&cb);
+ return 0;
+}
+SYS_INIT(ap_power_sleep_s0ix_event, APPLICATION, 1);