[PATCH] s at91 timer: clockevents on sam926x

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello all,

I have taken a stab at implementing a clocksource and a event device on
my AT91SAM9260. I based it on David Brownell's work on the 9200. I would
love some feedback as I'm not confident I have everything right.
Specifically:

- I'm not sure how to pick the .shift member of struct clocksource. It
seems that it would be based on the register size, but it's not clear to
me.

- More importantly, I do not get a dyn_tick file in
the /sys/devices/system/timer/timer0 directory and can't seem to turn on
the feature even with dyntick=enable in the kernel parameters. Have I
missed something in the driver?

Kindly CC me on any responses as currently I only read the archives.

Signed-off-by: Andrew J. Herzig <[email protected]>
---
arch/arm/Kconfig                      |    2 
arch/arm/mach-at91/at91sam926x_time.c |  225 ++++++++++++++++++------ 2
files changed, 176 insertions(+), 51 deletions(-)

diff -uprN -X linux-2.6.23-rc2-vanilla/Documentation/dontdiff
linux-2.6.23-rc2-vanilla/arch/arm/Kconfig
linux-2.6.23-rc2-testpatch/arch/arm/Kconfig
--- linux-2.6.23-rc2-vanilla/arch/arm/Kconfig   2007-08-08
13:28:38.000000000 -0400
+++ linux-2.6.23-rc2-testpatch/arch/arm/Kconfig 2007-08-08
15:17:15.000000000 -0400
@@ -179,6 +179,8 @@ config ARCH_VERSATILE
 config ARCH_AT91
        bool "Atmel AT91"
        select GENERIC_GPIO
+       select GENERIC_TIME
+       select GENERIC_CLOCKEVENTS
        help
          This enables support for systems based on the Atmel AT91RM9200
          and AT91SAM9xxx processors.
diff -uprN -X linux-2.6.23-rc2-vanilla/Documentation/dontdiff
linux-2.6.23-rc2-vanilla/arch/arm/mach-at91/at91sam926x_time.c
linux-2.6.23-rc2-testpatch/arch/arm/mach-at91/at91sam926x_time.c
--- linux-2.6.23-rc2-vanilla/arch/arm/mach-at91/at91sam926x_time.c
2007-08-08 13:28:37.000000000 -0400
+++ linux-2.6.23-rc2-testpatch/arch/arm/mach-at91/at91sam926x_time.c
2007-08-08 15:16:15.000000000 -0400
@@ -1,44 +1,57 @@
 /*
  * linux/arch/arm/mach-at91/at91sam926x_time.c
  *
- * Copyright (C) 2005-2006 M. Amine SAYA, ATMEL Rousset, France
- * Revision     2005 M. Nicolas Diremdjian, ATMEL Rousset, France
+ * clockevents and clocksource implementation based on a compination
+ * of the PIT and RTT system hardware.
+ * Copyright (c) 2007 Andrew J. Herzig <[email protected]>.
+ *
+ * Derived largely from David Brownell's implementation for the RM9200.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  */
 
-#include <linux/init.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_pit.h>
+#include <asm/arch/at91_rtt.h>
 
+static struct clock_event_device clkevt;
 
-#define PIT_CPIV(x)    ((x) & AT91_PIT_CPIV)
-#define PIT_PICNT(x)   (((x) & AT91_PIT_PICNT) >> 20)
+/* According to 6221D–ATARM–12-Mar-07 p.103, the status register
+ * is cleared 2 SLOW_CLOCK cycles after a read. Therefore, to avoid
+ * a race, we must wait for the register to actually clear before
+ * re-enabling interrupts. If there's a better fix for this, I don't
+ * know it.
+ */
+static inline void clear_RTT_SR(void)
+{
+       while (at91_sys_read(AT91_RTT_SR))
+               ;
+}
 
 /*
- * 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.
+ * The counter value (CRTV) is updated asychronously to the master
+ * clock, so we are advised to read RTT_VR twice to avoid glitching.
  */
-static unsigned long at91sam926x_gettimeoffset(void)
+static inline unsigned long read_CRTV(void)
 {
-       unsigned long elapsed;
-       unsigned long t = at91_sys_read(AT91_PIT_PIIR);
+       unsigned long x1, x2;
 
-       elapsed = (PIT_PICNT(t) * LATCH) + PIT_CPIV(t);         /*
hardware clock cycles */
-
-       return (unsigned long)(elapsed * jiffies_to_usecs(1)) / LATCH;
+       x1 = at91_sys_read(AT91_RTT_VR);
+       while (1) {
+               x2 = at91_sys_read(AT91_RTT_VR);
+               if (x1 == x2)
+                       break;
+               x1 = x2;
+       }
+       return x1;
 }
 
 /*
@@ -48,66 +61,176 @@ static irqreturn_t at91sam926x_timer_int
 {
        volatile long nr_ticks;
 
-       if (at91_sys_read(AT91_PIT_SR) & AT91_PIT_PITS) {       /* This
is a shared interrupt */
-               write_seqlock(&xtime_lock);
+       /* Periodic mode. */
+       if (at91_sys_read(AT91_PIT_SR) & AT91_PIT_PITS) { /* shared
interrupt */
 
-               /* Get number to ticks performed before interrupt and
clear PIT interrupt */
-               nr_ticks = PIT_PICNT(at91_sys_read(AT91_PIT_PIVR));
+               /* Get number of system  ticks between last interrupt
+                * and now and clear PIT interrupt. This is to support
+                * dynticks.
+                */
+               nr_ticks = (at91_sys_read(AT91_PIT_PIVR) &
AT91_PIT_PICNT)
+                       >> 20;
                do {
-                       timer_tick();
+                       /* Call evt handler for every sys tick missed.
*/
+                       clkevt.event_handler(&clkevt);
                        nr_ticks--;
                } while (nr_ticks);
 
-               write_sequnlock(&xtime_lock);
                return IRQ_HANDLED;
-       } else
-               return IRQ_NONE;                /* not handled */
+       }
+
+       /* "Oneshot" timer (alarm mode). */
+       if (at91_sys_read(AT91_RTT_SR) & AT91_RTT_ALMS) {
+               clear_RTT_SR();
+               clkevt.event_handler(&clkevt);
+               return IRQ_HANDLED;
+       }
+       return IRQ_NONE;                /* not handled, this IRQ is
shared */
 }
 
 static struct irqaction at91sam926x_timer_irq = {
-       .name           = "at91_tick",
-       .flags          = IRQF_SHARED | IRQF_DISABLED | IRQF_TIMER |
IRQF_IRQPOLL,
-       .handler        = at91sam926x_timer_interrupt
+       .name    = "at91_tick",
+       .flags   = IRQF_SHARED | IRQF_DISABLED
+                  | IRQF_TIMER | IRQF_IRQPOLL,
+       .handler = at91sam926x_timer_interrupt
+};
+
+static cycle_t read_clksrc(void)
+{
+       return read_CRTV();
+}
+
+static struct clocksource clksrc = {
+       .name   = "at91_rtt",
+       .rating = 150,
+       .read   = read_clksrc,
+       .mask   = CLOCKSOURCE_MASK(32),
+       .shift  = 10,
+       .flags  = CLOCK_SOURCE_IS_CONTINUOUS,
 };
 
-void at91sam926x_timer_reset(void)
+#ifdef CONFIG_PM
+
+static void at91sam926x_timer_suspend(void)
+{
+       printk(KERN_ALERT "926x_timer_suspend\n");
+       /* Disable both PIT and RTT counters. */
+       at91_sys_write(AT91_PIT_MR, 0);
+       at91_sys_write(AT91_RTT_MR, 0);
+}
+
+static void at91sam926x_timer_resume(void)
+{
+       printk(KERN_ALERT "926x_timer_resume\n");
+       at91_sys_write(AT91_RTT_MR, 1 | AT91_RTT_RTTRST);
+       at91_sys_write(AT91_PIT_MR, (LATCH & AT91_PIT_PIV)
+                      | AT91_PIT_PITIEN | AT91_PIT_PITEN);
+}
+#else
+#define at91sam926x_timer_suspend      NULL
+#define at91sam926x_timer_resume       NULL
+#endif
+
+static void
+at91sam926x_timer_mode(enum clock_event_mode mode,
+                      struct clock_event_device *dev)
 {
-       /* Disable timer */
+       /* Disable timer interrupts and clear any pending. */
        at91_sys_write(AT91_PIT_MR, 0);
+       at91_sys_read(AT91_PIT_PIVR);
 
-       /* Clear any pending interrupts */
-       (void) at91_sys_read(AT91_PIT_PIVR);
+       at91_sys_write(AT91_RTT_MR, 1);
+       clear_RTT_SR();
 
-       /* Set Period Interval timer and enable its interrupt */
-       at91_sys_write(AT91_PIT_MR, (LATCH & AT91_PIT_PIV) |
AT91_PIT_PITIEN | AT91_PIT_PITEN);
+       switch(mode) {
+       case CLOCK_EVT_MODE_PERIODIC:
+               /* Standard PIT; period = HZ. */
+               at91_sys_write(AT91_PIT_MR, (LATCH & AT91_PIT_PIV)
+                              | AT91_PIT_PITIEN | AT91_PIT_PITEN);
+               break;
+       case CLOCK_EVT_MODE_ONESHOT:
+               /* Oneshot hanled by the RTT hardware.
+                * Set alarm to just missed. Real alarm value will get
+                * set by next_event() before counter rolls back around.
+                */
+               at91_sys_write(AT91_RTT_AR, read_CRTV());
+               at91_sys_write(AT91_RTT_MR, AT91_RTT_ALMIEN | 1);
+               break;
+       case CLOCK_EVT_MODE_SHUTDOWN:
+               at91sam926x_timer_suspend();
+               break;
+       case CLOCK_EVT_MODE_RESUME:
+               at91sam926x_timer_resume();
+               break;
+       case CLOCK_EVT_MODE_UNUSED:
+               break;
+       }
 }
 
+static int at91sam926x_timer_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);
+        alm = read_CRTV();
+
+        /* Cancel any pending alarm; flush any pending IRQ */
+        at91_sys_write(AT91_RTT_AR, alm);
+        clear_RTT_SR();
+
+        /* Schedule alarm by writing to alarm register. */
+        alm += delta;
+        at91_sys_write(AT91_RTT_AR, 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 = at91sam926x_timer_next_event,
+       .set_mode      = at91sam926x_timer_mode,
+};
+
 /*
  * Set up timer interrupt.
  */
-void __init at91sam926x_timer_init(void)
+static void __init at91sam926x_timer_init(void)
 {
-       /* Initialize and enable the timer */
-       at91sam926x_timer_reset();
+
+       /* Disable interrupts and clear any pending.
+        * The 32 kHz "slow clock" drives the RTT timer. We adjust
+        * the prescaler down to 1 for better resolution. Otherwise
+        * we get a 1 Hz default. This however requires waiting
+        * for the RTT_SR to clear before re-enabling interrupts.
+        */
+       at91_sys_write(AT91_RTT_MR, 1 | AT91_RTT_RTTRST);
+       at91_sys_read(AT91_RTT_SR);
 
        /* Make IRQs happen for the system timer. */
        setup_irq(AT91_ID_SYS, &at91sam926x_timer_irq);
+       clkevt.mult = div_sc(AT91_SLOW_CLOCK, NSEC_PER_SEC,
clkevt.shift);
+       clkevt.max_delta_ns = clockevent_delta2ns(AT91_RTT_ALMV,
&clkevt);
+       clkevt.min_delta_ns = clockevent_delta2ns(2, &clkevt) + 1;
+       clockevents_register_device(&clkevt);
+
+       /* Register clocksource. */
+       clksrc.mult = clocksource_hz2mult(AT91_SLOW_CLOCK,
clksrc.shift);
+       clocksource_register(&clksrc);
 }
 
-#ifdef CONFIG_PM
-static void at91sam926x_timer_suspend(void)
-{
-       /* Disable timer */
-       at91_sys_write(AT91_PIT_MR, 0);
-}
-#else
-#define at91sam926x_timer_suspend      NULL
-#endif
-
 struct sys_timer at91sam926x_timer = {
        .init           = at91sam926x_timer_init,
-       .offset         = at91sam926x_gettimeoffset,
        .suspend        = at91sam926x_timer_suspend,
-       .resume         = at91sam926x_timer_reset,
+       .resume         = at91sam926x_timer_resume,
 };
-

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux