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 v4.11 453 lines 12 kB view raw
1/* 2 * Copyright (c) 2013 Samsung Electronics Co., Ltd. 3 * http://www.samsung.com 4 * 5 * Amit Daniel Kachhap <amit.daniel@samsung.com> 6 * 7 * EXYNOS5440 - CPU frequency scaling support 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License version 2 as 11 * published by the Free Software Foundation. 12*/ 13 14#include <linux/clk.h> 15#include <linux/cpu.h> 16#include <linux/cpufreq.h> 17#include <linux/err.h> 18#include <linux/interrupt.h> 19#include <linux/io.h> 20#include <linux/module.h> 21#include <linux/of_address.h> 22#include <linux/of_irq.h> 23#include <linux/pm_opp.h> 24#include <linux/platform_device.h> 25#include <linux/slab.h> 26 27/* Register definitions */ 28#define XMU_DVFS_CTRL 0x0060 29#define XMU_PMU_P0_7 0x0064 30#define XMU_C0_3_PSTATE 0x0090 31#define XMU_P_LIMIT 0x00a0 32#define XMU_P_STATUS 0x00a4 33#define XMU_PMUEVTEN 0x00d0 34#define XMU_PMUIRQEN 0x00d4 35#define XMU_PMUIRQ 0x00d8 36 37/* PMU mask and shift definations */ 38#define P_VALUE_MASK 0x7 39 40#define XMU_DVFS_CTRL_EN_SHIFT 0 41 42#define P0_7_CPUCLKDEV_SHIFT 21 43#define P0_7_CPUCLKDEV_MASK 0x7 44#define P0_7_ATBCLKDEV_SHIFT 18 45#define P0_7_ATBCLKDEV_MASK 0x7 46#define P0_7_CSCLKDEV_SHIFT 15 47#define P0_7_CSCLKDEV_MASK 0x7 48#define P0_7_CPUEMA_SHIFT 28 49#define P0_7_CPUEMA_MASK 0xf 50#define P0_7_L2EMA_SHIFT 24 51#define P0_7_L2EMA_MASK 0xf 52#define P0_7_VDD_SHIFT 8 53#define P0_7_VDD_MASK 0x7f 54#define P0_7_FREQ_SHIFT 0 55#define P0_7_FREQ_MASK 0xff 56 57#define C0_3_PSTATE_VALID_SHIFT 8 58#define C0_3_PSTATE_CURR_SHIFT 4 59#define C0_3_PSTATE_NEW_SHIFT 0 60 61#define PSTATE_CHANGED_EVTEN_SHIFT 0 62 63#define PSTATE_CHANGED_IRQEN_SHIFT 0 64 65#define PSTATE_CHANGED_SHIFT 0 66 67/* some constant values for clock divider calculation */ 68#define CPU_DIV_FREQ_MAX 500 69#define CPU_DBG_FREQ_MAX 375 70#define CPU_ATB_FREQ_MAX 500 71 72#define PMIC_LOW_VOLT 0x30 73#define PMIC_HIGH_VOLT 0x28 74 75#define CPUEMA_HIGH 0x2 76#define CPUEMA_MID 0x4 77#define CPUEMA_LOW 0x7 78 79#define L2EMA_HIGH 0x1 80#define L2EMA_MID 0x3 81#define L2EMA_LOW 0x4 82 83#define DIV_TAB_MAX 2 84/* frequency unit is 20MHZ */ 85#define FREQ_UNIT 20 86#define MAX_VOLTAGE 1550000 /* In microvolt */ 87#define VOLTAGE_STEP 12500 /* In microvolt */ 88 89#define CPUFREQ_NAME "exynos5440_dvfs" 90#define DEF_TRANS_LATENCY 100000 91 92enum cpufreq_level_index { 93 L0, L1, L2, L3, L4, 94 L5, L6, L7, L8, L9, 95}; 96#define CPUFREQ_LEVEL_END (L7 + 1) 97 98struct exynos_dvfs_data { 99 void __iomem *base; 100 struct resource *mem; 101 int irq; 102 struct clk *cpu_clk; 103 unsigned int latency; 104 struct cpufreq_frequency_table *freq_table; 105 unsigned int freq_count; 106 struct device *dev; 107 bool dvfs_enabled; 108 struct work_struct irq_work; 109}; 110 111static struct exynos_dvfs_data *dvfs_info; 112static DEFINE_MUTEX(cpufreq_lock); 113static struct cpufreq_freqs freqs; 114 115static int init_div_table(void) 116{ 117 struct cpufreq_frequency_table *pos, *freq_tbl = dvfs_info->freq_table; 118 unsigned int tmp, clk_div, ema_div, freq, volt_id; 119 struct dev_pm_opp *opp; 120 121 cpufreq_for_each_entry(pos, freq_tbl) { 122 opp = dev_pm_opp_find_freq_exact(dvfs_info->dev, 123 pos->frequency * 1000, true); 124 if (IS_ERR(opp)) { 125 dev_err(dvfs_info->dev, 126 "failed to find valid OPP for %u KHZ\n", 127 pos->frequency); 128 return PTR_ERR(opp); 129 } 130 131 freq = pos->frequency / 1000; /* In MHZ */ 132 clk_div = ((freq / CPU_DIV_FREQ_MAX) & P0_7_CPUCLKDEV_MASK) 133 << P0_7_CPUCLKDEV_SHIFT; 134 clk_div |= ((freq / CPU_ATB_FREQ_MAX) & P0_7_ATBCLKDEV_MASK) 135 << P0_7_ATBCLKDEV_SHIFT; 136 clk_div |= ((freq / CPU_DBG_FREQ_MAX) & P0_7_CSCLKDEV_MASK) 137 << P0_7_CSCLKDEV_SHIFT; 138 139 /* Calculate EMA */ 140 volt_id = dev_pm_opp_get_voltage(opp); 141 142 volt_id = (MAX_VOLTAGE - volt_id) / VOLTAGE_STEP; 143 if (volt_id < PMIC_HIGH_VOLT) { 144 ema_div = (CPUEMA_HIGH << P0_7_CPUEMA_SHIFT) | 145 (L2EMA_HIGH << P0_7_L2EMA_SHIFT); 146 } else if (volt_id > PMIC_LOW_VOLT) { 147 ema_div = (CPUEMA_LOW << P0_7_CPUEMA_SHIFT) | 148 (L2EMA_LOW << P0_7_L2EMA_SHIFT); 149 } else { 150 ema_div = (CPUEMA_MID << P0_7_CPUEMA_SHIFT) | 151 (L2EMA_MID << P0_7_L2EMA_SHIFT); 152 } 153 154 tmp = (clk_div | ema_div | (volt_id << P0_7_VDD_SHIFT) 155 | ((freq / FREQ_UNIT) << P0_7_FREQ_SHIFT)); 156 157 __raw_writel(tmp, dvfs_info->base + XMU_PMU_P0_7 + 4 * 158 (pos - freq_tbl)); 159 dev_pm_opp_put(opp); 160 } 161 162 return 0; 163} 164 165static void exynos_enable_dvfs(unsigned int cur_frequency) 166{ 167 unsigned int tmp, cpu; 168 struct cpufreq_frequency_table *freq_table = dvfs_info->freq_table; 169 struct cpufreq_frequency_table *pos; 170 /* Disable DVFS */ 171 __raw_writel(0, dvfs_info->base + XMU_DVFS_CTRL); 172 173 /* Enable PSTATE Change Event */ 174 tmp = __raw_readl(dvfs_info->base + XMU_PMUEVTEN); 175 tmp |= (1 << PSTATE_CHANGED_EVTEN_SHIFT); 176 __raw_writel(tmp, dvfs_info->base + XMU_PMUEVTEN); 177 178 /* Enable PSTATE Change IRQ */ 179 tmp = __raw_readl(dvfs_info->base + XMU_PMUIRQEN); 180 tmp |= (1 << PSTATE_CHANGED_IRQEN_SHIFT); 181 __raw_writel(tmp, dvfs_info->base + XMU_PMUIRQEN); 182 183 /* Set initial performance index */ 184 cpufreq_for_each_entry(pos, freq_table) 185 if (pos->frequency == cur_frequency) 186 break; 187 188 if (pos->frequency == CPUFREQ_TABLE_END) { 189 dev_crit(dvfs_info->dev, "Boot up frequency not supported\n"); 190 /* Assign the highest frequency */ 191 pos = freq_table; 192 cur_frequency = pos->frequency; 193 } 194 195 dev_info(dvfs_info->dev, "Setting dvfs initial frequency = %uKHZ", 196 cur_frequency); 197 198 for (cpu = 0; cpu < CONFIG_NR_CPUS; cpu++) { 199 tmp = __raw_readl(dvfs_info->base + XMU_C0_3_PSTATE + cpu * 4); 200 tmp &= ~(P_VALUE_MASK << C0_3_PSTATE_NEW_SHIFT); 201 tmp |= ((pos - freq_table) << C0_3_PSTATE_NEW_SHIFT); 202 __raw_writel(tmp, dvfs_info->base + XMU_C0_3_PSTATE + cpu * 4); 203 } 204 205 /* Enable DVFS */ 206 __raw_writel(1 << XMU_DVFS_CTRL_EN_SHIFT, 207 dvfs_info->base + XMU_DVFS_CTRL); 208} 209 210static int exynos_target(struct cpufreq_policy *policy, unsigned int index) 211{ 212 unsigned int tmp; 213 int i; 214 struct cpufreq_frequency_table *freq_table = dvfs_info->freq_table; 215 216 mutex_lock(&cpufreq_lock); 217 218 freqs.old = policy->cur; 219 freqs.new = freq_table[index].frequency; 220 221 cpufreq_freq_transition_begin(policy, &freqs); 222 223 /* Set the target frequency in all C0_3_PSTATE register */ 224 for_each_cpu(i, policy->cpus) { 225 tmp = __raw_readl(dvfs_info->base + XMU_C0_3_PSTATE + i * 4); 226 tmp &= ~(P_VALUE_MASK << C0_3_PSTATE_NEW_SHIFT); 227 tmp |= (index << C0_3_PSTATE_NEW_SHIFT); 228 229 __raw_writel(tmp, dvfs_info->base + XMU_C0_3_PSTATE + i * 4); 230 } 231 mutex_unlock(&cpufreq_lock); 232 return 0; 233} 234 235static void exynos_cpufreq_work(struct work_struct *work) 236{ 237 unsigned int cur_pstate, index; 238 struct cpufreq_policy *policy = cpufreq_cpu_get(0); /* boot CPU */ 239 struct cpufreq_frequency_table *freq_table = dvfs_info->freq_table; 240 241 /* Ensure we can access cpufreq structures */ 242 if (unlikely(dvfs_info->dvfs_enabled == false)) 243 goto skip_work; 244 245 mutex_lock(&cpufreq_lock); 246 freqs.old = policy->cur; 247 248 cur_pstate = __raw_readl(dvfs_info->base + XMU_P_STATUS); 249 if (cur_pstate >> C0_3_PSTATE_VALID_SHIFT & 0x1) 250 index = (cur_pstate >> C0_3_PSTATE_CURR_SHIFT) & P_VALUE_MASK; 251 else 252 index = (cur_pstate >> C0_3_PSTATE_NEW_SHIFT) & P_VALUE_MASK; 253 254 if (likely(index < dvfs_info->freq_count)) { 255 freqs.new = freq_table[index].frequency; 256 } else { 257 dev_crit(dvfs_info->dev, "New frequency out of range\n"); 258 freqs.new = freqs.old; 259 } 260 cpufreq_freq_transition_end(policy, &freqs, 0); 261 262 cpufreq_cpu_put(policy); 263 mutex_unlock(&cpufreq_lock); 264skip_work: 265 enable_irq(dvfs_info->irq); 266} 267 268static irqreturn_t exynos_cpufreq_irq(int irq, void *id) 269{ 270 unsigned int tmp; 271 272 tmp = __raw_readl(dvfs_info->base + XMU_PMUIRQ); 273 if (tmp >> PSTATE_CHANGED_SHIFT & 0x1) { 274 __raw_writel(tmp, dvfs_info->base + XMU_PMUIRQ); 275 disable_irq_nosync(irq); 276 schedule_work(&dvfs_info->irq_work); 277 } 278 return IRQ_HANDLED; 279} 280 281static void exynos_sort_descend_freq_table(void) 282{ 283 struct cpufreq_frequency_table *freq_tbl = dvfs_info->freq_table; 284 int i = 0, index; 285 unsigned int tmp_freq; 286 /* 287 * Exynos5440 clock controller state logic expects the cpufreq table to 288 * be in descending order. But the OPP library constructs the table in 289 * ascending order. So to make the table descending we just need to 290 * swap the i element with the N - i element. 291 */ 292 for (i = 0; i < dvfs_info->freq_count / 2; i++) { 293 index = dvfs_info->freq_count - i - 1; 294 tmp_freq = freq_tbl[i].frequency; 295 freq_tbl[i].frequency = freq_tbl[index].frequency; 296 freq_tbl[index].frequency = tmp_freq; 297 } 298} 299 300static int exynos_cpufreq_cpu_init(struct cpufreq_policy *policy) 301{ 302 policy->clk = dvfs_info->cpu_clk; 303 return cpufreq_generic_init(policy, dvfs_info->freq_table, 304 dvfs_info->latency); 305} 306 307static struct cpufreq_driver exynos_driver = { 308 .flags = CPUFREQ_STICKY | CPUFREQ_ASYNC_NOTIFICATION | 309 CPUFREQ_NEED_INITIAL_FREQ_CHECK, 310 .verify = cpufreq_generic_frequency_table_verify, 311 .target_index = exynos_target, 312 .get = cpufreq_generic_get, 313 .init = exynos_cpufreq_cpu_init, 314 .name = CPUFREQ_NAME, 315 .attr = cpufreq_generic_attr, 316}; 317 318static const struct of_device_id exynos_cpufreq_match[] = { 319 { 320 .compatible = "samsung,exynos5440-cpufreq", 321 }, 322 {}, 323}; 324MODULE_DEVICE_TABLE(of, exynos_cpufreq_match); 325 326static int exynos_cpufreq_probe(struct platform_device *pdev) 327{ 328 int ret = -EINVAL; 329 struct device_node *np; 330 struct resource res; 331 unsigned int cur_frequency; 332 333 np = pdev->dev.of_node; 334 if (!np) 335 return -ENODEV; 336 337 dvfs_info = devm_kzalloc(&pdev->dev, sizeof(*dvfs_info), GFP_KERNEL); 338 if (!dvfs_info) { 339 ret = -ENOMEM; 340 goto err_put_node; 341 } 342 343 dvfs_info->dev = &pdev->dev; 344 345 ret = of_address_to_resource(np, 0, &res); 346 if (ret) 347 goto err_put_node; 348 349 dvfs_info->base = devm_ioremap_resource(dvfs_info->dev, &res); 350 if (IS_ERR(dvfs_info->base)) { 351 ret = PTR_ERR(dvfs_info->base); 352 goto err_put_node; 353 } 354 355 dvfs_info->irq = irq_of_parse_and_map(np, 0); 356 if (!dvfs_info->irq) { 357 dev_err(dvfs_info->dev, "No cpufreq irq found\n"); 358 ret = -ENODEV; 359 goto err_put_node; 360 } 361 362 ret = dev_pm_opp_of_add_table(dvfs_info->dev); 363 if (ret) { 364 dev_err(dvfs_info->dev, "failed to init OPP table: %d\n", ret); 365 goto err_put_node; 366 } 367 368 ret = dev_pm_opp_init_cpufreq_table(dvfs_info->dev, 369 &dvfs_info->freq_table); 370 if (ret) { 371 dev_err(dvfs_info->dev, 372 "failed to init cpufreq table: %d\n", ret); 373 goto err_free_opp; 374 } 375 dvfs_info->freq_count = dev_pm_opp_get_opp_count(dvfs_info->dev); 376 exynos_sort_descend_freq_table(); 377 378 if (of_property_read_u32(np, "clock-latency", &dvfs_info->latency)) 379 dvfs_info->latency = DEF_TRANS_LATENCY; 380 381 dvfs_info->cpu_clk = devm_clk_get(dvfs_info->dev, "armclk"); 382 if (IS_ERR(dvfs_info->cpu_clk)) { 383 dev_err(dvfs_info->dev, "Failed to get cpu clock\n"); 384 ret = PTR_ERR(dvfs_info->cpu_clk); 385 goto err_free_table; 386 } 387 388 cur_frequency = clk_get_rate(dvfs_info->cpu_clk); 389 if (!cur_frequency) { 390 dev_err(dvfs_info->dev, "Failed to get clock rate\n"); 391 ret = -EINVAL; 392 goto err_free_table; 393 } 394 cur_frequency /= 1000; 395 396 INIT_WORK(&dvfs_info->irq_work, exynos_cpufreq_work); 397 ret = devm_request_irq(dvfs_info->dev, dvfs_info->irq, 398 exynos_cpufreq_irq, IRQF_TRIGGER_NONE, 399 CPUFREQ_NAME, dvfs_info); 400 if (ret) { 401 dev_err(dvfs_info->dev, "Failed to register IRQ\n"); 402 goto err_free_table; 403 } 404 405 ret = init_div_table(); 406 if (ret) { 407 dev_err(dvfs_info->dev, "Failed to initialise div table\n"); 408 goto err_free_table; 409 } 410 411 exynos_enable_dvfs(cur_frequency); 412 ret = cpufreq_register_driver(&exynos_driver); 413 if (ret) { 414 dev_err(dvfs_info->dev, 415 "%s: failed to register cpufreq driver\n", __func__); 416 goto err_free_table; 417 } 418 419 of_node_put(np); 420 dvfs_info->dvfs_enabled = true; 421 return 0; 422 423err_free_table: 424 dev_pm_opp_free_cpufreq_table(dvfs_info->dev, &dvfs_info->freq_table); 425err_free_opp: 426 dev_pm_opp_of_remove_table(dvfs_info->dev); 427err_put_node: 428 of_node_put(np); 429 dev_err(&pdev->dev, "%s: failed initialization\n", __func__); 430 return ret; 431} 432 433static int exynos_cpufreq_remove(struct platform_device *pdev) 434{ 435 cpufreq_unregister_driver(&exynos_driver); 436 dev_pm_opp_free_cpufreq_table(dvfs_info->dev, &dvfs_info->freq_table); 437 dev_pm_opp_of_remove_table(dvfs_info->dev); 438 return 0; 439} 440 441static struct platform_driver exynos_cpufreq_platdrv = { 442 .driver = { 443 .name = "exynos5440-cpufreq", 444 .of_match_table = exynos_cpufreq_match, 445 }, 446 .probe = exynos_cpufreq_probe, 447 .remove = exynos_cpufreq_remove, 448}; 449module_platform_driver(exynos_cpufreq_platdrv); 450 451MODULE_AUTHOR("Amit Daniel Kachhap <amit.daniel@samsung.com>"); 452MODULE_DESCRIPTION("Exynos5440 cpufreq driver"); 453MODULE_LICENSE("GPL");