Merge tag 'split-asm_system_h-for-linus-20120328' of git://git.kernel.org/pub/scm...
[linux-flexiantxendom0-3.2.10.git] / drivers / cpufreq / omap-cpufreq.c
index 0a5d95c..17fa04d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  CPU frequency scaling for OMAP
+ *  CPU frequency scaling for OMAP using OPP information
  *
  *  Copyright (C) 2005 Nokia Corporation
  *  Written by Tony Lindgren <tony@atomide.com>
 #include <linux/io.h>
 #include <linux/opp.h>
 #include <linux/cpu.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
 
-#include <asm/system.h>
 #include <asm/smp_plat.h>
 #include <asm/cpu.h>
 
 #include <plat/clock.h>
 #include <plat/omap-pm.h>
 #include <plat/common.h>
+#include <plat/omap_device.h>
 
 #include <mach/hardware.h>
 
-#define VERY_HI_RATE   900000000
+/* OPP tolerance in percentage */
+#define        OPP_TOLERANCE   4
 
 #ifdef CONFIG_SMP
 struct lpj_info {
@@ -48,24 +51,17 @@ static struct lpj_info global_lpj_ref;
 #endif
 
 static struct cpufreq_frequency_table *freq_table;
+static atomic_t freq_table_users = ATOMIC_INIT(0);
 static struct clk *mpu_clk;
+static char *mpu_clk_name;
+static struct device *mpu_dev;
+static struct regulator *mpu_reg;
 
 static int omap_verify_speed(struct cpufreq_policy *policy)
 {
-       if (freq_table)
-               return cpufreq_frequency_table_verify(policy, freq_table);
-
-       if (policy->cpu)
+       if (!freq_table)
                return -EINVAL;
-
-       cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
-                                    policy->cpuinfo.max_freq);
-
-       policy->min = clk_round_rate(mpu_clk, policy->min * 1000) / 1000;
-       policy->max = clk_round_rate(mpu_clk, policy->max * 1000) / 1000;
-       cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
-                                    policy->cpuinfo.max_freq);
-       return 0;
+       return cpufreq_frequency_table_verify(policy, freq_table);
 }
 
 static unsigned int omap_getspeed(unsigned int cpu)
@@ -83,18 +79,33 @@ static int omap_target(struct cpufreq_policy *policy,
                       unsigned int target_freq,
                       unsigned int relation)
 {
-       int i, ret = 0;
+       unsigned int i;
+       int r, ret = 0;
        struct cpufreq_freqs freqs;
+       struct opp *opp;
+       unsigned long freq, volt = 0, volt_old = 0, tol = 0;
 
-       /* Ensure desired rate is within allowed range.  Some govenors
-        * (ondemand) will just pass target_freq=0 to get the minimum. */
-       if (target_freq < policy->min)
-               target_freq = policy->min;
-       if (target_freq > policy->max)
-               target_freq = policy->max;
+       if (!freq_table) {
+               dev_err(mpu_dev, "%s: cpu%d: no freq table!\n", __func__,
+                               policy->cpu);
+               return -EINVAL;
+       }
+
+       ret = cpufreq_frequency_table_target(policy, freq_table, target_freq,
+                       relation, &i);
+       if (ret) {
+               dev_dbg(mpu_dev, "%s: cpu%d: no freq match for %d(ret=%d)\n",
+                       __func__, policy->cpu, target_freq, ret);
+               return ret;
+       }
+       freqs.new = freq_table[i].frequency;
+       if (!freqs.new) {
+               dev_err(mpu_dev, "%s: cpu%d: no match for freq %d\n", __func__,
+                       policy->cpu, target_freq);
+               return -EINVAL;
+       }
 
        freqs.old = omap_getspeed(policy->cpu);
-       freqs.new = clk_round_rate(mpu_clk, target_freq * 1000) / 1000;
        freqs.cpu = policy->cpu;
 
        if (freqs.old == freqs.new && policy->cur == freqs.new)
@@ -106,13 +117,50 @@ static int omap_target(struct cpufreq_policy *policy,
                cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
        }
 
-#ifdef CONFIG_CPU_FREQ_DEBUG
-       pr_info("cpufreq-omap: transition: %u --> %u\n", freqs.old, freqs.new);
-#endif
+       freq = freqs.new * 1000;
+
+       if (mpu_reg) {
+               opp = opp_find_freq_ceil(mpu_dev, &freq);
+               if (IS_ERR(opp)) {
+                       dev_err(mpu_dev, "%s: unable to find MPU OPP for %d\n",
+                               __func__, freqs.new);
+                       return -EINVAL;
+               }
+               volt = opp_get_voltage(opp);
+               tol = volt * OPP_TOLERANCE / 100;
+               volt_old = regulator_get_voltage(mpu_reg);
+       }
+
+       dev_dbg(mpu_dev, "cpufreq-omap: %u MHz, %ld mV --> %u MHz, %ld mV\n", 
+               freqs.old / 1000, volt_old ? volt_old / 1000 : -1,
+               freqs.new / 1000, volt ? volt / 1000 : -1);
+
+       /* scaling up?  scale voltage before frequency */
+       if (mpu_reg && (freqs.new > freqs.old)) {
+               r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol);
+               if (r < 0) {
+                       dev_warn(mpu_dev, "%s: unable to scale voltage up.\n",
+                                __func__);
+                       freqs.new = freqs.old;
+                       goto done;
+               }
+       }
 
        ret = clk_set_rate(mpu_clk, freqs.new * 1000);
-       freqs.new = omap_getspeed(policy->cpu);
 
+       /* scaling down?  scale voltage after frequency */
+       if (mpu_reg && (freqs.new < freqs.old)) {
+               r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol);
+               if (r < 0) {
+                       dev_warn(mpu_dev, "%s: unable to scale voltage down.\n",
+                                __func__);
+                       ret = clk_set_rate(mpu_clk, freqs.old * 1000);
+                       freqs.new = freqs.old;
+                       goto done;
+               }
+       }
+
+       freqs.new = omap_getspeed(policy->cpu);
 #ifdef CONFIG_SMP
        /*
         * Note that loops_per_jiffy is not updated on SMP systems in
@@ -139,6 +187,7 @@ static int omap_target(struct cpufreq_policy *policy,
                                        freqs.new);
 #endif
 
+done:
        /* notifiers */
        for_each_cpu(i, policy->cpus) {
                freqs.cpu = i;
@@ -148,44 +197,42 @@ static int omap_target(struct cpufreq_policy *policy,
        return ret;
 }
 
+static inline void freq_table_free(void)
+{
+       if (atomic_dec_and_test(&freq_table_users))
+               opp_free_cpufreq_table(mpu_dev, &freq_table);
+}
+
 static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy)
 {
        int result = 0;
-       struct device *mpu_dev;
-
-       if (cpu_is_omap24xx())
-               mpu_clk = clk_get(NULL, "virt_prcm_set");
-       else if (cpu_is_omap34xx())
-               mpu_clk = clk_get(NULL, "dpll1_ck");
-       else if (cpu_is_omap44xx())
-               mpu_clk = clk_get(NULL, "dpll_mpu_ck");
 
+       mpu_clk = clk_get(NULL, mpu_clk_name);
        if (IS_ERR(mpu_clk))
                return PTR_ERR(mpu_clk);
 
-       if (policy->cpu >= NR_CPUS)
-               return -EINVAL;
+       if (policy->cpu >= NR_CPUS) {
+               result = -EINVAL;
+               goto fail_ck;
+       }
 
        policy->cur = policy->min = policy->max = omap_getspeed(policy->cpu);
-       mpu_dev = omap2_get_mpuss_device();
 
-       if (!mpu_dev) {
-               pr_warning("%s: unable to get the mpu device\n", __func__);
-               return -EINVAL;
-       }
-       opp_init_cpufreq_table(mpu_dev, &freq_table);
+       if (atomic_inc_return(&freq_table_users) == 1)
+               result = opp_init_cpufreq_table(mpu_dev, &freq_table);
 
-       if (freq_table) {
-               result = cpufreq_frequency_table_cpuinfo(policy, freq_table);
-               if (!result)
-                       cpufreq_frequency_table_get_attr(freq_table,
-                                                       policy->cpu);
-       } else {
-               policy->cpuinfo.min_freq = clk_round_rate(mpu_clk, 0) / 1000;
-               policy->cpuinfo.max_freq = clk_round_rate(mpu_clk,
-                                                       VERY_HI_RATE) / 1000;
+       if (result) {
+               dev_err(mpu_dev, "%s: cpu%d: failed creating freq table[%d]\n",
+                               __func__, policy->cpu, result);
+               goto fail_ck;
        }
 
+       result = cpufreq_frequency_table_cpuinfo(policy, freq_table);
+       if (result)
+               goto fail_table;
+
+       cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
+
        policy->min = policy->cpuinfo.min_freq;
        policy->max = policy->cpuinfo.max_freq;
        policy->cur = omap_getspeed(policy->cpu);
@@ -206,11 +253,17 @@ static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy)
        policy->cpuinfo.transition_latency = 300 * 1000;
 
        return 0;
+
+fail_table:
+       freq_table_free();
+fail_ck:
+       clk_put(mpu_clk);
+       return result;
 }
 
 static int omap_cpu_exit(struct cpufreq_policy *policy)
 {
-       clk_exit_cpufreq_table(&freq_table);
+       freq_table_free();
        clk_put(mpu_clk);
        return 0;
 }
@@ -233,6 +286,41 @@ static struct cpufreq_driver omap_driver = {
 
 static int __init omap_cpufreq_init(void)
 {
+       if (cpu_is_omap24xx())
+               mpu_clk_name = "virt_prcm_set";
+       else if (cpu_is_omap34xx())
+               mpu_clk_name = "dpll1_ck";
+       else if (cpu_is_omap44xx())
+               mpu_clk_name = "dpll_mpu_ck";
+
+       if (!mpu_clk_name) {
+               pr_err("%s: unsupported Silicon?\n", __func__);
+               return -EINVAL;
+       }
+
+       mpu_dev = omap_device_get_by_hwmod_name("mpu");
+       if (!mpu_dev) {
+               pr_warning("%s: unable to get the mpu device\n", __func__);
+               return -EINVAL;
+       }
+
+       mpu_reg = regulator_get(mpu_dev, "vcc");
+       if (IS_ERR(mpu_reg)) {
+               pr_warning("%s: unable to get MPU regulator\n", __func__);
+               mpu_reg = NULL;
+       } else {
+               /* 
+                * Ensure physical regulator is present.
+                * (e.g. could be dummy regulator.)
+                */
+               if (regulator_get_voltage(mpu_reg) < 0) {
+                       pr_warn("%s: physical regulator not present for MPU\n",
+                               __func__);
+                       regulator_put(mpu_reg);
+                       mpu_reg = NULL;
+               }
+       }
+
        return cpufreq_register_driver(&omap_driver);
 }