1 // SPDX-License-Identifier: GPL-2.0+
4 * Generic 8254 timer/counter support
5 * Copyright (C) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
7 * Based on 8253.h and various subdevice implementations in comedi drivers.
9 * COMEDI - Linux Control and Measurement Device Interface
10 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
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
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.
25 * This module provides the following basic functions:
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
36 * NOTE: The counters are reset by setting them to I8254_MODE0 as part of
37 * this initialization.
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)
48 * In addition I8254_BCD and I8254_BINARY specify the counting mode:
49 * I8254_BCD BCD counting
50 * I8254_BINARY Binary counting
53 * Writes an initial value to a counter.
55 * The largest possible initial count is 0; this is equivalent to 2^16
56 * for binary counting and 10^4 for BCD counting.
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.
65 * Reads the current value from a counter.
67 * comedi_8254_status()
68 * Reads the status of a counter.
71 * Sets a counters operation mode and writes the initial value.
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:
77 * comedi_8254_ns_to_timer()
78 * Calculates the divisor value needed for a single counter to generate
81 * comedi_8254_cascade_ns_to_timer()
82 * Calculates the two divisor values needed to the generate the pacer
85 * comedi_8254_update_divisors()
86 * Transfers the intermediate divisor values to the current divisors.
88 * comedi_8254_pacer_enable()
89 * Programs the mode of the cascaded counters and writes the current
92 * To expose the counters as a subdevice for general purpose use the following
93 * functions a provided:
95 * comedi_8254_subdevice_init()
96 * Initializes a comedi_subdevice to use the 8254 timer.
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.
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:
106 * INSN_CONFIG_SET_COUNTER_MODE calls comedi_8254_set_mode()
107 * INSN_CONFIG_8254_READ_STATUS calls comedi_8254_status()
109 * The (*insn_config) member of comedi_8254 can be initialized by the external
110 * driver to handle any additional instructions.
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.
116 #include <linux/module.h>
117 #include <linux/slab.h>
118 #include <linux/io.h>
120 #include "../comedidev.h"
122 #include "comedi_8254.h"
124 static unsigned int __i8254_read(struct comedi_8254 *i8254, unsigned int reg)
126 unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift;
129 switch (i8254->iosize) {
133 val = readb(i8254->mmio + reg_offset);
135 val = inb(i8254->iobase + reg_offset);
139 val = readw(i8254->mmio + reg_offset);
141 val = inw(i8254->iobase + reg_offset);
145 val = readl(i8254->mmio + reg_offset);
147 val = inl(i8254->iobase + reg_offset);
153 static void __i8254_write(struct comedi_8254 *i8254,
154 unsigned int val, unsigned int reg)
156 unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift;
158 switch (i8254->iosize) {
162 writeb(val, i8254->mmio + reg_offset);
164 outb(val, i8254->iobase + reg_offset);
168 writew(val, i8254->mmio + reg_offset);
170 outw(val, i8254->iobase + reg_offset);
174 writel(val, i8254->mmio + reg_offset);
176 outl(val, i8254->iobase + reg_offset);
182 * comedi_8254_status - return the status of a counter
183 * @i8254: comedi_8254 struct for the timer
184 * @counter: the counter number
186 unsigned int comedi_8254_status(struct comedi_8254 *i8254, unsigned int counter)
193 cmd = I8254_CTRL_READBACK_STATUS | I8254_CTRL_READBACK_SEL_CTR(counter);
194 __i8254_write(i8254, cmd, I8254_CTRL_REG);
196 return __i8254_read(i8254, counter);
198 EXPORT_SYMBOL_GPL(comedi_8254_status);
201 * comedi_8254_read - read the current counter value
202 * @i8254: comedi_8254 struct for the timer
203 * @counter: the counter number
205 unsigned int comedi_8254_read(struct comedi_8254 *i8254, unsigned int counter)
213 __i8254_write(i8254, I8254_CTRL_SEL_CTR(counter) | I8254_CTRL_LATCH,
216 /* read LSB then MSB */
217 val = __i8254_read(i8254, counter);
218 val |= (__i8254_read(i8254, counter) << 8);
222 EXPORT_SYMBOL_GPL(comedi_8254_read);
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
230 void comedi_8254_write(struct comedi_8254 *i8254,
231 unsigned int counter, unsigned int val)
240 /* load LSB then MSB */
242 __i8254_write(i8254, byte, counter);
243 byte = (val >> 8) & 0xff;
244 __i8254_write(i8254, byte, counter);
246 EXPORT_SYMBOL_GPL(comedi_8254_write);
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
254 int comedi_8254_set_mode(struct comedi_8254 *i8254, unsigned int counter,
261 if (mode > (I8254_MODE5 | I8254_BCD))
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);
271 EXPORT_SYMBOL_GPL(comedi_8254_set_mode);
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
280 int comedi_8254_load(struct comedi_8254 *i8254, unsigned int counter,
281 unsigned int val, unsigned int mode)
287 if (mode > (I8254_MODE5 | I8254_BCD))
290 comedi_8254_set_mode(i8254, counter, mode);
291 comedi_8254_write(i8254, counter, val);
295 EXPORT_SYMBOL_GPL(comedi_8254_load);
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
304 void comedi_8254_pacer_enable(struct comedi_8254 *i8254,
305 unsigned int counter1,
306 unsigned int counter2,
311 if (counter1 > 2 || counter2 > 2 || counter1 == counter2)
315 mode = I8254_MODE2 | I8254_BINARY;
317 mode = I8254_MODE0 | I8254_BINARY;
319 comedi_8254_set_mode(i8254, counter1, mode);
320 comedi_8254_set_mode(i8254, counter2, mode);
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.
328 comedi_8254_write(i8254, counter2, i8254->divisor2);
329 comedi_8254_write(i8254, counter1, i8254->divisor1);
332 EXPORT_SYMBOL_GPL(comedi_8254_pacer_enable);
335 * comedi_8254_update_divisors - update the divisors for the cascaded counters
336 * @i8254: comedi_8254 struct for the timer
338 void comedi_8254_update_divisors(struct comedi_8254 *i8254)
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;
345 EXPORT_SYMBOL_GPL(comedi_8254_update_divisors);
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
353 void comedi_8254_cascade_ns_to_timer(struct comedi_8254 *i8254,
354 unsigned int *nanosec,
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;
369 unsigned int ns_high;
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)
381 div = *nanosec / i8254->osc_base;
382 d2 = I8254_MAX_COUNT;
386 for (d1 = start; d1 <= div / d1 + 1 && d1 <= I8254_MAX_COUNT; 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) {
395 if (ns >= *nanosec && ns < ns_lub) {
403 switch (flags & CMDF_ROUND_MASK) {
404 case CMDF_ROUND_NEAREST:
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) {
420 case CMDF_ROUND_DOWN:
426 *nanosec = d1 * d2 * i8254->osc_base;
427 i8254->next_div1 = d1;
428 i8254->next_div2 = d2;
430 EXPORT_SYMBOL_GPL(comedi_8254_cascade_ns_to_timer);
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
438 void comedi_8254_ns_to_timer(struct comedi_8254 *i8254,
439 unsigned int *nanosec, unsigned int flags)
441 unsigned int divisor;
443 switch (flags & CMDF_ROUND_MASK) {
445 case CMDF_ROUND_NEAREST:
446 divisor = DIV_ROUND_CLOSEST(*nanosec, i8254->osc_base);
449 divisor = DIV_ROUND_UP(*nanosec, i8254->osc_base);
451 case CMDF_ROUND_DOWN:
452 divisor = *nanosec / i8254->osc_base;
457 if (divisor > I8254_MAX_COUNT)
458 divisor = I8254_MAX_COUNT;
460 *nanosec = divisor * i8254->osc_base;
461 i8254->next_div = divisor;
463 EXPORT_SYMBOL_GPL(comedi_8254_ns_to_timer);
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
471 void comedi_8254_set_busy(struct comedi_8254 *i8254,
472 unsigned int counter, bool busy)
475 i8254->busy[counter] = busy;
477 EXPORT_SYMBOL_GPL(comedi_8254_set_busy);
479 static int comedi_8254_insn_read(struct comedi_device *dev,
480 struct comedi_subdevice *s,
481 struct comedi_insn *insn,
484 struct comedi_8254 *i8254 = s->private;
485 unsigned int chan = CR_CHAN(insn->chanspec);
488 if (i8254->busy[chan])
491 for (i = 0; i < insn->n; i++)
492 data[i] = comedi_8254_read(i8254, chan);
497 static int comedi_8254_insn_write(struct comedi_device *dev,
498 struct comedi_subdevice *s,
499 struct comedi_insn *insn,
502 struct comedi_8254 *i8254 = s->private;
503 unsigned int chan = CR_CHAN(insn->chanspec);
505 if (i8254->busy[chan])
509 comedi_8254_write(i8254, chan, data[insn->n - 1]);
514 static int comedi_8254_insn_config(struct comedi_device *dev,
515 struct comedi_subdevice *s,
516 struct comedi_insn *insn,
519 struct comedi_8254 *i8254 = s->private;
520 unsigned int chan = CR_CHAN(insn->chanspec);
523 if (i8254->busy[chan])
527 case INSN_CONFIG_RESET:
528 ret = comedi_8254_set_mode(i8254, chan,
529 I8254_MODE0 | I8254_BINARY);
533 case INSN_CONFIG_SET_COUNTER_MODE:
534 ret = comedi_8254_set_mode(i8254, chan, data[1]);
538 case INSN_CONFIG_8254_READ_STATUS:
539 data[1] = comedi_8254_status(i8254, chan);
543 * If available, call the driver provided (*insn_config)
544 * to handle any driver implemented instructions.
546 if (i8254->insn_config)
547 return i8254->insn_config(dev, s, insn, data);
556 * comedi_8254_subdevice_init - initialize a comedi_subdevice for the 8254 timer
557 * @s: comedi_subdevice struct
558 * @i8254: comedi_8254 struct
560 void comedi_8254_subdevice_init(struct comedi_subdevice *s,
561 struct comedi_8254 *i8254)
563 s->type = COMEDI_SUBD_COUNTER;
564 s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
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;
574 EXPORT_SYMBOL_GPL(comedi_8254_subdevice_init);
576 static struct comedi_8254 *__i8254_init(unsigned long iobase,
578 unsigned int osc_base,
580 unsigned int regshift)
582 struct comedi_8254 *i8254;
585 /* sanity check that the iosize is valid */
586 if (!(iosize == I8254_IO8 || iosize == I8254_IO16 ||
587 iosize == I8254_IO32))
590 i8254 = kzalloc(sizeof(*i8254), GFP_KERNEL);
594 i8254->iobase = iobase;
596 i8254->iosize = iosize;
597 i8254->regshift = regshift;
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;
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);
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
617 struct comedi_8254 *comedi_8254_init(unsigned long iobase,
618 unsigned int osc_base,
620 unsigned int regshift)
622 return __i8254_init(iobase, NULL, osc_base, iosize, regshift);
624 EXPORT_SYMBOL_GPL(comedi_8254_init);
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
634 struct comedi_8254 *comedi_8254_mm_init(void __iomem *mmio,
635 unsigned int osc_base,
637 unsigned int regshift)
639 return __i8254_init(0, mmio, osc_base, iosize, regshift);
641 EXPORT_SYMBOL_GPL(comedi_8254_mm_init);
643 static int __init comedi_8254_module_init(void)
647 module_init(comedi_8254_module_init);
649 static void __exit comedi_8254_module_exit(void)
652 module_exit(comedi_8254_module_exit);
654 MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
655 MODULE_DESCRIPTION("Comedi: Generic 8254 timer/counter support");
656 MODULE_LICENSE("GPL");