Browse Source

Staging: comedi: add rtd520 driver

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>
Dan Christian 16 years ago
parent
commit
3d9f073994

+ 1 - 0
drivers/staging/comedi/drivers/Makefile

@@ -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

+ 429 - 0
drivers/staging/comedi/drivers/plx9080.h

@@ -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 */

+ 2283 - 0
drivers/staging/comedi/drivers/rtd520.c

@@ -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);

+ 412 - 0
drivers/staging/comedi/drivers/rtd520.h

@@ -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