PM / devfreq: rockchip_dmc: Avoid glitches due to slow CPU
authorFinley Xiao <finley.xiao@rock-chips.com>
Mon, 15 May 2017 02:12:34 +0000 (10:12 +0800)
committerHuang, Tao <huangtao@rock-chips.com>
Wed, 17 May 2017 03:45:45 +0000 (11:45 +0800)
We weren't giving enough time for DMC to change frequencies
when the CPU was running slow.

Change-Id: I84e1a4ad7b5ccddafb0016f3d5d6eef147a58591
Signed-off-by: Finley Xiao <finley.xiao@rock-chips.com>
drivers/devfreq/rockchip_dmc.c

index 81a2f2c89897cce41a61ce54fb1677b197bb949c..912092a1a8bc85e01af061ac7b4f4e696b0e7836 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/arm-smccc.h>
 #include <linux/clk.h>
 #include <linux/cpu.h>
+#include <linux/cpufreq.h>
 #include <linux/delay.h>
 #include <linux/devfreq.h>
 #include <linux/devfreq-event.h>
@@ -164,6 +165,7 @@ struct rockchip_dmcfreq {
        struct regulator *vdd_center;
        unsigned long rate, target_rate;
        unsigned long volt, target_volt;
+       unsigned int min_cpu_freq;
 };
 
 static int rockchip_dmcfreq_target(struct device *dev, unsigned long *freq,
@@ -171,8 +173,10 @@ static int rockchip_dmcfreq_target(struct device *dev, unsigned long *freq,
 {
        struct rockchip_dmcfreq *dmcfreq = dev_get_drvdata(dev);
        struct dev_pm_opp *opp;
+       struct cpufreq_policy *policy;
        unsigned long old_clk_rate = dmcfreq->rate;
        unsigned long temp_rate, target_volt, target_rate;
+       unsigned int cpu_cur, cpufreq_cur;
        int err;
 
        rcu_read_lock();
@@ -204,6 +208,40 @@ static int rockchip_dmcfreq_target(struct device *dev, unsigned long *freq,
 
        mutex_lock(&dmcfreq->lock);
 
+       /*
+        * We need to prevent cpu hotplug from happening while a dmc freq rate
+        * change is happening.
+        *
+        * Do this before taking the policy rwsem to avoid deadlocks between the
+        * mutex that is locked/unlocked in cpu_hotplug_disable/enable. And it
+        * can also avoid deadlocks between the mutex that is locked/unlocked
+        * in get/put_online_cpus (such as store_scaling_max_freq()).
+        */
+       get_online_cpus();
+
+       /*
+        * Go to specified cpufreq and block other cpufreq changes since
+        * set_rate needs to complete during vblank.
+        */
+       cpu_cur = smp_processor_id();
+       policy = cpufreq_cpu_get(cpu_cur);
+       if (!policy) {
+               dev_err(dev, "cpu%d policy NULL\n", cpu_cur);
+               goto cpufreq;
+       }
+       down_write(&policy->rwsem);
+       cpufreq_cur = cpufreq_quick_get(cpu_cur);
+
+       /* If we're thermally throttled; don't change; */
+       if (dmcfreq->min_cpu_freq && cpufreq_cur < dmcfreq->min_cpu_freq) {
+               if (policy->max >= dmcfreq->min_cpu_freq)
+                       __cpufreq_driver_target(policy, dmcfreq->min_cpu_freq,
+                                               CPUFREQ_RELATION_L);
+               else
+                       dev_dbg(dev, "CPU may too slow for DMC (%d MHz)\n",
+                               policy->max);
+       }
+
        /*
         * If frequency scaling from low to high, adjust voltage first.
         * If frequency scaling from high to low, adjust frequency first.
@@ -218,16 +256,7 @@ static int rockchip_dmcfreq_target(struct device *dev, unsigned long *freq,
                }
        }
 
-       /*
-        * We need to prevent cpu hotplug from happening while a dmc freq rate
-        * change is happening.
-        */
-       get_online_cpus();
-
        err = clk_set_rate(dmcfreq->dmc_clk, target_rate);
-
-       put_online_cpus();
-
        if (err) {
                dev_err(dev, "Cannot set frequency %lu (%d)\n",
                        target_rate, err);
@@ -262,6 +291,11 @@ static int rockchip_dmcfreq_target(struct device *dev, unsigned long *freq,
 
        dmcfreq->volt = target_volt;
 out:
+       __cpufreq_driver_target(policy, cpufreq_cur, CPUFREQ_RELATION_L);
+       up_write(&policy->rwsem);
+       cpufreq_cpu_put(policy);
+cpufreq:
+       put_online_cpus();
        mutex_unlock(&dmcfreq->lock);
        return err;
 }
@@ -890,6 +924,7 @@ static int rockchip_dmcfreq_probe(struct platform_device *pdev)
                             &data->ondemand_data.upthreshold);
        of_property_read_u32(np, "downdifferential",
                             &data->ondemand_data.downdifferential);
+       of_property_read_u32(np, "min-cpu-freq", &data->min_cpu_freq);
 
        data->rate = clk_get_rate(data->dmc_clk);
        data->volt = regulator_get_voltage(data->vdd_center);