From bd156147eb63ae525e0ac67868e41a808f03c532 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Fri, 1 Dec 2006 13:23:47 +0900 Subject: [PATCH] sh: dyntick infrastructure. This adds basic NO_IDLE_HZ support to the SH timer API so timers are able to wire it up. Taken from the ARM version, as it fit in to our API with very few changes needed. Signed-off-by: Paul Mundt --- arch/sh/Kconfig | 18 ++++++ arch/sh/kernel/time.c | 124 ++++++++++++++++++++++++++++++++++++++++- include/asm-sh/timer.h | 21 +++++++ 3 files changed, 162 insertions(+), 1 deletion(-) diff --git a/arch/sh/Kconfig b/arch/sh/Kconfig index 48308dc86e..aa1ebc561b 100644 --- a/arch/sh/Kconfig +++ b/arch/sh/Kconfig @@ -423,6 +423,24 @@ config SH_TIMER_IRQ default "140" if CPU_SUBTYPE_SH7206 default "16" +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. + + Please note that dynamic tick may affect the accuracy of + timekeeping on some platforms depending on the implementation. + config SH_PCLK_FREQ int "Peripheral clock frequency (in Hz)" default "27000000" if CPU_SUBTYPE_SH73180 || CPU_SUBTYPE_SH7343 diff --git a/arch/sh/kernel/time.c b/arch/sh/kernel/time.c index c55d6f217a..1b91c72149 100644 --- a/arch/sh/kernel/time.c +++ b/arch/sh/kernel/time.c @@ -174,6 +174,108 @@ 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 = sys_timer->dyn_tick; + unsigned long flags; + int ret = -ENODEV; + + if (dyn_tick) { + spin_lock_irqsave(&dyn_tick->lock, flags); + ret = 0; + if (!(dyn_tick->state & DYN_TICK_ENABLED)) { + ret = dyn_tick->enable(); + + if (ret == 0) + dyn_tick->state |= DYN_TICK_ENABLED; + } + spin_unlock_irqrestore(&dyn_tick->lock, flags); + } + + return ret; +} + +static int timer_dyn_tick_disable(void) +{ + struct dyn_tick_timer *dyn_tick = sys_timer->dyn_tick; + unsigned long flags; + int ret = -ENODEV; + + if (dyn_tick) { + spin_lock_irqsave(&dyn_tick->lock, flags); + ret = 0; + if (dyn_tick->state & DYN_TICK_ENABLED) { + ret = dyn_tick->disable(); + + if (ret == 0) + dyn_tick->state &= ~DYN_TICK_ENABLED; + } + spin_unlock_irqrestore(&dyn_tick->lock, flags); + } + + return ret; +} + +/* + * Reprogram the system timer for at least the calculated time interval. + * This function should be called from the idle thread with IRQs disabled, + * immediately before sleeping. + */ +void timer_dyn_reprogram(void) +{ + struct dyn_tick_timer *dyn_tick = sys_timer->dyn_tick; + unsigned long next, seq, flags; + + if (!dyn_tick) + return; + + spin_lock_irqsave(&dyn_tick->lock, flags); + if (dyn_tick->state & DYN_TICK_ENABLED) { + next = next_timer_interrupt(); + do { + seq = read_seqbegin(&xtime_lock); + dyn_tick->reprogram(next - jiffies); + } while (read_seqretry(&xtime_lock, seq)); + } + spin_unlock_irqrestore(&dyn_tick->lock, flags); +} + +static ssize_t timer_show_dyn_tick(struct sys_device *dev, char *buf) +{ + return sprintf(buf, "%i\n", + (sys_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); @@ -181,7 +283,22 @@ static int __init timer_init_sysfs(void) return ret; sys_timer->dev.cls = &timer_sysclass; - return sysdev_register(&sys_timer->dev); + ret = sysdev_register(&sys_timer->dev); + +#ifdef CONFIG_NO_IDLE_HZ + if (ret == 0 && sys_timer->dyn_tick) { + ret = sysdev_create_file(&sys_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; } device_initcall(timer_init_sysfs); @@ -205,6 +322,11 @@ void __init time_init(void) sys_timer = get_sys_timer(); printk(KERN_INFO "Using %s for system timer\n", sys_timer->name); +#ifdef CONFIG_NO_IDLE_HZ + if (sys_timer->dyn_tick) + spin_lock_init(&sys_timer->dyn_tick->lock); +#endif + #if defined(CONFIG_SH_KGDB) /* * Set up kgdb as requested. We do it here because the serial diff --git a/include/asm-sh/timer.h b/include/asm-sh/timer.h index 5a014bca9d..17b5e76a4c 100644 --- a/include/asm-sh/timer.h +++ b/include/asm-sh/timer.h @@ -18,8 +18,29 @@ struct sys_timer { struct sys_device dev; struct sys_timer_ops *ops; + +#ifdef CONFIG_NO_IDLE_HZ + struct dyn_tick_timer *dyn_tick; +#endif }; +#ifdef CONFIG_NO_IDLE_HZ +#define DYN_TICK_ENABLED (1 << 1) + +struct dyn_tick_timer { + spinlock_t lock; + unsigned int state; /* Current state */ + int (*enable)(void); /* Enables dynamic tick */ + int (*disable)(void); /* Disables dynamic tick */ + void (*reprogram)(unsigned long); /* Reprograms the timer */ + int (*handler)(int, void *); +}; + +void timer_dyn_reprogram(void); +#else +#define timer_dyn_reprogram() do { } while (0) +#endif + #define TICK_SIZE (tick_nsec / 1000) extern struct sys_timer tmu_timer, cmt_timer, mtu2_timer; -- 2.39.5