new driver
authorDavid Schleef <ds@schleef.org>
Thu, 21 Nov 2002 04:30:09 +0000 (04:30 +0000)
committerDavid Schleef <ds@schleef.org>
Thu, 21 Nov 2002 04:30:09 +0000 (04:30 +0000)
comedi/drivers/cb_pcimdas.c [new file with mode: 0644]

diff --git a/comedi/drivers/cb_pcimdas.c b/comedi/drivers/cb_pcimdas.c
new file mode 100644 (file)
index 0000000..c500e02
--- /dev/null
@@ -0,0 +1,783 @@
+/*
+    comedi/drivers/cb_pcimdas.c
+    Comedi driver for Computer Boards PCIM-DAS1602/16
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 2000 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: cb_pcimdas.o
+Description: Computer Boards PCI Migration series boards
+Devices: (Computer Boards) PCIM-DAS1602/16 [cb_pcimdas]
+Author: Richard Bytheway
+Updated: Wed, 13 Nov 2002 12:34:56 +0000
+Status: experimental
+
+Written to support the PCIM-DAS1602/16 on a 2.4 series kernel.
+
+Configuration Options:
+    [0] - PCI bus number
+    [1] - PCI slot number
+
+Developed from cb_pcidas and skel by Richard Bytheway (mocelet@sucs.org). 
+Only supports DIO, AO and simple AI in it's present form.
+No interrupts, multi channel or FIFO AI, although the card looks like it could support this.
+See http://www.measurementcomputing.com/PDFManuals/pcim-das1602_16.pdf for more details.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/timex.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+#include <linux/comedidev.h>
+#include "plx9052.h"
+
+//#define CBPCIMDAS_DEBUG
+#undefine CBPCIMDAS_DEBUG
+
+/* Registers for the PCIM-DAS1602/16 */
+
+// sizes of io regions (bytes)
+#define BADR0_SIZE 2  //??
+#define BADR1_SIZE 4
+#define BADR2_SIZE 6
+#define BADR3_SIZE 16
+#define BADR4_SIZE 4
+
+//DAC Offsets
+#define ADC_TRIG 0
+#define DAC0_OFFSET 2
+#define DAC1_OFFSET 4
+
+//AI and Counter Constants
+#define MUX_LIMITS 0
+#define MAIN_CONN_DIO 1
+#define ADC_STAT 2
+#define ADC_CONV_STAT 3
+#define ADC_INT 4
+#define ADC_PACER 5
+#define BURST_MODE 6
+#define PROG_GAIN 7
+#define CLK8254_1_DATA 8
+#define CLK8254_2_DATA 9
+#define CLK8254_3_DATA 10
+#define CLK8254_CONTROL 11
+#define USER_COUNTER 12
+#define RESID_COUNT_H 13
+#define RESID_COUNT_L 14
+
+//DIO Offsets
+#define PORT_A 0
+#define PORT_B 1
+#define PORT_C 2
+#define DIO_CONFIG 3
+
+//DIO Constants
+#define ALL_OUTPUT 128
+#define PORT_A_IN 16
+#define PORT_B_IN 2
+#define PORT_CH_IN 8
+#define PORT_CL_IN 1
+#define PORT_A_MASK 0x0000ff
+#define PORT_B_MASK 0x00ff00
+#define PORT_C_MASK 0xff0000
+#define PORT_CL_MASK 0x0f0000
+#define PORT_CH_MASK 0xf00000
+
+
+/* Board description */
+typedef struct cb_pcimdas_board_struct
+{
+       char *name;
+       unsigned short device_id;
+       int ai_se_chans;        // Inputs in single-ended mode
+       int ai_diff_chans;      // Inputs in differential mode
+       int ai_bits;    // analog input resolution
+       int ai_speed;   // fastest conversion period in ns
+       int ao_nchan;   // number of analog out channels
+       int ao_bits;    // analogue output resolution
+       int has_ao_fifo;        // analog output has fifo
+       int ao_scan_speed;      // analog output speed for 1602 series (for a scan, not conversion)
+       int fifo_size;  // number of samples fifo can hold
+       int dio_bits;  // number of dio bits
+       int has_dio;    // has DIO
+       comedi_lrange *ranges;
+} cb_pcimdas_board;
+
+static cb_pcimdas_board cb_pcimdas_boards[] =
+{
+       {
+               name:           "PCIM-DAS1602/16",
+               device_id:      0x56,
+               ai_se_chans:    16,
+               ai_diff_chans:  8,
+               ai_bits:        16,
+               ai_speed:       10000, //??
+               ao_nchan:       2,     
+               ao_bits:        12,
+               has_ao_fifo:    0,     //??
+               ao_scan_speed:  10000, //??
+               fifo_size:      1024,   
+               dio_bits:       24,
+               has_dio:        1,
+//             ranges:         &cb_pcimdas_ranges,
+       },
+};
+
+/* This is used by modprobe to translate PCI IDs to drivers.  Should
+ * only be used for PCI and ISA-PnP devices */
+static struct pci_device_id cb_pcimdas_pci_table[] __devinitdata = {
+       { PCI_VENDOR_ID_COMPUTERBOARDS, 0x0056, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+       { 0 }
+};
+MODULE_DEVICE_TABLE(pci, cb_pcimdas_pci_table);
+
+#define N_BOARDS 1 // Max number of boards supported
+
+/*
+ * Useful for shorthand access to the particular board structure
+ */
+#define thisboard ((cb_pcimdas_board *)dev->board_ptr)
+
+/* this structure is for data unique to this hardware driver.  If
+   several hardware drivers keep similar information in this structure,
+   feel free to suggest moving the variable to the comedi_device struct.  */
+typedef struct{
+       int data;
+
+       // would be useful for a PCI device 
+       struct pci_dev *pci_dev;
+
+       //base addresses
+       unsigned int BADR0;
+       unsigned int BADR1;
+       unsigned int BADR2;
+       unsigned int BADR3;
+       unsigned int BADR4;
+
+       /* Used for AO readback */
+       lsampl_t ao_readback[2];
+
+       // Used for DIO
+       unsigned short int port_a;   // copy of BADR4+0
+       unsigned short int port_b;   // copy of BADR4+1
+       unsigned short int port_c;   // copy of BADR4+2
+       unsigned short int dio_mode; // copy of BADR4+3
+       
+}cb_pcimdas_private;
+
+/*
+ * most drivers define the following macro to make it easy to
+ * access the private structure.
+ */
+#define devpriv ((cb_pcimdas_private *)dev->private)
+
+/*
+ * The comedi_driver structure tells the Comedi core module
+ * which functions to call to configure/deconfigure (attach/detach)
+ * the board, and also about the kernel module that contains
+ * the device code.
+ */
+static int cb_pcimdas_attach(comedi_device *dev,comedi_devconfig *it);
+static int cb_pcimdas_detach(comedi_device *dev);
+static comedi_driver driver_cb_pcimdas={
+       driver_name:    "cb_pcimdas",
+       module:         THIS_MODULE,
+       attach:         cb_pcimdas_attach,
+       detach:         cb_pcimdas_detach,
+};
+
+static int cb_pcimdas_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
+static int cb_pcimdas_ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
+static int cb_pcimdas_ao_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
+static int cb_pcimdas_dio_insn_bits(comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn,lsampl_t *data);
+static int cb_pcimdas_dio_insn_config(comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn,lsampl_t *data);
+static int cb_pcimdas_ai_cmdtest(comedi_device *dev,comedi_subdevice *s,
+       comedi_cmd *cmd);
+static int cb_pcimdas_ns_to_timer(unsigned int *ns,int round);
+
+/*
+ * Attach is called by the Comedi core to configure the driver
+ * for a particular board.  If you specified a board_name array
+ * in the driver structure, dev->board_ptr contains that
+ * address.
+ */
+static int cb_pcimdas_attach(comedi_device *dev,comedi_devconfig *it)
+{
+       comedi_subdevice *s;
+       struct pci_dev* pcidev;
+       int index;
+       unsigned long BADR0, BADR1, BADR2, BADR3, BADR4;
+       int err;
+       int i;
+
+       printk("comedi%d: cb_pcimdas: ",dev->minor);    
+
+/*
+ * Allocate the private structure area.
+ */
+       if(alloc_private(dev,sizeof(cb_pcimdas_private))<0)
+               return -ENOMEM;
+
+/*
+ * Probe the device to determine what device in the series it is.
+ */
+       printk("\n");
+
+       pci_for_each_dev(pcidev)
+       {
+               // is it not a computer boards card?
+               if(pcidev->vendor != PCI_VENDOR_ID_COMPUTERBOARDS)
+                       continue;
+               // loop through cards supported by this driver
+               for(index = 0; index < N_BOARDS; index++)
+               {
+                       if(cb_pcimdas_boards[index].device_id != pcidev->device)
+                               continue;
+                       // was a particular bus/slot requested?
+                       if(it->options[0] || it->options[1])
+                       {
+                               // are we on the wrong bus/slot?
+                               if(pcidev->bus->number != it->options[0] ||
+                                  PCI_SLOT(pcidev->devfn) != it->options[1])
+                               {
+                                       continue;
+                               }
+                       }
+                       devpriv->pci_dev = pcidev;
+                       dev->board_ptr = cb_pcimdas_boards + index;
+                       goto found;
+               }
+       }
+
+       printk("No supported ComputerBoards/MeasurementComputing card found on "
+               "requested position\n");
+       return -EIO;
+
+found:
+
+       printk("Found %s on bus %i, slot %i\n", cb_pcimdas_boards[index].name,
+               devpriv->pci_dev->bus->number, PCI_SLOT(devpriv->pci_dev->devfn));
+
+       // Warn about non-tested features
+       switch(thisboard->device_id)
+       {
+               case 0x56:
+                       break;
+               default:
+                       printk( "THIS CARD IS UNSUPPORTED.\n"
+                               "PLEASE REPORT USAGE TO <mocelet@sucs.org>\n");
+       };
+
+       if(pci_enable_device(devpriv->pci_dev))
+               return -EIO;
+
+// Since I don't know how large BADR0 is, lets not request it for now.
+// It doesn't appear to be needed for operation of the Board.
+//
+//     BADR0 = pci_resource_start(devpriv->pci_dev, 0); 
+       BADR1 = pci_resource_start(devpriv->pci_dev, 1); 
+       BADR2 = pci_resource_start(devpriv->pci_dev, 2); 
+       BADR3 = pci_resource_start(devpriv->pci_dev, 3); 
+       BADR4 = pci_resource_start(devpriv->pci_dev, 4); 
+
+       // reserve io ports
+       err = 0;
+//     if(check_mem_region(BADR0, BADR0_SIZE) < 0)
+//             err+=1;
+       if(check_region(BADR1, BADR1_SIZE) < 0)
+               err+=2;
+       if(check_region(BADR2, BADR2_SIZE) < 0)
+               err+=4;
+       if(check_region(BADR3, BADR3_SIZE) < 0)
+               err+=8;
+       if(check_region(BADR4, BADR4_SIZE) < 0)
+               err+=16;
+       if(err)
+       {
+               printk(" I/O port conflict %d\n",err);
+               return -EIO;
+       }
+//     request_mem_region(BADR0, BADR0_SIZE, "cb_pcimdas");
+//     devpriv->BADR0 = BADR0;
+       request_region(BADR1, BADR1_SIZE, "cb_pcimdas");
+       devpriv->BADR1 = BADR1;
+       request_region(BADR2, BADR2_SIZE, "cb_pcimdas");
+       devpriv->BADR2 = BADR2;
+       request_region(BADR3, BADR3_SIZE, "cb_pcimdas");
+       devpriv->BADR3 = BADR3;
+       request_region(BADR4, BADR4_SIZE, "cb_pcimdas");
+       devpriv->BADR4 = BADR4;
+
+#ifdef CBPCIMDAS_DEBUG
+       printk("devpriv->BADR0 = %d\n",devpriv->BADR0);
+       printk("devpriv->BADR1 = %d\n",devpriv->BADR1);
+       printk("devpriv->BADR2 = %d\n",devpriv->BADR2);
+       printk("devpriv->BADR3 = %d\n",devpriv->BADR3);
+       printk("devpriv->BADR4 = %d\n",devpriv->BADR4);
+#endif
+
+
+// Dont support IRQ yet
+//     // get irq
+//     if(comedi_request_irq(devpriv->pci_dev->irq, cb_pcimdas_interrupt, SA_SHIRQ, "cb_pcimdas", dev ))
+//     {
+//             printk(" unable to allocate irq %d\n", devpriv->pci_dev->irq);
+//             return -EINVAL;
+//     }
+//     dev->irq = devpriv->pci_dev->irq;
+
+       //Initialize dev->board_name
+       dev->board_name = thisboard->name;
+
+
+/*
+ * Allocate the subdevice structures.  alloc_subdevice() is a
+ * convenient macro defined in comedidev.h.  It relies on
+ * n_subdevices being set correctly.
+ */
+       dev->n_subdevices=3;
+       if(alloc_subdevices(dev)<0) 
+               return -ENOMEM;
+
+       s=dev->subdevices+0;
+       //dev->read_subdev=s;
+       // analog input subdevice 
+       s->type=COMEDI_SUBD_AI;
+       s->subdev_flags=SDF_READABLE;
+       s->n_chan=thisboard->ai_se_chans;
+       s->maxdata=(1<<thisboard->ai_bits)-1;
+       s->range_table=&range_unknown;
+       s->len_chanlist=1;  // This is the maximum chanlist length that
+                            // the board can handle 
+       s->insn_read = cb_pcimdas_ai_rinsn;
+
+       s=dev->subdevices+1;
+       // analog output subdevice
+       s->type=COMEDI_SUBD_AO;
+       s->subdev_flags=SDF_WRITABLE;
+       s->n_chan=thisboard->ao_nchan;
+       s->maxdata=1<<thisboard->ao_bits;
+       s->range_table=&range_unknown; //ranges are hardware settable, but not software readable.
+       s->insn_write = &cb_pcimdas_ao_winsn;
+       s->insn_read = &cb_pcimdas_ao_rinsn;
+
+       s=dev->subdevices+2;
+       /* digital i/o subdevice */
+       if(thisboard->has_dio){
+               s->type=COMEDI_SUBD_DIO;
+               s->subdev_flags=SDF_READABLE|SDF_WRITABLE;
+               s->n_chan=thisboard->dio_bits;
+               s->maxdata=1;
+               s->range_table=&range_digital;
+               s->insn_bits = cb_pcimdas_dio_insn_bits;
+               s->insn_config = cb_pcimdas_dio_insn_config;
+       }else{
+               s->type = COMEDI_SUBD_UNUSED;
+       }
+       
+       printk("attached\n");
+
+       return 1;
+}
+
+
+/*
+ * _detach is called to deconfigure a device.  It should deallocate
+ * resources.  
+ * This function is also called when _attach() fails, so it should be
+ * careful not to release resources that were not necessarily
+ * allocated by _attach().  dev->private and dev->subdevices are
+ * deallocated automatically by the core.
+ */
+static int cb_pcimdas_detach(comedi_device *dev)
+{
+#ifdef CBPCIMDAS_DEBUG
+       printk("devpriv->BADR0 = %d\n",devpriv->BADR0);
+       printk("devpriv->BADR1 = %d\n",devpriv->BADR1);
+       printk("devpriv->BADR2 = %d\n",devpriv->BADR2);
+       printk("devpriv->BADR3 = %d\n",devpriv->BADR3);
+       printk("devpriv->BADR4 = %d\n",devpriv->BADR4);
+#endif
+       printk("comedi%d: cb_pcimdas: remove\n",dev->minor);
+               if(devpriv->BADR0)
+                       release_mem_region(devpriv->BADR0, BADR0_SIZE);
+               if(devpriv->BADR1)
+                       release_region(devpriv->BADR1, BADR1_SIZE);
+               if(devpriv->BADR2)
+                       release_region(devpriv->BADR2, BADR2_SIZE);
+               if(devpriv->BADR3)
+                       release_region(devpriv->BADR3, BADR3_SIZE);
+               if(devpriv->BADR4)
+                       release_region(devpriv->BADR4, BADR4_SIZE);
+       
+       if(dev->irq)
+               comedi_free_irq(dev->irq, dev);
+
+       return 0;
+}
+
+/*
+ * "instructions" read/write data in "one-shot" or "software-triggered"
+ * mode.
+ */
+static int cb_pcimdas_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+       int n,i;
+       unsigned int d;
+       unsigned int busy;
+       int chan = CR_CHAN(insn->chanspec);
+       unsigned short chanlims;
+       int maxchans;
+
+       // only support sw initiated reads from a single channel
+
+       //check channel number
+       if ((inb(devpriv->BADR3+2) & 0x20)==0) //differential mode
+               maxchans=thisboard->ai_diff_chans;
+       else
+               maxchans=thisboard->ai_se_chans;
+
+       if (chan>(maxchans-1)) 
+               return -ETIMEDOUT;  //*** Wrong error code. Fixme.
+
+       //configure for sw initiated read
+       d=inb(devpriv->BADR3+5);
+       if ((d & 0x03)>0) {                     //only reset if needed.
+               d=d & 0xfd;
+               outb(d,devpriv->BADR3+5);
+       }
+       outb(0x01,devpriv->BADR3+6); //set bursting off, conversions on
+       outb(0x00,devpriv->BADR3+7); //set range to 10V. UP/BP is controlled by a switch on the board
+
+       // write channel limits to multiplexer, set Low (bits 0-3) and High (bits 4-7) channels to chan.
+       chanlims=chan | (chan<<4);
+       outb(chanlims,devpriv->BADR3+0);
+
+       /* wait for mux to settle */
+       udelay(10); //docs say wait 10µs
+
+       /* convert n samples */
+       for(n=0;n<insn->n;n++){
+               /* trigger conversion */
+               outw(0,devpriv->BADR2+0);
+
+#define TIMEOUT 1000   //typically takes 5 loops on a lightly loaded Pentium 100MHz,
+                       //this is likely to be 100 loops on a 2GHz machine, so set 1000 as the limit.
+
+               /* wait for conversion to end */
+               for(i=0;i<TIMEOUT;i++){
+                       busy = inb(devpriv->BADR3+2)&0x80;
+                       if(!busy)break;
+               }
+               if(i==TIMEOUT){
+                       printk("timeout\n");
+                       return -ETIMEDOUT;
+               }
+               /* read data */
+               d = inw(devpriv->BADR2+0);
+
+               /* mangle the data as necessary */
+               //d ^= 1<<(thisboard->ai_bits-1); // 16 bit data from ADC, so no mangle needed.
+
+               data[n] = d;
+       }
+
+       /* return the number of samples read/written */
+       return n;
+}
+
+static int cb_pcimdas_ai_cmdtest(comedi_device *dev,comedi_subdevice *s,
+       comedi_cmd *cmd)
+{
+       int err=0;
+       int tmp;
+
+       /* cmdtest tests a particular command to see if it is valid.
+        * Using the cmdtest ioctl, a user can create a valid cmd
+        * and then have it executes by the cmd ioctl.
+        *
+        * cmdtest returns 1,2,3,4 or 0, depending on which tests
+        * the command passes. */
+
+       /* 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|TRIG_EXT;
+       if(!cmd->scan_begin_src || tmp!=cmd->scan_begin_src)err++;
+
+       tmp=cmd->convert_src;
+       cmd->convert_src &= TRIG_TIMER|TRIG_EXT;
+       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_TIMER &&
+          cmd->scan_begin_src!=TRIG_EXT)err++;
+       if(cmd->convert_src!=TRIG_TIMER &&
+          cmd->convert_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++;
+       }
+
+#define MAX_SPEED      10000           /* in nanoseconds */
+#define MIN_SPEED      1000000000      /* in nanoseconds */
+
+       if(cmd->scan_begin_src==TRIG_TIMER){
+               if(cmd->scan_begin_arg<MAX_SPEED){
+                       cmd->scan_begin_arg=MAX_SPEED;
+                       err++;
+               }
+               if(cmd->scan_begin_arg>MIN_SPEED){
+                       cmd->scan_begin_arg=MIN_SPEED;
+                       err++;
+               }
+       }else{
+               /* external trigger */
+               /* should be level/edge, hi/lo specification here */
+               /* should specify multiple external triggers */
+               if(cmd->scan_begin_arg>9){
+                       cmd->scan_begin_arg=9;
+                       err++;
+               }
+       }
+       if(cmd->convert_src==TRIG_TIMER){
+               if(cmd->convert_arg<MAX_SPEED){
+                       cmd->convert_arg=MAX_SPEED;
+                       err++;
+               }
+               if(cmd->convert_arg>MIN_SPEED){
+                       cmd->convert_arg=MIN_SPEED;
+                       err++;
+               }
+       }else{
+               /* external trigger */
+               /* see above */
+               if(cmd->convert_arg>9){
+                       cmd->convert_arg=9;
+                       err++;
+               }
+       }
+
+       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;
+               cb_pcimdas_ns_to_timer(&cmd->scan_begin_arg,cmd->flags&TRIG_ROUND_MASK);
+               if(tmp!=cmd->scan_begin_arg)err++;
+       }
+       if(cmd->convert_src==TRIG_TIMER){
+               tmp=cmd->convert_arg;
+               cb_pcimdas_ns_to_timer(&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++;
+               }
+       }
+
+       if(err)return 4;
+
+       return 0;
+}
+
+
+static int cb_pcimdas_ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+       int i;
+       int chan = CR_CHAN(insn->chanspec);
+
+       /* Writing a list of values to an AO channel is probably not
+        * very useful, but that's how the interface is defined. */
+       for(i=0;i<insn->n;i++){
+               switch ( chan ) {
+               case 0:  
+                       outw(data[i] & 0x0FFF,devpriv->BADR2+DAC0_OFFSET);
+                       break;
+               case 1:
+                       outw(data[i] & 0x0FFF,devpriv->BADR2+DAC1_OFFSET);
+                       break;
+               default:
+                       return -1;
+               }
+               devpriv->ao_readback[chan] = data[i];
+       }
+
+       /* return the number of samples read/written */
+       return i;
+}
+
+/* AO subdevices should have a read insn as well as a write insn.
+ * Usually this means copying a value stored in devpriv. */
+static int cb_pcimdas_ao_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+       int i;
+       int chan = CR_CHAN(insn->chanspec);
+
+       for(i=0;i<insn->n;i++)
+               data[i] = devpriv->ao_readback[chan];
+
+       return i;
+}
+
+/* DIO devices are slightly special.  Although it is possible to
+ * implement the insn_read/insn_write interface, it is much more
+ * useful to applications if you implement the insn_bits interface.
+ * This allows packed reading/writing of the DIO channels.  The
+ * comedi core can convert between insn_bits and insn_read/write */
+static int cb_pcimdas_dio_insn_bits(comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn,lsampl_t *data)
+{
+// Much of this based on the 8255 driver.
+
+       if(data[0]){
+               s->state &= ~data[0];
+               s->state |= (data[0]&data[1]);
+               
+               if(data[0]&PORT_A_MASK) {
+                       devpriv->port_a=s->state&0xff;
+                       outb(devpriv->port_a,devpriv->BADR4+PORT_A);
+               }
+               if(data[0]&PORT_B_MASK) {
+                       devpriv->port_b=(s->state>>8)&0xff;
+                       outb(devpriv->port_b,devpriv->BADR4+PORT_B);
+               }
+               if(data[0]&PORT_C_MASK) {
+                       devpriv->port_c=(s->state>>16)&0xff;
+                       outb(devpriv->port_c,devpriv->BADR4+PORT_C);
+               }
+       }
+
+       data[1]=inb(devpriv->BADR4+PORT_A);
+       data[1]|=(inb(devpriv->BADR4+PORT_B)<<8);
+       data[1]|=(inb(devpriv->BADR4+PORT_C)<<16);
+
+       return 2; //should this be 2?
+}
+
+static int cb_pcimdas_dio_insn_config(comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn,lsampl_t *data)
+{
+// DIO config on this card is per port (or half port for port C), not per line.
+
+       unsigned int mask;
+       unsigned int bits;
+       int config;
+
+       mask=1<<CR_CHAN(insn->chanspec);
+       if(mask&PORT_A_MASK){
+               bits=PORT_A_MASK;
+       }else if(mask&PORT_B_MASK){
+               bits=PORT_B_MASK;
+       }else if(mask&PORT_CL_MASK){
+               bits=PORT_CL_MASK;
+       }else{
+               bits=PORT_CH_MASK;
+       }
+
+       switch(data[0]){
+       case COMEDI_INPUT:
+               s->io_bits&=~bits;
+               break;
+       case COMEDI_OUTPUT:
+               s->io_bits|=bits;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       config=ALL_OUTPUT;
+       /* 0 in io_bits indicates output, 1 in config indicates input */
+       if(!(s->io_bits&PORT_A_MASK))
+               config|=PORT_A_IN;
+       if(!(s->io_bits&PORT_B_MASK))
+               config|=PORT_B_IN;
+       if(!(s->io_bits&PORT_CL_MASK))
+               config|=PORT_CL_IN;
+       if(!(s->io_bits&PORT_CH_MASK))
+               config|=PORT_CH_IN;
+
+       outb(config,devpriv->BADR4+DIO_CONFIG);
+       devpriv->dio_mode=config;
+
+       return 1;
+}
+
+/*
+ * A convenient macro that defines init_module() and cleanup_module(),
+ * as necessary.
+ */
+COMEDI_INITCLEANUP(driver_cb_pcimdas);
+
+