diff options
Diffstat (limited to 'drivers/acpi/device_pm.c')
-rw-r--r-- | drivers/acpi/device_pm.c | 277 |
1 files changed, 175 insertions, 102 deletions
diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index fbcc73f7a099..e4ffaeec9ec2 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -387,6 +387,7 @@ EXPORT_SYMBOL(acpi_bus_power_manageable); #ifdef CONFIG_PM static DEFINE_MUTEX(acpi_pm_notifier_lock); +static DEFINE_MUTEX(acpi_pm_notifier_install_lock); void acpi_pm_wakeup_event(struct device *dev) { @@ -443,24 +444,25 @@ acpi_status acpi_add_pm_notifier(struct acpi_device *adev, struct device *dev, if (!dev && !func) return AE_BAD_PARAMETER; - mutex_lock(&acpi_pm_notifier_lock); + mutex_lock(&acpi_pm_notifier_install_lock); if (adev->wakeup.flags.notifier_present) goto out; - adev->wakeup.ws = wakeup_source_register(dev_name(&adev->dev)); - adev->wakeup.context.dev = dev; - adev->wakeup.context.func = func; - status = acpi_install_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY, acpi_pm_notify_handler, NULL); if (ACPI_FAILURE(status)) goto out; + mutex_lock(&acpi_pm_notifier_lock); + adev->wakeup.ws = wakeup_source_register(dev_name(&adev->dev)); + adev->wakeup.context.dev = dev; + adev->wakeup.context.func = func; adev->wakeup.flags.notifier_present = true; + mutex_unlock(&acpi_pm_notifier_lock); out: - mutex_unlock(&acpi_pm_notifier_lock); + mutex_unlock(&acpi_pm_notifier_install_lock); return status; } @@ -472,7 +474,7 @@ acpi_status acpi_remove_pm_notifier(struct acpi_device *adev) { acpi_status status = AE_BAD_PARAMETER; - mutex_lock(&acpi_pm_notifier_lock); + mutex_lock(&acpi_pm_notifier_install_lock); if (!adev->wakeup.flags.notifier_present) goto out; @@ -483,14 +485,15 @@ acpi_status acpi_remove_pm_notifier(struct acpi_device *adev) if (ACPI_FAILURE(status)) goto out; + mutex_lock(&acpi_pm_notifier_lock); adev->wakeup.context.func = NULL; adev->wakeup.context.dev = NULL; wakeup_source_unregister(adev->wakeup.ws); - adev->wakeup.flags.notifier_present = false; + mutex_unlock(&acpi_pm_notifier_lock); out: - mutex_unlock(&acpi_pm_notifier_lock); + mutex_unlock(&acpi_pm_notifier_install_lock); return status; } @@ -581,8 +584,7 @@ static int acpi_dev_pm_get_state(struct device *dev, struct acpi_device *adev, d_min = ret; wakeup = device_may_wakeup(dev) && adev->wakeup.flags.valid && adev->wakeup.sleep_state >= target_state; - } else if (dev_pm_qos_flags(dev, PM_QOS_FLAG_REMOTE_WAKEUP) != - PM_QOS_FLAGS_NONE) { + } else { wakeup = adev->wakeup.flags.valid; } @@ -848,48 +850,48 @@ static int acpi_dev_pm_full_power(struct acpi_device *adev) } /** - * acpi_dev_runtime_suspend - Put device into a low-power state using ACPI. + * acpi_dev_suspend - Put device into a low-power state using ACPI. * @dev: Device to put into a low-power state. + * @wakeup: Whether or not to enable wakeup for the device. * - * Put the given device into a runtime low-power state using the standard ACPI + * Put the given device into a low-power state using the standard ACPI * mechanism. Set up remote wakeup if desired, choose the state to put the * device into (this checks if remote wakeup is expected to work too), and set * the power state of the device. */ -int acpi_dev_runtime_suspend(struct device *dev) +int acpi_dev_suspend(struct device *dev, bool wakeup) { struct acpi_device *adev = ACPI_COMPANION(dev); - bool remote_wakeup; + u32 target_state = acpi_target_system_state(); int error; if (!adev) return 0; - remote_wakeup = dev_pm_qos_flags(dev, PM_QOS_FLAG_REMOTE_WAKEUP) > - PM_QOS_FLAGS_NONE; - if (remote_wakeup) { - error = acpi_device_wakeup_enable(adev, ACPI_STATE_S0); + if (wakeup && acpi_device_can_wakeup(adev)) { + error = acpi_device_wakeup_enable(adev, target_state); if (error) return -EAGAIN; + } else { + wakeup = false; } - error = acpi_dev_pm_low_power(dev, adev, ACPI_STATE_S0); - if (error && remote_wakeup) + error = acpi_dev_pm_low_power(dev, adev, target_state); + if (error && wakeup) acpi_device_wakeup_disable(adev); return error; } -EXPORT_SYMBOL_GPL(acpi_dev_runtime_suspend); +EXPORT_SYMBOL_GPL(acpi_dev_suspend); /** - * acpi_dev_runtime_resume - Put device into the full-power state using ACPI. + * acpi_dev_resume - Put device into the full-power state using ACPI. * @dev: Device to put into the full-power state. * * Put the given device into the full-power state using the standard ACPI - * mechanism at run time. Set the power state of the device to ACPI D0 and - * disable remote wakeup. + * mechanism. Set the power state of the device to ACPI D0 and disable wakeup. */ -int acpi_dev_runtime_resume(struct device *dev) +int acpi_dev_resume(struct device *dev) { struct acpi_device *adev = ACPI_COMPANION(dev); int error; @@ -901,7 +903,7 @@ int acpi_dev_runtime_resume(struct device *dev) acpi_device_wakeup_disable(adev); return error; } -EXPORT_SYMBOL_GPL(acpi_dev_runtime_resume); +EXPORT_SYMBOL_GPL(acpi_dev_resume); /** * acpi_subsys_runtime_suspend - Suspend device using ACPI. @@ -913,7 +915,7 @@ EXPORT_SYMBOL_GPL(acpi_dev_runtime_resume); int acpi_subsys_runtime_suspend(struct device *dev) { int ret = pm_generic_runtime_suspend(dev); - return ret ? ret : acpi_dev_runtime_suspend(dev); + return ret ? ret : acpi_dev_suspend(dev, true); } EXPORT_SYMBOL_GPL(acpi_subsys_runtime_suspend); @@ -926,68 +928,33 @@ EXPORT_SYMBOL_GPL(acpi_subsys_runtime_suspend); */ int acpi_subsys_runtime_resume(struct device *dev) { - int ret = acpi_dev_runtime_resume(dev); + int ret = acpi_dev_resume(dev); return ret ? ret : pm_generic_runtime_resume(dev); } EXPORT_SYMBOL_GPL(acpi_subsys_runtime_resume); #ifdef CONFIG_PM_SLEEP -/** - * acpi_dev_suspend_late - Put device into a low-power state using ACPI. - * @dev: Device to put into a low-power state. - * - * Put the given device into a low-power state during system transition to a - * sleep state using the standard ACPI mechanism. Set up system wakeup if - * desired, choose the state to put the device into (this checks if system - * wakeup is expected to work too), and set the power state of the device. - */ -int acpi_dev_suspend_late(struct device *dev) +static bool acpi_dev_needs_resume(struct device *dev, struct acpi_device *adev) { - struct acpi_device *adev = ACPI_COMPANION(dev); - u32 target_state; - bool wakeup; - int error; - - if (!adev) - return 0; - - target_state = acpi_target_system_state(); - wakeup = device_may_wakeup(dev) && acpi_device_can_wakeup(adev); - if (wakeup) { - error = acpi_device_wakeup_enable(adev, target_state); - if (error) - return error; - } + u32 sys_target = acpi_target_system_state(); + int ret, state; - error = acpi_dev_pm_low_power(dev, adev, target_state); - if (error && wakeup) - acpi_device_wakeup_disable(adev); + if (!pm_runtime_suspended(dev) || !adev || + device_may_wakeup(dev) != !!adev->wakeup.prepare_count) + return true; - return error; -} -EXPORT_SYMBOL_GPL(acpi_dev_suspend_late); + if (sys_target == ACPI_STATE_S0) + return false; -/** - * acpi_dev_resume_early - Put device into the full-power state using ACPI. - * @dev: Device to put into the full-power state. - * - * Put the given device into the full-power state using the standard ACPI - * mechanism during system transition to the working state. Set the power - * state of the device to ACPI D0 and disable remote wakeup. - */ -int acpi_dev_resume_early(struct device *dev) -{ - struct acpi_device *adev = ACPI_COMPANION(dev); - int error; + if (adev->power.flags.dsw_present) + return true; - if (!adev) - return 0; + ret = acpi_dev_pm_get_state(dev, adev, sys_target, NULL, &state); + if (ret) + return true; - error = acpi_dev_pm_full_power(adev); - acpi_device_wakeup_disable(adev); - return error; + return state != adev->power.state; } -EXPORT_SYMBOL_GPL(acpi_dev_resume_early); /** * acpi_subsys_prepare - Prepare device for system transition to a sleep state. @@ -996,39 +963,53 @@ EXPORT_SYMBOL_GPL(acpi_dev_resume_early); int acpi_subsys_prepare(struct device *dev) { struct acpi_device *adev = ACPI_COMPANION(dev); - u32 sys_target; - int ret, state; - ret = pm_generic_prepare(dev); - if (ret < 0) - return ret; - - if (!adev || !pm_runtime_suspended(dev) - || device_may_wakeup(dev) != !!adev->wakeup.prepare_count) - return 0; + if (dev->driver && dev->driver->pm && dev->driver->pm->prepare) { + int ret = dev->driver->pm->prepare(dev); - sys_target = acpi_target_system_state(); - if (sys_target == ACPI_STATE_S0) - return 1; + if (ret < 0) + return ret; - if (adev->power.flags.dsw_present) - return 0; + if (!ret && dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_PREPARE)) + return 0; + } - ret = acpi_dev_pm_get_state(dev, adev, sys_target, NULL, &state); - return !ret && state == adev->power.state; + return !acpi_dev_needs_resume(dev, adev); } EXPORT_SYMBOL_GPL(acpi_subsys_prepare); /** + * acpi_subsys_complete - Finalize device's resume during system resume. + * @dev: Device to handle. + */ +void acpi_subsys_complete(struct device *dev) +{ + pm_generic_complete(dev); + /* + * If the device had been runtime-suspended before the system went into + * the sleep state it is going out of and it has never been resumed till + * now, resume it in case the firmware powered it up. + */ + if (dev->power.direct_complete && pm_resume_via_firmware()) + pm_request_resume(dev); +} +EXPORT_SYMBOL_GPL(acpi_subsys_complete); + +/** * acpi_subsys_suspend - Run the device driver's suspend callback. * @dev: Device to handle. * - * Follow PCI and resume devices suspended at run time before running their - * system suspend callbacks. + * Follow PCI and resume devices from runtime suspend before running their + * system suspend callbacks, unless the driver can cope with runtime-suspended + * devices during system suspend and there are no ACPI-specific reasons for + * resuming them. */ int acpi_subsys_suspend(struct device *dev) { - pm_runtime_resume(dev); + if (!dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_SUSPEND) || + acpi_dev_needs_resume(dev, ACPI_COMPANION(dev))) + pm_runtime_resume(dev); + return pm_generic_suspend(dev); } EXPORT_SYMBOL_GPL(acpi_subsys_suspend); @@ -1042,12 +1023,48 @@ EXPORT_SYMBOL_GPL(acpi_subsys_suspend); */ int acpi_subsys_suspend_late(struct device *dev) { - int ret = pm_generic_suspend_late(dev); - return ret ? ret : acpi_dev_suspend_late(dev); + int ret; + + if (dev_pm_smart_suspend_and_suspended(dev)) + return 0; + + ret = pm_generic_suspend_late(dev); + return ret ? ret : acpi_dev_suspend(dev, device_may_wakeup(dev)); } EXPORT_SYMBOL_GPL(acpi_subsys_suspend_late); /** + * acpi_subsys_suspend_noirq - Run the device driver's "noirq" suspend callback. + * @dev: Device to suspend. + */ +int acpi_subsys_suspend_noirq(struct device *dev) +{ + if (dev_pm_smart_suspend_and_suspended(dev)) + return 0; + + return pm_generic_suspend_noirq(dev); +} +EXPORT_SYMBOL_GPL(acpi_subsys_suspend_noirq); + +/** + * acpi_subsys_resume_noirq - Run the device driver's "noirq" resume callback. + * @dev: Device to handle. + */ +int acpi_subsys_resume_noirq(struct device *dev) +{ + /* + * Devices with DPM_FLAG_SMART_SUSPEND may be left in runtime suspend + * during system suspend, so update their runtime PM status to "active" + * as they will be put into D0 going forward. + */ + if (dev_pm_smart_suspend_and_suspended(dev)) + pm_runtime_set_active(dev); + + return pm_generic_resume_noirq(dev); +} +EXPORT_SYMBOL_GPL(acpi_subsys_resume_noirq); + +/** * acpi_subsys_resume_early - Resume device using ACPI. * @dev: Device to Resume. * @@ -1057,7 +1074,7 @@ EXPORT_SYMBOL_GPL(acpi_subsys_suspend_late); */ int acpi_subsys_resume_early(struct device *dev) { - int ret = acpi_dev_resume_early(dev); + int ret = acpi_dev_resume(dev); return ret ? ret : pm_generic_resume_early(dev); } EXPORT_SYMBOL_GPL(acpi_subsys_resume_early); @@ -1074,11 +1091,60 @@ int acpi_subsys_freeze(struct device *dev) * runtime-suspended devices should not be touched during freeze/thaw * transitions. */ - pm_runtime_resume(dev); + if (!dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_SUSPEND)) + pm_runtime_resume(dev); + return pm_generic_freeze(dev); } EXPORT_SYMBOL_GPL(acpi_subsys_freeze); +/** + * acpi_subsys_freeze_late - Run the device driver's "late" freeze callback. + * @dev: Device to handle. + */ +int acpi_subsys_freeze_late(struct device *dev) +{ + + if (dev_pm_smart_suspend_and_suspended(dev)) + return 0; + + return pm_generic_freeze_late(dev); +} +EXPORT_SYMBOL_GPL(acpi_subsys_freeze_late); + +/** + * acpi_subsys_freeze_noirq - Run the device driver's "noirq" freeze callback. + * @dev: Device to handle. + */ +int acpi_subsys_freeze_noirq(struct device *dev) +{ + + if (dev_pm_smart_suspend_and_suspended(dev)) + return 0; + + return pm_generic_freeze_noirq(dev); +} +EXPORT_SYMBOL_GPL(acpi_subsys_freeze_noirq); + +/** + * acpi_subsys_thaw_noirq - Run the device driver's "noirq" thaw callback. + * @dev: Device to handle. + */ +int acpi_subsys_thaw_noirq(struct device *dev) +{ + /* + * If the device is in runtime suspend, the "thaw" code may not work + * correctly with it, so skip the driver callback and make the PM core + * skip all of the subsequent "thaw" callbacks for the device. + */ + if (dev_pm_smart_suspend_and_suspended(dev)) { + dev->power.direct_complete = true; + return 0; + } + + return pm_generic_thaw_noirq(dev); +} +EXPORT_SYMBOL_GPL(acpi_subsys_thaw_noirq); #endif /* CONFIG_PM_SLEEP */ static struct dev_pm_domain acpi_general_pm_domain = { @@ -1087,13 +1153,20 @@ static struct dev_pm_domain acpi_general_pm_domain = { .runtime_resume = acpi_subsys_runtime_resume, #ifdef CONFIG_PM_SLEEP .prepare = acpi_subsys_prepare, - .complete = pm_complete_with_resume_check, + .complete = acpi_subsys_complete, .suspend = acpi_subsys_suspend, .suspend_late = acpi_subsys_suspend_late, + .suspend_noirq = acpi_subsys_suspend_noirq, + .resume_noirq = acpi_subsys_resume_noirq, .resume_early = acpi_subsys_resume_early, .freeze = acpi_subsys_freeze, + .freeze_late = acpi_subsys_freeze_late, + .freeze_noirq = acpi_subsys_freeze_noirq, + .thaw_noirq = acpi_subsys_thaw_noirq, .poweroff = acpi_subsys_suspend, .poweroff_late = acpi_subsys_suspend_late, + .poweroff_noirq = acpi_subsys_suspend_noirq, + .restore_noirq = acpi_subsys_resume_noirq, .restore_early = acpi_subsys_resume_early, #endif }, |