2 comedi/drivers/pcl816.c
4 Author: Juan Grigera <juan@grigera.com.ar>
5 based on pcl818 by Michal Dobes <dobes@tesnet.cz> and bits of pcl812
7 hardware driver for Advantech cards:
13 Description: Advantech PCL-816 cards, PCL-814
14 Author: Juan Grigera <juan@grigera.com.ar>
15 Devices: [Advantech] PCL-816 (pcl816), PCL-814B (pcl814b)
17 Updated: Tue, 2 Apr 2002 23:15:21 -0800
19 PCL 816 and 814B have 16 SE/DIFF ADCs, 16 DACs, 16 DI and 16 DO.
20 Differences are at resolution (16 vs 12 bits).
22 The driver support AI command mode, other subdevices not written.
24 Analog output and digital input and output are not supported.
26 Configuration Options:
28 [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
29 [2] - DMA (0=disable, 1, 3)
30 [3] - 0, 10=10MHz clock for 8254
31 1= 1MHz clock for 8254
35 #include <linux/module.h>
36 #include "../comedidev.h"
38 #include <linux/gfp.h>
39 #include <linux/delay.h>
41 #include <linux/interrupt.h>
44 #include "comedi_fc.h"
50 #define PCL816_DO_DI_LSB_REG 0x00
51 #define PCL816_DO_DI_MSB_REG 0x01
52 #define PCL816_TIMER_BASE 0x04
53 #define PCL816_AI_LSB_REG 0x08
54 #define PCL816_AI_MSB_REG 0x09
55 #define PCL816_RANGE_REG 0x09
56 #define PCL816_CLRINT_REG 0x0a
57 #define PCL816_MUX_REG 0x0b
58 #define PCL816_MUX_SCAN(_first, _last) (((_last) << 4) | (_first))
59 #define PCL816_CTRL_REG 0x0c
60 #define PCL816_CTRL_DISABLE_TRIG (0 << 0)
61 #define PCL816_CTRL_SOFT_TRIG (1 << 0)
62 #define PCL816_CTRL_PACER_TRIG (1 << 1)
63 #define PCL816_CTRL_EXT_TRIG (1 << 2)
64 #define PCL816_CTRL_POE (1 << 3)
65 #define PCL816_CTRL_DMAEN (1 << 4)
66 #define PCL816_CTRL_INTEN (1 << 5)
67 #define PCL816_CTRL_DMASRC_SLOT0 (0 << 6)
68 #define PCL816_CTRL_DMASRC_SLOT1 (1 << 6)
69 #define PCL816_CTRL_DMASRC_SLOT2 (2 << 6)
70 #define PCL816_STATUS_REG 0x0d
71 #define PCL816_STATUS_NEXT_CHAN_MASK (0xf << 0)
72 #define PCL816_STATUS_INTSRC_MASK (3 << 4)
73 #define PCL816_STATUS_INTSRC_SLOT0 (0 << 4)
74 #define PCL816_STATUS_INTSRC_SLOT1 (1 << 4)
75 #define PCL816_STATUS_INTSRC_SLOT2 (2 << 4)
76 #define PCL816_STATUS_INTSRC_DMA (3 << 4)
77 #define PCL816_STATUS_INTACT (1 << 6)
78 #define PCL816_STATUS_DRDY (1 << 7)
80 #define MAGIC_DMA_WORD 0x5a5a
82 static const struct comedi_lrange range_pcl816 = {
102 static const struct pcl816_board boardtypes[] = {
105 .ai_maxdata = 0xffff,
106 .ao_maxdata = 0xffff,
110 .ai_maxdata = 0x3fff,
111 .ao_maxdata = 0x3fff,
116 struct pcl816_private {
117 unsigned int dma; /* used DMA, 0=don't use DMA */
118 unsigned int dmapages;
119 unsigned int hwdmasize;
120 unsigned long dmabuf[2]; /* pointers to begin of DMA buffers */
121 unsigned int hwdmaptr[2]; /* hardware address of DMA buffers */
122 int next_dma_buf; /* which DMA buffer will be used next round */
123 long dma_runs_to_end; /* how many we must permorm DMA transfer to end of record */
124 unsigned long last_dma_run; /* how many bytes we must transfer on last DMA page */
125 int ai_act_scan; /* how many scans we finished */
126 unsigned int ai_poll_ptr; /* how many sampes transfer poll */
127 unsigned int divisor1;
128 unsigned int divisor2;
129 unsigned int ai_cmd_running:1;
130 unsigned int ai_cmd_canceled:1;
133 static void pcl816_start_pacer(struct comedi_device *dev, bool load_counters)
135 struct pcl816_private *devpriv = dev->private;
136 unsigned long timer_base = dev->iobase + PCL816_TIMER_BASE;
138 i8254_set_mode(timer_base, 0, 0, I8254_MODE1 | I8254_BINARY);
139 i8254_write(timer_base, 0, 0, 0x00ff);
142 i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
143 i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
147 i8254_write(timer_base, 0, 2, devpriv->divisor2);
148 i8254_write(timer_base, 0, 1, devpriv->divisor1);
152 static void pcl816_ai_setup_dma(struct comedi_device *dev,
153 struct comedi_subdevice *s)
155 struct pcl816_private *devpriv = dev->private;
156 struct comedi_cmd *cmd = &s->async->cmd;
157 unsigned int dma_flags;
160 bytes = devpriv->hwdmasize;
161 if (cmd->stop_src == TRIG_COUNT) {
163 bytes = cmd->stop_arg * cfc_bytes_per_scan(s);
165 /* how many DMA pages we must fill */
166 devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize;
168 /* on last dma transfer must be moved */
169 devpriv->last_dma_run = bytes % devpriv->hwdmasize;
170 devpriv->dma_runs_to_end--;
171 if (devpriv->dma_runs_to_end >= 0)
172 bytes = devpriv->hwdmasize;
174 devpriv->dma_runs_to_end = -1;
176 devpriv->next_dma_buf = 0;
177 set_dma_mode(devpriv->dma, DMA_MODE_READ);
178 dma_flags = claim_dma_lock();
179 clear_dma_ff(devpriv->dma);
180 set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]);
181 set_dma_count(devpriv->dma, bytes);
182 release_dma_lock(dma_flags);
183 enable_dma(devpriv->dma);
186 static void pcl816_ai_setup_next_dma(struct comedi_device *dev,
187 struct comedi_subdevice *s)
189 struct pcl816_private *devpriv = dev->private;
190 struct comedi_cmd *cmd = &s->async->cmd;
191 unsigned long dma_flags;
193 disable_dma(devpriv->dma);
194 if (devpriv->dma_runs_to_end > -1 || cmd->stop_src == TRIG_NONE) {
195 /* switch dma bufs */
196 devpriv->next_dma_buf = 1 - devpriv->next_dma_buf;
197 set_dma_mode(devpriv->dma, DMA_MODE_READ);
198 dma_flags = claim_dma_lock();
199 set_dma_addr(devpriv->dma,
200 devpriv->hwdmaptr[devpriv->next_dma_buf]);
201 if (devpriv->dma_runs_to_end)
202 set_dma_count(devpriv->dma, devpriv->hwdmasize);
204 set_dma_count(devpriv->dma, devpriv->last_dma_run);
205 release_dma_lock(dma_flags);
206 enable_dma(devpriv->dma);
209 devpriv->dma_runs_to_end--;
212 static void pcl816_ai_set_chan_range(struct comedi_device *dev,
216 outb(chan, dev->iobase + PCL816_MUX_REG);
217 outb(range, dev->iobase + PCL816_RANGE_REG);
220 static void pcl816_ai_set_chan_scan(struct comedi_device *dev,
221 unsigned int first_chan,
222 unsigned int last_chan)
224 outb(PCL816_MUX_SCAN(first_chan, last_chan),
225 dev->iobase + PCL816_MUX_REG);
228 static void pcl816_ai_setup_chanlist(struct comedi_device *dev,
229 unsigned int *chanlist,
232 unsigned int first_chan = CR_CHAN(chanlist[0]);
233 unsigned int last_chan;
237 /* store range list to card */
238 for (i = 0; i < seglen; i++) {
239 last_chan = CR_CHAN(chanlist[i]);
240 range = CR_RANGE(chanlist[i]);
242 pcl816_ai_set_chan_range(dev, last_chan, range);
247 pcl816_ai_set_chan_scan(dev, first_chan, last_chan);
250 static void pcl816_ai_clear_eoc(struct comedi_device *dev)
252 /* writing any value clears the interrupt request */
253 outb(0, dev->iobase + PCL816_CLRINT_REG);
256 static void pcl816_ai_soft_trig(struct comedi_device *dev)
258 /* writing any value triggers a software conversion */
259 outb(0, dev->iobase + PCL816_AI_LSB_REG);
262 static unsigned int pcl816_ai_get_sample(struct comedi_device *dev,
263 struct comedi_subdevice *s)
267 val = inb(dev->iobase + PCL816_AI_MSB_REG) << 8;
268 val |= inb(dev->iobase + PCL816_AI_LSB_REG);
270 return val & s->maxdata;
273 static int pcl816_ai_eoc(struct comedi_device *dev,
274 struct comedi_subdevice *s,
275 struct comedi_insn *insn,
276 unsigned long context)
280 status = inb(dev->iobase + PCL816_STATUS_REG);
281 if ((status & PCL816_STATUS_DRDY) == 0)
286 static bool pcl816_ai_next_chan(struct comedi_device *dev,
287 struct comedi_subdevice *s)
289 struct pcl816_private *devpriv = dev->private;
290 struct comedi_cmd *cmd = &s->async->cmd;
292 s->async->events |= COMEDI_CB_BLOCK;
294 s->async->cur_chan++;
295 if (s->async->cur_chan >= cmd->chanlist_len) {
296 s->async->cur_chan = 0;
297 devpriv->ai_act_scan++;
298 s->async->events |= COMEDI_CB_EOS;
301 if (cmd->stop_src == TRIG_COUNT &&
302 devpriv->ai_act_scan >= cmd->stop_arg) {
303 /* all data sampled */
304 s->async->events |= COMEDI_CB_EOA;
311 static void transfer_from_dma_buf(struct comedi_device *dev,
312 struct comedi_subdevice *s,
314 unsigned int bufptr, unsigned int len)
318 for (i = 0; i < len; i++) {
319 comedi_buf_put(s, ptr[bufptr++]);
321 if (!pcl816_ai_next_chan(dev, s))
326 static irqreturn_t pcl816_interrupt(int irq, void *d)
328 struct comedi_device *dev = d;
329 struct comedi_subdevice *s = dev->read_subdev;
330 struct pcl816_private *devpriv = dev->private;
335 if (!dev->attached || !devpriv->ai_cmd_running) {
336 pcl816_ai_clear_eoc(dev);
340 if (devpriv->ai_cmd_canceled) {
341 devpriv->ai_cmd_canceled = 0;
342 pcl816_ai_clear_eoc(dev);
346 ptr = (unsigned short *)devpriv->dmabuf[devpriv->next_dma_buf];
348 pcl816_ai_setup_next_dma(dev, s);
350 len = (devpriv->hwdmasize >> 1) - devpriv->ai_poll_ptr;
351 bufptr = devpriv->ai_poll_ptr;
352 devpriv->ai_poll_ptr = 0;
354 transfer_from_dma_buf(dev, s, ptr, bufptr, len);
356 pcl816_ai_clear_eoc(dev);
358 cfc_handle_events(dev, s);
362 static int check_channel_list(struct comedi_device *dev,
363 struct comedi_subdevice *s,
364 unsigned int *chanlist,
365 unsigned int chanlen)
367 unsigned int chansegment[16];
368 unsigned int i, nowmustbechan, seglen, segpos;
370 /* correct channel and range number check itself comedi/range.c */
372 dev_err(dev->class_dev, "range/channel list is empty!\n");
377 /* first channel is every time ok */
378 chansegment[0] = chanlist[0];
379 for (i = 1, seglen = 1; i < chanlen; i++, seglen++) {
380 /* we detect loop, this must by finish */
381 if (chanlist[0] == chanlist[i])
384 (CR_CHAN(chansegment[i - 1]) + 1) % chanlen;
385 if (nowmustbechan != CR_CHAN(chanlist[i])) {
386 /* channel list isn't continuous :-( */
387 dev_dbg(dev->class_dev,
388 "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n",
389 i, CR_CHAN(chanlist[i]), nowmustbechan,
390 CR_CHAN(chanlist[0]));
393 /* well, this is next correct channel in list */
394 chansegment[i] = chanlist[i];
397 /* check whole chanlist */
398 for (i = 0, segpos = 0; i < chanlen; i++) {
399 if (chanlist[i] != chansegment[i % seglen]) {
400 dev_dbg(dev->class_dev,
401 "bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n",
402 i, CR_CHAN(chansegment[i]),
403 CR_RANGE(chansegment[i]),
404 CR_AREF(chansegment[i]),
405 CR_CHAN(chanlist[i % seglen]),
406 CR_RANGE(chanlist[i % seglen]),
407 CR_AREF(chansegment[i % seglen]));
408 return 0; /* chan/gain list is strange */
415 return seglen; /* we can serve this with MUX logic */
418 static int pcl816_ai_cmdtest(struct comedi_device *dev,
419 struct comedi_subdevice *s, struct comedi_cmd *cmd)
421 struct pcl816_private *devpriv = dev->private;
425 /* Step 1 : check if triggers are trivially valid */
427 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
428 err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
429 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_EXT | TRIG_TIMER);
430 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
431 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
436 /* Step 2a : make sure trigger sources are unique */
438 err |= cfc_check_trigger_is_unique(cmd->convert_src);
439 err |= cfc_check_trigger_is_unique(cmd->stop_src);
441 /* Step 2b : and mutually compatible */
447 /* Step 3: check if arguments are trivially valid */
449 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
450 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
452 if (cmd->convert_src == TRIG_TIMER)
453 err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 10000);
455 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
457 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
459 if (cmd->stop_src == TRIG_COUNT)
460 err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
462 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
468 /* step 4: fix up any arguments */
469 if (cmd->convert_src == TRIG_TIMER) {
470 arg = cmd->convert_arg;
471 i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ,
475 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
482 /* step 5: complain about special chanlist considerations */
485 if (!check_channel_list(dev, s, cmd->chanlist,
487 return 5; /* incorrect channels list */
493 static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
495 struct pcl816_private *devpriv = dev->private;
496 struct comedi_cmd *cmd = &s->async->cmd;
500 if (devpriv->ai_cmd_running)
503 pcl816_start_pacer(dev, false);
505 seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len);
508 pcl816_ai_setup_chanlist(dev, cmd->chanlist, seglen);
511 devpriv->ai_act_scan = 0;
512 s->async->cur_chan = 0;
513 devpriv->ai_cmd_running = 1;
514 devpriv->ai_poll_ptr = 0;
515 devpriv->ai_cmd_canceled = 0;
517 pcl816_ai_setup_dma(dev, s);
519 pcl816_start_pacer(dev, true);
521 ctrl = PCL816_CTRL_INTEN | PCL816_CTRL_DMAEN | PCL816_CTRL_DMASRC_SLOT0;
522 if (cmd->convert_src == TRIG_TIMER)
523 ctrl |= PCL816_CTRL_PACER_TRIG;
525 ctrl |= PCL816_CTRL_EXT_TRIG;
527 outb(ctrl, dev->iobase + PCL816_CTRL_REG);
528 outb((devpriv->dma << 4) | dev->irq, dev->iobase + PCL816_STATUS_REG);
533 static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
535 struct pcl816_private *devpriv = dev->private;
537 unsigned int top1, top2, i;
539 spin_lock_irqsave(&dev->spinlock, flags);
541 for (i = 0; i < 20; i++) {
542 top1 = get_dma_residue(devpriv->dma); /* where is now DMA */
543 top2 = get_dma_residue(devpriv->dma);
548 spin_unlock_irqrestore(&dev->spinlock, flags);
552 /* where is now DMA in buffer */
553 top1 = devpriv->hwdmasize - top1;
554 top1 >>= 1; /* sample position */
555 top2 = top1 - devpriv->ai_poll_ptr;
556 if (top2 < 1) { /* no new samples */
557 spin_unlock_irqrestore(&dev->spinlock, flags);
561 transfer_from_dma_buf(dev, s,
562 (unsigned short *)devpriv->dmabuf[devpriv->
564 devpriv->ai_poll_ptr, top2);
566 devpriv->ai_poll_ptr = top1; /* new buffer position */
567 spin_unlock_irqrestore(&dev->spinlock, flags);
569 cfc_handle_events(dev, s);
571 return comedi_buf_n_bytes_ready(s);
574 static int pcl816_ai_cancel(struct comedi_device *dev,
575 struct comedi_subdevice *s)
577 struct pcl816_private *devpriv = dev->private;
579 if (!devpriv->ai_cmd_running)
582 outb(PCL816_CTRL_DISABLE_TRIG, dev->iobase + PCL816_CTRL_REG);
583 pcl816_ai_clear_eoc(dev);
586 i8254_set_mode(dev->iobase + PCL816_TIMER_BASE, 0,
587 2, I8254_MODE0 | I8254_BINARY);
588 i8254_set_mode(dev->iobase + PCL816_TIMER_BASE, 0,
589 1, I8254_MODE0 | I8254_BINARY);
591 devpriv->ai_cmd_running = 0;
592 devpriv->ai_cmd_canceled = 1;
597 static int pcl816_ai_insn_read(struct comedi_device *dev,
598 struct comedi_subdevice *s,
599 struct comedi_insn *insn,
602 unsigned int chan = CR_CHAN(insn->chanspec);
603 unsigned int range = CR_RANGE(insn->chanspec);
607 outb(PCL816_CTRL_SOFT_TRIG, dev->iobase + PCL816_CTRL_REG);
609 pcl816_ai_set_chan_range(dev, chan, range);
610 pcl816_ai_set_chan_scan(dev, chan, chan);
612 for (i = 0; i < insn->n; i++) {
613 pcl816_ai_clear_eoc(dev);
614 pcl816_ai_soft_trig(dev);
616 ret = comedi_timeout(dev, s, insn, pcl816_ai_eoc, 0);
620 data[i] = pcl816_ai_get_sample(dev, s);
622 outb(PCL816_CTRL_DISABLE_TRIG, dev->iobase + PCL816_CTRL_REG);
623 pcl816_ai_clear_eoc(dev);
625 return ret ? ret : insn->n;
628 static int pcl816_di_insn_bits(struct comedi_device *dev,
629 struct comedi_subdevice *s,
630 struct comedi_insn *insn,
633 data[1] = inb(dev->iobase + PCL816_DO_DI_LSB_REG) |
634 (inb(dev->iobase + PCL816_DO_DI_MSB_REG) << 8);
639 static int pcl816_do_insn_bits(struct comedi_device *dev,
640 struct comedi_subdevice *s,
641 struct comedi_insn *insn,
644 if (comedi_dio_update_state(s, data)) {
645 outb(s->state & 0xff, dev->iobase + PCL816_DO_DI_LSB_REG);
646 outb((s->state >> 8), dev->iobase + PCL816_DO_DI_MSB_REG);
654 static void pcl816_reset(struct comedi_device *dev)
656 unsigned long timer_base = dev->iobase + PCL816_TIMER_BASE;
658 outb(PCL816_CTRL_DISABLE_TRIG, dev->iobase + PCL816_CTRL_REG);
659 pcl816_ai_set_chan_range(dev, 0, 0);
660 pcl816_ai_clear_eoc(dev);
663 i8254_set_mode(timer_base, 0, 2, I8254_MODE0 | I8254_BINARY);
664 i8254_set_mode(timer_base, 0, 1, I8254_MODE0 | I8254_BINARY);
665 i8254_set_mode(timer_base, 0, 0, I8254_MODE0 | I8254_BINARY);
667 /* set all digital outputs low */
668 outb(0, dev->iobase + PCL816_DO_DI_LSB_REG);
669 outb(0, dev->iobase + PCL816_DO_DI_MSB_REG);
672 static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it)
674 const struct pcl816_board *board = dev->board_ptr;
675 struct pcl816_private *devpriv;
676 struct comedi_subdevice *s;
680 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
684 ret = comedi_request_region(dev, it->options[0], 0x10);
688 /* we can use IRQ 2-7 for async command support */
689 if (it->options[1] >= 2 && it->options[1] <= 7) {
690 ret = request_irq(it->options[1], pcl816_interrupt, 0,
691 dev->board_name, dev);
693 dev->irq = it->options[1];
696 /* we need an IRQ to do DMA on channel 3 or 1 */
697 if (dev->irq && (it->options[2] == 3 || it->options[2] == 1)) {
698 ret = request_dma(it->options[2], dev->board_name);
700 dev_err(dev->class_dev,
701 "unable to request DMA channel %d\n",
705 devpriv->dma = it->options[2];
707 devpriv->dmapages = 2; /* we need 16KB */
708 devpriv->hwdmasize = (1 << devpriv->dmapages) * PAGE_SIZE;
710 for (i = 0; i < 2; i++) {
711 unsigned long dmabuf;
713 dmabuf = __get_dma_pages(GFP_KERNEL, devpriv->dmapages);
717 devpriv->dmabuf[i] = dmabuf;
718 devpriv->hwdmaptr[i] = virt_to_bus((void *)dmabuf);
722 ret = comedi_alloc_subdevices(dev, 4);
726 s = &dev->subdevices[0];
727 s->type = COMEDI_SUBD_AI;
728 s->subdev_flags = SDF_CMD_READ | SDF_DIFF;
730 s->maxdata = board->ai_maxdata;
731 s->range_table = &range_pcl816;
732 s->insn_read = pcl816_ai_insn_read;
734 dev->read_subdev = s;
735 s->subdev_flags |= SDF_CMD_READ;
736 s->len_chanlist = board->ai_chanlist;
737 s->do_cmdtest = pcl816_ai_cmdtest;
738 s->do_cmd = pcl816_ai_cmd;
739 s->poll = pcl816_ai_poll;
740 s->cancel = pcl816_ai_cancel;
743 /* Analog OUtput subdevice */
744 s = &dev->subdevices[2];
745 s->type = COMEDI_SUBD_UNUSED;
747 subdevs[1] = COMEDI_SUBD_AO;
748 s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
750 s->maxdata = board->ao_maxdata;
751 s->range_table = &range_pcl816;
754 /* Digital Input subdevice */
755 s = &dev->subdevices[2];
756 s->type = COMEDI_SUBD_DI;
757 s->subdev_flags = SDF_READABLE;
760 s->range_table = &range_digital;
761 s->insn_bits = pcl816_di_insn_bits;
763 /* Digital Output subdevice */
764 s = &dev->subdevices[3];
765 s->type = COMEDI_SUBD_DO;
766 s->subdev_flags = SDF_WRITABLE;
769 s->range_table = &range_digital;
770 s->insn_bits = pcl816_do_insn_bits;
777 static void pcl816_detach(struct comedi_device *dev)
779 struct pcl816_private *devpriv = dev->private;
782 pcl816_ai_cancel(dev, dev->read_subdev);
785 free_dma(devpriv->dma);
786 if (devpriv->dmabuf[0])
787 free_pages(devpriv->dmabuf[0], devpriv->dmapages);
788 if (devpriv->dmabuf[1])
789 free_pages(devpriv->dmabuf[1], devpriv->dmapages);
791 comedi_legacy_detach(dev);
794 static struct comedi_driver pcl816_driver = {
795 .driver_name = "pcl816",
796 .module = THIS_MODULE,
797 .attach = pcl816_attach,
798 .detach = pcl816_detach,
799 .board_name = &boardtypes[0].name,
800 .num_names = ARRAY_SIZE(boardtypes),
801 .offset = sizeof(struct pcl816_board),
803 module_comedi_driver(pcl816_driver);
805 MODULE_AUTHOR("Comedi http://www.comedi.org");
806 MODULE_DESCRIPTION("Comedi low-level driver");
807 MODULE_LICENSE("GPL");