rk3188: add rk timer support
author黄涛 <huangtao@rock-chips.com>
Tue, 15 Jan 2013 03:10:05 +0000 (11:10 +0800)
committer黄涛 <huangtao@rock-chips.com>
Tue, 15 Jan 2013 03:10:05 +0000 (11:10 +0800)
arch/arm/Kconfig
arch/arm/mach-rk3188/Makefile
arch/arm/mach-rk3188/rk_timer.c [new file with mode: 0644]
arch/arm/plat-rk/Kconfig
arch/arm/plat-rk/Makefile
arch/arm/plat-rk/rk_timer.c [new file with mode: 0644]

index 50f3ae922fc03f59b6560a5637828e73d38f5bf2..fde5a928ee034c5d3ca747be025646ece5beaed3 100644 (file)
@@ -862,6 +862,7 @@ config ARCH_OMAP
 config ARCH_RK29
        bool "Rockchip RK29xx"
        select PLAT_RK
+       select HAVE_SCHED_CLOCK
        select CPU_V7
        select ARM_GIC
        select PL330
@@ -874,6 +875,7 @@ config ARCH_RK29
 config ARCH_RK2928
        bool "Rockchip RK2928"
        select PLAT_RK
+       select HAVE_SCHED_CLOCK
        select CPU_V7
        select ARM_GIC
        select RK_PL330_DMA
@@ -886,6 +888,7 @@ config ARCH_RK2928
 config ARCH_RK30
        bool "Rockchip RK30xx/RK3108/RK3168"
        select PLAT_RK
+       select HAVE_SCHED_CLOCK
        select CPU_V7
        select ARM_GIC
        select RK_PL330_DMA
@@ -903,6 +906,7 @@ config ARCH_RK3188
        select CPU_V7
        select ARM_GIC
        select RK_PL330_DMA
+       select RK_TIMER
        select HAVE_SMP
        select MIGHT_HAVE_CACHE_L2X0
        select ARM_ERRATA_764369
@@ -1086,7 +1090,6 @@ config PLAT_PXA
 config PLAT_RK
        bool
        select CLKDEV_LOOKUP
-       select HAVE_SCHED_CLOCK
        select ARCH_HAS_CPUFREQ
        select GENERIC_CLOCKEVENTS
        select ARCH_REQUIRE_GPIOLIB
@@ -1507,7 +1510,7 @@ config LOCAL_TIMERS
        bool "Use local timer interrupts"
        depends on SMP
        default y
-       select HAVE_ARM_TWD if (!ARCH_MSM_SCORPIONMP && !EXYNOS4_MCT)
+       select HAVE_ARM_TWD if (!ARCH_MSM_SCORPIONMP && !EXYNOS4_MCT && !RK_TIMER)
        help
          Enable support for local timers on SMP platforms, rather then the
          legacy IPI broadcast method.  Local timers allows the system
index d98d674555bd455556ad7cc847eda5c044201903..f5874a6edc3b950c9eb505738c44bd8445e46ea8 100644 (file)
@@ -10,5 +10,6 @@ obj-y += ../mach-rk30/common.o
 CFLAGS_common.o += -DTEXT_OFFSET=$(TEXT_OFFSET)
 obj-y += ../mach-rk30/devices.o
 obj-y += io.o
+obj-y += rk_timer.o
 
 obj-$(CONFIG_MACH_RK3188_FPGA) += board-rk3188-fpga.o
diff --git a/arch/arm/mach-rk3188/rk_timer.c b/arch/arm/mach-rk3188/rk_timer.c
new file mode 100644 (file)
index 0000000..225d850
--- /dev/null
@@ -0,0 +1,108 @@
+#include <linux/platform_device.h>
+#include <asm/mach/time.h>
+#include <mach/io.h>
+#include <mach/irqs.h>
+
+#define TIMER_NAME     "rk_timer"
+#define BASE           RK30_TIMER0_BASE
+#define OFFSET         0x20
+
+static struct resource rk_timer_resources[] __initdata = {
+       {
+               .name   = "cs_base",
+               .start  = (unsigned long) BASE + 5 * OFFSET,
+               .flags  = IORESOURCE_MEM,
+       }, {
+               .name   = "cs_clk",
+               .start  = (unsigned long) "timer6",
+       }, {
+               .name   = "cs_pclk",
+               .start  = (unsigned long) "pclk_timer0",
+       },
+
+       {
+               .name   = "ce_base0",
+               .start  = (unsigned long) BASE + 0 * OFFSET,
+               .flags  = IORESOURCE_MEM,
+       }, {
+               .name   = "ce_irq0",
+               .start  = (unsigned long) IRQ_TIMER0,
+               .flags  = IORESOURCE_IRQ,
+       }, {
+               .name   = "ce_clk0",
+               .start  = (unsigned long) "timer0",
+       }, {
+               .name   = "ce_pclk0",
+               .start  = (unsigned long) "pclk_timer0",
+       },
+
+       {
+               .name   = "ce_base1",
+               .start  = (unsigned long) BASE + 1 * OFFSET,
+               .flags  = IORESOURCE_MEM,
+       }, {
+               .name   = "ce_irq1",
+               .start  = (unsigned long) IRQ_TIMER1,
+               .flags  = IORESOURCE_IRQ,
+       }, {
+               .name   = "ce_clk1",
+               .start  = (unsigned long) "timer1",
+       }, {
+               .name   = "ce_pclk1",
+               .start  = (unsigned long) "pclk_timer0",
+       },
+
+       {
+               .name   = "ce_base2",
+               .start  = (unsigned long) BASE + 3 * OFFSET,
+               .flags  = IORESOURCE_MEM,
+       }, {
+               .name   = "ce_irq2",
+               .start  = (unsigned long) IRQ_TIMER4,
+               .flags  = IORESOURCE_IRQ,
+       }, {
+               .name   = "ce_clk2",
+               .start  = (unsigned long) "timer4",
+       }, {
+               .name   = "ce_pclk2",
+               .start  = (unsigned long) "pclk_timer0",
+       },
+
+       {
+               .name   = "ce_base3",
+               .start  = (unsigned long) BASE + 4 * OFFSET,
+               .flags  = IORESOURCE_MEM,
+       }, {
+               .name   = "ce_irq3",
+               .start  = (unsigned long) IRQ_TIMER5,
+               .flags  = IORESOURCE_IRQ,
+       }, {
+               .name   = "ce_clk3",
+               .start  = (unsigned long) "timer5",
+       }, {
+               .name   = "ce_pclk3",
+               .start  = (unsigned long) "pclk_timer0",
+       },
+};
+
+static struct platform_device rk_timer_device __initdata = {
+        .name           = TIMER_NAME,
+        .id             = 0,
+        .resource       = rk_timer_resources,
+        .num_resources  = ARRAY_SIZE(rk_timer_resources),
+};
+
+static struct platform_device *rk_timer_devices[] __initdata = {
+       &rk_timer_device,
+};
+
+static void __init rk_timer_init(void)
+{
+       early_platform_add_devices(rk_timer_devices, ARRAY_SIZE(rk_timer_devices));
+        early_platform_driver_register_all(TIMER_NAME);
+        early_platform_driver_probe(TIMER_NAME, 1, 0);
+}
+
+struct sys_timer rk30_timer = {
+       .init = rk_timer_init
+};
index 47ceeade6135f9a572243c435d62cab5f9354c98..fd7ac3b5e421428ff5b8bf0738963dcd6763657b 100755 (executable)
@@ -195,4 +195,7 @@ config RK_FPGA
 config RK_CONFIG
        bool
 
+config RK_TIMER
+       bool
+
 endif
index accfca14b1f46d778f119da13fab82adc1dbfabb..c94e1851476d71ff6a192af4660e53758ae29973 100644 (file)
@@ -15,3 +15,4 @@ CFLAGS_sram.o += -mthumb
 obj-$(CONFIG_DDR_TEST) += memtester.o ddr_test.o
 obj-$(CONFIG_DDR_FREQ) += ddr_freq.o
 obj-y += pwm.o
+obj-$(CONFIG_RK_TIMER) += rk_timer.o
diff --git a/arch/arm/plat-rk/rk_timer.c b/arch/arm/plat-rk/rk_timer.c
new file mode 100644 (file)
index 0000000..24dc1b5
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+ *
+ * Copyright (C) 2013 ROCKCHIP, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/platform_device.h>
+
+#include <asm/localtimer.h>
+
+#define TIMER_NAME "rk_timer"
+
+#define TIMER_LOAD_COUNT0               0x00
+#define TIMER_LOAD_COUNT1               0x04
+#define TIMER_CURRENT_VALUE0            0x08
+#define TIMER_CURRENT_VALUE1            0x0c
+#define TIMER_CONTROL_REG               0x10
+#define TIMER_INT_STATUS                0x18
+
+#define TIMER_DISABLE                   (0 << 0)
+#define TIMER_ENABLE                    (1 << 0)
+#define TIMER_MODE_FREE_RUNNING         (0 << 1)
+#define TIMER_MODE_USER_DEFINED_COUNT   (1 << 1)
+#define TIMER_INT_MASK                  (0 << 2)
+#define TIMER_INT_UNMASK                (1 << 2)
+
+static inline void rk_timer_disable(void __iomem *base)
+{
+       writel_relaxed(TIMER_DISABLE, base + TIMER_CONTROL_REG);
+       dsb();
+}
+
+static inline void rk_timer_enable(void __iomem *base, u32 flags)
+{
+       writel_relaxed(TIMER_ENABLE | flags, base + TIMER_CONTROL_REG);
+       dsb();
+}
+
+static inline u64 rk_timer_read_current_value(void __iomem *base)
+{
+       /*
+        * 1. Read the upper 32-bit timer counter register
+        * 2. Read the lower 32-bit timer counter register
+        * 3. Read the upper 32-bit timer counter register again. If the value is different to the 32-bit
+        * upper value read previously, go back to step 2. Otherwise the 64-bit timer counter value
+        * is correct.
+        */
+       u32 upper, lower;
+
+       do {
+               upper = readl_relaxed(base + TIMER_CURRENT_VALUE1);
+               lower = readl_relaxed(base + TIMER_CURRENT_VALUE0);
+       } while (upper != readl_relaxed(base + TIMER_CURRENT_VALUE1));
+
+       return ((u64) upper << 32) + lower;
+}
+
+struct rk_timer {
+       void __iomem *cs_base;
+       struct clk *cs_clk;
+       struct clk *cs_pclk;
+
+       void __iomem *ce_base[NR_CPUS];
+       struct irqaction ce_irq[NR_CPUS];
+       struct clk *ce_clk[NR_CPUS];
+       struct clk *ce_pclk[NR_CPUS];
+       char ce_name[NR_CPUS][16];
+};
+static struct rk_timer timer;
+
+static const char *platform_get_string_byname(struct platform_device *dev, const char *name)
+{
+       struct resource *r = platform_get_resource_byname(dev, 0, name);
+
+       return r ? (const char *)r->start : NULL;
+}
+
+static int rk_timer_set_next_event(unsigned long cycles, struct clock_event_device *evt)
+{
+       void __iomem *base = timer.ce_base[smp_processor_id()];
+
+       rk_timer_disable(base);
+       writel_relaxed(cycles, base + TIMER_LOAD_COUNT0);
+       dsb();
+       rk_timer_enable(base, TIMER_MODE_USER_DEFINED_COUNT | TIMER_INT_UNMASK);
+       return 0;
+}
+
+static void rk_timer_set_mode(enum clock_event_mode mode, struct clock_event_device *evt)
+{
+       void __iomem *base = timer.ce_base[smp_processor_id()];
+
+       switch (mode) {
+       case CLOCK_EVT_MODE_PERIODIC:
+               rk_timer_disable(base);
+               writel_relaxed(24000000 / HZ - 1, base + TIMER_LOAD_COUNT0);
+               dsb();
+               rk_timer_enable(base, TIMER_MODE_FREE_RUNNING | TIMER_INT_UNMASK);
+               break;
+       case CLOCK_EVT_MODE_RESUME:
+       case CLOCK_EVT_MODE_ONESHOT:
+               break;
+       case CLOCK_EVT_MODE_UNUSED:
+       case CLOCK_EVT_MODE_SHUTDOWN:
+               rk_timer_disable(base);
+               break;
+       }
+}
+
+static irqreturn_t rk_timer_clockevent_interrupt(int irq, void *dev_id)
+{
+       struct clock_event_device *evt = dev_id;
+       void __iomem *base = timer.ce_base[smp_processor_id()];
+
+       /* clear interrupt */
+       writel_relaxed(1, base + TIMER_INT_STATUS);
+       if (evt->mode == CLOCK_EVT_MODE_ONESHOT) {
+               writel_relaxed(TIMER_DISABLE, base + TIMER_CONTROL_REG);
+       }
+       dsb();
+
+       evt->event_handler(evt);
+
+       return IRQ_HANDLED;
+}
+
+static __cpuinit int rk_timer_init_clockevent(struct clock_event_device *ce, unsigned int cpu)
+{
+       struct irqaction *irq = &timer.ce_irq[cpu];
+       void __iomem *base = timer.ce_base[cpu];
+
+       ce->name = timer.ce_name[cpu];
+       ce->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
+       ce->set_next_event = rk_timer_set_next_event;
+       ce->set_mode = rk_timer_set_mode;
+       ce->irq = irq->irq;
+       ce->cpumask = cpumask_of(cpu);
+
+       rk_timer_disable(base);
+
+       irq->dev_id = ce;
+       irq_set_affinity(irq->irq, cpumask_of(cpu));
+       setup_irq(irq->irq, irq);
+
+       clockevents_config_and_register(ce, 24000000, 0xF, 0xFFFFFFFF);
+
+       return 0;
+}
+
+static cycle_t rk_timer_read(struct clocksource *cs)
+{
+       return ~rk_timer_read_current_value(timer.cs_base);
+}
+
+static struct clocksource rk_timer_clocksource = {
+       .name           = TIMER_NAME,
+       .rating         = 200,
+       .read           = rk_timer_read,
+       .mask           = CLOCKSOURCE_MASK(64),
+       .flags          = CLOCK_SOURCE_IS_CONTINUOUS,
+};
+
+static void __init rk_timer_init_clocksource(void)
+{
+       static char err[] __initdata = KERN_ERR "%s: can't register clocksource!\n";
+       struct clocksource *cs = &rk_timer_clocksource;
+       void __iomem *base = timer.cs_base;
+
+       clk_enable(timer.cs_pclk);
+       clk_enable(timer.cs_clk);
+
+       rk_timer_disable(base);
+       writel_relaxed(0xFFFFFFFF, base + TIMER_LOAD_COUNT0);
+       writel_relaxed(0xFFFFFFFF, base + TIMER_LOAD_COUNT1);
+       dsb();
+       rk_timer_enable(base, TIMER_MODE_FREE_RUNNING | TIMER_INT_MASK);
+
+       if (clocksource_register_hz(cs, 24000000))
+               printk(err, cs->name);
+}
+
+#ifndef CONFIG_LOCAL_TIMERS
+static struct clock_event_device rk_timer_clockevent;
+#endif
+
+static int __init rk_timer_probe(struct platform_device *pdev)
+{
+       struct resource *res;
+       int cpu;
+
+       timer.cs_clk = clk_get(NULL, platform_get_string_byname(pdev, "cs_clk"));
+       timer.cs_pclk = clk_get(NULL, platform_get_string_byname(pdev, "cs_pclk"));
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cs_base");
+       timer.cs_base = (void *)res->start;
+
+       for (cpu = 0; cpu < NR_CPUS; cpu++) {
+               char name[16];
+               struct irqaction *irq = &timer.ce_irq[cpu];
+
+               snprintf(timer.ce_name[cpu], sizeof(timer.ce_name[cpu]), TIMER_NAME "%d", cpu);
+
+               snprintf(name, sizeof(name), "ce_clk%d", cpu);
+               timer.ce_clk[cpu] = clk_get(NULL, platform_get_string_byname(pdev, name));
+
+               snprintf(name, sizeof(name), "ce_pclk%d", cpu);
+               timer.ce_pclk[cpu] = clk_get(NULL, platform_get_string_byname(pdev, name));
+
+               snprintf(name, sizeof(name), "ce_base%d", cpu);
+               res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
+               timer.ce_base[cpu] = (void *)res->start;
+
+               snprintf(name, sizeof(name), "ce_irq%d", cpu);
+               irq->irq = platform_get_irq_byname(pdev, name);
+               irq->name = timer.ce_name[cpu];
+               irq->flags =  IRQF_DISABLED | IRQF_TIMER | IRQF_NOBALANCING;
+               irq->handler = rk_timer_clockevent_interrupt;
+
+               clk_enable(timer.ce_pclk[cpu]);
+               clk_enable(timer.ce_clk[cpu]);
+       }
+
+       rk_timer_init_clocksource();
+#ifndef CONFIG_LOCAL_TIMERS
+       rk_timer_clockevent.rating = 200;
+       rk_timer_init_clockevent(&rk_timer_clockevent, 0);
+#endif
+
+       printk("rk_timer: version 1.0\n");
+       return 0;
+}
+
+static struct platform_driver rk_timer_driver __initdata = {
+       .probe          = rk_timer_probe,
+       .driver         = {
+               .name   = TIMER_NAME,
+       }
+};
+
+early_platform_init(TIMER_NAME, &rk_timer_driver);
+
+#ifdef CONFIG_LOCAL_TIMERS
+/*
+ * Setup the local clock events for a CPU.
+ */
+static int __cpuinit rk_local_timer_setup(struct clock_event_device *clk)
+{
+       clk->rating = 450;
+       return rk_timer_init_clockevent(clk, smp_processor_id());
+}
+
+/*
+ * Setup the local clock events for a CPU.
+ */
+int __cpuinit local_timer_setup(struct clock_event_device *evt)
+{
+       return rk_local_timer_setup(evt);
+}
+
+/*
+ * local_timer_ack: checks for a local timer interrupt.
+ *
+ * If a local timer interrupt has occurred, acknowledge and return 1.
+ * Otherwise, return 0.
+ */
+int local_timer_ack(void)
+{
+       return 0;
+}
+#endif