From: Evgeny Voevodin Date: Tue, 26 Jun 2012 11:52:05 +0000 (+0400) Subject: ARM: Tizen: Switch PWM as system clock source. X-Git-Tag: 2.2.1_release^2~83^2~7^2~32 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=1894eee08cef3cc5318e492257dce984f28eaf14;p=sdk%2Femulator%2Femulator-kernel.git ARM: Tizen: Switch PWM as system clock source. MCT without local timers is found as unstable clock source which can sometimes give target time slower then on host. This could be due to emulation overhead. PWM timer is simpler, faster and gives much less overhead. It seems that it's rather stable and doesn't give errors in time. Signed-off-by: Evgeny Voevodin --- diff --git a/arch/arm/configs/tizen_defconfig b/arch/arm/configs/tizen_defconfig index 120ebc13b397..2bc4412ee123 100644 --- a/arch/arm/configs/tizen_defconfig +++ b/arch/arm/configs/tizen_defconfig @@ -369,7 +369,8 @@ CONFIG_ARCH_EXYNOS4=y CONFIG_CPU_EXYNOS4210=y CONFIG_SOC_EXYNOS4212=y CONFIG_SOC_EXYNOS4412=y -CONFIG_EXYNOS4_MCT=y +# CONFIG_EXYNOS4_MCT is not set +CONFIG_TIZEN_PWM=y CONFIG_EXYNOS4_DEV_DMA=y CONFIG_EXYNOS4_DEV_AHCI=y CONFIG_EXYNOS4_SETUP_FIMD0=y diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig index 5432a9bcab6d..2749431174cd 100644 --- a/arch/arm/mach-exynos/Kconfig +++ b/arch/arm/mach-exynos/Kconfig @@ -70,6 +70,13 @@ config EXYNOS4_MCT help Use MCT (Multi Core Timer) as kernel timers +config TIZEN_PWM + bool "Tizen PWM" + depends on MACH_TIZEN + default y + help + Support for Tizen PWM timer. + config EXYNOS4_DEV_DMA bool help diff --git a/arch/arm/mach-exynos/Makefile b/arch/arm/mach-exynos/Makefile index 770b07c127e2..f607748abb56 100644 --- a/arch/arm/mach-exynos/Makefile +++ b/arch/arm/mach-exynos/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_ARCH_EXYNOS4) += pmu.o obj-$(CONFIG_SMP) += platsmp.o headsmp.o obj-$(CONFIG_EXYNOS4_MCT) += mct.o +obj-$(CONFIG_TIZEN_PWM) += tizen-pwm.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o diff --git a/arch/arm/mach-exynos/common.h b/arch/arm/mach-exynos/common.h index 677b5467df18..f89ebdd1b7fc 100644 --- a/arch/arm/mach-exynos/common.h +++ b/arch/arm/mach-exynos/common.h @@ -13,6 +13,9 @@ #define __ARCH_ARM_MACH_EXYNOS_COMMON_H extern struct sys_timer exynos4_timer; +#ifdef CONFIG_MACH_TIZEN +extern struct sys_timer tizen_timer; +#endif void exynos_init_io(struct map_desc *mach_desc, int size); void exynos4_init_irq(void); diff --git a/arch/arm/mach-exynos/mach-tizen.c b/arch/arm/mach-exynos/mach-tizen.c index 345ad6a0ecef..abe5f81458ed 100644 --- a/arch/arm/mach-exynos/mach-tizen.c +++ b/arch/arm/mach-exynos/mach-tizen.c @@ -1413,7 +1413,7 @@ MACHINE_START(TIZEN, "Tizen") .map_io = tizen_map_io, .handle_irq = gic_handle_irq, .init_machine = tizen_machine_init, - .timer = &exynos4_timer, + .timer = &tizen_timer, .reserve = &tizen_reserve, .restart = tizen_restart, MACHINE_END diff --git a/arch/arm/mach-exynos/tizen-pwm.c b/arch/arm/mach-exynos/tizen-pwm.c new file mode 100644 index 000000000000..9b31fc9a19c7 --- /dev/null +++ b/arch/arm/mach-exynos/tizen-pwm.c @@ -0,0 +1,309 @@ +/* linux/arch/arm/mach-exynos4/time.c + * + * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * EXYNOS4 (and compatible) HRT support + * PWM 2/4 is used for this feature + * + * 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. + * + * Based on kernel-v3.0/arch/arm/mach-exynos4/time.c +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +static unsigned long clock_count_per_tick; + +static struct clk *tin2; +static struct clk *tin4; +static struct clk *tdiv2; +static struct clk *tdiv4; +static struct clk *timerclk; + +static void exynos4_pwm_stop(unsigned int pwm_id) +{ + unsigned long tcon; + + tcon = __raw_readl(S3C2410_TCON); + + switch (pwm_id) { + case 2: + tcon &= ~S3C2410_TCON_T2START; + break; + case 4: + tcon &= ~S3C2410_TCON_T4START; + break; + default: + break; + } + __raw_writel(tcon, S3C2410_TCON); +} + +static void exynos4_pwm_init(unsigned int pwm_id, unsigned long tcnt) +{ + unsigned long tcon; + + tcon = __raw_readl(S3C2410_TCON); + + /* timers reload after counting zero, so reduce the count by 1 */ + tcnt--; + + /* ensure timer is stopped... */ + switch (pwm_id) { + case 2: + tcon &= ~(0xf<<12); + tcon |= S3C2410_TCON_T2MANUALUPD; + + __raw_writel(tcnt, S3C2410_TCNTB(2)); + __raw_writel(tcnt, S3C2410_TCMPB(2)); + __raw_writel(tcon, S3C2410_TCON); + + break; + case 4: + tcon &= ~(7<<20); + tcon |= S3C2410_TCON_T4MANUALUPD; + + __raw_writel(tcnt, S3C2410_TCNTB(4)); + __raw_writel(tcon, S3C2410_TCON); + + break; + default: + break; + } +} + +static inline void exynos4_pwm_start(unsigned int pwm_id, bool periodic) +{ + unsigned long tcon; + + tcon = __raw_readl(S3C2410_TCON); + + switch (pwm_id) { + case 2: + tcon |= S3C2410_TCON_T2START; + tcon &= ~S3C2410_TCON_T2MANUALUPD; + + if (periodic) + tcon |= S3C2410_TCON_T2RELOAD; + else + tcon &= ~S3C2410_TCON_T2RELOAD; + break; + case 4: + tcon |= S3C2410_TCON_T4START; + tcon &= ~S3C2410_TCON_T4MANUALUPD; + + if (periodic) + tcon |= S3C2410_TCON_T4RELOAD; + else + tcon &= ~S3C2410_TCON_T4RELOAD; + break; + default: + break; + } + __raw_writel(tcon, S3C2410_TCON); +} + +static int exynos4_pwm_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + exynos4_pwm_init(2, cycles); + exynos4_pwm_start(2, 0); + return 0; +} + +static void exynos4_pwm_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + exynos4_pwm_stop(2); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + exynos4_pwm_init(2, clock_count_per_tick); + exynos4_pwm_start(2, 1); + break; + case CLOCK_EVT_MODE_ONESHOT: + break; + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_RESUME: + break; + } +} + +static struct clock_event_device pwm_event_device = { + .name = "pwm_timer2", + .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, + .rating = 250, + .shift = 32, + .set_next_event = exynos4_pwm_set_next_event, + .set_mode = exynos4_pwm_set_mode, +}; + +irqreturn_t exynos4_clock_event_isr(int irq, void *dev_id) +{ + struct clock_event_device *evt = &pwm_event_device; + + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction exynos4_clock_event_irq = { + .name = "pwm_timer2_irq", + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, + .handler = exynos4_clock_event_isr, +}; + +static void __init exynos4_clockevent_init(void) +{ + unsigned long pclk; + unsigned long clock_rate; + struct clk *tscaler; + + pclk = clk_get_rate(timerclk); + + /* configure clock tick */ + + tscaler = clk_get_parent(tdiv2); + + clk_set_rate(tscaler, pclk / 2); + clk_set_rate(tdiv2, pclk / 2); + clk_set_parent(tin2, tdiv2); + + clock_rate = clk_get_rate(tin2); + + clock_count_per_tick = clock_rate / HZ; + + pwm_event_device.mult = + div_sc(clock_rate, NSEC_PER_SEC, pwm_event_device.shift); + pwm_event_device.max_delta_ns = + clockevent_delta2ns(-1, &pwm_event_device); + pwm_event_device.min_delta_ns = + clockevent_delta2ns(1, &pwm_event_device); + + pwm_event_device.cpumask = cpumask_of(0); + clockevents_register_device(&pwm_event_device); + + setup_irq(IRQ_TIMER2, &exynos4_clock_event_irq); +} + +static cycle_t exynos4_pwm4_read(struct clocksource *cs) +{ + return (cycle_t) ~__raw_readl(S3C_TIMERREG(0x40)); +} + +#ifdef CONFIG_PM +static void exynos4_pwm4_resume(struct clocksource *cs) +{ + unsigned long pclk; + + pclk = clk_get_rate(timerclk); + + clk_set_rate(tdiv4, pclk / 2); + clk_set_parent(tin4, tdiv4); + + exynos4_pwm_init(4, ~0); + exynos4_pwm_start(4, 1); +} +#endif + +struct clocksource pwm_clocksource = { + .name = "pwm_timer4", + .rating = 250, + .read = exynos4_pwm4_read, + .mask = CLOCKSOURCE_MASK(32), + .flags = CLOCK_SOURCE_IS_CONTINUOUS , +#ifdef CONFIG_PM + .resume = exynos4_pwm4_resume, +#endif +}; + +static void __init exynos4_clocksource_init(void) +{ + unsigned long pclk; + unsigned long clock_rate; + + pclk = clk_get_rate(timerclk); + + clk_set_rate(tdiv4, pclk / 2); + clk_set_parent(tin4, tdiv4); + + clock_rate = clk_get_rate(tin2); + + exynos4_pwm_init(4, ~0); + exynos4_pwm_start(4, 1); + + if (clocksource_register_hz(&pwm_clocksource, clock_rate)) + panic("%s: can't register clocksource\n", pwm_clocksource.name); +} + +static void __init exynos4_timer_resources(void) +{ + struct platform_device tmpdev; + char devname[15]; + + tmpdev.dev.bus = &platform_bus_type; + + timerclk = clk_get(NULL, "sclk_pwm"); + if (IS_ERR(timerclk)) + panic("failed to get timers clock for system timer"); + + clk_enable(timerclk); + + tmpdev.id = 2; + sprintf(devname, "s3c24xx-pwm.%d", tmpdev.id); + tmpdev.dev.init_name = devname; + + tin2 = clk_get(&tmpdev.dev, "pwm-tin"); + if (IS_ERR(tin2)) + panic("failed to get pwm-tin2 clock for system timer"); + + tdiv2 = clk_get(&tmpdev.dev, "pwm-tdiv"); + if (IS_ERR(tdiv2)) + panic("failed to get pwm-tdiv2 clock for system timer"); + clk_enable(tin2); + + tmpdev.id = 4; + sprintf(devname, "s3c24xx-pwm.%d", tmpdev.id); + tmpdev.dev.init_name = devname; + + tin4 = clk_get(&tmpdev.dev, "pwm-tin"); + if (IS_ERR(tin4)) + panic("failed to get pwm-tin4 clock for system timer"); + + tdiv4 = clk_get(&tmpdev.dev, "pwm-tdiv"); + if (IS_ERR(tdiv4)) + panic("failed to get pwm-tdiv4 clock for system timer"); + + clk_enable(tin4); +} + +static void __init tizen_timer_init(void) +{ +#ifdef CONFIG_LOCAL_TIMERS + twd_base = S5P_VA_TWD; +#endif + + exynos4_timer_resources(); + exynos4_clockevent_init(); + exynos4_clocksource_init(); +} + +struct sys_timer tizen_timer = { + .init = tizen_timer_init, +};