blob: 5830ae3ddd19d721d722e00c03ed417e19ff3320 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/* 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>
Russell Kingd052d1b2005-10-29 19:07:23 +010035#include <linux/platform_device.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070036#include <linux/sysdev.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070037#include <linux/interrupt.h>
38#include <linux/ioport.h>
Russell Kingf8ce2542006-01-07 16:15:52 +000039#include <linux/clk.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070040
41#include <asm/hardware.h>
42#include <asm/atomic.h>
43#include <asm/irq.h>
44#include <asm/io.h>
45
Linus Torvalds1da177e2005-04-16 15:20:36 -070046#include <asm/arch/regs-clock.h>
47
48#include "clock.h"
49#include "cpu.h"
50
51/* clock information */
52
53static LIST_HEAD(clocks);
54static DECLARE_MUTEX(clocks_sem);
55
56/* old functions */
57
58void inline s3c24xx_clk_enable(unsigned int clocks, unsigned int enable)
59{
60 unsigned long clkcon;
61 unsigned long flags;
62
63 local_irq_save(flags);
64
65 clkcon = __raw_readl(S3C2410_CLKCON);
66 clkcon &= ~clocks;
67
68 if (enable)
69 clkcon |= clocks;
70
71 /* ensure none of the special function bits set */
72 clkcon &= ~(S3C2410_CLKCON_IDLE|S3C2410_CLKCON_POWER);
73
74 __raw_writel(clkcon, S3C2410_CLKCON);
75
76 local_irq_restore(flags);
77}
78
79/* enable and disable calls for use with the clk struct */
80
81static int clk_null_enable(struct clk *clk, int enable)
82{
83 return 0;
84}
85
86int s3c24xx_clkcon_enable(struct clk *clk, int enable)
87{
88 s3c24xx_clk_enable(clk->ctrlbit, enable);
89 return 0;
90}
91
92/* Clock API calls */
93
94struct clk *clk_get(struct device *dev, const char *id)
95{
96 struct clk *p;
97 struct clk *clk = ERR_PTR(-ENOENT);
98 int idno;
99
Ben Dooksc086f282005-10-18 07:51:34 +0100100 if (dev == NULL || dev->bus != &platform_bus_type)
101 idno = -1;
102 else
103 idno = to_platform_device(dev)->id;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700104
105 down(&clocks_sem);
106
107 list_for_each_entry(p, &clocks, list) {
108 if (p->id == idno &&
109 strcmp(id, p->name) == 0 &&
110 try_module_get(p->owner)) {
111 clk = p;
112 break;
113 }
114 }
115
116 /* check for the case where a device was supplied, but the
117 * clock that was being searched for is not device specific */
118
119 if (IS_ERR(clk)) {
120 list_for_each_entry(p, &clocks, list) {
121 if (p->id == -1 && strcmp(id, p->name) == 0 &&
122 try_module_get(p->owner)) {
123 clk = p;
124 break;
125 }
126 }
127 }
128
129 up(&clocks_sem);
130 return clk;
131}
132
133void clk_put(struct clk *clk)
134{
135 module_put(clk->owner);
136}
137
138int clk_enable(struct clk *clk)
139{
140 if (IS_ERR(clk))
141 return -EINVAL;
142
143 return (clk->enable)(clk, 1);
144}
145
146void clk_disable(struct clk *clk)
147{
148 if (!IS_ERR(clk))
149 (clk->enable)(clk, 0);
150}
151
152
Linus Torvalds1da177e2005-04-16 15:20:36 -0700153unsigned long clk_get_rate(struct clk *clk)
154{
155 if (IS_ERR(clk))
156 return 0;
157
158 if (clk->rate != 0)
159 return clk->rate;
160
161 while (clk->parent != NULL && clk->rate == 0)
162 clk = clk->parent;
163
164 return clk->rate;
165}
166
167long clk_round_rate(struct clk *clk, unsigned long rate)
168{
169 return rate;
170}
171
172int clk_set_rate(struct clk *clk, unsigned long rate)
173{
174 return -EINVAL;
175}
176
177struct clk *clk_get_parent(struct clk *clk)
178{
179 return clk->parent;
180}
181
182EXPORT_SYMBOL(clk_get);
183EXPORT_SYMBOL(clk_put);
184EXPORT_SYMBOL(clk_enable);
185EXPORT_SYMBOL(clk_disable);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700186EXPORT_SYMBOL(clk_get_rate);
187EXPORT_SYMBOL(clk_round_rate);
188EXPORT_SYMBOL(clk_set_rate);
189EXPORT_SYMBOL(clk_get_parent);
190
191/* base clocks */
192
193static struct clk clk_xtal = {
194 .name = "xtal",
195 .id = -1,
196 .rate = 0,
197 .parent = NULL,
198 .ctrlbit = 0,
199};
200
201static struct clk clk_f = {
202 .name = "fclk",
203 .id = -1,
204 .rate = 0,
205 .parent = NULL,
206 .ctrlbit = 0,
207};
208
209static struct clk clk_h = {
210 .name = "hclk",
211 .id = -1,
212 .rate = 0,
213 .parent = NULL,
214 .ctrlbit = 0,
215};
216
217static struct clk clk_p = {
218 .name = "pclk",
219 .id = -1,
220 .rate = 0,
221 .parent = NULL,
222 .ctrlbit = 0,
223};
224
225/* clocks that could be registered by external code */
226
227struct clk s3c24xx_dclk0 = {
228 .name = "dclk0",
229 .id = -1,
230};
231
232struct clk s3c24xx_dclk1 = {
233 .name = "dclk1",
234 .id = -1,
235};
236
237struct clk s3c24xx_clkout0 = {
238 .name = "clkout0",
239 .id = -1,
240};
241
242struct clk s3c24xx_clkout1 = {
243 .name = "clkout1",
244 .id = -1,
245};
246
247struct clk s3c24xx_uclk = {
248 .name = "uclk",
249 .id = -1,
250};
251
252
253/* clock definitions */
254
255static struct clk init_clocks[] = {
256 { .name = "nand",
257 .id = -1,
258 .parent = &clk_h,
259 .enable = s3c24xx_clkcon_enable,
260 .ctrlbit = S3C2410_CLKCON_NAND
261 },
262 { .name = "lcd",
263 .id = -1,
264 .parent = &clk_h,
265 .enable = s3c24xx_clkcon_enable,
266 .ctrlbit = S3C2410_CLKCON_LCDC
267 },
268 { .name = "usb-host",
269 .id = -1,
270 .parent = &clk_h,
271 .enable = s3c24xx_clkcon_enable,
272 .ctrlbit = S3C2410_CLKCON_USBH
273 },
274 { .name = "usb-device",
275 .id = -1,
276 .parent = &clk_h,
277 .enable = s3c24xx_clkcon_enable,
278 .ctrlbit = S3C2410_CLKCON_USBD
279 },
280 { .name = "timers",
281 .id = -1,
282 .parent = &clk_p,
283 .enable = s3c24xx_clkcon_enable,
284 .ctrlbit = S3C2410_CLKCON_PWMT
285 },
286 { .name = "sdi",
287 .id = -1,
288 .parent = &clk_p,
289 .enable = s3c24xx_clkcon_enable,
290 .ctrlbit = S3C2410_CLKCON_SDI
291 },
292 { .name = "uart",
293 .id = 0,
294 .parent = &clk_p,
295 .enable = s3c24xx_clkcon_enable,
296 .ctrlbit = S3C2410_CLKCON_UART0
297 },
298 { .name = "uart",
299 .id = 1,
300 .parent = &clk_p,
301 .enable = s3c24xx_clkcon_enable,
302 .ctrlbit = S3C2410_CLKCON_UART1
303 },
304 { .name = "uart",
305 .id = 2,
306 .parent = &clk_p,
307 .enable = s3c24xx_clkcon_enable,
308 .ctrlbit = S3C2410_CLKCON_UART2
309 },
310 { .name = "gpio",
311 .id = -1,
312 .parent = &clk_p,
313 .enable = s3c24xx_clkcon_enable,
314 .ctrlbit = S3C2410_CLKCON_GPIO
315 },
316 { .name = "rtc",
317 .id = -1,
318 .parent = &clk_p,
319 .enable = s3c24xx_clkcon_enable,
320 .ctrlbit = S3C2410_CLKCON_RTC
321 },
322 { .name = "adc",
323 .id = -1,
324 .parent = &clk_p,
325 .enable = s3c24xx_clkcon_enable,
326 .ctrlbit = S3C2410_CLKCON_ADC
327 },
328 { .name = "i2c",
329 .id = -1,
330 .parent = &clk_p,
331 .enable = s3c24xx_clkcon_enable,
332 .ctrlbit = S3C2410_CLKCON_IIC
333 },
334 { .name = "iis",
335 .id = -1,
336 .parent = &clk_p,
337 .enable = s3c24xx_clkcon_enable,
338 .ctrlbit = S3C2410_CLKCON_IIS
339 },
340 { .name = "spi",
341 .id = -1,
342 .parent = &clk_p,
343 .enable = s3c24xx_clkcon_enable,
344 .ctrlbit = S3C2410_CLKCON_SPI
345 },
346 { .name = "watchdog",
347 .id = -1,
348 .parent = &clk_p,
349 .ctrlbit = 0
350 }
351};
352
353/* initialise the clock system */
354
355int s3c24xx_register_clock(struct clk *clk)
356{
357 clk->owner = THIS_MODULE;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700358
359 if (clk->enable == NULL)
360 clk->enable = clk_null_enable;
361
362 /* add to the list of available clocks */
363
364 down(&clocks_sem);
365 list_add(&clk->list, &clocks);
366 up(&clocks_sem);
367
368 return 0;
369}
370
371/* initalise all the clocks */
372
373int __init s3c24xx_setup_clocks(unsigned long xtal,
374 unsigned long fclk,
375 unsigned long hclk,
376 unsigned long pclk)
377{
Ben Dooksd6b0bf22005-08-29 22:46:30 +0100378 unsigned long clkslow = __raw_readl(S3C2410_CLKSLOW);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700379 struct clk *clkp = init_clocks;
380 int ptr;
381 int ret;
382
383 printk(KERN_INFO "S3C2410 Clocks, (c) 2004 Simtec Electronics\n");
384
385 /* initialise the main system clocks */
386
387 clk_xtal.rate = xtal;
388
389 clk_h.rate = hclk;
390 clk_p.rate = pclk;
391 clk_f.rate = fclk;
392
393 /* it looks like just setting the register here is not good
394 * enough, and causes the odd hang at initial boot time, so
395 * do all of them indivdually.
396 *
397 * I think disabling the LCD clock if the LCD is active is
398 * very dangerous, and therefore the bootloader should be
399 * careful to not enable the LCD clock if it is not needed.
400 *
401 * and of course, this looks neater
402 */
403
404 s3c24xx_clk_enable(S3C2410_CLKCON_NAND, 0);
405 s3c24xx_clk_enable(S3C2410_CLKCON_USBH, 0);
406 s3c24xx_clk_enable(S3C2410_CLKCON_USBD, 0);
407 s3c24xx_clk_enable(S3C2410_CLKCON_ADC, 0);
408 s3c24xx_clk_enable(S3C2410_CLKCON_IIC, 0);
409 s3c24xx_clk_enable(S3C2410_CLKCON_SPI, 0);
410
411 /* assume uart clocks are correctly setup */
412
413 /* register our clocks */
414
415 if (s3c24xx_register_clock(&clk_xtal) < 0)
416 printk(KERN_ERR "failed to register master xtal\n");
417
418 if (s3c24xx_register_clock(&clk_f) < 0)
419 printk(KERN_ERR "failed to register cpu fclk\n");
420
421 if (s3c24xx_register_clock(&clk_h) < 0)
422 printk(KERN_ERR "failed to register cpu hclk\n");
423
424 if (s3c24xx_register_clock(&clk_p) < 0)
425 printk(KERN_ERR "failed to register cpu pclk\n");
426
427 /* register clocks from clock array */
428
429 for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) {
430 ret = s3c24xx_register_clock(clkp);
431 if (ret < 0) {
432 printk(KERN_ERR "Failed to register clock %s (%d)\n",
433 clkp->name, ret);
434 }
435 }
436
Ben Dooksd6b0bf22005-08-29 22:46:30 +0100437 /* show the clock-slow value */
438
439 printk("CLOCK: Slow mode (%ld.%ld MHz), %s, MPLL %s, UPLL %s\n",
440 print_mhz(xtal / ( 2 * S3C2410_CLKSLOW_GET_SLOWVAL(clkslow))),
441 (clkslow & S3C2410_CLKSLOW_SLOW) ? "slow" : "fast",
442 (clkslow & S3C2410_CLKSLOW_MPLL_OFF) ? "off" : "on",
443 (clkslow & S3C2410_CLKSLOW_UCLK_OFF) ? "off" : "on");
444
Linus Torvalds1da177e2005-04-16 15:20:36 -0700445 return 0;
446}