diff options
author | Nicolas Pitre <nico@cam.org> | 2009-05-15 00:42:36 -0400 |
---|---|---|
committer | Nicolas Pitre <nico@cam.org> | 2009-06-08 13:04:54 -0400 |
commit | a399e3fa795afa058e4485b25c498e0c5a860428 (patch) | |
tree | ce58f69f27ff8dc3b188f7e66dff9c19910d3646 /arch/arm/plat-orion/time.c | |
parent | 8a3269fc21cc4405d80b362139c078cf655a505a (diff) |
[ARM] orion: make sure sched_clock() usage of cnt32_to_63() is safe
With a TCLK = 200MHz, the half period of the hardware timer is roughly
10 seconds. Because cnt32_to_63() must be called at least once per
half period of the base hardware counter, it is a bit risky to rely
solely on scheduling to generate frequent enough calls. Let's use a
kernel timer to ensure this.
Signed-off-by: Nicolas Pitre <nico@marvell.com>
Diffstat (limited to 'arch/arm/plat-orion/time.c')
-rw-r--r-- | arch/arm/plat-orion/time.c | 44 |
1 files changed, 32 insertions, 12 deletions
diff --git a/arch/arm/plat-orion/time.c b/arch/arm/plat-orion/time.c index b856cec81702..715a30177f28 100644 --- a/arch/arm/plat-orion/time.c +++ b/arch/arm/plat-orion/time.c | |||
@@ -12,14 +12,15 @@ | |||
12 | */ | 12 | */ |
13 | 13 | ||
14 | #include <linux/kernel.h> | 14 | #include <linux/kernel.h> |
15 | #include <linux/sched.h> | ||
16 | #include <linux/cnt32_to_63.h> | ||
17 | #include <linux/timer.h> | ||
15 | #include <linux/clockchips.h> | 18 | #include <linux/clockchips.h> |
16 | #include <linux/interrupt.h> | 19 | #include <linux/interrupt.h> |
17 | #include <linux/irq.h> | 20 | #include <linux/irq.h> |
18 | #include <asm/mach/time.h> | 21 | #include <asm/mach/time.h> |
19 | #include <mach/bridge-regs.h> | 22 | #include <mach/bridge-regs.h> |
20 | #include <mach/hardware.h> | 23 | #include <mach/hardware.h> |
21 | #include <linux/sched.h> | ||
22 | #include <linux/cnt32_to_63.h> | ||
23 | 24 | ||
24 | /* | 25 | /* |
25 | * Number of timer ticks per jiffy. | 26 | * Number of timer ticks per jiffy. |
@@ -44,14 +45,36 @@ static u32 ticks_per_jiffy; | |||
44 | /* | 45 | /* |
45 | * Orion's sched_clock implementation. It has a resolution of | 46 | * Orion's sched_clock implementation. It has a resolution of |
46 | * at least 7.5ns (133MHz TCLK) and a maximum value of 834 days. | 47 | * at least 7.5ns (133MHz TCLK) and a maximum value of 834 days. |
48 | * | ||
49 | * Because the hardware timer period is quite short (21 secs if | ||
50 | * 200MHz TCLK) and because cnt32_to_63() needs to be called at | ||
51 | * least once per half period to work properly, a kernel timer is | ||
52 | * set up to ensure this requirement is always met. | ||
47 | */ | 53 | */ |
48 | #define TCLK2NS_SCALE_FACTOR 8 | 54 | #define TCLK2NS_SCALE_FACTOR 8 |
49 | 55 | ||
50 | static unsigned long tclk2ns_scale; | 56 | static unsigned long tclk2ns_scale; |
51 | 57 | ||
52 | static void __init set_tclk2ns_scale(unsigned long tclk) | 58 | unsigned long long sched_clock(void) |
59 | { | ||
60 | unsigned long long v = cnt32_to_63(0xffffffff - readl(TIMER0_VAL)); | ||
61 | return (v * tclk2ns_scale) >> TCLK2NS_SCALE_FACTOR; | ||
62 | } | ||
63 | |||
64 | static struct timer_list cnt32_to_63_keepwarm_timer; | ||
65 | |||
66 | static void cnt32_to_63_keepwarm(unsigned long data) | ||
67 | { | ||
68 | mod_timer(&cnt32_to_63_keepwarm_timer, round_jiffies(jiffies + data)); | ||
69 | (void) sched_clock(); | ||
70 | } | ||
71 | |||
72 | static void __init setup_sched_clock(unsigned long tclk) | ||
53 | { | 73 | { |
54 | unsigned long long v = NSEC_PER_SEC; | 74 | unsigned long long v; |
75 | unsigned long data; | ||
76 | |||
77 | v = NSEC_PER_SEC; | ||
55 | v <<= TCLK2NS_SCALE_FACTOR; | 78 | v <<= TCLK2NS_SCALE_FACTOR; |
56 | v += tclk/2; | 79 | v += tclk/2; |
57 | do_div(v, tclk); | 80 | do_div(v, tclk); |
@@ -63,12 +86,10 @@ static void __init set_tclk2ns_scale(unsigned long tclk) | |||
63 | if (v & 1) | 86 | if (v & 1) |
64 | v++; | 87 | v++; |
65 | tclk2ns_scale = v; | 88 | tclk2ns_scale = v; |
66 | } | ||
67 | 89 | ||
68 | unsigned long long sched_clock(void) | 90 | data = (0xffffffffUL / tclk / 2 - 2) * HZ; |
69 | { | 91 | setup_timer(&cnt32_to_63_keepwarm_timer, cnt32_to_63_keepwarm, data); |
70 | unsigned long long v = cnt32_to_63(0xffffffff - readl(TIMER0_VAL)); | 92 | mod_timer(&cnt32_to_63_keepwarm_timer, round_jiffies(jiffies + data)); |
71 | return (v * tclk2ns_scale) >> TCLK2NS_SCALE_FACTOR; | ||
72 | } | 93 | } |
73 | 94 | ||
74 | /* | 95 | /* |
@@ -210,9 +231,9 @@ void __init orion_time_init(unsigned int irq, unsigned int tclk) | |||
210 | ticks_per_jiffy = (tclk + HZ/2) / HZ; | 231 | ticks_per_jiffy = (tclk + HZ/2) / HZ; |
211 | 232 | ||
212 | /* | 233 | /* |
213 | * Set scale for sched_clock | 234 | * Set scale and timer for sched_clock |
214 | */ | 235 | */ |
215 | set_tclk2ns_scale(tclk); | 236 | setup_sched_clock(tclk); |
216 | 237 | ||
217 | /* | 238 | /* |
218 | * Setup free-running clocksource timer (interrupts | 239 | * Setup free-running clocksource timer (interrupts |
@@ -227,7 +248,6 @@ void __init orion_time_init(unsigned int irq, unsigned int tclk) | |||
227 | orion_clksrc.mult = clocksource_hz2mult(tclk, orion_clksrc.shift); | 248 | orion_clksrc.mult = clocksource_hz2mult(tclk, orion_clksrc.shift); |
228 | clocksource_register(&orion_clksrc); | 249 | clocksource_register(&orion_clksrc); |
229 | 250 | ||
230 | |||
231 | /* | 251 | /* |
232 | * Setup clockevent timer (interrupt-driven.) | 252 | * Setup clockevent timer (interrupt-driven.) |
233 | */ | 253 | */ |