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 | |
127 | static 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 | |
141 | static 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 | |
155 | static 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 | |
171 | static 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 | |
185 | static 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 | |
199 | static 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 | |
213 | static unsigned int __i8254_read(struct comedi_8254 *i8254, unsigned int reg) |
214 | { |
215 | return 0xff & i8254->iocb(i8254, 0, reg, 0); |
216 | } |
217 | |
218 | static 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 | */ |
229 | unsigned 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 | } |
241 | EXPORT_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 | */ |
248 | unsigned 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 | } |
265 | EXPORT_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 | */ |
273 | void 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 | } |
289 | EXPORT_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 | */ |
297 | int 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 | } |
314 | EXPORT_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 | */ |
323 | int 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 | } |
338 | EXPORT_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 | */ |
347 | void 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 | } |
375 | EXPORT_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 | */ |
381 | void 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 | } |
388 | EXPORT_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 | */ |
396 | void 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 | } |
473 | EXPORT_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 | */ |
481 | void 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 | } |
506 | EXPORT_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 | */ |
514 | void 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 | } |
520 | EXPORT_SYMBOL_GPL(comedi_8254_set_busy); |
521 | |
522 | static 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 | |
540 | static 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 | |
557 | static 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 | */ |
603 | void 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 | } |
617 | EXPORT_SYMBOL_GPL(comedi_8254_subdevice_init); |
618 | |
619 | static 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 | */ |
667 | struct 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 | } |
689 | EXPORT_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 | */ |
703 | struct 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 | } |
725 | EXPORT_SYMBOL_GPL(comedi_8254_mm_alloc); |
726 | |
727 | static int __init comedi_8254_module_init(void) |
728 | { |
729 | return 0; |
730 | } |
731 | module_init(comedi_8254_module_init); |
732 | |
733 | static void __exit comedi_8254_module_exit(void) |
734 | { |
735 | } |
736 | module_exit(comedi_8254_module_exit); |
737 | |
738 | MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>" ); |
739 | MODULE_DESCRIPTION("Comedi: Generic 8254 timer/counter support" ); |
740 | MODULE_LICENSE("GPL" ); |
741 | |