]> err.no Git - linux-2.6/commitdiff
[ARM] 4539/1: clocksource and clockevents for at91rm9200
authorDavid Brownell <david-b@pacbell.net>
Tue, 31 Jul 2007 00:41:26 +0000 (01:41 +0100)
committerRussell King <rmk+kernel@arm.linux.org.uk>
Fri, 12 Oct 2007 22:43:15 +0000 (23:43 +0100)
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>
arch/arm/mach-at91/Kconfig
arch/arm/mach-at91/at91rm9200_time.c

index fd956f1299d162dc0b3de717af9b9d932644ef1e..05a9f8a1b45ee5320ddcf3c1d6f18aef96f326be 100644 (file)
@@ -7,6 +7,8 @@ choice
 
 config ARCH_AT91RM9200
        bool "AT91RM9200"
+       select GENERIC_TIME
+       select GENERIC_CLOCKEVENTS
 
 config ARCH_AT91SAM9260
        bool "AT91SAM9260 or AT91SAM9XE"
index a6340357585da93efe24eb9a9b0e4aa1d21b9365..50392ff7151306b0fd3504285bd7157631dc6df3 100644 (file)
  * 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,
 };