]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/staging/comedi/drivers/dt3000.c
Merge branch 'omap-pool'
[linux-2.6-omap-h63xx.git] / drivers / staging / comedi / drivers / dt3000.c
diff --git a/drivers/staging/comedi/drivers/dt3000.c b/drivers/staging/comedi/drivers/dt3000.c
new file mode 100644 (file)
index 0000000..d946798
--- /dev/null
@@ -0,0 +1,983 @@
+/*
+    comedi/drivers/dt3000.c
+    Data Translation DT3000 series driver
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 1999 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: dt3000
+Description: Data Translation DT3000 series
+Author: ds
+Devices: [Data Translation] DT3001 (dt3000), DT3001-PGL, DT3002, DT3003,
+  DT3003-PGL, DT3004, DT3005, DT3004-200
+Updated: Mon, 14 Apr 2008 15:41:24 +0100
+Status: works
+
+Configuration Options:
+  [0] - PCI bus of device (optional)
+  [1] - PCI slot of device (optional)
+  If bus/slot is not specified, the first supported
+  PCI device found will be used.
+
+There is code to support AI commands, but it may not work.
+
+AO commands are not supported.
+*/
+
+/*
+   The DT3000 series is Data Translation's attempt to make a PCI
+   data acquisition board.  The design of this series is very nice,
+   since each board has an on-board DSP (Texas Instruments TMS320C52).
+   However, a few details are a little annoying.  The boards lack
+   bus-mastering DMA, which eliminates them from serious work.
+   They also are not capable of autocalibration, which is a common
+   feature in modern hardware.  The default firmware is pretty bad,
+   making it nearly impossible to write an RT compatible driver.
+   It would make an interesting project to write a decent firmware
+   for these boards.
+
+   Data Translation originally wanted an NDA for the documentation
+   for the 3k series.  However, if you ask nicely, they might send
+   you the docs without one, also.
+*/
+
+#define DEBUG 1
+
+#include "../comedidev.h"
+#include <linux/delay.h>
+
+#include "comedi_pci.h"
+
+#define PCI_VENDOR_ID_DT       0x1116
+
+static const struct comedi_lrange range_dt3000_ai = { 4, {
+                       RANGE(-10, 10),
+                       RANGE(-5, 5),
+                       RANGE(-2.5, 2.5),
+                       RANGE(-1.25, 1.25)
+       }
+};
+static const struct comedi_lrange range_dt3000_ai_pgl = { 4, {
+                       RANGE(-10, 10),
+                       RANGE(-1, 1),
+                       RANGE(-0.1, 0.1),
+                       RANGE(-0.02, 0.02)
+       }
+};
+
+struct dt3k_boardtype {
+
+       const char *name;
+       unsigned int device_id;
+       int adchan;
+       int adbits;
+       int ai_speed;
+       const struct comedi_lrange *adrange;
+       int dachan;
+       int dabits;
+};
+
+
+static const struct dt3k_boardtype dt3k_boardtypes[] = {
+      {name:"dt3001",
+             device_id:0x22,
+             adchan:   16,
+             adbits:   12,
+             adrange:  &range_dt3000_ai,
+             ai_speed:3000,
+             dachan:   2,
+             dabits:   12,
+               },
+      {name:"dt3001-pgl",
+             device_id:0x27,
+             adchan:   16,
+             adbits:   12,
+             adrange:  &range_dt3000_ai_pgl,
+             ai_speed:3000,
+             dachan:   2,
+             dabits:   12,
+               },
+      {name:"dt3002",
+             device_id:0x23,
+             adchan:   32,
+             adbits:   12,
+             adrange:  &range_dt3000_ai,
+             ai_speed:3000,
+             dachan:   0,
+             dabits:   0,
+               },
+      {name:"dt3003",
+             device_id:0x24,
+             adchan:   64,
+             adbits:   12,
+             adrange:  &range_dt3000_ai,
+             ai_speed:3000,
+             dachan:   2,
+             dabits:   12,
+               },
+      {name:"dt3003-pgl",
+             device_id:0x28,
+             adchan:   64,
+             adbits:   12,
+             adrange:  &range_dt3000_ai_pgl,
+             ai_speed:3000,
+             dachan:   2,
+             dabits:   12,
+               },
+      {name:"dt3004",
+             device_id:0x25,
+             adchan:   16,
+             adbits:   16,
+             adrange:  &range_dt3000_ai,
+             ai_speed:10000,
+             dachan:   2,
+             dabits:   12,
+               },
+      {name:"dt3005",          /* a.k.a. 3004-200 */
+             device_id:0x26,
+             adchan:   16,
+             adbits:   16,
+             adrange:  &range_dt3000_ai,
+             ai_speed:5000,
+             dachan:   2,
+             dabits:   12,
+               },
+};
+
+#define n_dt3k_boards sizeof(dt3k_boardtypes)/sizeof(struct dt3k_boardtype)
+#define this_board ((const struct dt3k_boardtype *)dev->board_ptr)
+
+static DEFINE_PCI_DEVICE_TABLE(dt3k_pci_table) = {
+       {PCI_VENDOR_ID_DT, 0x0022, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_DT, 0x0027, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_DT, 0x0023, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_DT, 0x0024, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_DT, 0x0028, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_DT, 0x0025, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_DT, 0x0026, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {0}
+};
+
+MODULE_DEVICE_TABLE(pci, dt3k_pci_table);
+
+#define DT3000_SIZE            (4*0x1000)
+
+/* dual-ported RAM location definitions */
+
+#define DPR_DAC_buffer         (4*0x000)
+#define DPR_ADC_buffer         (4*0x800)
+#define DPR_Command            (4*0xfd3)
+#define DPR_SubSys             (4*0xfd3)
+#define DPR_Encode             (4*0xfd4)
+#define DPR_Params(a)          (4*(0xfd5+(a)))
+#define DPR_Tick_Reg_Lo                (4*0xff5)
+#define DPR_Tick_Reg_Hi                (4*0xff6)
+#define DPR_DA_Buf_Front       (4*0xff7)
+#define DPR_DA_Buf_Rear                (4*0xff8)
+#define DPR_AD_Buf_Front       (4*0xff9)
+#define DPR_AD_Buf_Rear                (4*0xffa)
+#define DPR_Int_Mask           (4*0xffb)
+#define DPR_Intr_Flag          (4*0xffc)
+#define DPR_Response_Mbx       (4*0xffe)
+#define DPR_Command_Mbx                (4*0xfff)
+
+#define AI_FIFO_DEPTH  2003
+#define AO_FIFO_DEPTH  2048
+
+/* command list */
+
+#define CMD_GETBRDINFO         0
+#define CMD_CONFIG             1
+#define CMD_GETCONFIG          2
+#define CMD_START              3
+#define CMD_STOP               4
+#define CMD_READSINGLE         5
+#define CMD_WRITESINGLE                6
+#define CMD_CALCCLOCK          7
+#define CMD_READEVENTS         8
+#define CMD_WRITECTCTRL                16
+#define CMD_READCTCTRL         17
+#define CMD_WRITECT            18
+#define CMD_READCT             19
+#define CMD_WRITEDATA          32
+#define CMD_READDATA           33
+#define CMD_WRITEIO            34
+#define CMD_READIO             35
+#define CMD_WRITECODE          36
+#define CMD_READCODE           37
+#define CMD_EXECUTE            38
+#define CMD_HALT               48
+
+#define SUBS_AI                0
+#define SUBS_AO                1
+#define SUBS_DIN       2
+#define SUBS_DOUT      3
+#define SUBS_MEM       4
+#define SUBS_CT                5
+
+/* interrupt flags */
+#define DT3000_CMDONE          0x80
+#define DT3000_CTDONE          0x40
+#define DT3000_DAHWERR         0x20
+#define DT3000_DASWERR         0x10
+#define DT3000_DAEMPTY         0x08
+#define DT3000_ADHWERR         0x04
+#define DT3000_ADSWERR         0x02
+#define DT3000_ADFULL          0x01
+
+#define DT3000_COMPLETION_MASK 0xff00
+#define DT3000_COMMAND_MASK    0x00ff
+#define DT3000_NOTPROCESSED    0x0000
+#define DT3000_NOERROR         0x5500
+#define DT3000_ERROR           0xaa00
+#define DT3000_NOTSUPPORTED    0xff00
+
+#define DT3000_EXTERNAL_CLOCK  1
+#define DT3000_RISING_EDGE     2
+
+#define TMODE_MASK             0x1c
+
+#define DT3000_AD_TRIG_INTERNAL                (0<<2)
+#define DT3000_AD_TRIG_EXTERNAL                (1<<2)
+#define DT3000_AD_RETRIG_INTERNAL      (2<<2)
+#define DT3000_AD_RETRIG_EXTERNAL      (3<<2)
+#define DT3000_AD_EXTRETRIG            (4<<2)
+
+#define DT3000_CHANNEL_MODE_SE         0
+#define DT3000_CHANNEL_MODE_DI         1
+
+struct dt3k_private {
+
+       struct pci_dev *pci_dev;
+       resource_size_t phys_addr;
+       void *io_addr;
+       unsigned int lock;
+       unsigned int ao_readback[2];
+       unsigned int ai_front;
+       unsigned int ai_rear;
+};
+
+#define devpriv ((struct dt3k_private *)dev->private)
+
+static int dt3000_attach(struct comedi_device * dev, struct comedi_devconfig * it);
+static int dt3000_detach(struct comedi_device * dev);
+static struct comedi_driver driver_dt3000 = {
+      driver_name:"dt3000",
+      module:THIS_MODULE,
+      attach:dt3000_attach,
+      detach:dt3000_detach,
+};
+
+COMEDI_PCI_INITCLEANUP(driver_dt3000, dt3k_pci_table);
+
+static void dt3k_ai_empty_fifo(struct comedi_device * dev, struct comedi_subdevice * s);
+static int dt3k_ns_to_timer(unsigned int timer_base, unsigned int *arg,
+       unsigned int round_mode);
+static int dt3k_ai_cancel(struct comedi_device * dev, struct comedi_subdevice * s);
+#ifdef DEBUG
+static void debug_intr_flags(unsigned int flags);
+#endif
+
+#define TIMEOUT 100
+
+static int dt3k_send_cmd(struct comedi_device * dev, unsigned int cmd)
+{
+       int i;
+       unsigned int status = 0;
+
+       writew(cmd, devpriv->io_addr + DPR_Command_Mbx);
+
+       for (i = 0; i < TIMEOUT; i++) {
+               status = readw(devpriv->io_addr + DPR_Command_Mbx);
+               if ((status & DT3000_COMPLETION_MASK) != DT3000_NOTPROCESSED)
+                       break;
+               comedi_udelay(1);
+       }
+       if ((status & DT3000_COMPLETION_MASK) == DT3000_NOERROR) {
+               return 0;
+       }
+
+       printk("dt3k_send_cmd() timeout/error status=0x%04x\n", status);
+
+       return -ETIME;
+}
+
+static unsigned int dt3k_readsingle(struct comedi_device * dev, unsigned int subsys,
+       unsigned int chan, unsigned int gain)
+{
+       writew(subsys, devpriv->io_addr + DPR_SubSys);
+
+       writew(chan, devpriv->io_addr + DPR_Params(0));
+       writew(gain, devpriv->io_addr + DPR_Params(1));
+
+       dt3k_send_cmd(dev, CMD_READSINGLE);
+
+       return readw(devpriv->io_addr + DPR_Params(2));
+}
+
+static void dt3k_writesingle(struct comedi_device * dev, unsigned int subsys,
+       unsigned int chan, unsigned int data)
+{
+       writew(subsys, devpriv->io_addr + DPR_SubSys);
+
+       writew(chan, devpriv->io_addr + DPR_Params(0));
+       writew(0, devpriv->io_addr + DPR_Params(1));
+       writew(data, devpriv->io_addr + DPR_Params(2));
+
+       dt3k_send_cmd(dev, CMD_WRITESINGLE);
+}
+
+static int debug_n_ints = 0;
+
+// FIXME! Assumes shared interrupt is for this card.
+// What's this debug_n_ints stuff? Obviously needs some work...
+static irqreturn_t dt3k_interrupt(int irq, void *d PT_REGS_ARG)
+{
+       struct comedi_device *dev = d;
+       struct comedi_subdevice *s;
+       unsigned int status;
+
+       if (!dev->attached) {
+               return IRQ_NONE;
+       }
+
+       s = dev->subdevices + 0;
+       status = readw(devpriv->io_addr + DPR_Intr_Flag);
+#ifdef DEBUG
+       debug_intr_flags(status);
+#endif
+
+       if (status & DT3000_ADFULL) {
+               dt3k_ai_empty_fifo(dev, s);
+               s->async->events |= COMEDI_CB_BLOCK;
+       }
+
+       if (status & (DT3000_ADSWERR | DT3000_ADHWERR)) {
+               s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+       }
+
+       debug_n_ints++;
+       if (debug_n_ints >= 10) {
+               dt3k_ai_cancel(dev, s);
+               s->async->events |= COMEDI_CB_EOA;
+       }
+
+       comedi_event(dev, s);
+       return IRQ_HANDLED;
+}
+
+#ifdef DEBUG
+static char *intr_flags[] = {
+       "AdFull", "AdSwError", "AdHwError", "DaEmpty",
+       "DaSwError", "DaHwError", "CtDone", "CmDone",
+};
+static void debug_intr_flags(unsigned int flags)
+{
+       int i;
+       printk("dt3k: intr_flags:");
+       for (i = 0; i < 8; i++) {
+               if (flags & (1 << i)) {
+                       printk(" %s", intr_flags[i]);
+               }
+       }
+       printk("\n");
+}
+#endif
+
+static void dt3k_ai_empty_fifo(struct comedi_device * dev, struct comedi_subdevice * s)
+{
+       int front;
+       int rear;
+       int count;
+       int i;
+       short data;
+
+       front = readw(devpriv->io_addr + DPR_AD_Buf_Front);
+       count = front - devpriv->ai_front;
+       if (count < 0)
+               count += AI_FIFO_DEPTH;
+
+       printk("reading %d samples\n", count);
+
+       rear = devpriv->ai_rear;
+
+       for (i = 0; i < count; i++) {
+               data = readw(devpriv->io_addr + DPR_ADC_buffer + rear);
+               comedi_buf_put(s->async, data);
+               rear++;
+               if (rear >= AI_FIFO_DEPTH)
+                       rear = 0;
+       }
+
+       devpriv->ai_rear = rear;
+       writew(rear, devpriv->io_addr + DPR_AD_Buf_Rear);
+}
+
+static int dt3k_ai_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s,
+       struct 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_TIMER;
+       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;
+       if (!cmd->stop_src || tmp != cmd->stop_src)
+               err++;
+
+       if (err)
+               return 1;
+
+       /* step 2: make sure trigger sources are unique and mutually compatible */
+
+       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_TIMER) {
+               if (cmd->scan_begin_arg < this_board->ai_speed) {
+                       cmd->scan_begin_arg = this_board->ai_speed;
+                       err++;
+               }
+               if (cmd->scan_begin_arg > 100 * 16 * 65535) {
+                       cmd->scan_begin_arg = 100 * 16 * 65535;
+                       err++;
+               }
+       } else {
+               /* not supported */
+       }
+       if (cmd->convert_src == TRIG_TIMER) {
+               if (cmd->convert_arg < this_board->ai_speed) {
+                       cmd->convert_arg = this_board->ai_speed;
+                       err++;
+               }
+               if (cmd->convert_arg > 50 * 16 * 65535) {
+                       cmd->convert_arg = 50 * 16 * 65535;
+                       err++;
+               }
+       } else {
+               /* not supported */
+       }
+
+       if (cmd->scan_end_arg != cmd->chanlist_len) {
+               cmd->scan_end_arg = cmd->chanlist_len;
+               err++;
+       }
+       if (cmd->stop_src == TRIG_COUNT) {
+               if (cmd->stop_arg > 0x00ffffff) {
+                       cmd->stop_arg = 0x00ffffff;
+                       err++;
+               }
+       } else {
+               /* TRIG_NONE */
+               if (cmd->stop_arg != 0) {
+                       cmd->stop_arg = 0;
+                       err++;
+               }
+       }
+
+       if (err)
+               return 3;
+
+       /* step 4: fix up any arguments */
+
+       if (cmd->scan_begin_src == TRIG_TIMER) {
+               tmp = cmd->scan_begin_arg;
+               dt3k_ns_to_timer(100, &cmd->scan_begin_arg,
+                       cmd->flags & TRIG_ROUND_MASK);
+               if (tmp != cmd->scan_begin_arg)
+                       err++;
+       } else {
+               /* not supported */
+       }
+       if (cmd->convert_src == TRIG_TIMER) {
+               tmp = cmd->convert_arg;
+               dt3k_ns_to_timer(50, &cmd->convert_arg,
+                       cmd->flags & TRIG_ROUND_MASK);
+               if (tmp != cmd->convert_arg)
+                       err++;
+               if (cmd->scan_begin_src == TRIG_TIMER &&
+                       cmd->scan_begin_arg <
+                       cmd->convert_arg * cmd->scan_end_arg) {
+                       cmd->scan_begin_arg =
+                               cmd->convert_arg * cmd->scan_end_arg;
+                       err++;
+               }
+       } else {
+               /* not supported */
+       }
+
+       if (err)
+               return 4;
+
+       return 0;
+}
+
+static int dt3k_ns_to_timer(unsigned int timer_base, unsigned int *nanosec,
+       unsigned int round_mode)
+{
+       int divider, base, prescale;
+
+       /* This function needs improvment */
+       /* Don't know if divider==0 works. */
+
+       for (prescale = 0; prescale < 16; prescale++) {
+               base = timer_base * (prescale + 1);
+               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;
+                       break;
+               }
+               if (divider < 65536) {
+                       *nanosec = divider * base;
+                       return (prescale << 16) | (divider);
+               }
+       }
+
+       prescale = 15;
+       base = timer_base * (1 << prescale);
+       divider = 65535;
+       *nanosec = divider * base;
+       return (prescale << 16) | (divider);
+}
+
+static int dt3k_ai_cmd(struct comedi_device * dev, struct comedi_subdevice * s)
+{
+       struct comedi_cmd *cmd = &s->async->cmd;
+       int i;
+       unsigned int chan, range, aref;
+       unsigned int divider;
+       unsigned int tscandiv;
+       int ret;
+       unsigned int mode;
+
+       printk("dt3k_ai_cmd:\n");
+       for (i = 0; i < cmd->chanlist_len; i++) {
+               chan = CR_CHAN(cmd->chanlist[i]);
+               range = CR_RANGE(cmd->chanlist[i]);
+
+               writew((range << 6) | chan,
+                       devpriv->io_addr + DPR_ADC_buffer + i);
+       }
+       aref = CR_AREF(cmd->chanlist[0]);
+
+       writew(cmd->scan_end_arg, devpriv->io_addr + DPR_Params(0));
+       printk("param[0]=0x%04x\n", cmd->scan_end_arg);
+
+       if (cmd->convert_src == TRIG_TIMER) {
+               divider = dt3k_ns_to_timer(50, &cmd->convert_arg,
+                       cmd->flags & TRIG_ROUND_MASK);
+               writew((divider >> 16), devpriv->io_addr + DPR_Params(1));
+               printk("param[1]=0x%04x\n", divider >> 16);
+               writew((divider & 0xffff), devpriv->io_addr + DPR_Params(2));
+               printk("param[2]=0x%04x\n", divider & 0xffff);
+       } else {
+               /* not supported */
+       }
+
+       if (cmd->scan_begin_src == TRIG_TIMER) {
+               tscandiv = dt3k_ns_to_timer(100, &cmd->scan_begin_arg,
+                       cmd->flags & TRIG_ROUND_MASK);
+               writew((tscandiv >> 16), devpriv->io_addr + DPR_Params(3));
+               printk("param[3]=0x%04x\n", tscandiv >> 16);
+               writew((tscandiv & 0xffff), devpriv->io_addr + DPR_Params(4));
+               printk("param[4]=0x%04x\n", tscandiv & 0xffff);
+       } else {
+               /* not supported */
+       }
+
+       mode = DT3000_AD_RETRIG_INTERNAL | 0 | 0;
+       writew(mode, devpriv->io_addr + DPR_Params(5));
+       printk("param[5]=0x%04x\n", mode);
+       writew(aref == AREF_DIFF, devpriv->io_addr + DPR_Params(6));
+       printk("param[6]=0x%04x\n", aref == AREF_DIFF);
+
+       writew(AI_FIFO_DEPTH / 2, devpriv->io_addr + DPR_Params(7));
+       printk("param[7]=0x%04x\n", AI_FIFO_DEPTH / 2);
+
+       writew(SUBS_AI, devpriv->io_addr + DPR_SubSys);
+       ret = dt3k_send_cmd(dev, CMD_CONFIG);
+
+       writew(DT3000_ADFULL | DT3000_ADSWERR | DT3000_ADHWERR,
+               devpriv->io_addr + DPR_Int_Mask);
+
+       debug_n_ints = 0;
+
+       writew(SUBS_AI, devpriv->io_addr + DPR_SubSys);
+       ret = dt3k_send_cmd(dev, CMD_START);
+
+       return 0;
+}
+
+static int dt3k_ai_cancel(struct comedi_device * dev, struct comedi_subdevice * s)
+{
+       int ret;
+
+       writew(SUBS_AI, devpriv->io_addr + DPR_SubSys);
+       ret = dt3k_send_cmd(dev, CMD_STOP);
+
+       writew(0, devpriv->io_addr + DPR_Int_Mask);
+
+       return 0;
+}
+
+static int dt3k_ai_insn(struct comedi_device * dev, struct comedi_subdevice * s,
+       struct comedi_insn * insn, unsigned int * data)
+{
+       int i;
+       unsigned int chan, gain, aref;
+
+       chan = CR_CHAN(insn->chanspec);
+       gain = CR_RANGE(insn->chanspec);
+       /* XXX docs don't explain how to select aref */
+       aref = CR_AREF(insn->chanspec);
+
+       for (i = 0; i < insn->n; i++) {
+               data[i] = dt3k_readsingle(dev, SUBS_AI, chan, gain);
+       }
+
+       return i;
+}
+
+static int dt3k_ao_insn(struct comedi_device * dev, struct comedi_subdevice * s,
+       struct comedi_insn * insn, unsigned int * data)
+{
+       int i;
+       unsigned int chan;
+
+       chan = CR_CHAN(insn->chanspec);
+       for (i = 0; i < insn->n; i++) {
+               dt3k_writesingle(dev, SUBS_AO, chan, data[i]);
+               devpriv->ao_readback[chan] = data[i];
+       }
+
+       return i;
+}
+
+static int dt3k_ao_insn_read(struct comedi_device * dev, struct comedi_subdevice * s,
+       struct comedi_insn * insn, unsigned int * data)
+{
+       int i;
+       unsigned int chan;
+
+       chan = CR_CHAN(insn->chanspec);
+       for (i = 0; i < insn->n; i++) {
+               data[i] = devpriv->ao_readback[chan];
+       }
+
+       return i;
+}
+
+static void dt3k_dio_config(struct comedi_device * dev, int bits)
+{
+       /* XXX */
+       writew(SUBS_DOUT, devpriv->io_addr + DPR_SubSys);
+
+       writew(bits, devpriv->io_addr + DPR_Params(0));
+#if 0
+       /* don't know */
+       writew(0, devpriv->io_addr + DPR_Params(1));
+       writew(0, devpriv->io_addr + DPR_Params(2));
+#endif
+
+       dt3k_send_cmd(dev, CMD_CONFIG);
+}
+
+static int dt3k_dio_insn_config(struct comedi_device * dev, struct comedi_subdevice * s,
+       struct comedi_insn * insn, unsigned int * data)
+{
+       int mask;
+
+       mask = (CR_CHAN(insn->chanspec) < 4) ? 0x0f : 0xf0;
+
+       switch (data[0]) {
+       case INSN_CONFIG_DIO_OUTPUT:
+               s->io_bits |= mask;
+               break;
+       case INSN_CONFIG_DIO_INPUT:
+               s->io_bits &= ~mask;
+               break;
+       case INSN_CONFIG_DIO_QUERY:
+               data[1] =
+                       (s->io_bits & (1 << CR_CHAN(insn->
+                                       chanspec))) ? COMEDI_OUTPUT :
+                       COMEDI_INPUT;
+               return insn->n;
+               break;
+       default:
+               return -EINVAL;
+               break;
+       }
+       mask = (s->io_bits & 0x01) | ((s->io_bits & 0x10) >> 3);
+       dt3k_dio_config(dev, mask);
+
+       return insn->n;
+}
+
+static int dt3k_dio_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s,
+       struct comedi_insn * insn, unsigned int * data)
+{
+       if (insn->n != 2)
+               return -EINVAL;
+
+       if (data[0]) {
+               s->state &= ~data[0];
+               s->state |= data[1] & data[0];
+               dt3k_writesingle(dev, SUBS_DOUT, 0, s->state);
+       }
+       data[1] = dt3k_readsingle(dev, SUBS_DIN, 0, 0);
+
+       return 2;
+}
+
+static int dt3k_mem_insn_read(struct comedi_device * dev, struct comedi_subdevice * s,
+       struct comedi_insn * insn, unsigned int * data)
+{
+       unsigned int addr = CR_CHAN(insn->chanspec);
+       int i;
+
+       for (i = 0; i < insn->n; i++) {
+               writew(SUBS_MEM, devpriv->io_addr + DPR_SubSys);
+               writew(addr, devpriv->io_addr + DPR_Params(0));
+               writew(1, devpriv->io_addr + DPR_Params(1));
+
+               dt3k_send_cmd(dev, CMD_READCODE);
+
+               data[i] = readw(devpriv->io_addr + DPR_Params(2));
+       }
+
+       return i;
+}
+
+static int dt_pci_probe(struct comedi_device * dev, int bus, int slot);
+
+static int dt3000_attach(struct comedi_device * dev, struct comedi_devconfig * it)
+{
+       struct comedi_subdevice *s;
+       int bus, slot;
+       int ret = 0;
+
+       printk("dt3000:");
+       bus = it->options[0];
+       slot = it->options[1];
+
+       if ((ret = alloc_private(dev, sizeof(struct dt3k_private))) < 0)
+               return ret;
+
+       ret = dt_pci_probe(dev, bus, slot);
+       if (ret < 0)
+               return ret;
+       if (ret == 0) {
+               printk(" no DT board found\n");
+               return -ENODEV;
+       }
+
+       dev->board_name = this_board->name;
+
+       if (comedi_request_irq(devpriv->pci_dev->irq, dt3k_interrupt,
+                       IRQF_SHARED, "dt3000", dev)) {
+               printk(" unable to allocate IRQ %u\n", devpriv->pci_dev->irq);
+               return -EINVAL;
+       }
+       dev->irq = devpriv->pci_dev->irq;
+
+       if ((ret = alloc_subdevices(dev, 4)) < 0)
+               return ret;
+
+       s = dev->subdevices;
+       dev->read_subdev = s;
+
+       /* ai subdevice */
+       s->type = COMEDI_SUBD_AI;
+       s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF | SDF_CMD_READ;
+       s->n_chan = this_board->adchan;
+       s->insn_read = dt3k_ai_insn;
+       s->maxdata = (1 << this_board->adbits) - 1;
+       s->len_chanlist = 512;
+       s->range_table = &range_dt3000_ai;      /* XXX */
+       s->do_cmd = dt3k_ai_cmd;
+       s->do_cmdtest = dt3k_ai_cmdtest;
+       s->cancel = dt3k_ai_cancel;
+
+       s++;
+       /* ao subsystem */
+       s->type = COMEDI_SUBD_AO;
+       s->subdev_flags = SDF_WRITABLE;
+       s->n_chan = 2;
+       s->insn_read = dt3k_ao_insn_read;
+       s->insn_write = dt3k_ao_insn;
+       s->maxdata = (1 << this_board->dabits) - 1;
+       s->len_chanlist = 1;
+       s->range_table = &range_bipolar10;
+
+       s++;
+       /* dio subsystem */
+       s->type = COMEDI_SUBD_DIO;
+       s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+       s->n_chan = 8;
+       s->insn_config = dt3k_dio_insn_config;
+       s->insn_bits = dt3k_dio_insn_bits;
+       s->maxdata = 1;
+       s->len_chanlist = 8;
+       s->range_table = &range_digital;
+
+       s++;
+       /* mem subsystem */
+       s->type = COMEDI_SUBD_MEMORY;
+       s->subdev_flags = SDF_READABLE;
+       s->n_chan = 0x1000;
+       s->insn_read = dt3k_mem_insn_read;
+       s->maxdata = 0xff;
+       s->len_chanlist = 1;
+       s->range_table = &range_unknown;
+
+#if 0
+       s++;
+       /* proc subsystem */
+       s->type = COMEDI_SUBD_PROC;
+#endif
+
+       return 0;
+}
+
+static int dt3000_detach(struct comedi_device * dev)
+{
+       if (dev->irq)
+               comedi_free_irq(dev->irq, dev);
+
+       if (devpriv) {
+               if (devpriv->pci_dev) {
+                       if (devpriv->phys_addr) {
+                               comedi_pci_disable(devpriv->pci_dev);
+                       }
+                       pci_dev_put(devpriv->pci_dev);
+               }
+               if (devpriv->io_addr)
+                       iounmap(devpriv->io_addr);
+       }
+       /* XXX */
+
+       return 0;
+}
+
+static struct pci_dev *dt_pci_find_device(struct pci_dev *from, int *board);
+static int setup_pci(struct comedi_device * dev);
+
+static int dt_pci_probe(struct comedi_device * dev, int bus, int slot)
+{
+       int board;
+       int ret;
+       struct pci_dev *pcidev;
+
+       pcidev = NULL;
+       while ((pcidev = dt_pci_find_device(pcidev, &board)) != NULL) {
+               if ((bus == 0 && slot == 0) ||
+                       (pcidev->bus->number == bus &&
+                        PCI_SLOT(pcidev->devfn) == slot)) {
+                       break;
+               }
+       }
+       devpriv->pci_dev = pcidev;
+
+       if (board >= 0)
+               dev->board_ptr = dt3k_boardtypes + board;
+
+       if (!devpriv->pci_dev)
+               return 0;
+
+       if ((ret = setup_pci(dev)) < 0)
+               return ret;
+
+       return 1;
+}
+
+static int setup_pci(struct comedi_device * dev)
+{
+       resource_size_t addr;
+       int ret;
+
+       ret = comedi_pci_enable(devpriv->pci_dev, "dt3000");
+       if (ret < 0)
+               return ret;
+
+       addr = pci_resource_start(devpriv->pci_dev, 0);
+       devpriv->phys_addr = addr;
+       devpriv->io_addr = ioremap(devpriv->phys_addr, DT3000_SIZE);
+       if (!devpriv->io_addr)
+               return -ENOMEM;
+#if DEBUG
+       printk("0x%08llx mapped to %p, ",
+               (unsigned long long)devpriv->phys_addr, devpriv->io_addr);
+#endif
+
+       return 0;
+}
+
+static struct pci_dev *dt_pci_find_device(struct pci_dev *from, int *board)
+{
+       int i;
+
+       for (from = pci_get_device(PCI_VENDOR_ID_DT, PCI_ANY_ID, from);
+               from != NULL;
+               from = pci_get_device(PCI_VENDOR_ID_DT, PCI_ANY_ID, from)) {
+               for (i = 0; i < n_dt3k_boards; i++) {
+                       if (from->device == dt3k_boardtypes[i].device_id) {
+                               *board = i;
+                               return from;
+                       }
+               }
+               printk("unknown Data Translation PCI device found with device_id=0x%04x\n", from->device);
+       }
+       *board = -1;
+       return from;
+}