From 2f73a06843e357190a7a3924c7afb0534cf6adef Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Wed, 14 Sep 2011 10:32:51 +0200 Subject: [PATCH] ARM: plat-nomadik: timer: Add support for periodic timers This adds support for a periodic mode in the MTU (Nomadik) timer clockevent driver. It also include changes needed for deeper powerstates where MTU block gets powered off. Cc: Thomas Gleixner Signed-off-by: Jonas Aaberg Signed-off-by: Linus Walleij --- arch/arm/plat-nomadik/timer.c | 88 ++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/arch/arm/plat-nomadik/timer.c b/arch/arm/plat-nomadik/timer.c index bd638c552f0..a04b5215b6d 100644 --- a/arch/arm/plat-nomadik/timer.c +++ b/arch/arm/plat-nomadik/timer.c @@ -23,7 +23,12 @@ #include +static bool clkevt_periodic; +static u32 clk_prescale; +static u32 nmdk_cycle; /* write-once */ + void __iomem *mtu_base; /* Assigned by machine code */ + #ifdef CONFIG_NOMADIK_MTU_SCHED_CLOCK /* * Override the global weak sched_clock symbol with this @@ -49,31 +54,55 @@ static void notrace nomadik_update_sched_clock(void) update_sched_clock(&cd, cyc, (u32)~0); } #endif + /* Clockevent device: use one-shot mode */ +static int nmdk_clkevt_next(unsigned long evt, struct clock_event_device *ev) +{ + writel(1 << 1, mtu_base + MTU_IMSC); + writel(evt, mtu_base + MTU_LR(1)); + /* Load highest value, enable device, enable interrupts */ + writel(MTU_CRn_ONESHOT | clk_prescale | + MTU_CRn_32BITS | MTU_CRn_ENA, + mtu_base + MTU_CR(1)); + + return 0; +} + +static void nmdk_clkevt_reset(void) +{ + if (clkevt_periodic) { + + /* Timer: configure load and background-load, and fire it up */ + writel(nmdk_cycle, mtu_base + MTU_LR(1)); + writel(nmdk_cycle, mtu_base + MTU_BGLR(1)); + + writel(MTU_CRn_PERIODIC | clk_prescale | + MTU_CRn_32BITS | MTU_CRn_ENA, + mtu_base + MTU_CR(1)); + writel(1 << 1, mtu_base + MTU_IMSC); + } else { + /* Generate an interrupt to start the clockevent again */ + (void) nmdk_clkevt_next(nmdk_cycle, NULL); + } +} + static void nmdk_clkevt_mode(enum clock_event_mode mode, struct clock_event_device *dev) { - u32 cr; switch (mode) { case CLOCK_EVT_MODE_PERIODIC: - pr_err("%s: periodic mode not supported\n", __func__); + clkevt_periodic = true; + nmdk_clkevt_reset(); break; case CLOCK_EVT_MODE_ONESHOT: - /* Load highest value, enable device, enable interrupts */ - cr = readl(mtu_base + MTU_CR(1)); - writel(0, mtu_base + MTU_LR(1)); - writel(cr | MTU_CRn_ENA, mtu_base + MTU_CR(1)); - writel(1 << 1, mtu_base + MTU_IMSC); + clkevt_periodic = false; break; case CLOCK_EVT_MODE_SHUTDOWN: case CLOCK_EVT_MODE_UNUSED: - /* disable irq */ writel(0, mtu_base + MTU_IMSC); /* disable timer */ - cr = readl(mtu_base + MTU_CR(1)); - cr &= ~MTU_CRn_ENA; - writel(cr, mtu_base + MTU_CR(1)); + writel(0, mtu_base + MTU_CR(1)); /* load some high default value */ writel(0xffffffff, mtu_base + MTU_LR(1)); break; @@ -82,16 +111,9 @@ static void nmdk_clkevt_mode(enum clock_event_mode mode, } } -static int nmdk_clkevt_next(unsigned long evt, struct clock_event_device *ev) -{ - /* writing the value has immediate effect */ - writel(evt, mtu_base + MTU_LR(1)); - return 0; -} - static struct clock_event_device nmdk_clkevt = { .name = "mtu_1", - .features = CLOCK_EVT_FEAT_ONESHOT, + .features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC, .rating = 200, .set_mode = nmdk_clkevt_mode, .set_next_event = nmdk_clkevt_next, @@ -116,11 +138,23 @@ static struct irqaction nmdk_timer_irq = { .dev_id = &nmdk_clkevt, }; +static void nmdk_clksrc_reset(void) +{ + /* Disable */ + writel(0, mtu_base + MTU_CR(0)); + + /* ClockSource: configure load and background-load, and fire it up */ + writel(nmdk_cycle, mtu_base + MTU_LR(0)); + writel(nmdk_cycle, mtu_base + MTU_BGLR(0)); + + writel(clk_prescale | MTU_CRn_32BITS | MTU_CRn_ENA, + mtu_base + MTU_CR(0)); +} + void __init nmdk_timer_init(void) { unsigned long rate; struct clk *clk0; - u32 cr = MTU_CRn_32BITS; clk0 = clk_get_sys("mtu0", NULL); BUG_ON(IS_ERR(clk0)); @@ -138,16 +172,16 @@ void __init nmdk_timer_init(void) rate = clk_get_rate(clk0); if (rate > 32000000) { rate /= 16; - cr |= MTU_CRn_PRESCALE_16; + clk_prescale = MTU_CRn_PRESCALE_16; } else { - cr |= MTU_CRn_PRESCALE_1; + clk_prescale = MTU_CRn_PRESCALE_1; } + nmdk_cycle = (rate + HZ/2) / HZ; + + /* Timer 0 is the free running clocksource */ - writel(cr, mtu_base + MTU_CR(0)); - writel(0, mtu_base + MTU_LR(0)); - writel(0, mtu_base + MTU_BGLR(0)); - writel(cr | MTU_CRn_ENA, mtu_base + MTU_CR(0)); + nmdk_clksrc_reset(); if (clocksource_mmio_init(mtu_base + MTU_VAL(0), "mtu_0", rate, 200, 32, clocksource_mmio_readl_down)) @@ -160,8 +194,6 @@ void __init nmdk_timer_init(void) clockevents_calc_mult_shift(&nmdk_clkevt, rate, MTU_MIN_RANGE); - writel(cr | MTU_CRn_ONESHOT, mtu_base + MTU_CR(1)); /* off, currently */ - nmdk_clkevt.max_delta_ns = clockevent_delta2ns(0xffffffff, &nmdk_clkevt); nmdk_clkevt.min_delta_ns = -- 2.39.5