1 /* linux/arch/arm/plat-s3c24xx/s3c2440-cpufreq.c
2  *
3  * Copyright (c) 2006-2009 Simtec Electronics
4  *	http://armlinux.simtec.co.uk/
5  *	Ben Dooks <ben@simtec.co.uk>
6  *	Vincent Sanders <vince@simtec.co.uk>
7  *
8  * S3C2440/S3C2442 CPU Frequency scaling
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License version 2 as
12  * published by the Free Software Foundation.
13 */
14 
15 #include <linux/init.h>
16 #include <linux/module.h>
17 #include <linux/interrupt.h>
18 #include <linux/ioport.h>
19 #include <linux/cpufreq.h>
20 #include <linux/device.h>
21 #include <linux/delay.h>
22 #include <linux/clk.h>
23 #include <linux/err.h>
24 #include <linux/io.h>
25 
26 #include <mach/hardware.h>
27 
28 #include <asm/mach/arch.h>
29 #include <asm/mach/map.h>
30 
31 #include <mach/regs-clock.h>
32 
33 #include <plat/cpu.h>
34 #include <plat/cpu-freq-core.h>
35 #include <plat/clock.h>
36 
37 static struct clk *xtal;
38 static struct clk *fclk;
39 static struct clk *hclk;
40 static struct clk *armclk;
41 
42 /* HDIV: 1, 2, 3, 4, 6, 8 */
43 
within_khz(unsigned long a,unsigned long b)44 static inline int within_khz(unsigned long a, unsigned long b)
45 {
46 	long diff = a - b;
47 
48 	return (diff >= -1000 && diff <= 1000);
49 }
50 
51 /**
52  * s3c2440_cpufreq_calcdivs - calculate divider settings
53  * @cfg: The cpu frequency settings.
54  *
55  * Calcualte the divider values for the given frequency settings
56  * specified in @cfg. The values are stored in @cfg for later use
57  * by the relevant set routine if the request settings can be reached.
58  */
s3c2440_cpufreq_calcdivs(struct s3c_cpufreq_config * cfg)59 int s3c2440_cpufreq_calcdivs(struct s3c_cpufreq_config *cfg)
60 {
61 	unsigned int hdiv, pdiv;
62 	unsigned long hclk, fclk, armclk;
63 	unsigned long hclk_max;
64 
65 	fclk = cfg->freq.fclk;
66 	armclk = cfg->freq.armclk;
67 	hclk_max = cfg->max.hclk;
68 
69 	s3c_freq_dbg("%s: fclk is %lu, armclk %lu, max hclk %lu\n",
70 		     __func__, fclk, armclk, hclk_max);
71 
72 	if (armclk > fclk) {
73 		printk(KERN_WARNING "%s: armclk > fclk\n", __func__);
74 		armclk = fclk;
75 	}
76 
77 	/* if we are in DVS, we need HCLK to be <= ARMCLK */
78 	if (armclk < fclk && armclk < hclk_max)
79 		hclk_max = armclk;
80 
81 	for (hdiv = 1; hdiv < 9; hdiv++) {
82 		if (hdiv == 5 || hdiv == 7)
83 			hdiv++;
84 
85 		hclk = (fclk / hdiv);
86 		if (hclk <= hclk_max || within_khz(hclk, hclk_max))
87 			break;
88 	}
89 
90 	s3c_freq_dbg("%s: hclk %lu, div %d\n", __func__, hclk, hdiv);
91 
92 	if (hdiv > 8)
93 		goto invalid;
94 
95 	pdiv = (hclk > cfg->max.pclk) ? 2 : 1;
96 
97 	if ((hclk / pdiv) > cfg->max.pclk)
98 		pdiv++;
99 
100 	s3c_freq_dbg("%s: pdiv %d\n", __func__, pdiv);
101 
102 	if (pdiv > 2)
103 		goto invalid;
104 
105 	pdiv *= hdiv;
106 
107 	/* calculate a valid armclk */
108 
109 	if (armclk < hclk)
110 		armclk = hclk;
111 
112 	/* if we're running armclk lower than fclk, this really means
113 	 * that the system should go into dvs mode, which means that
114 	 * armclk is connected to hclk. */
115 	if (armclk < fclk) {
116 		cfg->divs.dvs = 1;
117 		armclk = hclk;
118 	} else
119 		cfg->divs.dvs = 0;
120 
121 	cfg->freq.armclk = armclk;
122 
123 	/* store the result, and then return */
124 
125 	cfg->divs.h_divisor = hdiv;
126 	cfg->divs.p_divisor = pdiv;
127 
128 	return 0;
129 
130  invalid:
131 	return -EINVAL;
132 }
133 
134 #define CAMDIVN_HCLK_HALF (S3C2440_CAMDIVN_HCLK3_HALF | \
135 			   S3C2440_CAMDIVN_HCLK4_HALF)
136 
137 /**
138  * s3c2440_cpufreq_setdivs - set the cpu frequency divider settings
139  * @cfg: The cpu frequency settings.
140  *
141  * Set the divisors from the settings in @cfg, which where generated
142  * during the calculation phase by s3c2440_cpufreq_calcdivs().
143  */
s3c2440_cpufreq_setdivs(struct s3c_cpufreq_config * cfg)144 static void s3c2440_cpufreq_setdivs(struct s3c_cpufreq_config *cfg)
145 {
146 	unsigned long clkdiv, camdiv;
147 
148 	s3c_freq_dbg("%s: divsiors: h=%d, p=%d\n", __func__,
149 		     cfg->divs.h_divisor, cfg->divs.p_divisor);
150 
151 	clkdiv = __raw_readl(S3C2410_CLKDIVN);
152 	camdiv = __raw_readl(S3C2440_CAMDIVN);
153 
154 	clkdiv &= ~(S3C2440_CLKDIVN_HDIVN_MASK | S3C2440_CLKDIVN_PDIVN);
155 	camdiv &= ~CAMDIVN_HCLK_HALF;
156 
157 	switch (cfg->divs.h_divisor) {
158 	case 1:
159 		clkdiv |= S3C2440_CLKDIVN_HDIVN_1;
160 		break;
161 
162 	case 2:
163 		clkdiv |= S3C2440_CLKDIVN_HDIVN_2;
164 		break;
165 
166 	case 6:
167 		camdiv |= S3C2440_CAMDIVN_HCLK3_HALF;
168 	case 3:
169 		clkdiv |= S3C2440_CLKDIVN_HDIVN_3_6;
170 		break;
171 
172 	case 8:
173 		camdiv |= S3C2440_CAMDIVN_HCLK4_HALF;
174 	case 4:
175 		clkdiv |= S3C2440_CLKDIVN_HDIVN_4_8;
176 		break;
177 
178 	default:
179 		BUG();	/* we don't expect to get here. */
180 	}
181 
182 	if (cfg->divs.p_divisor != cfg->divs.h_divisor)
183 		clkdiv |= S3C2440_CLKDIVN_PDIVN;
184 
185 	/* todo - set pclk. */
186 
187 	/* Write the divisors first with hclk intentionally halved so that
188 	 * when we write clkdiv we will under-frequency instead of over. We
189 	 * then make a short delay and remove the hclk halving if necessary.
190 	 */
191 
192 	__raw_writel(camdiv | CAMDIVN_HCLK_HALF, S3C2440_CAMDIVN);
193 	__raw_writel(clkdiv, S3C2410_CLKDIVN);
194 
195 	ndelay(20);
196 	__raw_writel(camdiv, S3C2440_CAMDIVN);
197 
198 	clk_set_parent(armclk, cfg->divs.dvs ? hclk : fclk);
199 }
200 
run_freq_for(unsigned long max_hclk,unsigned long fclk,int * divs,struct cpufreq_frequency_table * table,size_t table_size)201 static int run_freq_for(unsigned long max_hclk, unsigned long fclk,
202 			int *divs,
203 			struct cpufreq_frequency_table *table,
204 			size_t table_size)
205 {
206 	unsigned long freq;
207 	int index = 0;
208 	int div;
209 
210 	for (div = *divs; div > 0; div = *divs++) {
211 		freq = fclk / div;
212 
213 		if (freq > max_hclk && div != 1)
214 			continue;
215 
216 		freq /= 1000; /* table is in kHz */
217 		index = s3c_cpufreq_addfreq(table, index, table_size, freq);
218 		if (index < 0)
219 			break;
220 	}
221 
222 	return index;
223 }
224 
225 static int hclk_divs[] = { 1, 2, 3, 4, 6, 8, -1 };
226 
s3c2440_cpufreq_calctable(struct s3c_cpufreq_config * cfg,struct cpufreq_frequency_table * table,size_t table_size)227 static int s3c2440_cpufreq_calctable(struct s3c_cpufreq_config *cfg,
228 				     struct cpufreq_frequency_table *table,
229 				     size_t table_size)
230 {
231 	int ret;
232 
233 	WARN_ON(cfg->info == NULL);
234 	WARN_ON(cfg->board == NULL);
235 
236 	ret = run_freq_for(cfg->info->max.hclk,
237 			   cfg->info->max.fclk,
238 			   hclk_divs,
239 			   table, table_size);
240 
241 	s3c_freq_dbg("%s: returning %d\n", __func__, ret);
242 
243 	return ret;
244 }
245 
246 struct s3c_cpufreq_info s3c2440_cpufreq_info = {
247 	.max		= {
248 		.fclk	= 400000000,
249 		.hclk	= 133333333,
250 		.pclk	=  66666666,
251 	},
252 
253 	.locktime_m	= 300,
254 	.locktime_u	= 300,
255 	.locktime_bits	= 16,
256 
257 	.name		= "s3c244x",
258 	.calc_iotiming	= s3c2410_iotiming_calc,
259 	.set_iotiming	= s3c2410_iotiming_set,
260 	.get_iotiming	= s3c2410_iotiming_get,
261 	.set_fvco	= s3c2410_set_fvco,
262 
263 	.set_refresh	= s3c2410_cpufreq_setrefresh,
264 	.set_divs	= s3c2440_cpufreq_setdivs,
265 	.calc_divs	= s3c2440_cpufreq_calcdivs,
266 	.calc_freqtable	= s3c2440_cpufreq_calctable,
267 
268 	.resume_clocks	= s3c244x_setup_clocks,
269 
270 	.debug_io_show  = s3c_cpufreq_debugfs_call(s3c2410_iotiming_debugfs),
271 };
272 
s3c2440_cpufreq_add(struct device * dev,struct subsys_interface * sif)273 static int s3c2440_cpufreq_add(struct device *dev,
274 			       struct subsys_interface *sif)
275 {
276 	xtal = s3c_cpufreq_clk_get(NULL, "xtal");
277 	hclk = s3c_cpufreq_clk_get(NULL, "hclk");
278 	fclk = s3c_cpufreq_clk_get(NULL, "fclk");
279 	armclk = s3c_cpufreq_clk_get(NULL, "armclk");
280 
281 	if (IS_ERR(xtal) || IS_ERR(hclk) || IS_ERR(fclk) || IS_ERR(armclk)) {
282 		printk(KERN_ERR "%s: failed to get clocks\n", __func__);
283 		return -ENOENT;
284 	}
285 
286 	return s3c_cpufreq_register(&s3c2440_cpufreq_info);
287 }
288 
289 static struct subsys_interface s3c2440_cpufreq_interface = {
290 	.name		= "s3c2440_cpufreq",
291 	.subsys		= &s3c2440_subsys,
292 	.add_dev	= s3c2440_cpufreq_add,
293 };
294 
s3c2440_cpufreq_init(void)295 static int s3c2440_cpufreq_init(void)
296 {
297 	return subsys_interface_register(&s3c2440_cpufreq_interface);
298 }
299 
300 /* arch_initcall adds the clocks we need, so use subsys_initcall. */
301 subsys_initcall(s3c2440_cpufreq_init);
302 
303 static struct subsys_interface s3c2442_cpufreq_interface = {
304 	.name		= "s3c2442_cpufreq",
305 	.subsys		= &s3c2442_subsys,
306 	.add_dev	= s3c2440_cpufreq_add,
307 };
308 
s3c2442_cpufreq_init(void)309 static int s3c2442_cpufreq_init(void)
310 {
311 	return subsys_interface_register(&s3c2442_cpufreq_interface);
312 }
313 
314 subsys_initcall(s3c2442_cpufreq_init);
315