Linux 3.9-rc5
[deliverable/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");
This page took 0.096983 seconds and 5 git commands to generate.