Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
s3c2440-cpufreq.c
Go to the documentation of this file.
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 <[email protected]>
6  * Vincent Sanders <[email protected]>
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 
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 
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 
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,
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 
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 
271 };
272 
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 
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 
309 static int s3c2442_cpufreq_init(void)
310 {
311  return subsys_interface_register(&s3c2442_cpufreq_interface);
312 }
313 
314 subsys_initcall(s3c2442_cpufreq_init);