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