Staging: comedi: add icp_multi driver
authorAnne Smorthit <anne.smorthit@sfwte.ch>
Wed, 19 Nov 2008 17:30:18 +0000 (09:30 -0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 6 Jan 2009 21:52:23 +0000 (13:52 -0800)
This adds the icp_multi driver to the tree.

From: Anne Smorthit <anne.smorthit@sfwte.ch>
Cc: David Schleef <ds@schleef.org>
Cc: Frank Mori Hess <fmhess@users.sourceforge.net>
Cc: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/staging/comedi/drivers/Makefile
drivers/staging/comedi/drivers/icp_multi.c [new file with mode: 0644]
drivers/staging/comedi/drivers/icp_multi.h [new file with mode: 0644]

index 158660994765e9bbc9326b2fbda8be808000fd9c..4d16634c9341555759561b3388ce86018f9e74b0 100644 (file)
@@ -9,6 +9,7 @@ obj-$(CONFIG_COMEDI)                    += comedi_parport.o
 
 # Comedi PCI drivers
 obj-$(CONFIG_COMEDI_PCI_DRIVERS)       += mite.o
+obj-$(CONFIG_COMEDI_PCI_DRIVERS)       += icp_multi.o
 
 # Comedi USB drivers
 obj-$(CONFIG_COMEDI_USB_DRIVERS)       += usbdux.o
diff --git a/drivers/staging/comedi/drivers/icp_multi.c b/drivers/staging/comedi/drivers/icp_multi.c
new file mode 100644 (file)
index 0000000..59144d7
--- /dev/null
@@ -0,0 +1,1085 @@
+/*
+    comedi/drivers/icp_multi.c
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 1997-2002 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: icp_multi
+Description: Inova ICP_MULTI
+Author: Anne Smorthit <anne.smorthit@sfwte.ch>
+Devices: [Inova] ICP_MULTI (icp_multi)
+Status: works
+
+The driver works for analog input and output and digital input and output.
+It does not work with interrupts or with the counters.  Currently no support
+for DMA.
+
+It has 16 single-ended or 8 differential Analogue Input channels with 12-bit
+resolution.  Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA.  Input
+ranges can be individually programmed for each channel.  Voltage or current
+measurement is selected by jumper.
+
+There are 4 x 12-bit Analogue Outputs.  Ranges : 5V, 10V, +/-5V, +/-10V
+
+16 x Digital Inputs, 24V
+
+8 x Digital Outputs, 24V, 1A
+
+4 x 16-bit counters
+
+Options:
+ [0] - PCI bus number - if bus number and slot number are 0,
+                        then driver search for first unused card
+ [1] - PCI slot number
+*/
+
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+#include <linux/pci.h>
+
+#include "icp_multi.h"
+
+#define DEVICE_ID      0x8000  /* Device ID */
+
+#define ICP_MULTI_EXTDEBUG
+
+// Hardware types of the cards
+#define TYPE_ICP_MULTI 0
+
+#define IORANGE_ICP_MULTI      32
+
+#define ICP_MULTI_ADC_CSR      0       /* R/W: ADC command/status register */
+#define ICP_MULTI_AI           2       /* R:   Analogue input data */
+#define ICP_MULTI_DAC_CSR      4       /* R/W: DAC command/status register */
+#define ICP_MULTI_AO           6       /* R/W: Analogue output data */
+#define ICP_MULTI_DI           8       /* R/W: Digital inouts */
+#define ICP_MULTI_DO           0x0A    /* R/W: Digital outputs */
+#define ICP_MULTI_INT_EN       0x0C    /* R/W: Interrupt enable register */
+#define ICP_MULTI_INT_STAT     0x0E    /* R/W: Interrupt status register */
+#define ICP_MULTI_CNTR0                0x10    /* R/W: Counter 0 */
+#define ICP_MULTI_CNTR1                0x12    /* R/W: counter 1 */
+#define ICP_MULTI_CNTR2                0x14    /* R/W: Counter 2 */
+#define ICP_MULTI_CNTR3                0x16    /* R/W: Counter 3 */
+
+#define ICP_MULTI_SIZE         0x20    /* 32 bytes */
+
+// Define bits from ADC command/status register
+#define        ADC_ST          0x0001  /* Start ADC */
+#define        ADC_BSY         0x0001  /* ADC busy */
+#define ADC_BI         0x0010  /* Bipolar input range 1 = bipolar */
+#define ADC_RA         0x0020  /* Input range 0 = 5V, 1 = 10V */
+#define        ADC_DI          0x0040  /* Differential input mode 1 = differential */
+
+// Define bits from DAC command/status register
+#define        DAC_ST          0x0001  /* Start DAC */
+#define DAC_BSY                0x0001  /* DAC busy */
+#define        DAC_BI          0x0010  /* Bipolar input range 1 = bipolar */
+#define        DAC_RA          0x0020  /* Input range 0 = 5V, 1 = 10V */
+
+// Define bits from interrupt enable/status registers
+#define        ADC_READY       0x0001  /* A/d conversion ready interrupt */
+#define        DAC_READY       0x0002  /* D/a conversion ready interrupt */
+#define        DOUT_ERROR      0x0004  /* Digital output error interrupt */
+#define        DIN_STATUS      0x0008  /* Digital input status change interrupt */
+#define        CIE0            0x0010  /* Counter 0 overrun interrupt */
+#define        CIE1            0x0020  /* Counter 1 overrun interrupt */
+#define        CIE2            0x0040  /* Counter 2 overrun interrupt */
+#define        CIE3            0x0080  /* Counter 3 overrun interrupt */
+
+// Useful definitions
+#define        Status_IRQ      0x00ff  // All interrupts
+
+// Define analogue range
+static const comedi_lrange range_analog = { 4, {
+                       UNI_RANGE(5),
+                       UNI_RANGE(10),
+                       BIP_RANGE(5),
+                       BIP_RANGE(10)
+       }
+};
+
+static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
+
+/*
+==============================================================================
+       Forward declarations
+==============================================================================
+*/
+static int icp_multi_attach(comedi_device * dev, comedi_devconfig * it);
+static int icp_multi_detach(comedi_device * dev);
+
+/*
+==============================================================================
+       Data & Structure declarations
+==============================================================================
+*/
+static unsigned short pci_list_builded = 0;    /*>0 list of card is known */
+
+typedef struct {
+       const char *name;       // driver name
+       int device_id;
+       int iorange;            // I/O range len
+       char have_irq;          // 1=card support IRQ
+       char cardtype;          // 0=ICP Multi
+       int n_aichan;           // num of A/D chans
+       int n_aichand;          // num of A/D chans in diff mode
+       int n_aochan;           // num of D/A chans
+       int n_dichan;           // num of DI chans
+       int n_dochan;           // num of DO chans
+       int n_ctrs;             // num of counters
+       int ai_maxdata;         // resolution of A/D
+       int ao_maxdata;         // resolution of D/A
+       const comedi_lrange *rangelist_ai;      // rangelist for A/D
+       const char *rangecode;  // range codes for programming
+       const comedi_lrange *rangelist_ao;      // rangelist for D/A
+} boardtype;
+
+static const boardtype boardtypes[] = {
+       {"icp_multi",           // Driver name
+                       DEVICE_ID,      // PCI device ID
+                       IORANGE_ICP_MULTI,      // I/O range length
+                       1,      // 1=Card supports interrupts
+                       TYPE_ICP_MULTI, // Card type = ICP MULTI
+                       16,     // Num of A/D channels
+                       8,      // Num of A/D channels in diff mode
+                       4,      // Num of D/A channels
+                       16,     // Num of digital inputs
+                       8,      // Num of digital outputs
+                       4,      // Num of counters
+                       0x0fff, // Resolution of A/D
+                       0x0fff, // Resolution of D/A
+                       &range_analog,  // Rangelist for A/D
+                       range_codes_analog,     // Range codes for programming
+               &range_analog}, // Rangelist for D/A
+};
+
+#define n_boardtypes (sizeof(boardtypes)/sizeof(boardtype))
+
+static comedi_driver driver_icp_multi = {
+      driver_name:"icp_multi",
+      module:THIS_MODULE,
+      attach:icp_multi_attach,
+      detach:icp_multi_detach,
+      num_names:n_boardtypes,
+      board_name:&boardtypes[0].name,
+      offset:sizeof(boardtype),
+};
+
+COMEDI_INITCLEANUP(driver_icp_multi);
+
+typedef struct {
+       struct pcilst_struct *card;     // pointer to card
+       char valid;             // card is usable
+       void *io_addr;          // Pointer to mapped io address
+       resource_size_t phys_iobase;    // Physical io address
+       unsigned int AdcCmdStatus;      // ADC Command/Status register
+       unsigned int DacCmdStatus;      // DAC Command/Status register
+       unsigned int IntEnable; // Interrupt Enable register
+       unsigned int IntStatus; // Interrupt Status register
+       unsigned int act_chanlist[32];  // list of scaned channel
+       unsigned char act_chanlist_len; // len of scanlist
+       unsigned char act_chanlist_pos; // actual position in MUX list
+       unsigned int *ai_chanlist;      // actaul chanlist
+       sampl_t *ai_data;       // data buffer
+       sampl_t ao_data[4];     // data output buffer
+       sampl_t di_data;        // Digital input data
+       unsigned int do_data;   // Remember digital output data
+} icp_multi_private;
+
+#define devpriv ((icp_multi_private *)dev->private)
+#define this_board ((const boardtype *)dev->board_ptr)
+
+/*
+==============================================================================
+       More forward declarations
+==============================================================================
+*/
+
+#if 0
+static int check_channel_list(comedi_device * dev, comedi_subdevice * s,
+       unsigned int *chanlist, unsigned int n_chan);
+#endif
+static void setup_channel_list(comedi_device * dev, comedi_subdevice * s,
+       unsigned int *chanlist, unsigned int n_chan);
+static int icp_multi_reset(comedi_device * dev);
+
+/*
+==============================================================================
+       Functions
+==============================================================================
+*/
+
+/*
+==============================================================================
+
+       Name:   icp_multi_insn_read_ai
+
+       Description:
+               This function reads a single analogue input.
+
+       Parameters:
+               comedi_device *dev      Pointer to current device structure
+               comedi_subdevice *s     Pointer to current subdevice structure
+               comedi_insn *insn       Pointer to current comedi instruction
+               lsampl_t *data          Pointer to analogue input data
+
+       Returns:int                     Nmuber of instructions executed
+
+==============================================================================
+*/
+static int icp_multi_insn_read_ai(comedi_device * dev, comedi_subdevice * s,
+       comedi_insn * insn, lsampl_t * data)
+{
+       int n, timeout;
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG: BGN: icp_multi_insn_read_ai(...)\n");
+#endif
+       // Disable A/D conversion ready interrupt
+       devpriv->IntEnable &= ~ADC_READY;
+       writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
+
+       // Clear interrupt status
+       devpriv->IntStatus |= ADC_READY;
+       writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
+
+       // Set up appropriate channel, mode and range data, for specified channel
+       setup_channel_list(dev, s, &insn->chanspec, 1);
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp_multi A ST=%4x IO=%p\n",
+               readw(devpriv->io_addr + ICP_MULTI_ADC_CSR),
+               devpriv->io_addr + ICP_MULTI_ADC_CSR);
+#endif
+
+       for (n = 0; n < insn->n; n++) {
+               // Set start ADC bit
+               devpriv->AdcCmdStatus |= ADC_ST;
+               writew(devpriv->AdcCmdStatus,
+                       devpriv->io_addr + ICP_MULTI_ADC_CSR);
+               devpriv->AdcCmdStatus &= ~ADC_ST;
+
+#ifdef ICP_MULTI_EXTDEBUG
+               printk("icp multi B n=%d ST=%4x\n", n,
+                       readw(devpriv->io_addr + ICP_MULTI_ADC_CSR));
+#endif
+
+               comedi_udelay(1);
+
+#ifdef ICP_MULTI_EXTDEBUG
+               printk("icp multi C n=%d ST=%4x\n", n,
+                       readw(devpriv->io_addr + ICP_MULTI_ADC_CSR));
+#endif
+
+               // Wait for conversion to complete, or get fed up waiting
+               timeout = 100;
+               while (timeout--) {
+                       if (!(readw(devpriv->io_addr +
+                                               ICP_MULTI_ADC_CSR) & ADC_BSY))
+                               goto conv_finish;
+
+#ifdef ICP_MULTI_EXTDEBUG
+                       if (!(timeout % 10))
+                               printk("icp multi D n=%d tm=%d ST=%4x\n", n,
+                                       timeout,
+                                       readw(devpriv->io_addr +
+                                               ICP_MULTI_ADC_CSR));
+#endif
+
+                       comedi_udelay(1);
+               }
+
+               // If we reach here, a timeout has occurred
+               comedi_error(dev, "A/D insn timeout");
+
+               // Disable interrupt
+               devpriv->IntEnable &= ~ADC_READY;
+               writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
+
+               // Clear interrupt status
+               devpriv->IntStatus |= ADC_READY;
+               writew(devpriv->IntStatus,
+                       devpriv->io_addr + ICP_MULTI_INT_STAT);
+
+               // Clear data received
+               data[n] = 0;
+
+#ifdef ICP_MULTI_EXTDEBUG
+               printk("icp multi EDBG: END: icp_multi_insn_read_ai(...) n=%d\n", n);
+#endif
+               return -ETIME;
+
+             conv_finish:
+               data[n] =
+                       (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff;
+       }
+
+       // Disable interrupt
+       devpriv->IntEnable &= ~ADC_READY;
+       writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
+
+       // Clear interrupt status
+       devpriv->IntStatus |= ADC_READY;
+       writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG: END: icp_multi_insn_read_ai(...) n=%d\n", n);
+#endif
+       return n;
+}
+
+/*
+==============================================================================
+
+       Name:   icp_multi_insn_write_ao
+
+       Description:
+               This function writes a single analogue output.
+
+       Parameters:
+               comedi_device *dev      Pointer to current device structure
+               comedi_subdevice *s     Pointer to current subdevice structure
+               comedi_insn *insn       Pointer to current comedi instruction
+               lsampl_t *data          Pointer to analogue output data
+
+       Returns:int                     Nmuber of instructions executed
+
+==============================================================================
+*/
+static int icp_multi_insn_write_ao(comedi_device * dev, comedi_subdevice * s,
+       comedi_insn * insn, lsampl_t * data)
+{
+       int n, chan, range, timeout;
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG: BGN: icp_multi_insn_write_ao(...)\n");
+#endif
+       // Disable D/A conversion ready interrupt
+       devpriv->IntEnable &= ~DAC_READY;
+       writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
+
+       // Clear interrupt status
+       devpriv->IntStatus |= DAC_READY;
+       writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
+
+       // Get channel number and range
+       chan = CR_CHAN(insn->chanspec);
+       range = CR_RANGE(insn->chanspec);
+
+       // Set up range and channel data
+       // Bit 4 = 1 : Bipolar
+       // Bit 5 = 0 : 5V
+       // Bit 5 = 1 : 10V
+       // Bits 8-9 : Channel number
+       devpriv->DacCmdStatus &= 0xfccf;
+       devpriv->DacCmdStatus |= this_board->rangecode[range];
+       devpriv->DacCmdStatus |= (chan << 8);
+
+       writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR);
+
+       for (n = 0; n < insn->n; n++) {
+               // Wait for analogue output data register to be ready for new data, or get fed up waiting
+               timeout = 100;
+               while (timeout--) {
+                       if (!(readw(devpriv->io_addr +
+                                               ICP_MULTI_DAC_CSR) & DAC_BSY))
+                               goto dac_ready;
+
+#ifdef ICP_MULTI_EXTDEBUG
+                       if (!(timeout % 10))
+                               printk("icp multi A n=%d tm=%d ST=%4x\n", n,
+                                       timeout,
+                                       readw(devpriv->io_addr +
+                                               ICP_MULTI_DAC_CSR));
+#endif
+
+                       comedi_udelay(1);
+               }
+
+               // If we reach here, a timeout has occurred
+               comedi_error(dev, "D/A insn timeout");
+
+               // Disable interrupt
+               devpriv->IntEnable &= ~DAC_READY;
+               writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
+
+               // Clear interrupt status
+               devpriv->IntStatus |= DAC_READY;
+               writew(devpriv->IntStatus,
+                       devpriv->io_addr + ICP_MULTI_INT_STAT);
+
+               // Clear data received
+               devpriv->ao_data[chan] = 0;
+
+#ifdef ICP_MULTI_EXTDEBUG
+               printk("icp multi EDBG: END: icp_multi_insn_write_ao(...) n=%d\n", n);
+#endif
+               return -ETIME;
+
+             dac_ready:
+               // Write data to analogue output data register
+               writew(data[n], devpriv->io_addr + ICP_MULTI_AO);
+
+               // Set DAC_ST bit to write the data to selected channel
+               devpriv->DacCmdStatus |= DAC_ST;
+               writew(devpriv->DacCmdStatus,
+                       devpriv->io_addr + ICP_MULTI_DAC_CSR);
+               devpriv->DacCmdStatus &= ~DAC_ST;
+
+               // Save analogue output data
+               devpriv->ao_data[chan] = data[n];
+       }
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG: END: icp_multi_insn_write_ao(...) n=%d\n", n);
+#endif
+       return n;
+}
+
+/*
+==============================================================================
+
+       Name:   icp_multi_insn_read_ao
+
+       Description:
+               This function reads a single analogue output.
+
+       Parameters:
+               comedi_device *dev      Pointer to current device structure
+               comedi_subdevice *s     Pointer to current subdevice structure
+               comedi_insn *insn       Pointer to current comedi instruction
+               lsampl_t *data          Pointer to analogue output data
+
+       Returns:int                     Nmuber of instructions executed
+
+==============================================================================
+*/
+static int icp_multi_insn_read_ao(comedi_device * dev, comedi_subdevice * s,
+       comedi_insn * insn, lsampl_t * data)
+{
+       int n, chan;
+
+       // Get channel number
+       chan = CR_CHAN(insn->chanspec);
+
+       // Read analogue outputs
+       for (n = 0; n < insn->n; n++)
+               data[n] = devpriv->ao_data[chan];
+
+       return n;
+}
+
+/*
+==============================================================================
+
+       Name:   icp_multi_insn_bits_di
+
+       Description:
+               This function reads the digital inputs.
+
+       Parameters:
+               comedi_device *dev      Pointer to current device structure
+               comedi_subdevice *s     Pointer to current subdevice structure
+               comedi_insn *insn       Pointer to current comedi instruction
+               lsampl_t *data          Pointer to analogue output data
+
+       Returns:int                     Nmuber of instructions executed
+
+==============================================================================
+*/
+static int icp_multi_insn_bits_di(comedi_device * dev, comedi_subdevice * s,
+       comedi_insn * insn, lsampl_t * data)
+{
+       data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
+
+       return 2;
+}
+
+/*
+==============================================================================
+
+       Name:   icp_multi_insn_bits_do
+
+       Description:
+               This function writes the appropriate digital outputs.
+
+       Parameters:
+               comedi_device *dev      Pointer to current device structure
+               comedi_subdevice *s     Pointer to current subdevice structure
+               comedi_insn *insn       Pointer to current comedi instruction
+               lsampl_t *data          Pointer to analogue output data
+
+       Returns:int                     Nmuber of instructions executed
+
+==============================================================================
+*/
+static int icp_multi_insn_bits_do(comedi_device * dev, comedi_subdevice * s,
+       comedi_insn * insn, lsampl_t * data)
+{
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG: BGN: icp_multi_insn_bits_do(...)\n");
+#endif
+
+       if (data[0]) {
+               s->state &= ~data[0];
+               s->state |= (data[0] & data[1]);
+
+               printk("Digital outputs = %4x \n", s->state);
+
+               writew(s->state, devpriv->io_addr + ICP_MULTI_DO);
+       }
+
+       data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG: END: icp_multi_insn_bits_do(...)\n");
+#endif
+       return 2;
+}
+
+/*
+==============================================================================
+
+       Name:   icp_multi_insn_read_ctr
+
+       Description:
+               This function reads the specified counter.
+
+       Parameters:
+               comedi_device *dev      Pointer to current device structure
+               comedi_subdevice *s     Pointer to current subdevice structure
+               comedi_insn *insn       Pointer to current comedi instruction
+               lsampl_t *data          Pointer to counter data
+
+       Returns:int                     Nmuber of instructions executed
+
+==============================================================================
+*/
+static int icp_multi_insn_read_ctr(comedi_device * dev, comedi_subdevice * s,
+       comedi_insn * insn, lsampl_t * data)
+{
+       return 0;
+}
+
+/*
+==============================================================================
+
+       Name:   icp_multi_insn_write_ctr
+
+       Description:
+               This function write to the specified counter.
+
+       Parameters:
+               comedi_device *dev      Pointer to current device structure
+               comedi_subdevice *s     Pointer to current subdevice structure
+               comedi_insn *insn       Pointer to current comedi instruction
+               lsampl_t *data          Pointer to counter data
+
+       Returns:int                     Nmuber of instructions executed
+
+==============================================================================
+*/
+static int icp_multi_insn_write_ctr(comedi_device * dev, comedi_subdevice * s,
+       comedi_insn * insn, lsampl_t * data)
+{
+       return 0;
+}
+
+/*
+==============================================================================
+
+       Name:   interrupt_service_icp_multi
+
+       Description:
+               This function is the interrupt service routine for all
+               interrupts generated by the icp multi board.
+
+       Parameters:
+               int irq
+               void *d                 Pointer to current device
+
+==============================================================================
+*/
+static irqreturn_t interrupt_service_icp_multi(int irq, void *d PT_REGS_ARG)
+{
+       comedi_device *dev = d;
+       int int_no;
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG: BGN: interrupt_service_icp_multi(%d,...)\n",
+               irq);
+#endif
+
+       // Is this interrupt from our board?
+       int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ;
+       if (!int_no)
+               // No, exit
+               return IRQ_NONE;
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG: interrupt_service_icp_multi() ST: %4x\n",
+               readw(devpriv->io_addr + ICP_MULTI_INT_STAT));
+#endif
+
+       // Determine which interrupt is active & handle it
+       switch (int_no) {
+       case ADC_READY:
+               break;
+       case DAC_READY:
+               break;
+       case DOUT_ERROR:
+               break;
+       case DIN_STATUS:
+               break;
+       case CIE0:
+               break;
+       case CIE1:
+               break;
+       case CIE2:
+               break;
+       case CIE3:
+               break;
+       default:
+               break;
+
+       }
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG: END: interrupt_service_icp_multi(...)\n");
+#endif
+       return IRQ_HANDLED;
+}
+
+#if 0
+/*
+==============================================================================
+
+       Name:   check_channel_list
+
+       Description:
+               This function checks if the channel list, provided by user
+               is built correctly
+
+       Parameters:
+               comedi_device *dev      Pointer to current sevice structure
+               comedi_subdevice *s     Pointer to current subdevice structure
+               unsigned int *chanlist  Pointer to packed channel list
+               unsigned int n_chan     Number of channels to scan
+
+       Returns:int 0 = failure
+                   1 = success
+
+==============================================================================
+*/
+static int check_channel_list(comedi_device * dev, comedi_subdevice * s,
+       unsigned int *chanlist, unsigned int n_chan)
+{
+       unsigned int i;
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG:  check_channel_list(...,%d)\n", n_chan);
+#endif
+       // Check that we at least have one channel to check
+       if (n_chan < 1) {
+               comedi_error(dev, "range/channel list is empty!");
+               return 0;
+       }
+       // Check all channels
+       for (i = 0; i < n_chan; i++) {
+               // Check that channel number is < maximum
+               if (CR_AREF(chanlist[i]) == AREF_DIFF) {
+                       if (CR_CHAN(chanlist[i]) > this_board->n_aichand) {
+                               comedi_error(dev,
+                                       "Incorrect differential ai channel number");
+                               return 0;
+                       }
+               } else {
+                       if (CR_CHAN(chanlist[i]) > this_board->n_aichan) {
+                               comedi_error(dev,
+                                       "Incorrect ai channel number");
+                               return 0;
+                       }
+               }
+       }
+       return 1;
+}
+#endif
+
+/*
+==============================================================================
+
+       Name:   setup_channel_list
+
+       Description:
+               This function sets the appropriate channel selection,
+               differential input mode and range bits in the ADC Command/
+               Status register.
+
+       Parameters:
+               comedi_device *dev      Pointer to current sevice structure
+               comedi_subdevice *s     Pointer to current subdevice structure
+               unsigned int *chanlist  Pointer to packed channel list
+               unsigned int n_chan     Number of channels to scan
+
+       Returns:Void
+
+==============================================================================
+*/
+static void setup_channel_list(comedi_device * dev, comedi_subdevice * s,
+       unsigned int *chanlist, unsigned int n_chan)
+{
+       unsigned int i, range, chanprog;
+       unsigned int diff;
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG:  setup_channel_list(...,%d)\n", n_chan);
+#endif
+       devpriv->act_chanlist_len = n_chan;
+       devpriv->act_chanlist_pos = 0;
+
+       for (i = 0; i < n_chan; i++) {
+               // Get channel
+               chanprog = CR_CHAN(chanlist[i]);
+
+               // Determine if it is a differential channel (Bit 15  = 1)
+               if (CR_AREF(chanlist[i]) == AREF_DIFF) {
+                       diff = 1;
+                       chanprog &= 0x0007;
+               } else {
+                       diff = 0;
+                       chanprog &= 0x000f;
+               }
+
+               // Clear channel, range and input mode bits in A/D command/status register
+               devpriv->AdcCmdStatus &= 0xf00f;
+
+               // Set channel number and differential mode status bit
+               if (diff) {
+                       // Set channel number, bits 9-11 & mode, bit 6
+                       devpriv->AdcCmdStatus |= (chanprog << 9);
+                       devpriv->AdcCmdStatus |= ADC_DI;
+               } else
+                       // Set channel number, bits 8-11
+                       devpriv->AdcCmdStatus |= (chanprog << 8);
+
+               // Get range for current channel
+               range = this_board->rangecode[CR_RANGE(chanlist[i])];
+               // Set range. bits 4-5
+               devpriv->AdcCmdStatus |= range;
+
+               /* Output channel, range, mode to ICP Multi */
+               writew(devpriv->AdcCmdStatus,
+                       devpriv->io_addr + ICP_MULTI_ADC_CSR);
+
+#ifdef ICP_MULTI_EXTDEBUG
+               printk("GS: %2d. [%4x]=%4x %4x\n", i, chanprog, range,
+                       devpriv->act_chanlist[i]);
+#endif
+       }
+
+}
+
+/*
+==============================================================================
+
+       Name:   icp_multi_reset
+
+       Description:
+               This function resets the icp multi device to a 'safe' state
+
+       Parameters:
+               comedi_device *dev      Pointer to current sevice structure
+
+       Returns:int     0 = success
+
+==============================================================================
+*/
+static int icp_multi_reset(comedi_device * dev)
+{
+       unsigned int i;
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp_multi EDBG: BGN: icp_multi_reset(...)\n");
+#endif
+       // Clear INT enables and requests
+       writew(0, devpriv->io_addr + ICP_MULTI_INT_EN);
+       writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT);
+
+       if (this_board->n_aochan)
+               // Set DACs to 0..5V range and 0V output
+               for (i = 0; i < this_board->n_aochan; i++) {
+                       devpriv->DacCmdStatus &= 0xfcce;
+
+                       // Set channel number
+                       devpriv->DacCmdStatus |= (i << 8);
+
+                       // Output 0V
+                       writew(0, devpriv->io_addr + ICP_MULTI_AO);
+
+                       // Set start conversion bit
+                       devpriv->DacCmdStatus |= DAC_ST;
+
+                       // Output to command / status register
+                       writew(devpriv->DacCmdStatus,
+                               devpriv->io_addr + ICP_MULTI_DAC_CSR);
+
+                       // Delay to allow DAC time to recover
+                       comedi_udelay(1);
+               }
+       // Digital outputs to 0
+       writew(0, devpriv->io_addr + ICP_MULTI_DO);
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG: END: icp_multi_reset(...)\n");
+#endif
+       return 0;
+}
+
+/*
+==============================================================================
+
+       Name:   icp_multi_attach
+
+       Description:
+               This function sets up all the appropriate data for the current
+               device.
+
+       Parameters:
+               comedi_device *dev      Pointer to current device structure
+               comedi_devconfig *it    Pointer to current device configuration
+
+       Returns:int     0 = success
+
+==============================================================================
+*/
+static int icp_multi_attach(comedi_device * dev, comedi_devconfig * it)
+{
+       comedi_subdevice *s;
+       int ret, subdev, n_subdevices;
+       unsigned int irq;
+       struct pcilst_struct *card = NULL;
+       resource_size_t io_addr[5], iobase;
+       unsigned char pci_bus, pci_slot, pci_func;
+
+       printk("icp_multi EDBG: BGN: icp_multi_attach(...)\n");
+
+       // Alocate private data storage space
+       if ((ret = alloc_private(dev, sizeof(icp_multi_private))) < 0)
+               return ret;
+
+       // Initialise list of PCI cards in system, if not already done so
+       if (pci_list_builded++ == 0) {
+               pci_card_list_init(PCI_VENDOR_ID_ICP,
+#ifdef ICP_MULTI_EXTDEBUG
+                       1
+#else
+                       0
+#endif
+                       );
+       }
+
+       printk("Anne's comedi%d: icp_multi: board=%s", dev->minor,
+               this_board->name);
+
+       if ((card = select_and_alloc_pci_card(PCI_VENDOR_ID_ICP,
+                               this_board->device_id, it->options[0],
+                               it->options[1])) == NULL)
+               return -EIO;
+
+       devpriv->card = card;
+
+       if ((pci_card_data(card, &pci_bus, &pci_slot, &pci_func, &io_addr[0],
+                               &irq)) < 0) {
+               printk(" - Can't get configuration data!\n");
+               return -EIO;
+       }
+
+       iobase = io_addr[2];
+       devpriv->phys_iobase = iobase;
+
+       printk(", b:s:f=%d:%d:%d, io=0x%8llx \n", pci_bus, pci_slot, pci_func,
+               (unsigned long long)iobase);
+
+       devpriv->io_addr = ioremap(iobase, ICP_MULTI_SIZE);
+
+       if (devpriv->io_addr == NULL) {
+               printk("ioremap failed.\n");
+               return -ENOMEM;
+       }
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("0x%08llx mapped to %p, ", (unsigned long long)iobase,
+               devpriv->io_addr);
+#endif
+
+       dev->board_name = this_board->name;
+
+       n_subdevices = 0;
+       if (this_board->n_aichan)
+               n_subdevices++;
+       if (this_board->n_aochan)
+               n_subdevices++;
+       if (this_board->n_dichan)
+               n_subdevices++;
+       if (this_board->n_dochan)
+               n_subdevices++;
+       if (this_board->n_ctrs)
+               n_subdevices++;
+
+       if ((ret = alloc_subdevices(dev, n_subdevices)) < 0) {
+               return ret;
+       }
+
+       icp_multi_reset(dev);
+
+       if (this_board->have_irq) {
+               if (irq) {
+                       if (comedi_request_irq(irq, interrupt_service_icp_multi,
+                                       IRQF_SHARED, "Inova Icp Multi", dev)) {
+                               printk(", unable to allocate IRQ %u, DISABLING IT", irq);
+                               irq = 0;        /* Can't use IRQ */
+                       } else
+                               printk(", irq=%u", irq);
+               } else
+                       printk(", IRQ disabled");
+       } else
+               irq = 0;
+
+       dev->irq = irq;
+
+       printk(".\n");
+
+       subdev = 0;
+
+       if (this_board->n_aichan) {
+               s = dev->subdevices + subdev;
+               dev->read_subdev = s;
+               s->type = COMEDI_SUBD_AI;
+               s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND;
+               if (this_board->n_aichand)
+                       s->subdev_flags |= SDF_DIFF;
+               s->n_chan = this_board->n_aichan;
+               s->maxdata = this_board->ai_maxdata;
+               s->len_chanlist = this_board->n_aichan;
+               s->range_table = this_board->rangelist_ai;
+               s->insn_read = icp_multi_insn_read_ai;
+               subdev++;
+       }
+
+       if (this_board->n_aochan) {
+               s = dev->subdevices + subdev;
+               s->type = COMEDI_SUBD_AO;
+               s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
+               s->n_chan = this_board->n_aochan;
+               s->maxdata = this_board->ao_maxdata;
+               s->len_chanlist = this_board->n_aochan;
+               s->range_table = this_board->rangelist_ao;
+               s->insn_write = icp_multi_insn_write_ao;
+               s->insn_read = icp_multi_insn_read_ao;
+               subdev++;
+       }
+
+       if (this_board->n_dichan) {
+               s = dev->subdevices + subdev;
+               s->type = COMEDI_SUBD_DI;
+               s->subdev_flags = SDF_READABLE;
+               s->n_chan = this_board->n_dichan;
+               s->maxdata = 1;
+               s->len_chanlist = this_board->n_dichan;
+               s->range_table = &range_digital;
+               s->io_bits = 0;
+               s->insn_bits = icp_multi_insn_bits_di;
+               subdev++;
+       }
+
+       if (this_board->n_dochan) {
+               s = dev->subdevices + subdev;
+               s->type = COMEDI_SUBD_DO;
+               s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+               s->n_chan = this_board->n_dochan;
+               s->maxdata = 1;
+               s->len_chanlist = this_board->n_dochan;
+               s->range_table = &range_digital;
+               s->io_bits = (1 << this_board->n_dochan) - 1;
+               s->state = 0;
+               s->insn_bits = icp_multi_insn_bits_do;
+               subdev++;
+       }
+
+       if (this_board->n_ctrs) {
+               s = dev->subdevices + subdev;
+               s->type = COMEDI_SUBD_COUNTER;
+               s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
+               s->n_chan = this_board->n_ctrs;
+               s->maxdata = 0xffff;
+               s->len_chanlist = this_board->n_ctrs;
+               s->state = 0;
+               s->insn_read = icp_multi_insn_read_ctr;
+               s->insn_write = icp_multi_insn_write_ctr;
+               subdev++;
+       }
+
+       devpriv->valid = 1;
+
+#ifdef ICP_MULTI_EXTDEBUG
+       printk("icp multi EDBG: END: icp_multi_attach(...)\n");
+#endif
+
+       return 0;
+}
+
+/*
+==============================================================================
+
+       Name:   icp_multi_detach
+
+       Description:
+               This function releases all the resources used by the current
+               device.
+
+       Parameters:
+               comedi_device *dev      Pointer to current device structure
+
+       Returns:int     0 = success
+
+==============================================================================
+*/
+static int icp_multi_detach(comedi_device * dev)
+{
+
+       if (dev->private)
+               if (devpriv->valid)
+                       icp_multi_reset(dev);
+
+       if (dev->irq)
+               comedi_free_irq(dev->irq, dev);
+
+       if (dev->private && devpriv->io_addr)
+               iounmap(devpriv->io_addr);
+
+       if (dev->private && devpriv->card)
+               pci_card_free(devpriv->card);
+
+       if (--pci_list_builded == 0) {
+               pci_card_list_cleanup(PCI_VENDOR_ID_ICP);
+       }
+
+       return 0;
+}
diff --git a/drivers/staging/comedi/drivers/icp_multi.h b/drivers/staging/comedi/drivers/icp_multi.h
new file mode 100644 (file)
index 0000000..6df4a8d
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+    comedi/drivers/icp_multi.h
+
+    Stuff for ICP Multi
+
+    Author: Anne Smorthit <anne.smorthit@sfwte.ch>
+
+*/
+
+#ifndef _ICP_MULTI_H_
+#define _ICP_MULTI_H_
+
+#include "../comedidev.h"
+#include "comedi_pci.h"
+
+/****************************************************************************/
+
+struct pcilst_struct {
+       struct pcilst_struct *next;
+       int used;
+       struct pci_dev *pcidev;
+       unsigned short vendor;
+       unsigned short device;
+       unsigned char pci_bus;
+       unsigned char pci_slot;
+       unsigned char pci_func;
+       resource_size_t io_addr[5];
+       unsigned int irq;
+};
+
+struct pcilst_struct *inova_devices;   // ptr to root list of all Inova devices
+
+/****************************************************************************/
+
+static void pci_card_list_init(unsigned short pci_vendor, char display);
+static void pci_card_list_cleanup(unsigned short pci_vendor);
+static struct pcilst_struct *find_free_pci_card_by_device(unsigned short
+       vendor_id, unsigned short device_id);
+static int find_free_pci_card_by_position(unsigned short vendor_id,
+       unsigned short device_id, unsigned short pci_bus,
+       unsigned short pci_slot, struct pcilst_struct **card);
+static struct pcilst_struct *select_and_alloc_pci_card(unsigned short vendor_id,
+       unsigned short device_id, unsigned short pci_bus,
+       unsigned short pci_slot);
+
+static int pci_card_alloc(struct pcilst_struct *amcc);
+static int pci_card_free(struct pcilst_struct *amcc);
+static void pci_card_list_display(void);
+static int pci_card_data(struct pcilst_struct *amcc,
+       unsigned char *pci_bus, unsigned char *pci_slot,
+       unsigned char *pci_func, resource_size_t * io_addr, unsigned int *irq);
+
+/****************************************************************************/
+
+/* build list of Inova cards in this system */
+static void pci_card_list_init(unsigned short pci_vendor, char display)
+{
+       struct pci_dev *pcidev;
+       struct pcilst_struct *inova, *last;
+       int i;
+
+       inova_devices = NULL;
+       last = NULL;
+
+       for (pcidev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL);
+               pcidev != NULL;
+               pcidev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pcidev)) {
+               if (pcidev->vendor == pci_vendor) {
+                       inova = kmalloc(sizeof(*inova), GFP_KERNEL);
+                       if (!inova) {
+                               printk("icp_multi: pci_card_list_init: allocation failed\n");
+                               pci_dev_put(pcidev);
+                               break;
+                       }
+                       memset(inova, 0, sizeof(*inova));
+
+                       inova->pcidev = pci_dev_get(pcidev);
+                       if (last) {
+                               last->next = inova;
+                       } else {
+                               inova_devices = inova;
+                       }
+                       last = inova;
+
+                       inova->vendor = pcidev->vendor;
+                       inova->device = pcidev->device;
+                       inova->pci_bus = pcidev->bus->number;
+                       inova->pci_slot = PCI_SLOT(pcidev->devfn);
+                       inova->pci_func = PCI_FUNC(pcidev->devfn);
+                       /* Note: resources may be invalid if PCI device
+                        * not enabled, but they are corrected in
+                        * pci_card_alloc. */
+                       for (i = 0; i < 5; i++)
+                               inova->io_addr[i] =
+                                       pci_resource_start(pcidev, i);
+                       inova->irq = pcidev->irq;
+               }
+       }
+
+       if (display)
+               pci_card_list_display();
+}
+
+/****************************************************************************/
+/* free up list of amcc cards in this system */
+static void pci_card_list_cleanup(unsigned short pci_vendor)
+{
+       struct pcilst_struct *inova, *next;
+
+       for (inova = inova_devices; inova; inova = next) {
+               next = inova->next;
+               pci_dev_put(inova->pcidev);
+               kfree(inova);
+       }
+
+       inova_devices = NULL;
+}
+
+/****************************************************************************/
+/* find first unused card with this device_id */
+static struct pcilst_struct *find_free_pci_card_by_device(unsigned short
+       vendor_id, unsigned short device_id)
+{
+       struct pcilst_struct *inova, *next;
+
+       for (inova = inova_devices; inova; inova = next) {
+               next = inova->next;
+               if ((!inova->used) && (inova->device == device_id)
+                       && (inova->vendor == vendor_id))
+                       return inova;
+
+       }
+
+       return NULL;
+}
+
+/****************************************************************************/
+/* find card on requested position */
+static int find_free_pci_card_by_position(unsigned short vendor_id,
+       unsigned short device_id, unsigned short pci_bus,
+       unsigned short pci_slot, struct pcilst_struct **card)
+{
+       struct pcilst_struct *inova, *next;
+
+       *card = NULL;
+       for (inova = inova_devices; inova; inova = next) {
+               next = inova->next;
+               if ((inova->vendor == vendor_id) && (inova->device == device_id)
+                       && (inova->pci_bus == pci_bus)
+                       && (inova->pci_slot == pci_slot)) {
+                       if (!(inova->used)) {
+                               *card = inova;
+                               return 0;       // ok, card is found
+                       } else {
+                               return 2;       // card exist but is used
+                       }
+               }
+       }
+
+       return 1;               // no card found
+}
+
+/****************************************************************************/
+/* mark card as used */
+static int pci_card_alloc(struct pcilst_struct *inova)
+{
+       int i;
+
+       if (!inova) {
+               rt_printk(" - BUG!! inova is NULL!\n");
+               return -1;
+       }
+
+       if (inova->used)
+               return 1;
+       if (comedi_pci_enable(inova->pcidev, "icp_multi")) {
+               rt_printk(" - Can't enable PCI device and request regions!\n");
+               return -1;
+       }
+       /* Resources will be accurate now. */
+       for (i = 0; i < 5; i++)
+               inova->io_addr[i] = pci_resource_start(inova->pcidev, i);
+       inova->irq = inova->pcidev->irq;
+       inova->used = 1;
+       return 0;
+}
+
+/****************************************************************************/
+/* mark card as free */
+static int pci_card_free(struct pcilst_struct *inova)
+{
+       if (!inova)
+               return -1;
+
+       if (!inova->used)
+               return 1;
+       inova->used = 0;
+       comedi_pci_disable(inova->pcidev);
+       return 0;
+}
+
+/****************************************************************************/
+/* display list of found cards */
+static void pci_card_list_display(void)
+{
+       struct pcilst_struct *inova, *next;
+
+       printk("Anne's List of pci cards\n");
+       printk("bus:slot:func vendor device io_inova io_daq irq used\n");
+
+       for (inova = inova_devices; inova; inova = next) {
+               next = inova->next;
+               printk("%2d   %2d   %2d  0x%4x 0x%4x   0x%8llx 0x%8llx  %2u  %2d\n", inova->pci_bus, inova->pci_slot, inova->pci_func, inova->vendor, inova->device, (unsigned long long)inova->io_addr[0], (unsigned long long)inova->io_addr[2], inova->irq, inova->used);
+
+       }
+}
+
+/****************************************************************************/
+/* return all card information for driver */
+static int pci_card_data(struct pcilst_struct *inova,
+       unsigned char *pci_bus, unsigned char *pci_slot,
+       unsigned char *pci_func, resource_size_t * io_addr, unsigned int *irq)
+{
+       int i;
+
+       if (!inova)
+               return -1;
+       *pci_bus = inova->pci_bus;
+       *pci_slot = inova->pci_slot;
+       *pci_func = inova->pci_func;
+       for (i = 0; i < 5; i++)
+               io_addr[i] = inova->io_addr[i];
+       *irq = inova->irq;
+       return 0;
+}
+
+/****************************************************************************/
+/* select and alloc card */
+static struct pcilst_struct *select_and_alloc_pci_card(unsigned short vendor_id,
+       unsigned short device_id, unsigned short pci_bus,
+       unsigned short pci_slot)
+{
+       struct pcilst_struct *card;
+       int err;
+
+       if ((pci_bus < 1) & (pci_slot < 1)) {   // use autodetection
+               if ((card = find_free_pci_card_by_device(vendor_id,
+                                       device_id)) == NULL) {
+                       rt_printk(" - Unused card not found in system!\n");
+                       return NULL;
+               }
+       } else {
+               switch (find_free_pci_card_by_position(vendor_id, device_id,
+                               pci_bus, pci_slot, &card)) {
+               case 1:
+                       rt_printk
+                               (" - Card not found on requested position b:s %d:%d!\n",
+                               pci_bus, pci_slot);
+                       return NULL;
+               case 2:
+                       rt_printk
+                               (" - Card on requested position is used b:s %d:%d!\n",
+                               pci_bus, pci_slot);
+                       return NULL;
+               }
+       }
+
+       if ((err = pci_card_alloc(card)) != 0) {
+               if (err > 0)
+                       rt_printk(" - Can't allocate card!\n");
+               /* else: error already printed. */
+               return NULL;
+       }
+
+       return card;
+}
+
+#endif