diff options
| author | Mark Brown <broonie@kernel.org> | 2020-12-28 17:20:00 +0300 | 
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2020-12-28 17:20:00 +0300 | 
| commit | 2ae6f64ce1ce304b502461fdfe0b96c8171ae2cc (patch) | |
| tree | 88e987c447daf2c29e2d4c15e58d1029b0cc78c2 /drivers/acpi/x86/s2idle.c | |
| parent | 3b66e4a8e58a85af3212c7117d7a29c9ef6679a2 (diff) | |
| parent | 5c8fe583cce542aa0b84adc939ce85293de36e5e (diff) | |
| download | linux-2ae6f64ce1ce304b502461fdfe0b96c8171ae2cc.tar.xz | |
Merge tag 'v5.11-rc1' into regulator-5.11
Linux 5.11-rc1
Diffstat (limited to 'drivers/acpi/x86/s2idle.c')
| -rw-r--r-- | drivers/acpi/x86/s2idle.c | 460 | 
1 files changed, 460 insertions, 0 deletions
diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c new file mode 100644 index 000000000000..25fea34b544c --- /dev/null +++ b/drivers/acpi/x86/s2idle.c @@ -0,0 +1,460 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Architecture-specific ACPI-based support for suspend-to-idle. + * + * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> + * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> + * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> + * + * On platforms supporting the Low Power S0 Idle interface there is an ACPI + * device object with the PNP0D80 compatible device ID (System Power Management + * Controller) and a specific _DSM method under it.  That method, if present, + * can be used to indicate to the platform that the OS is transitioning into a + * low-power state in which certain types of activity are not desirable or that + * it is leaving such a state, which allows the platform to adjust its operation + * mode accordingly. + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/suspend.h> + +#include "../sleep.h" + +#ifdef CONFIG_SUSPEND + +static bool sleep_no_lps0 __read_mostly; +module_param(sleep_no_lps0, bool, 0644); +MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface"); + +static const struct acpi_device_id lps0_device_ids[] = { +	{"PNP0D80", }, +	{"", }, +}; + +#define ACPI_LPS0_DSM_UUID	"c4eb40a0-6cd2-11e2-bcfd-0800200c9a66" + +#define ACPI_LPS0_GET_DEVICE_CONSTRAINTS	1 +#define ACPI_LPS0_SCREEN_OFF	3 +#define ACPI_LPS0_SCREEN_ON	4 +#define ACPI_LPS0_ENTRY		5 +#define ACPI_LPS0_EXIT		6 + +/* AMD */ +#define ACPI_LPS0_DSM_UUID_AMD      "e3f32452-febc-43ce-9039-932122d37721" +#define ACPI_LPS0_SCREEN_OFF_AMD    4 +#define ACPI_LPS0_SCREEN_ON_AMD     5 + +static acpi_handle lps0_device_handle; +static guid_t lps0_dsm_guid; +static char lps0_dsm_func_mask; + +/* Device constraint entry structure */ +struct lpi_device_info { +	char *name; +	int enabled; +	union acpi_object *package; +}; + +/* Constraint package structure */ +struct lpi_device_constraint { +	int uid; +	int min_dstate; +	int function_states; +}; + +struct lpi_constraints { +	acpi_handle handle; +	int min_dstate; +}; + +/* AMD */ +/* Device constraint entry structure */ +struct lpi_device_info_amd { +	int revision; +	int count; +	union acpi_object *package; +}; + +/* Constraint package structure */ +struct lpi_device_constraint_amd { +	char *name; +	int enabled; +	int function_states; +	int min_dstate; +}; + +static struct lpi_constraints *lpi_constraints_table; +static int lpi_constraints_table_size; +static int rev_id; + +static void lpi_device_get_constraints_amd(void) +{ +	union acpi_object *out_obj; +	int i, j, k; + +	out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid, +					  1, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, +					  NULL, ACPI_TYPE_PACKAGE); + +	if (!out_obj) +		return; + +	acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n", +			  out_obj ? "successful" : "failed"); + +	for (i = 0; i < out_obj->package.count; i++) { +		union acpi_object *package = &out_obj->package.elements[i]; +		struct lpi_device_info_amd info = { }; + +		if (package->type == ACPI_TYPE_INTEGER) { +			switch (i) { +			case 0: +				info.revision = package->integer.value; +				break; +			case 1: +				info.count = package->integer.value; +				break; +			} +		} else if (package->type == ACPI_TYPE_PACKAGE) { +			lpi_constraints_table = kcalloc(package->package.count, +							sizeof(*lpi_constraints_table), +							GFP_KERNEL); + +			if (!lpi_constraints_table) +				goto free_acpi_buffer; + +			acpi_handle_debug(lps0_device_handle, +					  "LPI: constraints list begin:\n"); + +			for (j = 0; j < package->package.count; ++j) { +				union acpi_object *info_obj = &package->package.elements[j]; +				struct lpi_device_constraint_amd dev_info = {}; +				struct lpi_constraints *list; +				acpi_status status; + +				for (k = 0; k < info_obj->package.count; ++k) { +					union acpi_object *obj = &info_obj->package.elements[k]; +					union acpi_object *obj_new; + +					list = &lpi_constraints_table[lpi_constraints_table_size]; +					list->min_dstate = -1; + +					obj_new = &obj[k]; +					switch (k) { +					case 0: +						dev_info.enabled = obj->integer.value; +						break; +					case 1: +						dev_info.name = obj->string.pointer; +						break; +					case 2: +						dev_info.function_states = obj->integer.value; +						break; +					case 3: +						dev_info.min_dstate = obj->integer.value; +						break; +					} + +					if (!dev_info.enabled || !dev_info.name || +					    !dev_info.min_dstate) +						continue; + +					status = acpi_get_handle(NULL, dev_info.name, +								 &list->handle); +					if (ACPI_FAILURE(status)) +						continue; + +					acpi_handle_debug(lps0_device_handle, +							  "Name:%s\n", dev_info.name); + +					list->min_dstate = dev_info.min_dstate; + +					if (list->min_dstate < 0) { +						acpi_handle_debug(lps0_device_handle, +								  "Incomplete constraint defined\n"); +						continue; +					} +				} +				lpi_constraints_table_size++; +			} +		} +	} + +	acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n"); + +free_acpi_buffer: +	ACPI_FREE(out_obj); +} + +static void lpi_device_get_constraints(void) +{ +	union acpi_object *out_obj; +	int i; + +	out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid, +					  1, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, +					  NULL, ACPI_TYPE_PACKAGE); + +	acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n", +			  out_obj ? "successful" : "failed"); + +	if (!out_obj) +		return; + +	lpi_constraints_table = kcalloc(out_obj->package.count, +					sizeof(*lpi_constraints_table), +					GFP_KERNEL); +	if (!lpi_constraints_table) +		goto free_acpi_buffer; + +	acpi_handle_debug(lps0_device_handle, "LPI: constraints list begin:\n"); + +	for (i = 0; i < out_obj->package.count; i++) { +		struct lpi_constraints *constraint; +		acpi_status status; +		union acpi_object *package = &out_obj->package.elements[i]; +		struct lpi_device_info info = { }; +		int package_count = 0, j; + +		if (!package) +			continue; + +		for (j = 0; j < package->package.count; ++j) { +			union acpi_object *element = +					&(package->package.elements[j]); + +			switch (element->type) { +			case ACPI_TYPE_INTEGER: +				info.enabled = element->integer.value; +				break; +			case ACPI_TYPE_STRING: +				info.name = element->string.pointer; +				break; +			case ACPI_TYPE_PACKAGE: +				package_count = element->package.count; +				info.package = element->package.elements; +				break; +			} +		} + +		if (!info.enabled || !info.package || !info.name) +			continue; + +		constraint = &lpi_constraints_table[lpi_constraints_table_size]; + +		status = acpi_get_handle(NULL, info.name, &constraint->handle); +		if (ACPI_FAILURE(status)) +			continue; + +		acpi_handle_debug(lps0_device_handle, +				  "index:%d Name:%s\n", i, info.name); + +		constraint->min_dstate = -1; + +		for (j = 0; j < package_count; ++j) { +			union acpi_object *info_obj = &info.package[j]; +			union acpi_object *cnstr_pkg; +			union acpi_object *obj; +			struct lpi_device_constraint dev_info; + +			switch (info_obj->type) { +			case ACPI_TYPE_INTEGER: +				/* version */ +				break; +			case ACPI_TYPE_PACKAGE: +				if (info_obj->package.count < 2) +					break; + +				cnstr_pkg = info_obj->package.elements; +				obj = &cnstr_pkg[0]; +				dev_info.uid = obj->integer.value; +				obj = &cnstr_pkg[1]; +				dev_info.min_dstate = obj->integer.value; + +				acpi_handle_debug(lps0_device_handle, +					"uid:%d min_dstate:%s\n", +					dev_info.uid, +					acpi_power_state_string(dev_info.min_dstate)); + +				constraint->min_dstate = dev_info.min_dstate; +				break; +			} +		} + +		if (constraint->min_dstate < 0) { +			acpi_handle_debug(lps0_device_handle, +					  "Incomplete constraint defined\n"); +			continue; +		} + +		lpi_constraints_table_size++; +	} + +	acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n"); + +free_acpi_buffer: +	ACPI_FREE(out_obj); +} + +static void lpi_check_constraints(void) +{ +	int i; + +	for (i = 0; i < lpi_constraints_table_size; ++i) { +		acpi_handle handle = lpi_constraints_table[i].handle; +		struct acpi_device *adev; + +		if (!handle || acpi_bus_get_device(handle, &adev)) +			continue; + +		acpi_handle_debug(handle, +			"LPI: required min power state:%s current power state:%s\n", +			acpi_power_state_string(lpi_constraints_table[i].min_dstate), +			acpi_power_state_string(adev->power.state)); + +		if (!adev->flags.power_manageable) { +			acpi_handle_info(handle, "LPI: Device not power manageable\n"); +			lpi_constraints_table[i].handle = NULL; +			continue; +		} + +		if (adev->power.state < lpi_constraints_table[i].min_dstate) +			acpi_handle_info(handle, +				"LPI: Constraint not met; min power state:%s current power state:%s\n", +				acpi_power_state_string(lpi_constraints_table[i].min_dstate), +				acpi_power_state_string(adev->power.state)); +	} +} + +static void acpi_sleep_run_lps0_dsm(unsigned int func) +{ +	union acpi_object *out_obj; + +	if (!(lps0_dsm_func_mask & (1 << func))) +		return; + +	out_obj = acpi_evaluate_dsm(lps0_device_handle, &lps0_dsm_guid, rev_id, func, NULL); +	ACPI_FREE(out_obj); + +	acpi_handle_debug(lps0_device_handle, "_DSM function %u evaluation %s\n", +			  func, out_obj ? "successful" : "failed"); +} + +static bool acpi_s2idle_vendor_amd(void) +{ +	return boot_cpu_data.x86_vendor == X86_VENDOR_AMD; +} + +static int lps0_device_attach(struct acpi_device *adev, +			      const struct acpi_device_id *not_used) +{ +	union acpi_object *out_obj; + +	if (lps0_device_handle) +		return 0; + +	if (!(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)) +		return 0; + +	if (acpi_s2idle_vendor_amd()) { +		guid_parse(ACPI_LPS0_DSM_UUID_AMD, &lps0_dsm_guid); +		out_obj = acpi_evaluate_dsm(adev->handle, &lps0_dsm_guid, 0, 0, NULL); +		rev_id = 0; +	} else { +		guid_parse(ACPI_LPS0_DSM_UUID, &lps0_dsm_guid); +		out_obj = acpi_evaluate_dsm(adev->handle, &lps0_dsm_guid, 1, 0, NULL); +		rev_id = 1; +	} + +	/* Check if the _DSM is present and as expected. */ +	if (!out_obj || out_obj->type != ACPI_TYPE_BUFFER) { +		acpi_handle_debug(adev->handle, +				  "_DSM function 0 evaluation failed\n"); +		return 0; +	} + +	lps0_dsm_func_mask = *(char *)out_obj->buffer.pointer; + +	ACPI_FREE(out_obj); + +	acpi_handle_debug(adev->handle, "_DSM function mask: 0x%x\n", +			  lps0_dsm_func_mask); + +	lps0_device_handle = adev->handle; + +	if (acpi_s2idle_vendor_amd()) +		lpi_device_get_constraints_amd(); +	else +		lpi_device_get_constraints(); + +	/* +	 * Use suspend-to-idle by default if the default suspend mode was not +	 * set from the command line. +	 */ +	if (mem_sleep_default > PM_SUSPEND_MEM && !acpi_sleep_default_s3) +		mem_sleep_current = PM_SUSPEND_TO_IDLE; + +	/* +	 * Some LPS0 systems, like ASUS Zenbook UX430UNR/i7-8550U, require the +	 * EC GPE to be enabled while suspended for certain wakeup devices to +	 * work, so mark it as wakeup-capable. +	 */ +	acpi_ec_mark_gpe_for_wake(); + +	return 0; +} + +static struct acpi_scan_handler lps0_handler = { +	.ids = lps0_device_ids, +	.attach = lps0_device_attach, +}; + +int acpi_s2idle_prepare_late(void) +{ +	if (!lps0_device_handle || sleep_no_lps0) +		return 0; + +	if (pm_debug_messages_on) +		lpi_check_constraints(); + +	if (acpi_s2idle_vendor_amd()) { +		acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF_AMD); +	} else { +		acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF); +		acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY); +	} + +	return 0; +} + +void acpi_s2idle_restore_early(void) +{ +	if (!lps0_device_handle || sleep_no_lps0) +		return; + +	if (acpi_s2idle_vendor_amd()) { +		acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON_AMD); +	} else { +		acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT); +		acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON); +	} +} + +static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = { +	.begin = acpi_s2idle_begin, +	.prepare = acpi_s2idle_prepare, +	.prepare_late = acpi_s2idle_prepare_late, +	.wake = acpi_s2idle_wake, +	.restore_early = acpi_s2idle_restore_early, +	.restore = acpi_s2idle_restore, +	.end = acpi_s2idle_end, +}; + +void acpi_s2idle_setup(void) +{ +	acpi_scan_add_handler(&lps0_handler); +	s2idle_set_ops(&acpi_s2idle_ops_lps0); +} + +#endif /* CONFIG_SUSPEND */  | 
