aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/mach-at91/at91rm9200_time.c
diff options
context:
space:
mode:
authorDavid Brownell2007-07-31 01:41:26 +0100
committerRussell King2007-10-12 23:43:15 +0100
commit5e802dfab7bbbee1e63607a3e6d1ceb78ec4ceeb (patch)
treeeb85a0c5746afae2c6ca0959bb9c58caf83351eb /arch/arm/mach-at91/at91rm9200_time.c
parentf2c10d6c669e5b792c48e86da37ec7fde0a2e302 (diff)
[ARM] 4539/1: clocksource and clockevents for at91rm9200
GENERIC_TIME and GENERIC_CLOCKEVENTS support for the at91rm9200. - Oneshot mode (used for NO_HZ and high res timers) uses the alarm to emulate a real oneshot timer; the trickiest bit is how to avoid some lowlevel races. Thanks to Remy Bohmer for various fixes to this code. - Tighten up periodic mode support using the PIT. - Streamline reads of the 32KHz counter. Thanks to Marc Pignat for some testing results: the CRTR register has *very* odd behavior. The reread appears to work around stranger glitches than just getting an old clock value (which would quickly self-correct). - Remove the rounding-up of tick_usec to 10.009 msec (32KiHz/100), since that no longer acts correct (time increases too fast). Note that the at91sam9 and at91x40 chips need other solutions, since they don't have the same system timer module. Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Acked-by: Bill Gatliff <bgat@billgatliff.com> Acked-by:Remy Bohmer <linux@bohmer.net> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm/mach-at91/at91rm9200_time.c')
-rw-r--r--arch/arm/mach-at91/at91rm9200_time.c195
1 files changed, 130 insertions, 65 deletions
diff --git a/arch/arm/mach-at91/at91rm9200_time.c b/arch/arm/mach-at91/at91rm9200_time.c
index a6340357585d..50392ff71513 100644
--- a/arch/arm/mach-at91/at91rm9200_time.c
+++ b/arch/arm/mach-at91/at91rm9200_time.c
@@ -19,70 +19,64 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
-#include <linux/init.h>
+#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
-#include <linux/kernel.h>
-#include <linux/sched.h>
-#include <linux/time.h>
+#include <linux/clockchips.h>
-#include <asm/hardware.h>
-#include <asm/io.h>
#include <asm/mach/time.h>
#include <asm/arch/at91_st.h>
static unsigned long last_crtr;
+static u32 irqmask;
+static struct clock_event_device clkevt;
/*
- * The ST_CRTR is updated asynchronously to the master clock. It is therefore
- * necessary to read it twice (with the same value) to ensure accuracy.
+ * The ST_CRTR is updated asynchronously to the master clock ... but
+ * the updates as seen by the CPU don't seem to be strictly monotonic.
+ * Waiting until we read the same value twice avoids glitching.
*/
-static inline unsigned long read_CRTR(void) {
+static inline unsigned long read_CRTR(void)
+{
unsigned long x1, x2;
+ x1 = at91_sys_read(AT91_ST_CRTR);
do {
- x1 = at91_sys_read(AT91_ST_CRTR);
x2 = at91_sys_read(AT91_ST_CRTR);
- } while (x1 != x2);
-
+ if (x1 == x2)
+ break;
+ x1 = x2;
+ } while (1);
return x1;
}
/*
- * Returns number of microseconds since last timer interrupt. Note that interrupts
- * will have been disabled by do_gettimeofday()
- * 'LATCH' is hwclock ticks (see CLOCK_TICK_RATE in timex.h) per jiffy.
- * 'tick' is usecs per jiffy (linux/timex.h).
- */
-static unsigned long at91rm9200_gettimeoffset(void)
-{
- unsigned long elapsed;
-
- elapsed = (read_CRTR() - last_crtr) & AT91_ST_ALMV;
-
- return (unsigned long)(elapsed * (tick_nsec / 1000)) / LATCH;
-}
-
-/*
* IRQ handler for the timer.
*/
static irqreturn_t at91rm9200_timer_interrupt(int irq, void *dev_id)
{
- if (at91_sys_read(AT91_ST_SR) & AT91_ST_PITS) { /* This is a shared interrupt */
- write_seqlock(&xtime_lock);
+ u32 sr = at91_sys_read(AT91_ST_SR) & irqmask;
- while (((read_CRTR() - last_crtr) & AT91_ST_ALMV) >= LATCH) {
- timer_tick();
- last_crtr = (last_crtr + LATCH) & AT91_ST_ALMV;
- }
+ /* simulate "oneshot" timer with alarm */
+ if (sr & AT91_ST_ALMS) {
+ clkevt.event_handler(&clkevt);
+ return IRQ_HANDLED;
+ }
- write_sequnlock(&xtime_lock);
+ /* periodic mode should handle delayed ticks */
+ if (sr & AT91_ST_PITS) {
+ u32 crtr = read_CRTR();
+ while (((crtr - last_crtr) & AT91_ST_CRTV) >= LATCH) {
+ last_crtr += LATCH;
+ clkevt.event_handler(&clkevt);
+ }
return IRQ_HANDLED;
}
- else
- return IRQ_NONE; /* not handled */
+
+ /* this irq is shared ... */
+ return IRQ_NONE;
}
static struct irqaction at91rm9200_timer_irq = {
@@ -91,56 +85,127 @@ static struct irqaction at91rm9200_timer_irq = {
.handler = at91rm9200_timer_interrupt
};
-void at91rm9200_timer_reset(void)
+static cycle_t read_clk32k(void)
{
- last_crtr = 0;
+ return read_CRTR();
+}
- /* Real time counter incremented every 30.51758 microseconds */
- at91_sys_write(AT91_ST_RTMR, 1);
+static struct clocksource clk32k = {
+ .name = "32k_counter",
+ .rating = 150,
+ .read = read_clk32k,
+ .mask = CLOCKSOURCE_MASK(20),
+ .shift = 10,
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+};
+
+static void
+clkevt32k_mode(enum clock_event_mode mode, struct clock_event_device *dev)
+{
+ /* Disable and flush pending timer interrupts */
+ at91_sys_write(AT91_ST_IDR, AT91_ST_PITS | AT91_ST_ALMS);
+ (void) at91_sys_read(AT91_ST_SR);
- /* Set Period Interval timer */
- at91_sys_write(AT91_ST_PIMR, LATCH);
+ last_crtr = read_CRTR();
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ /* PIT for periodic irqs; fixed rate of 1/HZ */
+ irqmask = AT91_ST_PITS;
+ at91_sys_write(AT91_ST_PIMR, LATCH);
+ break;
+ case CLOCK_EVT_MODE_ONESHOT:
+ /* ALM for oneshot irqs, set by next_event()
+ * before 32 seconds have passed
+ */
+ irqmask = AT91_ST_ALMS;
+ at91_sys_write(AT91_ST_RTAR, last_crtr);
+ break;
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ case CLOCK_EVT_MODE_UNUSED:
+ case CLOCK_EVT_MODE_RESUME:
+ irqmask = 0;
+ break;
+ }
+ at91_sys_write(AT91_ST_IER, irqmask);
+}
- /* Clear any pending interrupts */
+static int
+clkevt32k_next_event(unsigned long delta, struct clock_event_device *dev)
+{
+ unsigned long flags;
+ u32 alm;
+ int status = 0;
+
+ BUG_ON(delta < 2);
+
+ /* Use "raw" primitives so we behave correctly on RT kernels. */
+ raw_local_irq_save(flags);
+
+ /* The alarm IRQ uses absolute time (now+delta), not the relative
+ * time (delta) in our calling convention. Like all clockevents
+ * using such "match" hardware, we have a race to defend against.
+ *
+ * Our defense here is to have set up the clockevent device so the
+ * delta is at least two. That way we never end up writing RTAR
+ * with the value then held in CRTR ... which would mean the match
+ * wouldn't trigger until 32 seconds later, after CRTR wraps.
+ */
+ alm = read_CRTR();
+
+ /* Cancel any pending alarm; flush any pending IRQ */
+ at91_sys_write(AT91_ST_RTAR, alm);
(void) at91_sys_read(AT91_ST_SR);
- /* Enable Period Interval Timer interrupt */
- at91_sys_write(AT91_ST_IER, AT91_ST_PITS);
+ /* Schedule alarm by writing RTAR. */
+ alm += delta;
+ at91_sys_write(AT91_ST_RTAR, alm);
+
+ raw_local_irq_restore(flags);
+ return status;
}
+static struct clock_event_device clkevt = {
+ .name = "at91_tick",
+ .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
+ .shift = 32,
+ .rating = 150,
+ .cpumask = CPU_MASK_CPU0,
+ .set_next_event = clkevt32k_next_event,
+ .set_mode = clkevt32k_mode,
+};
+
/*
- * Set up timer interrupt.
+ * ST (system timer) module supports both clockevents and clocksource.
*/
void __init at91rm9200_timer_init(void)
{
- /* Disable all timer interrupts */
- at91_sys_write(AT91_ST_IDR, AT91_ST_PITS | AT91_ST_WDOVF | AT91_ST_RTTINC | AT91_ST_ALMS);
- (void) at91_sys_read(AT91_ST_SR); /* Clear any pending interrupts */
+ /* Disable all timer interrupts, and clear any pending ones */
+ at91_sys_write(AT91_ST_IDR,
+ AT91_ST_PITS | AT91_ST_WDOVF | AT91_ST_RTTINC | AT91_ST_ALMS);
+ (void) at91_sys_read(AT91_ST_SR);
/* Make IRQs happen for the system timer */
setup_irq(AT91_ID_SYS, &at91rm9200_timer_irq);
- /* Change the kernel's 'tick' value to 10009 usec. (the default is 10000) */
- tick_usec = (LATCH * 1000000) / CLOCK_TICK_RATE;
+ /* The 32KiHz "Slow Clock" (tick every 30517.58 nanoseconds) is used
+ * directly for the clocksource and all clockevents, after adjusting
+ * its prescaler from the 1 Hz default.
+ */
+ at91_sys_write(AT91_ST_RTMR, 1);
- /* Initialize and enable the timer interrupt */
- at91rm9200_timer_reset();
-}
+ /* Setup timer clockevent, with minimum of two ticks (important!!) */
+ clkevt.mult = div_sc(AT91_SLOW_CLOCK, NSEC_PER_SEC, clkevt.shift);
+ clkevt.max_delta_ns = clockevent_delta2ns(AT91_ST_ALMV, &clkevt);
+ clkevt.min_delta_ns = clockevent_delta2ns(2, &clkevt) + 1;
+ clkevt.cpumask = cpumask_of_cpu(0);
+ clockevents_register_device(&clkevt);
-#ifdef CONFIG_PM
-static void at91rm9200_timer_suspend(void)
-{
- /* disable Period Interval Timer interrupt */
- at91_sys_write(AT91_ST_IDR, AT91_ST_PITS);
+ /* register clocksource */
+ clk32k.mult = clocksource_hz2mult(AT91_SLOW_CLOCK, clk32k.shift);
+ clocksource_register(&clk32k);
}
-#else
-#define at91rm9200_timer_suspend NULL
-#endif
struct sys_timer at91rm9200_timer = {
.init = at91rm9200_timer_init,
- .offset = at91rm9200_gettimeoffset,
- .suspend = at91rm9200_timer_suspend,
- .resume = at91rm9200_timer_reset,
};