diff options
author | Ben Dooks <ben-linux@fluff.org> | 2008-12-18 14:52:00 +0000 |
---|---|---|
committer | Ben Dooks <ben-linux@fluff.org> | 2008-12-18 14:52:00 +0000 |
commit | c6ad115876763e4f15055982ecb9579cb7abab5f (patch) | |
tree | 75d2b0f5f31540ff8a2dcdde9e0f087b25c68868 /arch/arm/plat-s3c24xx | |
parent | 1d19fdba149f4db055902c30b27adfb7d1ead058 (diff) | |
parent | 28ab44c5be60a9b91021a7cc7b4e202775c04764 (diff) |
Merge branch 'next-s3c24xx' into next-merged
Diffstat (limited to 'arch/arm/plat-s3c24xx')
26 files changed, 1072 insertions, 1534 deletions
diff --git a/arch/arm/plat-s3c24xx/Kconfig b/arch/arm/plat-s3c24xx/Kconfig index 7fe28aaa08c..0e07de2c9a9 100644 --- a/arch/arm/plat-s3c24xx/Kconfig +++ b/arch/arm/plat-s3c24xx/Kconfig @@ -15,6 +15,19 @@ config PLAT_S3C24XX if PLAT_S3C24XX +# code that is shared between a number of the s3c24xx implementations + +config S3C2410_CLOCK + bool + help + Clock code for the S3C2410, and similar processors which + is currently includes the S3C2410, S3C2440, S3C2442. + +config S3C24XX_DCLK + bool + help + Clock code for supporting DCLK/CLKOUT on S3C24XX architectures + config CPU_S3C244X bool depends on ARCH_S3C2410 && (CPU_S3C2440 || CPU_S3C2442) @@ -70,6 +83,29 @@ config S3C2410_DMA_DEBUG Enable debugging output for the DMA code. This option sends info to the kernel log, at priority KERN_DEBUG. +config S3C24XX_ADC + bool "ADC common driver support" + help + Core support for the ADC block found in the S3C24XX SoC systems + for drivers such as the touchscreen and hwmon to use to share + this resource. + +# SPI default pin configuration code + +config S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13 + bool + help + SPI GPIO configuration code for BUS0 when connected to + GPE11, GPE12 and GPE13. + +config S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7 + bool + help + SPI GPIO configuration code for BUS 1 when connected to + GPG5, GPG6 and GPG7. + +# common code for s3c24xx based machines, such as the SMDKs. + config MACH_SMDK bool help diff --git a/arch/arm/plat-s3c24xx/Makefile b/arch/arm/plat-s3c24xx/Makefile index d82767b2b83..a8cfdefc29e 100644 --- a/arch/arm/plat-s3c24xx/Makefile +++ b/arch/arm/plat-s3c24xx/Makefile @@ -17,9 +17,8 @@ obj-y += irq.o obj-y += devs.o obj-y += gpio.o obj-y += gpiolib.o -obj-y += time.o obj-y += clock.o -obj-y += pwm-clock.o +obj-$(CONFIG_S3C24XX_DCLK) += clock-dclk.o # Architecture dependant builds @@ -30,5 +29,15 @@ obj-$(CONFIG_PM_SIMTEC) += pm-simtec.o obj-$(CONFIG_PM) += pm.o obj-$(CONFIG_PM) += sleep.o obj-$(CONFIG_HAVE_PWM) += pwm.o +obj-$(CONFIG_S3C2410_CLOCK) += s3c2410-clock.o obj-$(CONFIG_S3C2410_DMA) += dma.o +obj-$(CONFIG_S3C24XX_ADC) += adc.o + +# SPI gpio central GPIO functions + +obj-$(CONFIG_S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13) += spi-bus0-gpe11_12_13.o +obj-$(CONFIG_S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7) += spi-bus1-gpg5_6_7.o + +# machine common support + obj-$(CONFIG_MACH_SMDK) += common-smdk.o diff --git a/arch/arm/plat-s3c24xx/adc.c b/arch/arm/plat-s3c24xx/adc.c new file mode 100644 index 00000000000..9a5c767e0a4 --- /dev/null +++ b/arch/arm/plat-s3c24xx/adc.c @@ -0,0 +1,372 @@ +/* arch/arm/plat-s3c24xx/adc.c + * + * Copyright (c) 2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org> + * + * S3C24XX ADC device core + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/io.h> + +#include <plat/regs-adc.h> +#include <plat/adc.h> + +/* This driver is designed to control the usage of the ADC block between + * the touchscreen and any other drivers that may need to use it, such as + * the hwmon driver. + * + * Priority will be given to the touchscreen driver, but as this itself is + * rate limited it should not starve other requests which are processed in + * order that they are received. + * + * Each user registers to get a client block which uniquely identifies it + * and stores information such as the necessary functions to callback when + * action is required. + */ + +struct s3c_adc_client { + struct platform_device *pdev; + struct list_head pend; + + unsigned int nr_samples; + unsigned char is_ts; + unsigned char channel; + + void (*select_cb)(unsigned selected); + void (*convert_cb)(unsigned val1, unsigned val2); +}; + +struct adc_device { + struct platform_device *pdev; + struct platform_device *owner; + struct clk *clk; + struct s3c_adc_client *cur; + struct s3c_adc_client *ts_pend; + void __iomem *regs; + + unsigned int prescale; + + int irq; +}; + +static struct adc_device *adc_dev; + +static LIST_HEAD(adc_pending); + +#define adc_dbg(_adc, msg...) dev_dbg(&(_adc)->pdev->dev, msg) + +static inline void s3c_adc_convert(struct adc_device *adc) +{ + unsigned con = readl(adc->regs + S3C2410_ADCCON); + + con |= S3C2410_ADCCON_ENABLE_START; + writel(con, adc->regs + S3C2410_ADCCON); +} + +static inline void s3c_adc_select(struct adc_device *adc, + struct s3c_adc_client *client) +{ + unsigned con = readl(adc->regs + S3C2410_ADCCON); + + client->select_cb(1); + + con &= ~S3C2410_ADCCON_MUXMASK; + con &= ~S3C2410_ADCCON_STDBM; + con &= ~S3C2410_ADCCON_STARTMASK; + + if (!client->is_ts) + con |= S3C2410_ADCCON_SELMUX(client->channel); + + writel(con, adc->regs + S3C2410_ADCCON); +} + +static void s3c_adc_dbgshow(struct adc_device *adc) +{ + adc_dbg(adc, "CON=%08x, TSC=%08x, DLY=%08x\n", + readl(adc->regs + S3C2410_ADCCON), + readl(adc->regs + S3C2410_ADCTSC), + readl(adc->regs + S3C2410_ADCDLY)); +} + +void s3c_adc_try(struct adc_device *adc) +{ + struct s3c_adc_client *next = adc->ts_pend; + + if (!next && !list_empty(&adc_pending)) { + next = list_first_entry(&adc_pending, + struct s3c_adc_client, pend); + list_del(&next->pend); + } else + adc->ts_pend = NULL; + + if (next) { + adc_dbg(adc, "new client is %p\n", next); + adc->cur = next; + s3c_adc_select(adc, next); + s3c_adc_convert(adc); + s3c_adc_dbgshow(adc); + } +} + +int s3c_adc_start(struct s3c_adc_client *client, + unsigned int channel, unsigned int nr_samples) +{ + struct adc_device *adc = adc_dev; + unsigned long flags; + + if (!adc) { + printk(KERN_ERR "%s: failed to find adc\n", __func__); + return -EINVAL; + } + + if (client->is_ts && adc->ts_pend) + return -EAGAIN; + + local_irq_save(flags); + + client->channel = channel; + client->nr_samples = nr_samples; + + if (client->is_ts) + adc->ts_pend = client; + else + list_add_tail(&client->pend, &adc_pending); + + if (!adc->cur) + s3c_adc_try(adc); + local_irq_restore(flags); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c_adc_start); + +static void s3c_adc_default_select(unsigned select) +{ +} + +struct s3c_adc_client *s3c_adc_register(struct platform_device *pdev, + void (*select)(unsigned int selected), + void (*conv)(unsigned d0, unsigned d1), + unsigned int is_ts) +{ + struct s3c_adc_client *client; + + WARN_ON(!pdev); + WARN_ON(!conv); + + if (!select) + select = s3c_adc_default_select; + + if (!conv || !pdev) + return ERR_PTR(-EINVAL); + + client = kzalloc(sizeof(struct s3c_adc_client), GFP_KERNEL); + if (!client) { + dev_err(&pdev->dev, "no memory for adc client\n"); + return ERR_PTR(-ENOMEM); + } + + client->pdev = pdev; + client->is_ts = is_ts; + client->select_cb = select; + client->convert_cb = conv; + + return client; +} +EXPORT_SYMBOL_GPL(s3c_adc_register); + +void s3c_adc_release(struct s3c_adc_client *client) +{ + /* We should really check that nothing is in progress. */ + kfree(client); +} +EXPORT_SYMBOL_GPL(s3c_adc_release); + +static irqreturn_t s3c_adc_irq(int irq, void *pw) +{ + struct adc_device *adc = pw; + struct s3c_adc_client *client = adc->cur; + unsigned long flags; + unsigned data0, data1; + + if (!client) { + dev_warn(&adc->pdev->dev, "%s: no adc pending\n", __func__); + return IRQ_HANDLED; + } + + data0 = readl(adc->regs + S3C2410_ADCDAT0); + data1 = readl(adc->regs + S3C2410_ADCDAT1); + adc_dbg(adc, "read %d: 0x%04x, 0x%04x\n", client->nr_samples, data0, data1); + + (client->convert_cb)(data0 & 0x3ff, data1 & 0x3ff); + + if (--client->nr_samples > 0) { + /* fire another conversion for this */ + + client->select_cb(1); + s3c_adc_convert(adc); + } else { + local_irq_save(flags); + (client->select_cb)(0); + adc->cur = NULL; + + s3c_adc_try(adc); + local_irq_restore(flags); + } + + return IRQ_HANDLED; +} + +static int s3c_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct adc_device *adc; + struct resource *regs; + int ret; + + adc = kzalloc(sizeof(struct adc_device), GFP_KERNEL); + if (adc == NULL) { + dev_err(dev, "failed to allocate adc_device\n"); + return -ENOMEM; + } + + adc->pdev = pdev; + adc->prescale = S3C2410_ADCCON_PRSCVL(49); + + adc->irq = platform_get_irq(pdev, 1); + if (adc->irq <= 0) { + dev_err(dev, "failed to get adc irq\n"); + ret = -ENOENT; + goto err_alloc; + } + + ret = request_irq(adc->irq, s3c_adc_irq, 0, dev_name(dev), adc); + if (ret < 0) { + dev_err(dev, "failed to attach adc irq\n"); + goto err_alloc; + } + + adc->clk = clk_get(dev, "adc"); + if (IS_ERR(adc->clk)) { + dev_err(dev, "failed to get adc clock\n"); + ret = PTR_ERR(adc->clk); + goto err_irq; + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) { + dev_err(dev, "failed to find registers\n"); + ret = -ENXIO; + goto err_clk; + } + + adc->regs = ioremap(regs->start, resource_size(regs)); + if (!adc->regs) { + dev_err(dev, "failed to map registers\n"); + ret = -ENXIO; + goto err_clk; + } + + clk_enable(adc->clk); + + writel(adc->prescale | S3C2410_ADCCON_PRSCEN, + adc->regs + S3C2410_ADCCON); + + dev_info(dev, "attached adc driver\n"); + + platform_set_drvdata(pdev, adc); + adc_dev = adc; + + return 0; + + err_clk: + clk_put(adc->clk); + + err_irq: + free_irq(adc->irq, adc); + + err_alloc: + kfree(adc); + return ret; +} + +static int s3c_adc_remove(struct platform_device *pdev) +{ + struct adc_device *adc = platform_get_drvdata(pdev); + + iounmap(adc->regs); + free_irq(adc->irq, adc); + clk_disable(adc->clk); + clk_put(adc->clk); + kfree(adc); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c_adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct adc_device *adc = platform_get_drvdata(pdev); + u32 con; + + con = readl(adc->regs + S3C2410_ADCCON); + con |= S3C2410_ADCCON_STDBM; + writel(con, adc->regs + S3C2410_ADCCON); + + clk_disable(adc->clk); + + return 0; +} + +static int s3c_adc_resume(struct platform_device *pdev) +{ + struct adc_device *adc = platform_get_drvdata(pdev); + + clk_enable(adc->clk); + + writel(adc->prescale | S3C2410_ADCCON_PRSCEN, + adc->regs + S3C2410_ADCCON); + + return 0; +} + +#else +#define s3c_adc_suspend NULL +#define s3c_adc_resume NULL +#endif + +static struct platform_driver s3c_adc_driver = { + .driver = { + .name = "s3c24xx-adc", + .owner = THIS_MODULE, + }, + .probe = s3c_adc_probe, + .remove = __devexit_p(s3c_adc_remove), + .suspend = s3c_adc_suspend, + .resume = s3c_adc_resume, +}; + +static int __init adc_init(void) +{ + int ret; + + ret = platform_driver_register(&s3c_adc_driver); + if (ret) + printk(KERN_ERR "%s: failed to add adc driver\n", __func__); + + return ret; +} + +arch_initcall(adc_init); diff --git a/arch/arm/plat-s3c24xx/clock-dclk.c b/arch/arm/plat-s3c24xx/clock-dclk.c new file mode 100644 index 00000000000..5b75a797b5a --- /dev/null +++ b/arch/arm/plat-s3c24xx/clock-dclk.c @@ -0,0 +1,194 @@ +/* linux/arch/arm/plat-s3c24xx/clock-dclk.c + * + * Copyright (c) 2004,2008 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * http://armlinux.simtec.co.uk/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * S3C24XX - definitions for DCLK and CLKOUT registers + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/clk.h> +#include <linux/io.h> + +#include <mach/regs-clock.h> +#include <mach/regs-gpio.h> + +#include <plat/clock.h> +#include <plat/cpu.h> + +/* clocks that could be registered by external code */ + +static int s3c24xx_dclk_enable(struct clk *clk, int enable) +{ + unsigned long dclkcon = __raw_readl(S3C24XX_DCLKCON); + + if (enable) + dclkcon |= clk->ctrlbit; + else + dclkcon &= ~clk->ctrlbit; + + __raw_writel(dclkcon, S3C24XX_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(S3C24XX_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, S3C24XX_DCLKCON); + + return 0; +} +static unsigned long s3c24xx_calc_div(struct clk *clk, unsigned long rate) +{ + unsigned long div; + + if ((rate == 0) || !clk->parent) + return 0; + + div = clk_get_rate(clk->parent) / rate; + if (div < 2) + div = 2; + else if (div > 16) + div = 16; + + return div; +} + +static unsigned long s3c24xx_round_dclk_rate(struct clk *clk, + unsigned long rate) +{ + unsigned long div = s3c24xx_calc_div(clk, rate); + + if (div == 0) + return 0; + + return clk_get_rate(clk->parent) / div; +} + +static int s3c24xx_set_dclk_rate(struct clk *clk, unsigned long rate) +{ + unsigned long mask, data, div = s3c24xx_calc_div(clk, rate); + + if (div == 0) + return -EINVAL; + + if (clk == &s3c24xx_dclk0) { + mask = S3C2410_DCLKCON_DCLK0_DIV_MASK | + S3C2410_DCLKCON_DCLK0_CMP_MASK; + data = S3C2410_DCLKCON_DCLK0_DIV(div) | + S3C2410_DCLKCON_DCLK0_CMP((div + 1) / 2); + } else if (clk == &s3c24xx_dclk1) { + mask = S3C2410_DCLKCON_DCLK1_DIV_MASK | + S3C2410_DCLKCON_DCLK1_CMP_MASK; + data = S3C2410_DCLKCON_DCLK1_DIV(div) | + S3C2410_DCLKCON_DCLK1_CMP((div + 1) / 2); + } else + return -EINVAL; + + clk->rate = clk_get_rate(clk->parent) / div; + __raw_writel(((__raw_readl(S3C24XX_DCLKCON) & ~mask) | data), + S3C24XX_DCLKCON); + return clk->rate; +} +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_clkout0) + 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, + .set_rate = s3c24xx_set_dclk_rate, + .round_rate = s3c24xx_round_dclk_rate, +}; + +struct clk s3c24xx_dclk1 = { + .name = "dclk1", + .id = -1, + .ctrlbit = S3C2410_DCLKCON_DCLK1EN, + .enable = s3c24xx_dclk_enable, + .set_parent = s3c24xx_dclk_setparent, + .set_rate = s3c24xx_set_dclk_rate, + .round_rate = s3c24xx_round_dclk_rate, +}; + +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, +}; diff --git a/arch/arm/plat-s3c24xx/clock.c b/arch/arm/plat-s3c24xx/clock.c index a005ddbd9ef..8474d05274b 100644 --- a/arch/arm/plat-s3c24xx/clock.c +++ b/arch/arm/plat-s3c24xx/clock.c @@ -27,18 +27,8 @@ */ #include <linux/init.h> -#include <linux/module.h> #include <linux/kernel.h> -#include <linux/list.h> -#include <linux/errno.h> -#include <linux/err.h> -#include <linux/platform_device.h> -#include <linux/sysdev.h> -#include <linux/interrupt.h> -#include <linux/ioport.h> #include <linux/clk.h> -#include <linux/mutex.h> -#include <linux/delay.h> #include <linux/io.h> #include <mach/hardware.h> @@ -47,490 +37,23 @@ #include <mach/regs-clock.h> #include <mach/regs-gpio.h> +#include <plat/cpu-freq.h> + #include <plat/clock.h> #include <plat/cpu.h> - -/* clock information */ - -static LIST_HEAD(clocks); - -DEFINE_MUTEX(clocks_mutex); - -/* enable and disable calls for use with the clk struct */ - -static int clk_null_enable(struct clk *clk, int enable) -{ - return 0; -} - -/* Clock API calls */ - -struct clk *clk_get(struct device *dev, const char *id) -{ - struct clk *p; - struct clk *clk = ERR_PTR(-ENOENT); - int idno; - - if (dev == NULL || dev->bus != &platform_bus_type) - idno = -1; - else - idno = to_platform_device(dev)->id; - - mutex_lock(&clocks_mutex); - - list_for_each_entry(p, &clocks, list) { - if (p->id == idno && - strcmp(id, p->name) == 0 && - try_module_get(p->owner)) { - clk = p; - break; - } - } - - /* check for the case where a device was supplied, but the - * clock that was being searched for is not device specific */ - - if (IS_ERR(clk)) { - list_for_each_entry(p, &clocks, list) { - if (p->id == -1 && strcmp(id, p->name) == 0 && - try_module_get(p->owner)) { - clk = p; - break; - } - } - } - - mutex_unlock(&clocks_mutex); - return clk; -} - -void clk_put(struct clk *clk) -{ - module_put(clk->owner); -} - -int clk_enable(struct clk *clk) -{ - if (IS_ERR(clk) || clk == NULL) - return -EINVAL; - - clk_enable(clk->parent); - - mutex_lock(&clocks_mutex); - - if ((clk->usage++) == 0) - (clk->enable)(clk, 1); - - mutex_unlock(&clocks_mutex); - return 0; -} - -void clk_disable(struct clk *clk) -{ - 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)) - return 0; - - if (clk->rate != 0) - return clk->rate; - - if (clk->get_rate != NULL) - return (clk->get_rate)(clk); - - if (clk->parent != NULL) - return clk_get_rate(clk->parent); - - return clk->rate; -} - -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) -{ - int ret; - - if (IS_ERR(clk)) - return -EINVAL; - - /* We do not default just do a clk->rate = rate as - * the clock may have been made this way by choice. - */ - - WARN_ON(clk->set_rate == NULL); - - if (clk->set_rate == NULL) - 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) -{ - 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_get_rate); -EXPORT_SYMBOL(clk_round_rate); -EXPORT_SYMBOL(clk_set_rate); -EXPORT_SYMBOL(clk_get_parent); -EXPORT_SYMBOL(clk_set_parent); - -/* base clocks */ - -static int clk_default_setrate(struct clk *clk, unsigned long rate) -{ - clk->rate = rate; - return 0; -} - -struct clk clk_xtal = { - .name = "xtal", - .id = -1, - .rate = 0, - .parent = NULL, - .ctrlbit = 0, -}; - -struct clk clk_mpll = { - .name = "mpll", - .id = -1, - .set_rate = clk_default_setrate, -}; - -struct clk clk_upll = { - .name = "upll", - .id = -1, - .parent = NULL, - .ctrlbit = 0, -}; - -struct clk clk_f = { - .name = "fclk", - .id = -1, - .rate = 0, - .parent = &clk_mpll, - .ctrlbit = 0, - .set_rate = clk_default_setrate, -}; - -struct clk clk_h = { - .name = "hclk", - .id = -1, - .rate = 0, - .parent = NULL, - .ctrlbit = 0, - .set_rate = clk_default_setrate, -}; - -struct clk clk_p = { - .name = "pclk", - .id = -1, - .rate = 0, - .parent = NULL, - .ctrlbit = 0, - .set_rate = clk_default_setrate, -}; - -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(S3C24XX_DCLKCON); - - if (enable) - dclkcon |= clk->ctrlbit; - else - dclkcon &= ~clk->ctrlbit; - - __raw_writel(dclkcon, S3C24XX_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(S3C24XX_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, S3C24XX_DCLKCON); - - return 0; -} - -static unsigned long s3c24xx_calc_div(struct clk *clk, unsigned long rate) -{ - unsigned long div; - - if ((rate == 0) || !clk->parent) - return 0; - - div = clk_get_rate(clk->parent) / rate; - if (div < 2) - div = 2; - else if (div > 16) - div = 16; - - return div; -} - -static unsigned long s3c24xx_round_dclk_rate(struct clk *clk, - unsigned long rate) -{ - unsigned long div = s3c24xx_calc_div(clk, rate); - - if (div == 0) - return 0; - - return clk_get_rate(clk->parent) / div; -} - -static int s3c24xx_set_dclk_rate(struct clk *clk, unsigned long rate) -{ - unsigned long mask, data, div = s3c24xx_calc_div(clk, rate); - - if (div == 0) - return -EINVAL; - - if (clk == &s3c24xx_dclk0) { - mask = S3C2410_DCLKCON_DCLK0_DIV_MASK | - S3C2410_DCLKCON_DCLK0_CMP_MASK; - data = S3C2410_DCLKCON_DCLK0_DIV(div) | - S3C2410_DCLKCON_DCLK0_CMP((div + 1) / 2); - } else if (clk == &s3c24xx_dclk1) { - mask = S3C2410_DCLKCON_DCLK1_DIV_MASK | - S3C2410_DCLKCON_DCLK1_CMP_MASK; - data = S3C2410_DCLKCON_DCLK1_DIV(div) | - S3C2410_DCLKCON_DCLK1_CMP((div + 1) / 2); - } else - return -EINVAL; - - clk->rate = clk_get_rate(clk->parent) / div; - __raw_writel(((__raw_readl(S3C24XX_DCLKCON) & ~mask) | data), - S3C24XX_DCLKCON); - return clk->rate; -} - -static int s3c24xx_clkout_setparent(struct clk *clk, struct clk *parent) -{ - unsigned long mask; - unsigned long source; - - /* calculate the MISCCR setting for the clock * |