linux/arch/x86/kernel/mfgpt_32.c
<<
>>
Prefs
   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 32.768kHz input clock - it's 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 and the maximum interval:
  18 *
  19 *  Divisor   Hz      Min Delta (s)  Max Delta (s)
  20 *   1        32768   .00048828125      2.000
  21 *   2        16384   .0009765625       4.000
  22 *   4         8192   .001953125        8.000
  23 *   8         4096   .00390625        16.000
  24 *   16        2048   .0078125         32.000
  25 *   32        1024   .015625          64.000
  26 *   64         512   .03125          128.000
  27 *  128         256   .0625           256.000
  28 *  256         128   .125            512.000
  29 */
  30
  31#include <linux/kernel.h>
  32#include <linux/interrupt.h>
  33#include <linux/module.h>
  34#include <asm/geode.h>
  35
  36#define MFGPT_DEFAULT_IRQ       7
  37
  38static struct mfgpt_timer_t {
  39        unsigned int avail:1;
  40} mfgpt_timers[MFGPT_MAX_TIMERS];
  41
  42/* Selected from the table above */
  43
  44#define MFGPT_DIVISOR 16
  45#define MFGPT_SCALE  4     /* divisor = 2^(scale) */
  46#define MFGPT_HZ  (32768 / MFGPT_DIVISOR)
  47#define MFGPT_PERIODIC (MFGPT_HZ / HZ)
  48
  49/* Allow for disabling of MFGPTs */
  50static int disable;
  51static int __init mfgpt_disable(char *s)
  52{
  53        disable = 1;
  54        return 1;
  55}
  56__setup("nomfgpt", mfgpt_disable);
  57
  58/* Reset the MFGPT timers. This is required by some broken BIOSes which already
  59 * do the same and leave the system in an unstable state. TinyBIOS 0.98 is
  60 * affected at least (0.99 is OK with MFGPT workaround left to off).
  61 */
  62static int __init mfgpt_fix(char *s)
  63{
  64        u32 val, dummy;
  65
  66        /* The following udocumented bit resets the MFGPT timers */
  67        val = 0xFF; dummy = 0;
  68        wrmsr(MSR_MFGPT_SETUP, val, dummy);
  69        return 1;
  70}
  71__setup("mfgptfix", mfgpt_fix);
  72
  73/*
  74 * Check whether any MFGPTs are available for the kernel to use.  In most
  75 * cases, firmware that uses AMD's VSA code will claim all timers during
  76 * bootup; we certainly don't want to take them if they're already in use.
  77 * In other cases (such as with VSAless OpenFirmware), the system firmware
  78 * leaves timers available for us to use.
  79 */
  80
  81
  82static int timers = -1;
  83
  84static void geode_mfgpt_detect(void)
  85{
  86        int i;
  87        u16 val;
  88
  89        timers = 0;
  90
  91        if (disable) {
  92                printk(KERN_INFO "geode-mfgpt:  MFGPT support is disabled\n");
  93                goto done;
  94        }
  95
  96        if (!geode_get_dev_base(GEODE_DEV_MFGPT)) {
  97                printk(KERN_INFO "geode-mfgpt:  MFGPT LBAR is not set up\n");
  98                goto done;
  99        }
 100
 101        for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
 102                val = geode_mfgpt_read(i, MFGPT_REG_SETUP);
 103                if (!(val & MFGPT_SETUP_SETUP)) {
 104                        mfgpt_timers[i].avail = 1;
 105                        timers++;
 106                }
 107        }
 108
 109done:
 110        printk(KERN_INFO "geode-mfgpt:  %d MFGPT timers available.\n", timers);
 111}
 112
 113int geode_mfgpt_toggle_event(int timer, int cmp, int event, int enable)
 114{
 115        u32 msr, mask, value, dummy;
 116        int shift = (cmp == MFGPT_CMP1) ? 0 : 8;
 117
 118        if (timer < 0 || timer >= MFGPT_MAX_TIMERS)
 119                return -EIO;
 120
 121        /*
 122         * The register maps for these are described in sections 6.17.1.x of
 123         * the AMD Geode CS5536 Companion Device Data Book.
 124         */
 125        switch (event) {
 126        case MFGPT_EVENT_RESET:
 127                /*
 128                 * XXX: According to the docs, we cannot reset timers above
 129                 * 6; that is, resets for 7 and 8 will be ignored.  Is this
 130                 * a problem?   -dilinger
 131                 */
 132                msr = MSR_MFGPT_NR;
 133                mask = 1 << (timer + 24);
 134                break;
 135
 136        case MFGPT_EVENT_NMI:
 137                msr = MSR_MFGPT_NR;
 138                mask = 1 << (timer + shift);
 139                break;
 140
 141        case MFGPT_EVENT_IRQ:
 142                msr = MSR_MFGPT_IRQ;
 143                mask = 1 << (timer + shift);
 144                break;
 145
 146        default:
 147                return -EIO;
 148        }
 149
 150        rdmsr(msr, value, dummy);
 151
 152        if (enable)
 153                value |= mask;
 154        else
 155                value &= ~mask;
 156
 157        wrmsr(msr, value, dummy);
 158        return 0;
 159}
 160EXPORT_SYMBOL_GPL(geode_mfgpt_toggle_event);
 161
 162int geode_mfgpt_set_irq(int timer, int cmp, int *irq, int enable)
 163{
 164        u32 zsel, lpc, dummy;
 165        int shift;
 166
 167        if (timer < 0 || timer >= MFGPT_MAX_TIMERS)
 168                return -EIO;
 169
 170        /*
 171         * Unfortunately, MFGPTs come in pairs sharing their IRQ lines. If VSA
 172         * is using the same CMP of the timer's Siamese twin, the IRQ is set to
 173         * 2, and we mustn't use nor change it.
 174         * XXX: Likewise, 2 Linux drivers might clash if the 2nd overwrites the
 175         * IRQ of the 1st. This can only happen if forcing an IRQ, calling this
 176         * with *irq==0 is safe. Currently there _are_ no 2 drivers.
 177         */
 178        rdmsr(MSR_PIC_ZSEL_LOW, zsel, dummy);
 179        shift = ((cmp == MFGPT_CMP1 ? 0 : 4) + timer % 4) * 4;
 180        if (((zsel >> shift) & 0xF) == 2)
 181                return -EIO;
 182
 183        /* Choose IRQ: if none supplied, keep IRQ already set or use default */
 184        if (!*irq)
 185                *irq = (zsel >> shift) & 0xF;
 186        if (!*irq)
 187                *irq = MFGPT_DEFAULT_IRQ;
 188
 189        /* Can't use IRQ if it's 0 (=disabled), 2, or routed to LPC */
 190        if (*irq < 1 || *irq == 2 || *irq > 15)
 191                return -EIO;
 192        rdmsr(MSR_PIC_IRQM_LPC, lpc, dummy);
 193        if (lpc & (1 << *irq))
 194                return -EIO;
 195
 196        /* All chosen and checked - go for it */
 197        if (geode_mfgpt_toggle_event(timer, cmp, MFGPT_EVENT_IRQ, enable))
 198                return -EIO;
 199        if (enable) {
 200                zsel = (zsel & ~(0xF << shift)) | (*irq << shift);
 201                wrmsr(MSR_PIC_ZSEL_LOW, zsel, dummy);
 202        }
 203
 204        return 0;
 205}
 206
 207static int mfgpt_get(int timer)
 208{
 209        mfgpt_timers[timer].avail = 0;
 210        printk(KERN_INFO "geode-mfgpt:  Registered timer %d\n", timer);
 211        return timer;
 212}
 213
 214int geode_mfgpt_alloc_timer(int timer, int domain)
 215{
 216        int i;
 217
 218        if (timers == -1) {
 219                /* timers haven't been detected yet */
 220                geode_mfgpt_detect();
 221        }
 222
 223        if (!timers)
 224                return -1;
 225
 226        if (timer >= MFGPT_MAX_TIMERS)
 227                return -1;
 228
 229        if (timer < 0) {
 230                /* Try to find an available timer */
 231                for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
 232                        if (mfgpt_timers[i].avail)
 233                                return mfgpt_get(i);
 234
 235                        if (i == 5 && domain == MFGPT_DOMAIN_WORKING)
 236                                break;
 237                }
 238        } else {
 239                /* If they requested a specific timer, try to honor that */
 240                if (mfgpt_timers[timer].avail)
 241                        return mfgpt_get(timer);
 242        }
 243
 244        /* No timers available - too bad */
 245        return -1;
 246}
 247EXPORT_SYMBOL_GPL(geode_mfgpt_alloc_timer);
 248
 249
 250#ifdef CONFIG_GEODE_MFGPT_TIMER
 251
 252/*
 253 * The MFPGT timers on the CS5536 provide us with suitable timers to use
 254 * as clock event sources - not as good as a HPET or APIC, but certainly
 255 * better then the PIT.  This isn't a general purpose MFGPT driver, but
 256 * a simplified one designed specifically to act as a clock event source.
 257 * For full details about the MFGPT, please consult the CS5536 data sheet.
 258 */
 259
 260#include <linux/clocksource.h>
 261#include <linux/clockchips.h>
 262
 263static unsigned int mfgpt_tick_mode = CLOCK_EVT_MODE_SHUTDOWN;
 264static u16 mfgpt_event_clock;
 265
 266static int irq;
 267static int __init mfgpt_setup(char *str)
 268{
 269        get_option(&str, &irq);
 270        return 1;
 271}
 272__setup("mfgpt_irq=", mfgpt_setup);
 273
 274static void mfgpt_disable_timer(u16 clock)
 275{
 276        /* avoid races by clearing CMP1 and CMP2 unconditionally */
 277        geode_mfgpt_write(clock, MFGPT_REG_SETUP, (u16) ~MFGPT_SETUP_CNTEN |
 278                        MFGPT_SETUP_CMP1 | MFGPT_SETUP_CMP2);
 279}
 280
 281static int mfgpt_next_event(unsigned long, struct clock_event_device *);
 282static void mfgpt_set_mode(enum clock_event_mode, struct clock_event_device *);
 283
 284static struct clock_event_device mfgpt_clockevent = {
 285        .name = "mfgpt-timer",
 286        .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
 287        .set_mode = mfgpt_set_mode,
 288        .set_next_event = mfgpt_next_event,
 289        .rating = 250,
 290        .cpumask = CPU_MASK_ALL,
 291        .shift = 32
 292};
 293
 294static void mfgpt_start_timer(u16 delta)
 295{
 296        geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_CMP2, (u16) delta);
 297        geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_COUNTER, 0);
 298
 299        geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP,
 300                          MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);
 301}
 302
 303static void mfgpt_set_mode(enum clock_event_mode mode,
 304                           struct clock_event_device *evt)
 305{
 306        mfgpt_disable_timer(mfgpt_event_clock);
 307
 308        if (mode == CLOCK_EVT_MODE_PERIODIC)
 309                mfgpt_start_timer(MFGPT_PERIODIC);
 310
 311        mfgpt_tick_mode = mode;
 312}
 313
 314static int mfgpt_next_event(unsigned long delta, struct clock_event_device *evt)
 315{
 316        mfgpt_start_timer(delta);
 317        return 0;
 318}
 319
 320static irqreturn_t mfgpt_tick(int irq, void *dev_id)
 321{
 322        u16 val = geode_mfgpt_read(mfgpt_event_clock, MFGPT_REG_SETUP);
 323
 324        /* See if the interrupt was for us */
 325        if (!(val & (MFGPT_SETUP_SETUP  | MFGPT_SETUP_CMP2 | MFGPT_SETUP_CMP1)))
 326                return IRQ_NONE;
 327
 328        /* Turn off the clock (and clear the event) */
 329        mfgpt_disable_timer(mfgpt_event_clock);
 330
 331        if (mfgpt_tick_mode == CLOCK_EVT_MODE_SHUTDOWN)
 332                return IRQ_HANDLED;
 333
 334        /* Clear the counter */
 335        geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_COUNTER, 0);
 336
 337        /* Restart the clock in periodic mode */
 338
 339        if (mfgpt_tick_mode == CLOCK_EVT_MODE_PERIODIC) {
 340                geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP,
 341                                  MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);
 342        }
 343
 344        mfgpt_clockevent.event_handler(&mfgpt_clockevent);
 345        return IRQ_HANDLED;
 346}
 347
 348static struct irqaction mfgptirq  = {
 349        .handler = mfgpt_tick,
 350        .flags = IRQF_DISABLED | IRQF_NOBALANCING,
 351        .mask = CPU_MASK_NONE,
 352        .name = "mfgpt-timer"
 353};
 354
 355int __init mfgpt_timer_setup(void)
 356{
 357        int timer, ret;
 358        u16 val;
 359
 360        timer = geode_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING);
 361        if (timer < 0) {
 362                printk(KERN_ERR
 363                       "mfgpt-timer:  Could not allocate a MFPGT timer\n");
 364                return -ENODEV;
 365        }
 366
 367        mfgpt_event_clock = timer;
 368
 369        /* Set up the IRQ on the MFGPT side */
 370        if (geode_mfgpt_setup_irq(mfgpt_event_clock, MFGPT_CMP2, &irq)) {
 371                printk(KERN_ERR "mfgpt-timer:  Could not set up IRQ %d\n", irq);
 372                return -EIO;
 373        }
 374
 375        /* And register it with the kernel */
 376        ret = setup_irq(irq, &mfgptirq);
 377
 378        if (ret) {
 379                printk(KERN_ERR
 380                       "mfgpt-timer:  Unable to set up the interrupt.\n");
 381                goto err;
 382        }
 383
 384        /* Set the clock scale and enable the event mode for CMP2 */
 385        val = MFGPT_SCALE | (3 << 8);
 386
 387        geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP, val);
 388
 389        /* Set up the clock event */
 390        mfgpt_clockevent.mult = div_sc(MFGPT_HZ, NSEC_PER_SEC,
 391                                       mfgpt_clockevent.shift);
 392        mfgpt_clockevent.min_delta_ns = clockevent_delta2ns(0xF,
 393                        &mfgpt_clockevent);
 394        mfgpt_clockevent.max_delta_ns = clockevent_delta2ns(0xFFFE,
 395                        &mfgpt_clockevent);
 396
 397        printk(KERN_INFO
 398               "mfgpt-timer:  Registering MFGPT timer %d as a clock event, using IRQ %d\n",
 399               timer, irq);
 400        clockevents_register_device(&mfgpt_clockevent);
 401
 402        return 0;
 403
 404err:
 405        geode_mfgpt_release_irq(mfgpt_event_clock, MFGPT_CMP2, &irq);
 406        printk(KERN_ERR
 407               "mfgpt-timer:  Unable to set up the MFGPT clock source\n");
 408        return -EIO;
 409}
 410
 411#endif
 412
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.