1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Architecture-specific ACPI-based support for suspend-to-idle. |
4 | * |
5 | * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> |
6 | * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> |
7 | * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> |
8 | * |
9 | * On platforms supporting the Low Power S0 Idle interface there is an ACPI |
10 | * device object with the PNP0D80 compatible device ID (System Power Management |
11 | * Controller) and a specific _DSM method under it. That method, if present, |
12 | * can be used to indicate to the platform that the OS is transitioning into a |
13 | * low-power state in which certain types of activity are not desirable or that |
14 | * it is leaving such a state, which allows the platform to adjust its operation |
15 | * mode accordingly. |
16 | */ |
17 | |
18 | #include <linux/acpi.h> |
19 | #include <linux/device.h> |
20 | #include <linux/dmi.h> |
21 | #include <linux/suspend.h> |
22 | |
23 | #include "../sleep.h" |
24 | |
25 | #ifdef CONFIG_SUSPEND |
26 | |
27 | static bool sleep_no_lps0 __read_mostly; |
28 | module_param(sleep_no_lps0, bool, 0644); |
29 | MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface" ); |
30 | |
31 | static const struct acpi_device_id lps0_device_ids[] = { |
32 | {"PNP0D80" , }, |
33 | {"" , }, |
34 | }; |
35 | |
36 | /* Microsoft platform agnostic UUID */ |
37 | #define ACPI_LPS0_DSM_UUID_MICROSOFT "11e00d56-ce64-47ce-837b-1f898f9aa461" |
38 | |
39 | #define ACPI_LPS0_DSM_UUID "c4eb40a0-6cd2-11e2-bcfd-0800200c9a66" |
40 | |
41 | #define ACPI_LPS0_GET_DEVICE_CONSTRAINTS 1 |
42 | #define ACPI_LPS0_SCREEN_OFF 3 |
43 | #define ACPI_LPS0_SCREEN_ON 4 |
44 | #define ACPI_LPS0_ENTRY 5 |
45 | #define ACPI_LPS0_EXIT 6 |
46 | #define ACPI_LPS0_MS_ENTRY 7 |
47 | #define ACPI_LPS0_MS_EXIT 8 |
48 | |
49 | /* AMD */ |
50 | #define ACPI_LPS0_DSM_UUID_AMD "e3f32452-febc-43ce-9039-932122d37721" |
51 | #define ACPI_LPS0_ENTRY_AMD 2 |
52 | #define ACPI_LPS0_EXIT_AMD 3 |
53 | #define ACPI_LPS0_SCREEN_OFF_AMD 4 |
54 | #define ACPI_LPS0_SCREEN_ON_AMD 5 |
55 | |
56 | static acpi_handle lps0_device_handle; |
57 | static guid_t lps0_dsm_guid; |
58 | static int lps0_dsm_func_mask; |
59 | |
60 | static guid_t lps0_dsm_guid_microsoft; |
61 | static int lps0_dsm_func_mask_microsoft; |
62 | static int lps0_dsm_state; |
63 | |
64 | /* Device constraint entry structure */ |
65 | struct lpi_device_info { |
66 | char *name; |
67 | int enabled; |
68 | union acpi_object *package; |
69 | }; |
70 | |
71 | /* Constraint package structure */ |
72 | struct lpi_device_constraint { |
73 | int uid; |
74 | int min_dstate; |
75 | int function_states; |
76 | }; |
77 | |
78 | struct lpi_constraints { |
79 | acpi_handle handle; |
80 | int min_dstate; |
81 | }; |
82 | |
83 | /* AMD Constraint package structure */ |
84 | struct lpi_device_constraint_amd { |
85 | char *name; |
86 | int enabled; |
87 | int function_states; |
88 | int min_dstate; |
89 | }; |
90 | |
91 | static LIST_HEAD(lps0_s2idle_devops_head); |
92 | |
93 | static struct lpi_constraints *lpi_constraints_table; |
94 | static int lpi_constraints_table_size; |
95 | static int rev_id; |
96 | |
97 | #define for_each_lpi_constraint(entry) \ |
98 | for (int i = 0; \ |
99 | entry = &lpi_constraints_table[i], i < lpi_constraints_table_size; \ |
100 | i++) |
101 | |
102 | static void lpi_device_get_constraints_amd(void) |
103 | { |
104 | union acpi_object *out_obj; |
105 | int i, j, k; |
106 | |
107 | out_obj = acpi_evaluate_dsm_typed(handle: lps0_device_handle, guid: &lps0_dsm_guid, |
108 | rev: rev_id, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, |
109 | NULL, ACPI_TYPE_PACKAGE); |
110 | |
111 | acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n" , |
112 | out_obj ? "successful" : "failed" ); |
113 | |
114 | if (!out_obj) |
115 | return; |
116 | |
117 | for (i = 0; i < out_obj->package.count; i++) { |
118 | union acpi_object *package = &out_obj->package.elements[i]; |
119 | |
120 | if (package->type == ACPI_TYPE_PACKAGE) { |
121 | if (lpi_constraints_table) { |
122 | acpi_handle_err(lps0_device_handle, |
123 | "Duplicate constraints list\n" ); |
124 | goto free_acpi_buffer; |
125 | } |
126 | |
127 | lpi_constraints_table = kcalloc(n: package->package.count, |
128 | size: sizeof(*lpi_constraints_table), |
129 | GFP_KERNEL); |
130 | |
131 | if (!lpi_constraints_table) |
132 | goto free_acpi_buffer; |
133 | |
134 | acpi_handle_debug(lps0_device_handle, |
135 | "LPI: constraints list begin:\n" ); |
136 | |
137 | for (j = 0; j < package->package.count; j++) { |
138 | union acpi_object *info_obj = &package->package.elements[j]; |
139 | struct lpi_device_constraint_amd dev_info = {}; |
140 | struct lpi_constraints *list; |
141 | acpi_status status; |
142 | |
143 | list = &lpi_constraints_table[lpi_constraints_table_size]; |
144 | |
145 | for (k = 0; k < info_obj->package.count; k++) { |
146 | union acpi_object *obj = &info_obj->package.elements[k]; |
147 | |
148 | switch (k) { |
149 | case 0: |
150 | dev_info.enabled = obj->integer.value; |
151 | break; |
152 | case 1: |
153 | dev_info.name = obj->string.pointer; |
154 | break; |
155 | case 2: |
156 | dev_info.function_states = obj->integer.value; |
157 | break; |
158 | case 3: |
159 | dev_info.min_dstate = obj->integer.value; |
160 | break; |
161 | } |
162 | } |
163 | |
164 | acpi_handle_debug(lps0_device_handle, |
165 | "Name:%s, Enabled: %d, States: %d, MinDstate: %d\n" , |
166 | dev_info.name, |
167 | dev_info.enabled, |
168 | dev_info.function_states, |
169 | dev_info.min_dstate); |
170 | |
171 | if (!dev_info.enabled || !dev_info.name || |
172 | !dev_info.min_dstate) |
173 | continue; |
174 | |
175 | status = acpi_get_handle(NULL, pathname: dev_info.name, ret_handle: &list->handle); |
176 | if (ACPI_FAILURE(status)) |
177 | continue; |
178 | |
179 | list->min_dstate = dev_info.min_dstate; |
180 | |
181 | lpi_constraints_table_size++; |
182 | } |
183 | } |
184 | } |
185 | |
186 | acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n" ); |
187 | |
188 | free_acpi_buffer: |
189 | ACPI_FREE(out_obj); |
190 | } |
191 | |
192 | static void lpi_device_get_constraints(void) |
193 | { |
194 | union acpi_object *out_obj; |
195 | int i; |
196 | |
197 | out_obj = acpi_evaluate_dsm_typed(handle: lps0_device_handle, guid: &lps0_dsm_guid, |
198 | rev: 1, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, |
199 | NULL, ACPI_TYPE_PACKAGE); |
200 | |
201 | acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n" , |
202 | out_obj ? "successful" : "failed" ); |
203 | |
204 | if (!out_obj) |
205 | return; |
206 | |
207 | lpi_constraints_table = kcalloc(n: out_obj->package.count, |
208 | size: sizeof(*lpi_constraints_table), |
209 | GFP_KERNEL); |
210 | if (!lpi_constraints_table) |
211 | goto free_acpi_buffer; |
212 | |
213 | acpi_handle_debug(lps0_device_handle, "LPI: constraints list begin:\n" ); |
214 | |
215 | for (i = 0; i < out_obj->package.count; i++) { |
216 | struct lpi_constraints *constraint; |
217 | acpi_status status; |
218 | union acpi_object *package = &out_obj->package.elements[i]; |
219 | struct lpi_device_info info = { }; |
220 | int package_count = 0, j; |
221 | |
222 | if (!package) |
223 | continue; |
224 | |
225 | for (j = 0; j < package->package.count; j++) { |
226 | union acpi_object *element = |
227 | &(package->package.elements[j]); |
228 | |
229 | switch (element->type) { |
230 | case ACPI_TYPE_INTEGER: |
231 | info.enabled = element->integer.value; |
232 | break; |
233 | case ACPI_TYPE_STRING: |
234 | info.name = element->string.pointer; |
235 | break; |
236 | case ACPI_TYPE_PACKAGE: |
237 | package_count = element->package.count; |
238 | info.package = element->package.elements; |
239 | break; |
240 | } |
241 | } |
242 | |
243 | if (!info.enabled || !info.package || !info.name) |
244 | continue; |
245 | |
246 | constraint = &lpi_constraints_table[lpi_constraints_table_size]; |
247 | |
248 | status = acpi_get_handle(NULL, pathname: info.name, ret_handle: &constraint->handle); |
249 | if (ACPI_FAILURE(status)) |
250 | continue; |
251 | |
252 | acpi_handle_debug(lps0_device_handle, |
253 | "index:%d Name:%s\n" , i, info.name); |
254 | |
255 | constraint->min_dstate = -1; |
256 | |
257 | for (j = 0; j < package_count; j++) { |
258 | union acpi_object *info_obj = &info.package[j]; |
259 | union acpi_object *cnstr_pkg; |
260 | union acpi_object *obj; |
261 | struct lpi_device_constraint dev_info; |
262 | |
263 | switch (info_obj->type) { |
264 | case ACPI_TYPE_INTEGER: |
265 | /* version */ |
266 | break; |
267 | case ACPI_TYPE_PACKAGE: |
268 | if (info_obj->package.count < 2) |
269 | break; |
270 | |
271 | cnstr_pkg = info_obj->package.elements; |
272 | obj = &cnstr_pkg[0]; |
273 | dev_info.uid = obj->integer.value; |
274 | obj = &cnstr_pkg[1]; |
275 | dev_info.min_dstate = obj->integer.value; |
276 | |
277 | acpi_handle_debug(lps0_device_handle, |
278 | "uid:%d min_dstate:%s\n" , |
279 | dev_info.uid, |
280 | acpi_power_state_string(dev_info.min_dstate)); |
281 | |
282 | constraint->min_dstate = dev_info.min_dstate; |
283 | break; |
284 | } |
285 | } |
286 | |
287 | if (constraint->min_dstate < 0) { |
288 | acpi_handle_debug(lps0_device_handle, |
289 | "Incomplete constraint defined\n" ); |
290 | continue; |
291 | } |
292 | |
293 | lpi_constraints_table_size++; |
294 | } |
295 | |
296 | acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n" ); |
297 | |
298 | free_acpi_buffer: |
299 | ACPI_FREE(out_obj); |
300 | } |
301 | |
302 | /** |
303 | * acpi_get_lps0_constraint - Get the LPS0 constraint for a device. |
304 | * @adev: Device to get the constraint for. |
305 | * |
306 | * The LPS0 constraint is the shallowest (minimum) power state in which the |
307 | * device can be so as to allow the platform as a whole to achieve additional |
308 | * energy conservation by utilizing a system-wide low-power state. |
309 | * |
310 | * Returns: |
311 | * - ACPI power state value of the constraint for @adev on success. |
312 | * - Otherwise, ACPI_STATE_UNKNOWN. |
313 | */ |
314 | int acpi_get_lps0_constraint(struct acpi_device *adev) |
315 | { |
316 | struct lpi_constraints *entry; |
317 | |
318 | for_each_lpi_constraint(entry) { |
319 | if (adev->handle == entry->handle) |
320 | return entry->min_dstate; |
321 | } |
322 | |
323 | return ACPI_STATE_UNKNOWN; |
324 | } |
325 | |
326 | static void lpi_check_constraints(void) |
327 | { |
328 | struct lpi_constraints *entry; |
329 | |
330 | for_each_lpi_constraint(entry) { |
331 | struct acpi_device *adev = acpi_fetch_acpi_dev(handle: entry->handle); |
332 | |
333 | if (!adev) |
334 | continue; |
335 | |
336 | acpi_handle_debug(entry->handle, |
337 | "LPI: required min power state:%s current power state:%s\n" , |
338 | acpi_power_state_string(entry->min_dstate), |
339 | acpi_power_state_string(adev->power.state)); |
340 | |
341 | if (!adev->flags.power_manageable) { |
342 | acpi_handle_info(entry->handle, "LPI: Device not power manageable\n" ); |
343 | entry->handle = NULL; |
344 | continue; |
345 | } |
346 | |
347 | if (adev->power.state < entry->min_dstate) |
348 | acpi_handle_info(entry->handle, |
349 | "LPI: Constraint not met; min power state:%s current power state:%s\n" , |
350 | acpi_power_state_string(entry->min_dstate), |
351 | acpi_power_state_string(adev->power.state)); |
352 | } |
353 | } |
354 | |
355 | static bool acpi_s2idle_vendor_amd(void) |
356 | { |
357 | return boot_cpu_data.x86_vendor == X86_VENDOR_AMD; |
358 | } |
359 | |
360 | static const char *acpi_sleep_dsm_state_to_str(unsigned int state) |
361 | { |
362 | if (lps0_dsm_func_mask_microsoft || !acpi_s2idle_vendor_amd()) { |
363 | switch (state) { |
364 | case ACPI_LPS0_SCREEN_OFF: |
365 | return "screen off" ; |
366 | case ACPI_LPS0_SCREEN_ON: |
367 | return "screen on" ; |
368 | case ACPI_LPS0_ENTRY: |
369 | return "lps0 entry" ; |
370 | case ACPI_LPS0_EXIT: |
371 | return "lps0 exit" ; |
372 | case ACPI_LPS0_MS_ENTRY: |
373 | return "lps0 ms entry" ; |
374 | case ACPI_LPS0_MS_EXIT: |
375 | return "lps0 ms exit" ; |
376 | } |
377 | } else { |
378 | switch (state) { |
379 | case ACPI_LPS0_SCREEN_ON_AMD: |
380 | return "screen on" ; |
381 | case ACPI_LPS0_SCREEN_OFF_AMD: |
382 | return "screen off" ; |
383 | case ACPI_LPS0_ENTRY_AMD: |
384 | return "lps0 entry" ; |
385 | case ACPI_LPS0_EXIT_AMD: |
386 | return "lps0 exit" ; |
387 | } |
388 | } |
389 | |
390 | return "unknown" ; |
391 | } |
392 | |
393 | static void acpi_sleep_run_lps0_dsm(unsigned int func, unsigned int func_mask, guid_t dsm_guid) |
394 | { |
395 | union acpi_object *out_obj; |
396 | |
397 | if (!(func_mask & (1 << func))) |
398 | return; |
399 | |
400 | out_obj = acpi_evaluate_dsm(handle: lps0_device_handle, guid: &dsm_guid, |
401 | rev: rev_id, func, NULL); |
402 | ACPI_FREE(out_obj); |
403 | |
404 | lps0_dsm_state = func; |
405 | if (pm_debug_messages_on) { |
406 | acpi_handle_info(lps0_device_handle, |
407 | "%s transitioned to state %s\n" , |
408 | out_obj ? "Successfully" : "Failed to" , |
409 | acpi_sleep_dsm_state_to_str(lps0_dsm_state)); |
410 | } |
411 | } |
412 | |
413 | |
414 | static int validate_dsm(acpi_handle handle, const char *uuid, int rev, guid_t *dsm_guid) |
415 | { |
416 | union acpi_object *obj; |
417 | int ret = -EINVAL; |
418 | |
419 | guid_parse(uuid, u: dsm_guid); |
420 | |
421 | /* Check if the _DSM is present and as expected. */ |
422 | obj = acpi_evaluate_dsm_typed(handle, guid: dsm_guid, rev, func: 0, NULL, ACPI_TYPE_BUFFER); |
423 | if (!obj || obj->buffer.length == 0 || obj->buffer.length > sizeof(u32)) { |
424 | acpi_handle_debug(handle, |
425 | "_DSM UUID %s rev %d function 0 evaluation failed\n" , uuid, rev); |
426 | goto out; |
427 | } |
428 | |
429 | ret = *(int *)obj->buffer.pointer; |
430 | acpi_handle_debug(handle, "_DSM UUID %s rev %d function mask: 0x%x\n" , uuid, rev, ret); |
431 | |
432 | out: |
433 | ACPI_FREE(obj); |
434 | return ret; |
435 | } |
436 | |
437 | struct amd_lps0_hid_device_data { |
438 | const bool check_off_by_one; |
439 | }; |
440 | |
441 | static const struct amd_lps0_hid_device_data amd_picasso = { |
442 | .check_off_by_one = true, |
443 | }; |
444 | |
445 | static const struct amd_lps0_hid_device_data amd_cezanne = { |
446 | .check_off_by_one = false, |
447 | }; |
448 | |
449 | static const struct acpi_device_id amd_hid_ids[] = { |
450 | {"AMD0004" , (kernel_ulong_t)&amd_picasso, }, |
451 | {"AMD0005" , (kernel_ulong_t)&amd_picasso, }, |
452 | {"AMDI0005" , (kernel_ulong_t)&amd_picasso, }, |
453 | {"AMDI0006" , (kernel_ulong_t)&amd_cezanne, }, |
454 | {} |
455 | }; |
456 | |
457 | static int lps0_device_attach(struct acpi_device *adev, |
458 | const struct acpi_device_id *not_used) |
459 | { |
460 | if (lps0_device_handle) |
461 | return 0; |
462 | |
463 | lps0_dsm_func_mask_microsoft = validate_dsm(handle: adev->handle, |
464 | ACPI_LPS0_DSM_UUID_MICROSOFT, rev: 0, |
465 | dsm_guid: &lps0_dsm_guid_microsoft); |
466 | if (acpi_s2idle_vendor_amd()) { |
467 | static const struct acpi_device_id *dev_id; |
468 | const struct amd_lps0_hid_device_data *data; |
469 | |
470 | for (dev_id = &amd_hid_ids[0]; dev_id->id[0]; dev_id++) |
471 | if (acpi_dev_hid_uid_match(adev, dev_id->id, NULL)) |
472 | break; |
473 | if (dev_id->id[0]) |
474 | data = (const struct amd_lps0_hid_device_data *) dev_id->driver_data; |
475 | else |
476 | data = &amd_cezanne; |
477 | lps0_dsm_func_mask = validate_dsm(handle: adev->handle, |
478 | ACPI_LPS0_DSM_UUID_AMD, rev: rev_id, dsm_guid: &lps0_dsm_guid); |
479 | if (lps0_dsm_func_mask > 0x3 && data->check_off_by_one) { |
480 | lps0_dsm_func_mask = (lps0_dsm_func_mask << 1) | 0x1; |
481 | acpi_handle_debug(adev->handle, "_DSM UUID %s: Adjusted function mask: 0x%x\n" , |
482 | ACPI_LPS0_DSM_UUID_AMD, lps0_dsm_func_mask); |
483 | } else if (lps0_dsm_func_mask_microsoft > 0 && rev_id) { |
484 | lps0_dsm_func_mask_microsoft = -EINVAL; |
485 | acpi_handle_debug(adev->handle, "_DSM Using AMD method\n" ); |
486 | } |
487 | } else { |
488 | rev_id = 1; |
489 | lps0_dsm_func_mask = validate_dsm(handle: adev->handle, |
490 | ACPI_LPS0_DSM_UUID, rev: rev_id, dsm_guid: &lps0_dsm_guid); |
491 | if (lps0_dsm_func_mask > 0 && lps0_dsm_func_mask_microsoft > 0) { |
492 | unsigned int func_mask; |
493 | |
494 | /* |
495 | * Avoid evaluating the same _DSM function for two |
496 | * different UUIDs and prioritize the MSFT one. |
497 | */ |
498 | func_mask = lps0_dsm_func_mask & lps0_dsm_func_mask_microsoft; |
499 | if (func_mask) { |
500 | acpi_handle_info(adev->handle, |
501 | "Duplicate LPS0 _DSM functions (mask: 0x%x)\n" , |
502 | func_mask); |
503 | lps0_dsm_func_mask &= ~func_mask; |
504 | } |
505 | } |
506 | } |
507 | |
508 | if (lps0_dsm_func_mask < 0 && lps0_dsm_func_mask_microsoft < 0) |
509 | return 0; //function evaluation failed |
510 | |
511 | lps0_device_handle = adev->handle; |
512 | |
513 | if (acpi_s2idle_vendor_amd()) |
514 | lpi_device_get_constraints_amd(); |
515 | else |
516 | lpi_device_get_constraints(); |
517 | |
518 | /* |
519 | * Use suspend-to-idle by default if ACPI_FADT_LOW_POWER_S0 is set in |
520 | * the FADT and the default suspend mode was not set from the command |
521 | * line. |
522 | */ |
523 | if ((acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) && |
524 | mem_sleep_default > PM_SUSPEND_MEM && !acpi_sleep_default_s3) { |
525 | mem_sleep_current = PM_SUSPEND_TO_IDLE; |
526 | pr_info("Low-power S0 idle used by default for system suspend\n" ); |
527 | } |
528 | |
529 | /* |
530 | * Some LPS0 systems, like ASUS Zenbook UX430UNR/i7-8550U, require the |
531 | * EC GPE to be enabled while suspended for certain wakeup devices to |
532 | * work, so mark it as wakeup-capable. |
533 | */ |
534 | acpi_ec_mark_gpe_for_wake(); |
535 | |
536 | return 0; |
537 | } |
538 | |
539 | static struct acpi_scan_handler lps0_handler = { |
540 | .ids = lps0_device_ids, |
541 | .attach = lps0_device_attach, |
542 | }; |
543 | |
544 | int acpi_s2idle_prepare_late(void) |
545 | { |
546 | struct acpi_s2idle_dev_ops *handler; |
547 | |
548 | if (!lps0_device_handle || sleep_no_lps0) |
549 | return 0; |
550 | |
551 | if (pm_debug_messages_on) |
552 | lpi_check_constraints(); |
553 | |
554 | /* Screen off */ |
555 | if (lps0_dsm_func_mask > 0) |
556 | acpi_sleep_run_lps0_dsm(func: acpi_s2idle_vendor_amd() ? |
557 | ACPI_LPS0_SCREEN_OFF_AMD : |
558 | ACPI_LPS0_SCREEN_OFF, |
559 | func_mask: lps0_dsm_func_mask, dsm_guid: lps0_dsm_guid); |
560 | |
561 | if (lps0_dsm_func_mask_microsoft > 0) |
562 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF, |
563 | func_mask: lps0_dsm_func_mask_microsoft, dsm_guid: lps0_dsm_guid_microsoft); |
564 | |
565 | /* LPS0 entry */ |
566 | if (lps0_dsm_func_mask > 0 && acpi_s2idle_vendor_amd()) |
567 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY_AMD, |
568 | func_mask: lps0_dsm_func_mask, dsm_guid: lps0_dsm_guid); |
569 | |
570 | if (lps0_dsm_func_mask_microsoft > 0) { |
571 | /* Modern Standby entry */ |
572 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY, |
573 | func_mask: lps0_dsm_func_mask_microsoft, dsm_guid: lps0_dsm_guid_microsoft); |
574 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY, |
575 | func_mask: lps0_dsm_func_mask_microsoft, dsm_guid: lps0_dsm_guid_microsoft); |
576 | } |
577 | |
578 | if (lps0_dsm_func_mask > 0 && !acpi_s2idle_vendor_amd()) |
579 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY, |
580 | func_mask: lps0_dsm_func_mask, dsm_guid: lps0_dsm_guid); |
581 | |
582 | list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) { |
583 | if (handler->prepare) |
584 | handler->prepare(); |
585 | } |
586 | |
587 | return 0; |
588 | } |
589 | |
590 | void acpi_s2idle_check(void) |
591 | { |
592 | struct acpi_s2idle_dev_ops *handler; |
593 | |
594 | if (!lps0_device_handle || sleep_no_lps0) |
595 | return; |
596 | |
597 | list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) { |
598 | if (handler->check) |
599 | handler->check(); |
600 | } |
601 | } |
602 | |
603 | void acpi_s2idle_restore_early(void) |
604 | { |
605 | struct acpi_s2idle_dev_ops *handler; |
606 | |
607 | if (!lps0_device_handle || sleep_no_lps0) |
608 | return; |
609 | |
610 | list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) |
611 | if (handler->restore) |
612 | handler->restore(); |
613 | |
614 | /* LPS0 exit */ |
615 | if (lps0_dsm_func_mask > 0) |
616 | acpi_sleep_run_lps0_dsm(func: acpi_s2idle_vendor_amd() ? |
617 | ACPI_LPS0_EXIT_AMD : |
618 | ACPI_LPS0_EXIT, |
619 | func_mask: lps0_dsm_func_mask, dsm_guid: lps0_dsm_guid); |
620 | |
621 | if (lps0_dsm_func_mask_microsoft > 0) { |
622 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT, |
623 | func_mask: lps0_dsm_func_mask_microsoft, dsm_guid: lps0_dsm_guid_microsoft); |
624 | /* Modern Standby exit */ |
625 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT, |
626 | func_mask: lps0_dsm_func_mask_microsoft, dsm_guid: lps0_dsm_guid_microsoft); |
627 | } |
628 | |
629 | /* Screen on */ |
630 | if (lps0_dsm_func_mask_microsoft > 0) |
631 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON, |
632 | func_mask: lps0_dsm_func_mask_microsoft, dsm_guid: lps0_dsm_guid_microsoft); |
633 | if (lps0_dsm_func_mask > 0) |
634 | acpi_sleep_run_lps0_dsm(func: acpi_s2idle_vendor_amd() ? |
635 | ACPI_LPS0_SCREEN_ON_AMD : |
636 | ACPI_LPS0_SCREEN_ON, |
637 | func_mask: lps0_dsm_func_mask, dsm_guid: lps0_dsm_guid); |
638 | } |
639 | |
640 | static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = { |
641 | .begin = acpi_s2idle_begin, |
642 | .prepare = acpi_s2idle_prepare, |
643 | .prepare_late = acpi_s2idle_prepare_late, |
644 | .check = acpi_s2idle_check, |
645 | .wake = acpi_s2idle_wake, |
646 | .restore_early = acpi_s2idle_restore_early, |
647 | .restore = acpi_s2idle_restore, |
648 | .end = acpi_s2idle_end, |
649 | }; |
650 | |
651 | void __init acpi_s2idle_setup(void) |
652 | { |
653 | acpi_scan_add_handler(handler: &lps0_handler); |
654 | s2idle_set_ops(ops: &acpi_s2idle_ops_lps0); |
655 | } |
656 | |
657 | int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg) |
658 | { |
659 | unsigned int sleep_flags; |
660 | |
661 | if (!lps0_device_handle || sleep_no_lps0) |
662 | return -ENODEV; |
663 | |
664 | sleep_flags = lock_system_sleep(); |
665 | list_add(new: &arg->list_node, head: &lps0_s2idle_devops_head); |
666 | unlock_system_sleep(sleep_flags); |
667 | |
668 | return 0; |
669 | } |
670 | EXPORT_SYMBOL_GPL(acpi_register_lps0_dev); |
671 | |
672 | void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg) |
673 | { |
674 | unsigned int sleep_flags; |
675 | |
676 | if (!lps0_device_handle || sleep_no_lps0) |
677 | return; |
678 | |
679 | sleep_flags = lock_system_sleep(); |
680 | list_del(entry: &arg->list_node); |
681 | unlock_system_sleep(sleep_flags); |
682 | } |
683 | EXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev); |
684 | |
685 | #endif /* CONFIG_SUSPEND */ |
686 | |