dt282x.c revision d7b6c574d17fed09a0d9fa4bc7674c13fa156805
1/*
2 * dt282x.c
3 * Comedi driver for Data Translation DT2821 series
4 *
5 * COMEDI - Linux Control and Measurement Device Interface
6 * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 */
18
19/*
20 * Driver: dt282x
21 * Description: Data Translation DT2821 series (including DT-EZ)
22 * Author: ds
23 * Devices: (Data Translation) DT2821 [dt2821]
24 *	    (Data Translation) DT2821-F-16SE [dt2821-f]
25 *	    (Data Translation) DT2821-F-8DI [dt2821-f]
26 *	    (Data Translation) DT2821-G-16SE [dt2821-g]
27 *	    (Data Translation) DT2821-G-8DI [dt2821-g]
28 *	    (Data Translation) DT2823 [dt2823]
29 *	    (Data Translation) DT2824-PGH [dt2824-pgh]
30 *	    (Data Translation) DT2824-PGL [dt2824-pgl]
31 *	    (Data Translation) DT2825 [dt2825]
32 *	    (Data Translation) DT2827 [dt2827]
33 *	    (Data Translation) DT2828 [dt2828]
34 *	    (Data Translation) DT2928 [dt2829]
35 *	    (Data Translation) DT21-EZ [dt21-ez]
36 *	    (Data Translation) DT23-EZ [dt23-ez]
37 *	    (Data Translation) DT24-EZ [dt24-ez]
38 *	    (Data Translation) DT24-EZ-PGL [dt24-ez-pgl]
39 * Status: complete
40 * Updated: Wed, 22 Aug 2001 17:11:34 -0700
41 *
42 * Configuration options:
43 *   [0] - I/O port base address
44 *   [1] - IRQ (optional, required for async command support)
45 *   [2] - DMA 1 (optional, required for async command support)
46 *   [3] - DMA 2 (optional, required for async command support)
47 *   [4] - AI jumpered for 0=single ended, 1=differential
48 *   [5] - AI jumpered for 0=straight binary, 1=2's complement
49 *   [6] - AO 0 data format (deprecated, see below)
50 *   [7] - AO 1 data format (deprecated, see below)
51 *   [8] - AI jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5]
52 *   [9] - AO channel 0 range (deprecated, see below)
53 *   [10]- AO channel 1 range (deprecated, see below)
54 *
55 * Notes:
56 *   - AO commands might be broken.
57 *   - If you try to run a command on both the AI and AO subdevices
58 *     simultaneously, bad things will happen.  The driver needs to
59 *     be fixed to check for this situation and return an error.
60 *   - AO range is not programmable. The AO subdevice has a range_table
61 *     containing all the possible analog output ranges. Use the range
62 *     that matches your board configuration to convert between data
63 *     values and physical units. The format of the data written to the
64 *     board is handled automatically based on the unipolar/bipolar
65 *     range that is selected.
66 */
67
68#include <linux/module.h>
69#include "../comedidev.h"
70
71#include <linux/delay.h>
72#include <linux/gfp.h>
73#include <linux/interrupt.h>
74#include <linux/io.h>
75
76#include <asm/dma.h>
77
78#include "comedi_fc.h"
79
80/*
81 * Register map
82 */
83#define DT2821_ADCSR_REG		0x00
84#define DT2821_ADCSR_ADERR		(1 << 15)
85#define DT2821_ADCSR_ADCLK		(1 << 9)
86#define DT2821_ADCSR_MUXBUSY		(1 << 8)
87#define DT2821_ADCSR_ADDONE		(1 << 7)
88#define DT2821_ADCSR_IADDONE		(1 << 6)
89#define DT2821_ADCSR_GS(x)		(((x) & 0x3) << 4)
90#define DT2821_ADCSR_CHAN(x)		(((x) & 0xf) << 0)
91#define DT2821_CHANCSR_REG		0x02
92#define DT2821_CHANCSR_LLE		(1 << 15)
93#define DT2821_CHANCSR_PRESLA(x)	(((x) & 0xf) >> 8)
94#define DT2821_CHANCSR_NUMB(x)		((((x) - 1) & 0xf) << 0)
95#define DT2821_ADDAT_REG		0x04
96#define DT2821_DACSR_REG		0x06
97#define DT2821_DACSR_DAERR		(1 << 15)
98#define DT2821_DACSR_YSEL(x)		((x) << 9)
99#define DT2821_DACSR_SSEL		(1 << 8)
100#define DT2821_DACSR_DACRDY		(1 << 7)
101#define DT2821_DACSR_IDARDY		(1 << 6)
102#define DT2821_DACSR_DACLK		(1 << 5)
103#define DT2821_DACSR_HBOE		(1 << 1)
104#define DT2821_DACSR_LBOE		(1 << 0)
105#define DT2821_DADAT_REG		0x08
106#define DT2821_DIODAT_REG		0x0a
107#define DT2821_SUPCSR_REG		0x0c
108#define DT2821_SUPCSR_DMAD		(1 << 15)
109#define DT2821_SUPCSR_ERRINTEN		(1 << 14)
110#define DT2821_SUPCSR_CLRDMADNE		(1 << 13)
111#define DT2821_SUPCSR_DDMA		(1 << 12)
112#define DT2821_SUPCSR_DS_PIO		(0 << 10)
113#define DT2821_SUPCSR_DS_AD_CLK		(1 << 10)
114#define DT2821_SUPCSR_DS_DA_CLK		(2 << 10)
115#define DT2821_SUPCSR_DS_AD_TRIG	(3 << 10)
116#define DT2821_SUPCSR_BUFFB		(1 << 9)
117#define DT2821_SUPCSR_SCDN		(1 << 8)
118#define DT2821_SUPCSR_DACON		(1 << 7)
119#define DT2821_SUPCSR_ADCINIT		(1 << 6)
120#define DT2821_SUPCSR_DACINIT		(1 << 5)
121#define DT2821_SUPCSR_PRLD		(1 << 4)
122#define DT2821_SUPCSR_STRIG		(1 << 3)
123#define DT2821_SUPCSR_XTRIG		(1 << 2)
124#define DT2821_SUPCSR_XCLK		(1 << 1)
125#define DT2821_SUPCSR_BDINIT		(1 << 0)
126#define DT2821_TMRCTR_REG		0x0e
127
128static const struct comedi_lrange range_dt282x_ai_lo_bipolar = {
129	4, {
130		BIP_RANGE(10),
131		BIP_RANGE(5),
132		BIP_RANGE(2.5),
133		BIP_RANGE(1.25)
134	}
135};
136
137static const struct comedi_lrange range_dt282x_ai_lo_unipolar = {
138	4, {
139		UNI_RANGE(10),
140		UNI_RANGE(5),
141		UNI_RANGE(2.5),
142		UNI_RANGE(1.25)
143	}
144};
145
146static const struct comedi_lrange range_dt282x_ai_5_bipolar = {
147	4, {
148		BIP_RANGE(5),
149		BIP_RANGE(2.5),
150		BIP_RANGE(1.25),
151		BIP_RANGE(0.625)
152	}
153};
154
155static const struct comedi_lrange range_dt282x_ai_5_unipolar = {
156	4, {
157		UNI_RANGE(5),
158		UNI_RANGE(2.5),
159		UNI_RANGE(1.25),
160		UNI_RANGE(0.625)
161	}
162};
163
164static const struct comedi_lrange range_dt282x_ai_hi_bipolar = {
165	4, {
166		BIP_RANGE(10),
167		BIP_RANGE(1),
168		BIP_RANGE(0.1),
169		BIP_RANGE(0.02)
170	}
171};
172
173static const struct comedi_lrange range_dt282x_ai_hi_unipolar = {
174	4, {
175		UNI_RANGE(10),
176		UNI_RANGE(1),
177		UNI_RANGE(0.1),
178		UNI_RANGE(0.02)
179	}
180};
181
182/*
183 * The Analog Output range is set per-channel using jumpers on the board.
184 * All of these ranges may not be available on some DT2821 series boards.
185 * The default jumper setting has both channels set for +/-10V output.
186 */
187static const struct comedi_lrange dt282x_ao_range = {
188	5, {
189		BIP_RANGE(10),
190		BIP_RANGE(5),
191		BIP_RANGE(2.5),
192		UNI_RANGE(10),
193		UNI_RANGE(5),
194	}
195};
196
197struct dt282x_board {
198	const char *name;
199	unsigned int ai_maxdata;
200	int adchan_se;
201	int adchan_di;
202	int ai_speed;
203	int ispgl;
204	int dachan;
205	unsigned int ao_maxdata;
206};
207
208static const struct dt282x_board boardtypes[] = {
209	{
210		.name		= "dt2821",
211		.ai_maxdata	= 0x0fff,
212		.adchan_se	= 16,
213		.adchan_di	= 8,
214		.ai_speed	= 20000,
215		.dachan		= 2,
216		.ao_maxdata	= 0x0fff,
217	}, {
218		.name		= "dt2821-f",
219		.ai_maxdata	= 0x0fff,
220		.adchan_se	= 16,
221		.adchan_di	= 8,
222		.ai_speed	= 6500,
223		.dachan		= 2,
224		.ao_maxdata	= 0x0fff,
225	}, {
226		.name		= "dt2821-g",
227		.ai_maxdata	= 0x0fff,
228		.adchan_se	= 16,
229		.adchan_di	= 8,
230		.ai_speed	= 4000,
231		.dachan		= 2,
232		.ao_maxdata	= 0x0fff,
233	}, {
234		.name		= "dt2823",
235		.ai_maxdata	= 0xffff,
236		.adchan_di	= 4,
237		.ai_speed	= 10000,
238		.dachan		= 2,
239		.ao_maxdata	= 0xffff,
240	}, {
241		.name		= "dt2824-pgh",
242		.ai_maxdata	= 0x0fff,
243		.adchan_se	= 16,
244		.adchan_di	= 8,
245		.ai_speed	= 20000,
246	}, {
247		.name		= "dt2824-pgl",
248		.ai_maxdata	= 0x0fff,
249		.adchan_se	= 16,
250		.adchan_di	= 8,
251		.ai_speed	= 20000,
252		.ispgl		= 1,
253	}, {
254		.name		= "dt2825",
255		.ai_maxdata	= 0x0fff,
256		.adchan_se	= 16,
257		.adchan_di	= 8,
258		.ai_speed	= 20000,
259		.ispgl		= 1,
260		.dachan		= 2,
261		.ao_maxdata	= 0x0fff,
262	}, {
263		.name		= "dt2827",
264		.ai_maxdata	= 0xffff,
265		.adchan_di	= 4,
266		.ai_speed	= 10000,
267		.dachan		= 2,
268		.ao_maxdata	= 0x0fff,
269	}, {
270		.name		= "dt2828",
271		.ai_maxdata	= 0x0fff,
272		.adchan_se	= 4,
273		.ai_speed	= 10000,
274		.dachan		= 2,
275		.ao_maxdata	= 0x0fff,
276	}, {
277		.name		= "dt2829",
278		.ai_maxdata	= 0xffff,
279		.adchan_se	= 8,
280		.ai_speed	= 33250,
281		.dachan		= 2,
282		.ao_maxdata	= 0xffff,
283	}, {
284		.name		= "dt21-ez",
285		.ai_maxdata	= 0x0fff,
286		.adchan_se	= 16,
287		.adchan_di	= 8,
288		.ai_speed	= 10000,
289		.dachan		= 2,
290		.ao_maxdata	= 0x0fff,
291	}, {
292		.name		= "dt23-ez",
293		.ai_maxdata	= 0xffff,
294		.adchan_se	= 16,
295		.adchan_di	= 8,
296		.ai_speed	= 10000,
297	}, {
298		.name		= "dt24-ez",
299		.ai_maxdata	= 0x0fff,
300		.adchan_se	= 16,
301		.adchan_di	= 8,
302		.ai_speed	= 10000,
303	}, {
304		.name		= "dt24-ez-pgl",
305		.ai_maxdata	= 0x0fff,
306		.adchan_se	= 16,
307		.adchan_di	= 8,
308		.ai_speed	= 10000,
309		.ispgl		= 1,
310	},
311};
312
313struct dt282x_private {
314	unsigned int ad_2scomp:1;
315
316	unsigned int divisor;
317
318	unsigned short ao_readback[2];
319
320	int dacsr;	/* software copies of registers */
321	int adcsr;
322	int supcsr;
323
324	int ntrig;
325	int nread;
326
327	struct {
328		int chan;
329		unsigned short *buf;	/* DMA buffer */
330		int size;	/* size of current transfer */
331	} dma[2];
332	int dma_maxsize;	/* max size of DMA transfer (in bytes) */
333	int current_dma_index;
334	int dma_dir;
335};
336
337static int dt282x_prep_ai_dma(struct comedi_device *dev, int dma_index, int n)
338{
339	struct dt282x_private *devpriv = dev->private;
340	int dma_chan;
341	unsigned long dma_ptr;
342	unsigned long flags;
343
344	if (!devpriv->ntrig)
345		return 0;
346
347	if (n == 0)
348		n = devpriv->dma_maxsize;
349	if (n > devpriv->ntrig * 2)
350		n = devpriv->ntrig * 2;
351	devpriv->ntrig -= n / 2;
352
353	devpriv->dma[dma_index].size = n;
354	dma_chan = devpriv->dma[dma_index].chan;
355	dma_ptr = virt_to_bus(devpriv->dma[dma_index].buf);
356
357	set_dma_mode(dma_chan, DMA_MODE_READ);
358	flags = claim_dma_lock();
359	clear_dma_ff(dma_chan);
360	set_dma_addr(dma_chan, dma_ptr);
361	set_dma_count(dma_chan, n);
362	release_dma_lock(flags);
363
364	enable_dma(dma_chan);
365
366	return n;
367}
368
369static int dt282x_prep_ao_dma(struct comedi_device *dev, int dma_index, int n)
370{
371	struct dt282x_private *devpriv = dev->private;
372	int dma_chan;
373	unsigned long dma_ptr;
374	unsigned long flags;
375
376	devpriv->dma[dma_index].size = n;
377	dma_chan = devpriv->dma[dma_index].chan;
378	dma_ptr = virt_to_bus(devpriv->dma[dma_index].buf);
379
380	set_dma_mode(dma_chan, DMA_MODE_WRITE);
381	flags = claim_dma_lock();
382	clear_dma_ff(dma_chan);
383	set_dma_addr(dma_chan, dma_ptr);
384	set_dma_count(dma_chan, n);
385	release_dma_lock(flags);
386
387	enable_dma(dma_chan);
388
389	return n;
390}
391
392static void dt282x_disable_dma(struct comedi_device *dev)
393{
394	struct dt282x_private *devpriv = dev->private;
395
396	disable_dma(devpriv->dma[0].chan);
397	disable_dma(devpriv->dma[1].chan);
398}
399
400static unsigned int dt282x_ns_to_timer(unsigned int *ns, unsigned int flags)
401{
402	unsigned int prescale, base, divider;
403
404	for (prescale = 0; prescale < 16; prescale++) {
405		if (prescale == 1)
406			continue;
407		base = 250 * (1 << prescale);
408		switch (flags & TRIG_ROUND_MASK) {
409		case TRIG_ROUND_NEAREST:
410		default:
411			divider = (*ns + base / 2) / base;
412			break;
413		case TRIG_ROUND_DOWN:
414			divider = (*ns) / base;
415			break;
416		case TRIG_ROUND_UP:
417			divider = (*ns + base - 1) / base;
418			break;
419		}
420		if (divider < 256) {
421			*ns = divider * base;
422			return (prescale << 8) | (255 - divider);
423		}
424	}
425	base = 250 * (1 << 15);
426	divider = 255;
427	*ns = divider * base;
428	return (15 << 8) | (255 - divider);
429}
430
431static void dt282x_munge(struct comedi_device *dev,
432			 struct comedi_subdevice *s,
433			 unsigned short *buf,
434			 unsigned int nbytes)
435{
436	struct dt282x_private *devpriv = dev->private;
437	unsigned int val;
438	int i;
439
440	if (nbytes % 2)
441		comedi_error(dev, "bug! odd number of bytes from dma xfer");
442
443	for (i = 0; i < nbytes / 2; i++) {
444		val = buf[i];
445		val &= s->maxdata;
446		if (devpriv->ad_2scomp)
447			val = comedi_offset_munge(s, val);
448
449		buf[i] = val;
450	}
451}
452
453static void dt282x_ao_dma_interrupt(struct comedi_device *dev,
454				    struct comedi_subdevice *s)
455{
456	struct dt282x_private *devpriv = dev->private;
457	int cur_dma = devpriv->current_dma_index;
458	void *ptr = devpriv->dma[cur_dma].buf;
459	int size;
460
461	outw(devpriv->supcsr | DT2821_SUPCSR_CLRDMADNE,
462	     dev->iobase + DT2821_SUPCSR_REG);
463
464	disable_dma(devpriv->dma[cur_dma].chan);
465
466	devpriv->current_dma_index = 1 - cur_dma;
467
468	size = cfc_read_array_from_buffer(s, ptr, devpriv->dma_maxsize);
469	if (size == 0) {
470		dev_err(dev->class_dev, "AO underrun\n");
471		s->async->events |= COMEDI_CB_OVERFLOW;
472	} else {
473		dt282x_prep_ao_dma(dev, cur_dma, size);
474	}
475}
476
477static void dt282x_ai_dma_interrupt(struct comedi_device *dev,
478				    struct comedi_subdevice *s)
479{
480	struct dt282x_private *devpriv = dev->private;
481	int cur_dma = devpriv->current_dma_index;
482	void *ptr = devpriv->dma[cur_dma].buf;
483	int size = devpriv->dma[cur_dma].size;
484	int ret;
485
486	outw(devpriv->supcsr | DT2821_SUPCSR_CLRDMADNE,
487	     dev->iobase + DT2821_SUPCSR_REG);
488
489	disable_dma(devpriv->dma[cur_dma].chan);
490
491	devpriv->current_dma_index = 1 - cur_dma;
492
493	dt282x_munge(dev, s, ptr, size);
494	ret = cfc_write_array_to_buffer(s, ptr, size);
495	if (ret != size) {
496		s->async->events |= COMEDI_CB_OVERFLOW;
497		return;
498	}
499
500	devpriv->nread -= size / 2;
501	if (devpriv->nread < 0) {
502		dev_info(dev->class_dev, "nread off by one\n");
503		devpriv->nread = 0;
504	}
505	if (!devpriv->nread) {
506		s->async->events |= COMEDI_CB_EOA;
507		return;
508	}
509#if 0
510	/* clear the dual dma flag, making this the last dma segment */
511	/* XXX probably wrong */
512	if (!devpriv->ntrig) {
513		devpriv->supcsr &= ~DT2821_SUPCSR_DDMA;
514		outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR_REG);
515	}
516#endif
517	/* restart the channel */
518	dt282x_prep_ai_dma(dev, cur_dma, 0);
519}
520
521static irqreturn_t dt282x_interrupt(int irq, void *d)
522{
523	struct comedi_device *dev = d;
524	struct dt282x_private *devpriv = dev->private;
525	struct comedi_subdevice *s = dev->read_subdev;
526	struct comedi_subdevice *s_ao = dev->write_subdev;
527	unsigned int supcsr, adcsr, dacsr;
528	int handled = 0;
529
530	if (!dev->attached) {
531		comedi_error(dev, "spurious interrupt");
532		return IRQ_HANDLED;
533	}
534
535	adcsr = inw(dev->iobase + DT2821_ADCSR_REG);
536	dacsr = inw(dev->iobase + DT2821_DACSR_REG);
537	supcsr = inw(dev->iobase + DT2821_SUPCSR_REG);
538	if (supcsr & DT2821_SUPCSR_DMAD) {
539		if (devpriv->dma_dir == DMA_MODE_READ)
540			dt282x_ai_dma_interrupt(dev, s);
541		else
542			dt282x_ao_dma_interrupt(dev, s_ao);
543		handled = 1;
544	}
545	if (adcsr & DT2821_ADCSR_ADERR) {
546		if (devpriv->nread != 0) {
547			comedi_error(dev, "A/D error");
548			s->async->events |= COMEDI_CB_ERROR;
549		}
550		handled = 1;
551	}
552	if (dacsr & DT2821_DACSR_DAERR) {
553		comedi_error(dev, "D/A error");
554		s_ao->async->events |= COMEDI_CB_ERROR;
555		handled = 1;
556	}
557#if 0
558	if (adcsr & DT2821_ADCSR_ADDONE) {
559		int ret;
560		unsigned short data;
561
562		data = inw(dev->iobase + DT2821_ADDAT_REG);
563		data &= s->maxdata;
564		if (devpriv->ad_2scomp)
565			data = comedi_offset_munge(s, data);
566
567		ret = comedi_buf_put(s, data);
568
569		if (ret == 0)
570			s->async->events |= COMEDI_CB_OVERFLOW;
571
572		devpriv->nread--;
573		if (!devpriv->nread) {
574			s->async->events |= COMEDI_CB_EOA;
575		} else {
576			if (supcsr & DT2821_SUPCSR_SCDN)
577				outw(devpriv->supcsr | DT2821_SUPCSR_STRIG,
578				     dev->iobase + DT2821_SUPCSR_REG);
579		}
580		handled = 1;
581	}
582#endif
583	cfc_handle_events(dev, s);
584	cfc_handle_events(dev, s_ao);
585
586	return IRQ_RETVAL(handled);
587}
588
589static void dt282x_load_changain(struct comedi_device *dev, int n,
590				 unsigned int *chanlist)
591{
592	struct dt282x_private *devpriv = dev->private;
593	int i;
594
595	outw(DT2821_CHANCSR_LLE | DT2821_CHANCSR_NUMB(n),
596	     dev->iobase + DT2821_CHANCSR_REG);
597	for (i = 0; i < n; i++) {
598		unsigned int chan = CR_CHAN(chanlist[i]);
599		unsigned int range = CR_RANGE(chanlist[i]);
600
601		outw(devpriv->adcsr |
602		     DT2821_ADCSR_GS(range) |
603		     DT2821_ADCSR_CHAN(chan),
604		     dev->iobase + DT2821_ADCSR_REG);
605	}
606	outw(DT2821_CHANCSR_NUMB(n), dev->iobase + DT2821_CHANCSR_REG);
607}
608
609static int dt282x_ai_timeout(struct comedi_device *dev,
610			     struct comedi_subdevice *s,
611			     struct comedi_insn *insn,
612			     unsigned long context)
613{
614	unsigned int status;
615
616	status = inw(dev->iobase + DT2821_ADCSR_REG);
617	switch (context) {
618	case DT2821_ADCSR_MUXBUSY:
619		if ((status & DT2821_ADCSR_MUXBUSY) == 0)
620			return 0;
621		break;
622	case DT2821_ADCSR_ADDONE:
623		if (status & DT2821_ADCSR_ADDONE)
624			return 0;
625		break;
626	default:
627		return -EINVAL;
628	}
629	return -EBUSY;
630}
631
632/*
633 *    Performs a single A/D conversion.
634 *      - Put channel/gain into channel-gain list
635 *      - preload multiplexer
636 *      - trigger conversion and wait for it to finish
637 */
638static int dt282x_ai_insn_read(struct comedi_device *dev,
639			       struct comedi_subdevice *s,
640			       struct comedi_insn *insn,
641			       unsigned int *data)
642{
643	struct dt282x_private *devpriv = dev->private;
644	unsigned int val;
645	int ret;
646	int i;
647
648	/* XXX should we really be enabling the ad clock here? */
649	devpriv->adcsr = DT2821_ADCSR_ADCLK;
650	outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR_REG);
651
652	dt282x_load_changain(dev, 1, &insn->chanspec);
653
654	outw(devpriv->supcsr | DT2821_SUPCSR_PRLD,
655	     dev->iobase + DT2821_SUPCSR_REG);
656	ret = comedi_timeout(dev, s, insn,
657			     dt282x_ai_timeout, DT2821_ADCSR_MUXBUSY);
658	if (ret)
659		return ret;
660
661	for (i = 0; i < insn->n; i++) {
662		outw(devpriv->supcsr | DT2821_SUPCSR_STRIG,
663		     dev->iobase + DT2821_SUPCSR_REG);
664
665		ret = comedi_timeout(dev, s, insn,
666				     dt282x_ai_timeout, DT2821_ADCSR_ADDONE);
667		if (ret)
668			return ret;
669
670		val = inw(dev->iobase + DT2821_ADDAT_REG);
671		val &= s->maxdata;
672		if (devpriv->ad_2scomp)
673			val = comedi_offset_munge(s, val);
674
675		data[i] = val;
676	}
677
678	return i;
679}
680
681static int dt282x_ai_cmdtest(struct comedi_device *dev,
682			     struct comedi_subdevice *s,
683			     struct comedi_cmd *cmd)
684{
685	const struct dt282x_board *board = comedi_board(dev);
686	struct dt282x_private *devpriv = dev->private;
687	int err = 0;
688	unsigned int arg;
689
690	/* Step 1 : check if triggers are trivially valid */
691
692	err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
693	err |= cfc_check_trigger_src(&cmd->scan_begin_src,
694					TRIG_FOLLOW | TRIG_EXT);
695	err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
696	err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
697	err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
698
699	if (err)
700		return 1;
701
702	/* Step 2a : make sure trigger sources are unique */
703
704	err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
705	err |= cfc_check_trigger_is_unique(cmd->stop_src);
706
707	/* Step 2b : and mutually compatible */
708
709	if (err)
710		return 2;
711
712	/* Step 3: check if arguments are trivially valid */
713
714	err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
715
716	if (cmd->scan_begin_src == TRIG_FOLLOW) {
717		/* internal trigger */
718		err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
719	} else {
720		/* external trigger */
721		/* should be level/edge, hi/lo specification here */
722		err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
723	}
724
725	err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 4000);
726
727#define SLOWEST_TIMER	(250*(1<<15)*255)
728	err |= cfc_check_trigger_arg_max(&cmd->convert_arg, SLOWEST_TIMER);
729	err |= cfc_check_trigger_arg_min(&cmd->convert_arg, board->ai_speed);
730	err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
731
732	if (cmd->stop_src == TRIG_COUNT) {
733		/* any count is allowed */
734	} else {	/* TRIG_NONE */
735		err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
736	}
737
738	if (err)
739		return 3;
740
741	/* step 4: fix up any arguments */
742
743	arg = cmd->convert_arg;
744	devpriv->divisor = dt282x_ns_to_timer(&arg, cmd->flags);
745	err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
746
747	if (err)
748		return 4;
749
750	return 0;
751}
752
753static int dt282x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
754{
755	struct dt282x_private *devpriv = dev->private;
756	struct comedi_cmd *cmd = &s->async->cmd;
757	int ret;
758
759	dt282x_disable_dma(dev);
760
761	outw(devpriv->divisor, dev->iobase + DT2821_TMRCTR_REG);
762
763	devpriv->supcsr = DT2821_SUPCSR_ERRINTEN;
764	if (cmd->scan_begin_src == TRIG_FOLLOW)
765		devpriv->supcsr = DT2821_SUPCSR_DS_AD_CLK;
766	else
767		devpriv->supcsr = DT2821_SUPCSR_DS_AD_TRIG;
768	outw(devpriv->supcsr |
769	     DT2821_SUPCSR_CLRDMADNE |
770	     DT2821_SUPCSR_BUFFB |
771	     DT2821_SUPCSR_ADCINIT,
772	     dev->iobase + DT2821_SUPCSR_REG);
773
774	devpriv->ntrig = cmd->stop_arg * cmd->scan_end_arg;
775	devpriv->nread = devpriv->ntrig;
776
777	devpriv->dma_dir = DMA_MODE_READ;
778	devpriv->current_dma_index = 0;
779	dt282x_prep_ai_dma(dev, 0, 0);
780	if (devpriv->ntrig) {
781		dt282x_prep_ai_dma(dev, 1, 0);
782		devpriv->supcsr |= DT2821_SUPCSR_DDMA;
783		outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR_REG);
784	}
785
786	devpriv->adcsr = 0;
787
788	dt282x_load_changain(dev, cmd->chanlist_len, cmd->chanlist);
789
790	devpriv->adcsr = DT2821_ADCSR_ADCLK | DT2821_ADCSR_IADDONE;
791	outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR_REG);
792
793	outw(devpriv->supcsr | DT2821_SUPCSR_PRLD,
794	     dev->iobase + DT2821_SUPCSR_REG);
795	ret = comedi_timeout(dev, s, NULL,
796			     dt282x_ai_timeout, DT2821_ADCSR_MUXBUSY);
797	if (ret)
798		return ret;
799
800	if (cmd->scan_begin_src == TRIG_FOLLOW) {
801		outw(devpriv->supcsr | DT2821_SUPCSR_STRIG,
802			dev->iobase + DT2821_SUPCSR_REG);
803	} else {
804		devpriv->supcsr |= DT2821_SUPCSR_XTRIG;
805		outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR_REG);
806	}
807
808	return 0;
809}
810
811static int dt282x_ai_cancel(struct comedi_device *dev,
812			    struct comedi_subdevice *s)
813{
814	struct dt282x_private *devpriv = dev->private;
815
816	dt282x_disable_dma(dev);
817
818	devpriv->adcsr = 0;
819	outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR_REG);
820
821	devpriv->supcsr = 0;
822	outw(devpriv->supcsr | DT2821_SUPCSR_ADCINIT,
823	     dev->iobase + DT2821_SUPCSR_REG);
824
825	return 0;
826}
827
828static int dt282x_ao_insn_read(struct comedi_device *dev,
829			       struct comedi_subdevice *s,
830			       struct comedi_insn *insn,
831			       unsigned int *data)
832{
833	struct dt282x_private *devpriv = dev->private;
834	unsigned int chan = CR_CHAN(insn->chanspec);
835	int i;
836
837	for (i = 0; i < insn->n; i++)
838		data[i] = devpriv->ao_readback[chan];
839
840	return insn->n;
841}
842
843static int dt282x_ao_insn_write(struct comedi_device *dev,
844				struct comedi_subdevice *s,
845				struct comedi_insn *insn,
846				unsigned int *data)
847{
848	struct dt282x_private *devpriv = dev->private;
849	unsigned int chan = CR_CHAN(insn->chanspec);
850	unsigned int range = CR_RANGE(insn->chanspec);
851	unsigned int val;
852	int i;
853
854	devpriv->dacsr |= DT2821_DACSR_SSEL | DT2821_DACSR_YSEL(chan);
855
856	for (i = 0; i < insn->n; i++) {
857		val = data[i];
858		devpriv->ao_readback[chan] = val;
859
860		if (comedi_range_is_bipolar(s, range))
861			val = comedi_offset_munge(s, val);
862
863		outw(devpriv->dacsr, dev->iobase + DT2821_DACSR_REG);
864
865		outw(val, dev->iobase + DT2821_DADAT_REG);
866
867		outw(devpriv->supcsr | DT2821_SUPCSR_DACON,
868		     dev->iobase + DT2821_SUPCSR_REG);
869	}
870
871	return insn->n;
872}
873
874static int dt282x_ao_cmdtest(struct comedi_device *dev,
875			     struct comedi_subdevice *s,
876			     struct comedi_cmd *cmd)
877{
878	struct dt282x_private *devpriv = dev->private;
879	int err = 0;
880	unsigned int arg;
881
882	/* Step 1 : check if triggers are trivially valid */
883
884	err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT);
885	err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
886	err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
887	err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
888	err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
889
890	if (err)
891		return 1;
892
893	/* Step 2a : make sure trigger sources are unique */
894
895	err |= cfc_check_trigger_is_unique(cmd->stop_src);
896
897	/* Step 2b : and mutually compatible */
898
899	if (err)
900		return 2;
901
902	/* Step 3: check if arguments are trivially valid */
903
904	err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
905	err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, 5000);
906	err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
907	err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
908
909	if (cmd->stop_src == TRIG_COUNT) {
910		/* any count is allowed */
911	} else {	/* TRIG_NONE */
912		err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
913	}
914
915	if (err)
916		return 3;
917
918	/* step 4: fix up any arguments */
919
920	arg = cmd->scan_begin_arg;
921	devpriv->divisor = dt282x_ns_to_timer(&arg, cmd->flags);
922	err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
923
924	if (err)
925		return 4;
926
927	return 0;
928
929}
930
931static int dt282x_ao_inttrig(struct comedi_device *dev,
932			     struct comedi_subdevice *s,
933			     unsigned int trig_num)
934{
935	struct dt282x_private *devpriv = dev->private;
936	struct comedi_cmd *cmd = &s->async->cmd;
937	int size;
938
939	if (trig_num != cmd->start_src)
940		return -EINVAL;
941
942	size = cfc_read_array_from_buffer(s, devpriv->dma[0].buf,
943					  devpriv->dma_maxsize);
944	if (size == 0) {
945		dev_err(dev->class_dev, "AO underrun\n");
946		return -EPIPE;
947	}
948	dt282x_prep_ao_dma(dev, 0, size);
949
950	size = cfc_read_array_from_buffer(s, devpriv->dma[1].buf,
951					  devpriv->dma_maxsize);
952	if (size == 0) {
953		dev_err(dev->class_dev, "AO underrun\n");
954		return -EPIPE;
955	}
956	dt282x_prep_ao_dma(dev, 1, size);
957
958	outw(devpriv->supcsr | DT2821_SUPCSR_STRIG,
959	     dev->iobase + DT2821_SUPCSR_REG);
960	s->async->inttrig = NULL;
961
962	return 1;
963}
964
965static int dt282x_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
966{
967	struct dt282x_private *devpriv = dev->private;
968	struct comedi_cmd *cmd = &s->async->cmd;
969
970	dt282x_disable_dma(dev);
971
972	devpriv->supcsr = DT2821_SUPCSR_ERRINTEN |
973			  DT2821_SUPCSR_DS_DA_CLK |
974			  DT2821_SUPCSR_DDMA;
975	outw(devpriv->supcsr |
976	     DT2821_SUPCSR_CLRDMADNE |
977	     DT2821_SUPCSR_BUFFB |
978	     DT2821_SUPCSR_DACINIT,
979	     dev->iobase + DT2821_SUPCSR_REG);
980
981	devpriv->ntrig = cmd->stop_arg * cmd->chanlist_len;
982	devpriv->nread = devpriv->ntrig;
983
984	devpriv->dma_dir = DMA_MODE_WRITE;
985	devpriv->current_dma_index = 0;
986
987	outw(devpriv->divisor, dev->iobase + DT2821_TMRCTR_REG);
988
989	/* clear all bits but the DIO direction bits */
990	devpriv->dacsr &= (DT2821_DACSR_LBOE | DT2821_DACSR_HBOE);
991
992	devpriv->dacsr |= (DT2821_DACSR_SSEL |
993			   DT2821_DACSR_DACLK |
994			   DT2821_DACSR_IDARDY);
995	outw(devpriv->dacsr, dev->iobase + DT2821_DACSR_REG);
996
997	s->async->inttrig = dt282x_ao_inttrig;
998
999	return 0;
1000}
1001
1002static int dt282x_ao_cancel(struct comedi_device *dev,
1003			    struct comedi_subdevice *s)
1004{
1005	struct dt282x_private *devpriv = dev->private;
1006
1007	dt282x_disable_dma(dev);
1008
1009	/* clear all bits but the DIO direction bits */
1010	devpriv->dacsr &= (DT2821_DACSR_LBOE | DT2821_DACSR_HBOE);
1011
1012	outw(devpriv->dacsr, dev->iobase + DT2821_DACSR_REG);
1013
1014	devpriv->supcsr = 0;
1015	outw(devpriv->supcsr | DT2821_SUPCSR_DACINIT,
1016	     dev->iobase + DT2821_SUPCSR_REG);
1017
1018	return 0;
1019}
1020
1021static int dt282x_dio_insn_bits(struct comedi_device *dev,
1022				struct comedi_subdevice *s,
1023				struct comedi_insn *insn,
1024				unsigned int *data)
1025{
1026	if (comedi_dio_update_state(s, data))
1027		outw(s->state, dev->iobase + DT2821_DIODAT_REG);
1028
1029	data[1] = inw(dev->iobase + DT2821_DIODAT_REG);
1030
1031	return insn->n;
1032}
1033
1034static int dt282x_dio_insn_config(struct comedi_device *dev,
1035				  struct comedi_subdevice *s,
1036				  struct comedi_insn *insn,
1037				  unsigned int *data)
1038{
1039	struct dt282x_private *devpriv = dev->private;
1040	unsigned int chan = CR_CHAN(insn->chanspec);
1041	unsigned int mask;
1042	int ret;
1043
1044	if (chan < 8)
1045		mask = 0x00ff;
1046	else
1047		mask = 0xff00;
1048
1049	ret = comedi_dio_insn_config(dev, s, insn, data, mask);
1050	if (ret)
1051		return ret;
1052
1053	devpriv->dacsr &= ~(DT2821_DACSR_LBOE | DT2821_DACSR_HBOE);
1054	if (s->io_bits & 0x00ff)
1055		devpriv->dacsr |= DT2821_DACSR_LBOE;
1056	if (s->io_bits & 0xff00)
1057		devpriv->dacsr |= DT2821_DACSR_HBOE;
1058
1059	outw(devpriv->dacsr, dev->iobase + DT2821_DACSR_REG);
1060
1061	return insn->n;
1062}
1063
1064static const struct comedi_lrange *const ai_range_table[] = {
1065	&range_dt282x_ai_lo_bipolar,
1066	&range_dt282x_ai_lo_unipolar,
1067	&range_dt282x_ai_5_bipolar,
1068	&range_dt282x_ai_5_unipolar
1069};
1070
1071static const struct comedi_lrange *const ai_range_pgl_table[] = {
1072	&range_dt282x_ai_hi_bipolar,
1073	&range_dt282x_ai_hi_unipolar
1074};
1075
1076static const struct comedi_lrange *opt_ai_range_lkup(int ispgl, int x)
1077{
1078	if (ispgl) {
1079		if (x < 0 || x >= 2)
1080			x = 0;
1081		return ai_range_pgl_table[x];
1082	} else {
1083		if (x < 0 || x >= 4)
1084			x = 0;
1085		return ai_range_table[x];
1086	}
1087}
1088
1089static int dt282x_grab_dma(struct comedi_device *dev, int dma1, int dma2)
1090{
1091	struct dt282x_private *devpriv = dev->private;
1092	int ret;
1093
1094	ret = request_dma(dma1, "dt282x A");
1095	if (ret)
1096		return -EBUSY;
1097	devpriv->dma[0].chan = dma1;
1098
1099	ret = request_dma(dma2, "dt282x B");
1100	if (ret)
1101		return -EBUSY;
1102	devpriv->dma[1].chan = dma2;
1103
1104	devpriv->dma_maxsize = PAGE_SIZE;
1105	devpriv->dma[0].buf = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
1106	devpriv->dma[1].buf = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
1107	if (!devpriv->dma[0].buf || !devpriv->dma[1].buf)
1108		return -ENOMEM;
1109
1110	return 0;
1111}
1112
1113static void dt282x_free_dma(struct comedi_device *dev)
1114{
1115	struct dt282x_private *devpriv = dev->private;
1116	int i;
1117
1118	if (!devpriv)
1119		return;
1120
1121	for (i = 0; i < 2; i++) {
1122		if (devpriv->dma[i].chan)
1123			free_dma(devpriv->dma[i].chan);
1124		if (devpriv->dma[i].buf)
1125			free_page((unsigned long)devpriv->dma[i].buf);
1126		devpriv->dma[i].chan = 0;
1127		devpriv->dma[i].buf = NULL;
1128	}
1129}
1130
1131static int dt282x_initialize(struct comedi_device *dev)
1132{
1133	/* Initialize board */
1134	outw(DT2821_SUPCSR_BDINIT, dev->iobase + DT2821_SUPCSR_REG);
1135	inw(dev->iobase + DT2821_ADCSR_REG);
1136
1137	/*
1138	 * At power up, some registers are in a well-known state.
1139	 * Check them to see if a DT2821 series board is present.
1140	 */
1141	if (((inw(dev->iobase + DT2821_ADCSR_REG) & 0xfff0) != 0x7c00) ||
1142	    ((inw(dev->iobase + DT2821_CHANCSR_REG) & 0xf0f0) != 0x70f0) ||
1143	    ((inw(dev->iobase + DT2821_DACSR_REG) & 0x7c93) != 0x7c90) ||
1144	    ((inw(dev->iobase + DT2821_SUPCSR_REG) & 0xf8ff) != 0x0000) ||
1145	    ((inw(dev->iobase + DT2821_TMRCTR_REG) & 0xff00) != 0xf000)) {
1146		dev_err(dev->class_dev, "board not found\n");
1147		return -EIO;
1148	}
1149	return 0;
1150}
1151
1152/*
1153   options:
1154   0	i/o base
1155   1	irq
1156   2	dma1
1157   3	dma2
1158   4	0=single ended, 1=differential
1159   5	ai 0=straight binary, 1=2's comp
1160   6	ao0 0=straight binary, 1=2's comp
1161   7	ao1 0=straight binary, 1=2's comp
1162   8	ai 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V
1163   9	ao0 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V, 4=±2.5 V
1164   10	ao1 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V, 4=±2.5 V
1165 */
1166static int dt282x_attach(struct comedi_device *dev, struct comedi_devconfig *it)
1167{
1168	const struct dt282x_board *board = comedi_board(dev);
1169	struct dt282x_private *devpriv;
1170	struct comedi_subdevice *s;
1171	int ret;
1172
1173	ret = comedi_request_region(dev, it->options[0], 0x10);
1174	if (ret)
1175		return ret;
1176
1177	ret = dt282x_initialize(dev);
1178	if (ret)
1179		return ret;
1180
1181	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
1182	if (!devpriv)
1183		return -ENOMEM;
1184
1185	/* an IRQ and 2 DMA channels are required for async command support */
1186	if (it->options[1] && it->options[2] && it->options[3]) {
1187		unsigned int irq = it->options[1];
1188		unsigned int dma1 = it->options[2];
1189		unsigned int dma2 = it->options[3];
1190
1191		if (dma2 < dma1) {
1192			unsigned int swap;
1193
1194			swap = dma1;
1195			dma1 = dma2;
1196			dma2 = swap;
1197		}
1198
1199		if (dma1 != dma2 &&
1200		    dma1 >= 5 && dma1 <= 7 &&
1201		    dma2 >= 5 && dma2 <= 7) {
1202			ret = request_irq(irq, dt282x_interrupt, 0,
1203					  dev->board_name, dev);
1204			if (ret == 0) {
1205				dev->irq = irq;
1206
1207				ret = dt282x_grab_dma(dev, dma1, dma2);
1208				if (ret < 0) {
1209					dt282x_free_dma(dev);
1210					free_irq(dev->irq, dev);
1211					dev->irq = 0;
1212				}
1213			}
1214		}
1215	}
1216
1217	ret = comedi_alloc_subdevices(dev, 3);
1218	if (ret)
1219		return ret;
1220
1221	/* Analog Input subdevice */
1222	s = &dev->subdevices[0];
1223	s->type		= COMEDI_SUBD_AI;
1224	s->subdev_flags	= SDF_READABLE;
1225	if ((it->options[4] && board->adchan_di) || board->adchan_se == 0) {
1226		s->subdev_flags	|= SDF_DIFF;
1227		s->n_chan	= board->adchan_di;
1228	} else {
1229		s->subdev_flags	|= SDF_COMMON;
1230		s->n_chan	= board->adchan_se;
1231	}
1232	s->maxdata	= board->ai_maxdata;
1233
1234	s->range_table = opt_ai_range_lkup(board->ispgl, it->options[8]);
1235	devpriv->ad_2scomp = it->options[5] ? 1 : 0;
1236
1237	s->insn_read	= dt282x_ai_insn_read;
1238	if (dev->irq) {
1239		dev->read_subdev = s;
1240		s->subdev_flags	|= SDF_CMD_READ;
1241		s->len_chanlist	= s->n_chan;
1242		s->do_cmdtest	= dt282x_ai_cmdtest;
1243		s->do_cmd	= dt282x_ai_cmd;
1244		s->cancel	= dt282x_ai_cancel;
1245	}
1246
1247	/* Analog Output subdevice */
1248	s = &dev->subdevices[1];
1249	if (board->dachan) {
1250		s->type		= COMEDI_SUBD_AO;
1251		s->subdev_flags	= SDF_WRITABLE;
1252		s->n_chan	= board->dachan;
1253		s->maxdata	= board->ao_maxdata;
1254
1255		/* ranges are per-channel, set by jumpers on the board */
1256		s->range_table	= &dt282x_ao_range;
1257
1258		s->insn_read	= dt282x_ao_insn_read;
1259		s->insn_write	= dt282x_ao_insn_write;
1260		if (dev->irq) {
1261			dev->write_subdev = s;
1262			s->subdev_flags	|= SDF_CMD_WRITE;
1263			s->len_chanlist	= s->n_chan;
1264			s->do_cmdtest	= dt282x_ao_cmdtest;
1265			s->do_cmd	= dt282x_ao_cmd;
1266			s->cancel	= dt282x_ao_cancel;
1267		}
1268	} else {
1269		s->type		= COMEDI_SUBD_UNUSED;
1270	}
1271
1272	/* Digital I/O subdevice */
1273	s = &dev->subdevices[2];
1274	s->type		= COMEDI_SUBD_DIO;
1275	s->subdev_flags	= SDF_READABLE | SDF_WRITABLE;
1276	s->n_chan	= 16;
1277	s->maxdata	= 1;
1278	s->range_table	= &range_digital;
1279	s->insn_bits	= dt282x_dio_insn_bits;
1280	s->insn_config	= dt282x_dio_insn_config;
1281
1282	return 0;
1283}
1284
1285static void dt282x_detach(struct comedi_device *dev)
1286{
1287	dt282x_free_dma(dev);
1288	comedi_legacy_detach(dev);
1289}
1290
1291static struct comedi_driver dt282x_driver = {
1292	.driver_name	= "dt282x",
1293	.module		= THIS_MODULE,
1294	.attach		= dt282x_attach,
1295	.detach		= dt282x_detach,
1296	.board_name	= &boardtypes[0].name,
1297	.num_names	= ARRAY_SIZE(boardtypes),
1298	.offset		= sizeof(struct dt282x_board),
1299};
1300module_comedi_driver(dt282x_driver);
1301
1302MODULE_AUTHOR("Comedi http://www.comedi.org");
1303MODULE_DESCRIPTION("Comedi driver for Data Translation DT2821 series");
1304MODULE_LICENSE("GPL");
1305