diff options
Diffstat (limited to 'arch')
-rw-r--r-- | arch/arm/Kconfig | 2 | ||||
-rw-r--r-- | arch/arm/include/asm/fiq.h | 5 | ||||
-rw-r--r-- | arch/arm/kernel/fiq.c | 79 | ||||
-rw-r--r-- | arch/arm/plat-s3c/Kconfig | 5 | ||||
-rw-r--r-- | arch/arm/plat-s3c/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/plat-s3c/include/plat/pwm.h | 45 | ||||
-rw-r--r-- | arch/arm/plat-s3c/pwm.c | 288 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/cpu.c | 10 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/include/plat/irq.h | 21 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/irq.c | 50 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/pwm-clock.c | 437 |
11 files changed, 931 insertions, 12 deletions
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index aef63c8e3d2..be8ebe3760d 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1455,6 +1455,8 @@ source "drivers/usb/Kconfig" source "drivers/uwb/Kconfig" +source "drivers/ar6000/Kconfig" + source "drivers/mmc/Kconfig" source "drivers/memstick/Kconfig" diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index 2242ce22ec6..7ade2b8445d 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -29,8 +29,9 @@ struct fiq_handler { extern int claim_fiq(struct fiq_handler *f); extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); -extern void set_fiq_regs(struct pt_regs *regs); -extern void get_fiq_regs(struct pt_regs *regs); +extern void set_fiq_c_handler(void (*handler)(void)); +extern void __attribute__((naked)) set_fiq_regs(struct pt_regs *regs); +extern void __attribute__((naked)) get_fiq_regs(struct pt_regs *regs); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 6ff7919613d..c07691ef4c0 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -8,6 +8,8 @@ * * FIQ support re-written by Russell King to be more generic * + * FIQ handler in C supoprt written by Andy Green <andy@openmoko.com> + * * We now properly support a method by which the FIQ handlers can * be stacked onto the vector. We still do not support sharing * the FIQ vector itself. @@ -124,6 +126,83 @@ void __naked get_fiq_regs(struct pt_regs *regs) : "r" (®s->ARM_r8), "I" (PSR_I_BIT | PSR_F_BIT | FIQ_MODE)); } +/* -------- FIQ handler in C --------- + * + * Major Caveats for using this + * --------------------------- + * * + * * 1) it CANNOT touch any vmalloc()'d memory, only memory + * that was kmalloc()'d. Static allocations in the monolithic kernel + * are kmalloc()'d so they are okay. You can touch memory-mapped IO, but + * the pointer for it has to have been stored in kmalloc'd memory. The + * reason for this is simple: every now and then Linux turns off interrupts + * and reorders the paging tables. If a FIQ happens during this time, the + * virtual memory space can be partly or entirely disordered or missing. + * + * 2) Because vmalloc() is used when a module is inserted, THIS FIQ + * ISR HAS TO BE IN THE MONOLITHIC KERNEL, not a module. But the way + * it is set up, you can all to enable and disable it from your module + * and intercommunicate with it through struct fiq_ipc + * fiq_ipc which you can define in + * asm/archfiq_ipc_type.h. The reason is the same as above, a + * FIQ could happen while even the ISR is not present in virtual memory + * space due to pagetables being changed at the time. + * + * 3) You can't call any Linux API code except simple macros + * - understand that FIQ can come in at any time, no matter what + * state of undress the kernel may privately be in, thinking it + * locked the door by turning off interrupts... FIQ is an + * unstoppable monster force (which is its value) + * - they are not vmalloc()'d memory safe + * - they might do crazy stuff like sleep: FIQ pisses fire and + * is not interested in 'sleep' that the weak seem to need + * - calling APIs from FIQ can re-enter un-renterable things + * - summary: you cannot interoperate with linux APIs directly in the FIQ ISR + * + * If you follow these rules, it is fantastic, an extremely powerful, solid, + * genuine hard realtime feature. + */ + +static void (*current_fiq_c_isr)(void); +#define FIQ_C_ISR_STACK_SIZE 256 + +static void __attribute__((naked)) __jump_to_isr(void) +{ + asm __volatile__ ("mov pc, r8"); +} + + +static void __attribute__((naked)) __actual_isr(void) +{ + asm __volatile__ ( + "stmdb sp!, {r0-r12, lr};" + "mov fp, sp;" + ); + + current_fiq_c_isr(); + + asm __volatile__ ( + "ldmia sp!, {r0-r12, lr};" + "subs pc, lr, #4;" + ); +} + +void set_fiq_c_handler(void (*isr)(void)) +{ + struct pt_regs regs; + + memset(®s, 0, sizeof(regs)); + regs.ARM_r8 = (unsigned long) __actual_isr; + regs.ARM_sp = 0xffff001c + FIQ_C_ISR_STACK_SIZE; + + set_fiq_handler(__jump_to_isr, 4); + + current_fiq_c_isr = isr; + + set_fiq_regs(®s); +} +/* -------- FIQ handler in C ---------*/ + int claim_fiq(struct fiq_handler *f) { int ret = 0; diff --git a/arch/arm/plat-s3c/Kconfig b/arch/arm/plat-s3c/Kconfig index 935c7558469..bae4b955edf 100644 --- a/arch/arm/plat-s3c/Kconfig +++ b/arch/arm/plat-s3c/Kconfig @@ -166,6 +166,11 @@ config S3C_DMA help Internal configuration for S3C DMA core +config S3C_PWM + bool + help + PWM timer code for the S3C2410, and similar processors + # device definitions to compile in config S3C_DEV_HSMMC diff --git a/arch/arm/plat-s3c/Makefile b/arch/arm/plat-s3c/Makefile index 0761766b183..4d8e4a35d34 100644 --- a/arch/arm/plat-s3c/Makefile +++ b/arch/arm/plat-s3c/Makefile @@ -38,3 +38,4 @@ obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += dev-audio.o obj-$(CONFIG_S3C_DEV_FB) += dev-fb.o obj-$(CONFIG_S3C_DEV_USB_HOST) += dev-usb.o obj-$(CONFIG_S3C_DEV_USB_HSOTG) += dev-usb-hsotg.o +obj-$(CONFIG_S3C_PWM) += pwm.o diff --git a/arch/arm/plat-s3c/include/plat/pwm.h b/arch/arm/plat-s3c/include/plat/pwm.h new file mode 100644 index 00000000000..6a41b0ad840 --- /dev/null +++ b/arch/arm/plat-s3c/include/plat/pwm.h @@ -0,0 +1,45 @@ +#ifndef __S3C2410_PWM_H +#define __S3C2410_PWM_H + +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/clk.h> + +#include <mach/io.h> +#include <mach/hardware.h> +#include <asm/mach-types.h> +#include <plat/regs-timer.h> + +enum pwm_timer { + PWM0, + PWM1, + PWM2, + PWM3, + PWM4 +}; + +struct s3c2410_pwm { + enum pwm_timer timerid; + struct clk *pclk; + unsigned long pclk_rate; + unsigned long prescaler; + unsigned long divider; + unsigned long counter; + unsigned long comparer; +}; + +struct s3c24xx_pwm_platform_data{ + /* callback to attach platform children (to enforce suspend / resume + * ordering */ + void (*attach_child_devices)(struct device *parent_device); +}; + +int s3c2410_pwm_init(struct s3c2410_pwm *s3c2410_pwm); +int s3c2410_pwm_enable(struct s3c2410_pwm *s3c2410_pwm); +int s3c2410_pwm_disable(struct s3c2410_pwm *s3c2410_pwm); +int s3c2410_pwm_start(struct s3c2410_pwm *s3c2410_pwm); +int s3c2410_pwm_stop(struct s3c2410_pwm *s3c2410_pwm); +int s3c2410_pwm_duty_cycle(int reg_value, struct s3c2410_pwm *s3c2410_pwm); +int s3c2410_pwm_dumpregs(void); + +#endif /* __S3C2410_PWM_H */ diff --git a/arch/arm/plat-s3c/pwm.c b/arch/arm/plat-s3c/pwm.c new file mode 100644 index 00000000000..250bd2bc9b8 --- /dev/null +++ b/arch/arm/plat-s3c/pwm.c @@ -0,0 +1,288 @@ +/* + * arch/arm/plat-s3c/pwm.c + * + * Copyright (c) by Javi Roman <javiroman@kernel-labs.org> + * for the Openmoko Project. + * + * S3C2410A SoC PWM 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. + * + * 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/kernel.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <mach/hardware.h> +#include <plat/regs-timer.h> +#include <plat/pwm.h> +#include <asm/io.h> + +#ifdef CONFIG_PM + static unsigned long standby_reg_tcon; + static unsigned long standby_reg_tcfg0; + static unsigned long standby_reg_tcfg1; +#endif + +int s3c2410_pwm_disable(struct s3c2410_pwm *pwm) +{ + unsigned long tcon; + + /* stop timer */ + tcon = __raw_readl(S3C2410_TCON); + tcon &= 0xffffff00; + __raw_writel(tcon, S3C2410_TCON); + + clk_disable(pwm->pclk); + clk_put(pwm->pclk); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_disable); + +int s3c2410_pwm_init(struct s3c2410_pwm *pwm) +{ + pwm->pclk = clk_get(NULL, "timers"); + if (IS_ERR(pwm->pclk)) + return PTR_ERR(pwm->pclk); + + clk_enable(pwm->pclk); + pwm->pclk_rate = clk_get_rate(pwm->pclk); + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_init); + +int s3c2410_pwm_enable(struct s3c2410_pwm *pwm) +{ + unsigned long tcfg0, tcfg1, tcnt, tcmp; + + /* control registers bits */ + tcfg1 = __raw_readl(S3C2410_TCFG1); + tcfg0 = __raw_readl(S3C2410_TCFG0); + + /* divider & scaler slection */ + switch (pwm->timerid) { + case PWM0: + tcfg1 &= ~S3C2410_TCFG1_MUX0_MASK; + tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK; + break; + case PWM1: + tcfg1 &= ~S3C2410_TCFG1_MUX1_MASK; + tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK; + break; + case PWM2: + tcfg1 &= ~S3C2410_TCFG1_MUX2_MASK; + tcfg0 &= ~S3C2410_TCFG_PRESCALER1_MASK; + break; + case PWM3: + tcfg1 &= ~S3C2410_TCFG1_MUX3_MASK; + tcfg0 &= ~S3C2410_TCFG_PRESCALER1_MASK; + break; + case PWM4: + /* timer four is not capable of doing PWM */ + break; + default: + clk_disable(pwm->pclk); + clk_put(pwm->pclk); + return -1; + } + + /* divider & scaler values */ + tcfg1 |= pwm->divider; + __raw_writel(tcfg1, S3C2410_TCFG1); + + switch (pwm->timerid) { + case PWM0: + case PWM1: + tcfg0 |= pwm->prescaler; + __raw_writel(tcfg0, S3C2410_TCFG0); + break; + default: + if ((tcfg0 | pwm->prescaler) != tcfg0) { + printk(KERN_WARNING "not changing prescaler of PWM %u," + " since it's shared with timer4 (clock tick)\n", + pwm->timerid); + } + break; + } + + /* timer count and compare buffer initial values */ + tcnt = pwm->counter; + tcmp = pwm->comparer; + + __raw_writel(tcnt, S3C2410_TCNTB(pwm->timerid)); + __raw_writel(tcmp, S3C2410_TCMPB(pwm->timerid)); + + /* ensure timer is stopped */ + s3c2410_pwm_stop(pwm); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_enable); + +int s3c2410_pwm_start(struct s3c2410_pwm *pwm) +{ + unsigned long tcon; + + tcon = __raw_readl(S3C2410_TCON); + + switch (pwm->timerid) { + case PWM0: + tcon |= S3C2410_TCON_T0START; + tcon &= ~S3C2410_TCON_T0MANUALUPD; + break; + case PWM1: + tcon |= S3C2410_TCON_T1START; + tcon &= ~S3C2410_TCON_T1MANUALUPD; + break; + case PWM2: + tcon |= S3C2410_TCON_T2START; + tcon &= ~S3C2410_TCON_T2MANUALUPD; + break; + case PWM3: + tcon |= S3C2410_TCON_T3START; + tcon &= ~S3C2410_TCON_T3MANUALUPD; + break; + case PWM4: + /* timer four is not capable of doing PWM */ + default: + return -ENODEV; + } + + __raw_writel(tcon, S3C2410_TCON); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_start); + +int s3c2410_pwm_stop(struct s3c2410_pwm *pwm) +{ + unsigned long tcon; + + tcon = __raw_readl(S3C2410_TCON); + + switch (pwm->timerid) { + case PWM0: + tcon &= ~0x00000000; + tcon |= S3C2410_TCON_T0RELOAD; + tcon |= S3C2410_TCON_T0MANUALUPD; + break; + case PWM1: + tcon &= ~0x00000080; + tcon |= S3C2410_TCON_T1RELOAD; + tcon |= S3C2410_TCON_T1MANUALUPD; + break; + case PWM2: + tcon &= ~0x00000800; + tcon |= S3C2410_TCON_T2RELOAD; + tcon |= S3C2410_TCON_T2MANUALUPD; + break; + case PWM3: + tcon &= ~0x00008000; + tcon |= S3C2410_TCON_T3RELOAD; + tcon |= S3C2410_TCON_T3MANUALUPD; + break; + case PWM4: + /* timer four is not capable of doing PWM */ + default: + return -ENODEV; + } + + __raw_writel(tcon, S3C2410_TCON); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_stop); + +int s3c2410_pwm_duty_cycle(int reg_value, struct s3c2410_pwm *pwm) +{ + __raw_writel(reg_value, S3C2410_TCMPB(pwm->timerid)); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_duty_cycle); + +int s3c2410_pwm_dumpregs(void) +{ + printk(KERN_INFO "TCON: %08lx, TCFG0: %08lx, TCFG1: %08lx\n", + (unsigned long) __raw_readl(S3C2410_TCON), + (unsigned long) __raw_readl(S3C2410_TCFG0), + (unsigned long) __raw_readl(S3C2410_TCFG1)); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_dumpregs); + +static int __init s3c24xx_pwm_probe(struct platform_device *pdev) +{ + struct s3c24xx_pwm_platform_data *pdata = pdev->dev.platform_data; + + dev_info(&pdev->dev, "s3c24xx_pwm is registered \n"); + + /* if platform was interested, give him a chance to register + * platform devices that switch power with us as the parent + * at registration time -- ensures suspend / resume ordering + */ + if (pdata) + if (pdata->attach_child_devices) + (pdata->attach_child_devices)(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c24xx_pwm_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* PWM config should be kept in suspending */ + standby_reg_tcon = __raw_readl(S3C2410_TCON); + standby_reg_tcfg0 = __raw_readl(S3C2410_TCFG0); + standby_reg_tcfg1 = __raw_readl(S3C2410_TCFG1); + + return 0; +} + +static int s3c24xx_pwm_resume(struct platform_device *pdev) +{ + __raw_writel(standby_reg_tcon, S3C2410_TCON); + __raw_writel(standby_reg_tcfg0, S3C2410_TCFG0); + __raw_writel(standby_reg_tcfg1, S3C2410_TCFG1); + + return 0; +} +#else +#define s3c24xx_pwm_suspend NULL +#define s3c24xx_pwm_resume NULL +#endif + +static struct platform_driver s3c24xx_pwm_driver = { + .driver = { + .name = "s3c24xx_pwm", + .owner = THIS_MODULE, + }, + .probe = s3c24xx_pwm_probe, + .suspend = s3c24xx_pwm_suspend, + .resume = s3c24xx_pwm_resume, +}; + +static int __init s3c24xx_pwm_init(void) +{ + return platform_driver_register(&s3c24xx_pwm_driver); +} + +static void __exit s3c24xx_pwm_exit(void) +{ +} + +MODULE_AUTHOR("Javi Roman <javiroman@kernel-labs.org>"); +MODULE_LICENSE("GPL"); + +module_init(s3c24xx_pwm_init); +module_exit(s3c24xx_pwm_exit); diff --git a/arch/arm/plat-s3c24xx/cpu.c b/arch/arm/plat-s3c24xx/cpu.c index 1932b7e0da1..ed4c19fa196 100644 --- a/arch/arm/plat-s3c24xx/cpu.c +++ b/arch/arm/plat-s3c24xx/cpu.c @@ -61,6 +61,7 @@ static const char name_s3c2410[] = "S3C2410"; static const char name_s3c2412[] = "S3C2412"; static const char name_s3c2440[] = "S3C2440"; static const char name_s3c2442[] = "S3C2442"; +static const char name_s3c2442b[] = "S3C2442B"; static const char name_s3c2443[] = "S3C2443"; static const char name_s3c2410a[] = "S3C2410A"; static const char name_s3c2440a[] = "S3C2440A"; @@ -112,6 +113,15 @@ static struct cpu_table cpu_ids[] __initdata = { .name = name_s3c2442 }, { + .idcode = 0x32440aab, + .idmask = 0xffffffff, + .map_io = s3c244x_map_io, + .init_clocks = s3c244x_init_clocks, + .init_uarts = s3c244x_init_uarts, + .init = s3c2442_init, + .name = name_s3c2442b + }, + { .idcode = 0x32412001, .idmask = 0xffffffff, .map_io = s3c2412_map_io, diff --git a/arch/arm/plat-s3c24xx/include/plat/irq.h b/arch/arm/plat-s3c24xx/include/plat/irq.h index 69e1be8bec3..11a866466d8 100644 --- a/arch/arm/plat-s3c24xx/include/plat/irq.h +++ b/arch/arm/plat-s3c24xx/include/plat/irq.h @@ -12,6 +12,7 @@ #include <linux/io.h> +#include <mach/irqs.h> #include <mach/hardware.h> #include <mach/regs-irq.h> #include <mach/regs-gpio.h> @@ -31,8 +32,15 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit, { unsigned long mask; unsigned long submask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif submask = __raw_readl(S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); submask |= (1UL << (irqno - IRQ_S3CUART_RX0)); @@ -45,6 +53,9 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit, /* write back masks */ __raw_writel(submask, S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } @@ -53,8 +64,15 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit) { unsigned long mask; unsigned long submask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif submask = __raw_readl(S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); submask &= ~(1UL << (irqno - IRQ_S3CUART_RX0)); @@ -63,6 +81,9 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit) /* write back masks */ __raw_writel(submask, S3C2410_INTSUBMSK); __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } diff --git a/arch/arm/plat-s3c24xx/irq.c b/arch/arm/plat-s3c24xx/irq.c index 958737775ad..4b21ac9d693 100644 --- a/arch/arm/plat-s3c24xx/irq.c +++ b/arch/arm/plat-s3c24xx/irq.c @@ -28,6 +28,8 @@ #include <asm/mach/irq.h> #include <plat/regs-irqtype.h> +#include <mach/regs-irq.h> +#include <mach/regs-gpio.h> #include <plat/cpu.h> #include <plat/pm.h> @@ -37,12 +39,20 @@ static void s3c_irq_mask(unsigned int irqno) { unsigned long mask; - +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif irqno -= IRQ_EINT0; - +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); mask |= 1UL << irqno; __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } static inline void @@ -59,9 +69,19 @@ s3c_irq_maskack(unsigned int irqno) { unsigned long bitval = 1UL << (irqno - IRQ_EINT0); unsigned long mask; - +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif + +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); __raw_writel(mask|bitval, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif __raw_writel(bitval, S3C2410_SRCPND); __raw_writel(bitval, S3C2410_INTPND); @@ -72,15 +92,25 @@ static void s3c_irq_unmask(unsigned int irqno) { unsigned long mask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif if (irqno != IRQ_TIMER4 && irqno != IRQ_EINT8t23) irqdbf2("s3c_irq_unmask %d\n", irqno); irqno -= IRQ_EINT0; +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); mask &= ~(1UL << irqno); __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } struct irq_chip s3c_irq_level_chip = { @@ -523,26 +553,26 @@ void __init s3c24xx_init_irq(void) last = 0; for (i = 0; i < 4; i++) { - pend = __raw_readl(S3C2410_INTPND); + pend = __raw_readl(S3C2410_SUBSRCPND); if (pend == 0 || pend == last) break; - __raw_writel(pend, S3C2410_SRCPND); - __raw_writel(pend, S3C2410_INTPND); - printk("irq: clearing pending status %08x\n", (int)pend); + printk("irq: clearing subpending status %08x\n", (int)pend); + __raw_writel(pend, S3C2410_SUBSRCPND); last = pend; } last = 0; for (i = 0; i < 4; i++) { - pend = __raw_readl(S3C2410_SUBSRCPND); + pend = __raw_readl(S3C2410_INTPND); if (pend == 0 || pend == last) break; - printk("irq: clearing subpending status %08x\n", (int)pend); - __raw_writel(pend, S3C2410_SUBSRCPND); + __raw_writel(pend, S3C2410_SRCPND); + __raw_writel(pend, S3C2410_INTPND); + printk("irq: clearing pending status %08x\n", (int)pend); last = pend; } diff --git a/arch/arm/plat-s3c24xx/pwm-clock.c b/arch/arm/plat-s3c24xx/pwm-clock.c new file mode 100644 index 00000000000..d41cccd6a25 --- /dev/null +++ b/arch/arm/plat-s3c24xx/pwm-clock.c @@ -0,0 +1,437 @@ +/* linux/arch/arm/plat-s3c24xx/pwm-clock.c + * + * Copyright (c) 2007 Simtec Electronics + * Copyright (c) 2007, 2008 Ben Dooks + * Ben Dooks <ben-linux@fluff.org> + * + * 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/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/errno.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> + +#include <mach/hardware.h> +#include <asm/irq.h> + +#include <mach/regs-clock.h> +#include <mach/regs-gpio.h> + +#include <asm/plat-s3c24xx/clock.h> +#include <asm/plat-s3c24xx/cpu.h> + +#include <asm/plat-s3c/regs-timer.h> + +/* Each of the timers 0 through 5 go through the following + * clock tree, with the inputs depending on the timers. + * + * pclk ---- [ prescaler 0 ] -+---> timer 0 + * +---> timer 1 + * + * pclk ---- [ prescaler 1 ] -+---> timer 2 + * +---> timer 3 + * \---> timer 4 + * + * Which are fed into the timers as so: + * + * prescaled 0 ---- [ div 2,4,8,16 ] ---\ + * [mux] -> timer 0 + * tclk 0 ------------------------------/ + * + * prescaled 0 ---- [ div 2,4,8,16 ] ---\ + * [mux] -> timer 1 + * tclk 0 ------------------------------/ + * + * + * prescaled 1 ---- [ div 2,4,8,16 ] ---\ + * [mux] -> timer 2 + * tclk 1 ------------------------------/ + * + * prescaled 1 ---- [ div 2,4,8,16 ] ---\ + * [mux] -> timer 3 + * tclk 1 ------------------------------/ + * + * prescaled 1 ---- [ div 2,4,8, 16 ] --\ + * [mux] -> timer 4 + * tclk 1 ------------------------------/ + * + * Since the mux and the divider are tied together in the + * same register space, it is impossible to set the parent + * and the rate at the same time. To avoid this, we add an + * intermediate 'prescaled-and-divided' clock to select + * as the parent for the timer input clock called tdiv. + * + * prescaled clk --> pwm-tdiv ---\ + * [ mux ] --> timer X + * tclk -------------------------/ +*/ + +static unsigned long clk_pwm_scaler_getrate(struct clk *clk) +{ + unsigned long tcfg0 = __raw_readl(S3C2410_TCFG0); + + if (clk->id == 1) { + tcfg0 &= S3C2410_TCFG_PRESCALER1_MASK; + tcfg0 >>= S3C2410_TCFG_PRESCALER1_SHIFT; + } else { + tcfg0 &= S3C2410_TCFG_PRESCALER0_MASK; + } + + return clk_get_rate(clk->parent) / (tcfg0 + 1); +} + +/* TODO - add set rate calls. */ + +static struct clk clk_timer_scaler[] = { + [0] = { + .name = "pwm-scaler0", + .id = -1, + .get_rate = clk_pwm_scaler_getrate, + }, + [1] = { + .name = "pwm-scaler1", + .id = -1, + .get_rate = clk_pwm_scaler_getrate, + }, +}; + +static struct clk clk_timer_tclk[] = { + [0] = { + .name = "pwm-tclk0", + .id = -1, + }, + [1] = { + .name = "pwm-tclk1", + .id = -1, + }, +}; + +struct pwm_tdiv_clk { + struct clk clk; + unsigned int divisor; +}; + +static inline struct pwm_tdiv_clk *to_tdiv(struct clk *clk) +{ + return container_of(clk, struct pwm_tdiv_clk, clk); +} + +static inline unsigned long tcfg_to_divisor(unsigned long tcfg1) +{ + return 1 << (1 + tcfg1); +} + +static unsigned long clk_pwm_tdiv_get_rate(struct clk *clk) +{ + unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); + unsigned int divisor; + + tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id); + tcfg1 &= S3C2410_TCFG1_MUX_MASK; + + if (tcfg1 == S3C2410_TCFG1_MUX_TCLK) + divisor = to_tdiv(clk)->divisor; + else + divisor = tcfg_to_divisor(tcfg1); + + return clk_get_rate(clk->parent) / divisor; +} + +static unsigned long clk_pwm_tdiv_round_rate(struct clk *clk, + unsigned long rate) +{ + unsigned long parent_rate; + unsigned long divisor; + + parent_rate = clk_get_rate(clk->parent); + divisor = parent_rate / rate; + + if (divisor <= 2) + divisor = 2; + else if (divisor <= 4) + divisor = 4; + else if (divisor <= 8) + divisor = 8; + else + divisor = 16; + + return parent_rate / divisor; +} + +static unsigned long clk_pwm_tdiv_bits(struct pwm_tdiv_clk *divclk) +{ + unsigned long bits; + + switch (divclk->divisor) { + case 2: + bits = S3C2410_TCFG1_MUX_DIV2; + break; + case 4: + bits = S3C2410_TCFG1_MUX_DIV4; + break; + case 8: + bits = S3C2410_TCFG1_MUX_DIV8; + break; + case 16: + default: + bits = S3C2410_TCFG1_MUX_DIV16; + break; + } + + return bits; +} + +static void clk_pwm_tdiv_update(struct pwm_tdiv_clk *divclk) +{ + unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); + unsigned long bits = clk_pwm_tdiv_bits(divclk); + unsigned long flags; + unsigned long shift = S3C2410_TCFG1_SHIFT(divclk->clk.id); + + local_irq_save(flags); + + tcfg1 = __raw_readl(S3C2410_TCFG1); + tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift); + tcfg1 |= bits << shift; + __raw_writel(tcfg1, S3C2410_TCFG1); + + local_irq_restore(flags); +} + +static int clk_pwm_tdiv_set_rate(struct clk *clk, unsigned long rate) +{ + struct pwm_tdiv_clk *divclk = to_tdiv(clk); + unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); + unsigned long parent_rate = clk_get_rate(clk->parent); + unsigned long divisor; + + tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id); + tcfg1 &= S3C2410_TCFG1_MUX_MASK; + + rate = clk_round_rate(clk, rate); + divisor = parent_rate / rate; + + if (divisor > 16) + return -EINVAL; + + divclk->divisor = divisor; + + /* Update the current MUX settings if we are currently + * selected as the clock source for this clock. */ + + if (tcfg1 != S3C2410_TCFG1_MUX_TCLK) + clk_pwm_tdiv_update(divclk); + + return 0; +} + +static struct pwm_tdiv_clk clk_timer_tdiv[] = { + [0] = { + .clk = { + .name = "pwm-tdiv", + .parent = &clk_timer_scaler[0], + .get_rate = clk_pwm_tdiv_get_rate, + .set_rate = clk_pwm_tdiv_set_rate, + .round_rate = clk_pwm_tdiv_round_rate, + }, + }, + [1] = { + .clk = { + .name = "pwm-tdiv", + .parent = &clk_timer_scaler[0], + .get_rate = clk_pwm_tdiv_get_rate, + .set_rate = clk_pwm_tdiv_set_rate, + .round_rate = clk_pwm_tdiv_round_rate, + } + }, + [2] = { + .clk = { + .name = "pwm-tdiv", + .parent = &clk_timer_scaler[1], + .get_rate = clk_pwm_tdiv_get_rate, + .set_rate = clk_pwm_tdiv_set_rate, + .round_rate = clk_pwm_tdiv_round_rate, + }, + }, + [3] = { + .clk = { + .name = "pwm-tdiv", + .parent = &clk_timer_scaler[1], + .get_rate = clk_pwm_tdiv_get_rate, + .set_rate = clk_pwm_tdiv_set_rate, + .round_rate = clk_pwm_tdiv_round_rate, + }, + }, + [4] = { + .clk = { + .name = "pwm-tdiv", + .parent = &clk_timer_scaler[1], + .get_rate = clk_pwm_tdiv_get_rate, + .set_rate = clk_pwm_tdiv_set_rate, + .round_rate = clk_pwm_tdiv_round_rate, + }, + }, +}; + +static int __init clk_pwm_tdiv_register(unsigned int id) +{ + struct pwm_tdiv_clk *divclk = &clk_timer_tdiv[id]; + unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); + + tcfg1 >>= S3C2410_TCFG1_SHIFT(id); + tcfg1 &= S3C2410_TCFG1_MUX_MASK; + + divclk->clk.id = id; + divclk->divisor = tcfg_to_divisor(tcfg1); + + return s3c24xx_register_clock(&divclk->clk); +} + +static inline struct clk *s3c24xx_pwmclk_tclk(unsigned int id) +{ + return (id >= 2) ? &clk_timer_tclk[1] : &clk_timer_tclk[0]; +} + +static inline struct clk *s3c24xx_pwmclk_tdiv(unsigned int id) +{ + return &clk_timer_tdiv[id].clk; +} + +static int clk_pwm_tin_set_parent(struct clk *clk, struct clk *parent) +{ + unsigned int id = clk->id; + unsigned long tcfg1; + unsigned long flags; + unsigned long bits; + unsigned long shift = S3C2410_TCFG1_SHIFT(id); + + if (parent == s3c24xx_pwmclk_tclk(id)) + bits = S3C2410_TCFG1_MUX_TCLK << shift; + else if (parent == s3c24xx_pwmclk_tdiv(id)) + bits = clk_pwm_tdiv_bits(to_tdiv(parent)) << shift; + else + return -EINVAL; + + clk->parent = parent; + + local_irq_save(flags); + + tcfg1 = __raw_readl(S3C2410_TCFG1); + tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift); + __raw_writel(tcfg1 | bits, S3C2410_TCFG1); + + local_irq_restore(flags); + + return 0; +} + +static struct clk clk_tin[] = { + [0] = { + .name = "pwm-tin", + .id = 0, + .set_parent = clk_pwm_tin_set_parent, + }, + [1] = { + .name = "pwm-tin", + .id = 1, + .set_parent = clk_pwm_tin_set_parent, + }, + [2] = { + .name = "pwm-tin", + .id = 2, + .set_parent = clk_pwm_tin_set_parent, + }, + [3] = { + .name = "pwm-tin", + .id = 3, + .set_parent = clk_pwm_tin_set_parent, + }, + [4] = { + .name = "pwm-tin", + .id = 4, + .set_parent = clk_pwm_tin_set_parent, + }, +}; + +static __init int clk_pwm_tin_register(struct clk *pwm) +{ + unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); + unsigned int id = pwm->id; + + struct clk *parent; + int ret; + + ret = s3c24xx_register_clock(pwm); + if (ret < 0) + return ret; + + tcfg1 >>= S3C2410_TCFG1_SHIFT(id); + tcfg1 &= S3C2410_TCFG1_MUX_MASK; + + if (tcfg1 == S3C2410_TCFG1_MUX_TCLK) + parent = s3c24xx_pwmclk_tclk(id); + else + parent = s3c24xx_pwmclk_tdiv(id); + + return clk_set_parent(pwm, parent); +} + +static __init int s3c24xx_pwmclk_init(void) +{ + struct clk *clk_timers; + unsigned int clk; + int ret; + + clk_timers = clk_get(NULL, "timers"); + if (IS_ERR(clk_timers)) { + printk(KERN_ERR "%s: no parent clock\n", __func__); + return -EINVAL; + } + + for (clk = 0; clk < ARRAY_SIZE(clk_timer_scaler); clk++) { + clk_timer_scaler[clk].parent = clk_timers; + ret = s3c24xx_register_clock(&clk_timer_scaler[clk]); + if (ret < 0) { + printk(KERN_ERR "error adding pwm scaler%d clock\n", clk); + goto err; + } + } + + for (clk = 0; clk < ARRAY_SIZE(clk_timer_tclk); clk++) { + ret = s3c24xx_register_clock(&clk_timer_tclk[clk]); + if (ret < 0) { + printk(KERN_ERR "error adding pww tclk%d\n", clk); + goto err; + } + } + + for (clk = 0; clk < ARRAY_SIZE(clk_timer_tdiv); clk++) { + ret = clk_pwm_tdiv_register(clk); + if (ret < 0) { + printk(KERN_ERR "error adding pwm%d tdiv clock\n", clk); + goto err; + } + } + + for (clk = 0; clk < ARRAY_SIZE(clk_tin); clk++) { + ret = clk_pwm_tin_register(&clk_tin[clk]); + if (ret < 0) { + printk(KERN_ERR "error adding pwm%d tin clock\n", clk); + goto err; + } + } + + return 0; + + err: + return ret; +} + +arch_initcall(s3c24xx_pwmclk_init); |