Merge tag 'asoc-v3.10-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie...
[firefly-linux-kernel-4.4.55.git] / drivers / staging / comedi / drivers / das16m1.c
1 /*
2     comedi/drivers/das16m1.c
3     CIO-DAS16/M1 driver
4     Author: Frank Mori Hess, based on code from the das16
5       driver.
6     Copyright (C) 2001 Frank Mori Hess <fmhess@users.sourceforge.net>
7
8     COMEDI - Linux Control and Measurement Device Interface
9     Copyright (C) 2000 David A. Schleef <ds@schleef.org>
10
11     This program is free software; you can redistribute it and/or modify
12     it under the terms of the GNU General Public License as published by
13     the Free Software Foundation; either version 2 of the License, or
14     (at your option) any later version.
15
16     This program is distributed in the hope that it will be useful,
17     but WITHOUT ANY WARRANTY; without even the implied warranty of
18     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19     GNU General Public License for more details.
20
21     You should have received a copy of the GNU General Public License
22     along with this program; if not, write to the Free Software
23     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24
25 ************************************************************************
26 */
27 /*
28 Driver: das16m1
29 Description: CIO-DAS16/M1
30 Author: Frank Mori Hess <fmhess@users.sourceforge.net>
31 Devices: [Measurement Computing] CIO-DAS16/M1 (das16m1)
32 Status: works
33
34 This driver supports a single board - the CIO-DAS16/M1.
35 As far as I know, there are no other boards that have
36 the same register layout.  Even the CIO-DAS16/M1/16 is
37 significantly different.
38
39 I was _barely_ able to reach the full 1 MHz capability
40 of this board, using a hard real-time interrupt
41 (set the TRIG_RT flag in your struct comedi_cmd and use
42 rtlinux or RTAI).  The board can't do dma, so the bottleneck is
43 pulling the data across the ISA bus.  I timed the interrupt
44 handler, and it took my computer ~470 microseconds to pull 512
45 samples from the board.  So at 1 Mhz sampling rate,
46 expect your CPU to be spending almost all of its
47 time in the interrupt handler.
48
49 This board has some unusual restrictions for its channel/gain list.  If the
50 list has 2 or more channels in it, then two conditions must be satisfied:
51 (1) - even/odd channels must appear at even/odd indices in the list
52 (2) - the list must have an even number of entries.
53
54 Options:
55         [0] - base io address
56         [1] - irq (optional, but you probably want it)
57
58 irq can be omitted, although the cmd interface will not work without it.
59 */
60
61 #include <linux/ioport.h>
62 #include <linux/interrupt.h>
63 #include "../comedidev.h"
64
65 #include "8255.h"
66 #include "8253.h"
67 #include "comedi_fc.h"
68
69 #define DAS16M1_SIZE 16
70 #define DAS16M1_SIZE2 8
71
72 #define DAS16M1_XTAL 100        /* 10 MHz master clock */
73
74 #define FIFO_SIZE 1024          /*  1024 sample fifo */
75
76 /*
77     CIO-DAS16_M1.pdf
78
79     "cio-das16/m1"
80
81   0     a/d bits 0-3, mux               start 12 bit
82   1     a/d bits 4-11           unused
83   2     status          control
84   3     di 4 bit                do 4 bit
85   4     unused                  clear interrupt
86   5     interrupt, pacer
87   6     channel/gain queue address
88   7     channel/gain queue data
89   89ab  8254
90   cdef  8254
91   400   8255
92   404-407       8254
93
94 */
95
96 #define DAS16M1_AI             0        /*  16-bit wide register */
97 #define   AI_CHAN(x)             ((x) & 0xf)
98 #define DAS16M1_CS             2
99 #define   EXT_TRIG_BIT           0x1
100 #define   OVRUN                  0x20
101 #define   IRQDATA                0x80
102 #define DAS16M1_DIO            3
103 #define DAS16M1_CLEAR_INTR     4
104 #define DAS16M1_INTR_CONTROL   5
105 #define   EXT_PACER              0x2
106 #define   INT_PACER              0x3
107 #define   PACER_MASK             0x3
108 #define   INTE                   0x80
109 #define DAS16M1_QUEUE_ADDR     6
110 #define DAS16M1_QUEUE_DATA     7
111 #define   Q_CHAN(x)              ((x) & 0x7)
112 #define   Q_RANGE(x)             (((x) & 0xf) << 4)
113 #define   UNIPOLAR               0x40
114 #define DAS16M1_8254_FIRST             0x8
115 #define DAS16M1_8254_FIRST_CNTRL       0xb
116 #define   TOTAL_CLEAR                    0x30
117 #define DAS16M1_8254_SECOND            0xc
118 #define DAS16M1_82C55                  0x400
119 #define DAS16M1_8254_THIRD             0x404
120
121 static const struct comedi_lrange range_das16m1 = { 9,
122         {
123          BIP_RANGE(5),
124          BIP_RANGE(2.5),
125          BIP_RANGE(1.25),
126          BIP_RANGE(0.625),
127          UNI_RANGE(10),
128          UNI_RANGE(5),
129          UNI_RANGE(2.5),
130          UNI_RANGE(1.25),
131          BIP_RANGE(10),
132          }
133 };
134
135 struct das16m1_private_struct {
136         unsigned int control_state;
137         volatile unsigned int adc_count;        /*  number of samples completed */
138         /* initial value in lower half of hardware conversion counter,
139          * needed to keep track of whether new count has been loaded into
140          * counter yet (loaded by first sample conversion) */
141         u16 initial_hw_count;
142         short ai_buffer[FIFO_SIZE];
143         unsigned int do_bits;   /*  saves status of digital output bits */
144         unsigned int divisor1;  /*  divides master clock to obtain conversion speed */
145         unsigned int divisor2;  /*  divides master clock to obtain conversion speed */
146         unsigned long extra_iobase;
147 };
148
149 static inline short munge_sample(short data)
150 {
151         return (data >> 4) & 0xfff;
152 }
153
154 static void munge_sample_array(short *array, unsigned int num_elements)
155 {
156         unsigned int i;
157
158         for (i = 0; i < num_elements; i++)
159                 array[i] = munge_sample(array[i]);
160 }
161
162 static int das16m1_cmd_test(struct comedi_device *dev,
163                             struct comedi_subdevice *s, struct comedi_cmd *cmd)
164 {
165         struct das16m1_private_struct *devpriv = dev->private;
166         unsigned int err = 0, tmp, i;
167
168         /* Step 1 : check if triggers are trivially valid */
169
170         err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
171         err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
172         err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
173         err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
174         err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
175
176         if (err)
177                 return 1;
178
179         /* Step 2a : make sure trigger sources are unique */
180
181         err |= cfc_check_trigger_is_unique(cmd->start_src);
182         err |= cfc_check_trigger_is_unique(cmd->convert_src);
183         err |= cfc_check_trigger_is_unique(cmd->stop_src);
184
185         /* Step 2b : and mutually compatible */
186
187         if (err)
188                 return 2;
189
190         /* Step 3: check if arguments are trivially valid */
191
192         err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
193
194         if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
195                 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
196
197         if (cmd->convert_src == TRIG_TIMER)
198                 err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 1000);
199
200         err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
201
202         if (cmd->stop_src == TRIG_COUNT) {
203                 /* any count is allowed */
204         } else {
205                 /* TRIG_NONE */
206                 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
207         }
208
209         if (err)
210                 return 3;
211
212         /* step 4: fix up arguments */
213
214         if (cmd->convert_src == TRIG_TIMER) {
215                 tmp = cmd->convert_arg;
216                 /* calculate counter values that give desired timing */
217                 i8253_cascade_ns_to_timer_2div(DAS16M1_XTAL,
218                                                &(devpriv->divisor1),
219                                                &(devpriv->divisor2),
220                                                &(cmd->convert_arg),
221                                                cmd->flags & TRIG_ROUND_MASK);
222                 if (tmp != cmd->convert_arg)
223                         err++;
224         }
225
226         if (err)
227                 return 4;
228
229         /*  check chanlist against board's peculiarities */
230         if (cmd->chanlist && cmd->chanlist_len > 1) {
231                 for (i = 0; i < cmd->chanlist_len; i++) {
232                         /*  even/odd channels must go into even/odd queue addresses */
233                         if ((i % 2) != (CR_CHAN(cmd->chanlist[i]) % 2)) {
234                                 comedi_error(dev, "bad chanlist:\n"
235                                              " even/odd channels must go have even/odd chanlist indices");
236                                 err++;
237                         }
238                 }
239                 if ((cmd->chanlist_len % 2) != 0) {
240                         comedi_error(dev,
241                                      "chanlist must be of even length or length 1");
242                         err++;
243                 }
244         }
245
246         if (err)
247                 return 5;
248
249         return 0;
250 }
251
252 /* This function takes a time in nanoseconds and sets the     *
253  * 2 pacer clocks to the closest frequency possible. It also  *
254  * returns the actual sampling period.                        */
255 static unsigned int das16m1_set_pacer(struct comedi_device *dev,
256                                       unsigned int ns, int rounding_flags)
257 {
258         struct das16m1_private_struct *devpriv = dev->private;
259
260         i8253_cascade_ns_to_timer_2div(DAS16M1_XTAL, &(devpriv->divisor1),
261                                        &(devpriv->divisor2), &ns,
262                                        rounding_flags & TRIG_ROUND_MASK);
263
264         /* Write the values of ctr1 and ctr2 into counters 1 and 2 */
265         i8254_load(dev->iobase + DAS16M1_8254_SECOND, 0, 1, devpriv->divisor1,
266                    2);
267         i8254_load(dev->iobase + DAS16M1_8254_SECOND, 0, 2, devpriv->divisor2,
268                    2);
269
270         return ns;
271 }
272
273 static int das16m1_cmd_exec(struct comedi_device *dev,
274                             struct comedi_subdevice *s)
275 {
276         struct das16m1_private_struct *devpriv = dev->private;
277         struct comedi_async *async = s->async;
278         struct comedi_cmd *cmd = &async->cmd;
279         unsigned int byte, i;
280
281         if (dev->irq == 0) {
282                 comedi_error(dev, "irq required to execute comedi_cmd");
283                 return -1;
284         }
285
286         /* disable interrupts and internal pacer */
287         devpriv->control_state &= ~INTE & ~PACER_MASK;
288         outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
289
290         /*  set software count */
291         devpriv->adc_count = 0;
292         /* Initialize lower half of hardware counter, used to determine how
293          * many samples are in fifo.  Value doesn't actually load into counter
294          * until counter's next clock (the next a/d conversion) */
295         i8254_load(dev->iobase + DAS16M1_8254_FIRST, 0, 1, 0, 2);
296         /* remember current reading of counter so we know when counter has
297          * actually been loaded */
298         devpriv->initial_hw_count =
299             i8254_read(dev->iobase + DAS16M1_8254_FIRST, 0, 1);
300         /* setup channel/gain queue */
301         for (i = 0; i < cmd->chanlist_len; i++) {
302                 outb(i, dev->iobase + DAS16M1_QUEUE_ADDR);
303                 byte =
304                     Q_CHAN(CR_CHAN(cmd->chanlist[i])) |
305                     Q_RANGE(CR_RANGE(cmd->chanlist[i]));
306                 outb(byte, dev->iobase + DAS16M1_QUEUE_DATA);
307         }
308
309         /* set counter mode and counts */
310         cmd->convert_arg =
311             das16m1_set_pacer(dev, cmd->convert_arg,
312                               cmd->flags & TRIG_ROUND_MASK);
313
314         /*  set control & status register */
315         byte = 0;
316         /* if we are using external start trigger (also board dislikes having
317          * both start and conversion triggers external simultaneously) */
318         if (cmd->start_src == TRIG_EXT && cmd->convert_src != TRIG_EXT)
319                 byte |= EXT_TRIG_BIT;
320
321         outb(byte, dev->iobase + DAS16M1_CS);
322         /* clear interrupt bit */
323         outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
324
325         /* enable interrupts and internal pacer */
326         devpriv->control_state &= ~PACER_MASK;
327         if (cmd->convert_src == TRIG_TIMER)
328                 devpriv->control_state |= INT_PACER;
329         else
330                 devpriv->control_state |= EXT_PACER;
331
332         devpriv->control_state |= INTE;
333         outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
334
335         return 0;
336 }
337
338 static int das16m1_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
339 {
340         struct das16m1_private_struct *devpriv = dev->private;
341
342         devpriv->control_state &= ~INTE & ~PACER_MASK;
343         outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
344
345         return 0;
346 }
347
348 static int das16m1_ai_rinsn(struct comedi_device *dev,
349                             struct comedi_subdevice *s,
350                             struct comedi_insn *insn, unsigned int *data)
351 {
352         struct das16m1_private_struct *devpriv = dev->private;
353         int i, n;
354         int byte;
355         const int timeout = 1000;
356
357         /* disable interrupts and internal pacer */
358         devpriv->control_state &= ~INTE & ~PACER_MASK;
359         outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
360
361         /* setup channel/gain queue */
362         outb(0, dev->iobase + DAS16M1_QUEUE_ADDR);
363         byte =
364             Q_CHAN(CR_CHAN(insn->chanspec)) | Q_RANGE(CR_RANGE(insn->chanspec));
365         outb(byte, dev->iobase + DAS16M1_QUEUE_DATA);
366
367         for (n = 0; n < insn->n; n++) {
368                 /* clear IRQDATA bit */
369                 outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
370                 /* trigger conversion */
371                 outb(0, dev->iobase);
372
373                 for (i = 0; i < timeout; i++) {
374                         if (inb(dev->iobase + DAS16M1_CS) & IRQDATA)
375                                 break;
376                 }
377                 if (i == timeout) {
378                         comedi_error(dev, "timeout");
379                         return -ETIME;
380                 }
381                 data[n] = munge_sample(inw(dev->iobase));
382         }
383
384         return n;
385 }
386
387 static int das16m1_di_rbits(struct comedi_device *dev,
388                             struct comedi_subdevice *s,
389                             struct comedi_insn *insn, unsigned int *data)
390 {
391         unsigned int bits;
392
393         bits = inb(dev->iobase + DAS16M1_DIO) & 0xf;
394         data[1] = bits;
395         data[0] = 0;
396
397         return insn->n;
398 }
399
400 static int das16m1_do_wbits(struct comedi_device *dev,
401                             struct comedi_subdevice *s,
402                             struct comedi_insn *insn, unsigned int *data)
403 {
404         struct das16m1_private_struct *devpriv = dev->private;
405         unsigned int wbits;
406
407         /*  only set bits that have been masked */
408         data[0] &= 0xf;
409         wbits = devpriv->do_bits;
410         /*  zero bits that have been masked */
411         wbits &= ~data[0];
412         /*  set masked bits */
413         wbits |= data[0] & data[1];
414         devpriv->do_bits = wbits;
415         data[1] = wbits;
416
417         outb(devpriv->do_bits, dev->iobase + DAS16M1_DIO);
418
419         return insn->n;
420 }
421
422 static void das16m1_handler(struct comedi_device *dev, unsigned int status)
423 {
424         struct das16m1_private_struct *devpriv = dev->private;
425         struct comedi_subdevice *s;
426         struct comedi_async *async;
427         struct comedi_cmd *cmd;
428         u16 num_samples;
429         u16 hw_counter;
430
431         s = dev->read_subdev;
432         async = s->async;
433         async->events = 0;
434         cmd = &async->cmd;
435
436         /*  figure out how many samples are in fifo */
437         hw_counter = i8254_read(dev->iobase + DAS16M1_8254_FIRST, 0, 1);
438         /* make sure hardware counter reading is not bogus due to initial value
439          * not having been loaded yet */
440         if (devpriv->adc_count == 0 && hw_counter == devpriv->initial_hw_count) {
441                 num_samples = 0;
442         } else {
443                 /* The calculation of num_samples looks odd, but it uses the following facts.
444                  * 16 bit hardware counter is initialized with value of zero (which really
445                  * means 0x1000).  The counter decrements by one on each conversion
446                  * (when the counter decrements from zero it goes to 0xffff).  num_samples
447                  * is a 16 bit variable, so it will roll over in a similar fashion to the
448                  * hardware counter.  Work it out, and this is what you get. */
449                 num_samples = -hw_counter - devpriv->adc_count;
450         }
451         /*  check if we only need some of the points */
452         if (cmd->stop_src == TRIG_COUNT) {
453                 if (num_samples > cmd->stop_arg * cmd->chanlist_len)
454                         num_samples = cmd->stop_arg * cmd->chanlist_len;
455         }
456         /*  make sure we dont try to get too many points if fifo has overrun */
457         if (num_samples > FIFO_SIZE)
458                 num_samples = FIFO_SIZE;
459         insw(dev->iobase, devpriv->ai_buffer, num_samples);
460         munge_sample_array(devpriv->ai_buffer, num_samples);
461         cfc_write_array_to_buffer(s, devpriv->ai_buffer,
462                                   num_samples * sizeof(short));
463         devpriv->adc_count += num_samples;
464
465         if (cmd->stop_src == TRIG_COUNT) {
466                 if (devpriv->adc_count >= cmd->stop_arg * cmd->chanlist_len) {  /* end of acquisition */
467                         das16m1_cancel(dev, s);
468                         async->events |= COMEDI_CB_EOA;
469                 }
470         }
471
472         /* this probably won't catch overruns since the card doesn't generate
473          * overrun interrupts, but we might as well try */
474         if (status & OVRUN) {
475                 das16m1_cancel(dev, s);
476                 async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
477                 comedi_error(dev, "fifo overflow");
478         }
479
480         comedi_event(dev, s);
481
482 }
483
484 static int das16m1_poll(struct comedi_device *dev, struct comedi_subdevice *s)
485 {
486         unsigned long flags;
487         unsigned int status;
488
489         /*  prevent race with interrupt handler */
490         spin_lock_irqsave(&dev->spinlock, flags);
491         status = inb(dev->iobase + DAS16M1_CS);
492         das16m1_handler(dev, status);
493         spin_unlock_irqrestore(&dev->spinlock, flags);
494
495         return s->async->buf_write_count - s->async->buf_read_count;
496 }
497
498 static irqreturn_t das16m1_interrupt(int irq, void *d)
499 {
500         int status;
501         struct comedi_device *dev = d;
502
503         if (!dev->attached) {
504                 comedi_error(dev, "premature interrupt");
505                 return IRQ_HANDLED;
506         }
507         /*  prevent race with comedi_poll() */
508         spin_lock(&dev->spinlock);
509
510         status = inb(dev->iobase + DAS16M1_CS);
511
512         if ((status & (IRQDATA | OVRUN)) == 0) {
513                 comedi_error(dev, "spurious interrupt");
514                 spin_unlock(&dev->spinlock);
515                 return IRQ_NONE;
516         }
517
518         das16m1_handler(dev, status);
519
520         /* clear interrupt */
521         outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
522
523         spin_unlock(&dev->spinlock);
524         return IRQ_HANDLED;
525 }
526
527 static int das16m1_irq_bits(unsigned int irq)
528 {
529         int ret;
530
531         switch (irq) {
532         case 10:
533                 ret = 0x0;
534                 break;
535         case 11:
536                 ret = 0x1;
537                 break;
538         case 12:
539                 ret = 0x2;
540                 break;
541         case 15:
542                 ret = 0x3;
543                 break;
544         case 2:
545                 ret = 0x4;
546                 break;
547         case 3:
548                 ret = 0x5;
549                 break;
550         case 5:
551                 ret = 0x6;
552                 break;
553         case 7:
554                 ret = 0x7;
555                 break;
556         default:
557                 return -1;
558                 break;
559         }
560         return ret << 4;
561 }
562
563 /*
564  * Options list:
565  *   0  I/O base
566  *   1  IRQ
567  */
568 static int das16m1_attach(struct comedi_device *dev,
569                           struct comedi_devconfig *it)
570 {
571         struct das16m1_private_struct *devpriv;
572         struct comedi_subdevice *s;
573         int ret;
574         unsigned int irq;
575
576         devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
577         if (!devpriv)
578                 return -ENOMEM;
579         dev->private = devpriv;
580
581         ret = comedi_request_region(dev, it->options[0], DAS16M1_SIZE);
582         if (ret)
583                 return ret;
584         /* Request an additional region for the 8255 */
585         ret = __comedi_request_region(dev, dev->iobase + DAS16M1_82C55,
586                                       DAS16M1_SIZE2);
587         if (ret)
588                 return ret;
589         devpriv->extra_iobase = dev->iobase + DAS16M1_82C55;
590
591         /* now for the irq */
592         irq = it->options[1];
593         /*  make sure it is valid */
594         if (das16m1_irq_bits(irq) >= 0) {
595                 ret = request_irq(irq, das16m1_interrupt, 0,
596                                   dev->driver->driver_name, dev);
597                 if (ret < 0)
598                         return ret;
599                 dev->irq = irq;
600                 printk
601                     ("irq %u\n", irq);
602         } else if (irq == 0) {
603                 printk
604                     (", no irq\n");
605         } else {
606                 comedi_error(dev, "invalid irq\n"
607                              " valid irqs are 2, 3, 5, 7, 10, 11, 12, or 15\n");
608                 return -EINVAL;
609         }
610
611         ret = comedi_alloc_subdevices(dev, 4);
612         if (ret)
613                 return ret;
614
615         s = &dev->subdevices[0];
616         dev->read_subdev = s;
617         /* ai */
618         s->type = COMEDI_SUBD_AI;
619         s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
620         s->n_chan = 8;
621         s->subdev_flags = SDF_DIFF;
622         s->len_chanlist = 256;
623         s->maxdata = (1 << 12) - 1;
624         s->range_table = &range_das16m1;
625         s->insn_read = das16m1_ai_rinsn;
626         s->do_cmdtest = das16m1_cmd_test;
627         s->do_cmd = das16m1_cmd_exec;
628         s->cancel = das16m1_cancel;
629         s->poll = das16m1_poll;
630
631         s = &dev->subdevices[1];
632         /* di */
633         s->type = COMEDI_SUBD_DI;
634         s->subdev_flags = SDF_READABLE;
635         s->n_chan = 4;
636         s->maxdata = 1;
637         s->range_table = &range_digital;
638         s->insn_bits = das16m1_di_rbits;
639
640         s = &dev->subdevices[2];
641         /* do */
642         s->type = COMEDI_SUBD_DO;
643         s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
644         s->n_chan = 4;
645         s->maxdata = 1;
646         s->range_table = &range_digital;
647         s->insn_bits = das16m1_do_wbits;
648
649         s = &dev->subdevices[3];
650         /* 8255 */
651         ret = subdev_8255_init(dev, s, NULL, devpriv->extra_iobase);
652         if (ret)
653                 return ret;
654
655         /*  disable upper half of hardware conversion counter so it doesn't mess with us */
656         outb(TOTAL_CLEAR, dev->iobase + DAS16M1_8254_FIRST_CNTRL);
657
658         /*  initialize digital output lines */
659         outb(devpriv->do_bits, dev->iobase + DAS16M1_DIO);
660
661         /* set the interrupt level */
662         if (dev->irq)
663                 devpriv->control_state = das16m1_irq_bits(dev->irq);
664         else
665                 devpriv->control_state = 0;
666         outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
667
668         return 0;
669 }
670
671 static void das16m1_detach(struct comedi_device *dev)
672 {
673         struct das16m1_private_struct *devpriv = dev->private;
674
675         comedi_spriv_free(dev, 3);
676         if (devpriv && devpriv->extra_iobase)
677                 release_region(devpriv->extra_iobase, DAS16M1_SIZE2);
678         comedi_legacy_detach(dev);
679 }
680
681 static struct comedi_driver das16m1_driver = {
682         .driver_name    = "das16m1",
683         .module         = THIS_MODULE,
684         .attach         = das16m1_attach,
685         .detach         = das16m1_detach,
686 };
687 module_comedi_driver(das16m1_driver);
688
689 MODULE_AUTHOR("Comedi http://www.comedi.org");
690 MODULE_DESCRIPTION("Comedi low-level driver");
691 MODULE_LICENSE("GPL");