diff options
author | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2013-01-17 17:11:08 +0400 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2013-01-20 01:09:22 +0400 |
commit | 9ce4e607111764673f7a59d7bc87a16ade5c7bba (patch) | |
tree | 54c7fbd2833e69b0c5e68eac418ef36d6e51e7a0 /drivers/acpi/device_pm.c | |
parent | 96bfd3cee2a741906b3ef5c1096d2f0a0b8025e0 (diff) | |
download | linux-9ce4e607111764673f7a59d7bc87a16ade5c7bba.tar.xz |
ACPI / PM: Move device power management functions to device_pm.c
Move ACPI device power management functions from drivers/acpi/bus.c
to drivers/acpi/device_pm.c.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Diffstat (limited to 'drivers/acpi/device_pm.c')
-rw-r--r-- | drivers/acpi/device_pm.c | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index 8be4b29e38aa..8bca7465c78d 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -30,6 +30,12 @@ #include <acpi/acpi.h> #include <acpi/acpi_bus.h> +#include <acpi/acpi_drivers.h> + +#include "internal.h" + +#define _COMPONENT ACPI_POWER_COMPONENT +ACPI_MODULE_NAME("device_pm"); static DEFINE_MUTEX(acpi_pm_notifier_lock); @@ -94,6 +100,288 @@ acpi_status acpi_remove_pm_notifier(struct acpi_device *adev, } /** + * acpi_power_state_string - String representation of ACPI device power state. + * @state: ACPI device power state to return the string representation of. + */ +const char *acpi_power_state_string(int state) +{ + switch (state) { + case ACPI_STATE_D0: + return "D0"; + case ACPI_STATE_D1: + return "D1"; + case ACPI_STATE_D2: + return "D2"; + case ACPI_STATE_D3_HOT: + return "D3hot"; + case ACPI_STATE_D3_COLD: + return "D3"; + default: + return "(unknown)"; + } +} + +/** + * acpi_device_get_power - Get power state of an ACPI device. + * @device: Device to get the power state of. + * @state: Place to store the power state of the device. + * + * This function does not update the device's power.state field, but it may + * update its parent's power.state field (when the parent's power state is + * unknown and the device's power state turns out to be D0). + */ +int acpi_device_get_power(struct acpi_device *device, int *state) +{ + int result = ACPI_STATE_UNKNOWN; + + if (!device || !state) + return -EINVAL; + + if (!device->flags.power_manageable) { + /* TBD: Non-recursive algorithm for walking up hierarchy. */ + *state = device->parent ? + device->parent->power.state : ACPI_STATE_D0; + goto out; + } + + /* + * Get the device's power state either directly (via _PSC) or + * indirectly (via power resources). + */ + if (device->power.flags.explicit_get) { + unsigned long long psc; + acpi_status status = acpi_evaluate_integer(device->handle, + "_PSC", NULL, &psc); + if (ACPI_FAILURE(status)) + return -ENODEV; + + result = psc; + } + /* The test below covers ACPI_STATE_UNKNOWN too. */ + if (result <= ACPI_STATE_D2) { + ; /* Do nothing. */ + } else if (device->power.flags.power_resources) { + int error = acpi_power_get_inferred_state(device, &result); + if (error) + return error; + } else if (result == ACPI_STATE_D3_HOT) { + result = ACPI_STATE_D3; + } + + /* + * If we were unsure about the device parent's power state up to this + * point, the fact that the device is in D0 implies that the parent has + * to be in D0 too. + */ + if (device->parent && device->parent->power.state == ACPI_STATE_UNKNOWN + && result == ACPI_STATE_D0) + device->parent->power.state = ACPI_STATE_D0; + + *state = result; + + out: + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] power state is %s\n", + device->pnp.bus_id, acpi_power_state_string(*state))); + + return 0; +} + +/** + * acpi_device_set_power - Set power state of an ACPI device. + * @device: Device to set the power state of. + * @state: New power state to set. + * + * Callers must ensure that the device is power manageable before using this + * function. + */ +int acpi_device_set_power(struct acpi_device *device, int state) +{ + int result = 0; + acpi_status status = AE_OK; + char object_name[5] = { '_', 'P', 'S', '0' + state, '\0' }; + bool cut_power = false; + + if (!device || (state < ACPI_STATE_D0) || (state > ACPI_STATE_D3_COLD)) + return -EINVAL; + + /* Make sure this is a valid target state */ + + if (state == device->power.state) { + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device is already at %s\n", + acpi_power_state_string(state))); + return 0; + } + + if (!device->power.states[state].flags.valid) { + printk(KERN_WARNING PREFIX "Device does not support %s\n", + acpi_power_state_string(state)); + return -ENODEV; + } + if (device->parent && (state < device->parent->power.state)) { + printk(KERN_WARNING PREFIX + "Cannot set device to a higher-powered" + " state than parent\n"); + return -ENODEV; + } + + /* For D3cold we should first transition into D3hot. */ + if (state == ACPI_STATE_D3_COLD + && device->power.states[ACPI_STATE_D3_COLD].flags.os_accessible) { + state = ACPI_STATE_D3_HOT; + object_name[3] = '3'; + cut_power = true; + } + + /* + * Transition Power + * ---------------- + * On transitions to a high-powered state we first apply power (via + * power resources) then evalute _PSx. Conversly for transitions to + * a lower-powered state. + */ + if (state < device->power.state) { + if (device->power.state >= ACPI_STATE_D3_HOT && + state != ACPI_STATE_D0) { + printk(KERN_WARNING PREFIX + "Cannot transition to non-D0 state from D3\n"); + return -ENODEV; + } + if (device->power.flags.power_resources) { + result = acpi_power_transition(device, state); + if (result) + goto end; + } + if (device->power.states[state].flags.explicit_set) { + status = acpi_evaluate_object(device->handle, + object_name, NULL, NULL); + if (ACPI_FAILURE(status)) { + result = -ENODEV; + goto end; + } + } + } else { + if (device->power.states[state].flags.explicit_set) { + status = acpi_evaluate_object(device->handle, + object_name, NULL, NULL); + if (ACPI_FAILURE(status)) { + result = -ENODEV; + goto end; + } + } + if (device->power.flags.power_resources) { + result = acpi_power_transition(device, state); + if (result) + goto end; + } + } + + if (cut_power) + result = acpi_power_transition(device, ACPI_STATE_D3_COLD); + + end: + if (result) + printk(KERN_WARNING PREFIX + "Device [%s] failed to transition to %s\n", + device->pnp.bus_id, + acpi_power_state_string(state)); + else { + device->power.state = state; + ACPI_DEBUG_PRINT((ACPI_DB_INFO, + "Device [%s] transitioned to %s\n", + device->pnp.bus_id, + acpi_power_state_string(state))); + } + + return result; +} +EXPORT_SYMBOL(acpi_device_set_power); + +int acpi_bus_set_power(acpi_handle handle, int state) +{ + struct acpi_device *device; + int result; + + result = acpi_bus_get_device(handle, &device); + if (result) + return result; + + if (!device->flags.power_manageable) { + ACPI_DEBUG_PRINT((ACPI_DB_INFO, + "Device [%s] is not power manageable\n", + dev_name(&device->dev))); + return -ENODEV; + } + + return acpi_device_set_power(device, state); +} +EXPORT_SYMBOL(acpi_bus_set_power); + +int acpi_bus_init_power(struct acpi_device *device) +{ + int state; + int result; + + if (!device) + return -EINVAL; + + device->power.state = ACPI_STATE_UNKNOWN; + + result = acpi_device_get_power(device, &state); + if (result) + return result; + + if (device->power.flags.power_resources) + result = acpi_power_on_resources(device, state); + + if (!result) + device->power.state = state; + + return result; +} + +int acpi_bus_update_power(acpi_handle handle, int *state_p) +{ + struct acpi_device *device; + int state; + int result; + + result = acpi_bus_get_device(handle, &device); + if (result) + return result; + + result = acpi_device_get_power(device, &state); + if (result) + return result; + + result = acpi_device_set_power(device, state); + if (!result && state_p) + *state_p = state; + + return result; +} +EXPORT_SYMBOL_GPL(acpi_bus_update_power); + +bool acpi_bus_power_manageable(acpi_handle handle) +{ + struct acpi_device *device; + int result; + + result = acpi_bus_get_device(handle, &device); + return result ? false : device->flags.power_manageable; +} +EXPORT_SYMBOL(acpi_bus_power_manageable); + +bool acpi_bus_can_wakeup(acpi_handle handle) +{ + struct acpi_device *device; + int result; + + result = acpi_bus_get_device(handle, &device); + return result ? false : device->wakeup.flags.valid; +} +EXPORT_SYMBOL(acpi_bus_can_wakeup); + +/** * acpi_device_power_state - Get preferred power state of ACPI device. * @dev: Device whose preferred target power state to return. * @adev: ACPI device node corresponding to @dev. |