summaryrefslogtreecommitdiffstats
path: root/target/linux/xburst/patches-3.8/0012-MIPS-JZ4740-Add-cpufreq-support.patch
blob: 54883ea77fedc9504ea8affb2edb3539b791a1e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
From d0f0d5739a31c12d349980ed05a670fa1e84696d Mon Sep 17 00:00:00 2001
From: Maarten ter Huurne <maarten@treewalker.org>
Date: Wed, 16 Mar 2011 03:16:04 +0100
Subject: [PATCH 12/21] MIPS: JZ4740: Add cpufreq support.

This is a squashed version of Uli's driver that was further developed in the opendingux-kernel repository.
---
 arch/mips/Kconfig                |    1 +
 arch/mips/jz4740/Makefile        |    1 +
 arch/mips/jz4740/cpufreq.c       |  226 ++++++++++++++++++++++++++++++++++++++
 arch/mips/kernel/cpufreq/Kconfig |   13 ++-
 4 files changed, 240 insertions(+), 1 deletions(-)
 create mode 100644 arch/mips/jz4740/cpufreq.c

--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -228,6 +228,7 @@ config MACH_JZ4740
 	select HAVE_PWM
 	select HAVE_CLK
 	select GENERIC_IRQ_CHIP
+	select CPU_SUPPORTS_CPUFREQ
 
 config LANTIQ
 	bool "Lantiq based platforms"
--- a/arch/mips/jz4740/Makefile
+++ b/arch/mips/jz4740/Makefile
@@ -16,3 +16,4 @@ obj-$(CONFIG_JZ4740_QI_LB60)	+= board-qi
 # PM support
 
 obj-$(CONFIG_PM) += pm.o
+obj-$(CONFIG_CPU_FREQ_JZ) += cpufreq.o
--- /dev/null
+++ b/arch/mips/jz4740/cpufreq.c
@@ -0,0 +1,226 @@
+/*
+ * linux/arch/mips/jz4740/cpufreq.c
+ *
+ * cpufreq driver for JZ4740
+ *
+ * Copyright (c) 2010       Ulrich Hecht <ulrich.hecht@gmail.com>
+ * Copyright (c) 2010       Maarten ter Huurne <maarten@treewalker.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+
+#include <linux/cpufreq.h>
+
+#include <linux/clk.h>
+#include <asm/mach-jz4740/base.h>
+
+#include "clock.h"
+
+#define DEBUG_CPUFREQ
+
+#ifdef DEBUG_CPUFREQ
+#define dprintk(X...) printk(KERN_INFO X)
+#else
+#define dprintk(X...) do { } while(0)
+#endif
+
+#define HCLK_MIN 30000
+/* TODO: The maximum MCLK most likely depends on the SDRAM chips used,
+         so it is board-specific. */
+#define MCLK_MAX 140000
+
+/* Same as jz_clk_main_divs, but with 24 and 32 removed because the hardware
+   spec states those dividers must not be used for CCLK or HCLK. */
+static const unsigned int jz4740_freq_cpu_divs[] = {1, 2, 3, 4, 6, 8, 12, 16};
+
+struct jz4740_freq_percpu_info {
+	unsigned int pll_rate;
+	struct cpufreq_frequency_table table[
+		ARRAY_SIZE(jz4740_freq_cpu_divs) + 1];
+};
+
+static struct clk *pll;
+static struct clk *cclk;
+
+static struct jz4740_freq_percpu_info jz4740_freq_info;
+
+static struct cpufreq_driver cpufreq_jz4740_driver;
+
+static void jz4740_freq_fill_table(struct cpufreq_policy *policy,
+				   unsigned int pll_rate)
+{
+	struct cpufreq_frequency_table *table = &jz4740_freq_info.table[0];
+	int i;
+
+#ifdef CONFIG_CPU_FREQ_STAT_DETAILS
+	/* for showing /sys/devices/system/cpu/cpuX/cpufreq/stats/ */
+	static bool init = false;
+	if (init)
+		cpufreq_frequency_table_put_attr(policy->cpu);
+	else
+		init = true;
+#endif
+
+	jz4740_freq_info.pll_rate = pll_rate;
+
+	for (i = 0; i < ARRAY_SIZE(jz4740_freq_cpu_divs); i++) {
+		unsigned int freq = pll_rate / jz4740_freq_cpu_divs[i];
+		if (freq < HCLK_MIN) break;
+		table[i].index = i;
+		table[i].frequency = freq;
+	}
+	table[i].index = i;
+	table[i].frequency = CPUFREQ_TABLE_END;
+
+	policy->min = table[i - 1].frequency;
+	policy->max = table[0].frequency;
+
+#ifdef CONFIG_CPU_FREQ_STAT_DETAILS
+	cpufreq_frequency_table_get_attr(table, policy->cpu);
+#endif
+}
+
+static unsigned int jz4740_freq_get(unsigned int cpu)
+{
+	return clk_get_rate(cclk) / 1000;
+}
+
+static int jz4740_freq_verify(struct cpufreq_policy *policy)
+{
+	unsigned int new_pll;
+
+	cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
+				     policy->cpuinfo.max_freq);
+
+	new_pll = clk_round_rate(pll, policy->max * 1000) / 1000;
+	if (jz4740_freq_info.pll_rate != new_pll)
+		jz4740_freq_fill_table(policy, new_pll);
+
+	return 0;
+}
+
+static int jz4740_freq_target(struct cpufreq_policy *policy,
+			  unsigned int target_freq,
+			  unsigned int relation)
+{
+	struct cpufreq_frequency_table *table = &jz4740_freq_info.table[0];
+	struct cpufreq_freqs freqs;
+	unsigned int new_index = 0;
+	unsigned int old_pll = clk_get_rate(pll) / 1000;
+	unsigned int new_pll = jz4740_freq_info.pll_rate;
+	int ret = 0;
+
+	if (cpufreq_frequency_table_target(policy, table,
+					   target_freq, relation, &new_index))
+		return -EINVAL;
+	freqs = (struct cpufreq_freqs) {
+		.old = jz4740_freq_get(policy->cpu),
+		.new = table[new_index].frequency,
+		.cpu = policy->cpu,
+		.flags = cpufreq_jz4740_driver.flags,
+	};
+	if (freqs.new != freqs.old || new_pll != old_pll) {
+		unsigned int cdiv, hdiv, mdiv, pdiv;
+		cdiv = jz4740_freq_cpu_divs[new_index];
+		hdiv = (cdiv == 3 || cdiv == 6) ? cdiv * 2 : cdiv * 3;
+		while (new_pll < HCLK_MIN * hdiv)
+			hdiv -= cdiv;
+		mdiv = hdiv;
+		if (new_pll > MCLK_MAX * mdiv) {
+			/* 4,4 performs better than 3,6 */
+			if (new_pll > MCLK_MAX * 4)
+				mdiv *= 2;
+			else
+				hdiv = mdiv = cdiv * 4;
+		}
+		pdiv = mdiv;
+		dprintk(KERN_INFO "%s: cclk %p, setting from %d to %d, "
+			"dividers %d, %d, %d, %d\n",
+			__FUNCTION__, cclk, freqs.old, freqs.new,
+			cdiv, hdiv, mdiv, pdiv);
+		cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
+		ret = clk_main_set_dividers(new_pll == old_pll,
+					    cdiv, hdiv, mdiv, pdiv);
+		if (ret) {
+			dprintk(KERN_INFO "failed to set dividers\n");
+		} else if (new_pll != old_pll) {
+			dprintk(KERN_INFO "%s: pll %p, setting from %d to %d\n",
+				__FUNCTION__, pll, old_pll, new_pll);
+			ret = clk_set_rate(pll, new_pll * 1000);
+		}
+		cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+	}
+
+	return ret;
+}
+
+static int jz4740_cpufreq_driver_init(struct cpufreq_policy *policy)
+{
+	int ret;
+
+	dprintk(KERN_INFO "Jz4740 cpufreq driver\n");
+
+	if (policy->cpu != 0)
+		return -EINVAL;
+
+	pll = clk_get(NULL, "pll");
+	if (IS_ERR(pll)) {
+		ret = PTR_ERR(pll);
+		goto err_exit;
+	}
+
+	cclk = clk_get(NULL, "cclk");
+	if (IS_ERR(cclk)) {
+		ret = PTR_ERR(cclk);
+		goto err_clk_put_pll;
+	}
+
+	policy->cpuinfo.min_freq = HCLK_MIN;
+	policy->cpuinfo.max_freq = 500000;
+	policy->cpuinfo.transition_latency = 100000; /* in nanoseconds */
+	policy->cur = jz4740_freq_get(policy->cpu);
+	policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
+	/* min and max are set by jz4740_freq_fill_table() */
+
+	jz4740_freq_fill_table(policy, clk_get_rate(pll) / 1000 /* in kHz */);
+
+	return 0;
+
+err_clk_put_pll:
+	clk_put(pll);
+err_exit:
+	return ret;
+}
+
+static struct cpufreq_driver cpufreq_jz4740_driver = {
+	.init	= jz4740_cpufreq_driver_init,
+	.verify	= jz4740_freq_verify,
+	.target	= jz4740_freq_target,
+	.get	= jz4740_freq_get,
+	.name	= "jz4740",
+};
+
+static int __init jz4740_cpufreq_init(void)
+{
+	return cpufreq_register_driver(&cpufreq_jz4740_driver);
+}
+
+static void __exit jz4740_cpufreq_exit(void)
+{
+	cpufreq_unregister_driver(&cpufreq_jz4740_driver);
+}
+
+module_init(jz4740_cpufreq_init);
+module_exit(jz4740_cpufreq_exit);
+
+MODULE_AUTHOR("Ulrich Hecht <ulrich.hecht@gmail.com>, "
+	      "Maarten ter Huurne <maarten@treewalker.org>");
+MODULE_DESCRIPTION("cpufreq driver for Jz4740");
+MODULE_LICENSE("GPL");
--- a/arch/mips/kernel/cpufreq/Kconfig
+++ b/arch/mips/kernel/cpufreq/Kconfig
@@ -8,7 +8,7 @@ config MIPS_EXTERNAL_TIMER
 config MIPS_CPUFREQ
 	bool
 	default y
-	depends on CPU_SUPPORTS_CPUFREQ && MIPS_EXTERNAL_TIMER
+	depends on CPU_SUPPORTS_CPUFREQ
 
 if MIPS_CPUFREQ
 
@@ -24,6 +24,7 @@ config LOONGSON2_CPUFREQ
 	tristate "Loongson2 CPUFreq Driver"
 	select CPU_FREQ_TABLE
 	depends on MIPS_CPUFREQ
+	depends on MIPS_EXTERNAL_TIMER
 	help
 	  This option adds a CPUFreq driver for loongson processors which
 	  support software configurable cpu frequency.
@@ -34,6 +35,16 @@ config LOONGSON2_CPUFREQ
 
 	  If in doubt, say N.
 
+config CPU_FREQ_JZ
+	tristate "CPUfreq driver for JZ CPUs"
+	select CPU_FREQ_TABLE
+	depends on MACH_JZ4740
+	default n
+	help
+	  This enables the CPUfreq driver for JZ CPUs.
+
+	  If in doubt, say N.
+
 endif	# CPU_FREQ
 
 endmenu