Linux Audio

Check our new training course

Yocto distribution development and maintenance

Need a Yocto distribution for your embedded project?
Loading...
Note: File does not exist in v3.1.
  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/suspend.h>
 21
 22#include "../sleep.h"
 23
 24#ifdef CONFIG_SUSPEND
 25
 26static bool sleep_no_lps0 __read_mostly;
 27module_param(sleep_no_lps0, bool, 0644);
 28MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface");
 29
 30static const struct acpi_device_id lps0_device_ids[] = {
 31	{"PNP0D80", },
 32	{"", },
 33};
 34
 35/* Microsoft platform agnostic UUID */
 36#define ACPI_LPS0_DSM_UUID_MICROSOFT      "11e00d56-ce64-47ce-837b-1f898f9aa461"
 37
 38#define ACPI_LPS0_DSM_UUID	"c4eb40a0-6cd2-11e2-bcfd-0800200c9a66"
 39
 40#define ACPI_LPS0_GET_DEVICE_CONSTRAINTS	1
 41#define ACPI_LPS0_SCREEN_OFF	3
 42#define ACPI_LPS0_SCREEN_ON	4
 43#define ACPI_LPS0_ENTRY		5
 44#define ACPI_LPS0_EXIT		6
 45#define ACPI_LPS0_MS_ENTRY      7
 46#define ACPI_LPS0_MS_EXIT       8
 47
 48/* AMD */
 49#define ACPI_LPS0_DSM_UUID_AMD      "e3f32452-febc-43ce-9039-932122d37721"
 50#define ACPI_LPS0_ENTRY_AMD         2
 51#define ACPI_LPS0_EXIT_AMD          3
 52#define ACPI_LPS0_SCREEN_OFF_AMD    4
 53#define ACPI_LPS0_SCREEN_ON_AMD     5
 54
 55static acpi_handle lps0_device_handle;
 56static guid_t lps0_dsm_guid;
 57static int lps0_dsm_func_mask;
 58
 59static guid_t lps0_dsm_guid_microsoft;
 60static int lps0_dsm_func_mask_microsoft;
 61
 62/* Device constraint entry structure */
 63struct lpi_device_info {
 64	char *name;
 65	int enabled;
 66	union acpi_object *package;
 67};
 68
 69/* Constraint package structure */
 70struct lpi_device_constraint {
 71	int uid;
 72	int min_dstate;
 73	int function_states;
 74};
 75
 76struct lpi_constraints {
 77	acpi_handle handle;
 78	int min_dstate;
 79};
 80
 81/* AMD Constraint package structure */
 82struct lpi_device_constraint_amd {
 83	char *name;
 84	int enabled;
 85	int function_states;
 86	int min_dstate;
 87};
 88
 89static struct lpi_constraints *lpi_constraints_table;
 90static int lpi_constraints_table_size;
 91static int rev_id;
 92
 93static void lpi_device_get_constraints_amd(void)
 94{
 95	union acpi_object *out_obj;
 96	int i, j, k;
 97
 98	out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid,
 99					  rev_id, ACPI_LPS0_GET_DEVICE_CONSTRAINTS,
100					  NULL, ACPI_TYPE_PACKAGE);
101
102	acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n",
103			  out_obj ? "successful" : "failed");
104
105	if (!out_obj)
106		return;
107
108	for (i = 0; i < out_obj->package.count; i++) {
109		union acpi_object *package = &out_obj->package.elements[i];
110
111		if (package->type == ACPI_TYPE_PACKAGE) {
112			lpi_constraints_table = kcalloc(package->package.count,
113							sizeof(*lpi_constraints_table),
114							GFP_KERNEL);
115
116			if (!lpi_constraints_table)
117				goto free_acpi_buffer;
118
119			acpi_handle_debug(lps0_device_handle,
120					  "LPI: constraints list begin:\n");
121
122			for (j = 0; j < package->package.count; ++j) {
123				union acpi_object *info_obj = &package->package.elements[j];
124				struct lpi_device_constraint_amd dev_info = {};
125				struct lpi_constraints *list;
126				acpi_status status;
127
128				for (k = 0; k < info_obj->package.count; ++k) {
129					union acpi_object *obj = &info_obj->package.elements[k];
130
131					list = &lpi_constraints_table[lpi_constraints_table_size];
132					list->min_dstate = -1;
133
134					switch (k) {
135					case 0:
136						dev_info.enabled = obj->integer.value;
137						break;
138					case 1:
139						dev_info.name = obj->string.pointer;
140						break;
141					case 2:
142						dev_info.function_states = obj->integer.value;
143						break;
144					case 3:
145						dev_info.min_dstate = obj->integer.value;
146						break;
147					}
148
149					if (!dev_info.enabled || !dev_info.name ||
150					    !dev_info.min_dstate)
151						continue;
152
153					status = acpi_get_handle(NULL, dev_info.name,
154								 &list->handle);
155					if (ACPI_FAILURE(status))
156						continue;
157
158					acpi_handle_debug(lps0_device_handle,
159							  "Name:%s\n", dev_info.name);
160
161					list->min_dstate = dev_info.min_dstate;
162
163					if (list->min_dstate < 0) {
164						acpi_handle_debug(lps0_device_handle,
165								  "Incomplete constraint defined\n");
166						continue;
167					}
168				}
169				lpi_constraints_table_size++;
170			}
171		}
172	}
173
174	acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n");
175
176free_acpi_buffer:
177	ACPI_FREE(out_obj);
178}
179
180static void lpi_device_get_constraints(void)
181{
182	union acpi_object *out_obj;
183	int i;
184
185	out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid,
186					  1, ACPI_LPS0_GET_DEVICE_CONSTRAINTS,
187					  NULL, ACPI_TYPE_PACKAGE);
188
189	acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n",
190			  out_obj ? "successful" : "failed");
191
192	if (!out_obj)
193		return;
194
195	lpi_constraints_table = kcalloc(out_obj->package.count,
196					sizeof(*lpi_constraints_table),
197					GFP_KERNEL);
198	if (!lpi_constraints_table)
199		goto free_acpi_buffer;
200
201	acpi_handle_debug(lps0_device_handle, "LPI: constraints list begin:\n");
202
203	for (i = 0; i < out_obj->package.count; i++) {
204		struct lpi_constraints *constraint;
205		acpi_status status;
206		union acpi_object *package = &out_obj->package.elements[i];
207		struct lpi_device_info info = { };
208		int package_count = 0, j;
209
210		if (!package)
211			continue;
212
213		for (j = 0; j < package->package.count; ++j) {
214			union acpi_object *element =
215					&(package->package.elements[j]);
216
217			switch (element->type) {
218			case ACPI_TYPE_INTEGER:
219				info.enabled = element->integer.value;
220				break;
221			case ACPI_TYPE_STRING:
222				info.name = element->string.pointer;
223				break;
224			case ACPI_TYPE_PACKAGE:
225				package_count = element->package.count;
226				info.package = element->package.elements;
227				break;
228			}
229		}
230
231		if (!info.enabled || !info.package || !info.name)
232			continue;
233
234		constraint = &lpi_constraints_table[lpi_constraints_table_size];
235
236		status = acpi_get_handle(NULL, info.name, &constraint->handle);
237		if (ACPI_FAILURE(status))
238			continue;
239
240		acpi_handle_debug(lps0_device_handle,
241				  "index:%d Name:%s\n", i, info.name);
242
243		constraint->min_dstate = -1;
244
245		for (j = 0; j < package_count; ++j) {
246			union acpi_object *info_obj = &info.package[j];
247			union acpi_object *cnstr_pkg;
248			union acpi_object *obj;
249			struct lpi_device_constraint dev_info;
250
251			switch (info_obj->type) {
252			case ACPI_TYPE_INTEGER:
253				/* version */
254				break;
255			case ACPI_TYPE_PACKAGE:
256				if (info_obj->package.count < 2)
257					break;
258
259				cnstr_pkg = info_obj->package.elements;
260				obj = &cnstr_pkg[0];
261				dev_info.uid = obj->integer.value;
262				obj = &cnstr_pkg[1];
263				dev_info.min_dstate = obj->integer.value;
264
265				acpi_handle_debug(lps0_device_handle,
266					"uid:%d min_dstate:%s\n",
267					dev_info.uid,
268					acpi_power_state_string(dev_info.min_dstate));
269
270				constraint->min_dstate = dev_info.min_dstate;
271				break;
272			}
273		}
274
275		if (constraint->min_dstate < 0) {
276			acpi_handle_debug(lps0_device_handle,
277					  "Incomplete constraint defined\n");
278			continue;
279		}
280
281		lpi_constraints_table_size++;
282	}
283
284	acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n");
285
286free_acpi_buffer:
287	ACPI_FREE(out_obj);
288}
289
290static void lpi_check_constraints(void)
291{
292	int i;
293
294	for (i = 0; i < lpi_constraints_table_size; ++i) {
295		acpi_handle handle = lpi_constraints_table[i].handle;
296		struct acpi_device *adev;
297
298		if (!handle || acpi_bus_get_device(handle, &adev))
299			continue;
300
301		acpi_handle_debug(handle,
302			"LPI: required min power state:%s current power state:%s\n",
303			acpi_power_state_string(lpi_constraints_table[i].min_dstate),
304			acpi_power_state_string(adev->power.state));
305
306		if (!adev->flags.power_manageable) {
307			acpi_handle_info(handle, "LPI: Device not power manageable\n");
308			lpi_constraints_table[i].handle = NULL;
309			continue;
310		}
311
312		if (adev->power.state < lpi_constraints_table[i].min_dstate)
313			acpi_handle_info(handle,
314				"LPI: Constraint not met; min power state:%s current power state:%s\n",
315				acpi_power_state_string(lpi_constraints_table[i].min_dstate),
316				acpi_power_state_string(adev->power.state));
317	}
318}
319
320static void acpi_sleep_run_lps0_dsm(unsigned int func, unsigned int func_mask, guid_t dsm_guid)
321{
322	union acpi_object *out_obj;
323
324	if (!(func_mask & (1 << func)))
325		return;
326
327	out_obj = acpi_evaluate_dsm(lps0_device_handle, &dsm_guid,
328					rev_id, func, NULL);
329	ACPI_FREE(out_obj);
330
331	acpi_handle_debug(lps0_device_handle, "_DSM function %u evaluation %s\n",
332			  func, out_obj ? "successful" : "failed");
333}
334
335static bool acpi_s2idle_vendor_amd(void)
336{
337	return boot_cpu_data.x86_vendor == X86_VENDOR_AMD;
338}
339
340static int validate_dsm(acpi_handle handle, const char *uuid, int rev, guid_t *dsm_guid)
341{
342	union acpi_object *obj;
343	int ret = -EINVAL;
344
345	guid_parse(uuid, dsm_guid);
346	obj = acpi_evaluate_dsm(handle, dsm_guid, rev, 0, NULL);
347
348	/* Check if the _DSM is present and as expected. */
349	if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length == 0 ||
350	    obj->buffer.length > sizeof(u32)) {
351		acpi_handle_debug(handle,
352				"_DSM UUID %s rev %d function 0 evaluation failed\n", uuid, rev);
353		goto out;
354	}
355
356	ret = *(int *)obj->buffer.pointer;
357	acpi_handle_debug(handle, "_DSM UUID %s rev %d function mask: 0x%x\n", uuid, rev, ret);
358
359out:
360	ACPI_FREE(obj);
361	return ret;
362}
363
364static int lps0_device_attach(struct acpi_device *adev,
365			      const struct acpi_device_id *not_used)
366{
367	if (lps0_device_handle)
368		return 0;
369
370	if (!(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0))
371		return 0;
372
373	if (acpi_s2idle_vendor_amd()) {
374		/* AMD0004, AMD0005, AMDI0005:
375		 * - Should use rev_id 0x0
376		 * - function mask > 0x3: Should use AMD method, but has off by one bug
377		 * - function mask = 0x3: Should use Microsoft method
378		 * AMDI0006:
379		 * - should use rev_id 0x0
380		 * - function mask = 0x3: Should use Microsoft method
381		 * AMDI0007:
382		 * - Should use rev_id 0x2
383		 * - Should only use AMD method
384		 */
385		const char *hid = acpi_device_hid(adev);
386		rev_id = strcmp(hid, "AMDI0007") ? 0 : 2;
387		lps0_dsm_func_mask = validate_dsm(adev->handle,
388					ACPI_LPS0_DSM_UUID_AMD, rev_id, &lps0_dsm_guid);
389		lps0_dsm_func_mask_microsoft = validate_dsm(adev->handle,
390					ACPI_LPS0_DSM_UUID_MICROSOFT, 0,
391					&lps0_dsm_guid_microsoft);
392		if (lps0_dsm_func_mask > 0x3 && (!strcmp(hid, "AMD0004") ||
393						 !strcmp(hid, "AMD0005") ||
394						 !strcmp(hid, "AMDI0005"))) {
395			lps0_dsm_func_mask = (lps0_dsm_func_mask << 1) | 0x1;
396			acpi_handle_debug(adev->handle, "_DSM UUID %s: Adjusted function mask: 0x%x\n",
397					  ACPI_LPS0_DSM_UUID_AMD, lps0_dsm_func_mask);
398		} else if (lps0_dsm_func_mask_microsoft > 0 && !strcmp(hid, "AMDI0007")) {
399			lps0_dsm_func_mask_microsoft = -EINVAL;
400			acpi_handle_debug(adev->handle, "_DSM Using AMD method\n");
401		}
402	} else {
403		rev_id = 1;
404		lps0_dsm_func_mask = validate_dsm(adev->handle,
405					ACPI_LPS0_DSM_UUID, rev_id, &lps0_dsm_guid);
406		lps0_dsm_func_mask_microsoft = -EINVAL;
407	}
408
409	if (lps0_dsm_func_mask < 0 && lps0_dsm_func_mask_microsoft < 0)
410		return 0; //function evaluation failed
411
412	lps0_device_handle = adev->handle;
413
414	if (acpi_s2idle_vendor_amd())
415		lpi_device_get_constraints_amd();
416	else
417		lpi_device_get_constraints();
418
419	/*
420	 * Use suspend-to-idle by default if the default suspend mode was not
421	 * set from the command line.
422	 */
423	if (mem_sleep_default > PM_SUSPEND_MEM && !acpi_sleep_default_s3)
424		mem_sleep_current = PM_SUSPEND_TO_IDLE;
425
426	/*
427	 * Some Intel based LPS0 systems, like ASUS Zenbook UX430UNR/i7-8550U don't
428	 * use intel-hid or intel-vbtn but require the EC GPE to be enabled while
429	 * suspended for certain wakeup devices to work, so mark it as wakeup-capable.
430	 *
431	 * Only enable on !AMD as enabling this universally causes problems for a number
432	 * of AMD based systems.
433	 */
434	if (!acpi_s2idle_vendor_amd())
435		acpi_ec_mark_gpe_for_wake();
436
437	return 0;
438}
439
440static struct acpi_scan_handler lps0_handler = {
441	.ids = lps0_device_ids,
442	.attach = lps0_device_attach,
443};
444
445int acpi_s2idle_prepare_late(void)
446{
447	if (!lps0_device_handle || sleep_no_lps0)
448		return 0;
449
450	if (pm_debug_messages_on)
451		lpi_check_constraints();
452
453	/* Screen off */
454	if (lps0_dsm_func_mask > 0)
455		acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
456					ACPI_LPS0_SCREEN_OFF_AMD :
457					ACPI_LPS0_SCREEN_OFF,
458					lps0_dsm_func_mask, lps0_dsm_guid);
459
460	if (lps0_dsm_func_mask_microsoft > 0)
461		acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF,
462				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
463
464	/* LPS0 entry */
465	if (lps0_dsm_func_mask > 0)
466		acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
467					ACPI_LPS0_ENTRY_AMD :
468					ACPI_LPS0_ENTRY,
469					lps0_dsm_func_mask, lps0_dsm_guid);
470	if (lps0_dsm_func_mask_microsoft > 0) {
471		acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY,
472				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
473		/* modern standby entry */
474		acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY,
475				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
476	}
477	return 0;
478}
479
480void acpi_s2idle_restore_early(void)
481{
482	if (!lps0_device_handle || sleep_no_lps0)
483		return;
484
485	/* Modern standby exit */
486	if (lps0_dsm_func_mask_microsoft > 0)
487		acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT,
488				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
489
490	/* LPS0 exit */
491	if (lps0_dsm_func_mask > 0)
492		acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
493					ACPI_LPS0_EXIT_AMD :
494					ACPI_LPS0_EXIT,
495					lps0_dsm_func_mask, lps0_dsm_guid);
496	if (lps0_dsm_func_mask_microsoft > 0)
497		acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT,
498				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
499
500	/* Screen on */
501	if (lps0_dsm_func_mask_microsoft > 0)
502		acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON,
503				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
504	if (lps0_dsm_func_mask > 0)
505		acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
506					ACPI_LPS0_SCREEN_ON_AMD :
507					ACPI_LPS0_SCREEN_ON,
508					lps0_dsm_func_mask, lps0_dsm_guid);
509}
510
511static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = {
512	.begin = acpi_s2idle_begin,
513	.prepare = acpi_s2idle_prepare,
514	.prepare_late = acpi_s2idle_prepare_late,
515	.wake = acpi_s2idle_wake,
516	.restore_early = acpi_s2idle_restore_early,
517	.restore = acpi_s2idle_restore,
518	.end = acpi_s2idle_end,
519};
520
521void acpi_s2idle_setup(void)
522{
523	acpi_scan_add_handler(&lps0_handler);
524	s2idle_set_ops(&acpi_s2idle_ops_lps0);
525}
526
527#endif /* CONFIG_SUSPEND */