brintos

brintos / linux-shallow public Read only

0
0
Text · 17.8 KiB · dd0b40b Raw
684 lines · c
1// SPDX-License-Identifier: GPL-2.02/*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 ACPI10 * device object with the PNP0D80 compatible device ID (System Power Management11 * 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 a13 * low-power state in which certain types of activity are not desirable or that14 * it is leaving such a state, which allows the platform to adjust its operation15 * 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_SUSPEND26 27static bool sleep_no_lps0 __read_mostly;28module_param(sleep_no_lps0, bool, 0644);29MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface");30 31static 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	142#define ACPI_LPS0_SCREEN_OFF	343#define ACPI_LPS0_SCREEN_ON	444#define ACPI_LPS0_ENTRY		545#define ACPI_LPS0_EXIT		646#define ACPI_LPS0_MS_ENTRY      747#define ACPI_LPS0_MS_EXIT       848 49/* AMD */50#define ACPI_LPS0_DSM_UUID_AMD      "e3f32452-febc-43ce-9039-932122d37721"51#define ACPI_LPS0_ENTRY_AMD         252#define ACPI_LPS0_EXIT_AMD          353#define ACPI_LPS0_SCREEN_OFF_AMD    454#define ACPI_LPS0_SCREEN_ON_AMD     555 56static acpi_handle lps0_device_handle;57static guid_t lps0_dsm_guid;58static int lps0_dsm_func_mask;59 60static guid_t lps0_dsm_guid_microsoft;61static int lps0_dsm_func_mask_microsoft;62static int lps0_dsm_state;63 64/* Device constraint entry structure */65struct lpi_device_info {66	char *name;67	int enabled;68	union acpi_object *package;69};70 71/* Constraint package structure */72struct lpi_device_constraint {73	int uid;74	int min_dstate;75	int function_states;76};77 78struct lpi_constraints {79	acpi_handle handle;80	int min_dstate;81};82 83/* AMD Constraint package structure */84struct lpi_device_constraint_amd {85	char *name;86	int enabled;87	int function_states;88	int min_dstate;89};90 91static LIST_HEAD(lps0_s2idle_devops_head);92 93static struct lpi_constraints *lpi_constraints_table;94static int lpi_constraints_table_size;95static 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 102static 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(lps0_device_handle, &lps0_dsm_guid,108					  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(package->package.count,128							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, dev_info.name, &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 188free_acpi_buffer:189	ACPI_FREE(out_obj);190}191 192static void lpi_device_get_constraints(void)193{194	union acpi_object *out_obj;195	int i;196 197	out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid,198					  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(out_obj->package.count,208					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, info.name, &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 298free_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 the307 * device can be so as to allow the platform as a whole to achieve additional308 * 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 */314int 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 326static 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(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 355static bool acpi_s2idle_vendor_amd(void)356{357	return boot_cpu_data.x86_vendor == X86_VENDOR_AMD;358}359 360static 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 393static 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(lps0_device_handle, &dsm_guid,401					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 414static 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, dsm_guid);420 421	/* Check if the _DSM is present and as expected. */422	obj = acpi_evaluate_dsm_typed(handle, dsm_guid, rev, 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 432out:433	ACPI_FREE(obj);434	return ret;435}436 437struct amd_lps0_hid_device_data {438	const bool check_off_by_one;439};440 441static const struct amd_lps0_hid_device_data amd_picasso = {442	.check_off_by_one = true,443};444 445static const struct amd_lps0_hid_device_data amd_cezanne = {446	.check_off_by_one = false,447};448 449static 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 457static 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(adev->handle,464						    ACPI_LPS0_DSM_UUID_MICROSOFT, 0,465						    &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		else476			data = &amd_cezanne;477		lps0_dsm_func_mask = validate_dsm(adev->handle,478					ACPI_LPS0_DSM_UUID_AMD, rev_id, &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(adev->handle,490					ACPI_LPS0_DSM_UUID, rev_id, &lps0_dsm_guid);491		if (lps0_dsm_func_mask > 0 && lps0_dsm_func_mask_microsoft > 0) {492			unsigned int func_mask;493 494			/*495			 * Log a message if the _DSM function sets for two496			 * different UUIDs overlap.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		}504	}505 506	if (lps0_dsm_func_mask < 0 && lps0_dsm_func_mask_microsoft < 0)507		return 0; //function evaluation failed508 509	lps0_device_handle = adev->handle;510 511	if (acpi_s2idle_vendor_amd())512		lpi_device_get_constraints_amd();513	else514		lpi_device_get_constraints();515 516	/*517	 * Use suspend-to-idle by default if ACPI_FADT_LOW_POWER_S0 is set in518	 * the FADT and the default suspend mode was not set from the command519	 * line.520	 */521	if ((acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) &&522	    mem_sleep_default > PM_SUSPEND_MEM && !acpi_sleep_default_s3) {523		mem_sleep_current = PM_SUSPEND_TO_IDLE;524		pr_info("Low-power S0 idle used by default for system suspend\n");525	}526 527	/*528	 * Some LPS0 systems, like ASUS Zenbook UX430UNR/i7-8550U, require the529	 * EC GPE to be enabled while suspended for certain wakeup devices to530	 * work, so mark it as wakeup-capable.531	 */532	acpi_ec_mark_gpe_for_wake();533 534	return 0;535}536 537static struct acpi_scan_handler lps0_handler = {538	.ids = lps0_device_ids,539	.attach = lps0_device_attach,540};541 542int acpi_s2idle_prepare_late(void)543{544	struct acpi_s2idle_dev_ops *handler;545 546	if (!lps0_device_handle || sleep_no_lps0)547		return 0;548 549	if (pm_debug_messages_on)550		lpi_check_constraints();551 552	/* Screen off */553	if (lps0_dsm_func_mask > 0)554		acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?555					ACPI_LPS0_SCREEN_OFF_AMD :556					ACPI_LPS0_SCREEN_OFF,557					lps0_dsm_func_mask, lps0_dsm_guid);558 559	if (lps0_dsm_func_mask_microsoft > 0)560		acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF,561				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);562 563	/* LPS0 entry */564	if (lps0_dsm_func_mask > 0 && acpi_s2idle_vendor_amd())565		acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY_AMD,566					lps0_dsm_func_mask, lps0_dsm_guid);567 568	if (lps0_dsm_func_mask_microsoft > 0) {569		/* Modern Standby entry */570		acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY,571				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);572		acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY,573				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);574	}575 576	if (lps0_dsm_func_mask > 0 && !acpi_s2idle_vendor_amd())577		acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY,578					lps0_dsm_func_mask, lps0_dsm_guid);579 580	list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) {581		if (handler->prepare)582			handler->prepare();583	}584 585	return 0;586}587 588void acpi_s2idle_check(void)589{590	struct acpi_s2idle_dev_ops *handler;591 592	if (!lps0_device_handle || sleep_no_lps0)593		return;594 595	list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) {596		if (handler->check)597			handler->check();598	}599}600 601void acpi_s2idle_restore_early(void)602{603	struct acpi_s2idle_dev_ops *handler;604 605	if (!lps0_device_handle || sleep_no_lps0)606		return;607 608	list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node)609		if (handler->restore)610			handler->restore();611 612	/* LPS0 exit */613	if (lps0_dsm_func_mask > 0)614		acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?615					ACPI_LPS0_EXIT_AMD :616					ACPI_LPS0_EXIT,617					lps0_dsm_func_mask, lps0_dsm_guid);618 619	if (lps0_dsm_func_mask_microsoft > 0) {620		acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT,621				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);622		/* Modern Standby exit */623		acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT,624				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);625	}626 627	/* Screen on */628	if (lps0_dsm_func_mask_microsoft > 0)629		acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON,630				lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);631	if (lps0_dsm_func_mask > 0)632		acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?633					ACPI_LPS0_SCREEN_ON_AMD :634					ACPI_LPS0_SCREEN_ON,635					lps0_dsm_func_mask, lps0_dsm_guid);636}637 638static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = {639	.begin = acpi_s2idle_begin,640	.prepare = acpi_s2idle_prepare,641	.prepare_late = acpi_s2idle_prepare_late,642	.check = acpi_s2idle_check,643	.wake = acpi_s2idle_wake,644	.restore_early = acpi_s2idle_restore_early,645	.restore = acpi_s2idle_restore,646	.end = acpi_s2idle_end,647};648 649void __init acpi_s2idle_setup(void)650{651	acpi_scan_add_handler(&lps0_handler);652	s2idle_set_ops(&acpi_s2idle_ops_lps0);653}654 655int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg)656{657	unsigned int sleep_flags;658 659	if (!lps0_device_handle || sleep_no_lps0)660		return -ENODEV;661 662	sleep_flags = lock_system_sleep();663	list_add(&arg->list_node, &lps0_s2idle_devops_head);664	unlock_system_sleep(sleep_flags);665 666	return 0;667}668EXPORT_SYMBOL_GPL(acpi_register_lps0_dev);669 670void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg)671{672	unsigned int sleep_flags;673 674	if (!lps0_device_handle || sleep_no_lps0)675		return;676 677	sleep_flags = lock_system_sleep();678	list_del(&arg->list_node);679	unlock_system_sleep(sleep_flags);680}681EXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev);682 683#endif /* CONFIG_SUSPEND */684