diff options
Diffstat (limited to 'arch/arm/plat-s3c24xx')
-rw-r--r-- | arch/arm/plat-s3c24xx/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/clock.c | 19 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/dma.c | 47 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/gpio.c | 29 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/irq.c | 2 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/pm.c | 247 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/s3c244x-clock.c | 137 |
7 files changed, 441 insertions, 41 deletions
diff --git a/arch/arm/plat-s3c24xx/Makefile b/arch/arm/plat-s3c24xx/Makefile index 8e5ccaa1f03c..131d20237dd7 100644 --- a/arch/arm/plat-s3c24xx/Makefile +++ b/arch/arm/plat-s3c24xx/Makefile @@ -23,6 +23,7 @@ obj-y += clock.o obj-$(CONFIG_CPU_S3C244X) += s3c244x.o obj-$(CONFIG_CPU_S3C244X) += s3c244x-irq.o +obj-$(CONFIG_CPU_S3C244X) += s3c244x-clock.o obj-$(CONFIG_PM_SIMTEC) += pm-simtec.o obj-$(CONFIG_PM) += pm.o obj-$(CONFIG_PM) += sleep.o diff --git a/arch/arm/plat-s3c24xx/clock.c b/arch/arm/plat-s3c24xx/clock.c index 79cda0faec86..99a44746f8f2 100644 --- a/arch/arm/plat-s3c24xx/clock.c +++ b/arch/arm/plat-s3c24xx/clock.c @@ -172,6 +172,15 @@ int clk_set_rate(struct clk *clk, unsigned long rate) 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); @@ -213,6 +222,12 @@ 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, @@ -224,6 +239,7 @@ struct clk clk_xtal = { struct clk clk_mpll = { .name = "mpll", .id = -1, + .set_rate = clk_default_setrate, }; struct clk clk_upll = { @@ -239,6 +255,7 @@ struct clk clk_f = { .rate = 0, .parent = &clk_mpll, .ctrlbit = 0, + .set_rate = clk_default_setrate, }; struct clk clk_h = { @@ -247,6 +264,7 @@ struct clk clk_h = { .rate = 0, .parent = NULL, .ctrlbit = 0, + .set_rate = clk_default_setrate, }; struct clk clk_p = { @@ -255,6 +273,7 @@ struct clk clk_p = { .rate = 0, .parent = NULL, .ctrlbit = 0, + .set_rate = clk_default_setrate, }; struct clk clk_usb_bus = { diff --git a/arch/arm/plat-s3c24xx/dma.c b/arch/arm/plat-s3c24xx/dma.c index aae1b9cbaf44..ac9ff1666fcc 100644 --- a/arch/arm/plat-s3c24xx/dma.c +++ b/arch/arm/plat-s3c24xx/dma.c @@ -525,7 +525,8 @@ int s3c2410_dma_enqueue(unsigned int channel, void *id, } } else if (chan->state == S3C2410_DMA_IDLE) { if (chan->flags & S3C2410_DMAF_AUTOSTART) { - s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_START); + s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL, + S3C2410_DMAOP_START); } } @@ -787,7 +788,7 @@ int s3c2410_dma_request(unsigned int channel, pr_debug("%s: channel initialised, %p\n", __FUNCTION__, chan); - return 0; + return chan->number | DMACH_LOW_LEVEL; } EXPORT_SYMBOL(s3c2410_dma_request); @@ -1173,6 +1174,7 @@ int s3c2410_dma_devconfig(int channel, chan->source = source; chan->dev_addr = devaddr; + chan->hw_cfg = hwcfg; switch (source) { case S3C2410_DMASRC_HW: @@ -1184,7 +1186,7 @@ int s3c2410_dma_devconfig(int channel, dma_wrreg(chan, S3C2410_DMA_DIDSTC, (0<<1) | (0<<0)); chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST); - return 0; + break; case S3C2410_DMASRC_MEM: /* source is memory */ @@ -1195,11 +1197,19 @@ int s3c2410_dma_devconfig(int channel, dma_wrreg(chan, S3C2410_DMA_DIDSTC, hwcfg & 3); chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DISRC); - return 0; + break; + + default: + printk(KERN_ERR "dma%d: invalid source type (%d)\n", + channel, source); + + return -EINVAL; } - printk(KERN_ERR "dma%d: invalid source type (%d)\n", channel, source); - return -EINVAL; + if (dma_sel.direction != NULL) + (dma_sel.direction)(chan, chan->map, source); + + return 0; } EXPORT_SYMBOL(s3c2410_dma_devconfig); @@ -1227,6 +1237,10 @@ int s3c2410_dma_getposition(dmach_t channel, dma_addr_t *src, dma_addr_t *dst) EXPORT_SYMBOL(s3c2410_dma_getposition); +static struct s3c2410_dma_chan *to_dma_chan(struct sys_device *dev) +{ + return container_of(dev, struct s3c2410_dma_chan, dev); +} /* system device class */ @@ -1234,7 +1248,7 @@ EXPORT_SYMBOL(s3c2410_dma_getposition); static int s3c2410_dma_suspend(struct sys_device *dev, pm_message_t state) { - struct s3c2410_dma_chan *cp = container_of(dev, struct s3c2410_dma_chan, dev); + struct s3c2410_dma_chan *cp = to_dma_chan(dev); printk(KERN_DEBUG "suspending dma channel %d\n", cp->number); @@ -1256,6 +1270,24 @@ static int s3c2410_dma_suspend(struct sys_device *dev, pm_message_t state) static int s3c2410_dma_resume(struct sys_device *dev) { + struct s3c2410_dma_chan *cp = to_dma_chan(dev); + unsigned int no = cp->number | DMACH_LOW_LEVEL; + + /* restore channel's hardware configuration */ + + if (!cp->in_use) + return 0; + + printk(KERN_INFO "dma%d: restoring configuration\n", cp->number); + + s3c2410_dma_config(no, cp->xfer_unit, cp->dcon); + s3c2410_dma_devconfig(no, cp->source, cp->hw_cfg, cp->dev_addr); + + /* re-select the dma source for this channel */ + + if (cp->map != NULL) + dma_sel.select(cp, cp->map); + return 0; } @@ -1445,6 +1477,7 @@ static struct s3c2410_dma_chan *s3c2410_dma_map_channel(int channel) found: dmach = &s3c2410_chans[ch]; + dmach->map = ch_map; dma_chan_map[channel] = dmach; /* select the channel */ diff --git a/arch/arm/plat-s3c24xx/gpio.c b/arch/arm/plat-s3c24xx/gpio.c index ec3a09c4d181..ee99dcc7f0bd 100644 --- a/arch/arm/plat-s3c24xx/gpio.c +++ b/arch/arm/plat-s3c24xx/gpio.c @@ -122,6 +122,19 @@ void s3c2410_gpio_pullup(unsigned int pin, unsigned int to) EXPORT_SYMBOL(s3c2410_gpio_pullup); +int s3c2410_gpio_getpull(unsigned int pin) +{ + void __iomem *base = S3C24XX_GPIO_BASE(pin); + unsigned long offs = S3C2410_GPIO_OFFSET(pin); + + if (pin < S3C2410_GPIO_BANKB) + return -EINVAL; + + return (__raw_readl(base + 0x08) & (1L << offs)) ? 1 : 0; +} + +EXPORT_SYMBOL(s3c2410_gpio_getpull); + void s3c2410_gpio_setpin(unsigned int pin, unsigned int to) { void __iomem *base = S3C24XX_GPIO_BASE(pin); @@ -186,3 +199,19 @@ int s3c2410_gpio_getirq(unsigned int pin) } EXPORT_SYMBOL(s3c2410_gpio_getirq); + +int s3c2410_gpio_irq2pin(unsigned int irq) +{ + if (irq >= IRQ_EINT0 && irq <= IRQ_EINT3) + return S3C2410_GPF0 + (irq - IRQ_EINT0); + + if (irq >= IRQ_EINT4 && irq <= IRQ_EINT7) + return S3C2410_GPF4 + (irq - IRQ_EINT4); + + if (irq >= IRQ_EINT8 && irq <= IRQ_EINT23) + return S3C2410_GPG0 + (irq - IRQ_EINT8); + + return -EINVAL; +} + +EXPORT_SYMBOL(s3c2410_gpio_irq2pin); diff --git a/arch/arm/plat-s3c24xx/irq.c b/arch/arm/plat-s3c24xx/irq.c index 8fbc88470261..d486f5112569 100644 --- a/arch/arm/plat-s3c24xx/irq.c +++ b/arch/arm/plat-s3c24xx/irq.c @@ -187,7 +187,7 @@ struct irq_chip s3c_irq_level_chip = { .set_wake = s3c_irq_wake }; -static struct irq_chip s3c_irq_chip = { +struct irq_chip s3c_irq_chip = { .name = "s3c", .ack = s3c_irq_ack, .mask = s3c_irq_mask, diff --git a/arch/arm/plat-s3c24xx/pm.c b/arch/arm/plat-s3c24xx/pm.c index 4fdb3117744f..bf5581a9aeea 100644 --- a/arch/arm/plat-s3c24xx/pm.c +++ b/arch/arm/plat-s3c24xx/pm.c @@ -83,38 +83,39 @@ static struct sleep_save core_save[] = { SAVE_ITEM(S3C2410_REFRESH), }; -static struct sleep_save gpio_save[] = { - SAVE_ITEM(S3C2410_GPACON), - SAVE_ITEM(S3C2410_GPADAT), - - SAVE_ITEM(S3C2410_GPBCON), - SAVE_ITEM(S3C2410_GPBDAT), - SAVE_ITEM(S3C2410_GPBUP), - - SAVE_ITEM(S3C2410_GPCCON), - SAVE_ITEM(S3C2410_GPCDAT), - SAVE_ITEM(S3C2410_GPCUP), - - SAVE_ITEM(S3C2410_GPDCON), - SAVE_ITEM(S3C2410_GPDDAT), - SAVE_ITEM(S3C2410_GPDUP), - - SAVE_ITEM(S3C2410_GPECON), - SAVE_ITEM(S3C2410_GPEDAT), - SAVE_ITEM(S3C2410_GPEUP), - - SAVE_ITEM(S3C2410_GPFCON), - SAVE_ITEM(S3C2410_GPFDAT), - SAVE_ITEM(S3C2410_GPFUP), - - SAVE_ITEM(S3C2410_GPGCON), - SAVE_ITEM(S3C2410_GPGDAT), - SAVE_ITEM(S3C2410_GPGUP), - - SAVE_ITEM(S3C2410_GPHCON), - SAVE_ITEM(S3C2410_GPHDAT), - SAVE_ITEM(S3C2410_GPHUP), +static struct gpio_sleep { + void __iomem *base; + unsigned int gpcon; + unsigned int gpdat; + unsigned int gpup; +} gpio_save[] = { + [0] = { + .base = S3C2410_GPACON, + }, + [1] = { + .base = S3C2410_GPBCON, + }, + [2] = { + .base = S3C2410_GPCCON, + }, + [3] = { + .base = S3C2410_GPDCON, + }, + [4] = { + .base = S3C2410_GPECON, + }, + [5] = { + .base = S3C2410_GPFCON, + }, + [6] = { + .base = S3C2410_GPGCON, + }, + [7] = { + .base = S3C2410_GPHCON, + }, +}; +static struct sleep_save misc_save[] = { SAVE_ITEM(S3C2410_DCLKCON), }; @@ -486,6 +487,184 @@ static void s3c2410_pm_configure_extint(void) } } +/* offsets for CON/DAT/UP registers */ + +#define OFFS_CON (S3C2410_GPACON - S3C2410_GPACON) +#define OFFS_DAT (S3C2410_GPADAT - S3C2410_GPACON) +#define OFFS_UP (S3C2410_GPBUP - S3C2410_GPBCON) + +/* s3c2410_pm_save_gpios() + * + * Save the state of the GPIOs + */ + +static void s3c2410_pm_save_gpios(void) +{ + struct gpio_sleep *gps = gpio_save; + unsigned int gpio; + + for (gpio = 0; gpio < ARRAY_SIZE(gpio_save); gpio++, gps++) { + void __iomem *base = gps->base; + + gps->gpcon = __raw_readl(base + OFFS_CON); + gps->gpdat = __raw_readl(base + OFFS_DAT); + + if (gpio > 0) + gps->gpup = __raw_readl(base + OFFS_UP); + + } +} + +/* Test whether the given masked+shifted bits of an GPIO configuration + * are one of the SFN (special function) modes. */ + +static inline int is_sfn(unsigned long con) +{ + return (con == 2 || con == 3); +} + +/* Test if the given masked+shifted GPIO configuration is an input */ + +static inline int is_in(unsigned long con) +{ + return con == 0; +} + +/* Test if the given masked+shifted GPIO configuration is an output */ + +static inline int is_out(unsigned long con) +{ + return con == 1; +} + +/* s3c2410_pm_restore_gpio() + * + * Restore one of the GPIO banks that was saved during suspend. This is + * not as simple as once thought, due to the possibility of glitches + * from the order that the CON and DAT registers are set in. + * + * The three states the pin can be are {IN,OUT,SFN} which gives us 9 + * combinations of changes to check. Three of these, if the pin stays + * in the same configuration can be discounted. This leaves us with + * the following: + * + * { IN => OUT } Change DAT first + * { IN => SFN } Change CON first + * { OUT => SFN } Change CON first, so new data will not glitch + * { OUT => IN } Change CON first, so new data will not glitch + * { SFN => IN } Change CON first + * { SFN => OUT } Change DAT first, so new data will not glitch [1] + * + * We do not currently deal with the UP registers as these control + * weak resistors, so a small delay in change should not need to bring + * these into the calculations. + * + * [1] this assumes that writing to a pin DAT whilst in SFN will set the + * state for when it is next output. + */ + +static void s3c2410_pm_restore_gpio(int index, struct gpio_sleep *gps) +{ + void __iomem *base = gps->base; + unsigned long gps_gpcon = gps->gpcon; + unsigned long gps_gpdat = gps->gpdat; + unsigned long old_gpcon; + unsigned long old_gpdat; + unsigned long old_gpup = 0x0; + unsigned long gpcon; + int nr; + + old_gpcon = __raw_readl(base + OFFS_CON); + old_gpdat = __raw_readl(base + OFFS_DAT); + + if (base == S3C2410_GPACON) { + /* GPACON only has one bit per control / data and no PULLUPs. + * GPACON[x] = 0 => Output, 1 => SFN */ + + /* first set all SFN bits to SFN */ + + gpcon = old_gpcon | gps->gpcon; + __raw_writel(gpcon, base + OFFS_CON); + + /* now set all the other bits */ + + __raw_writel(gps_gpdat, base + OFFS_DAT); + __raw_writel(gps_gpcon, base + OFFS_CON); + } else { + unsigned long old, new, mask; + unsigned long change_mask = 0x0; + + old_gpup = __raw_readl(base + OFFS_UP); + + /* Create a change_mask of all the items that need to have + * their CON value changed before their DAT value, so that + * we minimise the work between the two settings. + */ + + for (nr = 0, mask = 0x03; nr < 32; nr += 2, mask <<= 2) { + old = (old_gpcon & mask) >> nr; + new = (gps_gpcon & mask) >> nr; + + /* If there is no change, then skip */ + + if (old == new) + continue; + + /* If both are special function, then skip */ + + if (is_sfn(old) && is_sfn(new)) + continue; + + /* Change is IN => OUT, do not change now */ + + if (is_in(old) && is_out(new)) + continue; + + /* Change is SFN => OUT, do not change now */ + + if (is_sfn(old) && is_out(new)) + continue; + + /* We should now be at the case of IN=>SFN, + * OUT=>SFN, OUT=>IN, SFN=>IN. */ + + change_mask |= mask; + } + + /* Write the new CON settings */ + + gpcon = old_gpcon & ~change_mask; + gpcon |= gps_gpcon & change_mask; + + __raw_writel(gpcon, base + OFFS_CON); + + /* Now change any items that require DAT,CON */ + + __raw_writel(gps_gpdat, base + OFFS_DAT); + __raw_writel(gps_gpcon, base + OFFS_CON); + __raw_writel(gps->gpup, base + OFFS_UP); + } + + DBG("GPIO[%d] CON %08lx => %08lx, DAT %08lx => %08lx\n", + index, old_gpcon, gps_gpcon, old_gpdat, gps_gpdat); +} + + +/** s3c2410_pm_restore_gpios() + * + * Restore the state of the GPIOs + */ + +static void s3c2410_pm_restore_gpios(void) +{ + struct gpio_sleep *gps = gpio_save; + int gpio; + + for (gpio = 0; gpio < ARRAY_SIZE(gpio_save); gpio++, gps++) { + s3c2410_pm_restore_gpio(gpio, gps); + } +} + void (*pm_cpu_prep)(void); void (*pm_cpu_sleep)(void); @@ -535,7 +714,8 @@ static int s3c2410_pm_enter(suspend_state_t state) /* save all necessary core registers not covered by the drivers */ - s3c2410_pm_do_save(gpio_save, ARRAY_SIZE(gpio_save)); + s3c2410_pm_save_gpios(); + s3c2410_pm_do_save(misc_save, ARRAY_SIZE(misc_save)); s3c2410_pm_do_save(core_save, ARRAY_SIZE(core_save)); s3c2410_pm_do_save(uart_save, ARRAY_SIZE(uart_save)); @@ -585,8 +765,9 @@ static int s3c2410_pm_enter(suspend_state_t state) /* restore the system state */ s3c2410_pm_do_restore_core(core_save, ARRAY_SIZE(core_save)); - s3c2410_pm_do_restore(gpio_save, ARRAY_SIZE(gpio_save)); + s3c2410_pm_do_restore(misc_save, ARRAY_SIZE(misc_save)); s3c2410_pm_do_restore(uart_save, ARRAY_SIZE(uart_save)); + s3c2410_pm_restore_gpios(); s3c2410_pm_debug_init(); diff --git a/arch/arm/plat-s3c24xx/s3c244x-clock.c b/arch/arm/plat-s3c24xx/s3c244x-clock.c new file mode 100644 index 000000000000..faf3e0f9f4e2 --- /dev/null +++ b/arch/arm/plat-s3c24xx/s3c244x-clock.c @@ -0,0 +1,137 @@ +/* linux/arch/arm/plat-s3c24xx/s3c24xx-clock.c + * + * Copyright (c) 2004-2005,2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * S3C2440/S3C2442 Common clock support + * + * 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, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#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/device.h> +#include <linux/sysdev.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/mutex.h> +#include <linux/clk.h> + +#include <asm/hardware.h> +#include <asm/atomic.h> +#include <asm/irq.h> +#include <asm/io.h> + +#include <asm/arch/regs-clock.h> + +#include <asm/plat-s3c24xx/clock.h> +#include <asm/plat-s3c24xx/cpu.h> + +static int s3c2440_setparent_armclk(struct clk *clk, struct clk *parent) +{ + unsigned long camdivn; + unsigned long dvs; + + if (parent == &clk_f) + dvs = 0; + else if (parent == &clk_h) + dvs = S3C2440_CAMDIVN_DVSEN; + else + return -EINVAL; + + clk->parent = parent; + + camdivn = __raw_readl(S3C2440_CAMDIVN); + camdivn &= ~S3C2440_CAMDIVN_DVSEN; + camdivn |= dvs; + __raw_writel(camdivn, S3C2440_CAMDIVN); + + return 0; +} + +static struct clk clk_arm = { + .name = "armclk", + .id = -1, + .set_parent = s3c2440_setparent_armclk, +}; + +static int s3c244x_clk_add(struct sys_device *sysdev) +{ + unsigned long camdivn = __raw_readl(S3C2440_CAMDIVN); + unsigned long clkdivn; + struct clk *clock_upll; + int ret; + + printk("S3C244X: Clock Support, DVS %s\n", + (camdivn & S3C2440_CAMDIVN_DVSEN) ? "on" : "off"); + + clk_arm.parent = (camdivn & S3C2440_CAMDIVN_DVSEN) ? &clk_h : &clk_f; + + ret = s3c24xx_register_clock(&clk_arm); + if (ret < 0) { + printk(KERN_ERR "S3C24XX: Failed to add armclk (%d)\n", ret); + return ret; + } + + clock_upll = clk_get(NULL, "upll"); + if (IS_ERR(clock_upll)) { + printk(KERN_ERR "S3C244X: Failed to get upll clock\n"); + return -ENOENT; + } + + /* check rate of UPLL, and if it is near 96MHz, then change + * to using half the UPLL rate for the system */ + + if (clk_get_rate(clock_upll) > (94 * MHZ)) { + clk_usb_bus.rate = clk_get_rate(clock_upll) / 2; + + mutex_lock(&clocks_mutex); + + clkdivn = __raw_readl(S3C2410_CLKDIVN); + clkdivn |= S3C2440_CLKDIVN_UCLK; + __raw_writel(clkdivn, S3C2410_CLKDIVN); + + mutex_unlock(&clocks_mutex); + } + + return 0; +} + +static struct sysdev_driver s3c2440_clk_driver = { + .add = s3c244x_clk_add, +}; + +static int s3c2440_clk_init(void) +{ + return sysdev_driver_register(&s3c2440_sysclass, &s3c2440_clk_driver); +} + +arch_initcall(s3c2440_clk_init); + +static struct sysdev_driver s3c2442_clk_driver = { + .add = s3c244x_clk_add, +}; + +static int s3c2442_clk_init(void) +{ + return sysdev_driver_register(&s3c2442_sysclass, &s3c2442_clk_driver); +} + +arch_initcall(s3c2442_clk_init); |