From 0f56471dd616f211d6204e5118a6a3d1d0359fb5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E9=BB=84=E6=B6=9B?= Date: Tue, 15 Jan 2013 11:10:05 +0800 Subject: [PATCH] rk3188: add rk timer support --- arch/arm/Kconfig | 7 +- arch/arm/mach-rk3188/Makefile | 1 + arch/arm/mach-rk3188/rk_timer.c | 108 ++++++++++++ arch/arm/plat-rk/Kconfig | 3 + arch/arm/plat-rk/Makefile | 1 + arch/arm/plat-rk/rk_timer.c | 284 ++++++++++++++++++++++++++++++++ 6 files changed, 402 insertions(+), 2 deletions(-) create mode 100644 arch/arm/mach-rk3188/rk_timer.c create mode 100644 arch/arm/plat-rk/rk_timer.c diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 50f3ae922fc0..fde5a928ee03 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -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 diff --git a/arch/arm/mach-rk3188/Makefile b/arch/arm/mach-rk3188/Makefile index d98d674555bd..f5874a6edc3b 100644 --- a/arch/arm/mach-rk3188/Makefile +++ b/arch/arm/mach-rk3188/Makefile @@ -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 index 000000000000..225d8506d61b --- /dev/null +++ b/arch/arm/mach-rk3188/rk_timer.c @@ -0,0 +1,108 @@ +#include +#include +#include +#include + +#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 +}; diff --git a/arch/arm/plat-rk/Kconfig b/arch/arm/plat-rk/Kconfig index 47ceeade6135..fd7ac3b5e421 100755 --- a/arch/arm/plat-rk/Kconfig +++ b/arch/arm/plat-rk/Kconfig @@ -195,4 +195,7 @@ config RK_FPGA config RK_CONFIG bool +config RK_TIMER + bool + endif diff --git a/arch/arm/plat-rk/Makefile b/arch/arm/plat-rk/Makefile index accfca14b1f4..c94e1851476d 100644 --- a/arch/arm/plat-rk/Makefile +++ b/arch/arm/plat-rk/Makefile @@ -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 index 000000000000..24dc1b508ce8 --- /dev/null +++ b/arch/arm/plat-rk/rk_timer.c @@ -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 +#include +#include +#include +#include +#include +#include + +#include + +#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 -- 2.34.1