diff options
author | Baruch Siach <baruch@tkos.co.il> | 2013-06-18 08:48:53 +0300 |
---|---|---|
committer | Chris Zankel <chris@zankel.net> | 2013-07-08 01:11:38 -0700 |
commit | 925f5532e83bfe236b4f69ba4265c19663cfa9c7 (patch) | |
tree | 354c55c160b21e40dfdde8b25563debbe0f32dc5 /arch/xtensa | |
parent | 8102f47ab5fcffc50f3ff94ca9b9073c12c18cc7 (diff) | |
download | linux-925f5532e83bfe236b4f69ba4265c19663cfa9c7.tar.bz2 |
xtensa: ccount based clockevent implementation
Reused some code from a preliminary implementation by Max Fillippov.
Cc: Max Filippov <jcmvbkbc@gmail.com>
Signed-off-by: Baruch Siach <baruch@tkos.co.il>
Signed-off-by: Chris Zankel <chris@zankel.net>
Diffstat (limited to 'arch/xtensa')
-rw-r--r-- | arch/xtensa/Kconfig | 1 | ||||
-rw-r--r-- | arch/xtensa/kernel/time.c | 110 |
2 files changed, 79 insertions, 32 deletions
diff --git a/arch/xtensa/Kconfig b/arch/xtensa/Kconfig index 0a1b95f81a32..dadcf824a5cb 100644 --- a/arch/xtensa/Kconfig +++ b/arch/xtensa/Kconfig @@ -6,6 +6,7 @@ config XTENSA select ARCH_WANT_FRAME_POINTERS select HAVE_IDE select GENERIC_ATOMIC64 + select GENERIC_CLOCKEVENTS select HAVE_GENERIC_HARDIRQS select VIRT_TO_BUS select GENERIC_IRQ_SHOW diff --git a/arch/xtensa/kernel/time.c b/arch/xtensa/kernel/time.c index a32bc2ea7982..ece4f9588625 100644 --- a/arch/xtensa/kernel/time.c +++ b/arch/xtensa/kernel/time.c @@ -16,6 +16,7 @@ #include <linux/sched.h> #include <linux/time.h> #include <linux/clocksource.h> +#include <linux/clockchips.h> #include <linux/interrupt.h> #include <linux/module.h> #include <linux/init.h> @@ -43,16 +44,80 @@ static struct clocksource ccount_clocksource = { .mask = CLOCKSOURCE_MASK(32), }; +static int ccount_timer_set_next_event(unsigned long delta, + struct clock_event_device *dev); +static void ccount_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt); +static struct ccount_timer_t { + struct clock_event_device evt; + int irq_enabled; +} ccount_timer = { + .evt = { + .name = "ccount_clockevent", + .features = CLOCK_EVT_FEAT_ONESHOT, + .rating = 300, + .set_next_event = ccount_timer_set_next_event, + .set_mode = ccount_timer_set_mode, + }, +}; + +static int ccount_timer_set_next_event(unsigned long delta, + struct clock_event_device *dev) +{ + unsigned long flags, next; + int ret = 0; + + local_irq_save(flags); + next = get_ccount() + delta; + set_linux_timer(next); + if (next - get_ccount() > delta) + ret = -ETIME; + local_irq_restore(flags); + + return ret; +} + +static void ccount_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + struct ccount_timer_t *timer = + container_of(evt, struct ccount_timer_t, evt); + + /* + * There is no way to disable the timer interrupt at the device level, + * only at the intenable register itself. Since enable_irq/disable_irq + * calls are nested, we need to make sure that these calls are + * balanced. + */ + switch (mode) { + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_UNUSED: + if (timer->irq_enabled) { + disable_irq(evt->irq); + timer->irq_enabled = 0; + } + break; + case CLOCK_EVT_MODE_RESUME: + case CLOCK_EVT_MODE_ONESHOT: + if (!timer->irq_enabled) { + enable_irq(evt->irq); + timer->irq_enabled = 1; + } + default: + break; + } +} + static irqreturn_t timer_interrupt(int irq, void *dev_id); static struct irqaction timer_irqaction = { .handler = timer_interrupt, - .flags = IRQF_DISABLED, + .flags = IRQF_TIMER, .name = "timer", + .dev_id = &ccount_timer, }; void __init time_init(void) { - unsigned int irq; #ifdef CONFIG_XTENSA_CALIBRATE_CCOUNT printk("Calibrating CPU frequency "); platform_calibrate_ccount(); @@ -61,11 +126,14 @@ void __init time_init(void) #endif clocksource_register_hz(&ccount_clocksource, CCOUNT_PER_JIFFY * HZ); - /* Initialize the linux timer interrupt. */ - - irq = irq_create_mapping(NULL, LINUX_TIMER_INT); - setup_irq(irq, &timer_irqaction); - set_linux_timer(get_ccount() + CCOUNT_PER_JIFFY); + ccount_timer.evt.cpumask = cpumask_of(0); + ccount_timer.evt.irq = irq_create_mapping(NULL, LINUX_TIMER_INT); + if (WARN(!ccount_timer.evt.irq, "error: can't map timer irq")) + return; + clockevents_config_and_register(&ccount_timer.evt, ccount_freq, 0xf, + 0xffffffff); + setup_irq(ccount_timer.evt.irq, &timer_irqaction); + ccount_timer.irq_enabled = 1; } /* @@ -74,36 +142,14 @@ void __init time_init(void) irqreturn_t timer_interrupt (int irq, void *dev_id) { + struct ccount_timer_t *timer = dev_id; + struct clock_event_device *evt = &timer->evt; - unsigned long next; - - next = get_linux_timer(); - -again: - while ((signed long)(get_ccount() - next) > 0) { - - profile_tick(CPU_PROFILING); -#ifndef CONFIG_SMP - update_process_times(user_mode(get_irq_regs())); -#endif - - xtime_update(1); /* Linux handler in kernel/time/timekeeping */ - - /* Note that writing CCOMPARE clears the interrupt. */ - - next += CCOUNT_PER_JIFFY; - set_linux_timer(next); - } + evt->event_handler(evt); /* Allow platform to do something useful (Wdog). */ - platform_heartbeat(); - /* Make sure we didn't miss any tick... */ - - if ((signed long)(get_ccount() - next) > 0) - goto again; - return IRQ_HANDLED; } |