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