| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * drivers/acpi/power.c - ACPI Power Resources management. |
|---|
| 3 | 4 | * |
|---|
| .. | .. |
|---|
| 5 | 6 | * Author: Andy Grover <andrew.grover@intel.com> |
|---|
| 6 | 7 | * Author: Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> |
|---|
| 7 | 8 | * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> |
|---|
| 8 | | - * |
|---|
| 9 | | - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 10 | | - * |
|---|
| 11 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 12 | | - * it under the terms of the GNU General Public License as published by |
|---|
| 13 | | - * the Free Software Foundation; either version 2 of the License, or (at |
|---|
| 14 | | - * your option) any later version. |
|---|
| 15 | | - * |
|---|
| 16 | | - * This program is distributed in the hope that it will be useful, but |
|---|
| 17 | | - * WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 18 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|---|
| 19 | | - * General Public License for more details. |
|---|
| 20 | | - * |
|---|
| 21 | | - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 22 | 9 | */ |
|---|
| 23 | 10 | |
|---|
| 24 | 11 | /* |
|---|
| .. | .. |
|---|
| 26 | 13 | * 1. via "Device Specific (D-State) Control" |
|---|
| 27 | 14 | * 2. via "Power Resource Control". |
|---|
| 28 | 15 | * The code below deals with ACPI Power Resources control. |
|---|
| 29 | | - * |
|---|
| 16 | + * |
|---|
| 30 | 17 | * An ACPI "power resource object" represents a software controllable power |
|---|
| 31 | 18 | * plane, clock plane, or other resource depended on by a device. |
|---|
| 32 | 19 | * |
|---|
| .. | .. |
|---|
| 49 | 36 | ACPI_MODULE_NAME("power"); |
|---|
| 50 | 37 | #define ACPI_POWER_CLASS "power_resource" |
|---|
| 51 | 38 | #define ACPI_POWER_DEVICE_NAME "Power Resource" |
|---|
| 52 | | -#define ACPI_POWER_FILE_INFO "info" |
|---|
| 53 | | -#define ACPI_POWER_FILE_STATUS "state" |
|---|
| 54 | 39 | #define ACPI_POWER_RESOURCE_STATE_OFF 0x00 |
|---|
| 55 | 40 | #define ACPI_POWER_RESOURCE_STATE_ON 0x01 |
|---|
| 56 | 41 | #define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF |
|---|
| 42 | + |
|---|
| 43 | +struct acpi_power_dependent_device { |
|---|
| 44 | + struct device *dev; |
|---|
| 45 | + struct list_head node; |
|---|
| 46 | +}; |
|---|
| 57 | 47 | |
|---|
| 58 | 48 | struct acpi_power_resource { |
|---|
| 59 | 49 | struct acpi_device device; |
|---|
| .. | .. |
|---|
| 64 | 54 | unsigned int ref_count; |
|---|
| 65 | 55 | bool wakeup_enabled; |
|---|
| 66 | 56 | struct mutex resource_lock; |
|---|
| 57 | + struct list_head dependents; |
|---|
| 67 | 58 | }; |
|---|
| 68 | 59 | |
|---|
| 69 | 60 | struct acpi_power_resource_entry { |
|---|
| .. | .. |
|---|
| 245 | 236 | return 0; |
|---|
| 246 | 237 | } |
|---|
| 247 | 238 | |
|---|
| 239 | +static int |
|---|
| 240 | +acpi_power_resource_add_dependent(struct acpi_power_resource *resource, |
|---|
| 241 | + struct device *dev) |
|---|
| 242 | +{ |
|---|
| 243 | + struct acpi_power_dependent_device *dep; |
|---|
| 244 | + int ret = 0; |
|---|
| 245 | + |
|---|
| 246 | + mutex_lock(&resource->resource_lock); |
|---|
| 247 | + list_for_each_entry(dep, &resource->dependents, node) { |
|---|
| 248 | + /* Only add it once */ |
|---|
| 249 | + if (dep->dev == dev) |
|---|
| 250 | + goto unlock; |
|---|
| 251 | + } |
|---|
| 252 | + |
|---|
| 253 | + dep = kzalloc(sizeof(*dep), GFP_KERNEL); |
|---|
| 254 | + if (!dep) { |
|---|
| 255 | + ret = -ENOMEM; |
|---|
| 256 | + goto unlock; |
|---|
| 257 | + } |
|---|
| 258 | + |
|---|
| 259 | + dep->dev = dev; |
|---|
| 260 | + list_add_tail(&dep->node, &resource->dependents); |
|---|
| 261 | + dev_dbg(dev, "added power dependency to [%s]\n", resource->name); |
|---|
| 262 | + |
|---|
| 263 | +unlock: |
|---|
| 264 | + mutex_unlock(&resource->resource_lock); |
|---|
| 265 | + return ret; |
|---|
| 266 | +} |
|---|
| 267 | + |
|---|
| 268 | +static void |
|---|
| 269 | +acpi_power_resource_remove_dependent(struct acpi_power_resource *resource, |
|---|
| 270 | + struct device *dev) |
|---|
| 271 | +{ |
|---|
| 272 | + struct acpi_power_dependent_device *dep; |
|---|
| 273 | + |
|---|
| 274 | + mutex_lock(&resource->resource_lock); |
|---|
| 275 | + list_for_each_entry(dep, &resource->dependents, node) { |
|---|
| 276 | + if (dep->dev == dev) { |
|---|
| 277 | + list_del(&dep->node); |
|---|
| 278 | + kfree(dep); |
|---|
| 279 | + dev_dbg(dev, "removed power dependency to [%s]\n", |
|---|
| 280 | + resource->name); |
|---|
| 281 | + break; |
|---|
| 282 | + } |
|---|
| 283 | + } |
|---|
| 284 | + mutex_unlock(&resource->resource_lock); |
|---|
| 285 | +} |
|---|
| 286 | + |
|---|
| 287 | +/** |
|---|
| 288 | + * acpi_device_power_add_dependent - Add dependent device of this ACPI device |
|---|
| 289 | + * @adev: ACPI device pointer |
|---|
| 290 | + * @dev: Dependent device |
|---|
| 291 | + * |
|---|
| 292 | + * If @adev has non-empty _PR0 the @dev is added as dependent device to all |
|---|
| 293 | + * power resources returned by it. This means that whenever these power |
|---|
| 294 | + * resources are turned _ON the dependent devices get runtime resumed. This |
|---|
| 295 | + * is needed for devices such as PCI to allow its driver to re-initialize |
|---|
| 296 | + * it after it went to D0uninitialized. |
|---|
| 297 | + * |
|---|
| 298 | + * If @adev does not have _PR0 this does nothing. |
|---|
| 299 | + * |
|---|
| 300 | + * Returns %0 in case of success and negative errno otherwise. |
|---|
| 301 | + */ |
|---|
| 302 | +int acpi_device_power_add_dependent(struct acpi_device *adev, |
|---|
| 303 | + struct device *dev) |
|---|
| 304 | +{ |
|---|
| 305 | + struct acpi_power_resource_entry *entry; |
|---|
| 306 | + struct list_head *resources; |
|---|
| 307 | + int ret; |
|---|
| 308 | + |
|---|
| 309 | + if (!adev->flags.power_manageable) |
|---|
| 310 | + return 0; |
|---|
| 311 | + |
|---|
| 312 | + resources = &adev->power.states[ACPI_STATE_D0].resources; |
|---|
| 313 | + list_for_each_entry(entry, resources, node) { |
|---|
| 314 | + ret = acpi_power_resource_add_dependent(entry->resource, dev); |
|---|
| 315 | + if (ret) |
|---|
| 316 | + goto err; |
|---|
| 317 | + } |
|---|
| 318 | + |
|---|
| 319 | + return 0; |
|---|
| 320 | + |
|---|
| 321 | +err: |
|---|
| 322 | + list_for_each_entry(entry, resources, node) |
|---|
| 323 | + acpi_power_resource_remove_dependent(entry->resource, dev); |
|---|
| 324 | + |
|---|
| 325 | + return ret; |
|---|
| 326 | +} |
|---|
| 327 | + |
|---|
| 328 | +/** |
|---|
| 329 | + * acpi_device_power_remove_dependent - Remove dependent device |
|---|
| 330 | + * @adev: ACPI device pointer |
|---|
| 331 | + * @dev: Dependent device |
|---|
| 332 | + * |
|---|
| 333 | + * Does the opposite of acpi_device_power_add_dependent() and removes the |
|---|
| 334 | + * dependent device if it is found. Can be called to @adev that does not |
|---|
| 335 | + * have _PR0 as well. |
|---|
| 336 | + */ |
|---|
| 337 | +void acpi_device_power_remove_dependent(struct acpi_device *adev, |
|---|
| 338 | + struct device *dev) |
|---|
| 339 | +{ |
|---|
| 340 | + struct acpi_power_resource_entry *entry; |
|---|
| 341 | + struct list_head *resources; |
|---|
| 342 | + |
|---|
| 343 | + if (!adev->flags.power_manageable) |
|---|
| 344 | + return; |
|---|
| 345 | + |
|---|
| 346 | + resources = &adev->power.states[ACPI_STATE_D0].resources; |
|---|
| 347 | + list_for_each_entry_reverse(entry, resources, node) |
|---|
| 348 | + acpi_power_resource_remove_dependent(entry->resource, dev); |
|---|
| 349 | +} |
|---|
| 350 | + |
|---|
| 248 | 351 | static int __acpi_power_on(struct acpi_power_resource *resource) |
|---|
| 249 | 352 | { |
|---|
| 353 | + struct acpi_power_dependent_device *dep; |
|---|
| 250 | 354 | acpi_status status = AE_OK; |
|---|
| 251 | 355 | |
|---|
| 252 | 356 | status = acpi_evaluate_object(resource->device.handle, "_ON", NULL, NULL); |
|---|
| .. | .. |
|---|
| 255 | 359 | |
|---|
| 256 | 360 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Power resource [%s] turned on\n", |
|---|
| 257 | 361 | resource->name)); |
|---|
| 362 | + |
|---|
| 363 | + /* |
|---|
| 364 | + * If there are other dependents on this power resource we need to |
|---|
| 365 | + * resume them now so that their drivers can re-initialize the |
|---|
| 366 | + * hardware properly after it went back to D0. |
|---|
| 367 | + */ |
|---|
| 368 | + if (list_empty(&resource->dependents) || |
|---|
| 369 | + list_is_singular(&resource->dependents)) |
|---|
| 370 | + return 0; |
|---|
| 371 | + |
|---|
| 372 | + list_for_each_entry(dep, &resource->dependents, node) { |
|---|
| 373 | + dev_dbg(dep->dev, "runtime resuming because [%s] turned on\n", |
|---|
| 374 | + resource->name); |
|---|
| 375 | + pm_request_resume(dep->dev); |
|---|
| 376 | + } |
|---|
| 258 | 377 | |
|---|
| 259 | 378 | return 0; |
|---|
| 260 | 379 | } |
|---|
| .. | .. |
|---|
| 526 | 645 | * -ENODEV if the execution of either _DSW or _PSW has failed |
|---|
| 527 | 646 | */ |
|---|
| 528 | 647 | int acpi_device_sleep_wake(struct acpi_device *dev, |
|---|
| 529 | | - int enable, int sleep_state, int dev_state) |
|---|
| 648 | + int enable, int sleep_state, int dev_state) |
|---|
| 530 | 649 | { |
|---|
| 531 | 650 | union acpi_object in_arg[3]; |
|---|
| 532 | 651 | struct acpi_object_list arg_list = { 3, in_arg }; |
|---|
| .. | .. |
|---|
| 535 | 654 | /* |
|---|
| 536 | 655 | * Try to execute _DSW first. |
|---|
| 537 | 656 | * |
|---|
| 538 | | - * Three agruments are needed for the _DSW object: |
|---|
| 657 | + * Three arguments are needed for the _DSW object: |
|---|
| 539 | 658 | * Argument 0: enable/disable the wake capabilities |
|---|
| 540 | 659 | * Argument 1: target system state |
|---|
| 541 | 660 | * Argument 2: target device state |
|---|
| 542 | 661 | * When _DSW object is called to disable the wake capabilities, maybe |
|---|
| 543 | | - * the first argument is filled. The values of the other two agruments |
|---|
| 662 | + * the first argument is filled. The values of the other two arguments |
|---|
| 544 | 663 | * are meaningless. |
|---|
| 545 | 664 | */ |
|---|
| 546 | 665 | in_arg[0].type = ACPI_TYPE_INTEGER; |
|---|
| .. | .. |
|---|
| 571 | 690 | |
|---|
| 572 | 691 | /* |
|---|
| 573 | 692 | * Prepare a wakeup device, two steps (Ref ACPI 2.0:P229): |
|---|
| 574 | | - * 1. Power on the power resources required for the wakeup device |
|---|
| 693 | + * 1. Power on the power resources required for the wakeup device |
|---|
| 575 | 694 | * 2. Execute _DSW (Device Sleep Wake) or (deprecated in ACPI 3.0) _PSW (Power |
|---|
| 576 | 695 | * State Wake) for the device, if present |
|---|
| 577 | 696 | */ |
|---|
| .. | .. |
|---|
| 767 | 886 | kfree(resource); |
|---|
| 768 | 887 | } |
|---|
| 769 | 888 | |
|---|
| 770 | | -static ssize_t acpi_power_in_use_show(struct device *dev, |
|---|
| 771 | | - struct device_attribute *attr, |
|---|
| 772 | | - char *buf) { |
|---|
| 889 | +static ssize_t resource_in_use_show(struct device *dev, |
|---|
| 890 | + struct device_attribute *attr, |
|---|
| 891 | + char *buf) |
|---|
| 892 | +{ |
|---|
| 773 | 893 | struct acpi_power_resource *resource; |
|---|
| 774 | 894 | |
|---|
| 775 | 895 | resource = to_power_resource(to_acpi_device(dev)); |
|---|
| 776 | 896 | return sprintf(buf, "%u\n", !!resource->ref_count); |
|---|
| 777 | 897 | } |
|---|
| 778 | | -static DEVICE_ATTR(resource_in_use, 0444, acpi_power_in_use_show, NULL); |
|---|
| 898 | +static DEVICE_ATTR_RO(resource_in_use); |
|---|
| 779 | 899 | |
|---|
| 780 | 900 | static void acpi_power_sysfs_remove(struct acpi_device *device) |
|---|
| 781 | 901 | { |
|---|
| .. | .. |
|---|
| 823 | 943 | ACPI_STA_DEFAULT); |
|---|
| 824 | 944 | mutex_init(&resource->resource_lock); |
|---|
| 825 | 945 | INIT_LIST_HEAD(&resource->list_node); |
|---|
| 946 | + INIT_LIST_HEAD(&resource->dependents); |
|---|
| 826 | 947 | resource->name = device->pnp.bus_id; |
|---|
| 827 | 948 | strcpy(acpi_device_name(device), ACPI_POWER_DEVICE_NAME); |
|---|
| 828 | 949 | strcpy(acpi_device_class(device), ACPI_POWER_CLASS); |
|---|