ARM: Tizen: Switch PWM as system clock source.
authorEvgeny Voevodin <e.voevodin@samsung.com>
Tue, 26 Jun 2012 11:52:05 +0000 (15:52 +0400)
committerEvgeny Voevodin <e.voevodin@samsung.com>
Fri, 14 Sep 2012 05:42:21 +0000 (09:42 +0400)
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 <e.voevodin@samsung.com>
arch/arm/configs/tizen_defconfig
arch/arm/mach-exynos/Kconfig
arch/arm/mach-exynos/Makefile
arch/arm/mach-exynos/common.h
arch/arm/mach-exynos/mach-tizen.c
arch/arm/mach-exynos/tizen-pwm.c [new file with mode: 0644]

index 120ebc13b397e9d66617449b728eda1839baccc2..2bc4412ee1233569df8c2361488b19f531c9b5e6 100644 (file)
@@ -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
index 5432a9bcab6d788cdec4bb732874fc41f7259d45..2749431174cd37c6aa1c275b48f03b7409f61661 100644 (file)
@@ -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
index 770b07c127e21df2ccda2d1b91ee8ca8fa9dcb11..f607748abb5674818e86d58470c89cdcdbc7b535 100644 (file)
@@ -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
 
index 677b5467df186298ecd2a8f0046e4e6f46c9fdc5..f89ebdd1b7fcab15544eddfcdad0e9522c3141ee 100644 (file)
@@ -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);
index 345ad6a0ecef451d4eb43f2a1e5de1f2c8ed50d7..abe5f81458ed9b20eac902947abf9acce8e59663 100644 (file)
@@ -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 (file)
index 0000000..9b31fc9
--- /dev/null
@@ -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 <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/platform_device.h>
+
+#include <asm/smp_twd.h>
+
+#include <mach/map.h>
+#include <plat/regs-timer.h>
+#include <asm/mach/time.h>
+
+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,
+};