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

source code of linux/drivers/comedi/drivers/comedi_8254.c