public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] [ARM] twd: Allow twd rescaling to match cpu frequency
@ 2010-09-02  3:04 Colin Cross
  2010-11-12 12:39 ` Linus Walleij
  0 siblings, 1 reply; 6+ messages in thread
From: Colin Cross @ 2010-09-02  3:04 UTC (permalink / raw)
  To: linux-kernel, linux-arm-kernel
  Cc: Colin Cross, Russell King, srinidhi kasagar, Linus Walleij,
	Varun Swara, Catalin Marinas

The clock to the ARM TWD local timer scales with the cpu
frequency.  To allow the cpu frequency to change while
maintaining a constant TWD frequency, pick a lower target
frequency for the TWD and use the prescaler to divide down
to the closest lower frequency.

This patch provides a new initialization function that takes
a target TWD frequency and the relation between the cpu
clock and the TWD clock, required to be an integer divider
>= 2 by the ARM spec.  It also provides a function to be
called from cpufreq drivers to set the prescaler whenever
the cpu frequency changes.

Also fixes a typo in the printk of the calibrated frequency.

Change-Id: I45c76ae1ed501c40b94709c15834c8a0b6116c84
Signed-off-by: Colin Cross <ccross@android.com>
---
 arch/arm/include/asm/smp_twd.h |   19 +++++++++++++
 arch/arm/kernel/smp_twd.c      |   57 +++++++++++++++++++++++++++++++++++++---
 2 files changed, 72 insertions(+), 4 deletions(-)

diff --git a/arch/arm/include/asm/smp_twd.h b/arch/arm/include/asm/smp_twd.h
index 634f357..79aebe2 100644
--- a/arch/arm/include/asm/smp_twd.h
+++ b/arch/arm/include/asm/smp_twd.h
@@ -17,6 +17,7 @@
 #define TWD_TIMER_CONTROL_ONESHOT	(0 << 1)
 #define TWD_TIMER_CONTROL_PERIODIC	(1 << 1)
 #define TWD_TIMER_CONTROL_IT_ENABLE	(1 << 2)
+#define TWD_TIMER_CONTROL_PRESCALE_MASK	(0xFF << 8)
 
 struct clock_event_device;
 
@@ -26,4 +27,22 @@ void twd_timer_stop(void);
 int twd_timer_ack(void);
 void twd_timer_setup(struct clock_event_device *);
 
+/*
+ * Use this setup function on systems where the cpu clock frequency may
+ * change.  periphclk_prescaler is the fixed divider value between the cpu
+ * clock and the PERIPHCLK clock that feeds the TWD.  target_rate should be
+ * low enough that the prescaler can accurately reach the target rate from the
+ * lowest cpu frequency.
+ */
+void twd_timer_setup_scalable(struct clock_event_device *,
+	unsigned long target_rate, unsigned int periphclk_prescaler);
+
+/*
+ * Recalculate the twd prescaler value when the cpu frequency changes. To
+ * prevent early timer interrupts, must be called before changing the cpu
+ * frequency if the frequency is increasing, or after if the frequency is
+ * decreasing.
+ */
+void twd_recalc_prescaler(unsigned long new_rate);
+
 #endif
diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c
index 35882fb..2b4f927 100644
--- a/arch/arm/kernel/smp_twd.c
+++ b/arch/arm/kernel/smp_twd.c
@@ -25,6 +25,8 @@
 void __iomem *twd_base;
 
 static unsigned long twd_timer_rate;
+static unsigned long twd_periphclk_prescaler;
+static unsigned long twd_target_rate;
 
 static void twd_set_mode(enum clock_event_mode mode,
 			struct clock_event_device *clk)
@@ -79,10 +81,31 @@ int twd_timer_ack(void)
 	return 0;
 }
 
-static void __cpuinit twd_calibrate_rate(void)
+void twd_recalc_prescaler(unsigned long new_rate)
+{
+	u32 ctrl;
+	int prescaler;
+	unsigned long periphclk_rate;
+
+	BUG_ON(twd_periphclk_prescaler == 0 || twd_timer_rate == 0);
+
+	periphclk_rate = new_rate / twd_periphclk_prescaler;
+
+	prescaler = DIV_ROUND_UP(periphclk_rate, twd_timer_rate);
+	prescaler = clamp(prescaler - 1, 0, 0xFF);
+
+	ctrl = __raw_readl(twd_base + TWD_TIMER_CONTROL);
+	ctrl &= ~TWD_TIMER_CONTROL_PRESCALE_MASK;
+	ctrl |= prescaler << 8;
+	__raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL);
+}
+
+static void __cpuinit twd_calibrate_rate(unsigned long target_rate,
+	unsigned int periphclk_prescaler)
 {
 	unsigned long load, count;
 	u64 waitjiffies;
+	unsigned long cpu_rate;
 
 	/*
 	 * If this is the first time round, we need to work out how fast
@@ -113,8 +136,22 @@ static void __cpuinit twd_calibrate_rate(void)
 
 		twd_timer_rate = (0xFFFFFFFFU - count) * (HZ / 5);
 
+		/*
+		 * If a target rate has been requested, adjust the TWD prescaler
+		 * to get the closest lower frequency.
+		 */
+		if (target_rate) {
+			twd_periphclk_prescaler = periphclk_prescaler;
+			twd_target_rate = target_rate;
+
+			cpu_rate = twd_timer_rate * periphclk_prescaler;
+			twd_recalc_prescaler(cpu_rate);
+
+			twd_timer_rate = twd_target_rate;
+		}
+
 		printk("%lu.%02luMHz.\n", twd_timer_rate / 1000000,
-			(twd_timer_rate / 100000) % 100);
+			(twd_timer_rate / 10000) % 100);
 	}
 
 	load = twd_timer_rate / HZ;
@@ -125,11 +162,12 @@ static void __cpuinit twd_calibrate_rate(void)
 /*
  * Setup the local clock events for a CPU.
  */
-void __cpuinit twd_timer_setup(struct clock_event_device *clk)
+static void __cpuinit __twd_timer_setup(struct clock_event_device *clk,
+	unsigned long target_rate, unsigned int periphclk_prescaler)
 {
 	unsigned long flags;
 
-	twd_calibrate_rate();
+	twd_calibrate_rate(target_rate, periphclk_prescaler);
 
 	clk->name = "local_timer";
 	clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT |
@@ -151,6 +189,17 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk)
 	clockevents_register_device(clk);
 }
 
+void __cpuinit twd_timer_setup_scalable(struct clock_event_device *clk,
+	unsigned long target_rate, unsigned int periphclk_prescaler)
+{
+	__twd_timer_setup(clk, target_rate, periphclk_prescaler);
+}
+
+void __cpuinit twd_timer_setup(struct clock_event_device *clk)
+{
+	__twd_timer_setup(clk, 0, 0);
+}
+
 #ifdef CONFIG_HOTPLUG_CPU
 /*
  * take a local timer down
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2010-11-17  3:56 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-09-02  3:04 [PATCH] [ARM] twd: Allow twd rescaling to match cpu frequency Colin Cross
2010-11-12 12:39 ` Linus Walleij
2010-11-12 12:49   ` Harald Gustafsson
2010-11-12 12:59     ` Russell King - ARM Linux
2010-11-12 13:05       ` Harald Gustafsson
2010-11-17  3:55         ` Colin Cross

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox