xref: /linux/drivers/comedi/drivers/das16m1.c (revision 0be3ff0c)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Comedi driver for CIO-DAS16/M1
4  * Author: Frank Mori Hess, based on code from the das16 driver.
5  * Copyright (C) 2001 Frank Mori Hess <fmhess@users.sourceforge.net>
6  *
7  * COMEDI - Linux Control and Measurement Device Interface
8  * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
9  */
10 
11 /*
12  * Driver: das16m1
13  * Description: CIO-DAS16/M1
14  * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
15  * Devices: [Measurement Computing] CIO-DAS16/M1 (das16m1)
16  * Status: works
17  *
18  * This driver supports a single board - the CIO-DAS16/M1. As far as I know,
19  * there are no other boards that have the same register layout. Even the
20  * CIO-DAS16/M1/16 is significantly different.
21  *
22  * I was _barely_ able to reach the full 1 MHz capability of this board, using
23  * a hard real-time interrupt (set the TRIG_RT flag in your struct comedi_cmd
24  * and use rtlinux or RTAI). The board can't do dma, so the bottleneck is
25  * pulling the data across the ISA bus. I timed the interrupt handler, and it
26  * took my computer ~470 microseconds to pull 512 samples from the board. So
27  * at 1 Mhz sampling rate, expect your CPU to be spending almost all of its
28  * time in the interrupt handler.
29  *
30  * This board has some unusual restrictions for its channel/gain list.  If the
31  * list has 2 or more channels in it, then two conditions must be satisfied:
32  * (1) - even/odd channels must appear at even/odd indices in the list
33  * (2) - the list must have an even number of entries.
34  *
35  * Configuration options:
36  *   [0] - base io address
37  *   [1] - irq (optional, but you probably want it)
38  *
39  * irq can be omitted, although the cmd interface will not work without it.
40  */
41 
42 #include <linux/module.h>
43 #include <linux/slab.h>
44 #include <linux/interrupt.h>
45 #include <linux/comedi/comedidev.h>
46 #include <linux/comedi/comedi_8255.h>
47 #include <linux/comedi/comedi_8254.h>
48 
49 /*
50  * Register map (dev->iobase)
51  */
52 #define DAS16M1_AI_REG			0x00	/* 16-bit register */
53 #define DAS16M1_AI_TO_CHAN(x)		(((x) >> 0) & 0xf)
54 #define DAS16M1_AI_TO_SAMPLE(x)		(((x) >> 4) & 0xfff)
55 #define DAS16M1_CS_REG			0x02
56 #define DAS16M1_CS_EXT_TRIG		BIT(0)
57 #define DAS16M1_CS_OVRUN		BIT(5)
58 #define DAS16M1_CS_IRQDATA		BIT(7)
59 #define DAS16M1_DI_REG			0x03
60 #define DAS16M1_DO_REG			0x03
61 #define DAS16M1_CLR_INTR_REG		0x04
62 #define DAS16M1_INTR_CTRL_REG		0x05
63 #define DAS16M1_INTR_CTRL_PACER(x)	(((x) & 0x3) << 0)
64 #define DAS16M1_INTR_CTRL_PACER_EXT	DAS16M1_INTR_CTRL_PACER(2)
65 #define DAS16M1_INTR_CTRL_PACER_INT	DAS16M1_INTR_CTRL_PACER(3)
66 #define DAS16M1_INTR_CTRL_PACER_MASK	DAS16M1_INTR_CTRL_PACER(3)
67 #define DAS16M1_INTR_CTRL_IRQ(x)	(((x) & 0x7) << 4)
68 #define DAS16M1_INTR_CTRL_INTE		BIT(7)
69 #define DAS16M1_Q_ADDR_REG		0x06
70 #define DAS16M1_Q_REG			0x07
71 #define DAS16M1_Q_CHAN(x)              (((x) & 0x7) << 0)
72 #define DAS16M1_Q_RANGE(x)             (((x) & 0xf) << 4)
73 #define DAS16M1_8254_IOBASE1		0x08
74 #define DAS16M1_8254_IOBASE2		0x0c
75 #define DAS16M1_8255_IOBASE		0x400
76 #define DAS16M1_8254_IOBASE3		0x404
77 
78 #define DAS16M1_SIZE2			0x08
79 
80 #define DAS16M1_AI_FIFO_SZ		1024	/* # samples */
81 
82 static const struct comedi_lrange range_das16m1 = {
83 	9, {
84 		BIP_RANGE(5),
85 		BIP_RANGE(2.5),
86 		BIP_RANGE(1.25),
87 		BIP_RANGE(0.625),
88 		UNI_RANGE(10),
89 		UNI_RANGE(5),
90 		UNI_RANGE(2.5),
91 		UNI_RANGE(1.25),
92 		BIP_RANGE(10)
93 	}
94 };
95 
96 struct das16m1_private {
97 	struct comedi_8254 *counter;
98 	unsigned int intr_ctrl;
99 	unsigned int adc_count;
100 	u16 initial_hw_count;
101 	unsigned short ai_buffer[DAS16M1_AI_FIFO_SZ];
102 	unsigned long extra_iobase;
103 };
104 
105 static void das16m1_ai_set_queue(struct comedi_device *dev,
106 				 unsigned int *chanspec, unsigned int len)
107 {
108 	unsigned int i;
109 
110 	for (i = 0; i < len; i++) {
111 		unsigned int chan = CR_CHAN(chanspec[i]);
112 		unsigned int range = CR_RANGE(chanspec[i]);
113 
114 		outb(i, dev->iobase + DAS16M1_Q_ADDR_REG);
115 		outb(DAS16M1_Q_CHAN(chan) | DAS16M1_Q_RANGE(range),
116 		     dev->iobase + DAS16M1_Q_REG);
117 	}
118 }
119 
120 static void das16m1_ai_munge(struct comedi_device *dev,
121 			     struct comedi_subdevice *s,
122 			     void *data, unsigned int num_bytes,
123 			     unsigned int start_chan_index)
124 {
125 	unsigned short *array = data;
126 	unsigned int nsamples = comedi_bytes_to_samples(s, num_bytes);
127 	unsigned int i;
128 
129 	/*
130 	 * The fifo values have the channel number in the lower 4-bits and
131 	 * the sample in the upper 12-bits. This just shifts the values
132 	 * to remove the channel numbers.
133 	 */
134 	for (i = 0; i < nsamples; i++)
135 		array[i] = DAS16M1_AI_TO_SAMPLE(array[i]);
136 }
137 
138 static int das16m1_ai_check_chanlist(struct comedi_device *dev,
139 				     struct comedi_subdevice *s,
140 				     struct comedi_cmd *cmd)
141 {
142 	int i;
143 
144 	if (cmd->chanlist_len == 1)
145 		return 0;
146 
147 	if ((cmd->chanlist_len % 2) != 0) {
148 		dev_dbg(dev->class_dev,
149 			"chanlist must be of even length or length 1\n");
150 		return -EINVAL;
151 	}
152 
153 	for (i = 0; i < cmd->chanlist_len; i++) {
154 		unsigned int chan = CR_CHAN(cmd->chanlist[i]);
155 
156 		if ((i % 2) != (chan % 2)) {
157 			dev_dbg(dev->class_dev,
158 				"even/odd channels must go have even/odd chanlist indices\n");
159 			return -EINVAL;
160 		}
161 	}
162 
163 	return 0;
164 }
165 
166 static int das16m1_ai_cmdtest(struct comedi_device *dev,
167 			      struct comedi_subdevice *s,
168 			      struct comedi_cmd *cmd)
169 {
170 	int err = 0;
171 
172 	/* Step 1 : check if triggers are trivially valid */
173 
174 	err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
175 	err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
176 	err |= comedi_check_trigger_src(&cmd->convert_src,
177 					TRIG_TIMER | TRIG_EXT);
178 	err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
179 	err |= comedi_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 |= comedi_check_trigger_is_unique(cmd->start_src);
187 	err |= comedi_check_trigger_is_unique(cmd->convert_src);
188 	err |= comedi_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: check if arguments are trivially valid */
196 
197 	err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
198 
199 	if (cmd->scan_begin_src == TRIG_FOLLOW)	/* internal trigger */
200 		err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
201 
202 	if (cmd->convert_src == TRIG_TIMER)
203 		err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 1000);
204 
205 	err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
206 					   cmd->chanlist_len);
207 
208 	if (cmd->stop_src == TRIG_COUNT)
209 		err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
210 	else	/* TRIG_NONE */
211 		err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
212 
213 	if (err)
214 		return 3;
215 
216 	/* step 4: fix up arguments */
217 
218 	if (cmd->convert_src == TRIG_TIMER) {
219 		unsigned int arg = cmd->convert_arg;
220 
221 		comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
222 		err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
223 	}
224 
225 	if (err)
226 		return 4;
227 
228 	/* Step 5: check channel list if it exists */
229 	if (cmd->chanlist && cmd->chanlist_len > 0)
230 		err |= das16m1_ai_check_chanlist(dev, s, cmd);
231 
232 	if (err)
233 		return 5;
234 
235 	return 0;
236 }
237 
238 static int das16m1_ai_cmd(struct comedi_device *dev,
239 			  struct comedi_subdevice *s)
240 {
241 	struct das16m1_private *devpriv = dev->private;
242 	struct comedi_async *async = s->async;
243 	struct comedi_cmd *cmd = &async->cmd;
244 	unsigned int byte;
245 
246 	/*  set software count */
247 	devpriv->adc_count = 0;
248 
249 	/*
250 	 * Initialize lower half of hardware counter, used to determine how
251 	 * many samples are in fifo.  Value doesn't actually load into counter
252 	 * until counter's next clock (the next a/d conversion).
253 	 */
254 	comedi_8254_set_mode(devpriv->counter, 1, I8254_MODE2 | I8254_BINARY);
255 	comedi_8254_write(devpriv->counter, 1, 0);
256 
257 	/*
258 	 * Remember current reading of counter so we know when counter has
259 	 * actually been loaded.
260 	 */
261 	devpriv->initial_hw_count = comedi_8254_read(devpriv->counter, 1);
262 
263 	das16m1_ai_set_queue(dev, cmd->chanlist, cmd->chanlist_len);
264 
265 	/* enable interrupts and set internal pacer counter mode and counts */
266 	devpriv->intr_ctrl &= ~DAS16M1_INTR_CTRL_PACER_MASK;
267 	if (cmd->convert_src == TRIG_TIMER) {
268 		comedi_8254_update_divisors(dev->pacer);
269 		comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
270 		devpriv->intr_ctrl |= DAS16M1_INTR_CTRL_PACER_INT;
271 	} else {	/* TRIG_EXT */
272 		devpriv->intr_ctrl |= DAS16M1_INTR_CTRL_PACER_EXT;
273 	}
274 
275 	/*  set control & status register */
276 	byte = 0;
277 	/*
278 	 * If we are using external start trigger (also board dislikes having
279 	 * both start and conversion triggers external simultaneously).
280 	 */
281 	if (cmd->start_src == TRIG_EXT && cmd->convert_src != TRIG_EXT)
282 		byte |= DAS16M1_CS_EXT_TRIG;
283 
284 	outb(byte, dev->iobase + DAS16M1_CS_REG);
285 
286 	/* clear interrupt */
287 	outb(0, dev->iobase + DAS16M1_CLR_INTR_REG);
288 
289 	devpriv->intr_ctrl |= DAS16M1_INTR_CTRL_INTE;
290 	outb(devpriv->intr_ctrl, dev->iobase + DAS16M1_INTR_CTRL_REG);
291 
292 	return 0;
293 }
294 
295 static int das16m1_ai_cancel(struct comedi_device *dev,
296 			     struct comedi_subdevice *s)
297 {
298 	struct das16m1_private *devpriv = dev->private;
299 
300 	/* disable interrupts and pacer */
301 	devpriv->intr_ctrl &= ~(DAS16M1_INTR_CTRL_INTE |
302 				DAS16M1_INTR_CTRL_PACER_MASK);
303 	outb(devpriv->intr_ctrl, dev->iobase + DAS16M1_INTR_CTRL_REG);
304 
305 	return 0;
306 }
307 
308 static int das16m1_ai_eoc(struct comedi_device *dev,
309 			  struct comedi_subdevice *s,
310 			  struct comedi_insn *insn,
311 			  unsigned long context)
312 {
313 	unsigned int status;
314 
315 	status = inb(dev->iobase + DAS16M1_CS_REG);
316 	if (status & DAS16M1_CS_IRQDATA)
317 		return 0;
318 	return -EBUSY;
319 }
320 
321 static int das16m1_ai_insn_read(struct comedi_device *dev,
322 				struct comedi_subdevice *s,
323 				struct comedi_insn *insn,
324 				unsigned int *data)
325 {
326 	int ret;
327 	int i;
328 
329 	das16m1_ai_set_queue(dev, &insn->chanspec, 1);
330 
331 	for (i = 0; i < insn->n; i++) {
332 		unsigned short val;
333 
334 		/* clear interrupt */
335 		outb(0, dev->iobase + DAS16M1_CLR_INTR_REG);
336 		/* trigger conversion */
337 		outb(0, dev->iobase + DAS16M1_AI_REG);
338 
339 		ret = comedi_timeout(dev, s, insn, das16m1_ai_eoc, 0);
340 		if (ret)
341 			return ret;
342 
343 		val = inw(dev->iobase + DAS16M1_AI_REG);
344 		data[i] = DAS16M1_AI_TO_SAMPLE(val);
345 	}
346 
347 	return insn->n;
348 }
349 
350 static int das16m1_di_insn_bits(struct comedi_device *dev,
351 				struct comedi_subdevice *s,
352 				struct comedi_insn *insn,
353 				unsigned int *data)
354 {
355 	data[1] = inb(dev->iobase + DAS16M1_DI_REG) & 0xf;
356 
357 	return insn->n;
358 }
359 
360 static int das16m1_do_insn_bits(struct comedi_device *dev,
361 				struct comedi_subdevice *s,
362 				struct comedi_insn *insn,
363 				unsigned int *data)
364 {
365 	if (comedi_dio_update_state(s, data))
366 		outb(s->state, dev->iobase + DAS16M1_DO_REG);
367 
368 	data[1] = s->state;
369 
370 	return insn->n;
371 }
372 
373 static void das16m1_handler(struct comedi_device *dev, unsigned int status)
374 {
375 	struct das16m1_private *devpriv = dev->private;
376 	struct comedi_subdevice *s = dev->read_subdev;
377 	struct comedi_async *async = s->async;
378 	struct comedi_cmd *cmd = &async->cmd;
379 	u16 num_samples;
380 	u16 hw_counter;
381 
382 	/* figure out how many samples are in fifo */
383 	hw_counter = comedi_8254_read(devpriv->counter, 1);
384 	/*
385 	 * Make sure hardware counter reading is not bogus due to initial
386 	 * value not having been loaded yet.
387 	 */
388 	if (devpriv->adc_count == 0 &&
389 	    hw_counter == devpriv->initial_hw_count) {
390 		num_samples = 0;
391 	} else {
392 		/*
393 		 * The calculation of num_samples looks odd, but it uses the
394 		 * following facts. 16 bit hardware counter is initialized with
395 		 * value of zero (which really means 0x1000).  The counter
396 		 * decrements by one on each conversion (when the counter
397 		 * decrements from zero it goes to 0xffff).  num_samples is a
398 		 * 16 bit variable, so it will roll over in a similar fashion
399 		 * to the hardware counter.  Work it out, and this is what you
400 		 * get.
401 		 */
402 		num_samples = -hw_counter - devpriv->adc_count;
403 	}
404 	/*  check if we only need some of the points */
405 	if (cmd->stop_src == TRIG_COUNT) {
406 		if (num_samples > cmd->stop_arg * cmd->chanlist_len)
407 			num_samples = cmd->stop_arg * cmd->chanlist_len;
408 	}
409 	/*  make sure we don't try to get too many points if fifo has overrun */
410 	if (num_samples > DAS16M1_AI_FIFO_SZ)
411 		num_samples = DAS16M1_AI_FIFO_SZ;
412 	insw(dev->iobase, devpriv->ai_buffer, num_samples);
413 	comedi_buf_write_samples(s, devpriv->ai_buffer, num_samples);
414 	devpriv->adc_count += num_samples;
415 
416 	if (cmd->stop_src == TRIG_COUNT) {
417 		if (devpriv->adc_count >= cmd->stop_arg * cmd->chanlist_len) {
418 			/* end of acquisition */
419 			async->events |= COMEDI_CB_EOA;
420 		}
421 	}
422 
423 	/*
424 	 * This probably won't catch overruns since the card doesn't generate
425 	 * overrun interrupts, but we might as well try.
426 	 */
427 	if (status & DAS16M1_CS_OVRUN) {
428 		async->events |= COMEDI_CB_ERROR;
429 		dev_err(dev->class_dev, "fifo overflow\n");
430 	}
431 
432 	comedi_handle_events(dev, s);
433 }
434 
435 static int das16m1_ai_poll(struct comedi_device *dev,
436 			   struct comedi_subdevice *s)
437 {
438 	unsigned long flags;
439 	unsigned int status;
440 
441 	/*  prevent race with interrupt handler */
442 	spin_lock_irqsave(&dev->spinlock, flags);
443 	status = inb(dev->iobase + DAS16M1_CS_REG);
444 	das16m1_handler(dev, status);
445 	spin_unlock_irqrestore(&dev->spinlock, flags);
446 
447 	return comedi_buf_n_bytes_ready(s);
448 }
449 
450 static irqreturn_t das16m1_interrupt(int irq, void *d)
451 {
452 	int status;
453 	struct comedi_device *dev = d;
454 
455 	if (!dev->attached) {
456 		dev_err(dev->class_dev, "premature interrupt\n");
457 		return IRQ_HANDLED;
458 	}
459 	/*  prevent race with comedi_poll() */
460 	spin_lock(&dev->spinlock);
461 
462 	status = inb(dev->iobase + DAS16M1_CS_REG);
463 
464 	if ((status & (DAS16M1_CS_IRQDATA | DAS16M1_CS_OVRUN)) == 0) {
465 		dev_err(dev->class_dev, "spurious interrupt\n");
466 		spin_unlock(&dev->spinlock);
467 		return IRQ_NONE;
468 	}
469 
470 	das16m1_handler(dev, status);
471 
472 	/* clear interrupt */
473 	outb(0, dev->iobase + DAS16M1_CLR_INTR_REG);
474 
475 	spin_unlock(&dev->spinlock);
476 	return IRQ_HANDLED;
477 }
478 
479 static int das16m1_irq_bits(unsigned int irq)
480 {
481 	switch (irq) {
482 	case 10:
483 		return 0x0;
484 	case 11:
485 		return 0x1;
486 	case 12:
487 		return 0x2;
488 	case 15:
489 		return 0x3;
490 	case 2:
491 		return 0x4;
492 	case 3:
493 		return 0x5;
494 	case 5:
495 		return 0x6;
496 	case 7:
497 		return 0x7;
498 	default:
499 		return 0x0;
500 	}
501 }
502 
503 static int das16m1_attach(struct comedi_device *dev,
504 			  struct comedi_devconfig *it)
505 {
506 	struct das16m1_private *devpriv;
507 	struct comedi_subdevice *s;
508 	int ret;
509 
510 	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
511 	if (!devpriv)
512 		return -ENOMEM;
513 
514 	ret = comedi_request_region(dev, it->options[0], 0x10);
515 	if (ret)
516 		return ret;
517 	/* Request an additional region for the 8255 and 3rd 8254 */
518 	ret = __comedi_request_region(dev, dev->iobase + DAS16M1_8255_IOBASE,
519 				      DAS16M1_SIZE2);
520 	if (ret)
521 		return ret;
522 	devpriv->extra_iobase = dev->iobase + DAS16M1_8255_IOBASE;
523 
524 	/* only irqs 2, 3, 4, 5, 6, 7, 10, 11, 12, 14, and 15 are valid */
525 	if ((1 << it->options[1]) & 0xdcfc) {
526 		ret = request_irq(it->options[1], das16m1_interrupt, 0,
527 				  dev->board_name, dev);
528 		if (ret == 0)
529 			dev->irq = it->options[1];
530 	}
531 
532 	dev->pacer = comedi_8254_init(dev->iobase + DAS16M1_8254_IOBASE2,
533 				      I8254_OSC_BASE_10MHZ, I8254_IO8, 0);
534 	if (!dev->pacer)
535 		return -ENOMEM;
536 
537 	devpriv->counter = comedi_8254_init(dev->iobase + DAS16M1_8254_IOBASE1,
538 					    0, I8254_IO8, 0);
539 	if (!devpriv->counter)
540 		return -ENOMEM;
541 
542 	ret = comedi_alloc_subdevices(dev, 4);
543 	if (ret)
544 		return ret;
545 
546 	/* Analog Input subdevice */
547 	s = &dev->subdevices[0];
548 	s->type		= COMEDI_SUBD_AI;
549 	s->subdev_flags	= SDF_READABLE | SDF_DIFF;
550 	s->n_chan	= 8;
551 	s->maxdata	= 0x0fff;
552 	s->range_table	= &range_das16m1;
553 	s->insn_read	= das16m1_ai_insn_read;
554 	if (dev->irq) {
555 		dev->read_subdev = s;
556 		s->subdev_flags	|= SDF_CMD_READ;
557 		s->len_chanlist	= 256;
558 		s->do_cmdtest	= das16m1_ai_cmdtest;
559 		s->do_cmd	= das16m1_ai_cmd;
560 		s->cancel	= das16m1_ai_cancel;
561 		s->poll		= das16m1_ai_poll;
562 		s->munge	= das16m1_ai_munge;
563 	}
564 
565 	/* Digital Input subdevice */
566 	s = &dev->subdevices[1];
567 	s->type		= COMEDI_SUBD_DI;
568 	s->subdev_flags	= SDF_READABLE;
569 	s->n_chan	= 4;
570 	s->maxdata	= 1;
571 	s->range_table	= &range_digital;
572 	s->insn_bits	= das16m1_di_insn_bits;
573 
574 	/* Digital Output subdevice */
575 	s = &dev->subdevices[2];
576 	s->type		= COMEDI_SUBD_DO;
577 	s->subdev_flags	= SDF_WRITABLE;
578 	s->n_chan	= 4;
579 	s->maxdata	= 1;
580 	s->range_table	= &range_digital;
581 	s->insn_bits	= das16m1_do_insn_bits;
582 
583 	/* Digital I/O subdevice (8255) */
584 	s = &dev->subdevices[3];
585 	ret = subdev_8255_init(dev, s, NULL, DAS16M1_8255_IOBASE);
586 	if (ret)
587 		return ret;
588 
589 	/*  initialize digital output lines */
590 	outb(0, dev->iobase + DAS16M1_DO_REG);
591 
592 	/* set the interrupt level */
593 	devpriv->intr_ctrl = DAS16M1_INTR_CTRL_IRQ(das16m1_irq_bits(dev->irq));
594 	outb(devpriv->intr_ctrl, dev->iobase + DAS16M1_INTR_CTRL_REG);
595 
596 	return 0;
597 }
598 
599 static void das16m1_detach(struct comedi_device *dev)
600 {
601 	struct das16m1_private *devpriv = dev->private;
602 
603 	if (devpriv) {
604 		if (devpriv->extra_iobase)
605 			release_region(devpriv->extra_iobase, DAS16M1_SIZE2);
606 		kfree(devpriv->counter);
607 	}
608 	comedi_legacy_detach(dev);
609 }
610 
611 static struct comedi_driver das16m1_driver = {
612 	.driver_name	= "das16m1",
613 	.module		= THIS_MODULE,
614 	.attach		= das16m1_attach,
615 	.detach		= das16m1_detach,
616 };
617 module_comedi_driver(das16m1_driver);
618 
619 MODULE_AUTHOR("Comedi https://www.comedi.org");
620 MODULE_DESCRIPTION("Comedi driver for CIO-DAS16/M1 ISA cards");
621 MODULE_LICENSE("GPL");
622