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