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 #include <linux/comedi/comedidev.h>
120 #include <linux/comedi/comedi_8254.h>
121
__i8254_read(struct comedi_8254 * i8254,unsigned int reg)122 static unsigned int __i8254_read(struct comedi_8254 *i8254, unsigned int reg)
123 {
124 unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift;
125 unsigned int val;
126
127 switch (i8254->iosize) {
128 default:
129 case I8254_IO8:
130 if (i8254->mmio)
131 val = readb(i8254->mmio + reg_offset);
132 else
133 val = inb(i8254->iobase + reg_offset);
134 break;
135 case I8254_IO16:
136 if (i8254->mmio)
137 val = readw(i8254->mmio + reg_offset);
138 else
139 val = inw(i8254->iobase + reg_offset);
140 break;
141 case I8254_IO32:
142 if (i8254->mmio)
143 val = readl(i8254->mmio + reg_offset);
144 else
145 val = inl(i8254->iobase + reg_offset);
146 break;
147 }
148 return val & 0xff;
149 }
150
__i8254_write(struct comedi_8254 * i8254,unsigned int val,unsigned int reg)151 static void __i8254_write(struct comedi_8254 *i8254,
152 unsigned int val, unsigned int reg)
153 {
154 unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift;
155
156 switch (i8254->iosize) {
157 default:
158 case I8254_IO8:
159 if (i8254->mmio)
160 writeb(val, i8254->mmio + reg_offset);
161 else
162 outb(val, i8254->iobase + reg_offset);
163 break;
164 case I8254_IO16:
165 if (i8254->mmio)
166 writew(val, i8254->mmio + reg_offset);
167 else
168 outw(val, i8254->iobase + reg_offset);
169 break;
170 case I8254_IO32:
171 if (i8254->mmio)
172 writel(val, i8254->mmio + reg_offset);
173 else
174 outl(val, i8254->iobase + reg_offset);
175 break;
176 }
177 }
178
179 /**
180 * comedi_8254_status - return the status of a counter
181 * @i8254: comedi_8254 struct for the timer
182 * @counter: the counter number
183 */
comedi_8254_status(struct comedi_8254 * i8254,unsigned int counter)184 unsigned int comedi_8254_status(struct comedi_8254 *i8254, unsigned int counter)
185 {
186 unsigned int cmd;
187
188 if (counter > 2)
189 return 0;
190
191 cmd = I8254_CTRL_READBACK_STATUS | I8254_CTRL_READBACK_SEL_CTR(counter);
192 __i8254_write(i8254, cmd, I8254_CTRL_REG);
193
194 return __i8254_read(i8254, counter);
195 }
196 EXPORT_SYMBOL_GPL(comedi_8254_status);
197
198 /**
199 * comedi_8254_read - read the current counter value
200 * @i8254: comedi_8254 struct for the timer
201 * @counter: the counter number
202 */
comedi_8254_read(struct comedi_8254 * i8254,unsigned int counter)203 unsigned int comedi_8254_read(struct comedi_8254 *i8254, unsigned int counter)
204 {
205 unsigned int val;
206
207 if (counter > 2)
208 return 0;
209
210 /* latch counter */
211 __i8254_write(i8254, I8254_CTRL_SEL_CTR(counter) | I8254_CTRL_LATCH,
212 I8254_CTRL_REG);
213
214 /* read LSB then MSB */
215 val = __i8254_read(i8254, counter);
216 val |= (__i8254_read(i8254, counter) << 8);
217
218 return val;
219 }
220 EXPORT_SYMBOL_GPL(comedi_8254_read);
221
222 /**
223 * comedi_8254_write - load a 16-bit initial counter value
224 * @i8254: comedi_8254 struct for the timer
225 * @counter: the counter number
226 * @val: the initial value
227 */
comedi_8254_write(struct comedi_8254 * i8254,unsigned int counter,unsigned int val)228 void comedi_8254_write(struct comedi_8254 *i8254,
229 unsigned int counter, unsigned int val)
230 {
231 unsigned int byte;
232
233 if (counter > 2)
234 return;
235 if (val > 0xffff)
236 return;
237
238 /* load LSB then MSB */
239 byte = val & 0xff;
240 __i8254_write(i8254, byte, counter);
241 byte = (val >> 8) & 0xff;
242 __i8254_write(i8254, byte, counter);
243 }
244 EXPORT_SYMBOL_GPL(comedi_8254_write);
245
246 /**
247 * comedi_8254_set_mode - set the mode of a counter
248 * @i8254: comedi_8254 struct for the timer
249 * @counter: the counter number
250 * @mode: the I8254_MODEx and I8254_BCD|I8254_BINARY
251 */
comedi_8254_set_mode(struct comedi_8254 * i8254,unsigned int counter,unsigned int mode)252 int comedi_8254_set_mode(struct comedi_8254 *i8254, unsigned int counter,
253 unsigned int mode)
254 {
255 unsigned int byte;
256
257 if (counter > 2)
258 return -EINVAL;
259 if (mode > (I8254_MODE5 | I8254_BCD))
260 return -EINVAL;
261
262 byte = I8254_CTRL_SEL_CTR(counter) | /* select counter */
263 I8254_CTRL_LSB_MSB | /* load LSB then MSB */
264 mode; /* mode and BCD|binary */
265 __i8254_write(i8254, byte, I8254_CTRL_REG);
266
267 return 0;
268 }
269 EXPORT_SYMBOL_GPL(comedi_8254_set_mode);
270
271 /**
272 * comedi_8254_load - program the mode and initial count of a counter
273 * @i8254: comedi_8254 struct for the timer
274 * @counter: the counter number
275 * @mode: the I8254_MODEx and I8254_BCD|I8254_BINARY
276 * @val: the initial value
277 */
comedi_8254_load(struct comedi_8254 * i8254,unsigned int counter,unsigned int val,unsigned int mode)278 int comedi_8254_load(struct comedi_8254 *i8254, unsigned int counter,
279 unsigned int val, unsigned int mode)
280 {
281 if (counter > 2)
282 return -EINVAL;
283 if (val > 0xffff)
284 return -EINVAL;
285 if (mode > (I8254_MODE5 | I8254_BCD))
286 return -EINVAL;
287
288 comedi_8254_set_mode(i8254, counter, mode);
289 comedi_8254_write(i8254, counter, val);
290
291 return 0;
292 }
293 EXPORT_SYMBOL_GPL(comedi_8254_load);
294
295 /**
296 * comedi_8254_pacer_enable - set the mode and load the cascaded counters
297 * @i8254: comedi_8254 struct for the timer
298 * @counter1: the counter number for the first divisor
299 * @counter2: the counter number for the second divisor
300 * @enable: flag to enable (load) the counters
301 */
comedi_8254_pacer_enable(struct comedi_8254 * i8254,unsigned int counter1,unsigned int counter2,bool enable)302 void comedi_8254_pacer_enable(struct comedi_8254 *i8254,
303 unsigned int counter1,
304 unsigned int counter2,
305 bool enable)
306 {
307 unsigned int mode;
308
309 if (counter1 > 2 || counter2 > 2 || counter1 == counter2)
310 return;
311
312 if (enable)
313 mode = I8254_MODE2 | I8254_BINARY;
314 else
315 mode = I8254_MODE0 | I8254_BINARY;
316
317 comedi_8254_set_mode(i8254, counter1, mode);
318 comedi_8254_set_mode(i8254, counter2, mode);
319
320 if (enable) {
321 /*
322 * Divisors are loaded second counter then first counter to
323 * avoid possible issues with the first counter expiring
324 * before the second counter is loaded.
325 */
326 comedi_8254_write(i8254, counter2, i8254->divisor2);
327 comedi_8254_write(i8254, counter1, i8254->divisor1);
328 }
329 }
330 EXPORT_SYMBOL_GPL(comedi_8254_pacer_enable);
331
332 /**
333 * comedi_8254_update_divisors - update the divisors for the cascaded counters
334 * @i8254: comedi_8254 struct for the timer
335 */
comedi_8254_update_divisors(struct comedi_8254 * i8254)336 void comedi_8254_update_divisors(struct comedi_8254 *i8254)
337 {
338 /* masking is done since counter maps zero to 0x10000 */
339 i8254->divisor = i8254->next_div & 0xffff;
340 i8254->divisor1 = i8254->next_div1 & 0xffff;
341 i8254->divisor2 = i8254->next_div2 & 0xffff;
342 }
343 EXPORT_SYMBOL_GPL(comedi_8254_update_divisors);
344
345 /**
346 * comedi_8254_cascade_ns_to_timer - calculate the cascaded divisor values
347 * @i8254: comedi_8254 struct for the timer
348 * @nanosec: the desired ns time
349 * @flags: comedi_cmd flags
350 */
comedi_8254_cascade_ns_to_timer(struct comedi_8254 * i8254,unsigned int * nanosec,unsigned int flags)351 void comedi_8254_cascade_ns_to_timer(struct comedi_8254 *i8254,
352 unsigned int *nanosec,
353 unsigned int flags)
354 {
355 unsigned int d1 = i8254->next_div1 ? i8254->next_div1 : I8254_MAX_COUNT;
356 unsigned int d2 = i8254->next_div2 ? i8254->next_div2 : I8254_MAX_COUNT;
357 unsigned int div = d1 * d2;
358 unsigned int ns_lub = 0xffffffff;
359 unsigned int ns_glb = 0;
360 unsigned int d1_lub = 0;
361 unsigned int d1_glb = 0;
362 unsigned int d2_lub = 0;
363 unsigned int d2_glb = 0;
364 unsigned int start;
365 unsigned int ns;
366 unsigned int ns_low;
367 unsigned int ns_high;
368
369 /* exit early if everything is already correct */
370 if (div * i8254->osc_base == *nanosec &&
371 d1 > 1 && d1 <= I8254_MAX_COUNT &&
372 d2 > 1 && d2 <= I8254_MAX_COUNT &&
373 /* check for overflow */
374 div > d1 && div > d2 &&
375 div * i8254->osc_base > div &&
376 div * i8254->osc_base > i8254->osc_base)
377 return;
378
379 div = *nanosec / i8254->osc_base;
380 d2 = I8254_MAX_COUNT;
381 start = div / d2;
382 if (start < 2)
383 start = 2;
384 for (d1 = start; d1 <= div / d1 + 1 && d1 <= I8254_MAX_COUNT; d1++) {
385 for (d2 = div / d1;
386 d1 * d2 <= div + d1 + 1 && d2 <= I8254_MAX_COUNT; d2++) {
387 ns = i8254->osc_base * d1 * d2;
388 if (ns <= *nanosec && ns > ns_glb) {
389 ns_glb = ns;
390 d1_glb = d1;
391 d2_glb = d2;
392 }
393 if (ns >= *nanosec && ns < ns_lub) {
394 ns_lub = ns;
395 d1_lub = d1;
396 d2_lub = d2;
397 }
398 }
399 }
400
401 switch (flags & CMDF_ROUND_MASK) {
402 case CMDF_ROUND_NEAREST:
403 default:
404 ns_high = d1_lub * d2_lub * i8254->osc_base;
405 ns_low = d1_glb * d2_glb * i8254->osc_base;
406 if (ns_high - *nanosec < *nanosec - ns_low) {
407 d1 = d1_lub;
408 d2 = d2_lub;
409 } else {
410 d1 = d1_glb;
411 d2 = d2_glb;
412 }
413 break;
414 case CMDF_ROUND_UP:
415 d1 = d1_lub;
416 d2 = d2_lub;
417 break;
418 case CMDF_ROUND_DOWN:
419 d1 = d1_glb;
420 d2 = d2_glb;
421 break;
422 }
423
424 *nanosec = d1 * d2 * i8254->osc_base;
425 i8254->next_div1 = d1;
426 i8254->next_div2 = d2;
427 }
428 EXPORT_SYMBOL_GPL(comedi_8254_cascade_ns_to_timer);
429
430 /**
431 * comedi_8254_ns_to_timer - calculate the divisor value for nanosec timing
432 * @i8254: comedi_8254 struct for the timer
433 * @nanosec: the desired ns time
434 * @flags: comedi_cmd flags
435 */
comedi_8254_ns_to_timer(struct comedi_8254 * i8254,unsigned int * nanosec,unsigned int flags)436 void comedi_8254_ns_to_timer(struct comedi_8254 *i8254,
437 unsigned int *nanosec, unsigned int flags)
438 {
439 unsigned int divisor;
440
441 switch (flags & CMDF_ROUND_MASK) {
442 default:
443 case CMDF_ROUND_NEAREST:
444 divisor = DIV_ROUND_CLOSEST(*nanosec, i8254->osc_base);
445 break;
446 case CMDF_ROUND_UP:
447 divisor = DIV_ROUND_UP(*nanosec, i8254->osc_base);
448 break;
449 case CMDF_ROUND_DOWN:
450 divisor = *nanosec / i8254->osc_base;
451 break;
452 }
453 if (divisor < 2)
454 divisor = 2;
455 if (divisor > I8254_MAX_COUNT)
456 divisor = I8254_MAX_COUNT;
457
458 *nanosec = divisor * i8254->osc_base;
459 i8254->next_div = divisor;
460 }
461 EXPORT_SYMBOL_GPL(comedi_8254_ns_to_timer);
462
463 /**
464 * comedi_8254_set_busy - set/clear the "busy" flag for a given counter
465 * @i8254: comedi_8254 struct for the timer
466 * @counter: the counter number
467 * @busy: set/clear flag
468 */
comedi_8254_set_busy(struct comedi_8254 * i8254,unsigned int counter,bool busy)469 void comedi_8254_set_busy(struct comedi_8254 *i8254,
470 unsigned int counter, bool busy)
471 {
472 if (counter < 3)
473 i8254->busy[counter] = busy;
474 }
475 EXPORT_SYMBOL_GPL(comedi_8254_set_busy);
476
comedi_8254_insn_read(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)477 static int comedi_8254_insn_read(struct comedi_device *dev,
478 struct comedi_subdevice *s,
479 struct comedi_insn *insn,
480 unsigned int *data)
481 {
482 struct comedi_8254 *i8254 = s->private;
483 unsigned int chan = CR_CHAN(insn->chanspec);
484 int i;
485
486 if (i8254->busy[chan])
487 return -EBUSY;
488
489 for (i = 0; i < insn->n; i++)
490 data[i] = comedi_8254_read(i8254, chan);
491
492 return insn->n;
493 }
494
comedi_8254_insn_write(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)495 static int comedi_8254_insn_write(struct comedi_device *dev,
496 struct comedi_subdevice *s,
497 struct comedi_insn *insn,
498 unsigned int *data)
499 {
500 struct comedi_8254 *i8254 = s->private;
501 unsigned int chan = CR_CHAN(insn->chanspec);
502
503 if (i8254->busy[chan])
504 return -EBUSY;
505
506 if (insn->n)
507 comedi_8254_write(i8254, chan, data[insn->n - 1]);
508
509 return insn->n;
510 }
511
comedi_8254_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)512 static int comedi_8254_insn_config(struct comedi_device *dev,
513 struct comedi_subdevice *s,
514 struct comedi_insn *insn,
515 unsigned int *data)
516 {
517 struct comedi_8254 *i8254 = s->private;
518 unsigned int chan = CR_CHAN(insn->chanspec);
519 int ret;
520
521 if (i8254->busy[chan])
522 return -EBUSY;
523
524 switch (data[0]) {
525 case INSN_CONFIG_RESET:
526 ret = comedi_8254_set_mode(i8254, chan,
527 I8254_MODE0 | I8254_BINARY);
528 if (ret)
529 return ret;
530 break;
531 case INSN_CONFIG_SET_COUNTER_MODE:
532 ret = comedi_8254_set_mode(i8254, chan, data[1]);
533 if (ret)
534 return ret;
535 break;
536 case INSN_CONFIG_8254_READ_STATUS:
537 data[1] = comedi_8254_status(i8254, chan);
538 break;
539 default:
540 /*
541 * If available, call the driver provided (*insn_config)
542 * to handle any driver implemented instructions.
543 */
544 if (i8254->insn_config)
545 return i8254->insn_config(dev, s, insn, data);
546
547 return -EINVAL;
548 }
549
550 return insn->n;
551 }
552
553 /**
554 * comedi_8254_subdevice_init - initialize a comedi_subdevice for the 8254 timer
555 * @s: comedi_subdevice struct
556 * @i8254: comedi_8254 struct
557 */
comedi_8254_subdevice_init(struct comedi_subdevice * s,struct comedi_8254 * i8254)558 void comedi_8254_subdevice_init(struct comedi_subdevice *s,
559 struct comedi_8254 *i8254)
560 {
561 s->type = COMEDI_SUBD_COUNTER;
562 s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
563 s->n_chan = 3;
564 s->maxdata = 0xffff;
565 s->range_table = &range_unknown;
566 s->insn_read = comedi_8254_insn_read;
567 s->insn_write = comedi_8254_insn_write;
568 s->insn_config = comedi_8254_insn_config;
569
570 s->private = i8254;
571 }
572 EXPORT_SYMBOL_GPL(comedi_8254_subdevice_init);
573
__i8254_init(unsigned long iobase,void __iomem * mmio,unsigned int osc_base,unsigned int iosize,unsigned int regshift)574 static struct comedi_8254 *__i8254_init(unsigned long iobase,
575 void __iomem *mmio,
576 unsigned int osc_base,
577 unsigned int iosize,
578 unsigned int regshift)
579 {
580 struct comedi_8254 *i8254;
581 int i;
582
583 /* sanity check that the iosize is valid */
584 if (!(iosize == I8254_IO8 || iosize == I8254_IO16 ||
585 iosize == I8254_IO32))
586 return NULL;
587
588 i8254 = kzalloc(sizeof(*i8254), GFP_KERNEL);
589 if (!i8254)
590 return NULL;
591
592 i8254->iobase = iobase;
593 i8254->mmio = mmio;
594 i8254->iosize = iosize;
595 i8254->regshift = regshift;
596
597 /* default osc_base to the max speed of a generic 8254 timer */
598 i8254->osc_base = osc_base ? osc_base : I8254_OSC_BASE_10MHZ;
599
600 /* reset all the counters by setting them to I8254_MODE0 */
601 for (i = 0; i < 3; i++)
602 comedi_8254_set_mode(i8254, i, I8254_MODE0 | I8254_BINARY);
603
604 return i8254;
605 }
606
607 /**
608 * comedi_8254_init - allocate and initialize the 8254 device for pio access
609 * @iobase: port I/O base address
610 * @osc_base: base time of the counter in ns
611 * OPTIONAL - only used by comedi_8254_cascade_ns_to_timer()
612 * @iosize: I/O register size
613 * @regshift: register gap shift
614 */
comedi_8254_init(unsigned long iobase,unsigned int osc_base,unsigned int iosize,unsigned int regshift)615 struct comedi_8254 *comedi_8254_init(unsigned long iobase,
616 unsigned int osc_base,
617 unsigned int iosize,
618 unsigned int regshift)
619 {
620 return __i8254_init(iobase, NULL, osc_base, iosize, regshift);
621 }
622 EXPORT_SYMBOL_GPL(comedi_8254_init);
623
624 /**
625 * comedi_8254_mm_init - allocate and initialize the 8254 device for mmio access
626 * @mmio: memory mapped I/O base address
627 * @osc_base: base time of the counter in ns
628 * OPTIONAL - only used by comedi_8254_cascade_ns_to_timer()
629 * @iosize: I/O register size
630 * @regshift: register gap shift
631 */
comedi_8254_mm_init(void __iomem * mmio,unsigned int osc_base,unsigned int iosize,unsigned int regshift)632 struct comedi_8254 *comedi_8254_mm_init(void __iomem *mmio,
633 unsigned int osc_base,
634 unsigned int iosize,
635 unsigned int regshift)
636 {
637 return __i8254_init(0, mmio, osc_base, iosize, regshift);
638 }
639 EXPORT_SYMBOL_GPL(comedi_8254_mm_init);
640
comedi_8254_module_init(void)641 static int __init comedi_8254_module_init(void)
642 {
643 return 0;
644 }
645 module_init(comedi_8254_module_init);
646
comedi_8254_module_exit(void)647 static void __exit comedi_8254_module_exit(void)
648 {
649 }
650 module_exit(comedi_8254_module_exit);
651
652 MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
653 MODULE_DESCRIPTION("Comedi: Generic 8254 timer/counter support");
654 MODULE_LICENSE("GPL");
655