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