summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/sh/Kconfig18
-rw-r--r--arch/sh/kernel/time.c124
-rw-r--r--include/asm-sh/timer.h21
3 files changed, 162 insertions, 1 deletions
diff --git a/arch/sh/Kconfig b/arch/sh/Kconfig
index 48308dc86e33..aa1ebc561b84 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 c55d6f217a46..1b91c7214979 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 5a014bca9d58..17b5e76a4c31 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;