1 /* 2 * CPU frequency scaling for OMAP using OPP information 3 * 4 * Copyright (C) 2005 Nokia Corporation 5 * Written by Tony Lindgren <tony@atomide.com> 6 * 7 * Based on cpu-sa1110.c, Copyright (C) 2001 Russell King 8 * 9 * Copyright (C) 2007-2011 Texas Instruments, Inc. 10 * - OMAP3/4 support by Rajendra Nayak, Santosh Shilimkar 11 * 12 * This program is free software; you can redistribute it and/or modify 13 * it under the terms of the GNU General Public License version 2 as 14 * published by the Free Software Foundation. 15 */ 16 #include <linux/types.h> 17 #include <linux/kernel.h> 18 #include <linux/sched.h> 19 #include <linux/cpufreq.h> 20 #include <linux/delay.h> 21 #include <linux/init.h> 22 #include <linux/err.h> 23 #include <linux/clk.h> 24 #include <linux/io.h> 25 #include <linux/pm_opp.h> 26 #include <linux/cpu.h> 27 #include <linux/module.h> 28 #include <linux/platform_device.h> 29 #include <linux/regulator/consumer.h> 30 31 #include <asm/smp_plat.h> 32 #include <asm/cpu.h> 33 34 /* OPP tolerance in percentage */ 35 #define OPP_TOLERANCE 4 36 37 static struct cpufreq_frequency_table *freq_table; 38 static atomic_t freq_table_users = ATOMIC_INIT(0); 39 static struct clk *mpu_clk; 40 static struct device *mpu_dev; 41 static struct regulator *mpu_reg; 42 43 static unsigned int omap_getspeed(unsigned int cpu) 44 { 45 unsigned long rate; 46 47 if (cpu >= NR_CPUS) 48 return 0; 49 50 rate = clk_get_rate(mpu_clk) / 1000; 51 return rate; 52 } 53 54 static int omap_target(struct cpufreq_policy *policy, unsigned int index) 55 { 56 int r, ret; 57 struct dev_pm_opp *opp; 58 unsigned long freq, volt = 0, volt_old = 0, tol = 0; 59 unsigned int old_freq, new_freq; 60 61 old_freq = omap_getspeed(policy->cpu); 62 new_freq = freq_table[index].frequency; 63 64 freq = new_freq * 1000; 65 ret = clk_round_rate(mpu_clk, freq); 66 if (IS_ERR_VALUE(ret)) { 67 dev_warn(mpu_dev, 68 "CPUfreq: Cannot find matching frequency for %lu\n", 69 freq); 70 return ret; 71 } 72 freq = ret; 73 74 if (mpu_reg) { 75 rcu_read_lock(); 76 opp = dev_pm_opp_find_freq_ceil(mpu_dev, &freq); 77 if (IS_ERR(opp)) { 78 rcu_read_unlock(); 79 dev_err(mpu_dev, "%s: unable to find MPU OPP for %d\n", 80 __func__, new_freq); 81 return -EINVAL; 82 } 83 volt = dev_pm_opp_get_voltage(opp); 84 rcu_read_unlock(); 85 tol = volt * OPP_TOLERANCE / 100; 86 volt_old = regulator_get_voltage(mpu_reg); 87 } 88 89 dev_dbg(mpu_dev, "cpufreq-omap: %u MHz, %ld mV --> %u MHz, %ld mV\n", 90 old_freq / 1000, volt_old ? volt_old / 1000 : -1, 91 new_freq / 1000, volt ? volt / 1000 : -1); 92 93 /* scaling up? scale voltage before frequency */ 94 if (mpu_reg && (new_freq > old_freq)) { 95 r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol); 96 if (r < 0) { 97 dev_warn(mpu_dev, "%s: unable to scale voltage up.\n", 98 __func__); 99 return r; 100 } 101 } 102 103 ret = clk_set_rate(mpu_clk, new_freq * 1000); 104 105 /* scaling down? scale voltage after frequency */ 106 if (mpu_reg && (new_freq < old_freq)) { 107 r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol); 108 if (r < 0) { 109 dev_warn(mpu_dev, "%s: unable to scale voltage down.\n", 110 __func__); 111 clk_set_rate(mpu_clk, old_freq * 1000); 112 return r; 113 } 114 } 115 116 return ret; 117 } 118 119 static inline void freq_table_free(void) 120 { 121 if (atomic_dec_and_test(&freq_table_users)) 122 dev_pm_opp_free_cpufreq_table(mpu_dev, &freq_table); 123 } 124 125 static int omap_cpu_init(struct cpufreq_policy *policy) 126 { 127 int result; 128 129 mpu_clk = clk_get(NULL, "cpufreq_ck"); 130 if (IS_ERR(mpu_clk)) 131 return PTR_ERR(mpu_clk); 132 133 if (!freq_table) { 134 result = dev_pm_opp_init_cpufreq_table(mpu_dev, &freq_table); 135 if (result) { 136 dev_err(mpu_dev, 137 "%s: cpu%d: failed creating freq table[%d]\n", 138 __func__, policy->cpu, result); 139 goto fail; 140 } 141 } 142 143 atomic_inc_return(&freq_table_users); 144 145 /* FIXME: what's the actual transition time? */ 146 result = cpufreq_generic_init(policy, freq_table, 300 * 1000); 147 if (!result) 148 return 0; 149 150 freq_table_free(); 151 fail: 152 clk_put(mpu_clk); 153 return result; 154 } 155 156 static int omap_cpu_exit(struct cpufreq_policy *policy) 157 { 158 cpufreq_frequency_table_put_attr(policy->cpu); 159 freq_table_free(); 160 clk_put(mpu_clk); 161 return 0; 162 } 163 164 static struct cpufreq_driver omap_driver = { 165 .flags = CPUFREQ_STICKY, 166 .verify = cpufreq_generic_frequency_table_verify, 167 .target_index = omap_target, 168 .get = omap_getspeed, 169 .init = omap_cpu_init, 170 .exit = omap_cpu_exit, 171 .name = "omap", 172 .attr = cpufreq_generic_attr, 173 }; 174 175 static int omap_cpufreq_probe(struct platform_device *pdev) 176 { 177 mpu_dev = get_cpu_device(0); 178 if (!mpu_dev) { 179 pr_warning("%s: unable to get the mpu device\n", __func__); 180 return -EINVAL; 181 } 182 183 mpu_reg = regulator_get(mpu_dev, "vcc"); 184 if (IS_ERR(mpu_reg)) { 185 pr_warning("%s: unable to get MPU regulator\n", __func__); 186 mpu_reg = NULL; 187 } else { 188 /* 189 * Ensure physical regulator is present. 190 * (e.g. could be dummy regulator.) 191 */ 192 if (regulator_get_voltage(mpu_reg) < 0) { 193 pr_warn("%s: physical regulator not present for MPU\n", 194 __func__); 195 regulator_put(mpu_reg); 196 mpu_reg = NULL; 197 } 198 } 199 200 return cpufreq_register_driver(&omap_driver); 201 } 202 203 static int omap_cpufreq_remove(struct platform_device *pdev) 204 { 205 return cpufreq_unregister_driver(&omap_driver); 206 } 207 208 static struct platform_driver omap_cpufreq_platdrv = { 209 .driver = { 210 .name = "omap-cpufreq", 211 .owner = THIS_MODULE, 212 }, 213 .probe = omap_cpufreq_probe, 214 .remove = omap_cpufreq_remove, 215 }; 216 module_platform_driver(omap_cpufreq_platdrv); 217 218 MODULE_DESCRIPTION("cpufreq driver for OMAP SoCs"); 219 MODULE_LICENSE("GPL"); 220