]> nv-tegra.nvidia Code Review - linux-2.6.git/blob - arch/arm/plat-s3c24xx/s3c2440-cpufreq.c
Merge branch 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab...
[linux-2.6.git] / arch / arm / plat-s3c24xx / s3c2440-cpufreq.c
1 /* linux/arch/arm/plat-s3c24xx/s3c2440-cpufreq.c
2  *
3  * Copyright (c) 2006,2008,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/sysdev.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
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  */
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  */
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
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
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
273 static int s3c2440_cpufreq_add(struct sys_device *sysdev)
274 {
275         xtal = s3c_cpufreq_clk_get(NULL, "xtal");
276         hclk = s3c_cpufreq_clk_get(NULL, "hclk");
277         fclk = s3c_cpufreq_clk_get(NULL, "fclk");
278         armclk = s3c_cpufreq_clk_get(NULL, "armclk");
279
280         if (IS_ERR(xtal) || IS_ERR(hclk) || IS_ERR(fclk) || IS_ERR(armclk)) {
281                 printk(KERN_ERR "%s: failed to get clocks\n", __func__);
282                 return -ENOENT;
283         }
284
285         return s3c_cpufreq_register(&s3c2440_cpufreq_info);
286 }
287
288 static struct sysdev_driver s3c2440_cpufreq_driver = {
289         .add            = s3c2440_cpufreq_add,
290 };
291
292 static int s3c2440_cpufreq_init(void)
293 {
294         return sysdev_driver_register(&s3c2440_sysclass,
295                                       &s3c2440_cpufreq_driver);
296 }
297
298 /* arch_initcall adds the clocks we need, so use subsys_initcall. */
299 subsys_initcall(s3c2440_cpufreq_init);
300
301 static struct sysdev_driver s3c2442_cpufreq_driver = {
302         .add            = s3c2440_cpufreq_add,
303 };
304
305 static int s3c2442_cpufreq_init(void)
306 {
307         return sysdev_driver_register(&s3c2442_sysclass,
308                                       &s3c2442_cpufreq_driver);
309 }
310
311 subsys_initcall(s3c2442_cpufreq_init);