diff options
author | Andres Salomon <dilinger@queued.net> | 2007-10-12 17:04:06 -0400 |
---|---|---|
committer | Thomas Gleixner <tglx@inhelltoy.tec.linutronix.de> | 2007-10-12 17:04:06 -0400 |
commit | 8f36881b3c972ebc019195692849f22488f9c0a3 (patch) | |
tree | dd14aeb1fdb6f516f154a276d21f82c58d5e35b3 | |
parent | 83d7384f8d4aa216b49cf9cb286ea743054b119f (diff) |
x86: Geode MFGPT clock event device support
Add support for an MFGPT clock event device; this allows us to use MFGPTs as
the basis for high-resolution timers.
Signed-off-by: Jordan Crouse <jordan.crouse@amd.com>
Signed-off-by: Andres Salomon <dilinger@debian.org>
Cc: Andi Kleen <ak@suse.de>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: john stultz <johnstul@us.ibm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Arjan van de Ven <arjan@linux.intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
-rw-r--r-- | Documentation/kernel-parameters.txt | 4 | ||||
-rw-r--r-- | arch/i386/Kconfig | 10 | ||||
-rw-r--r-- | arch/x86/kernel/mfgpt_32.c | 165 |
3 files changed, 179 insertions, 0 deletions
diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 2128de6d80ea..f27cdd7125f2 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt | |||
@@ -1009,6 +1009,10 @@ and is between 256 and 4096 characters. It is defined in the file | |||
1009 | meye.*= [HW] Set MotionEye Camera parameters | 1009 | meye.*= [HW] Set MotionEye Camera parameters |
1010 | See Documentation/video4linux/meye.txt. | 1010 | See Documentation/video4linux/meye.txt. |
1011 | 1011 | ||
1012 | mfgpt_irq= [IA-32] Specify the IRQ to use for the | ||
1013 | Multi-Function General Purpose Timers on AMD Geode | ||
1014 | platforms. | ||
1015 | |||
1012 | mga= [HW,DRM] | 1016 | mga= [HW,DRM] |
1013 | 1017 | ||
1014 | mousedev.tap_time= | 1018 | mousedev.tap_time= |
diff --git a/arch/i386/Kconfig b/arch/i386/Kconfig index 2d85e4b87307..6bbbc2755e44 100644 --- a/arch/i386/Kconfig +++ b/arch/i386/Kconfig | |||
@@ -1206,6 +1206,16 @@ config SCx200HR_TIMER | |||
1206 | processor goes idle (as is done by the scheduler). The | 1206 | processor goes idle (as is done by the scheduler). The |
1207 | other workaround is idle=poll boot option. | 1207 | other workaround is idle=poll boot option. |
1208 | 1208 | ||
1209 | config GEODE_MFGPT_TIMER | ||
1210 | bool "Geode Multi-Function General Purpose Timer (MFGPT) events" | ||
1211 | depends on MGEODE_LX && GENERIC_TIME && GENERIC_CLOCKEVENTS | ||
1212 | default y | ||
1213 | help | ||
1214 | This driver provides a clock event source based on the MFGPT | ||
1215 | timer(s) in the CS5535 and CS5536 companion chip for the geode. | ||
1216 | MFGPTs have a better resolution and max interval than the | ||
1217 | generic PIT, and are suitable for use as high-res timers. | ||
1218 | |||
1209 | config K8_NB | 1219 | config K8_NB |
1210 | def_bool y | 1220 | def_bool y |
1211 | depends on AGP_AMD64 | 1221 | depends on AGP_AMD64 |
diff --git a/arch/x86/kernel/mfgpt_32.c b/arch/x86/kernel/mfgpt_32.c index 3a63a2dd8d27..0ab680f2d9db 100644 --- a/arch/x86/kernel/mfgpt_32.c +++ b/arch/x86/kernel/mfgpt_32.c | |||
@@ -48,6 +48,12 @@ static struct mfgpt_timer_t { | |||
48 | #define MFGPT_HZ (32000 / MFGPT_DIVISOR) | 48 | #define MFGPT_HZ (32000 / MFGPT_DIVISOR) |
49 | #define MFGPT_PERIODIC (MFGPT_HZ / HZ) | 49 | #define MFGPT_PERIODIC (MFGPT_HZ / HZ) |
50 | 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 | |||
51 | /* Allow for disabling of MFGPTs */ | 57 | /* Allow for disabling of MFGPTs */ |
52 | static int disable; | 58 | static int disable; |
53 | static int __init mfgpt_disable(char *s) | 59 | static int __init mfgpt_disable(char *s) |
@@ -82,6 +88,9 @@ int __init geode_mfgpt_detect(void) | |||
82 | } | 88 | } |
83 | } | 89 | } |
84 | 90 | ||
91 | /* set up clock event device, if desired */ | ||
92 | i = mfgpt_timer_setup(); | ||
93 | |||
85 | return count; | 94 | return count; |
86 | } | 95 | } |
87 | 96 | ||
@@ -195,3 +204,159 @@ int geode_mfgpt_alloc_timer(int timer, int domain, struct module *owner) | |||
195 | return -1; | 204 | return -1; |
196 | } | 205 | } |
197 | 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 | ||