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