]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/blob - drivers/staging/comedi/drivers/pcl816.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
[mirror_ubuntu-hirsute-kernel.git] / drivers / staging / comedi / drivers / pcl816.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * pcl816.c
4 * Comedi driver for Advantech PCL-816 cards
5 *
6 * Author: Juan Grigera <juan@grigera.com.ar>
7 * based on pcl818 by Michal Dobes <dobes@tesnet.cz> and bits of pcl812
8 */
9
10 /*
11 * Driver: pcl816
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>
15 * Status: works
16 * Updated: Tue, 2 Apr 2002 23:15:21 -0800
17 *
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).
20 *
21 * The driver support AI command mode, other subdevices not written.
22 *
23 * Analog output and digital input and output are not supported.
24 *
25 * Configuration Options:
26 * [0] - IO Base
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
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 * Register I/O map
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
74 static 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
87 struct pcl816_board {
88 const char *name;
89 int ai_maxdata;
90 int ai_chanlist;
91 };
92
93 static 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
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;
110 };
111
112 static 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 * Determine dma size based on the buffer maxsize plus the number of
126 * unread samples and the number of samples remaining in the command.
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
136 static 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
144 static 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
152 static 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 /* 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]);
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
174 static void pcl816_ai_clear_eoc(struct comedi_device *dev)
175 {
176 /* writing any value clears the interrupt request */
177 outb(0, dev->iobase + PCL816_CLRINT_REG);
178 }
179
180 static void pcl816_ai_soft_trig(struct comedi_device *dev)
181 {
182 /* writing any value triggers a software conversion */
183 outb(0, dev->iobase + PCL816_AI_LSB_REG);
184 }
185
186 static 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
197 static 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
210 static 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
224 static 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
241 static 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 /* restart dma with the next buffer */
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
279 static 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 /* correct channel and range number check itself comedi/range.c */
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 /* 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])
299 break;
300 nowmustbechan =
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]));
308 return 0;
309 }
310 /* well, this is next correct channel in list */
311 chansegment[i] = chanlist[i];
312 }
313
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 */
326 }
327 }
328 } else {
329 seglen = 1;
330 }
331
332 return seglen; /* we can serve this with MUX logic */
333 }
334
335 static int pcl816_ai_cmdtest(struct comedi_device *dev,
336 struct comedi_subdevice *s, struct comedi_cmd *cmd)
337 {
338 int err = 0;
339
340 /* Step 1 : check if triggers are trivially valid */
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 /* Step 2a : make sure trigger sources are unique */
353
354 err |= comedi_check_trigger_is_unique(cmd->convert_src);
355 err |= comedi_check_trigger_is_unique(cmd->stop_src);
356
357 /* Step 2b : and mutually compatible */
358
359 if (err)
360 return 2;
361
362 /* Step 3: check if arguments are trivially valid */
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 /* TRIG_EXT */
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 /* TRIG_NONE */
378 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
379
380 if (err)
381 return 3;
382
383 /* step 4: fix up any arguments */
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 /* step 5: complain about special chanlist considerations */
395
396 if (cmd->chanlist) {
397 if (!check_channel_list(dev, s, cmd->chanlist,
398 cmd->chanlist_len))
399 return 5; /* incorrect channels list */
400 }
401
402 return 0;
403 }
404
405 static 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 /* setup and enable dma for the first buffer */
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 /* TRIG_EXT */
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
450 static 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 /* new buffer position */
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 /* no new samples */
476 ret = 0;
477 }
478 spin_unlock_irqrestore(&dev->spinlock, flags);
479
480 return ret;
481 }
482
483 static 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
502 static 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
533 static 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
544 static 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
559 static 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 /* 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);
568 }
569
570 static 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 /* 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))
580 return;
581
582 if (request_irq(irq_num, pcl816_interrupt, 0, dev->board_name, dev))
583 return;
584
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);
588 if (!devpriv->dma)
589 free_irq(irq_num, dev);
590 else
591 dev->irq = irq_num;
592 }
593
594 static 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
602 static 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 /* an IRQ and DMA are required to support async commands */
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 /* Piggyback Slot1 subdevice */
647 s = &dev->subdevices[1];
648 s->type = COMEDI_SUBD_UNUSED;
649
650 /* Digital Input subdevice */
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 /* Digital Output subdevice */
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
673 static 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
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),
691 };
692 module_comedi_driver(pcl816_driver);
693
694 MODULE_AUTHOR("Comedi http://www.comedi.org");
695 MODULE_DESCRIPTION("Comedi low-level driver");
696 MODULE_LICENSE("GPL");