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#include <linux/module.h>
34#include <linux/gfp.h>
35#include <linux/delay.h>
36#include <linux/io.h>
37#include <linux/interrupt.h>
38
39#include "../comedidev.h"
40
41#include "comedi_isadma.h"
42#include "comedi_8254.h"
43
44
45
46
47#define PCL816_DO_DI_LSB_REG 0x00
48#define PCL816_DO_DI_MSB_REG 0x01
49#define PCL816_TIMER_BASE 0x04
50#define PCL816_AI_LSB_REG 0x08
51#define PCL816_AI_MSB_REG 0x09
52#define PCL816_RANGE_REG 0x09
53#define PCL816_CLRINT_REG 0x0a
54#define PCL816_MUX_REG 0x0b
55#define PCL816_MUX_SCAN(_first, _last) (((_last) << 4) | (_first))
56#define PCL816_CTRL_REG 0x0c
57#define PCL816_CTRL_SOFT_TRIG BIT(0)
58#define PCL816_CTRL_PACER_TRIG BIT(1)
59#define PCL816_CTRL_EXT_TRIG BIT(2)
60#define PCL816_CTRL_POE BIT(3)
61#define PCL816_CTRL_DMAEN BIT(4)
62#define PCL816_CTRL_INTEN BIT(5)
63#define PCL816_CTRL_DMASRC_SLOT(x) (((x) & 0x3) << 6)
64#define PCL816_STATUS_REG 0x0d
65#define PCL816_STATUS_NEXT_CHAN_MASK (0xf << 0)
66#define PCL816_STATUS_INTSRC_SLOT(x) (((x) & 0x3) << 4)
67#define PCL816_STATUS_INTSRC_DMA PCL816_STATUS_INTSRC_SLOT(3)
68#define PCL816_STATUS_INTSRC_MASK PCL816_STATUS_INTSRC_SLOT(3)
69#define PCL816_STATUS_INTACT BIT(6)
70#define PCL816_STATUS_DRDY BIT(7)
71
72#define MAGIC_DMA_WORD 0x5a5a
73
74static const struct comedi_lrange range_pcl816 = {
75 8, {
76 BIP_RANGE(10),
77 BIP_RANGE(5),
78 BIP_RANGE(2.5),
79 BIP_RANGE(1.25),
80 UNI_RANGE(10),
81 UNI_RANGE(5),
82 UNI_RANGE(2.5),
83 UNI_RANGE(1.25)
84 }
85};
86
87struct pcl816_board {
88 const char *name;
89 int ai_maxdata;
90 int ai_chanlist;
91};
92
93static const struct pcl816_board boardtypes[] = {
94 {
95 .name = "pcl816",
96 .ai_maxdata = 0xffff,
97 .ai_chanlist = 1024,
98 }, {
99 .name = "pcl814b",
100 .ai_maxdata = 0x3fff,
101 .ai_chanlist = 1024,
102 },
103};
104
105struct pcl816_private {
106 struct comedi_isadma *dma;
107 unsigned int ai_poll_ptr;
108 unsigned int ai_cmd_running:1;
109 unsigned int ai_cmd_canceled:1;
110};
111
112static void pcl816_ai_setup_dma(struct comedi_device *dev,
113 struct comedi_subdevice *s,
114 unsigned int unread_samples)
115{
116 struct pcl816_private *devpriv = dev->private;
117 struct comedi_isadma *dma = devpriv->dma;
118 struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
119 unsigned int max_samples = comedi_bytes_to_samples(s, desc->maxsize);
120 unsigned int nsamples;
121
122 comedi_isadma_disable(dma->chan);
123
124
125
126
127
128 nsamples = comedi_nsamples_left(s, max_samples + unread_samples);
129 if (nsamples > unread_samples) {
130 nsamples -= unread_samples;
131 desc->size = comedi_samples_to_bytes(s, nsamples);
132 comedi_isadma_program(desc);
133 }
134}
135
136static void pcl816_ai_set_chan_range(struct comedi_device *dev,
137 unsigned int chan,
138 unsigned int range)
139{
140 outb(chan, dev->iobase + PCL816_MUX_REG);
141 outb(range, dev->iobase + PCL816_RANGE_REG);
142}
143
144static void pcl816_ai_set_chan_scan(struct comedi_device *dev,
145 unsigned int first_chan,
146 unsigned int last_chan)
147{
148 outb(PCL816_MUX_SCAN(first_chan, last_chan),
149 dev->iobase + PCL816_MUX_REG);
150}
151
152static void pcl816_ai_setup_chanlist(struct comedi_device *dev,
153 unsigned int *chanlist,
154 unsigned int seglen)
155{
156 unsigned int first_chan = CR_CHAN(chanlist[0]);
157 unsigned int last_chan;
158 unsigned int range;
159 unsigned int i;
160
161
162 for (i = 0; i < seglen; i++) {
163 last_chan = CR_CHAN(chanlist[i]);
164 range = CR_RANGE(chanlist[i]);
165
166 pcl816_ai_set_chan_range(dev, last_chan, range);
167 }
168
169 udelay(1);
170
171 pcl816_ai_set_chan_scan(dev, first_chan, last_chan);
172}
173
174static void pcl816_ai_clear_eoc(struct comedi_device *dev)
175{
176
177 outb(0, dev->iobase + PCL816_CLRINT_REG);
178}
179
180static void pcl816_ai_soft_trig(struct comedi_device *dev)
181{
182
183 outb(0, dev->iobase + PCL816_AI_LSB_REG);
184}
185
186static unsigned int pcl816_ai_get_sample(struct comedi_device *dev,
187 struct comedi_subdevice *s)
188{
189 unsigned int val;
190
191 val = inb(dev->iobase + PCL816_AI_MSB_REG) << 8;
192 val |= inb(dev->iobase + PCL816_AI_LSB_REG);
193
194 return val & s->maxdata;
195}
196
197static int pcl816_ai_eoc(struct comedi_device *dev,
198 struct comedi_subdevice *s,
199 struct comedi_insn *insn,
200 unsigned long context)
201{
202 unsigned int status;
203
204 status = inb(dev->iobase + PCL816_STATUS_REG);
205 if ((status & PCL816_STATUS_DRDY) == 0)
206 return 0;
207 return -EBUSY;
208}
209
210static bool pcl816_ai_next_chan(struct comedi_device *dev,
211 struct comedi_subdevice *s)
212{
213 struct comedi_cmd *cmd = &s->async->cmd;
214
215 if (cmd->stop_src == TRIG_COUNT &&
216 s->async->scans_done >= cmd->stop_arg) {
217 s->async->events |= COMEDI_CB_EOA;
218 return false;
219 }
220
221 return true;
222}
223
224static void transfer_from_dma_buf(struct comedi_device *dev,
225 struct comedi_subdevice *s,
226 unsigned short *ptr,
227 unsigned int bufptr, unsigned int len)
228{
229 unsigned short val;
230 int i;
231
232 for (i = 0; i < len; i++) {
233 val = ptr[bufptr++];
234 comedi_buf_write_samples(s, &val, 1);
235
236 if (!pcl816_ai_next_chan(dev, s))
237 return;
238 }
239}
240
241static irqreturn_t pcl816_interrupt(int irq, void *d)
242{
243 struct comedi_device *dev = d;
244 struct comedi_subdevice *s = dev->read_subdev;
245 struct pcl816_private *devpriv = dev->private;
246 struct comedi_isadma *dma = devpriv->dma;
247 struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma];
248 unsigned int nsamples;
249 unsigned int bufptr;
250
251 if (!dev->attached || !devpriv->ai_cmd_running) {
252 pcl816_ai_clear_eoc(dev);
253 return IRQ_HANDLED;
254 }
255
256 if (devpriv->ai_cmd_canceled) {
257 devpriv->ai_cmd_canceled = 0;
258 pcl816_ai_clear_eoc(dev);
259 return IRQ_HANDLED;
260 }
261
262 nsamples = comedi_bytes_to_samples(s, desc->size) -
263 devpriv->ai_poll_ptr;
264 bufptr = devpriv->ai_poll_ptr;
265 devpriv->ai_poll_ptr = 0;
266
267
268 dma->cur_dma = 1 - dma->cur_dma;
269 pcl816_ai_setup_dma(dev, s, nsamples);
270
271 transfer_from_dma_buf(dev, s, desc->virt_addr, bufptr, nsamples);
272
273 pcl816_ai_clear_eoc(dev);
274
275 comedi_handle_events(dev, s);
276 return IRQ_HANDLED;
277}
278
279static int check_channel_list(struct comedi_device *dev,
280 struct comedi_subdevice *s,
281 unsigned int *chanlist,
282 unsigned int chanlen)
283{
284 unsigned int chansegment[16];
285 unsigned int i, nowmustbechan, seglen;
286
287
288 if (chanlen < 1) {
289 dev_err(dev->class_dev, "range/channel list is empty!\n");
290 return 0;
291 }
292
293 if (chanlen > 1) {
294
295 chansegment[0] = chanlist[0];
296 for (i = 1, seglen = 1; i < chanlen; i++, seglen++) {
297
298 if (chanlist[0] == chanlist[i])
299 break;
300 nowmustbechan =
301 (CR_CHAN(chansegment[i - 1]) + 1) % chanlen;
302 if (nowmustbechan != CR_CHAN(chanlist[i])) {
303
304 dev_dbg(dev->class_dev,
305 "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n",
306 i, CR_CHAN(chanlist[i]), nowmustbechan,
307 CR_CHAN(chanlist[0]));
308 return 0;
309 }
310
311 chansegment[i] = chanlist[i];
312 }
313
314
315 for (i = 0; i < chanlen; i++) {
316 if (chanlist[i] != chansegment[i % seglen]) {
317 dev_dbg(dev->class_dev,
318 "bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n",
319 i, CR_CHAN(chansegment[i]),
320 CR_RANGE(chansegment[i]),
321 CR_AREF(chansegment[i]),
322 CR_CHAN(chanlist[i % seglen]),
323 CR_RANGE(chanlist[i % seglen]),
324 CR_AREF(chansegment[i % seglen]));
325 return 0;
326 }
327 }
328 } else {
329 seglen = 1;
330 }
331
332 return seglen;
333}
334
335static int pcl816_ai_cmdtest(struct comedi_device *dev,
336 struct comedi_subdevice *s, struct comedi_cmd *cmd)
337{
338 int err = 0;
339
340
341
342 err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
343 err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
344 err |= comedi_check_trigger_src(&cmd->convert_src,
345 TRIG_EXT | TRIG_TIMER);
346 err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
347 err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
348
349 if (err)
350 return 1;
351
352
353
354 err |= comedi_check_trigger_is_unique(cmd->convert_src);
355 err |= comedi_check_trigger_is_unique(cmd->stop_src);
356
357
358
359 if (err)
360 return 2;
361
362
363
364 err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
365 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
366
367 if (cmd->convert_src == TRIG_TIMER)
368 err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000);
369 else
370 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
371
372 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
373 cmd->chanlist_len);
374
375 if (cmd->stop_src == TRIG_COUNT)
376 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
377 else
378 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
379
380 if (err)
381 return 3;
382
383
384 if (cmd->convert_src == TRIG_TIMER) {
385 unsigned int arg = cmd->convert_arg;
386
387 comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
388 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
389 }
390
391 if (err)
392 return 4;
393
394
395
396 if (cmd->chanlist) {
397 if (!check_channel_list(dev, s, cmd->chanlist,
398 cmd->chanlist_len))
399 return 5;
400 }
401
402 return 0;
403}
404
405static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
406{
407 struct pcl816_private *devpriv = dev->private;
408 struct comedi_isadma *dma = devpriv->dma;
409 struct comedi_cmd *cmd = &s->async->cmd;
410 unsigned int ctrl;
411 unsigned int seglen;
412
413 if (devpriv->ai_cmd_running)
414 return -EBUSY;
415
416 seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len);
417 if (seglen < 1)
418 return -EINVAL;
419 pcl816_ai_setup_chanlist(dev, cmd->chanlist, seglen);
420 udelay(1);
421
422 devpriv->ai_cmd_running = 1;
423 devpriv->ai_poll_ptr = 0;
424 devpriv->ai_cmd_canceled = 0;
425
426
427 dma->cur_dma = 0;
428 pcl816_ai_setup_dma(dev, s, 0);
429
430 comedi_8254_set_mode(dev->pacer, 0, I8254_MODE1 | I8254_BINARY);
431 comedi_8254_write(dev->pacer, 0, 0x0ff);
432 udelay(1);
433 comedi_8254_update_divisors(dev->pacer);
434 comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
435
436 ctrl = PCL816_CTRL_INTEN | PCL816_CTRL_DMAEN |
437 PCL816_CTRL_DMASRC_SLOT(0);
438 if (cmd->convert_src == TRIG_TIMER)
439 ctrl |= PCL816_CTRL_PACER_TRIG;
440 else
441 ctrl |= PCL816_CTRL_EXT_TRIG;
442
443 outb(ctrl, dev->iobase + PCL816_CTRL_REG);
444 outb((dma->chan << 4) | dev->irq,
445 dev->iobase + PCL816_STATUS_REG);
446
447 return 0;
448}
449
450static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
451{
452 struct pcl816_private *devpriv = dev->private;
453 struct comedi_isadma *dma = devpriv->dma;
454 struct comedi_isadma_desc *desc;
455 unsigned long flags;
456 unsigned int poll;
457 int ret;
458
459 spin_lock_irqsave(&dev->spinlock, flags);
460
461 poll = comedi_isadma_poll(dma);
462 poll = comedi_bytes_to_samples(s, poll);
463 if (poll > devpriv->ai_poll_ptr) {
464 desc = &dma->desc[dma->cur_dma];
465 transfer_from_dma_buf(dev, s, desc->virt_addr,
466 devpriv->ai_poll_ptr,
467 poll - devpriv->ai_poll_ptr);
468
469 devpriv->ai_poll_ptr = poll;
470
471 comedi_handle_events(dev, s);
472
473 ret = comedi_buf_n_bytes_ready(s);
474 } else {
475
476 ret = 0;
477 }
478 spin_unlock_irqrestore(&dev->spinlock, flags);
479
480 return ret;
481}
482
483static int pcl816_ai_cancel(struct comedi_device *dev,
484 struct comedi_subdevice *s)
485{
486 struct pcl816_private *devpriv = dev->private;
487
488 if (!devpriv->ai_cmd_running)
489 return 0;
490
491 outb(0, dev->iobase + PCL816_CTRL_REG);
492 pcl816_ai_clear_eoc(dev);
493
494 comedi_8254_pacer_enable(dev->pacer, 1, 2, false);
495
496 devpriv->ai_cmd_running = 0;
497 devpriv->ai_cmd_canceled = 1;
498
499 return 0;
500}
501
502static int pcl816_ai_insn_read(struct comedi_device *dev,
503 struct comedi_subdevice *s,
504 struct comedi_insn *insn,
505 unsigned int *data)
506{
507 unsigned int chan = CR_CHAN(insn->chanspec);
508 unsigned int range = CR_RANGE(insn->chanspec);
509 int ret = 0;
510 int i;
511
512 outb(PCL816_CTRL_SOFT_TRIG, dev->iobase + PCL816_CTRL_REG);
513
514 pcl816_ai_set_chan_range(dev, chan, range);
515 pcl816_ai_set_chan_scan(dev, chan, chan);
516
517 for (i = 0; i < insn->n; i++) {
518 pcl816_ai_clear_eoc(dev);
519 pcl816_ai_soft_trig(dev);
520
521 ret = comedi_timeout(dev, s, insn, pcl816_ai_eoc, 0);
522 if (ret)
523 break;
524
525 data[i] = pcl816_ai_get_sample(dev, s);
526 }
527 outb(0, dev->iobase + PCL816_CTRL_REG);
528 pcl816_ai_clear_eoc(dev);
529
530 return ret ? ret : insn->n;
531}
532
533static int pcl816_di_insn_bits(struct comedi_device *dev,
534 struct comedi_subdevice *s,
535 struct comedi_insn *insn,
536 unsigned int *data)
537{
538 data[1] = inb(dev->iobase + PCL816_DO_DI_LSB_REG) |
539 (inb(dev->iobase + PCL816_DO_DI_MSB_REG) << 8);
540
541 return insn->n;
542}
543
544static int pcl816_do_insn_bits(struct comedi_device *dev,
545 struct comedi_subdevice *s,
546 struct comedi_insn *insn,
547 unsigned int *data)
548{
549 if (comedi_dio_update_state(s, data)) {
550 outb(s->state & 0xff, dev->iobase + PCL816_DO_DI_LSB_REG);
551 outb((s->state >> 8), dev->iobase + PCL816_DO_DI_MSB_REG);
552 }
553
554 data[1] = s->state;
555
556 return insn->n;
557}
558
559static void pcl816_reset(struct comedi_device *dev)
560{
561 outb(0, dev->iobase + PCL816_CTRL_REG);
562 pcl816_ai_set_chan_range(dev, 0, 0);
563 pcl816_ai_clear_eoc(dev);
564
565
566 outb(0, dev->iobase + PCL816_DO_DI_LSB_REG);
567 outb(0, dev->iobase + PCL816_DO_DI_MSB_REG);
568}
569
570static void pcl816_alloc_irq_and_dma(struct comedi_device *dev,
571 struct comedi_devconfig *it)
572{
573 struct pcl816_private *devpriv = dev->private;
574 unsigned int irq_num = it->options[1];
575 unsigned int dma_chan = it->options[2];
576
577
578 if (!(irq_num >= 2 && irq_num <= 7) ||
579 !(dma_chan == 3 || dma_chan == 1))
580 return;
581
582 if (request_irq(irq_num, pcl816_interrupt, 0, dev->board_name, dev))
583 return;
584
585
586 devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan,
587 PAGE_SIZE * 4, COMEDI_ISADMA_READ);
588 if (!devpriv->dma)
589 free_irq(irq_num, dev);
590 else
591 dev->irq = irq_num;
592}
593
594static void pcl816_free_dma(struct comedi_device *dev)
595{
596 struct pcl816_private *devpriv = dev->private;
597
598 if (devpriv)
599 comedi_isadma_free(devpriv->dma);
600}
601
602static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it)
603{
604 const struct pcl816_board *board = dev->board_ptr;
605 struct pcl816_private *devpriv;
606 struct comedi_subdevice *s;
607 int ret;
608
609 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
610 if (!devpriv)
611 return -ENOMEM;
612
613 ret = comedi_request_region(dev, it->options[0], 0x10);
614 if (ret)
615 return ret;
616
617
618 pcl816_alloc_irq_and_dma(dev, it);
619
620 dev->pacer = comedi_8254_init(dev->iobase + PCL816_TIMER_BASE,
621 I8254_OSC_BASE_10MHZ, I8254_IO8, 0);
622 if (!dev->pacer)
623 return -ENOMEM;
624
625 ret = comedi_alloc_subdevices(dev, 4);
626 if (ret)
627 return ret;
628
629 s = &dev->subdevices[0];
630 s->type = COMEDI_SUBD_AI;
631 s->subdev_flags = SDF_CMD_READ | SDF_DIFF;
632 s->n_chan = 16;
633 s->maxdata = board->ai_maxdata;
634 s->range_table = &range_pcl816;
635 s->insn_read = pcl816_ai_insn_read;
636 if (dev->irq) {
637 dev->read_subdev = s;
638 s->subdev_flags |= SDF_CMD_READ;
639 s->len_chanlist = board->ai_chanlist;
640 s->do_cmdtest = pcl816_ai_cmdtest;
641 s->do_cmd = pcl816_ai_cmd;
642 s->poll = pcl816_ai_poll;
643 s->cancel = pcl816_ai_cancel;
644 }
645
646
647 s = &dev->subdevices[1];
648 s->type = COMEDI_SUBD_UNUSED;
649
650
651 s = &dev->subdevices[2];
652 s->type = COMEDI_SUBD_DI;
653 s->subdev_flags = SDF_READABLE;
654 s->n_chan = 16;
655 s->maxdata = 1;
656 s->range_table = &range_digital;
657 s->insn_bits = pcl816_di_insn_bits;
658
659
660 s = &dev->subdevices[3];
661 s->type = COMEDI_SUBD_DO;
662 s->subdev_flags = SDF_WRITABLE;
663 s->n_chan = 16;
664 s->maxdata = 1;
665 s->range_table = &range_digital;
666 s->insn_bits = pcl816_do_insn_bits;
667
668 pcl816_reset(dev);
669
670 return 0;
671}
672
673static void pcl816_detach(struct comedi_device *dev)
674{
675 if (dev->private) {
676 pcl816_ai_cancel(dev, dev->read_subdev);
677 pcl816_reset(dev);
678 }
679 pcl816_free_dma(dev);
680 comedi_legacy_detach(dev);
681}
682
683static struct comedi_driver pcl816_driver = {
684 .driver_name = "pcl816",
685 .module = THIS_MODULE,
686 .attach = pcl816_attach,
687 .detach = pcl816_detach,
688 .board_name = &boardtypes[0].name,
689 .num_names = ARRAY_SIZE(boardtypes),
690 .offset = sizeof(struct pcl816_board),
691};
692module_comedi_driver(pcl816_driver);
693
694MODULE_AUTHOR("Comedi https://www.comedi.org");
695MODULE_DESCRIPTION("Comedi low-level driver");
696MODULE_LICENSE("GPL");
697