162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Architecture-specific ACPI-based support for suspend-to-idle.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
662306a36Sopenharmony_ci * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
762306a36Sopenharmony_ci * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * On platforms supporting the Low Power S0 Idle interface there is an ACPI
1062306a36Sopenharmony_ci * device object with the PNP0D80 compatible device ID (System Power Management
1162306a36Sopenharmony_ci * Controller) and a specific _DSM method under it.  That method, if present,
1262306a36Sopenharmony_ci * can be used to indicate to the platform that the OS is transitioning into a
1362306a36Sopenharmony_ci * low-power state in which certain types of activity are not desirable or that
1462306a36Sopenharmony_ci * it is leaving such a state, which allows the platform to adjust its operation
1562306a36Sopenharmony_ci * mode accordingly.
1662306a36Sopenharmony_ci */
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include <linux/acpi.h>
1962306a36Sopenharmony_ci#include <linux/device.h>
2062306a36Sopenharmony_ci#include <linux/dmi.h>
2162306a36Sopenharmony_ci#include <linux/suspend.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include "../sleep.h"
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#ifdef CONFIG_SUSPEND
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic bool sleep_no_lps0 __read_mostly;
2862306a36Sopenharmony_cimodule_param(sleep_no_lps0, bool, 0644);
2962306a36Sopenharmony_ciMODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface");
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic const struct acpi_device_id lps0_device_ids[] = {
3262306a36Sopenharmony_ci	{"PNP0D80", },
3362306a36Sopenharmony_ci	{"", },
3462306a36Sopenharmony_ci};
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/* Microsoft platform agnostic UUID */
3762306a36Sopenharmony_ci#define ACPI_LPS0_DSM_UUID_MICROSOFT      "11e00d56-ce64-47ce-837b-1f898f9aa461"
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci#define ACPI_LPS0_DSM_UUID	"c4eb40a0-6cd2-11e2-bcfd-0800200c9a66"
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci#define ACPI_LPS0_GET_DEVICE_CONSTRAINTS	1
4262306a36Sopenharmony_ci#define ACPI_LPS0_SCREEN_OFF	3
4362306a36Sopenharmony_ci#define ACPI_LPS0_SCREEN_ON	4
4462306a36Sopenharmony_ci#define ACPI_LPS0_ENTRY		5
4562306a36Sopenharmony_ci#define ACPI_LPS0_EXIT		6
4662306a36Sopenharmony_ci#define ACPI_LPS0_MS_ENTRY      7
4762306a36Sopenharmony_ci#define ACPI_LPS0_MS_EXIT       8
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci/* AMD */
5062306a36Sopenharmony_ci#define ACPI_LPS0_DSM_UUID_AMD      "e3f32452-febc-43ce-9039-932122d37721"
5162306a36Sopenharmony_ci#define ACPI_LPS0_ENTRY_AMD         2
5262306a36Sopenharmony_ci#define ACPI_LPS0_EXIT_AMD          3
5362306a36Sopenharmony_ci#define ACPI_LPS0_SCREEN_OFF_AMD    4
5462306a36Sopenharmony_ci#define ACPI_LPS0_SCREEN_ON_AMD     5
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic acpi_handle lps0_device_handle;
5762306a36Sopenharmony_cistatic guid_t lps0_dsm_guid;
5862306a36Sopenharmony_cistatic int lps0_dsm_func_mask;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic guid_t lps0_dsm_guid_microsoft;
6162306a36Sopenharmony_cistatic int lps0_dsm_func_mask_microsoft;
6262306a36Sopenharmony_cistatic int lps0_dsm_state;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci/* Device constraint entry structure */
6562306a36Sopenharmony_cistruct lpi_device_info {
6662306a36Sopenharmony_ci	char *name;
6762306a36Sopenharmony_ci	int enabled;
6862306a36Sopenharmony_ci	union acpi_object *package;
6962306a36Sopenharmony_ci};
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci/* Constraint package structure */
7262306a36Sopenharmony_cistruct lpi_device_constraint {
7362306a36Sopenharmony_ci	int uid;
7462306a36Sopenharmony_ci	int min_dstate;
7562306a36Sopenharmony_ci	int function_states;
7662306a36Sopenharmony_ci};
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistruct lpi_constraints {
7962306a36Sopenharmony_ci	acpi_handle handle;
8062306a36Sopenharmony_ci	int min_dstate;
8162306a36Sopenharmony_ci};
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci/* AMD Constraint package structure */
8462306a36Sopenharmony_cistruct lpi_device_constraint_amd {
8562306a36Sopenharmony_ci	char *name;
8662306a36Sopenharmony_ci	int enabled;
8762306a36Sopenharmony_ci	int function_states;
8862306a36Sopenharmony_ci	int min_dstate;
8962306a36Sopenharmony_ci};
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic LIST_HEAD(lps0_s2idle_devops_head);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic struct lpi_constraints *lpi_constraints_table;
9462306a36Sopenharmony_cistatic int lpi_constraints_table_size;
9562306a36Sopenharmony_cistatic int rev_id;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci#define for_each_lpi_constraint(entry)						\
9862306a36Sopenharmony_ci	for (int i = 0;								\
9962306a36Sopenharmony_ci	     entry = &lpi_constraints_table[i], i < lpi_constraints_table_size;	\
10062306a36Sopenharmony_ci	     i++)
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_cistatic void lpi_device_get_constraints_amd(void)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	union acpi_object *out_obj;
10562306a36Sopenharmony_ci	int i, j, k;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid,
10862306a36Sopenharmony_ci					  rev_id, ACPI_LPS0_GET_DEVICE_CONSTRAINTS,
10962306a36Sopenharmony_ci					  NULL, ACPI_TYPE_PACKAGE);
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n",
11262306a36Sopenharmony_ci			  out_obj ? "successful" : "failed");
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	if (!out_obj)
11562306a36Sopenharmony_ci		return;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	for (i = 0; i < out_obj->package.count; i++) {
11862306a36Sopenharmony_ci		union acpi_object *package = &out_obj->package.elements[i];
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci		if (package->type == ACPI_TYPE_PACKAGE) {
12162306a36Sopenharmony_ci			if (lpi_constraints_table) {
12262306a36Sopenharmony_ci				acpi_handle_err(lps0_device_handle,
12362306a36Sopenharmony_ci						"Duplicate constraints list\n");
12462306a36Sopenharmony_ci				goto free_acpi_buffer;
12562306a36Sopenharmony_ci			}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci			lpi_constraints_table = kcalloc(package->package.count,
12862306a36Sopenharmony_ci							sizeof(*lpi_constraints_table),
12962306a36Sopenharmony_ci							GFP_KERNEL);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci			if (!lpi_constraints_table)
13262306a36Sopenharmony_ci				goto free_acpi_buffer;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci			acpi_handle_debug(lps0_device_handle,
13562306a36Sopenharmony_ci					  "LPI: constraints list begin:\n");
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci			for (j = 0; j < package->package.count; j++) {
13862306a36Sopenharmony_ci				union acpi_object *info_obj = &package->package.elements[j];
13962306a36Sopenharmony_ci				struct lpi_device_constraint_amd dev_info = {};
14062306a36Sopenharmony_ci				struct lpi_constraints *list;
14162306a36Sopenharmony_ci				acpi_status status;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci				list = &lpi_constraints_table[lpi_constraints_table_size];
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci				for (k = 0; k < info_obj->package.count; k++) {
14662306a36Sopenharmony_ci					union acpi_object *obj = &info_obj->package.elements[k];
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci					switch (k) {
14962306a36Sopenharmony_ci					case 0:
15062306a36Sopenharmony_ci						dev_info.enabled = obj->integer.value;
15162306a36Sopenharmony_ci						break;
15262306a36Sopenharmony_ci					case 1:
15362306a36Sopenharmony_ci						dev_info.name = obj->string.pointer;
15462306a36Sopenharmony_ci						break;
15562306a36Sopenharmony_ci					case 2:
15662306a36Sopenharmony_ci						dev_info.function_states = obj->integer.value;
15762306a36Sopenharmony_ci						break;
15862306a36Sopenharmony_ci					case 3:
15962306a36Sopenharmony_ci						dev_info.min_dstate = obj->integer.value;
16062306a36Sopenharmony_ci						break;
16162306a36Sopenharmony_ci					}
16262306a36Sopenharmony_ci				}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci				acpi_handle_debug(lps0_device_handle,
16562306a36Sopenharmony_ci						  "Name:%s, Enabled: %d, States: %d, MinDstate: %d\n",
16662306a36Sopenharmony_ci						  dev_info.name,
16762306a36Sopenharmony_ci						  dev_info.enabled,
16862306a36Sopenharmony_ci						  dev_info.function_states,
16962306a36Sopenharmony_ci						  dev_info.min_dstate);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci				if (!dev_info.enabled || !dev_info.name ||
17262306a36Sopenharmony_ci				    !dev_info.min_dstate)
17362306a36Sopenharmony_ci					continue;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci				status = acpi_get_handle(NULL, dev_info.name, &list->handle);
17662306a36Sopenharmony_ci				if (ACPI_FAILURE(status))
17762306a36Sopenharmony_ci					continue;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci				list->min_dstate = dev_info.min_dstate;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci				lpi_constraints_table_size++;
18262306a36Sopenharmony_ci			}
18362306a36Sopenharmony_ci		}
18462306a36Sopenharmony_ci	}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n");
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_cifree_acpi_buffer:
18962306a36Sopenharmony_ci	ACPI_FREE(out_obj);
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic void lpi_device_get_constraints(void)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	union acpi_object *out_obj;
19562306a36Sopenharmony_ci	int i;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid,
19862306a36Sopenharmony_ci					  1, ACPI_LPS0_GET_DEVICE_CONSTRAINTS,
19962306a36Sopenharmony_ci					  NULL, ACPI_TYPE_PACKAGE);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n",
20262306a36Sopenharmony_ci			  out_obj ? "successful" : "failed");
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	if (!out_obj)
20562306a36Sopenharmony_ci		return;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	lpi_constraints_table = kcalloc(out_obj->package.count,
20862306a36Sopenharmony_ci					sizeof(*lpi_constraints_table),
20962306a36Sopenharmony_ci					GFP_KERNEL);
21062306a36Sopenharmony_ci	if (!lpi_constraints_table)
21162306a36Sopenharmony_ci		goto free_acpi_buffer;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	acpi_handle_debug(lps0_device_handle, "LPI: constraints list begin:\n");
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	for (i = 0; i < out_obj->package.count; i++) {
21662306a36Sopenharmony_ci		struct lpi_constraints *constraint;
21762306a36Sopenharmony_ci		acpi_status status;
21862306a36Sopenharmony_ci		union acpi_object *package = &out_obj->package.elements[i];
21962306a36Sopenharmony_ci		struct lpi_device_info info = { };
22062306a36Sopenharmony_ci		int package_count = 0, j;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci		if (!package)
22362306a36Sopenharmony_ci			continue;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci		for (j = 0; j < package->package.count; j++) {
22662306a36Sopenharmony_ci			union acpi_object *element =
22762306a36Sopenharmony_ci					&(package->package.elements[j]);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci			switch (element->type) {
23062306a36Sopenharmony_ci			case ACPI_TYPE_INTEGER:
23162306a36Sopenharmony_ci				info.enabled = element->integer.value;
23262306a36Sopenharmony_ci				break;
23362306a36Sopenharmony_ci			case ACPI_TYPE_STRING:
23462306a36Sopenharmony_ci				info.name = element->string.pointer;
23562306a36Sopenharmony_ci				break;
23662306a36Sopenharmony_ci			case ACPI_TYPE_PACKAGE:
23762306a36Sopenharmony_ci				package_count = element->package.count;
23862306a36Sopenharmony_ci				info.package = element->package.elements;
23962306a36Sopenharmony_ci				break;
24062306a36Sopenharmony_ci			}
24162306a36Sopenharmony_ci		}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci		if (!info.enabled || !info.package || !info.name)
24462306a36Sopenharmony_ci			continue;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci		constraint = &lpi_constraints_table[lpi_constraints_table_size];
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci		status = acpi_get_handle(NULL, info.name, &constraint->handle);
24962306a36Sopenharmony_ci		if (ACPI_FAILURE(status))
25062306a36Sopenharmony_ci			continue;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci		acpi_handle_debug(lps0_device_handle,
25362306a36Sopenharmony_ci				  "index:%d Name:%s\n", i, info.name);
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci		constraint->min_dstate = -1;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci		for (j = 0; j < package_count; j++) {
25862306a36Sopenharmony_ci			union acpi_object *info_obj = &info.package[j];
25962306a36Sopenharmony_ci			union acpi_object *cnstr_pkg;
26062306a36Sopenharmony_ci			union acpi_object *obj;
26162306a36Sopenharmony_ci			struct lpi_device_constraint dev_info;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci			switch (info_obj->type) {
26462306a36Sopenharmony_ci			case ACPI_TYPE_INTEGER:
26562306a36Sopenharmony_ci				/* version */
26662306a36Sopenharmony_ci				break;
26762306a36Sopenharmony_ci			case ACPI_TYPE_PACKAGE:
26862306a36Sopenharmony_ci				if (info_obj->package.count < 2)
26962306a36Sopenharmony_ci					break;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci				cnstr_pkg = info_obj->package.elements;
27262306a36Sopenharmony_ci				obj = &cnstr_pkg[0];
27362306a36Sopenharmony_ci				dev_info.uid = obj->integer.value;
27462306a36Sopenharmony_ci				obj = &cnstr_pkg[1];
27562306a36Sopenharmony_ci				dev_info.min_dstate = obj->integer.value;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci				acpi_handle_debug(lps0_device_handle,
27862306a36Sopenharmony_ci					"uid:%d min_dstate:%s\n",
27962306a36Sopenharmony_ci					dev_info.uid,
28062306a36Sopenharmony_ci					acpi_power_state_string(dev_info.min_dstate));
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci				constraint->min_dstate = dev_info.min_dstate;
28362306a36Sopenharmony_ci				break;
28462306a36Sopenharmony_ci			}
28562306a36Sopenharmony_ci		}
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci		if (constraint->min_dstate < 0) {
28862306a36Sopenharmony_ci			acpi_handle_debug(lps0_device_handle,
28962306a36Sopenharmony_ci					  "Incomplete constraint defined\n");
29062306a36Sopenharmony_ci			continue;
29162306a36Sopenharmony_ci		}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci		lpi_constraints_table_size++;
29462306a36Sopenharmony_ci	}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n");
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cifree_acpi_buffer:
29962306a36Sopenharmony_ci	ACPI_FREE(out_obj);
30062306a36Sopenharmony_ci}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci/**
30362306a36Sopenharmony_ci * acpi_get_lps0_constraint - Get the LPS0 constraint for a device.
30462306a36Sopenharmony_ci * @adev: Device to get the constraint for.
30562306a36Sopenharmony_ci *
30662306a36Sopenharmony_ci * The LPS0 constraint is the shallowest (minimum) power state in which the
30762306a36Sopenharmony_ci * device can be so as to allow the platform as a whole to achieve additional
30862306a36Sopenharmony_ci * energy conservation by utilizing a system-wide low-power state.
30962306a36Sopenharmony_ci *
31062306a36Sopenharmony_ci * Returns:
31162306a36Sopenharmony_ci *  - ACPI power state value of the constraint for @adev on success.
31262306a36Sopenharmony_ci *  - Otherwise, ACPI_STATE_UNKNOWN.
31362306a36Sopenharmony_ci */
31462306a36Sopenharmony_ciint acpi_get_lps0_constraint(struct acpi_device *adev)
31562306a36Sopenharmony_ci{
31662306a36Sopenharmony_ci	struct lpi_constraints *entry;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	for_each_lpi_constraint(entry) {
31962306a36Sopenharmony_ci		if (adev->handle == entry->handle)
32062306a36Sopenharmony_ci			return entry->min_dstate;
32162306a36Sopenharmony_ci	}
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	return ACPI_STATE_UNKNOWN;
32462306a36Sopenharmony_ci}
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_cistatic void lpi_check_constraints(void)
32762306a36Sopenharmony_ci{
32862306a36Sopenharmony_ci	struct lpi_constraints *entry;
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	for_each_lpi_constraint(entry) {
33162306a36Sopenharmony_ci		struct acpi_device *adev = acpi_fetch_acpi_dev(entry->handle);
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci		if (!adev)
33462306a36Sopenharmony_ci			continue;
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci		acpi_handle_debug(entry->handle,
33762306a36Sopenharmony_ci			"LPI: required min power state:%s current power state:%s\n",
33862306a36Sopenharmony_ci			acpi_power_state_string(entry->min_dstate),
33962306a36Sopenharmony_ci			acpi_power_state_string(adev->power.state));
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci		if (!adev->flags.power_manageable) {
34262306a36Sopenharmony_ci			acpi_handle_info(entry->handle, "LPI: Device not power manageable\n");
34362306a36Sopenharmony_ci			entry->handle = NULL;
34462306a36Sopenharmony_ci			continue;
34562306a36Sopenharmony_ci		}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci		if (adev->power.state < entry->min_dstate)
34862306a36Sopenharmony_ci			acpi_handle_info(entry->handle,
34962306a36Sopenharmony_ci				"LPI: Constraint not met; min power state:%s current power state:%s\n",
35062306a36Sopenharmony_ci				acpi_power_state_string(entry->min_dstate),
35162306a36Sopenharmony_ci				acpi_power_state_string(adev->power.state));
35262306a36Sopenharmony_ci	}
35362306a36Sopenharmony_ci}
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_cistatic bool acpi_s2idle_vendor_amd(void)
35662306a36Sopenharmony_ci{
35762306a36Sopenharmony_ci	return boot_cpu_data.x86_vendor == X86_VENDOR_AMD;
35862306a36Sopenharmony_ci}
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_cistatic const char *acpi_sleep_dsm_state_to_str(unsigned int state)
36162306a36Sopenharmony_ci{
36262306a36Sopenharmony_ci	if (lps0_dsm_func_mask_microsoft || !acpi_s2idle_vendor_amd()) {
36362306a36Sopenharmony_ci		switch (state) {
36462306a36Sopenharmony_ci		case ACPI_LPS0_SCREEN_OFF:
36562306a36Sopenharmony_ci			return "screen off";
36662306a36Sopenharmony_ci		case ACPI_LPS0_SCREEN_ON:
36762306a36Sopenharmony_ci			return "screen on";
36862306a36Sopenharmony_ci		case ACPI_LPS0_ENTRY:
36962306a36Sopenharmony_ci			return "lps0 entry";
37062306a36Sopenharmony_ci		case ACPI_LPS0_EXIT:
37162306a36Sopenharmony_ci			return "lps0 exit";
37262306a36Sopenharmony_ci		case ACPI_LPS0_MS_ENTRY:
37362306a36Sopenharmony_ci			return "lps0 ms entry";
37462306a36Sopenharmony_ci		case ACPI_LPS0_MS_EXIT:
37562306a36Sopenharmony_ci			return "lps0 ms exit";
37662306a36Sopenharmony_ci		}
37762306a36Sopenharmony_ci	} else {
37862306a36Sopenharmony_ci		switch (state) {
37962306a36Sopenharmony_ci		case ACPI_LPS0_SCREEN_ON_AMD:
38062306a36Sopenharmony_ci			return "screen on";
38162306a36Sopenharmony_ci		case ACPI_LPS0_SCREEN_OFF_AMD:
38262306a36Sopenharmony_ci			return "screen off";
38362306a36Sopenharmony_ci		case ACPI_LPS0_ENTRY_AMD:
38462306a36Sopenharmony_ci			return "lps0 entry";
38562306a36Sopenharmony_ci		case ACPI_LPS0_EXIT_AMD:
38662306a36Sopenharmony_ci			return "lps0 exit";
38762306a36Sopenharmony_ci		}
38862306a36Sopenharmony_ci	}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	return "unknown";
39162306a36Sopenharmony_ci}
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_cistatic void acpi_sleep_run_lps0_dsm(unsigned int func, unsigned int func_mask, guid_t dsm_guid)
39462306a36Sopenharmony_ci{
39562306a36Sopenharmony_ci	union acpi_object *out_obj;
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	if (!(func_mask & (1 << func)))
39862306a36Sopenharmony_ci		return;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	out_obj = acpi_evaluate_dsm(lps0_device_handle, &dsm_guid,
40162306a36Sopenharmony_ci					rev_id, func, NULL);
40262306a36Sopenharmony_ci	ACPI_FREE(out_obj);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	lps0_dsm_state = func;
40562306a36Sopenharmony_ci	if (pm_debug_messages_on) {
40662306a36Sopenharmony_ci		acpi_handle_info(lps0_device_handle,
40762306a36Sopenharmony_ci				"%s transitioned to state %s\n",
40862306a36Sopenharmony_ci				 out_obj ? "Successfully" : "Failed to",
40962306a36Sopenharmony_ci				 acpi_sleep_dsm_state_to_str(lps0_dsm_state));
41062306a36Sopenharmony_ci	}
41162306a36Sopenharmony_ci}
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_cistatic int validate_dsm(acpi_handle handle, const char *uuid, int rev, guid_t *dsm_guid)
41562306a36Sopenharmony_ci{
41662306a36Sopenharmony_ci	union acpi_object *obj;
41762306a36Sopenharmony_ci	int ret = -EINVAL;
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	guid_parse(uuid, dsm_guid);
42062306a36Sopenharmony_ci	obj = acpi_evaluate_dsm(handle, dsm_guid, rev, 0, NULL);
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	/* Check if the _DSM is present and as expected. */
42362306a36Sopenharmony_ci	if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length == 0 ||
42462306a36Sopenharmony_ci	    obj->buffer.length > sizeof(u32)) {
42562306a36Sopenharmony_ci		acpi_handle_debug(handle,
42662306a36Sopenharmony_ci				"_DSM UUID %s rev %d function 0 evaluation failed\n", uuid, rev);
42762306a36Sopenharmony_ci		goto out;
42862306a36Sopenharmony_ci	}
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	ret = *(int *)obj->buffer.pointer;
43162306a36Sopenharmony_ci	acpi_handle_debug(handle, "_DSM UUID %s rev %d function mask: 0x%x\n", uuid, rev, ret);
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ciout:
43462306a36Sopenharmony_ci	ACPI_FREE(obj);
43562306a36Sopenharmony_ci	return ret;
43662306a36Sopenharmony_ci}
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_cistruct amd_lps0_hid_device_data {
43962306a36Sopenharmony_ci	const bool check_off_by_one;
44062306a36Sopenharmony_ci};
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_cistatic const struct amd_lps0_hid_device_data amd_picasso = {
44362306a36Sopenharmony_ci	.check_off_by_one = true,
44462306a36Sopenharmony_ci};
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_cistatic const struct amd_lps0_hid_device_data amd_cezanne = {
44762306a36Sopenharmony_ci	.check_off_by_one = false,
44862306a36Sopenharmony_ci};
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_cistatic const struct acpi_device_id amd_hid_ids[] = {
45162306a36Sopenharmony_ci	{"AMD0004",	(kernel_ulong_t)&amd_picasso,	},
45262306a36Sopenharmony_ci	{"AMD0005",	(kernel_ulong_t)&amd_picasso,	},
45362306a36Sopenharmony_ci	{"AMDI0005",	(kernel_ulong_t)&amd_picasso,	},
45462306a36Sopenharmony_ci	{"AMDI0006",	(kernel_ulong_t)&amd_cezanne,	},
45562306a36Sopenharmony_ci	{}
45662306a36Sopenharmony_ci};
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_cistatic int lps0_device_attach(struct acpi_device *adev,
45962306a36Sopenharmony_ci			      const struct acpi_device_id *not_used)
46062306a36Sopenharmony_ci{
46162306a36Sopenharmony_ci	if (lps0_device_handle)
46262306a36Sopenharmony_ci		return 0;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	lps0_dsm_func_mask_microsoft = validate_dsm(adev->handle,
46562306a36Sopenharmony_ci						    ACPI_LPS0_DSM_UUID_MICROSOFT, 0,
46662306a36Sopenharmony_ci						    &lps0_dsm_guid_microsoft);
46762306a36Sopenharmony_ci	if (acpi_s2idle_vendor_amd()) {
46862306a36Sopenharmony_ci		static const struct acpi_device_id *dev_id;
46962306a36Sopenharmony_ci		const struct amd_lps0_hid_device_data *data;
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci		for (dev_id = &amd_hid_ids[0]; dev_id->id[0]; dev_id++)
47262306a36Sopenharmony_ci			if (acpi_dev_hid_uid_match(adev, dev_id->id, NULL))
47362306a36Sopenharmony_ci				break;
47462306a36Sopenharmony_ci		if (dev_id->id[0])
47562306a36Sopenharmony_ci			data = (const struct amd_lps0_hid_device_data *) dev_id->driver_data;
47662306a36Sopenharmony_ci		else
47762306a36Sopenharmony_ci			data = &amd_cezanne;
47862306a36Sopenharmony_ci		lps0_dsm_func_mask = validate_dsm(adev->handle,
47962306a36Sopenharmony_ci					ACPI_LPS0_DSM_UUID_AMD, rev_id, &lps0_dsm_guid);
48062306a36Sopenharmony_ci		if (lps0_dsm_func_mask > 0x3 && data->check_off_by_one) {
48162306a36Sopenharmony_ci			lps0_dsm_func_mask = (lps0_dsm_func_mask << 1) | 0x1;
48262306a36Sopenharmony_ci			acpi_handle_debug(adev->handle, "_DSM UUID %s: Adjusted function mask: 0x%x\n",
48362306a36Sopenharmony_ci					  ACPI_LPS0_DSM_UUID_AMD, lps0_dsm_func_mask);
48462306a36Sopenharmony_ci		} else if (lps0_dsm_func_mask_microsoft > 0 && rev_id) {
48562306a36Sopenharmony_ci			lps0_dsm_func_mask_microsoft = -EINVAL;
48662306a36Sopenharmony_ci			acpi_handle_debug(adev->handle, "_DSM Using AMD method\n");
48762306a36Sopenharmony_ci		}
48862306a36Sopenharmony_ci	} else {
48962306a36Sopenharmony_ci		rev_id = 1;
49062306a36Sopenharmony_ci		lps0_dsm_func_mask = validate_dsm(adev->handle,
49162306a36Sopenharmony_ci					ACPI_LPS0_DSM_UUID, rev_id, &lps0_dsm_guid);
49262306a36Sopenharmony_ci		lps0_dsm_func_mask_microsoft = -EINVAL;
49362306a36Sopenharmony_ci	}
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	if (lps0_dsm_func_mask < 0 && lps0_dsm_func_mask_microsoft < 0)
49662306a36Sopenharmony_ci		return 0; //function evaluation failed
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	lps0_device_handle = adev->handle;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	if (acpi_s2idle_vendor_amd())
50162306a36Sopenharmony_ci		lpi_device_get_constraints_amd();
50262306a36Sopenharmony_ci	else
50362306a36Sopenharmony_ci		lpi_device_get_constraints();
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	/*
50662306a36Sopenharmony_ci	 * Use suspend-to-idle by default if ACPI_FADT_LOW_POWER_S0 is set in
50762306a36Sopenharmony_ci	 * the FADT and the default suspend mode was not set from the command
50862306a36Sopenharmony_ci	 * line.
50962306a36Sopenharmony_ci	 */
51062306a36Sopenharmony_ci	if ((acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) &&
51162306a36Sopenharmony_ci	    mem_sleep_default > PM_SUSPEND_MEM && !acpi_sleep_default_s3) {
51262306a36Sopenharmony_ci		mem_sleep_current = PM_SUSPEND_TO_IDLE;
51362306a36Sopenharmony_ci		pr_info("Low-power S0 idle used by default for system suspend\n");
51462306a36Sopenharmony_ci	}
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci	/*
51762306a36Sopenharmony_ci	 * Some LPS0 systems, like ASUS Zenbook UX430UNR/i7-8550U, require the
51862306a36Sopenharmony_ci	 * EC GPE to be enabled while suspended for certain wakeup devices to
51962306a36Sopenharmony_ci	 * work, so mark it as wakeup-capable.
52062306a36Sopenharmony_ci	 */
52162306a36Sopenharmony_ci	acpi_ec_mark_gpe_for_wake();
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	return 0;
52462306a36Sopenharmony_ci}
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_cistatic struct acpi_scan_handler lps0_handler = {
52762306a36Sopenharmony_ci	.ids = lps0_device_ids,
52862306a36Sopenharmony_ci	.attach = lps0_device_attach,
52962306a36Sopenharmony_ci};
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ciint acpi_s2idle_prepare_late(void)
53262306a36Sopenharmony_ci{
53362306a36Sopenharmony_ci	struct acpi_s2idle_dev_ops *handler;
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	if (!lps0_device_handle || sleep_no_lps0)
53662306a36Sopenharmony_ci		return 0;
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	if (pm_debug_messages_on)
53962306a36Sopenharmony_ci		lpi_check_constraints();
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	/* Screen off */
54262306a36Sopenharmony_ci	if (lps0_dsm_func_mask > 0)
54362306a36Sopenharmony_ci		acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
54462306a36Sopenharmony_ci					ACPI_LPS0_SCREEN_OFF_AMD :
54562306a36Sopenharmony_ci					ACPI_LPS0_SCREEN_OFF,
54662306a36Sopenharmony_ci					lps0_dsm_func_mask, lps0_dsm_guid);
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	if (lps0_dsm_func_mask_microsoft > 0)
54962306a36Sopenharmony_ci		acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF,
55062306a36Sopenharmony_ci				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	/* LPS0 entry */
55362306a36Sopenharmony_ci	if (lps0_dsm_func_mask > 0)
55462306a36Sopenharmony_ci		acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
55562306a36Sopenharmony_ci					ACPI_LPS0_ENTRY_AMD :
55662306a36Sopenharmony_ci					ACPI_LPS0_ENTRY,
55762306a36Sopenharmony_ci					lps0_dsm_func_mask, lps0_dsm_guid);
55862306a36Sopenharmony_ci	if (lps0_dsm_func_mask_microsoft > 0) {
55962306a36Sopenharmony_ci		/* modern standby entry */
56062306a36Sopenharmony_ci		acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY,
56162306a36Sopenharmony_ci				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
56262306a36Sopenharmony_ci		acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY,
56362306a36Sopenharmony_ci				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
56462306a36Sopenharmony_ci	}
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) {
56762306a36Sopenharmony_ci		if (handler->prepare)
56862306a36Sopenharmony_ci			handler->prepare();
56962306a36Sopenharmony_ci	}
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	return 0;
57262306a36Sopenharmony_ci}
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_civoid acpi_s2idle_check(void)
57562306a36Sopenharmony_ci{
57662306a36Sopenharmony_ci	struct acpi_s2idle_dev_ops *handler;
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_ci	if (!lps0_device_handle || sleep_no_lps0)
57962306a36Sopenharmony_ci		return;
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ci	list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) {
58262306a36Sopenharmony_ci		if (handler->check)
58362306a36Sopenharmony_ci			handler->check();
58462306a36Sopenharmony_ci	}
58562306a36Sopenharmony_ci}
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_civoid acpi_s2idle_restore_early(void)
58862306a36Sopenharmony_ci{
58962306a36Sopenharmony_ci	struct acpi_s2idle_dev_ops *handler;
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci	if (!lps0_device_handle || sleep_no_lps0)
59262306a36Sopenharmony_ci		return;
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci	list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node)
59562306a36Sopenharmony_ci		if (handler->restore)
59662306a36Sopenharmony_ci			handler->restore();
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	/* LPS0 exit */
59962306a36Sopenharmony_ci	if (lps0_dsm_func_mask > 0)
60062306a36Sopenharmony_ci		acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
60162306a36Sopenharmony_ci					ACPI_LPS0_EXIT_AMD :
60262306a36Sopenharmony_ci					ACPI_LPS0_EXIT,
60362306a36Sopenharmony_ci					lps0_dsm_func_mask, lps0_dsm_guid);
60462306a36Sopenharmony_ci	if (lps0_dsm_func_mask_microsoft > 0)
60562306a36Sopenharmony_ci		acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT,
60662306a36Sopenharmony_ci				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci	/* Modern standby exit */
60962306a36Sopenharmony_ci	if (lps0_dsm_func_mask_microsoft > 0)
61062306a36Sopenharmony_ci		acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT,
61162306a36Sopenharmony_ci				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
61262306a36Sopenharmony_ci
61362306a36Sopenharmony_ci	/* Screen on */
61462306a36Sopenharmony_ci	if (lps0_dsm_func_mask_microsoft > 0)
61562306a36Sopenharmony_ci		acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON,
61662306a36Sopenharmony_ci				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
61762306a36Sopenharmony_ci	if (lps0_dsm_func_mask > 0)
61862306a36Sopenharmony_ci		acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
61962306a36Sopenharmony_ci					ACPI_LPS0_SCREEN_ON_AMD :
62062306a36Sopenharmony_ci					ACPI_LPS0_SCREEN_ON,
62162306a36Sopenharmony_ci					lps0_dsm_func_mask, lps0_dsm_guid);
62262306a36Sopenharmony_ci}
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_cistatic const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = {
62562306a36Sopenharmony_ci	.begin = acpi_s2idle_begin,
62662306a36Sopenharmony_ci	.prepare = acpi_s2idle_prepare,
62762306a36Sopenharmony_ci	.prepare_late = acpi_s2idle_prepare_late,
62862306a36Sopenharmony_ci	.check = acpi_s2idle_check,
62962306a36Sopenharmony_ci	.wake = acpi_s2idle_wake,
63062306a36Sopenharmony_ci	.restore_early = acpi_s2idle_restore_early,
63162306a36Sopenharmony_ci	.restore = acpi_s2idle_restore,
63262306a36Sopenharmony_ci	.end = acpi_s2idle_end,
63362306a36Sopenharmony_ci};
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_civoid __init acpi_s2idle_setup(void)
63662306a36Sopenharmony_ci{
63762306a36Sopenharmony_ci	acpi_scan_add_handler(&lps0_handler);
63862306a36Sopenharmony_ci	s2idle_set_ops(&acpi_s2idle_ops_lps0);
63962306a36Sopenharmony_ci}
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ciint acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg)
64262306a36Sopenharmony_ci{
64362306a36Sopenharmony_ci	unsigned int sleep_flags;
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	if (!lps0_device_handle || sleep_no_lps0)
64662306a36Sopenharmony_ci		return -ENODEV;
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	sleep_flags = lock_system_sleep();
64962306a36Sopenharmony_ci	list_add(&arg->list_node, &lps0_s2idle_devops_head);
65062306a36Sopenharmony_ci	unlock_system_sleep(sleep_flags);
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_ci	return 0;
65362306a36Sopenharmony_ci}
65462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(acpi_register_lps0_dev);
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_civoid acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg)
65762306a36Sopenharmony_ci{
65862306a36Sopenharmony_ci	unsigned int sleep_flags;
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	if (!lps0_device_handle || sleep_no_lps0)
66162306a36Sopenharmony_ci		return;
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_ci	sleep_flags = lock_system_sleep();
66462306a36Sopenharmony_ci	list_del(&arg->list_node);
66562306a36Sopenharmony_ci	unlock_system_sleep(sleep_flags);
66662306a36Sopenharmony_ci}
66762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev);
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_ci#endif /* CONFIG_SUSPEND */
670