diff options
Diffstat (limited to 'arch/x86/kernel/mfgpt_32.c')
-rw-r--r-- | arch/x86/kernel/mfgpt_32.c | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/arch/x86/kernel/mfgpt_32.c b/arch/x86/kernel/mfgpt_32.c new file mode 100644 index 000000000000..0ab680f2d9db --- /dev/null +++ b/arch/x86/kernel/mfgpt_32.c | |||
@@ -0,0 +1,362 @@ | |||
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 | |||
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 | |||
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 | |||
91 | /* set up clock event device, if desired */ | ||
92 | i = mfgpt_timer_setup(); | ||
93 | |||
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 | |||
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 | ||