linux/drivers/comedi/drivers/comedi_8254.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * comedi_8254.c
   4 * Generic 8254 timer/counter support
   5 * Copyright (C) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
   6 *
   7 * Based on 8253.h and various subdevice implementations in comedi drivers.
   8 *
   9 * COMEDI - Linux Control and Measurement Device Interface
  10 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
  11 */
  12
  13/*
  14 * Module: comedi_8254
  15 * Description: Generic 8254 timer/counter support
  16 * Author: H Hartley Sweeten <hsweeten@visionengravers.com>
  17 * Updated: Thu Jan 8 16:45:45 MST 2015
  18 * Status: works
  19 *
  20 * This module is not used directly by end-users. Rather, it is used by other
  21 * drivers to provide support for an 8254 Programmable Interval Timer. These
  22 * counters are typically used to generate the pacer clock used for data
  23 * acquisition. Some drivers also expose the counters for general purpose use.
  24 *
  25 * This module provides the following basic functions:
  26 *
  27 * comedi_8254_init() / comedi_8254_mm_init()
  28 *      Initializes this module to access the 8254 registers. The _mm version
  29 *      sets up the module for MMIO register access the other for PIO access.
  30 *      The pointer returned from these functions is normally stored in the
  31 *      comedi_device dev->pacer and will be freed by the comedi core during
  32 *      the driver (*detach). If a driver has multiple 8254 devices, they need
  33 *      to be stored in the drivers private data and freed when the driver is
  34 *      detached.
  35 *
  36 *      NOTE: The counters are reset by setting them to I8254_MODE0 as part of
  37 *      this initialization.
  38 *
  39 * comedi_8254_set_mode()
  40 *      Sets a counters operation mode:
  41 *              I8254_MODE0     Interrupt on terminal count
  42 *              I8254_MODE1     Hardware retriggerable one-shot
  43 *              I8254_MODE2     Rate generator
  44 *              I8254_MODE3     Square wave mode
  45 *              I8254_MODE4     Software triggered strobe
  46 *              I8254_MODE5     Hardware triggered strobe (retriggerable)
  47 *
  48 *      In addition I8254_BCD and I8254_BINARY specify the counting mode:
  49 *              I8254_BCD       BCD counting
  50 *              I8254_BINARY    Binary counting
  51 *
  52 * comedi_8254_write()
  53 *      Writes an initial value to a counter.
  54 *
  55 *      The largest possible initial count is 0; this is equivalent to 2^16
  56 *      for binary counting and 10^4 for BCD counting.
  57 *
  58 *      NOTE: The counter does not stop when it reaches zero. In Mode 0, 1, 4,
  59 *      and 5 the counter "wraps around" to the highest count, either 0xffff
  60 *      for binary counting or 9999 for BCD counting, and continues counting.
  61 *      Modes 2 and 3 are periodic; the counter reloads itself with the initial
  62 *      count and continues counting from there.
  63 *
  64 * comedi_8254_read()
  65 *      Reads the current value from a counter.
  66 *
  67 * comedi_8254_status()
  68 *      Reads the status of a counter.
  69 *
  70 * comedi_8254_load()
  71 *      Sets a counters operation mode and writes the initial value.
  72 *
  73 * Typically the pacer clock is created by cascading two of the 16-bit counters
  74 * to create a 32-bit rate generator (I8254_MODE2). These functions are
  75 * provided to handle the cascaded counters:
  76 *
  77 * comedi_8254_ns_to_timer()
  78 *      Calculates the divisor value needed for a single counter to generate
  79 *      ns timing.
  80 *
  81 * comedi_8254_cascade_ns_to_timer()
  82 *      Calculates the two divisor values needed to the generate the pacer
  83 *      clock (in ns).
  84 *
  85 * comedi_8254_update_divisors()
  86 *      Transfers the intermediate divisor values to the current divisors.
  87 *
  88 * comedi_8254_pacer_enable()
  89 *      Programs the mode of the cascaded counters and writes the current
  90 *      divisor values.
  91 *
  92 * To expose the counters as a subdevice for general purpose use the following
  93 * functions a provided:
  94 *
  95 * comedi_8254_subdevice_init()
  96 *      Initializes a comedi_subdevice to use the 8254 timer.
  97 *
  98 * comedi_8254_set_busy()
  99 *      Internally flags a counter as "busy". This is done to protect the
 100 *      counters that are used for the cascaded 32-bit pacer.
 101 *
 102 * The subdevice provides (*insn_read) and (*insn_write) operations to read
 103 * the current value and write an initial value to a counter. A (*insn_config)
 104 * operation is also provided to handle the following comedi instructions:
 105 *
 106 *      INSN_CONFIG_SET_COUNTER_MODE    calls comedi_8254_set_mode()
 107 *      INSN_CONFIG_8254_READ_STATUS    calls comedi_8254_status()
 108 *
 109 * The (*insn_config) member of comedi_8254 can be initialized by the external
 110 * driver to handle any additional instructions.
 111 *
 112 * NOTE: Gate control, clock routing, and any interrupt handling for the
 113 * counters is not handled by this module. These features are driver dependent.
 114 */
 115
 116#include <linux/module.h>
 117#include <linux/slab.h>
 118#include <linux/io.h>
 119
 120#include "../comedidev.h"
 121
 122#include "comedi_8254.h"
 123
 124static unsigned int __i8254_read(struct comedi_8254 *i8254, unsigned int reg)
 125{
 126        unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift;
 127        unsigned int val;
 128
 129        switch (i8254->iosize) {
 130        default:
 131        case I8254_IO8:
 132                if (i8254->mmio)
 133                        val = readb(i8254->mmio + reg_offset);
 134                else
 135                        val = inb(i8254->iobase + reg_offset);
 136                break;
 137        case I8254_IO16:
 138                if (i8254->mmio)
 139                        val = readw(i8254->mmio + reg_offset);
 140                else
 141                        val = inw(i8254->iobase + reg_offset);
 142                break;
 143        case I8254_IO32:
 144                if (i8254->mmio)
 145                        val = readl(i8254->mmio + reg_offset);
 146                else
 147                        val = inl(i8254->iobase + reg_offset);
 148                break;
 149        }
 150        return val & 0xff;
 151}
 152
 153static void __i8254_write(struct comedi_8254 *i8254,
 154                          unsigned int val, unsigned int reg)
 155{
 156        unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift;
 157
 158        switch (i8254->iosize) {
 159        default:
 160        case I8254_IO8:
 161                if (i8254->mmio)
 162                        writeb(val, i8254->mmio + reg_offset);
 163                else
 164                        outb(val, i8254->iobase + reg_offset);
 165                break;
 166        case I8254_IO16:
 167                if (i8254->mmio)
 168                        writew(val, i8254->mmio + reg_offset);
 169                else
 170                        outw(val, i8254->iobase + reg_offset);
 171                break;
 172        case I8254_IO32:
 173                if (i8254->mmio)
 174                        writel(val, i8254->mmio + reg_offset);
 175                else
 176                        outl(val, i8254->iobase + reg_offset);
 177                break;
 178        }
 179}
 180
 181/**
 182 * comedi_8254_status - return the status of a counter
 183 * @i8254:      comedi_8254 struct for the timer
 184 * @counter:    the counter number
 185 */
 186unsigned int comedi_8254_status(struct comedi_8254 *i8254, unsigned int counter)
 187{
 188        unsigned int cmd;
 189
 190        if (counter > 2)
 191                return 0;
 192
 193        cmd = I8254_CTRL_READBACK_STATUS | I8254_CTRL_READBACK_SEL_CTR(counter);
 194        __i8254_write(i8254, cmd, I8254_CTRL_REG);
 195
 196        return __i8254_read(i8254, counter);
 197}
 198EXPORT_SYMBOL_GPL(comedi_8254_status);
 199
 200/**
 201 * comedi_8254_read - read the current counter value
 202 * @i8254:      comedi_8254 struct for the timer
 203 * @counter:    the counter number
 204 */
 205unsigned int comedi_8254_read(struct comedi_8254 *i8254, unsigned int counter)
 206{
 207        unsigned int val;
 208
 209        if (counter > 2)
 210                return 0;
 211
 212        /* latch counter */
 213        __i8254_write(i8254, I8254_CTRL_SEL_CTR(counter) | I8254_CTRL_LATCH,
 214                      I8254_CTRL_REG);
 215
 216        /* read LSB then MSB */
 217        val = __i8254_read(i8254, counter);
 218        val |= (__i8254_read(i8254, counter) << 8);
 219
 220        return val;
 221}
 222EXPORT_SYMBOL_GPL(comedi_8254_read);
 223
 224/**
 225 * comedi_8254_write - load a 16-bit initial counter value
 226 * @i8254:      comedi_8254 struct for the timer
 227 * @counter:    the counter number
 228 * @val:        the initial value
 229 */
 230void comedi_8254_write(struct comedi_8254 *i8254,
 231                       unsigned int counter, unsigned int val)
 232{
 233        unsigned int byte;
 234
 235        if (counter > 2)
 236                return;
 237        if (val > 0xffff)
 238                return;
 239
 240        /* load LSB then MSB */
 241        byte = val & 0xff;
 242        __i8254_write(i8254, byte, counter);
 243        byte = (val >> 8) & 0xff;
 244        __i8254_write(i8254, byte, counter);
 245}
 246EXPORT_SYMBOL_GPL(comedi_8254_write);
 247
 248/**
 249 * comedi_8254_set_mode - set the mode of a counter
 250 * @i8254:      comedi_8254 struct for the timer
 251 * @counter:    the counter number
 252 * @mode:       the I8254_MODEx and I8254_BCD|I8254_BINARY
 253 */
 254int comedi_8254_set_mode(struct comedi_8254 *i8254, unsigned int counter,
 255                         unsigned int mode)
 256{
 257        unsigned int byte;
 258
 259        if (counter > 2)
 260                return -EINVAL;
 261        if (mode > (I8254_MODE5 | I8254_BCD))
 262                return -EINVAL;
 263
 264        byte = I8254_CTRL_SEL_CTR(counter) |    /* select counter */
 265               I8254_CTRL_LSB_MSB |             /* load LSB then MSB */
 266               mode;                            /* mode and BCD|binary */
 267        __i8254_write(i8254, byte, I8254_CTRL_REG);
 268
 269        return 0;
 270}
 271EXPORT_SYMBOL_GPL(comedi_8254_set_mode);
 272
 273/**
 274 * comedi_8254_load - program the mode and initial count of a counter
 275 * @i8254:      comedi_8254 struct for the timer
 276 * @counter:    the counter number
 277 * @mode:       the I8254_MODEx and I8254_BCD|I8254_BINARY
 278 * @val:        the initial value
 279 */
 280int comedi_8254_load(struct comedi_8254 *i8254, unsigned int counter,
 281                     unsigned int val, unsigned int mode)
 282{
 283        if (counter > 2)
 284                return -EINVAL;
 285        if (val > 0xffff)
 286                return -EINVAL;
 287        if (mode > (I8254_MODE5 | I8254_BCD))
 288                return -EINVAL;
 289
 290        comedi_8254_set_mode(i8254, counter, mode);
 291        comedi_8254_write(i8254, counter, val);
 292
 293        return 0;
 294}
 295EXPORT_SYMBOL_GPL(comedi_8254_load);
 296
 297/**
 298 * comedi_8254_pacer_enable - set the mode and load the cascaded counters
 299 * @i8254:      comedi_8254 struct for the timer
 300 * @counter1:   the counter number for the first divisor
 301 * @counter2:   the counter number for the second divisor
 302 * @enable:     flag to enable (load) the counters
 303 */
 304void comedi_8254_pacer_enable(struct comedi_8254 *i8254,
 305                              unsigned int counter1,
 306                              unsigned int counter2,
 307                              bool enable)
 308{
 309        unsigned int mode;
 310
 311        if (counter1 > 2 || counter2 > 2 || counter1 == counter2)
 312                return;
 313
 314        if (enable)
 315                mode = I8254_MODE2 | I8254_BINARY;
 316        else
 317                mode = I8254_MODE0 | I8254_BINARY;
 318
 319        comedi_8254_set_mode(i8254, counter1, mode);
 320        comedi_8254_set_mode(i8254, counter2, mode);
 321
 322        if (enable) {
 323                /*
 324                 * Divisors are loaded second counter then first counter to
 325                 * avoid possible issues with the first counter expiring
 326                 * before the second counter is loaded.
 327                 */
 328                comedi_8254_write(i8254, counter2, i8254->divisor2);
 329                comedi_8254_write(i8254, counter1, i8254->divisor1);
 330        }
 331}
 332EXPORT_SYMBOL_GPL(comedi_8254_pacer_enable);
 333
 334/**
 335 * comedi_8254_update_divisors - update the divisors for the cascaded counters
 336 * @i8254:      comedi_8254 struct for the timer
 337 */
 338void comedi_8254_update_divisors(struct comedi_8254 *i8254)
 339{
 340        /* masking is done since counter maps zero to 0x10000 */
 341        i8254->divisor = i8254->next_div & 0xffff;
 342        i8254->divisor1 = i8254->next_div1 & 0xffff;
 343        i8254->divisor2 = i8254->next_div2 & 0xffff;
 344}
 345EXPORT_SYMBOL_GPL(comedi_8254_update_divisors);
 346
 347/**
 348 * comedi_8254_cascade_ns_to_timer - calculate the cascaded divisor values
 349 * @i8254:      comedi_8254 struct for the timer
 350 * @nanosec:    the desired ns time
 351 * @flags:      comedi_cmd flags
 352 */
 353void comedi_8254_cascade_ns_to_timer(struct comedi_8254 *i8254,
 354                                     unsigned int *nanosec,
 355                                     unsigned int flags)
 356{
 357        unsigned int d1 = i8254->next_div1 ? i8254->next_div1 : I8254_MAX_COUNT;
 358        unsigned int d2 = i8254->next_div2 ? i8254->next_div2 : I8254_MAX_COUNT;
 359        unsigned int div = d1 * d2;
 360        unsigned int ns_lub = 0xffffffff;
 361        unsigned int ns_glb = 0;
 362        unsigned int d1_lub = 0;
 363        unsigned int d1_glb = 0;
 364        unsigned int d2_lub = 0;
 365        unsigned int d2_glb = 0;
 366        unsigned int start;
 367        unsigned int ns;
 368        unsigned int ns_low;
 369        unsigned int ns_high;
 370
 371        /* exit early if everything is already correct */
 372        if (div * i8254->osc_base == *nanosec &&
 373            d1 > 1 && d1 <= I8254_MAX_COUNT &&
 374            d2 > 1 && d2 <= I8254_MAX_COUNT &&
 375            /* check for overflow */
 376            div > d1 && div > d2 &&
 377            div * i8254->osc_base > div &&
 378            div * i8254->osc_base > i8254->osc_base)
 379                return;
 380
 381        div = *nanosec / i8254->osc_base;
 382        d2 = I8254_MAX_COUNT;
 383        start = div / d2;
 384        if (start < 2)
 385                start = 2;
 386        for (d1 = start; d1 <= div / d1 + 1 && d1 <= I8254_MAX_COUNT; d1++) {
 387                for (d2 = div / d1;
 388                     d1 * d2 <= div + d1 + 1 && d2 <= I8254_MAX_COUNT; d2++) {
 389                        ns = i8254->osc_base * d1 * d2;
 390                        if (ns <= *nanosec && ns > ns_glb) {
 391                                ns_glb = ns;
 392                                d1_glb = d1;
 393                                d2_glb = d2;
 394                        }
 395                        if (ns >= *nanosec && ns < ns_lub) {
 396                                ns_lub = ns;
 397                                d1_lub = d1;
 398                                d2_lub = d2;
 399                        }
 400                }
 401        }
 402
 403        switch (flags & CMDF_ROUND_MASK) {
 404        case CMDF_ROUND_NEAREST:
 405        default:
 406                ns_high = d1_lub * d2_lub * i8254->osc_base;
 407                ns_low = d1_glb * d2_glb * i8254->osc_base;
 408                if (ns_high - *nanosec < *nanosec - ns_low) {
 409                        d1 = d1_lub;
 410                        d2 = d2_lub;
 411                } else {
 412                        d1 = d1_glb;
 413                        d2 = d2_glb;
 414                }
 415                break;
 416        case CMDF_ROUND_UP:
 417                d1 = d1_lub;
 418                d2 = d2_lub;
 419                break;
 420        case CMDF_ROUND_DOWN:
 421                d1 = d1_glb;
 422                d2 = d2_glb;
 423                break;
 424        }
 425
 426        *nanosec = d1 * d2 * i8254->osc_base;
 427        i8254->next_div1 = d1;
 428        i8254->next_div2 = d2;
 429}
 430EXPORT_SYMBOL_GPL(comedi_8254_cascade_ns_to_timer);
 431
 432/**
 433 * comedi_8254_ns_to_timer - calculate the divisor value for nanosec timing
 434 * @i8254:      comedi_8254 struct for the timer
 435 * @nanosec:    the desired ns time
 436 * @flags:      comedi_cmd flags
 437 */
 438void comedi_8254_ns_to_timer(struct comedi_8254 *i8254,
 439                             unsigned int *nanosec, unsigned int flags)
 440{
 441        unsigned int divisor;
 442
 443        switch (flags & CMDF_ROUND_MASK) {
 444        default:
 445        case CMDF_ROUND_NEAREST:
 446                divisor = DIV_ROUND_CLOSEST(*nanosec, i8254->osc_base);
 447                break;
 448        case CMDF_ROUND_UP:
 449                divisor = DIV_ROUND_UP(*nanosec, i8254->osc_base);
 450                break;
 451        case CMDF_ROUND_DOWN:
 452                divisor = *nanosec / i8254->osc_base;
 453                break;
 454        }
 455        if (divisor < 2)
 456                divisor = 2;
 457        if (divisor > I8254_MAX_COUNT)
 458                divisor = I8254_MAX_COUNT;
 459
 460        *nanosec = divisor * i8254->osc_base;
 461        i8254->next_div = divisor;
 462}
 463EXPORT_SYMBOL_GPL(comedi_8254_ns_to_timer);
 464
 465/**
 466 * comedi_8254_set_busy - set/clear the "busy" flag for a given counter
 467 * @i8254:      comedi_8254 struct for the timer
 468 * @counter:    the counter number
 469 * @busy:       set/clear flag
 470 */
 471void comedi_8254_set_busy(struct comedi_8254 *i8254,
 472                          unsigned int counter, bool busy)
 473{
 474        if (counter < 3)
 475                i8254->busy[counter] = busy;
 476}
 477EXPORT_SYMBOL_GPL(comedi_8254_set_busy);
 478
 479static int comedi_8254_insn_read(struct comedi_device *dev,
 480                                 struct comedi_subdevice *s,
 481                                 struct comedi_insn *insn,
 482                                 unsigned int *data)
 483{
 484        struct comedi_8254 *i8254 = s->private;
 485        unsigned int chan = CR_CHAN(insn->chanspec);
 486        int i;
 487
 488        if (i8254->busy[chan])
 489                return -EBUSY;
 490
 491        for (i = 0; i < insn->n; i++)
 492                data[i] = comedi_8254_read(i8254, chan);
 493
 494        return insn->n;
 495}
 496
 497static int comedi_8254_insn_write(struct comedi_device *dev,
 498                                  struct comedi_subdevice *s,
 499                                  struct comedi_insn *insn,
 500                                  unsigned int *data)
 501{
 502        struct comedi_8254 *i8254 = s->private;
 503        unsigned int chan = CR_CHAN(insn->chanspec);
 504
 505        if (i8254->busy[chan])
 506                return -EBUSY;
 507
 508        if (insn->n)
 509                comedi_8254_write(i8254, chan, data[insn->n - 1]);
 510
 511        return insn->n;
 512}
 513
 514static int comedi_8254_insn_config(struct comedi_device *dev,
 515                                   struct comedi_subdevice *s,
 516                                   struct comedi_insn *insn,
 517                                   unsigned int *data)
 518{
 519        struct comedi_8254 *i8254 = s->private;
 520        unsigned int chan = CR_CHAN(insn->chanspec);
 521        int ret;
 522
 523        if (i8254->busy[chan])
 524                return -EBUSY;
 525
 526        switch (data[0]) {
 527        case INSN_CONFIG_RESET:
 528                ret = comedi_8254_set_mode(i8254, chan,
 529                                           I8254_MODE0 | I8254_BINARY);
 530                if (ret)
 531                        return ret;
 532                break;
 533        case INSN_CONFIG_SET_COUNTER_MODE:
 534                ret = comedi_8254_set_mode(i8254, chan, data[1]);
 535                if (ret)
 536                        return ret;
 537                break;
 538        case INSN_CONFIG_8254_READ_STATUS:
 539                data[1] = comedi_8254_status(i8254, chan);
 540                break;
 541        default:
 542                /*
 543                 * If available, call the driver provided (*insn_config)
 544                 * to handle any driver implemented instructions.
 545                 */
 546                if (i8254->insn_config)
 547                        return i8254->insn_config(dev, s, insn, data);
 548
 549                return -EINVAL;
 550        }
 551
 552        return insn->n;
 553}
 554
 555/**
 556 * comedi_8254_subdevice_init - initialize a comedi_subdevice for the 8254 timer
 557 * @s:          comedi_subdevice struct
 558 * @i8254:      comedi_8254 struct
 559 */
 560void comedi_8254_subdevice_init(struct comedi_subdevice *s,
 561                                struct comedi_8254 *i8254)
 562{
 563        s->type         = COMEDI_SUBD_COUNTER;
 564        s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
 565        s->n_chan       = 3;
 566        s->maxdata      = 0xffff;
 567        s->range_table  = &range_unknown;
 568        s->insn_read    = comedi_8254_insn_read;
 569        s->insn_write   = comedi_8254_insn_write;
 570        s->insn_config  = comedi_8254_insn_config;
 571
 572        s->private      = i8254;
 573}
 574EXPORT_SYMBOL_GPL(comedi_8254_subdevice_init);
 575
 576static struct comedi_8254 *__i8254_init(unsigned long iobase,
 577                                        void __iomem *mmio,
 578                                        unsigned int osc_base,
 579                                        unsigned int iosize,
 580                                        unsigned int regshift)
 581{
 582        struct comedi_8254 *i8254;
 583        int i;
 584
 585        /* sanity check that the iosize is valid */
 586        if (!(iosize == I8254_IO8 || iosize == I8254_IO16 ||
 587              iosize == I8254_IO32))
 588                return NULL;
 589
 590        i8254 = kzalloc(sizeof(*i8254), GFP_KERNEL);
 591        if (!i8254)
 592                return NULL;
 593
 594        i8254->iobase   = iobase;
 595        i8254->mmio     = mmio;
 596        i8254->iosize   = iosize;
 597        i8254->regshift = regshift;
 598
 599        /* default osc_base to the max speed of a generic 8254 timer */
 600        i8254->osc_base = osc_base ? osc_base : I8254_OSC_BASE_10MHZ;
 601
 602        /* reset all the counters by setting them to I8254_MODE0 */
 603        for (i = 0; i < 3; i++)
 604                comedi_8254_set_mode(i8254, i, I8254_MODE0 | I8254_BINARY);
 605
 606        return i8254;
 607}
 608
 609/**
 610 * comedi_8254_init - allocate and initialize the 8254 device for pio access
 611 * @iobase:     port I/O base address
 612 * @osc_base:   base time of the counter in ns
 613 *              OPTIONAL - only used by comedi_8254_cascade_ns_to_timer()
 614 * @iosize:     I/O register size
 615 * @regshift:   register gap shift
 616 */
 617struct comedi_8254 *comedi_8254_init(unsigned long iobase,
 618                                     unsigned int osc_base,
 619                                     unsigned int iosize,
 620                                     unsigned int regshift)
 621{
 622        return __i8254_init(iobase, NULL, osc_base, iosize, regshift);
 623}
 624EXPORT_SYMBOL_GPL(comedi_8254_init);
 625
 626/**
 627 * comedi_8254_mm_init - allocate and initialize the 8254 device for mmio access
 628 * @mmio:       memory mapped I/O base address
 629 * @osc_base:   base time of the counter in ns
 630 *              OPTIONAL - only used by comedi_8254_cascade_ns_to_timer()
 631 * @iosize:     I/O register size
 632 * @regshift:   register gap shift
 633 */
 634struct comedi_8254 *comedi_8254_mm_init(void __iomem *mmio,
 635                                        unsigned int osc_base,
 636                                        unsigned int iosize,
 637                                        unsigned int regshift)
 638{
 639        return __i8254_init(0, mmio, osc_base, iosize, regshift);
 640}
 641EXPORT_SYMBOL_GPL(comedi_8254_mm_init);
 642
 643static int __init comedi_8254_module_init(void)
 644{
 645        return 0;
 646}
 647module_init(comedi_8254_module_init);
 648
 649static void __exit comedi_8254_module_exit(void)
 650{
 651}
 652module_exit(comedi_8254_module_exit);
 653
 654MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
 655MODULE_DESCRIPTION("Comedi: Generic 8254 timer/counter support");
 656MODULE_LICENSE("GPL");
 657