diff options
Diffstat (limited to 'arch/arm')
-rw-r--r-- | arch/arm/Kconfig | 15 | ||||
-rw-r--r-- | arch/arm/kernel/irq.c | 14 | ||||
-rw-r--r-- | arch/arm/kernel/time.c | 103 |
3 files changed, 132 insertions, 0 deletions
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index bfcf4228036..c8d94dcd8ef 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -346,6 +346,21 @@ config PREEMPT Say Y here if you are building a kernel for a desktop, embedded or real-time system. Say N if you are unsure. +config NO_IDLE_HZ + bool "Dynamic tick timer" + help + Select this option if you want to disable continuous timer ticks + and have them programmed to occur as required. This option saves + power as the system can remain in idle state for longer. + + By default dynamic tick is disabled during the boot, and can be + manually enabled with: + + echo 1 > /sys/devices/system/timer/timer0/dyn_tick + + Alternatively, if you want dynamic tick automatically enabled + during boot, pass "dyntick=enable" via the kernel command string. + config ARCH_DISCONTIGMEM_ENABLE bool default (ARCH_LH7A40X && !LH7A40X_CONTIGMEM) diff --git a/arch/arm/kernel/irq.c b/arch/arm/kernel/irq.c index ff187f4308f..395137a8fad 100644 --- a/arch/arm/kernel/irq.c +++ b/arch/arm/kernel/irq.c @@ -4,6 +4,10 @@ * Copyright (C) 1992 Linus Torvalds * Modifications for ARM processor Copyright (C) 1995-2000 Russell King. * + * Support for Dynamic Tick Timer Copyright (C) 2004-2005 Nokia Corporation. + * Dynamic Tick Timer written by Tony Lindgren <tony@atomide.com> and + * Tuukka Tikkanen <tuukka.tikkanen@elektrobit.com>. + * * 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. @@ -37,6 +41,7 @@ #include <asm/irq.h> #include <asm/system.h> #include <asm/mach/irq.h> +#include <asm/mach/time.h> /* * Maximum IRQ count. Currently, this is arbitary. However, it should @@ -329,6 +334,15 @@ __do_irq(unsigned int irq, struct irqaction *action, struct pt_regs *regs) spin_unlock(&irq_controller_lock); +#ifdef CONFIG_NO_IDLE_HZ + if (!(action->flags & SA_TIMER) && system_timer->dyn_tick != NULL) { + write_seqlock(&xtime_lock); + if (system_timer->dyn_tick->state & DYN_TICK_ENABLED) + system_timer->dyn_tick->handler(irq, 0, regs); + write_sequnlock(&xtime_lock); + } +#endif + if (!(action->flags & SA_INTERRUPT)) local_irq_enable(); diff --git a/arch/arm/kernel/time.c b/arch/arm/kernel/time.c index c232f24f4a6..06054c9ba07 100644 --- a/arch/arm/kernel/time.c +++ b/arch/arm/kernel/time.c @@ -381,6 +381,95 @@ static struct sysdev_class timer_sysclass = { .resume = timer_resume, }; +#ifdef CONFIG_NO_IDLE_HZ +static int timer_dyn_tick_enable(void) +{ + struct dyn_tick_timer *dyn_tick = system_timer->dyn_tick; + unsigned long flags; + int ret = -ENODEV; + + if (dyn_tick) { + write_seqlock_irqsave(&xtime_lock, flags); + ret = 0; + if (!(dyn_tick->state & DYN_TICK_ENABLED)) { + ret = dyn_tick->enable(); + + if (ret == 0) + dyn_tick->state |= DYN_TICK_ENABLED; + } + write_sequnlock_irqrestore(&xtime_lock, flags); + } + + return ret; +} + +static int timer_dyn_tick_disable(void) +{ + struct dyn_tick_timer *dyn_tick = system_timer->dyn_tick; + unsigned long flags; + int ret = -ENODEV; + + if (dyn_tick) { + write_seqlock_irqsave(&xtime_lock, flags); + ret = 0; + if (dyn_tick->state & DYN_TICK_ENABLED) { + ret = dyn_tick->disable(); + + if (ret == 0) + dyn_tick->state &= ~DYN_TICK_ENABLED; + } + write_sequnlock_irqrestore(&xtime_lock, flags); + } + + return ret; +} + +void timer_dyn_reprogram(void) +{ + struct dyn_tick_timer *dyn_tick = system_timer->dyn_tick; + unsigned long flags; + + write_seqlock_irqsave(&xtime_lock, flags); + if (dyn_tick->state & DYN_TICK_ENABLED) + dyn_tick->reprogram(next_timer_interrupt() - jiffies); + write_sequnlock_irqrestore(&xtime_lock, flags); +} + +static ssize_t timer_show_dyn_tick(struct sys_device *dev, char *buf) +{ + return sprintf(buf, "%i\n", + (system_timer->dyn_tick->state & DYN_TICK_ENABLED) >> 1); +} + +static ssize_t timer_set_dyn_tick(struct sys_device *dev, const char *buf, + size_t count) +{ + unsigned int enable = simple_strtoul(buf, NULL, 2); + + if (enable) + timer_dyn_tick_enable(); + else + timer_dyn_tick_disable(); + + return count; +} +static SYSDEV_ATTR(dyn_tick, 0644, timer_show_dyn_tick, timer_set_dyn_tick); + +/* + * dyntick=enable|disable + */ +static char dyntick_str[4] __initdata = ""; + +static int __init dyntick_setup(char *str) +{ + if (str) + strlcpy(dyntick_str, str, sizeof(dyntick_str)); + return 1; +} + +__setup("dyntick=", dyntick_setup); +#endif + static int __init timer_init_sysfs(void) { int ret = sysdev_class_register(&timer_sysclass); @@ -388,6 +477,20 @@ static int __init timer_init_sysfs(void) system_timer->dev.cls = &timer_sysclass; ret = sysdev_register(&system_timer->dev); } + +#ifdef CONFIG_NO_IDLE_HZ + if (ret == 0 && system_timer->dyn_tick) { + ret = sysdev_create_file(&system_timer->dev, &attr_dyn_tick); + + /* + * Turn on dynamic tick after calibrate delay + * for correct bogomips + */ + if (ret == 0 && dyntick_str[0] == 'e') + ret = timer_dyn_tick_enable(); + } +#endif + return ret; } |