diff options
Diffstat (limited to 'arch/arm/mach-s3c2410/clock.c')
-rw-r--r-- | arch/arm/mach-s3c2410/clock.c | 507 |
1 files changed, 507 insertions, 0 deletions
diff --git a/arch/arm/mach-s3c2410/clock.c b/arch/arm/mach-s3c2410/clock.c new file mode 100644 index 000000000000..e23f534d4e1d --- /dev/null +++ b/arch/arm/mach-s3c2410/clock.c | |||
@@ -0,0 +1,507 @@ | |||
1 | /* linux/arch/arm/mach-s3c2410/clock.c | ||
2 | * | ||
3 | * Copyright (c) 2004-2005 Simtec Electronics | ||
4 | * Ben Dooks <ben@simtec.co.uk> | ||
5 | * | ||
6 | * S3C2410 Clock control support | ||
7 | * | ||
8 | * Based on, and code from linux/arch/arm/mach-versatile/clock.c | ||
9 | ** | ||
10 | ** Copyright (C) 2004 ARM Limited. | ||
11 | ** Written by Deep Blue Solutions Limited. | ||
12 | * | ||
13 | * | ||
14 | * This program is free software; you can redistribute it and/or modify | ||
15 | * it under the terms of the GNU General Public License as published by | ||
16 | * the Free Software Foundation; either version 2 of the License, or | ||
17 | * (at your option) any later version. | ||
18 | * | ||
19 | * This program is distributed in the hope that it will be useful, | ||
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
22 | * GNU General Public License for more details. | ||
23 | * | ||
24 | * You should have received a copy of the GNU General Public License | ||
25 | * along with this program; if not, write to the Free Software | ||
26 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
27 | */ | ||
28 | |||
29 | #include <linux/init.h> | ||
30 | #include <linux/module.h> | ||
31 | #include <linux/kernel.h> | ||
32 | #include <linux/list.h> | ||
33 | #include <linux/errno.h> | ||
34 | #include <linux/err.h> | ||
35 | #include <linux/device.h> | ||
36 | #include <linux/sysdev.h> | ||
37 | |||
38 | #include <linux/interrupt.h> | ||
39 | #include <linux/ioport.h> | ||
40 | |||
41 | #include <asm/hardware.h> | ||
42 | #include <asm/atomic.h> | ||
43 | #include <asm/irq.h> | ||
44 | #include <asm/io.h> | ||
45 | |||
46 | #include <asm/hardware/clock.h> | ||
47 | #include <asm/arch/regs-clock.h> | ||
48 | |||
49 | #include "clock.h" | ||
50 | #include "cpu.h" | ||
51 | |||
52 | /* clock information */ | ||
53 | |||
54 | static LIST_HEAD(clocks); | ||
55 | static DECLARE_MUTEX(clocks_sem); | ||
56 | |||
57 | /* old functions */ | ||
58 | |||
59 | void inline s3c24xx_clk_enable(unsigned int clocks, unsigned int enable) | ||
60 | { | ||
61 | unsigned long clkcon; | ||
62 | unsigned long flags; | ||
63 | |||
64 | local_irq_save(flags); | ||
65 | |||
66 | clkcon = __raw_readl(S3C2410_CLKCON); | ||
67 | clkcon &= ~clocks; | ||
68 | |||
69 | if (enable) | ||
70 | clkcon |= clocks; | ||
71 | |||
72 | /* ensure none of the special function bits set */ | ||
73 | clkcon &= ~(S3C2410_CLKCON_IDLE|S3C2410_CLKCON_POWER); | ||
74 | |||
75 | __raw_writel(clkcon, S3C2410_CLKCON); | ||
76 | |||
77 | local_irq_restore(flags); | ||
78 | } | ||
79 | |||
80 | /* enable and disable calls for use with the clk struct */ | ||
81 | |||
82 | static int clk_null_enable(struct clk *clk, int enable) | ||
83 | { | ||
84 | return 0; | ||
85 | } | ||
86 | |||
87 | int s3c24xx_clkcon_enable(struct clk *clk, int enable) | ||
88 | { | ||
89 | s3c24xx_clk_enable(clk->ctrlbit, enable); | ||
90 | return 0; | ||
91 | } | ||
92 | |||
93 | /* Clock API calls */ | ||
94 | |||
95 | struct clk *clk_get(struct device *dev, const char *id) | ||
96 | { | ||
97 | struct clk *p; | ||
98 | struct clk *clk = ERR_PTR(-ENOENT); | ||
99 | int idno; | ||
100 | |||
101 | idno = (dev == NULL) ? -1 : to_platform_device(dev)->id; | ||
102 | |||
103 | down(&clocks_sem); | ||
104 | |||
105 | list_for_each_entry(p, &clocks, list) { | ||
106 | if (p->id == idno && | ||
107 | strcmp(id, p->name) == 0 && | ||
108 | try_module_get(p->owner)) { | ||
109 | clk = p; | ||
110 | break; | ||
111 | } | ||
112 | } | ||
113 | |||
114 | /* check for the case where a device was supplied, but the | ||
115 | * clock that was being searched for is not device specific */ | ||
116 | |||
117 | if (IS_ERR(clk)) { | ||
118 | list_for_each_entry(p, &clocks, list) { | ||
119 | if (p->id == -1 && strcmp(id, p->name) == 0 && | ||
120 | try_module_get(p->owner)) { | ||
121 | clk = p; | ||
122 | break; | ||
123 | } | ||
124 | } | ||
125 | } | ||
126 | |||
127 | up(&clocks_sem); | ||
128 | return clk; | ||
129 | } | ||
130 | |||
131 | void clk_put(struct clk *clk) | ||
132 | { | ||
133 | module_put(clk->owner); | ||
134 | } | ||
135 | |||
136 | int clk_enable(struct clk *clk) | ||
137 | { | ||
138 | if (IS_ERR(clk)) | ||
139 | return -EINVAL; | ||
140 | |||
141 | return (clk->enable)(clk, 1); | ||
142 | } | ||
143 | |||
144 | void clk_disable(struct clk *clk) | ||
145 | { | ||
146 | if (!IS_ERR(clk)) | ||
147 | (clk->enable)(clk, 0); | ||
148 | } | ||
149 | |||
150 | |||
151 | int clk_use(struct clk *clk) | ||
152 | { | ||
153 | atomic_inc(&clk->used); | ||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | |||
158 | void clk_unuse(struct clk *clk) | ||
159 | { | ||
160 | atomic_dec(&clk->used); | ||
161 | } | ||
162 | |||
163 | unsigned long clk_get_rate(struct clk *clk) | ||
164 | { | ||
165 | if (IS_ERR(clk)) | ||
166 | return 0; | ||
167 | |||
168 | if (clk->rate != 0) | ||
169 | return clk->rate; | ||
170 | |||
171 | while (clk->parent != NULL && clk->rate == 0) | ||
172 | clk = clk->parent; | ||
173 | |||
174 | return clk->rate; | ||
175 | } | ||
176 | |||
177 | long clk_round_rate(struct clk *clk, unsigned long rate) | ||
178 | { | ||
179 | return rate; | ||
180 | } | ||
181 | |||
182 | int clk_set_rate(struct clk *clk, unsigned long rate) | ||
183 | { | ||
184 | return -EINVAL; | ||
185 | } | ||
186 | |||
187 | struct clk *clk_get_parent(struct clk *clk) | ||
188 | { | ||
189 | return clk->parent; | ||
190 | } | ||
191 | |||
192 | EXPORT_SYMBOL(clk_get); | ||
193 | EXPORT_SYMBOL(clk_put); | ||
194 | EXPORT_SYMBOL(clk_enable); | ||
195 | EXPORT_SYMBOL(clk_disable); | ||
196 | EXPORT_SYMBOL(clk_use); | ||
197 | EXPORT_SYMBOL(clk_unuse); | ||
198 | EXPORT_SYMBOL(clk_get_rate); | ||
199 | EXPORT_SYMBOL(clk_round_rate); | ||
200 | EXPORT_SYMBOL(clk_set_rate); | ||
201 | EXPORT_SYMBOL(clk_get_parent); | ||
202 | |||
203 | /* base clocks */ | ||
204 | |||
205 | static struct clk clk_xtal = { | ||
206 | .name = "xtal", | ||
207 | .id = -1, | ||
208 | .rate = 0, | ||
209 | .parent = NULL, | ||
210 | .ctrlbit = 0, | ||
211 | }; | ||
212 | |||
213 | static struct clk clk_f = { | ||
214 | .name = "fclk", | ||
215 | .id = -1, | ||
216 | .rate = 0, | ||
217 | .parent = NULL, | ||
218 | .ctrlbit = 0, | ||
219 | }; | ||
220 | |||
221 | static struct clk clk_h = { | ||
222 | .name = "hclk", | ||
223 | .id = -1, | ||
224 | .rate = 0, | ||
225 | .parent = NULL, | ||
226 | .ctrlbit = 0, | ||
227 | }; | ||
228 | |||
229 | static struct clk clk_p = { | ||
230 | .name = "pclk", | ||
231 | .id = -1, | ||
232 | .rate = 0, | ||
233 | .parent = NULL, | ||
234 | .ctrlbit = 0, | ||
235 | }; | ||
236 | |||
237 | /* clocks that could be registered by external code */ | ||
238 | |||
239 | struct clk s3c24xx_dclk0 = { | ||
240 | .name = "dclk0", | ||
241 | .id = -1, | ||
242 | }; | ||
243 | |||
244 | struct clk s3c24xx_dclk1 = { | ||
245 | .name = "dclk1", | ||
246 | .id = -1, | ||
247 | }; | ||
248 | |||
249 | struct clk s3c24xx_clkout0 = { | ||
250 | .name = "clkout0", | ||
251 | .id = -1, | ||
252 | }; | ||
253 | |||
254 | struct clk s3c24xx_clkout1 = { | ||
255 | .name = "clkout1", | ||
256 | .id = -1, | ||
257 | }; | ||
258 | |||
259 | struct clk s3c24xx_uclk = { | ||
260 | .name = "uclk", | ||
261 | .id = -1, | ||
262 | }; | ||
263 | |||
264 | |||
265 | /* clock definitions */ | ||
266 | |||
267 | static struct clk init_clocks[] = { | ||
268 | { .name = "nand", | ||
269 | .id = -1, | ||
270 | .parent = &clk_h, | ||
271 | .enable = s3c24xx_clkcon_enable, | ||
272 | .ctrlbit = S3C2410_CLKCON_NAND | ||
273 | }, | ||
274 | { .name = "lcd", | ||
275 | .id = -1, | ||
276 | .parent = &clk_h, | ||
277 | .enable = s3c24xx_clkcon_enable, | ||
278 | .ctrlbit = S3C2410_CLKCON_LCDC | ||
279 | }, | ||
280 | { .name = "usb-host", | ||
281 | .id = -1, | ||
282 | .parent = &clk_h, | ||
283 | .enable = s3c24xx_clkcon_enable, | ||
284 | .ctrlbit = S3C2410_CLKCON_USBH | ||
285 | }, | ||
286 | { .name = "usb-device", | ||
287 | .id = -1, | ||
288 | .parent = &clk_h, | ||
289 | .enable = s3c24xx_clkcon_enable, | ||
290 | .ctrlbit = S3C2410_CLKCON_USBD | ||
291 | }, | ||
292 | { .name = "timers", | ||
293 | .id = -1, | ||
294 | .parent = &clk_p, | ||
295 | .enable = s3c24xx_clkcon_enable, | ||
296 | .ctrlbit = S3C2410_CLKCON_PWMT | ||
297 | }, | ||
298 | { .name = "sdi", | ||
299 | .id = -1, | ||
300 | .parent = &clk_p, | ||
301 | .enable = s3c24xx_clkcon_enable, | ||
302 | .ctrlbit = S3C2410_CLKCON_SDI | ||
303 | }, | ||
304 | { .name = "uart", | ||
305 | .id = 0, | ||
306 | .parent = &clk_p, | ||
307 | .enable = s3c24xx_clkcon_enable, | ||
308 | .ctrlbit = S3C2410_CLKCON_UART0 | ||
309 | }, | ||
310 | { .name = "uart", | ||
311 | .id = 1, | ||
312 | .parent = &clk_p, | ||
313 | .enable = s3c24xx_clkcon_enable, | ||
314 | .ctrlbit = S3C2410_CLKCON_UART1 | ||
315 | }, | ||
316 | { .name = "uart", | ||
317 | .id = 2, | ||
318 | .parent = &clk_p, | ||
319 | .enable = s3c24xx_clkcon_enable, | ||
320 | .ctrlbit = S3C2410_CLKCON_UART2 | ||
321 | }, | ||
322 | { .name = "gpio", | ||
323 | .id = -1, | ||
324 | .parent = &clk_p, | ||
325 | .enable = s3c24xx_clkcon_enable, | ||
326 | .ctrlbit = S3C2410_CLKCON_GPIO | ||
327 | }, | ||
328 | { .name = "rtc", | ||
329 | .id = -1, | ||
330 | .parent = &clk_p, | ||
331 | .enable = s3c24xx_clkcon_enable, | ||
332 | .ctrlbit = S3C2410_CLKCON_RTC | ||
333 | }, | ||
334 | { .name = "adc", | ||
335 | .id = -1, | ||
336 | .parent = &clk_p, | ||
337 | .enable = s3c24xx_clkcon_enable, | ||
338 | .ctrlbit = S3C2410_CLKCON_ADC | ||
339 | }, | ||
340 | { .name = "i2c", | ||
341 | .id = -1, | ||
342 | .parent = &clk_p, | ||
343 | .enable = s3c24xx_clkcon_enable, | ||
344 | .ctrlbit = S3C2410_CLKCON_IIC | ||
345 | }, | ||
346 | { .name = "iis", | ||
347 | .id = -1, | ||
348 | .parent = &clk_p, | ||
349 | .enable = s3c24xx_clkcon_enable, | ||
350 | .ctrlbit = S3C2410_CLKCON_IIS | ||
351 | }, | ||
352 | { .name = "spi", | ||
353 | .id = -1, | ||
354 | .parent = &clk_p, | ||
355 | .enable = s3c24xx_clkcon_enable, | ||
356 | .ctrlbit = S3C2410_CLKCON_SPI | ||
357 | }, | ||
358 | { .name = "watchdog", | ||
359 | .id = -1, | ||
360 | .parent = &clk_p, | ||
361 | .ctrlbit = 0 | ||
362 | } | ||
363 | }; | ||
364 | |||
365 | /* initialise the clock system */ | ||
366 | |||
367 | int s3c24xx_register_clock(struct clk *clk) | ||
368 | { | ||
369 | clk->owner = THIS_MODULE; | ||
370 | atomic_set(&clk->used, 0); | ||
371 | |||
372 | if (clk->enable == NULL) | ||
373 | clk->enable = clk_null_enable; | ||
374 | |||
375 | /* add to the list of available clocks */ | ||
376 | |||
377 | down(&clocks_sem); | ||
378 | list_add(&clk->list, &clocks); | ||
379 | up(&clocks_sem); | ||
380 | |||
381 | return 0; | ||
382 | } | ||
383 | |||
384 | /* initalise all the clocks */ | ||
385 | |||
386 | int __init s3c24xx_setup_clocks(unsigned long xtal, | ||
387 | unsigned long fclk, | ||
388 | unsigned long hclk, | ||
389 | unsigned long pclk) | ||
390 | { | ||
391 | struct clk *clkp = init_clocks; | ||
392 | int ptr; | ||
393 | int ret; | ||
394 | |||
395 | printk(KERN_INFO "S3C2410 Clocks, (c) 2004 Simtec Electronics\n"); | ||
396 | |||
397 | /* initialise the main system clocks */ | ||
398 | |||
399 | clk_xtal.rate = xtal; | ||
400 | |||
401 | clk_h.rate = hclk; | ||
402 | clk_p.rate = pclk; | ||
403 | clk_f.rate = fclk; | ||
404 | |||
405 | /* it looks like just setting the register here is not good | ||
406 | * enough, and causes the odd hang at initial boot time, so | ||
407 | * do all of them indivdually. | ||
408 | * | ||
409 | * I think disabling the LCD clock if the LCD is active is | ||
410 | * very dangerous, and therefore the bootloader should be | ||
411 | * careful to not enable the LCD clock if it is not needed. | ||
412 | * | ||
413 | * and of course, this looks neater | ||
414 | */ | ||
415 | |||
416 | s3c24xx_clk_enable(S3C2410_CLKCON_NAND, 0); | ||
417 | s3c24xx_clk_enable(S3C2410_CLKCON_USBH, 0); | ||
418 | s3c24xx_clk_enable(S3C2410_CLKCON_USBD, 0); | ||
419 | s3c24xx_clk_enable(S3C2410_CLKCON_ADC, 0); | ||
420 | s3c24xx_clk_enable(S3C2410_CLKCON_IIC, 0); | ||
421 | s3c24xx_clk_enable(S3C2410_CLKCON_SPI, 0); | ||
422 | |||
423 | /* assume uart clocks are correctly setup */ | ||
424 | |||
425 | /* register our clocks */ | ||
426 | |||
427 | if (s3c24xx_register_clock(&clk_xtal) < 0) | ||
428 | printk(KERN_ERR "failed to register master xtal\n"); | ||
429 | |||
430 | if (s3c24xx_register_clock(&clk_f) < 0) | ||
431 | printk(KERN_ERR "failed to register cpu fclk\n"); | ||
432 | |||
433 | if (s3c24xx_register_clock(&clk_h) < 0) | ||
434 | printk(KERN_ERR "failed to register cpu hclk\n"); | ||
435 | |||
436 | if (s3c24xx_register_clock(&clk_p) < 0) | ||
437 | printk(KERN_ERR "failed to register cpu pclk\n"); | ||
438 | |||
439 | /* register clocks from clock array */ | ||
440 | |||
441 | for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) { | ||
442 | ret = s3c24xx_register_clock(clkp); | ||
443 | if (ret < 0) { | ||
444 | printk(KERN_ERR "Failed to register clock %s (%d)\n", | ||
445 | clkp->name, ret); | ||
446 | } | ||
447 | } | ||
448 | |||
449 | return 0; | ||
450 | } | ||
451 | |||
452 | /* S3C2440 extended clock support */ | ||
453 | |||
454 | #ifdef CONFIG_CPU_S3C2440 | ||
455 | |||
456 | static struct clk s3c2440_clk_upll = { | ||
457 | .name = "upll", | ||
458 | .id = -1, | ||
459 | }; | ||
460 | |||
461 | static struct clk s3c2440_clk_cam = { | ||
462 | .name = "camif", | ||
463 | .parent = &clk_h, | ||
464 | .id = -1, | ||
465 | .enable = s3c24xx_clkcon_enable, | ||
466 | .ctrlbit = S3C2440_CLKCON_CAMERA, | ||
467 | }; | ||
468 | |||
469 | static struct clk s3c2440_clk_ac97 = { | ||
470 | .name = "ac97", | ||
471 | .parent = &clk_p, | ||
472 | .id = -1, | ||
473 | .enable = s3c24xx_clkcon_enable, | ||
474 | .ctrlbit = S3C2440_CLKCON_CAMERA, | ||
475 | }; | ||
476 | |||
477 | static int s3c2440_clk_add(struct sys_device *sysdev) | ||
478 | { | ||
479 | unsigned long upllcon = __raw_readl(S3C2410_UPLLCON); | ||
480 | |||
481 | s3c2440_clk_upll.rate = s3c2410_get_pll(upllcon, clk_xtal.rate) * 2; | ||
482 | |||
483 | printk("S3C2440: Clock Support, UPLL %ld.%03ld MHz\n", | ||
484 | print_mhz(s3c2440_clk_upll.rate)); | ||
485 | |||
486 | s3c24xx_register_clock(&s3c2440_clk_ac97); | ||
487 | s3c24xx_register_clock(&s3c2440_clk_cam); | ||
488 | s3c24xx_register_clock(&s3c2440_clk_upll); | ||
489 | |||
490 | clk_disable(&s3c2440_clk_ac97); | ||
491 | clk_disable(&s3c2440_clk_cam); | ||
492 | |||
493 | return 0; | ||
494 | } | ||
495 | |||
496 | static struct sysdev_driver s3c2440_clk_driver = { | ||
497 | .add = s3c2440_clk_add, | ||
498 | }; | ||
499 | |||
500 | static int s3c24xx_clk_driver(void) | ||
501 | { | ||
502 | return sysdev_driver_register(&s3c2440_sysclass, &s3c2440_clk_driver); | ||
503 | } | ||
504 | |||
505 | arch_initcall(s3c24xx_clk_driver); | ||
506 | |||
507 | #endif /* CONFIG_CPU_S3C2440 */ | ||