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