| 1 | // SPDX-License-Identifier: GPL-2.0 | 
|---|
| 2 | /* | 
|---|
| 3 | * Architecture-specific ACPI-based support for suspend-to-idle. | 
|---|
| 4 | * | 
|---|
| 5 | * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 
|---|
| 6 | * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> | 
|---|
| 7 | * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> | 
|---|
| 8 | * | 
|---|
| 9 | * On platforms supporting the Low Power S0 Idle interface there is an ACPI | 
|---|
| 10 | * device object with the PNP0D80 compatible device ID (System Power Management | 
|---|
| 11 | * Controller) and a specific _DSM method under it.  That method, if present, | 
|---|
| 12 | * can be used to indicate to the platform that the OS is transitioning into a | 
|---|
| 13 | * low-power state in which certain types of activity are not desirable or that | 
|---|
| 14 | * it is leaving such a state, which allows the platform to adjust its operation | 
|---|
| 15 | * mode accordingly. | 
|---|
| 16 | */ | 
|---|
| 17 |  | 
|---|
| 18 | #include <linux/acpi.h> | 
|---|
| 19 | #include <linux/device.h> | 
|---|
| 20 | #include <linux/dmi.h> | 
|---|
| 21 | #include <linux/suspend.h> | 
|---|
| 22 |  | 
|---|
| 23 | #include "../sleep.h" | 
|---|
| 24 |  | 
|---|
| 25 | #ifdef CONFIG_SUSPEND | 
|---|
| 26 |  | 
|---|
| 27 | static bool sleep_no_lps0 __read_mostly; | 
|---|
| 28 | module_param(sleep_no_lps0, bool, 0644); | 
|---|
| 29 | MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface"); | 
|---|
| 30 |  | 
|---|
| 31 | static const struct acpi_device_id lps0_device_ids[] = { | 
|---|
| 32 | { "PNP0D80", }, | 
|---|
| 33 | {.id: "", }, | 
|---|
| 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	1 | 
|---|
| 42 | #define ACPI_LPS0_SCREEN_OFF	3 | 
|---|
| 43 | #define ACPI_LPS0_SCREEN_ON	4 | 
|---|
| 44 | #define ACPI_LPS0_ENTRY		5 | 
|---|
| 45 | #define ACPI_LPS0_EXIT		6 | 
|---|
| 46 | #define ACPI_LPS0_MS_ENTRY      7 | 
|---|
| 47 | #define ACPI_LPS0_MS_EXIT       8 | 
|---|
| 48 |  | 
|---|
| 49 | /* AMD */ | 
|---|
| 50 | #define ACPI_LPS0_DSM_UUID_AMD      "e3f32452-febc-43ce-9039-932122d37721" | 
|---|
| 51 | #define ACPI_LPS0_ENTRY_AMD         2 | 
|---|
| 52 | #define ACPI_LPS0_EXIT_AMD          3 | 
|---|
| 53 | #define ACPI_LPS0_SCREEN_OFF_AMD    4 | 
|---|
| 54 | #define ACPI_LPS0_SCREEN_ON_AMD     5 | 
|---|
| 55 |  | 
|---|
| 56 | static acpi_handle lps0_device_handle; | 
|---|
| 57 | static guid_t lps0_dsm_guid; | 
|---|
| 58 | static int lps0_dsm_func_mask; | 
|---|
| 59 |  | 
|---|
| 60 | static guid_t lps0_dsm_guid_microsoft; | 
|---|
| 61 | static int lps0_dsm_func_mask_microsoft; | 
|---|
| 62 | static int lps0_dsm_state; | 
|---|
| 63 |  | 
|---|
| 64 | /* Device constraint entry structure */ | 
|---|
| 65 | struct lpi_device_info { | 
|---|
| 66 | char *name; | 
|---|
| 67 | int enabled; | 
|---|
| 68 | union acpi_object *package; | 
|---|
| 69 | }; | 
|---|
| 70 |  | 
|---|
| 71 | /* Constraint package structure */ | 
|---|
| 72 | struct lpi_device_constraint { | 
|---|
| 73 | int uid; | 
|---|
| 74 | int min_dstate; | 
|---|
| 75 | int function_states; | 
|---|
| 76 | }; | 
|---|
| 77 |  | 
|---|
| 78 | struct lpi_constraints { | 
|---|
| 79 | acpi_handle handle; | 
|---|
| 80 | int min_dstate; | 
|---|
| 81 | }; | 
|---|
| 82 |  | 
|---|
| 83 | /* AMD Constraint package structure */ | 
|---|
| 84 | struct lpi_device_constraint_amd { | 
|---|
| 85 | char *name; | 
|---|
| 86 | int enabled; | 
|---|
| 87 | int function_states; | 
|---|
| 88 | int min_dstate; | 
|---|
| 89 | }; | 
|---|
| 90 |  | 
|---|
| 91 | static LIST_HEAD(lps0_s2idle_devops_head); | 
|---|
| 92 |  | 
|---|
| 93 | static struct lpi_constraints *lpi_constraints_table; | 
|---|
| 94 | static int lpi_constraints_table_size; | 
|---|
| 95 | static 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 |  | 
|---|
| 102 | static 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(handle: lps0_device_handle, guid: &lps0_dsm_guid, | 
|---|
| 108 | rev: 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, pathname: dev_info.name, ret_handle: &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 |  | 
|---|
| 188 | free_acpi_buffer: | 
|---|
| 189 | ACPI_FREE(out_obj); | 
|---|
| 190 | } | 
|---|
| 191 |  | 
|---|
| 192 | static void lpi_device_get_constraints(void) | 
|---|
| 193 | { | 
|---|
| 194 | union acpi_object *out_obj; | 
|---|
| 195 | int i; | 
|---|
| 196 |  | 
|---|
| 197 | out_obj = acpi_evaluate_dsm_typed(handle: lps0_device_handle, guid: &lps0_dsm_guid, | 
|---|
| 198 | rev: 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, pathname: info.name, ret_handle: &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 |  | 
|---|
| 298 | free_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 the | 
|---|
| 307 | * device can be so as to allow the platform as a whole to achieve additional | 
|---|
| 308 | * 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 | */ | 
|---|
| 314 | int 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 |  | 
|---|
| 326 | static 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(handle: 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 |  | 
|---|
| 355 | static bool acpi_s2idle_vendor_amd(void) | 
|---|
| 356 | { | 
|---|
| 357 | return boot_cpu_data.x86_vendor == X86_VENDOR_AMD; | 
|---|
| 358 | } | 
|---|
| 359 |  | 
|---|
| 360 | static 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 |  | 
|---|
| 393 | static 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(handle: lps0_device_handle, guid: &dsm_guid, | 
|---|
| 401 | rev: 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 |  | 
|---|
| 414 | static 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, u: dsm_guid); | 
|---|
| 420 |  | 
|---|
| 421 | /* Check if the _DSM is present and as expected. */ | 
|---|
| 422 | obj = acpi_evaluate_dsm_typed(handle, guid: dsm_guid, rev, func: 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 |  | 
|---|
| 432 | out: | 
|---|
| 433 | ACPI_FREE(obj); | 
|---|
| 434 | return ret; | 
|---|
| 435 | } | 
|---|
| 436 |  | 
|---|
| 437 | struct amd_lps0_hid_device_data { | 
|---|
| 438 | const bool check_off_by_one; | 
|---|
| 439 | }; | 
|---|
| 440 |  | 
|---|
| 441 | static const struct amd_lps0_hid_device_data amd_picasso = { | 
|---|
| 442 | .check_off_by_one = true, | 
|---|
| 443 | }; | 
|---|
| 444 |  | 
|---|
| 445 | static const struct amd_lps0_hid_device_data amd_cezanne = { | 
|---|
| 446 | .check_off_by_one = false, | 
|---|
| 447 | }; | 
|---|
| 448 |  | 
|---|
| 449 | static const struct acpi_device_id amd_hid_ids[] = { | 
|---|
| 450 | { "AMD0004",	(kernel_ulong_t)&amd_picasso,	}, | 
|---|
| 451 | {.id: "AMD0005",	.driver_data: (kernel_ulong_t)&amd_picasso,	}, | 
|---|
| 452 | {.id: "AMDI0005",	.driver_data: (kernel_ulong_t)&amd_picasso,	}, | 
|---|
| 453 | {.id: "AMDI0006",	.driver_data: (kernel_ulong_t)&amd_cezanne,	}, | 
|---|
| 454 | {} | 
|---|
| 455 | }; | 
|---|
| 456 |  | 
|---|
| 457 | static 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(handle: adev->handle, | 
|---|
| 464 | ACPI_LPS0_DSM_UUID_MICROSOFT, rev: 0, | 
|---|
| 465 | dsm_guid: &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 | else | 
|---|
| 476 | data = &amd_cezanne; | 
|---|
| 477 | lps0_dsm_func_mask = validate_dsm(handle: adev->handle, | 
|---|
| 478 | ACPI_LPS0_DSM_UUID_AMD, rev: rev_id, dsm_guid: &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(handle: adev->handle, | 
|---|
| 490 | ACPI_LPS0_DSM_UUID, rev: rev_id, dsm_guid: &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 two | 
|---|
| 496 | * 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 failed | 
|---|
| 508 |  | 
|---|
| 509 | lps0_device_handle = adev->handle; | 
|---|
| 510 |  | 
|---|
| 511 | if (acpi_s2idle_vendor_amd()) | 
|---|
| 512 | lpi_device_get_constraints_amd(); | 
|---|
| 513 | else | 
|---|
| 514 | lpi_device_get_constraints(); | 
|---|
| 515 |  | 
|---|
| 516 | /* | 
|---|
| 517 | * Use suspend-to-idle by default if ACPI_FADT_LOW_POWER_S0 is set in | 
|---|
| 518 | * the FADT and the default suspend mode was not set from the command | 
|---|
| 519 | * 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 the | 
|---|
| 529 | * EC GPE to be enabled while suspended for certain wakeup devices to | 
|---|
| 530 | * work, so mark it as wakeup-capable. | 
|---|
| 531 | */ | 
|---|
| 532 | acpi_ec_mark_gpe_for_wake(); | 
|---|
| 533 |  | 
|---|
| 534 | return 0; | 
|---|
| 535 | } | 
|---|
| 536 |  | 
|---|
| 537 | static struct acpi_scan_handler lps0_handler = { | 
|---|
| 538 | .ids = lps0_device_ids, | 
|---|
| 539 | .attach = lps0_device_attach, | 
|---|
| 540 | }; | 
|---|
| 541 |  | 
|---|
| 542 | int 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(func: acpi_s2idle_vendor_amd() ? | 
|---|
| 555 | ACPI_LPS0_SCREEN_OFF_AMD : | 
|---|
| 556 | ACPI_LPS0_SCREEN_OFF, | 
|---|
| 557 | func_mask: lps0_dsm_func_mask, dsm_guid: lps0_dsm_guid); | 
|---|
| 558 |  | 
|---|
| 559 | if (lps0_dsm_func_mask_microsoft > 0) | 
|---|
| 560 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF, | 
|---|
| 561 | func_mask: lps0_dsm_func_mask_microsoft, dsm_guid: 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 | func_mask: lps0_dsm_func_mask, dsm_guid: 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 | func_mask: lps0_dsm_func_mask_microsoft, dsm_guid: lps0_dsm_guid_microsoft); | 
|---|
| 572 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY, | 
|---|
| 573 | func_mask: lps0_dsm_func_mask_microsoft, dsm_guid: 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 | func_mask: lps0_dsm_func_mask, dsm_guid: 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 |  | 
|---|
| 588 | void 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 |  | 
|---|
| 601 | void 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(func: acpi_s2idle_vendor_amd() ? | 
|---|
| 615 | ACPI_LPS0_EXIT_AMD : | 
|---|
| 616 | ACPI_LPS0_EXIT, | 
|---|
| 617 | func_mask: lps0_dsm_func_mask, dsm_guid: lps0_dsm_guid); | 
|---|
| 618 |  | 
|---|
| 619 | if (lps0_dsm_func_mask_microsoft > 0) { | 
|---|
| 620 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT, | 
|---|
| 621 | func_mask: lps0_dsm_func_mask_microsoft, dsm_guid: lps0_dsm_guid_microsoft); | 
|---|
| 622 | /* Modern Standby exit */ | 
|---|
| 623 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT, | 
|---|
| 624 | func_mask: lps0_dsm_func_mask_microsoft, dsm_guid: 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 | func_mask: lps0_dsm_func_mask_microsoft, dsm_guid: lps0_dsm_guid_microsoft); | 
|---|
| 631 | if (lps0_dsm_func_mask > 0) | 
|---|
| 632 | acpi_sleep_run_lps0_dsm(func: acpi_s2idle_vendor_amd() ? | 
|---|
| 633 | ACPI_LPS0_SCREEN_ON_AMD : | 
|---|
| 634 | ACPI_LPS0_SCREEN_ON, | 
|---|
| 635 | func_mask: lps0_dsm_func_mask, dsm_guid: lps0_dsm_guid); | 
|---|
| 636 | } | 
|---|
| 637 |  | 
|---|
| 638 | static 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 |  | 
|---|
| 649 | void __init acpi_s2idle_setup(void) | 
|---|
| 650 | { | 
|---|
| 651 | acpi_scan_add_handler(handler: &lps0_handler); | 
|---|
| 652 | s2idle_set_ops(ops: &acpi_s2idle_ops_lps0); | 
|---|
| 653 | } | 
|---|
| 654 |  | 
|---|
| 655 | int 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(new: &arg->list_node, head: &lps0_s2idle_devops_head); | 
|---|
| 664 | unlock_system_sleep(sleep_flags); | 
|---|
| 665 |  | 
|---|
| 666 | return 0; | 
|---|
| 667 | } | 
|---|
| 668 | EXPORT_SYMBOL_GPL(acpi_register_lps0_dev); | 
|---|
| 669 |  | 
|---|
| 670 | void 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(entry: &arg->list_node); | 
|---|
| 679 | unlock_system_sleep(sleep_flags); | 
|---|
| 680 | } | 
|---|
| 681 | EXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev); | 
|---|
| 682 |  | 
|---|
| 683 | #endif /* CONFIG_SUSPEND */ | 
|---|
| 684 |  | 
|---|