]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/staging/comedi/drivers/das16m1.c
Merge tag 'late-omap' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
[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     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 };
147
148 static inline short munge_sample(short data)
149 {
150         return (data >> 4) & 0xfff;
151 }
152
153 static void munge_sample_array(short *array, unsigned int num_elements)
154 {
155         unsigned int i;
156
157         for (i = 0; i < num_elements; i++)
158                 array[i] = munge_sample(array[i]);
159 }
160
161 static int das16m1_cmd_test(struct comedi_device *dev,
162                             struct comedi_subdevice *s, struct comedi_cmd *cmd)
163 {
164         struct das16m1_private_struct *devpriv = dev->private;
165         unsigned int err = 0, tmp, i;
166
167         /* Step 1 : check if triggers are trivially valid */
168
169         err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
170         err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
171         err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
172         err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
173         err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
174
175         if (err)
176                 return 1;
177
178         /* Step 2a : make sure trigger sources are unique */
179
180         err |= cfc_check_trigger_is_unique(cmd->start_src);
181         err |= cfc_check_trigger_is_unique(cmd->convert_src);
182         err |= cfc_check_trigger_is_unique(cmd->stop_src);
183
184         /* Step 2b : and mutually compatible */
185
186         if (err)
187                 return 2;
188
189         /* Step 3: check if arguments are trivially valid */
190
191         err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
192
193         if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
194                 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
195
196         if (cmd->convert_src == TRIG_TIMER)
197                 err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 1000);
198
199         err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
200
201         if (cmd->stop_src == TRIG_COUNT) {
202                 /* any count is allowed */
203         } else {
204                 /* TRIG_NONE */
205                 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
206         }
207
208         if (err)
209                 return 3;
210
211         /* step 4: fix up arguments */
212
213         if (cmd->convert_src == TRIG_TIMER) {
214                 tmp = cmd->convert_arg;
215                 /* calculate counter values that give desired timing */
216                 i8253_cascade_ns_to_timer_2div(DAS16M1_XTAL,
217                                                &(devpriv->divisor1),
218                                                &(devpriv->divisor2),
219                                                &(cmd->convert_arg),
220                                                cmd->flags & TRIG_ROUND_MASK);
221                 if (tmp != cmd->convert_arg)
222                         err++;
223         }
224
225         if (err)
226                 return 4;
227
228         /*  check chanlist against board's peculiarities */
229         if (cmd->chanlist && cmd->chanlist_len > 1) {
230                 for (i = 0; i < cmd->chanlist_len; i++) {
231                         /*  even/odd channels must go into even/odd queue addresses */
232                         if ((i % 2) != (CR_CHAN(cmd->chanlist[i]) % 2)) {
233                                 comedi_error(dev, "bad chanlist:\n"
234                                              " even/odd channels must go have even/odd chanlist indices");
235                                 err++;
236                         }
237                 }
238                 if ((cmd->chanlist_len % 2) != 0) {
239                         comedi_error(dev,
240                                      "chanlist must be of even length or length 1");
241                         err++;
242                 }
243         }
244
245         if (err)
246                 return 5;
247
248         return 0;
249 }
250
251 /* This function takes a time in nanoseconds and sets the     *
252  * 2 pacer clocks to the closest frequency possible. It also  *
253  * returns the actual sampling period.                        */
254 static unsigned int das16m1_set_pacer(struct comedi_device *dev,
255                                       unsigned int ns, int rounding_flags)
256 {
257         struct das16m1_private_struct *devpriv = dev->private;
258
259         i8253_cascade_ns_to_timer_2div(DAS16M1_XTAL, &(devpriv->divisor1),
260                                        &(devpriv->divisor2), &ns,
261                                        rounding_flags & TRIG_ROUND_MASK);
262
263         /* Write the values of ctr1 and ctr2 into counters 1 and 2 */
264         i8254_load(dev->iobase + DAS16M1_8254_SECOND, 0, 1, devpriv->divisor1,
265                    2);
266         i8254_load(dev->iobase + DAS16M1_8254_SECOND, 0, 2, devpriv->divisor2,
267                    2);
268
269         return ns;
270 }
271
272 static int das16m1_cmd_exec(struct comedi_device *dev,
273                             struct comedi_subdevice *s)
274 {
275         struct das16m1_private_struct *devpriv = dev->private;
276         struct comedi_async *async = s->async;
277         struct comedi_cmd *cmd = &async->cmd;
278         unsigned int byte, i;
279
280         if (dev->irq == 0) {
281                 comedi_error(dev, "irq required to execute comedi_cmd");
282                 return -1;
283         }
284
285         /* disable interrupts and internal pacer */
286         devpriv->control_state &= ~INTE & ~PACER_MASK;
287         outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
288
289         /*  set software count */
290         devpriv->adc_count = 0;
291         /* Initialize lower half of hardware counter, used to determine how
292          * many samples are in fifo.  Value doesn't actually load into counter
293          * until counter's next clock (the next a/d conversion) */
294         i8254_load(dev->iobase + DAS16M1_8254_FIRST, 0, 1, 0, 2);
295         /* remember current reading of counter so we know when counter has
296          * actually been loaded */
297         devpriv->initial_hw_count =
298             i8254_read(dev->iobase + DAS16M1_8254_FIRST, 0, 1);
299         /* setup channel/gain queue */
300         for (i = 0; i < cmd->chanlist_len; i++) {
301                 outb(i, dev->iobase + DAS16M1_QUEUE_ADDR);
302                 byte =
303                     Q_CHAN(CR_CHAN(cmd->chanlist[i])) |
304                     Q_RANGE(CR_RANGE(cmd->chanlist[i]));
305                 outb(byte, dev->iobase + DAS16M1_QUEUE_DATA);
306         }
307
308         /* set counter mode and counts */
309         cmd->convert_arg =
310             das16m1_set_pacer(dev, cmd->convert_arg,
311                               cmd->flags & TRIG_ROUND_MASK);
312
313         /*  set control & status register */
314         byte = 0;
315         /* if we are using external start trigger (also board dislikes having
316          * both start and conversion triggers external simultaneously) */
317         if (cmd->start_src == TRIG_EXT && cmd->convert_src != TRIG_EXT)
318                 byte |= EXT_TRIG_BIT;
319
320         outb(byte, dev->iobase + DAS16M1_CS);
321         /* clear interrupt bit */
322         outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
323
324         /* enable interrupts and internal pacer */
325         devpriv->control_state &= ~PACER_MASK;
326         if (cmd->convert_src == TRIG_TIMER)
327                 devpriv->control_state |= INT_PACER;
328         else
329                 devpriv->control_state |= EXT_PACER;
330
331         devpriv->control_state |= INTE;
332         outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
333
334         return 0;
335 }
336
337 static int das16m1_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
338 {
339         struct das16m1_private_struct *devpriv = dev->private;
340
341         devpriv->control_state &= ~INTE & ~PACER_MASK;
342         outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
343
344         return 0;
345 }
346
347 static int das16m1_ai_rinsn(struct comedi_device *dev,
348                             struct comedi_subdevice *s,
349                             struct comedi_insn *insn, unsigned int *data)
350 {
351         struct das16m1_private_struct *devpriv = dev->private;
352         int i, n;
353         int byte;
354         const int timeout = 1000;
355
356         /* disable interrupts and internal pacer */
357         devpriv->control_state &= ~INTE & ~PACER_MASK;
358         outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
359
360         /* setup channel/gain queue */
361         outb(0, dev->iobase + DAS16M1_QUEUE_ADDR);
362         byte =
363             Q_CHAN(CR_CHAN(insn->chanspec)) | Q_RANGE(CR_RANGE(insn->chanspec));
364         outb(byte, dev->iobase + DAS16M1_QUEUE_DATA);
365
366         for (n = 0; n < insn->n; n++) {
367                 /* clear IRQDATA bit */
368                 outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
369                 /* trigger conversion */
370                 outb(0, dev->iobase);
371
372                 for (i = 0; i < timeout; i++) {
373                         if (inb(dev->iobase + DAS16M1_CS) & IRQDATA)
374                                 break;
375                 }
376                 if (i == timeout) {
377                         comedi_error(dev, "timeout");
378                         return -ETIME;
379                 }
380                 data[n] = munge_sample(inw(dev->iobase));
381         }
382
383         return n;
384 }
385
386 static int das16m1_di_rbits(struct comedi_device *dev,
387                             struct comedi_subdevice *s,
388                             struct comedi_insn *insn, unsigned int *data)
389 {
390         unsigned int bits;
391
392         bits = inb(dev->iobase + DAS16M1_DIO) & 0xf;
393         data[1] = bits;
394         data[0] = 0;
395
396         return insn->n;
397 }
398
399 static int das16m1_do_wbits(struct comedi_device *dev,
400                             struct comedi_subdevice *s,
401                             struct comedi_insn *insn, unsigned int *data)
402 {
403         struct das16m1_private_struct *devpriv = dev->private;
404         unsigned int wbits;
405
406         /*  only set bits that have been masked */
407         data[0] &= 0xf;
408         wbits = devpriv->do_bits;
409         /*  zero bits that have been masked */
410         wbits &= ~data[0];
411         /*  set masked bits */
412         wbits |= data[0] & data[1];
413         devpriv->do_bits = wbits;
414         data[1] = wbits;
415
416         outb(devpriv->do_bits, dev->iobase + DAS16M1_DIO);
417
418         return insn->n;
419 }
420
421 static void das16m1_handler(struct comedi_device *dev, unsigned int status)
422 {
423         struct das16m1_private_struct *devpriv = dev->private;
424         struct comedi_subdevice *s;
425         struct comedi_async *async;
426         struct comedi_cmd *cmd;
427         u16 num_samples;
428         u16 hw_counter;
429
430         s = dev->read_subdev;
431         async = s->async;
432         async->events = 0;
433         cmd = &async->cmd;
434
435         /*  figure out how many samples are in fifo */
436         hw_counter = i8254_read(dev->iobase + DAS16M1_8254_FIRST, 0, 1);
437         /* make sure hardware counter reading is not bogus due to initial value
438          * not having been loaded yet */
439         if (devpriv->adc_count == 0 && hw_counter == devpriv->initial_hw_count) {
440                 num_samples = 0;
441         } else {
442                 /* The calculation of num_samples looks odd, but it uses the following facts.
443                  * 16 bit hardware counter is initialized with value of zero (which really
444                  * means 0x1000).  The counter decrements by one on each conversion
445                  * (when the counter decrements from zero it goes to 0xffff).  num_samples
446                  * is a 16 bit variable, so it will roll over in a similar fashion to the
447                  * hardware counter.  Work it out, and this is what you get. */
448                 num_samples = -hw_counter - devpriv->adc_count;
449         }
450         /*  check if we only need some of the points */
451         if (cmd->stop_src == TRIG_COUNT) {
452                 if (num_samples > cmd->stop_arg * cmd->chanlist_len)
453                         num_samples = cmd->stop_arg * cmd->chanlist_len;
454         }
455         /*  make sure we dont try to get too many points if fifo has overrun */
456         if (num_samples > FIFO_SIZE)
457                 num_samples = FIFO_SIZE;
458         insw(dev->iobase, devpriv->ai_buffer, num_samples);
459         munge_sample_array(devpriv->ai_buffer, num_samples);
460         cfc_write_array_to_buffer(s, devpriv->ai_buffer,
461                                   num_samples * sizeof(short));
462         devpriv->adc_count += num_samples;
463
464         if (cmd->stop_src == TRIG_COUNT) {
465                 if (devpriv->adc_count >= cmd->stop_arg * cmd->chanlist_len) {  /* end of acquisition */
466                         das16m1_cancel(dev, s);
467                         async->events |= COMEDI_CB_EOA;
468                 }
469         }
470
471         /* this probably won't catch overruns since the card doesn't generate
472          * overrun interrupts, but we might as well try */
473         if (status & OVRUN) {
474                 das16m1_cancel(dev, s);
475                 async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
476                 comedi_error(dev, "fifo overflow");
477         }
478
479         comedi_event(dev, s);
480
481 }
482
483 static int das16m1_poll(struct comedi_device *dev, struct comedi_subdevice *s)
484 {
485         unsigned long flags;
486         unsigned int status;
487
488         /*  prevent race with interrupt handler */
489         spin_lock_irqsave(&dev->spinlock, flags);
490         status = inb(dev->iobase + DAS16M1_CS);
491         das16m1_handler(dev, status);
492         spin_unlock_irqrestore(&dev->spinlock, flags);
493
494         return s->async->buf_write_count - s->async->buf_read_count;
495 }
496
497 static irqreturn_t das16m1_interrupt(int irq, void *d)
498 {
499         int status;
500         struct comedi_device *dev = d;
501
502         if (dev->attached == 0) {
503                 comedi_error(dev, "premature interrupt");
504                 return IRQ_HANDLED;
505         }
506         /*  prevent race with comedi_poll() */
507         spin_lock(&dev->spinlock);
508
509         status = inb(dev->iobase + DAS16M1_CS);
510
511         if ((status & (IRQDATA | OVRUN)) == 0) {
512                 comedi_error(dev, "spurious interrupt");
513                 spin_unlock(&dev->spinlock);
514                 return IRQ_NONE;
515         }
516
517         das16m1_handler(dev, status);
518
519         /* clear interrupt */
520         outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
521
522         spin_unlock(&dev->spinlock);
523         return IRQ_HANDLED;
524 }
525
526 static int das16m1_irq_bits(unsigned int irq)
527 {
528         int ret;
529
530         switch (irq) {
531         case 10:
532                 ret = 0x0;
533                 break;
534         case 11:
535                 ret = 0x1;
536                 break;
537         case 12:
538                 ret = 0x2;
539                 break;
540         case 15:
541                 ret = 0x3;
542                 break;
543         case 2:
544                 ret = 0x4;
545                 break;
546         case 3:
547                 ret = 0x5;
548                 break;
549         case 5:
550                 ret = 0x6;
551                 break;
552         case 7:
553                 ret = 0x7;
554                 break;
555         default:
556                 return -1;
557                 break;
558         }
559         return ret << 4;
560 }
561
562 /*
563  * Options list:
564  *   0  I/O base
565  *   1  IRQ
566  */
567 static int das16m1_attach(struct comedi_device *dev,
568                           struct comedi_devconfig *it)
569 {
570         struct das16m1_private_struct *devpriv;
571         struct comedi_subdevice *s;
572         int ret;
573         unsigned int irq;
574         unsigned long iobase;
575
576         dev->board_name = dev->driver->driver_name;
577
578         iobase = it->options[0];
579
580         devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
581         if (!devpriv)
582                 return -ENOMEM;
583         dev->private = devpriv;
584
585         if (!request_region(iobase, DAS16M1_SIZE, dev->board_name)) {
586                 comedi_error(dev, "I/O port conflict\n");
587                 return -EIO;
588         }
589         if (!request_region(iobase + DAS16M1_82C55, DAS16M1_SIZE2,
590                             dev->board_name)) {
591                 release_region(iobase, DAS16M1_SIZE);
592                 comedi_error(dev, "I/O port conflict\n");
593                 return -EIO;
594         }
595         dev->iobase = iobase;
596
597         /* now for the irq */
598         irq = it->options[1];
599         /*  make sure it is valid */
600         if (das16m1_irq_bits(irq) >= 0) {
601                 ret = request_irq(irq, das16m1_interrupt, 0,
602                                   dev->driver->driver_name, dev);
603                 if (ret < 0)
604                         return ret;
605                 dev->irq = irq;
606                 printk
607                     ("irq %u\n", irq);
608         } else if (irq == 0) {
609                 printk
610                     (", no irq\n");
611         } else {
612                 comedi_error(dev, "invalid irq\n"
613                              " valid irqs are 2, 3, 5, 7, 10, 11, 12, or 15\n");
614                 return -EINVAL;
615         }
616
617         ret = comedi_alloc_subdevices(dev, 4);
618         if (ret)
619                 return ret;
620
621         s = &dev->subdevices[0];
622         dev->read_subdev = s;
623         /* ai */
624         s->type = COMEDI_SUBD_AI;
625         s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
626         s->n_chan = 8;
627         s->subdev_flags = SDF_DIFF;
628         s->len_chanlist = 256;
629         s->maxdata = (1 << 12) - 1;
630         s->range_table = &range_das16m1;
631         s->insn_read = das16m1_ai_rinsn;
632         s->do_cmdtest = das16m1_cmd_test;
633         s->do_cmd = das16m1_cmd_exec;
634         s->cancel = das16m1_cancel;
635         s->poll = das16m1_poll;
636
637         s = &dev->subdevices[1];
638         /* di */
639         s->type = COMEDI_SUBD_DI;
640         s->subdev_flags = SDF_READABLE;
641         s->n_chan = 4;
642         s->maxdata = 1;
643         s->range_table = &range_digital;
644         s->insn_bits = das16m1_di_rbits;
645
646         s = &dev->subdevices[2];
647         /* do */
648         s->type = COMEDI_SUBD_DO;
649         s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
650         s->n_chan = 4;
651         s->maxdata = 1;
652         s->range_table = &range_digital;
653         s->insn_bits = das16m1_do_wbits;
654
655         s = &dev->subdevices[3];
656         /* 8255 */
657         subdev_8255_init(dev, s, NULL, dev->iobase + DAS16M1_82C55);
658
659         /*  disable upper half of hardware conversion counter so it doesn't mess with us */
660         outb(TOTAL_CLEAR, dev->iobase + DAS16M1_8254_FIRST_CNTRL);
661
662         /*  initialize digital output lines */
663         outb(devpriv->do_bits, dev->iobase + DAS16M1_DIO);
664
665         /* set the interrupt level */
666         if (dev->irq)
667                 devpriv->control_state = das16m1_irq_bits(dev->irq);
668         else
669                 devpriv->control_state = 0;
670         outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
671
672         return 0;
673 }
674
675 static void das16m1_detach(struct comedi_device *dev)
676 {
677         if (dev->subdevices)
678                 subdev_8255_cleanup(dev, &dev->subdevices[3]);
679         if (dev->irq)
680                 free_irq(dev->irq, dev);
681         if (dev->iobase) {
682                 release_region(dev->iobase, DAS16M1_SIZE);
683                 release_region(dev->iobase + DAS16M1_82C55, DAS16M1_SIZE2);
684         }
685 }
686
687 static struct comedi_driver das16m1_driver = {
688         .driver_name    = "das16m1",
689         .module         = THIS_MODULE,
690         .attach         = das16m1_attach,
691         .detach         = das16m1_detach,
692 };
693 module_comedi_driver(das16m1_driver);
694
695 MODULE_AUTHOR("Comedi http://www.comedi.org");
696 MODULE_DESCRIPTION("Comedi low-level driver");
697 MODULE_LICENSE("GPL");