Commit | Line | Data |
---|---|---|
83d7384f AS |
1 | /* |
2 | * Driver/API for AMD Geode Multi-Function General Purpose Timers (MFGPT) | |
3 | * | |
4 | * Copyright (C) 2006, Advanced Micro Devices, Inc. | |
5 | * Copyright (C) 2007, Andres Salomon <dilinger@debian.org> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of version 2 of the GNU General Public License | |
9 | * as published by the Free Software Foundation. | |
10 | * | |
11 | * The MFGPTs are documented in AMD Geode CS5536 Companion Device Data Book. | |
12 | */ | |
13 | ||
14 | /* | |
15 | * We are using the 32Khz input clock - its the only one that has the | |
16 | * ranges we find desirable. The following table lists the suitable | |
17 | * divisors and the associated hz, minimum interval | |
18 | * and the maximum interval: | |
19 | * | |
20 | * Divisor Hz Min Delta (S) Max Delta (S) | |
21 | * 1 32000 .0005 2.048 | |
22 | * 2 16000 .001 4.096 | |
23 | * 4 8000 .002 8.192 | |
24 | * 8 4000 .004 16.384 | |
25 | * 16 2000 .008 32.768 | |
26 | * 32 1000 .016 65.536 | |
27 | * 64 500 .032 131.072 | |
28 | * 128 250 .064 262.144 | |
29 | * 256 125 .128 524.288 | |
30 | */ | |
31 | ||
32 | #include <linux/kernel.h> | |
33 | #include <linux/interrupt.h> | |
34 | #include <linux/module.h> | |
35 | #include <asm/geode.h> | |
36 | ||
37 | #define F_AVAIL 0x01 | |
38 | ||
39 | static struct mfgpt_timer_t { | |
40 | int flags; | |
41 | struct module *owner; | |
42 | } mfgpt_timers[MFGPT_MAX_TIMERS]; | |
43 | ||
44 | /* Selected from the table above */ | |
45 | ||
46 | #define MFGPT_DIVISOR 16 | |
47 | #define MFGPT_SCALE 4 /* divisor = 2^(scale) */ | |
48 | #define MFGPT_HZ (32000 / MFGPT_DIVISOR) | |
49 | #define MFGPT_PERIODIC (MFGPT_HZ / HZ) | |
50 | ||
8f36881b AS |
51 | #ifdef CONFIG_GEODE_MFGPT_TIMER |
52 | static int __init mfgpt_timer_setup(void); | |
53 | #else | |
54 | #define mfgpt_timer_setup() (0) | |
55 | #endif | |
56 | ||
83d7384f AS |
57 | /* Allow for disabling of MFGPTs */ |
58 | static int disable; | |
59 | static int __init mfgpt_disable(char *s) | |
60 | { | |
61 | disable = 1; | |
62 | return 1; | |
63 | } | |
64 | __setup("nomfgpt", mfgpt_disable); | |
65 | ||
66 | /* | |
67 | * Check whether any MFGPTs are available for the kernel to use. In most | |
68 | * cases, firmware that uses AMD's VSA code will claim all timers during | |
69 | * bootup; we certainly don't want to take them if they're already in use. | |
70 | * In other cases (such as with VSAless OpenFirmware), the system firmware | |
71 | * leaves timers available for us to use. | |
72 | */ | |
73 | int __init geode_mfgpt_detect(void) | |
74 | { | |
75 | int count = 0, i; | |
76 | u16 val; | |
77 | ||
78 | if (disable) { | |
79 | printk(KERN_INFO "geode-mfgpt: Skipping MFGPT setup\n"); | |
80 | return 0; | |
81 | } | |
82 | ||
83 | for (i = 0; i < MFGPT_MAX_TIMERS; i++) { | |
84 | val = geode_mfgpt_read(i, MFGPT_REG_SETUP); | |
85 | if (!(val & MFGPT_SETUP_SETUP)) { | |
86 | mfgpt_timers[i].flags = F_AVAIL; | |
87 | count++; | |
88 | } | |
89 | } | |
90 | ||
8f36881b AS |
91 | /* set up clock event device, if desired */ |
92 | i = mfgpt_timer_setup(); | |
93 | ||
83d7384f AS |
94 | return count; |
95 | } | |
96 | ||
97 | int geode_mfgpt_toggle_event(int timer, int cmp, int event, int enable) | |
98 | { | |
99 | u32 msr, mask, value, dummy; | |
100 | int shift = (cmp == MFGPT_CMP1) ? 0 : 8; | |
101 | ||
102 | if (timer < 0 || timer >= MFGPT_MAX_TIMERS) | |
103 | return -EIO; | |
104 | ||
105 | /* | |
106 | * The register maps for these are described in sections 6.17.1.x of | |
107 | * the AMD Geode CS5536 Companion Device Data Book. | |
108 | */ | |
109 | switch (event) { | |
110 | case MFGPT_EVENT_RESET: | |
111 | /* | |
112 | * XXX: According to the docs, we cannot reset timers above | |
113 | * 6; that is, resets for 7 and 8 will be ignored. Is this | |
114 | * a problem? -dilinger | |
115 | */ | |
116 | msr = MFGPT_NR_MSR; | |
117 | mask = 1 << (timer + 24); | |
118 | break; | |
119 | ||
120 | case MFGPT_EVENT_NMI: | |
121 | msr = MFGPT_NR_MSR; | |
122 | mask = 1 << (timer + shift); | |
123 | break; | |
124 | ||
125 | case MFGPT_EVENT_IRQ: | |
126 | msr = MFGPT_IRQ_MSR; | |
127 | mask = 1 << (timer + shift); | |
128 | break; | |
129 | ||
130 | default: | |
131 | return -EIO; | |
132 | } | |
133 | ||
134 | rdmsr(msr, value, dummy); | |
135 | ||
136 | if (enable) | |
137 | value |= mask; | |
138 | else | |
139 | value &= ~mask; | |
140 | ||
141 | wrmsr(msr, value, dummy); | |
142 | return 0; | |
143 | } | |
144 | ||
145 | int geode_mfgpt_set_irq(int timer, int cmp, int irq, int enable) | |
146 | { | |
147 | u32 val, dummy; | |
148 | int offset; | |
149 | ||
150 | if (timer < 0 || timer >= MFGPT_MAX_TIMERS) | |
151 | return -EIO; | |
152 | ||
153 | if (geode_mfgpt_toggle_event(timer, cmp, MFGPT_EVENT_IRQ, enable)) | |
154 | return -EIO; | |
155 | ||
156 | rdmsr(MSR_PIC_ZSEL_LOW, val, dummy); | |
157 | ||
158 | offset = (timer % 4) * 4; | |
159 | ||
160 | val &= ~((0xF << offset) | (0xF << (offset + 16))); | |
161 | ||
162 | if (enable) { | |
163 | val |= (irq & 0x0F) << (offset); | |
164 | val |= (irq & 0x0F) << (offset + 16); | |
165 | } | |
166 | ||
167 | wrmsr(MSR_PIC_ZSEL_LOW, val, dummy); | |
168 | return 0; | |
169 | } | |
170 | ||
171 | static int mfgpt_get(int timer, struct module *owner) | |
172 | { | |
173 | mfgpt_timers[timer].flags &= ~F_AVAIL; | |
174 | mfgpt_timers[timer].owner = owner; | |
175 | printk(KERN_INFO "geode-mfgpt: Registered timer %d\n", timer); | |
176 | return timer; | |
177 | } | |
178 | ||
179 | int geode_mfgpt_alloc_timer(int timer, int domain, struct module *owner) | |
180 | { | |
181 | int i; | |
182 | ||
183 | if (!geode_get_dev_base(GEODE_DEV_MFGPT)) | |
184 | return -ENODEV; | |
185 | if (timer >= MFGPT_MAX_TIMERS) | |
186 | return -EIO; | |
187 | ||
188 | if (timer < 0) { | |
189 | /* Try to find an available timer */ | |
190 | for (i = 0; i < MFGPT_MAX_TIMERS; i++) { | |
191 | if (mfgpt_timers[i].flags & F_AVAIL) | |
192 | return mfgpt_get(i, owner); | |
193 | ||
194 | if (i == 5 && domain == MFGPT_DOMAIN_WORKING) | |
195 | break; | |
196 | } | |
197 | } else { | |
198 | /* If they requested a specific timer, try to honor that */ | |
199 | if (mfgpt_timers[timer].flags & F_AVAIL) | |
200 | return mfgpt_get(timer, owner); | |
201 | } | |
202 | ||
203 | /* No timers available - too bad */ | |
204 | return -1; | |
205 | } | |
206 | ||
8f36881b AS |
207 | |
208 | #ifdef CONFIG_GEODE_MFGPT_TIMER | |
209 | ||
210 | /* | |
211 | * The MFPGT timers on the CS5536 provide us with suitable timers to use | |
212 | * as clock event sources - not as good as a HPET or APIC, but certainly | |
213 | * better then the PIT. This isn't a general purpose MFGPT driver, but | |
214 | * a simplified one designed specifically to act as a clock event source. | |
215 | * For full details about the MFGPT, please consult the CS5536 data sheet. | |
216 | */ | |
217 | ||
218 | #include <linux/clocksource.h> | |
219 | #include <linux/clockchips.h> | |
220 | ||
221 | static unsigned int mfgpt_tick_mode = CLOCK_EVT_MODE_SHUTDOWN; | |
222 | static u16 mfgpt_event_clock; | |
223 | ||
224 | static int irq = 7; | |
225 | static int __init mfgpt_setup(char *str) | |
226 | { | |
227 | get_option(&str, &irq); | |
228 | return 1; | |
229 | } | |
230 | __setup("mfgpt_irq=", mfgpt_setup); | |
231 | ||
232 | static inline void mfgpt_disable_timer(u16 clock) | |
233 | { | |
234 | u16 val = geode_mfgpt_read(clock, MFGPT_REG_SETUP); | |
235 | geode_mfgpt_write(clock, MFGPT_REG_SETUP, val & ~MFGPT_SETUP_CNTEN); | |
236 | } | |
237 | ||
238 | static int mfgpt_next_event(unsigned long, struct clock_event_device *); | |
239 | static void mfgpt_set_mode(enum clock_event_mode, struct clock_event_device *); | |
240 | ||
241 | static struct clock_event_device mfgpt_clockevent = { | |
242 | .name = "mfgpt-timer", | |
243 | .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, | |
244 | .set_mode = mfgpt_set_mode, | |
245 | .set_next_event = mfgpt_next_event, | |
246 | .rating = 250, | |
247 | .cpumask = CPU_MASK_ALL, | |
248 | .shift = 32 | |
249 | }; | |
250 | ||
251 | static inline void mfgpt_start_timer(u16 clock, u16 delta) | |
252 | { | |
253 | geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_CMP2, (u16) delta); | |
254 | geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_COUNTER, 0); | |
255 | ||
256 | geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP, | |
257 | MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2); | |
258 | } | |
259 | ||
260 | static void mfgpt_set_mode(enum clock_event_mode mode, | |
261 | struct clock_event_device *evt) | |
262 | { | |
263 | mfgpt_disable_timer(mfgpt_event_clock); | |
264 | ||
265 | if (mode == CLOCK_EVT_MODE_PERIODIC) | |
266 | mfgpt_start_timer(mfgpt_event_clock, MFGPT_PERIODIC); | |
267 | ||
268 | mfgpt_tick_mode = mode; | |
269 | } | |
270 | ||
271 | static int mfgpt_next_event(unsigned long delta, struct clock_event_device *evt) | |
272 | { | |
273 | mfgpt_start_timer(mfgpt_event_clock, delta); | |
274 | return 0; | |
275 | } | |
276 | ||
277 | /* Assume (foolishly?), that this interrupt was due to our tick */ | |
278 | ||
279 | static irqreturn_t mfgpt_tick(int irq, void *dev_id) | |
280 | { | |
281 | if (mfgpt_tick_mode == CLOCK_EVT_MODE_SHUTDOWN) | |
282 | return IRQ_HANDLED; | |
283 | ||
284 | /* Turn off the clock */ | |
285 | mfgpt_disable_timer(mfgpt_event_clock); | |
286 | ||
287 | /* Clear the counter */ | |
288 | geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_COUNTER, 0); | |
289 | ||
290 | /* Restart the clock in periodic mode */ | |
291 | ||
292 | if (mfgpt_tick_mode == CLOCK_EVT_MODE_PERIODIC) { | |
293 | geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP, | |
294 | MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2); | |
295 | } | |
296 | ||
297 | mfgpt_clockevent.event_handler(&mfgpt_clockevent); | |
298 | return IRQ_HANDLED; | |
299 | } | |
300 | ||
301 | static struct irqaction mfgptirq = { | |
302 | .handler = mfgpt_tick, | |
303 | .flags = IRQF_DISABLED | IRQF_NOBALANCING, | |
304 | .mask = CPU_MASK_NONE, | |
305 | .name = "mfgpt-timer" | |
306 | }; | |
307 | ||
308 | static int __init mfgpt_timer_setup(void) | |
309 | { | |
310 | int timer, ret; | |
311 | u16 val; | |
312 | ||
313 | timer = geode_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING, | |
314 | THIS_MODULE); | |
315 | if (timer < 0) { | |
316 | printk(KERN_ERR | |
317 | "mfgpt-timer: Could not allocate a MFPGT timer\n"); | |
318 | return -ENODEV; | |
319 | } | |
320 | ||
321 | mfgpt_event_clock = timer; | |
322 | /* Set the clock scale and enable the event mode for CMP2 */ | |
323 | val = MFGPT_SCALE | (3 << 8); | |
324 | ||
325 | geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP, val); | |
326 | ||
327 | /* Set up the IRQ on the MFGPT side */ | |
328 | if (geode_mfgpt_setup_irq(mfgpt_event_clock, MFGPT_CMP2, irq)) { | |
329 | printk(KERN_ERR "mfgpt-timer: Could not set up IRQ %d\n", irq); | |
330 | return -EIO; | |
331 | } | |
332 | ||
333 | /* And register it with the kernel */ | |
334 | ret = setup_irq(irq, &mfgptirq); | |
335 | ||
336 | if (ret) { | |
337 | printk(KERN_ERR | |
338 | "mfgpt-timer: Unable to set up the interrupt.\n"); | |
339 | goto err; | |
340 | } | |
341 | ||
342 | /* Set up the clock event */ | |
343 | mfgpt_clockevent.mult = div_sc(MFGPT_HZ, NSEC_PER_SEC, 32); | |
344 | mfgpt_clockevent.min_delta_ns = clockevent_delta2ns(0xF, | |
345 | &mfgpt_clockevent); | |
346 | mfgpt_clockevent.max_delta_ns = clockevent_delta2ns(0xFFFE, | |
347 | &mfgpt_clockevent); | |
348 | ||
349 | printk(KERN_INFO | |
350 | "mfgpt-timer: registering the MFGT timer as a clock event.\n"); | |
351 | clockevents_register_device(&mfgpt_clockevent); | |
352 | ||
353 | return 0; | |
354 | ||
355 | err: | |
356 | geode_mfgpt_release_irq(mfgpt_event_clock, MFGPT_CMP2, irq); | |
357 | printk(KERN_ERR | |
358 | "mfgpt-timer: Unable to set up the MFGPT clock source\n"); | |
359 | return -EIO; | |
360 | } | |
361 | ||
362 | #endif |