X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=arch%2Farm%2Fmach-s3c2410%2Fclock.c;h=6de713ad319a1c798c3444da58c2f545d933fb37;hb=43bc926fffd92024b46cafaf7350d669ba9ca884;hp=8d986b8401c2474f3948bb3a4301c26a9784b7f1;hpb=cee37fe97739d85991964371c1f3a745c00dd236;p=linux-2.6.git diff --git a/arch/arm/mach-s3c2410/clock.c b/arch/arm/mach-s3c2410/clock.c index 8d986b840..6de713ad3 100644 --- a/arch/arm/mach-s3c2410/clock.c +++ b/arch/arm/mach-s3c2410/clock.c @@ -32,19 +32,20 @@ #include #include #include -#include +#include #include - #include #include +#include +#include +#include #include -#include #include #include -#include #include +#include #include "clock.h" #include "cpu.h" @@ -52,29 +53,26 @@ /* clock information */ static LIST_HEAD(clocks); -static DECLARE_MUTEX(clocks_sem); + +DEFINE_MUTEX(clocks_mutex); /* old functions */ void inline s3c24xx_clk_enable(unsigned int clocks, unsigned int enable) { unsigned long clkcon; - unsigned long flags; - - local_irq_save(flags); clkcon = __raw_readl(S3C2410_CLKCON); - clkcon &= ~clocks; if (enable) clkcon |= clocks; + else + clkcon &= ~clocks; /* ensure none of the special function bits set */ clkcon &= ~(S3C2410_CLKCON_IDLE|S3C2410_CLKCON_POWER); __raw_writel(clkcon, S3C2410_CLKCON); - - local_irq_restore(flags); } /* enable and disable calls for use with the clk struct */ @@ -98,9 +96,12 @@ struct clk *clk_get(struct device *dev, const char *id) struct clk *clk = ERR_PTR(-ENOENT); int idno; - idno = (dev == NULL) ? -1 : to_platform_device(dev)->id; + if (dev == NULL || dev->bus != &platform_bus_type) + idno = -1; + else + idno = to_platform_device(dev)->id; - down(&clocks_sem); + mutex_lock(&clocks_mutex); list_for_each_entry(p, &clocks, list) { if (p->id == idno && @@ -124,7 +125,7 @@ struct clk *clk_get(struct device *dev, const char *id) } } - up(&clocks_sem); + mutex_unlock(&clocks_mutex); return clk; } @@ -135,31 +136,35 @@ void clk_put(struct clk *clk) int clk_enable(struct clk *clk) { - if (IS_ERR(clk)) + if (IS_ERR(clk) || clk == NULL) return -EINVAL; - return (clk->enable)(clk, 1); -} + clk_enable(clk->parent); -void clk_disable(struct clk *clk) -{ - if (!IS_ERR(clk)) - (clk->enable)(clk, 0); -} + mutex_lock(&clocks_mutex); + if ((clk->usage++) == 0) + (clk->enable)(clk, 1); -int clk_use(struct clk *clk) -{ - atomic_inc(&clk->used); + mutex_unlock(&clocks_mutex); return 0; } - -void clk_unuse(struct clk *clk) +void clk_disable(struct clk *clk) { - atomic_dec(&clk->used); + if (IS_ERR(clk) || clk == NULL) + return; + + mutex_lock(&clocks_mutex); + + if ((--clk->usage) == 0) + (clk->enable)(clk, 0); + + mutex_unlock(&clocks_mutex); + clk_disable(clk->parent); } + unsigned long clk_get_rate(struct clk *clk) { if (IS_ERR(clk)) @@ -176,12 +181,24 @@ unsigned long clk_get_rate(struct clk *clk) long clk_round_rate(struct clk *clk, unsigned long rate) { + if (!IS_ERR(clk) && clk->round_rate) + return (clk->round_rate)(clk, rate); + return rate; } int clk_set_rate(struct clk *clk, unsigned long rate) { - return -EINVAL; + int ret; + + if (IS_ERR(clk)) + return -EINVAL; + + mutex_lock(&clocks_mutex); + ret = (clk->set_rate)(clk, rate); + mutex_unlock(&clocks_mutex); + + return ret; } struct clk *clk_get_parent(struct clk *clk) @@ -189,16 +206,54 @@ struct clk *clk_get_parent(struct clk *clk) return clk->parent; } +int clk_set_parent(struct clk *clk, struct clk *parent) +{ + int ret = 0; + + if (IS_ERR(clk)) + return -EINVAL; + + mutex_lock(&clocks_mutex); + + if (clk->set_parent) + ret = (clk->set_parent)(clk, parent); + + mutex_unlock(&clocks_mutex); + + return ret; +} + EXPORT_SYMBOL(clk_get); EXPORT_SYMBOL(clk_put); EXPORT_SYMBOL(clk_enable); EXPORT_SYMBOL(clk_disable); -EXPORT_SYMBOL(clk_use); -EXPORT_SYMBOL(clk_unuse); EXPORT_SYMBOL(clk_get_rate); EXPORT_SYMBOL(clk_round_rate); EXPORT_SYMBOL(clk_set_rate); EXPORT_SYMBOL(clk_get_parent); +EXPORT_SYMBOL(clk_set_parent); + +/* base clock enable */ + +static int s3c24xx_upll_enable(struct clk *clk, int enable) +{ + unsigned long clkslow = __raw_readl(S3C2410_CLKSLOW); + unsigned long orig = clkslow; + + if (enable) + clkslow &= ~S3C2410_CLKSLOW_UCLK_OFF; + else + clkslow |= S3C2410_CLKSLOW_UCLK_OFF; + + __raw_writel(clkslow, S3C2410_CLKSLOW); + + /* if we started the UPLL, then allow to settle */ + + if (enable && (orig & S3C2410_CLKSLOW_UCLK_OFF)) + udelay(200); + + return 0; +} /* base clocks */ @@ -210,6 +265,14 @@ static struct clk clk_xtal = { .ctrlbit = 0, }; +static struct clk clk_upll = { + .name = "upll", + .id = -1, + .parent = NULL, + .enable = s3c24xx_upll_enable, + .ctrlbit = 0, +}; + static struct clk clk_f = { .name = "fclk", .id = -1, @@ -234,26 +297,128 @@ static struct clk clk_p = { .ctrlbit = 0, }; +struct clk clk_usb_bus = { + .name = "usb-bus", + .id = -1, + .rate = 0, + .parent = &clk_upll, +}; + /* clocks that could be registered by external code */ +static int s3c24xx_dclk_enable(struct clk *clk, int enable) +{ + unsigned long dclkcon = __raw_readl(S3C2410_DCLKCON); + + if (enable) + dclkcon |= clk->ctrlbit; + else + dclkcon &= ~clk->ctrlbit; + + __raw_writel(dclkcon, S3C2410_DCLKCON); + + return 0; +} + +static int s3c24xx_dclk_setparent(struct clk *clk, struct clk *parent) +{ + unsigned long dclkcon; + unsigned int uclk; + + if (parent == &clk_upll) + uclk = 1; + else if (parent == &clk_p) + uclk = 0; + else + return -EINVAL; + + clk->parent = parent; + + dclkcon = __raw_readl(S3C2410_DCLKCON); + + if (clk->ctrlbit == S3C2410_DCLKCON_DCLK0EN) { + if (uclk) + dclkcon |= S3C2410_DCLKCON_DCLK0_UCLK; + else + dclkcon &= ~S3C2410_DCLKCON_DCLK0_UCLK; + } else { + if (uclk) + dclkcon |= S3C2410_DCLKCON_DCLK1_UCLK; + else + dclkcon &= ~S3C2410_DCLKCON_DCLK1_UCLK; + } + + __raw_writel(dclkcon, S3C2410_DCLKCON); + + return 0; +} + + +static int s3c24xx_clkout_setparent(struct clk *clk, struct clk *parent) +{ + unsigned long mask; + unsigned long source; + + /* calculate the MISCCR setting for the clock */ + + if (parent == &clk_xtal) + source = S3C2410_MISCCR_CLK0_MPLL; + else if (parent == &clk_upll) + source = S3C2410_MISCCR_CLK0_UPLL; + else if (parent == &clk_f) + source = S3C2410_MISCCR_CLK0_FCLK; + else if (parent == &clk_h) + source = S3C2410_MISCCR_CLK0_HCLK; + else if (parent == &clk_p) + source = S3C2410_MISCCR_CLK0_PCLK; + else if (clk == &s3c24xx_clkout0 && parent == &s3c24xx_dclk0) + source = S3C2410_MISCCR_CLK0_DCLK0; + else if (clk == &s3c24xx_clkout1 && parent == &s3c24xx_dclk1) + source = S3C2410_MISCCR_CLK0_DCLK0; + else + return -EINVAL; + + clk->parent = parent; + + if (clk == &s3c24xx_dclk0) + mask = S3C2410_MISCCR_CLK0_MASK; + else { + source <<= 4; + mask = S3C2410_MISCCR_CLK1_MASK; + } + + s3c2410_modify_misccr(mask, source); + return 0; +} + +/* external clock definitions */ + struct clk s3c24xx_dclk0 = { .name = "dclk0", .id = -1, + .ctrlbit = S3C2410_DCLKCON_DCLK0EN, + .enable = s3c24xx_dclk_enable, + .set_parent = s3c24xx_dclk_setparent, }; struct clk s3c24xx_dclk1 = { .name = "dclk1", .id = -1, + .ctrlbit = S3C2410_DCLKCON_DCLK0EN, + .enable = s3c24xx_dclk_enable, + .set_parent = s3c24xx_dclk_setparent, }; struct clk s3c24xx_clkout0 = { .name = "clkout0", .id = -1, + .set_parent = s3c24xx_clkout_setparent, }; struct clk s3c24xx_clkout1 = { .name = "clkout1", .id = -1, + .set_parent = s3c24xx_clkout_setparent, }; struct clk s3c24xx_uclk = { @@ -262,103 +427,104 @@ struct clk s3c24xx_uclk = { }; -/* clock definitions */ +/* standard clock definitions */ static struct clk init_clocks[] = { - { .name = "nand", - .id = -1, - .parent = &clk_h, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_NAND - }, - { .name = "lcd", - .id = -1, - .parent = &clk_h, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_LCDC - }, - { .name = "usb-host", - .id = -1, - .parent = &clk_h, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_USBH - }, - { .name = "usb-device", - .id = -1, - .parent = &clk_h, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_USBD - }, - { .name = "timers", - .id = -1, - .parent = &clk_p, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_PWMT - }, - { .name = "sdi", - .id = -1, - .parent = &clk_p, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_SDI - }, - { .name = "uart", - .id = 0, - .parent = &clk_p, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_UART0 - }, - { .name = "uart", - .id = 1, - .parent = &clk_p, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_UART1 - }, - { .name = "uart", - .id = 2, - .parent = &clk_p, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_UART2 - }, - { .name = "gpio", - .id = -1, - .parent = &clk_p, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_GPIO - }, - { .name = "rtc", - .id = -1, - .parent = &clk_p, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_RTC - }, - { .name = "adc", - .id = -1, - .parent = &clk_p, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_ADC - }, - { .name = "i2c", - .id = -1, - .parent = &clk_p, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_IIC - }, - { .name = "iis", - .id = -1, - .parent = &clk_p, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_IIS - }, - { .name = "spi", - .id = -1, - .parent = &clk_p, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2410_CLKCON_SPI - }, - { .name = "watchdog", - .id = -1, - .parent = &clk_p, - .ctrlbit = 0 + { + .name = "nand", + .id = -1, + .parent = &clk_h, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_NAND, + }, { + .name = "lcd", + .id = -1, + .parent = &clk_h, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_LCDC, + }, { + .name = "usb-host", + .id = -1, + .parent = &clk_h, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_USBH, + }, { + .name = "usb-device", + .id = -1, + .parent = &clk_h, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_USBD, + }, { + .name = "timers", + .id = -1, + .parent = &clk_p, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_PWMT, + }, { + .name = "sdi", + .id = -1, + .parent = &clk_p, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_SDI, + }, { + .name = "uart", + .id = 0, + .parent = &clk_p, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_UART0, + }, { + .name = "uart", + .id = 1, + .parent = &clk_p, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_UART1, + }, { + .name = "uart", + .id = 2, + .parent = &clk_p, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_UART2, + }, { + .name = "gpio", + .id = -1, + .parent = &clk_p, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_GPIO, + }, { + .name = "rtc", + .id = -1, + .parent = &clk_p, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_RTC, + }, { + .name = "adc", + .id = -1, + .parent = &clk_p, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_ADC, + }, { + .name = "i2c", + .id = -1, + .parent = &clk_p, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_IIC, + }, { + .name = "iis", + .id = -1, + .parent = &clk_p, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_IIS, + }, { + .name = "spi", + .id = -1, + .parent = &clk_p, + .enable = s3c24xx_clkcon_enable, + .ctrlbit = S3C2410_CLKCON_SPI, + }, { + .name = "watchdog", + .id = -1, + .parent = &clk_p, + .ctrlbit = 0, } }; @@ -367,16 +533,23 @@ static struct clk init_clocks[] = { int s3c24xx_register_clock(struct clk *clk) { clk->owner = THIS_MODULE; - atomic_set(&clk->used, 0); if (clk->enable == NULL) clk->enable = clk_null_enable; + /* if this is a standard clock, set the usage state */ + + if (clk->ctrlbit && clk->enable == s3c24xx_clkcon_enable) { + unsigned long clkcon = __raw_readl(S3C2410_CLKCON); + + clk->usage = (clkcon & clk->ctrlbit) ? 1 : 0; + } + /* add to the list of available clocks */ - down(&clocks_sem); + mutex_lock(&clocks_mutex); list_add(&clk->list, &clocks); - up(&clocks_sem); + mutex_unlock(&clocks_mutex); return 0; } @@ -388,6 +561,8 @@ int __init s3c24xx_setup_clocks(unsigned long xtal, unsigned long hclk, unsigned long pclk) { + unsigned long upllcon = __raw_readl(S3C2410_UPLLCON); + unsigned long clkslow = __raw_readl(S3C2410_CLKSLOW); struct clk *clkp = init_clocks; int ptr; int ret; @@ -397,21 +572,23 @@ int __init s3c24xx_setup_clocks(unsigned long xtal, /* initialise the main system clocks */ clk_xtal.rate = xtal; + clk_upll.rate = s3c2410_get_pll(upllcon, xtal); clk_h.rate = hclk; clk_p.rate = pclk; clk_f.rate = fclk; - /* it looks like just setting the register here is not good - * enough, and causes the odd hang at initial boot time, so - * do all of them indivdually. - * - * I think disabling the LCD clock if the LCD is active is - * very dangerous, and therefore the bootloader should be - * careful to not enable the LCD clock if it is not needed. + /* We must be careful disabling the clocks we are not intending to + * be using at boot time, as subsytems such as the LCD which do + * their own DMA requests to the bus can cause the system to lockup + * if they where in the middle of requesting bus access. * - * and of course, this looks neater - */ + * Disabling the LCD clock if the LCD is active is very dangerous, + * and therefore the bootloader should be careful to not enable + * the LCD clock if it is not needed. + */ + + mutex_lock(&clocks_mutex); s3c24xx_clk_enable(S3C2410_CLKCON_NAND, 0); s3c24xx_clk_enable(S3C2410_CLKCON_USBH, 0); @@ -420,6 +597,8 @@ int __init s3c24xx_setup_clocks(unsigned long xtal, s3c24xx_clk_enable(S3C2410_CLKCON_IIC, 0); s3c24xx_clk_enable(S3C2410_CLKCON_SPI, 0); + mutex_unlock(&clocks_mutex); + /* assume uart clocks are correctly setup */ /* register our clocks */ @@ -427,6 +606,9 @@ int __init s3c24xx_setup_clocks(unsigned long xtal, if (s3c24xx_register_clock(&clk_xtal) < 0) printk(KERN_ERR "failed to register master xtal\n"); + if (s3c24xx_register_clock(&clk_upll) < 0) + printk(KERN_ERR "failed to register upll clock\n"); + if (s3c24xx_register_clock(&clk_f) < 0) printk(KERN_ERR "failed to register cpu fclk\n"); @@ -436,6 +618,10 @@ int __init s3c24xx_setup_clocks(unsigned long xtal, if (s3c24xx_register_clock(&clk_p) < 0) printk(KERN_ERR "failed to register cpu pclk\n"); + + if (s3c24xx_register_clock(&clk_usb_bus) < 0) + printk(KERN_ERR "failed to register usb bus clock\n"); + /* register clocks from clock array */ for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) { @@ -446,62 +632,13 @@ int __init s3c24xx_setup_clocks(unsigned long xtal, } } - return 0; -} - -/* S3C2440 extended clock support */ - -#ifdef CONFIG_CPU_S3C2440 + /* show the clock-slow value */ -static struct clk s3c2440_clk_upll = { - .name = "upll", - .id = -1, -}; - -static struct clk s3c2440_clk_cam = { - .name = "camif", - .parent = &clk_h, - .id = -1, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2440_CLKCON_CAMERA, -}; - -static struct clk s3c2440_clk_ac97 = { - .name = "ac97", - .parent = &clk_p, - .id = -1, - .enable = s3c24xx_clkcon_enable, - .ctrlbit = S3C2440_CLKCON_CAMERA, -}; - -static int s3c2440_clk_add(struct sys_device *sysdev) -{ - unsigned long upllcon = __raw_readl(S3C2410_UPLLCON); - - s3c2440_clk_upll.rate = s3c2410_get_pll(upllcon, clk_xtal.rate); - - printk("S3C2440: Clock Support, UPLL %ld.%03ld MHz\n", - print_mhz(s3c2440_clk_upll.rate)); - - s3c24xx_register_clock(&s3c2440_clk_ac97); - s3c24xx_register_clock(&s3c2440_clk_cam); - s3c24xx_register_clock(&s3c2440_clk_upll); - - clk_disable(&s3c2440_clk_ac97); - clk_disable(&s3c2440_clk_cam); + printk("CLOCK: Slow mode (%ld.%ld MHz), %s, MPLL %s, UPLL %s\n", + print_mhz(xtal / ( 2 * S3C2410_CLKSLOW_GET_SLOWVAL(clkslow))), + (clkslow & S3C2410_CLKSLOW_SLOW) ? "slow" : "fast", + (clkslow & S3C2410_CLKSLOW_MPLL_OFF) ? "off" : "on", + (clkslow & S3C2410_CLKSLOW_UCLK_OFF) ? "off" : "on"); return 0; } - -static struct sysdev_driver s3c2440_clk_driver = { - .add = s3c2440_clk_add, -}; - -static int s3c24xx_clk_driver(void) -{ - return sysdev_driver_register(&s3c2440_sysclass, &s3c2440_clk_driver); -} - -arch_initcall(s3c24xx_clk_driver); - -#endif /* CONFIG_CPU_S3C2440 */