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