Staging: comedi: add rtd520 driver
authorDan Christian <dac@ptolemy.arc.nasa.gov>
Wed, 19 Nov 2008 22:21:25 +0000 (14:21 -0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 6 Jan 2009 21:52:26 +0000 (13:52 -0800)
This adds the rtd520 comedi driver to the build.

From: Dan Christian <dac@ptolemy.arc.nasa.gov>
Cc: David Schleef <ds@schleef.org>
Cc: Frank Mori Hess <fmhess@users.sourceforge.net>
Cc: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/staging/comedi/drivers/Makefile
drivers/staging/comedi/drivers/plx9080.h [new file with mode: 0644]
drivers/staging/comedi/drivers/rtd520.c [new file with mode: 0644]
drivers/staging/comedi/drivers/rtd520.h [new file with mode: 0644]

index cec0625..3dbd4f1 100644 (file)
@@ -11,6 +11,7 @@ obj-$(CONFIG_COMEDI)                  += comedi_parport.o
 obj-$(CONFIG_COMEDI_PCI_DRIVERS)       += mite.o
 obj-$(CONFIG_COMEDI_PCI_DRIVERS)       += icp_multi.o
 obj-$(CONFIG_COMEDI_PCI_DRIVERS)       += me4000.o
+obj-$(CONFIG_COMEDI_PCI_DRIVERS)       += rtd520.o
 obj-$(CONFIG_COMEDI_PCI_DRIVERS)       += s626.o
 
 # Comedi USB drivers
diff --git a/drivers/staging/comedi/drivers/plx9080.h b/drivers/staging/comedi/drivers/plx9080.h
new file mode 100644 (file)
index 0000000..a5a1a68
--- /dev/null
@@ -0,0 +1,429 @@
+/* plx9080.h
+ *
+ * Copyright (C) 2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * I modified this file from the plx9060.h header for the
+ * wanXL device driver in the linux kernel,
+ * for the register offsets and bit definitions.  Made minor modifications,
+ * added plx9080 registers and
+ * stripped out stuff that was specifically for the wanXL driver.
+ * Note: I've only made sure the definitions are correct as far
+ * as I make use of them.  There are still various plx9060-isms
+ * left in this header file.
+ *
+ ********************************************************************
+ *
+ * Copyright (C) 1999 RG Studio s.c., http://www.rgstudio.com.pl/
+ * Written by Krzysztof Halasa <khc@rgstudio.com.pl>
+ *
+ * Portions (C) SBE Inc., used by permission.
+ *
+ * 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.
+ */
+
+#ifndef __COMEDI_PLX9080_H
+#define __COMEDI_PLX9080_H
+
+// descriptor block used for chained dma transfers
+struct plx_dma_desc {
+       volatile uint32_t pci_start_addr;
+       volatile uint32_t local_start_addr;
+       /* transfer_size is in bytes, only first 23 bits of register are used */
+       volatile uint32_t transfer_size;
+       /* address of next descriptor (quad word aligned), plus some
+        * additional bits (see PLX_DMA0_DESCRIPTOR_REG) */
+       volatile uint32_t next;
+};
+
+/**********************************************************************
+**            Register Offsets and Bit Definitions
+**
+** Note: All offsets zero relative.  IE. Some standard base address
+** must be added to the Register Number to properly access the register.
+**
+**********************************************************************/
+
+#define PLX_LAS0RNG_REG         0x0000 /* L, Local Addr Space 0 Range Register */
+#define PLX_LAS1RNG_REG         0x00f0 /* L, Local Addr Space 1 Range Register */
+#define  LRNG_IO           0x00000001  /* Map to: 1=I/O, 0=Mem */
+#define  LRNG_ANY32        0x00000000  /* Locate anywhere in 32 bit */
+#define  LRNG_LT1MB        0x00000002  /* Locate in 1st meg */
+#define  LRNG_ANY64        0x00000004  /* Locate anywhere in 64 bit */
+#define  LRNG_MEM_MASK     0xfffffff0  // bits that specify range for memory io
+#define  LRNG_IO_MASK     0xfffffffa   // bits that specify range for normal io
+
+#define PLX_LAS0MAP_REG         0x0004 /* L, Local Addr Space 0 Remap Register */
+#define PLX_LAS1MAP_REG         0x00f4 /* L, Local Addr Space 1 Remap Register */
+#define  LMAP_EN           0x00000001  /* Enable slave decode */
+#define  LMAP_MEM_MASK     0xfffffff0  // bits that specify decode for memory io
+#define  LMAP_IO_MASK     0xfffffffa   // bits that specify decode bits for normal io
+
+/* Mode/Arbitration Register.
+*/
+#define PLX_MARB_REG         0x8       /* L, Local Arbitration Register */
+#define PLX_DMAARB_REG      0xac
+enum marb_bits {
+       MARB_LLT_MASK = 0x000000ff,     /* Local Bus Latency Timer */
+       MARB_LPT_MASK = 0x0000ff00,     /* Local Bus Pause Timer */
+       MARB_LTEN = 0x00010000, /* Latency Timer Enable */
+       MARB_LPEN = 0x00020000, /* Pause Timer Enable */
+       MARB_BREQ = 0x00040000, /* Local Bus BREQ Enable */
+       MARB_DMA_PRIORITY_MASK = 0x00180000,
+       MARB_LBDS_GIVE_UP_BUS_MODE = 0x00200000,        /* local bus direct slave give up bus mode */
+       MARB_DS_LLOCK_ENABLE = 0x00400000,      /* direct slave LLOCKo# enable */
+       MARB_PCI_REQUEST_MODE = 0x00800000,
+       MARB_PCIv21_MODE = 0x01000000,  /* pci specification v2.1 mode */
+       MARB_PCI_READ_NO_WRITE_MODE = 0x02000000,
+       MARB_PCI_READ_WITH_WRITE_FLUSH_MODE = 0x04000000,
+       MARB_GATE_TIMER_WITH_BREQ = 0x08000000, /* gate local bus latency timer with BREQ */
+       MARB_PCI_READ_NO_FLUSH_MODE = 0x10000000,
+       MARB_USE_SUBSYSTEM_IDS = 0x20000000,
+};
+
+#define PLX_BIGEND_REG 0xc
+enum bigend_bits {
+       BIGEND_CONFIG = 0x1,    /* use big endian ordering for configuration register accesses */
+       BIGEND_DIRECT_MASTER = 0x2,
+       BIGEND_DIRECT_SLAVE_LOCAL0 = 0x4,
+       BIGEND_ROM = 0x8,
+       BIGEND_BYTE_LANE = 0x10,        /* use byte lane consisting of most significant bits instead of least significant */
+       BIGEND_DIRECT_SLAVE_LOCAL1 = 0x20,
+       BIGEND_DMA1 = 0x40,
+       BIGEND_DMA0 = 0x80,
+};
+
+/* Note: The Expansion ROM  stuff is only relevant to the PC environment.
+**       This expansion ROM code is executed by the host CPU at boot time.
+**       For this reason no bit definitions are provided here.
+*/
+#define PLX_ROMRNG_REG         0x0010  /* L, Expn ROM Space Range Register */
+#define PLX_ROMMAP_REG         0x0014  /* L, Local Addr Space Range Register */
+
+#define PLX_REGION0_REG         0x0018 /* L, Local Bus Region 0 Descriptor */
+#define  RGN_WIDTH         0x00000002  /* Local bus width bits */
+#define  RGN_8BITS         0x00000000  /* 08 bit Local Bus */
+#define  RGN_16BITS        0x00000001  /* 16 bit Local Bus */
+#define  RGN_32BITS        0x00000002  /* 32 bit Local Bus */
+#define  RGN_MWS           0x0000003C  /* Memory Access Wait States */
+#define  RGN_0MWS          0x00000000
+#define  RGN_1MWS          0x00000004
+#define  RGN_2MWS          0x00000008
+#define  RGN_3MWS          0x0000000C
+#define  RGN_4MWS          0x00000010
+#define  RGN_6MWS          0x00000018
+#define  RGN_8MWS          0x00000020
+#define  RGN_MRE           0x00000040  /* Memory Space Ready Input Enable */
+#define  RGN_MBE           0x00000080  /* Memory Space Bterm Input Enable */
+#define  RGN_READ_PREFETCH_DISABLE 0x00000100
+#define  RGN_ROM_PREFETCH_DISABLE 0x00000200
+#define  RGN_READ_PREFETCH_COUNT_ENABLE 0x00000400
+#define  RGN_RWS           0x003C0000  /* Expn ROM Wait States */
+#define  RGN_RRE           0x00400000  /* ROM Space Ready Input Enable */
+#define  RGN_RBE           0x00800000  /* ROM Space Bterm Input Enable */
+#define  RGN_MBEN          0x01000000  /* Memory Space Burst Enable */
+#define  RGN_RBEN          0x04000000  /* ROM Space Burst Enable */
+#define  RGN_THROT         0x08000000  /* De-assert TRDY when FIFO full */
+#define  RGN_TRD           0xF0000000  /* Target Ready Delay /8 */
+
+#define PLX_REGION1_REG         0x00f8 /* L, Local Bus Region 1 Descriptor */
+
+#define PLX_DMRNG_REG          0x001C  /* L, Direct Master Range Register */
+
+#define PLX_LBAPMEM_REG        0x0020  /* L, Lcl Base Addr for PCI mem space */
+
+#define PLX_LBAPIO_REG         0x0024  /* L, Lcl Base Addr for PCI I/O space */
+
+#define PLX_DMMAP_REG          0x0028  /* L, Direct Master Remap Register */
+#define  DMM_MAE           0x00000001  /* Direct Mstr Memory Acc Enable */
+#define  DMM_IAE           0x00000002  /* Direct Mstr I/O Acc Enable */
+#define  DMM_LCK           0x00000004  /* LOCK Input Enable */
+#define  DMM_PF4           0x00000008  /* Prefetch 4 Mode Enable */
+#define  DMM_THROT         0x00000010  /* Assert IRDY when read FIFO full */
+#define  DMM_PAF0          0x00000000  /* Programmable Almost fill level */
+#define  DMM_PAF1          0x00000020  /* Programmable Almost fill level */
+#define  DMM_PAF2          0x00000040  /* Programmable Almost fill level */
+#define  DMM_PAF3          0x00000060  /* Programmable Almost fill level */
+#define  DMM_PAF4          0x00000080  /* Programmable Almost fill level */
+#define  DMM_PAF5          0x000000A0  /* Programmable Almost fill level */
+#define  DMM_PAF6          0x000000C0  /* Programmable Almost fill level */
+#define  DMM_PAF7          0x000000D0  /* Programmable Almost fill level */
+#define  DMM_MAP           0xFFFF0000  /* Remap Address Bits */
+
+#define PLX_CAR_REG            0x002C  /* L, Configuration Address Register */
+#define  CAR_CT0           0x00000000  /* Config Type 0 */
+#define  CAR_CT1           0x00000001  /* Config Type 1 */
+#define  CAR_REG           0x000000FC  /* Register Number Bits */
+#define  CAR_FUN           0x00000700  /* Function Number Bits */
+#define  CAR_DEV           0x0000F800  /* Device Number Bits */
+#define  CAR_BUS           0x00FF0000  /* Bus Number Bits */
+#define  CAR_CFG           0x80000000  /* Config Spc Access Enable */
+
+#define PLX_DBR_IN_REG         0x0060  /* L, PCI to Local Doorbell Register */
+
+#define PLX_DBR_OUT_REG        0x0064  /* L, Local to PCI Doorbell Register */
+
+#define PLX_INTRCS_REG         0x0068  /* L, Interrupt Control/Status Reg */
+#define  ICS_AERR          0x00000001  /* Assert LSERR on ABORT */
+#define  ICS_PERR          0x00000002  /* Assert LSERR on Parity Error */
+#define  ICS_SERR          0x00000004  /* Generate PCI SERR# */
+#define  ICS_MBIE          0x00000008  // mailbox interrupt enable
+#define  ICS_PIE           0x00000100  /* PCI Interrupt Enable */
+#define  ICS_PDIE          0x00000200  /* PCI Doorbell Interrupt Enable */
+#define  ICS_PAIE          0x00000400  /* PCI Abort Interrupt Enable */
+#define  ICS_PLIE          0x00000800  /* PCI Local Int Enable */
+#define  ICS_RAE           0x00001000  /* Retry Abort Enable */
+#define  ICS_PDIA          0x00002000  /* PCI Doorbell Interrupt Active */
+#define  ICS_PAIA          0x00004000  /* PCI Abort Interrupt Active */
+#define  ICS_LIA           0x00008000  /* Local Interrupt Active */
+#define  ICS_LIE           0x00010000  /* Local Interrupt Enable */
+#define  ICS_LDIE          0x00020000  /* Local Doorbell Int Enable */
+#define  ICS_DMA0_E        0x00040000  /* DMA #0 Interrupt Enable */
+#define  ICS_DMA1_E        0x00080000  /* DMA #1 Interrupt Enable */
+#define  ICS_LDIA          0x00100000  /* Local Doorbell Int Active */
+#define  ICS_DMA0_A        0x00200000  /* DMA #0 Interrupt Active */
+#define  ICS_DMA1_A        0x00400000  /* DMA #1 Interrupt Active */
+#define  ICS_BIA           0x00800000  /* BIST Interrupt Active */
+#define  ICS_TA_DM         0x01000000  /* Target Abort - Direct Master */
+#define  ICS_TA_DMA0       0x02000000  /* Target Abort - DMA #0 */
+#define  ICS_TA_DMA1       0x04000000  /* Target Abort - DMA #1 */
+#define  ICS_TA_RA         0x08000000  /* Target Abort - Retry Timeout */
+#define  ICS_MBIA(x)       (0x10000000 << ((x) & 0x3)) // mailbox x is active
+
+#define PLX_CONTROL_REG        0x006C  /* L, EEPROM Cntl & PCI Cmd Codes */
+#define  CTL_RDMA          0x0000000E  /* DMA Read Command */
+#define  CTL_WDMA          0x00000070  /* DMA Write Command */
+#define  CTL_RMEM          0x00000600  /* Memory Read Command */
+#define  CTL_WMEM          0x00007000  /* Memory Write Command */
+#define  CTL_USERO         0x00010000  /* USERO output pin control bit */
+#define  CTL_USERI         0x00020000  /* USERI input pin bit */
+#define  CTL_EE_CLK        0x01000000  /* EEPROM Clock line */
+#define  CTL_EE_CS         0x02000000  /* EEPROM Chip Select */
+#define  CTL_EE_W          0x04000000  /* EEPROM Write bit */
+#define  CTL_EE_R          0x08000000  /* EEPROM Read bit */
+#define  CTL_EECHK         0x10000000  /* EEPROM Present bit */
+#define  CTL_EERLD         0x20000000  /* EEPROM Reload Register */
+#define  CTL_RESET         0x40000000  /* !! Adapter Reset !! */
+#define  CTL_READY         0x80000000  /* Local Init Done */
+
+#define PLX_ID_REG     0x70    // hard-coded plx vendor and device ids
+
+#define PLX_REVISION_REG       0x74    // silicon revision
+
+#define PLX_DMA0_MODE_REG      0x80    // dma channel 0 mode register
+#define PLX_DMA1_MODE_REG      0x94    // dma channel 0 mode register
+#define  PLX_LOCAL_BUS_16_WIDE_BITS    0x1
+#define  PLX_LOCAL_BUS_32_WIDE_BITS    0x3
+#define  PLX_LOCAL_BUS_WIDTH_MASK      0x3
+#define  PLX_DMA_EN_READYIN_BIT        0x40    // enable ready in input
+#define  PLX_EN_BTERM_BIT      0x80    // enable BTERM# input
+#define  PLX_DMA_LOCAL_BURST_EN_BIT    0x100   // enable local burst mode
+#define  PLX_EN_CHAIN_BIT      0x200   // enables chaining
+#define  PLX_EN_DMA_DONE_INTR_BIT      0x400   // enables interrupt on dma done
+#define  PLX_LOCAL_ADDR_CONST_BIT      0x800   // hold local address constant (don't increment)
+#define  PLX_DEMAND_MODE_BIT   0x1000  // enables demand-mode for dma transfer
+#define  PLX_EOT_ENABLE_BIT    0x4000
+#define  PLX_STOP_MODE_BIT 0x8000
+#define  PLX_DMA_INTR_PCI_BIT  0x20000 // routes dma interrupt to pci bus (instead of local bus)
+
+#define PLX_DMA0_PCI_ADDRESS_REG       0x84    // pci address that dma transfers start at
+#define PLX_DMA1_PCI_ADDRESS_REG       0x98
+
+#define PLX_DMA0_LOCAL_ADDRESS_REG     0x88    // local address that dma transfers start at
+#define PLX_DMA1_LOCAL_ADDRESS_REG     0x9c
+
+#define PLX_DMA0_TRANSFER_SIZE_REG     0x8c    // number of bytes to transfer (first 23 bits)
+#define PLX_DMA1_TRANSFER_SIZE_REG     0xa0
+
+#define PLX_DMA0_DESCRIPTOR_REG        0x90    // descriptor pointer register
+#define PLX_DMA1_DESCRIPTOR_REG        0xa4
+#define  PLX_DESC_IN_PCI_BIT   0x1     // descriptor is located in pci space (not local space)
+#define  PLX_END_OF_CHAIN_BIT  0x2     // end of chain bit
+#define  PLX_INTR_TERM_COUNT   0x4     // interrupt when this descriptor's transfer is finished
+#define  PLX_XFER_LOCAL_TO_PCI 0x8     // transfer from local to pci bus (not pci to local)
+
+#define PLX_DMA0_CS_REG        0xa8    // command status register
+#define PLX_DMA1_CS_REG        0xa9
+#define  PLX_DMA_EN_BIT        0x1     // enable dma channel
+#define  PLX_DMA_START_BIT     0x2     // start dma transfer
+#define  PLX_DMA_ABORT_BIT     0x4     // abort dma transfer
+#define  PLX_CLEAR_DMA_INTR_BIT        0x8     // clear dma interrupt
+#define  PLX_DMA_DONE_BIT      0x10    // transfer done status bit
+
+#define PLX_DMA0_THRESHOLD_REG 0xb0    // command status register
+
+/*
+ * Accesses near the end of memory can cause the PLX chip
+ * to pre-fetch data off of end-of-ram.  Limit the size of
+ * memory so host-side accesses cannot occur.
+ */
+
+#define PLX_PREFETCH   32
+
+/*
+ * The PCI Interface, via the PCI-9060 Chip, has up to eight (8) Mailbox
+ * Registers.  The PUTS (Power-Up Test Suite) handles the board-side
+ * interface/interaction using the first 4 registers.  Specifications for
+ * the use of the full PUTS' command and status interface is contained
+ * within a separate SBE PUTS Manual.  The Host-Side Device Driver only
+ * uses a subset of the full PUTS interface.
+ */
+
+/*****************************************/
+/***    MAILBOX #(-1) - MEM ACCESS STS ***/
+/*****************************************/
+
+#define MBX_STS_VALID      0x57584744  /* 'WXGD' */
+#define MBX_STS_DILAV      0x44475857  /* swapped = 'DGXW' */
+
+/*****************************************/
+/***    MAILBOX #0  -  PUTS STATUS     ***/
+/*****************************************/
+
+#define MBX_STS_MASK       0x000000ff  /* PUTS Status Register bits */
+#define MBX_STS_TMASK      0x0000000f  /* register bits for TEST number */
+
+#define MBX_STS_PCIRESET   0x00000100  /* Host issued PCI reset request */
+#define MBX_STS_BUSY       0x00000080  /* PUTS is in progress */
+#define MBX_STS_ERROR      0x00000040  /* PUTS has failed */
+#define MBX_STS_RESERVED   0x000000c0  /* Undefined -> status in transition.
+                                          We are in process of changing
+                                          bits; we SET Error bit before
+                                          RESET of Busy bit */
+
+#define MBX_RESERVED_5     0x00000020  /* FYI: reserved/unused bit */
+#define MBX_RESERVED_4     0x00000010  /* FYI: reserved/unused bit */
+
+/******************************************/
+/***    MAILBOX #1  -  PUTS COMMANDS    ***/
+/******************************************/
+
+/*
+ * Any attempt to execute an unimplement command results in the PUTS
+ * interface executing a NOOP and continuing as if the offending command
+ * completed normally.  Note: this supplies a simple method to interrogate
+ * mailbox command processing functionality.
+ */
+
+#define MBX_CMD_MASK       0xffff0000  /* PUTS Command Register bits */
+
+#define MBX_CMD_ABORTJ     0x85000000  /* abort and jump */
+#define MBX_CMD_RESETP     0x86000000  /* reset and pause at start */
+#define MBX_CMD_PAUSE      0x87000000  /* pause immediately */
+#define MBX_CMD_PAUSEC     0x88000000  /* pause on completion */
+#define MBX_CMD_RESUME     0x89000000  /* resume operation */
+#define MBX_CMD_STEP       0x8a000000  /* single step tests */
+
+#define MBX_CMD_BSWAP      0x8c000000  /* identify byte swap scheme */
+#define MBX_CMD_BSWAP_0    0x8c000000  /* use scheme 0 */
+#define MBX_CMD_BSWAP_1    0x8c000001  /* use scheme 1 */
+
+#define MBX_CMD_SETHMS     0x8d000000  /* setup host memory access window
+                                          size */
+#define MBX_CMD_SETHBA     0x8e000000  /* setup host memory access base
+                                          address */
+#define MBX_CMD_MGO        0x8f000000  /* perform memory setup and continue
+                                          (IE. Done) */
+#define MBX_CMD_NOOP       0xFF000000  /* dummy, illegal command */
+
+/*****************************************/
+/***    MAILBOX #2  -  MEMORY SIZE     ***/
+/*****************************************/
+
+#define MBX_MEMSZ_MASK     0xffff0000  /* PUTS Memory Size Register bits */
+
+#define MBX_MEMSZ_128KB    0x00020000  /* 128 kilobyte board */
+#define MBX_MEMSZ_256KB    0x00040000  /* 256 kilobyte board */
+#define MBX_MEMSZ_512KB    0x00080000  /* 512 kilobyte board */
+#define MBX_MEMSZ_1MB      0x00100000  /* 1 megabyte board */
+#define MBX_MEMSZ_2MB      0x00200000  /* 2 megabyte board */
+#define MBX_MEMSZ_4MB      0x00400000  /* 4 megabyte board */
+#define MBX_MEMSZ_8MB      0x00800000  /* 8 megabyte board */
+#define MBX_MEMSZ_16MB     0x01000000  /* 16 megabyte board */
+
+/***************************************/
+/***    MAILBOX #2  -  BOARD TYPE    ***/
+/***************************************/
+
+#define MBX_BTYPE_MASK          0x0000ffff     /* PUTS Board Type Register */
+#define MBX_BTYPE_FAMILY_MASK   0x0000ff00     /* PUTS Board Family Register */
+#define MBX_BTYPE_SUBTYPE_MASK  0x000000ff     /* PUTS Board Subtype */
+
+#define MBX_BTYPE_PLX9060       0x00000100     /* PLX family type */
+#define MBX_BTYPE_PLX9080       0x00000300     /* PLX wanXL100s family type */
+
+#define MBX_BTYPE_WANXL_4       0x00000104     /* wanXL400, 4-port */
+#define MBX_BTYPE_WANXL_2       0x00000102     /* wanXL200, 2-port */
+#define MBX_BTYPE_WANXL_1s      0x00000301     /* wanXL100s, 1-port */
+#define MBX_BTYPE_WANXL_1t      0x00000401     /* wanXL100T1, 1-port */
+
+/*****************************************/
+/***    MAILBOX #3  -  SHMQ MAILBOX    ***/
+/*****************************************/
+
+#define MBX_SMBX_MASK           0x000000ff     /* PUTS SHMQ Mailbox bits */
+
+/***************************************/
+/***    GENERIC HOST-SIDE DRIVER     ***/
+/***************************************/
+
+#define MBX_ERR    0
+#define MBX_OK     1
+
+/* mailbox check routine - type of testing */
+#define MBXCHK_STS      0x00   /* check for PUTS status */
+#define MBXCHK_NOWAIT   0x01   /* dont care about PUTS status */
+
+/* system allocates this many bytes for address mapping mailbox space */
+#define MBX_ADDR_SPACE_360 0x80        /* wanXL100s/200/400 */
+#define MBX_ADDR_MASK_360 (MBX_ADDR_SPACE_360-1)
+
+static inline int plx9080_abort_dma(void *iobase, unsigned int channel)
+{
+       void *dma_cs_addr;
+       uint8_t dma_status;
+       const int timeout = 10000;
+       unsigned int i;
+
+       if (channel)
+               dma_cs_addr = iobase + PLX_DMA1_CS_REG;
+       else
+               dma_cs_addr = iobase + PLX_DMA0_CS_REG;
+
+       // abort dma transfer if necessary
+       dma_status = readb(dma_cs_addr);
+       if ((dma_status & PLX_DMA_EN_BIT) == 0) {
+               return 0;
+       }
+       // wait to make sure done bit is zero
+       for (i = 0; (dma_status & PLX_DMA_DONE_BIT) && i < timeout; i++) {
+               comedi_udelay(1);
+               dma_status = readb(dma_cs_addr);
+       }
+       if (i == timeout) {
+               rt_printk
+                       ("plx9080: cancel() timed out waiting for dma %i done clear\n",
+                       channel);
+               return -ETIMEDOUT;
+       }
+       // disable and abort channel
+       writeb(PLX_DMA_ABORT_BIT, dma_cs_addr);
+       // wait for dma done bit
+       dma_status = readb(dma_cs_addr);
+       for (i = 0; (dma_status & PLX_DMA_DONE_BIT) == 0 && i < timeout; i++) {
+               comedi_udelay(1);
+               dma_status = readb(dma_cs_addr);
+       }
+       if (i == timeout) {
+               rt_printk
+                       ("plx9080: cancel() timed out waiting for dma %i done set\n",
+                       channel);
+               return -ETIMEDOUT;
+       }
+
+       return 0;
+}
+
+#endif /* __COMEDI_PLX9080_H */
diff --git a/drivers/staging/comedi/drivers/rtd520.c b/drivers/staging/comedi/drivers/rtd520.c
new file mode 100644 (file)
index 0000000..65d5242
--- /dev/null
@@ -0,0 +1,2283 @@
+/*
+    comedi/drivers/rtd520.c
+    Comedi driver for Real Time Devices (RTD) PCI4520/DM7520
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 2001 David A. Schleef <ds@schleef.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+/*
+Driver: rtd520
+Description: Real Time Devices PCI4520/DM7520
+Author: Dan Christian
+Devices: [Real Time Devices] DM7520HR-1 (rtd520), DM7520HR-8,
+  PCI4520, PCI4520-8
+Status: Works.  Only tested on DM7520-8.  Not SMP safe.
+
+Configuration options:
+  [0] - PCI bus of device (optional)
+          If bus/slot is not specified, the first available PCI
+          device will be used.
+  [1] - PCI slot of device (optional)
+*/
+/*
+    Created by Dan Christian, NASA Ames Research Center.
+
+    The PCI4520 is a PCI card.  The DM7520 is a PC/104-plus card.
+    Both have:
+    8/16 12 bit ADC with FIFO and channel gain table
+    8 bits high speed digital out (for external MUX) (or 8 in or 8 out)
+    8 bits high speed digital in with FIFO and interrupt on change (or 8 IO)
+    2 12 bit DACs with FIFOs
+    2 bits output
+    2 bits input
+    bus mastering DMA
+    timers: ADC sample, pacer, burst, about, delay, DA1, DA2
+    sample counter
+    3 user timer/counters (8254)
+    external interrupt
+
+    The DM7520 has slightly fewer features (fewer gain steps).
+
+    These boards can support external multiplexors and multi-board
+    synchronization, but this driver doesn't support that.
+
+    Board docs: http://www.rtdusa.com/PC104/DM/analog%20IO/dm7520.htm
+    Data sheet: http://www.rtdusa.com/pdf/dm7520.pdf
+    Example source: http://www.rtdusa.com/examples/dm/dm7520.zip
+    Call them and ask for the register level manual.
+    PCI chip: http://www.plxtech.com/products/toolbox/9080.htm
+
+    Notes:
+    This board is memory mapped.  There is some IO stuff, but it isn't needed.
+
+    I use a pretty loose naming style within the driver (rtd_blah).
+    All externally visible names should be rtd520_blah.
+    I use camelCase for structures (and inside them).
+    I may also use upper CamelCase for function names (old habit).
+
+    This board is somewhat related to the RTD PCI4400 board.
+
+    I borrowed heavily from the ni_mio_common, ni_atmio16d, mite, and
+    das1800, since they have the best documented code.  Driver
+    cb_pcidas64.c uses the same DMA controller.
+
+    As far as I can tell, the About interrupt doesnt work if Sample is
+    also enabled.  It turns out that About really isn't needed, since
+    we always count down samples read.
+
+    There was some timer/counter code, but it didn't follow the right API.
+
+*/
+
+/*
+  driver status:
+
+  Analog-In supports instruction and command mode.
+
+  With DMA, you can sample at 1.15Mhz with 70% idle on a 400Mhz K6-2
+  (single channel, 64K read buffer).  I get random system lockups when
+  using DMA with ALI-15xx based systems.  I haven't been able to test
+  any other chipsets.  The lockups happen soon after the start of an
+  acquistion, not in the middle of a long run.
+
+  Without DMA, you can do 620Khz sampling with 20% idle on a 400Mhz K6-2
+  (with a 256K read buffer).
+
+  Digital-IO and Analog-Out only support instruction mode.
+
+*/
+
+#include <linux/delay.h>
+
+#include "../comedidev.h"
+#include "comedi_pci.h"
+
+#define DRV_NAME "rtd520"
+
+/*======================================================================
+  Driver specific stuff (tunable)
+======================================================================*/
+/* Enable this to test the new DMA support. You may get hard lock ups */
+/*#define USE_DMA*/
+
+/* We really only need 2 buffers.  More than that means being much
+   smarter about knowing which ones are full. */
+#define DMA_CHAIN_COUNT 2      /* max DMA segments/buffers in a ring (min 2) */
+
+/* Target period for periodic transfers.  This sets the user read latency. */
+/* Note: There are certain rates where we give this up and transfer 1/2 FIFO */
+/* If this is too low, efficiency is poor */
+#define TRANS_TARGET_PERIOD 10000000   /* 10 ms (in nanoseconds) */
+
+/* Set a practical limit on how long a list to support (affects memory use) */
+/* The board support a channel list up to the FIFO length (1K or 8K) */
+#define RTD_MAX_CHANLIST       128     /* max channel list that we allow */
+
+/* tuning for ai/ao instruction done polling */
+#ifdef FAST_SPIN
+#define WAIT_QUIETLY           /* as nothing, spin on done bit */
+#define RTD_ADC_TIMEOUT        66000   /* 2 msec at 33mhz bus rate */
+#define RTD_DAC_TIMEOUT        66000
+#define RTD_DMA_TIMEOUT        33000   /* 1 msec */
+#else
+/* by delaying, power and electrical noise are reduced somewhat */
+#define WAIT_QUIETLY   comedi_udelay (1)
+#define RTD_ADC_TIMEOUT        2000    /* in usec */
+#define RTD_DAC_TIMEOUT        2000    /* in usec */
+#define RTD_DMA_TIMEOUT        1000    /* in usec */
+#endif
+
+/*======================================================================
+  Board specific stuff
+======================================================================*/
+
+/* registers  */
+#define PCI_VENDOR_ID_RTD      0x1435
+/*
+  The board has three memory windows: las0, las1, and lcfg (the PCI chip)
+  Las1 has the data and can be burst DMAed 32bits at a time.
+*/
+#define LCFG_PCIINDEX  0
+/* PCI region 1 is a 256 byte IO space mapping.  Use??? */
+#define LAS0_PCIINDEX  2       /* PCI memory resources */
+#define LAS1_PCIINDEX  3
+#define LCFG_PCISIZE   0x100
+#define LAS0_PCISIZE   0x200
+#define LAS1_PCISIZE   0x10
+
+#define RTD_CLOCK_RATE 8000000 /* 8Mhz onboard clock */
+#define RTD_CLOCK_BASE 125     /* clock period in ns */
+
+/* Note: these speed are slower than the spec, but fit the counter resolution*/
+#define RTD_MAX_SPEED  1625    /* when sampling, in nanoseconds */
+/* max speed if we don't have to wait for settling */
+#define RTD_MAX_SPEED_1        875     /* if single channel, in nanoseconds */
+
+#define RTD_MIN_SPEED  2097151875      /* (24bit counter) in nanoseconds */
+/* min speed when only 1 channel (no burst counter) */
+#define RTD_MIN_SPEED_1        5000000 /* 200Hz, in nanoseconds */
+
+#include "rtd520.h"
+#include "plx9080.h"
+
+/* Setup continuous ring of 1/2 FIFO transfers.  See RTD manual p91 */
+#define DMA_MODE_BITS (\
+                      PLX_LOCAL_BUS_16_WIDE_BITS \
+                      | PLX_DMA_EN_READYIN_BIT \
+                      | PLX_DMA_LOCAL_BURST_EN_BIT \
+                      | PLX_EN_CHAIN_BIT \
+                      | PLX_DMA_INTR_PCI_BIT \
+                      | PLX_LOCAL_ADDR_CONST_BIT \
+                      | PLX_DEMAND_MODE_BIT)
+
+#define DMA_TRANSFER_BITS (\
+/* descriptors in PCI memory*/         PLX_DESC_IN_PCI_BIT \
+/* interrupt at end of block */ | PLX_INTR_TERM_COUNT \
+/* from board to PCI */                | PLX_XFER_LOCAL_TO_PCI)
+
+/*======================================================================
+  Comedi specific stuff
+======================================================================*/
+
+/*
+  The board has 3 input modes and the gains of 1,2,4,...32 (, 64, 128)
+*/
+static const comedi_lrange rtd_ai_7520_range = { 18, {
+                       /* +-5V input range gain steps */
+                       BIP_RANGE(5.0),
+                       BIP_RANGE(5.0 / 2),
+                       BIP_RANGE(5.0 / 4),
+                       BIP_RANGE(5.0 / 8),
+                       BIP_RANGE(5.0 / 16),
+                       BIP_RANGE(5.0 / 32),
+                       /* +-10V input range gain steps */
+                       BIP_RANGE(10.0),
+                       BIP_RANGE(10.0 / 2),
+                       BIP_RANGE(10.0 / 4),
+                       BIP_RANGE(10.0 / 8),
+                       BIP_RANGE(10.0 / 16),
+                       BIP_RANGE(10.0 / 32),
+                       /* +10V input range gain steps */
+                       UNI_RANGE(10.0),
+                       UNI_RANGE(10.0 / 2),
+                       UNI_RANGE(10.0 / 4),
+                       UNI_RANGE(10.0 / 8),
+                       UNI_RANGE(10.0 / 16),
+                       UNI_RANGE(10.0 / 32),
+
+       }
+};
+
+/* PCI4520 has two more gains (6 more entries) */
+static const comedi_lrange rtd_ai_4520_range = { 24, {
+                       /* +-5V input range gain steps */
+                       BIP_RANGE(5.0),
+                       BIP_RANGE(5.0 / 2),
+                       BIP_RANGE(5.0 / 4),
+                       BIP_RANGE(5.0 / 8),
+                       BIP_RANGE(5.0 / 16),
+                       BIP_RANGE(5.0 / 32),
+                       BIP_RANGE(5.0 / 64),
+                       BIP_RANGE(5.0 / 128),
+                       /* +-10V input range gain steps */
+                       BIP_RANGE(10.0),
+                       BIP_RANGE(10.0 / 2),
+                       BIP_RANGE(10.0 / 4),
+                       BIP_RANGE(10.0 / 8),
+                       BIP_RANGE(10.0 / 16),
+                       BIP_RANGE(10.0 / 32),
+                       BIP_RANGE(10.0 / 64),
+                       BIP_RANGE(10.0 / 128),
+                       /* +10V input range gain steps */
+                       UNI_RANGE(10.0),
+                       UNI_RANGE(10.0 / 2),
+                       UNI_RANGE(10.0 / 4),
+                       UNI_RANGE(10.0 / 8),
+                       UNI_RANGE(10.0 / 16),
+                       UNI_RANGE(10.0 / 32),
+                       UNI_RANGE(10.0 / 64),
+                       UNI_RANGE(10.0 / 128),
+       }
+};
+
+/* Table order matches range values */
+static const comedi_lrange rtd_ao_range = { 4, {
+                       RANGE(0, 5),
+                       RANGE(0, 10),
+                       RANGE(-5, 5),
+                       RANGE(-10, 10),
+       }
+};
+
+/*
+  Board descriptions
+ */
+typedef struct rtdBoard_struct {
+       const char *name;       /* must be first */
+       int device_id;
+       int aiChans;
+       int aiBits;
+       int aiMaxGain;
+       int range10Start;       /* start of +-10V range */
+       int rangeUniStart;      /* start of +10V range */
+} rtdBoard;
+
+static const rtdBoard rtd520Boards[] = {
+       {
+             name:     "DM7520",
+             device_id:0x7520,
+             aiChans:  16,
+             aiBits:   12,
+             aiMaxGain:32,
+             range10Start:6,
+             rangeUniStart:12,
+               },
+       {
+             name:     "PCI4520",
+             device_id:0x4520,
+             aiChans:  16,
+             aiBits:   12,
+             aiMaxGain:128,
+             range10Start:8,
+             rangeUniStart:16,
+               },
+};
+
+static DEFINE_PCI_DEVICE_TABLE(rtd520_pci_table) = {
+       {PCI_VENDOR_ID_RTD, 0x7520, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {PCI_VENDOR_ID_RTD, 0x4520, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {0}
+};
+
+MODULE_DEVICE_TABLE(pci, rtd520_pci_table);
+
+/*
+ * Useful for shorthand access to the particular board structure
+ */
+#define thisboard ((const rtdBoard *)dev->board_ptr)
+
+/*
+   This structure is for data unique to this hardware driver.
+   This is also unique for each board in the system.
+*/
+typedef struct {
+       /* memory mapped board structures */
+       void *las0;
+       void *las1;
+       void *lcfg;
+
+       unsigned long intCount; /* interrupt count */
+       long aiCount;           /* total transfer size (samples) */
+       int transCount;         /* # to tranfer data. 0->1/2FIFO */
+       int flags;              /* flag event modes */
+
+       /* PCI device info */
+       struct pci_dev *pci_dev;
+       int got_regions;        /* non-zero if PCI regions owned */
+
+       /* channel list info */
+       /* chanBipolar tracks whether a channel is bipolar (and needs +2048) */
+       unsigned char chanBipolar[RTD_MAX_CHANLIST / 8];        /* bit array */
+
+       /* read back data */
+       lsampl_t aoValue[2];    /* Used for AO read back */
+
+       /* timer gate (when enabled) */
+       u8 utcGate[4];          /* 1 extra allows simple range check */
+
+       /* shadow registers affect other registers, but cant be read back */
+       /* The macros below update these on writes */
+       u16 intMask;            /* interrupt mask */
+       u16 intClearMask;       /* interrupt clear mask */
+       u8 utcCtrl[4];          /* crtl mode for 3 utc + read back */
+       u8 dioStatus;           /* could be read back (dio0Ctrl) */
+#ifdef USE_DMA
+       /* Always DMA 1/2 FIFO.  Buffer (dmaBuff?) is (at least) twice that size.
+          After transferring, interrupt processes 1/2 FIFO and passes to comedi */
+       s16 dma0Offset;         /* current processing offset (0, 1/2) */
+       uint16_t *dma0Buff[DMA_CHAIN_COUNT];    /* DMA buffers (for ADC) */
+       dma_addr_t dma0BuffPhysAddr[DMA_CHAIN_COUNT];   /* physical addresses */
+       struct plx_dma_desc *dma0Chain; /* DMA descriptor ring for dmaBuff */
+       dma_addr_t dma0ChainPhysAddr;   /* physical addresses */
+       /* shadow registers */
+       u8 dma0Control;
+       u8 dma1Control;
+#endif                         /* USE_DMA */
+       unsigned fifoLen;
+} rtdPrivate;
+
+/* bit defines for "flags" */
+#define SEND_EOS       0x01    /* send End Of Scan events */
+#define DMA0_ACTIVE    0x02    /* DMA0 is active */
+#define DMA1_ACTIVE    0x04    /* DMA1 is active */
+
+/* Macros for accessing channel list bit array */
+#define CHAN_ARRAY_TEST(array,index) \
+       (((array)[(index)/8] >> ((index) & 0x7)) & 0x1)
+#define CHAN_ARRAY_SET(array,index) \
+       (((array)[(index)/8] |= 1 << ((index) & 0x7)))
+#define CHAN_ARRAY_CLEAR(array,index) \
+       (((array)[(index)/8] &= ~(1 << ((index) & 0x7))))
+
+/*
+ * most drivers define the following macro to make it easy to
+ * access the private structure.
+ */
+#define devpriv ((rtdPrivate *)dev->private)
+
+/* Macros to access registers */
+
+/* Reset board */
+#define RtdResetBoard(dev) \
+    writel (0, devpriv->las0+LAS0_BOARD_RESET)
+
+/* Reset channel gain table read pointer */
+#define RtdResetCGT(dev) \
+    writel (0, devpriv->las0+LAS0_CGT_RESET)
+
+/* Reset channel gain table read and write pointers */
+#define RtdClearCGT(dev) \
+    writel (0, devpriv->las0+LAS0_CGT_CLEAR)
+
+/* Reset channel gain table read and write pointers */
+#define RtdEnableCGT(dev,v) \
+    writel ((v > 0) ? 1 : 0, devpriv->las0+LAS0_CGT_ENABLE)
+
+/* Write channel gain table entry */
+#define RtdWriteCGTable(dev,v) \
+    writel (v, devpriv->las0+LAS0_CGT_WRITE)
+
+/* Write Channel Gain Latch */
+#define RtdWriteCGLatch(dev,v) \
+    writel (v, devpriv->las0+LAS0_CGL_WRITE)
+
+/* Reset ADC FIFO */
+#define RtdAdcClearFifo(dev) \
+    writel (0, devpriv->las0+LAS0_ADC_FIFO_CLEAR)
+
+/* Set ADC start conversion source select (write only) */
+#define RtdAdcConversionSource(dev,v) \
+    writel (v, devpriv->las0+LAS0_ADC_CONVERSION)
+
+/* Set burst start source select (write only) */
+#define RtdBurstStartSource(dev,v) \
+    writel (v, devpriv->las0+LAS0_BURST_START)
+
+/* Set Pacer start source select (write only) */
+#define RtdPacerStartSource(dev,v) \
+    writel (v, devpriv->las0+LAS0_PACER_START)
+
+/* Set Pacer stop source select (write only) */
+#define RtdPacerStopSource(dev,v) \
+    writel (v, devpriv->las0+LAS0_PACER_STOP)
+
+/* Set Pacer clock source select (write only) 0=external 1=internal */
+#define RtdPacerClockSource(dev,v) \
+    writel ((v > 0) ? 1 : 0, devpriv->las0+LAS0_PACER_SELECT)
+
+/* Set sample counter source select (write only) */
+#define RtdAdcSampleCounterSource(dev,v) \
+    writel (v, devpriv->las0+LAS0_ADC_SCNT_SRC)
+
+/* Set Pacer trigger mode select (write only) 0=single cycle, 1=repeat */
+#define RtdPacerTriggerMode(dev,v) \
+    writel ((v > 0) ? 1 : 0, devpriv->las0+LAS0_PACER_REPEAT)
+
+/* Set About counter stop enable (write only) */
+#define RtdAboutStopEnable(dev,v) \
+    writel ((v > 0) ? 1 : 0, devpriv->las0+LAS0_ACNT_STOP_ENABLE)
+
+/* Set external trigger polarity (write only) 0=positive edge, 1=negative */
+#define RtdTriggerPolarity(dev,v) \
+    writel ((v > 0) ? 1 : 0, devpriv->las0+LAS0_ETRG_POLARITY)
+
+/* Start single ADC conversion */
+#define RtdAdcStart(dev) \
+    writew (0, devpriv->las0+LAS0_ADC)
+
+/* Read one ADC data value (12bit (with sign extend) as 16bit) */
+/* Note: matches what DMA would get.  Actual value >> 3 */
+#define RtdAdcFifoGet(dev) \
+    readw (devpriv->las1+LAS1_ADC_FIFO)
+
+/* Read two ADC data values (DOESNT WORK) */
+#define RtdAdcFifoGet2(dev) \
+    readl (devpriv->las1+LAS1_ADC_FIFO)
+
+/* FIFO status */
+#define RtdFifoStatus(dev) \
+    readl (devpriv->las0+LAS0_ADC)
+
+/* pacer start/stop read=start, write=stop*/
+#define RtdPacerStart(dev) \
+    readl (devpriv->las0+LAS0_PACER)
+#define RtdPacerStop(dev) \
+    writel (0, devpriv->las0+LAS0_PACER)
+
+/* Interrupt status */
+#define RtdInterruptStatus(dev) \
+    readw (devpriv->las0+LAS0_IT)
+
+/* Interrupt mask */
+#define RtdInterruptMask(dev,v) \
+    writew ((devpriv->intMask = (v)),devpriv->las0+LAS0_IT)
+
+/* Interrupt status clear (only bits set in mask) */
+#define RtdInterruptClear(dev) \
+    readw (devpriv->las0+LAS0_CLEAR)
+
+/* Interrupt clear mask */
+#define RtdInterruptClearMask(dev,v) \
+    writew ((devpriv->intClearMask = (v)), devpriv->las0+LAS0_CLEAR)
+
+/* Interrupt overrun status */
+#define RtdInterruptOverrunStatus(dev) \
+    readl (devpriv->las0+LAS0_OVERRUN)
+
+/* Interrupt overrun clear */
+#define RtdInterruptOverrunClear(dev) \
+    writel (0, devpriv->las0+LAS0_OVERRUN)
+
+/* Pacer counter, 24bit */
+#define RtdPacerCount(dev) \
+    readl (devpriv->las0+LAS0_PCLK)
+#define RtdPacerCounter(dev,v) \
+    writel ((v) & 0xffffff,devpriv->las0+LAS0_PCLK)
+
+/* Burst counter, 10bit */
+#define RtdBurstCount(dev) \
+    readl (devpriv->las0+LAS0_BCLK)
+#define RtdBurstCounter(dev,v) \
+    writel ((v) & 0x3ff,devpriv->las0+LAS0_BCLK)
+
+/* Delay counter, 16bit */
+#define RtdDelayCount(dev) \
+    readl (devpriv->las0+LAS0_DCLK)
+#define RtdDelayCounter(dev,v) \
+    writel ((v) & 0xffff, devpriv->las0+LAS0_DCLK)
+
+/* About counter, 16bit */
+#define RtdAboutCount(dev) \
+    readl (devpriv->las0+LAS0_ACNT)
+#define RtdAboutCounter(dev,v) \
+    writel ((v) & 0xffff, devpriv->las0+LAS0_ACNT)
+
+/* ADC sample counter, 10bit */
+#define RtdAdcSampleCount(dev) \
+    readl (devpriv->las0+LAS0_ADC_SCNT)
+#define RtdAdcSampleCounter(dev,v) \
+    writel ((v) & 0x3ff, devpriv->las0+LAS0_ADC_SCNT)
+
+/* User Timer/Counter (8254) */
+#define RtdUtcCounterGet(dev,n) \
+    readb (devpriv->las0 \
+        + ((n <= 0) ? LAS0_UTC0 : ((1 == n) ? LAS0_UTC1 : LAS0_UTC2)))
+
+#define RtdUtcCounterPut(dev,n,v) \
+    writeb ((v) & 0xff, devpriv->las0 \
+        + ((n <= 0) ? LAS0_UTC0 : ((1 == n) ? LAS0_UTC1 : LAS0_UTC2)))
+
+/* Set UTC (8254) control byte  */
+#define RtdUtcCtrlPut(dev,n,v) \
+    writeb (devpriv->utcCtrl[(n) & 3] = (((n) & 3) << 6) | ((v) & 0x3f), \
+      devpriv->las0 + LAS0_UTC_CTRL)
+
+/* Set UTCn clock source (write only) */
+#define RtdUtcClockSource(dev,n,v) \
+    writew (v, devpriv->las0 \
+        + ((n <= 0) ? LAS0_UTC0_CLOCK : \
+           ((1 == n) ? LAS0_UTC1_CLOCK : LAS0_UTC2_CLOCK)))
+
+/* Set UTCn gate source (write only) */
+#define RtdUtcGateSource(dev,n,v) \
+    writew (v, devpriv->las0 \
+        + ((n <= 0) ? LAS0_UTC0_GATE : \
+           ((1 == n) ? LAS0_UTC1_GATE : LAS0_UTC2_GATE)))
+
+/* User output N source select (write only) */
+#define RtdUsrOutSource(dev,n,v) \
+    writel (v,devpriv->las0+((n <= 0) ? LAS0_UOUT0_SELECT : LAS0_UOUT1_SELECT))
+
+/* Digital IO */
+#define RtdDio0Read(dev) \
+    (readw (devpriv->las0+LAS0_DIO0) & 0xff)
+#define RtdDio0Write(dev,v) \
+    writew ((v) & 0xff, devpriv->las0+LAS0_DIO0)
+
+#define RtdDio1Read(dev) \
+    (readw (devpriv->las0+LAS0_DIO1) & 0xff)
+#define RtdDio1Write(dev,v) \
+    writew ((v) & 0xff, devpriv->las0+LAS0_DIO1)
+
+#define RtdDioStatusRead(dev) \
+    (readw (devpriv->las0+LAS0_DIO_STATUS) & 0xff)
+#define RtdDioStatusWrite(dev,v) \
+    writew ((devpriv->dioStatus = (v)), devpriv->las0+LAS0_DIO_STATUS)
+
+#define RtdDio0CtrlRead(dev) \
+    (readw (devpriv->las0+LAS0_DIO0_CTRL) & 0xff)
+#define RtdDio0CtrlWrite(dev,v) \
+    writew ((v) & 0xff, devpriv->las0+LAS0_DIO0_CTRL)
+
+/* Digital to Analog converter */
+/* Write one data value (sign + 12bit + marker bits) */
+/* Note: matches what DMA would put.  Actual value << 3 */
+#define RtdDacFifoPut(dev,n,v) \
+    writew ((v), devpriv->las1 +(((n) == 0) ? LAS1_DAC1_FIFO : LAS1_DAC2_FIFO))
+
+/* Start single DAC conversion */
+#define RtdDacUpdate(dev,n) \
+    writew (0, devpriv->las0 +(((n) == 0) ? LAS0_DAC1 : LAS0_DAC2))
+
+/* Start single DAC conversion on both DACs */
+#define RtdDacBothUpdate(dev) \
+    writew (0, devpriv->las0+LAS0_DAC)
+
+/* Set DAC output type and range */
+#define RtdDacRange(dev,n,v) \
+    writew ((v) & 7, devpriv->las0 \
+       +(((n) == 0) ? LAS0_DAC1_CTRL : LAS0_DAC2_CTRL))
+
+/* Reset DAC FIFO */
+#define RtdDacClearFifo(dev,n) \
+    writel (0, devpriv->las0+(((n) == 0) ? LAS0_DAC1_RESET : LAS0_DAC2_RESET))
+
+/* Set source for DMA 0 (write only, shadow?) */
+#define RtdDma0Source(dev,n) \
+    writel ((n) & 0xf, devpriv->las0+LAS0_DMA0_SRC)
+
+/* Set source for DMA 1 (write only, shadow?) */
+#define RtdDma1Source(dev,n) \
+    writel ((n) & 0xf, devpriv->las0+LAS0_DMA1_SRC)
+
+/* Reset board state for DMA 0 */
+#define RtdDma0Reset(dev) \
+    writel (0, devpriv->las0+LAS0_DMA0_RESET)
+
+/* Reset board state for DMA 1 */
+#define RtdDma1Reset(dev) \
+    writel (0, devpriv->las0+LAS0_DMA1_SRC)
+
+/* PLX9080 interrupt mask and status */
+#define RtdPlxInterruptRead(dev) \
+    readl (devpriv->lcfg+LCFG_ITCSR)
+#define RtdPlxInterruptWrite(dev,v) \
+    writel (v, devpriv->lcfg+LCFG_ITCSR)
+
+/* Set  mode for DMA 0 */
+#define RtdDma0Mode(dev,m) \
+    writel ((m), devpriv->lcfg+LCFG_DMAMODE0)
+
+/* Set PCI address for DMA 0 */
+#define RtdDma0PciAddr(dev,a) \
+    writel ((a), devpriv->lcfg+LCFG_DMAPADR0)
+
+/* Set local address for DMA 0 */
+#define RtdDma0LocalAddr(dev,a) \
+    writel ((a), devpriv->lcfg+LCFG_DMALADR0)
+
+/* Set byte count for DMA 0 */
+#define RtdDma0Count(dev,c) \
+    writel ((c), devpriv->lcfg+LCFG_DMASIZ0)
+
+/* Set next descriptor for DMA 0 */
+#define RtdDma0Next(dev,a) \
+    writel ((a), devpriv->lcfg+LCFG_DMADPR0)
+
+/* Set  mode for DMA 1 */
+#define RtdDma1Mode(dev,m) \
+    writel ((m), devpriv->lcfg+LCFG_DMAMODE1)
+
+/* Set PCI address for DMA 1 */
+#define RtdDma1PciAddr(dev,a) \
+    writel ((a), devpriv->lcfg+LCFG_DMAADR1)
+
+/* Set local address for DMA 1 */
+#define RtdDma1LocalAddr(dev,a) \
+    writel ((a), devpriv->lcfg+LCFG_DMALADR1)
+
+/* Set byte count for DMA 1 */
+#define RtdDma1Count(dev,c) \
+    writel ((c), devpriv->lcfg+LCFG_DMASIZ1)
+
+/* Set next descriptor for DMA 1 */
+#define RtdDma1Next(dev,a) \
+    writel ((a), devpriv->lcfg+LCFG_DMADPR1)
+
+/* Set control for DMA 0 (write only, shadow?) */
+#define RtdDma0Control(dev,n) \
+    writeb (devpriv->dma0Control = (n), devpriv->lcfg+LCFG_DMACSR0)
+
+/* Get status for DMA 0 */
+#define RtdDma0Status(dev) \
+    readb (devpriv->lcfg+LCFG_DMACSR0)
+
+/* Set control for DMA 1 (write only, shadow?) */
+#define RtdDma1Control(dev,n) \
+    writeb (devpriv->dma1Control = (n), devpriv->lcfg+LCFG_DMACSR1)
+
+/* Get status for DMA 1 */
+#define RtdDma1Status(dev) \
+    readb (devpriv->lcfg+LCFG_DMACSR1)
+
+/*
+ * The comedi_driver structure tells the Comedi core module
+ * which functions to call to configure/deconfigure (attac/detach)
+ * the board, and also about the kernel module that contains
+ * the device code.
+ */
+static int rtd_attach(comedi_device * dev, comedi_devconfig * it);
+static int rtd_detach(comedi_device * dev);
+
+static comedi_driver rtd520Driver = {
+      driver_name: DRV_NAME,
+      module:THIS_MODULE,
+      attach:rtd_attach,
+      detach:rtd_detach,
+};
+
+static int rtd_ai_rinsn(comedi_device * dev, comedi_subdevice * s,
+       comedi_insn * insn, lsampl_t * data);
+static int rtd_ao_winsn(comedi_device * dev, comedi_subdevice * s,
+       comedi_insn * insn, lsampl_t * data);
+static int rtd_ao_rinsn(comedi_device * dev, comedi_subdevice * s,
+       comedi_insn * insn, lsampl_t * data);
+static int rtd_dio_insn_bits(comedi_device * dev, comedi_subdevice * s,
+       comedi_insn * insn, lsampl_t * data);
+static int rtd_dio_insn_config(comedi_device * dev, comedi_subdevice * s,
+       comedi_insn * insn, lsampl_t * data);
+static int rtd_ai_cmdtest(comedi_device * dev, comedi_subdevice * s,
+       comedi_cmd * cmd);
+static int rtd_ai_cmd(comedi_device * dev, comedi_subdevice * s);
+static int rtd_ai_cancel(comedi_device * dev, comedi_subdevice * s);
+//static int rtd_ai_poll (comedi_device *dev,comedi_subdevice *s);
+static int rtd_ns_to_timer(unsigned int *ns, int roundMode);
+static irqreturn_t rtd_interrupt(int irq, void *d PT_REGS_ARG);
+static int rtd520_probe_fifo_depth(comedi_device *dev);
+
+/*
+ * Attach is called by the Comedi core to configure the driver
+ * for a particular board.  If you specified a board_name array
+ * in the driver structure, dev->board_ptr contains that
+ * address.
+ */
+static int rtd_attach(comedi_device * dev, comedi_devconfig * it)
+{                              /* board name and options flags */
+       comedi_subdevice *s;
+       struct pci_dev *pcidev;
+       int ret;
+       resource_size_t physLas0;       /* configuation */
+       resource_size_t physLas1;       /* data area */
+       resource_size_t physLcfg;       /* PLX9080 */
+#ifdef USE_DMA
+       int index;
+#endif
+
+       printk("comedi%d: rtd520 attaching.\n", dev->minor);
+
+#if defined (CONFIG_COMEDI_DEBUG) && defined (USE_DMA)
+       /* You can set this a load time: modprobe comedi comedi_debug=1 */
+       if (0 == comedi_debug)  /* force DMA debug printks */
+               comedi_debug = 1;
+#endif
+
+       /*
+        * Allocate the private structure area.  alloc_private() is a
+        * convenient macro defined in comedidev.h.
+        */
+       if (alloc_private(dev, sizeof(rtdPrivate)) < 0)
+               return -ENOMEM;
+
+       /*
+        * Probe the device to determine what device in the series it is.
+        */
+       for (pcidev = pci_get_device(PCI_VENDOR_ID_RTD, PCI_ANY_ID, NULL);
+               pcidev != NULL;
+               pcidev = pci_get_device(PCI_VENDOR_ID_RTD, PCI_ANY_ID, pcidev)) {
+               int i;
+
+               if (it->options[0] || it->options[1]) {
+                       if (pcidev->bus->number != it->options[0]
+                               || PCI_SLOT(pcidev->devfn) !=
+                               it->options[1]) {
+                               continue;
+                       }
+               }
+               for(i = 0; i < sizeof(rtd520Boards) / sizeof(rtd520Boards[0]); ++i)
+               {
+                       if(pcidev->device == rtd520Boards[i].device_id)
+                       {
+                               dev->board_ptr = &rtd520Boards[i];
+                               break;
+                       }
+               }
+               if(dev->board_ptr) break;       /* found one */
+       }
+       if (!pcidev) {
+               if (it->options[0] && it->options[1]) {
+                       printk("No RTD card at bus=%d slot=%d.\n",
+                               it->options[0], it->options[1]);
+               } else {
+                       printk("No RTD card found.\n");
+               }
+               return -EIO;
+       }
+       devpriv->pci_dev = pcidev;
+       dev->board_name = thisboard->name;
+
+       if ((ret = comedi_pci_enable(pcidev, DRV_NAME)) < 0) {
+               printk("Failed to enable PCI device and request regions.\n");
+               return ret;
+       }
+       devpriv->got_regions = 1;
+
+       /*
+        * Initialize base addresses
+        */
+       /* Get the physical address from PCI config */
+       physLas0 = pci_resource_start(devpriv->pci_dev, LAS0_PCIINDEX);
+       physLas1 = pci_resource_start(devpriv->pci_dev, LAS1_PCIINDEX);
+       physLcfg = pci_resource_start(devpriv->pci_dev, LCFG_PCIINDEX);
+       /* Now have the kernel map this into memory */
+       /* ASSUME page aligned */
+       devpriv->las0 = ioremap_nocache(physLas0, LAS0_PCISIZE);
+       devpriv->las1 = ioremap_nocache(physLas1, LAS1_PCISIZE);
+       devpriv->lcfg = ioremap_nocache(physLcfg, LCFG_PCISIZE);
+
+       if (!devpriv->las0 || !devpriv->las1 || !devpriv->lcfg) {
+               return -ENOMEM;
+       }
+
+       DPRINTK("%s: LAS0=%llx, LAS1=%llx, CFG=%llx.\n", dev->board_name,
+               (unsigned long long)physLas0, (unsigned long long)physLas1,
+               (unsigned long long)physLcfg);
+       {                       /* The RTD driver does this */
+               unsigned char pci_latency;
+               u16 revision;
+               /*uint32_t epld_version; */
+
+               pci_read_config_word(devpriv->pci_dev, PCI_REVISION_ID,
+                       &revision);
+               DPRINTK("%s: PCI revision %d.\n", dev->board_name, revision);
+
+               pci_read_config_byte(devpriv->pci_dev,
+                       PCI_LATENCY_TIMER, &pci_latency);
+               if (pci_latency < 32) {
+                       printk("%s: PCI latency changed from %d to %d\n",
+                               dev->board_name, pci_latency, 32);
+                       pci_write_config_byte(devpriv->pci_dev,
+                               PCI_LATENCY_TIMER, 32);
+               } else {
+                       DPRINTK("rtd520: PCI latency = %d\n", pci_latency);
+               }
+
+               /* Undocumented EPLD version (doesnt match RTD driver results) */
+               /*DPRINTK ("rtd520: Reading epld from %p\n",
+                  devpriv->las0+0);
+                  epld_version = readl (devpriv->las0+0);
+                  if ((epld_version & 0xF0) >> 4 == 0x0F) {
+                  DPRINTK("rtd520: pre-v8 EPLD. (%x)\n", epld_version);
+                  } else {
+                  DPRINTK("rtd520: EPLD version %x.\n", epld_version >> 4);
+                  } */
+       }
+
+       /* Show board configuration */
+       printk("%s:", dev->board_name);
+
+       /*
+        * Allocate the subdevice structures.  alloc_subdevice() is a
+        * convenient macro defined in comedidev.h.
+        */
+       if (alloc_subdevices(dev, 4) < 0) {
+               return -ENOMEM;
+       }
+
+       s = dev->subdevices + 0;
+       dev->read_subdev = s;
+       /* analog input subdevice */
+       s->type = COMEDI_SUBD_AI;
+       s->subdev_flags =
+               SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF |
+               SDF_CMD_READ;
+       s->n_chan = thisboard->aiChans;
+       s->maxdata = (1 << thisboard->aiBits) - 1;
+       if (thisboard->aiMaxGain <= 32) {
+               s->range_table = &rtd_ai_7520_range;
+       } else {
+               s->range_table = &rtd_ai_4520_range;
+       }
+       s->len_chanlist = RTD_MAX_CHANLIST;     /* devpriv->fifoLen */
+       s->insn_read = rtd_ai_rinsn;
+       s->do_cmd = rtd_ai_cmd;
+       s->do_cmdtest = rtd_ai_cmdtest;
+       s->cancel = rtd_ai_cancel;
+       /*s->poll = rtd_ai_poll; *//* not ready yet */
+
+       s = dev->subdevices + 1;
+       /* analog output subdevice */
+       s->type = COMEDI_SUBD_AO;
+       s->subdev_flags = SDF_WRITABLE;
+       s->n_chan = 2;
+       s->maxdata = (1 << thisboard->aiBits) - 1;
+       s->range_table = &rtd_ao_range;
+       s->insn_write = rtd_ao_winsn;
+       s->insn_read = rtd_ao_rinsn;
+
+       s = dev->subdevices + 2;
+       /* digital i/o subdevice */
+       s->type = COMEDI_SUBD_DIO;
+       s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+       /* we only support port 0 right now.  Ignoring port 1 and user IO */
+       s->n_chan = 8;
+       s->maxdata = 1;
+       s->range_table = &range_digital;
+       s->insn_bits = rtd_dio_insn_bits;
+       s->insn_config = rtd_dio_insn_config;
+
+       /* timer/counter subdevices (not currently supported) */
+       s = dev->subdevices + 3;
+       s->type = COMEDI_SUBD_COUNTER;
+       s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+       s->n_chan = 3;
+       s->maxdata = 0xffff;
+
+       /* initialize board, per RTD spec */
+       /* also, initialize shadow registers */
+       RtdResetBoard(dev);
+       comedi_udelay(100);     /* needed? */
+       RtdPlxInterruptWrite(dev, 0);
+       RtdInterruptMask(dev, 0);       /* and sets shadow */
+       RtdInterruptClearMask(dev, ~0); /* and sets shadow */
+       RtdInterruptClear(dev); /* clears bits set by mask */
+       RtdInterruptOverrunClear(dev);
+       RtdClearCGT(dev);
+       RtdAdcClearFifo(dev);
+       RtdDacClearFifo(dev, 0);
+       RtdDacClearFifo(dev, 1);
+       /* clear digital IO fifo */
+       RtdDioStatusWrite(dev, 0);      /* safe state, set shadow */
+       RtdUtcCtrlPut(dev, 0, 0x30);    /* safe state, set shadow */
+       RtdUtcCtrlPut(dev, 1, 0x30);    /* safe state, set shadow */
+       RtdUtcCtrlPut(dev, 2, 0x30);    /* safe state, set shadow */
+       RtdUtcCtrlPut(dev, 3, 0);       /* safe state, set shadow */
+       /* TODO: set user out source ??? */
+
+       /* check if our interrupt is available and get it */
+       if ((ret = comedi_request_irq(devpriv->pci_dev->irq, rtd_interrupt,
+                               IRQF_SHARED, DRV_NAME, dev)) < 0) {
+               printk("Could not get interrupt! (%u)\n",
+                       devpriv->pci_dev->irq);
+               return ret;
+       }
+       dev->irq = devpriv->pci_dev->irq;
+       printk("( irq=%u )", dev->irq);
+
+       ret = rtd520_probe_fifo_depth(dev);
+       if(ret < 0) {
+               return ret;
+       }
+       devpriv->fifoLen = ret;
+       printk("( fifoLen=%d )", devpriv->fifoLen);
+
+#ifdef USE_DMA
+       if (dev->irq > 0) {
+               printk("( DMA buff=%d )\n", DMA_CHAIN_COUNT);
+               /* The PLX9080 has 2 DMA controllers, but there could be 4 sources:
+                  ADC, digital, DAC1, and DAC2.  Since only the ADC supports cmd mode
+                  right now, this isn't an issue (yet) */
+               devpriv->dma0Offset = 0;
+
+               for (index = 0; index < DMA_CHAIN_COUNT; index++) {
+                       devpriv->dma0Buff[index] =
+                               pci_alloc_consistent(devpriv->pci_dev,
+                               sizeof(u16) * devpriv->fifoLen / 2,
+                               &devpriv->dma0BuffPhysAddr[index]);
+                       if (devpriv->dma0Buff[index] == NULL) {
+                               ret = -ENOMEM;
+                               goto rtd_attach_die_error;
+                       }
+                       /*DPRINTK ("buff[%d] @ %p virtual, %x PCI\n",
+                          index,
+                          devpriv->dma0Buff[index], devpriv->dma0BuffPhysAddr[index]); */
+               }
+
+               /* setup DMA descriptor ring (use cpu_to_le32 for byte ordering?) */
+               devpriv->dma0Chain =
+                       pci_alloc_consistent(devpriv->pci_dev,
+                       sizeof(struct plx_dma_desc) * DMA_CHAIN_COUNT,
+                       &devpriv->dma0ChainPhysAddr);
+               for (index = 0; index < DMA_CHAIN_COUNT; index++) {
+                       devpriv->dma0Chain[index].pci_start_addr =
+                               devpriv->dma0BuffPhysAddr[index];
+                       devpriv->dma0Chain[index].local_start_addr =
+                               DMALADDR_ADC;
+                       devpriv->dma0Chain[index].transfer_size =
+                               sizeof(u16) * devpriv->fifoLen / 2;
+                       devpriv->dma0Chain[index].next =
+                               (devpriv->dma0ChainPhysAddr + ((index +
+                                               1) % (DMA_CHAIN_COUNT))
+                               * sizeof(devpriv->dma0Chain[0]))
+                               | DMA_TRANSFER_BITS;
+                       /*DPRINTK ("ring[%d] @%lx PCI: %x, local: %x, N: 0x%x, next: %x\n",
+                          index,
+                          ((long)devpriv->dma0ChainPhysAddr
+                          + (index * sizeof(devpriv->dma0Chain[0]))),
+                          devpriv->dma0Chain[index].pci_start_addr,
+                          devpriv->dma0Chain[index].local_start_addr,
+                          devpriv->dma0Chain[index].transfer_size,
+                          devpriv->dma0Chain[index].next); */
+               }
+
+               if (devpriv->dma0Chain == NULL) {
+                       ret = -ENOMEM;
+                       goto rtd_attach_die_error;
+               }
+
+               RtdDma0Mode(dev, DMA_MODE_BITS);
+               RtdDma0Source(dev, DMAS_ADFIFO_HALF_FULL);      /* set DMA trigger source */
+       } else {
+               printk("( no IRQ->no DMA )");
+       }
+#endif /* USE_DMA */
+
+       if (dev->irq) {         /* enable plx9080 interrupts */
+               RtdPlxInterruptWrite(dev, ICS_PIE | ICS_PLIE);
+       }
+
+       printk("\ncomedi%d: rtd520 driver attached.\n", dev->minor);
+
+       return 1;
+
+#if 0
+       /* hit an error, clean up memory and return ret */
+//rtd_attach_die_error:
+#ifdef USE_DMA
+       for (index = 0; index < DMA_CHAIN_COUNT; index++) {
+               if (NULL != devpriv->dma0Buff[index]) { /* free buffer memory */
+                       pci_free_consistent(devpriv->pci_dev,
+                               sizeof(u16) * devpriv->fifoLen / 2,
+                               devpriv->dma0Buff[index],
+                               devpriv->dma0BuffPhysAddr[index]);
+                       devpriv->dma0Buff[index] = NULL;
+               }
+       }
+       if (NULL != devpriv->dma0Chain) {
+               pci_free_consistent(devpriv->pci_dev,
+                       sizeof(struct plx_dma_desc)
+                       * DMA_CHAIN_COUNT,
+                       devpriv->dma0Chain, devpriv->dma0ChainPhysAddr);
+               devpriv->dma0Chain = NULL;
+       }
+#endif /* USE_DMA */
+       /* subdevices and priv are freed by the core */
+       if (dev->irq) {
+               /* disable interrupt controller */
+               RtdPlxInterruptWrite(dev, RtdPlxInterruptRead(dev)
+                       & ~(ICS_PLIE | ICS_DMA0_E | ICS_DMA1_E));
+               comedi_free_irq(dev->irq, dev);
+       }
+
+       /* release all regions that were allocated */
+       if (devpriv->las0) {
+               iounmap(devpriv->las0);
+       }
+       if (devpriv->las1) {
+               iounmap(devpriv->las1);
+       }
+       if (devpriv->lcfg) {
+               iounmap(devpriv->lcfg);
+       }
+       if (devpriv->pci_dev) {
+               pci_dev_put(devpriv->pci_dev);
+       }
+       return ret;
+#endif
+}
+
+/*
+ * _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 rtd_detach(comedi_device * dev)
+{
+#ifdef USE_DMA
+       int index;
+#endif
+
+       DPRINTK("comedi%d: rtd520: removing (%ld ints)\n",
+               dev->minor, (devpriv ? devpriv->intCount : 0L));
+       if (devpriv && devpriv->lcfg) {
+               DPRINTK("(int status 0x%x, overrun status 0x%x, fifo status 0x%x)...\n", 0xffff & RtdInterruptStatus(dev), 0xffff & RtdInterruptOverrunStatus(dev), (0xffff & RtdFifoStatus(dev)) ^ 0x6666);
+       }
+
+       if (devpriv) {
+               /* Shut down any board ops by resetting it */
+#ifdef USE_DMA
+               if (devpriv->lcfg) {
+                       RtdDma0Control(dev, 0); /* disable DMA */
+                       RtdDma1Control(dev, 0); /* disable DMA */
+                       RtdPlxInterruptWrite(dev, ICS_PIE | ICS_PLIE);
+               }
+#endif /* USE_DMA */
+               if (devpriv->las0) {
+                       RtdResetBoard(dev);
+                       RtdInterruptMask(dev, 0);
+                       RtdInterruptClearMask(dev, ~0);
+                       RtdInterruptClear(dev); /* clears bits set by mask */
+               }
+#ifdef USE_DMA
+               /* release DMA */
+               for (index = 0; index < DMA_CHAIN_COUNT; index++) {
+                       if (NULL != devpriv->dma0Buff[index]) {
+                               pci_free_consistent(devpriv->pci_dev,
+                                       sizeof(u16) * devpriv->fifoLen / 2,
+                                       devpriv->dma0Buff[index],
+                                       devpriv->dma0BuffPhysAddr[index]);
+                               devpriv->dma0Buff[index] = NULL;
+                       }
+               }
+               if (NULL != devpriv->dma0Chain) {
+                       pci_free_consistent(devpriv->pci_dev,
+                               sizeof(struct plx_dma_desc) * DMA_CHAIN_COUNT,
+                               devpriv->dma0Chain, devpriv->dma0ChainPhysAddr);
+                       devpriv->dma0Chain = NULL;
+               }
+#endif /* USE_DMA */
+
+               /* release IRQ */
+               if (dev->irq) {
+                       /* disable interrupt controller */
+                       RtdPlxInterruptWrite(dev, RtdPlxInterruptRead(dev)
+                               & ~(ICS_PLIE | ICS_DMA0_E | ICS_DMA1_E));
+                       comedi_free_irq(dev->irq, dev);
+               }
+
+               /* release all regions that were allocated */
+               if (devpriv->las0) {
+                       iounmap(devpriv->las0);
+               }
+               if (devpriv->las1) {
+                       iounmap(devpriv->las1);
+               }
+               if (devpriv->lcfg) {
+                       iounmap(devpriv->lcfg);
+               }
+               if (devpriv->pci_dev) {
+                       if (devpriv->got_regions) {
+                               comedi_pci_disable(devpriv->pci_dev);
+                       }
+                       pci_dev_put(devpriv->pci_dev);
+               }
+       }
+
+       printk("comedi%d: rtd520: removed.\n", dev->minor);
+
+       return 0;
+}
+
+/*
+  Convert a single comedi channel-gain entry to a RTD520 table entry
+*/
+static unsigned short rtdConvertChanGain(comedi_device * dev,
+       unsigned int comediChan, int chanIndex)
+{                              /* index in channel list */
+       unsigned int chan, range, aref;
+       unsigned short r = 0;
+
+       chan = CR_CHAN(comediChan);
+       range = CR_RANGE(comediChan);
+       aref = CR_AREF(comediChan);
+
+       r |= chan & 0xf;
+
+       /* Note: we also setup the channel list bipolar flag array */
+       if (range < thisboard->range10Start) {  /* first batch are +-5 */
+               r |= 0x000;     /* +-5 range */
+               r |= (range & 0x7) << 4;        /* gain */
+               CHAN_ARRAY_SET(devpriv->chanBipolar, chanIndex);
+       } else if (range < thisboard->rangeUniStart) {  /* second batch are +-10 */
+               r |= 0x100;     /* +-10 range */
+               r |= ((range - thisboard->range10Start) & 0x7) << 4;    /* gain */
+               CHAN_ARRAY_SET(devpriv->chanBipolar, chanIndex);
+       } else {                /* last batch is +10 */
+               r |= 0x200;     /* +10 range */
+               r |= ((range - thisboard->rangeUniStart) & 0x7) << 4;   /* gain */
+               CHAN_ARRAY_CLEAR(devpriv->chanBipolar, chanIndex);
+       }
+
+       switch (aref) {
+       case AREF_GROUND:       /* on-board ground */
+               break;
+
+       case AREF_COMMON:
+               r |= 0x80;      /* ref external analog common */
+               break;
+
+       case AREF_DIFF:
+               r |= 0x400;     /* differential inputs */
+               break;
+
+       case AREF_OTHER:        /* ??? */
+               break;
+       }
+       /*printk ("chan=%d r=%d a=%d -> 0x%x\n",
+          chan, range, aref, r); */
+       return r;
+}
+
+/*
+  Setup the channel-gain table from a comedi list
+*/
+static void rtd_load_channelgain_list(comedi_device * dev,
+       unsigned int n_chan, unsigned int *list)
+{
+       if (n_chan > 1) {       /* setup channel gain table */
+               int ii;
+               RtdClearCGT(dev);
+               RtdEnableCGT(dev, 1);   /* enable table */
+               for (ii = 0; ii < n_chan; ii++) {
+                       RtdWriteCGTable(dev, rtdConvertChanGain(dev, list[ii],
+                                       ii));
+               }
+       } else {                /* just use the channel gain latch */
+               RtdEnableCGT(dev, 0);   /* disable table, enable latch */
+               RtdWriteCGLatch(dev, rtdConvertChanGain(dev, list[0], 0));
+       }
+}
+
+/* determine fifo size by doing adc conversions until the fifo half
+empty status flag clears */
+static int rtd520_probe_fifo_depth(comedi_device *dev)
+{
+       lsampl_t chanspec = CR_PACK(0, 0, AREF_GROUND);
+       unsigned i;
+       static const unsigned limit = 0x2000;
+       unsigned fifo_size = 0;
+
+       RtdAdcClearFifo(dev);
+       rtd_load_channelgain_list(dev, 1, &chanspec);
+       RtdAdcConversionSource(dev, 0); /* software */
+       /* convert  samples */
+       for (i = 0; i < limit; ++i) {
+               unsigned fifo_status;
+               /* trigger conversion */
+               RtdAdcStart(dev);
+               comedi_udelay(1);
+               fifo_status = RtdFifoStatus(dev);
+               if((fifo_status & FS_ADC_HEMPTY) == 0) {
+                       fifo_size = 2 * i;
+                       break;
+               }
+       }
+       if(i == limit)
+       {
+               rt_printk("\ncomedi: %s: failed to probe fifo size.\n", DRV_NAME);
+               return -EIO;
+       }
+       RtdAdcClearFifo(dev);
+       if(fifo_size != 0x400 || fifo_size != 0x2000)
+       {
+               rt_printk("\ncomedi: %s: unexpected fifo size of %i, expected 1024 or 8192.\n",
+                       DRV_NAME, fifo_size);
+               return -EIO;
+       }
+       return fifo_size;
+}
+
+/*
+  "instructions" read/write data in "one-shot" or "software-triggered"
+  mode (simplest case).
+  This doesnt use interrupts.
+
+  Note, we don't do any settling delays.  Use a instruction list to
+  select, delay, then read.
+ */
+static int rtd_ai_rinsn(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data)
+{
+       int n, ii;
+       int stat;
+
+       /* clear any old fifo data */
+       RtdAdcClearFifo(dev);
+
+       /* write channel to multiplexer and clear channel gain table */
+       rtd_load_channelgain_list(dev, 1, &insn->chanspec);
+
+       /* set conversion source */
+       RtdAdcConversionSource(dev, 0); /* software */
+
+       /* convert n samples */
+       for (n = 0; n < insn->n; n++) {
+               s16 d;
+               /* trigger conversion */
+               RtdAdcStart(dev);
+
+               for (ii = 0; ii < RTD_ADC_TIMEOUT; ++ii) {
+                       stat = RtdFifoStatus(dev);
+                       if (stat & FS_ADC_NOT_EMPTY)    /* 1 -> not empty */
+                               break;
+                       WAIT_QUIETLY;
+               }
+               if (ii >= RTD_ADC_TIMEOUT) {
+                       DPRINTK("rtd520: Error: ADC never finished! FifoStatus=0x%x\n", stat ^ 0x6666);
+                       return -ETIMEDOUT;
+               }
+
+               /* read data */
+               d = RtdAdcFifoGet(dev); /* get 2s comp value */
+               /*printk ("rtd520: Got 0x%x after %d usec\n", d, ii+1); */
+               d = d >> 3;     /* low 3 bits are marker lines */
+               if (CHAN_ARRAY_TEST(devpriv->chanBipolar, 0)) {
+                       data[n] = d + 2048;     /* convert to comedi unsigned data */
+               } else {
+                       data[n] = d;
+               }
+       }
+
+       /* return the number of samples read/written */
+       return n;
+}
+
+/*
+  Get what we know is there.... Fast!
+  This uses 1/2 the bus cycles of read_dregs (below).
+
+  The manual claims that we can do a lword read, but it doesn't work here.
+*/
+static int ai_read_n(comedi_device * dev, comedi_subdevice * s, int count)
+{
+       int ii;
+
+       for (ii = 0; ii < count; ii++) {
+               sampl_t sample;
+               s16 d;
+
+               if (0 == devpriv->aiCount) {    /* done */
+                       d = RtdAdcFifoGet(dev); /* Read N and discard */
+                       continue;
+               }
+#if 0
+               if (0 == (RtdFifoStatus(dev) & FS_ADC_NOT_EMPTY)) {     /* DEBUG */
+                       DPRINTK("comedi: READ OOPS on %d of %d\n", ii + 1,
+                               count);
+                       break;
+               }
+#endif
+               d = RtdAdcFifoGet(dev); /* get 2s comp value */
+
+               d = d >> 3;     /* low 3 bits are marker lines */
+               if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) {
+                       sample = d + 2048;      /* convert to comedi unsigned data */
+               } else {
+                       sample = d;
+               }
+               if (!comedi_buf_put(s->async, sample))
+                       return -1;
+
+               if (devpriv->aiCount > 0)       /* < 0, means read forever */
+                       devpriv->aiCount--;
+       }
+       return 0;
+}
+
+/*
+  unknown amout of data is waiting in fifo.
+*/
+static int ai_read_dregs(comedi_device * dev, comedi_subdevice * s)
+{
+       while (RtdFifoStatus(dev) & FS_ADC_NOT_EMPTY) { /* 1 -> not empty */
+               sampl_t sample;
+               s16 d = RtdAdcFifoGet(dev);     /* get 2s comp value */
+
+               if (0 == devpriv->aiCount) {    /* done */
+                       continue;       /* read rest */
+               }
+
+               d = d >> 3;     /* low 3 bits are marker lines */
+               if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) {
+                       sample = d + 2048;      /* convert to comedi unsigned data */
+               } else {
+                       sample = d;
+               }
+               if (!comedi_buf_put(s->async, sample))
+                       return -1;
+
+               if (devpriv->aiCount > 0)       /* < 0, means read forever */
+                       devpriv->aiCount--;
+       }
+       return 0;
+}
+
+#ifdef USE_DMA
+/*
+  Terminate a DMA transfer and wait for everything to quiet down
+*/
+void abort_dma(comedi_device * dev, unsigned int channel)
+{                              /* DMA channel 0, 1 */
+       unsigned long dma_cs_addr;      /* the control/status register */
+       uint8_t status;
+       unsigned int ii;
+       //unsigned long flags;
+
+       dma_cs_addr = (unsigned long)devpriv->lcfg
+               + ((channel == 0) ? LCFG_DMACSR0 : LCFG_DMACSR1);
+
+       // spinlock for plx dma control/status reg
+       //comedi_spin_lock_irqsave( &dev->spinlock, flags );
+
+       // abort dma transfer if necessary
+       status = readb(dma_cs_addr);
+       if ((status & PLX_DMA_EN_BIT) == 0) {   /* not enabled (Error?) */
+               DPRINTK("rtd520: AbortDma on non-active channel %d (0x%x)\n",
+                       channel, status);
+               goto abortDmaExit;
+       }
+
+       /* wait to make sure done bit is zero (needed?) */
+       for (ii = 0; (status & PLX_DMA_DONE_BIT) && ii < RTD_DMA_TIMEOUT; ii++) {
+               WAIT_QUIETLY;
+               status = readb(dma_cs_addr);
+       }
+       if (status & PLX_DMA_DONE_BIT) {
+               printk("rtd520: Timeout waiting for dma %i done clear\n",
+                       channel);
+               goto abortDmaExit;
+       }
+
+       /* disable channel (required) */
+       writeb(0, dma_cs_addr);
+       comedi_udelay(1);       /* needed?? */
+       /* set abort bit for channel */
+       writeb(PLX_DMA_ABORT_BIT, dma_cs_addr);
+
+       // wait for dma done bit to be set
+       status = readb(dma_cs_addr);
+       for (ii = 0;
+               (status & PLX_DMA_DONE_BIT) == 0 && ii < RTD_DMA_TIMEOUT;
+               ii++) {
+               status = readb(dma_cs_addr);
+               WAIT_QUIETLY;
+       }
+       if ((status & PLX_DMA_DONE_BIT) == 0) {
+               printk("rtd520: Timeout waiting for dma %i done set\n",
+                       channel);
+       }
+
+      abortDmaExit:
+       //comedi_spin_unlock_irqrestore( &dev->spinlock, flags );
+}
+
+/*
+  Process what is in the DMA transfer buffer and pass to comedi
+  Note: this is not re-entrant
+*/
+static int ai_process_dma(comedi_device * dev, comedi_subdevice * s)
+{
+       int ii, n;
+       s16 *dp;
+
+       if (devpriv->aiCount == 0)      /* transfer already complete */
+               return 0;
+
+       dp = devpriv->dma0Buff[devpriv->dma0Offset];
+       for (ii = 0; ii < devpriv->fifoLen / 2;) {      /* convert samples */
+               sampl_t sample;
+
+               if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) {
+                       sample = (*dp >> 3) + 2048;     /* convert to comedi unsigned data */
+               } else {
+                       sample = *dp >> 3;      /* low 3 bits are marker lines */
+               }
+               *dp++ = sample; /* put processed value back */
+
+               if (++s->async->cur_chan >= s->async->cmd.chanlist_len)
+                       s->async->cur_chan = 0;
+
+               ++ii;           /* number ready to transfer */
+               if (devpriv->aiCount > 0) {     /* < 0, means read forever */
+                       if (--devpriv->aiCount == 0) {  /* done */
+                               /*DPRINTK ("rtd520: Final %d samples\n", ii); */
+                               break;
+                       }
+               }
+       }
+
+       /* now pass the whole array to the comedi buffer */
+       dp = devpriv->dma0Buff[devpriv->dma0Offset];
+       n = comedi_buf_write_alloc(s->async, ii * sizeof(s16));
+       if (n < (ii * sizeof(s16))) {   /* any residual is an error */
+               DPRINTK("rtd520:ai_process_dma buffer overflow %d samples!\n",
+                       ii - (n / sizeof(s16)));
+               s->async->events |= COMEDI_CB_ERROR;
+               return -1;
+       }
+       comedi_buf_memcpy_to(s->async, 0, dp, n);
+       comedi_buf_write_free(s->async, n);
+
+       /* always at least 1 scan -- 1/2 FIFO is larger than our max scan list */
+       s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
+
+       if (++devpriv->dma0Offset >= DMA_CHAIN_COUNT) { /* next buffer */
+               devpriv->dma0Offset = 0;
+       }
+       return 0;
+}
+#endif /* USE_DMA */
+
+/*
+  Handle all rtd520 interrupts.
+  Runs atomically and is never re-entered.
+  This is a "slow handler";  other interrupts may be active.
+  The data conversion may someday happen in a "bottom half".
+*/
+static irqreturn_t rtd_interrupt(int irq,      /* interrupt number (ignored) */
+       void *d                 /* our data */
+       PT_REGS_ARG)
+{                              /* cpu context (ignored) */
+       comedi_device *dev = d; /* must be called "dev" for devpriv */
+       u16 status;
+       u16 fifoStatus;
+       comedi_subdevice *s = dev->subdevices + 0;      /* analog in subdevice */
+
+       if (!dev->attached) {
+               return IRQ_NONE;
+       }
+
+       devpriv->intCount++;    /* DEBUG statistics */
+
+       fifoStatus = RtdFifoStatus(dev);
+       /* check for FIFO full, this automatically halts the ADC! */
+       if (!(fifoStatus & FS_ADC_NOT_FULL)) {  /* 0 -> full */
+               DPRINTK("rtd520: FIFO full! fifo_status=0x%x\n", (fifoStatus ^ 0x6666) & 0x7777);       /* should be all 0s */
+               goto abortTransfer;
+       }
+#ifdef USE_DMA
+       if (devpriv->flags & DMA0_ACTIVE) {     /* Check DMA */
+               u32 istatus = RtdPlxInterruptRead(dev);
+
+               if (istatus & ICS_DMA0_A) {
+                       if (ai_process_dma(dev, s) < 0) {
+                               DPRINTK("rtd520: comedi read buffer overflow (DMA) with %ld to go!\n", devpriv->aiCount);
+                               RtdDma0Control(dev,
+                                       (devpriv->
+                                               dma0Control &
+                                               ~PLX_DMA_START_BIT)
+                                       | PLX_CLEAR_DMA_INTR_BIT);
+                               goto abortTransfer;
+                       }
+
+                       /*DPRINTK ("rtd520: DMA transfer: %ld to go, istatus %x\n",
+                          devpriv->aiCount, istatus); */
+                       RtdDma0Control(dev,
+                               (devpriv->dma0Control & ~PLX_DMA_START_BIT)
+                               | PLX_CLEAR_DMA_INTR_BIT);
+                       if (0 == devpriv->aiCount) {    /* counted down */
+                               DPRINTK("rtd520: Samples Done (DMA).\n");
+                               goto transferDone;
+                       }
+                       comedi_event(dev, s);
+               } else {
+                       /*DPRINTK ("rtd520: No DMA ready: istatus %x\n", istatus); */
+               }
+       }
+       /* Fall through and check for other interrupt sources */
+#endif /* USE_DMA */
+
+       status = RtdInterruptStatus(dev);
+       /* if interrupt was not caused by our board, or handled above */
+       if (0 == status) {
+               return IRQ_HANDLED;
+       }
+
+       if (status & IRQM_ADC_ABOUT_CNT) {      /* sample count -> read FIFO */
+               /* since the priority interrupt controller may have queued a sample
+                  counter interrupt, even though we have already finished,
+                  we must handle the possibility that there is no data here */
+               if (!(fifoStatus & FS_ADC_HEMPTY)) {    /* 0 -> 1/2 full */
+                       /*DPRINTK("rtd520: Sample int, reading 1/2FIFO.  fifo_status 0x%x\n",
+                          (fifoStatus ^ 0x6666) & 0x7777); */
+                       if (ai_read_n(dev, s, devpriv->fifoLen / 2) < 0) {
+                               DPRINTK("rtd520: comedi read buffer overflow (1/2FIFO) with %ld to go!\n", devpriv->aiCount);
+                               goto abortTransfer;
+                       }
+                       if (0 == devpriv->aiCount) {    /* counted down */
+                               DPRINTK("rtd520: Samples Done (1/2). fifo_status was 0x%x\n", (fifoStatus ^ 0x6666) & 0x7777);  /* should be all 0s */
+                               goto transferDone;
+                       }
+                       comedi_event(dev, s);
+               } else if (devpriv->transCount > 0) {   /* read often */
+                       /*DPRINTK("rtd520: Sample int, reading %d  fifo_status 0x%x\n",
+                          devpriv->transCount, (fifoStatus ^ 0x6666) & 0x7777); */
+                       if (fifoStatus & FS_ADC_NOT_EMPTY) {    /* 1 -> not empty */
+                               if (ai_read_n(dev, s, devpriv->transCount) < 0) {
+                                       DPRINTK("rtd520: comedi read buffer overflow (N) with %ld to go!\n", devpriv->aiCount);
+                                       goto abortTransfer;
+                               }
+                               if (0 == devpriv->aiCount) {    /* counted down */
+                                       DPRINTK("rtd520: Samples Done (N). fifo_status was 0x%x\n", (fifoStatus ^ 0x6666) & 0x7777);
+                                       goto transferDone;
+                               }
+                               comedi_event(dev, s);
+                       }
+               } else {        /* wait for 1/2 FIFO (old) */
+                       DPRINTK("rtd520: Sample int.  Wait for 1/2. fifo_status 0x%x\n", (fifoStatus ^ 0x6666) & 0x7777);
+               }
+       } else {
+               DPRINTK("rtd520: unknown interrupt source!\n");
+       }
+
+       if (0xffff & RtdInterruptOverrunStatus(dev)) {  /* interrupt overrun */
+               DPRINTK("rtd520: Interrupt overrun with %ld to go! over_status=0x%x\n", devpriv->aiCount, 0xffff & RtdInterruptOverrunStatus(dev));
+               goto abortTransfer;
+       }
+
+       /* clear the interrupt */
+       RtdInterruptClearMask(dev, status);
+       RtdInterruptClear(dev);
+       return IRQ_HANDLED;
+
+      abortTransfer:
+       RtdAdcClearFifo(dev);   /* clears full flag */
+       s->async->events |= COMEDI_CB_ERROR;
+       devpriv->aiCount = 0;   /* stop and don't transfer any more */
+       /* fall into transferDone */
+
+      transferDone:
+       RtdPacerStopSource(dev, 0);     /* stop on SOFTWARE stop */
+       RtdPacerStop(dev);      /* Stop PACER */
+       RtdAdcConversionSource(dev, 0); /* software trigger only */
+       RtdInterruptMask(dev, 0);       /* mask out SAMPLE */
+#ifdef USE_DMA
+       if (devpriv->flags & DMA0_ACTIVE) {
+               RtdPlxInterruptWrite(dev,       /* disable any more interrupts */
+                       RtdPlxInterruptRead(dev) & ~ICS_DMA0_E);
+               abort_dma(dev, 0);
+               devpriv->flags &= ~DMA0_ACTIVE;
+               /* if Using DMA, then we should have read everything by now */
+               if (devpriv->aiCount > 0) {
+                       DPRINTK("rtd520: Lost DMA data! %ld remain\n",
+                               devpriv->aiCount);
+               }
+       }
+#endif /* USE_DMA */
+
+       if (devpriv->aiCount > 0) {     /* there shouldn't be anything left */
+               fifoStatus = RtdFifoStatus(dev);
+               DPRINTK("rtd520: Finishing up. %ld remain, fifoStat=%x\n", devpriv->aiCount, (fifoStatus ^ 0x6666) & 0x7777);   /* should read all 0s */
+               ai_read_dregs(dev, s);  /* read anything left in FIFO */
+       }
+
+       s->async->events |= COMEDI_CB_EOA;      /* signal end to comedi */
+       comedi_event(dev, s);
+
+       /* clear the interrupt */
+       status = RtdInterruptStatus(dev);
+       RtdInterruptClearMask(dev, status);
+       RtdInterruptClear(dev);
+
+       fifoStatus = RtdFifoStatus(dev);        /* DEBUG */
+       DPRINTK("rtd520: Acquisition complete. %ld ints, intStat=%x, overStat=%x\n", devpriv->intCount, status, 0xffff & RtdInterruptOverrunStatus(dev));
+
+       return IRQ_HANDLED;
+}
+
+#if 0
+/*
+  return the number of samples available
+*/
+static int rtd_ai_poll(comedi_device * dev, comedi_subdevice * s)
+{
+       /* TODO: This needs to mask interrupts, read_dregs, and then re-enable */
+       /* Not sure what to do if DMA is active */
+       return s->async->buf_write_count - s->async->buf_read_count;
+}
+#endif
+
+/*
+  cmdtest tests a particular command to see if it is valid.
+  Using the cmdtest ioctl, a user can create a valid cmd
+  and then have it executed by the cmd ioctl (asyncronously).
+
+  cmdtest returns 1,2,3,4 or 0, depending on which tests
+  the command passes.
+*/
+
+static int rtd_ai_cmdtest(comedi_device * dev,
+       comedi_subdevice * s, comedi_cmd * cmd)
+{
+       int err = 0;
+       int tmp;
+
+       /* step 1: make sure trigger sources are trivially valid */
+
+       tmp = cmd->start_src;
+       cmd->start_src &= TRIG_NOW;
+       if (!cmd->start_src || tmp != cmd->start_src) {
+               err++;
+       }
+
+       tmp = cmd->scan_begin_src;
+       cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT;
+       if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) {
+               err++;
+       }
+
+       tmp = cmd->convert_src;
+       cmd->convert_src &= TRIG_TIMER | TRIG_EXT;
+       if (!cmd->convert_src || tmp != cmd->convert_src) {
+               err++;
+       }
+
+       tmp = cmd->scan_end_src;
+       cmd->scan_end_src &= TRIG_COUNT;
+       if (!cmd->scan_end_src || tmp != cmd->scan_end_src) {
+               err++;
+       }
+
+       tmp = cmd->stop_src;
+       cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
+       if (!cmd->stop_src || tmp != cmd->stop_src) {
+               err++;
+       }
+
+       if (err)
+               return 1;
+
+       /* step 2: make sure trigger sources are unique
+          and mutually compatible */
+       /* note that mutual compatiblity is not an issue here */
+       if (cmd->scan_begin_src != TRIG_TIMER &&
+               cmd->scan_begin_src != TRIG_EXT) {
+               err++;
+       }
+       if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) {
+               err++;
+       }
+       if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) {
+               err++;
+       }
+
+       if (err) {
+               return 2;
+       }
+
+       /* step 3: make sure arguments are trivially compatible */
+
+       if (cmd->start_arg != 0) {
+               cmd->start_arg = 0;
+               err++;
+       }
+
+       if (cmd->scan_begin_src == TRIG_TIMER) {
+               /* Note: these are time periods, not actual rates */
+               if (1 == cmd->chanlist_len) {   /* no scanning */
+                       if (cmd->scan_begin_arg < RTD_MAX_SPEED_1) {
+                               cmd->scan_begin_arg = RTD_MAX_SPEED_1;
+                               rtd_ns_to_timer(&cmd->scan_begin_arg,
+                                       TRIG_ROUND_UP);
+                               err++;
+                       }
+                       if (cmd->scan_begin_arg > RTD_MIN_SPEED_1) {
+                               cmd->scan_begin_arg = RTD_MIN_SPEED_1;
+                               rtd_ns_to_timer(&cmd->scan_begin_arg,
+                                       TRIG_ROUND_DOWN);
+                               err++;
+                       }
+               } else {
+                       if (cmd->scan_begin_arg < RTD_MAX_SPEED) {
+                               cmd->scan_begin_arg = RTD_MAX_SPEED;
+                               rtd_ns_to_timer(&cmd->scan_begin_arg,
+                                       TRIG_ROUND_UP);
+                               err++;
+                       }
+                       if (cmd->scan_begin_arg > RTD_MIN_SPEED) {
+                               cmd->scan_begin_arg = RTD_MIN_SPEED;
+                               rtd_ns_to_timer(&cmd->scan_begin_arg,
+                                       TRIG_ROUND_DOWN);
+                               err++;
+                       }
+               }
+       } else {
+               /* external trigger */
+               /* should be level/edge, hi/lo specification here */
+               /* should specify multiple external triggers */
+               if (cmd->scan_begin_arg > 9) {
+                       cmd->scan_begin_arg = 9;
+                       err++;
+               }
+       }
+       if (cmd->convert_src == TRIG_TIMER) {
+               if (1 == cmd->chanlist_len) {   /* no scanning */
+                       if (cmd->convert_arg < RTD_MAX_SPEED_1) {
+                               cmd->convert_arg = RTD_MAX_SPEED_1;
+                               rtd_ns_to_timer(&cmd->convert_arg,
+                                       TRIG_ROUND_UP);
+                               err++;
+                       }
+                       if (cmd->convert_arg > RTD_MIN_SPEED_1) {
+                               cmd->convert_arg = RTD_MIN_SPEED_1;
+                               rtd_ns_to_timer(&cmd->convert_arg,
+                                       TRIG_ROUND_DOWN);
+                               err++;
+                       }
+               } else {
+                       if (cmd->convert_arg < RTD_MAX_SPEED) {
+                               cmd->convert_arg = RTD_MAX_SPEED;
+                               rtd_ns_to_timer(&cmd->convert_arg,
+                                       TRIG_ROUND_UP);
+                               err++;
+                       }
+                       if (cmd->convert_arg > RTD_MIN_SPEED) {
+                               cmd->convert_arg = RTD_MIN_SPEED;
+                               rtd_ns_to_timer(&cmd->convert_arg,
+                                       TRIG_ROUND_DOWN);
+                               err++;
+                       }
+               }
+       } else {
+               /* external trigger */
+               /* see above */
+               if (cmd->convert_arg > 9) {
+                       cmd->convert_arg = 9;
+                       err++;
+               }
+       }
+
+#if 0
+       if (cmd->scan_end_arg != cmd->chanlist_len) {
+               cmd->scan_end_arg = cmd->chanlist_len;
+               err++;
+       }
+#endif
+       if (cmd->stop_src == TRIG_COUNT) {
+               /* TODO check for rounding error due to counter wrap */
+
+       } else {
+               /* TRIG_NONE */
+               if (cmd->stop_arg != 0) {
+                       cmd->stop_arg = 0;
+                       err++;
+               }
+       }
+
+       if (err) {
+               return 3;
+       }
+
+       /* step 4: fix up any arguments */
+
+       if (cmd->chanlist_len > RTD_MAX_CHANLIST) {
+               cmd->chanlist_len = RTD_MAX_CHANLIST;
+               err++;
+       }
+       if (cmd->scan_begin_src == TRIG_TIMER) {
+               tmp = cmd->scan_begin_arg;
+               rtd_ns_to_timer(&cmd->scan_begin_arg,
+                       cmd->flags & TRIG_ROUND_MASK);
+               if (tmp != cmd->scan_begin_arg) {
+                       err++;
+               }
+       }
+       if (cmd->convert_src == TRIG_TIMER) {
+               tmp = cmd->convert_arg;
+               rtd_ns_to_timer(&cmd->convert_arg,
+                       cmd->flags & TRIG_ROUND_MASK);
+               if (tmp != cmd->convert_arg) {
+                       err++;
+               }
+               if (cmd->scan_begin_src == TRIG_TIMER
+                       && (cmd->scan_begin_arg
+                               < (cmd->convert_arg * cmd->scan_end_arg))) {
+                       cmd->scan_begin_arg =
+                               cmd->convert_arg * cmd->scan_end_arg;
+                       err++;
+               }
+       }
+
+       if (err) {
+               return 4;
+       }
+
+       return 0;
+}
+
+/*
+  Execute a analog in command with many possible triggering options.
+  The data get stored in the async structure of the subdevice.
+  This is usually done by an interrupt handler.
+  Userland gets to the data using read calls.
+*/
+static int rtd_ai_cmd(comedi_device * dev, comedi_subdevice * s)
+{
+       comedi_cmd *cmd = &s->async->cmd;
+       int timer;
+
+       /* stop anything currently running */
+       RtdPacerStopSource(dev, 0);     /* stop on SOFTWARE stop */
+       RtdPacerStop(dev);      /* make sure PACER is stopped */
+       RtdAdcConversionSource(dev, 0); /* software trigger only */
+       RtdInterruptMask(dev, 0);
+#ifdef USE_DMA
+       if (devpriv->flags & DMA0_ACTIVE) {     /* cancel anything running */
+               RtdPlxInterruptWrite(dev,       /* disable any more interrupts */
+                       RtdPlxInterruptRead(dev) & ~ICS_DMA0_E);
+               abort_dma(dev, 0);
+               devpriv->flags &= ~DMA0_ACTIVE;
+               if (RtdPlxInterruptRead(dev) & ICS_DMA0_A) {    /*clear pending int */
+                       RtdDma0Control(dev, PLX_CLEAR_DMA_INTR_BIT);
+               }
+       }
+       RtdDma0Reset(dev);      /* reset onboard state */
+#endif /* USE_DMA */
+       RtdAdcClearFifo(dev);   /* clear any old data */
+       RtdInterruptOverrunClear(dev);
+       devpriv->intCount = 0;
+
+       if (!dev->irq) {        /* we need interrupts for this */
+               DPRINTK("rtd520: ERROR! No interrupt available!\n");
+               return -ENXIO;
+       }
+
+       /* start configuration */
+       /* load channel list and reset CGT */
+       rtd_load_channelgain_list(dev, cmd->chanlist_len, cmd->chanlist);
+
+       /* setup the common case and override if needed */
+       if (cmd->chanlist_len > 1) {
+               /*DPRINTK ("rtd520: Multi channel setup\n"); */
+               RtdPacerStartSource(dev, 0);    /* software triggers pacer */
+               RtdBurstStartSource(dev, 1);    /* PACER triggers burst */
+               RtdAdcConversionSource(dev, 2); /* BURST triggers ADC */
+       } else {                /* single channel */
+               /*DPRINTK ("rtd520: single channel setup\n"); */
+               RtdPacerStartSource(dev, 0);    /* software triggers pacer */
+               RtdAdcConversionSource(dev, 1); /* PACER triggers ADC */
+       }
+       RtdAboutCounter(dev, devpriv->fifoLen / 2 - 1); /* 1/2 FIFO */
+
+       if (TRIG_TIMER == cmd->scan_begin_src) {
+               /* scan_begin_arg is in nanoseconds */
+               /* find out how many samples to wait before transferring */
+               if (cmd->flags & TRIG_WAKE_EOS) {
+                       /* this may generate un-sustainable interrupt rates */
+                       /* the application is responsible for doing the right thing */
+                       devpriv->transCount = cmd->chanlist_len;
+                       devpriv->flags |= SEND_EOS;
+               } else {
+                       /* arrange to transfer data periodically */
+                       devpriv->transCount
+                               =
+                               (TRANS_TARGET_PERIOD * cmd->chanlist_len) /
+                               cmd->scan_begin_arg;
+                       if (devpriv->transCount < cmd->chanlist_len) {
+                               /* tranfer after each scan (and avoid 0) */
+                               devpriv->transCount = cmd->chanlist_len;
+                       } else {        /* make a multiple of scan length */
+                               devpriv->transCount =
+                                       (devpriv->transCount +
+                                       cmd->chanlist_len - 1)
+                                       / cmd->chanlist_len;
+                               devpriv->transCount *= cmd->chanlist_len;
+                       }
+                       devpriv->flags |= SEND_EOS;
+               }
+               if (devpriv->transCount >= (devpriv->fifoLen / 2)) {
+                       /* out of counter range, use 1/2 fifo instead */
+                       devpriv->transCount = 0;
+                       devpriv->flags &= ~SEND_EOS;
+               } else {
+                       /* interrupt for each tranfer */
+                       RtdAboutCounter(dev, devpriv->transCount - 1);
+               }
+
+               DPRINTK("rtd520: scanLen=%d tranferCount=%d fifoLen=%d\n  scanTime(ns)=%d flags=0x%x\n", cmd->chanlist_len, devpriv->transCount, devpriv->fifoLen, cmd->scan_begin_arg, devpriv->flags);
+       } else {                /* unknown timing, just use 1/2 FIFO */
+               devpriv->transCount = 0;
+               devpriv->flags &= ~SEND_EOS;
+       }
+       RtdPacerClockSource(dev, 1);    /* use INTERNAL 8Mhz clock source */
+       RtdAboutStopEnable(dev, 1);     /* just interrupt, dont stop */
+
+       /* BUG??? these look like enumerated values, but they are bit fields */
+
+       /* First, setup when to stop */
+       switch (cmd->stop_src) {
+       case TRIG_COUNT:        /* stop after N scans */
+               devpriv->aiCount = cmd->stop_arg * cmd->chanlist_len;
+               if ((devpriv->transCount > 0)
+                       && (devpriv->transCount > devpriv->aiCount)) {
+                       devpriv->transCount = devpriv->aiCount;
+               }
+               break;
+
+       case TRIG_NONE: /* stop when cancel is called */
+               devpriv->aiCount = -1;  /* read forever */
+               break;
+
+       default:
+               DPRINTK("rtd520: Warning! ignoring stop_src mode %d\n",
+                       cmd->stop_src);
+       }
+
+       /* Scan timing */
+       switch (cmd->scan_begin_src) {
+       case TRIG_TIMER:        /* periodic scanning */
+               timer = rtd_ns_to_timer(&cmd->scan_begin_arg,
+                       TRIG_ROUND_NEAREST);
+               /* set PACER clock */
+               /*DPRINTK ("rtd520: loading %d into pacer\n", timer); */
+               RtdPacerCounter(dev, timer);
+
+               break;
+
+       case TRIG_EXT:
+               RtdPacerStartSource(dev, 1);    /* EXTERNALy trigger pacer */
+               break;
+
+       default:
+               DPRINTK("rtd520: Warning! ignoring scan_begin_src mode %d\n",
+                       cmd->scan_begin_src);
+       }
+
+       /* Sample timing within a scan */
+       switch (cmd->convert_src) {
+       case TRIG_TIMER:        /* periodic */
+               if (cmd->chanlist_len > 1) {    /* only needed for multi-channel */
+                       timer = rtd_ns_to_timer(&cmd->convert_arg,
+                               TRIG_ROUND_NEAREST);
+                       /* setup BURST clock */
+                       /*DPRINTK ("rtd520: loading %d into burst\n", timer); */
+                       RtdBurstCounter(dev, timer);
+               }
+
+               break;
+
+       case TRIG_EXT:          /* external */
+               RtdBurstStartSource(dev, 2);    /* EXTERNALy trigger burst */
+               break;
+
+       default:
+               DPRINTK("rtd520: Warning! ignoring convert_src mode %d\n",
+                       cmd->convert_src);
+       }
+       /* end configuration */
+
+       /* This doesn't seem to work.  There is no way to clear an interrupt
+          that the priority controller has queued! */
+       RtdInterruptClearMask(dev, ~0); /* clear any existing flags */
+       RtdInterruptClear(dev);
+
+       /* TODO: allow multiple interrupt sources */
+       if (devpriv->transCount > 0) {  /* transfer every N samples */
+               RtdInterruptMask(dev, IRQM_ADC_ABOUT_CNT);
+               DPRINTK("rtd520: Transferring every %d\n", devpriv->transCount);
+       } else {                /* 1/2 FIFO transfers */
+#ifdef USE_DMA
+               devpriv->flags |= DMA0_ACTIVE;
+
+               /* point to first transfer in ring */
+               devpriv->dma0Offset = 0;
+               RtdDma0Mode(dev, DMA_MODE_BITS);
+               RtdDma0Next(dev,        /* point to first block */
+                       devpriv->dma0Chain[DMA_CHAIN_COUNT - 1].next);
+               RtdDma0Source(dev, DMAS_ADFIFO_HALF_FULL);      /* set DMA trigger source */
+
+               RtdPlxInterruptWrite(dev,       /* enable interrupt */
+                       RtdPlxInterruptRead(dev) | ICS_DMA0_E);
+               /* Must be 2 steps.  See PLX app note about "Starting a DMA transfer" */
+               RtdDma0Control(dev, PLX_DMA_EN_BIT);    /* enable DMA (clear INTR?) */
+               RtdDma0Control(dev, PLX_DMA_EN_BIT | PLX_DMA_START_BIT);        /*start DMA */
+               DPRINTK("rtd520: Using DMA0 transfers. plxInt %x RtdInt %x\n",
+                       RtdPlxInterruptRead(dev), devpriv->intMask);
+#else /* USE_DMA */
+               RtdInterruptMask(dev, IRQM_ADC_ABOUT_CNT);
+               DPRINTK("rtd520: Transferring every 1/2 FIFO\n");
+#endif /* USE_DMA */
+       }
+
+       /* BUG: start_src is ASSUMED to be TRIG_NOW */
+       /* BUG? it seems like things are running before the "start" */
+       RtdPacerStart(dev);     /* Start PACER */
+       return 0;
+}
+
+/*
+  Stop a running data aquisition.
+*/
+static int rtd_ai_cancel(comedi_device * dev, comedi_subdevice * s)
+{
+       u16 status;
+
+       RtdPacerStopSource(dev, 0);     /* stop on SOFTWARE stop */
+       RtdPacerStop(dev);      /* Stop PACER */
+       RtdAdcConversionSource(dev, 0); /* software trigger only */
+       RtdInterruptMask(dev, 0);
+       devpriv->aiCount = 0;   /* stop and don't transfer any more */
+#ifdef USE_DMA
+       if (devpriv->flags & DMA0_ACTIVE) {
+               RtdPlxInterruptWrite(dev,       /* disable any more interrupts */
+                       RtdPlxInterruptRead(dev) & ~ICS_DMA0_E);
+               abort_dma(dev, 0);
+               devpriv->flags &= ~DMA0_ACTIVE;
+       }
+#endif /* USE_DMA */
+       status = RtdInterruptStatus(dev);
+       DPRINTK("rtd520: Acquisition canceled. %ld ints, intStat=%x, overStat=%x\n", devpriv->intCount, status, 0xffff & RtdInterruptOverrunStatus(dev));
+       return 0;
+}
+
+/*
+  Given a desired period and the clock period (both in ns),
+  return the proper counter value (divider-1).
+  Sets the original period to be the true value.
+  Note: you have to check if the value is larger than the counter range!
+*/
+static int rtd_ns_to_timer_base(unsigned int *nanosec, /* desired period (in ns) */
+       int round_mode, int base)
+{                              /* clock period (in ns) */
+       int divider;
+
+       switch (round_mode) {
+       case TRIG_ROUND_NEAREST:
+       default:
+               divider = (*nanosec + base / 2) / base;
+               break;
+       case TRIG_ROUND_DOWN:
+               divider = (*nanosec) / base;
+               break;
+       case TRIG_ROUND_UP:
+               divider = (*nanosec + base - 1) / base;
+               break;
+       }
+       if (divider < 2)
+               divider = 2;    /* min is divide by 2 */
+
+       /* Note: we don't check for max, because different timers
+          have different ranges */
+
+       *nanosec = base * divider;
+       return divider - 1;     /* countdown is divisor+1 */
+}
+
+/*
+  Given a desired period (in ns),
+  return the proper counter value (divider-1) for the internal clock.
+  Sets the original period to be the true value.
+*/
+static int rtd_ns_to_timer(unsigned int *ns, int round_mode)
+{
+       return rtd_ns_to_timer_base(ns, round_mode, RTD_CLOCK_BASE);
+}
+
+/*
+  Output one (or more) analog values to a single port as fast as possible.
+*/
+static int rtd_ao_winsn(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data)
+{
+       int i;
+       int chan = CR_CHAN(insn->chanspec);
+       int range = CR_RANGE(insn->chanspec);
+
+       /* Configure the output range (table index matches the range values) */
+       RtdDacRange(dev, chan, range);
+
+       /* Writing a list of values to an AO channel is probably not
+        * very useful, but that's how the interface is defined. */
+       for (i = 0; i < insn->n; ++i) {
+               int val = data[i] << 3;
+               int stat = 0;   /* initialize to avoid bogus warning */
+               int ii;
+
+               /* VERIFY: comedi range and offset conversions */
+
+               if ((range > 1) /* bipolar */
+                       &&(data[i] < 2048)) {
+                       /* offset and sign extend */
+                       val = (((int)data[i]) - 2048) << 3;
+               } else {        /* unipolor */
+                       val = data[i] << 3;
+               }
+
+               DPRINTK("comedi: rtd520 DAC chan=%d range=%d writing %d as 0x%x\n", chan, range, data[i], val);
+
+               /* a typical programming sequence */
+               RtdDacFifoPut(dev, chan, val);  /* put the value in */
+               RtdDacUpdate(dev, chan);        /* trigger the conversion */
+
+               devpriv->aoValue[chan] = data[i];       /* save for read back */
+
+               for (ii = 0; ii < RTD_DAC_TIMEOUT; ++ii) {
+                       stat = RtdFifoStatus(dev);
+                       /* 1 -> not empty */
+                       if (stat & ((0 == chan) ? FS_DAC1_NOT_EMPTY :
+                                       FS_DAC2_NOT_EMPTY))
+                               break;
+                       WAIT_QUIETLY;
+               }
+               if (ii >= RTD_DAC_TIMEOUT) {
+                       DPRINTK("rtd520: Error: DAC never finished! FifoStatus=0x%x\n", stat ^ 0x6666);
+                       return -ETIMEDOUT;
+               }
+       }
+
+       /* return the number of samples read/written */
+       return i;
+}
+
+/* AO subdevices should have a read insn as well as a write insn.
+ * Usually this means copying a value stored in devpriv. */
+static int rtd_ao_rinsn(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data)
+{
+       int i;
+       int chan = CR_CHAN(insn->chanspec);
+
+       for (i = 0; i < insn->n; i++) {
+               data[i] = devpriv->aoValue[chan];
+       }
+
+       return i;
+}
+
+/*
+   Write a masked set of bits and the read back the port.
+   We track what the bits should be (i.e. we don't read the port first).
+
+   DIO devices are slightly special.  Although it is possible to
+ * implement the insn_read/insn_write interface, it is much more
+ * useful to applications if you implement the insn_bits interface.
+ * This allows packed reading/writing of the DIO channels.  The
+ * comedi core can convert between insn_bits and insn_read/write
+ */
+static int rtd_dio_insn_bits(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data)
+{
+       if (insn->n != 2)
+               return -EINVAL;
+
+       /* The insn data is a mask in data[0] and the new data
+        * in data[1], each channel cooresponding to a bit. */
+       if (data[0]) {
+               s->state &= ~data[0];
+               s->state |= data[0] & data[1];
+
+               /* Write out the new digital output lines */
+               RtdDio0Write(dev, s->state);
+       }
+       /* on return, data[1] contains the value of the digital
+        * input lines. */
+       data[1] = RtdDio0Read(dev);
+
+       /*DPRINTK("rtd520:port_0 wrote: 0x%x read: 0x%x\n", s->state, data[1]); */
+
+       return 2;
+}
+
+/*
+  Configure one bit on a IO port as Input or Output (hence the name :-).
+*/
+static int rtd_dio_insn_config(comedi_device * dev,
+       comedi_subdevice * s, comedi_insn * insn, lsampl_t * data)
+{
+       int chan = CR_CHAN(insn->chanspec);
+
+       /* The input or output configuration of each digital line is
+        * configured by a special insn_config instruction.  chanspec
+        * contains the channel to be changed, and data[0] contains the
+        * value COMEDI_INPUT or COMEDI_OUTPUT. */
+       switch (data[0]) {
+       case INSN_CONFIG_DIO_OUTPUT:
+               s->io_bits |= 1 << chan;        /* 1 means Out */
+               break;
+       case INSN_CONFIG_DIO_INPUT:
+               s->io_bits &= ~(1 << chan);
+               break;
+       case INSN_CONFIG_DIO_QUERY:
+               data[1] =
+                       (s->
+                       io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT;
+               return insn->n;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       DPRINTK("rtd520: port_0_direction=0x%x (1 means out)\n", s->io_bits);
+       /* TODO support digital match interrupts and strobes */
+       RtdDioStatusWrite(dev, 0x01);   /* make Dio0Ctrl point to direction */
+       RtdDio0CtrlWrite(dev, s->io_bits);      /* set direction 1 means Out */
+       RtdDioStatusWrite(dev, 0);      /* make Dio0Ctrl clear interrupts */
+
+       /* port1 can only be all input or all output */
+
+       /* there are also 2 user input lines and 2 user output lines */
+
+       return 1;
+}
+
+/*
+ * A convenient macro that defines init_module() and cleanup_module(),
+ * as necessary.
+ */
+COMEDI_PCI_INITCLEANUP(rtd520Driver, rtd520_pci_table);
diff --git a/drivers/staging/comedi/drivers/rtd520.h b/drivers/staging/comedi/drivers/rtd520.h
new file mode 100644 (file)
index 0000000..0eb50b8
--- /dev/null
@@ -0,0 +1,412 @@
+/*
+    comedi/drivers/rtd520.h
+    Comedi driver defines for Real Time Devices (RTD) PCI4520/DM7520
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 2001 David A. Schleef <ds@schleef.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/*
+    Created by Dan Christian, NASA Ames Research Center.
+    See board notes in rtd520.c
+*/
+
+/*
+  LAS0 Runtime Area
+  Local Address Space 0 Offset         Read Function   Write Function
+*/
+#define LAS0_SPARE_00    0x0000        // -                               -
+#define LAS0_SPARE_04    0x0004        // -                               -
+#define LAS0_USER_IO     0x0008        // Read User Inputs                Write User Outputs
+#define LAS0_SPARE_0C    0x000C        // -                               -
+#define LAS0_ADC         0x0010        // Read FIFO Status                Software A/D Start
+#define LAS0_DAC1        0x0014        // -                               Software D/A1 Update
+#define LAS0_DAC2        0x0018        // -                               Software D/A2 Update
+#define LAS0_SPARE_1C    0x001C        // -                               -
+#define LAS0_SPARE_20    0x0020        // -                               -
+#define LAS0_DAC         0x0024        // -                               Software Simultaneous D/A1 and D/A2 Update
+#define LAS0_PACER       0x0028        // Software Pacer Start            Software Pacer Stop
+#define LAS0_TIMER       0x002C        // Read Timer Counters Status      HDIN Software Trigger
+#define LAS0_IT          0x0030        // Read Interrupt Status           Write Interrupt Enable Mask Register
+#define LAS0_CLEAR       0x0034        // Clear ITs set by Clear Mask     Set Interrupt Clear Mask
+#define LAS0_OVERRUN     0x0038        // Read pending interrupts         Clear Overrun Register
+#define LAS0_SPARE_3C    0x003C        // -                               -
+
+/*
+  LAS0 Runtime Area Timer/Counter,Dig.IO
+  Name                 Local Address                   Function
+*/
+#define LAS0_PCLK        0x0040        // Pacer Clock value (24bit)             Pacer Clock load (24bit)
+#define LAS0_BCLK        0x0044        // Burst Clock value (10bit)             Burst Clock load (10bit)
+#define LAS0_ADC_SCNT    0x0048        // A/D Sample counter value (10bit)      A/D Sample counter load (10bit)
+#define LAS0_DAC1_UCNT   0x004C        // D/A1 Update counter value (10 bit)    D/A1 Update counter load (10bit)
+#define LAS0_DAC2_UCNT   0x0050        // D/A2 Update counter value (10 bit)    D/A2 Update counter load (10bit)
+#define LAS0_DCNT        0x0054        // Delay counter value (16 bit)          Delay counter load (16bit)
+#define LAS0_ACNT        0x0058        // About counter value (16 bit)          About counter load (16bit)
+#define LAS0_DAC_CLK     0x005C        // DAC clock value (16bit)               DAC clock load (16bit)
+#define LAS0_UTC0        0x0060        // 8254 TC Counter 0 User TC 0 value     Load count in TC Counter 0
+#define LAS0_UTC1        0x0064        // 8254 TC Counter 1 User TC 1 value     Load count in TC Counter 1
+#define LAS0_UTC2        0x0068        // 8254 TC Counter 2 User TC 2 value     Load count in TC Counter 2
+#define LAS0_UTC_CTRL    0x006C        // 8254 TC Control Word                  Program counter mode for TC
+#define LAS0_DIO0        0x0070        // Digital I/O Port 0 Read Port          Digital I/O Port 0 Write Port
+#define LAS0_DIO1        0x0074        // Digital I/O Port 1 Read Port          Digital I/O Port 1 Write Port
+#define LAS0_DIO0_CTRL   0x0078        // Clear digital IRQ status flag/read    Clear digital chip/program Port 0
+#define LAS0_DIO_STATUS  0x007C        // Read Digital I/O Status word          Program digital control register &
+
+/*
+  LAS0 Setup Area
+  Name                 Local Address                   Function
+*/
+#define LAS0_BOARD_RESET        0x0100 // Board reset
+#define LAS0_DMA0_SRC           0x0104 // DMA 0 Sources select
+#define LAS0_DMA1_SRC           0x0108 // DMA 1 Sources select
+#define LAS0_ADC_CONVERSION     0x010C // A/D Conversion Signal select
+#define LAS0_BURST_START        0x0110 // Burst Clock Start Trigger select
+#define LAS0_PACER_START        0x0114 // Pacer Clock Start Trigger select
+#define LAS0_PACER_STOP         0x0118 // Pacer Clock Stop Trigger select
+#define LAS0_ACNT_STOP_ENABLE   0x011C // About Counter Stop Enable
+#define LAS0_PACER_REPEAT       0x0120 // Pacer Start Trigger Mode select
+#define LAS0_DIN_START          0x0124 // High Speed Digital Input Sampling Signal select
+#define LAS0_DIN_FIFO_CLEAR     0x0128 // Digital Input FIFO Clear
+#define LAS0_ADC_FIFO_CLEAR     0x012C // A/D FIFO Clear
+#define LAS0_CGT_WRITE          0x0130 // Channel Gain Table Write
+#define LAS0_CGL_WRITE          0x0134 // Channel Gain Latch Write
+#define LAS0_CG_DATA            0x0138 // Digital Table Write
+#define LAS0_CGT_ENABLE                0x013C  // Channel Gain Table Enable
+#define LAS0_CG_ENABLE          0x0140 // Digital Table Enable
+#define LAS0_CGT_PAUSE          0x0144 // Table Pause Enable
+#define LAS0_CGT_RESET          0x0148 // Reset Channel Gain Table
+#define LAS0_CGT_CLEAR          0x014C // Clear Channel Gain Table
+#define LAS0_DAC1_CTRL          0x0150 // D/A1 output type/range
+#define LAS0_DAC1_SRC           0x0154 // D/A1 update source
+#define LAS0_DAC1_CYCLE         0x0158 // D/A1 cycle mode
+#define LAS0_DAC1_RESET         0x015C // D/A1 FIFO reset
+#define LAS0_DAC1_FIFO_CLEAR    0x0160 // D/A1 FIFO clear
+#define LAS0_DAC2_CTRL          0x0164 // D/A2 output type/range
+#define LAS0_DAC2_SRC           0x0168 // D/A2 update source
+#define LAS0_DAC2_CYCLE         0x016C // D/A2 cycle mode
+#define LAS0_DAC2_RESET         0x0170 // D/A2 FIFO reset
+#define LAS0_DAC2_FIFO_CLEAR    0x0174 // D/A2 FIFO clear
+#define LAS0_ADC_SCNT_SRC       0x0178 // A/D Sample Counter Source select
+#define LAS0_PACER_SELECT       0x0180 // Pacer Clock select
+#define LAS0_SBUS0_SRC          0x0184 // SyncBus 0 Source select
+#define LAS0_SBUS0_ENABLE       0x0188 // SyncBus 0 enable
+#define LAS0_SBUS1_SRC          0x018C // SyncBus 1 Source select
+#define LAS0_SBUS1_ENABLE       0x0190 // SyncBus 1 enable
+#define LAS0_SBUS2_SRC          0x0198 // SyncBus 2 Source select
+#define LAS0_SBUS2_ENABLE       0x019C // SyncBus 2 enable
+#define LAS0_ETRG_POLARITY      0x01A4 // External Trigger polarity select
+#define LAS0_EINT_POLARITY      0x01A8 // External Interrupt polarity select
+#define LAS0_UTC0_CLOCK         0x01AC // UTC0 Clock select
+#define LAS0_UTC0_GATE          0x01B0 // UTC0 Gate select
+#define LAS0_UTC1_CLOCK         0x01B4 // UTC1 Clock select
+#define LAS0_UTC1_GATE          0x01B8 // UTC1 Gate select
+#define LAS0_UTC2_CLOCK         0x01BC // UTC2 Clock select
+#define LAS0_UTC2_GATE          0x01C0 // UTC2 Gate select
+#define LAS0_UOUT0_SELECT       0x01C4 // User Output 0 source select
+#define LAS0_UOUT1_SELECT       0x01C8 // User Output 1 source select
+#define LAS0_DMA0_RESET         0x01CC // DMA0 Request state machine reset
+#define LAS0_DMA1_RESET         0x01D0 // DMA1 Request state machine reset
+
+/*
+  LAS1
+  Name                 Local Address                   Function
+*/
+#define LAS1_ADC_FIFO            0x0000        // Read A/D FIFO (16bit) -
+#define LAS1_HDIO_FIFO           0x0004        // Read High Speed Digital Input FIFO (16bit) -
+#define LAS1_DAC1_FIFO           0x0008        // - Write D/A1 FIFO (16bit)
+#define LAS1_DAC2_FIFO           0x000C        // - Write D/A2 FIFO (16bit)
+
+/*
+  LCFG: PLX 9080 local config & runtime registers
+  Name                 Local Address                   Function
+*/
+#define LCFG_ITCSR              0x0068 // INTCSR, Interrupt Control/Status Register
+#define LCFG_DMAMODE0           0x0080 // DMA Channel 0 Mode Register
+#define LCFG_DMAPADR0           0x0084 // DMA Channel 0 PCI Address Register
+#define LCFG_DMALADR0           0x0088 // DMA Channel 0 Local Address Reg
+#define LCFG_DMASIZ0            0x008C // DMA Channel 0 Transfer Size (Bytes) Register
+#define LCFG_DMADPR0            0x0090 // DMA Channel 0 Descriptor Pointer Register
+#define LCFG_DMAMODE1           0x0094 // DMA Channel 1 Mode Register
+#define LCFG_DMAPADR1           0x0098 // DMA Channel 1 PCI Address Register
+#define LCFG_DMALADR1           0x009C // DMA Channel 1 Local Address Register
+#define LCFG_DMASIZ1            0x00A0 // DMA Channel 1 Transfer Size (Bytes) Register
+#define LCFG_DMADPR1            0x00A4 // DMA Channel 1 Descriptor Pointer Register
+#define LCFG_DMACSR0            0x00A8 // DMA Channel 0 Command/Status Register
+#define LCFG_DMACSR1            0x00A9 // DMA Channel 0 Command/Status Register
+#define LCFG_DMAARB             0x00AC // DMA Arbitration Register
+#define LCFG_DMATHR             0x00B0 // DMA Threshold Register
+
+/*======================================================================
+  Resister bit definitions
+======================================================================*/
+
+// FIFO Status Word Bits (RtdFifoStatus)
+#define FS_DAC1_NOT_EMPTY    0x0001    // D0  - DAC1 FIFO not empty
+#define FS_DAC1_HEMPTY   0x0002        // D1  - DAC1 FIFO half empty
+#define FS_DAC1_NOT_FULL     0x0004    // D2  - DAC1 FIFO not full
+#define FS_DAC2_NOT_EMPTY    0x0010    // D4  - DAC2 FIFO not empty
+#define FS_DAC2_HEMPTY   0x0020        // D5  - DAC2 FIFO half empty
+#define FS_DAC2_NOT_FULL     0x0040    // D6  - DAC2 FIFO not full
+#define FS_ADC_NOT_EMPTY     0x0100    // D8  - ADC FIFO not empty
+#define FS_ADC_HEMPTY    0x0200        // D9  - ADC FIFO half empty
+#define FS_ADC_NOT_FULL      0x0400    // D10 - ADC FIFO not full
+#define FS_DIN_NOT_EMPTY     0x1000    // D12 - DIN FIFO not empty
+#define FS_DIN_HEMPTY    0x2000        // D13 - DIN FIFO half empty
+#define FS_DIN_NOT_FULL      0x4000    // D14 - DIN FIFO not full
+
+// Timer Status Word Bits (GetTimerStatus)
+#define TS_PCLK_GATE   0x0001
+// D0 - Pacer Clock Gate [0 - gated, 1 - enabled]
+#define TS_BCLK_GATE   0x0002
+// D1 - Burst Clock Gate [0 - disabled, 1 - running]
+#define TS_DCNT_GATE   0x0004
+// D2 - Pacer Clock Delayed Start Trigger [0 - delay over, 1 - delay in
+// progress]
+#define TS_ACNT_GATE   0x0008
+// D3 - Pacer Clock About Trigger [0 - completed, 1 - in progress]
+#define TS_PCLK_RUN    0x0010
+// D4 - Pacer Clock Shutdown Flag [0 - Pacer Clock cannot be start
+// triggered only by Software Pacer Start Command, 1 - Pacer Clock can
+// be start triggered]
+
+// External Trigger polarity select
+// External Interrupt polarity select
+#define POL_POSITIVE         0x0       // positive edge
+#define POL_NEGATIVE         0x1       // negative edge
+
+// User Output Signal select (SetUout0Source, SetUout1Source)
+#define UOUT_ADC                0x0    // A/D Conversion Signal
+#define UOUT_DAC1               0x1    // D/A1 Update
+#define UOUT_DAC2               0x2    // D/A2 Update
+#define UOUT_SOFTWARE           0x3    // Software Programmable
+
+// Pacer clock select (SetPacerSource)
+#define PCLK_INTERNAL           1      // Internal Pacer Clock
+#define PCLK_EXTERNAL           0      // External Pacer Clock
+
+// A/D Sample Counter Sources (SetAdcntSource, SetupSampleCounter)
+#define ADC_SCNT_CGT_RESET         0x0 // needs restart with StartPacer
+#define ADC_SCNT_FIFO_WRITE        0x1
+
+// A/D Conversion Signal Select (for SetConversionSelect)
+#define ADC_START_SOFTWARE         0x0 // Software A/D Start
+#define ADC_START_PCLK             0x1 // Pacer Clock (Ext. Int. see Func.509)
+#define ADC_START_BCLK             0x2 // Burst Clock
+#define ADC_START_DIGITAL_IT       0x3 // Digital Interrupt
+#define ADC_START_DAC1_MARKER1     0x4 // D/A 1 Data Marker 1
+#define ADC_START_DAC2_MARKER1     0x5 // D/A 2 Data Marker 1
+#define ADC_START_SBUS0            0x6 // SyncBus 0
+#define ADC_START_SBUS1            0x7 // SyncBus 1
+#define ADC_START_SBUS2            0x8 // SyncBus 2
+
+// Burst Clock start trigger select (SetBurstStart)
+#define BCLK_START_SOFTWARE        0x0 // Software A/D Start (StartBurst)
+#define BCLK_START_PCLK            0x1 // Pacer Clock
+#define BCLK_START_ETRIG           0x2 // External Trigger
+#define BCLK_START_DIGITAL_IT      0x3 // Digital Interrupt
+#define BCLK_START_SBUS0           0x4 // SyncBus 0
+#define BCLK_START_SBUS1           0x5 // SyncBus 1
+#define BCLK_START_SBUS2           0x6 // SyncBus 2
+
+// Pacer Clock start trigger select (SetPacerStart)
+#define PCLK_START_SOFTWARE        0x0 // Software Pacer Start (StartPacer)
+#define PCLK_START_ETRIG           0x1 // External trigger
+#define PCLK_START_DIGITAL_IT      0x2 // Digital interrupt
+#define PCLK_START_UTC2            0x3 // User TC 2 out
+#define PCLK_START_SBUS0           0x4 // SyncBus 0
+#define PCLK_START_SBUS1           0x5 // SyncBus 1
+#define PCLK_START_SBUS2           0x6 // SyncBus 2
+#define PCLK_START_D_SOFTWARE      0x8 // Delayed Software Pacer Start
+#define PCLK_START_D_ETRIG         0x9 // Delayed external trigger
+#define PCLK_START_D_DIGITAL_IT    0xA // Delayed digital interrupt
+#define PCLK_START_D_UTC2          0xB // Delayed User TC 2 out
+#define PCLK_START_D_SBUS0         0xC // Delayed SyncBus 0
+#define PCLK_START_D_SBUS1         0xD // Delayed SyncBus 1
+#define PCLK_START_D_SBUS2         0xE // Delayed SyncBus 2
+#define PCLK_START_ETRIG_GATED     0xF // External Trigger Gated controlled mode
+
+// Pacer Clock Stop Trigger select (SetPacerStop)
+#define PCLK_STOP_SOFTWARE         0x0 // Software Pacer Stop (StopPacer)
+#define PCLK_STOP_ETRIG            0x1 // External Trigger
+#define PCLK_STOP_DIGITAL_IT       0x2 // Digital Interrupt
+#define PCLK_STOP_ACNT             0x3 // About Counter
+#define PCLK_STOP_UTC2             0x4 // User TC2 out
+#define PCLK_STOP_SBUS0            0x5 // SyncBus 0
+#define PCLK_STOP_SBUS1            0x6 // SyncBus 1
+#define PCLK_STOP_SBUS2            0x7 // SyncBus 2
+#define PCLK_STOP_A_SOFTWARE       0x8 // About Software Pacer Stop
+#define PCLK_STOP_A_ETRIG          0x9 // About External Trigger
+#define PCLK_STOP_A_DIGITAL_IT     0xA // About Digital Interrupt
+#define PCLK_STOP_A_UTC2           0xC // About User TC2 out
+#define PCLK_STOP_A_SBUS0          0xD // About SyncBus 0
+#define PCLK_STOP_A_SBUS1          0xE // About SyncBus 1
+#define PCLK_STOP_A_SBUS2          0xF // About SyncBus 2
+
+// About Counter Stop Enable
+#define ACNT_STOP                  0x0 // stop enable
+#define ACNT_NO_STOP               0x1 // stop disabled
+
+// DAC update source (SetDAC1Start & SetDAC2Start)
+#define DAC_START_SOFTWARE         0x0 // Software Update
+#define DAC_START_CGT              0x1 // CGT controlled Update
+#define DAC_START_DAC_CLK          0x2 // D/A Clock
+#define DAC_START_EPCLK            0x3 // External Pacer Clock
+#define DAC_START_SBUS0            0x4 // SyncBus 0
+#define DAC_START_SBUS1            0x5 // SyncBus 1
+#define DAC_START_SBUS2            0x6 // SyncBus 2
+
+// DAC Cycle Mode (SetDAC1Cycle, SetDAC2Cycle, SetupDAC)
+#define DAC_CYCLE_SINGLE           0x0 // not cycle
+#define DAC_CYCLE_MULTI            0x1 // cycle
+
+// 8254 Operation Modes (Set8254Mode, SetupTimerCounter)
+#define M8254_EVENT_COUNTER        0   // Event Counter
+#define M8254_HW_ONE_SHOT          1   // Hardware-Retriggerable One-Shot
+#define M8254_RATE_GENERATOR       2   // Rate Generator
+#define M8254_SQUARE_WAVE          3   // Square Wave Mode
+#define M8254_SW_STROBE            4   // Software Triggered Strobe
+#define M8254_HW_STROBE            5   // Hardware Triggered Strobe (Retriggerable)
+
+// User Timer/Counter 0 Clock Select (SetUtc0Clock)
+#define CUTC0_8MHZ                 0x0 // 8MHz
+#define CUTC0_EXT_TC_CLOCK1        0x1 // Ext. TC Clock 1
+#define CUTC0_EXT_TC_CLOCK2        0x2 // Ext. TC Clock 2
+#define CUTC0_EXT_PCLK             0x3 // Ext. Pacer Clock
+
+// User Timer/Counter 1 Clock Select (SetUtc1Clock)
+#define CUTC1_8MHZ                 0x0 // 8MHz
+#define CUTC1_EXT_TC_CLOCK1        0x1 // Ext. TC Clock 1
+#define CUTC1_EXT_TC_CLOCK2        0x2 // Ext. TC Clock 2
+#define CUTC1_EXT_PCLK             0x3 // Ext. Pacer Clock
+#define CUTC1_UTC0_OUT             0x4 // User Timer/Counter 0 out
+#define CUTC1_DIN_SIGNAL           0x5 // High-Speed Digital Input   Sampling signal
+
+// User Timer/Counter 2 Clock Select (SetUtc2Clock)
+#define CUTC2_8MHZ                 0x0 // 8MHz
+#define CUTC2_EXT_TC_CLOCK1        0x1 // Ext. TC Clock 1
+#define CUTC2_EXT_TC_CLOCK2        0x2 // Ext. TC Clock 2
+#define CUTC2_EXT_PCLK             0x3 // Ext. Pacer Clock
+#define CUTC2_UTC1_OUT             0x4 // User Timer/Counter 1 out
+
+// User Timer/Counter 0 Gate Select (SetUtc0Gate)
+#define GUTC0_NOT_GATED            0x0 // Not gated
+#define GUTC0_GATED                0x1 // Gated
+#define GUTC0_EXT_TC_GATE1         0x2 // Ext. TC Gate 1
+#define GUTC0_EXT_TC_GATE2         0x3 // Ext. TC Gate 2
+
+// User Timer/Counter 1 Gate Select (SetUtc1Gate)
+#define GUTC1_NOT_GATED            0x0 // Not gated
+#define GUTC1_GATED                0x1 // Gated
+#define GUTC1_EXT_TC_GATE1         0x2 // Ext. TC Gate 1
+#define GUTC1_EXT_TC_GATE2         0x3 // Ext. TC Gate 2
+#define GUTC1_UTC0_OUT             0x4 // User Timer/Counter 0 out
+
+// User Timer/Counter 2 Gate Select (SetUtc2Gate)
+#define GUTC2_NOT_GATED            0x0 // Not gated
+#define GUTC2_GATED                0x1 // Gated
+#define GUTC2_EXT_TC_GATE1         0x2 // Ext. TC Gate 1
+#define GUTC2_EXT_TC_GATE2         0x3 // Ext. TC Gate 2
+#define GUTC2_UTC1_OUT             0x4 // User Timer/Counter 1 out
+
+// Interrupt Source Masks (SetITMask, ClearITMask, GetITStatus)
+#define IRQM_ADC_FIFO_WRITE        0x0001      // ADC FIFO Write
+#define IRQM_CGT_RESET             0x0002      // Reset CGT
+#define IRQM_CGT_PAUSE             0x0008      // Pause CGT
+#define IRQM_ADC_ABOUT_CNT         0x0010      // About Counter out
+#define IRQM_ADC_DELAY_CNT         0x0020      // Delay Counter out
+#define IRQM_ADC_SAMPLE_CNT       0x0040       // ADC Sample Counter
+#define IRQM_DAC1_UCNT             0x0080      // DAC1 Update Counter
+#define IRQM_DAC2_UCNT             0x0100      // DAC2 Update Counter
+#define IRQM_UTC1                  0x0200      // User TC1 out
+#define IRQM_UTC1_INV              0x0400      // User TC1 out, inverted
+#define IRQM_UTC2                  0x0800      // User TC2 out
+#define IRQM_DIGITAL_IT            0x1000      // Digital Interrupt
+#define IRQM_EXTERNAL_IT           0x2000      // External Interrupt
+#define IRQM_ETRIG_RISING          0x4000      // External Trigger rising-edge
+#define IRQM_ETRIG_FALLING         0x8000      // External Trigger falling-edge
+
+// DMA Request Sources (LAS0)
+#define DMAS_DISABLED              0x0 // DMA Disabled
+#define DMAS_ADC_SCNT              0x1 // ADC Sample Counter
+#define DMAS_DAC1_UCNT             0x2 // D/A1 Update Counter
+#define DMAS_DAC2_UCNT             0x3 // D/A2 Update Counter
+#define DMAS_UTC1                  0x4 // User TC1 out
+#define DMAS_ADFIFO_HALF_FULL      0x8 // A/D FIFO half full
+#define DMAS_DAC1_FIFO_HALF_EMPTY  0x9 // D/A1 FIFO half empty
+#define DMAS_DAC2_FIFO_HALF_EMPTY  0xA // D/A2 FIFO half empty
+
+// DMA Local Addresses   (0x40000000+LAS1 offset)
+#define DMALADDR_ADC       0x40000000  // A/D FIFO
+#define DMALADDR_HDIN      0x40000004  // High Speed Digital Input FIFO
+#define DMALADDR_DAC1      0x40000008  // D/A1 FIFO
+#define DMALADDR_DAC2      0x4000000C  // D/A2 FIFO
+
+// Port 0 compare modes (SetDIO0CompareMode)
+#define DIO_MODE_EVENT     0   // Event Mode
+#define DIO_MODE_MATCH     1   // Match Mode
+
+// Digital Table Enable (Port 1 disable)
+#define DTBL_DISABLE       0   // Enable Digital Table
+#define DTBL_ENABLE        1   // Disable Digital Table
+
+// Sampling Signal for High Speed Digital Input (SetHdinStart)
+#define HDIN_SOFTWARE      0x0 // Software Trigger
+#define HDIN_ADC           0x1 // A/D Conversion Signal
+#define HDIN_UTC0          0x2 // User TC out 0
+#define HDIN_UTC1          0x3 // User TC out 1
+#define HDIN_UTC2          0x4 // User TC out 2
+#define HDIN_EPCLK         0x5 // External Pacer Clock
+#define HDIN_ETRG          0x6 // External Trigger
+
+// Channel Gain Table / Channel Gain Latch
+#define CSC_LATCH          0   // Channel Gain Latch mode
+#define CSC_CGT            1   // Channel Gain Table mode
+
+// Channel Gain Table Pause Enable
+#define CGT_PAUSE_DISABLE  0   // Channel Gain Table Pause Disable
+#define CGT_PAUSE_ENABLE   1   // Channel Gain Table Pause Enable
+
+// DAC output type/range (p63)
+#define AOUT_UNIP5         0   // 0..+5 Volt
+#define AOUT_UNIP10        1   // 0..+10 Volt
+#define AOUT_BIP5          2   // -5..+5 Volt
+#define AOUT_BIP10         3   // -10..+10 Volt
+
+// Ghannel Gain Table field definitions (p61)
+// Gain
+#define GAIN1              0
+#define GAIN2              1
+#define GAIN4              2
+#define GAIN8              3
+#define GAIN16             4
+#define GAIN32             5
+#define GAIN64             6
+#define GAIN128            7
+
+// Input range/polarity
+#define AIN_BIP5           0   // -5..+5 Volt
+#define AIN_BIP10          1   // -10..+10 Volt
+#define AIN_UNIP10         2   // 0..+10 Volt
+
+// non referenced single ended select bit
+#define NRSE_AGND          0   // AGND referenced SE input
+#define NRSE_AINS          1   // AIN SENSE referenced SE input
+
+// single ended vs differential
+#define GND_SE         0       // Single-Ended
+#define GND_DIFF       1       // Differential