diff options
Diffstat (limited to 'arch/arm/plat-nomadik/timer.c')
-rw-r--r-- | arch/arm/plat-nomadik/timer.c | 89 |
1 files changed, 80 insertions, 9 deletions
diff --git a/arch/arm/plat-nomadik/timer.c b/arch/arm/plat-nomadik/timer.c index aedf9c1d645e..63cdc6025bd7 100644 --- a/arch/arm/plat-nomadik/timer.c +++ b/arch/arm/plat-nomadik/timer.c | |||
@@ -3,6 +3,7 @@ | |||
3 | * | 3 | * |
4 | * Copyright (C) 2008 STMicroelectronics | 4 | * Copyright (C) 2008 STMicroelectronics |
5 | * Copyright (C) 2010 Alessandro Rubini | 5 | * Copyright (C) 2010 Alessandro Rubini |
6 | * Copyright (C) 2010 Linus Walleij for ST-Ericsson | ||
6 | * | 7 | * |
7 | * This program is free software; you can redistribute it and/or modify | 8 | * This program is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License version 2, as | 9 | * it under the terms of the GNU General Public License version 2, as |
@@ -16,11 +17,13 @@ | |||
16 | #include <linux/clk.h> | 17 | #include <linux/clk.h> |
17 | #include <linux/jiffies.h> | 18 | #include <linux/jiffies.h> |
18 | #include <linux/err.h> | 19 | #include <linux/err.h> |
20 | #include <linux/cnt32_to_63.h> | ||
21 | #include <linux/timer.h> | ||
19 | #include <asm/mach/time.h> | 22 | #include <asm/mach/time.h> |
20 | 23 | ||
21 | #include <plat/mtu.h> | 24 | #include <plat/mtu.h> |
22 | 25 | ||
23 | void __iomem *mtu_base; /* ssigned by machine code */ | 26 | void __iomem *mtu_base; /* Assigned by machine code */ |
24 | 27 | ||
25 | /* | 28 | /* |
26 | * Kernel assumes that sched_clock can be called early | 29 | * Kernel assumes that sched_clock can be called early |
@@ -48,16 +51,82 @@ static struct clocksource nmdk_clksrc = { | |||
48 | /* | 51 | /* |
49 | * Override the global weak sched_clock symbol with this | 52 | * Override the global weak sched_clock symbol with this |
50 | * local implementation which uses the clocksource to get some | 53 | * local implementation which uses the clocksource to get some |
51 | * better resolution when scheduling the kernel. We accept that | 54 | * better resolution when scheduling the kernel. |
52 | * this wraps around for now, since it is just a relative time | 55 | * |
53 | * stamp. (Inspired by OMAP implementation.) | 56 | * Because the hardware timer period may be quite short |
57 | * (32.3 secs on the 133 MHz MTU timer selection on ux500) | ||
58 | * and because cnt32_to_63() needs to be called at least once per | ||
59 | * half period to work properly, a kernel keepwarm() timer is set up | ||
60 | * to ensure this requirement is always met. | ||
61 | * | ||
62 | * Also the sched_clock timer will wrap around at some point, | ||
63 | * here we set it to run continously for a year. | ||
54 | */ | 64 | */ |
65 | #define SCHED_CLOCK_MIN_WRAP 3600*24*365 | ||
66 | static struct timer_list cnt32_to_63_keepwarm_timer; | ||
67 | static u32 sched_mult; | ||
68 | static u32 sched_shift; | ||
69 | |||
55 | unsigned long long notrace sched_clock(void) | 70 | unsigned long long notrace sched_clock(void) |
56 | { | 71 | { |
57 | return clocksource_cyc2ns(nmdk_clksrc.read( | 72 | u64 cycles; |
58 | &nmdk_clksrc), | 73 | |
59 | nmdk_clksrc.mult, | 74 | if (unlikely(!mtu_base)) |
60 | nmdk_clksrc.shift); | 75 | return 0; |
76 | |||
77 | cycles = cnt32_to_63(-readl(mtu_base + MTU_VAL(0))); | ||
78 | /* | ||
79 | * sched_mult is guaranteed to be even so will | ||
80 | * shift out bit 63 | ||
81 | */ | ||
82 | return (cycles * sched_mult) >> sched_shift; | ||
83 | } | ||
84 | |||
85 | /* Just kick sched_clock every so often */ | ||
86 | static void cnt32_to_63_keepwarm(unsigned long data) | ||
87 | { | ||
88 | mod_timer(&cnt32_to_63_keepwarm_timer, round_jiffies(jiffies + data)); | ||
89 | (void) sched_clock(); | ||
90 | } | ||
91 | |||
92 | /* | ||
93 | * Set up a timer to keep sched_clock():s 32_to_63 algorithm warm | ||
94 | * once in half a 32bit timer wrap interval. | ||
95 | */ | ||
96 | static void __init nmdk_sched_clock_init(unsigned long rate) | ||
97 | { | ||
98 | u32 v; | ||
99 | unsigned long delta; | ||
100 | u64 days; | ||
101 | |||
102 | /* Find the apropriate mult and shift factors */ | ||
103 | clocks_calc_mult_shift(&sched_mult, &sched_shift, | ||
104 | rate, NSEC_PER_SEC, SCHED_CLOCK_MIN_WRAP); | ||
105 | /* We need to multiply by an even number to get rid of bit 63 */ | ||
106 | if (sched_mult & 1) | ||
107 | sched_mult++; | ||
108 | |||
109 | /* Let's see what we get, take max counter and scale it */ | ||
110 | days = (0xFFFFFFFFFFFFFFFFLLU * sched_mult) >> sched_shift; | ||
111 | do_div(days, NSEC_PER_SEC); | ||
112 | do_div(days, (3600*24)); | ||
113 | |||
114 | pr_info("sched_clock: using %d bits @ %lu Hz wrap in %lu days\n", | ||
115 | (64 - sched_shift), rate, (unsigned long) days); | ||
116 | |||
117 | /* | ||
118 | * Program a timer to kick us at half 32bit wraparound | ||
119 | * Formula: seconds per wrap = (2^32) / f | ||
120 | */ | ||
121 | v = 0xFFFFFFFFUL / rate; | ||
122 | /* We want half of the wrap time to keep cnt32_to_63 warm */ | ||
123 | v /= 2; | ||
124 | pr_debug("sched_clock: prescaled timer rate: %lu Hz, " | ||
125 | "initialize keepwarm timer every %d seconds\n", rate, v); | ||
126 | /* Convert seconds to jiffies */ | ||
127 | delta = msecs_to_jiffies(v*1000); | ||
128 | setup_timer(&cnt32_to_63_keepwarm_timer, cnt32_to_63_keepwarm, delta); | ||
129 | mod_timer(&cnt32_to_63_keepwarm_timer, round_jiffies(jiffies + delta)); | ||
61 | } | 130 | } |
62 | 131 | ||
63 | /* Clockevent device: use one-shot mode */ | 132 | /* Clockevent device: use one-shot mode */ |
@@ -161,13 +230,15 @@ void __init nmdk_timer_init(void) | |||
161 | writel(0, mtu_base + MTU_BGLR(0)); | 230 | writel(0, mtu_base + MTU_BGLR(0)); |
162 | writel(cr | MTU_CRn_ENA, mtu_base + MTU_CR(0)); | 231 | writel(cr | MTU_CRn_ENA, mtu_base + MTU_CR(0)); |
163 | 232 | ||
164 | /* Now the scheduling clock is ready */ | 233 | /* Now the clock source is ready */ |
165 | nmdk_clksrc.read = nmdk_read_timer; | 234 | nmdk_clksrc.read = nmdk_read_timer; |
166 | 235 | ||
167 | if (clocksource_register(&nmdk_clksrc)) | 236 | if (clocksource_register(&nmdk_clksrc)) |
168 | pr_err("timer: failed to initialize clock source %s\n", | 237 | pr_err("timer: failed to initialize clock source %s\n", |
169 | nmdk_clksrc.name); | 238 | nmdk_clksrc.name); |
170 | 239 | ||
240 | nmdk_sched_clock_init(rate); | ||
241 | |||
171 | /* Timer 1 is used for events */ | 242 | /* Timer 1 is used for events */ |
172 | 243 | ||
173 | clockevents_calc_mult_shift(&nmdk_clkevt, rate, MTU_MIN_RANGE); | 244 | clockevents_calc_mult_shift(&nmdk_clkevt, rate, MTU_MIN_RANGE); |