This client driver allows you to use a GPIO pin as a source for PPS
[linux-2.6/next.git] / arch / arm / mach-s3c2440 / s3c2440-cpufreq.c
blob976002fb1b8f9f5c9f5cf35c98a45aae0509c4b4
1 /* linux/arch/arm/plat-s3c24xx/s3c2440-cpufreq.c
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>
8 * S3C2440/S3C2442 CPU Frequency scaling
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.
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>
26 #include <mach/hardware.h>
28 #include <asm/mach/arch.h>
29 #include <asm/mach/map.h>
31 #include <mach/regs-clock.h>
33 #include <plat/cpu.h>
34 #include <plat/cpu-freq-core.h>
35 #include <plat/clock.h>
37 static struct clk *xtal;
38 static struct clk *fclk;
39 static struct clk *hclk;
40 static struct clk *armclk;
42 /* HDIV: 1, 2, 3, 4, 6, 8 */
44 static inline int within_khz(unsigned long a, unsigned long b)
46 long diff = a - b;
48 return (diff >= -1000 && diff <= 1000);
51 /**
52 * s3c2440_cpufreq_calcdivs - calculate divider settings
53 * @cfg: The cpu frequency settings.
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.
59 int s3c2440_cpufreq_calcdivs(struct s3c_cpufreq_config *cfg)
61 unsigned int hdiv, pdiv;
62 unsigned long hclk, fclk, armclk;
63 unsigned long hclk_max;
65 fclk = cfg->freq.fclk;
66 armclk = cfg->freq.armclk;
67 hclk_max = cfg->max.hclk;
69 s3c_freq_dbg("%s: fclk is %lu, armclk %lu, max hclk %lu\n",
70 __func__, fclk, armclk, hclk_max);
72 if (armclk > fclk) {
73 printk(KERN_WARNING "%s: armclk > fclk\n", __func__);
74 armclk = fclk;
77 /* if we are in DVS, we need HCLK to be <= ARMCLK */
78 if (armclk < fclk && armclk < hclk_max)
79 hclk_max = armclk;
81 for (hdiv = 1; hdiv < 9; hdiv++) {
82 if (hdiv == 5 || hdiv == 7)
83 hdiv++;
85 hclk = (fclk / hdiv);
86 if (hclk <= hclk_max || within_khz(hclk, hclk_max))
87 break;
90 s3c_freq_dbg("%s: hclk %lu, div %d\n", __func__, hclk, hdiv);
92 if (hdiv > 8)
93 goto invalid;
95 pdiv = (hclk > cfg->max.pclk) ? 2 : 1;
97 if ((hclk / pdiv) > cfg->max.pclk)
98 pdiv++;
100 s3c_freq_dbg("%s: pdiv %d\n", __func__, pdiv);
102 if (pdiv > 2)
103 goto invalid;
105 pdiv *= hdiv;
107 /* calculate a valid armclk */
109 if (armclk < hclk)
110 armclk = hclk;
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;
121 cfg->freq.armclk = armclk;
123 /* store the result, and then return */
125 cfg->divs.h_divisor = hdiv;
126 cfg->divs.p_divisor = pdiv;
128 return 0;
130 invalid:
131 return -EINVAL;
134 #define CAMDIVN_HCLK_HALF (S3C2440_CAMDIVN_HCLK3_HALF | \
135 S3C2440_CAMDIVN_HCLK4_HALF)
138 * s3c2440_cpufreq_setdivs - set the cpu frequency divider settings
139 * @cfg: The cpu frequency settings.
141 * Set the divisors from the settings in @cfg, which where generated
142 * during the calculation phase by s3c2440_cpufreq_calcdivs().
144 static void s3c2440_cpufreq_setdivs(struct s3c_cpufreq_config *cfg)
146 unsigned long clkdiv, camdiv;
148 s3c_freq_dbg("%s: divsiors: h=%d, p=%d\n", __func__,
149 cfg->divs.h_divisor, cfg->divs.p_divisor);
151 clkdiv = __raw_readl(S3C2410_CLKDIVN);
152 camdiv = __raw_readl(S3C2440_CAMDIVN);
154 clkdiv &= ~(S3C2440_CLKDIVN_HDIVN_MASK | S3C2440_CLKDIVN_PDIVN);
155 camdiv &= ~CAMDIVN_HCLK_HALF;
157 switch (cfg->divs.h_divisor) {
158 case 1:
159 clkdiv |= S3C2440_CLKDIVN_HDIVN_1;
160 break;
162 case 2:
163 clkdiv |= S3C2440_CLKDIVN_HDIVN_2;
164 break;
166 case 6:
167 camdiv |= S3C2440_CAMDIVN_HCLK3_HALF;
168 case 3:
169 clkdiv |= S3C2440_CLKDIVN_HDIVN_3_6;
170 break;
172 case 8:
173 camdiv |= S3C2440_CAMDIVN_HCLK4_HALF;
174 case 4:
175 clkdiv |= S3C2440_CLKDIVN_HDIVN_4_8;
176 break;
178 default:
179 BUG(); /* we don't expect to get here. */
182 if (cfg->divs.p_divisor != cfg->divs.h_divisor)
183 clkdiv |= S3C2440_CLKDIVN_PDIVN;
185 /* todo - set pclk. */
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.
192 __raw_writel(camdiv | CAMDIVN_HCLK_HALF, S3C2440_CAMDIVN);
193 __raw_writel(clkdiv, S3C2410_CLKDIVN);
195 ndelay(20);
196 __raw_writel(camdiv, S3C2440_CAMDIVN);
198 clk_set_parent(armclk, cfg->divs.dvs ? hclk : fclk);
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)
206 unsigned long freq;
207 int index = 0;
208 int div;
210 for (div = *divs; div > 0; div = *divs++) {
211 freq = fclk / div;
213 if (freq > max_hclk && div != 1)
214 continue;
216 freq /= 1000; /* table is in kHz */
217 index = s3c_cpufreq_addfreq(table, index, table_size, freq);
218 if (index < 0)
219 break;
222 return index;
225 static int hclk_divs[] = { 1, 2, 3, 4, 6, 8, -1 };
227 static int s3c2440_cpufreq_calctable(struct s3c_cpufreq_config *cfg,
228 struct cpufreq_frequency_table *table,
229 size_t table_size)
231 int ret;
233 WARN_ON(cfg->info == NULL);
234 WARN_ON(cfg->board == NULL);
236 ret = run_freq_for(cfg->info->max.hclk,
237 cfg->info->max.fclk,
238 hclk_divs,
239 table, table_size);
241 s3c_freq_dbg("%s: returning %d\n", __func__, ret);
243 return ret;
246 struct s3c_cpufreq_info s3c2440_cpufreq_info = {
247 .max = {
248 .fclk = 400000000,
249 .hclk = 133333333,
250 .pclk = 66666666,
253 .locktime_m = 300,
254 .locktime_u = 300,
255 .locktime_bits = 16,
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,
263 .set_refresh = s3c2410_cpufreq_setrefresh,
264 .set_divs = s3c2440_cpufreq_setdivs,
265 .calc_divs = s3c2440_cpufreq_calcdivs,
266 .calc_freqtable = s3c2440_cpufreq_calctable,
268 .resume_clocks = s3c244x_setup_clocks,
270 .debug_io_show = s3c_cpufreq_debugfs_call(s3c2410_iotiming_debugfs),
273 static int s3c2440_cpufreq_add(struct sys_device *sysdev)
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");
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;
285 return s3c_cpufreq_register(&s3c2440_cpufreq_info);
288 static struct sysdev_driver s3c2440_cpufreq_driver = {
289 .add = s3c2440_cpufreq_add,
292 static int s3c2440_cpufreq_init(void)
294 return sysdev_driver_register(&s3c2440_sysclass,
295 &s3c2440_cpufreq_driver);
298 /* arch_initcall adds the clocks we need, so use subsys_initcall. */
299 subsys_initcall(s3c2440_cpufreq_init);
301 static struct sysdev_driver s3c2442_cpufreq_driver = {
302 .add = s3c2440_cpufreq_add,
305 static int s3c2442_cpufreq_init(void)
307 return sysdev_driver_register(&s3c2442_sysclass,
308 &s3c2442_cpufreq_driver);
311 subsys_initcall(s3c2442_cpufreq_init);