diff options
author | Manuel Lauss <mano@roarinelk.homelinux.net> | 2008-12-21 03:26:23 -0500 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2009-01-11 04:57:27 -0500 |
commit | 0c694de12b54fa96b9555e07603f567906ce21c8 (patch) | |
tree | c7528273c1d86069cb6e83bd2b36706f663f1eb2 /arch/mips/alchemy/common/time.c | |
parent | 779e7d41ad004946603da139da99ba775f74cb1c (diff) |
MIPS: Alchemy: RTC counter clocksource / clockevent support.
Add support for the 32 kHz counter1 (RTC) as clocksource / clockevent
device. As a nice side effect, this also enables use of the 'wait'
instruction for runtime idle power savings.
If the counters aren't enabled/working properly, fall back on the
cp0 counter clock code.
Signed-off-by: Manuel Lauss <mano@roarinelk.homelinux.net>
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'arch/mips/alchemy/common/time.c')
-rw-r--r-- | arch/mips/alchemy/common/time.c | 292 |
1 files changed, 123 insertions, 169 deletions
diff --git a/arch/mips/alchemy/common/time.c b/arch/mips/alchemy/common/time.c index 15185708ad8c..57f0aec590b8 100644 --- a/arch/mips/alchemy/common/time.c +++ b/arch/mips/alchemy/common/time.c | |||
@@ -1,5 +1,7 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright (C) 2008 Manuel Lauss <mano@roarinelk.homelinux.net> | ||
2 | * | 3 | * |
4 | * Previous incarnations were: | ||
3 | * Copyright (C) 2001, 2006, 2008 MontaVista Software, <source@mvista.com> | 5 | * Copyright (C) 2001, 2006, 2008 MontaVista Software, <source@mvista.com> |
4 | * Copied and modified Carsten Langgaard's time.c | 6 | * Copied and modified Carsten Langgaard's time.c |
5 | * | 7 | * |
@@ -23,131 +25,27 @@ | |||
23 | * | 25 | * |
24 | * ######################################################################## | 26 | * ######################################################################## |
25 | * | 27 | * |
26 | * Setting up the clock on the MIPS boards. | 28 | * Clocksource/event using the 32.768kHz-clocked Counter1 ('RTC' in the |
27 | * | 29 | * databooks). Firmware/Board init code must enable the counters in the |
28 | * We provide the clock interrupt processing and the timer offset compute | 30 | * counter control register, otherwise the CP0 counter clocksource/event |
29 | * functions. If CONFIG_PM is selected, we also ensure the 32KHz timer is | 31 | * will be installed instead (and use of 'wait' instruction is prohibited). |
30 | * available. -- Dan | ||
31 | */ | 32 | */ |
32 | 33 | ||
33 | #include <linux/types.h> | 34 | #include <linux/clockchips.h> |
34 | #include <linux/init.h> | 35 | #include <linux/clocksource.h> |
36 | #include <linux/interrupt.h> | ||
35 | #include <linux/spinlock.h> | 37 | #include <linux/spinlock.h> |
36 | 38 | ||
37 | #include <asm/mipsregs.h> | ||
38 | #include <asm/time.h> | 39 | #include <asm/time.h> |
39 | #include <asm/mach-au1x00/au1000.h> | 40 | #include <asm/mach-au1x00/au1000.h> |
40 | 41 | ||
41 | static int no_au1xxx_32khz; | 42 | /* 32kHz clock enabled and detected */ |
42 | extern int allow_au1k_wait; /* default off for CP0 Counter */ | 43 | #define CNTR_OK (SYS_CNTRL_E0 | SYS_CNTRL_32S) |
43 | 44 | ||
44 | #ifdef CONFIG_PM | 45 | extern int allow_au1k_wait; /* default off for CP0 Counter */ |
45 | #if HZ < 100 || HZ > 1000 | ||
46 | #error "unsupported HZ value! Must be in [100,1000]" | ||
47 | #endif | ||
48 | #define MATCH20_INC (328 * 100 / HZ) /* magic number 328 is for HZ=100... */ | ||
49 | static unsigned long last_pc0, last_match20; | ||
50 | #endif | ||
51 | 46 | ||
52 | static DEFINE_SPINLOCK(time_lock); | 47 | static DEFINE_SPINLOCK(time_lock); |
53 | 48 | ||
54 | unsigned long wtimer; | ||
55 | |||
56 | #ifdef CONFIG_PM | ||
57 | static irqreturn_t counter0_irq(int irq, void *dev_id) | ||
58 | { | ||
59 | unsigned long pc0; | ||
60 | int time_elapsed; | ||
61 | static int jiffie_drift; | ||
62 | |||
63 | if (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_M20) { | ||
64 | /* should never happen! */ | ||
65 | printk(KERN_WARNING "counter 0 w status error\n"); | ||
66 | return IRQ_NONE; | ||
67 | } | ||
68 | |||
69 | pc0 = au_readl(SYS_TOYREAD); | ||
70 | if (pc0 < last_match20) | ||
71 | /* counter overflowed */ | ||
72 | time_elapsed = (0xffffffff - last_match20) + pc0; | ||
73 | else | ||
74 | time_elapsed = pc0 - last_match20; | ||
75 | |||
76 | while (time_elapsed > 0) { | ||
77 | do_timer(1); | ||
78 | #ifndef CONFIG_SMP | ||
79 | update_process_times(user_mode(get_irq_regs())); | ||
80 | #endif | ||
81 | time_elapsed -= MATCH20_INC; | ||
82 | last_match20 += MATCH20_INC; | ||
83 | jiffie_drift++; | ||
84 | } | ||
85 | |||
86 | last_pc0 = pc0; | ||
87 | au_writel(last_match20 + MATCH20_INC, SYS_TOYMATCH2); | ||
88 | au_sync(); | ||
89 | |||
90 | /* | ||
91 | * Our counter ticks at 10.009765625 ms/tick, we we're running | ||
92 | * almost 10 uS too slow per tick. | ||
93 | */ | ||
94 | |||
95 | if (jiffie_drift >= 999) { | ||
96 | jiffie_drift -= 999; | ||
97 | do_timer(1); /* increment jiffies by one */ | ||
98 | #ifndef CONFIG_SMP | ||
99 | update_process_times(user_mode(get_irq_regs())); | ||
100 | #endif | ||
101 | } | ||
102 | |||
103 | return IRQ_HANDLED; | ||
104 | } | ||
105 | |||
106 | struct irqaction counter0_action = { | ||
107 | .handler = counter0_irq, | ||
108 | .flags = IRQF_DISABLED, | ||
109 | .name = "alchemy-toy", | ||
110 | .dev_id = NULL, | ||
111 | }; | ||
112 | |||
113 | /* When we wakeup from sleep, we have to "catch up" on all of the | ||
114 | * timer ticks we have missed. | ||
115 | */ | ||
116 | void wakeup_counter0_adjust(void) | ||
117 | { | ||
118 | unsigned long pc0; | ||
119 | int time_elapsed; | ||
120 | |||
121 | pc0 = au_readl(SYS_TOYREAD); | ||
122 | if (pc0 < last_match20) | ||
123 | /* counter overflowed */ | ||
124 | time_elapsed = (0xffffffff - last_match20) + pc0; | ||
125 | else | ||
126 | time_elapsed = pc0 - last_match20; | ||
127 | |||
128 | while (time_elapsed > 0) { | ||
129 | time_elapsed -= MATCH20_INC; | ||
130 | last_match20 += MATCH20_INC; | ||
131 | } | ||
132 | |||
133 | last_pc0 = pc0; | ||
134 | au_writel(last_match20 + MATCH20_INC, SYS_TOYMATCH2); | ||
135 | au_sync(); | ||
136 | |||
137 | } | ||
138 | |||
139 | /* This is just for debugging to set the timer for a sleep delay. */ | ||
140 | void wakeup_counter0_set(int ticks) | ||
141 | { | ||
142 | unsigned long pc0; | ||
143 | |||
144 | pc0 = au_readl(SYS_TOYREAD); | ||
145 | last_pc0 = pc0; | ||
146 | au_writel(last_match20 + (MATCH20_INC * ticks), SYS_TOYMATCH2); | ||
147 | au_sync(); | ||
148 | } | ||
149 | #endif | ||
150 | |||
151 | /* | 49 | /* |
152 | * I haven't found anyone that doesn't use a 12 MHz source clock, | 50 | * I haven't found anyone that doesn't use a 12 MHz source clock, |
153 | * but just in case..... | 51 | * but just in case..... |
@@ -162,37 +60,15 @@ void wakeup_counter0_set(int ticks) | |||
162 | * this advertised speed will introduce error and sometimes not work | 60 | * this advertised speed will introduce error and sometimes not work |
163 | * properly. This function is futher convoluted to still allow configurations | 61 | * properly. This function is futher convoluted to still allow configurations |
164 | * to do that in case they have really, really old silicon with a | 62 | * to do that in case they have really, really old silicon with a |
165 | * write-only PLL register, that we need the 32 KHz when power management | 63 | * write-only PLL register. -- Dan |
166 | * "wait" is enabled, and we need to detect if the 32 KHz isn't present | ||
167 | * but requested......got it? :-) -- Dan | ||
168 | */ | 64 | */ |
169 | unsigned long calc_clock(void) | 65 | unsigned long calc_clock(void) |
170 | { | 66 | { |
171 | unsigned long cpu_speed; | 67 | unsigned long cpu_speed; |
172 | unsigned long flags; | 68 | unsigned long flags; |
173 | unsigned long counter; | ||
174 | 69 | ||
175 | spin_lock_irqsave(&time_lock, flags); | 70 | spin_lock_irqsave(&time_lock, flags); |
176 | 71 | ||
177 | /* Power management cares if we don't have a 32 KHz counter. */ | ||
178 | no_au1xxx_32khz = 0; | ||
179 | counter = au_readl(SYS_COUNTER_CNTRL); | ||
180 | if (counter & SYS_CNTRL_E0) { | ||
181 | int trim_divide = 16; | ||
182 | |||
183 | au_writel(counter | SYS_CNTRL_EN1, SYS_COUNTER_CNTRL); | ||
184 | |||
185 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_T1S); | ||
186 | /* RTC now ticks at 32.768/16 kHz */ | ||
187 | au_writel(trim_divide - 1, SYS_RTCTRIM); | ||
188 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_T1S); | ||
189 | |||
190 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_C1S); | ||
191 | au_writel(0, SYS_TOYWRITE); | ||
192 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_C1S); | ||
193 | } else | ||
194 | no_au1xxx_32khz = 1; | ||
195 | |||
196 | /* | 72 | /* |
197 | * On early Au1000, sys_cpupll was write-only. Since these | 73 | * On early Au1000, sys_cpupll was write-only. Since these |
198 | * silicon versions of Au1000 are not sold by AMD, we don't bend | 74 | * silicon versions of Au1000 are not sold by AMD, we don't bend |
@@ -215,8 +91,65 @@ unsigned long calc_clock(void) | |||
215 | return cpu_speed; | 91 | return cpu_speed; |
216 | } | 92 | } |
217 | 93 | ||
94 | static cycle_t au1x_counter1_read(void) | ||
95 | { | ||
96 | return au_readl(SYS_RTCREAD); | ||
97 | } | ||
98 | |||
99 | static struct clocksource au1x_counter1_clocksource = { | ||
100 | .name = "alchemy-counter1", | ||
101 | .read = au1x_counter1_read, | ||
102 | .mask = CLOCKSOURCE_MASK(32), | ||
103 | .flags = CLOCK_SOURCE_IS_CONTINUOUS, | ||
104 | .rating = 100, | ||
105 | }; | ||
106 | |||
107 | static int au1x_rtcmatch2_set_next_event(unsigned long delta, | ||
108 | struct clock_event_device *cd) | ||
109 | { | ||
110 | delta += au_readl(SYS_RTCREAD); | ||
111 | /* wait for register access */ | ||
112 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_M21) | ||
113 | ; | ||
114 | au_writel(delta, SYS_RTCMATCH2); | ||
115 | au_sync(); | ||
116 | |||
117 | return 0; | ||
118 | } | ||
119 | |||
120 | static void au1x_rtcmatch2_set_mode(enum clock_event_mode mode, | ||
121 | struct clock_event_device *cd) | ||
122 | { | ||
123 | } | ||
124 | |||
125 | static irqreturn_t au1x_rtcmatch2_irq(int irq, void *dev_id) | ||
126 | { | ||
127 | struct clock_event_device *cd = dev_id; | ||
128 | cd->event_handler(cd); | ||
129 | return IRQ_HANDLED; | ||
130 | } | ||
131 | |||
132 | static struct clock_event_device au1x_rtcmatch2_clockdev = { | ||
133 | .name = "rtcmatch2", | ||
134 | .features = CLOCK_EVT_FEAT_ONESHOT, | ||
135 | .rating = 100, | ||
136 | .irq = AU1000_RTC_MATCH2_INT, | ||
137 | .set_next_event = au1x_rtcmatch2_set_next_event, | ||
138 | .set_mode = au1x_rtcmatch2_set_mode, | ||
139 | .cpumask = CPU_MASK_ALL, | ||
140 | }; | ||
141 | |||
142 | static struct irqaction au1x_rtcmatch2_irqaction = { | ||
143 | .handler = au1x_rtcmatch2_irq, | ||
144 | .flags = IRQF_DISABLED | IRQF_TIMER, | ||
145 | .name = "timer", | ||
146 | .dev_id = &au1x_rtcmatch2_clockdev, | ||
147 | }; | ||
148 | |||
218 | void __init plat_time_init(void) | 149 | void __init plat_time_init(void) |
219 | { | 150 | { |
151 | struct clock_event_device *cd = &au1x_rtcmatch2_clockdev; | ||
152 | unsigned long t; | ||
220 | unsigned int est_freq = calc_clock(); | 153 | unsigned int est_freq = calc_clock(); |
221 | 154 | ||
222 | est_freq += 5000; /* round */ | 155 | est_freq += 5000; /* round */ |
@@ -225,41 +158,62 @@ void __init plat_time_init(void) | |||
225 | est_freq / 1000000, ((est_freq % 1000000) * 100) / 1000000); | 158 | est_freq / 1000000, ((est_freq % 1000000) * 100) / 1000000); |
226 | set_au1x00_speed(est_freq); | 159 | set_au1x00_speed(est_freq); |
227 | 160 | ||
228 | #ifdef CONFIG_PM | 161 | /* Check if firmware (YAMON, ...) has enabled 32kHz and clock |
229 | /* | 162 | * has been detected. If so install the rtcmatch2 clocksource, |
230 | * setup counter 0, since it keeps ticking after a | 163 | * otherwise don't bother. Note that both bits being set is by |
231 | * 'wait' instruction has been executed. The CP0 timer and | 164 | * no means a definite guarantee that the counters actually work |
232 | * counter 1 do NOT continue running after 'wait' | 165 | * (the 32S bit seems to be stuck set to 1 once a single clock- |
233 | * | 166 | * edge is detected, hence the timeouts). |
234 | * It's too early to call request_irq() here, so we handle | ||
235 | * counter 0 interrupt as a special irq and it doesn't show | ||
236 | * up under /proc/interrupts. | ||
237 | * | ||
238 | * Check to ensure we really have a 32 KHz oscillator before | ||
239 | * we do this. | ||
240 | */ | 167 | */ |
241 | if (no_au1xxx_32khz) | 168 | if (CNTR_OK != (au_readl(SYS_COUNTER_CNTRL) & CNTR_OK)) |
242 | printk(KERN_WARNING "WARNING: no 32KHz clock found.\n"); | 169 | goto cntr_err; |
243 | else { | ||
244 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_C0S); | ||
245 | au_writel(0, SYS_TOYWRITE); | ||
246 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_C0S); | ||
247 | 170 | ||
248 | au_writel(au_readl(SYS_WAKEMSK) | (1 << 8), SYS_WAKEMSK); | 171 | /* |
249 | au_writel(~0, SYS_WAKESRC); | 172 | * setup counter 1 (RTC) to tick at full speed |
250 | au_sync(); | 173 | */ |
251 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_M20); | 174 | t = 0xffffff; |
175 | while ((au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_T1S) && t--) | ||
176 | asm volatile ("nop"); | ||
177 | if (!t) | ||
178 | goto cntr_err; | ||
252 | 179 | ||
253 | /* Setup match20 to interrupt once every HZ */ | 180 | au_writel(0, SYS_RTCTRIM); /* 32.768 kHz */ |
254 | last_pc0 = last_match20 = au_readl(SYS_TOYREAD); | 181 | au_sync(); |
255 | au_writel(last_match20 + MATCH20_INC, SYS_TOYMATCH2); | ||
256 | au_sync(); | ||
257 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_M20); | ||
258 | setup_irq(AU1000_TOY_MATCH2_INT, &counter0_action); | ||
259 | 182 | ||
260 | /* We can use the real 'wait' instruction. */ | 183 | t = 0xffffff; |
261 | allow_au1k_wait = 1; | 184 | while ((au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_C1S) && t--) |
262 | } | 185 | asm volatile ("nop"); |
186 | if (!t) | ||
187 | goto cntr_err; | ||
188 | au_writel(0, SYS_RTCWRITE); | ||
189 | au_sync(); | ||
263 | 190 | ||
264 | #endif | 191 | t = 0xffffff; |
192 | while ((au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_C1S) && t--) | ||
193 | asm volatile ("nop"); | ||
194 | if (!t) | ||
195 | goto cntr_err; | ||
196 | |||
197 | /* register counter1 clocksource and event device */ | ||
198 | clocksource_set_clock(&au1x_counter1_clocksource, 32768); | ||
199 | clocksource_register(&au1x_counter1_clocksource); | ||
200 | |||
201 | cd->shift = 32; | ||
202 | cd->mult = div_sc(32768, NSEC_PER_SEC, cd->shift); | ||
203 | cd->max_delta_ns = clockevent_delta2ns(0xffffffff, cd); | ||
204 | cd->min_delta_ns = clockevent_delta2ns(8, cd); /* ~0.25ms */ | ||
205 | clockevents_register_device(cd); | ||
206 | setup_irq(AU1000_RTC_MATCH2_INT, &au1x_rtcmatch2_irqaction); | ||
207 | |||
208 | printk(KERN_INFO "Alchemy clocksource installed\n"); | ||
209 | |||
210 | /* can now use 'wait' */ | ||
211 | allow_au1k_wait = 1; | ||
212 | return; | ||
213 | |||
214 | cntr_err: | ||
215 | /* counters unusable, use C0 counter */ | ||
216 | r4k_clockevent_init(); | ||
217 | init_r4k_clocksource(); | ||
218 | allow_au1k_wait = 0; | ||
265 | } | 219 | } |