From 59c7dd3dc37dc42339406d08f1cde4d6194a4ccf Mon Sep 17 00:00:00 2001
From: Ivan Martinez <imr@oersted.dtu.dk>
Date: Thu, 12 Feb 2009 15:47:34 -0800
Subject: [PATCH] Staging: comedi: add cb_pcidas driver

For MeasurementComputing PCI-DAS series with the AMCC S5933 PCI
controller

From: Ivan Martinez <imr@oersted.dtu.dk>
Cc: David Schleef <ds@schleef.org>
Cc: Frank Mori Hess <fmhess@users.sourceforge.net>
Cc: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
---
 drivers/staging/comedi/drivers/cb_pcidas.c | 1828 ++++++++++++++++++++
 1 file changed, 1828 insertions(+)
 create mode 100644 drivers/staging/comedi/drivers/cb_pcidas.c

diff --git a/drivers/staging/comedi/drivers/cb_pcidas.c b/drivers/staging/comedi/drivers/cb_pcidas.c
new file mode 100644
index 0000000000000..63c8a802f5996
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_pcidas.c
@@ -0,0 +1,1828 @@
+/*
+    comedi/drivers/cb_pcidas.c
+
+    Developed by Ivan Martinez and Frank Mori Hess, with valuable help from
+    David Schleef and the rest of the Comedi developers comunity.
+
+    Copyright (C) 2001-2003 Ivan Martinez <imr@oersted.dtu.dk>
+    Copyright (C) 2001,2002 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+
+    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_pcidas
+Description: MeasurementComputing PCI-DAS series with the AMCC S5933 PCI controller
+Author: Ivan Martinez <imr@oersted.dtu.dk>,
+  Frank Mori Hess <fmhess@users.sourceforge.net>
+Updated: 2003-3-11
+Devices: [Measurement Computing] PCI-DAS1602/16 (cb_pcidas),
+  PCI-DAS1602/16jr, PCI-DAS1602/12, PCI-DAS1200, PCI-DAS1200jr,
+  PCI-DAS1000, PCI-DAS1001, PCI_DAS1002
+
+Status:
+  There are many reports of the driver being used with most of the
+  supported cards. Despite no detailed log is maintained, it can
+  be said that the driver is quite tested and stable.
+
+  The boards may be autocalibrated using the comedi_calibrate
+  utility.
+
+Configuration options:
+  [0] - PCI bus of device (optional)
+  [1] - PCI slot of device (optional)
+  If bus/slot is not specified, the first supported
+  PCI device found will be used.
+
+For commands, the scanned channels must be consecutive
+(i.e. 4-5-6-7, 2-3-4,...), and must all have the same
+range and aref.
+*/
+/*
+
+TODO:
+
+analog triggering on 1602 series
+*/
+
+#include "../comedidev.h"
+#include <linux/delay.h>
+
+#include "8253.h"
+#include "8255.h"
+#include "amcc_s5933.h"
+#include "comedi_pci.h"
+#include "comedi_fc.h"
+
+#undef CB_PCIDAS_DEBUG		// disable debugging code
+//#define CB_PCIDAS_DEBUG       // enable debugging code
+
+// PCI vendor number of ComputerBoards/MeasurementComputing
+#define PCI_VENDOR_ID_CB	0x1307
+#define TIMER_BASE 100		// 10MHz master clock
+#define AI_BUFFER_SIZE 1024	// maximum fifo size of any supported board
+#define AO_BUFFER_SIZE 1024	// maximum fifo size of any supported board
+#define NUM_CHANNELS_8800 8
+#define NUM_CHANNELS_7376 1
+#define NUM_CHANNELS_8402 2
+#define NUM_CHANNELS_DAC08 1
+
+/* PCI-DAS base addresses */
+
+// indices of base address regions
+#define S5933_BADRINDEX 0
+#define CONT_STAT_BADRINDEX 1
+#define ADC_FIFO_BADRINDEX 2
+#define PACER_BADRINDEX 3
+#define AO_BADRINDEX 4
+// sizes of io regions
+#define CONT_STAT_SIZE 10
+#define ADC_FIFO_SIZE 4
+#define PACER_SIZE 12
+#define AO_SIZE 4
+
+/* Control/Status registers */
+#define INT_ADCFIFO	0	// INTERRUPT / ADC FIFO register
+#define   INT_EOS 0x1		// interrupt end of scan
+#define   INT_FHF 0x2		// interrupt fifo half full
+#define   INT_FNE 0x3		// interrupt fifo not empty
+#define   INT_MASK 0x3		// mask of interrupt select bits
+#define   INTE 0x4		// interrupt enable
+#define   DAHFIE 0x8		// dac half full interrupt enable
+#define   EOAIE	0x10		// end of aquisition interrupt enable
+#define   DAHFI	0x20		// dac half full read status / write interrupt clear
+#define   EOAI 0x40		// read end of acq. interrupt status / write clear
+#define   INT 0x80		// read interrupt status / write clear
+#define   EOBI 0x200		// read end of burst interrupt status
+#define   ADHFI 0x400		// read half-full interrupt status
+#define   ADNEI 0x800		// read fifo not empty interrupt latch status
+#define   ADNE 0x1000		// read, fifo not empty (realtime, not latched) status
+#define   DAEMIE	0x1000	// write, dac empty interrupt enable
+#define   LADFUL 0x2000		// read fifo overflow / write clear
+#define   DAEMI 0x4000		// dac fifo empty interrupt status / write clear
+
+#define ADCMUX_CONT	2	// ADC CHANNEL MUX AND CONTROL register
+#define   BEGIN_SCAN(x)	((x) & 0xf)
+#define   END_SCAN(x)	(((x) & 0xf) << 4)
+#define   GAIN_BITS(x)	(((x) & 0x3) << 8)
+#define   UNIP	0x800		// Analog front-end unipolar for range
+#define   SE	0x400		// Inputs in single-ended mode
+#define   PACER_MASK	0x3000	// pacer source bits
+#define   PACER_INT 0x1000	// internal pacer
+#define   PACER_EXT_FALL	0x2000	// external falling edge
+#define   PACER_EXT_RISE	0x3000	// external rising edge
+#define   EOC	0x4000		// adc not busy
+
+#define TRIG_CONTSTAT 4		// TRIGGER CONTROL/STATUS register
+#define   SW_TRIGGER 0x1	// software start trigger
+#define   EXT_TRIGGER 0x2	// external start trigger
+#define   ANALOG_TRIGGER 0x3	// external analog trigger
+#define   TRIGGER_MASK	0x3	// mask of bits that determine start trigger
+#define   TGEN	0x10		// enable external start trigger
+#define   BURSTE 0x20		// burst mode enable
+#define   XTRCL	0x80		// clear external trigger
+
+#define CALIBRATION_REG	6	// CALIBRATION register
+#define   SELECT_8800_BIT	0x100	// select 8800 caldac
+#define   SELECT_TRIMPOT_BIT	0x200	// select ad7376 trim pot
+#define   SELECT_DAC08_BIT	0x400	// select dac08 caldac
+#define   CAL_SRC_BITS(x)	(((x) & 0x7) << 11)
+#define   CAL_EN_BIT	0x4000	// read calibration source instead of analog input channel 0
+#define   SERIAL_DATA_IN_BIT	0x8000	// serial data stream going to 8800 and 7376
+
+#define DAC_CSR	0x8		// dac control and status register
+enum dac_csr_bits {
+	DACEN = 0x2,		// dac enable
+	DAC_MODE_UPDATE_BOTH = 0x80,	// update both dacs when dac0 is written
+};
+static inline unsigned int DAC_RANGE(unsigned int channel, unsigned int range)
+{
+	return (range & 0x3) << (8 + 2 * (channel & 0x1));
+}
+static inline unsigned int DAC_RANGE_MASK(unsigned int channel)
+{
+	return 0x3 << (8 + 2 * (channel & 0x1));
+};
+
+// bits for 1602 series only
+enum dac_csr_bits_1602 {
+	DAC_EMPTY = 0x1,	// dac fifo empty, read, write clear
+	DAC_START = 0x4,	// start/arm dac fifo operations
+	DAC_PACER_MASK = 0x18,	// bits that set dac pacer source
+	DAC_PACER_INT = 0x8,	// dac internal pacing
+	DAC_PACER_EXT_FALL = 0x10,	// dac external pacing, falling edge
+	DAC_PACER_EXT_RISE = 0x18,	// dac external pacing, rising edge
+};
+static inline unsigned int DAC_CHAN_EN(unsigned int channel)
+{
+	return 1 << (5 + (channel & 0x1));	// enable channel 0 or 1
+};
+
+/* analog input fifo */
+#define ADCDATA	0		// ADC DATA register
+#define ADCFIFOCLR	2	// ADC FIFO CLEAR
+
+// pacer, counter, dio registers
+#define ADC8254 0
+#define DIO_8255 4
+#define DAC8254 8
+
+// analog output registers for 100x, 1200 series
+static inline unsigned int DAC_DATA_REG(unsigned int channel)
+{
+	return 2 * (channel & 0x1);
+}
+
+/* analog output registers for 1602 series*/
+#define DACDATA	0		// DAC DATA register
+#define DACFIFOCLR	2	// DAC FIFO CLEAR
+
+// bit in hexadecimal representation of range index that indicates unipolar input range
+#define IS_UNIPOLAR 0x4
+// analog input ranges for most boards
+static const 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)
+		}
+};
+
+// pci-das1001 input ranges
+static const comedi_lrange cb_pcidas_alt_ranges = {
+	8,
+	{
+			BIP_RANGE(10),
+			BIP_RANGE(1),
+			BIP_RANGE(0.1),
+			BIP_RANGE(0.01),
+			UNI_RANGE(10),
+			UNI_RANGE(1),
+			UNI_RANGE(0.1),
+			UNI_RANGE(0.01)
+		}
+};
+
+// analog output ranges
+static const comedi_lrange cb_pcidas_ao_ranges = {
+	4,
+	{
+			BIP_RANGE(5),
+			BIP_RANGE(10),
+			UNI_RANGE(5),
+			UNI_RANGE(10),
+		}
+};
+
+enum trimpot_model {
+	AD7376,
+	AD8402,
+};
+
+typedef struct cb_pcidas_board_struct {
+	const char *name;
+	unsigned short device_id;
+	int ai_se_chans;	// Inputs in single-ended mode
+	int ai_diff_chans;	// Inputs in differential mode
+	int ai_bits;		// analog input resolution
+	int ai_speed;		// fastest conversion period in ns
+	int ao_nchan;		// number of analog out channels
+	int has_ao_fifo;	// analog output has fifo
+	int ao_scan_speed;	// analog output speed for 1602 series (for a scan, not conversion)
+	int fifo_size;		// number of samples fifo can hold
+	const comedi_lrange *ranges;
+	enum trimpot_model trimpot;
+	unsigned has_dac08:1;
+} cb_pcidas_board;
+
+static const cb_pcidas_board cb_pcidas_boards[] = {
+	{
+	      name:	"pci-das1602/16",
+	      device_id:0x1,
+	      ai_se_chans:16,
+	      ai_diff_chans:8,
+	      ai_bits:	16,
+	      ai_speed:5000,
+	      ao_nchan:2,
+	      has_ao_fifo:1,
+	      ao_scan_speed:10000,
+	      fifo_size:512,
+	      ranges:	&cb_pcidas_ranges,
+	      trimpot:	AD8402,
+	      has_dac08:1,
+		},
+	{
+	      name:	"pci-das1200",
+	      device_id:0xF,
+	      ai_se_chans:16,
+	      ai_diff_chans:8,
+	      ai_bits:	12,
+	      ai_speed:3200,
+	      ao_nchan:2,
+	      has_ao_fifo:0,
+	      fifo_size:1024,
+	      ranges:	&cb_pcidas_ranges,
+	      trimpot:	AD7376,
+	      has_dac08:0,
+		},
+	{
+	      name:	"pci-das1602/12",
+	      device_id:0x10,
+	      ai_se_chans:16,
+	      ai_diff_chans:8,
+	      ai_bits:	12,
+	      ai_speed:3200,
+	      ao_nchan:2,
+	      has_ao_fifo:1,
+	      ao_scan_speed:4000,
+	      fifo_size:1024,
+	      ranges:	&cb_pcidas_ranges,
+	      trimpot:	AD7376,
+	      has_dac08:0,
+		},
+	{
+	      name:	"pci-das1200/jr",
+	      device_id:0x19,
+	      ai_se_chans:16,
+	      ai_diff_chans:8,
+	      ai_bits:	12,
+	      ai_speed:3200,
+	      ao_nchan:0,
+	      has_ao_fifo:0,
+	      fifo_size:1024,
+	      ranges:	&cb_pcidas_ranges,
+	      trimpot:	AD7376,
+	      has_dac08:0,
+		},
+	{
+	      name:	"pci-das1602/16/jr",
+	      device_id:0x1C,
+	      ai_se_chans:16,
+	      ai_diff_chans:8,
+	      ai_bits:	16,
+	      ai_speed:5000,
+	      ao_nchan:0,
+	      has_ao_fifo:0,
+	      fifo_size:512,
+	      ranges:	&cb_pcidas_ranges,
+	      trimpot:	AD8402,
+	      has_dac08:1,
+		},
+	{
+	      name:	"pci-das1000",
+	      device_id:0x4C,
+	      ai_se_chans:16,
+	      ai_diff_chans:8,
+	      ai_bits:	12,
+	      ai_speed:4000,
+	      ao_nchan:0,
+	      has_ao_fifo:0,
+	      fifo_size:1024,
+	      ranges:	&cb_pcidas_ranges,
+	      trimpot:	AD7376,
+	      has_dac08:0,
+		},
+	{
+	      name:	"pci-das1001",
+	      device_id:0x1a,
+	      ai_se_chans:16,
+	      ai_diff_chans:8,
+	      ai_bits:	12,
+	      ai_speed:6800,
+	      ao_nchan:2,
+	      has_ao_fifo:0,
+	      fifo_size:1024,
+	      ranges:	&cb_pcidas_alt_ranges,
+	      trimpot:	AD7376,
+	      has_dac08:0,
+		},
+	{
+	      name:	"pci-das1002",
+	      device_id:0x1b,
+	      ai_se_chans:16,
+	      ai_diff_chans:8,
+	      ai_bits:	12,
+	      ai_speed:6800,
+	      ao_nchan:2,
+	      has_ao_fifo:0,
+	      fifo_size:1024,
+	      ranges:	&cb_pcidas_ranges,
+	      trimpot:	AD7376,
+	      has_dac08:0,
+		},
+};
+
+// Number of boards in cb_pcidas_boards
+#define N_BOARDS	(sizeof(cb_pcidas_boards) / sizeof(cb_pcidas_board))
+
+static DEFINE_PCI_DEVICE_TABLE(cb_pcidas_pci_table) = {
+	{PCI_VENDOR_ID_CB, 0x0001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CB, 0x000f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CB, 0x0010, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CB, 0x0019, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CB, 0x001c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CB, 0x004c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CB, 0x001a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CB, 0x001b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{0}
+};
+
+MODULE_DEVICE_TABLE(pci, cb_pcidas_pci_table);
+
+/*
+ * Useful for shorthand access to the particular board structure
+ */
+#define thisboard ((const 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 {
+	/* would be useful for a PCI device */
+	struct pci_dev *pci_dev;
+	// base addresses
+	unsigned long s5933_config;
+	unsigned long control_status;
+	unsigned long adc_fifo;
+	unsigned long pacer_counter_dio;
+	unsigned long ao_registers;
+	// divisors of master clock for analog input pacing
+	unsigned int divisor1;
+	unsigned int divisor2;
+	volatile unsigned int count;	// number of analog input samples remaining
+	volatile unsigned int adc_fifo_bits;	// bits to write to interupt/adcfifo register
+	volatile unsigned int s5933_intcsr_bits;	// bits to write to amcc s5933 interrupt control/status register
+	volatile unsigned int ao_control_bits;	// bits to write to ao control and status register
+	sampl_t ai_buffer[AI_BUFFER_SIZE];
+	sampl_t ao_buffer[AO_BUFFER_SIZE];
+	// divisors of master clock for analog output pacing
+	unsigned int ao_divisor1;
+	unsigned int ao_divisor2;
+	volatile unsigned int ao_count;	// number of analog output samples remaining
+	int ao_value[2];	// remember what the analog outputs are set to, to allow readback
+	unsigned int caldac_value[NUM_CHANNELS_8800];	// for readback of caldac
+	unsigned int trimpot_value[NUM_CHANNELS_8402];	// for readback of trimpot
+	unsigned int dac08_value;
+	unsigned int calibration_source;
+} 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 comedi_driver driver_cb_pcidas = {
+      driver_name:"cb_pcidas",
+      module:THIS_MODULE,
+      attach:cb_pcidas_attach,
+      detach:cb_pcidas_detach,
+};
+
+static int cb_pcidas_ai_rinsn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data);
+static int ai_config_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data);
+static int cb_pcidas_ao_nofifo_winsn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data);
+static int cb_pcidas_ao_fifo_winsn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data);
+static int cb_pcidas_ao_readback_insn(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_ao_cmd(comedi_device * dev, comedi_subdevice * s);
+static int cb_pcidas_ao_inttrig(comedi_device * dev, comedi_subdevice * subdev,
+	unsigned int trig_num);
+static int cb_pcidas_ao_cmdtest(comedi_device * dev, comedi_subdevice * s,
+	comedi_cmd * cmd);
+static irqreturn_t cb_pcidas_interrupt(int irq, void *d PT_REGS_ARG);
+static void handle_ao_interrupt(comedi_device * dev, unsigned int status);
+static int cb_pcidas_cancel(comedi_device * dev, comedi_subdevice * s);
+static int cb_pcidas_ao_cancel(comedi_device * dev, comedi_subdevice * s);
+static void cb_pcidas_load_counters(comedi_device * dev, unsigned int *ns,
+	int round_flags);
+static int eeprom_read_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data);
+static int caldac_read_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data);
+static int caldac_write_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data);
+static int trimpot_read_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data);
+static int cb_pcidas_trimpot_write(comedi_device * dev, unsigned int channel,
+	lsampl_t value);
+static int trimpot_write_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data);
+static int dac08_read_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data);
+static int dac08_write(comedi_device * dev, lsampl_t value);
+static int dac08_write_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data);
+static int caldac_8800_write(comedi_device * dev, unsigned int address,
+	uint8_t value);
+static int trimpot_7376_write(comedi_device * dev, uint8_t value);
+static int trimpot_8402_write(comedi_device * dev, unsigned int channel,
+	uint8_t value);
+static int nvram_read(comedi_device * dev, unsigned int address,
+	uint8_t * data);
+
+static inline unsigned int cal_enable_bits(comedi_device * dev)
+{
+	return CAL_EN_BIT | CAL_SRC_BITS(devpriv->calibration_source);
+}
+
+/*
+ * Attach is called by the Comedi core to configure the driver
+ * for a particular board.
+ */
+static int cb_pcidas_attach(comedi_device * dev, comedi_devconfig * it)
+{
+	comedi_subdevice *s;
+	struct pci_dev *pcidev;
+	int index;
+	int i;
+
+	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.
+ */
+	printk("\n");
+
+	for (pcidev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL);
+		pcidev != NULL;
+		pcidev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pcidev)) {
+		// is it not a computer boards card?
+		if (pcidev->vendor != PCI_VENDOR_ID_CB)
+			continue;
+		// loop through cards supported by this driver
+		for (index = 0; index < N_BOARDS; index++) {
+			if (cb_pcidas_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;
+				}
+			}
+			devpriv->pci_dev = pcidev;
+			dev->board_ptr = cb_pcidas_boards + index;
+			goto found;
+		}
+	}
+
+	printk("No supported ComputerBoards/MeasurementComputing card found on "
+		"requested position\n");
+	return -EIO;
+
+      found:
+
+	printk("Found %s on bus %i, slot %i\n", cb_pcidas_boards[index].name,
+		pcidev->bus->number, PCI_SLOT(pcidev->devfn));
+
+	/*
+	 * Enable PCI device and reserve I/O ports.
+	 */
+	if (comedi_pci_enable(pcidev, "cb_pcidas")) {
+		printk(" Failed to enable PCI device and request regions\n");
+		return -EIO;
+	}
+	/*
+	 * Initialize devpriv->control_status and devpriv->adc_fifo to point to
+	 * their base address.
+	 */
+	devpriv->s5933_config =
+		pci_resource_start(devpriv->pci_dev, S5933_BADRINDEX);
+	devpriv->control_status =
+		pci_resource_start(devpriv->pci_dev, CONT_STAT_BADRINDEX);
+	devpriv->adc_fifo =
+		pci_resource_start(devpriv->pci_dev, ADC_FIFO_BADRINDEX);
+	devpriv->pacer_counter_dio =
+		pci_resource_start(devpriv->pci_dev, PACER_BADRINDEX);
+	if (thisboard->ao_nchan) {
+		devpriv->ao_registers =
+			pci_resource_start(devpriv->pci_dev, AO_BADRINDEX);
+	}
+	// disable and clear interrupts on amcc s5933
+	outl(INTCSR_INBOX_INTR_STATUS,
+		devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+
+	// get irq
+	if (comedi_request_irq(devpriv->pci_dev->irq, cb_pcidas_interrupt,
+			IRQF_SHARED, "cb_pcidas", dev)) {
+		printk(" unable to allocate irq %d\n", devpriv->pci_dev->irq);
+		return -EINVAL;
+	}
+	dev->irq = devpriv->pci_dev->irq;
+
+	//Initialize dev->board_name
+	dev->board_name = thisboard->name;
+
+/*
+ * Allocate the subdevice structures.
+ */
+	if (alloc_subdevices(dev, 7) < 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_DIFF | SDF_CMD_READ;
+	/* WARNING: Number of inputs in differential mode is ignored */
+	s->n_chan = thisboard->ai_se_chans;
+	s->len_chanlist = thisboard->ai_se_chans;
+	s->maxdata = (1 << thisboard->ai_bits) - 1;
+	s->range_table = thisboard->ranges;
+	s->insn_read = cb_pcidas_ai_rinsn;
+	s->insn_config = ai_config_insn;
+	s->do_cmd = cb_pcidas_ai_cmd;
+	s->do_cmdtest = cb_pcidas_ai_cmdtest;
+	s->cancel = cb_pcidas_cancel;
+
+	/* analog output subdevice */
+	s = dev->subdevices + 1;
+	if (thisboard->ao_nchan) {
+		s->type = COMEDI_SUBD_AO;
+		s->subdev_flags = SDF_READABLE | SDF_WRITABLE | 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 = &cb_pcidas_ao_ranges;
+		s->insn_read = cb_pcidas_ao_readback_insn;
+		if (thisboard->has_ao_fifo) {
+			dev->write_subdev = s;
+			s->subdev_flags |= SDF_CMD_WRITE;
+			s->insn_write = cb_pcidas_ao_fifo_winsn;
+			s->do_cmdtest = cb_pcidas_ao_cmdtest;
+			s->do_cmd = cb_pcidas_ao_cmd;
+			s->cancel = cb_pcidas_ao_cancel;
+		} else {
+			s->insn_write = cb_pcidas_ao_nofifo_winsn;
+		}
+	} else {
+		s->type = COMEDI_SUBD_UNUSED;
+	}
+
+	/* 8255 */
+	s = dev->subdevices + 2;
+	subdev_8255_init(dev, s, NULL, devpriv->pacer_counter_dio + DIO_8255);
+
+	// serial EEPROM,
+	s = dev->subdevices + 3;
+	s->type = COMEDI_SUBD_MEMORY;
+	s->subdev_flags = SDF_READABLE | SDF_INTERNAL;
+	s->n_chan = 256;
+	s->maxdata = 0xff;
+	s->insn_read = eeprom_read_insn;
+
+	// 8800 caldac
+	s = dev->subdevices + 4;
+	s->type = COMEDI_SUBD_CALIB;
+	s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+	s->n_chan = NUM_CHANNELS_8800;
+	s->maxdata = 0xff;
+	s->insn_read = caldac_read_insn;
+	s->insn_write = caldac_write_insn;
+	for (i = 0; i < s->n_chan; i++)
+		caldac_8800_write(dev, i, s->maxdata / 2);
+
+	// trim potentiometer
+	s = dev->subdevices + 5;
+	s->type = COMEDI_SUBD_CALIB;
+	s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+	if (thisboard->trimpot == AD7376) {
+		s->n_chan = NUM_CHANNELS_7376;
+		s->maxdata = 0x7f;
+	} else {
+		s->n_chan = NUM_CHANNELS_8402;
+		s->maxdata = 0xff;
+	}
+	s->insn_read = trimpot_read_insn;
+	s->insn_write = trimpot_write_insn;
+	for (i = 0; i < s->n_chan; i++)
+		cb_pcidas_trimpot_write(dev, i, s->maxdata / 2);
+
+	// dac08 caldac
+	s = dev->subdevices + 6;
+	if (thisboard->has_dac08) {
+		s->type = COMEDI_SUBD_CALIB;
+		s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+		s->n_chan = NUM_CHANNELS_DAC08;
+		s->insn_read = dac08_read_insn;
+		s->insn_write = dac08_write_insn;
+		s->maxdata = 0xff;
+		dac08_write(dev, s->maxdata / 2);
+	} else
+		s->type = COMEDI_SUBD_UNUSED;
+
+	// make sure mailbox 4 is empty
+	inl(devpriv->s5933_config + AMCC_OP_REG_IMB4);
+	/* Set bits to enable incoming mailbox interrupts on amcc s5933. */
+	devpriv->s5933_intcsr_bits =
+		INTCSR_INBOX_BYTE(3) | INTCSR_INBOX_SELECT(3) |
+		INTCSR_INBOX_FULL_INT;
+	// clear and enable interrupt on amcc s5933
+	outl(devpriv->s5933_intcsr_bits | INTCSR_INBOX_INTR_STATUS,
+		devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+
+	return 1;
+}
+
+/*
+ * cb_pcidas_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);
+
+	if (devpriv) {
+		if (devpriv->s5933_config) {
+			// disable and clear interrupts on amcc s5933
+			outl(INTCSR_INBOX_INTR_STATUS,
+				devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+#ifdef CB_PCIDAS_DEBUG
+			rt_printk("detaching, incsr is 0x%x\n",
+				inl(devpriv->s5933_config +
+					AMCC_OP_REG_INTCSR));
+#endif
+		}
+	}
+	if (dev->irq)
+		comedi_free_irq(dev->irq, dev);
+	if (dev->subdevices)
+		subdev_8255_cleanup(dev, dev->subdevices + 2);
+	if (devpriv && devpriv->pci_dev) {
+		if (devpriv->s5933_config) {
+			comedi_pci_disable(devpriv->pci_dev);
+		}
+		pci_dev_put(devpriv->pci_dev);
+	}
+
+	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, i;
+	unsigned int bits;
+	static const int timeout = 10000;
+	int channel;
+	// enable calibration input if appropriate
+	if (insn->chanspec & CR_ALT_SOURCE) {
+		outw(cal_enable_bits(dev),
+			devpriv->control_status + CALIBRATION_REG);
+		channel = 0;
+	} else {
+		outw(0, devpriv->control_status + CALIBRATION_REG);
+		channel = CR_CHAN(insn->chanspec);
+	}
+	// set mux limits and gain
+	bits = BEGIN_SCAN(channel) |
+		END_SCAN(channel) | GAIN_BITS(CR_RANGE(insn->chanspec));
+	// set unipolar/bipolar
+	if (CR_RANGE(insn->chanspec) & IS_UNIPOLAR)
+		bits |= UNIP;
+	// set singleended/differential
+	if (CR_AREF(insn->chanspec) != AREF_DIFF)
+		bits |= SE;
+	outw(bits, devpriv->control_status + ADCMUX_CONT);
+
+	/* clear fifo */
+	outw(0, devpriv->adc_fifo + ADCFIFOCLR);
+
+	/* convert n samples */
+	for (n = 0; n < insn->n; n++) {
+		/* trigger conversion */
+		outw(0, devpriv->adc_fifo + ADCDATA);
+
+		/* wait for conversion to end */
+		/* return -ETIMEDOUT if there is a timeout */
+		for (i = 0; i < timeout; i++) {
+			if (inw(devpriv->control_status + ADCMUX_CONT) & EOC)
+				break;
+		}
+		if (i == timeout)
+			return -ETIMEDOUT;
+
+		/* read data */
+		data[n] = inw(devpriv->adc_fifo + ADCDATA);
+	}
+
+	/* return the number of samples read/written */
+	return n;
+}
+
+static int ai_config_calibration_source(comedi_device * dev, lsampl_t * data)
+{
+	static const int num_calibration_sources = 8;
+	lsampl_t source = data[1];
+
+	if (source >= num_calibration_sources) {
+		printk("invalid calibration source: %i\n", source);
+		return -EINVAL;
+	}
+
+	devpriv->calibration_source = source;
+
+	return 2;
+}
+
+static int ai_config_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int id = data[0];
+
+	switch (id) {
+	case INSN_CONFIG_ALT_SOURCE:
+		return ai_config_calibration_source(dev, data);
+		break;
+	default:
+		return -EINVAL;
+		break;
+	}
+	return -EINVAL;
+}
+
+// analog output insn for pcidas-1000 and 1200 series
+static int cb_pcidas_ao_nofifo_winsn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int channel;
+	unsigned long flags;
+
+	// set channel and range
+	channel = CR_CHAN(insn->chanspec);
+	comedi_spin_lock_irqsave(&dev->spinlock, flags);
+	devpriv->ao_control_bits &=
+		~DAC_MODE_UPDATE_BOTH & ~DAC_RANGE_MASK(channel);
+	devpriv->ao_control_bits |=
+		DACEN | DAC_RANGE(channel, CR_RANGE(insn->chanspec));
+	outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR);
+	comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+
+	// remember value for readback
+	devpriv->ao_value[channel] = data[0];
+	// send data
+	outw(data[0], devpriv->ao_registers + DAC_DATA_REG(channel));
+
+	return 1;
+}
+
+// analog output insn for pcidas-1602 series
+static int cb_pcidas_ao_fifo_winsn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int channel;
+	unsigned long flags;
+
+	// clear dac fifo
+	outw(0, devpriv->ao_registers + DACFIFOCLR);
+
+	// set channel and range
+	channel = CR_CHAN(insn->chanspec);
+	comedi_spin_lock_irqsave(&dev->spinlock, flags);
+	devpriv->ao_control_bits &=
+		~DAC_CHAN_EN(0) & ~DAC_CHAN_EN(1) & ~DAC_RANGE_MASK(channel) &
+		~DAC_PACER_MASK;
+	devpriv->ao_control_bits |=
+		DACEN | DAC_RANGE(channel,
+		CR_RANGE(insn->chanspec)) | DAC_CHAN_EN(channel) | DAC_START;
+	outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR);
+	comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+
+	// remember value for readback
+	devpriv->ao_value[channel] = data[0];
+	// send data
+	outw(data[0], devpriv->ao_registers + DACDATA);
+
+	return 1;
+}
+
+// analog output readback insn
+// XXX loses track of analog output value back after an analog ouput command is executed
+static int cb_pcidas_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 eeprom_read_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	uint8_t nvram_data;
+	int retval;
+
+	retval = nvram_read(dev, CR_CHAN(insn->chanspec), &nvram_data);
+	if (retval < 0)
+		return retval;
+
+	data[0] = nvram_data;
+
+	return 1;
+}
+
+static int caldac_write_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	const unsigned int channel = CR_CHAN(insn->chanspec);
+
+	return caldac_8800_write(dev, channel, data[0]);
+}
+
+static int caldac_read_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	data[0] = devpriv->caldac_value[CR_CHAN(insn->chanspec)];
+
+	return 1;
+}
+
+/* 1602/16 pregain offset */
+static int dac08_write(comedi_device * dev, lsampl_t value)
+{
+	if (devpriv->dac08_value == value)
+		return 1;
+
+	devpriv->dac08_value = value;
+
+	outw(cal_enable_bits(dev) | (value & 0xff),
+		devpriv->control_status + CALIBRATION_REG);
+	comedi_udelay(1);
+	outw(cal_enable_bits(dev) | SELECT_DAC08_BIT | (value & 0xff),
+		devpriv->control_status + CALIBRATION_REG);
+	comedi_udelay(1);
+	outw(cal_enable_bits(dev) | (value & 0xff),
+		devpriv->control_status + CALIBRATION_REG);
+	comedi_udelay(1);
+
+	return 1;
+}
+
+static int dac08_write_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	return dac08_write(dev, data[0]);
+}
+
+static int dac08_read_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	data[0] = devpriv->dac08_value;
+
+	return 1;
+}
+
+static int cb_pcidas_trimpot_write(comedi_device * dev,
+	unsigned int channel, lsampl_t value)
+{
+	if (devpriv->trimpot_value[channel] == value)
+		return 1;
+
+	devpriv->trimpot_value[channel] = value;
+	switch (thisboard->trimpot) {
+	case AD7376:
+		trimpot_7376_write(dev, value);
+		break;
+	case AD8402:
+		trimpot_8402_write(dev, channel, value);
+		break;
+	default:
+		comedi_error(dev, "driver bug?");
+		return -1;
+		break;
+	}
+
+	return 1;
+}
+
+static int trimpot_write_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	unsigned int channel = CR_CHAN(insn->chanspec);
+
+	return cb_pcidas_trimpot_write(dev, channel, data[0]);
+}
+
+static int trimpot_read_insn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	unsigned int channel = CR_CHAN(insn->chanspec);
+
+	data[0] = devpriv->trimpot_value[channel];
+
+	return 1;
+}
+
+static int cb_pcidas_ai_cmdtest(comedi_device * dev, comedi_subdevice * s,
+	comedi_cmd * cmd)
+{
+	int err = 0;
+	int tmp;
+	int i, gain, start_chan;
+
+	/* 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 | TRIG_EXT;
+	if (!cmd->start_src || tmp != cmd->start_src)
+		err++;
+
+	tmp = cmd->scan_begin_src;
+	cmd->scan_begin_src &= TRIG_FOLLOW | 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_NOW | 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 */
+
+	if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT)
+		err++;
+	if (cmd->scan_begin_src != TRIG_FOLLOW &&
+		cmd->scan_begin_src != TRIG_TIMER &&
+		cmd->scan_begin_src != TRIG_EXT)
+		err++;
+	if (cmd->convert_src != TRIG_TIMER &&
+		cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_NOW)
+		err++;
+	if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
+		err++;
+
+	// make sure trigger sources are compatible with each other
+	if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW)
+		err++;
+	if (cmd->scan_begin_src != TRIG_FOLLOW && cmd->convert_src != TRIG_NOW)
+		err++;
+	if (cmd->start_src == TRIG_EXT &&
+		(cmd->convert_src == TRIG_EXT
+			|| cmd->scan_begin_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++;
+	}
+
+	if (cmd->scan_begin_src == TRIG_TIMER) {
+		if (cmd->scan_begin_arg <
+			thisboard->ai_speed * cmd->chanlist_len) {
+			cmd->scan_begin_arg =
+				thisboard->ai_speed * cmd->chanlist_len;
+			err++;
+		}
+	}
+	if (cmd->convert_src == TRIG_TIMER) {
+		if (cmd->convert_arg < thisboard->ai_speed) {
+			cmd->convert_arg = thisboard->ai_speed;
+			err++;
+		}
+	}
+
+	if (cmd->scan_end_arg != cmd->chanlist_len) {
+		cmd->scan_end_arg = cmd->chanlist_len;
+		err++;
+	}
+	if (cmd->stop_src == TRIG_NONE) {
+		/* 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;
+		i8253_cascade_ns_to_timer_2div(TIMER_BASE,
+			&(devpriv->divisor1), &(devpriv->divisor2),
+			&(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;
+		i8253_cascade_ns_to_timer_2div(TIMER_BASE,
+			&(devpriv->divisor1), &(devpriv->divisor2),
+			&(cmd->convert_arg), cmd->flags & TRIG_ROUND_MASK);
+		if (tmp != cmd->convert_arg)
+			err++;
+	}
+
+	if (err)
+		return 4;
+
+	// check channel/gain list against card's limitations
+	if (cmd->chanlist) {
+		gain = CR_RANGE(cmd->chanlist[0]);
+		start_chan = CR_CHAN(cmd->chanlist[0]);
+		for (i = 1; i < cmd->chanlist_len; i++) {
+			if (CR_CHAN(cmd->chanlist[i]) !=
+				(start_chan + i) % s->n_chan) {
+				comedi_error(dev,
+					"entries in chanlist must be consecutive channels, counting upwards\n");
+				err++;
+			}
+			if (CR_RANGE(cmd->chanlist[i]) != gain) {
+				comedi_error(dev,
+					"entries in chanlist must all have the same gain\n");
+				err++;
+			}
+		}
+	}
+
+	if (err)
+		return 5;
+
+	return 0;
+}
+
+static int cb_pcidas_ai_cmd(comedi_device * dev, comedi_subdevice * s)
+{
+	comedi_async *async = s->async;
+	comedi_cmd *cmd = &async->cmd;
+	unsigned int bits;
+	unsigned long flags;
+
+	// make sure CAL_EN_BIT is disabled
+	outw(0, devpriv->control_status + CALIBRATION_REG);
+	// initialize before settings pacer source and count values
+	outw(0, devpriv->control_status + TRIG_CONTSTAT);
+	// clear fifo
+	outw(0, devpriv->adc_fifo + ADCFIFOCLR);
+
+	// set mux limits, gain and pacer source
+	bits = BEGIN_SCAN(CR_CHAN(cmd->chanlist[0])) |
+		END_SCAN(CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1])) |
+		GAIN_BITS(CR_RANGE(cmd->chanlist[0]));
+	// set unipolar/bipolar
+	if (CR_RANGE(cmd->chanlist[0]) & IS_UNIPOLAR)
+		bits |= UNIP;
+	// set singleended/differential
+	if (CR_AREF(cmd->chanlist[0]) != AREF_DIFF)
+		bits |= SE;
+	// set pacer source
+	if (cmd->convert_src == TRIG_EXT || cmd->scan_begin_src == TRIG_EXT)
+		bits |= PACER_EXT_RISE;
+	else
+		bits |= PACER_INT;
+	outw(bits, devpriv->control_status + ADCMUX_CONT);
+
+#ifdef CB_PCIDAS_DEBUG
+	rt_printk("comedi: sent 0x%x to adcmux control\n", bits);
+#endif
+
+	// load counters
+	if (cmd->convert_src == TRIG_TIMER)
+		cb_pcidas_load_counters(dev, &cmd->convert_arg,
+			cmd->flags & TRIG_ROUND_MASK);
+	else if (cmd->scan_begin_src == TRIG_TIMER)
+		cb_pcidas_load_counters(dev, &cmd->scan_begin_arg,
+			cmd->flags & TRIG_ROUND_MASK);
+
+	// set number of conversions
+	if (cmd->stop_src == TRIG_COUNT) {
+		devpriv->count = cmd->chanlist_len * cmd->stop_arg;
+	}
+	// enable interrupts
+	comedi_spin_lock_irqsave(&dev->spinlock, flags);
+	devpriv->adc_fifo_bits |= INTE;
+	devpriv->adc_fifo_bits &= ~INT_MASK;
+	if (cmd->flags & TRIG_WAKE_EOS) {
+		if (cmd->convert_src == TRIG_NOW && cmd->chanlist_len > 1)
+			devpriv->adc_fifo_bits |= INT_EOS;	// interrupt end of burst
+		else
+			devpriv->adc_fifo_bits |= INT_FNE;	// interrupt fifo not empty
+	} else {
+		devpriv->adc_fifo_bits |= INT_FHF;	//interrupt fifo half full
+	}
+#ifdef CB_PCIDAS_DEBUG
+	rt_printk("comedi: adc_fifo_bits are 0x%x\n", devpriv->adc_fifo_bits);
+#endif
+	// enable (and clear) interrupts
+	outw(devpriv->adc_fifo_bits | EOAI | INT | LADFUL,
+		devpriv->control_status + INT_ADCFIFO);
+	comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+
+	// set start trigger and burst mode
+	bits = 0;
+	if (cmd->start_src == TRIG_NOW)
+		bits |= SW_TRIGGER;
+	else if (cmd->start_src == TRIG_EXT)
+		bits |= EXT_TRIGGER | TGEN | XTRCL;
+	else {
+		comedi_error(dev, "bug!");
+		return -1;
+	}
+	if (cmd->convert_src == TRIG_NOW && cmd->chanlist_len > 1)
+		bits |= BURSTE;
+	outw(bits, devpriv->control_status + TRIG_CONTSTAT);
+#ifdef CB_PCIDAS_DEBUG
+	rt_printk("comedi: sent 0x%x to trig control\n", bits);
+#endif
+
+	return 0;
+}
+
+static int cb_pcidas_ao_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_INT;
+	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_NOW;
+	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 */
+
+	if (cmd->scan_begin_src != TRIG_TIMER &&
+		cmd->scan_begin_src != TRIG_EXT)
+		err++;
+	if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
+		err++;
+
+	if (err)
+		return 2;
+
+	/* step 3: make sure arguments are trivially compatible */
+
+	if (cmd->start_arg != 0) {
+		cmd->start_arg = 0;
+		err++;
+	}
+
+	if (cmd->scan_begin_src == TRIG_TIMER) {
+		if (cmd->scan_begin_arg < thisboard->ao_scan_speed) {
+			cmd->scan_begin_arg = thisboard->ao_scan_speed;
+			err++;
+		}
+	}
+
+	if (cmd->scan_end_arg != cmd->chanlist_len) {
+		cmd->scan_end_arg = cmd->chanlist_len;
+		err++;
+	}
+	if (cmd->stop_src == TRIG_NONE) {
+		/* 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;
+		i8253_cascade_ns_to_timer_2div(TIMER_BASE,
+			&(devpriv->ao_divisor1), &(devpriv->ao_divisor2),
+			&(cmd->scan_begin_arg), cmd->flags & TRIG_ROUND_MASK);
+		if (tmp != cmd->scan_begin_arg)
+			err++;
+	}
+
+	if (err)
+		return 4;
+
+	// check channel/gain list against card's limitations
+	if (cmd->chanlist && cmd->chanlist_len > 1) {
+		if (CR_CHAN(cmd->chanlist[0]) != 0 ||
+			CR_CHAN(cmd->chanlist[1]) != 1) {
+			comedi_error(dev,
+				"channels must be ordered channel 0, channel 1 in chanlist\n");
+			err++;
+		}
+	}
+
+	if (err)
+		return 5;
+
+	return 0;
+}
+
+static int cb_pcidas_ao_cmd(comedi_device * dev, comedi_subdevice * s)
+{
+	comedi_async *async = s->async;
+	comedi_cmd *cmd = &async->cmd;
+	unsigned int i;
+	unsigned long flags;
+
+	// set channel limits, gain
+	comedi_spin_lock_irqsave(&dev->spinlock, flags);
+	for (i = 0; i < cmd->chanlist_len; i++) {
+		// enable channel
+		devpriv->ao_control_bits |=
+			DAC_CHAN_EN(CR_CHAN(cmd->chanlist[i]));
+		// set range
+		devpriv->ao_control_bits |= DAC_RANGE(CR_CHAN(cmd->chanlist[i]),
+			CR_RANGE(cmd->chanlist[i]));
+	}
+
+	// disable analog out before settings pacer source and count values
+	outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR);
+	comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+
+	// clear fifo
+	outw(0, devpriv->ao_registers + DACFIFOCLR);
+
+	// load counters
+	if (cmd->scan_begin_src == TRIG_TIMER) {
+		i8253_cascade_ns_to_timer_2div(TIMER_BASE,
+			&(devpriv->ao_divisor1), &(devpriv->ao_divisor2),
+			&(cmd->scan_begin_arg), cmd->flags);
+
+		/* Write the values of ctr1 and ctr2 into counters 1 and 2 */
+		i8254_load(devpriv->pacer_counter_dio + DAC8254, 0, 1,
+			devpriv->ao_divisor1, 2);
+		i8254_load(devpriv->pacer_counter_dio + DAC8254, 0, 2,
+			devpriv->ao_divisor2, 2);
+	}
+	// set number of conversions
+	if (cmd->stop_src == TRIG_COUNT) {
+		devpriv->ao_count = cmd->chanlist_len * cmd->stop_arg;
+	}
+	// set pacer source
+	comedi_spin_lock_irqsave(&dev->spinlock, flags);
+	switch (cmd->scan_begin_src) {
+	case TRIG_TIMER:
+		devpriv->ao_control_bits |= DAC_PACER_INT;
+		break;
+	case TRIG_EXT:
+		devpriv->ao_control_bits |= DAC_PACER_EXT_RISE;
+		break;
+	default:
+		comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+		comedi_error(dev, "error setting dac pacer source");
+		return -1;
+		break;
+	}
+	comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+
+	async->inttrig = cb_pcidas_ao_inttrig;
+
+	return 0;
+}
+
+static int cb_pcidas_ao_inttrig(comedi_device * dev, comedi_subdevice * s,
+	unsigned int trig_num)
+{
+	unsigned int num_bytes, num_points = thisboard->fifo_size;
+	comedi_async *async = s->async;
+	comedi_cmd *cmd = &s->async->cmd;
+	unsigned long flags;
+
+	if (trig_num != 0)
+		return -EINVAL;
+
+	// load up fifo
+	if (cmd->stop_src == TRIG_COUNT && devpriv->ao_count < num_points)
+		num_points = devpriv->ao_count;
+
+	num_bytes = cfc_read_array_from_buffer(s, devpriv->ao_buffer,
+		num_points * sizeof(sampl_t));
+	num_points = num_bytes / sizeof(sampl_t);
+
+	if (cmd->stop_src == TRIG_COUNT) {
+		devpriv->ao_count -= num_points;
+	}
+	// write data to board's fifo
+	outsw(devpriv->ao_registers + DACDATA, devpriv->ao_buffer, num_bytes);
+
+	// enable dac half-full and empty interrupts
+	comedi_spin_lock_irqsave(&dev->spinlock, flags);
+	devpriv->adc_fifo_bits |= DAEMIE | DAHFIE;
+#ifdef CB_PCIDAS_DEBUG
+	rt_printk("comedi: adc_fifo_bits are 0x%x\n", devpriv->adc_fifo_bits);
+#endif
+	// enable and clear interrupts
+	outw(devpriv->adc_fifo_bits | DAEMI | DAHFI,
+		devpriv->control_status + INT_ADCFIFO);
+
+	// start dac
+	devpriv->ao_control_bits |= DAC_START | DACEN | DAC_EMPTY;
+	outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR);
+#ifdef CB_PCIDAS_DEBUG
+	rt_printk("comedi: sent 0x%x to dac control\n",
+		devpriv->ao_control_bits);
+#endif
+	comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+
+	async->inttrig = NULL;
+
+	return 0;
+}
+
+static irqreturn_t cb_pcidas_interrupt(int irq, void *d PT_REGS_ARG)
+{
+	comedi_device *dev = (comedi_device *) d;
+	comedi_subdevice *s = dev->read_subdev;
+	comedi_async *async;
+	int status, s5933_status;
+	int half_fifo = thisboard->fifo_size / 2;
+	unsigned int num_samples, i;
+	static const int timeout = 10000;
+	unsigned long flags;
+
+	if (dev->attached == 0) {
+		return IRQ_NONE;
+	}
+
+	async = s->async;
+	async->events = 0;
+
+	s5933_status = inl(devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+#ifdef CB_PCIDAS_DEBUG
+	rt_printk("intcsr 0x%x\n", s5933_status);
+	rt_printk("mbef 0x%x\n", inl(devpriv->s5933_config + AMCC_OP_REG_MBEF));
+#endif
+
+	if ((INTCSR_INTR_ASSERTED & s5933_status) == 0)
+		return IRQ_NONE;
+
+	// make sure mailbox 4 is empty
+	inl_p(devpriv->s5933_config + AMCC_OP_REG_IMB4);
+	// clear interrupt on amcc s5933
+	outl(devpriv->s5933_intcsr_bits | INTCSR_INBOX_INTR_STATUS,
+		devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+
+	status = inw(devpriv->control_status + INT_ADCFIFO);
+#ifdef CB_PCIDAS_DEBUG
+	if ((status & (INT | EOAI | LADFUL | DAHFI | DAEMI)) == 0) {
+		comedi_error(dev, "spurious interrupt");
+	}
+#endif
+
+	// check for analog output interrupt
+	if (status & (DAHFI | DAEMI)) {
+		handle_ao_interrupt(dev, status);
+	}
+	// check for analog input interrupts
+	// if fifo half-full
+	if (status & ADHFI) {
+		// read data
+		num_samples = half_fifo;
+		if (async->cmd.stop_src == TRIG_COUNT &&
+			num_samples > devpriv->count) {
+			num_samples = devpriv->count;
+		}
+		insw(devpriv->adc_fifo + ADCDATA, devpriv->ai_buffer,
+			num_samples);
+		cfc_write_array_to_buffer(s, devpriv->ai_buffer,
+			num_samples * sizeof(sampl_t));
+		devpriv->count -= num_samples;
+		if (async->cmd.stop_src == TRIG_COUNT && devpriv->count == 0) {
+			async->events |= COMEDI_CB_EOA;
+			cb_pcidas_cancel(dev, s);
+		}
+		// clear half-full interrupt latch
+		comedi_spin_lock_irqsave(&dev->spinlock, flags);
+		outw(devpriv->adc_fifo_bits | INT,
+			devpriv->control_status + INT_ADCFIFO);
+		comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+		// else if fifo not empty
+	} else if (status & (ADNEI | EOBI)) {
+		for (i = 0; i < timeout; i++) {
+			// break if fifo is empty
+			if ((ADNE & inw(devpriv->control_status +
+						INT_ADCFIFO)) == 0)
+				break;
+			cfc_write_to_buffer(s, inw(devpriv->adc_fifo));
+			if (async->cmd.stop_src == TRIG_COUNT && --devpriv->count == 0) {	/* end of acquisition */
+				cb_pcidas_cancel(dev, s);
+				async->events |= COMEDI_CB_EOA;
+				break;
+			}
+		}
+		// clear not-empty interrupt latch
+		comedi_spin_lock_irqsave(&dev->spinlock, flags);
+		outw(devpriv->adc_fifo_bits | INT,
+			devpriv->control_status + INT_ADCFIFO);
+		comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+	} else if (status & EOAI) {
+		comedi_error(dev,
+			"bug! encountered end of aquisition interrupt?");
+		// clear EOA interrupt latch
+		comedi_spin_lock_irqsave(&dev->spinlock, flags);
+		outw(devpriv->adc_fifo_bits | EOAI,
+			devpriv->control_status + INT_ADCFIFO);
+		comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+	}
+	//check for fifo overflow
+	if (status & LADFUL) {
+		comedi_error(dev, "fifo overflow");
+		// clear overflow interrupt latch
+		comedi_spin_lock_irqsave(&dev->spinlock, flags);
+		outw(devpriv->adc_fifo_bits | LADFUL,
+			devpriv->control_status + INT_ADCFIFO);
+		comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+		cb_pcidas_cancel(dev, s);
+		async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+	}
+
+	comedi_event(dev, s);
+
+	return IRQ_HANDLED;
+}
+
+static void handle_ao_interrupt(comedi_device * dev, unsigned int status)
+{
+	comedi_subdevice *s = dev->write_subdev;
+	comedi_async *async = s->async;
+	comedi_cmd *cmd = &async->cmd;
+	unsigned int half_fifo = thisboard->fifo_size / 2;
+	unsigned int num_points;
+	unsigned int flags;
+
+	async->events = 0;
+
+	if (status & DAEMI) {
+		// clear dac empty interrupt latch
+		comedi_spin_lock_irqsave(&dev->spinlock, flags);
+		outw(devpriv->adc_fifo_bits | DAEMI,
+			devpriv->control_status + INT_ADCFIFO);
+		comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+		if (inw(devpriv->ao_registers + DAC_CSR) & DAC_EMPTY) {
+			if (cmd->stop_src == TRIG_NONE ||
+				(cmd->stop_src == TRIG_COUNT
+					&& devpriv->ao_count)) {
+				comedi_error(dev, "dac fifo underflow");
+				cb_pcidas_ao_cancel(dev, s);
+				async->events |= COMEDI_CB_ERROR;
+			}
+			async->events |= COMEDI_CB_EOA;
+		}
+	} else if (status & DAHFI) {
+		unsigned int num_bytes;
+
+		// figure out how many points we are writing to fifo
+		num_points = half_fifo;
+		if (cmd->stop_src == TRIG_COUNT &&
+			devpriv->ao_count < num_points)
+			num_points = devpriv->ao_count;
+		num_bytes =
+			cfc_read_array_from_buffer(s, devpriv->ao_buffer,
+			num_points * sizeof(sampl_t));
+		num_points = num_bytes / sizeof(sampl_t);
+
+		if (async->cmd.stop_src == TRIG_COUNT) {
+			devpriv->ao_count -= num_points;
+		}
+		// write data to board's fifo
+		outsw(devpriv->ao_registers + DACDATA, devpriv->ao_buffer,
+			num_points);
+		// clear half-full interrupt latch
+		comedi_spin_lock_irqsave(&dev->spinlock, flags);
+		outw(devpriv->adc_fifo_bits | DAHFI,
+			devpriv->control_status + INT_ADCFIFO);
+		comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+	}
+
+	comedi_event(dev, s);
+}
+
+// cancel analog input command
+static int cb_pcidas_cancel(comedi_device * dev, comedi_subdevice * s)
+{
+	unsigned long flags;
+
+	comedi_spin_lock_irqsave(&dev->spinlock, flags);
+	// disable interrupts
+	devpriv->adc_fifo_bits &= ~INTE & ~EOAIE;
+	outw(devpriv->adc_fifo_bits, devpriv->control_status + INT_ADCFIFO);
+	comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+
+	// disable start trigger source and burst mode
+	outw(0, devpriv->control_status + TRIG_CONTSTAT);
+	// software pacer source
+	outw(0, devpriv->control_status + ADCMUX_CONT);
+
+	return 0;
+}
+
+// cancel analog output command
+static int cb_pcidas_ao_cancel(comedi_device * dev, comedi_subdevice * s)
+{
+	unsigned long flags;
+
+	comedi_spin_lock_irqsave(&dev->spinlock, flags);
+	// disable interrupts
+	devpriv->adc_fifo_bits &= ~DAHFIE & ~DAEMIE;
+	outw(devpriv->adc_fifo_bits, devpriv->control_status + INT_ADCFIFO);
+
+	// disable output
+	devpriv->ao_control_bits &= ~DACEN & ~DAC_PACER_MASK;
+	outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR);
+	comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
+
+	return 0;
+}
+
+static void cb_pcidas_load_counters(comedi_device * dev, unsigned int *ns,
+	int rounding_flags)
+{
+	i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1),
+		&(devpriv->divisor2), ns, rounding_flags & TRIG_ROUND_MASK);
+
+	/* Write the values of ctr1 and ctr2 into counters 1 and 2 */
+	i8254_load(devpriv->pacer_counter_dio + ADC8254, 0, 1,
+		devpriv->divisor1, 2);
+	i8254_load(devpriv->pacer_counter_dio + ADC8254, 0, 2,
+		devpriv->divisor2, 2);
+}
+
+static void write_calibration_bitstream(comedi_device * dev,
+	unsigned int register_bits, unsigned int bitstream,
+	unsigned int bitstream_length)
+{
+	static const int write_delay = 1;
+	unsigned int bit;
+
+	for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) {
+		if (bitstream & bit)
+			register_bits |= SERIAL_DATA_IN_BIT;
+		else
+			register_bits &= ~SERIAL_DATA_IN_BIT;
+		comedi_udelay(write_delay);
+		outw(register_bits, devpriv->control_status + CALIBRATION_REG);
+	}
+}
+
+static int caldac_8800_write(comedi_device * dev, unsigned int address,
+	uint8_t value)
+{
+	static const int num_caldac_channels = 8;
+	static const int bitstream_length = 11;
+	unsigned int bitstream = ((address & 0x7) << 8) | value;
+	static const int caldac_8800_comedi_udelay = 1;
+
+	if (address >= num_caldac_channels) {
+		comedi_error(dev, "illegal caldac channel");
+		return -1;
+	}
+
+	if (value == devpriv->caldac_value[address])
+		return 1;
+
+	devpriv->caldac_value[address] = value;
+
+	write_calibration_bitstream(dev, cal_enable_bits(dev), bitstream,
+		bitstream_length);
+
+	comedi_udelay(caldac_8800_comedi_udelay);
+	outw(cal_enable_bits(dev) | SELECT_8800_BIT,
+		devpriv->control_status + CALIBRATION_REG);
+	comedi_udelay(caldac_8800_comedi_udelay);
+	outw(cal_enable_bits(dev), devpriv->control_status + CALIBRATION_REG);
+
+	return 1;
+}
+
+static int trimpot_7376_write(comedi_device * dev, uint8_t value)
+{
+	static const int bitstream_length = 7;
+	unsigned int bitstream = value & 0x7f;
+	unsigned int register_bits;
+	static const int ad7376_comedi_udelay = 1;
+
+	register_bits = cal_enable_bits(dev) | SELECT_TRIMPOT_BIT;
+	comedi_udelay(ad7376_comedi_udelay);
+	outw(register_bits, devpriv->control_status + CALIBRATION_REG);
+
+	write_calibration_bitstream(dev, register_bits, bitstream,
+		bitstream_length);
+
+	comedi_udelay(ad7376_comedi_udelay);
+	outw(cal_enable_bits(dev), devpriv->control_status + CALIBRATION_REG);
+
+	return 0;
+}
+
+/* For 1602/16 only
+ * ch 0 : adc gain
+ * ch 1 : adc postgain offset */
+static int trimpot_8402_write(comedi_device * dev, unsigned int channel,
+	uint8_t value)
+{
+	static const int bitstream_length = 10;
+	unsigned int bitstream = ((channel & 0x3) << 8) | (value & 0xff);
+	unsigned int register_bits;
+	static const int ad8402_comedi_udelay = 1;
+
+	register_bits = cal_enable_bits(dev) | SELECT_TRIMPOT_BIT;
+	comedi_udelay(ad8402_comedi_udelay);
+	outw(register_bits, devpriv->control_status + CALIBRATION_REG);
+
+	write_calibration_bitstream(dev, register_bits, bitstream,
+		bitstream_length);
+
+	comedi_udelay(ad8402_comedi_udelay);
+	outw(cal_enable_bits(dev), devpriv->control_status + CALIBRATION_REG);
+
+	return 0;
+}
+
+static int wait_for_nvram_ready(unsigned long s5933_base_addr)
+{
+	static const int timeout = 1000;
+	unsigned int i;
+
+	for (i = 0; i < timeout; i++) {
+		if ((inb(s5933_base_addr +
+					AMCC_OP_REG_MCSR_NVCMD) & MCSR_NV_BUSY)
+			== 0)
+			return 0;
+		comedi_udelay(1);
+	}
+	return -1;
+}
+
+static int nvram_read(comedi_device * dev, unsigned int address, uint8_t * data)
+{
+	unsigned long iobase = devpriv->s5933_config;
+
+	if (wait_for_nvram_ready(iobase) < 0)
+		return -ETIMEDOUT;
+
+	outb(MCSR_NV_ENABLE | MCSR_NV_LOAD_LOW_ADDR,
+		iobase + AMCC_OP_REG_MCSR_NVCMD);
+	outb(address & 0xff, iobase + AMCC_OP_REG_MCSR_NVDATA);
+	outb(MCSR_NV_ENABLE | MCSR_NV_LOAD_HIGH_ADDR,
+		iobase + AMCC_OP_REG_MCSR_NVCMD);
+	outb((address >> 8) & 0xff, iobase + AMCC_OP_REG_MCSR_NVDATA);
+	outb(MCSR_NV_ENABLE | MCSR_NV_READ, iobase + AMCC_OP_REG_MCSR_NVCMD);
+
+	if (wait_for_nvram_ready(iobase) < 0)
+		return -ETIMEDOUT;
+
+	*data = inb(iobase + AMCC_OP_REG_MCSR_NVDATA);
+
+	return 0;
+}
+
+/*
+ * A convenient macro that defines init_module() and cleanup_module(),
+ * as necessary.
+ */
+COMEDI_PCI_INITCLEANUP(driver_cb_pcidas, cb_pcidas_pci_table);
-- 
2.30.2