Merge branch '3.2-without-smb2' of git://git.samba.org/sfrench/cifs-2.6
[pandora-kernel.git] / drivers / base / power / runtime.c
index acb3f83..6bb3aaf 100644 (file)
@@ -9,6 +9,7 @@
 
 #include <linux/sched.h>
 #include <linux/pm_runtime.h>
+#include <trace/events/rpm.h>
 #include "power.h"
 
 static int rpm_resume(struct device *dev, int rpmflags);
@@ -154,6 +155,31 @@ static int rpm_check_suspend_allowed(struct device *dev)
        return retval;
 }
 
+/**
+ * __rpm_callback - Run a given runtime PM callback for a given device.
+ * @cb: Runtime PM callback to run.
+ * @dev: Device to run the callback for.
+ */
+static int __rpm_callback(int (*cb)(struct device *), struct device *dev)
+       __releases(&dev->power.lock) __acquires(&dev->power.lock)
+{
+       int retval;
+
+       if (dev->power.irq_safe)
+               spin_unlock(&dev->power.lock);
+       else
+               spin_unlock_irq(&dev->power.lock);
+
+       retval = cb(dev);
+
+       if (dev->power.irq_safe)
+               spin_lock(&dev->power.lock);
+       else
+               spin_lock_irq(&dev->power.lock);
+
+       return retval;
+}
+
 /**
  * rpm_idle - Notify device bus type if the device can be suspended.
  * @dev: Device to notify the bus type about.
@@ -171,6 +197,7 @@ static int rpm_idle(struct device *dev, int rpmflags)
        int (*callback)(struct device *);
        int retval;
 
+       trace_rpm_idle(dev, rpmflags);
        retval = rpm_check_suspend_allowed(dev);
        if (retval < 0)
                ;       /* Conditions are wrong. */
@@ -225,24 +252,14 @@ static int rpm_idle(struct device *dev, int rpmflags)
        else
                callback = NULL;
 
-       if (callback) {
-               if (dev->power.irq_safe)
-                       spin_unlock(&dev->power.lock);
-               else
-                       spin_unlock_irq(&dev->power.lock);
-
-               callback(dev);
-
-               if (dev->power.irq_safe)
-                       spin_lock(&dev->power.lock);
-               else
-                       spin_lock_irq(&dev->power.lock);
-       }
+       if (callback)
+               __rpm_callback(callback, dev);
 
        dev->power.idle_notification = false;
        wake_up_all(&dev->power.wait_queue);
 
  out:
+       trace_rpm_return_int(dev, _THIS_IP_, retval);
        return retval;
 }
 
@@ -252,22 +269,14 @@ static int rpm_idle(struct device *dev, int rpmflags)
  * @dev: Device to run the callback for.
  */
 static int rpm_callback(int (*cb)(struct device *), struct device *dev)
-       __releases(&dev->power.lock) __acquires(&dev->power.lock)
 {
        int retval;
 
        if (!cb)
                return -ENOSYS;
 
-       if (dev->power.irq_safe) {
-               retval = cb(dev);
-       } else {
-               spin_unlock_irq(&dev->power.lock);
-
-               retval = cb(dev);
+       retval = __rpm_callback(cb, dev);
 
-               spin_lock_irq(&dev->power.lock);
-       }
        dev->power.runtime_error = retval;
        return retval != -EACCES ? retval : -EIO;
 }
@@ -277,14 +286,16 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev)
  * @dev: Device to suspend.
  * @rpmflags: Flag bits.
  *
- * Check if the device's runtime 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).
+ * Check if the device's runtime PM status allows it to be suspended.
+ * Cancel a pending idle notification, autosuspend or suspend. 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. If the RPM_ASYNC flag is set then queue a suspend request;
+ * 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).
  *
  * This function must be called under dev->power.lock with interrupts disabled.
  */
@@ -295,7 +306,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)
        struct device *parent = NULL;
        int retval;
 
-       dev_dbg(dev, "%s flags 0x%x\n", __func__, rpmflags);
+       trace_rpm_suspend(dev, rpmflags);
 
  repeat:
        retval = rpm_check_suspend_allowed(dev);
@@ -347,6 +358,15 @@ static int rpm_suspend(struct device *dev, int rpmflags)
                        goto out;
                }
 
+               if (dev->power.irq_safe) {
+                       spin_unlock(&dev->power.lock);
+
+                       cpu_relax();
+
+                       spin_lock(&dev->power.lock);
+                       goto repeat;
+               }
+
                /* Wait for the other suspend running in parallel with us. */
                for (;;) {
                        prepare_to_wait(&dev->power.wait_queue, &wait,
@@ -400,15 +420,16 @@ static int rpm_suspend(struct device *dev, int rpmflags)
                        dev->power.runtime_error = 0;
                else
                        pm_runtime_cancel_pending(dev);
-       } else {
+               wake_up_all(&dev->power.wait_queue);
+               goto out;
+       }
  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);
 
@@ -430,7 +451,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)
        }
 
  out:
-       dev_dbg(dev, "%s returns %d\n", __func__, retval);
+       trace_rpm_return_int(dev, _THIS_IP_, retval);
 
        return retval;
 }
@@ -459,7 +480,7 @@ static int rpm_resume(struct device *dev, int rpmflags)
        struct device *parent = NULL;
        int retval = 0;
 
-       dev_dbg(dev, "%s flags 0x%x\n", __func__, rpmflags);
+       trace_rpm_resume(dev, rpmflags);
 
  repeat:
        if (dev->power.runtime_error)
@@ -496,6 +517,15 @@ static int rpm_resume(struct device *dev, int rpmflags)
                        goto out;
                }
 
+               if (dev->power.irq_safe) {
+                       spin_unlock(&dev->power.lock);
+
+                       cpu_relax();
+
+                       spin_lock(&dev->power.lock);
+                       goto repeat;
+               }
+
                /* Wait for the operation carried out in parallel with us. */
                for (;;) {
                        prepare_to_wait(&dev->power.wait_queue, &wait,
@@ -615,7 +645,7 @@ static int rpm_resume(struct device *dev, int rpmflags)
                spin_lock_irq(&dev->power.lock);
        }
 
-       dev_dbg(dev, "%s returns %d\n", __func__, retval);
+       trace_rpm_return_int(dev, _THIS_IP_, retval);
 
        return retval;
 }
@@ -732,13 +762,16 @@ EXPORT_SYMBOL_GPL(pm_schedule_suspend);
  * return immediately if it is larger than zero.  Then carry out an idle
  * notification, either synchronous or asynchronous.
  *
- * This routine may be called in atomic context if the RPM_ASYNC flag is set.
+ * This routine may be called in atomic context if the RPM_ASYNC flag is set,
+ * or if pm_runtime_irq_safe() has been called.
  */
 int __pm_runtime_idle(struct device *dev, int rpmflags)
 {
        unsigned long flags;
        int retval;
 
+       might_sleep_if(!(rpmflags & RPM_ASYNC) && !dev->power.irq_safe);
+
        if (rpmflags & RPM_GET_PUT) {
                if (!atomic_dec_and_test(&dev->power.usage_count))
                        return 0;
@@ -761,13 +794,16 @@ EXPORT_SYMBOL_GPL(__pm_runtime_idle);
  * return immediately if it is larger than zero.  Then carry out a suspend,
  * either synchronous or asynchronous.
  *
- * This routine may be called in atomic context if the RPM_ASYNC flag is set.
+ * This routine may be called in atomic context if the RPM_ASYNC flag is set,
+ * or if pm_runtime_irq_safe() has been called.
  */
 int __pm_runtime_suspend(struct device *dev, int rpmflags)
 {
        unsigned long flags;
        int retval;
 
+       might_sleep_if(!(rpmflags & RPM_ASYNC) && !dev->power.irq_safe);
+
        if (rpmflags & RPM_GET_PUT) {
                if (!atomic_dec_and_test(&dev->power.usage_count))
                        return 0;
@@ -789,13 +825,16 @@ EXPORT_SYMBOL_GPL(__pm_runtime_suspend);
  * If the RPM_GET_PUT flag is set, increment the device's usage count.  Then
  * carry out a resume, either synchronous or asynchronous.
  *
- * This routine may be called in atomic context if the RPM_ASYNC flag is set.
+ * This routine may be called in atomic context if the RPM_ASYNC flag is set,
+ * or if pm_runtime_irq_safe() has been called.
  */
 int __pm_runtime_resume(struct device *dev, int rpmflags)
 {
        unsigned long flags;
        int retval;
 
+       might_sleep_if(!(rpmflags & RPM_ASYNC) && !dev->power.irq_safe);
+
        if (rpmflags & RPM_GET_PUT)
                atomic_inc(&dev->power.usage_count);