1
0
mirror of git://projects.qi-hardware.com/openwrt-xburst.git synced 2025-04-21 12:27:27 +03:00
Files
openwrt-xburst/target/linux/ubicom32/files/arch/ubicom32/kernel/timer_device.c
kaloz 250089c5e9 [ubicom32]: add 2.6.32 support
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@19823 3c298f89-4303-0410-b956-a3cf2f4a3e73
2010-02-23 10:43:53 +00:00

302 lines
7.7 KiB
C

/*
* 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);
}
}