1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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
133static 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
162static 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
192
193
194
195unsigned 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}
207EXPORT_SYMBOL_GPL(comedi_8254_status);
208
209
210
211
212
213
214unsigned 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
222 __i8254_write(i8254, I8254_CTRL_SEL_CTR(counter) | I8254_CTRL_LATCH,
223 I8254_CTRL_REG);
224
225
226 val = __i8254_read(i8254, counter);
227 val |= (__i8254_read(i8254, counter) << 8);
228
229 return val;
230}
231EXPORT_SYMBOL_GPL(comedi_8254_read);
232
233
234
235
236
237
238
239void 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
250 byte = val & 0xff;
251 __i8254_write(i8254, byte, counter);
252 byte = (val >> 8) & 0xff;
253 __i8254_write(i8254, byte, counter);
254}
255EXPORT_SYMBOL_GPL(comedi_8254_write);
256
257
258
259
260
261
262
263int 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) |
274 I8254_CTRL_LSB_MSB |
275 mode;
276 __i8254_write(i8254, byte, I8254_CTRL_REG);
277
278 return 0;
279}
280EXPORT_SYMBOL_GPL(comedi_8254_set_mode);
281
282
283
284
285
286
287
288
289int 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}
304EXPORT_SYMBOL_GPL(comedi_8254_load);
305
306
307
308
309
310
311
312
313void 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
334
335
336
337 comedi_8254_write(i8254, counter2, i8254->divisor2);
338 comedi_8254_write(i8254, counter1, i8254->divisor1);
339 }
340}
341EXPORT_SYMBOL_GPL(comedi_8254_pacer_enable);
342
343
344
345
346
347void comedi_8254_update_divisors(struct comedi_8254 *i8254)
348{
349
350 i8254->divisor = i8254->next_div & 0xffff;
351 i8254->divisor1 = i8254->next_div1 & 0xffff;
352 i8254->divisor2 = i8254->next_div2 & 0xffff;
353}
354EXPORT_SYMBOL_GPL(comedi_8254_update_divisors);
355
356
357
358
359
360
361
362void 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
381 if (div * i8254->osc_base == *nanosec &&
382 d1 > 1 && d1 <= I8254_MAX_COUNT &&
383 d2 > 1 && d2 <= I8254_MAX_COUNT &&
384
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}
439EXPORT_SYMBOL_GPL(comedi_8254_cascade_ns_to_timer);
440
441
442
443
444
445
446
447void 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}
472EXPORT_SYMBOL_GPL(comedi_8254_ns_to_timer);
473
474
475
476
477
478
479
480void 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}
486EXPORT_SYMBOL_GPL(comedi_8254_set_busy);
487
488static 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
506static 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
523static 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
553
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
566
567
568void 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}
582EXPORT_SYMBOL_GPL(comedi_8254_subdevice_init);
583
584static 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
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
608 i8254->osc_base = osc_base ? osc_base : I8254_OSC_BASE_10MHZ;
609
610
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
619
620
621
622
623
624
625struct 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}
632EXPORT_SYMBOL_GPL(comedi_8254_init);
633
634
635
636
637
638
639
640
641
642struct 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}
649EXPORT_SYMBOL_GPL(comedi_8254_mm_init);
650
651static int __init comedi_8254_module_init(void)
652{
653 return 0;
654}
655module_init(comedi_8254_module_init);
656
657static void __exit comedi_8254_module_exit(void)
658{
659}
660module_exit(comedi_8254_module_exit);
661
662MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
663MODULE_DESCRIPTION("Comedi: Generic 8254 timer/counter support");
664MODULE_LICENSE("GPL");
665