Merge master.kernel.org:/home/rmk/linux-2.6-arm
[safe/jmp/linux-2.6] / arch / arm / mach-realview / localtimer.c
index 529eb69..1c01d13 100644 (file)
 #include <linux/jiffies.h>
 #include <linux/percpu.h>
 #include <linux/clockchips.h>
+#include <linux/irq.h>
+#include <linux/io.h>
 
-#include <asm/mach/time.h>
 #include <asm/hardware/arm_twd.h>
 #include <asm/hardware/gic.h>
-#include <asm/hardware.h>
-#include <asm/io.h>
+#include <mach/hardware.h>
 #include <asm/irq.h>
 
-#define TWD_BASE(cpu)  (__io_address(REALVIEW_TWD_BASE) + \
-                        ((cpu) * REALVIEW_TWD_SIZE))
-
 static DEFINE_PER_CPU(struct clock_event_device, local_clockevent);
 
 /*
@@ -41,8 +38,46 @@ void local_timer_interrupt(void)
 
 #ifdef CONFIG_LOCAL_TIMERS
 
+/* set up by the platform code */
+void __iomem *twd_base;
+
 static unsigned long mpcore_timer_rate;
 
+static void local_timer_set_mode(enum clock_event_mode mode,
+                                struct clock_event_device *clk)
+{
+       unsigned long ctrl;
+
+       switch(mode) {
+       case CLOCK_EVT_MODE_PERIODIC:
+               /* timer load already set up */
+               ctrl = TWD_TIMER_CONTROL_ENABLE | TWD_TIMER_CONTROL_IT_ENABLE
+                       | TWD_TIMER_CONTROL_PERIODIC;
+               break;
+       case CLOCK_EVT_MODE_ONESHOT:
+               /* period set, and timer enabled in 'next_event' hook */
+               ctrl = TWD_TIMER_CONTROL_IT_ENABLE | TWD_TIMER_CONTROL_ONESHOT;
+               break;
+       case CLOCK_EVT_MODE_UNUSED:
+       case CLOCK_EVT_MODE_SHUTDOWN:
+       default:
+               ctrl = 0;
+       }
+
+       __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL);
+}
+
+static int local_timer_set_next_event(unsigned long evt,
+                                     struct clock_event_device *unused)
+{
+       unsigned long ctrl = __raw_readl(twd_base + TWD_TIMER_CONTROL);
+
+       __raw_writel(evt, twd_base + TWD_TIMER_COUNTER);
+       __raw_writel(ctrl | TWD_TIMER_CONTROL_ENABLE, twd_base + TWD_TIMER_CONTROL);
+
+       return 0;
+}
+
 /*
  * local_timer_ack: checks for a local timer interrupt.
  *
@@ -51,22 +86,18 @@ static unsigned long mpcore_timer_rate;
  */
 int local_timer_ack(void)
 {
-       void __iomem *base = TWD_BASE(smp_processor_id());
-
-       if (__raw_readl(base + TWD_TIMER_INTSTAT)) {
-               __raw_writel(1, base + TWD_TIMER_INTSTAT);
+       if (__raw_readl(twd_base + TWD_TIMER_INTSTAT)) {
+               __raw_writel(1, twd_base + TWD_TIMER_INTSTAT);
                return 1;
        }
 
        return 0;
 }
 
-void __cpuinit local_timer_setup(unsigned int cpu)
+static void __cpuinit twd_calibrate_rate(void)
 {
-       void __iomem *base = TWD_BASE(cpu);
-       unsigned int load, offset;
+       unsigned long load, count;
        u64 waitjiffies;
-       unsigned int count;
 
        /*
         * If this is the first time round, we need to work out how fast
@@ -85,15 +116,15 @@ void __cpuinit local_timer_setup(unsigned int cpu)
                waitjiffies += 5;
 
                                 /* enable, no interrupt or reload */
-               __raw_writel(0x1, base + TWD_TIMER_CONTROL);
+               __raw_writel(0x1, twd_base + TWD_TIMER_CONTROL);
 
                                 /* maximum value */
-               __raw_writel(0xFFFFFFFFU, base + TWD_TIMER_COUNTER);
+               __raw_writel(0xFFFFFFFFU, twd_base + TWD_TIMER_COUNTER);
 
                while (get_jiffies_64() < waitjiffies)
                        udelay(10);
 
-               count = __raw_readl(base + TWD_TIMER_COUNTER);
+               count = __raw_readl(twd_base + TWD_TIMER_COUNTER);
 
                mpcore_timer_rate = (0xFFFFFFFFU - count) * (HZ / 5);
 
@@ -103,45 +134,46 @@ void __cpuinit local_timer_setup(unsigned int cpu)
 
        load = mpcore_timer_rate / HZ;
 
-       __raw_writel(load, base + TWD_TIMER_LOAD);
-       __raw_writel(0x7,  base + TWD_TIMER_CONTROL);
-
-       /*
-        * Now maneuver our local tick into the right part of the jiffy.
-        * Start by working out where within the tick our local timer
-        * interrupt should go.
-        */
-       offset = ((mpcore_timer_rate / HZ) / (NR_CPUS + 1)) * (cpu + 1);
-
-       /*
-        * gettimeoffset() will return a number of us since the last tick.
-        * Convert this number of us to a local timer tick count.
-        * Be careful of integer overflow whilst keeping maximum precision.
-        *
-        * with HZ=100 and 1MHz (fpga) ~ 1GHz processor:
-        * load = 1 ~ 10,000
-        * mpcore_timer_rate/10000 = 100 ~ 100,000
-        *
-        * so the multiply value will be less than 10^9 always.
-        */
-       load = (system_timer->offset() * (mpcore_timer_rate / 10000)) / 100;
-
-       /* Add on our offset to get the load value */
-       load = (load + offset) % (mpcore_timer_rate / HZ);
+       __raw_writel(load, twd_base + TWD_TIMER_LOAD);
+}
 
-       __raw_writel(load, base + TWD_TIMER_COUNTER);
+/*
+ * Setup the local clock events for a CPU.
+ */
+void __cpuinit local_timer_setup(void)
+{
+       unsigned int cpu = smp_processor_id();
+       struct clock_event_device *clk = &per_cpu(local_clockevent, cpu);
+       unsigned long flags;
+
+       twd_calibrate_rate();
+
+       clk->name               = "local_timer";
+       clk->features           = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
+       clk->rating             = 350;
+       clk->set_mode           = local_timer_set_mode;
+       clk->set_next_event     = local_timer_set_next_event;
+       clk->irq                = IRQ_LOCALTIMER;
+       clk->cpumask            = cpumask_of(cpu);
+       clk->shift              = 20;
+       clk->mult               = div_sc(mpcore_timer_rate, NSEC_PER_SEC, clk->shift);
+       clk->max_delta_ns       = clockevent_delta2ns(0xffffffff, clk);
+       clk->min_delta_ns       = clockevent_delta2ns(0xf, clk);
 
        /* Make sure our local interrupt controller has this enabled */
-       __raw_writel(1 << IRQ_LOCALTIMER,
-                    __io_address(REALVIEW_GIC_DIST_BASE) + GIC_DIST_ENABLE_SET);
+       local_irq_save(flags);
+       get_irq_chip(IRQ_LOCALTIMER)->unmask(IRQ_LOCALTIMER);
+       local_irq_restore(flags);
+
+       clockevents_register_device(clk);
 }
 
 /*
  * take a local timer down
  */
-void __cpuexit local_timer_stop(unsigned int cpu)
+void __cpuexit local_timer_stop(void)
 {
-       __raw_writel(0, TWD_BASE(cpu) + TWD_TIMER_CONTROL);
+       __raw_writel(0, twd_base + TWD_TIMER_CONTROL);
 }
 
 #else  /* CONFIG_LOCAL_TIMERS */
@@ -151,16 +183,20 @@ static void dummy_timer_set_mode(enum clock_event_mode mode,
 {
 }
 
-void __cpuinit local_timer_setup(unsigned int cpu)
+void __cpuinit local_timer_setup(void)
 {
+       unsigned int cpu = smp_processor_id();
        struct clock_event_device *clk = &per_cpu(local_clockevent, cpu);
 
        clk->name               = "dummy_timer";
-       clk->features           = CLOCK_EVT_FEAT_DUMMY;
-       clk->rating             = 200;
+       clk->features           = CLOCK_EVT_FEAT_ONESHOT |
+                                 CLOCK_EVT_FEAT_PERIODIC |
+                                 CLOCK_EVT_FEAT_DUMMY;
+       clk->rating             = 400;
+       clk->mult               = 1;
        clk->set_mode           = dummy_timer_set_mode;
        clk->broadcast          = smp_timer_broadcast;
-       clk->cpumask            = cpumask_of_cpu(cpu);
+       clk->cpumask            = cpumask_of(cpu);
 
        clockevents_register_device(clk);
 }