new driver
authorDavid Schleef <ds@schleef.org>
Mon, 5 Feb 2001 18:09:31 +0000 (18:09 +0000)
committerDavid Schleef <ds@schleef.org>
Mon, 5 Feb 2001 18:09:31 +0000 (18:09 +0000)
comedi/Config.in
comedi/drivers/Makefile
comedi/drivers/cb_pcidas.c [new file with mode: 0644]

index dc38b6a605fd730dfe675bcd0554fbebb73fc891..050f084841f4b0a2edff968f0131281f796a464d 100644 (file)
@@ -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
index 9667e35398d0d7bf594e89dc28ed531dbe7a2a80..6bf97bd9624b03171b9825f409239578133dd0a0 100644 (file)
@@ -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 (file)
index 0000000..2153136
--- /dev/null
@@ -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 <ivanmr@altavista.com>.
+
+               Options:
+               [0] - PCI bus number
+               [1] - PCI slot number
+
+    Copyright (C) 2001 Ivan Martinez <ivanmr@altavista.com>, 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 <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.
+
+*/
+
+#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 <asm/io.h>
+#include <linux/comedidev.h>
+
+#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 <ivanmr@altavista.com>.\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);
+