aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/ubicom32/files/arch/ubicom32/kernel/timer_device.c
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/ubicom32/files/arch/ubicom32/kernel/timer_device.c')
-rw-r--r--target/linux/ubicom32/files/arch/ubicom32/kernel/timer_device.c301
1 files changed, 301 insertions, 0 deletions
diff --git a/target/linux/ubicom32/files/arch/ubicom32/kernel/timer_device.c b/target/linux/ubicom32/files/arch/ubicom32/kernel/timer_device.c
new file mode 100644
index 000000000..1943cbb9e
--- /dev/null
+++ b/target/linux/ubicom32/files/arch/ubicom32/kernel/timer_device.c
@@ -0,0 +1,301 @@
+/*
+ * arch/ubicom32/kernel/timer_device.c
+ * Implements a Ubicom32 clock device and event devices.
+ *
+ * (C) Copyright 2009, Ubicom, Inc.
+ *
+ * This file is part of the Ubicom32 Linux Kernel Port.
+ *
+ * The Ubicom32 Linux Kernel Port is free software: you can redistribute
+ * it and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Ubicom32 Linux Kernel Port is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with the Ubicom32 Linux Kernel Port. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Ubicom32 implementation derived from (with many thanks):
+ * arch/m68knommu
+ * arch/blackfin
+ * arch/parisc
+ */
+#include <linux/types.h>
+#include <linux/clockchips.h>
+#include <linux/clocksource.h>
+#include <linux/spinlock.h>
+#include <asm/ip5000.h>
+#include <asm/machdep.h>
+
+#if defined(CONFIG_SMP)
+#include <asm/smp.h>
+#endif
+
+#if defined(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST)
+#define MAX_TIMERS (2 + CONFIG_TIMER_EXTRA_ALLOC)
+#else
+#define MAX_TIMERS (NR_CPUS + CONFIG_TIMER_EXTRA_ALLOC)
+#endif
+
+#if (MAX_TIMERS > 10)
+#error "Ubicom32 only has 10 timers"
+#endif
+
+static unsigned int frequency;
+static struct clock_event_device timer_device_devs[MAX_TIMERS];
+static struct irqaction timer_device_irqs[MAX_TIMERS];
+static int timer_device_next_timer = 0;
+
+DEFINE_SPINLOCK(timer_device_lock);
+
+/*
+ * timer_device_set_next_event()
+ * Cause the timer to go off "cycles" from now.
+ */
+static int timer_device_set_next_event(unsigned long cycles, struct clock_event_device *dev)
+{
+ timer_set(dev->irq, cycles);
+ return 0;
+}
+
+/*
+ * timer_device_set_mode()
+ * Handle the mode switch for a clock event device.
+ */
+static void timer_device_set_mode(enum clock_event_mode mode, struct clock_event_device *dev)
+{
+ switch (mode) {
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ /*
+ * Make sure the vector is disabled
+ * until the next event is set.
+ */
+ printk(KERN_NOTICE "timer[%d]: shutdown\n", dev->irq);
+ ldsr_disable_vector(dev->irq);
+ break;
+
+ case CLOCK_EVT_MODE_ONESHOT:
+ /*
+ * Make sure the vector is disabled
+ * until the next event is set.
+ */
+ printk(KERN_NOTICE "timer[%d]: oneshot\n", dev->irq);
+ ldsr_disable_vector(dev->irq);
+ break;
+
+ case CLOCK_EVT_MODE_PERIODIC:
+ /*
+ * The periodic request is 1 per jiffies
+ */
+ printk(KERN_NOTICE "timer[%d]: periodic: %d cycles\n",
+ dev->irq, frequency / CONFIG_HZ);
+ timer_set(dev->irq, frequency / CONFIG_HZ);
+ break;
+
+ case CLOCK_EVT_MODE_UNUSED:
+ case CLOCK_EVT_MODE_RESUME:
+ printk(KERN_WARNING "timer[%d]: unimplemented mode: %d\n",
+ dev->irq, mode);
+ break;
+ };
+}
+
+/*
+ * timer_device_event()
+ * Call the device's event handler.
+ *
+ * The pointer is initialized by the generic Linux code
+ * to the function to be called.
+ */
+static irqreturn_t timer_device_event(int irq, void *dev_id)
+{
+ struct clock_event_device *dev = (struct clock_event_device *)dev_id;
+
+ if (dev->mode == CLOCK_EVT_MODE_PERIODIC) {
+ /*
+ * The periodic request is 1 per jiffies
+ */
+ timer_reset(dev->irq, frequency / CONFIG_HZ);
+ } else {
+ /*
+ * The timer will go off again at the rollover
+ * point. We must disable the IRQ to prevent
+ * getting a spurious interrupt.
+ */
+ ldsr_disable_vector(dev->irq);
+ }
+
+ if (!dev->event_handler) {
+ printk(KERN_CRIT "no registered event handler\n");
+ return IRQ_HANDLED;
+ }
+
+ dev->event_handler(dev);
+ return IRQ_HANDLED;
+}
+
+/*
+ * timer_device_clockbase_read()
+ * Provide a primary clocksource around the sysval timer.
+ */
+static cycle_t timer_device_clockbase_read(void)
+{
+ return (cycle_t)UBICOM32_IO_TIMER->sysval;
+}
+
+/*
+ * Primary Clock Source Description
+ *
+ * We use 24 for the shift factor because we want
+ * to ensure there are less than 2^24 clocks
+ * in a jiffie of 10 ms.
+ */
+static struct clocksource timer_device_clockbase = {
+ .name = "sysval",
+ .rating = 400,
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+ .mask = CLOCKSOURCE_MASK(32),
+ .shift = 24,
+ .mult = 0,
+ .read = timer_device_clockbase_read,
+};
+
+/*
+ * timer_device_alloc_event()
+ * Allocate a timer device event.
+ */
+static int timer_device_alloc_event(const char *name, int cpuid, const struct cpumask *cpumask)
+{
+ struct clock_event_device *dev;
+ struct irqaction *action;
+
+ /*
+ * Are we out of configured timers?
+ */
+ spin_lock(&timer_device_lock);
+ if (timer_device_next_timer >= MAX_TIMERS) {
+ spin_unlock(&timer_device_lock);
+ printk(KERN_WARNING "out of timer event entries\n");
+ return -1;
+ }
+ dev = &timer_device_devs[timer_device_next_timer];
+ action = &timer_device_irqs[timer_device_next_timer];
+ timer_device_next_timer++;
+ spin_unlock(&timer_device_lock);
+
+ /*
+ * Now allocate a timer to ourselves.
+ */
+ dev->irq = timer_alloc();
+ if (dev->irq == -1) {
+ spin_lock(&timer_device_lock);
+ timer_device_next_timer--;
+ spin_unlock(&timer_device_lock);
+ printk(KERN_WARNING "out of hardware timers\n");
+ return -1;
+ }
+
+ /*
+ * Init the IRQ action structure. Make sure
+ * this in place before you register the clock
+ * event device.
+ */
+ action->name = name;
+ action->flags = IRQF_DISABLED | IRQF_TIMER;
+ action->handler = timer_device_event;
+ //cpumask_copy(&action->mask, mask);
+ action->dev_id = dev;
+ setup_irq(dev->irq, action);
+ irq_set_affinity(dev->irq, cpumask);
+ ldsr_disable_vector(dev->irq);
+
+ /*
+ * init clock dev structure.
+ *
+ * The min_delta_ns is chosen to ensure that setting next
+ * event will never be requested with too small of value.
+ */
+ dev->name = name;
+ dev->rating = timer_device_clockbase.rating;
+ dev->shift = timer_device_clockbase.shift;
+ dev->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
+ dev->set_mode = timer_device_set_mode;
+ dev->set_next_event = timer_device_set_next_event;
+ dev->mult = div_sc(frequency, NSEC_PER_SEC, dev->shift);
+ dev->max_delta_ns = clockevent_delta2ns(0xffffffff, dev);
+ dev->min_delta_ns = clockevent_delta2ns(100, dev);
+ //dev->cpumask = mask;
+ printk(KERN_NOTICE "timer[%d]: %s - created\n", dev->irq, dev->name);
+
+ /*
+ * Now register the device.
+ */
+ clockevents_register_device(dev);
+ return dev->irq;
+}
+
+#if defined(CONFIG_LOCAL_TIMERS)
+/*
+ * local_timer_setup()
+ * Allocation function for creating a per cpu local timer.
+ */
+int __cpuinit local_timer_setup(unsigned int cpu)
+{
+ return timer_device_alloc_event("timer-cpu", cpu);
+}
+#endif
+
+/*
+ * timer_device_init()
+ * Create and init a generic clock driver for Ubicom32.
+ */
+void timer_device_init(void)
+{
+ int i;
+
+ /*
+ * Get the frequency from the processor device tree node or use
+ * the default if not available. We will store this as the frequency
+ * of the timer to avoid future calculations.
+ */
+ frequency = processor_frequency();
+ if (frequency == 0) {
+ frequency = CLOCK_TICK_RATE;
+ }
+
+ /*
+ * Setup the primary clock source around sysval. Linux does not
+ * supply a Mhz multiplier so convert down to khz.
+ */
+ timer_device_clockbase.mult =
+ clocksource_khz2mult(frequency / 1000,
+ timer_device_clockbase.shift);
+ if (clocksource_register(&timer_device_clockbase)) {
+ printk(KERN_ERR "timer: clocksource failed to register\n");
+ return;
+ }
+
+ /*
+ * Always allocate a primary timer.
+ */
+ timer_device_alloc_event("timer-primary", -1, cpu_all_mask);
+
+#if defined(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST)
+ /*
+ * If BROADCAST is selected we need to add a broadcast timer.
+ */
+ timer_device_alloc_event("timer-broadcast", -1, cpu_all_mask);
+#endif
+
+ /*
+ * Allocate extra timers that are requested.
+ */
+ for (i = 0; i < CONFIG_TIMER_EXTRA_ALLOC; i++) {
+ timer_device_alloc_event("timer-extra", -1, cpu_all_mask);
+ }
+}