| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * drivers/acpi/device_pm.c - ACPI device power management routines. |
|---|
| 3 | 4 | * |
|---|
| .. | .. |
|---|
| 5 | 6 | * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> |
|---|
| 6 | 7 | * |
|---|
| 7 | 8 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 8 | | - * |
|---|
| 9 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 10 | | - * it under the terms of the GNU General Public License version 2 as published |
|---|
| 11 | | - * by the Free Software Foundation. |
|---|
| 12 | | - * |
|---|
| 13 | | - * This program is distributed in the hope that it will be useful, but |
|---|
| 14 | | - * WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 15 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|---|
| 16 | | - * General Public License for more details. |
|---|
| 17 | 9 | * |
|---|
| 18 | 10 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 19 | 11 | */ |
|---|
| .. | .. |
|---|
| 26 | 18 | #include <linux/pm_runtime.h> |
|---|
| 27 | 19 | #include <linux/suspend.h> |
|---|
| 28 | 20 | |
|---|
| 21 | +#include "fan.h" |
|---|
| 29 | 22 | #include "internal.h" |
|---|
| 30 | 23 | |
|---|
| 31 | 24 | #define _COMPONENT ACPI_POWER_COMPONENT |
|---|
| .. | .. |
|---|
| 53 | 46 | } |
|---|
| 54 | 47 | } |
|---|
| 55 | 48 | |
|---|
| 49 | +static int acpi_dev_pm_explicit_get(struct acpi_device *device, int *state) |
|---|
| 50 | +{ |
|---|
| 51 | + unsigned long long psc; |
|---|
| 52 | + acpi_status status; |
|---|
| 53 | + |
|---|
| 54 | + status = acpi_evaluate_integer(device->handle, "_PSC", NULL, &psc); |
|---|
| 55 | + if (ACPI_FAILURE(status)) |
|---|
| 56 | + return -ENODEV; |
|---|
| 57 | + |
|---|
| 58 | + *state = psc; |
|---|
| 59 | + return 0; |
|---|
| 60 | +} |
|---|
| 61 | + |
|---|
| 56 | 62 | /** |
|---|
| 57 | 63 | * acpi_device_get_power - Get power state of an ACPI device. |
|---|
| 58 | 64 | * @device: Device to get the power state of. |
|---|
| .. | .. |
|---|
| 61 | 67 | * This function does not update the device's power.state field, but it may |
|---|
| 62 | 68 | * update its parent's power.state field (when the parent's power state is |
|---|
| 63 | 69 | * unknown and the device's power state turns out to be D0). |
|---|
| 70 | + * |
|---|
| 71 | + * Also, it does not update power resource reference counters to ensure that |
|---|
| 72 | + * the power state returned by it will be persistent and it may return a power |
|---|
| 73 | + * state shallower than previously set by acpi_device_set_power() for @device |
|---|
| 74 | + * (if that power state depends on any power resources). |
|---|
| 64 | 75 | */ |
|---|
| 65 | 76 | int acpi_device_get_power(struct acpi_device *device, int *state) |
|---|
| 66 | 77 | { |
|---|
| 67 | 78 | int result = ACPI_STATE_UNKNOWN; |
|---|
| 79 | + int error; |
|---|
| 68 | 80 | |
|---|
| 69 | 81 | if (!device || !state) |
|---|
| 70 | 82 | return -EINVAL; |
|---|
| .. | .. |
|---|
| 81 | 93 | * if available. |
|---|
| 82 | 94 | */ |
|---|
| 83 | 95 | if (device->power.flags.power_resources) { |
|---|
| 84 | | - int error = acpi_power_get_inferred_state(device, &result); |
|---|
| 96 | + error = acpi_power_get_inferred_state(device, &result); |
|---|
| 85 | 97 | if (error) |
|---|
| 86 | 98 | return error; |
|---|
| 87 | 99 | } |
|---|
| 88 | 100 | if (device->power.flags.explicit_get) { |
|---|
| 89 | | - acpi_handle handle = device->handle; |
|---|
| 90 | | - unsigned long long psc; |
|---|
| 91 | | - acpi_status status; |
|---|
| 101 | + int psc; |
|---|
| 92 | 102 | |
|---|
| 93 | | - status = acpi_evaluate_integer(handle, "_PSC", NULL, &psc); |
|---|
| 94 | | - if (ACPI_FAILURE(status)) |
|---|
| 95 | | - return -ENODEV; |
|---|
| 103 | + error = acpi_dev_pm_explicit_get(device, &psc); |
|---|
| 104 | + if (error) |
|---|
| 105 | + return error; |
|---|
| 96 | 106 | |
|---|
| 97 | 107 | /* |
|---|
| 98 | 108 | * The power resources settings may indicate a power state |
|---|
| .. | .. |
|---|
| 157 | 167 | || (state < ACPI_STATE_D0) || (state > ACPI_STATE_D3_COLD)) |
|---|
| 158 | 168 | return -EINVAL; |
|---|
| 159 | 169 | |
|---|
| 170 | + acpi_handle_debug(device->handle, "Power state change: %s -> %s\n", |
|---|
| 171 | + acpi_power_state_string(device->power.state), |
|---|
| 172 | + acpi_power_state_string(state)); |
|---|
| 173 | + |
|---|
| 160 | 174 | /* Make sure this is a valid target state */ |
|---|
| 161 | 175 | |
|---|
| 162 | | - if (state == device->power.state) { |
|---|
| 176 | + /* There is a special case for D0 addressed below. */ |
|---|
| 177 | + if (state > ACPI_STATE_D0 && state == device->power.state) { |
|---|
| 163 | 178 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] already in %s\n", |
|---|
| 164 | 179 | device->pnp.bus_id, |
|---|
| 165 | 180 | acpi_power_state_string(state))); |
|---|
| .. | .. |
|---|
| 209 | 224 | return -ENODEV; |
|---|
| 210 | 225 | } |
|---|
| 211 | 226 | |
|---|
| 212 | | - result = acpi_dev_pm_explicit_set(device, state); |
|---|
| 213 | | - if (result) |
|---|
| 214 | | - goto end; |
|---|
| 227 | + /* |
|---|
| 228 | + * If the device goes from D3hot to D3cold, _PS3 has been |
|---|
| 229 | + * evaluated for it already, so skip it in that case. |
|---|
| 230 | + */ |
|---|
| 231 | + if (device->power.state < ACPI_STATE_D3_HOT) { |
|---|
| 232 | + result = acpi_dev_pm_explicit_set(device, state); |
|---|
| 233 | + if (result) |
|---|
| 234 | + goto end; |
|---|
| 235 | + } |
|---|
| 215 | 236 | |
|---|
| 216 | 237 | if (device->power.flags.power_resources) |
|---|
| 217 | 238 | result = acpi_power_transition(device, target_state); |
|---|
| 218 | 239 | } else { |
|---|
| 240 | + int cur_state = device->power.state; |
|---|
| 241 | + |
|---|
| 219 | 242 | if (device->power.flags.power_resources) { |
|---|
| 220 | 243 | result = acpi_power_transition(device, ACPI_STATE_D0); |
|---|
| 221 | 244 | if (result) |
|---|
| 222 | 245 | goto end; |
|---|
| 223 | 246 | } |
|---|
| 247 | + |
|---|
| 248 | + if (cur_state == ACPI_STATE_D0) { |
|---|
| 249 | + int psc; |
|---|
| 250 | + |
|---|
| 251 | + /* Nothing to do here if _PSC is not present. */ |
|---|
| 252 | + if (!device->power.flags.explicit_get) |
|---|
| 253 | + return 0; |
|---|
| 254 | + |
|---|
| 255 | + /* |
|---|
| 256 | + * The power state of the device was set to D0 last |
|---|
| 257 | + * time, but that might have happened before a |
|---|
| 258 | + * system-wide transition involving the platform |
|---|
| 259 | + * firmware, so it may be necessary to evaluate _PS0 |
|---|
| 260 | + * for the device here. However, use extra care here |
|---|
| 261 | + * and evaluate _PSC to check the device's current power |
|---|
| 262 | + * state, and only invoke _PS0 if the evaluation of _PSC |
|---|
| 263 | + * is successful and it returns a power state different |
|---|
| 264 | + * from D0. |
|---|
| 265 | + */ |
|---|
| 266 | + result = acpi_dev_pm_explicit_get(device, &psc); |
|---|
| 267 | + if (result || psc == ACPI_STATE_D0) |
|---|
| 268 | + return 0; |
|---|
| 269 | + } |
|---|
| 270 | + |
|---|
| 224 | 271 | result = acpi_dev_pm_explicit_set(device, ACPI_STATE_D0); |
|---|
| 225 | 272 | } |
|---|
| 226 | 273 | |
|---|
| .. | .. |
|---|
| 413 | 460 | if (adev->wakeup.flags.notifier_present) { |
|---|
| 414 | 461 | pm_wakeup_ws_event(adev->wakeup.ws, 0, acpi_s2idle_wakeup()); |
|---|
| 415 | 462 | if (adev->wakeup.context.func) { |
|---|
| 416 | | - acpi_handle_debug(handle, "Running %pF for %s\n", |
|---|
| 463 | + acpi_handle_debug(handle, "Running %pS for %s\n", |
|---|
| 417 | 464 | adev->wakeup.context.func, |
|---|
| 418 | 465 | dev_name(adev->wakeup.context.dev)); |
|---|
| 419 | 466 | adev->wakeup.context.func(&adev->wakeup.context); |
|---|
| .. | .. |
|---|
| 729 | 776 | goto out; |
|---|
| 730 | 777 | } |
|---|
| 731 | 778 | |
|---|
| 779 | + acpi_handle_debug(adev->handle, "GPE%2X enabled for wakeup\n", |
|---|
| 780 | + (unsigned int)wakeup->gpe_number); |
|---|
| 781 | + |
|---|
| 732 | 782 | inc: |
|---|
| 733 | 783 | wakeup->enable_count++; |
|---|
| 734 | 784 | |
|---|
| .. | .. |
|---|
| 1018 | 1068 | { |
|---|
| 1019 | 1069 | int ret; |
|---|
| 1020 | 1070 | |
|---|
| 1021 | | - if (dev_pm_smart_suspend_and_suspended(dev)) |
|---|
| 1071 | + if (dev_pm_skip_suspend(dev)) |
|---|
| 1022 | 1072 | return 0; |
|---|
| 1023 | 1073 | |
|---|
| 1024 | 1074 | ret = pm_generic_suspend_late(dev); |
|---|
| .. | .. |
|---|
| 1034 | 1084 | { |
|---|
| 1035 | 1085 | int ret; |
|---|
| 1036 | 1086 | |
|---|
| 1037 | | - if (dev_pm_smart_suspend_and_suspended(dev)) { |
|---|
| 1038 | | - dev->power.may_skip_resume = true; |
|---|
| 1087 | + if (dev_pm_skip_suspend(dev)) |
|---|
| 1039 | 1088 | return 0; |
|---|
| 1040 | | - } |
|---|
| 1041 | 1089 | |
|---|
| 1042 | 1090 | ret = pm_generic_suspend_noirq(dev); |
|---|
| 1043 | 1091 | if (ret) |
|---|
| .. | .. |
|---|
| 1050 | 1098 | * acpi_subsys_complete() to take care of fixing up the device's state |
|---|
| 1051 | 1099 | * anyway, if need be. |
|---|
| 1052 | 1100 | */ |
|---|
| 1053 | | - dev->power.may_skip_resume = device_may_wakeup(dev) || |
|---|
| 1054 | | - !device_can_wakeup(dev); |
|---|
| 1101 | + if (device_can_wakeup(dev) && !device_may_wakeup(dev)) |
|---|
| 1102 | + dev->power.may_skip_resume = false; |
|---|
| 1055 | 1103 | |
|---|
| 1056 | 1104 | return 0; |
|---|
| 1057 | 1105 | } |
|---|
| .. | .. |
|---|
| 1063 | 1111 | */ |
|---|
| 1064 | 1112 | static int acpi_subsys_resume_noirq(struct device *dev) |
|---|
| 1065 | 1113 | { |
|---|
| 1066 | | - if (dev_pm_may_skip_resume(dev)) |
|---|
| 1114 | + if (dev_pm_skip_resume(dev)) |
|---|
| 1067 | 1115 | return 0; |
|---|
| 1068 | | - |
|---|
| 1069 | | - /* |
|---|
| 1070 | | - * Devices with DPM_FLAG_SMART_SUSPEND may be left in runtime suspend |
|---|
| 1071 | | - * during system suspend, so update their runtime PM status to "active" |
|---|
| 1072 | | - * as they will be put into D0 going forward. |
|---|
| 1073 | | - */ |
|---|
| 1074 | | - if (dev_pm_smart_suspend_and_suspended(dev)) |
|---|
| 1075 | | - pm_runtime_set_active(dev); |
|---|
| 1076 | 1116 | |
|---|
| 1077 | 1117 | return pm_generic_resume_noirq(dev); |
|---|
| 1078 | 1118 | } |
|---|
| .. | .. |
|---|
| 1087 | 1127 | */ |
|---|
| 1088 | 1128 | static int acpi_subsys_resume_early(struct device *dev) |
|---|
| 1089 | 1129 | { |
|---|
| 1090 | | - int ret = acpi_dev_resume(dev); |
|---|
| 1130 | + int ret; |
|---|
| 1131 | + |
|---|
| 1132 | + if (dev_pm_skip_resume(dev)) |
|---|
| 1133 | + return 0; |
|---|
| 1134 | + |
|---|
| 1135 | + ret = acpi_dev_resume(dev); |
|---|
| 1091 | 1136 | return ret ? ret : pm_generic_resume_early(dev); |
|---|
| 1092 | 1137 | } |
|---|
| 1093 | 1138 | |
|---|
| .. | .. |
|---|
| 1152 | 1197 | { |
|---|
| 1153 | 1198 | int ret; |
|---|
| 1154 | 1199 | |
|---|
| 1155 | | - if (dev_pm_smart_suspend_and_suspended(dev)) |
|---|
| 1200 | + if (dev_pm_skip_suspend(dev)) |
|---|
| 1156 | 1201 | return 0; |
|---|
| 1157 | 1202 | |
|---|
| 1158 | 1203 | ret = pm_generic_poweroff_late(dev); |
|---|
| .. | .. |
|---|
| 1168 | 1213 | */ |
|---|
| 1169 | 1214 | static int acpi_subsys_poweroff_noirq(struct device *dev) |
|---|
| 1170 | 1215 | { |
|---|
| 1171 | | - if (dev_pm_smart_suspend_and_suspended(dev)) |
|---|
| 1216 | + if (dev_pm_skip_suspend(dev)) |
|---|
| 1172 | 1217 | return 0; |
|---|
| 1173 | 1218 | |
|---|
| 1174 | 1219 | return pm_generic_poweroff_noirq(dev); |
|---|
| .. | .. |
|---|
| 1254 | 1299 | * with the generic ACPI PM domain. |
|---|
| 1255 | 1300 | */ |
|---|
| 1256 | 1301 | static const struct acpi_device_id special_pm_ids[] = { |
|---|
| 1257 | | - {"PNP0C0B", }, /* Generic ACPI fan */ |
|---|
| 1258 | | - {"INT3404", }, /* Fan */ |
|---|
| 1302 | + ACPI_FAN_DEVICE_IDS, |
|---|
| 1259 | 1303 | {} |
|---|
| 1260 | 1304 | }; |
|---|
| 1261 | 1305 | struct acpi_device *adev = ACPI_COMPANION(dev); |
|---|