Yesterday I played with Intel's powertop on the Neo and noticed we
generate 300+ wake-ups per second. It told me to enable CONFIG_NO_HZ
for better results, so I went there and it was enabled
(CONFIG_NO_IDLE_HZ on ARM), but it turns out the option doesn't do
anything except on the OMAPs and Intel Xscale (and x86). I then tried
to implement dyn-tick support for arch/arm/plat-s3c24xx/time.c (and do
some clean-up) but soon learnt why it wasn't supported. The S3C timers
are 16bit, plus there's a serious shortage of them compared to OMAP.
So I did implement it and it reduces the wake-ups considerably but
because of the added calculations it needed I'm not sure if it's good
or bad for battery life, someone would have to check that. I know on
OMAP this brings a huge saving, but the s3c's idle states probably
don't do much.

dyn-ticks on ARM is enabled by passing "dyntick=enable" in the boot
parameters or running
 # echo 1 > /sys/devices/system/timer/timer0/dyn_tick
(also lets you switch back to HZ mode later).

Beside battery life, I suspect it's bad for time-keeping precision,
but the difference shouldn't be more than 1% because my test would
notice that. It may also crash but in the current form hasn't crashed
for me yet.  I had it running for 12h 50m straight until the battery
died (yes! almost 13h on battery, with CPU & modem & whatnot powered
on).

======= X, OM2007.2, dyn-tick disabled =======

PowerTOP 1.11    (C) 2007, 2008 Intel Corporation
Collecting data for 15 seconds
< Detailed C-state information is not available.>
P-states (frequencies)
Wakeups-from-idle per second : 308.4    interval: 15.0s
no ACPI power usage estimate available
Top causes for wakeups:
  64.9% (200.0)       <interrupt> : S3C2410 Timer Tick
  33.4% (102.9)       <interrupt> : lis302dl
   0.3% (  1.0)   matchbox-window : schedule_timeout (process_timeout)
   0.3% (  0.8)       <interrupt> : s3c2440-i2c
   0.2% (  0.7)            Xglamo : do_setitimer (it_real_fn)
   0.2% (  0.5)     <kernel core> : queue_delayed_work_on
(delayed_work_timer_fn)
   0.2% (  0.5)        pulseaudio : schedule_timeout (process_timeout)
   0.1% (  0.4)       <interrupt> : s3c2440-uart
   0.1% (  0.4)   matchbox-panel- : schedule_timeout (process_timeout)
   0.1% (  0.3)     <kernel core> : neigh_table_init_no_netlink
(neigh_periodic_timer)
   0.1% (  0.2)         phone-kit : schedule_timeout (process_timeout)
   0.1% (  0.2)     <kernel core> : page_writeback_init (wb_timer_fn)
   0.0% (  0.1)              init : schedule_timeout (process_timeout)
   0.0% (  0.1)          dropbear : sk_reset_timer (tcp_write_timer)
   0.0% (  0.1)          dropbear : schedule_timeout (process_timeout)
   0.0% (  0.1)    openmoko-today : schedule_timeout (process_timeout)
   0.0% (  0.1)              gsmd : tty_flip_buffer_push (delayed_work_timer_fn)

======= X, OM2007.2, dyn-tick enabled =======

PowerTOP 1.11    (C) 2007, 2008 Intel Corporation
Collecting data for 15 seconds
< Detailed C-state information is not available.>
P-states (frequencies)
Wakeups-from-idle per second : 108.7    interval: 15.0s
no ACPI power usage estimate available
Top causes for wakeups:
  91.8% ( 99.7)       <interrupt> : lis302dl
   2.4% (  2.6)       <interrupt> : S3C2410 Timer Tick
   0.9% (  1.0)   matchbox-window : schedule_timeout (process_timeout)
   0.9% (  0.9)       <interrupt> : s3c2440-uart
   0.9% (  0.9)        pulseaudio : schedule_timeout (process_timeout)
   0.7% (  0.8)       <interrupt> : s3c2440-i2c
   0.5% (  0.5)       <interrupt> : s3c2410_udc
   0.4% (  0.5)     <kernel core> : queue_delayed_work_on
(delayed_work_timer_fn)
   0.4% (  0.4)   matchbox-panel- : schedule_timeout (process_timeout)
   0.2% (  0.3)     <kernel core> : neigh_table_init_no_netlink
(neigh_periodic_timer)
   0.2% (  0.2)     <kernel core> : page_writeback_init (wb_timer_fn)
   0.2% (  0.2)              gsmd : tty_flip_buffer_push
(delayed_work_timer_fn)
   0.2% (  0.2)         phone-kit : schedule_timeout (process_timeout)
   0.1% (  0.1)              init : schedule_timeout (process_timeout)
   0.1% (  0.1)          dropbear : sk_reset_timer (tcp_write_timer)
   0.1% (  0.1)     <kernel core> : __neigh_event_send (neigh_timer_handler)
   0.1% (  0.1)            Xglamo : do_setitimer (it_real_fn)

======= console, dyn-tick disabled =======

PowerTOP 1.11    (C) 2007, 2008 Intel Corporation
Collecting data for 15 seconds
< Detailed C-state information is not available.>
P-states (frequencies)
Wakeups-from-idle per second : 215.1    interval: 15.0s
no ACPI power usage estimate available
Top causes for wakeups:
  93.1% (200.1)       <interrupt> : S3C2410 Timer Tick
   2.7% (  5.7)       <interrupt> : s3c2440-i2c
   2.3% (  5.0)            Xglamo : fbcon_add_cursor_timer
(cursor_timer_handler)
   0.7% (  1.5)          events/0 : schedule_timeout (process_timeout)
   0.3% (  0.6)       <interrupt> : s3c2410_udc
   0.2% (  0.5)     <kernel core> : queue_delayed_work_on
(delayed_work_timer_fn)
   0.2% (  0.5)        pulseaudio : schedule_timeout (process_timeout)
   0.1% (  0.3)     <kernel core> : neigh_table_init_no_netlink
(neigh_periodic_timer)
   0.1% (  0.2)     <kernel core> : page_writeback_init (wb_timer_fn)
   0.1% (  0.1)       <interrupt> : pcf50633
   0.1% (  0.1)       <interrupt> : s3c2440-uart
   0.1% (  0.1)              init : schedule_timeout (process_timeout)
   0.1% (  0.1)              gsmd : do_setitimer (it_real_fn)
   0.0% (  0.1)          dropbear : sk_reset_timer (tcp_write_timer)
   0.0% (  0.1)          dropbear : schedule_timeout (process_timeout)

======= console, dyn-tick enabled =======

PowerTOP 1.11    (C) 2007, 2008 Intel Corporation
Collecting data for 15 seconds
< Detailed C-state information is not available.>
P-states (frequencies)
Wakeups-from-idle per second : 13.0     interval: 15.0s
no ACPI power usage estimate available
Top causes for wakeups:
  48.5% (  6.3)       <interrupt> : S3C2410 Timer Tick
  38.7% (  5.0)            Xglamo : fbcon_add_cursor_timer
(cursor_timer_handler)
   4.1% (  0.5)        pulseaudio : schedule_timeout (process_timeout)
   4.1% (  0.5)     <kernel core> : queue_delayed_work_on
(delayed_work_timer_fn)
   1.5% (  0.2)     <kernel core> : page_writeback_init (wb_timer_fn)
   1.5% (  0.2)     <kernel core> : neigh_table_init_no_netlink
(neigh_periodic_timer)
   1.0% (  0.1)              init : schedule_timeout (process_timeout)
   0.5% (  0.1)          dropbear : sk_reset_timer (tcp_write_timer)

(there's a lot of randomness, also I just notice Xglamo didn't get
killed with /etc/init.d/xserver-nodm stop, but importantly neod did)
Cheers
From 8247813c3ac0b8d7de7f2a89ca79b184ff73e62c Mon Sep 17 00:00:00 2001
From: Andrzej Zaborowski <[EMAIL PROTECTED]>
Date: Mon, 30 Jun 2008 05:08:27 +0200
Subject: [PATCH] Hacky CONFIG_NO_IDLE_HZ (dyn-tick) support for S3C24xx.

---
 arch/arm/plat-s3c24xx/time.c |  239 +++++++++++++++++++++++++++++++++++-------
 1 files changed, 202 insertions(+), 37 deletions(-)

diff --git a/arch/arm/plat-s3c24xx/time.c b/arch/arm/plat-s3c24xx/time.c
index 39fc33d..a2ce68f 100644
--- a/arch/arm/plat-s3c24xx/time.c
+++ b/arch/arm/plat-s3c24xx/time.c
@@ -3,6 +3,8 @@
  * Copyright (C) 2003-2005 Simtec Electronics
  *	Ben Dooks, <[EMAIL PROTECTED]>
  *
+ * dyn_tick support by Andrzej Zaborowski based on omap_dyn_tick_timer.
+ *
  * This program 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
@@ -44,6 +46,8 @@ static unsigned long timer_startval;
 static unsigned long timer_usec_ticks;
 static struct work_struct resume_work;
 
+unsigned long pclk;
+
 #define TIMER_USEC_SHIFT 16
 
 /* we use the shifted arithmetic to work out the ratio of timer ticks
@@ -180,11 +184,7 @@ static void s3c2410_timer_setup (void)
 		tcfg1 &= ~S3C2410_TCFG1_MUX4_MASK;
 		tcfg1 |= S3C2410_TCFG1_MUX4_TCLK1;
 	} else {
-		unsigned long pclk;
-		struct clk *clk;
-
-		/* for the h1940 (and others), we use the pclk from the core
-		 * to generate the timer values. since values around 50 to
+		/* since values around 50 to
 		 * 70MHz are not values we can directly generate the timer
 		 * value from, we need to pre-scale and divide before using it.
 		 *
@@ -192,20 +192,7 @@ static void s3c2410_timer_setup (void)
 		 * (8.45 ticks per usec)
 		 */
 
-		/* this is used as default if no other timer can be found */
-
-		clk = clk_get(NULL, "timers");
-		if (IS_ERR(clk))
-			panic("failed to get clock for system timer");
-
-		clk_enable(clk);
-
-		pclk = clk_get_rate(clk);
-
-		printk("pclk = %lu\n", pclk);
-
 		/* configure clock tick */
-
 		timer_usec_ticks = timer_mask_usec_ticks(6, pclk);
 		printk("timer_usec_ticks = %lu\n", timer_usec_ticks);
 
@@ -216,11 +203,6 @@ static void s3c2410_timer_setup (void)
 		tcfg0 |= ((6 - 1) / 2) << S3C2410_TCFG_PRESCALER1_SHIFT;
 
 		tcnt = (pclk / 6) / HZ;
-
-		/* start the timer running */
-		tcon |= S3C2410_TCON_T4START | S3C2410_TCON_T4RELOAD;
-		tcon &= ~S3C2410_TCON_T4MANUALUPD;
-		__raw_writel(tcon, S3C2410_TCON);
 	}
 
 	/* timers reload after counting zero, so reduce the count by 1 */
@@ -267,22 +249,26 @@ static void timer_resume_work(struct work_struct *work)
 	s3c2410_timer_setup();
 }
 
-/* ooh a nasty situation arises if we try to call s3c2410_timer_setup() from
- * the resume handler.  It is called in atomic context but the clock APIs
- * try to lock a mutex which may sleep.  We are in a bit of an unusual
- * situation because we don't have a tick source right now, but it should be
- * okay to try to schedule a work item... hopefully
- */
-
-static void s3c2410_timer_resume_atomic(void)
-{
-	int ret = schedule_work(&resume_work);
-	if (!ret)
-		printk(KERN_INFO"Failed to schedule_work tick ctr (%d)\n", ret);
-}
-
 static void __init s3c2410_timer_init (void)
 {
+	struct clk *clk;
+
+	if (!use_tclk1_12()) {
+		/* for the h1940 (and others), we use the pclk from the core
+		 * to generate the timer values.
+		 */
+
+		/* this is used as default if no other timer can be found */
+		clk = clk_get(NULL, "timers");
+		if (IS_ERR(clk))
+			panic("failed to get clock for system timer");
+
+		clk_enable(clk);
+
+		pclk = clk_get_rate(clk);
+		printk("pclk = %lu\n", pclk);
+	}
+
 	INIT_WORK(&resume_work, timer_resume_work);
 	s3c2410_timer_setup();
 	setup_irq(IRQ_TIMER4, &s3c2410_timer_irq);
@@ -304,8 +290,187 @@ static void s3c2410_timer_resume(void)
 		    "s3c2410_timer_resume_work already queued ???\n");
 }
 
+#ifdef CONFIG_NO_IDLE_HZ
+/*
+ * We'll set a constant prescaler so we don't have to bother setting it
+ * when reprogramming and so that we avoid costly divisions.
+ *
+ * (2 * HZ) << INPUT_FREQ_SHIFT is the desired frequency after prescaler.
+ * At HZ == 200, HZ * 1024 should work for PCLKs of up to ~53.5 MHz.
+ */
+#define INPUT_FREQ_SHIFT	9
+
+static int ticks_last;
+static int ticks_left;
+static uint32_t tcnto_last;
+
+static inline int s3c24xx_timer_read(void)
+{
+	uint32_t tcnto = __raw_readl(S3C2410_TCNTO(4));
+
+	/*
+	 * WARNING: sometimes we get called before TCNTB has been
+	 * loaded into the counter and TCNTO then returns its previous
+	 * value and kill us, so don't do anything before counter is
+	 * reloaded.
+	 */
+	if (unlikely(tcnto == tcnto_last))
+		return ticks_last;
+
+	tcnto_last = -1;
+	return tcnto <<
+		((__raw_readl(S3C2410_TCFG1) >> S3C2410_TCFG1_MUX4_SHIFT) & 3);
+}
+
+static inline void s3c24xx_timer_program(int ticks)
+{
+	uint32_t tcon = __raw_readl(S3C2410_TCON) & ~(7 << 20);
+	uint32_t tcfg1 = __raw_readl(S3C2410_TCFG1) & ~S3C2410_TCFG1_MUX4_MASK;
+
+	/* Just make sure the timer is stopped.  */
+	__raw_writel(tcon, S3C2410_TCON);
+
+	/* TODO: add likely()ies / unlikely()ies */
+	if (ticks >> 18) {
+		ticks_last = min(ticks, 0xffff << 3);
+		ticks_left = ticks - ticks_last;
+		__raw_writel(tcfg1 | S3C2410_TCFG1_MUX4_DIV16, S3C2410_TCFG1);
+		__raw_writel(ticks_last >> 3, S3C2410_TCNTB(4));
+	} else if (ticks >> 17) {
+		ticks_last = ticks;
+		ticks_left = 0;
+		__raw_writel(tcfg1 | S3C2410_TCFG1_MUX4_DIV8, S3C2410_TCFG1);
+		__raw_writel(ticks_last >> 2, S3C2410_TCNTB(4));
+	} else if (ticks >> 16) {
+		ticks_last = ticks;
+		ticks_left = 0;
+		__raw_writel(tcfg1 | S3C2410_TCFG1_MUX4_DIV4, S3C2410_TCFG1);
+		__raw_writel(ticks_last >> 1, S3C2410_TCNTB(4));
+	} else {
+		ticks_last = ticks;
+		ticks_left = 0;
+		__raw_writel(tcfg1 | S3C2410_TCFG1_MUX4_DIV2, S3C2410_TCFG1);
+		__raw_writel(ticks_last >> 0, S3C2410_TCNTB(4));
+	}
+
+	tcnto_last = __raw_readl(S3C2410_TCNTO(4));
+	__raw_writel(tcon | S3C2410_TCON_T4MANUALUPD,
+			S3C2410_TCON);
+	__raw_writel(tcon | S3C2410_TCON_T4START,
+			S3C2410_TCON);
+}
+
+/*
+ * If we have already waited all the time we were supposed to wait,
+ * kick the timer, setting the longest allowed timeout value just
+ * for time-keeping.
+ */
+static inline void s3c24xx_timer_program_idle(void)
+{
+	s3c24xx_timer_program(0xffff << 3);
+}
+
+static inline void s3c24xx_timer_update(int restart)
+{
+	int ticks_cur = s3c24xx_timer_read();
+	int jiffies_elapsed = (ticks_last - ticks_cur) >> INPUT_FREQ_SHIFT;
+	int subjiffy = ticks_last - (jiffies_elapsed << INPUT_FREQ_SHIFT);
+
+	if (restart) {
+		if (ticks_left >= (1 << INPUT_FREQ_SHIFT))
+			s3c24xx_timer_program(ticks_left);
+		else
+			s3c24xx_timer_program_idle();
+		ticks_last += subjiffy;
+	} else
+		ticks_last = subjiffy;
+
+	while (jiffies_elapsed --)
+		timer_tick();
+}
+
+/* Called when the timer expires.  */
+static irqreturn_t s3c24xx_timer_handler(int irq, void *dev_id)
+{
+	tcnto_last = -1;
+	s3c24xx_timer_update(1);
+
+	return IRQ_HANDLED;
+}
+
+/* Called to update jiffies with time elapsed.  */
+static irqreturn_t s3c24xx_timer_handler_dyn_tick(int irq, void *dev_id)
+{
+	s3c24xx_timer_update(0);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Programs the next timer interrupt needed.  Called when dynamic tick is
+ * enabled, and to reprogram the ticks to skip from pm_idle.  The CPU goes
+ * to sleep directly after this.
+ */
+static void s3c24xx_timer_reprogram_dyn_tick(unsigned long next_jiffies)
+{
+	int subjiffy_left = ticks_last - s3c24xx_timer_read();
+
+	s3c24xx_timer_program(max((int) next_jiffies, 1) << INPUT_FREQ_SHIFT);
+	ticks_last += subjiffy_left;
+}
+
+static unsigned long s3c24xx_timer_offset_dyn_tick(void)
+{
+	/* TODO */
+	return 0;
+}
+
+struct sys_timer s3c24xx_timer;
+static int s3c24xx_timer_enable_dyn_tick(void)
+{
+	/* Set our constant prescaler.  */
+	uint32_t tcfg0 = __raw_readl(S3C2410_TCFG0);
+	int prescaler =
+		max(min(256, (int) pclk / (HZ << (INPUT_FREQ_SHIFT + 1))), 1);
+
+	tcfg0 &= ~S3C2410_TCFG_PRESCALER1_MASK;
+	tcfg0 |= (prescaler - 1) << S3C2410_TCFG_PRESCALER1_SHIFT;
+	__raw_writel(tcfg0, S3C2410_TCFG0);
+
+	/* Override handlers.  */
+	s3c2410_timer_irq.handler = s3c24xx_timer_handler;
+	s3c24xx_timer.offset = s3c24xx_timer_offset_dyn_tick;
+
+	printk(KERN_INFO "dyn_tick enabled on s3c24xx timer 4, "
+			"%li Hz pclk with prescaler %i\n", pclk, prescaler);
+
+	s3c24xx_timer_program_idle();
+
+	return 0;
+}
+
+static int s3c24xx_timer_disable_dyn_tick(void)
+{
+	s3c2410_timer_irq.handler = s3c2410_timer_interrupt;
+	s3c24xx_timer.offset = s3c2410_gettimeoffset;
+	s3c2410_timer_setup();
+
+	return 0;
+}
+
+static struct dyn_tick_timer s3c24xx_dyn_tick_timer = {
+	.enable		= s3c24xx_timer_enable_dyn_tick,
+	.disable	= s3c24xx_timer_disable_dyn_tick,
+	.reprogram	= s3c24xx_timer_reprogram_dyn_tick,
+	.handler	= s3c24xx_timer_handler_dyn_tick,
+};
+#endif	/* CONFIG_NO_IDLE_HZ */
+
 struct sys_timer s3c24xx_timer = {
 	.init		= s3c2410_timer_init,
 	.offset		= s3c2410_gettimeoffset,
 	.resume		= s3c2410_timer_resume,
+#ifdef CONFIG_NO_IDLE_HZ
+	.dyn_tick	= &s3c24xx_dyn_tick_timer,
+#endif
 };
-- 
1.5.3.4

Reply via email to