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