-/*
- cb_pcidas64.c
- This is a driver for the ComputerBoards/MeasurementComputing PCI-DAS
- 64xxx cards.
-
- Author: Frank Mori Hess <fmhess@uiuc.edu>
-
- Options:
- [0] - PCI bus number
- [1] - PCI slot number
-
- Copyright (C) 2001 Frank Mori Hess <fmhess@uiuc.edu>
-
- COMEDI - Linux Control and Measurement Device Interface
- Copyright (C) 1997-8 David A. Schleef <ds@stm.lbl.gov>
-
- 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: cb_pcidas64.o
-Description: Driver for the ComputerBoards/MeasurementComputing
- PCI-DAS64xxx series with the PLX 9080 PCI controller.
-Author: Frank Mori Hess <fmhess@uiuc.edu>
-Status: Experimental
-Updated: 2001-8-27
-
-Configuration options:
- [0] - PCI bus of device (optional)
- [1] - PCI slot of device (optional)
-
-Basic insn support should work, but untested as far as I know.
- Feel free to send and success/failure reports to author. No
- command support yet.
-*/
-/*
-STATUS:
- insn supported
-
-TODO:
- command support
- calibration subdevice
- user counter subdevice
- there are a number of boards this driver will support when they are
- fully released, but does not since yet since the pci device id numbers
- are not yet available.
- add plx9080 stuff to make interrupts and dma work
- need to take care to prevent ai and ao from affecting each others register bits
- support prescaled 100khz clock for slow pacing
-*/
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/sched.h>
-#include <linux/mm.h>
-#include <linux/malloc.h>
-#include <linux/errno.h>
-#include <linux/ioport.h>
-#include <linux/delay.h>
-#include <linux/interrupt.h>
-#include <linux/timex.h>
-#include <linux/timer.h>
-#include <linux/pci.h>
-#include <linux/init.h>
-#include <asm/io.h>
-#include <linux/comedidev.h>
-#include "8253.h"
-#include "8255.h"
-#include "plx9080.h"
-
-#define PCIDAS64_DEBUG // enable debugging code
-//#undef PCIDAS64_DEBUG // disable debugging code
-
-// PCI vendor number of ComputerBoards/MeasurementComputing
-#define PCI_VENDOR_ID_CB 0x1307
-#define TIMER_BASE 25 // 40MHz master clock
-#define PRESCALED_TIMER_BASE 10000 // 100kHz 'prescaled' clock for slow aquisition, maybe I'll support this someday
-
-/* PCI-DAS64xxx base addresses */
-
-// indices of base address regions
-#define PLX9080_BADRINDEX 0
-#define MAIN_BADRINDEX 2
-#define DIO_COUNTER_BADRINDEX 3
-
-#define PLX9080_IOSIZE 0xec
-#define MAIN_IOSIZE 0x302
-#define DIO_COUNTER_IOSIZE 0x29
-
-// devpriv->main_iobase registers
-// write-only
-#define INTR_ENABLE_REG 0x0 // interrupt enable register
-#define EN_ADC_OVERRUN_BIT 0x8000 // enable adc overrun status bit
-#define EN_DAC_UNDERRUN_BIT 0x4000 // enable dac underrun status bit
-#define EN_ADC_DONE_INTR_BIT 0x8 // enable adc aquisition done interrupt
-#define EN_ADC_INTR_SRC_BIT 0x4 // enable adc interrupt source
-#define ADC_INTR_SRC_MASK 0x3 // bits that set adc interrupt source
-#define ADC_INTR_QFULL_BITS 0x0 // interrupt fifo quater full
-#define ADC_INTR_EOC_BITS 0x1 // interrupt end of conversion
-#define ADC_INTR_EOSCAN_BITS 0x2 // interrupt end of scan
-#define ADC_INTR_EOSEQ_BITS 0x3 // interrupt end of sequence (probably wont use this it's pretty fancy)
-#define HW_CONFIG_REG 0x2 // hardware config register
-#define HW_CONFIG_DUMMY_BITS 0x2400 // bits that don't do anything yet but are given default values
-#define HW_CONFIG_DUMMY_BITS_6402 0x0400 // dummy bits in 6402 manual are slightly different, probably doesn't matter
-#define EXT_QUEUE 0x200 // use external channel/gain queue (more versatile than internal queue)
-#define FIFO_SIZE_REG 0x4 // allows adjustment of fifo sizes, we will always use maximum
-#define FIFO_SIZE_DUMMY_BITS 0xf038 // bits that don't do anything yet but are given default values
-#define ADC_FIFO_SIZE_MASK 0x7 // bits that set adc fifo size
-#define ADC_FIFO_8K_BITS 0x0 // 8 kilosample adc fifo
-#define DAC_FIFO_SIZE_MASK 0xf00 // bits that set dac fifo size
-#define DAC_FIFO_16K_BITS 0x0
-#define ADC_CONTROL0_REG 0x10 // adc control register 0
-#define TRIG1_FALLING_BIT 0x20 // trig 1 uses falling edge
-#define ADC_EXT_CONV_FALLING_BIT 0x800 // external pacing uses falling edge
-#define ADC_ENABLE_BIT 0x8000 // master adc enable
-#define ADC_CONTROL1_REG 0x12 // adc control register 1
-#define SW_NOGATE_BIT 0x40 // disables software gate of adc
-#define ADC_MODE_BITS(x) (((x) & 0xf) << 12)
-#define ADC_SAMPLE_INTERVAL_LOWER_REG 0x16 // lower 16 bits of sample interval counter
-#define ADC_SAMPLE_INTERVAL_UPPER_REG 0x18 // upper 8 bits of sample interval counter
-#define ADC_DELAY_INTERVAL_LOWER_REG 0x1a // lower 16 bits of delay interval counter
-#define ADC_DELAY_INTERVAL_UPPER_REG 0x1c // upper 8 bits of delay interval counter
-#define ADC_COUNT_LOWER_REG 0x1e // lower 16 bits of hardware conversion/scan counter
-#define ADC_COUNT_UPPER_REG 0x20 // upper 8 bits of hardware conversion/scan counter
-#define ADC_START_REG 0x22 // software trigger to start aquisition
-#define ADC_CONVERT_REG 0x24 // initiates single conversion
-#define ADC_QUEUE_CLEAR_REG 0x26 // clears adc queue
-#define ADC_QUEUE_LOAD_REG 0x28 // loads adc queue
-#define CHAN_BITS(x) ((x) & 0x3f)
-#define GAIN_BITS(x) (((x) & 0x3) << 8) // translates range index to gain bits
-#define UNIP_BIT(x) (((x) & 0x4) << 11) // translates range index to unipolar/bipolar bit
-#define SE_BIT 0x1000 // single-ended/ differential bit
-#define QUEUE_EOSEQ_BIT 0x4000 // queue end of sequence
-#define QUEUE_EOSCAN_BIT 0x8000 // queue end of scan
-#define ADC_BUFFER_CLEAR_REG 0x2a
-#define ADC_QUEUE_HIGH_REG 0x2c // high channel for internal queue, use CHAN_BITS() macro above
-#define DAC_CONTROL0_REG 0x50 // dac control register 0
-#define DAC_ENABLE_BIT 0x8000 // dac controller enable bit
-#define DAC_CONTROL1_REG 0x52 // dac control register 0
-#define DAC_RANGE_BITS(channel, range) (((range) & 0x3) << (2 * ((channel) & 0x1)))
-#define DAC_OUTPUT_ENABLE_BIT 0x80 // dac output enable bit
-#define DAC_BUFFER_CLEAR_REG 0x66 // clear dac buffer
-#define DAC_CONVERT_REG(channel) ((0x70) + (2 * ((channel) & 0x1)))
-// read-only
-#define HW_STATUS_REG 0x0
-#define ADC_BUSY_BIT 0x8
-#define HW_REVISION(x) (((x) >> 12) & 0xf)
-#define PIPE1_READ_REG 0x4
-// read-write
-#define ADC_QUEUE_FIFO_REG 0x100 // external channel/gain queue, uses same bits as ADC_QUEUE_LOAD_REG
-
-// devpriv->dio_counter_iobase registers
-#define DIO_8255_OFFSET 0x0
-#define DO_REG 0x20
-#define DI_REG 0x28
-
-// analog input ranges for most boards
-static comedi_lrange ai_ranges =
-{
- 8,
- {
- BIP_RANGE(10),
- BIP_RANGE(5),
- BIP_RANGE(2.5),
- BIP_RANGE(1.25),
- UNI_RANGE(10),
- UNI_RANGE(5),
- UNI_RANGE(2.5),
- UNI_RANGE(1.25)
- }
-};
-
-// analog output ranges
-static comedi_lrange ao_ranges =
-{
- 4,
- {
- BIP_RANGE(5),
- BIP_RANGE(10),
- UNI_RANGE(5),
- UNI_RANGE(10),
- }
-};
-
-typedef struct pcidas64_board_struct
-{
- char *name;
- int device_id; // pci device id
- int ai_se_chans; // number of ai inputs in single-ended mode
- int ai_bits; // analog input resolution
- int ai_speed; // fastest conversion period in ns
- int ao_nchan; // number of analog out channels
- int ao_scan_speed; // analog output speed (for a scan, not conversion)
-} pcidas64_board;
-
-static pcidas64_board pcidas64_boards[] =
-{
- {
- name: "pci-das6402/16",
- device_id: 0x1d,
- ai_se_chans: 64,
- ai_bits: 16,
- ai_speed: 5000,
- ao_nchan: 2,
- ao_scan_speed: 10000,
- },
- {
- name: "pci-das6402/12", // XXX check
- device_id: 0x1e,
- ai_se_chans: 64,
- ai_bits: 12,
- ai_speed: 5000,
- ao_nchan: 2,
- ao_scan_speed: 10000,
- },
- {
- name: "pci-das64/m1/16",
- device_id: 0x35,
- ai_se_chans: 64,
- ai_bits: 16,
- ai_speed: 1000,
- ao_nchan: 2,
- ao_scan_speed: 10000,
- },
- {
- name: "pci-das64/m2/16",
- device_id: 0x36,
- ai_se_chans: 64,
- ai_bits: 16,
- ai_speed: 500,
- ao_nchan: 2,
- ao_scan_speed: 10000,
- },
- {
- name: "pci-das64/m3/16",
- device_id: 0x37,
- ai_se_chans: 64,
- ai_bits: 16,
- ai_speed: 333,
- ao_nchan: 2,
- ao_scan_speed: 10000,
- },
-
-#if 0
- {
- name: "pci-das6402/16/jr",
- device_id: 0 // XXX,
- ai_se_chans: 64,
- ai_bits: 16,
- ai_speed: 5000,
- ao_nchan: 0,
- ao_scan_speed: 10000,
- },
- {
- name: "pci-das64/m1/16/jr",
- device_id: 0 // XXX,
- ai_se_chans: 64,
- ai_bits: 16,
- ai_speed: 1000,
- ao_nchan: 0,
- ao_scan_speed: 10000,
- },
- {
- name: "pci-das64/m2/16/jr",
- device_id: 0 // XXX,
- ai_se_chans: 64,
- ai_bits: 16,
- ai_speed: 500,
- ao_nchan: 0,
- ao_scan_speed: 10000,
- },
- {
- name: "pci-das64/m3/16/jr",
- device_id: 0 // XXX,
- ai_se_chans: 64,
- ai_bits: 16,
- ai_speed: 333,
- ao_nchan: 0,
- ao_scan_speed: 10000,
- },
- {
- name: "pci-das64/m1/14",
- device_id: 0, // XXX
- ai_se_chans: 64,
- ai_bits: 14,
- ai_speed: 1000,
- ao_nchan: 2,
- ao_scan_speed: 10000,
- },
- {
- name: "pci-das64/m2/14",
- device_id: 0, // XXX
- ai_se_chans: 64,
- ai_bits: 14,
- ai_speed: 500,
- ao_nchan: 2,
- ao_scan_speed: 10000,
- },
- {
- name: "pci-das64/m3/14",
- device_id: 0, // XXX
- ai_se_chans: 64,
- ai_bits: 14,
- ai_speed: 333,
- ao_nchan: 2,
- ao_scan_speed: 10000,
- },
-#endif
-
-};
-// Number of boards in cb_pcidas_boards
-#define N_BOARDS (sizeof(pcidas64_boards) / sizeof(pcidas64_board))
-
-static struct pci_device_id pcidas64_pci_table[] __devinitdata = {
- { PCI_VENDOR_ID_CB, 0x001d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
- { PCI_VENDOR_ID_CB, 0x001e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
- { PCI_VENDOR_ID_CB, 0x0035, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
- { PCI_VENDOR_ID_CB, 0x0036, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
- { PCI_VENDOR_ID_CB, 0x0037, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
- { 0 }
-};
-MODULE_DEVICE_TABLE(pci, pcidas64_pci_table);
-
-/*
- * Useful for shorthand access to the particular board structure
- */
-#define thisboard ((pcidas64_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. */
-typedef struct
-{
- // base addresses (physical)
- unsigned long plx9080_phys_iobase;
- unsigned long main_phys_iobase;
- unsigned long dio_counter_phys_iobase;
- // base addresses (ioremapped)
- unsigned long plx9080_iobase;
- unsigned long main_iobase;
- unsigned long dio_counter_iobase;
- // divisor of master clock for analog input pacing
- unsigned int ai_divisor;
- volatile unsigned int ai_count; // number of analog input samples remaining
- // divisors of master clock for analog output pacing
- unsigned int ao_divisor;
- volatile unsigned int ao_count; // number of analog output samples remaining
- unsigned int ao_value[2]; // remember what the analog outputs are set to, to allow readback
- unsigned int hw_revision; // stc chip hardware revision number
- unsigned int do_bits; // remember digital ouput levels
-} pcidas64_private;
-
-/*
- * most drivers define the following macro to make it easy to
- * access the private structure.
- */
-#define devpriv ((pcidas64_private *)dev->private)
-
-/*
- * 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 attach(comedi_device *dev,comedi_devconfig *it);
-static int detach(comedi_device *dev);
-comedi_driver driver_cb_pcidas={
- driver_name: "cb_pcidas64",
- module: THIS_MODULE,
- attach: attach,
- detach: detach,
-};
-
-static int ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
-static int ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
-static int ao_readback_insn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
-static int ai_cmd(comedi_device *dev,comedi_subdevice *s);
-static int ai_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd);
-//static int ao_cmd(comedi_device *dev,comedi_subdevice *s);
-//static int ao_inttrig(comedi_device *dev, comedi_subdevice *subdev, unsigned int trig_num);
-//static int ao_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd);
-static void handle_interrupt(int irq, void *d, struct pt_regs *regs);
-static int ai_cancel(comedi_device *dev, comedi_subdevice *s);
-//static int ao_cancel(comedi_device *dev, comedi_subdevice *s);
-static int dio_callback(int dir, int port, int data, void *arg);
-static int di_rbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
-static int do_wbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
-static void check_adc_timing(comedi_cmd *cmd);
-static unsigned int get_divisor(unsigned int ns, unsigned int flags);
-
-/*
- * A convenient macro that defines init_module() and cleanup_module(),
- * as necessary.
- */
-COMEDI_INITCLEANUP(driver_cb_pcidas);
-
-/*
- * Attach is called by the Comedi core to configure the driver
- * for a particular board.
- */
-static int attach(comedi_device *dev, comedi_devconfig *it)
-{
- comedi_subdevice *s;
- struct pci_dev* pcidev;
- int index;
- // base addresses
- unsigned long plx9080_iobase;
- unsigned long main_iobase;
- unsigned long dio_counter_iobase;
-
- printk("comedi%d: cb_pcidas64\n",dev->minor);
-
-/*
- * Allocate the private structure area.
- */
- if(alloc_private(dev,sizeof(pcidas64_private)) < 0)
- return -ENOMEM;
-
-/*
- * Probe the device to determine what device in the series it is.
- */
-
- pci_for_each_dev(pcidev)
- {
- // is it not a computer boards card?
- if(pcidev->vendor != PCI_VENDOR_ID_CB)
- continue;
-#ifdef PCIDAS64_DEBUG
- printk(" found computer boards device id 0x%x on bus %i slot %i\n",
- pcidev->device, pcidev->bus->number, PCI_SLOT(pcidev->devfn));
-#endif
- // loop through cards supported by this driver
- for(index = 0; index < N_BOARDS; index++)
- {
- if(pcidas64_boards[index].device_id != pcidev->device)
- continue;
- // was a particular bus/slot requested?
- if(it->options[0] || it->options[1])
- {
- // are we on the wrong bus/slot?
- if(pcidev->bus->number != it->options[0] ||
- PCI_SLOT(pcidev->devfn) != it->options[1])
- {
- continue;
- }
- }
- dev->board_ptr = pcidas64_boards + index;
- goto found;
- }
- }
-
- printk("No supported ComputerBoards/MeasurementComputing card found\n");
- return -EIO;
-
-found:
-
- printk("Found %s on bus %i, slot %i\n", pcidas64_boards[index].name,
- pcidev->bus->number, PCI_SLOT(pcidev->devfn));
-
- //Initialize dev->board_name
- dev->board_name = thisboard->name;
-
- /* Initialize devpriv->control_status and devpriv->adc_fifo to point to
- * their base address.
- */
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0)
- plx9080_iobase =
- pcidev->base_address[PLX9080_BADRINDEX] &
- PCI_BASE_ADDRESS_MEM_MASK;
- main_iobase =
- pcidev->base_address[MAIN_BADRINDEX] &
- PCI_BASE_ADDRESS_MEM_MASK;
- dio_counter_iobase =
- pcidev->base_address[DIO_COUNTER_BADRINDEX] &
- PCI_BASE_ADDRESS_MEM_MASK;
-#else
- if(pci_enable_device(pcidev))
- return -EIO;
- pci_set_master(pcidev);
- plx9080_iobase =
- pcidev->resource[PLX9080_BADRINDEX].start &
- PCI_BASE_ADDRESS_MEM_MASK;
- main_iobase =
- pcidev->resource[MAIN_BADRINDEX].start &
- PCI_BASE_ADDRESS_MEM_MASK;
- dio_counter_iobase =
- pcidev->resource[DIO_COUNTER_BADRINDEX].start &
- PCI_BASE_ADDRESS_MEM_MASK;
-
- if(check_mem_region(plx9080_iobase, PLX9080_IOSIZE))
- {
- /* Couldn't allocate io space */
- printk(KERN_WARNING "couldn't allocate IO space\n");
- return -EIO;
- }
- if(check_mem_region(main_iobase, MAIN_IOSIZE))
- {
- /* Couldn't allocate io space */
- printk(KERN_WARNING "couldn't allocate IO space\n");
- return -EIO;
- }
- if(check_mem_region(dio_counter_iobase, DIO_COUNTER_IOSIZE))
- {
- /* Couldn't allocate io space */
- printk(KERN_WARNING "couldn't allocate IO space\n");
- return -EIO;
- }
-
- request_mem_region(plx9080_iobase, PLX9080_IOSIZE, "cb_pcidas64");
- devpriv->plx9080_phys_iobase = dio_counter_iobase;
- request_mem_region(main_iobase, MAIN_IOSIZE, "cb_pcidas64");
- devpriv->main_phys_iobase = dio_counter_iobase;
- request_mem_region(dio_counter_iobase, DIO_COUNTER_IOSIZE, "cb_pcidas64");
- devpriv->dio_counter_phys_iobase = dio_counter_iobase;
-
-#endif
-
- // remap, won't work with 2.0 kernels but who cares
- devpriv->plx9080_iobase = (unsigned long)ioremap(plx9080_iobase, PLX9080_IOSIZE);
- devpriv->main_iobase = (unsigned long)ioremap(main_iobase, MAIN_IOSIZE);
- devpriv->dio_counter_iobase = (unsigned long)ioremap(dio_counter_iobase, DIO_COUNTER_IOSIZE);
-
- devpriv->hw_revision = HW_REVISION(readw(devpriv->main_iobase + HW_STATUS_REG));
-
- // get irq
-/* if(comedi_request_irq(pcidev->irq, handle_interrupt, SA_SHIRQ, "cb_pcidas64", dev ))
- {
- printk(" unable to allocate irq %d\n", pcidev->irq);
- return -EINVAL;
- }
- dev->irq = pcidev->irq;
-*/
-#ifdef PCIDAS64_DEBUG
-
-printk(" plx9080 phys io addr 0x%lx\n", devpriv->plx9080_phys_iobase);
-printk(" main phys io addr 0x%lx\n", devpriv->main_phys_iobase);
-printk(" diocounter phys io addr 0x%lx\n", devpriv->dio_counter_phys_iobase);
-printk(" irq %i\n", dev->irq);
-
-printk(" plx9080 virt io addr 0x%lx\n", devpriv->plx9080_iobase);
-printk(" main virt io addr 0x%lx\n", devpriv->main_iobase);
-printk(" diocounter virt io addr 0x%lx\n", devpriv->dio_counter_iobase);
-printk(" irq %i\n", dev->irq);
-
-printk(" stc hardware revision %i\n", devpriv->hw_revision);
-
-// plx9080 dump
-printk(" plx interrupt status 0x%x\n", readl(devpriv->plx9080_iobase + PLX_INTRCS_REG));
-printk(" plx id bits 0x%x\n", readl(devpriv->plx9080_iobase + PLX_ID_REG));
-printk(" plx hardware revision 0x%x\n", readl(devpriv->plx9080_iobase + PLX_REVISION_REG));
-printk(" plx dma channel 0 mode 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_MODE_REG));
-printk(" plx dma channel 0 pci address 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG));
-printk(" plx dma channel 0 local address 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_LOCAL_ADDRESS_REG));
-printk(" plx dma channel 0 transfer size 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_TRANSFER_SIZE_REG));
-printk(" plx dma channel 0 descriptor 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG));
-printk(" plx dma channel 0 command status 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_CS_REG));
-printk(" plx dma channel 0 threshold 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_THRESHOLD_REG));
-
-#endif
-
-
-/*
- * Allocate the subdevice structures.
- */
- dev->n_subdevices = 7;
- if(alloc_subdevices(dev)<0)
- return -ENOMEM;
-
- s = dev->subdevices + 0;
- /* analog input subdevice */
- dev->read_subdev = s;
- s->type = COMEDI_SUBD_AI;
- s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF;
- /* XXX Number of inputs in differential mode is ignored */
- s->n_chan = thisboard->ai_se_chans;
- s->len_chanlist = 8092;
- s->maxdata = (1 << thisboard->ai_bits) - 1;
- s->range_table = &ai_ranges;
- s->insn_read = ai_rinsn;
- //s->do_cmd = ai_cmd;
- //s->do_cmdtest = ai_cmdtest;
- //s->cancel = ai_cancel;
-
- /* analog output subdevice */
- s = dev->subdevices + 1;
- if(thisboard->ao_nchan)
- {
- // dev->write_subdev = s;
- s->type = COMEDI_SUBD_AO;
- s->subdev_flags = SDF_READABLE | SDF_WRITEABLE | SDF_GROUND;
- s->n_chan = thisboard->ao_nchan;
- // analog out resolution is the same as analog input resolution, so use ai_bits
- s->maxdata = (1 << thisboard->ai_bits) - 1;
- s->range_table = &ao_ranges;
- s->insn_read = ao_readback_insn;
- s->insn_write = ao_winsn;
- // s->do_cmdtest = ao_cmdtest;
- // s->do_cmd = ao_cmd;
- // s->len_chanlist = thisboard->ao_nchan;
- // s->cancel = ao_cancel;
- } else
- {
- s->type = COMEDI_SUBD_UNUSED;
- }
-
- // digital input
- s = dev->subdevices + 2;
- s->type=COMEDI_SUBD_DI;
- s->subdev_flags = SDF_READABLE;
- s->n_chan = 4;
- s->maxdata = 1;
- s->range_table = &range_digital;
- s->insn_bits = di_rbits;
-
- // digital output
- s = dev->subdevices + 3;
- s->type=COMEDI_SUBD_DO;
- s->subdev_flags = SDF_WRITEABLE | SDF_READABLE;
- s->n_chan = 4;
- s->maxdata = 1;
- s->range_table = &range_digital;
- s->insn_bits = do_wbits;
-
- /* 8255 */
- s = dev->subdevices + 4;
- subdev_8255_init(dev, s, dio_callback,
- (void*) (devpriv->dio_counter_iobase + DIO_8255_OFFSET));
-
- // user counter subd XXX
- s = dev->subdevices + 5;
- s->type = COMEDI_SUBD_UNUSED;
-
- // calibration subd XXX
- s = dev->subdevices + 6;
- s->type = COMEDI_SUBD_UNUSED;
-
- return 0;
-}
-
-
-/*
- * _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 detach(comedi_device *dev)
-{
- printk("comedi%d: cb_pcidas: remove\n",dev->minor);
-
- if(devpriv)
- {
- if(devpriv->plx9080_iobase)
- iounmap((void*)devpriv->plx9080_iobase);
- if(devpriv->main_iobase)
- iounmap((void*)devpriv->main_iobase);
- if(devpriv->dio_counter_iobase)
- iounmap((void*)devpriv->dio_counter_iobase);
- if(devpriv->plx9080_phys_iobase)
- release_mem_region(devpriv->plx9080_iobase, PLX9080_IOSIZE);
- if(devpriv->main_iobase)
- release_mem_region(devpriv->main_phys_iobase, MAIN_IOSIZE);
- if(devpriv->dio_counter_iobase)
- release_mem_region(devpriv->dio_counter_phys_iobase, DIO_COUNTER_IOSIZE);
- }
- if(dev->irq)
- comedi_free_irq(dev->irq, dev);
- if(dev->subdevices)
- subdev_8255_cleanup(dev,dev->subdevices + 4);
-
- return 0;
-}
-
-static int ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
-{
- unsigned int bits, n, i;
- const int timeout = 1000;
-
- // disable card's interrupt sources
- writew(0, devpriv->main_iobase + INTR_ENABLE_REG);
-
- /* disable pacing, triggering, etc */
- writew(ADC_ENABLE_BIT, devpriv->main_iobase + ADC_CONTROL0_REG);
- writew(0, devpriv->main_iobase + ADC_CONTROL1_REG);
-
- // use internal queue
- writew(HW_CONFIG_DUMMY_BITS, devpriv->main_iobase + HW_CONFIG_REG);
-
- // load internal queue
- bits = 0;
- // set channel
- bits |= CHAN_BITS(CR_CHAN(insn->chanspec));
- // set gain
- bits |= GAIN_BITS(CR_RANGE(insn->chanspec));
- // set unipolar / bipolar
- bits |= UNIP_BIT(CR_RANGE(insn->chanspec));
- // set single-ended / differential
- if(CR_AREF(insn->chanspec) != AREF_DIFF)
- bits |= SE_BIT;
- // set stop channel
- writew(CHAN_BITS(CR_CHAN(insn->chanspec)), devpriv->main_iobase + ADC_QUEUE_HIGH_REG);
- // set start channel, and rest of settings
- writew(bits, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
-
- // clear adc buffer
- writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);
-
- for(n = 0; n < insn->n; n++)
- {
- /* trigger conversion */
- writew(0, devpriv->main_iobase + ADC_CONVERT_REG);
-
- // wait for data
- for(i = 0; i < timeout; i++)
- {
- if(!(readw(devpriv->main_iobase + HW_STATUS_REG) & ADC_BUSY_BIT))
- break;
- }
- if(i == timeout)
- {
- comedi_error(dev, " analog input read insn timed out");
- return -ETIME;
- }
- data[n] = readw(devpriv->main_iobase + PIPE1_READ_REG);
- }
-
- return n;
-}
-
-static int ai_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd)
-{ int err = 0;
- int tmp;
- unsigned int tmp_arg, tmp_arg2;
- int i;
- int aref;
-
- /* step 1: make sure trigger sources are trivially valid */
-
- 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_FOLLOW;
- 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_EXT | TRIG_NONE;
- if(!cmd->stop_src || tmp != cmd->stop_src) err++;
-
- if(err) return 1;
-
- /* step 2: make sure trigger sources are unique and mutually compatible */
-
- // uniqueness check
- if(cmd->start_src != TRIG_NOW &&
- cmd->start_src != TRIG_EXT) err++;
- if(cmd->scan_begin_src != TRIG_TIMER &&
- cmd->scan_begin_src != TRIG_FOLLOW) err++;
- if(cmd->convert_src != TRIG_TIMER &&
- cmd->convert_src != TRIG_EXT) err++;
- if(cmd->stop_src != TRIG_COUNT &&
- cmd->stop_src != TRIG_NONE &&
- cmd->stop_src != TRIG_EXT) err++;
-
- // compatibility check
- if(cmd->convert_src == TRIG_EXT &&
- cmd->scan_begin_src == TRIG_TIMER)\r
+/*\r
+ cb_pcidas64.c\r
+ This is a driver for the ComputerBoards/MeasurementComputing PCI-DAS\r
+ 64xxx cards.\r
+\r
+ Author: Frank Mori Hess <fmhess@uiuc.edu>\r
+\r
+ Options:\r
+ [0] - PCI bus number\r
+ [1] - PCI slot number\r
+\r
+ Copyright (C) 2001 Frank Mori Hess <fmhess@uiuc.edu>\r
+\r
+ COMEDI - Linux Control and Measurement Device Interface\r
+ Copyright (C) 1997-8 David A. Schleef <ds@stm.lbl.gov>\r
+\r
+ This program is free software; you can redistribute it and/or modify\r
+ it under the terms of the GNU General Public License as published by\r
+ the Free Software Foundation; either version 2 of the License, or\r
+ (at your option) any later version.\r
+\r
+ This program is distributed in the hope that it will be useful,\r
+ but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+ GNU General Public License for more details.\r
+\r
+ You should have received a copy of the GNU General Public License\r
+ along with this program; if not, write to the Free Software\r
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\r
+\r
+************************************************************************/\r
+\r
+/*\r
+\r
+Driver: cb_pcidas64.o\r
+Description: Driver for the ComputerBoards/MeasurementComputing\r
+ PCI-DAS64xxx series with the PLX 9080 PCI controller.\r
+Author: Frank Mori Hess <fmhess@uiuc.edu>\r
+Status: Experimental\r
+Updated: 2001-8-27\r
+Configuration options:\r
+ [0] - PCI bus of device (optional)\r
+ [1] - PCI slot of device (optional)\r
+\r
+Basic insn support should work, but untested as far as I know.\r
+Has command support for analog input, which may also work. Still\r
+needs dma support to be added to support very fast aquisition rates.\r
+This driver is in need of stout-hearted testers who aren't afraid to\r
+crash their computers in the name of progress.\r
+Feel free to send and success/failure reports to author.\r
+\r
+*/\r
+\r
+/*\r
+\r
+TODO:\r
+ command support for ao\r
+ pci-dma support\r
+ calibration subdevice\r
+ user counter subdevice\r
+ there are a number of boards this driver will support when they are\r
+ fully released, but does not since yet since the pci device id numbers\r
+ are not yet available.\r
+ need to take care to prevent ai and ao from affecting each others register bits\r
+ support prescaled 100khz clock for slow pacing\r
+*/\r
+\r
+#include <linux/kernel.h>\r
+#include <linux/module.h>\r
+#include <linux/sched.h>\r
+#include <linux/mm.h>\r
+#include <linux/malloc.h>\r
+#include <linux/errno.h>\r
+#include <linux/ioport.h>\r
+#include <linux/delay.h>\r
+#include <linux/interrupt.h>\r
+#include <linux/timex.h>\r
+#include <linux/timer.h>\r
+#include <linux/pci.h>\r
+#include <linux/init.h>\r
+#include <asm/io.h>\r
+#include <linux/comedidev.h>\r
+#include "8253.h"\r
+#include "8255.h"\r
+#include "plx9080.h"\r
+\r
+#define PCIDAS64_DEBUG // enable debugging code\r
+//#undef PCIDAS64_DEBUG // disable debugging code\r
+\r
+// PCI vendor number of ComputerBoards/MeasurementComputing\r
+#define PCI_VENDOR_ID_CB 0x1307\r
+#define TIMER_BASE 25 // 40MHz master clock\r
+#define PRESCALED_TIMER_BASE 10000 // 100kHz 'prescaled' clock for slow aquisition, maybe I'll support this someday\r
+#define QUARTER_AI_FIFO_SIZE 2048 // 1/4 analog input fifo size\r
+\r
+/* PCI-DAS64xxx base addresses */\r
+\r
+// indices of base address regions\r
+#define PLX9080_BADRINDEX 0\r
+#define MAIN_BADRINDEX 2\r
+#define DIO_COUNTER_BADRINDEX 3\r
+\r
+#define PLX9080_IOSIZE 0xec\r
+#define MAIN_IOSIZE 0x302\r
+#define DIO_COUNTER_IOSIZE 0x29\r
+\r
+// devpriv->main_iobase registers\r
+// write-only\r
+#define INTR_ENABLE_REG 0x0 // interrupt enable register\r
+#define EN_ADC_OVERRUN_BIT 0x8000 // enable adc overrun status bit\r
+#define EN_DAC_UNDERRUN_BIT 0x4000 // enable dac underrun status bit\r
+#define EN_ADC_DONE_INTR_BIT 0x8 // enable adc aquisition done interrupt\r
+#define EN_ADC_INTR_SRC_BIT 0x4 // enable adc interrupt source\r
+#define ADC_INTR_SRC_MASK 0x3 // bits that set adc interrupt source\r
+#define ADC_INTR_QFULL_BITS 0x0 // interrupt fifo quater full\r
+#define ADC_INTR_EOC_BITS 0x1 // interrupt end of conversion\r
+#define ADC_INTR_EOSCAN_BITS 0x2 // interrupt end of scan\r
+#define ADC_INTR_EOSEQ_BITS 0x3 // interrupt end of sequence (probably wont use this it's pretty fancy)\r
+#define HW_CONFIG_REG 0x2 // hardware config register\r
+#define HW_CONFIG_DUMMY_BITS 0x2400 // bits that don't do anything yet but are given default values\r
+#define HW_CONFIG_DUMMY_BITS_6402 0x0400 // dummy bits in 6402 manual are slightly different, probably doesn't matter\r
+#define EXT_QUEUE 0x200 // use external channel/gain queue (more versatile than internal queue)\r
+#define FIFO_SIZE_REG 0x4 // allows adjustment of fifo sizes, we will always use maximum\r
+#define FIFO_SIZE_DUMMY_BITS 0xf038 // bits that don't do anything yet but are given default values\r
+#define ADC_FIFO_SIZE_MASK 0x7 // bits that set adc fifo size\r
+#define ADC_FIFO_8K_BITS 0x0 // 8 kilosample adc fifo\r
+#define DAC_FIFO_SIZE_MASK 0xf00 // bits that set dac fifo size\r
+#define DAC_FIFO_16K_BITS 0x0\r
+#define ADC_CONTROL0_REG 0x10 // adc control register 0\r
+#define TRIG1_FALLING_BIT 0x20 // trig 1 uses falling edge\r
+#define ADC_EXT_CONV_FALLING_BIT 0x800 // external pacing uses falling edge\r
+#define ADC_ENABLE_BIT 0x8000 // master adc enable\r
+#define ADC_CONTROL1_REG 0x12 // adc control register 1\r
+#define ADC_CONTROL1_DUMMY_BITS 0x1 // dummy bits for adc control register 1\r
+#define SW_NOGATE_BIT 0x40 // disables software gate of adc\r
+#define ADC_MODE_BITS(x) (((x) & 0xf) << 12)\r
+#define ADC_SAMPLE_INTERVAL_LOWER_REG 0x16 // lower 16 bits of sample interval counter\r
+#define ADC_SAMPLE_INTERVAL_UPPER_REG 0x18 // upper 8 bits of sample interval counter\r
+#define ADC_DELAY_INTERVAL_LOWER_REG 0x1a // lower 16 bits of delay interval counter\r
+#define ADC_DELAY_INTERVAL_UPPER_REG 0x1c // upper 8 bits of delay interval counter\r
+#define ADC_COUNT_LOWER_REG 0x1e // lower 16 bits of hardware conversion/scan counter\r
+#define ADC_COUNT_UPPER_REG 0x20 // upper 8 bits of hardware conversion/scan counter\r
+#define ADC_START_REG 0x22 // software trigger to start aquisition\r
+#define ADC_CONVERT_REG 0x24 // initiates single conversion\r
+#define ADC_QUEUE_CLEAR_REG 0x26 // clears adc queue\r
+#define ADC_QUEUE_LOAD_REG 0x28 // loads adc queue\r
+#define CHAN_BITS(x) ((x) & 0x3f)\r
+#define GAIN_BITS(x) (((x) & 0x3) << 8) // translates range index to gain bits\r
+#define UNIP_BIT(x) (((x) & 0x4) << 11) // translates range index to unipolar/bipolar bit\r
+#define SE_BIT 0x1000 // single-ended/ differential bit\r
+#define QUEUE_EOSEQ_BIT 0x4000 // queue end of sequence\r
+#define QUEUE_EOSCAN_BIT 0x8000 // queue end of scan\r
+#define ADC_BUFFER_CLEAR_REG 0x2a\r
+#define ADC_QUEUE_HIGH_REG 0x2c // high channel for internal queue, use CHAN_BITS() macro above\r
+#define DAC_CONTROL0_REG 0x50 // dac control register 0\r
+#define DAC_ENABLE_BIT 0x8000 // dac controller enable bit\r
+#define DAC_CONTROL1_REG 0x52 // dac control register 0\r
+#define DAC_RANGE_BITS(channel, range) (((range) & 0x3) << (2 * ((channel) & 0x1)))\r
+#define DAC_OUTPUT_ENABLE_BIT 0x80 // dac output enable bit\r
+#define DAC_BUFFER_CLEAR_REG 0x66 // clear dac buffer\r
+#define DAC_CONVERT_REG(channel) ((0x70) + (2 * ((channel) & 0x1)))\r
+// read-only\r
+#define HW_STATUS_REG 0x0 // hardware status register, reading this apparently clears pending interrupts as well\r
+#define DAC_UNDERRUN_BIT 0x1\r
+#define ADC_OVERRUN_BIT 0x2\r
+#define DAC_ACTIVE_BIT 0x4\r
+#define ADC_ACTIVE_BIT 0x8\r
+#define DAC_INTR_PENDING_BIT 0x10\r
+#define ADC_INTR_PENDING_BIT 0x20\r
+#define DAC_DONE_BIT 0x40\r
+#define ADC_DONE_BIT 0x80\r
+#define EXT_INTR_PENDING_BIT 0x100\r
+#define ADC_STOP_BIT 0x200\r
+#define PIPE_FULL_BIT(x) (0x400 << ((x) & 0x1))\r
+#define HW_REVISION(x) (((x) >> 12) & 0xf)\r
+#define PIPE1_READ_REG 0x4\r
+// read-write\r
+#define ADC_QUEUE_FIFO_REG 0x100 // external channel/gain queue, uses same bits as ADC_QUEUE_LOAD_REG\r
+#define ADC_FIFO_REG 0x200 // adc data fifo\r
+\r
+// devpriv->dio_counter_iobase registers\r
+#define DIO_8255_OFFSET 0x0\r
+#define DO_REG 0x20\r
+#define DI_REG 0x28\r
+\r
+// analog input ranges for most boards\r
+static comedi_lrange ai_ranges =\r
+{\r
+ 8,\r
+ {\r
+ BIP_RANGE(10),\r
+ BIP_RANGE(5),\r
+ BIP_RANGE(2.5),\r
+ BIP_RANGE(1.25),\r
+ UNI_RANGE(10),\r
+ UNI_RANGE(5),\r
+ UNI_RANGE(2.5),\r
+ UNI_RANGE(1.25)\r
+ }\r
+};\r
+\r
+// analog output ranges\r
+static comedi_lrange ao_ranges =\r
+{\r
+ 4,\r
+ {\r
+ BIP_RANGE(5),\r
+ BIP_RANGE(10),\r
+ UNI_RANGE(5),\r
+ UNI_RANGE(10),\r
+ }\r
+};\r
+\r
+typedef struct pcidas64_board_struct\r
+{\r
+ char *name;\r
+ int device_id; // pci device id\r
+ int ai_se_chans; // number of ai inputs in single-ended mode\r
+ int ai_bits; // analog input resolution\r
+ int ai_speed; // fastest conversion period in ns\r
+ int ao_nchan; // number of analog out channels\r
+ int ao_scan_speed; // analog output speed (for a scan, not conversion)\r
+} pcidas64_board;\r
+\r
+static pcidas64_board pcidas64_boards[] =\r
+{\r
+ {\r
+ name: "pci-das6402/16",\r
+ device_id: 0x1d,\r
+ ai_se_chans: 64,\r
+ ai_bits: 16,\r
+ ai_speed: 5000,\r
+ ao_nchan: 2,\r
+ ao_scan_speed: 10000,\r
+ },\r
+ {\r
+ name: "pci-das6402/12", // XXX check\r
+ device_id: 0x1e,\r
+ ai_se_chans: 64,\r
+ ai_bits: 12,\r
+ ai_speed: 5000,\r
+ ao_nchan: 2,\r
+ ao_scan_speed: 10000,\r
+ },\r
+ {\r
+ name: "pci-das64/m1/16",\r
+ device_id: 0x35,\r
+ ai_se_chans: 64,\r
+ ai_bits: 16,\r
+ ai_speed: 1000,\r
+ ao_nchan: 2,\r
+ ao_scan_speed: 10000,\r
+ },\r
+ {\r
+ name: "pci-das64/m2/16",\r
+ device_id: 0x36,\r
+ ai_se_chans: 64,\r
+ ai_bits: 16,\r
+ ai_speed: 500,\r
+ ao_nchan: 2,\r
+ ao_scan_speed: 10000,\r
+ },\r
+ {\r
+ name: "pci-das64/m3/16",\r
+ device_id: 0x37,\r
+ ai_se_chans: 64,\r
+ ai_bits: 16,\r
+ ai_speed: 333,\r
+ ao_nchan: 2,\r
+ ao_scan_speed: 10000,\r
+ },\r
+\r
+#if 0\r
+ {\r
+ name: "pci-das6402/16/jr",\r
+ device_id: 0 // XXX,\r
+ ai_se_chans: 64,\r
+ ai_bits: 16,\r
+ ai_speed: 5000,\r
+ ao_nchan: 0,\r
+ ao_scan_speed: 10000,\r
+ },\r
+ {\r
+ name: "pci-das64/m1/16/jr",\r
+ device_id: 0 // XXX,\r
+ ai_se_chans: 64,\r
+ ai_bits: 16,\r
+ ai_speed: 1000,\r
+ ao_nchan: 0,\r
+ ao_scan_speed: 10000,\r
+ },\r
+ {\r
+ name: "pci-das64/m2/16/jr",\r
+ device_id: 0 // XXX,\r
+ ai_se_chans: 64,\r
+ ai_bits: 16,\r
+ ai_speed: 500,\r
+ ao_nchan: 0,\r
+ ao_scan_speed: 10000,\r
+ },\r
+ {\r
+ name: "pci-das64/m3/16/jr",\r
+ device_id: 0 // XXX,\r
+ ai_se_chans: 64,\r
+ ai_bits: 16,\r
+ ai_speed: 333,\r
+ ao_nchan: 0,\r
+ ao_scan_speed: 10000,\r
+ },\r
+ {\r
+ name: "pci-das64/m1/14",\r
+ device_id: 0, // XXX\r
+ ai_se_chans: 64,\r
+ ai_bits: 14,\r
+ ai_speed: 1000,\r
+ ao_nchan: 2,\r
+ ao_scan_speed: 10000,\r
+ },\r
+ {\r
+ name: "pci-das64/m2/14",\r
+ device_id: 0, // XXX\r
+ ai_se_chans: 64,\r
+ ai_bits: 14,\r
+ ai_speed: 500,\r
+ ao_nchan: 2,\r
+ ao_scan_speed: 10000,\r
+ },\r
+ {\r
+ name: "pci-das64/m3/14",\r
+ device_id: 0, // XXX\r
+ ai_se_chans: 64,\r
+ ai_bits: 14,\r
+ ai_speed: 333,\r
+ ao_nchan: 2,\r
+ ao_scan_speed: 10000,\r
+ },\r
+#endif\r
+\r
+};\r
+// Number of boards in cb_pcidas_boards\r
+#define N_BOARDS (sizeof(pcidas64_boards) / sizeof(pcidas64_board))\r
+\r
+static struct pci_device_id pcidas64_pci_table[] __devinitdata = {\r
+ { PCI_VENDOR_ID_CB, 0x001d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },\r
+ { PCI_VENDOR_ID_CB, 0x001e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },\r
+ { PCI_VENDOR_ID_CB, 0x0035, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },\r
+ { PCI_VENDOR_ID_CB, 0x0036, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },\r
+ { PCI_VENDOR_ID_CB, 0x0037, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },\r
+ { 0 }\r
+};\r
+MODULE_DEVICE_TABLE(pci, pcidas64_pci_table);\r
+\r
+/*\r
+ * Useful for shorthand access to the particular board structure\r
+ */\r
+#define thisboard ((pcidas64_board *)dev->board_ptr)\r
+\r
+/* this structure is for data unique to this hardware driver. If\r
+ several hardware drivers keep similar information in this structure,\r
+ feel free to suggest moving the variable to the comedi_device struct. */\r
+typedef struct\r
+{\r
+ // base addresses (physical)\r
+ unsigned long plx9080_phys_iobase;\r
+ unsigned long main_phys_iobase;\r
+ unsigned long dio_counter_phys_iobase;\r
+ // base addresses (ioremapped)\r
+ unsigned long plx9080_iobase;\r
+ unsigned long main_iobase;\r
+ unsigned long dio_counter_iobase;\r
+ // divisor of master clock for analog input pacing\r
+ unsigned int ai_divisor;\r
+ volatile unsigned int ai_count; // number of analog input samples remaining\r
+ // divisors of master clock for analog output pacing\r
+ unsigned int ao_divisor;\r
+ volatile unsigned int ao_count; // number of analog output samples remaining\r
+ unsigned int ao_value[2]; // remember what the analog outputs are set to, to allow readback\r
+ unsigned int hw_revision; // stc chip hardware revision number\r
+ unsigned int do_bits; // remember digital ouput levels\r
+} pcidas64_private;\r
+\r
+/*\r
+ * most drivers define the following macro to make it easy to\r
+ * access the private structure.\r
+ */\r
+#define devpriv ((pcidas64_private *)dev->private)\r
+\r
+/*\r
+ * The comedi_driver structure tells the Comedi core module\r
+ * which functions to call to configure/deconfigure (attach/detach)\r
+ * the board, and also about the kernel module that contains\r
+ * the device code.\r
+ */\r
+static int attach(comedi_device *dev,comedi_devconfig *it);\r
+static int detach(comedi_device *dev);\r
+comedi_driver driver_cb_pcidas={\r
+ driver_name: "cb_pcidas64",\r
+ module: THIS_MODULE,\r
+ attach: attach,\r
+ detach: detach,\r
+};\r
+\r
+static int ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);\r
+static int ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);\r
+static int ao_readback_insn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);\r
+static int ai_cmd(comedi_device *dev,comedi_subdevice *s);\r
+static int ai_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd);\r
+//static int ao_cmd(comedi_device *dev,comedi_subdevice *s);\r
+//static int ao_inttrig(comedi_device *dev, comedi_subdevice *subdev, unsigned int trig_num);\r
+//static int ao_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd);\r
+static void handle_interrupt(int irq, void *d, struct pt_regs *regs);\r
+static int ai_cancel(comedi_device *dev, comedi_subdevice *s);\r
+//static int ao_cancel(comedi_device *dev, comedi_subdevice *s);\r
+static int dio_callback(int dir, int port, int data, void *arg);\r
+static int di_rbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);\r
+static int do_wbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);\r
+static void check_adc_timing(comedi_cmd *cmd);\r
+static unsigned int get_divisor(unsigned int ns, unsigned int flags);\r
+\r
+/*\r
+ * A convenient macro that defines init_module() and cleanup_module(),\r
+ * as necessary.\r
+ */\r
+COMEDI_INITCLEANUP(driver_cb_pcidas);\r
+\r
+/*\r
+ * Attach is called by the Comedi core to configure the driver\r
+ * for a particular board.\r
+ */\r
+static int attach(comedi_device *dev, comedi_devconfig *it)\r
+{\r
+ comedi_subdevice *s;\r
+ struct pci_dev* pcidev;\r
+ int index;\r
+ // base addresses\r
+ unsigned long plx9080_iobase;\r
+ unsigned long main_iobase;\r
+ unsigned long dio_counter_iobase;\r
+\r
+ printk("comedi%d: cb_pcidas64\n",dev->minor);\r
+\r
+/*\r
+ * Allocate the private structure area.\r
+ */\r
+ if(alloc_private(dev,sizeof(pcidas64_private)) < 0)\r
+ return -ENOMEM;\r
+\r
+/*\r
+ * Probe the device to determine what device in the series it is.\r
+ */\r
+\r
+ pci_for_each_dev(pcidev)\r
+ {\r
+ // is it not a computer boards card?\r
+ if(pcidev->vendor != PCI_VENDOR_ID_CB)\r
+ continue;\r
+#ifdef PCIDAS64_DEBUG\r
+ printk(" found computer boards device id 0x%x on bus %i slot %i\n",\r
+ pcidev->device, pcidev->bus->number, PCI_SLOT(pcidev->devfn));\r
+#endif\r
+ // loop through cards supported by this driver\r
+ for(index = 0; index < N_BOARDS; index++)\r
+ {\r
+ if(pcidas64_boards[index].device_id != pcidev->device)\r
+ continue;\r
+ // was a particular bus/slot requested?\r
+ if(it->options[0] || it->options[1])\r
+ {\r
+ // are we on the wrong bus/slot?\r
+ if(pcidev->bus->number != it->options[0] ||\r
+ PCI_SLOT(pcidev->devfn) != it->options[1])\r
+ {\r
+ continue;\r
+ }\r
+ }\r
+ dev->board_ptr = pcidas64_boards + index;\r
+ goto found;\r
+ }\r
+ }\r
+\r
+ printk("No supported ComputerBoards/MeasurementComputing card found\n");\r
+ return -EIO;\r
+\r
+found:\r
+\r
+ printk("Found %s on bus %i, slot %i\n", pcidas64_boards[index].name,\r
+ pcidev->bus->number, PCI_SLOT(pcidev->devfn));\r
+\r
+ //Initialize dev->board_name\r
+ dev->board_name = thisboard->name;\r
+\r
+ /* Initialize devpriv->control_status and devpriv->adc_fifo to point to\r
+ * their base address.\r
+ */\r
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0)\r
+ plx9080_iobase =\r
+ pcidev->base_address[PLX9080_BADRINDEX] &\r
+ PCI_BASE_ADDRESS_MEM_MASK;\r
+ main_iobase =\r
+ pcidev->base_address[MAIN_BADRINDEX] &\r
+ PCI_BASE_ADDRESS_MEM_MASK;\r
+ dio_counter_iobase =\r
+ pcidev->base_address[DIO_COUNTER_BADRINDEX] &\r
+ PCI_BASE_ADDRESS_MEM_MASK;\r
+#else\r
+ if(pci_enable_device(pcidev))\r
+ return -EIO;\r
+ pci_set_master(pcidev);\r
+ plx9080_iobase =\r
+ pcidev->resource[PLX9080_BADRINDEX].start &\r
+ PCI_BASE_ADDRESS_MEM_MASK;\r
+ main_iobase =\r
+ pcidev->resource[MAIN_BADRINDEX].start &\r
+ PCI_BASE_ADDRESS_MEM_MASK;\r
+ dio_counter_iobase =\r
+ pcidev->resource[DIO_COUNTER_BADRINDEX].start &\r
+ PCI_BASE_ADDRESS_MEM_MASK;\r
+\r
+ if(check_mem_region(plx9080_iobase, PLX9080_IOSIZE))\r
+ {\r
+ /* Couldn't allocate io space */\r
+ printk(KERN_WARNING "couldn't allocate IO space\n");\r
+ return -EIO;\r
+ }\r
+ if(check_mem_region(main_iobase, MAIN_IOSIZE))\r
+ {\r
+ /* Couldn't allocate io space */\r
+ printk(KERN_WARNING "couldn't allocate IO space\n");\r
+ return -EIO;\r
+ }\r
+ if(check_mem_region(dio_counter_iobase, DIO_COUNTER_IOSIZE))\r
+ {\r
+ /* Couldn't allocate io space */\r
+ printk(KERN_WARNING "couldn't allocate IO space\n");\r
+ return -EIO;\r
+ }\r
+\r
+ request_mem_region(plx_iobase, PLX9080_IOSIZE, "cb_pcidas64");\r
+ devpriv->plx9080_phys_iobase = dio_counter_iobase;\r
+ request_mem_region(main_iobase, MAIN_IOSIZE, "cb_pcidas64");\r
+ devpriv->main_phys_iobase = dio_counter_iobase;\r
+ request_mem_region(dio_counter_iobase, DIO_COUNTER_IOSIZE, "cb_pcidas64");\r
+ devpriv->dio_counter_phys_iobase = dio_counter_iobase;\r
+\r
+#endif\r
+\r
+ // remap, won't work with 2.0 kernels but who cares\r
+ devpriv->plx9080_iobase = (unsigned long)ioremap(plx9080_iobase, PLX9080_IOSIZE);\r
+ devpriv->main_iobase = (unsigned long)ioremap(main_iobase, MAIN_IOSIZE);\r
+ devpriv->dio_counter_iobase = (unsigned long)ioremap(dio_counter_iobase, DIO_COUNTER_IOSIZE);\r
+\r
+ devpriv->hw_revision = HW_REVISION(readw(devpriv->main_iobase + HW_STATUS_REG));\r
+\r
+ // get irq\r
+ if(comedi_request_irq(pcidev->irq, handle_interrupt, SA_SHIRQ, "cb_pcidas64", dev ))\r
+ {\r
+ printk(" unable to allocate irq %d\n", pcidev->irq);\r
+ return -EINVAL;\r
+ }\r
+ dev->irq = pcidev->irq;\r
+\r
+#ifdef PCIDAS64_DEBUG\r
+\r
+printk(" plx9080 phys io addr 0x%lx\n", devpriv->plx9080_phys_iobase);\r
+printk(" main phys io addr 0x%lx\n", devpriv->main_phys_iobase);\r
+printk(" diocounter phys io addr 0x%lx\n", devpriv->dio_counter_phys_iobase);\r
+printk(" irq %i\n", dev->irq);\r
+\r
+printk(" plx9080 virt io addr 0x%lx\n", devpriv->plx9080_iobase);\r
+printk(" main virt io addr 0x%lx\n", devpriv->main_iobase);\r
+printk(" diocounter virt io addr 0x%lx\n", devpriv->dio_counter_iobase);\r
+printk(" irq %i\n", dev->irq);\r
+\r
+printk(" stc hardware revision %i\n", devpriv->hw_revision);\r
+\r
+// plx9080 dump\r
+printk(" plx interrupt status 0x%x\n", readl(devpriv->plx9080_iobase + PLX_INTRCS_REG));\r
+printk(" plx id bits 0x%x\n", readl(devpriv->plx9080_iobase + PLX_ID_REG));\r
+printk(" plx hardware revision 0x%x\n", readl(devpriv->plx9080_iobase + PLX_REVISION_REG));\r
+printk(" plx dma channel 0 mode 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_MODE_REG));\r
+printk(" plx dma channel 0 pci address 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG));\r
+printk(" plx dma channel 0 local address 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_LOCAL_ADDRESS_REG));\r
+printk(" plx dma channel 0 transfer size 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_TRANSFER_SIZE_REG));\r
+printk(" plx dma channel 0 descriptor 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG));\r
+printk(" plx dma channel 0 command status 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_CS_REG));\r
+printk(" plx dma channel 0 threshold 0x%x\n", readl(devpriv->plx9080_iobase + PLX_DMA0_THRESHOLD_REG));\r
+\r
+#endif\r
+\r
+\r
+/*\r
+ * Allocate the subdevice structures.\r
+ */\r
+ dev->n_subdevices = 7;\r
+ if(alloc_subdevices(dev)<0)\r
+ return -ENOMEM;\r
+\r
+ s = dev->subdevices + 0;\r
+ /* analog input subdevice */\r
+ dev->read_subdev = s;\r
+ s->type = COMEDI_SUBD_AI;\r
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF;\r
+ /* XXX Number of inputs in differential mode is ignored */\r
+ s->n_chan = thisboard->ai_se_chans;\r
+ s->len_chanlist = 8092;\r
+ s->maxdata = (1 << thisboard->ai_bits) - 1;\r
+ s->range_table = &ai_ranges;\r
+ s->insn_read = ai_rinsn;\r
+ s->do_cmd = ai_cmd;\r
+ s->do_cmdtest = ai_cmdtest;\r
+ s->cancel = ai_cancel;\r
+\r
+ /* analog output subdevice */\r
+ s = dev->subdevices + 1;\r
+ if(thisboard->ao_nchan)\r
+ {\r
+ // dev->write_subdev = s;\r
+ s->type = COMEDI_SUBD_AO;\r
+ s->subdev_flags = SDF_READABLE | SDF_WRITEABLE | SDF_GROUND;\r
+ s->n_chan = thisboard->ao_nchan;\r
+ // analog out resolution is the same as analog input resolution, so use ai_bits\r
+ s->maxdata = (1 << thisboard->ai_bits) - 1;\r
+ s->range_table = &ao_ranges;\r
+ s->insn_read = ao_readback_insn;\r
+ s->insn_write = ao_winsn;\r
+ // s->do_cmdtest = ao_cmdtest;\r
+ // s->do_cmd = ao_cmd;\r
+ // s->len_chanlist = thisboard->ao_nchan;\r
+ // s->cancel = ao_cancel;\r
+ } else\r
+ {\r
+ s->type = COMEDI_SUBD_UNUSED;\r
+ }\r
+\r
+ // digital input\r
+ s = dev->subdevices + 2;\r
+ s->type=COMEDI_SUBD_DI;\r
+ s->subdev_flags = SDF_READABLE;\r
+ s->n_chan = 4;\r
+ s->maxdata = 1;\r
+ s->range_table = &range_digital;\r
+ s->insn_bits = di_rbits;\r
+\r
+ // digital output\r
+ s = dev->subdevices + 3;\r
+ s->type=COMEDI_SUBD_DO;\r
+ s->subdev_flags = SDF_WRITEABLE | SDF_READABLE;\r
+ s->n_chan = 4;\r
+ s->maxdata = 1;\r
+ s->range_table = &range_digital;\r
+ s->insn_bits = do_wbits;\r
+\r
+ /* 8255 */\r
+ s = dev->subdevices + 4;\r
+ subdev_8255_init(dev, s, dio_callback,\r
+ (void*) (devpriv->dio_counter_iobase + DIO_8255_OFFSET));\r
+\r
+ // user counter subd XXX\r
+ s = dev->subdevices + 5;\r
+ s->type = COMEDI_SUBD_UNUSED;\r
+\r
+ // calibration subd XXX\r
+ s = dev->subdevices + 6;\r
+ s->type = COMEDI_SUBD_UNUSED; \r
+\r
+ return 0;\r
+}\r
+\r
+\r
+/*\r
+ * _detach is called to deconfigure a device. It should deallocate\r
+ * resources.\r
+ * This function is also called when _attach() fails, so it should be\r
+ * careful not to release resources that were not necessarily\r
+ * allocated by _attach(). dev->private and dev->subdevices are\r
+ * deallocated automatically by the core.\r
+ */\r
+static int detach(comedi_device *dev)\r
+{\r
+ printk("comedi%d: cb_pcidas: remove\n",dev->minor);\r
+\r
+ if(devpriv)\r
+ {\r
+ if(devpriv->plx9080_iobase)\r
+ iounmap((void*)devpriv->plx9080_iobase);\r
+ if(devpriv->main_iobase)\r
+ iounmap((void*)devpriv->main_iobase);\r
+ if(devpriv->dio_counter_iobase)\r
+ iounmap((void*)devpriv->dio_counter_iobase);\r
+ if(devpriv->plx9080_phys_iobase)\r
+ release_mem_region(devpriv->plx9080_iobase, PLX9080_IOSIZE);\r
+ if(devpriv->main_iobase)\r
+ release_mem_region(devpriv->main_phys_iobase, MAIN_IOSIZE);\r
+ if(devpriv->dio_counter_iobase)\r
+ release_mem_region(devpriv->dio_counter_phys_iobase, DIO_COUNTER_IOSIZE);\r
+ }\r
+ if(dev->irq)\r
+ comedi_free_irq(dev->irq, dev);\r
+ if(dev->subdevices)\r
+ subdev_8255_cleanup(dev,dev->subdevices + 4);\r
+\r
+ return 0;\r
+}\r
+\r
+static int ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)\r
+{\r
+ unsigned int bits, n, i;\r
+ const int timeout = 1000;\r
+\r
+ // disable card's interrupt sources\r
+ writew(0, devpriv->main_iobase + INTR_ENABLE_REG);\r
+\r
+ /* disable pacing, triggering, etc */\r
+ writew(ADC_ENABLE_BIT, devpriv->main_iobase + ADC_CONTROL0_REG);\r
+ writew(ADC_CONTROL1_DUMMY_BITS, devpriv->main_iobase + ADC_CONTROL1_REG);\r
+\r
+ // use internal queue\r
+ writew(HW_CONFIG_DUMMY_BITS, devpriv->main_iobase + HW_CONFIG_REG);\r
+\r
+ // load internal queue\r
+ bits = 0;\r
+ // set channel\r
+ bits |= CHAN_BITS(CR_CHAN(insn->chanspec));\r
+ // set gain\r
+ bits |= GAIN_BITS(CR_RANGE(insn->chanspec));\r
+ // set unipolar / bipolar\r
+ bits |= UNIP_BIT(CR_RANGE(insn->chanspec));\r
+ // set single-ended / differential\r
+ if(CR_AREF(insn->chanspec) != AREF_DIFF)\r
+ bits |= SE_BIT;\r
+ // set stop channel\r
+ writew(CHAN_BITS(CR_CHAN(insn->chanspec)), devpriv->main_iobase + ADC_QUEUE_HIGH_REG);\r
+ // set start channel, and rest of settings\r
+ writew(bits, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);\r
+\r
+ // clear adc buffer\r
+ writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);\r
+\r
+ for(n = 0; n < insn->n; n++)\r
+ {\r
+ /* trigger conversion */\r
+ writew(0, devpriv->main_iobase + ADC_CONVERT_REG);\r
+\r
+ // wait for data\r
+ for(i = 0; i < timeout; i++)\r
+ {\r
+ if(!(readw(devpriv->main_iobase + HW_STATUS_REG) & ADC_ACTIVE_BIT))\r
+ break;\r
+ }\r
+ if(i == timeout)\r
+ {\r
+ comedi_error(dev, " analog input read insn timed out");\r
+ return -ETIME;\r
+ }\r
+ data[n] = readw(devpriv->main_iobase + PIPE1_READ_REG);\r
+ }\r
+\r
+ return n;\r
+}\r
+\r
+static int ai_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd)\r
+{ int err = 0;\r
+ int tmp;\r
+ unsigned int tmp_arg, tmp_arg2;\r
+ int i;\r
+ int aref;\r
+\r
+ /* step 1: make sure trigger sources are trivially valid */\r
+\r
+ tmp = cmd->start_src;\r
+ cmd->start_src &= TRIG_NOW | TRIG_EXT;\r
+ if(!cmd->start_src || tmp != cmd->start_src) err++;\r
+\r
+ tmp = cmd->scan_begin_src;\r
+ cmd->scan_begin_src &= TRIG_TIMER | TRIG_FOLLOW;\r
+ if(!cmd->scan_begin_src || tmp != cmd->scan_begin_src) err++;\r
+\r
+ tmp = cmd->convert_src;\r
+ cmd->convert_src &= TRIG_TIMER | TRIG_EXT;\r
+ if(!cmd->convert_src || tmp != cmd->convert_src) err++;\r
+\r
+ tmp = cmd->scan_end_src;\r
+ cmd->scan_end_src &= TRIG_COUNT;\r
+ if(!cmd->scan_end_src || tmp != cmd->scan_end_src) err++;\r
+\r
+ tmp=cmd->stop_src;\r
+ cmd->stop_src &= TRIG_COUNT | TRIG_EXT | TRIG_NONE;\r
+ if(!cmd->stop_src || tmp != cmd->stop_src) err++;\r
+\r
+ if(err) return 1;\r
+\r
+ /* step 2: make sure trigger sources are unique and mutually compatible */\r
+\r
+ // uniqueness check\r
+ if(cmd->start_src != TRIG_NOW &&\r
+ cmd->start_src != TRIG_EXT) err++;\r
+ if(cmd->scan_begin_src != TRIG_TIMER &&\r
+ cmd->scan_begin_src != TRIG_FOLLOW) err++;\r
+ if(cmd->convert_src != TRIG_TIMER &&\r
+ cmd->convert_src != TRIG_EXT) err++;\r
+ if(cmd->stop_src != TRIG_COUNT &&\r
+ cmd->stop_src != TRIG_NONE &&\r
+ cmd->stop_src != TRIG_EXT) err++;\r
+\r
+ // compatibility check\r
+ if(cmd->convert_src == TRIG_EXT &&\r
+ cmd->scan_begin_src == TRIG_TIMER)\r\r
err++;\r
-
- if(err) return 2;
-
- /* step 3: make sure arguments are trivially compatible */
-
- if(cmd->start_arg != 0)
- {
- cmd->start_arg = 0;
- err++;
- }
- if(cmd->convert_src == TRIG_TIMER)
- {
- if(cmd->convert_arg < thisboard->ai_speed)
- {
- cmd->convert_arg = thisboard->ai_speed;
- err++;
- }
- if(cmd->scan_begin_src == TRIG_TIMER)\r
- {
- // if scans are timed faster than conversion rate allows\r
- if(cmd->convert_arg * cmd->chanlist_len > cmd->scan_begin_arg)
+\r
+ if(err) return 2;\r
+\r
+ /* step 3: make sure arguments are trivially compatible */\r
+\r
+ if(cmd->start_arg != 0)\r
+ {\r
+ cmd->start_arg = 0;\r
+ err++;\r
+ }\r
+ if(cmd->convert_src == TRIG_TIMER)\r
+ {\r
+ if(cmd->convert_arg < thisboard->ai_speed)\r
+ {\r
+ cmd->convert_arg = thisboard->ai_speed;\r
+ err++;\r
+ }\r
+ if(cmd->scan_begin_src == TRIG_TIMER)\r\r
+ {\r
+ // if scans are timed faster than conversion rate allows\r\r
+ if(cmd->convert_arg * cmd->chanlist_len > cmd->scan_begin_arg)\r
{\r
cmd->scan_begin_arg = cmd->convert_arg * cmd->chanlist_len;\r
err++;\r
}\r
}\r
- }
-
- if(!cmd->chanlist_len)
- {
- cmd->chanlist_len = 1;
- err++;
- }
- if(cmd->scan_end_arg != cmd->chanlist_len)
- {
- cmd->scan_end_arg = cmd->chanlist_len;
- err++;
- }
-
- switch(cmd->stop_src)
- {
- case TRIG_EXT:
- if(cmd->stop_arg)
- {
- cmd->stop_arg = 0;
- err++;
- }
- break;
- case TRIG_COUNT:
- if(!cmd->stop_arg)
- {
- cmd->stop_arg = 1;
- err++;
- }
- break;
- case TRIG_NONE:
- if(cmd->stop_arg != 0)
- {
- cmd->stop_arg = 0;
- err++;
- }
- break;
- default:
- break;
- }
-
- if(err) return 3;
-
- /* step 4: fix up any arguments */
-
- if(cmd->convert_src == TRIG_TIMER)
- {
- tmp_arg = cmd->convert_arg;
- tmp_arg2 = cmd->scan_begin_arg;
- check_adc_timing(cmd);
- if(tmp_arg != cmd->convert_arg) err++;
- if(tmp_arg2 != cmd->scan_begin_arg) err++;
- }
-
- if(err) return 4;
-
- // make sure user is doesn't change analog reference mid chanlist
- if(cmd->chanlist)
- {
- aref = CR_AREF(cmd->chanlist[0]);
- for(i = 1; i < cmd->chanlist_len; i++)
- {
- if(aref != CR_AREF(cmd->chanlist[i]))
- {
- comedi_error(dev, "all elements in chanlist must use the same analog reference");
- err++;
- break;
- }
- }
- }
-
- if(err) return 5;
-
- return 0;
-}
-
-static int ai_cmd(comedi_device *dev,comedi_subdevice *s)
-{
- comedi_async *async = s->async;
- comedi_cmd *cmd = &async->cmd;
- unsigned int bits;
- unsigned int convert_counter_value;
- unsigned int scan_counter_value;
- unsigned int i;
-
- // disable card's interrupt sources
- writew(0, devpriv->main_iobase + INTR_ENABLE_REG);
-
- /* disable pacing, triggering, etc */
- writew(0, devpriv->main_iobase + ADC_CONTROL0_REG);
- writew(0, devpriv->main_iobase + ADC_CONTROL1_REG);
-
- // use external queue
- writew(EXT_QUEUE | HW_CONFIG_DUMMY_BITS, devpriv->main_iobase + HW_CONFIG_REG);
-
- // set fifo size
- writew(ADC_FIFO_8K_BITS | FIFO_SIZE_DUMMY_BITS, devpriv->main_iobase + FIFO_SIZE_REG);
-
- // set conversion pacing
- if(cmd->convert_src == TRIG_TIMER)
- {
- check_adc_timing(cmd);
- // supposed to load counter with desired divisor minus 3
- convert_counter_value = cmd->convert_arg / TIMER_BASE - 3;
- // load lower 16 bits
- writew(convert_counter_value & 0xffff, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG);
- // load upper 8 bits
- writew((convert_counter_value >> 16) & 0xff, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
- // set scan pacing
- if(cmd->convert_src == TRIG_TIMER)
- {
- // figure out how long we need to delay at end of scan
- scan_counter_value = (cmd->scan_begin_arg - (cmd->convert_arg * cmd->chanlist_len))
- / TIMER_BASE;
- // load lower 16 bits
- writew(scan_counter_value & 0xffff, devpriv->main_iobase + ADC_DELAY_INTERVAL_LOWER_REG);
- // load upper 8 bits
- writew((scan_counter_value >> 16) & 0xff, devpriv->main_iobase + ADC_DELAY_INTERVAL_UPPER_REG);
- }
- }
-
- // load hardware conversion counter with non-zero value so it doesn't mess with us
- writew(~0, devpriv->main_iobase + ADC_COUNT_LOWER_REG);
-
- /* XXX cannot write to queue fifo while dac fifo is being written to
- * ( need spinlock, or try to use internal queue instead */
- // clear queue pointer
- writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG);
- // load external queue
- for(i = 0; i < cmd->chanlist_len; i++)
- {
- bits = 0;
- // set channel
- bits |= CHAN_BITS(CR_CHAN(cmd->chanlist[i]));
- // set gain
- bits |= GAIN_BITS(CR_RANGE(cmd->chanlist[i]));
- // set unipolar / bipolar
- bits |= UNIP_BIT(CR_RANGE(cmd->chanlist[i]));
- // set single-ended / differential
- if(CR_AREF(cmd->chanlist[i]) != AREF_DIFF)
- bits |= SE_BIT;
- // mark end of queue
- if(i == cmd->chanlist_len - 1)
- bits |= QUEUE_EOSCAN_BIT | QUEUE_EOSEQ_BIT;
- writew(bits, devpriv->main_iobase + ADC_QUEUE_FIFO_REG);
- }
- // prime queue holding register
- writew(0, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
-
- // clear adc buffer
- writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);
-
- // enable interrupts
- bits = EN_ADC_OVERRUN_BIT | EN_ADC_DONE_INTR_BIT;
- if(cmd->flags & TRIG_WAKE_EOS)
- bits |= ADC_INTR_EOSCAN_BITS;
- writew(bits, devpriv->main_iobase + INTR_ENABLE_REG);
-
- /* set mode, disable software conversion gate */
- bits = SW_NOGATE_BIT;
- if(cmd->convert_src == TRIG_EXT)
- bits |= ADC_MODE_BITS(13); // good old mode 13
- else
- bits |= ADC_MODE_BITS(8); // mode 8. What else could you need?
- writew(bits, devpriv->main_iobase + ADC_CONTROL1_REG);
-
- /* enable pacing, triggering, etc */
- bits = ADC_ENABLE_BIT;
- writew(bits, devpriv->main_iobase + ADC_CONTROL0_REG);
-
- // start aquisition
- writew(0, devpriv->main_iobase + ADC_START_REG);
-
- return 0;
-}
-
-static void handle_interrupt(int irq, void *d, struct pt_regs *regs)
-{
- return;
-}
-
-static int ai_cancel(comedi_device *dev, comedi_subdevice *s)
-{
- return 0;
-}
-
-static int ao_winsn(comedi_device *dev, comedi_subdevice *s,
- comedi_insn *insn, lsampl_t *data)
-{
- int chan = CR_CHAN(insn->chanspec);
- int range = CR_RANGE(insn->chanspec);
- unsigned int bits;
-
- // do some initializing
- writew(DAC_ENABLE_BIT, devpriv->main_iobase + DAC_CONTROL0_REG);
-
- // set range
- bits = DAC_OUTPUT_ENABLE_BIT;
- bits |= DAC_RANGE_BITS(chan, range);
- writew(bits, devpriv->main_iobase + DAC_CONTROL1_REG);
-
- // clear buffer
- writew(0, devpriv->main_iobase + DAC_BUFFER_CLEAR_REG);
-
- // write to channel
- writew(data[0], devpriv->main_iobase + DAC_CONVERT_REG(chan));
-
- // remember output value
- devpriv->ao_value[chan] = data[0];
-
- return 1;
-}
-
-static int ao_readback_insn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
-{
- data[0] = devpriv->ao_value[CR_CHAN(insn->chanspec)];
-
- return 1;
-}
-
-static int dio_callback(int dir, int port, int data, void *arg)
-{
- unsigned long iobase = (int)arg;
-
- if(dir)
- {
- writeb(data, iobase + port);
- return 0;
- }else
- {
- return readb(iobase + port);
- }
-}
-
-static int di_rbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)
-{
- lsampl_t bits;
-
- bits = readb(devpriv->dio_counter_iobase + DI_REG);
- bits &= 0xf;
- data[1] = bits;
- data[0] = 0;
-
- return 2;
-}
-
-static int do_wbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)
-{
- lsampl_t wbits;
-
- data[0] &= 0xf;
- wbits = devpriv->do_bits;
- // zero bits we are going to change
- wbits &= ~data[0];
- // set new bits
- wbits |= data[0] & data[1];
- devpriv->do_bits = wbits;
-
- writeb(devpriv->do_bits, devpriv->dio_counter_iobase + DO_REG);
-
- data[1] = wbits;
-
- return 2;
-}
-
-// utility function that rounds desired timing to an achievable time.
-// adc paces conversions from master clock by dividing by (x + 3) where x is 24 bit number
-static void check_adc_timing(comedi_cmd *cmd)
-{
- unsigned int convert_divisor, scan_divisor;
- const int min_convert_divisor = 3;
- const int max_convert_divisor = 0xffffff + min_convert_divisor;
- unsigned long long max_scan_divisor, min_scan_divisor;
-
- if(cmd->convert_src == TRIG_TIMER)
- {
- convert_divisor = get_divisor(cmd->convert_arg, cmd->flags);
- if(convert_divisor > max_convert_divisor) convert_divisor = max_convert_divisor;
- if(convert_divisor < min_convert_divisor) convert_divisor = min_convert_divisor;
- cmd->convert_arg = convert_divisor * TIMER_BASE;
-
- if(cmd->scan_begin_src == TRIG_TIMER)
- {
- scan_divisor = get_divisor(cmd->scan_begin_arg, cmd->flags);
- min_scan_divisor = convert_divisor * cmd->chanlist_len;
- max_scan_divisor = min_scan_divisor + 0xffffff;
- if(scan_divisor > max_scan_divisor) scan_divisor = max_scan_divisor;
- if(scan_divisor < min_scan_divisor) scan_divisor = min_scan_divisor;
- cmd->scan_begin_arg = scan_divisor * TIMER_BASE;
- }
- }
-
- return;
-}
-
-/* Gets nearest achievable timing given master clock speed, does not
- * take into account possible minimum/maximum divisor values. Used
- * by other timing checking functions. */
-static unsigned int get_divisor(unsigned int ns, unsigned int flags)
-{
- unsigned int divisor;
-
- switch(flags & TRIG_ROUND_MASK)
- {
- case TRIG_ROUND_UP:
- divisor = (ns + TIMER_BASE - 1) / TIMER_BASE;
- break;
- case TRIG_ROUND_DOWN:
- divisor = ns / TIMER_BASE;
- break;
- case TRIG_ROUND_NEAREST:
- default:
- divisor = (ns + TIMER_BASE / 2) / TIMER_BASE;
- break;
- }
-
- return divisor;
-}
+ }\r
+\r
+ if(!cmd->chanlist_len)\r
+ {\r
+ cmd->chanlist_len = 1;\r
+ err++;\r
+ }\r
+ if(cmd->scan_end_arg != cmd->chanlist_len)\r
+ {\r
+ cmd->scan_end_arg = cmd->chanlist_len;\r
+ err++;\r
+ }\r
+\r
+ switch(cmd->stop_src)\r
+ {\r
+ case TRIG_EXT:\r
+ if(cmd->stop_arg)\r
+ {\r
+ cmd->stop_arg = 0;\r
+ err++;\r
+ }\r
+ break;\r
+ case TRIG_COUNT:\r
+ if(!cmd->stop_arg)\r
+ {\r
+ cmd->stop_arg = 1;\r
+ err++;\r
+ }\r
+ break;\r
+ case TRIG_NONE:\r
+ if(cmd->stop_arg != 0)\r
+ {\r
+ cmd->stop_arg = 0;\r
+ err++;\r
+ }\r
+ break;\r
+ default:\r
+ break;\r
+ }\r
+\r
+ if(err) return 3;\r
+\r
+ /* step 4: fix up any arguments */\r
+\r
+ if(cmd->convert_src == TRIG_TIMER)\r
+ {\r
+ tmp_arg = cmd->convert_arg;\r
+ tmp_arg2 = cmd->scan_begin_arg;\r
+ check_adc_timing(cmd);\r
+ if(tmp_arg != cmd->convert_arg) err++;\r
+ if(tmp_arg2 != cmd->scan_begin_arg) err++;\r
+ }\r
+\r
+ if(err) return 4;\r
+\r
+ // make sure user is doesn't change analog reference mid chanlist\r
+ if(cmd->chanlist)\r
+ {\r
+ aref = CR_AREF(cmd->chanlist[0]);\r
+ for(i = 1; i < cmd->chanlist_len; i++)\r
+ {\r
+ if(aref != CR_AREF(cmd->chanlist[i]))\r
+ {\r
+ comedi_error(dev, "all elements in chanlist must use the same analog reference");\r
+ err++;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ if(err) return 5;\r
+\r
+ return 0;\r
+}\r
+\r
+static int ai_cmd(comedi_device *dev,comedi_subdevice *s)\r
+{\r
+ comedi_async *async = s->async;\r
+ comedi_cmd *cmd = &async->cmd;\r
+ unsigned int bits;\r
+ unsigned int convert_counter_value;\r
+ unsigned int scan_counter_value;\r
+ unsigned int i;\r
+\r
+ // disable card's interrupt sources\r
+ writew(0, devpriv->main_iobase + INTR_ENABLE_REG);\r
+\r
+ /* disable pacing, triggering, etc */\r
+ writew(0, devpriv->main_iobase + ADC_CONTROL0_REG);\r
+ writew(ADC_CONTROL1_DUMMY_BITS, devpriv->main_iobase + ADC_CONTROL1_REG);\r
+\r
+ // use external queue\r
+ writew(EXT_QUEUE | HW_CONFIG_DUMMY_BITS, devpriv->main_iobase + HW_CONFIG_REG);\r
+\r
+ // set fifo size\r
+ writew(ADC_FIFO_8K_BITS | FIFO_SIZE_DUMMY_BITS, devpriv->main_iobase + FIFO_SIZE_REG);\r
+\r
+ // set conversion pacing\r
+ if(cmd->convert_src == TRIG_TIMER)\r
+ {\r
+ check_adc_timing(cmd);\r
+ // supposed to load counter with desired divisor minus 3\r
+ convert_counter_value = cmd->convert_arg / TIMER_BASE - 3;\r
+ // load lower 16 bits\r
+ writew(convert_counter_value & 0xffff, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG);\r
+ // load upper 8 bits\r
+ writew((convert_counter_value >> 16) & 0xff, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);\r
+ // set scan pacing\r
+ if(cmd->convert_src == TRIG_TIMER)\r
+ {\r
+ // figure out how long we need to delay at end of scan\r
+ scan_counter_value = (cmd->scan_begin_arg - (cmd->convert_arg * (cmd->chanlist_len - 1)))\r
+ / TIMER_BASE;\r
+ // load lower 16 bits\r
+ writew(scan_counter_value & 0xffff, devpriv->main_iobase + ADC_DELAY_INTERVAL_LOWER_REG);\r
+ // load upper 8 bits\r
+ writew((scan_counter_value >> 16) & 0xff, devpriv->main_iobase + ADC_DELAY_INTERVAL_UPPER_REG);\r
+ }\r
+ }\r
+\r
+ // load hardware conversion counter with non-zero value so it doesn't mess with us\r
+ writew(~0, devpriv->main_iobase + ADC_COUNT_LOWER_REG);\r
+\r
+ /* XXX cannot write to queue fifo while dac fifo is being written to\r
+ * ( need spinlock, or try to use internal queue instead */\r
+ // clear queue pointer\r
+ writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG);\r
+ // load external queue\r
+ for(i = 0; i < cmd->chanlist_len; i++)\r
+ {\r
+ bits = 0;\r
+ // set channel\r
+ bits |= CHAN_BITS(CR_CHAN(cmd->chanlist[i]));\r
+ // set gain\r
+ bits |= GAIN_BITS(CR_RANGE(cmd->chanlist[i]));\r
+ // set unipolar / bipolar\r
+ bits |= UNIP_BIT(CR_RANGE(cmd->chanlist[i]));\r
+ // set single-ended / differential\r
+ if(CR_AREF(cmd->chanlist[i]) != AREF_DIFF)\r
+ bits |= SE_BIT;\r
+ // mark end of queue\r
+ if(i == cmd->chanlist_len - 1)\r
+ bits |= QUEUE_EOSCAN_BIT | QUEUE_EOSEQ_BIT;\r
+ writew(bits, devpriv->main_iobase + ADC_QUEUE_FIFO_REG);\r
+ }\r
+ // prime queue holding register\r
+ writew(0, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);\r
+\r
+ // clear adc buffer\r
+ writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);\r
+\r
+ // enable interrupts\r
+ bits = EN_ADC_OVERRUN_BIT | EN_ADC_DONE_INTR_BIT;\r
+ if(cmd->flags & TRIG_WAKE_EOS)\r
+ bits |= ADC_INTR_EOSCAN_BITS;\r
+ else\r
+ bits |= ADC_INTR_QFULL_BITS; // for clairity only, since quarter-full bits are zero\r
+ writew(bits, devpriv->main_iobase + INTR_ENABLE_REG);\r
+ // enable interrupts on plx 9080 XXX enabling more interrupt sources than are actually used\r
+ bits = ICS_PIE | ICS_PLIE | ICS_PAIE | ICS_PDIE | ICS_LIE | ICS_LDIE | ICS_DMA0_E | ICS_DMA1_E;\r
+ writel(bits, devpriv->plx9080_iobase + PLX_INTRCS_REG);\r
+\r
+ // disable dma for now XXX\r
+ writeb(0, devpriv->plx9080_iobase + PLX_DMA0_CS_REG);\r
+ writeb(0, devpriv->plx9080_iobase + PLX_DMA1_CS_REG);\r
+\r
+ /* set mode, disable software conversion gate */\r
+ bits = ADC_CONTROL1_DUMMY_BITS | SW_NOGATE_BIT;\r
+ if(cmd->convert_src == TRIG_EXT)\r
+ bits |= ADC_MODE_BITS(13); // good old mode 13\r
+ else\r
+ bits |= ADC_MODE_BITS(8); // mode 8. What else could you need?\r
+ writew(bits, devpriv->main_iobase + ADC_CONTROL1_REG);\r
+\r
+ /* enable pacing, triggering, etc */\r
+ bits = ADC_ENABLE_BIT;\r
+ writew(bits, devpriv->main_iobase + ADC_CONTROL0_REG);\r
+\r
+ // start aquisition\r
+ writew(0, devpriv->main_iobase + ADC_START_REG);\r
+\r
+ return 0;\r
+}\r
+\r
+static void handle_interrupt(int irq, void *d, struct pt_regs *regs)\r
+{\r
+#ifdef PCIDAS64_DEBUG\r
+#endif\r
+ comedi_device *dev = d;\r
+ comedi_subdevice *s = dev->read_subdev;\r
+ comedi_async *async = s->async;\r
+ comedi_cmd *cmd = &async->cmd;\r
+ unsigned int num_samples = 0;\r
+ unsigned int i;\r
+ u16 data;\r
+ unsigned int status;\r
+ u32 plx_status;\r
+ u32 plx_bits;\r
+#ifdef PCIDAS64_DEBUG\r
+ static unsigned int intr_count = 0;\r
+ const int debug_count = 10;\r
+#endif\r
+\r
+ status = readw(devpriv->main_iobase + HW_STATUS_REG);\r
+ plx_status = readl(devpriv->plx9080_iobase + PLX_INTRCS_REG);\r
+ if((status &\r
+ (ADC_INTR_PENDING_BIT | ADC_DONE_BIT | ADC_STOP_BIT |\r
+ DAC_INTR_PENDING_BIT | DAC_DONE_BIT | EXT_INTR_PENDING_BIT)) == 0 &&\r
+ (plx_status & (ICS_DMA0_A | ICS_DMA1_A | ICS_LDIA | ICS_LIA | ICS_PAIA | ICS_PDIA)) == 0)\r
+ {\r
+#ifdef PCIDAS64_DEBUG\r
+ rt_printk(" cb_pcidas64 spurious interrupt");\r
+#endif\r
+ return;\r
+ }\r
+#ifdef PCIDAS64_DEBUG\r
+ intr_count++;\r
+ if(intr_count < debug_count)\r
+ {\r
+ rt_printk(" cb_pcidas64 interrupt status 0x%x\n", status);\r
+ rt_printk(" plx status 0x%x\n", plx_status);\r
+ }\r
+#endif\r
+\r
+ async->events = 0;\r
+\r
+ // if interrupt was due to analog input data being available\r
+ if(status & ADC_INTR_PENDING_BIT)\r
+ {\r
+ // figure out how many samples we should read from board's fifo\r
+ /* XXX should use ADC read/write pointer registers to figure out\r
+ * how many samples are actually in fifo */\r
+ if(cmd->flags & TRIG_WAKE_EOS)\r
+ num_samples = cmd->chanlist_len;\r
+ else\r
+ num_samples = QUARTER_AI_FIFO_SIZE;\r
+ // read samples\r
+ for(i = 0; i < num_samples; i++)\r
+ {\r
+ data = readw(devpriv->main_iobase + ADC_FIFO_REG);\r
+ comedi_buf_put(async, data);\r
+ }\r
+ async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;\r
+ }\r
+\r
+ // clear possible plx9080 interrupt sources\r
+ if(plx_status & ICS_LDIA)\r
+ { // clear local doorbell interrupt\r
+ plx_bits = readl(devpriv->plx9080_iobase + PLX_DBR_OUT_REG);\r
+ writel(plx_bits, devpriv->plx9080_iobase + PLX_DBR_OUT_REG);\r
+#ifdef PCIDAS64_DEBUG\r
+ if(intr_count < debug_count)\r
+ rt_printk(" cleared local doorbell bits 0x%x\n", plx_bits);\r
+#endif\r
+ }\r
+ if(plx_status & ICS_DMA0_A)\r
+ { // dma chan 0 interrupt\r
+ writeb(PLX_CLEAR_DMA_INTR_BIT, devpriv->plx9080_iobase + PLX_DMA0_CS_REG);\r
+#ifdef PCIDAS64_DEBUG\r
+ if(intr_count < debug_count)\r
+ rt_printk(" cleared dma ch0 interrupt\n");\r
+#endif\r
+ }\r
+ if(plx_status & ICS_DMA1_A)\r
+ { // dma chan 1 interrupt\r
+ writeb(PLX_CLEAR_DMA_INTR_BIT, devpriv->plx9080_iobase + PLX_DMA1_CS_REG);\r
+#ifdef PCIDAS64_DEBUG\r
+ if(intr_count < debug_count)\r
+ rt_printk(" cleared dma ch1 interrupt\n");\r
+#endif\r
+ }\r
+\r
+ comedi_event(dev, s, async->events);\r
+ return;\r
+}\r
+\r
+static int ai_cancel(comedi_device *dev, comedi_subdevice *s)\r
+{\r
+ /* disable pacing, triggering, etc */\r
+ writew(0, devpriv->main_iobase + ADC_CONTROL0_REG);\r
+ writew(ADC_CONTROL1_DUMMY_BITS, devpriv->main_iobase + ADC_CONTROL1_REG);\r
+\r
+ return 0;\r
+}\r
+\r
+static int ao_winsn(comedi_device *dev, comedi_subdevice *s,\r
+ comedi_insn *insn, lsampl_t *data)\r
+{\r
+ int chan = CR_CHAN(insn->chanspec);\r
+ int range = CR_RANGE(insn->chanspec);\r
+ unsigned int bits;\r
+\r
+ // do some initializing\r
+ writew(DAC_ENABLE_BIT, devpriv->main_iobase + DAC_CONTROL0_REG);\r
+\r
+ // set range\r
+ bits = DAC_OUTPUT_ENABLE_BIT;\r
+ bits |= DAC_RANGE_BITS(chan, range);\r
+ writew(bits, devpriv->main_iobase + DAC_CONTROL1_REG);\r
+\r
+ // clear buffer\r
+ writew(0, devpriv->main_iobase + DAC_BUFFER_CLEAR_REG);\r
+\r
+ // write to channel\r
+ writew(data[0], devpriv->main_iobase + DAC_CONVERT_REG(chan));\r
+\r
+ // remember output value\r
+ devpriv->ao_value[chan] = data[0];\r
+\r
+ return 1;\r
+}\r
+\r
+static int ao_readback_insn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)\r
+{\r
+ data[0] = devpriv->ao_value[CR_CHAN(insn->chanspec)];\r
+\r
+ return 1;\r
+}\r
+\r
+static int dio_callback(int dir, int port, int data, void *arg)\r
+{\r
+ unsigned long iobase = (int)arg;\r
+\r
+ if(dir)\r
+ {\r
+ writeb(data, iobase + port);\r
+ return 0;\r
+ }else\r
+ {\r
+ return readb(iobase + port);\r
+ }\r
+}\r
+\r
+static int di_rbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)\r
+{\r
+ lsampl_t bits;\r
+\r
+ bits = readb(devpriv->dio_counter_iobase + DI_REG);\r
+ bits &= 0xf;\r
+ data[1] = bits;\r
+ data[0] = 0;\r
+\r
+ return 2;\r
+}\r
+\r
+static int do_wbits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)\r
+{\r
+ lsampl_t wbits;\r
+\r
+ data[0] &= 0xf;\r
+ wbits = devpriv->do_bits;\r
+ // zero bits we are going to change\r
+ wbits &= ~data[0];\r
+ // set new bits\r
+ wbits |= data[0] & data[1];\r
+ devpriv->do_bits = wbits;\r
+\r
+ writeb(devpriv->do_bits, devpriv->dio_counter_iobase + DO_REG);\r
+\r
+ data[1] = wbits;\r
+\r
+ return 2;\r
+}\r
+\r
+/* utility function that rounds desired timing to an achievable time, and\r
+ * sets cmd members appropriately.\r
+ * adc paces conversions from master clock by dividing by (x + 3) where x is 24 bit number\r
+ */\r
+static void check_adc_timing(comedi_cmd *cmd)\r
+{\r
+ unsigned int convert_divisor, scan_divisor;\r
+ const int max_counter_value = 0xffffff; // board uses 24 bit counters for pacing\r
+ const int min_convert_divisor = 3;\r
+ const int max_convert_divisor = max_counter_value + min_convert_divisor;\r
+ unsigned long long max_scan_divisor, min_scan_divisor;\r
+\r
+ if(cmd->convert_src == TRIG_TIMER)\r
+ {\r
+ convert_divisor = get_divisor(cmd->convert_arg, cmd->flags);\r
+ if(convert_divisor > max_convert_divisor) convert_divisor = max_convert_divisor;\r
+ if(convert_divisor < min_convert_divisor) convert_divisor = min_convert_divisor;\r
+ cmd->convert_arg = convert_divisor * TIMER_BASE;\r
+\r
+ if(cmd->scan_begin_src == TRIG_TIMER)\r
+ {\r
+ scan_divisor = get_divisor(cmd->scan_begin_arg, cmd->flags);\r
+ // XXX check for integer overflows\r
+ min_scan_divisor = convert_divisor * cmd->chanlist_len;\r
+ max_scan_divisor = (convert_divisor * cmd->chanlist_len - 1) + max_counter_value;\r
+ if(scan_divisor > max_scan_divisor) scan_divisor = max_scan_divisor;\r
+ if(scan_divisor < min_scan_divisor) scan_divisor = min_scan_divisor;\r
+ cmd->scan_begin_arg = scan_divisor * TIMER_BASE;\r
+ }\r
+ }\r
+\r
+ return;\r
+}\r
+\r
+/* Gets nearest achievable timing given master clock speed, does not\r
+ * take into account possible minimum/maximum divisor values. Used\r
+ * by other timing checking functions. */\r
+static unsigned int get_divisor(unsigned int ns, unsigned int flags)\r
+{\r
+ unsigned int divisor;\r
+\r
+ switch(flags & TRIG_ROUND_MASK)\r
+ {\r
+ case TRIG_ROUND_UP:\r
+ divisor = (ns + TIMER_BASE - 1) / TIMER_BASE;\r
+ break;\r
+ case TRIG_ROUND_DOWN:\r
+ divisor = ns / TIMER_BASE;\r
+ break;\r
+ case TRIG_ROUND_NEAREST:\r
+ default:\r
+ divisor = (ns + TIMER_BASE / 2) / TIMER_BASE;\r
+ break;\r
+ }\r
+\r
+ return divisor;\r
+}\r