new driver (doesn't work yet)
authorFrank Mori Hess <fmhess@speakeasy.net>
Mon, 17 Feb 2003 05:04:05 +0000 (05:04 +0000)
committerFrank Mori Hess <fmhess@speakeasy.net>
Mon, 17 Feb 2003 05:04:05 +0000 (05:04 +0000)
comedi/Config.in
comedi/drivers/Makefile.in
comedi/drivers/gsc_hpdi.c [new file with mode: 0644]

index 75144d2f2fdb009ea4cab658a087e2ce1c5a99d9..354cc7ef8178a962499710c74acab78485eaf198 100644 (file)
@@ -128,6 +128,7 @@ dep_tristate 'SSV DIL/Net PC driver' CONFIG_COMEDI_SSV_DNP $CONFIG_COMEDI
 dep_tristate 'Inova ICP Multi' CONFIG_COMEDI_ICP_MULTI $CONFIG_COMEDI
 dep_tristate 'Kolter-Electronic PCI Counter 1' CONFIG_COMEDI_KE_COUNTER $CONFIG_COMEDI
 dep_tristate 'Meilhaus ME-2000i, ME-2600i, ME-3000vm1' CONFIG_COMEDI_ME_DAQ $CONFIG_COMEDI
+dep_tristate 'General Standards Corporation High Speed Parallel Digital Interface' CONFIG_COMEDI_GSC_HPDI $CONFIG_COMEDI
 dep_tristate 'Comedi test driver' CONFIG_COMEDI_TEST $CONFIG_COMEDI
 dep_tristate 'Skeleton driver' CONFIG_COMEDI_SKEL $CONFIG_COMEDI
 
index 9d51e7070cc5b9059d1e589c82d46b61fb87dce2..0b40ae6f4f8448e2903cd8cbb389fdd355725829 100644 (file)
@@ -31,6 +31,7 @@ select(CONFIG_COMEDI_DT2817 dt2817.o)
 select(CONFIG_COMEDI_DT282X dt282x.o)
 select(CONFIG_COMEDI_DT3000 dt3000.o)
 select(CONFIG_COMEDI_FL512 fl512.o)
+select(CONFIG_COMEDI_GSC_HPDI gsc_hpdi.o)
 select(CONFIG_COMEDI_II_PCI20KC ii_pci20kc.o)
 select(CONFIG_COMEDI_ICP_MULTI icp_multi.o)
 select(CONFIG_COMEDI_KE_COUNTER ke_counter.o)
diff --git a/comedi/drivers/gsc_hpdi.c b/comedi/drivers/gsc_hpdi.c
new file mode 100644 (file)
index 0000000..d8430e0
--- /dev/null
@@ -0,0 +1,899 @@
+/*
+    gsc_hpdi.c
+    This is a driver for the General Standards Corporation High
+    Speed Parallel Digital Interface rs485 boards.
+
+    Author:  Frank Mori Hess <fmhess@users.sourceforge.net>
+    Copyright (C) 2003 Coherent Imaging Systems
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 1997-8 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: gsc_hpdi.o
+Description: Driver for the General Standards Corporation High
+    Speed Parallel Digital Interface rs485 boards.
+Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+Status: in development
+Updated: 2003-02-14
+Devices: [General Standards Corporation] PCI-HPDI32 (gsc_hpdi),
+  PMC-HPDI32
+
+Configuration options:
+   [0] - PCI bus of device (optional)
+   [1] - PCI slot of device (optional)
+
+There are some additional hpdi models available from GSC for which
+support could be added to this driver.
+
+*/
+
+#include <linux/comedidev.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+
+#include "plx9080.h"
+#include "comedi_fc.h"
+
+static int hpdi_attach( comedi_device *dev, comedi_devconfig *it );
+static int hpdi_detach( comedi_device *dev );
+void abort_dma( comedi_device *dev, unsigned int channel );
+static int hpdi_cmd( comedi_device *dev, comedi_subdevice *s );
+static int hpdi_cmd_test( comedi_device *dev, comedi_subdevice *s, comedi_cmd *cmd );
+static int hpdi_cancel( comedi_device *dev, comedi_subdevice *s );
+static void handle_interrupt(int irq, void *d, struct pt_regs *regs);
+static int dio_config_block_size( comedi_device *dev, lsampl_t *data );
+
+#undef HPDI_DEBUG      // disable debugging messages
+//#define HPDI_DEBUG   // enable debugging code
+
+#ifdef HPDI_DEBUG
+#define DEBUG_PRINT(format, args...)  rt_printk(format , ## args )
+#else
+#define DEBUG_PRINT(format, args...)
+#endif
+
+#define TIMER_BASE 50  // 20MHz master clock
+#define DMA_BUFFER_SIZE 0x1000
+/* maximum number of dma transfers we will chain together into a ring
+ * (and the maximum number of dma buffers we maintain) */
+#define DMA_RING_COUNT 64
+
+// indices of base address regions
+enum base_address_regions
+{
+       PLX9080_BADDRINDEX = 0,
+       HPDI_BADDRINDEX = 2,
+};
+
+enum hpdi_registers
+{
+       FIRMWARE_REV_REG = 0x0,
+       BOARD_CONTROL_REG = 0x4,
+       BOARD_STATUS_REG = 0x8,
+       TX_PROG_ALMOST_REG = 0xc,
+       RX_PROG_ALMOST_REG = 0x10,
+       FEATURES_REG = 0x14,
+       FIFO_REG = 0x18,
+       TX_STATUS_COUNT_REG = 0x1c,
+       TX_LINE_VALID_COUNT_REG = 0x20,
+       TX_LINE_INVALID_COUNT_REG = 0x24,
+       RX_STATUS_COUNT_REG = 0x28,
+       RX_LINE_COUNT_REG = 0x2c,
+       INTERRUPT_CONTROL_REG = 0x30,
+       INTERRUPT_STATUS_REG = 0x34,
+       TX_CLOCK_DIVIDER_REG = 0x38,
+       TX_FIFO_SIZE_REG = 0x40,
+       RX_FIFO_SIZE_REG = 0x44,
+       TX_FIFO_WORDS_REG = 0x48,
+       RX_FIFO_WORDS_REG = 0x4c,
+       INTERRUPT_EDGE_LEVEL_REG = 0x50,
+       INTERRUPT_POLARITY_REG = 0x54,
+};
+
+int command_channel_valid( unsigned int channel )
+{
+       if( channel == 0 || channel > 6 )
+       {
+               rt_printk( "gsc_hpdi: bug! invalid cable command channel\n");
+               return 0;
+       }
+       return 1;
+}
+
+// bit definitions
+
+enum firmware_revision_bits
+{
+       FEATURES_REG_PRESENT_BIT = 0x8000,
+};
+int firmware_revision( uint32_t fwr_bits )
+{
+       return fwr_bits & 0xff;
+}
+int pcb_revision( uint32_t fwr_bits )
+{
+       return ( fwr_bits >> 8 ) & 0xff;
+}
+int hpdi_subid( uint32_t fwr_bits )
+{
+       return ( fwr_bits >> 16 ) & 0xff;
+}
+
+enum board_control_bits
+{
+       BOARD_RESET_BIT = 0x1,  /* wait 10usec before accessing fifos */
+       TX_FIFO_RESET_BIT = 0x2,
+       RX_FIFO_RESET_BIT = 0x4,
+       TX_ENABLE_BIT = 0x10,
+       RX_ENABLE_BIT = 0x20,
+       DEMAND_DMA_DIRECTION_TX_BIT = 0x40, /* for channel 0, channel 1 can only transmit (when present) */
+       LINE_VALID_ON_STATUS_VALID_BIT = 0x80,
+       START_TX_BIT = 0x10,
+       CABLE_THROTTLE_ENABLE_BIT = 0x20,
+       TEST_MODE_ENABLE_BIT = 0x80000000,
+};
+uint32_t command_discrete_output_bits( unsigned int channel, int output, int output_value )
+{
+       uint32_t bits = 0;
+
+       if( command_channel_valid( channel ) == 0 )
+               return 0;
+       if( output )
+       {
+               bits |= 0x1 << ( 16 + channel );
+               if( output_value )
+               bits |= 0x1 << ( 24 + channel );
+       }else
+               bits |= 0x1 << ( 24 + channel );
+
+       return bits;
+}
+
+enum board_status_bits
+{
+       COMMAND_LINE_STATUS_MASK = 0x7f,
+       TX_IN_PROGRESS_BIT = 0x80,
+       TX_NOT_EMPTY_BIT = 0x100,
+       TX_NOT_ALMOST_EMPTY_BIT = 0x200,
+       TX_NOT_ALMOST_FULL_BIT = 0x400,
+       TX_NOT_FULL_BIT = 0x800,
+       RX_NOT_EMPTY_BIT = 0x1000,
+       RX_NOT_ALMOST_EMPTY_BIT = 0x2000,
+       RX_NOT_ALMOST_FULL_BIT = 0x4000,
+       RX_NOT_FULL_BIT = 0x8000,
+       BOARD_JUMPER0_INSTALLED_BIT = 0x10000,
+       BOARD_JUMPER1_INSTALLED_BIT = 0x20000,
+       TX_OVERRUN_BIT = 0x2000000,
+       RX_UNDERRUN_BIT = 0x4000000,
+       RX_OVERRUN_BIT = 0x8000000,
+};
+
+uint32_t almost_full_bits( unsigned int num_words )
+{
+// XXX need to add or subtract one?
+       return ( num_words << 16 ) & 0xff0000;
+}
+uint32_t almost_empty_bits( unsigned int num_words )
+{
+       return num_words & 0xffff;
+}
+unsigned int almost_full_num_words( uint32_t bits )
+{
+// XXX need to add or subtract one?
+       return ( bits >> 16 ) & 0xffff;
+}
+unsigned int almost_empty_num_words( uint32_t bits )
+{
+       return bits & 0xffff;
+}
+
+enum features_bits
+{
+       FIFO_SIZE_PRESENT_BIT = 0x1,
+       FIFO_WORDS_PRESENT_BIT = 0x2,
+       LEVEL_EDGE_INTERRUPTS_PRESENT_BIT = 0x4,
+       GPIO_SUPPORTED_BIT = 0x8,
+       PLX_DMA_CH1_SUPPORTED_BIT = 0x10,
+       OVERRUN_UNDERRUN_SUPPORTED_BIT = 0x20,
+};
+
+enum interrupt_sources
+{
+       FRAME_VALID_START_INTR = 0,
+       FRAME_VALID_END_INTR = 1,
+       TX_FIFO_EMPTY_INTR = 8,
+       TX_FIFO_ALMOST_EMPTY_INTR = 9,
+       TX_FIFO_ALMOST_FULL_INTR = 10,
+       TX_FIFO_FULL_INTR = 11,
+       RX_EMPTY_INTR = 12,
+       RX_ALMOST_EMPTY_INTR = 13,
+       RX_ALMOST_FULL_INTR = 14,
+       RX_FULL_INTR = 15,
+};
+int command_intr_source( unsigned int channel )
+{
+       if( command_channel_valid( channel ) == 0 )
+               channel = 1;
+       return channel + 1;
+}
+uint32_t intr_bit( int interrupt_source )
+{
+       return 0x1 << interrupt_source;
+}
+
+uint32_t tx_clock_divisor_bits( unsigned int divisor )
+{
+       return divisor & 0xff;
+}
+
+unsigned int fifo_size( uint32_t fifo_size_bits )
+{
+       return fifo_size_bits & 0xfffff;
+}
+
+unsigned int fifo_words( uint32_t fifo_words_bits )
+{
+       return fifo_words_bits & 0xfffff;
+}
+
+uint32_t intr_edge_bit( int interrupt_source )
+{
+       return 0x1 << interrupt_source;
+}
+
+uint32_t intr_active_high_bit( int interrupt_source )
+{
+       return 0x1 << interrupt_source;
+}
+
+typedef struct
+{
+       char *name;
+       int device_id;  // pci device id
+       int subdevice_id;       // pci subdevice id
+} hpdi_board;
+
+static const hpdi_board hpdi_boards[] =
+{
+       {
+               name: "pci-hpdi32",
+               device_id: PCI_DEVICE_ID_PLX_9080,
+               subdevice_id: 0x2400,
+       },
+#if 0
+       {
+               name: "pxi-hpdi32",
+               device_id: 0x9656,
+               subdevice_id: 0x2705,
+       },
+#endif
+};
+
+static inline unsigned int num_boards( void )
+{
+       return sizeof( hpdi_boards ) / sizeof( hpdi_board );
+}
+
+static struct pci_device_id hpdi_pci_table[] __devinitdata = {
+       { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9080, PCI_VENDOR_ID_PLX, 0x2400, 0, 0, 0 },
+       { 0 }
+};
+MODULE_DEVICE_TABLE(pci, hpdi_pci_table);
+
+static inline hpdi_board* board( const comedi_device *dev )
+{
+       return ( hpdi_board * ) dev->board_ptr;
+}
+
+typedef struct
+{
+       struct pci_dev *hw_dev; // pointer to board's pci_dev struct
+       // base addresses (physical)
+       unsigned long plx9080_phys_iobase;
+       unsigned long hpdi_phys_iobase;
+       // base addresses (ioremapped)
+       unsigned long plx9080_iobase;
+       unsigned long hpdi_iobase;
+       uint16_t *dio_buffer[ DMA_RING_COUNT ]; // dma buffers
+       dma_addr_t dio_buffer_phys_addr[ DMA_RING_COUNT ];      // physical addresses of dma buffers
+       struct plx_dma_desc *dma_desc;  // array of dma descriptors read by plx9080, allocated to get proper alignment
+       dma_addr_t dma_desc_phys_addr;  // physical address of dma descriptor array
+       volatile unsigned int dma_index;        // index of the dma descriptor/buffer that is currently being used
+       unsigned int tx_fifo_size;
+       unsigned int rx_fifo_size;
+       volatile unsigned long dio_count;
+       volatile uint32_t bits[ 24 ];   // software copies of values written to hpdi registers
+       unsigned dio_config_output : 1;
+} hpdi_private;
+
+static inline hpdi_private* priv( comedi_device *dev )
+{
+       return dev->private;
+}
+
+static comedi_driver driver_hpdi =
+{
+       driver_name:    "gsc_hpdi",
+       module:         THIS_MODULE,
+       attach:         hpdi_attach,
+       detach:         hpdi_detach,
+};
+
+COMEDI_INITCLEANUP( driver_hpdi );
+
+static int dio_config_insn( comedi_device *dev,comedi_subdevice *s,
+       comedi_insn *insn, lsampl_t *data)
+{
+       if( insn->n != 1 ) return -EINVAL;
+
+       switch( data[ 0 ] )
+       {
+               case COMEDI_OUTPUT:
+                       priv(dev)->dio_config_output = 1;
+                       return 1;
+                       break;
+               case COMEDI_INPUT:
+                       priv(dev)->dio_config_output = 0;
+                       return 1;
+                       break;
+               case INSN_CONFIG_BLOCK_SIZE:
+                       return dio_config_block_size( dev, data );
+                       break;
+               default:
+                       break;
+       }
+
+       return -EINVAL;
+}
+
+static void disable_plx_interrupts( comedi_device *dev )
+{
+       writel( 0, priv(dev)->plx9080_iobase + PLX_INTRCS_REG );
+}
+
+// initialize plx9080 chip
+static void init_plx9080(comedi_device *dev)
+{
+       uint32_t bits;
+       unsigned long plx_iobase = priv(dev)->plx9080_iobase;
+
+       // plx9080 dump
+       DEBUG_PRINT(" plx interrupt status 0x%x\n", readl(plx_iobase + PLX_INTRCS_REG));
+       DEBUG_PRINT(" plx id bits 0x%x\n", readl(plx_iobase + PLX_ID_REG));
+       DEBUG_PRINT(" plx control reg 0x%x\n", readl(priv(dev)->plx9080_iobase + PLX_CONTROL_REG));
+
+       DEBUG_PRINT(" plx revision 0x%x\n", readl(plx_iobase + PLX_REVISION_REG));
+       DEBUG_PRINT(" plx dma channel 0 mode 0x%x\n", readl(plx_iobase + PLX_DMA0_MODE_REG));
+       DEBUG_PRINT(" plx dma channel 1 mode 0x%x\n", readl(plx_iobase + PLX_DMA1_MODE_REG));
+       DEBUG_PRINT(" plx dma channel 0 pci address 0x%x\n", readl(plx_iobase + PLX_DMA0_PCI_ADDRESS_REG));
+       DEBUG_PRINT(" plx dma channel 0 local address 0x%x\n", readl(plx_iobase + PLX_DMA0_LOCAL_ADDRESS_REG));
+       DEBUG_PRINT(" plx dma channel 0 transfer size 0x%x\n", readl(plx_iobase + PLX_DMA0_TRANSFER_SIZE_REG));
+       DEBUG_PRINT(" plx dma channel 0 descriptor 0x%x\n", readl(plx_iobase + PLX_DMA0_DESCRIPTOR_REG));
+       DEBUG_PRINT(" plx dma channel 0 command status 0x%x\n", readb(plx_iobase + PLX_DMA0_CS_REG));
+       DEBUG_PRINT(" plx dma channel 0 threshold 0x%x\n", readl(plx_iobase + PLX_DMA0_THRESHOLD_REG));
+
+       disable_plx_interrupts( dev );
+
+       abort_dma(dev, 0);
+       abort_dma(dev, 1);
+
+       // configure dma0 mode
+       bits = 0;
+       // enable ready input
+       bits |= PLX_DMA_EN_READYIN_BIT;
+       // enable dma chaining
+       bits |= PLX_EN_CHAIN_BIT;
+       // enable interrupt on dma done (probably don't need this, since chain never finishes)
+       bits |= PLX_EN_DMA_DONE_INTR_BIT;
+       // don't increment local address during transfers (we are transferring from a fixed fifo register)
+       bits |= PLX_LOCAL_ADDR_CONST_BIT;
+       // route dma interrupt to pci bus
+       bits |= PLX_DMA_INTR_PCI_BIT;
+       // enable demand mode
+       bits |= PLX_DEMAND_MODE_BIT;
+       // enable local burst mode
+       bits |= PLX_DMA_LOCAL_BURST_EN_BIT;
+       bits |= PLX_LOCAL_BUS_32_WIDE_BITS;
+       writel(bits, plx_iobase + PLX_DMA0_MODE_REG);
+}
+
+/* Allocate and initialize the subdevice structures.
+ */
+static int setup_subdevices(comedi_device *dev)
+{
+       comedi_subdevice *s;
+
+       if( alloc_subdevices( dev, 1 ) < 0 )
+               return -ENOMEM;
+
+       s = dev->subdevices + 0;
+       /* analog input subdevice */
+       dev->read_subdev = s;
+       s->type = COMEDI_SUBD_DIO;
+       s->subdev_flags = SDF_READABLE | SDF_WRITEABLE | SDF_LSAMPL;
+       s->n_chan = 32;
+       s->len_chanlist = 32;
+       s->maxdata = 1;
+       s->range_table = &range_digital;
+       s->insn_config = dio_config_insn;
+       s->do_cmd = hpdi_cmd;
+       s->do_cmdtest = hpdi_cmd_test;
+       s->cancel = hpdi_cancel;
+
+       return 0;
+}
+
+static int init_hpdi( comedi_device *dev )
+{
+       uint32_t plx_intcsr_bits;
+
+       writel( BOARD_RESET_BIT, priv(dev)->hpdi_iobase + BOARD_CONTROL_REG );
+       udelay( 10 );
+
+       priv(dev)->tx_fifo_size = fifo_size( readl( priv(dev)->hpdi_iobase +
+               TX_FIFO_SIZE_REG ) );
+       priv(dev)->rx_fifo_size = fifo_size( readl( priv(dev)->hpdi_iobase +
+               RX_FIFO_SIZE_REG ) );
+
+       // enable interrupts
+       plx_intcsr_bits = ICS_AERR | ICS_PERR | ICS_PIE | ICS_PLIE | ICS_PAIE | ICS_LIE | ICS_DMA0_E;
+       writel( plx_intcsr_bits, priv(dev)->plx9080_iobase + PLX_INTRCS_REG );
+
+       return 0;
+}
+
+static int hpdi_attach(comedi_device *dev, comedi_devconfig *it)
+{
+       struct pci_dev* pcidev;
+       int i;
+       int retval;
+
+       printk( "comedi%d: gsc_hpdi\n", dev->minor );
+
+       if( alloc_private( dev, sizeof( hpdi_private ) ) < 0 )
+               return -ENOMEM;
+
+       pcidev = NULL;
+       for( i = 0; i < num_boards() && dev->board_ptr == NULL; i++ )
+       {
+               do
+               {
+                       pcidev = pci_find_subsys( PCI_VENDOR_ID_PLX, hpdi_boards[ i ].device_id,
+                               PCI_VENDOR_ID_PLX, hpdi_boards[ i ].subdevice_id, pcidev );
+                       // was a particular bus/slot requested?
+                       if( it->options[0] || it->options[1] )
+                       {
+                               // are we on the wrong bus/slot?
+                               if( pcidev->bus->number != it->options[0] ||
+                                       PCI_SLOT( pcidev->devfn ) != it->options[1] )
+                                       continue;
+                       }
+                       if( pcidev )
+                       {
+                               dev->board_ptr = hpdi_boards + i;
+                               break;
+                       }
+               }while( pcidev != NULL );
+       }
+       if( i == num_boards() )
+       {
+               printk("gsc_hpdi: no hpdi card found\n");
+               return -EIO;
+       }
+
+       printk("gsc_hpdi: found %s on bus %i, slot %i\n", board( dev )->name,
+               pcidev->bus->number, PCI_SLOT(pcidev->devfn));
+
+       if( pci_enable_device( pcidev ) )
+               return -EIO;
+       pci_set_master( pcidev );
+
+       priv(dev)->hw_dev = pcidev;
+
+       //Initialize dev->board_name
+       dev->board_name = board(dev)->name;
+
+       if( pci_request_regions( pcidev, driver_hpdi.driver_name ) )
+       {
+               /* Couldn't allocate io space */
+               printk(KERN_WARNING " failed to allocate io memory\n");
+               return -EIO;
+       }
+
+       priv(dev)->plx9080_phys_iobase = pci_resource_start(pcidev, PLX9080_BADDRINDEX);
+       priv(dev)->hpdi_phys_iobase = pci_resource_start(pcidev, HPDI_BADDRINDEX);
+
+       // remap, won't work with 2.0 kernels but who cares
+       priv(dev)->plx9080_iobase = (unsigned long) ioremap( priv(dev)->plx9080_phys_iobase,
+               pci_resource_len( pcidev, PLX9080_BADDRINDEX ) );
+       priv(dev)->hpdi_iobase = (unsigned long) ioremap( priv(dev)->hpdi_phys_iobase,
+               pci_resource_len( pcidev, HPDI_BADDRINDEX ) );
+
+       DEBUG_PRINT(" plx9080 remapped to 0x%lx\n", priv(dev)->plx9080_iobase);
+       DEBUG_PRINT(" hpdi remapped to 0x%lx\n", priv(dev)->hpdi_iobase);
+
+       // get irq
+       if( comedi_request_irq( pcidev->irq, handle_interrupt, SA_SHIRQ, driver_hpdi.driver_name,
+                dev ) )
+       {
+               printk( " unable to allocate irq %d\n", pcidev->irq );
+               return -EINVAL;
+       }
+       dev->irq = pcidev->irq;
+
+       printk(" irq %i\n", dev->irq);
+
+       init_plx9080(dev);
+
+       // alocate pci dma buffers
+       for( i = 0; i < DMA_RING_COUNT; i++ )
+       {
+               priv(dev)->dio_buffer[ i ] = pci_alloc_consistent( priv(dev)->hw_dev,
+                       DMA_BUFFER_SIZE, &priv(dev)->dio_buffer_phys_addr[ i ] );
+       }
+       // allocate dma descriptors
+       priv(dev)->dma_desc = pci_alloc_consistent( priv(dev)->hw_dev,
+               sizeof( struct plx_dma_desc ) * DMA_RING_COUNT,
+               &priv(dev)->dma_desc_phys_addr);
+       // initialize dma descriptors
+       for( i = 0; i < DMA_RING_COUNT; i++ )
+       {
+               priv(dev)->dma_desc[ i ].pci_start_addr = priv(dev)->dio_buffer_phys_addr[ i ];
+               priv(dev)->dma_desc[ i ].local_start_addr = FIFO_REG;
+               priv(dev)->dma_desc[ i ].transfer_size = DMA_BUFFER_SIZE;
+               priv(dev)->dma_desc[ i ].next = ( priv(dev)->dma_desc_phys_addr +
+                       ( ( i + 1 ) % ( DMA_RING_COUNT ) ) * sizeof( priv(dev)->dma_desc[ 0 ] ) ) |
+                       PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT | PLX_XFER_LOCAL_TO_PCI;
+       }
+
+       retval = setup_subdevices( dev );
+       if( retval < 0 )
+               return retval;
+
+       return init_hpdi( dev );
+}
+
+static int hpdi_detach(comedi_device *dev)
+{
+       unsigned int i;
+
+       printk( "comedi%d: gsc_hpdi: remove\n", dev->minor );
+
+       if( dev->irq )
+               comedi_free_irq( dev->irq, dev );
+       if( priv(dev) )
+       {
+               if( priv(dev)->hw_dev )
+               {
+                       if(priv(dev)->plx9080_iobase)
+                       {
+                               disable_plx_interrupts( dev );
+                               iounmap( (void*) priv(dev)->plx9080_iobase );
+                       }
+                       if( priv(dev)->hpdi_iobase )
+                               iounmap((void*)priv(dev)->hpdi_iobase);
+                       if( priv(dev)->plx9080_phys_iobase ||
+                               priv(dev)->hpdi_phys_iobase )
+                               pci_release_regions( priv(dev)->hw_dev );
+                       // free pci dma buffers
+                       for( i = 0; i < DMA_RING_COUNT; i++ )
+                       {
+                               if( priv(dev)->dio_buffer[ i ] )
+                                       pci_free_consistent( priv(dev)->hw_dev, DMA_BUFFER_SIZE,
+                                               priv(dev)->dio_buffer[ i ], priv(dev)->dio_buffer_phys_addr[ i ] );
+                       }
+                       // free dma descriptors
+                       if( priv(dev)->dma_desc )
+                               pci_free_consistent( priv(dev)->hw_dev, sizeof( struct plx_dma_desc ) *
+                               DMA_RING_COUNT, priv(dev)->dma_desc, priv(dev)->dma_desc_phys_addr );
+                       pci_disable_device( priv(dev)->hw_dev );
+               }
+       }
+
+       return 0;
+}
+
+static int dio_config_block_size( comedi_device *dev, lsampl_t *data )
+{
+       unsigned int block_size, requested_block_size;
+
+       requested_block_size = data[ 1 ];
+
+       block_size = requested_block_size;
+
+       data[ 1 ] = block_size;
+
+       return 2;
+}
+
+static int di_cmd_test(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd)
+{
+       int err = 0;
+       int tmp;
+       int i;
+
+       /* 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_EXT;
+       if( !cmd->scan_begin_src || tmp != cmd->scan_begin_src ) err++;
+
+       tmp = cmd->convert_src;
+       cmd->convert_src &= TRIG_NOW;
+       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 */
+
+       // uniqueness check
+       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->chanlist_len )
+       {
+               cmd->chanlist_len = 32;
+               err++;
+       }
+       if( cmd->scan_end_arg != cmd->chanlist_len )
+       {
+               cmd->scan_end_arg = cmd->chanlist_len;
+               err++;
+       }
+
+       switch(cmd->stop_src)
+       {
+               case TRIG_COUNT:
+                       if(!cmd->stop_arg)
+                       {
+                               cmd->stop_arg = 1;
+                               err++;
+                       }
+                       break;
+               case TRIG_NONE:
+                       if(cmd->stop_arg != 0)
+                       {
+                               cmd->stop_arg = 0;
+                               err++;
+                       }
+                       break;
+               default:
+                       break;
+       }
+
+       if(err) return 3;
+
+       /* step 4: fix up any arguments */
+
+       if(err) return 4;
+
+       if( cmd->chanlist )
+       {
+               for( i = 1; i < cmd->chanlist_len; i++ )
+               {
+                       if( CR_CHAN( cmd->chanlist[ i ] ) != i )
+                       {
+                               // XXX could support 8 channels or 16 channels
+                               comedi_error( dev, "chanlist must be channels 0 to 31 in order" );
+                               err++;
+                               break;
+                       }
+               }
+       }
+
+       if(err) return 5;
+
+       return 0;
+}
+
+static int hpdi_cmd_test( comedi_device *dev, comedi_subdevice *s, comedi_cmd *cmd )
+{
+       if( priv(dev)->dio_config_output )
+       {
+               return -EINVAL;
+       }else
+               return di_cmd_test( dev, s, cmd );
+}
+
+static inline void hpdi_writel( comedi_device *dev, uint32_t bits, unsigned int offset )
+{
+       writel( bits | priv(dev)->bits[ offset / sizeof( uint32_t ) ],
+               priv(dev)->hpdi_iobase + offset );
+}
+
+static int di_cmd(comedi_device *dev,comedi_subdevice *s)
+{
+       uint32_t bits;
+       unsigned long flags;
+
+       hpdi_writel( dev, RX_FIFO_RESET_BIT, BOARD_CONTROL_REG );
+
+       priv(dev)->dma_index = 0;
+
+       abort_dma(dev, 1);
+
+       // give location of first dma descriptor
+       bits = priv(dev)->dma_desc_phys_addr | PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT | PLX_XFER_LOCAL_TO_PCI;
+       writel( bits, priv(dev)->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG );
+
+       // spinlock for plx dma control/status reg
+       comedi_spin_lock_irqsave( &dev->spinlock, flags );
+       // enable dma transfer
+       writeb(PLX_DMA_EN_BIT | PLX_DMA_START_BIT | PLX_CLEAR_DMA_INTR_BIT, priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG);
+       comedi_spin_unlock_irqrestore( &dev->spinlock, flags );
+
+       hpdi_writel( dev, RX_ENABLE_BIT, BOARD_CONTROL_REG );
+
+       return 0;
+}
+
+static int hpdi_cmd( comedi_device *dev, comedi_subdevice *s )
+{
+       if( priv(dev)->dio_config_output )
+       {
+               return -EINVAL;
+       }else
+               return di_cmd( dev, s );
+}
+
+static void drain_dma_buffers(comedi_device *dev, unsigned int channel)
+{
+       comedi_async *async = dev->read_subdev->async;
+       uint32_t next_transfer_addr;
+       int j;
+       int num_samples = 0;
+       unsigned long pci_addr_reg;
+
+       if(channel)
+               pci_addr_reg = priv(dev)->plx9080_iobase + PLX_DMA1_PCI_ADDRESS_REG;
+       else
+               pci_addr_reg = priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG;
+
+       // loop until we have read all the full buffers
+       j = 0;
+       for(next_transfer_addr = readl(pci_addr_reg);
+               (next_transfer_addr < priv(dev)->dio_buffer_phys_addr[priv(dev)->dma_index] ||
+               next_transfer_addr >= priv(dev)->dio_buffer_phys_addr[priv(dev)->dma_index] + DMA_BUFFER_SIZE) &&
+               j < DMA_RING_COUNT;
+               j++ )
+       {
+               // transfer data from dma buffer to comedi buffer
+               num_samples = DMA_BUFFER_SIZE / sizeof( uint32_t );
+               if( async->cmd.stop_src == TRIG_COUNT )
+               {
+                       if(num_samples > priv(dev)->dio_count)
+                               num_samples = priv(dev)->dio_count;
+                       priv(dev)->dio_count -= num_samples;
+               }
+               cfc_write_array_to_buffer( dev->read_subdev,
+                       priv(dev)->dio_buffer[ priv(dev)->dma_index ], num_samples * sizeof( sampl_t ) );
+               priv(dev)->dma_index = (priv(dev)->dma_index + 1) % DMA_RING_COUNT;
+
+               DEBUG_PRINT("next buffer addr 0x%lx\n", (unsigned long) priv(dev)->ai_buffer_phys_addr[priv(dev)->dma_index]);
+               DEBUG_PRINT("pci addr reg 0x%x\n", next_transfer_addr);
+       }
+       // XXX check for buffer overrun somehow
+}
+
+static void handle_interrupt(int irq, void *d, struct pt_regs *regs)
+{
+       comedi_device *dev = d;
+       comedi_subdevice *s = dev->read_subdev;
+       comedi_async *async = s->async;
+       uint32_t hpdi_intr_status, hpdi_board_status;
+       uint32_t plx_status;
+       uint32_t plx_bits;
+       uint8_t dma0_status, dma1_status;
+       unsigned long flags;
+
+       plx_status = readl( priv(dev)->plx9080_iobase + PLX_INTRCS_REG );
+       hpdi_intr_status = readl( priv(dev)->hpdi_iobase + INTERRUPT_STATUS_REG );
+       hpdi_board_status = readl( priv(dev)->hpdi_iobase + BOARD_STATUS_REG );
+
+       DEBUG_PRINT("hpdi: intr status 0x%x, ", hpdi_intr_status);
+       DEBUG_PRINT("board status 0x%x, ", hpdi_board_status);
+       DEBUG_PRINT("plx status 0x%x\n", plx_status);
+
+       async->events = 0;
+
+       // spin lock makes sure noone else changes plx dma control reg
+       comedi_spin_lock_irqsave( &dev->spinlock, flags );
+       dma0_status = readb(priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG);
+       if(plx_status & ICS_DMA0_A)
+       {       // dma chan 0 interrupt
+               writeb((dma0_status & PLX_DMA_EN_BIT) | PLX_CLEAR_DMA_INTR_BIT, priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG);
+
+               DEBUG_PRINT("dma0 status 0x%x\n", dma0_status);
+
+               DEBUG_PRINT(" cleared dma ch0 interrupt\n");
+       }
+       comedi_spin_unlock_irqrestore( &dev->spinlock, flags );
+
+       // spin lock makes sure noone else changes plx dma control reg
+       comedi_spin_lock_irqsave( &dev->spinlock, flags );
+       dma1_status = readb(priv(dev)->plx9080_iobase + PLX_DMA1_CS_REG);
+       if(plx_status & ICS_DMA1_A)     // XXX
+       {       // dma chan 1 interrupt
+               // XXX possible race
+               writeb((dma1_status & PLX_DMA_EN_BIT) | PLX_CLEAR_DMA_INTR_BIT, priv(dev)->plx9080_iobase + PLX_DMA1_CS_REG);
+               DEBUG_PRINT("dma1 status 0x%x\n", dma1_status);
+
+               if(dma1_status & PLX_DMA_EN_BIT)
+               {
+                       drain_dma_buffers(dev, 1);
+               }
+               DEBUG_PRINT(" cleared dma ch1 interrupt\n");
+       }
+       comedi_spin_unlock_irqrestore( &dev->spinlock, flags );
+
+       // clear possible plx9080 interrupt sources
+       if(plx_status & ICS_LDIA)
+       { // clear local doorbell interrupt
+               plx_bits = readl(priv(dev)->plx9080_iobase + PLX_DBR_OUT_REG);
+               writel(plx_bits, priv(dev)->plx9080_iobase + PLX_DBR_OUT_REG);
+               DEBUG_PRINT(" cleared local doorbell bits 0x%x\n", plx_bits);
+       }
+
+       cfc_handle_events( dev, s );
+
+       DEBUG_PRINT("exiting handler\n");
+
+       return;
+}
+
+void abort_dma( comedi_device *dev, unsigned int channel )
+{
+       unsigned long flags;
+
+       // spinlock for plx dma control/status reg
+       comedi_spin_lock_irqsave( &dev->spinlock, flags );
+
+       plx9080_abort_dma( priv( dev )->plx9080_iobase, channel );
+
+       comedi_spin_unlock_irqrestore( &dev->spinlock, flags );
+}
+
+static int hpdi_cancel( comedi_device *dev, comedi_subdevice *s )
+{
+       hpdi_writel( dev, 0, BOARD_CONTROL_REG );
+
+       abort_dma(dev, 1);
+
+       return 0;
+}
+
+