diff --git a/drivers/staging/comedi/drivers/dt282x.c b/drivers/staging/comedi/drivers/dt282x.c
new file mode 100644
index 000000000000..28eadea2a424
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt282x.c
@@ -0,0 +1,1471 @@
+/*
+   comedi/drivers/dt282x.c
+   Hardware driver for Data Translation DT2821 series
+
+   COMEDI - Linux Control and Measurement Device Interface
+   Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+/*
+Driver: dt282x
+Description: Data Translation DT2821 series (including DT-EZ)
+Author: ds
+Devices: [Data Translation] DT2821 (dt2821),
+  DT2821-F-16SE (dt2821-f), DT2821-F-8DI (dt2821-f),
+  DT2821-G-16SE (dt2821-f), DT2821-G-8DI (dt2821-g),
+  DT2823 (dt2823),
+  DT2824-PGH (dt2824-pgh), DT2824-PGL (dt2824-pgl), DT2825 (dt2825),
+  DT2827 (dt2827), DT2828 (dt2828), DT21-EZ (dt21-ez), DT23-EZ (dt23-ez),
+  DT24-EZ (dt24-ez), DT24-EZ-PGL (dt24-ez-pgl)
+Status: complete
+Updated: Wed, 22 Aug 2001 17:11:34 -0700
+
+Configuration options:
+  [0] - I/O port base address
+  [1] - IRQ
+  [2] - DMA 1
+  [3] - DMA 2
+  [4] - AI jumpered for 0=single ended, 1=differential
+  [5] - AI jumpered for 0=straight binary, 1=2's complement
+  [6] - AO 0 jumpered for 0=straight binary, 1=2's complement
+  [7] - AO 1 jumpered for 0=straight binary, 1=2's complement
+  [8] - AI jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5]
+  [9] - AO 0 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5],
+        4=[-2.5,2.5]
+  [10]- A0 1 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5],
+        4=[-2.5,2.5]
+
+Notes:
+  - AO commands might be broken.
+  - If you try to run a command on both the AI and AO subdevices
+    simultaneously, bad things will happen.  The driver needs to
+    be fixed to check for this situation and return an error.
+*/
+
+#include "../comedidev.h"
+
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <asm/dma.h>
+#include "comedi_fc.h"
+
+#define DEBUG
+
+#define DT2821_TIMEOUT		100	/* 500 us */
+#define DT2821_SIZE 0x10
+
+/*
+ *    Registers in the DT282x
+ */
+
+#define DT2821_ADCSR	0x00	/* A/D Control/Status             */
+#define DT2821_CHANCSR	0x02	/* Channel Control/Status */
+#define DT2821_ADDAT	0x04	/* A/D data                       */
+#define DT2821_DACSR	0x06	/* D/A Control/Status             */
+#define DT2821_DADAT	0x08	/* D/A data                       */
+#define DT2821_DIODAT	0x0a	/* digital data                   */
+#define DT2821_SUPCSR	0x0c	/* Supervisor Control/Status      */
+#define DT2821_TMRCTR	0x0e	/* Timer/Counter          */
+
+/*
+ *  At power up, some registers are in a well-known state.  The
+ *  masks and values are as follows:
+ */
+
+#define DT2821_ADCSR_MASK 0xfff0
+#define DT2821_ADCSR_VAL 0x7c00
+
+#define DT2821_CHANCSR_MASK 0xf0f0
+#define DT2821_CHANCSR_VAL 0x70f0
+
+#define DT2821_DACSR_MASK 0x7c93
+#define DT2821_DACSR_VAL 0x7c90
+
+#define DT2821_SUPCSR_MASK 0xf8ff
+#define DT2821_SUPCSR_VAL 0x0000
+
+#define DT2821_TMRCTR_MASK 0xff00
+#define DT2821_TMRCTR_VAL 0xf000
+
+/*
+ *    Bit fields of each register
+ */
+
+/* ADCSR */
+
+#define DT2821_ADERR	0x8000	/* (R)   1 for A/D error  */
+#define DT2821_ADCLK	0x0200	/* (R/W) A/D clock enable */
+		/*      0x7c00           read as 1's            */
+#define DT2821_MUXBUSY	0x0100	/* (R)   multiplexer busy */
+#define DT2821_ADDONE	0x0080	/* (R)   A/D done         */
+#define DT2821_IADDONE	0x0040	/* (R/W) interrupt on A/D done    */
+		/*      0x0030           gain select            */
+		/*      0x000f           channel select         */
+
+/* CHANCSR */
+
+#define DT2821_LLE	0x8000	/* (R/W) Load List Enable */
+		/*      0x7000           read as 1's            */
+		/*      0x0f00     (R)   present address        */
+		/*      0x00f0           read as 1's            */
+		/*      0x000f     (R)   number of entries - 1  */
+
+/* DACSR */
+
+#define DT2821_DAERR	0x8000	/* (R)   D/A error                */
+#define DT2821_YSEL	0x0200	/* (R/W) DAC 1 select             */
+#define DT2821_SSEL	0x0100	/* (R/W) single channel select    */
+#define DT2821_DACRDY	0x0080	/* (R)   DAC ready                */
+#define DT2821_IDARDY	0x0040	/* (R/W) interrupt on DAC ready   */
+#define DT2821_DACLK	0x0020	/* (R/W) D/A clock enable */
+#define DT2821_HBOE	0x0002	/* (R/W) DIO high byte output enable      */
+#define DT2821_LBOE	0x0001	/* (R/W) DIO low byte output enable       */
+
+/* SUPCSR */
+
+#define DT2821_DMAD	0x8000	/* (R)   DMA done                 */
+#define DT2821_ERRINTEN	0x4000	/* (R/W) interrupt on error               */
+#define DT2821_CLRDMADNE 0x2000	/* (W)   clear DMA done                   */
+#define DT2821_DDMA	0x1000	/* (R/W) dual DMA                 */
+#define DT2821_DS1	0x0800	/* (R/W) DMA select 1                     */
+#define DT2821_DS0	0x0400	/* (R/W) DMA select 0                     */
+#define DT2821_BUFFB	0x0200	/* (R/W) buffer B selected                */
+#define DT2821_SCDN	0x0100	/* (R)   scan done                        */
+#define DT2821_DACON	0x0080	/* (W)   DAC single conversion            */
+#define DT2821_ADCINIT	0x0040	/* (W)   A/D initialize                   */
+#define DT2821_DACINIT	0x0020	/* (W)   D/A initialize                   */
+#define DT2821_PRLD	0x0010	/* (W)   preload multiplexer              */
+#define DT2821_STRIG	0x0008	/* (W)   software trigger         */
+#define DT2821_XTRIG	0x0004	/* (R/W) external trigger enable  */
+#define DT2821_XCLK	0x0002	/* (R/W) external clock enable            */
+#define DT2821_BDINIT	0x0001	/* (W)   initialize board         */
+
+static const comedi_lrange range_dt282x_ai_lo_bipolar = { 4, {
+			RANGE(-10, 10),
+			RANGE(-5, 5),
+			RANGE(-2.5, 2.5),
+			RANGE(-1.25, 1.25)
+	}
+};
+static const comedi_lrange range_dt282x_ai_lo_unipolar = { 4, {
+			RANGE(0, 10),
+			RANGE(0, 5),
+			RANGE(0, 2.5),
+			RANGE(0, 1.25)
+	}
+};
+static const comedi_lrange range_dt282x_ai_5_bipolar = { 4, {
+			RANGE(-5, 5),
+			RANGE(-2.5, 2.5),
+			RANGE(-1.25, 1.25),
+			RANGE(-0.625, 0.625),
+	}
+};
+static const comedi_lrange range_dt282x_ai_5_unipolar = { 4, {
+			RANGE(0, 5),
+			RANGE(0, 2.5),
+			RANGE(0, 1.25),
+			RANGE(0, 0.625),
+	}
+};
+static const comedi_lrange range_dt282x_ai_hi_bipolar = { 4, {
+			RANGE(-10, 10),
+			RANGE(-1, 1),
+			RANGE(-0.1, 0.1),
+			RANGE(-0.02, 0.02)
+	}
+};
+static const comedi_lrange range_dt282x_ai_hi_unipolar = { 4, {
+			RANGE(0, 10),
+			RANGE(0, 1),
+			RANGE(0, 0.1),
+			RANGE(0, 0.02)
+	}
+};
+
+typedef struct {
+	const char *name;
+	int adbits;
+	int adchan_se;
+	int adchan_di;
+	int ai_speed;
+	int ispgl;
+	int dachan;
+	int dabits;
+} boardtype_t;
+
+static const boardtype_t boardtypes[] = {
+      {name:"dt2821",
+	      adbits:	12,
+	      adchan_se:16,
+	      adchan_di:8,
+	      ai_speed:20000,
+	      ispgl:	0,
+	      dachan:	2,
+	      dabits:	12,
+		},
+      {name:"dt2821-f",
+	      adbits:	12,
+	      adchan_se:16,
+	      adchan_di:8,
+	      ai_speed:6500,
+	      ispgl:	0,
+	      dachan:	2,
+	      dabits:	12,
+		},
+      {name:"dt2821-g",
+	      adbits:	12,
+	      adchan_se:16,
+	      adchan_di:8,
+	      ai_speed:4000,
+	      ispgl:	0,
+	      dachan:	2,
+	      dabits:	12,
+		},
+      {name:"dt2823",
+	      adbits:	16,
+	      adchan_se:0,
+	      adchan_di:4,
+	      ai_speed:10000,
+	      ispgl:	0,
+	      dachan:	2,
+	      dabits:	16,
+		},
+      {name:"dt2824-pgh",
+	      adbits:	12,
+	      adchan_se:16,
+	      adchan_di:8,
+	      ai_speed:20000,
+	      ispgl:	0,
+	      dachan:	0,
+	      dabits:	0,
+		},
+      {name:"dt2824-pgl",
+	      adbits:	12,
+	      adchan_se:16,
+	      adchan_di:8,
+	      ai_speed:20000,
+	      ispgl:	1,
+	      dachan:	0,
+	      dabits:	0,
+		},
+      {name:"dt2825",
+	      adbits:	12,
+	      adchan_se:16,
+	      adchan_di:8,
+	      ai_speed:20000,
+	      ispgl:	1,
+	      dachan:	2,
+	      dabits:	12,
+		},
+      {name:"dt2827",
+	      adbits:	16,
+	      adchan_se:0,
+	      adchan_di:4,
+	      ai_speed:10000,
+	      ispgl:	0,
+	      dachan:	2,
+	      dabits:	12,
+		},
+      {name:"dt2828",
+	      adbits:	12,
+	      adchan_se:4,
+	      adchan_di:0,
+	      ai_speed:10000,
+	      ispgl:	0,
+	      dachan:	2,
+	      dabits:	12,
+		},
+      {name:"dt2829",
+	      adbits:	16,
+	      adchan_se:8,
+	      adchan_di:0,
+	      ai_speed:33250,
+	      ispgl:	0,
+	      dachan:	2,
+	      dabits:	16,
+		},
+      {name:"dt21-ez",
+	      adbits:	12,
+	      adchan_se:16,
+	      adchan_di:8,
+	      ai_speed:10000,
+	      ispgl:	0,
+	      dachan:	2,
+	      dabits:	12,
+		},
+      {name:"dt23-ez",
+	      adbits:	16,
+	      adchan_se:16,
+	      adchan_di:8,
+	      ai_speed:10000,
+	      ispgl:	0,
+	      dachan:	0,
+	      dabits:	0,
+		},
+      {name:"dt24-ez",
+	      adbits:	12,
+	      adchan_se:16,
+	      adchan_di:8,
+	      ai_speed:10000,
+	      ispgl:	0,
+	      dachan:	0,
+	      dabits:	0,
+		},
+      {name:"dt24-ez-pgl",
+	      adbits:	12,
+	      adchan_se:16,
+	      adchan_di:8,
+	      ai_speed:10000,
+	      ispgl:	1,
+	      dachan:	0,
+	      dabits:	0,
+		},
+};
+
+#define n_boardtypes sizeof(boardtypes)/sizeof(boardtype_t)
+#define this_board ((const boardtype_t *)dev->board_ptr)
+
+typedef struct {
+	int ad_2scomp;		/* we have 2's comp jumper set  */
+	int da0_2scomp;		/* same, for DAC0               */
+	int da1_2scomp;		/* same, for DAC1               */
+
+	const comedi_lrange *darangelist[2];
+
+	sampl_t ao[2];
+
+	volatile int dacsr;	/* software copies of registers */
+	volatile int adcsr;
+	volatile int supcsr;
+
+	volatile int ntrig;
+	volatile int nread;
+
+	struct {
+		int chan;
+		short *buf;	/* DMA buffer */
+		volatile int size;	/* size of current transfer */
+	} dma[2];
+	int dma_maxsize;	/* max size of DMA transfer (in bytes) */
+	int usedma;		/* driver uses DMA              */
+	volatile int current_dma_index;
+	int dma_dir;
+} dt282x_private;
+
+#define devpriv ((dt282x_private *)dev->private)
+#define boardtype (*(const boardtype_t *)dev->board_ptr)
+
+/*
+ *    Some useless abstractions
+ */
+#define chan_to_DAC(a)	((a)&1)
+#define update_dacsr(a)	outw(devpriv->dacsr|(a),dev->iobase+DT2821_DACSR)
+#define update_adcsr(a)	outw(devpriv->adcsr|(a),dev->iobase+DT2821_ADCSR)
+#define mux_busy() (inw(dev->iobase+DT2821_ADCSR)&DT2821_MUXBUSY)
+#define ad_done() (inw(dev->iobase+DT2821_ADCSR)&DT2821_ADDONE)
+#define update_supcsr(a)	outw(devpriv->supcsr|(a),dev->iobase+DT2821_SUPCSR)
+
+/*
+ *    danger! macro abuse... a is the expression to wait on, and b is
+ *      the statement(s) to execute if it doesn't happen.
+ */
+#define wait_for(a,b)	 				\
+	do{						\
+		int _i;					\
+		for(_i=0;_i<DT2821_TIMEOUT;_i++){	\
+			if(a){_i=0;break;}		\
+			comedi_udelay(5);			\
+		}					\
+		if(_i){b}				\
+	}while(0)
+
+static int dt282x_attach(comedi_device * dev, comedi_devconfig * it);
+static int dt282x_detach(comedi_device * dev);
+static comedi_driver driver_dt282x = {
+      driver_name:"dt282x",
+      module:THIS_MODULE,
+      attach:dt282x_attach,
+      detach:dt282x_detach,
+      board_name:&boardtypes[0].name,
+      num_names:n_boardtypes,
+      offset:sizeof(boardtype_t),
+};
+
+COMEDI_INITCLEANUP(driver_dt282x);
+
+static void free_resources(comedi_device * dev);
+static int prep_ai_dma(comedi_device * dev, int chan, int size);
+static int prep_ao_dma(comedi_device * dev, int chan, int size);
+static int dt282x_ai_cancel(comedi_device * dev, comedi_subdevice * s);
+static int dt282x_ao_cancel(comedi_device * dev, comedi_subdevice * s);
+static int dt282x_ns_to_timer(int *nanosec, int round_mode);
+static void dt282x_disable_dma(comedi_device * dev);
+
+static int dt282x_grab_dma(comedi_device * dev, int dma1, int dma2);
+
+static void dt282x_munge(comedi_device * dev, sampl_t * buf,
+	unsigned int nbytes)
+{
+	unsigned int i;
+	unsigned short mask = (1 << boardtype.adbits) - 1;
+	unsigned short sign = 1 << (boardtype.adbits - 1);
+	int n;
+
+	if (devpriv->ad_2scomp) {
+		sign = 1 << (boardtype.adbits - 1);
+	} else {
+		sign = 0;
+	}
+
+	if (nbytes % 2)
+		comedi_error(dev, "bug! odd number of bytes from dma xfer");
+	n = nbytes / 2;
+	for (i = 0; i < n; i++) {
+		buf[i] = (buf[i] & mask) ^ sign;
+	}
+}
+
+static void dt282x_ao_dma_interrupt(comedi_device * dev)
+{
+	void *ptr;
+	int size;
+	int i;
+	comedi_subdevice *s = dev->subdevices + 1;
+
+	update_supcsr(DT2821_CLRDMADNE);
+
+	if (!s->async->prealloc_buf) {
+		printk("async->data disappeared.  dang!\n");
+		return;
+	}
+
+	i = devpriv->current_dma_index;
+	ptr = devpriv->dma[i].buf;
+
+	disable_dma(devpriv->dma[i].chan);
+
+	devpriv->current_dma_index = 1 - i;
+
+	size = cfc_read_array_from_buffer(s, ptr, devpriv->dma_maxsize);
+	if (size == 0) {
+		rt_printk("dt282x: AO underrun\n");
+		dt282x_ao_cancel(dev, s);
+		s->async->events |= COMEDI_CB_OVERFLOW;
+		return;
+	}
+	prep_ao_dma(dev, i, size);
+	return;
+}
+
+static void dt282x_ai_dma_interrupt(comedi_device * dev)
+{
+	void *ptr;
+	int size;
+	int i;
+	int ret;
+	comedi_subdevice *s = dev->subdevices;
+
+	update_supcsr(DT2821_CLRDMADNE);
+
+	if (!s->async->prealloc_buf) {
+		printk("async->data disappeared.  dang!\n");
+		return;
+	}
+
+	i = devpriv->current_dma_index;
+	ptr = devpriv->dma[i].buf;
+	size = devpriv->dma[i].size;
+
+	disable_dma(devpriv->dma[i].chan);
+
+	devpriv->current_dma_index = 1 - i;
+
+	dt282x_munge(dev, ptr, size);
+	ret = cfc_write_array_to_buffer(s, ptr, size);
+	if (ret != size) {
+		dt282x_ai_cancel(dev, s);
+		return;
+	}
+	devpriv->nread -= size / 2;
+
+	if (devpriv->nread < 0) {
+		printk("dt282x: off by one\n");
+		devpriv->nread = 0;
+	}
+	if (!devpriv->nread) {
+		dt282x_ai_cancel(dev, s);
+		s->async->events |= COMEDI_CB_EOA;
+		return;
+	}
+#if 0
+	/* clear the dual dma flag, making this the last dma segment */
+	/* XXX probably wrong */
+	if (!devpriv->ntrig) {
+		devpriv->supcsr &= ~(DT2821_DDMA);
+		update_supcsr(0);
+	}
+#endif
+	/* restart the channel */
+	prep_ai_dma(dev, i, 0);
+}
+
+static int prep_ai_dma(comedi_device * dev, int dma_index, int n)
+{
+	int dma_chan;
+	unsigned long dma_ptr;
+	unsigned long flags;
+
+	if (!devpriv->ntrig)
+		return 0;
+
+	if (n == 0)
+		n = devpriv->dma_maxsize;
+	if (n > devpriv->ntrig * 2)
+		n = devpriv->ntrig * 2;
+	devpriv->ntrig -= n / 2;
+
+	devpriv->dma[dma_index].size = n;
+	dma_chan = devpriv->dma[dma_index].chan;
+	dma_ptr = virt_to_bus(devpriv->dma[dma_index].buf);
+
+	set_dma_mode(dma_chan, DMA_MODE_READ);
+	flags = claim_dma_lock();
+	clear_dma_ff(dma_chan);
+	set_dma_addr(dma_chan, dma_ptr);
+	set_dma_count(dma_chan, n);
+	release_dma_lock(flags);
+
+	enable_dma(dma_chan);
+
+	return n;
+}
+
+static int prep_ao_dma(comedi_device * dev, int dma_index, int n)
+{
+	int dma_chan;
+	unsigned long dma_ptr;
+	unsigned long flags;
+
+	devpriv->dma[dma_index].size = n;
+	dma_chan = devpriv->dma[dma_index].chan;
+	dma_ptr = virt_to_bus(devpriv->dma[dma_index].buf);
+
+	set_dma_mode(dma_chan, DMA_MODE_WRITE);
+	flags = claim_dma_lock();
+	clear_dma_ff(dma_chan);
+	set_dma_addr(dma_chan, dma_ptr);
+	set_dma_count(dma_chan, n);
+	release_dma_lock(flags);
+
+	enable_dma(dma_chan);
+
+	return n;
+}
+
+static irqreturn_t dt282x_interrupt(int irq, void *d PT_REGS_ARG)
+{
+	comedi_device *dev = d;
+	comedi_subdevice *s;
+	comedi_subdevice *s_ao;
+	unsigned int supcsr, adcsr, dacsr;
+	int handled = 0;
+
+	if (!dev->attached) {
+		comedi_error(dev, "spurious interrupt");
+		return IRQ_HANDLED;
+	}
+
+	s = dev->subdevices + 0;
+	s_ao = dev->subdevices + 1;
+	adcsr = inw(dev->iobase + DT2821_ADCSR);
+	dacsr = inw(dev->iobase + DT2821_DACSR);
+	supcsr = inw(dev->iobase + DT2821_SUPCSR);
+	if (supcsr & DT2821_DMAD) {
+		if (devpriv->dma_dir == DMA_MODE_READ)
+			dt282x_ai_dma_interrupt(dev);
+		else
+			dt282x_ao_dma_interrupt(dev);
+		handled = 1;
+	}
+	if (adcsr & DT2821_ADERR) {
+		if (devpriv->nread != 0) {
+			comedi_error(dev, "A/D error");
+			dt282x_ai_cancel(dev, s);
+			s->async->events |= COMEDI_CB_ERROR;
+		}
+		handled = 1;
+	}
+	if (dacsr & DT2821_DAERR) {
+#if 0
+		static int warn = 5;
+		if (--warn <= 0) {
+			disable_irq(dev->irq);
+			printk("disabling irq\n");
+		}
+#endif
+		comedi_error(dev, "D/A error");
+		dt282x_ao_cancel(dev, s_ao);
+		s->async->events |= COMEDI_CB_ERROR;
+		handled = 1;
+	}
+#if 0
+	if (adcsr & DT2821_ADDONE) {
+		int ret;
+		sampl_t data;
+
+		data = (sampl_t) inw(dev->iobase + DT2821_ADDAT);
+		data &= (1 << boardtype.adbits) - 1;
+		if (devpriv->ad_2scomp) {
+			data ^= 1 << (boardtype.adbits - 1);
+		}
+		ret = comedi_buf_put(s->async, data);
+		if (ret == 0) {
+			s->async->events |= COMEDI_CB_OVERFLOW;
+		}
+
+		devpriv->nread--;
+		if (!devpriv->nread) {
+			s->async->events |= COMEDI_CB_EOA;
+		} else {
+			if (supcsr & DT2821_SCDN)
+				update_supcsr(DT2821_STRIG);
+		}
+		handled = 1;
+	}
+#endif
+	comedi_event(dev, s);
+	/* printk("adcsr=0x%02x dacsr-0x%02x supcsr=0x%02x\n", adcsr, dacsr, supcsr); */
+	return IRQ_RETVAL(handled);
+}
+
+static void dt282x_load_changain(comedi_device * dev, int n,
+	unsigned int *chanlist)
+{
+	unsigned int i;
+	unsigned int chan, range;
+
+	outw(DT2821_LLE | (n - 1), dev->iobase + DT2821_CHANCSR);
+	for (i = 0; i < n; i++) {
+		chan = CR_CHAN(chanlist[i]);
+		range = CR_RANGE(chanlist[i]);
+		update_adcsr((range << 4) | (chan));
+	}
+	outw(n - 1, dev->iobase + DT2821_CHANCSR);
+}
+
+/*
+ *    Performs a single A/D conversion.
+ *      - Put channel/gain into channel-gain list
+ *      - preload multiplexer
+ *      - trigger conversion and wait for it to finish
+ */
+static int dt282x_ai_insn_read(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int i;
+
+	/* XXX should we really be enabling the ad clock here? */
+	devpriv->adcsr = DT2821_ADCLK;
+	update_adcsr(0);
+
+	dt282x_load_changain(dev, 1, &insn->chanspec);
+
+	update_supcsr(DT2821_PRLD);
+	wait_for(!mux_busy(), comedi_error(dev, "timeout\n");
+		return -ETIME;
+		);
+
+	for (i = 0; i < insn->n; i++) {
+		update_supcsr(DT2821_STRIG);
+		wait_for(ad_done(), comedi_error(dev, "timeout\n");
+			return -ETIME;
+			);
+
+		data[i] =
+			inw(dev->iobase +
+			DT2821_ADDAT) & ((1 << boardtype.adbits) - 1);
+		if (devpriv->ad_2scomp)
+			data[i] ^= (1 << (boardtype.adbits - 1));
+	}
+
+	return i;
+}
+
+static int dt282x_ai_cmdtest(comedi_device * dev, comedi_subdevice * s,
+	comedi_cmd * cmd)
+{
+	int err = 0;
+	int tmp;
+
+	/* step 1: make sure trigger sources are trivially valid */
+
+	tmp = cmd->start_src;
+	cmd->start_src &= TRIG_NOW;
+	if (!cmd->start_src || tmp != cmd->start_src)
+		err++;
+
+	tmp = cmd->scan_begin_src;
+	cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_EXT;
+	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
+		err++;
+
+	tmp = cmd->convert_src;
+	cmd->convert_src &= TRIG_TIMER;
+	if (!cmd->convert_src || tmp != cmd->convert_src)
+		err++;
+
+	tmp = cmd->scan_end_src;
+	cmd->scan_end_src &= TRIG_COUNT;
+	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
+		err++;
+
+	tmp = cmd->stop_src;
+	cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
+	if (!cmd->stop_src || tmp != cmd->stop_src)
+		err++;
+
+	if (err)
+		return 1;
+
+	/* step 2: make sure trigger sources are unique and mutually compatible */
+
+	/* note that mutual compatiblity is not an issue here */
+	if (cmd->scan_begin_src != TRIG_FOLLOW &&
+		cmd->scan_begin_src != TRIG_EXT)
+		err++;
+	if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
+		err++;
+
+	if (err)
+		return 2;
+
+	/* step 3: make sure arguments are trivially compatible */
+
+	if (cmd->start_arg != 0) {
+		cmd->start_arg = 0;
+		err++;
+	}
+	if (cmd->scan_begin_src == TRIG_FOLLOW) {
+		/* internal trigger */
+		if (cmd->scan_begin_arg != 0) {
+			cmd->scan_begin_arg = 0;
+			err++;
+		}
+	} else {
+		/* external trigger */
+		/* should be level/edge, hi/lo specification here */
+		if (cmd->scan_begin_arg != 0) {
+			cmd->scan_begin_arg = 0;
+			err++;
+		}
+	}
+	if (cmd->convert_arg < 4000) {
+		/* XXX board dependent */
+		cmd->convert_arg = 4000;
+		err++;
+	}
+#define SLOWEST_TIMER	(250*(1<<15)*255)
+	if (cmd->convert_arg > SLOWEST_TIMER) {
+		cmd->convert_arg = SLOWEST_TIMER;
+		err++;
+	}
+	if (cmd->convert_arg < this_board->ai_speed) {
+		cmd->convert_arg = this_board->ai_speed;
+		err++;
+	}
+	if (cmd->scan_end_arg != cmd->chanlist_len) {
+		cmd->scan_end_arg = cmd->chanlist_len;
+		err++;
+	}
+	if (cmd->stop_src == TRIG_COUNT) {
+		/* any count is allowed */
+	} else {
+		/* TRIG_NONE */
+		if (cmd->stop_arg != 0) {
+			cmd->stop_arg = 0;
+			err++;
+		}
+	}
+
+	if (err)
+		return 3;
+
+	/* step 4: fix up any arguments */
+
+	tmp = cmd->convert_arg;
+	dt282x_ns_to_timer(&cmd->convert_arg, cmd->flags & TRIG_ROUND_MASK);
+	if (tmp != cmd->convert_arg)
+		err++;
+
+	if (err)
+		return 4;
+
+	return 0;
+}
+
+static int dt282x_ai_cmd(comedi_device * dev, comedi_subdevice * s)
+{
+	comedi_cmd *cmd = &s->async->cmd;
+	int timer;
+
+	if (devpriv->usedma == 0) {
+		comedi_error(dev,
+			"driver requires 2 dma channels to execute command");
+		return -EIO;
+	}
+
+	dt282x_disable_dma(dev);
+
+	if (cmd->convert_arg < this_board->ai_speed)
+		cmd->convert_arg = this_board->ai_speed;
+	timer = dt282x_ns_to_timer(&cmd->convert_arg, TRIG_ROUND_NEAREST);
+	outw(timer, dev->iobase + DT2821_TMRCTR);
+
+	if (cmd->scan_begin_src == TRIG_FOLLOW) {
+		/* internal trigger */
+		devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS0;
+	} else {
+		/* external trigger */
+		devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS0 | DT2821_DS1;
+	}
+	update_supcsr(DT2821_CLRDMADNE | DT2821_BUFFB | DT2821_ADCINIT);
+
+	devpriv->ntrig = cmd->stop_arg * cmd->scan_end_arg;
+	devpriv->nread = devpriv->ntrig;
+
+	devpriv->dma_dir = DMA_MODE_READ;
+	devpriv->current_dma_index = 0;
+	prep_ai_dma(dev, 0, 0);
+	if (devpriv->ntrig) {
+		prep_ai_dma(dev, 1, 0);
+		devpriv->supcsr |= DT2821_DDMA;
+		update_supcsr(0);
+	}
+
+	devpriv->adcsr = 0;
+
+	dt282x_load_changain(dev, cmd->chanlist_len, cmd->chanlist);
+
+	devpriv->adcsr = DT2821_ADCLK | DT2821_IADDONE;
+	update_adcsr(0);
+
+	update_supcsr(DT2821_PRLD);
+	wait_for(!mux_busy(), comedi_error(dev, "timeout\n");
+		return -ETIME;
+		);
+
+	if (cmd->scan_begin_src == TRIG_FOLLOW) {
+		update_supcsr(DT2821_STRIG);
+	} else {
+		devpriv->supcsr |= DT2821_XTRIG;
+		update_supcsr(0);
+	}
+
+	return 0;
+}
+
+static void dt282x_disable_dma(comedi_device * dev)
+{
+	if (devpriv->usedma) {
+		disable_dma(devpriv->dma[0].chan);
+		disable_dma(devpriv->dma[1].chan);
+	}
+}
+
+static int dt282x_ai_cancel(comedi_device * dev, comedi_subdevice * s)
+{
+	dt282x_disable_dma(dev);
+
+	devpriv->adcsr = 0;
+	update_adcsr(0);
+
+	devpriv->supcsr = 0;
+	update_supcsr(DT2821_ADCINIT);
+
+	return 0;
+}
+
+static int dt282x_ns_to_timer(int *nanosec, int round_mode)
+{
+	int prescale, base, divider;
+
+	for (prescale = 0; prescale < 16; prescale++) {
+		if (prescale == 1)
+			continue;
+		base = 250 * (1 << prescale);
+		switch (round_mode) {
+		case TRIG_ROUND_NEAREST:
+		default:
+			divider = (*nanosec + base / 2) / base;
+			break;
+		case TRIG_ROUND_DOWN:
+			divider = (*nanosec) / base;
+			break;
+		case TRIG_ROUND_UP:
+			divider = (*nanosec + base - 1) / base;
+			break;
+		}
+		if (divider < 256) {
+			*nanosec = divider * base;
+			return (prescale << 8) | (255 - divider);
+		}
+	}
+	base = 250 * (1 << 15);
+	divider = 255;
+	*nanosec = divider * base;
+	return (15 << 8) | (255 - divider);
+}
+
+/*
+ *    Analog output routine.  Selects single channel conversion,
+ *      selects correct channel, converts from 2's compliment to
+ *      offset binary if necessary, loads the data into the DAC
+ *      data register, and performs the conversion.
+ */
+static int dt282x_ao_insn_read(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	data[0] = devpriv->ao[CR_CHAN(insn->chanspec)];
+
+	return 1;
+}
+
+static int dt282x_ao_insn_write(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	sampl_t d;
+	unsigned int chan;
+
+	chan = CR_CHAN(insn->chanspec);
+	d = data[0];
+	d &= (1 << boardtype.dabits) - 1;
+	devpriv->ao[chan] = d;
+
+	devpriv->dacsr |= DT2821_SSEL;
+
+	if (chan) {
+		/* select channel */
+		devpriv->dacsr |= DT2821_YSEL;
+		if (devpriv->da0_2scomp)
+			d ^= (1 << (boardtype.dabits - 1));
+	} else {
+		devpriv->dacsr &= ~DT2821_YSEL;
+		if (devpriv->da1_2scomp)
+			d ^= (1 << (boardtype.dabits - 1));
+	}
+
+	update_dacsr(0);
+
+	outw(d, dev->iobase + DT2821_DADAT);
+
+	update_supcsr(DT2821_DACON);
+
+	return 1;
+}
+
+static int dt282x_ao_cmdtest(comedi_device * dev, comedi_subdevice * s,
+	comedi_cmd * cmd)
+{
+	int err = 0;
+	int tmp;
+
+	/* step 1: make sure trigger sources are trivially valid */
+
+	tmp = cmd->start_src;
+	cmd->start_src &= TRIG_INT;
+	if (!cmd->start_src || tmp != cmd->start_src)
+		err++;
+
+	tmp = cmd->scan_begin_src;
+	cmd->scan_begin_src &= TRIG_TIMER;
+	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
+		err++;
+
+	tmp = cmd->convert_src;
+	cmd->convert_src &= TRIG_NOW;
+	if (!cmd->convert_src || tmp != cmd->convert_src)
+		err++;
+
+	tmp = cmd->scan_end_src;
+	cmd->scan_end_src &= TRIG_COUNT;
+	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
+		err++;
+
+	tmp = cmd->stop_src;
+	cmd->stop_src &= TRIG_NONE;
+	if (!cmd->stop_src || tmp != cmd->stop_src)
+		err++;
+
+	if (err)
+		return 1;
+
+	/* step 2: make sure trigger sources are unique and mutually compatible */
+
+	/* note that mutual compatiblity is not an issue here */
+	if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
+		err++;
+
+	if (err)
+		return 2;
+
+	/* step 3: make sure arguments are trivially compatible */
+
+	if (cmd->start_arg != 0) {
+		cmd->start_arg = 0;
+		err++;
+	}
+	if (cmd->scan_begin_arg < 5000 /* XXX unknown */ ) {
+		cmd->scan_begin_arg = 5000;
+		err++;
+	}
+	if (cmd->convert_arg != 0) {
+		cmd->convert_arg = 0;
+		err++;
+	}
+	if (cmd->scan_end_arg > 2) {
+		/* XXX chanlist stuff? */
+		cmd->scan_end_arg = 2;
+		err++;
+	}
+	if (cmd->stop_src == TRIG_COUNT) {
+		/* any count is allowed */
+	} else {
+		/* TRIG_NONE */
+		if (cmd->stop_arg != 0) {
+			cmd->stop_arg = 0;
+			err++;
+		}
+	}
+
+	if (err)
+		return 3;
+
+	/* step 4: fix up any arguments */
+
+	tmp = cmd->scan_begin_arg;
+	dt282x_ns_to_timer(&cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK);
+	if (tmp != cmd->scan_begin_arg)
+		err++;
+
+	if (err)
+		return 4;
+
+	return 0;
+
+}
+
+static int dt282x_ao_inttrig(comedi_device * dev, comedi_subdevice * s,
+	unsigned int x)
+{
+	int size;
+
+	if (x != 0)
+		return -EINVAL;
+
+	size = cfc_read_array_from_buffer(s, devpriv->dma[0].buf,
+		devpriv->dma_maxsize);
+	if (size == 0) {
+		rt_printk("dt282x: AO underrun\n");
+		return -EPIPE;
+	}
+	prep_ao_dma(dev, 0, size);
+
+	size = cfc_read_array_from_buffer(s, devpriv->dma[1].buf,
+		devpriv->dma_maxsize);
+	if (size == 0) {
+		rt_printk("dt282x: AO underrun\n");
+		return -EPIPE;
+	}
+	prep_ao_dma(dev, 1, size);
+
+	update_supcsr(DT2821_STRIG);
+	s->async->inttrig = NULL;
+
+	return 1;
+}
+
+static int dt282x_ao_cmd(comedi_device * dev, comedi_subdevice * s)
+{
+	int timer;
+	comedi_cmd *cmd = &s->async->cmd;
+
+	if (devpriv->usedma == 0) {
+		comedi_error(dev,
+			"driver requires 2 dma channels to execute command");
+		return -EIO;
+	}
+
+	dt282x_disable_dma(dev);
+
+	devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS1 | DT2821_DDMA;
+	update_supcsr(DT2821_CLRDMADNE | DT2821_BUFFB | DT2821_DACINIT);
+
+	devpriv->ntrig = cmd->stop_arg * cmd->chanlist_len;
+	devpriv->nread = devpriv->ntrig;
+
+	devpriv->dma_dir = DMA_MODE_WRITE;
+	devpriv->current_dma_index = 0;
+
+	timer = dt282x_ns_to_timer(&cmd->scan_begin_arg, TRIG_ROUND_NEAREST);
+	outw(timer, dev->iobase + DT2821_TMRCTR);
+
+	devpriv->dacsr = DT2821_SSEL | DT2821_DACLK | DT2821_IDARDY;
+	update_dacsr(0);
+
+	s->async->inttrig = dt282x_ao_inttrig;
+
+	return 0;
+}
+
+static int dt282x_ao_cancel(comedi_device * dev, comedi_subdevice * s)
+{
+	dt282x_disable_dma(dev);
+
+	devpriv->dacsr = 0;
+	update_dacsr(0);
+
+	devpriv->supcsr = 0;
+	update_supcsr(DT2821_DACINIT);
+
+	return 0;
+}
+
+static int dt282x_dio_insn_bits(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	if (data[0]) {
+		s->state &= ~data[0];
+		s->state |= (data[0] & data[1]);
+
+		outw(s->state, dev->iobase + DT2821_DIODAT);
+	}
+	data[1] = inw(dev->iobase + DT2821_DIODAT);
+
+	return 2;
+}
+
+static int dt282x_dio_insn_config(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int mask;
+
+	mask = (CR_CHAN(insn->chanspec) < 8) ? 0x00ff : 0xff00;
+	if (data[0])
+		s->io_bits |= mask;
+	else
+		s->io_bits &= ~mask;
+
+	if (s->io_bits & 0x00ff)
+		devpriv->dacsr |= DT2821_LBOE;
+	else
+		devpriv->dacsr &= ~DT2821_LBOE;
+	if (s->io_bits & 0xff00)
+		devpriv->dacsr |= DT2821_HBOE;
+	else
+		devpriv->dacsr &= ~DT2821_HBOE;
+
+	outw(devpriv->dacsr, dev->iobase + DT2821_DACSR);
+
+	return 1;
+}
+
+static const comedi_lrange *const ai_range_table[] = {
+	&range_dt282x_ai_lo_bipolar,
+	&range_dt282x_ai_lo_unipolar,
+	&range_dt282x_ai_5_bipolar,
+	&range_dt282x_ai_5_unipolar
+};
+static const comedi_lrange *const ai_range_pgl_table[] = {
+	&range_dt282x_ai_hi_bipolar,
+	&range_dt282x_ai_hi_unipolar
+};
+static const comedi_lrange *opt_ai_range_lkup(int ispgl, int x)
+{
+	if (ispgl) {
+		if (x < 0 || x >= 2)
+			x = 0;
+		return ai_range_pgl_table[x];
+	} else {
+		if (x < 0 || x >= 4)
+			x = 0;
+		return ai_range_table[x];
+	}
+}
+static const comedi_lrange *const ao_range_table[] = {
+	&range_bipolar10,
+	&range_unipolar10,
+	&range_bipolar5,
+	&range_unipolar5,
+	&range_bipolar2_5
+};
+static const comedi_lrange *opt_ao_range_lkup(int x)
+{
+	if (x < 0 || x >= 5)
+		x = 0;
+	return ao_range_table[x];
+}
+
+enum { opt_iobase = 0, opt_irq, opt_dma1, opt_dma2,	/* i/o base, irq, dma channels */
+	opt_diff,		/* differential */
+	opt_ai_twos, opt_ao0_twos, opt_ao1_twos,	/* twos comp */
+	opt_ai_range, opt_ao0_range, opt_ao1_range,	/* range */
+};
+
+/*
+   options:
+   0	i/o base
+   1	irq
+   2	dma1
+   3	dma2
+   4	0=single ended, 1=differential
+   5	ai 0=straight binary, 1=2's comp
+   6	ao0 0=straight binary, 1=2's comp
+   7	ao1 0=straight binary, 1=2's comp
+   8	ai 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V
+   9	ao0 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V, 4=±2.5 V
+   10	ao1 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V, 4=±2.5 V
+ */
+static int dt282x_attach(comedi_device * dev, comedi_devconfig * it)
+{
+	int i, irq;
+	int ret;
+	comedi_subdevice *s;
+	unsigned long iobase;
+
+	dev->board_name = this_board->name;
+
+	iobase = it->options[opt_iobase];
+	if (!iobase)
+		iobase = 0x240;
+
+	printk("comedi%d: dt282x: 0x%04lx", dev->minor, iobase);
+	if (!request_region(iobase, DT2821_SIZE, "dt282x")) {
+		printk(" I/O port conflict\n");
+		return -EBUSY;
+	}
+	dev->iobase = iobase;
+
+	outw(DT2821_BDINIT, dev->iobase + DT2821_SUPCSR);
+	i = inw(dev->iobase + DT2821_ADCSR);
+#ifdef DEBUG
+	printk(" fingerprint=%x,%x,%x,%x,%x",
+		inw(dev->iobase + DT2821_ADCSR),
+		inw(dev->iobase + DT2821_CHANCSR),
+		inw(dev->iobase + DT2821_DACSR),
+		inw(dev->iobase + DT2821_SUPCSR),
+		inw(dev->iobase + DT2821_TMRCTR));
+#endif
+
+	if (((inw(dev->iobase + DT2821_ADCSR) & DT2821_ADCSR_MASK)
+			!= DT2821_ADCSR_VAL) ||
+		((inw(dev->iobase + DT2821_CHANCSR) & DT2821_CHANCSR_MASK)
+			!= DT2821_CHANCSR_VAL) ||
+		((inw(dev->iobase + DT2821_DACSR) & DT2821_DACSR_MASK)
+			!= DT2821_DACSR_VAL) ||
+		((inw(dev->iobase + DT2821_SUPCSR) & DT2821_SUPCSR_MASK)
+			!= DT2821_SUPCSR_VAL) ||
+		((inw(dev->iobase + DT2821_TMRCTR) & DT2821_TMRCTR_MASK)
+			!= DT2821_TMRCTR_VAL)) {
+		printk(" board not found");
+		return -EIO;
+	}
+	/* should do board test */
+
+	irq = it->options[opt_irq];
+#if 0
+	if (irq < 0) {
+		unsigned long flags;
+		int irqs;
+
+		save_flags(flags);
+		sti();
+		irqs = probe_irq_on();
+
+		/* trigger interrupt */
+
+		comedi_udelay(100);
+
+		irq = probe_irq_off(irqs);
+		restore_flags(flags);
+		if (0 /* error */ ) {
+			printk(" error probing irq (bad)");
+		}
+	}
+#endif
+	if (irq > 0) {
+		printk(" ( irq = %d )", irq);
+		ret = comedi_request_irq(irq, dt282x_interrupt, 0, "dt282x",
+			dev);
+		if (ret < 0) {
+			printk(" failed to get irq\n");
+			return -EIO;
+		}
+		dev->irq = irq;
+	} else if (irq == 0) {
+		printk(" (no irq)");
+	} else {
+#if 0
+		printk(" (probe returned multiple irqs--bad)");
+#else
+		printk(" (irq probe not implemented)");
+#endif
+	}
+
+	if ((ret = alloc_private(dev, sizeof(dt282x_private))) < 0)
+		return ret;
+
+	ret = dt282x_grab_dma(dev, it->options[opt_dma1],
+		it->options[opt_dma2]);
+	if (ret < 0)
+		return ret;
+
+	if ((ret = alloc_subdevices(dev, 3)) < 0)
+		return ret;
+
+	s = dev->subdevices + 0;
+
+	dev->read_subdev = s;
+	/* ai subdevice */
+	s->type = COMEDI_SUBD_AI;
+	s->subdev_flags = SDF_READABLE | SDF_CMD_READ |
+		((it->options[opt_diff]) ? SDF_DIFF : SDF_COMMON);
+	s->n_chan =
+		(it->options[opt_diff]) ? boardtype.adchan_di : boardtype.
+		adchan_se;
+	s->insn_read = dt282x_ai_insn_read;
+	s->do_cmdtest = dt282x_ai_cmdtest;
+	s->do_cmd = dt282x_ai_cmd;
+	s->cancel = dt282x_ai_cancel;
+	s->maxdata = (1 << boardtype.adbits) - 1;
+	s->len_chanlist = 16;
+	s->range_table =
+		opt_ai_range_lkup(boardtype.ispgl, it->options[opt_ai_range]);
+	devpriv->ad_2scomp = it->options[opt_ai_twos];
+
+	s++;
+	if ((s->n_chan = boardtype.dachan)) {
+		/* ao subsystem */
+		s->type = COMEDI_SUBD_AO;
+		dev->write_subdev = s;
+		s->subdev_flags = SDF_WRITABLE | SDF_CMD_WRITE;
+		s->insn_read = dt282x_ao_insn_read;
+		s->insn_write = dt282x_ao_insn_write;
+		s->do_cmdtest = dt282x_ao_cmdtest;
+		s->do_cmd = dt282x_ao_cmd;
+		s->cancel = dt282x_ao_cancel;
+		s->maxdata = (1 << boardtype.dabits) - 1;
+		s->len_chanlist = 2;
+		s->range_table_list = devpriv->darangelist;
+		devpriv->darangelist[0] =
+			opt_ao_range_lkup(it->options[opt_ao0_range]);
+		devpriv->darangelist[1] =
+			opt_ao_range_lkup(it->options[opt_ao1_range]);
+		devpriv->da0_2scomp = it->options[opt_ao0_twos];
+		devpriv->da1_2scomp = it->options[opt_ao1_twos];
+	} else {
+		s->type = COMEDI_SUBD_UNUSED;
+	}
+
+	s++;
+	/* dio subsystem */
+	s->type = COMEDI_SUBD_DIO;
+	s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+	s->n_chan = 16;
+	s->insn_bits = dt282x_dio_insn_bits;
+	s->insn_config = dt282x_dio_insn_config;
+	s->maxdata = 1;
+	s->range_table = &range_digital;
+
+	printk("\n");
+
+	return 0;
+}
+
+static void free_resources(comedi_device * dev)
+{
+	if (dev->irq) {
+		comedi_free_irq(dev->irq, dev);
+	}
+	if (dev->iobase)
+		release_region(dev->iobase, DT2821_SIZE);
+	if (dev->private) {
+		if (devpriv->dma[0].chan)
+			free_dma(devpriv->dma[0].chan);
+		if (devpriv->dma[1].chan)
+			free_dma(devpriv->dma[1].chan);
+		if (devpriv->dma[0].buf)
+			free_page((unsigned long)devpriv->dma[0].buf);
+		if (devpriv->dma[1].buf)
+			free_page((unsigned long)devpriv->dma[1].buf);
+	}
+}
+
+static int dt282x_detach(comedi_device * dev)
+{
+	printk("comedi%d: dt282x: remove\n", dev->minor);
+
+	free_resources(dev);
+
+	return 0;
+}
+
+static int dt282x_grab_dma(comedi_device * dev, int dma1, int dma2)
+{
+	int ret;
+
+	devpriv->usedma = 0;
+
+	if (!dma1 && !dma2) {
+		printk(" (no dma)");
+		return 0;
+	}
+
+	if (dma1 == dma2 || dma1 < 5 || dma2 < 5 || dma1 > 7 || dma2 > 7)
+		return -EINVAL;
+
+	if (dma2 < dma1) {
+		int i;
+		i = dma1;
+		dma1 = dma2;
+		dma2 = i;
+	}
+
+	ret = request_dma(dma1, "dt282x A");
+	if (ret)
+		return -EBUSY;
+	devpriv->dma[0].chan = dma1;
+
+	ret = request_dma(dma2, "dt282x B");
+	if (ret)
+		return -EBUSY;
+	devpriv->dma[1].chan = dma2;
+
+	devpriv->dma_maxsize = PAGE_SIZE;
+	devpriv->dma[0].buf = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
+	devpriv->dma[1].buf = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);
+	if (!devpriv->dma[0].buf || !devpriv->dma[1].buf) {
+		printk(" can't get DMA memory");
+		return -ENOMEM;
+	}
+
+	printk(" (dma=%d,%d)", dma1, dma2);
+
+	devpriv->usedma = 1;
+
+	return 0;
+}