aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86/kernel/mfgpt_32.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/x86/kernel/mfgpt_32.c')
-rw-r--r--arch/x86/kernel/mfgpt_32.c165
1 files changed, 165 insertions, 0 deletions
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
52static 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 */
52static int disable; 58static int disable;
53static int __init mfgpt_disable(char *s) 59static 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
221static unsigned int mfgpt_tick_mode = CLOCK_EVT_MODE_SHUTDOWN;
222static u16 mfgpt_event_clock;
223
224static int irq = 7;
225static int __init mfgpt_setup(char *str)
226{
227 get_option(&str, &irq);
228 return 1;
229}
230__setup("mfgpt_irq=", mfgpt_setup);
231
232static 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
238static int mfgpt_next_event(unsigned long, struct clock_event_device *);
239static void mfgpt_set_mode(enum clock_event_mode, struct clock_event_device *);
240
241static 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
251static 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
260static 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
271static 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
279static 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
301static struct irqaction mfgptirq = {
302 .handler = mfgpt_tick,
303 .flags = IRQF_DISABLED | IRQF_NOBALANCING,
304 .mask = CPU_MASK_NONE,
305 .name = "mfgpt-timer"
306};
307
308static 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
355err:
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