]> rtime.felk.cvut.cz Git - sojka/nv-tegra/linux-3.10.git/commitdiff
PM / Sleep: add support for late early noirq suspend
authorBibek Basu <bbasu@nvidia.com>
Wed, 24 Dec 2014 10:35:04 +0000 (16:05 +0530)
committerLaxman Dewangan <ldewangan@nvidia.com>
Sun, 28 Dec 2014 11:35:03 +0000 (03:35 -0800)
Patch add support for late noirq suspend and early
noirq resume to provide one more level of hooks so
that we can push the penultimate drivers to this
last level and others can use then.
One sich driver is i2c

Bug 200059874

Change-Id: I10abbeef1d3035b85cdb31f46ae28f1407079329
Signed-off-by: Bibek Basu <bbasu@nvidia.com>
Reviewed-on: http://git-master/r/667647
GVS: Gerrit_Virtual_Submit
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
drivers/base/power/main.c
include/linux/pm.h
include/linux/suspend.h

index 55b9f1731788b216e893ba24874204ae1f70e75a..5fc9e0fe47507c1c3e07ce8ac38c8996dd9a9d2f 100644 (file)
@@ -52,6 +52,7 @@ static LIST_HEAD(dpm_prepared_list);
 static LIST_HEAD(dpm_suspended_list);
 static LIST_HEAD(dpm_late_early_list);
 static LIST_HEAD(dpm_noirq_list);
+static LIST_HEAD(dpm_noirq_late_early_list);
 
 struct suspend_stats suspend_stats;
 static DEFINE_MUTEX(dpm_list_mtx);
@@ -318,6 +319,40 @@ static pm_callback_t pm_noirq_op(const struct dev_pm_ops *ops, pm_message_t stat
        return NULL;
 }
 
+/**
+ * pm_noirq_late_early_op - Return the PM operation appropriate for given PM event.
+ * @ops: PM operations to choose from.
+ * @state: PM transition of the system being carried out.
+ *
+ * Runtime PM is disabled for @dev while this function is being executed.
+ */
+static pm_callback_t pm_noirq_late_early_op(const struct dev_pm_ops *ops,
+                                     pm_message_t state)
+{
+       switch (state.event) {
+#ifdef CONFIG_SUSPEND
+       case PM_EVENT_SUSPEND:
+               return ops->suspend_noirq_late;
+       case PM_EVENT_RESUME:
+               return ops->resume_noirq_early;
+#endif /* CONFIG_SUSPEND */
+#ifdef CONFIG_HIBERNATE_CALLBACKS
+       case PM_EVENT_FREEZE:
+       case PM_EVENT_QUIESCE:
+               return ops->freeze_noirq_late;
+       case PM_EVENT_HIBERNATE:
+               return ops->poweroff_noirq_late;
+       case PM_EVENT_THAW:
+       case PM_EVENT_RECOVER:
+               return ops->thaw_noirq_early;
+       case PM_EVENT_RESTORE:
+               return ops->restore_noirq_early;
+#endif /* CONFIG_HIBERNATE_CALLBACKS */
+       }
+
+       return NULL;
+}
+
 static char *pm_verb(int event)
 {
        switch (event) {
@@ -444,9 +479,91 @@ static void dpm_wd_clear(struct dpm_watchdog *wd)
 }
 
 /*------------------------- Resume routines -------------------------*/
+/**
+ * device_resume_noirq_early - Execute an "early noirq resume" callback
+ * for given device.
+ * @dev: Device to handle.
+ * @state: PM transition of the system being carried out.
+ *
+ * The driver of @dev will not receive interrupts while this function is being
+ * executed.
+ */
+static int device_resume_noirq_early(struct device *dev, pm_message_t state)
+{
+       pm_callback_t callback = NULL;
+       char *info = NULL;
+       int error = 0;
+
+       TRACE_DEVICE(dev);
+       TRACE_RESUME(0);
+
+       if (dev->power.syscore)
+               goto out;
+
+       if (dev->pm_domain) {
+               info = "noirq early power domain ";
+               callback = pm_noirq_late_early_op(&dev->pm_domain->ops, state);
+       } else if (dev->type && dev->type->pm) {
+               info = "noirq early type ";
+               callback = pm_noirq_late_early_op(dev->type->pm, state);
+       } else if (dev->class && dev->class->pm) {
+               info = "noirq early class ";
+               callback = pm_noirq_late_early_op(dev->class->pm, state);
+       } else if (dev->bus && dev->bus->pm) {
+               info = "noirq early bus ";
+               callback = pm_noirq_late_early_op(dev->bus->pm, state);
+       }
+
+       if (!callback && dev->driver && dev->driver->pm) {
+               info = "noirq early driver ";
+               callback = pm_noirq_late_early_op(dev->driver->pm, state);
+       }
+
+       error = dpm_run_callback(callback, dev, state, info);
+
+ out:
+       TRACE_RESUME(error);
+       return error;
+}
+
+/**
+ * dpm_resume_noirq_early - Execute "early noirq resume" callbacks for
+ * all devices.
+ * @state: PM transition of the system being carried out.
+ *
+ * Call the "early noirq" resume handlers for all devices in
+ * dpm_noirq_late_early_list and enable device drivers to receive interrupts.
+ */
+static void dpm_resume_noirq_early(pm_message_t state)
+{
+       ktime_t starttime = ktime_get();
+
+       mutex_lock(&dpm_list_mtx);
+       while (!list_empty(&dpm_noirq_late_early_list)) {
+               struct device *dev = to_device(dpm_noirq_late_early_list.next);
+               int error;
+
+               get_device(dev);
+               list_move_tail(&dev->power.entry, &dpm_noirq_list);
+               mutex_unlock(&dpm_list_mtx);
+
+               error = device_resume_noirq_early(dev, state);
+               if (error) {
+                       suspend_stats.failed_resume_noirq_early++;
+                       dpm_save_failed_step(SUSPEND_RESUME_NOIRQ_EARLY);
+                       dpm_save_failed_dev(dev_name(dev));
+                       pm_dev_err(dev, state, "early noirq", error);
+               }
+
+               mutex_lock(&dpm_list_mtx);
+               put_device(dev);
+       }
+       mutex_unlock(&dpm_list_mtx);
+       dpm_show_time(starttime, state, "early noirq");
+}
 
 /**
- * device_resume_noirq - Execute an "early resume" callback for given device.
+ * device_resume_noirq - Execute an "noirq resume" callback for given device.
  * @dev: Device to handle.
  * @state: PM transition of the system being carried out.
  *
@@ -613,6 +730,7 @@ static void dpm_resume_early(pm_message_t state)
  */
 void dpm_resume_start(pm_message_t state)
 {
+       dpm_resume_noirq_early(state);
        dpm_resume_noirq(state);
        dpm_resume_early(state);
 }
@@ -890,6 +1008,94 @@ static pm_message_t resume_event(pm_message_t sleep_state)
        }
        return PMSG_ON;
 }
+/**
+ * device_suspend_noirq_late - Execute a "late noirq suspend" callback for
+ * given device.
+ * @dev: Device to handle.
+ * @state: PM transition of the system being carried out.
+ *
+ * The driver of @dev will not receive interrupts while this function is being
+ * executed.
+ */
+static int device_suspend_noirq_late(struct device *dev, pm_message_t state)
+{
+       pm_callback_t callback = NULL;
+       char *info = NULL;
+
+       if (dev->power.syscore)
+               return 0;
+
+       if (dev->pm_domain) {
+               info = "late noirq power domain ";
+               callback = pm_noirq_late_early_op(&dev->pm_domain->ops, state);
+       } else if (dev->type && dev->type->pm) {
+               info = "late noirq type ";
+               callback = pm_noirq_late_early_op(dev->type->pm, state);
+       } else if (dev->class && dev->class->pm) {
+               info = "late noirq class ";
+               callback = pm_noirq_late_early_op(dev->class->pm, state);
+       } else if (dev->bus && dev->bus->pm) {
+               info = "late noirq bus ";
+               callback = pm_noirq_late_early_op(dev->bus->pm, state);
+       }
+
+       if (!callback && dev->driver && dev->driver->pm) {
+               info = "late noirq driver ";
+               callback = pm_noirq_late_early_op(dev->driver->pm, state);
+       }
+
+       return dpm_run_callback(callback, dev, state, info);
+}
+
+/**
+ * dpm_suspend_noirq_late - Execute "late noirq suspend" callbacks for
+ * all devices.
+ * @state: PM transition of the system being carried out.
+ *
+ * Prevent device drivers from receiving interrupts and call the
+ * "late noirq" suspend handlers for all non-sysdev devices.
+ */
+static int dpm_suspend_noirq_late(pm_message_t state)
+{
+       ktime_t starttime = ktime_get();
+       int error = 0;
+
+       /* irqs already disabled */
+       mutex_lock(&dpm_list_mtx);
+       while (!list_empty(&dpm_noirq_list)) {
+               struct device *dev = to_device(dpm_noirq_list.prev);
+
+               get_device(dev);
+               mutex_unlock(&dpm_list_mtx);
+
+               error = device_suspend_noirq_late(dev, state);
+
+               mutex_lock(&dpm_list_mtx);
+               if (error) {
+                       pm_dev_err(dev, state, " late noirq", error);
+                       suspend_stats.failed_suspend_noirq_late++;
+                       dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ_LATE);
+                       dpm_save_failed_dev(dev_name(dev));
+                       put_device(dev);
+                       break;
+               }
+               if (!list_empty(&dev->power.entry))
+                       list_move(&dev->power.entry,
+                                       &dpm_noirq_late_early_list);
+               put_device(dev);
+
+               if (pm_wakeup_pending()) {
+                       error = -EBUSY;
+                       break;
+               }
+       }
+       mutex_unlock(&dpm_list_mtx);
+       if (error)
+               dpm_resume_noirq_early(resume_event(state));
+       else
+               dpm_show_time(starttime, state, "late noirq");
+       return error;
+}
 
 /**
  * device_suspend_noirq - Execute a "late suspend" callback for given device.
@@ -1086,6 +1292,13 @@ int dpm_suspend_end(pm_message_t state)
                return error;
        }
 
+       error = dpm_suspend_noirq_late(state);
+       if (error) {
+               dpm_resume_noirq(resume_event(state));
+               dpm_resume_early(resume_event(state));
+               return error;
+       }
+
        return 0;
 }
 EXPORT_SYMBOL_GPL(dpm_suspend_end);
index b86d803aad77a248671ba3c12fabd7f60510cddd..cb089986039f47a9eddf7e46847c4cfe142154e3 100644 (file)
@@ -295,6 +295,12 @@ struct dev_pm_ops {
        int (*thaw_noirq)(struct device *dev);
        int (*poweroff_noirq)(struct device *dev);
        int (*restore_noirq)(struct device *dev);
+       int (*suspend_noirq_late)(struct device *dev);
+       int (*resume_noirq_early)(struct device *dev);
+       int (*freeze_noirq_late)(struct device *dev);
+       int (*thaw_noirq_early)(struct device *dev);
+       int (*poweroff_noirq_late)(struct device *dev);
+       int (*restore_noirq_early)(struct device *dev);
        int (*runtime_suspend)(struct device *dev);
        int (*runtime_resume)(struct device *dev);
        int (*runtime_idle)(struct device *dev);
index f7cbc1436917c659b5abd77876f0e399167096d8..4528430d803cf5a4530fb28be49c0579edde33a9 100644 (file)
@@ -46,6 +46,8 @@ enum suspend_stat_step {
        SUSPEND_SUSPEND,
        SUSPEND_SUSPEND_LATE,
        SUSPEND_SUSPEND_NOIRQ,
+       SUSPEND_SUSPEND_NOIRQ_LATE,
+       SUSPEND_RESUME_NOIRQ_EARLY,
        SUSPEND_RESUME_NOIRQ,
        SUSPEND_RESUME_EARLY,
        SUSPEND_RESUME
@@ -59,9 +61,11 @@ struct suspend_stats {
        int     failed_suspend;
        int     failed_suspend_late;
        int     failed_suspend_noirq;
+       int     failed_suspend_noirq_late;
        int     failed_resume;
        int     failed_resume_early;
        int     failed_resume_noirq;
+       int     failed_resume_noirq_early;
 #define        REC_FAILED_NUM  2
        int     last_failed_dev;
        char    failed_devs[REC_FAILED_NUM][40];