cpufreq: suspend governors on system suspend/hibernate
authorMark Brown <broonie@linaro.org>
Thu, 17 Apr 2014 23:48:32 +0000 (00:48 +0100)
committerMark Brown <broonie@linaro.org>
Wed, 23 Apr 2014 22:54:52 +0000 (23:54 +0100)
This patch adds cpufreq suspend/resume calls to dpm_{suspend|resume}()
for handling suspend/resume of cpufreq governors.

Lan Tianyu (Intel) & Jinhyuk Choi (Broadcom) found an issue where the
tunables configuration for clusters/sockets with non-boot CPUs was
lost after system suspend/resume, as we were notifying governors with
CPUFREQ_GOV_POLICY_EXIT on removal of the last CPU for that policy
which caused the tunables memory to be freed.

This is fixed by preventing any governor operations from being
carried out between the device suspend and device resume stages of
system suspend and resume, respectively.

We could have added these callbacks at dpm_{suspend|resume}_noirq()
level, but there is an additional problem that the majority of I/O
devices is already suspended at that point and if cpufreq drivers
want to change the frequency before suspending, then that not be
possible on some platforms (which depend on peripherals like i2c,
regulators, etc).

Reported-and-tested-by: Lan Tianyu <tianyu.lan@intel.com>
Reported-by: Jinhyuk Choi <jinchoi@broadcom.com>
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
[rjw: Changelog]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
(cherry-picked from 2f0aea9363609433b38ab13f0f86f38372668c68)
Signed-off-by: Mark Brown <broonie@linaro.org>
drivers/base/power/main.c
drivers/cpufreq/cpufreq.c
include/linux/cpufreq.h

index 5a9b6569dd74f8cc97ba15fde613f208bd752c46..220ec3a3ca750907bb26c3f2866e1c012b04db52 100644 (file)
@@ -28,6 +28,8 @@
 #include <linux/sched.h>
 #include <linux/async.h>
 #include <linux/suspend.h>
+#include <trace/events/power.h>
+#include <linux/cpufreq.h>
 #include <linux/cpuidle.h>
 #include "../base.h"
 #include "power.h"
@@ -713,6 +715,8 @@ void dpm_resume(pm_message_t state)
        mutex_unlock(&dpm_list_mtx);
        async_synchronize_full();
        dpm_show_time(starttime, state, NULL);
+
+       cpufreq_resume();
 }
 
 /**
@@ -1177,6 +1181,8 @@ int dpm_suspend(pm_message_t state)
 
        might_sleep();
 
+       cpufreq_suspend();
+
        mutex_lock(&dpm_list_mtx);
        pm_transition = state;
        async_error = 0;
index 2d53f47d1747360b8968c7c308c0f975777ef9ab..d444bfe83f133d454e232a2c1c3eb3ceaf05121c 100644 (file)
@@ -31,6 +31,8 @@
 #include <linux/completion.h>
 #include <linux/mutex.h>
 #include <linux/syscore_ops.h>
+#include <linux/suspend.h>
+#include <linux/tick.h>
 
 #include <trace/events/power.h>
 
@@ -47,6 +49,14 @@ static DEFINE_PER_CPU(char[CPUFREQ_NAME_LEN], cpufreq_cpu_governor);
 #endif
 static DEFINE_RWLOCK(cpufreq_driver_lock);
 
+/* Flag to suspend/resume CPUFreq governors */
+static bool cpufreq_suspended;
+
+static inline bool has_target(void)
+{
+       return cpufreq_driver->target;
+}
+
 /*
  * cpu_policy_rwsem is a per CPU reader-writer semaphore designed to cure
  * all cpufreq/hotplug/workqueue/etc related lock issues.
@@ -1273,82 +1283,89 @@ static struct subsys_interface cpufreq_interface = {
 
 
 /**
- * cpufreq_bp_suspend - Prepare the boot CPU for system suspend.
+ * cpufreq_suspend() - Suspend CPUFreq governors
  *
- * This function is only executed for the boot processor.  The other CPUs
- * have been put offline by means of CPU hotplug.
+ * Called during system wide Suspend/Hibernate cycles for suspending governors
+ * as some platforms can't change frequency after this point in suspend cycle.
+ * Because some of the devices (like: i2c, regulators, etc) they use for
+ * changing frequency are suspended quickly after this point.
  */
-static int cpufreq_bp_suspend(void)
+void cpufreq_suspend(void)
 {
-       int ret = 0;
+       struct cpufreq_policy *policy;
+       int cpu;
 
-       int cpu = smp_processor_id();
-       struct cpufreq_policy *cpu_policy;
+       if (!cpufreq_driver)
+               return;
 
-       pr_debug("suspending cpu %u\n", cpu);
+       if (!has_target())
+               return;
 
-       /* If there's no policy for the boot CPU, we have nothing to do. */
-       cpu_policy = cpufreq_cpu_get(cpu);
-       if (!cpu_policy)
-               return 0;
+       pr_debug("%s: Suspending Governors\n", __func__);
 
-       if (cpufreq_driver->suspend) {
-               ret = cpufreq_driver->suspend(cpu_policy);
-               if (ret)
-                       printk(KERN_ERR "cpufreq: suspend failed in ->suspend "
-                                       "step on CPU %u\n", cpu_policy->cpu);
+       for_each_possible_cpu(cpu) {
+               if (!cpu_online(cpu))
+                       continue;
+
+               policy = cpufreq_cpu_get(cpu);
+
+               if (__cpufreq_governor(policy, CPUFREQ_GOV_STOP))
+                       pr_err("%s: Failed to stop governor for policy: %p\n",
+                               __func__, policy);
+               else if (cpufreq_driver->suspend
+                   && cpufreq_driver->suspend(policy))
+                       pr_err("%s: Failed to suspend driver: %p\n", __func__,
+                               policy);
        }
 
-       cpufreq_cpu_put(cpu_policy);
-       return ret;
+       cpufreq_suspended = true;
 }
 
 /**
- * cpufreq_bp_resume - Restore proper frequency handling of the boot CPU.
+ * cpufreq_resume() - Resume CPUFreq governors
  *
- *     1.) resume CPUfreq hardware support (cpufreq_driver->resume())
- *     2.) schedule call cpufreq_update_policy() ASAP as interrupts are
- *         restored. It will verify that the current freq is in sync with
- *         what we believe it to be. This is a bit later than when it
- *         should be, but nonethteless it's better than calling
- *         cpufreq_driver->get() here which might re-enable interrupts...
- *
- * This function is only executed for the boot CPU.  The other CPUs have not
- * been turned on yet.
+ * Called during system wide Suspend/Hibernate cycle for resuming governors that
+ * are suspended with cpufreq_suspend().
  */
-static void cpufreq_bp_resume(void)
+void cpufreq_resume(void)
 {
-       int ret = 0;
-
-       int cpu = smp_processor_id();
-       struct cpufreq_policy *cpu_policy;
+       struct cpufreq_policy *policy;
+       int cpu;
 
-       pr_debug("resuming cpu %u\n", cpu);
+       if (!cpufreq_driver)
+               return;
 
-       /* If there's no policy for the boot CPU, we have nothing to do. */
-       cpu_policy = cpufreq_cpu_get(cpu);
-       if (!cpu_policy)
+       if (!has_target())
                return;
 
-       if (cpufreq_driver->resume) {
-               ret = cpufreq_driver->resume(cpu_policy);
-               if (ret) {
-                       printk(KERN_ERR "cpufreq: resume failed in ->resume "
-                                       "step on CPU %u\n", cpu_policy->cpu);
-                       goto fail;
-               }
-       }
+       pr_debug("%s: Resuming Governors\n", __func__);
 
-       schedule_work(&cpu_policy->update);
+       cpufreq_suspended = false;
 
-fail:
-       cpufreq_cpu_put(cpu_policy);
-}
+       for_each_possible_cpu(cpu) {
+               if (!cpu_online(cpu))
+                       continue;
 
-static struct syscore_ops cpufreq_syscore_ops = {
-       .suspend        = cpufreq_bp_suspend,
-       .resume         = cpufreq_bp_resume,
-};
+               policy = cpufreq_cpu_get(cpu);
+
+               if (__cpufreq_governor(policy, CPUFREQ_GOV_START)
+                   || __cpufreq_governor(policy, CPUFREQ_GOV_LIMITS))
+                       pr_err("%s: Failed to start governor for policy: %p\n",
+                               __func__, policy);
+               else if (cpufreq_driver->resume
+                   && cpufreq_driver->resume(policy))
+                       pr_err("%s: Failed to resume driver: %p\n", __func__,
+                               policy);
+
+               /*
+                * schedule call cpufreq_update_policy() for boot CPU, i.e. last
+                * policy in list. It will verify that the current freq is in
+                * sync with what we believe it to be.
+                */
+               if (cpu == 0)
+                       schedule_work(&policy->update);
+       }
+}
 
 /**
  *     cpufreq_get_current_driver - return current driver's name
@@ -1542,6 +1559,10 @@ static int __cpufreq_governor(struct cpufreq_policy *policy,
        struct cpufreq_governor *gov = NULL;
 #endif
 
+       /* Don't start any governor operations if we are entering suspend */
+       if (cpufreq_suspended)
+               return 0;
+
        if (policy->governor->max_transition_latency &&
            policy->cpuinfo.transition_latency >
            policy->governor->max_transition_latency) {
@@ -1974,7 +1995,6 @@ static int __init cpufreq_core_init(void)
 
        cpufreq_global_kobject = kobject_create_and_add("cpufreq", &cpu_subsys.dev_root->kobj);
        BUG_ON(!cpufreq_global_kobject);
-       register_syscore_ops(&cpufreq_syscore_ops);
 
        return 0;
 }
index 037d36ae63e52d521def4ef34efeaf327479bcec..00b0e0a9fca3198abdce719dfc4f527b96e3d541 100644 (file)
@@ -306,6 +306,18 @@ struct freq_attr {
 static struct freq_attr _name =                        \
 __ATTR(_name, 0444, show_##_name, NULL)
 
+#ifdef CONFIG_CPU_FREQ
+void cpufreq_suspend(void);
+void cpufreq_resume(void);
+#else
+static inline void cpufreq_suspend(void) {}
+static inline void cpufreq_resume(void) {}
+#endif
+
+/*********************************************************************
+ *                     CPUFREQ NOTIFIER INTERFACE                    *
+ *********************************************************************/
+
 #define cpufreq_freq_attr_ro_perm(_name, _perm)        \
 static struct freq_attr _name =                        \
 __ATTR(_name, _perm, show_##_name, NULL)