PM / Sleep: Add user space interface for manipulating wakeup sources, v3
[linux-2.6.git] / drivers / base / power / runtime.c
index aa23a64..5989487 100644 (file)
@@ -8,6 +8,7 @@
  */
 
 #include <linux/sched.h>
+#include <linux/export.h>
 #include <linux/pm_runtime.h>
 #include <trace/events/rpm.h>
 #include "power.h"
@@ -29,13 +30,10 @@ static int rpm_suspend(struct device *dev, int rpmflags);
 void update_pm_runtime_accounting(struct device *dev)
 {
        unsigned long now = jiffies;
-       int delta;
+       unsigned long delta;
 
        delta = now - dev->power.accounting_timestamp;
 
-       if (delta < 0)
-               delta = 0;
-
        dev->power.accounting_timestamp = now;
 
        if (dev->power.disable_depth > 0)
@@ -252,6 +250,9 @@ static int rpm_idle(struct device *dev, int rpmflags)
        else
                callback = NULL;
 
+       if (!callback && dev->driver && dev->driver->pm)
+               callback = dev->driver->pm->runtime_idle;
+
        if (callback)
                __rpm_callback(callback, dev);
 
@@ -291,11 +292,14 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev)
  * another suspend has been started earlier, either return immediately
  * or wait for it to finish, depending on the RPM_NOWAIT and RPM_ASYNC
  * flags. 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 its parent (if the suspend
- * succeeded and both ignore_children of parent->power and irq_safe of
- * dev->power are not set).
+ * otherwise run the ->runtime_suspend() callback directly. When
+ * ->runtime_suspend succeeded, if a deferred resume was requested while
+ * the callback was running then carry it out, otherwise send an idle
+ * notification for its parent (if the suspend succeeded and both
+ * ignore_children of parent->power and irq_safe of dev->power are not set).
+ * If ->runtime_suspend failed with -EAGAIN or -EBUSY, and if the RPM_AUTO
+ * flag is set and the next autosuspend-delay expiration time is in the
+ * future, schedule another autosuspend attempt.
  *
  * This function must be called under dev->power.lock with interrupts disabled.
  */
@@ -399,6 +403,12 @@ static int rpm_suspend(struct device *dev, int rpmflags)
                goto out;
        }
 
+       if (__dev_pm_qos_read_value(dev) < 0) {
+               /* Negative PM QoS constraint means "never suspend". */
+               retval = -EPERM;
+               goto out;
+       }
+
        __update_runtime_status(dev, RPM_SUSPENDING);
 
        if (dev->pm_domain)
@@ -412,23 +422,20 @@ static int rpm_suspend(struct device *dev, int rpmflags)
        else
                callback = NULL;
 
+       if (!callback && dev->driver && dev->driver->pm)
+               callback = dev->driver->pm->runtime_suspend;
+
        retval = rpm_callback(callback, dev);
-       if (retval) {
-               __update_runtime_status(dev, RPM_ACTIVE);
-               dev->power.deferred_resume = false;
-               if (retval == -EAGAIN || retval == -EBUSY)
-                       dev->power.runtime_error = 0;
-               else
-                       pm_runtime_cancel_pending(dev);
-       } else {
+       if (retval)
+               goto fail;
+
  no_callback:
-               __update_runtime_status(dev, RPM_SUSPENDED);
-               pm_runtime_deactivate_timer(dev);
+       __update_runtime_status(dev, RPM_SUSPENDED);
+       pm_runtime_deactivate_timer(dev);
 
-               if (dev->parent) {
-                       parent = dev->parent;
-                       atomic_add_unless(&parent->power.child_count, -1, 0);
-               }
+       if (dev->parent) {
+               parent = dev->parent;
+               atomic_add_unless(&parent->power.child_count, -1, 0);
        }
        wake_up_all(&dev->power.wait_queue);
 
@@ -453,6 +460,28 @@ static int rpm_suspend(struct device *dev, int rpmflags)
        trace_rpm_return_int(dev, _THIS_IP_, retval);
 
        return retval;
+
+ fail:
+       __update_runtime_status(dev, RPM_ACTIVE);
+       dev->power.deferred_resume = false;
+       wake_up_all(&dev->power.wait_queue);
+
+       if (retval == -EAGAIN || retval == -EBUSY) {
+               dev->power.runtime_error = 0;
+
+               /*
+                * If the callback routine failed an autosuspend, and
+                * if the last_busy time has been updated so that there
+                * is a new autosuspend expiration time, automatically
+                * reschedule another autosuspend.
+                */
+               if ((rpmflags & RPM_AUTO) &&
+                   pm_runtime_autosuspend_expiration(dev) != 0)
+                       goto repeat;
+       } else {
+               pm_runtime_cancel_pending(dev);
+       }
+       goto out;
 }
 
 /**
@@ -620,6 +649,9 @@ static int rpm_resume(struct device *dev, int rpmflags)
        else
                callback = NULL;
 
+       if (!callback && dev->driver && dev->driver->pm)
+               callback = dev->driver->pm->runtime_resume;
+
        retval = rpm_callback(callback, dev);
        if (retval) {
                __update_runtime_status(dev, RPM_SUSPENDED);