summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/base/power/runtime.c375
1 files changed, 140 insertions, 235 deletions
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
index 0c1db879544..d7b5d84c235 100644
--- a/drivers/base/power/runtime.c
+++ b/drivers/base/power/runtime.c
@@ -2,6 +2,7 @@
* drivers/base/power/runtime.c - Helper functions for device run-time PM
*
* Copyright (c) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
+ * Copyright (C) 2010 Alan Stern <stern@rowland.harvard.edu>
*
* This file is released under the GPLv2.
*/
@@ -11,8 +12,6 @@
#include <linux/jiffies.h>
static int __pm_runtime_resume(struct device *dev, int rpmflags);
-static int __pm_request_idle(struct device *dev);
-static int __pm_request_resume(struct device *dev);
/**
* update_pm_runtime_accounting - Update the time accounting of power states
@@ -79,40 +78,84 @@ static void pm_runtime_cancel_pending(struct device *dev)
}
/**
- * __pm_runtime_idle - Notify device bus type if the device can be suspended.
- * @dev: Device to notify the bus type about.
- *
- * This function must be called under dev->power.lock with interrupts disabled.
+ * rpm_check_suspend_allowed - Test whether a device may be suspended.
+ * @dev: Device to test.
*/
-static int __pm_runtime_idle(struct device *dev)
- __releases(&dev->power.lock) __acquires(&dev->power.lock)
+static int rpm_check_suspend_allowed(struct device *dev)
{
int retval = 0;
if (dev->power.runtime_error)
retval = -EINVAL;
- else if (dev->power.idle_notification)
- retval = -EINPROGRESS;
else if (atomic_read(&dev->power.usage_count) > 0
- || dev->power.disable_depth > 0
- || dev->power.runtime_status != RPM_ACTIVE)
+ || dev->power.disable_depth > 0)
retval = -EAGAIN;
else if (!pm_children_suspended(dev))
retval = -EBUSY;
+
+ /* Pending resume requests take precedence over suspends. */
+ else if ((dev->power.deferred_resume
+ && dev->power.status == RPM_SUSPENDING)
+ || (dev->power.request_pending
+ && dev->power.request == RPM_REQ_RESUME))
+ retval = -EAGAIN;
+ else if (dev->power.runtime_status == RPM_SUSPENDED)
+ retval = 1;
+
+ return retval;
+}
+
+
+/**
+ * __pm_runtime_idle - Notify device bus type if the device can be suspended.
+ * @dev: Device to notify the bus type about.
+ * @rpmflags: Flag bits.
+ *
+ * Check if the device's run-time PM status allows it to be suspended. If
+ * another idle notification has been started earlier, return immediately. If
+ * the RPM_ASYNC flag is set then queue an idle-notification request; otherwise
+ * run the ->runtime_idle() callback directly.
+ *
+ * This function must be called under dev->power.lock with interrupts disabled.
+ */
+static int __pm_runtime_idle(struct device *dev, int rpmflags)
+ __releases(&dev->power.lock) __acquires(&dev->power.lock)
+{
+ int retval;
+
+ retval = rpm_check_suspend_allowed(dev);
+ if (retval < 0)
+ ; /* Conditions are wrong. */
+
+ /* Idle notifications are allowed only in the RPM_ACTIVE state. */
+ else if (dev->power.runtime_status != RPM_ACTIVE)
+ retval = -EAGAIN;
+
+ /*
+ * Any pending request other than an idle notification takes
+ * precedence over us, except that the timer may be running.
+ */
+ else if (dev->power.request_pending &&
+ dev->power.request > RPM_REQ_IDLE)
+ retval = -EAGAIN;
+
+ /* Act as though RPM_NOWAIT is always set. */
+ else if (dev->power.idle_notification)
+ retval = -EINPROGRESS;
if (retval)
goto out;
- if (dev->power.request_pending) {
- /*
- * If an idle notification request is pending, cancel it. Any
- * other pending request takes precedence over us.
- */
- if (dev->power.request == RPM_REQ_IDLE) {
- dev->power.request = RPM_REQ_NONE;
- } else if (dev->power.request != RPM_REQ_NONE) {
- retval = -EAGAIN;
- goto out;
+ /* Pending requests need to be canceled. */
+ dev->power.request = RPM_REQ_NONE;
+
+ /* Carry out an asynchronous or a synchronous idle notification. */
+ if (rpmflags & RPM_ASYNC) {
+ dev->power.request = RPM_REQ_IDLE;
+ if (!dev->power.request_pending) {
+ dev->power.request_pending = true;
+ queue_work(pm_wq, &dev->power.work);
}
+ goto out;
}
dev->power.idle_notification = true;
@@ -154,7 +197,7 @@ int pm_runtime_idle(struct device *dev)
int retval;
spin_lock_irq(&dev->power.lock);
- retval = __pm_runtime_idle(dev);
+ retval = __pm_runtime_idle(dev, 0);
spin_unlock_irq(&dev->power.lock);
return retval;
@@ -166,11 +209,14 @@ EXPORT_SYMBOL_GPL(pm_runtime_idle);
* @dev: Device to suspend.
* @rpmflags: Flag bits.
*
- * Check if the device can be suspended and run the ->runtime_suspend() callback
- * provided by its bus type. If another suspend has been started earlier,
- * either return immediately or wait for it to finish, depending on the
- * RPM_NOWAIT flag. If an idle notification or suspend request is pending or
- * scheduled, cancel it.
+ * Check if the device's run-time PM status allows it to be suspended. If
+ * another suspend has been started earlier, either return immediately or wait
+ * for it to finish, depending on the RPM_NOWAIT and RPM_ASYNC flags. Cancel a
+ * pending idle notification. If the RPM_ASYNC flag is set then queue a
+ * suspend request; otherwise run the ->runtime_suspend() callback directly.
+ * If a deferred resume was requested while the callback was running then carry
+ * it out; otherwise send an idle notification for the device (if the suspend
+ * failed) or for its parent (if the suspend succeeded).
*
* This function must be called under dev->power.lock with interrupts disabled.
*/
@@ -179,41 +225,30 @@ static int __pm_runtime_suspend(struct device *dev, int rpmflags)
{
struct device *parent = NULL;
bool notify = false;
- int retval = 0;
+ int retval;
dev_dbg(dev, "%s flags 0x%x\n", __func__, rpmflags);
repeat:
- if (dev->power.runtime_error) {
- retval = -EINVAL;
- goto out;
- }
+ retval = rpm_check_suspend_allowed(dev);
- /* Pending resume requests take precedence over us. */
- if (dev->power.request_pending
- && dev->power.request == RPM_REQ_RESUME) {
+ if (retval < 0)
+ ; /* Conditions are wrong. */
+
+ /* Synchronous suspends are not allowed in the RPM_RESUMING state. */
+ else if (dev->power.runtime_status == RPM_RESUMING &&
+ !(rpmflags & RPM_ASYNC))
retval = -EAGAIN;
+ if (retval)
goto out;
- }
/* Other scheduled or pending requests need to be canceled. */
pm_runtime_cancel_pending(dev);
- if (dev->power.runtime_status == RPM_SUSPENDED)
- retval = 1;
- else if (dev->power.runtime_status == RPM_RESUMING
- || dev->power.disable_depth > 0
- || atomic_read(&dev->power.usage_count) > 0)
- retval = -EAGAIN;
- else if (!pm_children_suspended(dev))
- retval = -EBUSY;
- if (retval)
- goto out;
-
if (dev->power.runtime_status == RPM_SUSPENDING) {
DEFINE_WAIT(wait);
- if (rpmflags & RPM_NOWAIT) {
+ if (rpmflags & (RPM_ASYNC | RPM_NOWAIT)) {
retval = -EINPROGRESS;
goto out;
}
@@ -235,6 +270,16 @@ static int __pm_runtime_suspend(struct device *dev, int rpmflags)
goto repeat;
}
+ /* Carry out an asynchronous or a synchronous suspend. */
+ if (rpmflags & RPM_ASYNC) {
+ dev->power.request = RPM_REQ_SUSPEND;
+ if (!dev->power.request_pending) {
+ dev->power.request_pending = true;
+ queue_work(pm_wq, &dev->power.work);
+ }
+ goto out;
+ }
+
__update_runtime_status(dev, RPM_SUSPENDING);
dev->power.deferred_resume = false;
@@ -267,6 +312,7 @@ static int __pm_runtime_suspend(struct device *dev, int rpmflags)
if (retval) {
__update_runtime_status(dev, RPM_ACTIVE);
+ dev->power.deferred_resume = 0;
if (retval == -EAGAIN || retval == -EBUSY) {
if (dev->power.timer_expires == 0)
notify = true;
@@ -292,7 +338,7 @@ static int __pm_runtime_suspend(struct device *dev, int rpmflags)
}
if (notify)
- __pm_runtime_idle(dev);
+ __pm_runtime_idle(dev, 0);
if (parent && !parent->power.ignore_children) {
spin_unlock_irq(&dev->power.lock);
@@ -329,13 +375,15 @@ EXPORT_SYMBOL_GPL(pm_runtime_suspend);
* @dev: Device to resume.
* @rpmflags: Flag bits.
*
- * Check if the device can be woken up and run the ->runtime_resume() callback
- * provided by its bus type. If another resume has been started earlier,
- * either return imediately or wait for it to finish, depending on the
- * RPM_NOWAIT flag. If there's a suspend running in parallel with this
- * function, either tell the other process to resume after suspending
- * (deferred_resume) or wait for it to finish, depending on the RPM_NOWAIT
- * flag. Cancel any scheduled or pending requests.
+ * Check if the device's run-time PM status allows it to be resumed. Cancel
+ * any scheduled or pending requests. If another resume has been started
+ * earlier, either return imediately or wait for it to finish, depending on the
+ * RPM_NOWAIT and RPM_ASYNC flags. Similarly, if there's a suspend running in
+ * parallel with this function, either tell the other process to resume after
+ * suspending (deferred_resume) or wait for it to finish. If the RPM_ASYNC
+ * flag is set then queue a resume request; otherwise run the
+ * ->runtime_resume() callback directly. Queue an idle notification for the
+ * device if the resume succeeded.
*
* This function must be called under dev->power.lock with interrupts disabled.
*/
@@ -348,28 +396,30 @@ static int __pm_runtime_resume(struct device *dev, int rpmflags)
dev_dbg(dev, "%s flags 0x%x\n", __func__, rpmflags);
repeat:
- if (dev->power.runtime_error) {
+ if (dev->power.runtime_error)
retval = -EINVAL;
+ else if (dev->power.disable_depth > 0)
+ retval = -EAGAIN;
+ if (retval)
goto out;
- }
+ /* Other scheduled or pending requests need to be canceled. */
pm_runtime_cancel_pending(dev);
- if (dev->power.runtime_status == RPM_ACTIVE)
+ if (dev->power.runtime_status == RPM_ACTIVE) {
retval = 1;
- else if (dev->power.disable_depth > 0)
- retval = -EAGAIN;
- if (retval)
goto out;
+ }
if (dev->power.runtime_status == RPM_RESUMING
|| dev->power.runtime_status == RPM_SUSPENDING) {
DEFINE_WAIT(wait);
- if (rpmflags & RPM_NOWAIT) {
+ if (rpmflags & (RPM_ASYNC | RPM_NOWAIT)) {
if (dev->power.runtime_status == RPM_SUSPENDING)
dev->power.deferred_resume = true;
- retval = -EINPROGRESS;
+ else
+ retval = -EINPROGRESS;
goto out;
}
@@ -391,6 +441,17 @@ static int __pm_runtime_resume(struct device *dev, int rpmflags)
goto repeat;
}
+ /* Carry out an asynchronous or a synchronous resume. */
+ if (rpmflags & RPM_ASYNC) {
+ dev->power.request = RPM_REQ_RESUME;
+ if (!dev->power.request_pending) {
+ dev->power.request_pending = true;
+ queue_work(pm_wq, &dev->power.work);
+ }
+ retval = 0;
+ goto out;
+ }
+
if (!parent && dev->parent) {
/*
* Increment the parent's resume counter and resume it if
@@ -460,7 +521,7 @@ static int __pm_runtime_resume(struct device *dev, int rpmflags)
wake_up_all(&dev->power.wait_queue);
if (!retval)
- __pm_request_idle(dev);
+ __pm_runtime_idle(dev, RPM_ASYNC);
out:
if (parent) {
@@ -517,7 +578,7 @@ static void pm_runtime_work(struct work_struct *work)
case RPM_REQ_NONE:
break;
case RPM_REQ_IDLE:
- __pm_runtime_idle(dev);
+ __pm_runtime_idle(dev, RPM_NOWAIT);
break;
case RPM_REQ_SUSPEND:
__pm_runtime_suspend(dev, RPM_NOWAIT);
@@ -532,47 +593,6 @@ static void pm_runtime_work(struct work_struct *work)
}
/**
- * __pm_request_idle - Submit an idle notification request for given device.
- * @dev: Device to handle.
- *
- * Check if the device's run-time PM status is correct for suspending the device
- * and queue up a request to run __pm_runtime_idle() for it.
- *
- * This function must be called under dev->power.lock with interrupts disabled.
- */
-static int __pm_request_idle(struct device *dev)
-{
- int retval = 0;
-
- if (dev->power.runtime_error)
- retval = -EINVAL;
- else if (atomic_read(&dev->power.usage_count) > 0
- || dev->power.disable_depth > 0
- || dev->power.runtime_status == RPM_SUSPENDED
- || dev->power.runtime_status == RPM_SUSPENDING)
- retval = -EAGAIN;
- else if (!pm_children_suspended(dev))
- retval = -EBUSY;
- if (retval)
- return retval;
-
- if (dev->power.request_pending) {
- /* Any requests other then RPM_REQ_IDLE take precedence. */
- if (dev->power.request == RPM_REQ_NONE)
- dev->power.request = RPM_REQ_IDLE;
- else if (dev->power.request != RPM_REQ_IDLE)
- retval = -EAGAIN;
- return retval;
- }
-
- dev->power.request = RPM_REQ_IDLE;
- dev->power.request_pending = true;
- queue_work(pm_wq, &dev->power.work);
-
- return retval;
-}
-
-/**
* pm_request_idle - Submit an idle notification request for given device.
* @dev: Device to handle.
*/
@@ -582,7 +602,7 @@ int pm_request_idle(struct device *dev)
int retval;
spin_lock_irqsave(&dev->power.lock, flags);
- retval = __pm_request_idle(dev);
+ retval = __pm_runtime_idle(dev, RPM_ASYNC);
spin_unlock_irqrestore(&dev->power.lock, flags);
return retval;
@@ -590,59 +610,10 @@ int pm_request_idle(struct device *dev)
EXPORT_SYMBOL_GPL(pm_request_idle);
/**
- * __pm_request_suspend - Submit a suspend request for given device.
- * @dev: Device to suspend.
- *
- * This function must be called under dev->power.lock with interrupts disabled.
- */
-static int __pm_request_suspend(struct device *dev)
-{
- int retval = 0;
-
- if (dev->power.runtime_error)
- return -EINVAL;
-
- if (dev->power.runtime_status == RPM_SUSPENDED)
- retval = 1;
- else if (atomic_read(&dev->power.usage_count) > 0
- || dev->power.disable_depth > 0)
- retval = -EAGAIN;
- else if (dev->power.runtime_status == RPM_SUSPENDING)
- retval = -EINPROGRESS;
- else if (!pm_children_suspended(dev))
- retval = -EBUSY;
- if (retval < 0)
- return retval;
-
- pm_runtime_deactivate_timer(dev);
-
- if (dev->power.request_pending) {
- /*
- * Pending resume requests take precedence over us, but we can
- * overtake any other pending request.
- */
- if (dev->power.request == RPM_REQ_RESUME)
- retval = -EAGAIN;
- else if (dev->power.request != RPM_REQ_SUSPEND)
- dev->power.request = retval ?
- RPM_REQ_NONE : RPM_REQ_SUSPEND;
- return retval;
- } else if (retval) {
- return retval;
- }
-
- dev->power.request = RPM_REQ_SUSPEND;
- dev->power.request_pending = true;
- queue_work(pm_wq, &dev->power.work);
-
- return 0;
-}
-
-/**
* pm_suspend_timer_fn - Timer function for pm_schedule_suspend().
* @data: Device pointer passed by pm_schedule_suspend().
*
- * Check if the time is right and execute __pm_request_suspend() in that case.
+ * Check if the time is right and queue a suspend request.
*/
static void pm_suspend_timer_fn(unsigned long data)
{
@@ -656,7 +627,7 @@ static void pm_suspend_timer_fn(unsigned long data)
/* If 'expire' is after 'jiffies' we've been called too early. */
if (expires > 0 && !time_after(expires, jiffies)) {
dev->power.timer_expires = 0;
- __pm_request_suspend(dev);
+ __pm_runtime_suspend(dev, RPM_ASYNC);
}
spin_unlock_irqrestore(&dev->power.lock, flags);
@@ -670,47 +641,24 @@ static void pm_suspend_timer_fn(unsigned long data)
int pm_schedule_suspend(struct device *dev, unsigned int delay)
{
unsigned long flags;
- int retval = 0;
+ int retval;
spin_lock_irqsave(&dev->power.lock, flags);
- if (dev->power.runtime_error) {
- retval = -EINVAL;
- goto out;
- }
-
if (!delay) {
- retval = __pm_request_suspend(dev);
+ retval = __pm_runtime_suspend(dev, RPM_ASYNC);
goto out;
}
- pm_runtime_deactivate_timer(dev);
-
- if (dev->power.request_pending) {
- /*
- * Pending resume requests take precedence over us, but any
- * other pending requests have to be canceled.
- */
- if (dev->power.request == RPM_REQ_RESUME) {
- retval = -EAGAIN;
- goto out;
- }
- dev->power.request = RPM_REQ_NONE;
- }
-
- if (dev->power.runtime_status == RPM_SUSPENDED)
- retval = 1;
- else if (atomic_read(&dev->power.usage_count) > 0
- || dev->power.disable_depth > 0)
- retval = -EAGAIN;
- else if (!pm_children_suspended(dev))
- retval = -EBUSY;
+ retval = rpm_check_suspend_allowed(dev);
if (retval)
goto out;
+ /* Other scheduled or pending requests need to be canceled. */
+ pm_runtime_cancel_pending(dev);
+
dev->power.timer_expires = jiffies + msecs_to_jiffies(delay);
- if (!dev->power.timer_expires)
- dev->power.timer_expires = 1;
+ dev->power.timer_expires += !dev->power.timer_expires;
mod_timer(&dev->power.suspend_timer, dev->power.timer_expires);
out:
@@ -723,49 +671,6 @@ EXPORT_SYMBOL_GPL(pm_schedule_suspend);
/**
* pm_request_resume - Submit a resume request for given device.
* @dev: Device to resume.
- *
- * This function must be called under dev->power.lock with interrupts disabled.
- */
-static int __pm_request_resume(struct device *dev)
-{
- int retval = 0;
-
- if (dev->power.runtime_error)
- return -EINVAL;
-
- if (dev->power.runtime_status == RPM_ACTIVE)
- retval = 1;
- else if (dev->power.runtime_status == RPM_RESUMING)
- retval = -EINPROGRESS;
- else if (dev->power.disable_depth > 0)
- retval = -EAGAIN;
- if (retval < 0)
- return retval;
-
- pm_runtime_deactivate_timer(dev);
-
- if (dev->power.runtime_status == RPM_SUSPENDING) {
- dev->power.deferred_resume = true;
- return retval;
- }
- if (dev->power.request_pending) {
- /* If non-resume request is pending, we can overtake it. */
- dev->power.request = retval ? RPM_REQ_NONE : RPM_REQ_RESUME;
- return retval;
- }
- if (retval)
- return retval;
-
- dev->power.request = RPM_REQ_RESUME;
- dev->power.request_pending = true;
- queue_work(pm_wq, &dev->power.work);
-
- return retval;
-}
-
-/**
- * pm_request_resume - Submit a resume request for given device.
- * @dev: Device to resume.
*/
int pm_request_resume(struct device *dev)
{
@@ -773,7 +678,7 @@ int pm_request_resume(struct device *dev)
int retval;
spin_lock_irqsave(&dev->power.lock, flags);
- retval = __pm_request_resume(dev);
+ retval = __pm_runtime_resume(dev, RPM_ASYNC);
spin_unlock_irqrestore(&dev->power.lock, flags);
return retval;
@@ -1088,7 +993,7 @@ void pm_runtime_allow(struct device *dev)
dev->power.runtime_auto = true;
if (atomic_dec_and_test(&dev->power.usage_count))
- __pm_runtime_idle(dev);
+ __pm_runtime_idle(dev, 0);
out:
spin_unlock_irq(&dev->power.lock);