1731e0cc6SSantosh Shilimkar /* 2ffe4f0f1SNishanth Menon * CPU frequency scaling for OMAP using OPP information 3731e0cc6SSantosh Shilimkar * 4731e0cc6SSantosh Shilimkar * Copyright (C) 2005 Nokia Corporation 5731e0cc6SSantosh Shilimkar * Written by Tony Lindgren <tony@atomide.com> 6731e0cc6SSantosh Shilimkar * 7731e0cc6SSantosh Shilimkar * Based on cpu-sa1110.c, Copyright (C) 2001 Russell King 8731e0cc6SSantosh Shilimkar * 9731e0cc6SSantosh Shilimkar * Copyright (C) 2007-2011 Texas Instruments, Inc. 10731e0cc6SSantosh Shilimkar * - OMAP3/4 support by Rajendra Nayak, Santosh Shilimkar 11731e0cc6SSantosh Shilimkar * 12731e0cc6SSantosh Shilimkar * This program is free software; you can redistribute it and/or modify 13731e0cc6SSantosh Shilimkar * it under the terms of the GNU General Public License version 2 as 14731e0cc6SSantosh Shilimkar * published by the Free Software Foundation. 15731e0cc6SSantosh Shilimkar */ 161c5864e2SJoe Perches 171c5864e2SJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 181c5864e2SJoe Perches 19731e0cc6SSantosh Shilimkar #include <linux/types.h> 20731e0cc6SSantosh Shilimkar #include <linux/kernel.h> 21731e0cc6SSantosh Shilimkar #include <linux/sched.h> 22731e0cc6SSantosh Shilimkar #include <linux/cpufreq.h> 23731e0cc6SSantosh Shilimkar #include <linux/delay.h> 24731e0cc6SSantosh Shilimkar #include <linux/init.h> 25731e0cc6SSantosh Shilimkar #include <linux/err.h> 26731e0cc6SSantosh Shilimkar #include <linux/clk.h> 27731e0cc6SSantosh Shilimkar #include <linux/io.h> 28e4db1c74SNishanth Menon #include <linux/pm_opp.h> 2946c12216SRussell King #include <linux/cpu.h> 30c1b547bcSKevin Hilman #include <linux/module.h> 3149ded525SNishanth Menon #include <linux/platform_device.h> 3253dfe8a8SKevin Hilman #include <linux/regulator/consumer.h> 33731e0cc6SSantosh Shilimkar 34731e0cc6SSantosh Shilimkar #include <asm/smp_plat.h> 3546c12216SRussell King #include <asm/cpu.h> 36731e0cc6SSantosh Shilimkar 3742daffd2SAfzal Mohammed /* OPP tolerance in percentage */ 3842daffd2SAfzal Mohammed #define OPP_TOLERANCE 4 3942daffd2SAfzal Mohammed 40731e0cc6SSantosh Shilimkar static struct cpufreq_frequency_table *freq_table; 411c78217fSNishanth Menon static atomic_t freq_table_users = ATOMIC_INIT(0); 42a820ffa8SNishanth Menon static struct device *mpu_dev; 4353dfe8a8SKevin Hilman static struct regulator *mpu_reg; 44731e0cc6SSantosh Shilimkar 459c0ebcf7SViresh Kumar static int omap_target(struct cpufreq_policy *policy, unsigned int index) 46731e0cc6SSantosh Shilimkar { 47696d0b2cSviresh kumar int r, ret; 4847d43ba7SNishanth Menon struct dev_pm_opp *opp; 4942daffd2SAfzal Mohammed unsigned long freq, volt = 0, volt_old = 0, tol = 0; 50d4019f0aSViresh Kumar unsigned int old_freq, new_freq; 51731e0cc6SSantosh Shilimkar 52652ed95dSViresh Kumar old_freq = policy->cur; 53d4019f0aSViresh Kumar new_freq = freq_table[index].frequency; 54731e0cc6SSantosh Shilimkar 55d4019f0aSViresh Kumar freq = new_freq * 1000; 56652ed95dSViresh Kumar ret = clk_round_rate(policy->clk, freq); 57287980e4SArnd Bergmann if (ret < 0) { 588df0a663SKevin Hilman dev_warn(mpu_dev, 598df0a663SKevin Hilman "CPUfreq: Cannot find matching frequency for %lu\n", 608df0a663SKevin Hilman freq); 618df0a663SKevin Hilman return ret; 628df0a663SKevin Hilman } 638df0a663SKevin Hilman freq = ret; 6453dfe8a8SKevin Hilman 6553dfe8a8SKevin Hilman if (mpu_reg) { 665d4879cdSNishanth Menon opp = dev_pm_opp_find_freq_ceil(mpu_dev, &freq); 6753dfe8a8SKevin Hilman if (IS_ERR(opp)) { 6853dfe8a8SKevin Hilman dev_err(mpu_dev, "%s: unable to find MPU OPP for %d\n", 69d4019f0aSViresh Kumar __func__, new_freq); 7053dfe8a8SKevin Hilman return -EINVAL; 7153dfe8a8SKevin Hilman } 725d4879cdSNishanth Menon volt = dev_pm_opp_get_voltage(opp); 738a31d9d9SViresh Kumar dev_pm_opp_put(opp); 7442daffd2SAfzal Mohammed tol = volt * OPP_TOLERANCE / 100; 7553dfe8a8SKevin Hilman volt_old = regulator_get_voltage(mpu_reg); 7653dfe8a8SKevin Hilman } 7753dfe8a8SKevin Hilman 7853dfe8a8SKevin Hilman dev_dbg(mpu_dev, "cpufreq-omap: %u MHz, %ld mV --> %u MHz, %ld mV\n", 79d4019f0aSViresh Kumar old_freq / 1000, volt_old ? volt_old / 1000 : -1, 80d4019f0aSViresh Kumar new_freq / 1000, volt ? volt / 1000 : -1); 8144a49a23SViresh Kumar 8253dfe8a8SKevin Hilman /* scaling up? scale voltage before frequency */ 83d4019f0aSViresh Kumar if (mpu_reg && (new_freq > old_freq)) { 8442daffd2SAfzal Mohammed r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol); 8553dfe8a8SKevin Hilman if (r < 0) { 8653dfe8a8SKevin Hilman dev_warn(mpu_dev, "%s: unable to scale voltage up.\n", 8753dfe8a8SKevin Hilman __func__); 88d4019f0aSViresh Kumar return r; 8953dfe8a8SKevin Hilman } 9053dfe8a8SKevin Hilman } 91731e0cc6SSantosh Shilimkar 92652ed95dSViresh Kumar ret = clk_set_rate(policy->clk, new_freq * 1000); 93731e0cc6SSantosh Shilimkar 9453dfe8a8SKevin Hilman /* scaling down? scale voltage after frequency */ 95d4019f0aSViresh Kumar if (mpu_reg && (new_freq < old_freq)) { 9642daffd2SAfzal Mohammed r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol); 9753dfe8a8SKevin Hilman if (r < 0) { 9853dfe8a8SKevin Hilman dev_warn(mpu_dev, "%s: unable to scale voltage down.\n", 9953dfe8a8SKevin Hilman __func__); 100652ed95dSViresh Kumar clk_set_rate(policy->clk, old_freq * 1000); 101d4019f0aSViresh Kumar return r; 10253dfe8a8SKevin Hilman } 10353dfe8a8SKevin Hilman } 10453dfe8a8SKevin Hilman 105731e0cc6SSantosh Shilimkar return ret; 106731e0cc6SSantosh Shilimkar } 107731e0cc6SSantosh Shilimkar 1081c78217fSNishanth Menon static inline void freq_table_free(void) 1091c78217fSNishanth Menon { 1101c78217fSNishanth Menon if (atomic_dec_and_test(&freq_table_users)) 1115d4879cdSNishanth Menon dev_pm_opp_free_cpufreq_table(mpu_dev, &freq_table); 1121c78217fSNishanth Menon } 1131c78217fSNishanth Menon 1142760984fSPaul Gortmaker static int omap_cpu_init(struct cpufreq_policy *policy) 115731e0cc6SSantosh Shilimkar { 116982bce11SViresh Kumar int result; 117731e0cc6SSantosh Shilimkar 118652ed95dSViresh Kumar policy->clk = clk_get(NULL, "cpufreq_ck"); 119652ed95dSViresh Kumar if (IS_ERR(policy->clk)) 120652ed95dSViresh Kumar return PTR_ERR(policy->clk); 121731e0cc6SSantosh Shilimkar 122982bce11SViresh Kumar if (!freq_table) { 1235d4879cdSNishanth Menon result = dev_pm_opp_init_cpufreq_table(mpu_dev, &freq_table); 124bf2a359dSNishanth Menon if (result) { 125982bce11SViresh Kumar dev_err(mpu_dev, 126982bce11SViresh Kumar "%s: cpu%d: failed creating freq table[%d]\n", 127bf2a359dSNishanth Menon __func__, policy->cpu, result); 128*c4dcc8a1SViresh Kumar clk_put(policy->clk); 129*c4dcc8a1SViresh Kumar return result; 130982bce11SViresh Kumar } 131bf2a359dSNishanth Menon } 132bf2a359dSNishanth Menon 1331b865214SRajendra Nayak atomic_inc_return(&freq_table_users); 1341b865214SRajendra Nayak 135731e0cc6SSantosh Shilimkar /* FIXME: what's the actual transition time? */ 136*c4dcc8a1SViresh Kumar cpufreq_generic_init(policy, freq_table, 300 * 1000); 137a9a744ddSYangtao Li dev_pm_opp_of_register_em(policy->cpus); 13811e04fddSNishanth Menon 139*c4dcc8a1SViresh Kumar return 0; 140731e0cc6SSantosh Shilimkar } 141731e0cc6SSantosh Shilimkar 142731e0cc6SSantosh Shilimkar static int omap_cpu_exit(struct cpufreq_policy *policy) 143731e0cc6SSantosh Shilimkar { 1441c78217fSNishanth Menon freq_table_free(); 145652ed95dSViresh Kumar clk_put(policy->clk); 146731e0cc6SSantosh Shilimkar return 0; 147731e0cc6SSantosh Shilimkar } 148731e0cc6SSantosh Shilimkar 149731e0cc6SSantosh Shilimkar static struct cpufreq_driver omap_driver = { 150ae6b4271SViresh Kumar .flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK, 151d5ca1649SViresh Kumar .verify = cpufreq_generic_frequency_table_verify, 1529c0ebcf7SViresh Kumar .target_index = omap_target, 153652ed95dSViresh Kumar .get = cpufreq_generic_get, 154731e0cc6SSantosh Shilimkar .init = omap_cpu_init, 155731e0cc6SSantosh Shilimkar .exit = omap_cpu_exit, 156731e0cc6SSantosh Shilimkar .name = "omap", 157d5ca1649SViresh Kumar .attr = cpufreq_generic_attr, 158731e0cc6SSantosh Shilimkar }; 159731e0cc6SSantosh Shilimkar 16049ded525SNishanth Menon static int omap_cpufreq_probe(struct platform_device *pdev) 161731e0cc6SSantosh Shilimkar { 162747a7f64SKevin Hilman mpu_dev = get_cpu_device(0); 163747a7f64SKevin Hilman if (!mpu_dev) { 1641c5864e2SJoe Perches pr_warn("%s: unable to get the MPU device\n", __func__); 165747a7f64SKevin Hilman return -EINVAL; 166a820ffa8SNishanth Menon } 167a820ffa8SNishanth Menon 16853dfe8a8SKevin Hilman mpu_reg = regulator_get(mpu_dev, "vcc"); 16953dfe8a8SKevin Hilman if (IS_ERR(mpu_reg)) { 170b49c22a6SJoe Perches pr_warn("%s: unable to get MPU regulator\n", __func__); 17153dfe8a8SKevin Hilman mpu_reg = NULL; 17253dfe8a8SKevin Hilman } else { 17353dfe8a8SKevin Hilman /* 17453dfe8a8SKevin Hilman * Ensure physical regulator is present. 17553dfe8a8SKevin Hilman * (e.g. could be dummy regulator.) 17653dfe8a8SKevin Hilman */ 17753dfe8a8SKevin Hilman if (regulator_get_voltage(mpu_reg) < 0) { 17853dfe8a8SKevin Hilman pr_warn("%s: physical regulator not present for MPU\n", 17953dfe8a8SKevin Hilman __func__); 18053dfe8a8SKevin Hilman regulator_put(mpu_reg); 18153dfe8a8SKevin Hilman mpu_reg = NULL; 18253dfe8a8SKevin Hilman } 18353dfe8a8SKevin Hilman } 18453dfe8a8SKevin Hilman 185731e0cc6SSantosh Shilimkar return cpufreq_register_driver(&omap_driver); 186731e0cc6SSantosh Shilimkar } 187731e0cc6SSantosh Shilimkar 18849ded525SNishanth Menon static int omap_cpufreq_remove(struct platform_device *pdev) 189731e0cc6SSantosh Shilimkar { 19049ded525SNishanth Menon return cpufreq_unregister_driver(&omap_driver); 191731e0cc6SSantosh Shilimkar } 192731e0cc6SSantosh Shilimkar 19349ded525SNishanth Menon static struct platform_driver omap_cpufreq_platdrv = { 19449ded525SNishanth Menon .driver = { 19549ded525SNishanth Menon .name = "omap-cpufreq", 19649ded525SNishanth Menon }, 19749ded525SNishanth Menon .probe = omap_cpufreq_probe, 19849ded525SNishanth Menon .remove = omap_cpufreq_remove, 19949ded525SNishanth Menon }; 20049ded525SNishanth Menon module_platform_driver(omap_cpufreq_platdrv); 20149ded525SNishanth Menon 202731e0cc6SSantosh Shilimkar MODULE_DESCRIPTION("cpufreq driver for OMAP SoCs"); 203731e0cc6SSantosh Shilimkar MODULE_LICENSE("GPL"); 204