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); 57*287980e4SArnd 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) { 66f44d188aSNishanth Menon rcu_read_lock(); 675d4879cdSNishanth Menon opp = dev_pm_opp_find_freq_ceil(mpu_dev, &freq); 6853dfe8a8SKevin Hilman if (IS_ERR(opp)) { 69f44d188aSNishanth Menon rcu_read_unlock(); 7053dfe8a8SKevin Hilman dev_err(mpu_dev, "%s: unable to find MPU OPP for %d\n", 71d4019f0aSViresh Kumar __func__, new_freq); 7253dfe8a8SKevin Hilman return -EINVAL; 7353dfe8a8SKevin Hilman } 745d4879cdSNishanth Menon volt = dev_pm_opp_get_voltage(opp); 75f44d188aSNishanth Menon rcu_read_unlock(); 7642daffd2SAfzal Mohammed tol = volt * OPP_TOLERANCE / 100; 7753dfe8a8SKevin Hilman volt_old = regulator_get_voltage(mpu_reg); 7853dfe8a8SKevin Hilman } 7953dfe8a8SKevin Hilman 8053dfe8a8SKevin Hilman dev_dbg(mpu_dev, "cpufreq-omap: %u MHz, %ld mV --> %u MHz, %ld mV\n", 81d4019f0aSViresh Kumar old_freq / 1000, volt_old ? volt_old / 1000 : -1, 82d4019f0aSViresh Kumar new_freq / 1000, volt ? volt / 1000 : -1); 8344a49a23SViresh Kumar 8453dfe8a8SKevin Hilman /* scaling up? scale voltage before frequency */ 85d4019f0aSViresh Kumar if (mpu_reg && (new_freq > old_freq)) { 8642daffd2SAfzal Mohammed r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol); 8753dfe8a8SKevin Hilman if (r < 0) { 8853dfe8a8SKevin Hilman dev_warn(mpu_dev, "%s: unable to scale voltage up.\n", 8953dfe8a8SKevin Hilman __func__); 90d4019f0aSViresh Kumar return r; 9153dfe8a8SKevin Hilman } 9253dfe8a8SKevin Hilman } 93731e0cc6SSantosh Shilimkar 94652ed95dSViresh Kumar ret = clk_set_rate(policy->clk, new_freq * 1000); 95731e0cc6SSantosh Shilimkar 9653dfe8a8SKevin Hilman /* scaling down? scale voltage after frequency */ 97d4019f0aSViresh Kumar if (mpu_reg && (new_freq < old_freq)) { 9842daffd2SAfzal Mohammed r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol); 9953dfe8a8SKevin Hilman if (r < 0) { 10053dfe8a8SKevin Hilman dev_warn(mpu_dev, "%s: unable to scale voltage down.\n", 10153dfe8a8SKevin Hilman __func__); 102652ed95dSViresh Kumar clk_set_rate(policy->clk, old_freq * 1000); 103d4019f0aSViresh Kumar return r; 10453dfe8a8SKevin Hilman } 10553dfe8a8SKevin Hilman } 10653dfe8a8SKevin Hilman 107731e0cc6SSantosh Shilimkar return ret; 108731e0cc6SSantosh Shilimkar } 109731e0cc6SSantosh Shilimkar 1101c78217fSNishanth Menon static inline void freq_table_free(void) 1111c78217fSNishanth Menon { 1121c78217fSNishanth Menon if (atomic_dec_and_test(&freq_table_users)) 1135d4879cdSNishanth Menon dev_pm_opp_free_cpufreq_table(mpu_dev, &freq_table); 1141c78217fSNishanth Menon } 1151c78217fSNishanth Menon 1162760984fSPaul Gortmaker static int omap_cpu_init(struct cpufreq_policy *policy) 117731e0cc6SSantosh Shilimkar { 118982bce11SViresh Kumar int result; 119731e0cc6SSantosh Shilimkar 120652ed95dSViresh Kumar policy->clk = clk_get(NULL, "cpufreq_ck"); 121652ed95dSViresh Kumar if (IS_ERR(policy->clk)) 122652ed95dSViresh Kumar return PTR_ERR(policy->clk); 123731e0cc6SSantosh Shilimkar 124982bce11SViresh Kumar if (!freq_table) { 1255d4879cdSNishanth Menon result = dev_pm_opp_init_cpufreq_table(mpu_dev, &freq_table); 126bf2a359dSNishanth Menon if (result) { 127982bce11SViresh Kumar dev_err(mpu_dev, 128982bce11SViresh Kumar "%s: cpu%d: failed creating freq table[%d]\n", 129bf2a359dSNishanth Menon __func__, policy->cpu, result); 130982bce11SViresh Kumar goto fail; 131982bce11SViresh Kumar } 132bf2a359dSNishanth Menon } 133bf2a359dSNishanth Menon 1341b865214SRajendra Nayak atomic_inc_return(&freq_table_users); 1351b865214SRajendra Nayak 136731e0cc6SSantosh Shilimkar /* FIXME: what's the actual transition time? */ 137982bce11SViresh Kumar result = cpufreq_generic_init(policy, freq_table, 300 * 1000); 138982bce11SViresh Kumar if (!result) 139731e0cc6SSantosh Shilimkar return 0; 14011e04fddSNishanth Menon 1411c78217fSNishanth Menon freq_table_free(); 142982bce11SViresh Kumar fail: 143652ed95dSViresh Kumar clk_put(policy->clk); 14411e04fddSNishanth Menon return result; 145731e0cc6SSantosh Shilimkar } 146731e0cc6SSantosh Shilimkar 147731e0cc6SSantosh Shilimkar static int omap_cpu_exit(struct cpufreq_policy *policy) 148731e0cc6SSantosh Shilimkar { 1491c78217fSNishanth Menon freq_table_free(); 150652ed95dSViresh Kumar clk_put(policy->clk); 151731e0cc6SSantosh Shilimkar return 0; 152731e0cc6SSantosh Shilimkar } 153731e0cc6SSantosh Shilimkar 154731e0cc6SSantosh Shilimkar static struct cpufreq_driver omap_driver = { 155ae6b4271SViresh Kumar .flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK, 156d5ca1649SViresh Kumar .verify = cpufreq_generic_frequency_table_verify, 1579c0ebcf7SViresh Kumar .target_index = omap_target, 158652ed95dSViresh Kumar .get = cpufreq_generic_get, 159731e0cc6SSantosh Shilimkar .init = omap_cpu_init, 160731e0cc6SSantosh Shilimkar .exit = omap_cpu_exit, 161731e0cc6SSantosh Shilimkar .name = "omap", 162d5ca1649SViresh Kumar .attr = cpufreq_generic_attr, 163731e0cc6SSantosh Shilimkar }; 164731e0cc6SSantosh Shilimkar 16549ded525SNishanth Menon static int omap_cpufreq_probe(struct platform_device *pdev) 166731e0cc6SSantosh Shilimkar { 167747a7f64SKevin Hilman mpu_dev = get_cpu_device(0); 168747a7f64SKevin Hilman if (!mpu_dev) { 1691c5864e2SJoe Perches pr_warn("%s: unable to get the MPU device\n", __func__); 170747a7f64SKevin Hilman return -EINVAL; 171a820ffa8SNishanth Menon } 172a820ffa8SNishanth Menon 17353dfe8a8SKevin Hilman mpu_reg = regulator_get(mpu_dev, "vcc"); 17453dfe8a8SKevin Hilman if (IS_ERR(mpu_reg)) { 175b49c22a6SJoe Perches pr_warn("%s: unable to get MPU regulator\n", __func__); 17653dfe8a8SKevin Hilman mpu_reg = NULL; 17753dfe8a8SKevin Hilman } else { 17853dfe8a8SKevin Hilman /* 17953dfe8a8SKevin Hilman * Ensure physical regulator is present. 18053dfe8a8SKevin Hilman * (e.g. could be dummy regulator.) 18153dfe8a8SKevin Hilman */ 18253dfe8a8SKevin Hilman if (regulator_get_voltage(mpu_reg) < 0) { 18353dfe8a8SKevin Hilman pr_warn("%s: physical regulator not present for MPU\n", 18453dfe8a8SKevin Hilman __func__); 18553dfe8a8SKevin Hilman regulator_put(mpu_reg); 18653dfe8a8SKevin Hilman mpu_reg = NULL; 18753dfe8a8SKevin Hilman } 18853dfe8a8SKevin Hilman } 18953dfe8a8SKevin Hilman 190731e0cc6SSantosh Shilimkar return cpufreq_register_driver(&omap_driver); 191731e0cc6SSantosh Shilimkar } 192731e0cc6SSantosh Shilimkar 19349ded525SNishanth Menon static int omap_cpufreq_remove(struct platform_device *pdev) 194731e0cc6SSantosh Shilimkar { 19549ded525SNishanth Menon return cpufreq_unregister_driver(&omap_driver); 196731e0cc6SSantosh Shilimkar } 197731e0cc6SSantosh Shilimkar 19849ded525SNishanth Menon static struct platform_driver omap_cpufreq_platdrv = { 19949ded525SNishanth Menon .driver = { 20049ded525SNishanth Menon .name = "omap-cpufreq", 20149ded525SNishanth Menon }, 20249ded525SNishanth Menon .probe = omap_cpufreq_probe, 20349ded525SNishanth Menon .remove = omap_cpufreq_remove, 20449ded525SNishanth Menon }; 20549ded525SNishanth Menon module_platform_driver(omap_cpufreq_platdrv); 20649ded525SNishanth Menon 207731e0cc6SSantosh Shilimkar MODULE_DESCRIPTION("cpufreq driver for OMAP SoCs"); 208731e0cc6SSantosh Shilimkar MODULE_LICENSE("GPL"); 209