Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v3.13 284 lines 7.0 kB view raw
1/* 2 * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. 3 * http://www.samsung.com 4 * 5 * EXYNOS - CPU frequency scaling support for EXYNOS series 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10*/ 11 12#include <linux/kernel.h> 13#include <linux/err.h> 14#include <linux/clk.h> 15#include <linux/io.h> 16#include <linux/slab.h> 17#include <linux/regulator/consumer.h> 18#include <linux/cpufreq.h> 19#include <linux/suspend.h> 20 21#include <plat/cpu.h> 22 23#include "exynos-cpufreq.h" 24 25static struct exynos_dvfs_info *exynos_info; 26 27static struct regulator *arm_regulator; 28 29static unsigned int locking_frequency; 30static bool frequency_locked; 31static DEFINE_MUTEX(cpufreq_lock); 32 33static unsigned int exynos_getspeed(unsigned int cpu) 34{ 35 return clk_get_rate(exynos_info->cpu_clk) / 1000; 36} 37 38static int exynos_cpufreq_get_index(unsigned int freq) 39{ 40 struct cpufreq_frequency_table *freq_table = exynos_info->freq_table; 41 int index; 42 43 for (index = 0; 44 freq_table[index].frequency != CPUFREQ_TABLE_END; index++) 45 if (freq_table[index].frequency == freq) 46 break; 47 48 if (freq_table[index].frequency == CPUFREQ_TABLE_END) 49 return -EINVAL; 50 51 return index; 52} 53 54static int exynos_cpufreq_scale(unsigned int target_freq) 55{ 56 struct cpufreq_frequency_table *freq_table = exynos_info->freq_table; 57 unsigned int *volt_table = exynos_info->volt_table; 58 struct cpufreq_policy *policy = cpufreq_cpu_get(0); 59 unsigned int arm_volt, safe_arm_volt = 0; 60 unsigned int mpll_freq_khz = exynos_info->mpll_freq_khz; 61 unsigned int old_freq; 62 int index, old_index; 63 int ret = 0; 64 65 old_freq = policy->cur; 66 67 /* 68 * The policy max have been changed so that we cannot get proper 69 * old_index with cpufreq_frequency_table_target(). Thus, ignore 70 * policy and get the index from the raw frequency table. 71 */ 72 old_index = exynos_cpufreq_get_index(old_freq); 73 if (old_index < 0) { 74 ret = old_index; 75 goto out; 76 } 77 78 index = exynos_cpufreq_get_index(target_freq); 79 if (index < 0) { 80 ret = index; 81 goto out; 82 } 83 84 /* 85 * ARM clock source will be changed APLL to MPLL temporary 86 * To support this level, need to control regulator for 87 * required voltage level 88 */ 89 if (exynos_info->need_apll_change != NULL) { 90 if (exynos_info->need_apll_change(old_index, index) && 91 (freq_table[index].frequency < mpll_freq_khz) && 92 (freq_table[old_index].frequency < mpll_freq_khz)) 93 safe_arm_volt = volt_table[exynos_info->pll_safe_idx]; 94 } 95 arm_volt = volt_table[index]; 96 97 /* When the new frequency is higher than current frequency */ 98 if ((target_freq > old_freq) && !safe_arm_volt) { 99 /* Firstly, voltage up to increase frequency */ 100 ret = regulator_set_voltage(arm_regulator, arm_volt, arm_volt); 101 if (ret) { 102 pr_err("%s: failed to set cpu voltage to %d\n", 103 __func__, arm_volt); 104 return ret; 105 } 106 } 107 108 if (safe_arm_volt) { 109 ret = regulator_set_voltage(arm_regulator, safe_arm_volt, 110 safe_arm_volt); 111 if (ret) { 112 pr_err("%s: failed to set cpu voltage to %d\n", 113 __func__, safe_arm_volt); 114 return ret; 115 } 116 } 117 118 exynos_info->set_freq(old_index, index); 119 120 /* When the new frequency is lower than current frequency */ 121 if ((target_freq < old_freq) || 122 ((target_freq > old_freq) && safe_arm_volt)) { 123 /* down the voltage after frequency change */ 124 ret = regulator_set_voltage(arm_regulator, arm_volt, 125 arm_volt); 126 if (ret) { 127 pr_err("%s: failed to set cpu voltage to %d\n", 128 __func__, arm_volt); 129 goto out; 130 } 131 } 132 133out: 134 cpufreq_cpu_put(policy); 135 136 return ret; 137} 138 139static int exynos_target(struct cpufreq_policy *policy, unsigned int index) 140{ 141 struct cpufreq_frequency_table *freq_table = exynos_info->freq_table; 142 int ret = 0; 143 144 mutex_lock(&cpufreq_lock); 145 146 if (frequency_locked) 147 goto out; 148 149 ret = exynos_cpufreq_scale(freq_table[index].frequency); 150 151out: 152 mutex_unlock(&cpufreq_lock); 153 154 return ret; 155} 156 157#ifdef CONFIG_PM 158static int exynos_cpufreq_suspend(struct cpufreq_policy *policy) 159{ 160 return 0; 161} 162 163static int exynos_cpufreq_resume(struct cpufreq_policy *policy) 164{ 165 return 0; 166} 167#endif 168 169/** 170 * exynos_cpufreq_pm_notifier - block CPUFREQ's activities in suspend-resume 171 * context 172 * @notifier 173 * @pm_event 174 * @v 175 * 176 * While frequency_locked == true, target() ignores every frequency but 177 * locking_frequency. The locking_frequency value is the initial frequency, 178 * which is set by the bootloader. In order to eliminate possible 179 * inconsistency in clock values, we save and restore frequencies during 180 * suspend and resume and block CPUFREQ activities. Note that the standard 181 * suspend/resume cannot be used as they are too deep (syscore_ops) for 182 * regulator actions. 183 */ 184static int exynos_cpufreq_pm_notifier(struct notifier_block *notifier, 185 unsigned long pm_event, void *v) 186{ 187 int ret; 188 189 switch (pm_event) { 190 case PM_SUSPEND_PREPARE: 191 mutex_lock(&cpufreq_lock); 192 frequency_locked = true; 193 mutex_unlock(&cpufreq_lock); 194 195 ret = exynos_cpufreq_scale(locking_frequency); 196 if (ret < 0) 197 return NOTIFY_BAD; 198 199 break; 200 201 case PM_POST_SUSPEND: 202 mutex_lock(&cpufreq_lock); 203 frequency_locked = false; 204 mutex_unlock(&cpufreq_lock); 205 break; 206 } 207 208 return NOTIFY_OK; 209} 210 211static struct notifier_block exynos_cpufreq_nb = { 212 .notifier_call = exynos_cpufreq_pm_notifier, 213}; 214 215static int exynos_cpufreq_cpu_init(struct cpufreq_policy *policy) 216{ 217 return cpufreq_generic_init(policy, exynos_info->freq_table, 100000); 218} 219 220static struct cpufreq_driver exynos_driver = { 221 .flags = CPUFREQ_STICKY, 222 .verify = cpufreq_generic_frequency_table_verify, 223 .target_index = exynos_target, 224 .get = exynos_getspeed, 225 .init = exynos_cpufreq_cpu_init, 226 .exit = cpufreq_generic_exit, 227 .name = "exynos_cpufreq", 228 .attr = cpufreq_generic_attr, 229#ifdef CONFIG_PM 230 .suspend = exynos_cpufreq_suspend, 231 .resume = exynos_cpufreq_resume, 232#endif 233}; 234 235static int __init exynos_cpufreq_init(void) 236{ 237 int ret = -EINVAL; 238 239 exynos_info = kzalloc(sizeof(*exynos_info), GFP_KERNEL); 240 if (!exynos_info) 241 return -ENOMEM; 242 243 if (soc_is_exynos4210()) 244 ret = exynos4210_cpufreq_init(exynos_info); 245 else if (soc_is_exynos4212() || soc_is_exynos4412()) 246 ret = exynos4x12_cpufreq_init(exynos_info); 247 else if (soc_is_exynos5250()) 248 ret = exynos5250_cpufreq_init(exynos_info); 249 else 250 return 0; 251 252 if (ret) 253 goto err_vdd_arm; 254 255 if (exynos_info->set_freq == NULL) { 256 pr_err("%s: No set_freq function (ERR)\n", __func__); 257 goto err_vdd_arm; 258 } 259 260 arm_regulator = regulator_get(NULL, "vdd_arm"); 261 if (IS_ERR(arm_regulator)) { 262 pr_err("%s: failed to get resource vdd_arm\n", __func__); 263 goto err_vdd_arm; 264 } 265 266 locking_frequency = exynos_getspeed(0); 267 268 register_pm_notifier(&exynos_cpufreq_nb); 269 270 if (cpufreq_register_driver(&exynos_driver)) { 271 pr_err("%s: failed to register cpufreq driver\n", __func__); 272 goto err_cpufreq; 273 } 274 275 return 0; 276err_cpufreq: 277 unregister_pm_notifier(&exynos_cpufreq_nb); 278 279 regulator_put(arm_regulator); 280err_vdd_arm: 281 kfree(exynos_info); 282 return -EINVAL; 283} 284late_initcall(exynos_cpufreq_init);