From 5bf032b2155e3643cc39523398295c8950ae18e5 Mon Sep 17 00:00:00 2001 From: David Schleef Date: Thu, 9 Aug 2001 08:18:42 +0000 Subject: [PATCH] A new driver from Allan Willcox. --- comedi/drivers/Makefile | 1 + comedi/drivers/amplc_pci230.c | 1089 +++++++++++++++++++++++++++++++++ 2 files changed, 1090 insertions(+) create mode 100644 comedi/drivers/amplc_pci230.c diff --git a/comedi/drivers/Makefile b/comedi/drivers/Makefile index 44e2c85b..ee0fbe8e 100644 --- a/comedi/drivers/Makefile +++ b/comedi/drivers/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_COMEDI_MPC8260CPM) += mpc8260cpm.o obj-$(CONFIG_COMEDI_ADL_PCI9118) += adl_pci9118.o obj-$(CONFIG_COMEDI_ADV_PCI1710) += adv_pci1710.o +obj-$(CONFIG_COMEDI_APMLC_PCI230) += amplc_pci230.o obj-$(CONFIG_COMEDI_CB_PCIDAS) += cb_pcidas.o obj-$(CONFIG_COMEDI_CB_PCIDDA) += cb_pcidda.o diff --git a/comedi/drivers/amplc_pci230.c b/comedi/drivers/amplc_pci230.c new file mode 100644 index 00000000..871263b8 --- /dev/null +++ b/comedi/drivers/amplc_pci230.c @@ -0,0 +1,1089 @@ + /* + comedi/drivers/amplc_pci230.c + Driver for Amplicon PCI230 and PCI260 Multifunction I/O boards. + + Copyright (C) 2001 Allan Willcox + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 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. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "8253.h" +#include "8255.h" + + +/* PCI230 PCI configuration register information */ + +#define PCI_VENDOR_ID_AMPLICON 0x14dc +#define PCI_DEVICE_ID_PCI230 0x0000 +#define PCI_DEVICE_ID_PCI260 0x0006 + +#define PCI230_IO1_SIZE 32 /* Size of I/O space 1 */ +#define PCI230_IO2_SIZE 32 /* Size of I/O space 2 - Check this!!! only appears to be 8 16bit words = 16 bytes long*/ + +/* PCI230 I/O SPACE 1 REGISTERS */ +#define PCI230_PPI_X_A 0x00 /* User PPI port A */ +#define PCI230_PPI_X_B 0x01 /* User PPI port B */ +#define PCI230_PPI_X_C 0x02 /* User PPI port C */ +#define PCI230_PPI_X_CMD 0x03 /* User PPI control word */ +#define PCI230_Z2_CT0 0x14 /* 82C54 counter/timer 0 */ +#define PCI230_Z2_CT1 0x15 /* 82C54 counter/timer 1 */ +#define PCI230_Z2_CT2 0x16 /* 82C54 counter/timer 2 */ +#define PCI230_Z2_CTC 0x17 /* 82C54 counter/timer control word */ +#define PCI230_ZCLK_SCE 0x1A /* Group Z Clock Configuration Register */ +#define PCI230_ZGAT_SCE 0x1D /* Group Z Gate Configuration Register */ +#define PCI230_INT_SCE 0x1E /* ISR Interrupt source mask register/Interrupt status */ + +/* PCI230 I/O SPACE 2 REGISTERS */ +#define PCI230_DACCON 0x00 +#define PCI230_DACOUT1 0x02 +#define PCI230_DACOUT2 0x04 +#define PCI230_DACOUT3 0x06 +#define PCI230_ADCDATA 0x08 +#define PCI230_ADCCON 0x0A +#define PCI230_ADCEN 0x0C +#define PCI230_ADCG 0x0E + +/* CONVERTOR RELATED CONSTANTS */ +#define PCI230_DAC_SETTLE 5 /* Analogue output settling time in µS (DAC itself is 1µS nominally) */ +#define PCI230_ADC_SETTLE 1 /* Analogue input settling time in µS (ADC itself is 1.6µS nominally but we poll anyway) */ +#define PCI230_MUX_SETTLE 1 /* ADC MUX settling time in µS - guess */ + +/* DACCON VALUES */ +#define PCI230_DAC_BUSY_BIT 1 +#define PCI230_DAC_BIP_BIT 0 + +/* ADCCON WRITE VALUES */ +#define PCI230_ADC_TRIG_NONE 0 +#define PCI230_ADC_TRIG_SW 1 +#define PCI230_ADC_TRIG_EXTP 2 +#define PCI230_ADC_TRIG_EXTN 3 +#define PCI230_ADC_TRIG_Z2CT0 4 +#define PCI230_ADC_TRIG_Z2CT1 5 +#define PCI230_ADC_TRIG_Z2CT2 6 +#define PCI230_ADC_IR_UNI (0<<3) /* Input range unipolar */ +#define PCI230_ADC_IR_BIP (1<<3) /* Input range bipolar */ +#define PCI230_ADC_IM_SE (0<<4) /* Input mode single ended */ +#define PCI230_ADC_IM_DIF (1<<4) /* Input mode differential */ +#define PCI230_ADC_FIFO_EN (1<<8) +#define PCI230_ADC_INT_FIFO_EMPTY 0 +#define PCI230_ADC_INT_FIFO_NEMPTY (1<<9) +#define PCI230_ADC_INT_FIFO_NHALF (2<<9) +#define PCI230_ADC_INT_FIFO_HALF (3<<9) +#define PCI230_ADC_INT_FIFO_NFULL (4<<9) +#define PCI230_ADC_INT_FIFO_FULL (5<<9) +#define PCI230_ADC_FIFO_RESET (1<<12) +#define PCI230_ADC_GLOB_RESET (1<<13) +#define PCI230_ADC_CONV 0xffff /* Value to write to ADCDATA to trigger ADC conversion in sotware trigger mode */ +#define PCI230_ADC_SW_CHAN(n) ((n)<<12) /* ADCCON software trigger channel selection - bit shift channel into upper 4 bit nibble of word */ + +/* ADCCON READ VALUES */ +#define PCI230_ADC_BUSY_BIT 15 +#define PCI230_ADC_FIFO_EMPTY (1<<12) +#define PCI230_ADC_FIFO_FULL (1<<13) +#define PCI230_ADC_FIFO_HALF (1<<14) + +/* GROUP Z CLOCK CONFIGURATION REGISTER VALUES */ +#define PCI230_ZCLK_CT0 0 +#define PCI230_ZCLK_CT1 8 +#define PCI230_ZCLK_CT2 16 +#define PCI230_ZCLK_RES 24 +#define PCI230_ZCLK_SRC_PPCN 0 /* The counter/timer's CLK input from the SK1 connector. */ +#define PCI230_ZCLK_SRC_10MHZ 1 /* The internal 10MHz clock. */ +#define PCI230_ZCLK_SRC_1MHZ 2 /* The internal 1MHz clock. */ +#define PCI230_ZCLK_SRC_100KHZ 3 /* The internal 100kHz clock. */ +#define PCI230_ZCLK_SRC_10KHZ 4 /* The internal 10kHz clock. */ +#define PCI230_ZCLK_SRC_1KHZ 5 /* The internal 1kHz clock. */ +#define PCI230_ZCLK_SRC_OUTNM1 6 /* The output of the preceding counter/timer channel (OUT n-1). */ +#define PCI230_ZCLK_SRC_EXTCLK 7 /* The dedicated external clock input for the group (X1/X2, Y1/Y2, Z1/Z2). */ + +#define PCI230_TIMEBASE_10MHZ 100 /* 10MHz is 100ns. */ + +/* INTERRUPT ENABLES/STATUS REGISTER VALUES */ +#define PCI230_INT_DISABLE 0 +#define PCI230_INT_PPI_C0 1 +#define PCI230_INT_PPI_C3 2 +#define PCI230_INT_ADC_DAC 4 +#define PCI230_INT_ZCLK_CT0 32 + +#define PCI230_TEST_BIT(val, n) ((val>>n)&1) /* Assumes you number bit with zero offset, ie. 0-15 */ + +/* + * Board descriptions for two imaginary boards. Describing the + * boards in this way is optional, and completely driver-dependent. + * Some drivers use arrays such as this, other do not. + */ +typedef struct pci230_board_struct{ + char *name; + int ai_chans; + int ai_bits; + int have_ao; + int ao_chans; + int ao_bits; + int have_dio; +}pci230_board; +pci230_board pci230_boards[] = { + { + name: "Amplicon PCI230", + ai_chans: 16, + ai_bits: 12, + have_ao: 1, + ao_chans: 2, + ao_bits: 12, + have_dio: 1, + }, + { + name: "Amplicon PCI260", + ai_chans: 16, + ai_bits: 12, + have_ao: 0, + ao_chans: 0, + ao_bits: 0, + have_dio: 0, + }, +}; +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((pci230_board *)dev->board_ptr) + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the comedi_device struct. */ +struct pci230_private{ +// int data; + struct pci_dev *pci_dev; + lsampl_t ao_readback[2]; /* Used for AO readback */ + unsigned int pci_iobase; /* PCI230's I/O space 1 */ + /* Divisors for 8254 counter/timer. */ + unsigned int divisor0; + unsigned int divisor1; + unsigned int divisor2; + unsigned int int_en; /* Interrupt Enables bits. */ + volatile unsigned int count; /* Number of samples remaining. */ + unsigned int bipolar; /* Set if bipolar range so we know to mangle it in interrupt handler. */ +}; + +#define devpriv ((struct pci230_private *)dev->private) + +/* PCI230 analogue input range table */ +static comedi_lrange pci230_ai_range = { 7, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5) +}}; + +/* PCI230 analogue output range table */ +static comedi_lrange pci230_ao_range = { 2, { + UNI_RANGE(10), + BIP_RANGE(10) +}}; + +/* + * The comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int pci230_attach(comedi_device *dev,comedi_devconfig *it); +static int pci230_detach(comedi_device *dev); +comedi_driver driver_amplc_pci230={ + driver_name: "amplc_pci230", + module: THIS_MODULE, + attach: pci230_attach, + detach: pci230_detach, +/* It is not necessary to implement the following members if you are + * writing a driver for a ISA PnP or PCI card */ + /* Most drivers will support multiple types of boards by + * having an array of board structures. These were defined + * in pci230_boards[] above. Note that the element 'name' + * was first in the structure -- Comedi uses this fact to + * extract the name of the board without knowing any details + * about the structure except for its length. + * When a device is attached (by comedi_config), the name + * of the device is given to Comedi, and Comedi tries to + * match it by going through the list of board names. If + * there is a match, the address of the pointer is put + * into dev->board_ptr and driver->attach() is called. + * + * Note that these are not necessary if you can determine + * the type of board in software. ISA PnP, PCI, and PCMCIA + * devices are such boards. + */ + board_name: pci230_boards, + offset: sizeof(pci230_board), + num_names: sizeof(pci230_boards) / sizeof(pci230_board), +}; + +static int pci230_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data); +static int pci230_ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data); +static int pci230_ao_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data); +static int pci230_ai_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd); +static int pci230_ai_cmd(comedi_device *dev, comedi_subdevice *s); +static int pci230_ns_to_timer(unsigned int *ns,int round); +static int pci230_z2_ct0(comedi_device *dev, unsigned int *ns,int round); +static int pci230_z2_ct1(comedi_device *dev, unsigned int *ns,int round); +static int pci230_z2_ct2(comedi_device *dev, unsigned int *ns,int round); +static void pci230_interrupt(int irq, void *d, struct pt_regs *regs); +static int pci230__cancel(comedi_device *dev, comedi_subdevice *s); + +/* print out string and val as bits - up to 16 */ +static void printb(int val) { + printk("\t%d%d%d%d ",PCI230_TEST_BIT(val, 15), PCI230_TEST_BIT(val, 14), PCI230_TEST_BIT(val, 13), PCI230_TEST_BIT(val, 12)); + printk("%d%d%d%d ", PCI230_TEST_BIT(val, 11), PCI230_TEST_BIT(val, 10), PCI230_TEST_BIT(val, 9), PCI230_TEST_BIT(val, 8)); + printk("%d%d%d%d ", PCI230_TEST_BIT(val, 7), PCI230_TEST_BIT(val, 6), PCI230_TEST_BIT(val, 5), PCI230_TEST_BIT(val, 4)); + printk("%d%d%d%d\n",PCI230_TEST_BIT(val, 3), PCI230_TEST_BIT(val, 2), PCI230_TEST_BIT(val, 1), PCI230_TEST_BIT(val, 0)); +} + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int pci230_attach(comedi_device *dev,comedi_devconfig *it) +{ + comedi_subdevice *s; + int pci_iobase, iobase = 0; /* PCI230's I/O spaces 1 and 2 */ + struct pci_dev *pci_dev; + + printk("comedi%d: amplc_pci230\n",dev->minor); + + /* Find card */ + pci_for_each_dev(pci_dev){ + if(pci_dev->vendor == PCI_VENDOR_ID_AMPLICON && + pci_dev->device == PCI_DEVICE_ID_PCI230){ + printk("comedi%d: amplc_pci230: found a PCI230\n",dev->minor); + break; + } + } + if(!pci_dev){ + printk("comedi%d: amplc_pci230: No PCI230 found\n",dev->minor); + return -EIO; + } + + /* Read base addressses of the PCI230's two I/O regions from PCI configuration register */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0) + pci_iobase = pci_dev->base_address[2] & PCI_BASE_ADDRESS_IO_MASK; + iobase = pci_dev->base_address[3] & PCI_BASE_ADDRESS_IO_MASK; +#else + pci_iobase = pci_dev->resource[2].start; + iobase = pci_dev->resource[3].start; +#endif + + printk("comedi%d: amplc_pci230: I/O region 1 0x%04x I/O region 2 0x%04x\n",dev->minor, pci_iobase, iobase); + + /* Allocate the private structure area using alloc_private() (macro defined in comedidev.h.) */ + if((alloc_private(dev,sizeof(struct pci230_private)))<0) + return -ENOMEM; + devpriv->pci_dev = pci_dev; + + /* Reserve I/O space 1. */ + if(check_region(pci_iobase,PCI230_IO1_SIZE)<0){ + printk("comedi%d: amplc_pci230: I/O space 1 conflict\n",dev->minor); + return -EIO; + } + request_region(pci_iobase,PCI230_IO1_SIZE,"PCI230"); + devpriv->pci_iobase = pci_iobase; + + /* Reserve I/O space 2. */ + if(check_region(iobase,PCI230_IO2_SIZE)<0){ + printk("comedi%d: amplc_pci230: I/O space 2 conflict\n",dev->minor); + return -EIO; + } + request_region(iobase,PCI230_IO2_SIZE,"PCI230"); + dev->iobase = iobase; + +/* + * Initialize dev->board_name. Note that we can use the "thisboard" + * macro now, since we just initialized it in the last line. + */ + dev->board_name = thisboard->name; + + +/* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. It relies on + * n_subdevices being set correctly. + */ + dev->n_subdevices=3; + if(alloc_subdevices(dev)<0) + return -ENOMEM; + + s=dev->subdevices+0; + dev->read_subdev=s; + /* analog input subdevice */ + s->type=COMEDI_SUBD_AI; + s->subdev_flags=SDF_READABLE; + s->n_chan=thisboard->ai_chans; + s->maxdata=(1<ai_bits)-1; + s->range_table=&pci230_ai_range; + s->insn_read = &pci230_ai_rinsn; + s->do_cmd = &pci230_ai_cmd; + s->len_chanlist = thisboard->ai_chans; + s->do_cmdtest = &pci230_ai_cmdtest; + + s=dev->subdevices+1; + /* analog output subdevice */ + s->type=COMEDI_SUBD_AO; + s->subdev_flags=SDF_WRITEABLE; + s->n_chan=thisboard->ao_chans;; + s->maxdata=(1<ao_bits)-1; + s->range_table=&pci230_ao_range; + s->insn_write = &pci230_ao_winsn; + s->insn_read = &pci230_ao_rinsn; + + s=dev->subdevices+2; + /* digital i/o subdevice */ + if(thisboard->have_dio){ + subdev_8255_init(dev,s,NULL,(void *)(devpriv->pci_iobase + PCI230_PPI_X_A)); + }else{ + s->type = COMEDI_SUBD_UNUSED; + } + + /* Register the interrupt handler. */ + if(comedi_request_irq(devpriv->pci_dev->irq, pci230_interrupt, 0, "PCI230", dev )) + { + printk("comedi%d: amplc_pci230: unable to register irq %d\n", dev->minor, devpriv->pci_dev->irq); + return -EINVAL; + } + dev->irq = devpriv->pci_dev->irq; + printk("comedi%d: amplc_pci230: registered irq %d\n", dev->minor, devpriv->pci_dev->irq); + + printk("attached\n"); + + return 1; +} + + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int pci230_detach(comedi_device *dev) +{ + printk("comedi%d: amplc_pci230: remove\n",dev->minor); + + if(dev->subdevices) + subdev_8255_cleanup(dev,dev->subdevices + 2); /* Clean up dio subdevice. */ + + if(dev->iobase) + release_region(dev->iobase,PCI230_IO2_SIZE); + + if(dev->irq) + comedi_free_irq(dev->irq, dev); + + if(devpriv){ + if(devpriv->pci_iobase){ + release_region(devpriv->pci_iobase, PCI230_IO1_SIZE); + } + } + + return 0; +} + +/* + * "instructions" read/write data in "one-shot" or "software-triggered" + * mode. + */ +static int pci230_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data) +{ + int n,i; + int chan, range; + unsigned int d; + unsigned int status; + unsigned int adccon, adcen, adcg; + unsigned int bipolar; + + /* Unpack channel and range. */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + printk("\ncomedi%d: amplc_pci230::pci230_ai_rinsn() chan %d range %d\n",dev->minor, chan, range); + + /* If bit 2 of range unset, range is referring to bipolar element in range table */ + bipolar = !PCI230_TEST_BIT(range, 2); + adccon = PCI230_ADC_IM_SE | PCI230_ADC_TRIG_SW | PCI230_ADC_SW_CHAN(chan); + if (bipolar) { + adccon = adccon | PCI230_ADC_IR_BIP; + adcg = range<<(chan-chan%2); + } + else { + adccon = adccon | PCI230_ADC_IR_UNI; + adcg = ((range&(~4))+1)<<(chan-chan%2); + } + adcen = 1<iobase + PCI230_ADCCON); + printk("comedi%d: amplc_pci230::pci230_ai_rinsn() wrote PCI230_ADCCON",dev->minor); printb(adccon); + + /* Enable only this channel in the scan list - otherwise by default we'll get one sample from each channel. */ + outw_p(adcen, dev->iobase + PCI230_ADCEN); + printk("comedi%d: amplc_pci230::pci230_ai_rinsn() wrote PCI230_ADCEN", dev->minor); printb(adcen); + + /* Set gain for channel. */ + outw_p(adcg, dev->iobase + PCI230_ADCG); + printk("comedi%d: amplc_pci230::pci230_ai_rinsn() wrote PCI230_ADCG", dev->minor); printb(adcg); + + /* Wait for mux to settle */ + udelay(PCI230_MUX_SETTLE); + + /* Convert n samples */ + for(n=0;nn;n++){ + /* trigger conversion */ + outw_p(PCI230_ADC_CONV,dev->iobase + PCI230_ADCDATA); + +#define TIMEOUT 100 + /* wait for conversion to end */ + for(i=0;iiobase + PCI230_ADCCON); + printk("comedi%d: amplc_pci230::pci230_ai_rinsn() read PCI230_ADCCON",dev->minor); printb(status); + if(PCI230_TEST_BIT(status, PCI230_ADC_BUSY_BIT))break; + } + if(i==TIMEOUT){ + /* rt_printk() should be used instead of printk() + * whenever the code can be called from real-time. */ + rt_printk("timeout\n"); + return -ETIMEDOUT; + } + + /* read data */ + d = inw(dev->iobase + PCI230_ADCDATA); + printk("comedi%d: amplc_pci230::pci230_ai_rinsn() read PCI230_ADCDATA 0x%04x\n",dev->minor, d); + + /* PCI230 is 12 bit - stored in upper bits of 16 bit register (lower four bits reserved for expansion). */ + d = d>>4; + + /* If a bipolar range was specified, mangle it (twos complement->straight binary). */ + if (bipolar) { + d ^= 1<<(thisboard->ai_bits-1); + } + data[n] = d; + } + + /* return the number of samples read/written */ + return n; +} + +static int pci230_ai_cmdtest(comedi_device *dev,comedi_subdevice *s, + comedi_cmd *cmd) +{ + int err=0; + int tmp; + + printk("comedi%d: amplc_pci230::pci230_ai_cmdtest()\n",dev->minor); + + /* cmdtest tests a particular command to see if it is valid. + * Using the cmdtest ioctl, a user can create a valid cmd + * and then have it executes by the cmd ioctl. + * + * cmdtest returns 1,2,3,4 or 0, depending on which tests + * the command passes. */ + + /* Step 1: make sure trigger sources are trivially valid. + * "invalid source" returned by comedilib to user mode process + * if this fails. */ + + tmp=cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT; + if(!cmd->start_src || tmp!=cmd->start_src)err++; + + tmp=cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; + if(!cmd->scan_begin_src || tmp!=cmd->scan_begin_src)err++; + + tmp=cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if(!cmd->convert_src || tmp!=cmd->convert_src)err++; + + tmp=cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if(!cmd->scan_end_src || tmp!=cmd->scan_end_src)err++; + + tmp=cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if(!cmd->stop_src || tmp!=cmd->stop_src)err++; + + if(err)return 1; + + /* Step 2: make sure trigger sources are unique and mutually compatible + * "source conflict" returned by comedilib to user mode process + * if this fails. */ + + if(cmd->scan_begin_src!=TRIG_TIMER && + cmd->scan_begin_src!=TRIG_EXT)err++; + if(cmd->convert_src!=TRIG_TIMER && + cmd->convert_src!=TRIG_EXT)err++; + if(cmd->stop_src!=TRIG_COUNT && + cmd->stop_src!=TRIG_NONE)err++; + + if(err)return 2; + + /* Step 3: make sure arguments are trivially compatible. + * "invalid argument" returned by comedilib to user mode process + * if this fails. */ + + if(cmd->start_arg!=0){ + cmd->start_arg=0; + err++; + } + +#define MAX_SPEED 10000 /* in nanoseconds */ +#define MIN_SPEED 1000000000 /* in nanoseconds */ + + if(cmd->scan_begin_src==TRIG_TIMER){ + if(cmd->scan_begin_argscan_begin_arg=MAX_SPEED; + err++; + } + if(cmd->scan_begin_arg>MIN_SPEED){ + cmd->scan_begin_arg=MIN_SPEED; + err++; + } + }else{ + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ + if(cmd->scan_begin_arg>9){ + cmd->scan_begin_arg=9; + err++; + } + } + if(cmd->convert_src==TRIG_TIMER){ + if(cmd->convert_argconvert_arg=MAX_SPEED; + err++; + } + if(cmd->convert_arg>MIN_SPEED){ + cmd->convert_arg=MIN_SPEED; + err++; + } + }else{ + /* external trigger */ + /* see above */ + if(cmd->convert_arg>9){ + cmd->convert_arg=9; + err++; + } + } + + if(cmd->scan_end_arg!=cmd->chanlist_len){ + cmd->scan_end_arg=cmd->chanlist_len; + err++; + } + if(cmd->stop_src==TRIG_COUNT){ + if(cmd->stop_arg>0x00ffffff){ + cmd->stop_arg=0x00ffffff; + err++; + } + }else{ + /* TRIG_NONE */ + if(cmd->stop_arg!=0){ + cmd->stop_arg=0; + err++; + } + } + + if(err)return 3; + + /* Step 4: fix up any arguments. + * "argument conflict" returned by comedilib to user mode process + * if this fails. */ + + if(cmd->scan_begin_src==TRIG_TIMER){ + tmp=cmd->scan_begin_arg; + pci230_ns_to_timer(&cmd->scan_begin_arg,cmd->flags&TRIG_ROUND_MASK); + if(tmp!=cmd->scan_begin_arg)err++; + } + if(cmd->convert_src==TRIG_TIMER){ + tmp=cmd->convert_arg; + pci230_ns_to_timer(&cmd->convert_arg,cmd->flags&TRIG_ROUND_MASK); + if(tmp!=cmd->convert_arg)err++; + if(cmd->scan_begin_src==TRIG_TIMER && + cmd->scan_begin_argconvert_arg*cmd->scan_end_arg){ + cmd->scan_begin_arg=cmd->convert_arg*cmd->scan_end_arg; + err++; + } + } + + if(err)return 4; + + return 0; +} + +static int pci230_ai_cmd(comedi_device *dev,comedi_subdevice *s) +{ + int i, chan, range, aref; + unsigned int adccon, adcen, adcg; + + /* Get the command. */ + comedi_async *async = s->async; + comedi_cmd *cmd = &async->cmd; + + /* Print the command */ + printk("comedi%d: amplc_pci230::pci230_ai_cmd()\n",dev->minor); + printk("\tflags \t\t %d\n", cmd->flags); + printk("\tstart \t\t src %d arg %d\n", cmd->start_src, cmd->start_arg); + printk("\tscan_begin \t src %d arg %d\n", cmd->scan_begin_src, cmd->scan_begin_arg); + printk("\tconvert \t src %d arg %d\n", cmd->convert_src, cmd->convert_arg); + printk("\tscan_end \t src %d arg %d\n", cmd->scan_end_src, cmd->scan_end_arg); + printk("\tstop \t\t src %d arg %d\n", cmd->stop_src, cmd->stop_arg); + for (chan = 0; chan < cmd->chanlist_len; chan++) { + printk("\tchannel %d\t range %d\t aref %d\n", CR_CHAN(cmd->chanlist[chan]), CR_RANGE(cmd->chanlist[chan]), CR_AREF(cmd->chanlist[chan])); + } + + if(!dev->irq) + { + comedi_error(dev, "no irq assigned for PCI230, cannot do hardware conversions"); + return -1; + } + + /* Calculate number of conversions required. */ + if(cmd->stop_src == TRIG_COUNT) { + devpriv->count = cmd->stop_arg * cmd->chanlist_len; + printk("comedi%d: amplc_pci230::pci230_ai_cmd() total number of conversions %d\n",dev->minor, devpriv->count); + } + else { + devpriv->count = 0; + } + + /* Steps; + * - Disable board interrupts. + * - Reset FIFO, specify uni/bip, se/diff, and start conversion source to none. + * - Set channel scan list. + * - Set channel gains. + * - Set the counter timers to the specified sampling frequency. + * - Enable conversion complete interrupt. + * - Enable FIFO, set FIFO interrupt trigger level, set start conversion source to counter 1. + */ + + /* Disable interrupts. */ + outb(PCI230_INT_DISABLE, devpriv->pci_iobase + PCI230_INT_SCE); + printk("comedi%d: amplc_pci230::pci230_ai_cmd wrote PCI230_INT_SCE",dev->minor); printb(PCI230_INT_DISABLE); + + adccon = PCI230_ADC_FIFO_RESET | PCI230_ADC_IM_SE | PCI230_ADC_TRIG_NONE; + adcg = 0; + adcen = 0; + + /* If bit 2 of range unset, range is referring to bipolar element in range table */ + devpriv->bipolar = !PCI230_TEST_BIT(range, 2); + if (devpriv->bipolar) { + adccon |= PCI230_ADC_IR_BIP; + for (i = 0; i < cmd->chanlist_len; i++) { + chan = CR_CHAN(cmd->chanlist[i]); + range = CR_RANGE(cmd->chanlist[i]); + adcg |= range<<(chan-chan%2); + adcen |= 1<chanlist_len; i++) { + chan = CR_CHAN(cmd->chanlist[i]); + range = CR_RANGE(cmd->chanlist[i]); + adcg |= ((range&(~4))+1)<<(chan-chan%2); + adcen |= 1<iobase + PCI230_ADCCON); + printk("comedi%d: amplc_pci230::pci230_ai_cmd wrote PCI230_ADCCON",dev->minor); printb(adccon); + + /* Set channel scan list. */ + outw_p(adcen, dev->iobase + PCI230_ADCEN); + printk("comedi%d: amplc_pci230::pci230_ai_cmd wrote PCI230_ADCEN", dev->minor); printb(adcen); + + /* Set channel gains. */ + outw_p(adcg, dev->iobase + PCI230_ADCG); + printk("comedi%d: amplc_pci230::pci230_ai_cmd wrote PCI230_ADCG", dev->minor); printb(adcg); + + /* Set the counter timers to the specified sampling frequency. */ + pci230_z2_ct1(dev, &cmd->convert_arg, cmd->flags & TRIG_ROUND_MASK); + + /* Enable conversion complete interrupt. */ + outb(PCI230_INT_ADC_DAC, devpriv->pci_iobase + PCI230_INT_SCE); + printk("comedi%d: amplc_pci230::pci230_ai_cmd wrote PCI230_INT_SCE",dev->minor); printb(PCI230_INT_ADC_DAC); + + /* Enable FIFO, set FIFO interrupt trigger level, set start conversion source to counter 1. */ + adccon = (adccon & ~PCI230_ADC_FIFO_RESET) | PCI230_ADC_FIFO_EN | PCI230_ADC_TRIG_Z2CT1; + if (devpriv->count < 2048) { + adccon = adccon | PCI230_ADC_INT_FIFO_NEMPTY; + } + else { + adccon = adccon | PCI230_ADC_INT_FIFO_HALF; + } + outw_p(adccon, dev->iobase + PCI230_ADCCON); + printk("comedi%d: amplc_pci230::pci230_ai_cmd wrote PCI230_ADCCON",dev->minor); printb(adccon); + + return 0; +} + + +/* This function doesn't require a particular form, this is just + * what happens to be used in some of the drivers. It should + * convert ns nanoseconds to a counter value suitable for programming + * the device. Also, it should adjust ns so that it cooresponds to + * the actual time that the device will use. */ +static int pci230_ns_to_timer(unsigned int *ns,int round) +{ + unsigned int divisor0, divisor1; + i8253_cascade_ns_to_timer_2div(PCI230_TIMEBASE_10MHZ, &divisor0, &divisor1, ns, TRIG_ROUND_MASK); + printk("comedi: amplc_pci230::pci230_ns_to_timer divisor0 %d divisor1 %d ns %d\n",divisor0, divisor1, *ns); + return *ns; +} + +/* + * Set ZCLK_CT0 to square wave mode with period of ns. + * Default clk source for DAC. + */ +static int pci230_z2_ct0(comedi_device *dev, unsigned int *ns,int round) +{ + /* For two cascaded counter/timers, calculate the divide ratios required to give a square wave of period ns. */ + i8253_cascade_ns_to_timer_2div(PCI230_TIMEBASE_10MHZ, &devpriv->divisor2, &devpriv->divisor0, ns, TRIG_ROUND_MASK); + printk("comedi%d: amplc_pci230::pci230_z2_ct0() divisor2 %d divisor0 %d ns %d\n",dev->minor, devpriv->divisor2, devpriv->divisor0, *ns); + + /* Generic i8254_load calls; program counters' divide ratios. */ + i8254_load(devpriv->pci_iobase + PCI230_Z2_CT0, 2, devpriv->divisor2, 2); /* Counter 2, divisor2, square wave (8254 mode 2). */ + i8254_load(devpriv->pci_iobase + PCI230_Z2_CT0, 0, devpriv->divisor0, 2); /* Counter 0, divisor0, square wave (8254 mode 2). */ + + /* PCI 230 specific - ties up counter clk inputs with clk sources */ + outb(PCI230_ZCLK_CT2 | PCI230_ZCLK_SRC_10MHZ, devpriv->pci_iobase + PCI230_ZCLK_SCE); /* Program counter 2's input clock source. */ + outb(PCI230_ZCLK_CT0 | PCI230_ZCLK_SRC_OUTNM1, devpriv->pci_iobase + PCI230_ZCLK_SCE); /* Program counter 0's input clock source. */ + + return *ns; +} + +/* + * Set ZCLK_CT1 to square wave mode with period of ns. + * Default clk source for ADC. + */ +static int pci230_z2_ct1(comedi_device *dev, unsigned int *ns,int round) +{ + /* For two cascaded counter/timers, calculate the divide ratios required to give a square wave of period ns. */ + i8253_cascade_ns_to_timer_2div(PCI230_TIMEBASE_10MHZ, &devpriv->divisor0, &devpriv->divisor1, ns, TRIG_ROUND_MASK); + printk("comedi%d: amplc_pci230::pci230_z2_ct1() divisor0 %d divisor1 %d ns %d\n",dev->minor, devpriv->divisor0, devpriv->divisor1, *ns); + + /* Generic i8254_load calls; program counters' divide ratios. */ + i8254_load(devpriv->pci_iobase + PCI230_Z2_CT0, 0, devpriv->divisor0, 2); /* Counter 0, divisor0, square wave (8254 mode 2). */ + i8254_load(devpriv->pci_iobase + PCI230_Z2_CT0, 1, devpriv->divisor1, 2); /* Counter 1, divisor1, square wave (8254 mode 2). */ + + /* PCI 230 specific - ties up counter clk inputs with clk sources */ + outb(PCI230_ZCLK_CT0 | PCI230_ZCLK_SRC_10MHZ, devpriv->pci_iobase + PCI230_ZCLK_SCE); /* Program counter 0's input clock source. */ + outb(PCI230_ZCLK_CT1 | PCI230_ZCLK_SRC_OUTNM1, devpriv->pci_iobase + PCI230_ZCLK_SCE); /* Program counter 1's input clock source. */ + + return *ns; +} + +/* + * Set ZCLK_CT2 to square wave mode with period of ns. + */ +static int pci230_z2_ct2(comedi_device *dev, unsigned int *ns,int round) +{ + /* For two cascaded counter/timers, calculate the divide ratios required to give a square wave of period ns. */ + i8253_cascade_ns_to_timer_2div(PCI230_TIMEBASE_10MHZ, &devpriv->divisor1, &devpriv->divisor2, ns, TRIG_ROUND_MASK); + printk("comedi%d: amplc_pci230::pci230_z2_ct2() divisor1 %d divisor2 %d ns %d\n",dev->minor, devpriv->divisor1, devpriv->divisor2, *ns); + + /* Generic i8254_load calls; program counters' divide ratios. */ + i8254_load(devpriv->pci_iobase + PCI230_Z2_CT0, 1, devpriv->divisor1, 2); /* Counter 1, divisor1, square wave (8254 mode 2). */ + i8254_load(devpriv->pci_iobase + PCI230_Z2_CT0, 2, devpriv->divisor2, 2); /* Counter 2, divisor2, square wave (8254 mode 2). */ + + /* PCI 230 specific - ties up counter clk inputs with clk sources */ + outb(PCI230_ZCLK_CT1 | PCI230_ZCLK_SRC_10MHZ, devpriv->pci_iobase + PCI230_ZCLK_SCE); /* Program counter 1's input clock source. */ + outb(PCI230_ZCLK_CT2 | PCI230_ZCLK_SRC_OUTNM1, devpriv->pci_iobase + PCI230_ZCLK_SCE); /* Program counter 2's input clock source. */ + + return *ns; +} + +static int pci230_ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data) +{ + int i; + int chan, range; + unsigned int d; + unsigned int bipolar; + + /* Unpack channel and range. */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + /* Set range - see analogue output range table; 0 => unipolar 10V, 1 => bipolar +/-10V range scale */ + bipolar = PCI230_TEST_BIT(range, PCI230_DAC_BIP_BIT); + outw(range, dev->iobase + PCI230_DACCON); + + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for(i=0;in;i++){ + d = data[i]; + + /* Store the value to be written to the DAC in our pci230_private struct before mangling it. */ + devpriv->ao_readback[chan] = d; + + /* If a bipolar range was specified, mangle it (straight binary->twos complement). */ + if (bipolar) { + d ^= 1<<(thisboard->ai_bits-1); + } + + /* PCI230 is 12 bit - stored in upper bits of 16 bit register (lower four bits reserved for expansion). */ + d = d<<4; + + /* Write data. */ + outw(d, dev->iobase + (((chan) == 0) ? PCI230_DACOUT1 : PCI230_DACOUT2)); + printk("comedi%d: amplc_pci230::pci230_ao_rinsn() wrote PCI230_DACOUTx 0x%04x\n",dev->minor, d); + + /* If we're writing more than one sample, wait for output to settle between successive writes */ + if (insn->n > 1) { + udelay(PCI230_DAC_SETTLE); + } + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int pci230_ao_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for(i=0;in;i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +/* Interrupt handler */ +static void pci230_interrupt(int irq, void *d, struct pt_regs *regs) +{ + comedi_device *dev = (comedi_device*) d; + comedi_subdevice *s = dev->read_subdev; + comedi_async *async; + int status_int, status_fifo; + int i; + unsigned int data; + + /* + * Check to see whether this driver has been configured yet. + * This must be done first, because if the board is asserting + * interrupts and our driver's attach fn. hasn't been called + * we can't even talk to the board (base addresses for native + * IO regions aren't set...) + */ + if(dev->attached == 0) { + comedi_error(dev, "premature interrupt"); + return; + } + + printk("comedi%d: amplc_pci230::pci230_interrupt executing interrupt handler\n", dev->minor); + + /* Read interrupt status/enable register. */ + status_int = inb(devpriv->pci_iobase + PCI230_INT_SCE); + printk("comedi%d: amplc_pci230::pci230_interrupt read PCI230_INT_SCE",dev->minor); printb(status_int); + + /* Disable board interrupts. */ + outb(PCI230_INT_DISABLE, devpriv->pci_iobase + PCI230_INT_SCE); + printk("comedi%d: amplc_pci230::pci230_interrupt wrote PCI230_INT_SCE",dev->minor); printb(PCI230_INT_DISABLE); + + /* + * Check to see whether this board emitted the interrupt, and if it did, + * whether it is for a supported function. + */ + if (status_int == PCI230_INT_DISABLE) { + printk("comedi%d: amplc_pci230::pci230_interrupt spurious interrupt",dev->minor); + return; + } + else if (status_int & PCI230_INT_PPI_C0) { + printk("comedi%d: amplc_pci230::pci230_interrupt PPI C0 interrupt, not implemented",dev->minor); + return; + } + else if (status_int & PCI230_INT_PPI_C3) { + printk("comedi%d: amplc_pci230::pci230_interrupt PPI C3 interrupt, not implemented",dev->minor); + return; + } + else if (status_int & PCI230_INT_ZCLK_CT0) { + printk("comedi%d: amplc_pci230::pci230_interrupt ZCLK CT0 interrupt, not implemented",dev->minor); + return; + } + + /* Read FIFO state. */ + status_fifo = inw(dev->iobase + PCI230_ADCCON); + printk("comedi%d: amplc_pci230::pci230_interrupt read PCI230_ADCCON",dev->minor); printb(status_fifo); + + /* Check to see whether FIFO enabled. */ + if (!(status_fifo & PCI230_ADC_FIFO_EN)) { + printk("comedi%d: amplc_pci230::pci230_interrupt FIFO not enabled",dev->minor); + return; + } + + /* Ok., only reason we're here is because; + * - Board has raised a conversion complete interrupt. + * - FIFO is enabled. + */ + + async = s->async; + async->events = 0; + + if (status_fifo & PCI230_ADC_FIFO_FULL) { + /* + * Report error and return - if we didn´t do this, but instead handled it in a similar + * manner to the half full FIFO case, FIFO overruns would go unnoticed by the caller. + */ + printk("comedi%d: amplc_pci230::pci230_interrupt FIFO overflow",dev->minor); + + /* Cancel sampled conversion. */ + pci230__cancel(dev, s); + comedi_error(dev, "FIFO overrun"); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + comedi_event(dev, s, async->events); + async->events = 0; + return; + } + else if (status_fifo & PCI230_ADC_FIFO_HALF) { + /* FIFO is at least half full */ + printk("comedi%d: amplc_pci230::pci230_interrupt FIFO half full\n",dev->minor); + for (i = 0; i < 2048; i++) { + /* Read sample. */ + data = inw(dev->iobase + PCI230_ADCDATA); + + /* PCI230 is 12 bit - stored in upper bits of 16 bit register (lower four bits reserved for expansion). */ + data = data>>4; + + /* If a bipolar range was specified, mangle it (twos complement->straight binary). */ + if (devpriv->bipolar) { + data ^= 1<<(thisboard->ai_bits-1); + } + + /* Store in Comedi's circular buffer. */ + comedi_buf_put(async, (sampl_t) data); + + if(async->cmd.stop_src == TRIG_COUNT) + { + if(--devpriv->count == 0) { + /* Acquisition complete. */ + printk("comedi%d: amplc_pci230::pci230_interrupt acquisition complete\n",dev->minor); + pci230__cancel(dev, s); + async->events |= COMEDI_CB_EOA; + break; + } + } + } + /* More samples required, tell Comedi to block, and enable boards interrupt (don´t change trigger level). */ + async->events |= COMEDI_CB_BLOCK; + outb(PCI230_INT_ADC_DAC, devpriv->pci_iobase + PCI230_INT_SCE); + printk("comedi%d: amplc_pci230::pci230_interrupt wrote PCI230_INT_SCE\n",dev->minor); printb(PCI230_INT_ADC_DAC); + } + else if (status_fifo & PCI230_ADC_FIFO_EMPTY) { + /* FIFO empty but we got an interrupt */ + printk("comedi%d: amplc_pci230::pci230_interrupt FIFO empty - spurious interrupt\n",dev->minor); + } + else { + /* FIFO is less than half full, but not empty. */ + /* We should only ever get here if no. samples to read < half fifo size. */ + printk("comedi%d: amplc_pci230::pci230_interrupt FIFO less than half full, but not empty\n",dev->minor); + while (devpriv->count != 0) { + if (inw(dev->iobase + PCI230_ADCCON) & PCI230_ADC_FIFO_EMPTY) { + /* The FIFO is empty, block. */ + printk("comedi%d: amplc_pci230::pci230_interrupt FIFO now empty, want %d samples\n",dev->minor, devpriv->count); + + /* More samples required, tell Comedi to block, and enable boards interrupt (don´t change trigger level). */ + outb(PCI230_INT_ADC_DAC, devpriv->pci_iobase + PCI230_INT_SCE); + printk("comedi%d: amplc_pci230::pci230_interrupt wrote PCI230_INT_SCE\n",dev->minor); printb(PCI230_INT_ADC_DAC); + async->events |= COMEDI_CB_BLOCK; + comedi_event(dev, s, async->events); + return; + } + /* There are sample(s) to read from FIFO, read one. */ + data = inw(dev->iobase + PCI230_ADCDATA); + + /* PCI230 is 12 bit - stored in upper bits of 16 bit register (lower four bits reserved for expansion). */ + data = data>>4; + + /* If a bipolar range was specified, mangle it (twos complement->straight binary). */ + if (devpriv->bipolar) { + data ^= 1<<(thisboard->ai_bits-1); + } + + /* Store in Comedi's circular buffer. */ + comedi_buf_put(async, (sampl_t) data); + + if(devpriv->count > 0) devpriv->count--; + } + + /* Acquisition complete. */ + printk("comedi%d: amplc_pci230::pci230_interrupt acquisition complete\n",dev->minor); + pci230__cancel(dev, s); + async->events |= COMEDI_CB_EOA; + } + + comedi_event(dev, s, async->events); + async->events = 0; + + return; +} + +static int pci230__cancel(comedi_device *dev, comedi_subdevice *s) +{ + /* Disable interrupts. */ + outb(PCI230_INT_DISABLE, devpriv->pci_iobase + PCI230_INT_SCE); + printk("comedi%d: amplc_pci230::pci230__cancel disabled interrupts\n",dev->minor); + + /* Reset FIFO and set start conversion source to none. */ + outw_p(PCI230_ADC_FIFO_RESET | PCI230_ADC_TRIG_NONE, dev->iobase + PCI230_ADCCON); + printk("comedi%d: amplc_pci230::pci230__cancel reset FIFO and set conv src to none\n", dev->minor); + + /* Clear channel scan list. */ + outw_p(0x0000, dev->iobase + PCI230_ADCEN); + printk("comedi%d: amplc_pci230::pci230__cancel cleared scan list\n", dev->minor); + + /* Clear channel gains. */ + outw_p(0x0000, dev->iobase + PCI230_ADCG); + printk("comedi%d: amplc_pci230::pci230__cancel cleared channel gains\n", dev->minor); + + return 0; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +COMEDI_INITCLEANUP(driver_amplc_pci230); + -- 2.26.2