summaryrefslogtreecommitdiff
path: root/zephyr/subsys/ap_pwrseq/power_host_sleep.c
blob: ff512fa9410eff5394bce110390b5d2fbae8dddd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/* Copyright 2022 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <ap_power/ap_power_interface.h>
#include <x86_non_dsx_common_pwrseq_sm_handler.h>

LOG_MODULE_DECLARE(ap_pwrseq, CONFIG_AP_PWRSEQ_LOG_LEVEL);

#if CONFIG_PLATFORM_EC_HOST_INTERFACE_ESPI

/* If host doesn't program S0ix lazy wake mask, use default S0ix mask */
#define DEFAULT_WAKE_MASK_S0IX  (EC_HOST_EVENT_MASK(EC_HOST_EVENT_LID_OPEN) | \
				EC_HOST_EVENT_MASK(EC_HOST_EVENT_MODE_CHANGE))

/*
 * Set the wake mask according to the current power state:
 * 1. On transition to S0, wake mask is reset.
 * 2. In non-S0 states, active mask set by host gets a higher preference.
 * 3. If host has not set any active mask, then check if a lazy mask exists
 *    for the current power state.
 * 4. If state is S0ix and no lazy or active wake mask is set, then use default
 *    S0ix mask to be compatible with older BIOS versions.
 */
void power_update_wake_mask(void)
{
	host_event_t wake_mask;
	enum power_states_ndsx state;

	state = pwr_sm_get_state();

	if (state == SYS_POWER_STATE_S0)
		wake_mask = 0;
	else if (lpc_is_active_wm_set_by_host() ||
		ap_power_get_lazy_wake_mask(state, &wake_mask))
		return;
#if CONFIG_AP_PWRSEQ_S0IX
	if ((state == SYS_POWER_STATE_S0ix) && (wake_mask == 0))
		wake_mask = DEFAULT_WAKE_MASK_S0IX;
#endif

	lpc_set_host_event_mask(LPC_HOST_EVENT_WAKE, wake_mask);
}

static void power_update_wake_mask_deferred(struct k_work *work)
{
	power_update_wake_mask();
}

static K_WORK_DELAYABLE_DEFINE(
	power_update_wake_mask_deferred_data, power_update_wake_mask_deferred);

void ap_power_set_active_wake_mask(void)
{
	int rv;

	/*
	 * Allow state machine to stabilize and update wake mask after 5msec. It
	 * was observed that on platforms where host wakes up periodically from
	 * S0ix for hardware book-keeping activities, there is a small window
	 * where host is not really up and running software, but still SLP_S0#
	 * is de-asserted and hence setting wake mask right away can cause user
	 * wake events to be missed.
	 *
	 * Time for deferred callback was chosen to be 5msec based on the fact
	 * that it takes ~2msec for the periodic wake cycle to complete on the
	 * host for KBL.
	 */
	rv = k_work_schedule(&power_update_wake_mask_deferred_data, K_MSEC(5));
	if (rv == 0) {
		/*
		 * A work is already scheduled or submitted, since power state
		 * has changed again and the work is not processed, we should
		 * reschedule it.
		 */
		rv = k_work_reschedule(
			&power_update_wake_mask_deferred_data, K_MSEC(5));
	}
	__ASSERT(rv >= 0, "Set wake mask work queue error");
}

#else /* CONFIG_PLATFORM_EC_HOST_INTERFACE_ESPI */
static void ap_power_set_active_wake_mask(void) { }
#endif /* CONFIG_PLATFORM_EC_HOST_INTERFACE_ESPI */

#if CONFIG_AP_PWRSEQ_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;

/* Flag to notify listeners about suspend/resume events. */
enum ap_power_sleep_type sleep_state = AP_POWER_SLEEP_NONE;

/*
 * 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 power_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 power_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;
}

/*
 * Following functions are called in the S0ix path, not S3 path.
 */

/*
 * Notify the sleep type that is going to transit to; this is a token to
 * ensure both host sleep event passed by Host Command and SLP_S0 satisfy
 * the conditions to suspend or resume.
 *
 * @param new_state Notified sleep type
 */
static void ap_power_sleep_set_notify(enum ap_power_sleep_type new_state)
{
	sleep_state = new_state;
}

enum ap_power_sleep_type ap_power_sleep_get_notify(void)
{
	return sleep_state;
}

void ap_power_sleep_notify_transition(enum ap_power_sleep_type check_state)
{
	if (sleep_state != check_state)
		return;

	if (check_state == AP_POWER_SLEEP_SUSPEND) {
		/*
		 * Transition to S0ix;
		 * clear mask before others running suspend.
		 */
		power_s0ix_suspend_clear_masks();
		ap_power_ev_send_callbacks(AP_POWER_SUSPEND);
	} else if (check_state == AP_POWER_SLEEP_RESUME) {
		ap_power_ev_send_callbacks(AP_POWER_RESUME);
	}

	/* Transition is done; reset sleep state. */
	ap_power_sleep_set_notify(AP_POWER_SLEEP_NONE);
}
#endif /* CONFIG_AP_PWRSEQ_S0IX */

#if CONFIG_AP_PWRSEQ_HOST_SLEEP
#define HOST_SLEEP_EVENT_DEFAULT_RESET 0

void ap_power_reset_host_sleep_state(void)
{
	power_set_host_sleep_state(HOST_SLEEP_EVENT_DEFAULT_RESET);
	ap_power_chipset_handle_host_sleep_event(
			HOST_SLEEP_EVENT_DEFAULT_RESET, NULL);
}

/* TODO: hook to reset event */
void ap_power_handle_chipset_reset(void)
{
	if (ap_power_in_state(AP_POWER_STATE_STANDBY))
		ap_power_reset_host_sleep_state();
}

void ap_power_chipset_handle_host_sleep_event(
		enum host_sleep_event state,
		struct host_sleep_event_context *ctx)
{
	LOG_DBG("host sleep event = %d!", state);
#if CONFIG_AP_PWRSEQ_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.
		 */
		ap_power_sleep_set_notify(AP_POWER_SLEEP_SUSPEND);
		power_signal_enable(PWR_SLP_S0);

	} else if (state == HOST_SLEEP_EVENT_S0IX_RESUME) {
		/*
		 * Set sleep state to resume; restore SCI/SMI masks;
		 * SLP_S0 should be de-asserted already, disable interrupt.
		 */
		ap_power_sleep_set_notify(AP_POWER_SLEEP_RESUME);
		power_s0ix_resume_restore_masks();
		power_signal_disable(PWR_SLP_S0);

		/*
		 * 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(PWR_SLP_S0);
	}
#endif /* CONFIG_AP_PWRSEQ_S0IX */
}

#endif /* CONFIG_AP_PWRSEQ_HOST_SLEEP */