--- /dev/null
+/* 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 */
--- /dev/null
+/*
+ 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);
--- /dev/null
+/*
+ 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