1 // SPDX-License-Identifier: GPL-2.0
4 * Comedi driver for Advantech PCL-816 cards
6 * Author: Juan Grigera <juan@grigera.com.ar>
7 * based on pcl818 by Michal Dobes <dobes@tesnet.cz> and bits of pcl812
12 * Description: Advantech PCL-816 cards, PCL-814
13 * Devices: [Advantech] PCL-816 (pcl816), PCL-814B (pcl814b)
14 * Author: Juan Grigera <juan@grigera.com.ar>
16 * Updated: Tue, 2 Apr 2002 23:15:21 -0800
18 * PCL 816 and 814B have 16 SE/DIFF ADCs, 16 DACs, 16 DI and 16 DO.
19 * Differences are at resolution (16 vs 12 bits).
21 * The driver support AI command mode, other subdevices not written.
23 * Analog output and digital input and output are not supported.
25 * Configuration Options:
27 * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
28 * [2] - DMA (0=disable, 1, 3)
29 * [3] - 0, 10=10MHz clock for 8254
30 * 1= 1MHz clock for 8254
33 #include <linux/module.h>
34 #include <linux/gfp.h>
35 #include <linux/delay.h>
37 #include <linux/interrupt.h>
39 #include "../comedidev.h"
41 #include "comedi_isadma.h"
42 #include "comedi_8254.h"
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)
72 #define MAGIC_DMA_WORD 0x5a5a
74 static const struct comedi_lrange range_pcl816 = {
93 static const struct pcl816_board boardtypes[] = {
100 .ai_maxdata = 0x3fff,
105 struct pcl816_private {
106 struct comedi_isadma *dma;
107 unsigned int ai_poll_ptr; /* how many sampes transfer poll */
108 unsigned int ai_cmd_running:1;
109 unsigned int ai_cmd_canceled:1;
112 static void pcl816_ai_setup_dma(struct comedi_device *dev,
113 struct comedi_subdevice *s,
114 unsigned int unread_samples)
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;
122 comedi_isadma_disable(dma->chan);
125 * Determine dma size based on the buffer maxsize plus the number of
126 * unread samples and the number of samples remaining in the command.
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);
136 static void pcl816_ai_set_chan_range(struct comedi_device *dev,
140 outb(chan, dev->iobase + PCL816_MUX_REG);
141 outb(range, dev->iobase + PCL816_RANGE_REG);
144 static void pcl816_ai_set_chan_scan(struct comedi_device *dev,
145 unsigned int first_chan,
146 unsigned int last_chan)
148 outb(PCL816_MUX_SCAN(first_chan, last_chan),
149 dev->iobase + PCL816_MUX_REG);
152 static void pcl816_ai_setup_chanlist(struct comedi_device *dev,
153 unsigned int *chanlist,
156 unsigned int first_chan = CR_CHAN(chanlist[0]);
157 unsigned int last_chan;
161 /* store range list to card */
162 for (i = 0; i < seglen; i++) {
163 last_chan = CR_CHAN(chanlist[i]);
164 range = CR_RANGE(chanlist[i]);
166 pcl816_ai_set_chan_range(dev, last_chan, range);
171 pcl816_ai_set_chan_scan(dev, first_chan, last_chan);
174 static void pcl816_ai_clear_eoc(struct comedi_device *dev)
176 /* writing any value clears the interrupt request */
177 outb(0, dev->iobase + PCL816_CLRINT_REG);
180 static void pcl816_ai_soft_trig(struct comedi_device *dev)
182 /* writing any value triggers a software conversion */
183 outb(0, dev->iobase + PCL816_AI_LSB_REG);
186 static unsigned int pcl816_ai_get_sample(struct comedi_device *dev,
187 struct comedi_subdevice *s)
191 val = inb(dev->iobase + PCL816_AI_MSB_REG) << 8;
192 val |= inb(dev->iobase + PCL816_AI_LSB_REG);
194 return val & s->maxdata;
197 static int pcl816_ai_eoc(struct comedi_device *dev,
198 struct comedi_subdevice *s,
199 struct comedi_insn *insn,
200 unsigned long context)
204 status = inb(dev->iobase + PCL816_STATUS_REG);
205 if ((status & PCL816_STATUS_DRDY) == 0)
210 static bool pcl816_ai_next_chan(struct comedi_device *dev,
211 struct comedi_subdevice *s)
213 struct comedi_cmd *cmd = &s->async->cmd;
215 if (cmd->stop_src == TRIG_COUNT &&
216 s->async->scans_done >= cmd->stop_arg) {
217 s->async->events |= COMEDI_CB_EOA;
224 static void transfer_from_dma_buf(struct comedi_device *dev,
225 struct comedi_subdevice *s,
227 unsigned int bufptr, unsigned int len)
232 for (i = 0; i < len; i++) {
234 comedi_buf_write_samples(s, &val, 1);
236 if (!pcl816_ai_next_chan(dev, s))
241 static irqreturn_t pcl816_interrupt(int irq, void *d)
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;
251 if (!dev->attached || !devpriv->ai_cmd_running) {
252 pcl816_ai_clear_eoc(dev);
256 if (devpriv->ai_cmd_canceled) {
257 devpriv->ai_cmd_canceled = 0;
258 pcl816_ai_clear_eoc(dev);
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;
267 /* restart dma with the next buffer */
268 dma->cur_dma = 1 - dma->cur_dma;
269 pcl816_ai_setup_dma(dev, s, nsamples);
271 transfer_from_dma_buf(dev, s, desc->virt_addr, bufptr, nsamples);
273 pcl816_ai_clear_eoc(dev);
275 comedi_handle_events(dev, s);
279 static int check_channel_list(struct comedi_device *dev,
280 struct comedi_subdevice *s,
281 unsigned int *chanlist,
282 unsigned int chanlen)
284 unsigned int chansegment[16];
285 unsigned int i, nowmustbechan, seglen;
287 /* correct channel and range number check itself comedi/range.c */
289 dev_err(dev->class_dev, "range/channel list is empty!\n");
294 /* first channel is every time ok */
295 chansegment[0] = chanlist[0];
296 for (i = 1, seglen = 1; i < chanlen; i++, seglen++) {
297 /* we detect loop, this must by finish */
298 if (chanlist[0] == chanlist[i])
301 (CR_CHAN(chansegment[i - 1]) + 1) % chanlen;
302 if (nowmustbechan != CR_CHAN(chanlist[i])) {
303 /* channel list isn't continuous :-( */
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]));
310 /* well, this is next correct channel in list */
311 chansegment[i] = chanlist[i];
314 /* check whole chanlist */
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; /* chan/gain list is strange */
332 return seglen; /* we can serve this with MUX logic */
335 static int pcl816_ai_cmdtest(struct comedi_device *dev,
336 struct comedi_subdevice *s, struct comedi_cmd *cmd)
340 /* Step 1 : check if triggers are trivially valid */
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);
352 /* Step 2a : make sure trigger sources are unique */
354 err |= comedi_check_trigger_is_unique(cmd->convert_src);
355 err |= comedi_check_trigger_is_unique(cmd->stop_src);
357 /* Step 2b : and mutually compatible */
362 /* Step 3: check if arguments are trivially valid */
364 err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
365 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
367 if (cmd->convert_src == TRIG_TIMER)
368 err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000);
370 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
372 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
375 if (cmd->stop_src == TRIG_COUNT)
376 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
378 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
383 /* step 4: fix up any arguments */
384 if (cmd->convert_src == TRIG_TIMER) {
385 unsigned int arg = cmd->convert_arg;
387 comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
388 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
394 /* step 5: complain about special chanlist considerations */
397 if (!check_channel_list(dev, s, cmd->chanlist,
399 return 5; /* incorrect channels list */
405 static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
407 struct pcl816_private *devpriv = dev->private;
408 struct comedi_isadma *dma = devpriv->dma;
409 struct comedi_cmd *cmd = &s->async->cmd;
413 if (devpriv->ai_cmd_running)
416 seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len);
419 pcl816_ai_setup_chanlist(dev, cmd->chanlist, seglen);
422 devpriv->ai_cmd_running = 1;
423 devpriv->ai_poll_ptr = 0;
424 devpriv->ai_cmd_canceled = 0;
426 /* setup and enable dma for the first buffer */
428 pcl816_ai_setup_dma(dev, s, 0);
430 comedi_8254_set_mode(dev->pacer, 0, I8254_MODE1 | I8254_BINARY);
431 comedi_8254_write(dev->pacer, 0, 0x0ff);
433 comedi_8254_update_divisors(dev->pacer);
434 comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
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;
441 ctrl |= PCL816_CTRL_EXT_TRIG;
443 outb(ctrl, dev->iobase + PCL816_CTRL_REG);
444 outb((dma->chan << 4) | dev->irq,
445 dev->iobase + PCL816_STATUS_REG);
450 static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
452 struct pcl816_private *devpriv = dev->private;
453 struct comedi_isadma *dma = devpriv->dma;
454 struct comedi_isadma_desc *desc;
459 spin_lock_irqsave(&dev->spinlock, flags);
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 /* new buffer position */
469 devpriv->ai_poll_ptr = poll;
471 comedi_handle_events(dev, s);
473 ret = comedi_buf_n_bytes_ready(s);
478 spin_unlock_irqrestore(&dev->spinlock, flags);
483 static int pcl816_ai_cancel(struct comedi_device *dev,
484 struct comedi_subdevice *s)
486 struct pcl816_private *devpriv = dev->private;
488 if (!devpriv->ai_cmd_running)
491 outb(0, dev->iobase + PCL816_CTRL_REG);
492 pcl816_ai_clear_eoc(dev);
494 comedi_8254_pacer_enable(dev->pacer, 1, 2, false);
496 devpriv->ai_cmd_running = 0;
497 devpriv->ai_cmd_canceled = 1;
502 static int pcl816_ai_insn_read(struct comedi_device *dev,
503 struct comedi_subdevice *s,
504 struct comedi_insn *insn,
507 unsigned int chan = CR_CHAN(insn->chanspec);
508 unsigned int range = CR_RANGE(insn->chanspec);
512 outb(PCL816_CTRL_SOFT_TRIG, dev->iobase + PCL816_CTRL_REG);
514 pcl816_ai_set_chan_range(dev, chan, range);
515 pcl816_ai_set_chan_scan(dev, chan, chan);
517 for (i = 0; i < insn->n; i++) {
518 pcl816_ai_clear_eoc(dev);
519 pcl816_ai_soft_trig(dev);
521 ret = comedi_timeout(dev, s, insn, pcl816_ai_eoc, 0);
525 data[i] = pcl816_ai_get_sample(dev, s);
527 outb(0, dev->iobase + PCL816_CTRL_REG);
528 pcl816_ai_clear_eoc(dev);
530 return ret ? ret : insn->n;
533 static int pcl816_di_insn_bits(struct comedi_device *dev,
534 struct comedi_subdevice *s,
535 struct comedi_insn *insn,
538 data[1] = inb(dev->iobase + PCL816_DO_DI_LSB_REG) |
539 (inb(dev->iobase + PCL816_DO_DI_MSB_REG) << 8);
544 static int pcl816_do_insn_bits(struct comedi_device *dev,
545 struct comedi_subdevice *s,
546 struct comedi_insn *insn,
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);
559 static void pcl816_reset(struct comedi_device *dev)
561 outb(0, dev->iobase + PCL816_CTRL_REG);
562 pcl816_ai_set_chan_range(dev, 0, 0);
563 pcl816_ai_clear_eoc(dev);
565 /* set all digital outputs low */
566 outb(0, dev->iobase + PCL816_DO_DI_LSB_REG);
567 outb(0, dev->iobase + PCL816_DO_DI_MSB_REG);
570 static void pcl816_alloc_irq_and_dma(struct comedi_device *dev,
571 struct comedi_devconfig *it)
573 struct pcl816_private *devpriv = dev->private;
574 unsigned int irq_num = it->options[1];
575 unsigned int dma_chan = it->options[2];
577 /* only IRQs 2-7 and DMA channels 3 and 1 are valid */
578 if (!(irq_num >= 2 && irq_num <= 7) ||
579 !(dma_chan == 3 || dma_chan == 1))
582 if (request_irq(irq_num, pcl816_interrupt, 0, dev->board_name, dev))
585 /* DMA uses two 16K buffers */
586 devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan,
587 PAGE_SIZE * 4, COMEDI_ISADMA_READ);
589 free_irq(irq_num, dev);
594 static void pcl816_free_dma(struct comedi_device *dev)
596 struct pcl816_private *devpriv = dev->private;
599 comedi_isadma_free(devpriv->dma);
602 static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it)
604 const struct pcl816_board *board = dev->board_ptr;
605 struct pcl816_private *devpriv;
606 struct comedi_subdevice *s;
609 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
613 ret = comedi_request_region(dev, it->options[0], 0x10);
617 /* an IRQ and DMA are required to support async commands */
618 pcl816_alloc_irq_and_dma(dev, it);
620 dev->pacer = comedi_8254_init(dev->iobase + PCL816_TIMER_BASE,
621 I8254_OSC_BASE_10MHZ, I8254_IO8, 0);
625 ret = comedi_alloc_subdevices(dev, 4);
629 s = &dev->subdevices[0];
630 s->type = COMEDI_SUBD_AI;
631 s->subdev_flags = SDF_CMD_READ | SDF_DIFF;
633 s->maxdata = board->ai_maxdata;
634 s->range_table = &range_pcl816;
635 s->insn_read = pcl816_ai_insn_read;
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;
646 /* Piggyback Slot1 subdevice */
647 s = &dev->subdevices[1];
648 s->type = COMEDI_SUBD_UNUSED;
650 /* Digital Input subdevice */
651 s = &dev->subdevices[2];
652 s->type = COMEDI_SUBD_DI;
653 s->subdev_flags = SDF_READABLE;
656 s->range_table = &range_digital;
657 s->insn_bits = pcl816_di_insn_bits;
659 /* Digital Output subdevice */
660 s = &dev->subdevices[3];
661 s->type = COMEDI_SUBD_DO;
662 s->subdev_flags = SDF_WRITABLE;
665 s->range_table = &range_digital;
666 s->insn_bits = pcl816_do_insn_bits;
673 static void pcl816_detach(struct comedi_device *dev)
676 pcl816_ai_cancel(dev, dev->read_subdev);
679 pcl816_free_dma(dev);
680 comedi_legacy_detach(dev);
683 static 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),
692 module_comedi_driver(pcl816_driver);
694 MODULE_AUTHOR("Comedi https://www.comedi.org");
695 MODULE_DESCRIPTION("Comedi low-level driver");
696 MODULE_LICENSE("GPL");