All of lore.kernel.org
 help / color / mirror / Atom feed
From: ccross@android.com (Colin Cross)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH] [ARM] twd: Allow twd rescaling to match cpu frequency
Date: Wed,  1 Sep 2010 20:04:34 -0700	[thread overview]
Message-ID: <1283396674-4865-1-git-send-email-ccross@android.com> (raw)

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

WARNING: multiple messages have this Message-ID (diff)
From: Colin Cross <ccross@android.com>
To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org
Cc: Colin Cross <ccross@android.com>,
	Russell King <linux@arm.linux.org.uk>,
	srinidhi kasagar <srinidhi.kasagar@stericsson.com>,
	Linus Walleij <linus.walleij@stericsson.com>,
	Varun Swara <Varun.Swara@arm.com>,
	Catalin Marinas <catalin.marinas@arm.com>
Subject: [PATCH] [ARM] twd: Allow twd rescaling to match cpu frequency
Date: Wed,  1 Sep 2010 20:04:34 -0700	[thread overview]
Message-ID: <1283396674-4865-1-git-send-email-ccross@android.com> (raw)

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


             reply	other threads:[~2010-09-02  3:04 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-09-02  3:04 Colin Cross [this message]
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:39   ` Linus Walleij
2010-11-12 12:49   ` Harald Gustafsson
2010-11-12 12:49     ` Harald Gustafsson
2010-11-12 12:59     ` Russell King - ARM Linux
2010-11-12 12:59       ` Russell King - ARM Linux
2010-11-12 13:05       ` Harald Gustafsson
2010-11-12 13:05         ` Harald Gustafsson
2010-11-17  3:55         ` Colin Cross
2010-11-17  3:55           ` Colin Cross

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1283396674-4865-1-git-send-email-ccross@android.com \
    --to=ccross@android.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.