From 96341f71538c48dcec873873cabc11477cf26ae9 Mon Sep 17 00:00:00 2001 From: Anne Smorthit Date: Wed, 19 Nov 2008 09:30:18 -0800 Subject: [PATCH] Staging: comedi: add icp_multi driver This adds the icp_multi driver to the tree. From: Anne Smorthit Cc: David Schleef Cc: Frank Mori Hess Cc: Ian Abbott Signed-off-by: Greg Kroah-Hartman --- drivers/staging/comedi/drivers/Makefile | 1 + drivers/staging/comedi/drivers/icp_multi.c | 1085 ++++++++++++++++++++ drivers/staging/comedi/drivers/icp_multi.h | 278 +++++ 3 files changed, 1364 insertions(+) create mode 100644 drivers/staging/comedi/drivers/icp_multi.c create mode 100644 drivers/staging/comedi/drivers/icp_multi.h diff --git a/drivers/staging/comedi/drivers/Makefile b/drivers/staging/comedi/drivers/Makefile index 158660994765..4d16634c9341 100644 --- a/drivers/staging/comedi/drivers/Makefile +++ b/drivers/staging/comedi/drivers/Makefile @@ -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 index 000000000000..59144d7cb0bc --- /dev/null +++ b/drivers/staging/comedi/drivers/icp_multi.c @@ -0,0 +1,1085 @@ +/* + comedi/drivers/icp_multi.c + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2002 David A. Schleef + + 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 +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 +#include + +#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 index 000000000000..6df4a8d15ff2 --- /dev/null +++ b/drivers/staging/comedi/drivers/icp_multi.h @@ -0,0 +1,278 @@ +/* + comedi/drivers/icp_multi.h + + Stuff for ICP Multi + + Author: Anne Smorthit + +*/ + +#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 -- 2.20.1