| 1 | From d0f0d5739a31c12d349980ed05a670fa1e84696d Mon Sep 17 00:00:00 2001 |
| 2 | From: Maarten ter Huurne <maarten@treewalker.org> |
| 3 | Date: Wed, 16 Mar 2011 03:16:04 +0100 |
| 4 | Subject: [PATCH 12/28] MIPS: JZ4740: Add cpufreq support. |
| 5 | |
| 6 | This is a squashed version of Uli's driver that was further developed in the opendingux-kernel repository. |
| 7 | --- |
| 8 | arch/mips/Kconfig | 1 + |
| 9 | arch/mips/jz4740/Makefile | 1 + |
| 10 | arch/mips/jz4740/cpufreq.c | 226 ++++++++++++++++++++++++++++++++++++++ |
| 11 | arch/mips/kernel/cpufreq/Kconfig | 13 ++- |
| 12 | 4 files changed, 240 insertions(+), 1 deletions(-) |
| 13 | create mode 100644 arch/mips/jz4740/cpufreq.c |
| 14 | |
| 15 | diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig |
| 16 | index d46f1da..8128df7 100644 |
| 17 | --- a/arch/mips/Kconfig |
| 18 | +++ b/arch/mips/Kconfig |
| 19 | @@ -209,6 +209,7 @@ config MACH_JZ4740 |
| 20 | select HAVE_PWM |
| 21 | select HAVE_CLK |
| 22 | select GENERIC_IRQ_CHIP |
| 23 | + select CPU_SUPPORTS_CPUFREQ |
| 24 | |
| 25 | config LANTIQ |
| 26 | bool "Lantiq based platforms" |
| 27 | diff --git a/arch/mips/jz4740/Makefile b/arch/mips/jz4740/Makefile |
| 28 | index a9dff33..15f828e 100644 |
| 29 | --- a/arch/mips/jz4740/Makefile |
| 30 | +++ b/arch/mips/jz4740/Makefile |
| 31 | @@ -16,5 +16,6 @@ obj-$(CONFIG_JZ4740_QI_LB60) += board-qi_lb60.o |
| 32 | # PM support |
| 33 | |
| 34 | obj-$(CONFIG_PM) += pm.o |
| 35 | +obj-$(CONFIG_CPU_FREQ_JZ) += cpufreq.o |
| 36 | |
| 37 | ccflags-y := -Werror -Wall |
| 38 | diff --git a/arch/mips/jz4740/cpufreq.c b/arch/mips/jz4740/cpufreq.c |
| 39 | new file mode 100644 |
| 40 | index 0000000..aa41e9f |
| 41 | --- /dev/null |
| 42 | +++ b/arch/mips/jz4740/cpufreq.c |
| 43 | @@ -0,0 +1,226 @@ |
| 44 | +/* |
| 45 | + * linux/arch/mips/jz4740/cpufreq.c |
| 46 | + * |
| 47 | + * cpufreq driver for JZ4740 |
| 48 | + * |
| 49 | + * Copyright (c) 2010 Ulrich Hecht <ulrich.hecht@gmail.com> |
| 50 | + * Copyright (c) 2010 Maarten ter Huurne <maarten@treewalker.org> |
| 51 | + * |
| 52 | + * This program is free software; you can redistribute it and/or modify |
| 53 | + * it under the terms of the GNU General Public License version 2 as |
| 54 | + * published by the Free Software Foundation. |
| 55 | + */ |
| 56 | + |
| 57 | +#include <linux/kernel.h> |
| 58 | +#include <linux/module.h> |
| 59 | +#include <linux/init.h> |
| 60 | +#include <linux/err.h> |
| 61 | + |
| 62 | +#include <linux/cpufreq.h> |
| 63 | + |
| 64 | +#include <linux/clk.h> |
| 65 | +#include <asm/mach-jz4740/base.h> |
| 66 | + |
| 67 | +#include "clock.h" |
| 68 | + |
| 69 | +#define DEBUG_CPUFREQ |
| 70 | + |
| 71 | +#ifdef DEBUG_CPUFREQ |
| 72 | +#define dprintk(X...) printk(KERN_INFO X) |
| 73 | +#else |
| 74 | +#define dprintk(X...) do { } while(0) |
| 75 | +#endif |
| 76 | + |
| 77 | +#define HCLK_MIN 30000 |
| 78 | +/* TODO: The maximum MCLK most likely depends on the SDRAM chips used, |
| 79 | + so it is board-specific. */ |
| 80 | +#define MCLK_MAX 140000 |
| 81 | + |
| 82 | +/* Same as jz_clk_main_divs, but with 24 and 32 removed because the hardware |
| 83 | + spec states those dividers must not be used for CCLK or HCLK. */ |
| 84 | +static const unsigned int jz4740_freq_cpu_divs[] = {1, 2, 3, 4, 6, 8, 12, 16}; |
| 85 | + |
| 86 | +struct jz4740_freq_percpu_info { |
| 87 | + unsigned int pll_rate; |
| 88 | + struct cpufreq_frequency_table table[ |
| 89 | + ARRAY_SIZE(jz4740_freq_cpu_divs) + 1]; |
| 90 | +}; |
| 91 | + |
| 92 | +static struct clk *pll; |
| 93 | +static struct clk *cclk; |
| 94 | + |
| 95 | +static struct jz4740_freq_percpu_info jz4740_freq_info; |
| 96 | + |
| 97 | +static struct cpufreq_driver cpufreq_jz4740_driver; |
| 98 | + |
| 99 | +static void jz4740_freq_fill_table(struct cpufreq_policy *policy, |
| 100 | + unsigned int pll_rate) |
| 101 | +{ |
| 102 | + struct cpufreq_frequency_table *table = &jz4740_freq_info.table[0]; |
| 103 | + int i; |
| 104 | + |
| 105 | +#ifdef CONFIG_CPU_FREQ_STAT_DETAILS |
| 106 | + /* for showing /sys/devices/system/cpu/cpuX/cpufreq/stats/ */ |
| 107 | + static bool init = false; |
| 108 | + if (init) |
| 109 | + cpufreq_frequency_table_put_attr(policy->cpu); |
| 110 | + else |
| 111 | + init = true; |
| 112 | +#endif |
| 113 | + |
| 114 | + jz4740_freq_info.pll_rate = pll_rate; |
| 115 | + |
| 116 | + for (i = 0; i < ARRAY_SIZE(jz4740_freq_cpu_divs); i++) { |
| 117 | + unsigned int freq = pll_rate / jz4740_freq_cpu_divs[i]; |
| 118 | + if (freq < HCLK_MIN) break; |
| 119 | + table[i].index = i; |
| 120 | + table[i].frequency = freq; |
| 121 | + } |
| 122 | + table[i].index = i; |
| 123 | + table[i].frequency = CPUFREQ_TABLE_END; |
| 124 | + |
| 125 | + policy->min = table[i - 1].frequency; |
| 126 | + policy->max = table[0].frequency; |
| 127 | + |
| 128 | +#ifdef CONFIG_CPU_FREQ_STAT_DETAILS |
| 129 | + cpufreq_frequency_table_get_attr(table, policy->cpu); |
| 130 | +#endif |
| 131 | +} |
| 132 | + |
| 133 | +static unsigned int jz4740_freq_get(unsigned int cpu) |
| 134 | +{ |
| 135 | + return clk_get_rate(cclk) / 1000; |
| 136 | +} |
| 137 | + |
| 138 | +static int jz4740_freq_verify(struct cpufreq_policy *policy) |
| 139 | +{ |
| 140 | + unsigned int new_pll; |
| 141 | + |
| 142 | + cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, |
| 143 | + policy->cpuinfo.max_freq); |
| 144 | + |
| 145 | + new_pll = clk_round_rate(pll, policy->max * 1000) / 1000; |
| 146 | + if (jz4740_freq_info.pll_rate != new_pll) |
| 147 | + jz4740_freq_fill_table(policy, new_pll); |
| 148 | + |
| 149 | + return 0; |
| 150 | +} |
| 151 | + |
| 152 | +static int jz4740_freq_target(struct cpufreq_policy *policy, |
| 153 | + unsigned int target_freq, |
| 154 | + unsigned int relation) |
| 155 | +{ |
| 156 | + struct cpufreq_frequency_table *table = &jz4740_freq_info.table[0]; |
| 157 | + struct cpufreq_freqs freqs; |
| 158 | + unsigned int new_index = 0; |
| 159 | + unsigned int old_pll = clk_get_rate(pll) / 1000; |
| 160 | + unsigned int new_pll = jz4740_freq_info.pll_rate; |
| 161 | + int ret = 0; |
| 162 | + |
| 163 | + if (cpufreq_frequency_table_target(policy, table, |
| 164 | + target_freq, relation, &new_index)) |
| 165 | + return -EINVAL; |
| 166 | + freqs = (struct cpufreq_freqs) { |
| 167 | + .old = jz4740_freq_get(policy->cpu), |
| 168 | + .new = table[new_index].frequency, |
| 169 | + .cpu = policy->cpu, |
| 170 | + .flags = cpufreq_jz4740_driver.flags, |
| 171 | + }; |
| 172 | + if (freqs.new != freqs.old || new_pll != old_pll) { |
| 173 | + unsigned int cdiv, hdiv, mdiv, pdiv; |
| 174 | + cdiv = jz4740_freq_cpu_divs[new_index]; |
| 175 | + hdiv = (cdiv == 3 || cdiv == 6) ? cdiv * 2 : cdiv * 3; |
| 176 | + while (new_pll < HCLK_MIN * hdiv) |
| 177 | + hdiv -= cdiv; |
| 178 | + mdiv = hdiv; |
| 179 | + if (new_pll > MCLK_MAX * mdiv) { |
| 180 | + /* 4,4 performs better than 3,6 */ |
| 181 | + if (new_pll > MCLK_MAX * 4) |
| 182 | + mdiv *= 2; |
| 183 | + else |
| 184 | + hdiv = mdiv = cdiv * 4; |
| 185 | + } |
| 186 | + pdiv = mdiv; |
| 187 | + dprintk(KERN_INFO "%s: cclk %p, setting from %d to %d, " |
| 188 | + "dividers %d, %d, %d, %d\n", |
| 189 | + __FUNCTION__, cclk, freqs.old, freqs.new, |
| 190 | + cdiv, hdiv, mdiv, pdiv); |
| 191 | + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); |
| 192 | + ret = clk_main_set_dividers(new_pll == old_pll, |
| 193 | + cdiv, hdiv, mdiv, pdiv); |
| 194 | + if (ret) { |
| 195 | + dprintk(KERN_INFO "failed to set dividers\n"); |
| 196 | + } else if (new_pll != old_pll) { |
| 197 | + dprintk(KERN_INFO "%s: pll %p, setting from %d to %d\n", |
| 198 | + __FUNCTION__, pll, old_pll, new_pll); |
| 199 | + ret = clk_set_rate(pll, new_pll * 1000); |
| 200 | + } |
| 201 | + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); |
| 202 | + } |
| 203 | + |
| 204 | + return ret; |
| 205 | +} |
| 206 | + |
| 207 | +static int jz4740_cpufreq_driver_init(struct cpufreq_policy *policy) |
| 208 | +{ |
| 209 | + int ret; |
| 210 | + |
| 211 | + dprintk(KERN_INFO "Jz4740 cpufreq driver\n"); |
| 212 | + |
| 213 | + if (policy->cpu != 0) |
| 214 | + return -EINVAL; |
| 215 | + |
| 216 | + pll = clk_get(NULL, "pll"); |
| 217 | + if (IS_ERR(pll)) { |
| 218 | + ret = PTR_ERR(pll); |
| 219 | + goto err_exit; |
| 220 | + } |
| 221 | + |
| 222 | + cclk = clk_get(NULL, "cclk"); |
| 223 | + if (IS_ERR(cclk)) { |
| 224 | + ret = PTR_ERR(cclk); |
| 225 | + goto err_clk_put_pll; |
| 226 | + } |
| 227 | + |
| 228 | + policy->cpuinfo.min_freq = HCLK_MIN; |
| 229 | + policy->cpuinfo.max_freq = 500000; |
| 230 | + policy->cpuinfo.transition_latency = 100000; /* in nanoseconds */ |
| 231 | + policy->cur = jz4740_freq_get(policy->cpu); |
| 232 | + policy->governor = CPUFREQ_DEFAULT_GOVERNOR; |
| 233 | + /* min and max are set by jz4740_freq_fill_table() */ |
| 234 | + |
| 235 | + jz4740_freq_fill_table(policy, clk_get_rate(pll) / 1000 /* in kHz */); |
| 236 | + |
| 237 | + return 0; |
| 238 | + |
| 239 | +err_clk_put_pll: |
| 240 | + clk_put(pll); |
| 241 | +err_exit: |
| 242 | + return ret; |
| 243 | +} |
| 244 | + |
| 245 | +static struct cpufreq_driver cpufreq_jz4740_driver = { |
| 246 | + .init = jz4740_cpufreq_driver_init, |
| 247 | + .verify = jz4740_freq_verify, |
| 248 | + .target = jz4740_freq_target, |
| 249 | + .get = jz4740_freq_get, |
| 250 | + .name = "jz4740", |
| 251 | +}; |
| 252 | + |
| 253 | +static int __init jz4740_cpufreq_init(void) |
| 254 | +{ |
| 255 | + return cpufreq_register_driver(&cpufreq_jz4740_driver); |
| 256 | +} |
| 257 | + |
| 258 | +static void __exit jz4740_cpufreq_exit(void) |
| 259 | +{ |
| 260 | + cpufreq_unregister_driver(&cpufreq_jz4740_driver); |
| 261 | +} |
| 262 | + |
| 263 | +module_init(jz4740_cpufreq_init); |
| 264 | +module_exit(jz4740_cpufreq_exit); |
| 265 | + |
| 266 | +MODULE_AUTHOR("Ulrich Hecht <ulrich.hecht@gmail.com>, " |
| 267 | + "Maarten ter Huurne <maarten@treewalker.org>"); |
| 268 | +MODULE_DESCRIPTION("cpufreq driver for Jz4740"); |
| 269 | +MODULE_LICENSE("GPL"); |
| 270 | diff --git a/arch/mips/kernel/cpufreq/Kconfig b/arch/mips/kernel/cpufreq/Kconfig |
| 271 | index 58c601e..11af8e8 100644 |
| 272 | --- a/arch/mips/kernel/cpufreq/Kconfig |
| 273 | +++ b/arch/mips/kernel/cpufreq/Kconfig |
| 274 | @@ -8,7 +8,7 @@ config MIPS_EXTERNAL_TIMER |
| 275 | config MIPS_CPUFREQ |
| 276 | bool |
| 277 | default y |
| 278 | - depends on CPU_SUPPORTS_CPUFREQ && MIPS_EXTERNAL_TIMER |
| 279 | + depends on CPU_SUPPORTS_CPUFREQ |
| 280 | |
| 281 | if MIPS_CPUFREQ |
| 282 | |
| 283 | @@ -24,6 +24,7 @@ config LOONGSON2_CPUFREQ |
| 284 | tristate "Loongson2 CPUFreq Driver" |
| 285 | select CPU_FREQ_TABLE |
| 286 | depends on MIPS_CPUFREQ |
| 287 | + depends on MIPS_EXTERNAL_TIMER |
| 288 | help |
| 289 | This option adds a CPUFreq driver for loongson processors which |
| 290 | support software configurable cpu frequency. |
| 291 | @@ -34,6 +35,16 @@ config LOONGSON2_CPUFREQ |
| 292 | |
| 293 | If in doubt, say N. |
| 294 | |
| 295 | +config CPU_FREQ_JZ |
| 296 | + tristate "CPUfreq driver for JZ CPUs" |
| 297 | + select CPU_FREQ_TABLE |
| 298 | + depends on MACH_JZ4740 |
| 299 | + default n |
| 300 | + help |
| 301 | + This enables the CPUfreq driver for JZ CPUs. |
| 302 | + |
| 303 | + If in doubt, say N. |
| 304 | + |
| 305 | endif # CPU_FREQ |
| 306 | |
| 307 | endmenu |
| 308 | -- |
| 309 | 1.7.5.4 |
| 310 | |
| 311 | |