added support for stop_src TRIG_COUNT that I forgot, fixed end-of-line problems my...
authorFrank Mori Hess <fmhess@speakeasy.net>
Fri, 14 Sep 2001 18:55:13 +0000 (18:55 +0000)
committerFrank Mori Hess <fmhess@speakeasy.net>
Fri, 14 Sep 2001 18:55:13 +0000 (18:55 +0000)
comedi/drivers/cb_pcidas64.c

index 28af037993f622c99ee9a866d90ee052cf0a5f03..ae75bd9e8b745a3f7040ca1e20efde675c7ce3f5 100644 (file)
-/*\r
-    cb_pcidas64.c\r
-    This is a driver for the ComputerBoards/MeasurementComputing PCI-DAS\r
-    64xxx cards.\r
-\r
-    Author:  Frank Mori Hess <fmhess@uiuc.edu>\r
-\r
-    Options:\r
-    [0] - PCI bus number\r
-    [1] - PCI slot number\r
-\r
-    Copyright (C) 2001 Frank Mori Hess <fmhess@uiuc.edu>\r
-\r
-    COMEDI - Linux Control and Measurement Device Interface\r
-    Copyright (C) 1997-8 David A. Schleef <ds@stm.lbl.gov>\r
-\r
-    This program is free software; you can redistribute it and/or modify\r
-    it under the terms of the GNU General Public License as published by\r
-    the Free Software Foundation; either version 2 of the License, or\r
-    (at your option) any later version.\r
-\r
-    This program is distributed in the hope that it will be useful,\r
-    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
-    GNU General Public License for more details.\r
-\r
-    You should have received a copy of the GNU General Public License\r
-    along with this program; if not, write to the Free Software\r
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\r
-\r
-************************************************************************/\r
-\r
-/*\r
-\r
-Driver: cb_pcidas64.o\r
-Description: Driver for the ComputerBoards/MeasurementComputing\r
-   PCI-DAS64xxx series with the PLX 9080 PCI controller.\r
-Author: Frank Mori Hess <fmhess@uiuc.edu>\r
-Status: Experimental\r
-Updated: 2001-8-27\r
-Configuration options:\r
-   [0] - PCI bus of device (optional)\r
-   [1] - PCI slot of device (optional)\r
-\r
-Basic insn support should work, but untested as far as I know.\r
-Has command support for analog input, which may also work.  Still\r
-needs dma support to be added to support very fast aquisition rates.\r
-This driver is in need of stout-hearted testers who aren't afraid to\r
-crash their computers in the name of progress.\r
-Feel free to send and success/failure reports to author.\r
-\r
-*/\r
-\r
-/*\r
-\r
-TODO:\r
-       command support for ao\r
-       pci-dma support\r
-       calibration subdevice\r
-       user counter subdevice\r
-       there are a number of boards this driver will support when they are\r
-               fully released, but does not since yet since the pci device id numbers\r
-               are not yet available.\r
-       need to take care to prevent ai and ao from affecting each others register bits\r
-       support prescaled 100khz clock for slow pacing\r
-*/\r
-\r
-#include <linux/kernel.h>\r
-#include <linux/module.h>\r
-#include <linux/sched.h>\r
-#include <linux/mm.h>\r
-#include <linux/malloc.h>\r
-#include <linux/errno.h>\r
-#include <linux/ioport.h>\r
-#include <linux/delay.h>\r
-#include <linux/interrupt.h>\r
-#include <linux/timex.h>\r
-#include <linux/timer.h>\r
-#include <linux/pci.h>\r
-#include <linux/init.h>\r
-#include <asm/io.h>\r
-#include <linux/comedidev.h>\r
-#include "8253.h"\r
-#include "8255.h"\r
-#include "plx9080.h"\r
-\r
-#define PCIDAS64_DEBUG // enable debugging code\r
-//#undef PCIDAS64_DEBUG        // disable debugging code\r
-\r
-// PCI vendor number of ComputerBoards/MeasurementComputing\r
-#define PCI_VENDOR_ID_CB       0x1307\r
-#define TIMER_BASE 25  // 40MHz master clock\r
-#define PRESCALED_TIMER_BASE   10000   // 100kHz 'prescaled' clock for slow aquisition, maybe I'll support this someday\r
-#define QUARTER_AI_FIFO_SIZE 2048      // 1/4 analog input fifo size\r
-\r
-/* PCI-DAS64xxx base addresses */\r
-\r
-// indices of base address regions\r
-#define PLX9080_BADRINDEX 0\r
-#define MAIN_BADRINDEX 2\r
-#define DIO_COUNTER_BADRINDEX 3\r
-\r
-#define PLX9080_IOSIZE 0xec\r
-#define MAIN_IOSIZE 0x302\r
-#define DIO_COUNTER_IOSIZE 0x29\r
-\r
-// devpriv->main_iobase registers\r
-// write-only\r
-#define INTR_ENABLE_REG        0x0     // interrupt enable register\r
-#define    EN_ADC_OVERRUN_BIT  0x8000  // enable adc overrun status bit\r
-#define    EN_DAC_UNDERRUN_BIT 0x4000  // enable dac underrun status bit\r
-#define    EN_ADC_DONE_INTR_BIT        0x8     // enable adc aquisition done interrupt\r
-#define    EN_ADC_INTR_SRC_BIT 0x4     // enable adc interrupt source\r
-#define    ADC_INTR_SRC_MASK   0x3     // bits that set adc interrupt source\r
-#define    ADC_INTR_QFULL_BITS 0x0     // interrupt fifo quater full\r
-#define    ADC_INTR_EOC_BITS   0x1     // interrupt end of conversion\r
-#define    ADC_INTR_EOSCAN_BITS        0x2     // interrupt end of scan\r
-#define    ADC_INTR_EOSEQ_BITS 0x3     // interrupt end of sequence (probably wont use this it's pretty fancy)\r
-#define HW_CONFIG_REG  0x2     // hardware config register\r
-#define    HW_CONFIG_DUMMY_BITS        0x2400  // bits that don't do anything yet but are given default values\r
-#define    HW_CONFIG_DUMMY_BITS_6402   0x0400  // dummy bits in 6402 manual are slightly different, probably doesn't matter\r
-#define    EXT_QUEUE   0x200   // use external channel/gain queue (more versatile than internal queue)\r
-#define FIFO_SIZE_REG  0x4     // allows adjustment of fifo sizes, we will always use maximum\r
-#define    FIFO_SIZE_DUMMY_BITS        0xf038  // bits that don't do anything yet but are given default values\r
-#define    ADC_FIFO_SIZE_MASK  0x7     // bits that set adc fifo size\r
-#define    ADC_FIFO_8K_BITS    0x0     // 8 kilosample adc fifo\r
-#define    DAC_FIFO_SIZE_MASK  0xf00   // bits that set dac fifo size\r
-#define    DAC_FIFO_16K_BITS 0x0\r
-#define ADC_CONTROL0_REG       0x10    // adc control register 0\r
-#define    TRIG1_FALLING_BIT   0x20    // trig 1 uses falling edge\r
-#define    ADC_EXT_CONV_FALLING_BIT    0x800   // external pacing uses falling edge\r
-#define    ADC_ENABLE_BIT      0x8000  // master adc enable\r
-#define ADC_CONTROL1_REG       0x12    // adc control register 1\r
-#define    ADC_CONTROL1_DUMMY_BITS     0x1     // dummy bits for adc control register 1\r
-#define    SW_NOGATE_BIT       0x40    // disables software gate of adc\r
-#define    ADC_MODE_BITS(x)    (((x) & 0xf) << 12)\r
-#define ADC_SAMPLE_INTERVAL_LOWER_REG  0x16    // lower 16 bits of sample interval counter\r
-#define ADC_SAMPLE_INTERVAL_UPPER_REG  0x18    // upper 8 bits of sample interval counter\r
-#define ADC_DELAY_INTERVAL_LOWER_REG   0x1a    // lower 16 bits of delay interval counter\r
-#define ADC_DELAY_INTERVAL_UPPER_REG   0x1c    // upper 8 bits of delay interval counter\r
-#define ADC_COUNT_LOWER_REG    0x1e    // lower 16 bits of hardware conversion/scan counter\r
-#define ADC_COUNT_UPPER_REG    0x20    // upper 8 bits of hardware conversion/scan counter\r
-#define ADC_START_REG  0x22    // software trigger to start aquisition\r
-#define ADC_CONVERT_REG        0x24    // initiates single conversion\r
-#define ADC_QUEUE_CLEAR_REG    0x26    // clears adc queue\r
-#define ADC_QUEUE_LOAD_REG     0x28    // loads adc queue\r
-#define    CHAN_BITS(x)        ((x) & 0x3f)\r
-#define    GAIN_BITS(x)        (((x) & 0x3) << 8)      // translates range index to gain bits\r
-#define    UNIP_BIT(x) (((x) & 0x4) << 11)     // translates range index to unipolar/bipolar bit\r
-#define    SE_BIT      0x1000  // single-ended/ differential bit\r
-#define    QUEUE_EOSEQ_BIT     0x4000  // queue end of sequence\r
-#define    QUEUE_EOSCAN_BIT    0x8000  // queue end of scan\r
-#define ADC_BUFFER_CLEAR_REG   0x2a\r
-#define ADC_QUEUE_HIGH_REG     0x2c    // high channel for internal queue, use CHAN_BITS() macro above\r
-#define DAC_CONTROL0_REG       0x50    // dac control register 0\r
-#define    DAC_ENABLE_BIT      0x8000  // dac controller enable bit\r
-#define DAC_CONTROL1_REG       0x52    // dac control register 0\r
-#define    DAC_RANGE_BITS(channel, range)      (((range) & 0x3) << (2 * ((channel) & 0x1)))\r
-#define    DAC_OUTPUT_ENABLE_BIT       0x80    // dac output enable bit\r
-#define DAC_BUFFER_CLEAR_REG 0x66      // clear dac buffer\r
-#define DAC_CONVERT_REG(channel)       ((0x70) + (2 * ((channel) & 0x1)))\r
-// read-only\r
-#define HW_STATUS_REG  0x0     // hardware status register, reading this apparently clears pending interrupts as well\r
-#define   DAC_UNDERRUN_BIT     0x1\r
-#define   ADC_OVERRUN_BIT 0x2\r
-#define   DAC_ACTIVE_BIT       0x4\r
-#define   ADC_ACTIVE_BIT       0x8\r
-#define   DAC_INTR_PENDING_BIT 0x10\r
-#define   ADC_INTR_PENDING_BIT 0x20\r
-#define   DAC_DONE_BIT 0x40\r
-#define   ADC_DONE_BIT 0x80\r
-#define   EXT_INTR_PENDING_BIT 0x100\r
-#define   ADC_STOP_BIT 0x200\r
-#define   PIPE_FULL_BIT(x)     (0x400 << ((x) & 0x1))\r
-#define   HW_REVISION(x)       (((x) >> 12) & 0xf)\r
-#define PIPE1_READ_REG 0x4\r
-// read-write\r
-#define ADC_QUEUE_FIFO_REG     0x100   // external channel/gain queue, uses same bits as ADC_QUEUE_LOAD_REG\r
-#define ADC_FIFO_REG 0x200     // adc data fifo\r
-\r
-// devpriv->dio_counter_iobase registers\r
-#define DIO_8255_OFFSET        0x0\r
-#define DO_REG 0x20\r
-#define DI_REG 0x28\r
-\r
-// analog input ranges for most boards\r
-static comedi_lrange ai_ranges =\r
-{\r
-       8,\r
-       {\r
-               BIP_RANGE(10),\r
-               BIP_RANGE(5),\r
-               BIP_RANGE(2.5),\r
-               BIP_RANGE(1.25),\r
-               UNI_RANGE(10),\r
-               UNI_RANGE(5),\r
-               UNI_RANGE(2.5),\r
-               UNI_RANGE(1.25)\r
-       }\r
-};\r
-\r
-// analog output ranges\r
-static comedi_lrange ao_ranges =\r
-{\r
-       4,\r
-       {\r
-               BIP_RANGE(5),\r
-               BIP_RANGE(10),\r
-               UNI_RANGE(5),\r
-               UNI_RANGE(10),\r
-       }\r
-};\r
-\r
-typedef struct pcidas64_board_struct\r
-{\r
-       char *name;\r
-       int device_id;  // pci device id\r
-       int ai_se_chans;        // number of ai inputs in single-ended mode\r
-       int ai_bits;    // analog input resolution\r
-       int ai_speed;   // fastest conversion period in ns\r
-       int ao_nchan;   // number of analog out channels\r
-       int ao_scan_speed;      // analog output speed (for a scan, not conversion)\r
-} pcidas64_board;\r
-\r
-static pcidas64_board pcidas64_boards[] =\r
-{\r
-       {\r
-               name:           "pci-das6402/16",\r
-               device_id:      0x1d,\r
-               ai_se_chans:    64,\r
-               ai_bits:        16,\r
-               ai_speed:       5000,\r
-               ao_nchan: 2,\r
-               ao_scan_speed:  10000,\r
-       },\r
-       {\r
-               name:           "pci-das6402/12",       // XXX check\r
-               device_id:      0x1e,\r
-               ai_se_chans:    64,\r
-               ai_bits:        12,\r
-               ai_speed:       5000,\r
-               ao_nchan: 2,\r
-               ao_scan_speed:  10000,\r
-       },\r
-       {\r
-               name:           "pci-das64/m1/16",\r
-               device_id:      0x35,\r
-               ai_se_chans:    64,\r
-               ai_bits:        16,\r
-               ai_speed:       1000,\r
-               ao_nchan: 2,\r
-               ao_scan_speed:  10000,\r
-       },\r
-       {\r
-               name:           "pci-das64/m2/16",\r
-               device_id:      0x36,\r
-               ai_se_chans:    64,\r
-               ai_bits:        16,\r
-               ai_speed:       500,\r
-               ao_nchan: 2,\r
-               ao_scan_speed:  10000,\r
-       },\r
-       {\r
-               name:           "pci-das64/m3/16",\r
-               device_id:      0x37,\r
-               ai_se_chans:    64,\r
-               ai_bits:        16,\r
-               ai_speed:       333,\r
-               ao_nchan: 2,\r
-               ao_scan_speed:  10000,\r
-       },\r
-\r
-#if 0\r
-       {\r
-               name:           "pci-das6402/16/jr",\r
-               device_id:      0 // XXX,\r
-               ai_se_chans:    64,\r
-               ai_bits:        16,\r
-               ai_speed:       5000,\r
-               ao_nchan: 0,\r
-               ao_scan_speed:  10000,\r
-       },\r
-       {\r
-               name:           "pci-das64/m1/16/jr",\r
-               device_id:      0 // XXX,\r
-               ai_se_chans:    64,\r
-               ai_bits:        16,\r
-               ai_speed:       1000,\r
-               ao_nchan: 0,\r
-               ao_scan_speed:  10000,\r
-       },\r
-       {\r
-               name:           "pci-das64/m2/16/jr",\r
-               device_id:      0 // XXX,\r
-               ai_se_chans:    64,\r
-               ai_bits:        16,\r
-               ai_speed:       500,\r
-               ao_nchan: 0,\r
-               ao_scan_speed:  10000,\r
-       },\r
-       {\r
-               name:           "pci-das64/m3/16/jr",\r
-               device_id:      0 // XXX,\r
-               ai_se_chans:    64,\r
-               ai_bits:        16,\r
-               ai_speed:       333,\r
-               ao_nchan: 0,\r
-               ao_scan_speed:  10000,\r
-       },\r
-       {\r
-               name:           "pci-das64/m1/14",\r
-               device_id:      0,      // XXX\r
-               ai_se_chans:    64,\r
-               ai_bits:        14,\r
-               ai_speed:       1000,\r
-               ao_nchan: 2,\r
-               ao_scan_speed:  10000,\r
-       },\r
-       {\r
-               name:           "pci-das64/m2/14",\r
-               device_id:      0,      // XXX\r
-               ai_se_chans:    64,\r
-               ai_bits:        14,\r
-               ai_speed:       500,\r
-               ao_nchan: 2,\r
-               ao_scan_speed:  10000,\r
-       },\r
-       {\r
-               name:           "pci-das64/m3/14",\r
-               device_id:      0,      // XXX\r
-               ai_se_chans:    64,\r
-               ai_bits:        14,\r
-               ai_speed:       333,\r
-               ao_nchan: 2,\r
-               ao_scan_speed:  10000,\r
-       },\r
-#endif\r
-\r
-};\r
-// Number of boards in cb_pcidas_boards\r
-#define N_BOARDS       (sizeof(pcidas64_boards) / sizeof(pcidas64_board))\r
-\r
-static struct pci_device_id pcidas64_pci_table[] __devinitdata = {\r
-       { PCI_VENDOR_ID_CB, 0x001d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },\r
-       { PCI_VENDOR_ID_CB, 0x001e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },\r
-       { PCI_VENDOR_ID_CB, 0x0035, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },\r
-       { PCI_VENDOR_ID_CB, 0x0036, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },\r
-       { PCI_VENDOR_ID_CB, 0x0037, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },\r
-       { 0 }\r
-};\r
-MODULE_DEVICE_TABLE(pci, pcidas64_pci_table);\r
-\r
-/*\r
- * Useful for shorthand access to the particular board structure\r
- */\r
-#define thisboard ((pcidas64_board *)dev->board_ptr)\r
-\r
-/* this structure is for data unique to this hardware driver.  If\r
-   several hardware drivers keep similar information in this structure,\r
-   feel free to suggest moving the variable to the comedi_device struct.  */\r
-typedef struct\r
-{\r
-       // base addresses (physical)\r
-       unsigned long plx9080_phys_iobase;\r
-       unsigned long main_phys_iobase;\r
-       unsigned long dio_counter_phys_iobase;\r
-       // base addresses (ioremapped)\r
-       unsigned long plx9080_iobase;\r
-       unsigned long main_iobase;\r
-       unsigned long dio_counter_iobase;\r
-       // divisor of master clock for analog input pacing\r
-       unsigned int ai_divisor;\r
-       volatile unsigned int ai_count; // number of analog input samples remaining\r
-       // divisors of master clock for analog output pacing\r
-       unsigned int ao_divisor;\r
-       volatile unsigned int ao_count; // number of analog output samples remaining\r
-       unsigned int ao_value[2];       // remember what the analog outputs are set to, to allow readback\r
-       unsigned int hw_revision;       // stc chip hardware revision number\r
-       unsigned int do_bits;   // remember digital ouput levels\r
-} pcidas64_private;\r
-\r
-/*\r
- * most drivers define the following macro to make it easy to\r
- * access the private structure.\r
- */\r
-#define devpriv ((pcidas64_private *)dev->private)\r
-\r
-/*\r
- * The comedi_driver structure tells the Comedi core module\r
- * which functions to call to configure/deconfigure (attach/detach)\r
- * the board, and also about the kernel module that contains\r
- * the device code.\r
- */\r
-static int attach(comedi_device *dev,comedi_devconfig *it);\r
-static int detach(comedi_device *dev);\r
-comedi_driver driver_cb_pcidas={\r
-       driver_name:    "cb_pcidas64",\r
-       module:         THIS_MODULE,\r
-       attach:         attach,\r
-       detach:         detach,\r
-};\r
-\r
-static int ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);\r
-static int ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);\r
-static int ao_readback_insn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);\r
-static int ai_cmd(comedi_device *dev,comedi_subdevice *s);\r
-static int ai_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd);\r
-//static int ao_cmd(comedi_device *dev,comedi_subdevice *s);\r
-//static int ao_inttrig(comedi_device *dev, comedi_subdevice *subdev, unsigned int trig_num);\r
-//static int ao_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd);\r
-static void handle_interrupt(int irq, void *d, struct pt_regs *regs);\r
-static int ai_cancel(comedi_device *dev, comedi_subdevice *s);\r
-//static int ao_cancel(comedi_device *dev, comedi_subdevice *s);\r
-static int dio_callback(int dir, int port, int data, void *arg);\r
-static int di_rbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);\r
-static int do_wbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);\r
-static void check_adc_timing(comedi_cmd *cmd);\r
-static unsigned int get_divisor(unsigned int ns, unsigned int flags);\r
-\r
-/*\r
- * A convenient macro that defines init_module() and cleanup_module(),\r
- * as necessary.\r
- */\r
-COMEDI_INITCLEANUP(driver_cb_pcidas);\r
-\r
-/*\r
- * Attach is called by the Comedi core to configure the driver\r
- * for a particular board.\r
- */\r
-static int attach(comedi_device *dev, comedi_devconfig *it)\r
-{\r
-       comedi_subdevice *s;\r
-       struct pci_dev* pcidev;\r
-       int index;\r
-       // base addresses\r
-       unsigned long plx9080_iobase;\r
-       unsigned long main_iobase;\r
-       unsigned long dio_counter_iobase;\r
-\r
-       printk("comedi%d: cb_pcidas64\n",dev->minor);\r
-\r
-/*\r
- * Allocate the private structure area.\r
- */\r
-       if(alloc_private(dev,sizeof(pcidas64_private)) < 0)\r
-               return -ENOMEM;\r
-\r
-/*\r
- * Probe the device to determine what device in the series it is.\r
- */\r
-\r
-       pci_for_each_dev(pcidev)\r
-       {\r
-               // is it not a computer boards card?\r
-               if(pcidev->vendor != PCI_VENDOR_ID_CB)\r
-                       continue;\r
-#ifdef PCIDAS64_DEBUG\r
-               printk(" found computer boards device id 0x%x on bus %i slot %i\n",\r
-                       pcidev->device, pcidev->bus->number, PCI_SLOT(pcidev->devfn));\r
-#endif\r
-               // loop through cards supported by this driver\r
-               for(index = 0; index < N_BOARDS; index++)\r
-               {\r
-                       if(pcidas64_boards[index].device_id != pcidev->device)\r
-                               continue;\r
-                       // was a particular bus/slot requested?\r
-                       if(it->options[0] || it->options[1])\r
-                       {\r
-                               // are we on the wrong bus/slot?\r
-                               if(pcidev->bus->number != it->options[0] ||\r
-                                  PCI_SLOT(pcidev->devfn) != it->options[1])\r
-                               {\r
-                                       continue;\r
-                               }\r
-                       }\r
-                       dev->board_ptr = pcidas64_boards + index;\r
-                       goto found;\r
-               }\r
-       }\r
-\r
-       printk("No supported ComputerBoards/MeasurementComputing card found\n");\r
-       return -EIO;\r
-\r
-found:\r
-\r
-       printk("Found %s on bus %i, slot %i\n", pcidas64_boards[index].name,\r
-               pcidev->bus->number, PCI_SLOT(pcidev->devfn));\r
-\r
-       //Initialize dev->board_name\r
-       dev->board_name = thisboard->name;\r
-\r
-       /* Initialize devpriv->control_status and devpriv->adc_fifo to point to\r
-        * their base address.\r
-        */\r
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0)\r
-       plx9080_iobase =\r
-               pcidev->base_address[PLX9080_BADRINDEX] &\r
-               PCI_BASE_ADDRESS_MEM_MASK;\r
-       main_iobase =\r
-               pcidev->base_address[MAIN_BADRINDEX] &\r
-               PCI_BASE_ADDRESS_MEM_MASK;\r
-       dio_counter_iobase =\r
-               pcidev->base_address[DIO_COUNTER_BADRINDEX] &\r
-               PCI_BASE_ADDRESS_MEM_MASK;\r
-#else\r
-       if(pci_enable_device(pcidev))\r
-               return -EIO;\r
-       pci_set_master(pcidev);\r
-       plx9080_iobase =\r
-               pcidev->resource[PLX9080_BADRINDEX].start &\r
-               PCI_BASE_ADDRESS_MEM_MASK;\r
-       main_iobase =\r
-               pcidev->resource[MAIN_BADRINDEX].start &\r
-               PCI_BASE_ADDRESS_MEM_MASK;\r
-       dio_counter_iobase =\r
-               pcidev->resource[DIO_COUNTER_BADRINDEX].start &\r
-               PCI_BASE_ADDRESS_MEM_MASK;\r
-\r
-       if(check_mem_region(plx9080_iobase, PLX9080_IOSIZE))\r
-       {\r
-               /* Couldn't allocate io space */\r
-               printk(KERN_WARNING "couldn't allocate IO space\n");\r
-               return -EIO;\r
-       }\r
-       if(check_mem_region(main_iobase, MAIN_IOSIZE))\r
-       {\r
-               /* Couldn't allocate io space */\r
-               printk(KERN_WARNING "couldn't allocate IO space\n");\r
-               return -EIO;\r
-       }\r
-       if(check_mem_region(dio_counter_iobase, DIO_COUNTER_IOSIZE))\r
-       {\r
-               /* Couldn't allocate io space */\r
-               printk(KERN_WARNING "couldn't allocate IO space\n");\r
-               return -EIO;\r
-       }\r
-\r
-       request_mem_region(plx_iobase, PLX9080_IOSIZE, "cb_pcidas64");\r
-       devpriv->plx9080_phys_iobase = dio_counter_iobase;\r
-       request_mem_region(main_iobase, MAIN_IOSIZE, "cb_pcidas64");\r
-       devpriv->main_phys_iobase = dio_counter_iobase;\r
-       request_mem_region(dio_counter_iobase, DIO_COUNTER_IOSIZE, "cb_pcidas64");\r
-       devpriv->dio_counter_phys_iobase = dio_counter_iobase;\r
-\r
-#endif\r
-\r
-       // remap, won't work with 2.0 kernels but who cares\r
-       devpriv->plx9080_iobase = (unsigned long)ioremap(plx9080_iobase, PLX9080_IOSIZE);\r
-       devpriv->main_iobase = (unsigned long)ioremap(main_iobase, MAIN_IOSIZE);\r
-       devpriv->dio_counter_iobase = (unsigned long)ioremap(dio_counter_iobase, DIO_COUNTER_IOSIZE);\r
-\r
-       devpriv->hw_revision = HW_REVISION(readw(devpriv->main_iobase + HW_STATUS_REG));\r
-\r
-       // get irq\r
-       if(comedi_request_irq(pcidev->irq, handle_interrupt, SA_SHIRQ, "cb_pcidas64", dev ))\r
-       {\r
-               printk(" unable to allocate irq %d\n", pcidev->irq);\r
-               return -EINVAL;\r
-       }\r
-       dev->irq = pcidev->irq;\r
-\r
-#ifdef PCIDAS64_DEBUG\r
-\r
-printk(" plx9080 phys io addr 0x%lx\n", devpriv->plx9080_phys_iobase);\r
-printk(" main phys io addr 0x%lx\n", devpriv->main_phys_iobase);\r
-printk(" diocounter phys io addr 0x%lx\n", devpriv->dio_counter_phys_iobase);\r
-printk(" irq %i\n", dev->irq);\r
-\r
-printk(" plx9080 virt io addr 0x%lx\n", devpriv->plx9080_iobase);\r
-printk(" main virt io addr 0x%lx\n", devpriv->main_iobase);\r
-printk(" diocounter virt io addr 0x%lx\n", devpriv->dio_counter_iobase);\r
-printk(" irq %i\n", dev->irq);\r
-\r
-printk(" stc hardware revision %i\n", devpriv->hw_revision);\r
-\r
-// plx9080 dump\r
-printk(" plx interrupt status 0x%x\n", readl(devpriv->plx9080_iobase + PLX_INTRCS_REG));\r
-printk(" plx id bits 0x%x\n", readl(devpriv->plx9080_iobase + PLX_ID_REG));\r
-printk(" plx hardware revision 0x%x\n", readl(devpriv->plx9080_iobase + PLX_REVISION_REG));\r
-printk(" plx dma channel 0 mode 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_MODE_REG));\r
-printk(" plx dma channel 0 pci address 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG));\r
-printk(" plx dma channel 0 local address 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_LOCAL_ADDRESS_REG));\r
-printk(" plx dma channel 0 transfer size 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_TRANSFER_SIZE_REG));\r
-printk(" plx dma channel 0 descriptor 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG));\r
-printk(" plx dma channel 0 command status 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_CS_REG));\r
-printk(" plx dma channel 0 threshold 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_THRESHOLD_REG));\r
-\r
-#endif\r
-\r
-\r
-/*\r
- * Allocate the subdevice structures.\r
- */\r
-       dev->n_subdevices = 7;\r
-       if(alloc_subdevices(dev)<0)\r
-               return -ENOMEM;\r
-\r
-       s = dev->subdevices + 0;\r
-       /* analog input subdevice */\r
-       dev->read_subdev = s;\r
-       s->type = COMEDI_SUBD_AI;\r
-       s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF;\r
-       /* XXX Number of inputs in differential mode is ignored */\r
-       s->n_chan = thisboard->ai_se_chans;\r
-       s->len_chanlist = 8092;\r
-       s->maxdata = (1 << thisboard->ai_bits) - 1;\r
-       s->range_table = &ai_ranges;\r
-       s->insn_read = ai_rinsn;\r
-       s->do_cmd = ai_cmd;\r
-       s->do_cmdtest = ai_cmdtest;\r
-       s->cancel = ai_cancel;\r
-\r
-       /* analog output subdevice */\r
-       s = dev->subdevices + 1;\r
-       if(thisboard->ao_nchan)\r
-       {\r
-       //      dev->write_subdev = s;\r
-               s->type = COMEDI_SUBD_AO;\r
-               s->subdev_flags = SDF_READABLE | SDF_WRITEABLE | SDF_GROUND;\r
-               s->n_chan = thisboard->ao_nchan;\r
-               // analog out resolution is the same as analog input resolution, so use ai_bits\r
-               s->maxdata = (1 << thisboard->ai_bits) - 1;\r
-               s->range_table = &ao_ranges;\r
-               s->insn_read = ao_readback_insn;\r
-               s->insn_write = ao_winsn;\r
-       //      s->do_cmdtest = ao_cmdtest;\r
-       //      s->do_cmd = ao_cmd;\r
-       //      s->len_chanlist = thisboard->ao_nchan;\r
-       //      s->cancel = ao_cancel;\r
-       } else\r
-       {\r
-               s->type = COMEDI_SUBD_UNUSED;\r
-       }\r
-\r
-       // digital input\r
-       s = dev->subdevices + 2;\r
-       s->type=COMEDI_SUBD_DI;\r
-       s->subdev_flags = SDF_READABLE;\r
-       s->n_chan = 4;\r
-       s->maxdata = 1;\r
-       s->range_table = &range_digital;\r
-       s->insn_bits = di_rbits;\r
-\r
-       // digital output\r
-       s = dev->subdevices + 3;\r
-       s->type=COMEDI_SUBD_DO;\r
-       s->subdev_flags = SDF_WRITEABLE | SDF_READABLE;\r
-       s->n_chan = 4;\r
-       s->maxdata = 1;\r
-       s->range_table = &range_digital;\r
-       s->insn_bits = do_wbits;\r
-\r
-       /* 8255 */\r
-       s = dev->subdevices + 4;\r
-       subdev_8255_init(dev, s, dio_callback,\r
-               (void*) (devpriv->dio_counter_iobase + DIO_8255_OFFSET));\r
-\r
-       // user counter subd XXX\r
-       s = dev->subdevices + 5;\r
-       s->type = COMEDI_SUBD_UNUSED;\r
-\r
-       // calibration subd XXX\r
-       s = dev->subdevices + 6;\r
-       s->type = COMEDI_SUBD_UNUSED; \r
-\r
-       return 0;\r
-}\r
-\r
-\r
-/*\r
- * _detach is called to deconfigure a device.  It should deallocate\r
- * resources.\r
- * This function is also called when _attach() fails, so it should be\r
- * careful not to release resources that were not necessarily\r
- * allocated by _attach().  dev->private and dev->subdevices are\r
- * deallocated automatically by the core.\r
- */\r
-static int detach(comedi_device *dev)\r
-{\r
-       printk("comedi%d: cb_pcidas: remove\n",dev->minor);\r
-\r
-       if(devpriv)\r
-       {\r
-               if(devpriv->plx9080_iobase)\r
-                       iounmap((void*)devpriv->plx9080_iobase);\r
-               if(devpriv->main_iobase)\r
-                       iounmap((void*)devpriv->main_iobase);\r
-               if(devpriv->dio_counter_iobase)\r
-                       iounmap((void*)devpriv->dio_counter_iobase);\r
-               if(devpriv->plx9080_phys_iobase)\r
-                       release_mem_region(devpriv->plx9080_iobase, PLX9080_IOSIZE);\r
-               if(devpriv->main_iobase)\r
-                       release_mem_region(devpriv->main_phys_iobase, MAIN_IOSIZE);\r
-               if(devpriv->dio_counter_iobase)\r
-                       release_mem_region(devpriv->dio_counter_phys_iobase, DIO_COUNTER_IOSIZE);\r
-       }\r
-       if(dev->irq)\r
-               comedi_free_irq(dev->irq, dev);\r
-       if(dev->subdevices)\r
-               subdev_8255_cleanup(dev,dev->subdevices + 4);\r
-\r
-       return 0;\r
-}\r
-\r
-static int ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)\r
-{\r
-       unsigned int bits, n, i;\r
-       const int timeout = 1000;\r
-\r
-       // disable card's interrupt sources\r
-       writew(0, devpriv->main_iobase + INTR_ENABLE_REG);\r
-\r
-       /* disable pacing, triggering, etc */\r
-       writew(ADC_ENABLE_BIT, devpriv->main_iobase + ADC_CONTROL0_REG);\r
-       writew(ADC_CONTROL1_DUMMY_BITS, devpriv->main_iobase + ADC_CONTROL1_REG);\r
-\r
-       // use internal queue\r
-       writew(HW_CONFIG_DUMMY_BITS, devpriv->main_iobase + HW_CONFIG_REG);\r
-\r
-       // load internal queue\r
-       bits = 0;\r
-       // set channel\r
-       bits |= CHAN_BITS(CR_CHAN(insn->chanspec));\r
-       // set gain\r
-       bits |= GAIN_BITS(CR_RANGE(insn->chanspec));\r
-       // set unipolar / bipolar\r
-       bits |= UNIP_BIT(CR_RANGE(insn->chanspec));\r
-       // set single-ended / differential\r
-       if(CR_AREF(insn->chanspec) != AREF_DIFF)\r
-               bits |= SE_BIT;\r
-       // set stop channel\r
-       writew(CHAN_BITS(CR_CHAN(insn->chanspec)), devpriv->main_iobase + ADC_QUEUE_HIGH_REG);\r
-       // set start channel, and rest of settings\r
-       writew(bits, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);\r
-\r
-       // clear adc buffer\r
-       writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);\r
-\r
-       for(n = 0; n < insn->n; n++)\r
-       {\r
-               /* trigger conversion */\r
-               writew(0, devpriv->main_iobase + ADC_CONVERT_REG);\r
-\r
-               // wait for data\r
-               for(i = 0; i < timeout; i++)\r
-               {\r
-                       if(!(readw(devpriv->main_iobase + HW_STATUS_REG) & ADC_ACTIVE_BIT))\r
-                               break;\r
-               }\r
-               if(i == timeout)\r
-               {\r
-                       comedi_error(dev, " analog input read insn timed out");\r
-                       return -ETIME;\r
-               }\r
-               data[n] = readw(devpriv->main_iobase + PIPE1_READ_REG);\r
-       }\r
-\r
-       return n;\r
-}\r
-\r
-static int ai_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd)\r
-{      int err = 0;\r
-       int tmp;\r
-       unsigned int tmp_arg, tmp_arg2;\r
-       int i;\r
-       int aref;\r
-\r
-       /* step 1: make sure trigger sources are trivially valid */\r
-\r
-       tmp = cmd->start_src;\r
-       cmd->start_src &= TRIG_NOW | TRIG_EXT;\r
-       if(!cmd->start_src || tmp != cmd->start_src) err++;\r
-\r
-       tmp = cmd->scan_begin_src;\r
-       cmd->scan_begin_src &= TRIG_TIMER | TRIG_FOLLOW;\r
-       if(!cmd->scan_begin_src || tmp != cmd->scan_begin_src) err++;\r
-\r
-       tmp = cmd->convert_src;\r
-       cmd->convert_src &= TRIG_TIMER | TRIG_EXT;\r
-       if(!cmd->convert_src || tmp != cmd->convert_src) err++;\r
-\r
-       tmp = cmd->scan_end_src;\r
-       cmd->scan_end_src &= TRIG_COUNT;\r
-       if(!cmd->scan_end_src || tmp != cmd->scan_end_src) err++;\r
-\r
-       tmp=cmd->stop_src;\r
-       cmd->stop_src &= TRIG_COUNT | TRIG_EXT | TRIG_NONE;\r
-       if(!cmd->stop_src || tmp != cmd->stop_src) err++;\r
-\r
-       if(err) return 1;\r
-\r
-       /* step 2: make sure trigger sources are unique and mutually compatible */\r
-\r
-       // uniqueness check\r
-       if(cmd->start_src != TRIG_NOW &&\r
-               cmd->start_src != TRIG_EXT) err++;\r
-       if(cmd->scan_begin_src != TRIG_TIMER &&\r
-               cmd->scan_begin_src != TRIG_FOLLOW) err++;\r
-       if(cmd->convert_src != TRIG_TIMER &&\r
-               cmd->convert_src != TRIG_EXT) err++;\r
-       if(cmd->stop_src != TRIG_COUNT &&\r
-               cmd->stop_src != TRIG_NONE &&\r
-               cmd->stop_src != TRIG_EXT) err++;\r
-\r
-       // compatibility check\r
-       if(cmd->convert_src == TRIG_EXT &&\r
-               cmd->scan_begin_src == TRIG_TIMER)\r\r
-               err++;\r
-\r
-       if(err) return 2;\r
-\r
-       /* step 3: make sure arguments are trivially compatible */\r
-\r
-       if(cmd->start_arg != 0)\r
-       {\r
-               cmd->start_arg = 0;\r
-               err++;\r
-       }\r
-       if(cmd->convert_src == TRIG_TIMER)\r
-       {\r
-               if(cmd->convert_arg < thisboard->ai_speed)\r
-               {\r
-                       cmd->convert_arg = thisboard->ai_speed;\r
-                       err++;\r
-               }\r
-               if(cmd->scan_begin_src == TRIG_TIMER)\r\r
-               {\r
-                       // if scans are timed faster than conversion rate allows\r\r
-                       if(cmd->convert_arg * cmd->chanlist_len > cmd->scan_begin_arg)\r
-                       {\r
-                               cmd->scan_begin_arg = cmd->convert_arg * cmd->chanlist_len;\r
-                               err++;\r
-                       }\r
-               }\r
-       }\r
-\r
-       if(!cmd->chanlist_len)\r
-       {\r
-               cmd->chanlist_len = 1;\r
-               err++;\r
-       }\r
-       if(cmd->scan_end_arg != cmd->chanlist_len)\r
-       {\r
-               cmd->scan_end_arg = cmd->chanlist_len;\r
-               err++;\r
-       }\r
-\r
-       switch(cmd->stop_src)\r
-       {\r
-               case TRIG_EXT:\r
-                       if(cmd->stop_arg)\r
-                       {\r
-                               cmd->stop_arg = 0;\r
-                               err++;\r
-                       }\r
-                       break;\r
-               case TRIG_COUNT:\r
-                       if(!cmd->stop_arg)\r
-                       {\r
-                               cmd->stop_arg = 1;\r
-                               err++;\r
-                       }\r
-                       break;\r
-               case TRIG_NONE:\r
-                       if(cmd->stop_arg != 0)\r
-                       {\r
-                               cmd->stop_arg = 0;\r
-                               err++;\r
-                       }\r
-                       break;\r
-               default:\r
-                       break;\r
-       }\r
-\r
-       if(err) return 3;\r
-\r
-       /* step 4: fix up any arguments */\r
-\r
-       if(cmd->convert_src == TRIG_TIMER)\r
-       {\r
-               tmp_arg = cmd->convert_arg;\r
-               tmp_arg2 = cmd->scan_begin_arg;\r
-               check_adc_timing(cmd);\r
-               if(tmp_arg != cmd->convert_arg) err++;\r
-               if(tmp_arg2 != cmd->scan_begin_arg) err++;\r
-       }\r
-\r
-       if(err) return 4;\r
-\r
-       // make sure user is doesn't change analog reference mid chanlist\r
-       if(cmd->chanlist)\r
-       {\r
-               aref = CR_AREF(cmd->chanlist[0]);\r
-               for(i = 1; i < cmd->chanlist_len; i++)\r
-               {\r
-                       if(aref != CR_AREF(cmd->chanlist[i]))\r
-                       {\r
-                               comedi_error(dev, "all elements in chanlist must use the same analog reference");\r
-                               err++;\r
-                               break;\r
-                       }\r
-               }\r
-       }\r
-\r
-       if(err) return 5;\r
-\r
-       return 0;\r
-}\r
-\r
-static int ai_cmd(comedi_device *dev,comedi_subdevice *s)\r
-{\r
-       comedi_async *async = s->async;\r
-       comedi_cmd *cmd = &async->cmd;\r
-       unsigned int bits;\r
-       unsigned int convert_counter_value;\r
-       unsigned int scan_counter_value;\r
-       unsigned int i;\r
-\r
-       // disable card's interrupt sources\r
-       writew(0, devpriv->main_iobase + INTR_ENABLE_REG);\r
-\r
-       /* disable pacing, triggering, etc */\r
-       writew(0, devpriv->main_iobase + ADC_CONTROL0_REG);\r
-       writew(ADC_CONTROL1_DUMMY_BITS, devpriv->main_iobase + ADC_CONTROL1_REG);\r
-\r
-       // use external queue\r
-       writew(EXT_QUEUE | HW_CONFIG_DUMMY_BITS, devpriv->main_iobase + HW_CONFIG_REG);\r
-\r
-       // set fifo size\r
-       writew(ADC_FIFO_8K_BITS | FIFO_SIZE_DUMMY_BITS, devpriv->main_iobase + FIFO_SIZE_REG);\r
-\r
-       // set conversion pacing\r
-       if(cmd->convert_src == TRIG_TIMER)\r
-       {\r
-               check_adc_timing(cmd);\r
-               // supposed to load counter with desired divisor minus 3\r
-               convert_counter_value = cmd->convert_arg / TIMER_BASE - 3;\r
-               // load lower 16 bits\r
-               writew(convert_counter_value & 0xffff, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG);\r
-               // load upper 8 bits\r
-               writew((convert_counter_value >> 16) & 0xff, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);\r
-               // set scan pacing\r
-               if(cmd->convert_src == TRIG_TIMER)\r
-               {\r
-                       // figure out how long we need to delay at end of scan\r
-                       scan_counter_value = (cmd->scan_begin_arg - (cmd->convert_arg * (cmd->chanlist_len - 1)))\r
-                               / TIMER_BASE;\r
-                       // load lower 16 bits\r
-                       writew(scan_counter_value & 0xffff, devpriv->main_iobase + ADC_DELAY_INTERVAL_LOWER_REG);\r
-                       // load upper 8 bits\r
-                       writew((scan_counter_value >> 16) & 0xff, devpriv->main_iobase + ADC_DELAY_INTERVAL_UPPER_REG);\r
-               }\r
-       }\r
-\r
-       // load hardware conversion counter with non-zero value so it doesn't mess with us\r
-       writew(~0, devpriv->main_iobase + ADC_COUNT_LOWER_REG);\r
-\r
-       /* XXX cannot write to queue fifo while dac fifo is being written to\r
-        * ( need spinlock, or try to use internal queue instead */\r
-       // clear queue pointer\r
-       writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG);\r
-       // load external queue\r
-       for(i = 0; i < cmd->chanlist_len; i++)\r
-       {\r
-               bits = 0;\r
-               // set channel\r
-               bits |= CHAN_BITS(CR_CHAN(cmd->chanlist[i]));\r
-               // set gain\r
-               bits |= GAIN_BITS(CR_RANGE(cmd->chanlist[i]));\r
-               // set unipolar / bipolar\r
-               bits |= UNIP_BIT(CR_RANGE(cmd->chanlist[i]));\r
-               // set single-ended / differential\r
-               if(CR_AREF(cmd->chanlist[i]) != AREF_DIFF)\r
-                       bits |= SE_BIT;\r
-               // mark end of queue\r
-               if(i == cmd->chanlist_len - 1)\r
-                       bits |= QUEUE_EOSCAN_BIT | QUEUE_EOSEQ_BIT;\r
-               writew(bits, devpriv->main_iobase + ADC_QUEUE_FIFO_REG);\r
-       }\r
-       // prime queue holding register\r
-       writew(0, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);\r
-\r
-       // clear adc buffer\r
-       writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);\r
-\r
-       // enable interrupts\r
-       bits = EN_ADC_OVERRUN_BIT | EN_ADC_DONE_INTR_BIT;\r
-       if(cmd->flags & TRIG_WAKE_EOS)\r
-               bits |= ADC_INTR_EOSCAN_BITS;\r
-       else\r
-               bits |= ADC_INTR_QFULL_BITS;    // for clairity only, since quarter-full bits are zero\r
-       writew(bits, devpriv->main_iobase + INTR_ENABLE_REG);\r
-       // enable interrupts on plx 9080 XXX enabling more interrupt sources than are actually used\r
-       bits = ICS_PIE | ICS_PLIE | ICS_PAIE | ICS_PDIE | ICS_LIE | ICS_LDIE | ICS_DMA0_E | ICS_DMA1_E;\r
-       writel(bits, devpriv->plx9080_iobase + PLX_INTRCS_REG);\r
-\r
-       // disable dma for now XXX\r
-       writeb(0, devpriv->plx9080_iobase + PLX_DMA0_CS_REG);\r
-       writeb(0, devpriv->plx9080_iobase + PLX_DMA1_CS_REG);\r
-\r
-       /* set mode, disable software conversion gate */\r
-       bits = ADC_CONTROL1_DUMMY_BITS | SW_NOGATE_BIT;\r
-       if(cmd->convert_src == TRIG_EXT)\r
-               bits |= ADC_MODE_BITS(13);      // good old mode 13\r
-       else\r
-               bits |= ADC_MODE_BITS(8);       // mode 8.  What else could you need?\r
-       writew(bits, devpriv->main_iobase + ADC_CONTROL1_REG);\r
-\r
-       /* enable pacing, triggering, etc */\r
-       bits = ADC_ENABLE_BIT;\r
-       writew(bits, devpriv->main_iobase + ADC_CONTROL0_REG);\r
-\r
-       // start aquisition\r
-       writew(0, devpriv->main_iobase + ADC_START_REG);\r
-\r
-       return 0;\r
-}\r
-\r
-static void handle_interrupt(int irq, void *d, struct pt_regs *regs)\r
-{\r
-#ifdef PCIDAS64_DEBUG\r
-#endif\r
-       comedi_device *dev = d;\r
-       comedi_subdevice *s = dev->read_subdev;\r
-       comedi_async *async = s->async;\r
-       comedi_cmd *cmd = &async->cmd;\r
-       unsigned int num_samples = 0;\r
-       unsigned int i;\r
-       u16 data;\r
-       unsigned int status;\r
-       u32 plx_status;\r
-       u32 plx_bits;\r
-#ifdef PCIDAS64_DEBUG\r
-       static unsigned int intr_count = 0;\r
-       const int debug_count = 10;\r
-#endif\r
-\r
-       status = readw(devpriv->main_iobase + HW_STATUS_REG);\r
-       plx_status = readl(devpriv->plx9080_iobase + PLX_INTRCS_REG);\r
-       if((status &\r
-               (ADC_INTR_PENDING_BIT | ADC_DONE_BIT | ADC_STOP_BIT |\r
-               DAC_INTR_PENDING_BIT | DAC_DONE_BIT | EXT_INTR_PENDING_BIT)) == 0 &&\r
-               (plx_status & (ICS_DMA0_A | ICS_DMA1_A | ICS_LDIA | ICS_LIA | ICS_PAIA | ICS_PDIA)) == 0)\r
-       {\r
-#ifdef PCIDAS64_DEBUG\r
-               rt_printk(" cb_pcidas64 spurious interrupt");\r
-#endif\r
-               return;\r
-       }\r
-#ifdef PCIDAS64_DEBUG\r
-       intr_count++;\r
-       if(intr_count < debug_count)\r
-       {\r
-               rt_printk(" cb_pcidas64 interrupt status 0x%x\n", status);\r
-               rt_printk(" plx status 0x%x\n", plx_status);\r
-       }\r
-#endif\r
-\r
-       async->events = 0;\r
-\r
-       // if interrupt was due to analog input data being available\r
-       if(status & ADC_INTR_PENDING_BIT)\r
-       {\r
-               // figure out how many samples we should read from board's fifo\r
-               /* XXX should use ADC read/write pointer registers to figure out\r
-                * how many samples are actually in fifo */\r
-               if(cmd->flags & TRIG_WAKE_EOS)\r
-                       num_samples = cmd->chanlist_len;\r
-               else\r
-                       num_samples = QUARTER_AI_FIFO_SIZE;\r
-               // read samples\r
-               for(i = 0; i < num_samples; i++)\r
-               {\r
-                       data = readw(devpriv->main_iobase + ADC_FIFO_REG);\r
-                       comedi_buf_put(async, data);\r
-               }\r
-               async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;\r
-       }\r
-\r
-       // clear possible plx9080 interrupt sources\r
-       if(plx_status & ICS_LDIA)\r
-       { // clear local doorbell interrupt\r
-               plx_bits = readl(devpriv->plx9080_iobase + PLX_DBR_OUT_REG);\r
-               writel(plx_bits, devpriv->plx9080_iobase + PLX_DBR_OUT_REG);\r
-#ifdef PCIDAS64_DEBUG\r
-               if(intr_count < debug_count)\r
-                       rt_printk(" cleared local doorbell bits 0x%x\n", plx_bits);\r
-#endif\r
-       }\r
-       if(plx_status & ICS_DMA0_A)\r
-       {       // dma chan 0 interrupt\r
-               writeb(PLX_CLEAR_DMA_INTR_BIT, devpriv->plx9080_iobase + PLX_DMA0_CS_REG);\r
-#ifdef PCIDAS64_DEBUG\r
-               if(intr_count < debug_count)\r
-                       rt_printk(" cleared dma ch0 interrupt\n");\r
-#endif\r
-       }\r
-       if(plx_status & ICS_DMA1_A)\r
-       {       // dma chan 1 interrupt\r
-               writeb(PLX_CLEAR_DMA_INTR_BIT, devpriv->plx9080_iobase + PLX_DMA1_CS_REG);\r
-#ifdef PCIDAS64_DEBUG\r
-               if(intr_count < debug_count)\r
-                       rt_printk(" cleared dma ch1 interrupt\n");\r
-#endif\r
-       }\r
-\r
-       comedi_event(dev, s, async->events);\r
-       return;\r
-}\r
-\r
-static int ai_cancel(comedi_device *dev, comedi_subdevice *s)\r
-{\r
-       /* disable pacing, triggering, etc */\r
-       writew(0, devpriv->main_iobase + ADC_CONTROL0_REG);\r
-       writew(ADC_CONTROL1_DUMMY_BITS, devpriv->main_iobase + ADC_CONTROL1_REG);\r
-\r
-       return 0;\r
-}\r
-\r
-static int ao_winsn(comedi_device *dev, comedi_subdevice *s,\r
-       comedi_insn *insn, lsampl_t *data)\r
-{\r
-       int chan = CR_CHAN(insn->chanspec);\r
-       int range = CR_RANGE(insn->chanspec);\r
-       unsigned int bits;\r
-\r
-       // do some initializing\r
-       writew(DAC_ENABLE_BIT, devpriv->main_iobase + DAC_CONTROL0_REG);\r
-\r
-       // set range\r
-       bits = DAC_OUTPUT_ENABLE_BIT;\r
-       bits |= DAC_RANGE_BITS(chan, range);\r
-       writew(bits, devpriv->main_iobase + DAC_CONTROL1_REG);\r
-\r
-       // clear buffer\r
-       writew(0, devpriv->main_iobase + DAC_BUFFER_CLEAR_REG);\r
-\r
-       // write to channel\r
-       writew(data[0], devpriv->main_iobase + DAC_CONVERT_REG(chan));\r
-\r
-       // remember output value\r
-       devpriv->ao_value[chan] = data[0];\r
-\r
-       return 1;\r
-}\r
-\r
-static int ao_readback_insn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)\r
-{\r
-       data[0] = devpriv->ao_value[CR_CHAN(insn->chanspec)];\r
-\r
-       return 1;\r
-}\r
-\r
-static int dio_callback(int dir, int port, int data, void *arg)\r
-{\r
-       unsigned long iobase = (int)arg;\r
-\r
-       if(dir)\r
-       {\r
-               writeb(data, iobase + port);\r
-               return 0;\r
-       }else\r
-       {\r
-               return readb(iobase + port);\r
-       }\r
-}\r
-\r
-static int di_rbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)\r
-{\r
-       lsampl_t bits;\r
-\r
-       bits = readb(devpriv->dio_counter_iobase + DI_REG);\r
-       bits &= 0xf;\r
-       data[1] = bits;\r
-       data[0] = 0;\r
-\r
-       return 2;\r
-}\r
-\r
-static int do_wbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)\r
-{\r
-       lsampl_t wbits;\r
-\r
-       data[0] &= 0xf;\r
-       wbits = devpriv->do_bits;\r
-       // zero bits we are going to change\r
-       wbits &= ~data[0];\r
-       // set new bits\r
-       wbits |= data[0] & data[1];\r
-       devpriv->do_bits = wbits;\r
-\r
-       writeb(devpriv->do_bits, devpriv->dio_counter_iobase + DO_REG);\r
-\r
-       data[1] = wbits;\r
-\r
-       return 2;\r
-}\r
-\r
-/* utility function that rounds desired timing to an achievable time, and\r
- * sets cmd members appropriately.\r
- * adc paces conversions from master clock by dividing by (x + 3) where x is 24 bit number\r
- */\r
-static void check_adc_timing(comedi_cmd *cmd)\r
-{\r
-       unsigned int convert_divisor, scan_divisor;\r
-       const int max_counter_value = 0xffffff; // board uses 24 bit counters for pacing\r
-       const int min_convert_divisor = 3;\r
-       const int max_convert_divisor = max_counter_value + min_convert_divisor;\r
-       unsigned long long max_scan_divisor, min_scan_divisor;\r
-\r
-       if(cmd->convert_src == TRIG_TIMER)\r
-       {\r
-               convert_divisor = get_divisor(cmd->convert_arg, cmd->flags);\r
-               if(convert_divisor > max_convert_divisor) convert_divisor = max_convert_divisor;\r
-               if(convert_divisor < min_convert_divisor) convert_divisor = min_convert_divisor;\r
-               cmd->convert_arg = convert_divisor * TIMER_BASE;\r
-\r
-               if(cmd->scan_begin_src == TRIG_TIMER)\r
-               {\r
-                       scan_divisor = get_divisor(cmd->scan_begin_arg, cmd->flags);\r
-                       // XXX check for integer overflows\r
-                       min_scan_divisor = convert_divisor * cmd->chanlist_len;\r
-                       max_scan_divisor = (convert_divisor * cmd->chanlist_len - 1) + max_counter_value;\r
-                       if(scan_divisor > max_scan_divisor) scan_divisor = max_scan_divisor;\r
-                       if(scan_divisor < min_scan_divisor) scan_divisor = min_scan_divisor;\r
-                       cmd->scan_begin_arg = scan_divisor * TIMER_BASE;\r
-               }\r
-       }\r
-\r
-       return;\r
-}\r
-\r
-/* Gets nearest achievable timing given master clock speed, does not\r
- * take into account possible minimum/maximum divisor values.  Used\r
- * by other timing checking functions. */\r
-static unsigned int get_divisor(unsigned int ns, unsigned int flags)\r
-{\r
-       unsigned int divisor;\r
-\r
-       switch(flags & TRIG_ROUND_MASK)\r
-       {\r
-               case TRIG_ROUND_UP:\r
-                       divisor = (ns + TIMER_BASE - 1) / TIMER_BASE;\r
-                       break;\r
-               case TRIG_ROUND_DOWN:\r
-                       divisor = ns / TIMER_BASE;\r
-                       break;\r
-               case TRIG_ROUND_NEAREST:\r
-               default:\r
-                       divisor = (ns + TIMER_BASE / 2) / TIMER_BASE;\r
-                       break;\r
-       }\r
-\r
-       return divisor;\r
-}\r
+/*
+    cb_pcidas64.c
+    This is a driver for the ComputerBoards/MeasurementComputing PCI-DAS
+    64xxx cards.
+
+    Author:  Frank Mori Hess <fmhess@uiuc.edu>
+
+    Options:
+    [0] - PCI bus number
+    [1] - PCI slot number
+
+    Copyright (C) 2001 Frank Mori Hess <fmhess@uiuc.edu>
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 1997-8 David A. Schleef <ds@stm.lbl.gov>
+
+    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_pcidas64.o
+Description: Driver for the ComputerBoards/MeasurementComputing
+   PCI-DAS64xxx series with the PLX 9080 PCI controller.
+Author: Frank Mori Hess <fmhess@uiuc.edu>
+Status: Experimental
+Updated: 2001-8-27
+Configuration options:
+   [0] - PCI bus of device (optional)
+   [1] - PCI slot of device (optional)
+
+Basic insn support should work, but untested as far as I know.
+Has command support for analog input, which may also work.  Still
+needs dma support to be added to support very fast aquisition rates.
+This driver is in need of stout-hearted testers who aren't afraid to
+crash their computers in the name of progress.
+Feel free to send and success/failure reports to author.
+
+*/
+
+/*
+
+TODO:
+       command support for ao
+       pci-dma support
+       calibration subdevice
+       user counter subdevice
+       there are a number of boards this driver will support when they are
+               fully released, but does not since yet since the pci device id numbers
+               are not yet available.
+       need to take care to prevent ai and ao from affecting each others register bits
+       support prescaled 100khz clock for slow pacing
+       need to make sure all trigger sources are properly supported (TRIG_EXT)
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/malloc.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/pci.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/comedidev.h>
+#include "8253.h"
+#include "8255.h"
+#include "plx9080.h"
+
+#define PCIDAS64_DEBUG // enable debugging code
+//#undef PCIDAS64_DEBUG        // disable debugging code
+
+// PCI vendor number of ComputerBoards/MeasurementComputing
+#define PCI_VENDOR_ID_CB       0x1307
+#define TIMER_BASE 25  // 40MHz master clock
+#define PRESCALED_TIMER_BASE   10000   // 100kHz 'prescaled' clock for slow aquisition, maybe I'll support this someday
+#define QUARTER_AI_FIFO_SIZE 2048      // 1/4 analog input fifo size
+
+/* PCI-DAS64xxx base addresses */
+
+// indices of base address regions
+#define PLX9080_BADRINDEX 0
+#define MAIN_BADRINDEX 2
+#define DIO_COUNTER_BADRINDEX 3
+
+#define PLX9080_IOSIZE 0xec
+#define MAIN_IOSIZE 0x302
+#define DIO_COUNTER_IOSIZE 0x29
+
+// devpriv->main_iobase registers
+// write-only
+#define INTR_ENABLE_REG        0x0     // interrupt enable register
+#define    EN_ADC_OVERRUN_BIT  0x8000  // enable adc overrun status bit
+#define    EN_DAC_UNDERRUN_BIT 0x4000  // enable dac underrun status bit
+#define    EN_ADC_DONE_INTR_BIT        0x8     // enable adc aquisition done interrupt
+#define    EN_ADC_INTR_SRC_BIT 0x4     // enable adc interrupt source
+#define    ADC_INTR_SRC_MASK   0x3     // bits that set adc interrupt source
+#define    ADC_INTR_QFULL_BITS 0x0     // interrupt fifo quater full
+#define    ADC_INTR_EOC_BITS   0x1     // interrupt end of conversion
+#define    ADC_INTR_EOSCAN_BITS        0x2     // interrupt end of scan
+#define    ADC_INTR_EOSEQ_BITS 0x3     // interrupt end of sequence (probably wont use this it's pretty fancy)
+#define HW_CONFIG_REG  0x2     // hardware config register
+#define    HW_CONFIG_DUMMY_BITS        0x2400  // bits that don't do anything yet but are given default values
+#define    HW_CONFIG_DUMMY_BITS_6402   0x0400  // dummy bits in 6402 manual are slightly different, probably doesn't matter
+#define    EXT_QUEUE   0x200   // use external channel/gain queue (more versatile than internal queue)
+#define FIFO_SIZE_REG  0x4     // allows adjustment of fifo sizes, we will always use maximum
+#define    FIFO_SIZE_DUMMY_BITS        0xf038  // bits that don't do anything yet but are given default values
+#define    ADC_FIFO_SIZE_MASK  0x7     // bits that set adc fifo size
+#define    ADC_FIFO_8K_BITS    0x0     // 8 kilosample adc fifo
+#define    DAC_FIFO_SIZE_MASK  0xf00   // bits that set dac fifo size
+#define    DAC_FIFO_16K_BITS 0x0
+#define ADC_CONTROL0_REG       0x10    // adc control register 0
+#define    TRIG1_FALLING_BIT   0x20    // trig 1 uses falling edge
+#define    ADC_EXT_CONV_FALLING_BIT    0x800   // external pacing uses falling edge
+#define    ADC_ENABLE_BIT      0x8000  // master adc enable
+#define ADC_CONTROL1_REG       0x12    // adc control register 1
+#define    ADC_CONTROL1_DUMMY_BITS     0x1     // dummy bits for adc control register 1
+#define    SW_NOGATE_BIT       0x40    // disables software gate of adc
+#define    ADC_MODE_BITS(x)    (((x) & 0xf) << 12)
+#define ADC_SAMPLE_INTERVAL_LOWER_REG  0x16    // lower 16 bits of sample interval counter
+#define ADC_SAMPLE_INTERVAL_UPPER_REG  0x18    // upper 8 bits of sample interval counter
+#define ADC_DELAY_INTERVAL_LOWER_REG   0x1a    // lower 16 bits of delay interval counter
+#define ADC_DELAY_INTERVAL_UPPER_REG   0x1c    // upper 8 bits of delay interval counter
+#define ADC_COUNT_LOWER_REG    0x1e    // lower 16 bits of hardware conversion/scan counter
+#define ADC_COUNT_UPPER_REG    0x20    // upper 8 bits of hardware conversion/scan counter
+#define ADC_START_REG  0x22    // software trigger to start aquisition
+#define ADC_CONVERT_REG        0x24    // initiates single conversion
+#define ADC_QUEUE_CLEAR_REG    0x26    // clears adc queue
+#define ADC_QUEUE_LOAD_REG     0x28    // loads adc queue
+#define    CHAN_BITS(x)        ((x) & 0x3f)
+#define    GAIN_BITS(x)        (((x) & 0x3) << 8)      // translates range index to gain bits
+#define    UNIP_BIT(x) (((x) & 0x4) << 11)     // translates range index to unipolar/bipolar bit
+#define    SE_BIT      0x1000  // single-ended/ differential bit
+#define    QUEUE_EOSEQ_BIT     0x4000  // queue end of sequence
+#define    QUEUE_EOSCAN_BIT    0x8000  // queue end of scan
+#define ADC_BUFFER_CLEAR_REG   0x2a
+#define ADC_QUEUE_HIGH_REG     0x2c    // high channel for internal queue, use CHAN_BITS() macro above
+#define DAC_CONTROL0_REG       0x50    // dac control register 0
+#define    DAC_ENABLE_BIT      0x8000  // dac controller enable bit
+#define DAC_CONTROL1_REG       0x52    // dac control register 0
+#define    DAC_RANGE_BITS(channel, range)      (((range) & 0x3) << (2 * ((channel) & 0x1)))
+#define    DAC_OUTPUT_ENABLE_BIT       0x80    // dac output enable bit
+#define DAC_BUFFER_CLEAR_REG 0x66      // clear dac buffer
+#define DAC_CONVERT_REG(channel)       ((0x70) + (2 * ((channel) & 0x1)))
+// read-only
+#define HW_STATUS_REG  0x0     // hardware status register, reading this apparently clears pending interrupts as well
+#define   DAC_UNDERRUN_BIT     0x1
+#define   ADC_OVERRUN_BIT 0x2
+#define   DAC_ACTIVE_BIT       0x4
+#define   ADC_ACTIVE_BIT       0x8
+#define   DAC_INTR_PENDING_BIT 0x10
+#define   ADC_INTR_PENDING_BIT 0x20
+#define   DAC_DONE_BIT 0x40
+#define   ADC_DONE_BIT 0x80
+#define   EXT_INTR_PENDING_BIT 0x100
+#define   ADC_STOP_BIT 0x200
+#define   PIPE_FULL_BIT(x)     (0x400 << ((x) & 0x1))
+#define   HW_REVISION(x)       (((x) >> 12) & 0xf)
+#define PIPE1_READ_REG 0x4
+// read-write
+#define ADC_QUEUE_FIFO_REG     0x100   // external channel/gain queue, uses same bits as ADC_QUEUE_LOAD_REG
+#define ADC_FIFO_REG 0x200     // adc data fifo
+
+// devpriv->dio_counter_iobase registers
+#define DIO_8255_OFFSET        0x0
+#define DO_REG 0x20
+#define DI_REG 0x28
+
+// analog input ranges for most boards
+static comedi_lrange ai_ranges =
+{
+       8,
+       {
+               BIP_RANGE(10),
+               BIP_RANGE(5),
+               BIP_RANGE(2.5),
+               BIP_RANGE(1.25),
+               UNI_RANGE(10),
+               UNI_RANGE(5),
+               UNI_RANGE(2.5),
+               UNI_RANGE(1.25)
+       }
+};
+
+// analog output ranges
+static comedi_lrange ao_ranges =
+{
+       4,
+       {
+               BIP_RANGE(5),
+               BIP_RANGE(10),
+               UNI_RANGE(5),
+               UNI_RANGE(10),
+       }
+};
+
+typedef struct pcidas64_board_struct
+{
+       char *name;
+       int device_id;  // pci device id
+       int ai_se_chans;        // number of ai inputs in single-ended 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_scan_speed;      // analog output speed (for a scan, not conversion)
+} pcidas64_board;
+
+static pcidas64_board pcidas64_boards[] =
+{
+       {
+               name:           "pci-das6402/16",
+               device_id:      0x1d,
+               ai_se_chans:    64,
+               ai_bits:        16,
+               ai_speed:       5000,
+               ao_nchan: 2,
+               ao_scan_speed:  10000,
+       },
+       {
+               name:           "pci-das6402/12",       // XXX check
+               device_id:      0x1e,
+               ai_se_chans:    64,
+               ai_bits:        12,
+               ai_speed:       5000,
+               ao_nchan: 2,
+               ao_scan_speed:  10000,
+       },
+       {
+               name:           "pci-das64/m1/16",
+               device_id:      0x35,
+               ai_se_chans:    64,
+               ai_bits:        16,
+               ai_speed:       1000,
+               ao_nchan: 2,
+               ao_scan_speed:  10000,
+       },
+       {
+               name:           "pci-das64/m2/16",
+               device_id:      0x36,
+               ai_se_chans:    64,
+               ai_bits:        16,
+               ai_speed:       500,
+               ao_nchan: 2,
+               ao_scan_speed:  10000,
+       },
+       {
+               name:           "pci-das64/m3/16",
+               device_id:      0x37,
+               ai_se_chans:    64,
+               ai_bits:        16,
+               ai_speed:       333,
+               ao_nchan: 2,
+               ao_scan_speed:  10000,
+       },
+
+#if 0
+       {
+               name:           "pci-das6402/16/jr",
+               device_id:      0 // XXX,
+               ai_se_chans:    64,
+               ai_bits:        16,
+               ai_speed:       5000,
+               ao_nchan: 0,
+               ao_scan_speed:  10000,
+       },
+       {
+               name:           "pci-das64/m1/16/jr",
+               device_id:      0 // XXX,
+               ai_se_chans:    64,
+               ai_bits:        16,
+               ai_speed:       1000,
+               ao_nchan: 0,
+               ao_scan_speed:  10000,
+       },
+       {
+               name:           "pci-das64/m2/16/jr",
+               device_id:      0 // XXX,
+               ai_se_chans:    64,
+               ai_bits:        16,
+               ai_speed:       500,
+               ao_nchan: 0,
+               ao_scan_speed:  10000,
+       },
+       {
+               name:           "pci-das64/m3/16/jr",
+               device_id:      0 // XXX,
+               ai_se_chans:    64,
+               ai_bits:        16,
+               ai_speed:       333,
+               ao_nchan: 0,
+               ao_scan_speed:  10000,
+       },
+       {
+               name:           "pci-das64/m1/14",
+               device_id:      0,      // XXX
+               ai_se_chans:    64,
+               ai_bits:        14,
+               ai_speed:       1000,
+               ao_nchan: 2,
+               ao_scan_speed:  10000,
+       },
+       {
+               name:           "pci-das64/m2/14",
+               device_id:      0,      // XXX
+               ai_se_chans:    64,
+               ai_bits:        14,
+               ai_speed:       500,
+               ao_nchan: 2,
+               ao_scan_speed:  10000,
+       },
+       {
+               name:           "pci-das64/m3/14",
+               device_id:      0,      // XXX
+               ai_se_chans:    64,
+               ai_bits:        14,
+               ai_speed:       333,
+               ao_nchan: 2,
+               ao_scan_speed:  10000,
+       },
+#endif
+
+};
+// Number of boards in cb_pcidas_boards
+#define N_BOARDS       (sizeof(pcidas64_boards) / sizeof(pcidas64_board))
+
+static struct pci_device_id pcidas64_pci_table[] __devinitdata = {
+       { PCI_VENDOR_ID_CB, 0x001d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+       { PCI_VENDOR_ID_CB, 0x001e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+       { PCI_VENDOR_ID_CB, 0x0035, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+       { PCI_VENDOR_ID_CB, 0x0036, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+       { PCI_VENDOR_ID_CB, 0x0037, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+       { 0 }
+};
+MODULE_DEVICE_TABLE(pci, pcidas64_pci_table);
+
+/*
+ * Useful for shorthand access to the particular board structure
+ */
+#define thisboard ((pcidas64_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
+{
+       // base addresses (physical)
+       unsigned long plx9080_phys_iobase;
+       unsigned long main_phys_iobase;
+       unsigned long dio_counter_phys_iobase;
+       // base addresses (ioremapped)
+       unsigned long plx9080_iobase;
+       unsigned long main_iobase;
+       unsigned long dio_counter_iobase;
+       // divisor of master clock for analog input pacing
+       unsigned int ai_divisor;
+       volatile unsigned int ai_count; // number of analog input samples remaining
+       // divisors of master clock for analog output pacing
+       unsigned int ao_divisor;
+       volatile unsigned int ao_count; // number of analog output samples remaining
+       unsigned int ao_value[2];       // remember what the analog outputs are set to, to allow readback
+       unsigned int hw_revision;       // stc chip hardware revision number
+       unsigned int do_bits;   // remember digital ouput levels
+} pcidas64_private;
+
+/*
+ * most drivers define the following macro to make it easy to
+ * access the private structure.
+ */
+#define devpriv ((pcidas64_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 attach(comedi_device *dev,comedi_devconfig *it);
+static int detach(comedi_device *dev);
+comedi_driver driver_cb_pcidas={
+       driver_name:    "cb_pcidas64",
+       module:         THIS_MODULE,
+       attach:         attach,
+       detach:         detach,
+};
+
+static int ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
+static int ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
+static int ao_readback_insn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
+static int ai_cmd(comedi_device *dev,comedi_subdevice *s);
+static int ai_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd);
+//static int ao_cmd(comedi_device *dev,comedi_subdevice *s);
+//static int ao_inttrig(comedi_device *dev, comedi_subdevice *subdev, unsigned int trig_num);
+//static int ao_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd);
+static void handle_interrupt(int irq, void *d, struct pt_regs *regs);
+static int ai_cancel(comedi_device *dev, comedi_subdevice *s);
+//static int ao_cancel(comedi_device *dev, comedi_subdevice *s);
+static int dio_callback(int dir, int port, int data, void *arg);
+static int di_rbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int do_wbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static void check_adc_timing(comedi_cmd *cmd);
+static unsigned int get_divisor(unsigned int ns, unsigned int flags);
+
+/*
+ * A convenient macro that defines init_module() and cleanup_module(),
+ * as necessary.
+ */
+COMEDI_INITCLEANUP(driver_cb_pcidas);
+
+/*
+ * Attach is called by the Comedi core to configure the driver
+ * for a particular board.
+ */
+static int attach(comedi_device *dev, comedi_devconfig *it)
+{
+       comedi_subdevice *s;
+       struct pci_dev* pcidev;
+       int index;
+       // base addresses
+       unsigned long plx9080_iobase;
+       unsigned long main_iobase;
+       unsigned long dio_counter_iobase;
+
+       printk("comedi%d: cb_pcidas64\n",dev->minor);
+
+/*
+ * Allocate the private structure area.
+ */
+       if(alloc_private(dev,sizeof(pcidas64_private)) < 0)
+               return -ENOMEM;
+
+/*
+ * Probe the device to determine what device in the series it is.
+ */
+
+       pci_for_each_dev(pcidev)
+       {
+               // is it not a computer boards card?
+               if(pcidev->vendor != PCI_VENDOR_ID_CB)
+                       continue;
+#ifdef PCIDAS64_DEBUG
+               printk(" found computer boards device id 0x%x on bus %i slot %i\n",
+                       pcidev->device, pcidev->bus->number, PCI_SLOT(pcidev->devfn));
+#endif
+               // loop through cards supported by this driver
+               for(index = 0; index < N_BOARDS; index++)
+               {
+                       if(pcidas64_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;
+                               }
+                       }
+                       dev->board_ptr = pcidas64_boards + index;
+                       goto found;
+               }
+       }
+
+       printk("No supported ComputerBoards/MeasurementComputing card found\n");
+       return -EIO;
+
+found:
+
+       printk("Found %s on bus %i, slot %i\n", pcidas64_boards[index].name,
+               pcidev->bus->number, PCI_SLOT(pcidev->devfn));
+
+       //Initialize dev->board_name
+       dev->board_name = thisboard->name;
+
+       /* Initialize devpriv->control_status and devpriv->adc_fifo to point to
+        * their base address.
+        */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0)
+       plx9080_iobase =
+               pcidev->base_address[PLX9080_BADRINDEX] &
+               PCI_BASE_ADDRESS_MEM_MASK;
+       main_iobase =
+               pcidev->base_address[MAIN_BADRINDEX] &
+               PCI_BASE_ADDRESS_MEM_MASK;
+       dio_counter_iobase =
+               pcidev->base_address[DIO_COUNTER_BADRINDEX] &
+               PCI_BASE_ADDRESS_MEM_MASK;
+#else
+       if(pci_enable_device(pcidev))
+               return -EIO;
+       pci_set_master(pcidev);
+       plx9080_iobase =
+               pcidev->resource[PLX9080_BADRINDEX].start &
+               PCI_BASE_ADDRESS_MEM_MASK;
+       main_iobase =
+               pcidev->resource[MAIN_BADRINDEX].start &
+               PCI_BASE_ADDRESS_MEM_MASK;
+       dio_counter_iobase =
+               pcidev->resource[DIO_COUNTER_BADRINDEX].start &
+               PCI_BASE_ADDRESS_MEM_MASK;
+
+       if(check_mem_region(plx9080_iobase, PLX9080_IOSIZE))
+       {
+               /* Couldn't allocate io space */
+               printk(KERN_WARNING "couldn't allocate IO space\n");
+               return -EIO;
+       }
+       if(check_mem_region(main_iobase, MAIN_IOSIZE))
+       {
+               /* Couldn't allocate io space */
+               printk(KERN_WARNING "couldn't allocate IO space\n");
+               return -EIO;
+       }
+       if(check_mem_region(dio_counter_iobase, DIO_COUNTER_IOSIZE))
+       {
+               /* Couldn't allocate io space */
+               printk(KERN_WARNING "couldn't allocate IO space\n");
+               return -EIO;
+       }
+
+       request_mem_region(plx_iobase, PLX9080_IOSIZE, "cb_pcidas64");
+       devpriv->plx9080_phys_iobase = dio_counter_iobase;
+       request_mem_region(main_iobase, MAIN_IOSIZE, "cb_pcidas64");
+       devpriv->main_phys_iobase = dio_counter_iobase;
+       request_mem_region(dio_counter_iobase, DIO_COUNTER_IOSIZE, "cb_pcidas64");
+       devpriv->dio_counter_phys_iobase = dio_counter_iobase;
+
+#endif
+
+       // remap, won't work with 2.0 kernels but who cares
+       devpriv->plx9080_iobase = (unsigned long)ioremap(plx9080_iobase, PLX9080_IOSIZE);
+       devpriv->main_iobase = (unsigned long)ioremap(main_iobase, MAIN_IOSIZE);
+       devpriv->dio_counter_iobase = (unsigned long)ioremap(dio_counter_iobase, DIO_COUNTER_IOSIZE);
+
+       devpriv->hw_revision = HW_REVISION(readw(devpriv->main_iobase + HW_STATUS_REG));
+
+       // get irq
+       if(comedi_request_irq(pcidev->irq, handle_interrupt, SA_SHIRQ, "cb_pcidas64", dev ))
+       {
+               printk(" unable to allocate irq %d\n", pcidev->irq);
+               return -EINVAL;
+       }
+       dev->irq = pcidev->irq;
+
+#ifdef PCIDAS64_DEBUG
+
+printk(" plx9080 phys io addr 0x%lx\n", devpriv->plx9080_phys_iobase);
+printk(" main phys io addr 0x%lx\n", devpriv->main_phys_iobase);
+printk(" diocounter phys io addr 0x%lx\n", devpriv->dio_counter_phys_iobase);
+printk(" irq %i\n", dev->irq);
+
+printk(" plx9080 virt io addr 0x%lx\n", devpriv->plx9080_iobase);
+printk(" main virt io addr 0x%lx\n", devpriv->main_iobase);
+printk(" diocounter virt io addr 0x%lx\n", devpriv->dio_counter_iobase);
+printk(" irq %i\n", dev->irq);
+
+printk(" stc hardware revision %i\n", devpriv->hw_revision);
+
+// plx9080 dump
+printk(" plx interrupt status 0x%x\n", readl(devpriv->plx9080_iobase + PLX_INTRCS_REG));
+printk(" plx id bits 0x%x\n", readl(devpriv->plx9080_iobase + PLX_ID_REG));
+printk(" plx hardware revision 0x%x\n", readl(devpriv->plx9080_iobase + PLX_REVISION_REG));
+printk(" plx dma channel 0 mode 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_MODE_REG));
+printk(" plx dma channel 0 pci address 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG));
+printk(" plx dma channel 0 local address 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_LOCAL_ADDRESS_REG));
+printk(" plx dma channel 0 transfer size 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_TRANSFER_SIZE_REG));
+printk(" plx dma channel 0 descriptor 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG));
+printk(" plx dma channel 0 command status 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_CS_REG));
+printk(" plx dma channel 0 threshold 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_THRESHOLD_REG));
+
+#endif
+
+
+/*
+ * Allocate the subdevice structures.
+ */
+       dev->n_subdevices = 7;
+       if(alloc_subdevices(dev)<0)
+               return -ENOMEM;
+
+       s = dev->subdevices + 0;
+       /* analog input subdevice */
+       dev->read_subdev = s;
+       s->type = COMEDI_SUBD_AI;
+       s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF;
+       /* XXX Number of inputs in differential mode is ignored */
+       s->n_chan = thisboard->ai_se_chans;
+       s->len_chanlist = 8092;
+       s->maxdata = (1 << thisboard->ai_bits) - 1;
+       s->range_table = &ai_ranges;
+       s->insn_read = ai_rinsn;
+       s->do_cmd = ai_cmd;
+       s->do_cmdtest = ai_cmdtest;
+       s->cancel = ai_cancel;
+
+       /* analog output subdevice */
+       s = dev->subdevices + 1;
+       if(thisboard->ao_nchan)
+       {
+       //      dev->write_subdev = s;
+               s->type = COMEDI_SUBD_AO;
+               s->subdev_flags = SDF_READABLE | SDF_WRITEABLE | SDF_GROUND;
+               s->n_chan = thisboard->ao_nchan;
+               // analog out resolution is the same as analog input resolution, so use ai_bits
+               s->maxdata = (1 << thisboard->ai_bits) - 1;
+               s->range_table = &ao_ranges;
+               s->insn_read = ao_readback_insn;
+               s->insn_write = ao_winsn;
+       //      s->do_cmdtest = ao_cmdtest;
+       //      s->do_cmd = ao_cmd;
+       //      s->len_chanlist = thisboard->ao_nchan;
+       //      s->cancel = ao_cancel;
+       } else
+       {
+               s->type = COMEDI_SUBD_UNUSED;
+       }
+
+       // digital input
+       s = dev->subdevices + 2;
+       s->type=COMEDI_SUBD_DI;
+       s->subdev_flags = SDF_READABLE;
+       s->n_chan = 4;
+       s->maxdata = 1;
+       s->range_table = &range_digital;
+       s->insn_bits = di_rbits;
+
+       // digital output
+       s = dev->subdevices + 3;
+       s->type=COMEDI_SUBD_DO;
+       s->subdev_flags = SDF_WRITEABLE | SDF_READABLE;
+       s->n_chan = 4;
+       s->maxdata = 1;
+       s->range_table = &range_digital;
+       s->insn_bits = do_wbits;
+
+       /* 8255 */
+       s = dev->subdevices + 4;
+       subdev_8255_init(dev, s, dio_callback,
+               (void*) (devpriv->dio_counter_iobase + DIO_8255_OFFSET));
+
+       // user counter subd XXX
+       s = dev->subdevices + 5;
+       s->type = COMEDI_SUBD_UNUSED;
+
+       // calibration subd XXX
+       s = dev->subdevices + 6;
+       s->type = COMEDI_SUBD_UNUSED;
+
+       return 0;
+}
+
+
+/*
+ * _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 detach(comedi_device *dev)
+{
+       printk("comedi%d: cb_pcidas: remove\n",dev->minor);
+
+       if(devpriv)
+       {
+               if(devpriv->plx9080_iobase)
+                       iounmap((void*)devpriv->plx9080_iobase);
+               if(devpriv->main_iobase)
+                       iounmap((void*)devpriv->main_iobase);
+               if(devpriv->dio_counter_iobase)
+                       iounmap((void*)devpriv->dio_counter_iobase);
+               if(devpriv->plx9080_phys_iobase)
+                       release_mem_region(devpriv->plx9080_iobase, PLX9080_IOSIZE);
+               if(devpriv->main_iobase)
+                       release_mem_region(devpriv->main_phys_iobase, MAIN_IOSIZE);
+               if(devpriv->dio_counter_iobase)
+                       release_mem_region(devpriv->dio_counter_phys_iobase, DIO_COUNTER_IOSIZE);
+       }
+       if(dev->irq)
+               comedi_free_irq(dev->irq, dev);
+       if(dev->subdevices)
+               subdev_8255_cleanup(dev,dev->subdevices + 4);
+
+       return 0;
+}
+
+static int ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+       unsigned int bits, n, i;
+       const int timeout = 1000;
+
+       // disable card's interrupt sources
+       writew(0, devpriv->main_iobase + INTR_ENABLE_REG);
+
+       /* disable pacing, triggering, etc */
+       writew(ADC_ENABLE_BIT, devpriv->main_iobase + ADC_CONTROL0_REG);
+       writew(ADC_CONTROL1_DUMMY_BITS, devpriv->main_iobase + ADC_CONTROL1_REG);
+
+       // use internal queue
+       writew(HW_CONFIG_DUMMY_BITS, devpriv->main_iobase + HW_CONFIG_REG);
+
+       // load internal queue
+       bits = 0;
+       // set channel
+       bits |= CHAN_BITS(CR_CHAN(insn->chanspec));
+       // set gain
+       bits |= GAIN_BITS(CR_RANGE(insn->chanspec));
+       // set unipolar / bipolar
+       bits |= UNIP_BIT(CR_RANGE(insn->chanspec));
+       // set single-ended / differential
+       if(CR_AREF(insn->chanspec) != AREF_DIFF)
+               bits |= SE_BIT;
+       // set stop channel
+       writew(CHAN_BITS(CR_CHAN(insn->chanspec)), devpriv->main_iobase + ADC_QUEUE_HIGH_REG);
+       // set start channel, and rest of settings
+       writew(bits, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
+
+       // clear adc buffer
+       writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);
+
+       for(n = 0; n < insn->n; n++)
+       {
+               /* trigger conversion */
+               writew(0, devpriv->main_iobase + ADC_CONVERT_REG);
+
+               // wait for data
+               for(i = 0; i < timeout; i++)
+               {
+                       if(!(readw(devpriv->main_iobase + HW_STATUS_REG) & ADC_ACTIVE_BIT))
+                               break;
+               }
+               if(i == timeout)
+               {
+                       comedi_error(dev, " analog input read insn timed out");
+                       return -ETIME;
+               }
+               data[n] = readw(devpriv->main_iobase + PIPE1_READ_REG);
+       }
+
+       return n;
+}
+
+static int ai_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd)
+{      int err = 0;
+       int tmp;
+       unsigned int tmp_arg, tmp_arg2;
+       int i;
+       int aref;
+
+       /* step 1: make sure trigger sources are trivially valid */
+
+       tmp = cmd->start_src;
+       cmd->start_src &= TRIG_NOW | TRIG_EXT;
+       if(!cmd->start_src || tmp != cmd->start_src) err++;
+
+       tmp = cmd->scan_begin_src;
+       cmd->scan_begin_src &= TRIG_TIMER | TRIG_FOLLOW;
+       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_EXT | 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 */
+
+       // uniqueness check
+       if(cmd->start_src != TRIG_NOW &&
+               cmd->start_src != TRIG_EXT) err++;
+       if(cmd->scan_begin_src != TRIG_TIMER &&
+               cmd->scan_begin_src != TRIG_FOLLOW) err++;
+       if(cmd->convert_src != TRIG_TIMER &&
+               cmd->convert_src != TRIG_EXT) err++;
+       if(cmd->stop_src != TRIG_COUNT &&
+               cmd->stop_src != TRIG_NONE &&
+               cmd->stop_src != TRIG_EXT) err++;
+
+       // compatibility check
+       if(cmd->convert_src == TRIG_EXT &&
+               cmd->scan_begin_src == TRIG_TIMER)
+               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->convert_src == TRIG_TIMER)
+       {
+               if(cmd->convert_arg < thisboard->ai_speed)
+               {
+                       cmd->convert_arg = thisboard->ai_speed;
+                       err++;
+               }
+               if(cmd->scan_begin_src == TRIG_TIMER)
+               {
+                       // if scans are timed faster than conversion rate allows
+                       if(cmd->convert_arg * cmd->chanlist_len > cmd->scan_begin_arg)
+                       {
+                               cmd->scan_begin_arg = cmd->convert_arg * cmd->chanlist_len;
+                               err++;
+                       }
+               }
+       }
+
+       if(!cmd->chanlist_len)
+       {
+               cmd->chanlist_len = 1;
+               err++;
+       }
+       if(cmd->scan_end_arg != cmd->chanlist_len)
+       {
+               cmd->scan_end_arg = cmd->chanlist_len;
+               err++;
+       }
+
+       switch(cmd->stop_src)
+       {
+               case TRIG_EXT:
+                       if(cmd->stop_arg)
+                       {
+                               cmd->stop_arg = 0;
+                               err++;
+                       }
+                       break;
+               case TRIG_COUNT:
+                       if(!cmd->stop_arg)
+                       {
+                               cmd->stop_arg = 1;
+                               err++;
+                       }
+                       break;
+               case TRIG_NONE:
+                       if(cmd->stop_arg != 0)
+                       {
+                               cmd->stop_arg = 0;
+                               err++;
+                       }
+                       break;
+               default:
+                       break;
+       }
+
+       if(err) return 3;
+
+       /* step 4: fix up any arguments */
+
+       if(cmd->convert_src == TRIG_TIMER)
+       {
+               tmp_arg = cmd->convert_arg;
+               tmp_arg2 = cmd->scan_begin_arg;
+               check_adc_timing(cmd);
+               if(tmp_arg != cmd->convert_arg) err++;
+               if(tmp_arg2 != cmd->scan_begin_arg) err++;
+       }
+
+       if(err) return 4;
+
+       // make sure user is doesn't change analog reference mid chanlist
+       if(cmd->chanlist)
+       {
+               aref = CR_AREF(cmd->chanlist[0]);
+               for(i = 1; i < cmd->chanlist_len; i++)
+               {
+                       if(aref != CR_AREF(cmd->chanlist[i]))
+                       {
+                               comedi_error(dev, "all elements in chanlist must use the same analog reference");
+                               err++;
+                               break;
+                       }
+               }
+       }
+
+       if(err) return 5;
+
+       return 0;
+}
+
+static int ai_cmd(comedi_device *dev,comedi_subdevice *s)
+{
+       comedi_async *async = s->async;
+       comedi_cmd *cmd = &async->cmd;
+       unsigned int bits;
+       unsigned int convert_counter_value;
+       unsigned int scan_counter_value;
+       unsigned int i;
+
+       // disable card's interrupt sources
+       writew(0, devpriv->main_iobase + INTR_ENABLE_REG);
+
+       /* disable pacing, triggering, etc */
+       writew(0, devpriv->main_iobase + ADC_CONTROL0_REG);
+       writew(ADC_CONTROL1_DUMMY_BITS, devpriv->main_iobase + ADC_CONTROL1_REG);
+
+       // use external queue
+       writew(EXT_QUEUE | HW_CONFIG_DUMMY_BITS, devpriv->main_iobase + HW_CONFIG_REG);
+
+       // set fifo size
+       writew(ADC_FIFO_8K_BITS | FIFO_SIZE_DUMMY_BITS, devpriv->main_iobase + FIFO_SIZE_REG);
+
+       // set conversion pacing
+       if(cmd->convert_src == TRIG_TIMER)
+       {
+               check_adc_timing(cmd);
+               // supposed to load counter with desired divisor minus 3
+               convert_counter_value = cmd->convert_arg / TIMER_BASE - 3;
+               // load lower 16 bits
+               writew(convert_counter_value & 0xffff, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG);
+               // load upper 8 bits
+               writew((convert_counter_value >> 16) & 0xff, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
+               // set scan pacing
+               if(cmd->convert_src == TRIG_TIMER)
+               {
+                       // figure out how long we need to delay at end of scan
+                       scan_counter_value = (cmd->scan_begin_arg - (cmd->convert_arg * (cmd->chanlist_len - 1)))
+                               / TIMER_BASE;
+                       // load lower 16 bits
+                       writew(scan_counter_value & 0xffff, devpriv->main_iobase + ADC_DELAY_INTERVAL_LOWER_REG);
+                       // load upper 8 bits
+                       writew((scan_counter_value >> 16) & 0xff, devpriv->main_iobase + ADC_DELAY_INTERVAL_UPPER_REG);
+               }
+       }
+
+       // load hardware conversion counter with non-zero value so it doesn't mess with us
+       writew(~0, devpriv->main_iobase + ADC_COUNT_LOWER_REG);
+
+       // set software count
+       if(cmd->stop_src == TRIG_COUNT)
+               devpriv->ai_count = cmd->stop_arg * cmd->chanlist_len;
+
+       /* XXX cannot write to queue fifo while dac fifo is being written to
+        * ( need spinlock, or try to use internal queue instead */
+       // clear queue pointer
+       writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG);
+       // load external queue
+       for(i = 0; i < cmd->chanlist_len; i++)
+       {
+               bits = 0;
+               // set channel
+               bits |= CHAN_BITS(CR_CHAN(cmd->chanlist[i]));
+               // set gain
+               bits |= GAIN_BITS(CR_RANGE(cmd->chanlist[i]));
+               // set unipolar / bipolar
+               bits |= UNIP_BIT(CR_RANGE(cmd->chanlist[i]));
+               // set single-ended / differential
+               if(CR_AREF(cmd->chanlist[i]) != AREF_DIFF)
+                       bits |= SE_BIT;
+               // mark end of queue
+               if(i == cmd->chanlist_len - 1)
+                       bits |= QUEUE_EOSCAN_BIT | QUEUE_EOSEQ_BIT;
+               writew(bits, devpriv->main_iobase + ADC_QUEUE_FIFO_REG);
+       }
+       // prime queue holding register
+       writew(0, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
+
+       // clear adc buffer
+       writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);
+
+       // enable interrupts
+       bits = EN_ADC_OVERRUN_BIT | EN_ADC_DONE_INTR_BIT;
+       if(cmd->flags & TRIG_WAKE_EOS)
+               bits |= ADC_INTR_EOSCAN_BITS;
+       else
+               bits |= ADC_INTR_QFULL_BITS;    // for clairity only, since quarter-full bits are zero
+       writew(bits, devpriv->main_iobase + INTR_ENABLE_REG);
+       // enable interrupts on plx 9080 XXX enabling more interrupt sources than are actually used
+       bits = ICS_PIE | ICS_PLIE | ICS_PAIE | ICS_PDIE | ICS_LIE | ICS_LDIE | ICS_DMA0_E | ICS_DMA1_E;
+       writel(bits, devpriv->plx9080_iobase + PLX_INTRCS_REG);
+
+       // disable dma for now XXX
+       writeb(0, devpriv->plx9080_iobase + PLX_DMA0_CS_REG);
+       writeb(0, devpriv->plx9080_iobase + PLX_DMA1_CS_REG);
+
+       /* set mode, disable software conversion gate */
+       bits = ADC_CONTROL1_DUMMY_BITS | SW_NOGATE_BIT;
+       if(cmd->convert_src == TRIG_EXT)
+               bits |= ADC_MODE_BITS(13);      // good old mode 13
+       else
+               bits |= ADC_MODE_BITS(8);       // mode 8.  What else could you need?
+       writew(bits, devpriv->main_iobase + ADC_CONTROL1_REG);
+
+       /* enable pacing, triggering, etc */
+       bits = ADC_ENABLE_BIT;
+       writew(bits, devpriv->main_iobase + ADC_CONTROL0_REG);
+
+       // start aquisition
+       writew(0, devpriv->main_iobase + ADC_START_REG);
+
+       return 0;
+}
+
+static void handle_interrupt(int irq, void *d, struct pt_regs *regs)
+{
+#ifdef PCIDAS64_DEBUG
+#endif
+       comedi_device *dev = d;
+       comedi_subdevice *s = dev->read_subdev;
+       comedi_async *async = s->async;
+       comedi_cmd *cmd = &async->cmd;
+       unsigned int num_samples = 0;
+       unsigned int i;
+       u16 data;
+       unsigned int status;
+       u32 plx_status;
+       u32 plx_bits;
+#ifdef PCIDAS64_DEBUG
+       static unsigned int intr_count = 0;
+       const int debug_count = 10;
+#endif
+
+       status = readw(devpriv->main_iobase + HW_STATUS_REG);
+       plx_status = readl(devpriv->plx9080_iobase + PLX_INTRCS_REG);
+       if((status &
+               (ADC_INTR_PENDING_BIT | ADC_DONE_BIT | ADC_STOP_BIT |
+               DAC_INTR_PENDING_BIT | DAC_DONE_BIT | EXT_INTR_PENDING_BIT)) == 0 &&
+               (plx_status & (ICS_DMA0_A | ICS_DMA1_A | ICS_LDIA | ICS_LIA | ICS_PAIA | ICS_PDIA)) == 0)
+       {
+#ifdef PCIDAS64_DEBUG
+               intr_count++;
+               rt_printk(" cb_pcidas64 spurious interrupt");
+#endif
+               return;
+       }
+#ifdef PCIDAS64_DEBUG
+       intr_count++;
+       if(intr_count < debug_count)
+       {
+               rt_printk(" cb_pcidas64 interrupt status 0x%x\n", status);
+               rt_printk(" plx status 0x%x\n", plx_status);
+       }
+#endif
+
+       async->events = 0;
+
+       // if interrupt was due to analog input data being available
+       if(status & ADC_INTR_PENDING_BIT)
+       {
+               // figure out how many samples we should read from board's fifo
+               /* XXX should use ADC read/write pointer registers to figure out
+                * how many samples are actually in fifo */
+               if(cmd->flags & TRIG_WAKE_EOS)
+                       num_samples = cmd->chanlist_len;
+               else
+                       num_samples = QUARTER_AI_FIFO_SIZE;
+               if(cmd->stop_src == TRIG_COUNT)
+               {
+                       if(num_samples > devpriv->ai_count)
+                               num_samples = devpriv->ai_count;
+                       devpriv->ai_count -= num_samples;
+               }
+               // read samples
+               for(i = 0; i < num_samples; i++)
+               {
+                       data = readw(devpriv->main_iobase + ADC_FIFO_REG);
+                       comedi_buf_put(async, data);
+               }
+               async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
+       }
+
+       // check for fifo overrun
+       if(status & ADC_OVERRUN_BIT)
+       {
+               ai_cancel(dev, s);
+               async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+       }
+
+       // if we are have all the data, then quit
+       if(cmd->stop_src == TRIG_COUNT)
+       {
+               if(devpriv->ai_count <= 0)
+                       ai_cancel(dev, s);
+               async->events |= COMEDI_CB_EOA;
+       }
+
+       // clear possible plx9080 interrupt sources
+       if(plx_status & ICS_LDIA)
+       { // clear local doorbell interrupt
+               plx_bits = readl(devpriv->plx9080_iobase + PLX_DBR_OUT_REG);
+               writel(plx_bits, devpriv->plx9080_iobase + PLX_DBR_OUT_REG);
+#ifdef PCIDAS64_DEBUG
+               if(intr_count < debug_count)
+                       rt_printk(" cleared local doorbell bits 0x%x\n", plx_bits);
+#endif
+       }
+       if(plx_status & ICS_DMA0_A)
+       {       // dma chan 0 interrupt
+               writeb(PLX_CLEAR_DMA_INTR_BIT, devpriv->plx9080_iobase + PLX_DMA0_CS_REG);
+#ifdef PCIDAS64_DEBUG
+               if(intr_count < debug_count)
+                       rt_printk(" cleared dma ch0 interrupt\n");
+#endif
+       }
+       if(plx_status & ICS_DMA1_A)
+       {       // dma chan 1 interrupt
+               writeb(PLX_CLEAR_DMA_INTR_BIT, devpriv->plx9080_iobase + PLX_DMA1_CS_REG);
+#ifdef PCIDAS64_DEBUG
+               if(intr_count < debug_count)
+                       rt_printk(" cleared dma ch1 interrupt\n");
+#endif
+       }
+
+       comedi_event(dev, s, async->events);
+       return;
+}
+
+static int ai_cancel(comedi_device *dev, comedi_subdevice *s)
+{
+       /* disable pacing, triggering, etc */
+       writew(0, devpriv->main_iobase + ADC_CONTROL0_REG);
+       writew(ADC_CONTROL1_DUMMY_BITS, devpriv->main_iobase + ADC_CONTROL1_REG);
+
+       return 0;
+}
+
+static int ao_winsn(comedi_device *dev, comedi_subdevice *s,
+       comedi_insn *insn, lsampl_t *data)
+{
+       int chan = CR_CHAN(insn->chanspec);
+       int range = CR_RANGE(insn->chanspec);
+       unsigned int bits;
+
+       // do some initializing
+       writew(DAC_ENABLE_BIT, devpriv->main_iobase + DAC_CONTROL0_REG);
+
+       // set range
+       bits = DAC_OUTPUT_ENABLE_BIT;
+       bits |= DAC_RANGE_BITS(chan, range);
+       writew(bits, devpriv->main_iobase + DAC_CONTROL1_REG);
+
+       // clear buffer
+       writew(0, devpriv->main_iobase + DAC_BUFFER_CLEAR_REG);
+
+       // write to channel
+       writew(data[0], devpriv->main_iobase + DAC_CONVERT_REG(chan));
+
+       // remember output value
+       devpriv->ao_value[chan] = data[0];
+
+       return 1;
+}
+
+static int ao_readback_insn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+       data[0] = devpriv->ao_value[CR_CHAN(insn->chanspec)];
+
+       return 1;
+}
+
+static int dio_callback(int dir, int port, int data, void *arg)
+{
+       unsigned long iobase = (int)arg;
+
+       if(dir)
+       {
+               writeb(data, iobase + port);
+               return 0;
+       }else
+       {
+               return readb(iobase + port);
+       }
+}
+
+static int di_rbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)
+{
+       lsampl_t bits;
+
+       bits = readb(devpriv->dio_counter_iobase + DI_REG);
+       bits &= 0xf;
+       data[1] = bits;
+       data[0] = 0;
+
+       return 2;
+}
+
+static int do_wbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)
+{
+       lsampl_t wbits;
+
+       data[0] &= 0xf;
+       wbits = devpriv->do_bits;
+       // zero bits we are going to change
+       wbits &= ~data[0];
+       // set new bits
+       wbits |= data[0] & data[1];
+       devpriv->do_bits = wbits;
+
+       writeb(devpriv->do_bits, devpriv->dio_counter_iobase + DO_REG);
+
+       data[1] = wbits;
+
+       return 2;
+}
+
+/* utility function that rounds desired timing to an achievable time, and
+ * sets cmd members appropriately.
+ * adc paces conversions from master clock by dividing by (x + 3) where x is 24 bit number
+ */
+static void check_adc_timing(comedi_cmd *cmd)
+{
+       unsigned int convert_divisor, scan_divisor;
+       const int max_counter_value = 0xffffff; // board uses 24 bit counters for pacing
+       const int min_convert_divisor = 3;
+       const int max_convert_divisor = max_counter_value + min_convert_divisor;
+       unsigned long long max_scan_divisor, min_scan_divisor;
+
+       if(cmd->convert_src == TRIG_TIMER)
+       {
+               convert_divisor = get_divisor(cmd->convert_arg, cmd->flags);
+               if(convert_divisor > max_convert_divisor) convert_divisor = max_convert_divisor;
+               if(convert_divisor < min_convert_divisor) convert_divisor = min_convert_divisor;
+               cmd->convert_arg = convert_divisor * TIMER_BASE;
+
+               if(cmd->scan_begin_src == TRIG_TIMER)
+               {
+                       scan_divisor = get_divisor(cmd->scan_begin_arg, cmd->flags);
+                       // XXX check for integer overflows
+                       min_scan_divisor = convert_divisor * cmd->chanlist_len;
+                       max_scan_divisor = (convert_divisor * cmd->chanlist_len - 1) + max_counter_value;
+                       if(scan_divisor > max_scan_divisor) scan_divisor = max_scan_divisor;
+                       if(scan_divisor < min_scan_divisor) scan_divisor = min_scan_divisor;
+                       cmd->scan_begin_arg = scan_divisor * TIMER_BASE;
+               }
+       }
+
+       return;
+}
+
+/* Gets nearest achievable timing given master clock speed, does not
+ * take into account possible minimum/maximum divisor values.  Used
+ * by other timing checking functions. */
+static unsigned int get_divisor(unsigned int ns, unsigned int flags)
+{
+       unsigned int divisor;
+
+       switch(flags & TRIG_ROUND_MASK)
+       {
+               case TRIG_ROUND_UP:
+                       divisor = (ns + TIMER_BASE - 1) / TIMER_BASE;
+                       break;
+               case TRIG_ROUND_DOWN:
+                       divisor = ns / TIMER_BASE;
+                       break;
+               case TRIG_ROUND_NEAREST:
+               default:
+                       divisor = (ns + TIMER_BASE / 2) / TIMER_BASE;
+                       break;
+       }
+
+       return divisor;
+}