From f9c48029536491a48b4f9ca2ec262e120c10aeae Mon Sep 17 00:00:00 2001 From: David Schleef Date: Mon, 5 Feb 2001 18:09:31 +0000 Subject: [PATCH] new driver --- comedi/Config.in | 1 + comedi/drivers/Makefile | 2 + comedi/drivers/cb_pcidas.c | 673 +++++++++++++++++++++++++++++++++++++ 3 files changed, 676 insertions(+) create mode 100644 comedi/drivers/cb_pcidas.c diff --git a/comedi/Config.in b/comedi/Config.in index dc38b6a6..050f0848 100644 --- a/comedi/Config.in +++ b/comedi/Config.in @@ -77,6 +77,7 @@ dep_tristate 'DAS16' CONFIG_COMEDI_DAS16 $CONFIG_COMEDI dep_tristate 'DAS-6402 and compatibles' CONFIG_COMEDI_DAS6402 $CONFIG_COMEDI dep_tristate 'DAS-800 and compatibles' CONFIG_COMEDI_DAS800 $CONFIG_COMEDI dep_tristate 'DAS-1800 and compatibles' CONFIG_COMEDI_DAS1800 $CONFIG_COMEDI +dep_tristate 'Computer Boards PCI-DAS 1200 series' CONFIG_COMEDI_CB_PCIDAS $CONFIG_COMEDI dep_tristate 'Generic 8255 support' CONFIG_COMEDI_8255 $CONFIG_COMEDI dep_tristate 'Quanser Consulting MultiQ-3' CONFIG_COMEDI_MULTIQ3 $CONFIG_COMEDI dep_tristate 'Generic parallel port support' CONFIG_COMEDI_PARPORT $CONFIG_COMEDI diff --git a/comedi/drivers/Makefile b/comedi/drivers/Makefile index 9667e353..6bf97bd9 100644 --- a/comedi/drivers/Makefile +++ b/comedi/drivers/Makefile @@ -21,6 +21,8 @@ obj-$(CONFIG_COMEDI_8255) += 8255.o obj-$(CONFIG_COMEDI_ADL_PCI9118) += adl_pci9118.o +obj-$(CONFIG_COMEDI_CB_PCIDAS) += cb_pcidas.o + obj-$(CONFIG_COMEDI_DAQBOARD2000) += daqboard2000.o obj-$(CONFIG_COMEDI_DAS08_NEW) += das08-new.o diff --git a/comedi/drivers/cb_pcidas.c b/comedi/drivers/cb_pcidas.c new file mode 100644 index 00000000..21531360 --- /dev/null +++ b/comedi/drivers/cb_pcidas.c @@ -0,0 +1,673 @@ +/* + cb_pcidas.c + This intends to be a driver for the ComputerBoards PCI-DAS series. + SO FAR IT WAS ONLY TESTED WITH PCI-DAS1200. PLEASE REPORT IF YOU ARE + USING IT WITH A DIFFERENT CARD . + + Options: + [0] - PCI bus number + [1] - PCI slot number + + Copyright (C) 2001 Ivan Martinez , with + valuable help from David Schleef, Frank Mori Hess, and the rest of + the Comedi developers comunity. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-8 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 + +#define CB_ID 0x1307 // PCI vendor number of ComputerBoards +#define N_BOARDS 10 // Number of boards in cb_pcidas_boards + +/* PCI-DAS base addresses */ +#define CONT_STAT_BADRINDEX 1 + // CONTROL/STATUS is devpriv->pci_dev->base_address[1] +#define ADC_FIFO_BADRINDEX 2 + // ADC DATA, FIFO CLEAR is devpriv->pci_dev->base_address[2] +/* Control/Status registers */ +#define INT_ADCFIFO 0 // INTERRUPT / ADC FIFO register +#define NOINT 0020300 // Clears and disables interrupts + +#define ADCMUX_CONT 2 // ADC CHANNEL MUX AND CONTROL register +#define RANGE10V 0000000 // 10V +#define RANGE5V 0000400 // 5V +#define RANGE2V5 0001000 // 2.5V +#define RANGE1V25 0001400 // 1.25V +#define UNIP 0004000 // Analog front-end unipolar for range +#define BIP 0000000 // Analog front-end bipolar for range +#define SE 0002000 // Inputs in single-ended mode +#define DIFF 0000000 // Inputs in differential mode +#define SWPACER 0000000 // Pacer source is SW convert +#define EOC 0040000 // End of conversion + +#define TRIG_CONTSTAT 4 // TRIGGER CONTROL/STATUS register + +#define CALIBRATION 6 // CALIBRATION register + +/* ADC data, FIFO clear registers */ +#define ADCDATA 0 // ADC DATA register +#define BEGSWCONV 0 // Begin software conversion + +#define ADCFIFOCLR 2 // ADC FIFO CLEAR +#define CLEARFIFO 0 // Clear fifo + +#define BIPRANGES 4 // Number of bipolar ranges in cb_pcidas_ranges + +comedi_lrange cb_pcidas_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) + } +}; + +/* + * 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 cb_pcidas_board_struct +{ + char *name; + char status; // Driver status: + // 0 - tested + // 1 - manual read, not tested + // 2 - manual not read + unsigned short device_id; + int ai_se_chans; // Inputs in single-ended mode + int ai_diff_chans; // Inputs in differential mode + int ai_bits; + comedi_lrange *ranges; +} cb_pcidas_board; +static cb_pcidas_board cb_pcidas_boards[] = +{ + { + name: "pci-das1602/16", + status: 2, + device_id: 0x1, + ai_se_chans: 16, + ai_diff_chans: 8, + ai_bits: 16, + ranges: &cb_pcidas_ranges, + }, + { + name: "pci-das1200", + status: 0, + device_id: 0xF, + ai_se_chans: 16, + ai_diff_chans: 8, + ai_bits: 12, + ranges: &cb_pcidas_ranges, + }, + { + name: "pci-das1602/12", + status: 2, + device_id: 0x10, + ai_se_chans: 16, + ai_diff_chans: 8, + ai_bits: 12, + ranges: &cb_pcidas_ranges, + }, + { + name: "pci-das1200/jr", + status: 1, + device_id: 0x19, + ai_se_chans: 16, + ai_diff_chans: 8, + ai_bits: 12, + ranges: &cb_pcidas_ranges, + }, + { + name: "pci-das1602/16/jr", + status: 2, + device_id: 0x1C, + ai_se_chans: 64, + ai_diff_chans: 8, + ai_bits: 16, + ranges: &cb_pcidas_ranges, + }, + { + name: "pci-das6402/16", + status: 2, + device_id: 0x1D, + ai_se_chans: 64, + ai_diff_chans: 32, + ai_bits: 16, + ranges: &cb_pcidas_ranges, + }, + { + name: "pci-das64/m1/16", + status: 2, + device_id: 0x35, + ai_se_chans: 64, + ai_diff_chans: 32, + ai_bits: 16, + ranges: &cb_pcidas_ranges, + }, + { + name: "pci-das64/m2/16", + status: 2, + device_id: 0x36, + ai_se_chans: 64, + ai_diff_chans: 32, + ai_bits: 16, + ranges: &cb_pcidas_ranges, + }, + { + name: "pci-das64/m3/16", + status: 2, + device_id: 0x37, + ai_se_chans: 64, + ai_diff_chans: 32, + ai_bits: 16, + ranges: &cb_pcidas_ranges, + }, + { + name: "pci-das1000", + status: 2, + device_id: 0x4C, + ai_se_chans: 16, + ai_diff_chans: 8, + ai_bits: 12, + ranges: &cb_pcidas_ranges, + }, +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((cb_pcidas_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 +{ + int data; + + /* would be useful for a PCI device */ + struct pci_dev *pci_dev; + + unsigned long control_status; + unsigned long adc_fifo; + +} cb_pcidas_private; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((cb_pcidas_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 cb_pcidas_attach(comedi_device *dev,comedi_devconfig *it); +static int cb_pcidas_detach(comedi_device *dev); +static int cb_pcidas_recognize(char *name); +comedi_driver driver_cb_pcidas={ + driver_name: "cb_pcidas", + module: THIS_MODULE, + attach: cb_pcidas_attach, + detach: cb_pcidas_detach, + recognize: cb_pcidas_recognize, +}; + +static int cb_pcidas_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data); +//static int cb_pcidas_ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data); +//static int cb_pcidas_ai_cmd(comedi_device *dev,comedi_subdevice *s); +static int cb_pcidas_ai_cmdtest(comedi_device *dev,comedi_subdevice *s, + comedi_cmd *cmd); +static int cb_pcidas_ns_to_timer(unsigned int *ns,int round); + +/* + * The function cb_pcidas_recognize() is called when the Comedi core + * gets a request to configure a device. If the name of the device + * being configured matches with one of the devices that this + * driver can service, then a non-negative index should be returned. + * This index is put into dev->board, and then _attach() is called. + */ +static int cb_pcidas_recognize(char *name) +{ + if(!strcmp("cb_pcidas",name))return 0; + + return -1; +} + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. _recognize() has already been called, + * and dev->board contains whatever _recognize returned. + */ +static int cb_pcidas_attach(comedi_device *dev, comedi_devconfig *it) +{ + comedi_subdevice *s; + struct pci_dev* pci_dev_temp; + int index; + + printk("comedi%d: cb_pcidas: ",dev->minor); + +/* + * Allocate the private structure area. + */ + if(alloc_private(dev,sizeof(cb_pcidas_private))<0) + return -ENOMEM; + +/* + * Probe the device to determine what device in the series it is. + */ + devpriv->pci_dev = NULL; + pci_dev_temp = pci_devices; + + printk("\n"); + + while(pci_dev_temp && !devpriv->pci_dev) + { + if ((pci_dev_temp->bus->number == it->options[0]) && + (PCI_SLOT(pci_dev_temp->devfn) == it->options[1])) + { + devpriv->pci_dev = pci_dev_temp; + if (pci_dev_temp->vendor == CB_ID) + { + index = 0; + while((index < N_BOARDS) && + (cb_pcidas_boards[index].device_id != + pci_dev_temp->device)) + index++; + if (index < N_BOARDS) + { + dev->board = index; + printk("Found %s at requested position\n", + cb_pcidas_boards[dev->board].name); + } + else + { + rt_printk("Not a supported ComputerBoards card on requested " + "position\n"); + return -EIO; + } + } + else + { + rt_printk("Not a ComputerBoards card on requested position\n"); + return -EIO; + } + } + pci_dev_temp = pci_dev_temp->next; + } + + if (!devpriv->pci_dev) + { + rt_printk("No card on requested position\n"); + return -EIO; + } + +/* + * Initialize devpriv->control_status and devpriv->adc_fifo to point to + * their base address. + */ + devpriv->control_status = + devpriv->pci_dev->base_address[CONT_STAT_BADRINDEX] & + PCI_BASE_ADDRESS_IO_MASK; + devpriv->adc_fifo = devpriv->pci_dev->base_address[ADC_FIFO_BADRINDEX] & + PCI_BASE_ADDRESS_IO_MASK; + +/* + * Warn about the status of the driver. + */ + if (cb_pcidas_boards[dev->board].status == 2) + printk("WARNING: DRIVER FOR THIS BOARD NOT CHECKED WITH MANUAL. " + "WORKS ASSUMING FULL COMPATIBILITY WITH PCI-DAS1200. " + "PLEASE REPORT USAGE TO .\n"); + +/* + * Initialize dev->board_ptr. This can point to an element in the + * cb_pcidas_boards array, for quick access to board-specific information. + */ + dev->board_ptr = cb_pcidas_boards + dev->board; + +/* + * 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. + */ + dev->n_subdevices=1; + if(alloc_subdevices(dev)<0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF; + /* WARNING: Number of inputs in differential mode is ignored */ + s->n_chan = thisboard->ai_se_chans; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = thisboard->ranges; + s->insn_read = &cb_pcidas_ai_rinsn; +// s->do_cmd = &cb_pcidas_ai_cmd; + s->do_cmdtest = &cb_pcidas_ai_cmdtest; + + 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 cb_pcidas_detach(comedi_device *dev) +{ + printk("comedi%d: cb_pcidas: remove\n",dev->minor); + + return 0; +} + +/* + * "instructions" read/write data in "one-shot" or "software-triggered" + * mode. + */ +static int cb_pcidas_ai_rinsn(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int n; + unsigned int d; + unsigned int command; + + /* a typical programming sequence */ + + /* inputs mode */ + if (CR_AREF(insn->chanspec) == AREF_DIFF) + command = DIFF; + else + command = SE; + + /* input signals range */ + if (CR_RANGE(insn->chanspec) < BIPRANGES) + command |= BIP | (CR_RANGE(insn->chanspec) << 8); + else + command |= UNIP | ((CR_RANGE(insn->chanspec) - BIPRANGES) << 8); + + /* write channel to multiplexer */ + command |= CR_CHAN(insn->chanspec) | (CR_CHAN(insn->chanspec) << 4); + + outw_p(command, devpriv->control_status + ADCMUX_CONT); + + /* wait for mux to settle */ + /* I suppose I made it with outw_p... */ + + /* convert n samples */ + for (n = 0; n < insn->n; n++) + { + /* clear fifo */ + outw(CLEARFIFO, devpriv->adc_fifo + ADCFIFOCLR); + + /* trigger conversion */ + outw(BEGSWCONV, devpriv->adc_fifo + ADCDATA); + + /* wait for conversion to end */ + /* return -ETIMEDOUT if there is a timeout */ + while (!(inw(devpriv->control_status + ADCMUX_CONT) & EOC)); + + /* read data */ + d = inw(devpriv->adc_fifo + ADCDATA); + + data[n] = d; + } + + /* return the number of samples read/written */ + return n; +} + +/* + * I will program this later... ;-) + * +static int cb_pcidas_ai_cmd(comedi_device *dev,comedi_subdevice *s) +{ + printk("cb_pcidas_ai_cmd\n"); + printk("subdev: %d\n", cmd->subdev); + printk("flags: %d\n", cmd->flags); + printk("start_src: %d\n", cmd->start_src); + printk("start_arg: %d\n", cmd->start_arg); + printk("scan_begin_src: %d\n", cmd->scan_begin_src); + printk("convert_src: %d\n", cmd->convert_src); + printk("convert_arg: %d\n", cmd->convert_arg); + printk("scan_end_src: %d\n", cmd->scan_end_src); + printk("scan_end_arg: %d\n", cmd->scan_end_arg); + printk("stop_src: %d\n", cmd->stop_src); + printk("stop_arg: %d\n", cmd->stop_arg); + printk("chanlist_len: %d\n", cmd->chanlist_len); + printk("data_len: %d\n", cmd->data_len); +} +*/ + +static int cb_pcidas_ai_cmdtest(comedi_device *dev,comedi_subdevice *s, + comedi_cmd *cmd) +{ + int err=0; + int tmp; + + /* 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 */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if(!cmd->start_src && tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_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 */ + + /* note that mutual compatiblity is not an issue here */ + 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_TIMER && cmd->stop_src != TRIG_EXT) + err++; + + if(err) return 2; + + /* step 3: make sure arguments are trivially compatible */ + + 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_arg < MAX_SPEED) + { + cmd->scan_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_arg < MAX_SPEED) + { + cmd->convert_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 */ + + if(cmd->scan_begin_src == TRIG_TIMER) + { + tmp = cmd->scan_begin_arg; + cb_pcidas_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; + cb_pcidas_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_arg < cmd->convert_arg * cmd->scan_end_arg) + { + cmd->scan_begin_arg = cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } + + if(err) return 4; + + 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 cb_pcidas_ns_to_timer(unsigned int *ns,int round) +{ + /* trivial timer */ + return *ns; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +COMEDI_INITCLEANUP(driver_cb_pcidas); + -- 2.26.2