text: /*
   * comedi/drivers/cb_pcidas64.c
   * This is a driver for the ComputerBoards/MeasurementComputing PCI-DAS
   * 64xx, 60xx, and 4020 cards.
   *
   * Author:  Frank Mori Hess <fmhess@users.sourceforge.net>
   * Copyright (C) 2001, 2002 Frank Mori Hess
   *
   * Thanks also go to the following people:
   *
   * Steve Rosenbluth, for providing the source code for
   * his pci-das6402 driver, and source code for working QNX pci-6402
   * drivers by Greg Laird and Mariusz Bogacz.  None of the code was
   * used directly here, but it was useful as an additional source of
   * documentation on how to program the boards.
   *
   * John Sims, for much testing and feedback on pcidas-4020 support.
   *
   * 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.
   */
  
  /*
   * Driver: cb_pcidas64
   * Description: MeasurementComputing PCI-DAS64xx, 60XX, and 4020 series
   *   with the PLX 9080 PCI controller
   * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
   * Status: works
   * Updated: Fri, 02 Nov 2012 18:58:55 +0000
   * Devices: [Measurement Computing] PCI-DAS6402/16 (cb_pcidas64),
   *   PCI-DAS6402/12, PCI-DAS64/M1/16, PCI-DAS64/M2/16,
   *   PCI-DAS64/M3/16, PCI-DAS6402/16/JR, PCI-DAS64/M1/16/JR,
   *   PCI-DAS64/M2/16/JR, PCI-DAS64/M3/16/JR, PCI-DAS64/M1/14,
   *   PCI-DAS64/M2/14, PCI-DAS64/M3/14, PCI-DAS6013, PCI-DAS6014,
   *   PCI-DAS6023, PCI-DAS6025, PCI-DAS6030,
   *   PCI-DAS6031, PCI-DAS6032, PCI-DAS6033, PCI-DAS6034,
   *   PCI-DAS6035, PCI-DAS6036, PCI-DAS6040, PCI-DAS6052,
   *   PCI-DAS6070, PCI-DAS6071, PCI-DAS4020/12
   *
   * Configuration options:
   *   None.
   *
   * Manual attachment of PCI cards with the comedi_config utility is not
   * supported by this driver; they are attached automatically.
   *
   * These boards may be autocalibrated with the comedi_calibrate utility.
   *
   * To select the bnc trigger input on the 4020 (instead of the dio input),
   * specify a nonzero channel in the chanspec.  If you wish to use an external
   * master clock on the 4020, you may do so by setting the scan_begin_src
   * to TRIG_OTHER, and using an INSN_CONFIG_TIMER_1 configuration insn
   * to configure the divisor to use for the external clock.
   *
   * Some devices are not identified because the PCI device IDs are not yet
   * known. If you have such a board, please let the maintainers know.
   */
  
  /*
   * TODO:
   * make it return error if user attempts an ai command that uses the
   * external queue, and an ao command simultaneously user counter subdevice
   * there are a number of boards this driver will support when they are
   * fully released, but does not yet since the pci device id numbers
   * are not yet available.
   *
   * support prescaled 100khz clock for slow pacing (not available on 6000
   * series?)
   *
   * make ao fifo size adjustable like ai fifo
   */
  
  #include <linux/module.h>
  #include <linux/delay.h>
  #include <linux/interrupt.h>
  
  #include "../comedi_pci.h"
  
  #include "8255.h"
  #include "plx9080.h"
  
  #define TIMER_BASE 25		/*  40MHz master clock */
  /*
   * 100kHz 'prescaled' clock for slow acquisition,
   * maybe I'll support this someday
   */
  #define PRESCALED_TIMER_BASE	10000
  #define DMA_BUFFER_SIZE		0x1000
  #define DAC_FIFO_SIZE		0x2000
  
  /* maximum value that can be loaded into board's 24-bit counters */
  static const int max_counter_value = 0xffffff;
  
  /* PCI-DAS64xxx base addresses */
  
  /* devpriv->main_iobase registers */
  enum write_only_registers {
  	INTR_ENABLE_REG = 0x0,		/* interrupt enable register */
  	HW_CONFIG_REG = 0x2,		/* hardware config register */
  	DAQ_SYNC_REG = 0xc,
  	DAQ_ATRIG_LOW_4020_REG = 0xc,
  	ADC_CONTROL0_REG = 0x10,	/* adc control register 0 */
  	ADC_CONTROL1_REG = 0x12,	/* adc control register 1 */
  	CALIBRATION_REG = 0x14,
  	/* lower 16 bits of adc sample interval counter */
  	ADC_SAMPLE_INTERVAL_LOWER_REG = 0x16,
  	/* upper 8 bits of adc sample interval counter */
  	ADC_SAMPLE_INTERVAL_UPPER_REG = 0x18,
  	/* lower 16 bits of delay interval counter */
  	ADC_DELAY_INTERVAL_LOWER_REG = 0x1a,
  	/* upper 8 bits of delay interval counter */
  	ADC_DELAY_INTERVAL_UPPER_REG = 0x1c,
  	/* lower 16 bits of hardware conversion/scan counter */
  	ADC_COUNT_LOWER_REG = 0x1e,
  	/* upper 8 bits of hardware conversion/scan counter */
  	ADC_COUNT_UPPER_REG = 0x20,
  	ADC_START_REG = 0x22,	/* software trigger to start acquisition */
  	ADC_CONVERT_REG = 0x24,	/* initiates single conversion */
  	ADC_QUEUE_CLEAR_REG = 0x26,	/* clears adc queue */
  	ADC_QUEUE_LOAD_REG = 0x28,	/* loads adc queue */
  	ADC_BUFFER_CLEAR_REG = 0x2a,
  	/* high channel for internal queue, use adc_chan_bits() inline above */
  	ADC_QUEUE_HIGH_REG = 0x2c,
  	DAC_CONTROL0_REG = 0x50,	/* dac control register 0 */
  	DAC_CONTROL1_REG = 0x52,	/* dac control register 0 */
  	/* lower 16 bits of dac sample interval counter */
  	DAC_SAMPLE_INTERVAL_LOWER_REG = 0x54,
  	/* upper 8 bits of dac sample interval counter */
  	DAC_SAMPLE_INTERVAL_UPPER_REG = 0x56,
  	DAC_SELECT_REG = 0x60,
  	DAC_START_REG = 0x64,
  	DAC_BUFFER_CLEAR_REG = 0x66,	/* clear dac buffer */
  };
  
  static inline unsigned int dac_convert_reg(unsigned int channel)
  {
  	return 0x70 + (2 * (channel & 0x1));
  }
  
  static inline unsigned int dac_lsb_4020_reg(unsigned int channel)
  {
  	return 0x70 + (4 * (channel & 0x1));
  }
  
  static inline unsigned int dac_msb_4020_reg(unsigned int channel)
  {
  	return 0x72 + (4 * (channel & 0x1));
  }
  
  enum read_only_registers {
  	/*
  	 * hardware status register,
  	 * reading this apparently clears pending interrupts as well
  	 */
  	HW_STATUS_REG = 0x0,
  	PIPE1_READ_REG = 0x4,
  	ADC_READ_PNTR_REG = 0x8,
  	LOWER_XFER_REG = 0x10,
  	ADC_WRITE_PNTR_REG = 0xc,
  	PREPOST_REG = 0x14,
  };
  
  enum read_write_registers {
  	I8255_4020_REG = 0x48,	/* 8255 offset, for 4020 only */
  	/* external channel/gain queue, uses same bits as ADC_QUEUE_LOAD_REG */
  	ADC_QUEUE_FIFO_REG = 0x100,
  	ADC_FIFO_REG = 0x200,	/* adc data fifo */
  	/* dac data fifo, has weird interactions with external channel queue */
  	DAC_FIFO_REG = 0x300,
  };
  
  /* dev->mmio registers */
  enum dio_counter_registers {
  	DIO_8255_OFFSET = 0x0,
  	DO_REG = 0x20,
  	DI_REG = 0x28,
  	DIO_DIRECTION_60XX_REG = 0x40,
  	DIO_DATA_60XX_REG = 0x48,
  };
  
  /* bit definitions for write-only registers */
  
  enum intr_enable_contents {
  	ADC_INTR_SRC_MASK = 0x3,	/* adc interrupt source mask */
  	ADC_INTR_QFULL_BITS = 0x0,	/* interrupt fifo quarter full */
  	ADC_INTR_EOC_BITS = 0x1,	/* interrupt end of conversion */
  	ADC_INTR_EOSCAN_BITS = 0x2,	/* interrupt end of scan */
  	ADC_INTR_EOSEQ_BITS = 0x3,	/* interrupt end of sequence mask */
  	EN_ADC_INTR_SRC_BIT = 0x4,	/* enable adc interrupt source */
  	EN_ADC_DONE_INTR_BIT = 0x8,	/* enable adc acquisition done intr */
  	DAC_INTR_SRC_MASK = 0x30,
  	DAC_INTR_QEMPTY_BITS = 0x0,
  	DAC_INTR_HIGH_CHAN_BITS = 0x10,
  	EN_DAC_INTR_SRC_BIT = 0x40,	/* enable dac interrupt source */
  	EN_DAC_DONE_INTR_BIT = 0x80,
  	EN_ADC_ACTIVE_INTR_BIT = 0x200,	/* enable adc active interrupt */
  	EN_ADC_STOP_INTR_BIT = 0x400,	/* enable adc stop trigger interrupt */
  	EN_DAC_ACTIVE_INTR_BIT = 0x800,	/* enable dac active interrupt */
  	EN_DAC_UNDERRUN_BIT = 0x4000,	/* enable dac underrun status bit */
  	EN_ADC_OVERRUN_BIT = 0x8000,	/* enable adc overrun status bit */
  };
  
  enum hw_config_contents {
  	MASTER_CLOCK_4020_MASK = 0x3,	/* master clock source mask for 4020 */
  	INTERNAL_CLOCK_4020_BITS = 0x1,	/* use 40 MHz internal master clock */
  	BNC_CLOCK_4020_BITS = 0x2,	/* use BNC input for master clock */
  	EXT_CLOCK_4020_BITS = 0x3,	/* use dio input for master clock */
  	EXT_QUEUE_BIT = 0x200,		/* use external channel/gain queue */
  	/* use 225 nanosec strobe when loading dac instead of 50 nanosec */
  	SLOW_DAC_BIT = 0x400,
  	/*
  	 * bit with unknown function yet given as default value in pci-das64
  	 * manual
  	 */
  	HW_CONFIG_DUMMY_BITS = 0x2000,
  	/* bit selects channels 1/0 for analog input/output, otherwise 0/1 */
  	DMA_CH_SELECT_BIT = 0x8000,
  	FIFO_SIZE_REG = 0x4,		/* allows adjustment of fifo sizes */
  	DAC_FIFO_SIZE_MASK = 0xff00,	/* bits that set dac fifo size */
  	DAC_FIFO_BITS = 0xf800,		/* 8k sample ao fifo */
  };
  
  enum daq_atrig_low_4020_contents {
  	/* use trig/ext clk bnc input for analog gate signal */
  	EXT_AGATE_BNC_BIT = 0x8000,
  	/* use trig/ext clk bnc input for external stop trigger signal */
  	EXT_STOP_TRIG_BNC_BIT = 0x4000,
  	/* use trig/ext clk bnc input for external start trigger signal */
  	EXT_START_TRIG_BNC_BIT = 0x2000,
  };
  
  static inline u16 analog_trig_low_threshold_bits(u16 threshold)
  {
  	return threshold & 0xfff;
  }
  
  enum adc_control0_contents {
  	ADC_GATE_SRC_MASK = 0x3,	/* bits that select gate */
  	ADC_SOFT_GATE_BITS = 0x1,	/* software gate */
  	ADC_EXT_GATE_BITS = 0x2,	/* external digital gate */
  	ADC_ANALOG_GATE_BITS = 0x3,	/* analog level gate */
  	/* level-sensitive gate (for digital) */
  	ADC_GATE_LEVEL_BIT = 0x4,
  	ADC_GATE_POLARITY_BIT = 0x8,	/* gate active low */
  	ADC_START_TRIG_SOFT_BITS = 0x10,
  	ADC_START_TRIG_EXT_BITS = 0x20,
  	ADC_START_TRIG_ANALOG_BITS = 0x30,
  	ADC_START_TRIG_MASK = 0x30,
  	ADC_START_TRIG_FALLING_BIT = 0x40,	/* trig 1 uses falling edge */
  	/* external pacing uses falling edge */
  	ADC_EXT_CONV_FALLING_BIT = 0x800,
  	/* enable hardware scan counter */
  	ADC_SAMPLE_COUNTER_EN_BIT = 0x1000,
  	ADC_DMA_DISABLE_BIT = 0x4000,	/* disables dma */
  	ADC_ENABLE_BIT = 0x8000,	/* master adc enable */
  };
  
  enum adc_control1_contents {
  	/* should be set for boards with > 16 channels */
  	ADC_QUEUE_CONFIG_BIT = 0x1,
  	CONVERT_POLARITY_BIT = 0x10,
  	EOC_POLARITY_BIT = 0x20,
  	ADC_SW_GATE_BIT = 0x40,		/* software gate of adc */
  	ADC_DITHER_BIT = 0x200,		/* turn on extra noise for dithering */
  	RETRIGGER_BIT = 0x800,
  	ADC_LO_CHANNEL_4020_MASK = 0x300,
  	ADC_HI_CHANNEL_4020_MASK = 0xc00,
  	TWO_CHANNEL_4020_BITS = 0x1000,		/* two channel mode for 4020 */
  	FOUR_CHANNEL_4020_BITS = 0x2000,	/* four channel mode for 4020 */
  	CHANNEL_MODE_4020_MASK = 0x3000,
  	ADC_MODE_MASK = 0xf000,
  };
  
  static inline u16 adc_lo_chan_4020_bits(unsigned int channel)
  {
  	return (channel & 0x3) << 8;
  };
  
  static inline u16 adc_hi_chan_4020_bits(unsigned int channel)
  {
  	return (channel & 0x3) << 10;
  };
  
  static inline u16 adc_mode_bits(unsigned int mode)
  {
  	return (mode & 0xf) << 12;
  };
  
  enum calibration_contents {
  	SELECT_8800_BIT = 0x1,
  	SELECT_8402_64XX_BIT = 0x2,
  	SELECT_1590_60XX_BIT = 0x2,
  	CAL_EN_64XX_BIT = 0x40,		/* calibration enable for 64xx series */
  	SERIAL_DATA_IN_BIT = 0x80,
  	SERIAL_CLOCK_BIT = 0x100,
  	CAL_EN_60XX_BIT = 0x200,	/* calibration enable for 60xx series */
  	CAL_GAIN_BIT = 0x800,
  };
  
  /*
   * calibration sources for 6025 are:
   *  0 : ground
   *  1 : 10V
   *  2 : 5V
   *  3 : 0.5V
   *  4 : 0.05V
   *  5 : ground
   *  6 : dac channel 0
   *  7 : dac channel 1
   */
  
  static inline u16 adc_src_bits(unsigned int source)
  {
  	return (source & 0xf) << 3;
  };
  
  static inline u16 adc_convert_chan_4020_bits(unsigned int channel)
  {
  	return (channel & 0x3) << 8;
  };
  
  enum adc_queue_load_contents {
  	UNIP_BIT = 0x800,		/* unipolar/bipolar bit */
  	ADC_SE_DIFF_BIT = 0x1000,	/* single-ended/ differential bit */
  	/* non-referenced single-ended (common-mode input) */
  	ADC_COMMON_BIT = 0x2000,
  	QUEUE_EOSEQ_BIT = 0x4000,	/* queue end of sequence */
  	QUEUE_EOSCAN_BIT = 0x8000,	/* queue end of scan */
  };
  
  static inline u16 adc_chan_bits(unsigned int channel)
  {
  	return channel & 0x3f;
  };
  
  enum dac_control0_contents {
  	DAC_ENABLE_BIT = 0x8000,	/* dac controller enable bit */
  	DAC_CYCLIC_STOP_BIT = 0x4000,
  	DAC_WAVEFORM_MODE_BIT = 0x100,
  	DAC_EXT_UPDATE_FALLING_BIT = 0x80,
  	DAC_EXT_UPDATE_ENABLE_BIT = 0x40,
  	WAVEFORM_TRIG_MASK = 0x30,
  	WAVEFORM_TRIG_DISABLED_BITS = 0x0,
  	WAVEFORM_TRIG_SOFT_BITS = 0x10,
  	WAVEFORM_TRIG_EXT_BITS = 0x20,
  	WAVEFORM_TRIG_ADC1_BITS = 0x30,
  	WAVEFORM_TRIG_FALLING_BIT = 0x8,
  	WAVEFORM_GATE_LEVEL_BIT = 0x4,
  	WAVEFORM_GATE_ENABLE_BIT = 0x2,
  	WAVEFORM_GATE_SELECT_BIT = 0x1,
  };
  
  enum dac_control1_contents {
  	DAC_WRITE_POLARITY_BIT = 0x800,	/* board-dependent setting */
  	DAC1_EXT_REF_BIT = 0x200,
  	DAC0_EXT_REF_BIT = 0x100,
  	DAC_OUTPUT_ENABLE_BIT = 0x80,	/* dac output enable bit */
  	DAC_UPDATE_POLARITY_BIT = 0x40,	/* board-dependent setting */
  	DAC_SW_GATE_BIT = 0x20,
  	DAC1_UNIPOLAR_BIT = 0x8,
  	DAC0_UNIPOLAR_BIT = 0x2,
  };
  
  /* bit definitions for read-only registers */
  enum hw_status_contents {
  	DAC_UNDERRUN_BIT = 0x1,
  	ADC_OVERRUN_BIT = 0x2,
  	DAC_ACTIVE_BIT = 0x4,
  	ADC_ACTIVE_BIT = 0x8,
  	DAC_INTR_PENDING_BIT = 0x10,
  	ADC_INTR_PENDING_BIT = 0x20,
  	DAC_DONE_BIT = 0x40,
  	ADC_DONE_BIT = 0x80,
  	EXT_INTR_PENDING_BIT = 0x100,
  	ADC_STOP_BIT = 0x200,
  };
  
  static inline u16 pipe_full_bits(u16 hw_status_bits)
  {
  	return (hw_status_bits >> 10) & 0x3;
  };
  
  static inline unsigned int dma_chain_flag_bits(u16 prepost_bits)
  {
  	return (prepost_bits >> 6) & 0x3;
  }
  
  static inline unsigned int adc_upper_read_ptr_code(u16 prepost_bits)
  {
  	return (prepost_bits >> 12) & 0x3;
  }
  
  static inline unsigned int adc_upper_write_ptr_code(u16 prepost_bits)
  {
  	return (prepost_bits >> 14) & 0x3;
  }
  
  /* I2C addresses for 4020 */
  enum i2c_addresses {
  	RANGE_CAL_I2C_ADDR = 0x20,
  	CALDAC0_I2C_ADDR = 0xc,
  	CALDAC1_I2C_ADDR = 0xd,
  };
  
  enum range_cal_i2c_contents {
  	/* bits that set what source the adc converter measures */
  	ADC_SRC_4020_MASK = 0x70,
  	/* make bnc trig/ext clock threshold 0V instead of 2.5V */
  	BNC_TRIG_THRESHOLD_0V_BIT = 0x80,
  };
  
  static inline u8 adc_src_4020_bits(unsigned int source)
  {
  	return (source << 4) & ADC_SRC_4020_MASK;
  };
  
  static inline u8 attenuate_bit(unsigned int channel)
  {
  	/* attenuate channel (+-5V input range) */
  	return 1 << (channel & 0x3);
  };
  
  /* analog input ranges for 64xx boards */
  static const struct comedi_lrange ai_ranges_64xx = {
  	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)
  	}
  };
  
  static const u8 ai_range_code_64xx[8] = {
  	0x0, 0x1, 0x2, 0x3,	/* bipolar 10, 5, 2,5, 1.25 */
  	0x8, 0x9, 0xa, 0xb	/* unipolar 10, 5, 2.5, 1.25 */
  };
  
  /* analog input ranges for 64-Mx boards */
  static const struct comedi_lrange ai_ranges_64_mx = {
  	7, {
  		BIP_RANGE(5),
  		BIP_RANGE(2.5),
  		BIP_RANGE(1.25),
  		BIP_RANGE(0.625),
  		UNI_RANGE(5),
  		UNI_RANGE(2.5),
  		UNI_RANGE(1.25)
  	}
  };
  
  static const u8 ai_range_code_64_mx[7] = {
  	0x0, 0x1, 0x2, 0x3,	/* bipolar 5, 2.5, 1.25, 0.625 */
  	0x9, 0xa, 0xb		/* unipolar 5, 2.5, 1.25 */
  };
  
  /* analog input ranges for 60xx boards */
  static const struct comedi_lrange ai_ranges_60xx = {
  	4, {
  		BIP_RANGE(10),
  		BIP_RANGE(5),
  		BIP_RANGE(0.5),
  		BIP_RANGE(0.05)
  	}
  };
  
  static const u8 ai_range_code_60xx[4] = {
  	0x0, 0x1, 0x4, 0x7	/* bipolar 10, 5, 0.5, 0.05 */
  };
  
  /* analog input ranges for 6030, etc boards */
  static const struct comedi_lrange ai_ranges_6030 = {
  	14, {
  		BIP_RANGE(10),
  		BIP_RANGE(5),
  		BIP_RANGE(2),
  		BIP_RANGE(1),
  		BIP_RANGE(0.5),
  		BIP_RANGE(0.2),
  		BIP_RANGE(0.1),
  		UNI_RANGE(10),
  		UNI_RANGE(5),
  		UNI_RANGE(2),
  		UNI_RANGE(1),
  		UNI_RANGE(0.5),
  		UNI_RANGE(0.2),
  		UNI_RANGE(0.1)
  	}
  };
  
  static const u8 ai_range_code_6030[14] = {
  	0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, /* bip 10, 5, 2, 1, 0.5, 0.2, 0.1 */
  	0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf  /* uni 10, 5, 2, 1, 0.5, 0.2, 0.1 */
  };
  
  /* analog input ranges for 6052, etc boards */
  static const struct comedi_lrange ai_ranges_6052 = {
  	15, {
  		BIP_RANGE(10),
  		BIP_RANGE(5),
  		BIP_RANGE(2.5),
  		BIP_RANGE(1),
  		BIP_RANGE(0.5),
  		BIP_RANGE(0.25),
  		BIP_RANGE(0.1),
  		BIP_RANGE(0.05),
  		UNI_RANGE(10),
  		UNI_RANGE(5),
  		UNI_RANGE(2),
  		UNI_RANGE(1),
  		UNI_RANGE(0.5),
  		UNI_RANGE(0.2),
  		UNI_RANGE(0.1)
  	}
  };
  
  static const u8 ai_range_code_6052[15] = {
  	0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,	/* bipolar 10 ... 0.05 */
  	0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf	/* unipolar 10 ... 0.1 */
  };
  
  /* analog input ranges for 4020 board */
  static const struct comedi_lrange ai_ranges_4020 = {
  	2, {
  		BIP_RANGE(5),
  		BIP_RANGE(1)
  	}
  };
  
  /* analog output ranges */
  static const struct comedi_lrange ao_ranges_64xx = {
  	4, {
  		BIP_RANGE(5),
  		BIP_RANGE(10),
  		UNI_RANGE(5),
  		UNI_RANGE(10)
  	}
  };
  
  static const int ao_range_code_64xx[] = {
  	0x0,
  	0x1,
  	0x2,
  	0x3,
  };
  
  static const int ao_range_code_60xx[] = {
  	0x0,
  };
  
  static const struct comedi_lrange ao_ranges_6030 = {
  	2, {
  		BIP_RANGE(10),
  		UNI_RANGE(10)
  	}
  };
  
  static const int ao_range_code_6030[] = {
  	0x0,
  	0x2,
  };
  
  static const struct comedi_lrange ao_ranges_4020 = {
  	2, {
  		BIP_RANGE(5),
  		BIP_RANGE(10)
  	}
  };
  
  static const int ao_range_code_4020[] = {
  	0x1,
  	0x0,
  };
  
  enum register_layout {
  	LAYOUT_60XX,
  	LAYOUT_64XX,
  	LAYOUT_4020,
  };
  
  struct hw_fifo_info {
  	unsigned int num_segments;
  	unsigned int max_segment_length;
  	unsigned int sample_packing_ratio;
  	u16 fifo_size_reg_mask;
  };
  
  enum pcidas64_boardid {
  	BOARD_PCIDAS6402_16,
  	BOARD_PCIDAS6402_12,
  	BOARD_PCIDAS64_M1_16,
  	BOARD_PCIDAS64_M2_16,
  	BOARD_PCIDAS64_M3_16,
  	BOARD_PCIDAS6013,
  	BOARD_PCIDAS6014,
  	BOARD_PCIDAS6023,
  	BOARD_PCIDAS6025,
  	BOARD_PCIDAS6030,
  	BOARD_PCIDAS6031,
  	BOARD_PCIDAS6032,
  	BOARD_PCIDAS6033,
  	BOARD_PCIDAS6034,
  	BOARD_PCIDAS6035,
  	BOARD_PCIDAS6036,
  	BOARD_PCIDAS6040,
  	BOARD_PCIDAS6052,
  	BOARD_PCIDAS6070,
  	BOARD_PCIDAS6071,
  	BOARD_PCIDAS4020_12,
  	BOARD_PCIDAS6402_16_JR,
  	BOARD_PCIDAS64_M1_16_JR,
  	BOARD_PCIDAS64_M2_16_JR,
  	BOARD_PCIDAS64_M3_16_JR,
  	BOARD_PCIDAS64_M1_14,
  	BOARD_PCIDAS64_M2_14,
  	BOARD_PCIDAS64_M3_14,
  };
  
  struct pcidas64_board {
  	const char *name;
  	int ai_se_chans;	/* number of ai inputs in single-ended mode */
  	int ai_bits;		/* analog input resolution */
  	int ai_speed;		/* fastest conversion period in ns */
  	const struct comedi_lrange *ai_range_table;
  	const u8 *ai_range_code;
  	int ao_nchan;		/* number of analog out channels */
  	int ao_bits;		/* analog output resolution */
  	int ao_scan_speed;	/* analog output scan speed */
  	const struct comedi_lrange *ao_range_table;
  	const int *ao_range_code;
  	const struct hw_fifo_info *const ai_fifo;
  	/* different board families have slightly different registers */
  	enum register_layout layout;
  	unsigned has_8255:1;
  };
  
  static const struct hw_fifo_info ai_fifo_4020 = {
  	.num_segments = 2,
  	.max_segment_length = 0x8000,
  	.sample_packing_ratio = 2,
  	.fifo_size_reg_mask = 0x7f,
  };
  
  static const struct hw_fifo_info ai_fifo_64xx = {
  	.num_segments = 4,
  	.max_segment_length = 0x800,
  	.sample_packing_ratio = 1,
  	.fifo_size_reg_mask = 0x3f,
  };
  
  static const struct hw_fifo_info ai_fifo_60xx = {
  	.num_segments = 4,
  	.max_segment_length = 0x800,
  	.sample_packing_ratio = 1,
  	.fifo_size_reg_mask = 0x7f,
  };
  
  /*
   * maximum number of dma transfers we will chain together into a ring
   * (and the maximum number of dma buffers we maintain)
   */
  #define MAX_AI_DMA_RING_COUNT (0x80000 / DMA_BUFFER_SIZE)
  #define MIN_AI_DMA_RING_COUNT (0x10000 / DMA_BUFFER_SIZE)
  #define AO_DMA_RING_COUNT (0x10000 / DMA_BUFFER_SIZE)
  static inline unsigned int ai_dma_ring_count(const struct pcidas64_board *board)
  {
  	if (board->layout == LAYOUT_4020)
  		return MAX_AI_DMA_RING_COUNT;
  
  	return MIN_AI_DMA_RING_COUNT;
  }
  
  static const int bytes_in_sample = 2;
  
  static const struct pcidas64_board pcidas64_boards[] = {
  	[BOARD_PCIDAS6402_16] = {
  		.name		= "pci-das6402/16",
  		.ai_se_chans	= 64,
  		.ai_bits	= 16,
  		.ai_speed	= 5000,
  		.ao_nchan	= 2,
  		.ao_bits	= 16,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_64XX,
  		.ai_range_table	= &ai_ranges_64xx,
  		.ai_range_code	= ai_range_code_64xx,
  		.ao_range_table	= &ao_ranges_64xx,
  		.ao_range_code	= ao_range_code_64xx,
  		.ai_fifo	= &ai_fifo_64xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS6402_12] = {
  		.name		= "pci-das6402/12",	/* XXX check */
  		.ai_se_chans	= 64,
  		.ai_bits	= 12,
  		.ai_speed	= 5000,
  		.ao_nchan	= 2,
  		.ao_bits	= 12,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_64XX,
  		.ai_range_table	= &ai_ranges_64xx,
  		.ai_range_code	= ai_range_code_64xx,
  		.ao_range_table	= &ao_ranges_64xx,
  		.ao_range_code	= ao_range_code_64xx,
  		.ai_fifo	= &ai_fifo_64xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS64_M1_16] = {
  		.name		= "pci-das64/m1/16",
  		.ai_se_chans	= 64,
  		.ai_bits	= 16,
  		.ai_speed	= 1000,
  		.ao_nchan	= 2,
  		.ao_bits	= 16,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_64XX,
  		.ai_range_table	= &ai_ranges_64_mx,
  		.ai_range_code	= ai_range_code_64_mx,
  		.ao_range_table	= &ao_ranges_64xx,
  		.ao_range_code	= ao_range_code_64xx,
  		.ai_fifo	= &ai_fifo_64xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS64_M2_16] = {
  		.name = "pci-das64/m2/16",
  		.ai_se_chans	= 64,
  		.ai_bits	= 16,
  		.ai_speed	= 500,
  		.ao_nchan	= 2,
  		.ao_bits	= 16,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_64XX,
  		.ai_range_table	= &ai_ranges_64_mx,
  		.ai_range_code	= ai_range_code_64_mx,
  		.ao_range_table	= &ao_ranges_64xx,
  		.ao_range_code	= ao_range_code_64xx,
  		.ai_fifo	= &ai_fifo_64xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS64_M3_16] = {
  		.name		= "pci-das64/m3/16",
  		.ai_se_chans	= 64,
  		.ai_bits	= 16,
  		.ai_speed	= 333,
  		.ao_nchan	= 2,
  		.ao_bits	= 16,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_64XX,
  		.ai_range_table	= &ai_ranges_64_mx,
  		.ai_range_code	= ai_range_code_64_mx,
  		.ao_range_table	= &ao_ranges_64xx,
  		.ao_range_code	= ao_range_code_64xx,
  		.ai_fifo	= &ai_fifo_64xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS6013] = {
  		.name		= "pci-das6013",
  		.ai_se_chans	= 16,
  		.ai_bits	= 16,
  		.ai_speed	= 5000,
  		.ao_nchan	= 0,
  		.ao_bits	= 16,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_60xx,
  		.ai_range_code	= ai_range_code_60xx,
  		.ao_range_table	= &range_bipolar10,
  		.ao_range_code	= ao_range_code_60xx,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS6014] = {
  		.name		= "pci-das6014",
  		.ai_se_chans	= 16,
  		.ai_bits	= 16,
  		.ai_speed	= 5000,
  		.ao_nchan	= 2,
  		.ao_bits	= 16,
  		.ao_scan_speed	= 100000,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_60xx,
  		.ai_range_code	= ai_range_code_60xx,
  		.ao_range_table	= &range_bipolar10,
  		.ao_range_code	= ao_range_code_60xx,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS6023] = {
  		.name		= "pci-das6023",
  		.ai_se_chans	= 16,
  		.ai_bits	= 12,
  		.ai_speed	= 5000,
  		.ao_nchan	= 0,
  		.ao_scan_speed	= 100000,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_60xx,
  		.ai_range_code	= ai_range_code_60xx,
  		.ao_range_table	= &range_bipolar10,
  		.ao_range_code	= ao_range_code_60xx,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS6025] = {
  		.name		= "pci-das6025",
  		.ai_se_chans	= 16,
  		.ai_bits	= 12,
  		.ai_speed	= 5000,
  		.ao_nchan	= 2,
  		.ao_bits	= 12,
  		.ao_scan_speed	= 100000,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_60xx,
  		.ai_range_code	= ai_range_code_60xx,
  		.ao_range_table	= &range_bipolar10,
  		.ao_range_code	= ao_range_code_60xx,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS6030] = {
  		.name		= "pci-das6030",
  		.ai_se_chans	= 16,
  		.ai_bits	= 16,
  		.ai_speed	= 10000,
  		.ao_nchan	= 2,
  		.ao_bits	= 16,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_6030,
  		.ai_range_code	= ai_range_code_6030,
  		.ao_range_table	= &ao_ranges_6030,
  		.ao_range_code	= ao_range_code_6030,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS6031] = {
  		.name		= "pci-das6031",
  		.ai_se_chans	= 64,
  		.ai_bits	= 16,
  		.ai_speed	= 10000,
  		.ao_nchan	= 2,
  		.ao_bits	= 16,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_6030,
  		.ai_range_code	= ai_range_code_6030,
  		.ao_range_table	= &ao_ranges_6030,
  		.ao_range_code	= ao_range_code_6030,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS6032] = {
  		.name		= "pci-das6032",
  		.ai_se_chans	= 16,
  		.ai_bits	= 16,
  		.ai_speed	= 10000,
  		.ao_nchan	= 0,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_6030,
  		.ai_range_code	= ai_range_code_6030,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS6033] = {
  		.name		= "pci-das6033",
  		.ai_se_chans	= 64,
  		.ai_bits	= 16,
  		.ai_speed	= 10000,
  		.ao_nchan	= 0,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_6030,
  		.ai_range_code	= ai_range_code_6030,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS6034] = {
  		.name		= "pci-das6034",
  		.ai_se_chans	= 16,
  		.ai_bits	= 16,
  		.ai_speed	= 5000,
  		.ao_nchan	= 0,
  		.ao_scan_speed	= 0,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_60xx,
  		.ai_range_code	= ai_range_code_60xx,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS6035] = {
  		.name		= "pci-das6035",
  		.ai_se_chans	= 16,
  		.ai_bits	= 16,
  		.ai_speed	= 5000,
  		.ao_nchan	= 2,
  		.ao_bits	= 12,
  		.ao_scan_speed	= 100000,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_60xx,
  		.ai_range_code	= ai_range_code_60xx,
  		.ao_range_table	= &range_bipolar10,
  		.ao_range_code	= ao_range_code_60xx,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS6036] = {
  		.name		= "pci-das6036",
  		.ai_se_chans	= 16,
  		.ai_bits	= 16,
  		.ai_speed	= 5000,
  		.ao_nchan	= 2,
  		.ao_bits	= 16,
  		.ao_scan_speed	= 100000,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_60xx,
  		.ai_range_code	= ai_range_code_60xx,
  		.ao_range_table	= &range_bipolar10,
  		.ao_range_code	= ao_range_code_60xx,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS6040] = {
  		.name		= "pci-das6040",
  		.ai_se_chans	= 16,
  		.ai_bits	= 12,
  		.ai_speed	= 2000,
  		.ao_nchan	= 2,
  		.ao_bits	= 12,
  		.ao_scan_speed	= 1000,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_6052,
  		.ai_range_code	= ai_range_code_6052,
  		.ao_range_table	= &ao_ranges_6030,
  		.ao_range_code	= ao_range_code_6030,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS6052] = {
  		.name		= "pci-das6052",
  		.ai_se_chans	= 16,
  		.ai_bits	= 16,
  		.ai_speed	= 3333,
  		.ao_nchan	= 2,
  		.ao_bits	= 16,
  		.ao_scan_speed	= 3333,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_6052,
  		.ai_range_code	= ai_range_code_6052,
  		.ao_range_table	= &ao_ranges_6030,
  		.ao_range_code	= ao_range_code_6030,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS6070] = {
  		.name		= "pci-das6070",
  		.ai_se_chans	= 16,
  		.ai_bits	= 12,
  		.ai_speed	= 800,
  		.ao_nchan	= 2,
  		.ao_bits	= 12,
  		.ao_scan_speed	= 1000,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_6052,
  		.ai_range_code	= ai_range_code_6052,
  		.ao_range_table	= &ao_ranges_6030,
  		.ao_range_code	= ao_range_code_6030,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS6071] = {
  		.name		= "pci-das6071",
  		.ai_se_chans	= 64,
  		.ai_bits	= 12,
  		.ai_speed	= 800,
  		.ao_nchan	= 2,
  		.ao_bits	= 12,
  		.ao_scan_speed	= 1000,
  		.layout		= LAYOUT_60XX,
  		.ai_range_table	= &ai_ranges_6052,
  		.ai_range_code	= ai_range_code_6052,
  		.ao_range_table	= &ao_ranges_6030,
  		.ao_range_code	= ao_range_code_6030,
  		.ai_fifo	= &ai_fifo_60xx,
  		.has_8255	= 0,
  	},
  	[BOARD_PCIDAS4020_12] = {
  		.name		= "pci-das4020/12",
  		.ai_se_chans	= 4,
  		.ai_bits	= 12,
  		.ai_speed	= 50,
  		.ao_bits	= 12,
  		.ao_nchan	= 2,
  		.ao_scan_speed	= 0,	/* no hardware pacing on ao */
  		.layout		= LAYOUT_4020,
  		.ai_range_table	= &ai_ranges_4020,
  		.ao_range_table	= &ao_ranges_4020,
  		.ao_range_code	= ao_range_code_4020,
  		.ai_fifo	= &ai_fifo_4020,
  		.has_8255	= 1,
  	},
  #if 0
  	/* The device id for these boards is unknown */
  
  	[BOARD_PCIDAS6402_16_JR] = {
  		.name		= "pci-das6402/16/jr",
  		.ai_se_chans	= 64,
  		.ai_bits	= 16,
  		.ai_speed	= 5000,
  		.ao_nchan	= 0,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_64XX,
  		.ai_range_table	= &ai_ranges_64xx,
  		.ai_range_code	= ai_range_code_64xx,
  		.ai_fifo	= ai_fifo_64xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS64_M1_16_JR] = {
  		.name		= "pci-das64/m1/16/jr",
  		.ai_se_chans	= 64,
  		.ai_bits	= 16,
  		.ai_speed	= 1000,
  		.ao_nchan	= 0,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_64XX,
  		.ai_range_table	= &ai_ranges_64_mx,
  		.ai_range_code	= ai_range_code_64_mx,
  		.ai_fifo	= ai_fifo_64xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS64_M2_16_JR] = {
  		.name = "pci-das64/m2/16/jr",
  		.ai_se_chans	= 64,
  		.ai_bits	= 16,
  		.ai_speed	= 500,
  		.ao_nchan	= 0,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_64XX,
  		.ai_range_table	= &ai_ranges_64_mx,
  		.ai_range_code	= ai_range_code_64_mx,
  		.ai_fifo	= ai_fifo_64xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS64_M3_16_JR] = {
  		.name		= "pci-das64/m3/16/jr",
  		.ai_se_chans	= 64,
  		.ai_bits	= 16,
  		.ai_speed	= 333,
  		.ao_nchan	= 0,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_64XX,
  		.ai_range_table	= &ai_ranges_64_mx,
  		.ai_range_code	= ai_range_code_64_mx,
  		.ai_fifo	= ai_fifo_64xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS64_M1_14] = {
  		.name		= "pci-das64/m1/14",
  		.ai_se_chans	= 64,
  		.ai_bits	= 14,
  		.ai_speed	= 1000,
  		.ao_nchan	= 2,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_64XX,
  		.ai_range_table	= &ai_ranges_64_mx,
  		.ai_range_code	= ai_range_code_64_mx,
  		.ai_fifo	= ai_fifo_64xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS64_M2_14] = {
  		.name		= "pci-das64/m2/14",
  		.ai_se_chans	= 64,
  		.ai_bits	= 14,
  		.ai_speed	= 500,
  		.ao_nchan	= 2,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_64XX,
  		.ai_range_table	= &ai_ranges_64_mx,
  		.ai_range_code	= ai_range_code_64_mx,
  		.ai_fifo	= ai_fifo_64xx,
  		.has_8255	= 1,
  	},
  	[BOARD_PCIDAS64_M3_14] = {
  		.name		= "pci-das64/m3/14",
  		.ai_se_chans	= 64,
  		.ai_bits	= 14,
  		.ai_speed	= 333,
  		.ao_nchan	= 2,
  		.ao_scan_speed	= 10000,
  		.layout		= LAYOUT_64XX,
  		.ai_range_table	= &ai_ranges_64_mx,
  		.ai_range_code	= ai_range_code_64_mx,
  		.ai_fifo	= ai_fifo_64xx,
  		.has_8255	= 1,
  	},
  #endif
  };
  
  static inline unsigned short se_diff_bit_6xxx(struct comedi_device *dev,
  					      int use_differential)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  
  	if ((board->layout == LAYOUT_64XX && !use_differential) ||
  	    (board->layout == LAYOUT_60XX && use_differential))
  		return ADC_SE_DIFF_BIT;
  
  	return 0;
  }
  
  struct ext_clock_info {
  	/* master clock divisor to use for scans with external master clock */
  	unsigned int divisor;
  	/* chanspec for master clock input when used as scan begin src */
  	unsigned int chanspec;
  };
  
  /* this structure is for data unique to this hardware driver. */
  struct pcidas64_private {
  	/* base addresses (physical) */
  	resource_size_t main_phys_iobase;
  	resource_size_t dio_counter_phys_iobase;
  	/* base addresses (ioremapped) */
  	void __iomem *plx9080_iobase;
  	void __iomem *main_iobase;
  	/* local address (used by dma controller) */
  	u32 local0_iobase;
  	u32 local1_iobase;
  	/* dma buffers for analog input */
  	u16 *ai_buffer[MAX_AI_DMA_RING_COUNT];
  	/* physical addresses of ai dma buffers */
  	dma_addr_t ai_buffer_bus_addr[MAX_AI_DMA_RING_COUNT];
  	/*
  	 * array of ai dma descriptors read by plx9080,
  	 * allocated to get proper alignment
  	 */
  	struct plx_dma_desc *ai_dma_desc;
  	/* physical address of ai dma descriptor array */
  	dma_addr_t ai_dma_desc_bus_addr;
  	/*
  	 * index of the ai dma descriptor/buffer
  	 * that is currently being used
  	 */
  	unsigned int ai_dma_index;
  	/* dma buffers for analog output */
  	u16 *ao_buffer[AO_DMA_RING_COUNT];
  	/* physical addresses of ao dma buffers */
  	dma_addr_t ao_buffer_bus_addr[AO_DMA_RING_COUNT];
  	struct plx_dma_desc *ao_dma_desc;
  	dma_addr_t ao_dma_desc_bus_addr;
  	/* keeps track of buffer where the next ao sample should go */
  	unsigned int ao_dma_index;
  	unsigned int hw_revision;	/* stc chip hardware revision number */
  	/* last bits sent to INTR_ENABLE_REG register */
  	unsigned int intr_enable_bits;
  	/* last bits sent to ADC_CONTROL1_REG register */
  	u16 adc_control1_bits;
  	/* last bits sent to FIFO_SIZE_REG register */
  	u16 fifo_size_bits;
  	/* last bits sent to HW_CONFIG_REG register */
  	u16 hw_config_bits;
  	u16 dac_control1_bits;
  	/* last bits written to plx9080 control register */
  	u32 plx_control_bits;
  	/* last bits written to plx interrupt control and status register */
  	u32 plx_intcsr_bits;
  	/* index of calibration source readable through ai ch0 */
  	int calibration_source;
  	/* bits written to i2c calibration/range register */
  	u8 i2c_cal_range_bits;
  	/* configure digital triggers to trigger on falling edge */
  	unsigned int ext_trig_falling;
  	short ai_cmd_running;
  	unsigned int ai_fifo_segment_length;
  	struct ext_clock_info ext_clock;
  	unsigned short ao_bounce_buffer[DAC_FIFO_SIZE];
  };
  
  static unsigned int ai_range_bits_6xxx(const struct comedi_device *dev,
  				       unsigned int range_index)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  
  	return board->ai_range_code[range_index] << 8;
  }
  
  static unsigned int hw_revision(const struct comedi_device *dev,
  				u16 hw_status_bits)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  
  	if (board->layout == LAYOUT_4020)
  		return (hw_status_bits >> 13) & 0x7;
  
  	return (hw_status_bits >> 12) & 0xf;
  }
  
  static void set_dac_range_bits(struct comedi_device *dev,
  			       u16 *bits, unsigned int channel,
  			       unsigned int range)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	unsigned int code = board->ao_range_code[range];
  
  	if (channel > 1)
  		dev_err(dev->class_dev, "bug! bad channel?\n");
  	if (code & ~0x3)
  		dev_err(dev->class_dev, "bug! bad range code?\n");
  
  	*bits &= ~(0x3 << (2 * channel));
  	*bits |= code << (2 * channel);
  };
  
  static inline int ao_cmd_is_supported(const struct pcidas64_board *board)
  {
  	return board->ao_nchan && board->layout != LAYOUT_4020;
  }
  
  static void abort_dma(struct comedi_device *dev, unsigned int channel)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned long flags;
  
  	/* spinlock for plx dma control/status reg */
  	spin_lock_irqsave(&dev->spinlock, flags);
  
  	plx9080_abort_dma(devpriv->plx9080_iobase, channel);
  
  	spin_unlock_irqrestore(&dev->spinlock, flags);
  }
  
  static void disable_plx_interrupts(struct comedi_device *dev)
  {
  	struct pcidas64_private *devpriv = dev->private;
  
  	devpriv->plx_intcsr_bits = 0;
  	writel(devpriv->plx_intcsr_bits,
  	       devpriv->plx9080_iobase + PLX_REG_INTCSR);
  }
  
  static void disable_ai_interrupts(struct comedi_device *dev)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned long flags;
  
  	spin_lock_irqsave(&dev->spinlock, flags);
  	devpriv->intr_enable_bits &=
  		~EN_ADC_INTR_SRC_BIT & ~EN_ADC_DONE_INTR_BIT &
  		~EN_ADC_ACTIVE_INTR_BIT & ~EN_ADC_STOP_INTR_BIT &
  		~EN_ADC_OVERRUN_BIT & ~ADC_INTR_SRC_MASK;
  	writew(devpriv->intr_enable_bits,
  	       devpriv->main_iobase + INTR_ENABLE_REG);
  	spin_unlock_irqrestore(&dev->spinlock, flags);
  }
  
  static void enable_ai_interrupts(struct comedi_device *dev,
  				 const struct comedi_cmd *cmd)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	u32 bits;
  	unsigned long flags;
  
  	bits = EN_ADC_OVERRUN_BIT | EN_ADC_DONE_INTR_BIT |
  	       EN_ADC_ACTIVE_INTR_BIT | EN_ADC_STOP_INTR_BIT;
  	/*
  	 * Use pio transfer and interrupt on end of conversion
  	 * if CMDF_WAKE_EOS flag is set.
  	 */
  	if (cmd->flags & CMDF_WAKE_EOS) {
  		/* 4020 doesn't support pio transfers except for fifo dregs */
  		if (board->layout != LAYOUT_4020)
  			bits |= ADC_INTR_EOSCAN_BITS | EN_ADC_INTR_SRC_BIT;
  	}
  	spin_lock_irqsave(&dev->spinlock, flags);
  	devpriv->intr_enable_bits |= bits;
  	writew(devpriv->intr_enable_bits,
  	       devpriv->main_iobase + INTR_ENABLE_REG);
  	spin_unlock_irqrestore(&dev->spinlock, flags);
  }
  
  /* initialize plx9080 chip */
  static void init_plx9080(struct comedi_device *dev)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	u32 bits;
  	void __iomem *plx_iobase = devpriv->plx9080_iobase;
  
  	devpriv->plx_control_bits =
  		readl(devpriv->plx9080_iobase + PLX_REG_CNTRL);
  
  #ifdef __BIG_ENDIAN
  	bits = PLX_BIGEND_DMA0 | PLX_BIGEND_DMA1;
  #else
  	bits = 0;
  #endif
  	writel(bits, devpriv->plx9080_iobase + PLX_REG_BIGEND);
  
  	disable_plx_interrupts(dev);
  
  	abort_dma(dev, 0);
  	abort_dma(dev, 1);
  
  	/* configure dma0 mode */
  	bits = 0;
  	/* enable ready input, not sure if this is necessary */
  	bits |= PLX_DMAMODE_READYIEN;
  	/* enable bterm, not sure if this is necessary */
  	bits |= PLX_DMAMODE_BTERMIEN;
  	/* enable dma chaining */
  	bits |= PLX_DMAMODE_CHAINEN;
  	/*
  	 * enable interrupt on dma done
  	 * (probably don't need this, since chain never finishes)
  	 */
  	bits |= PLX_DMAMODE_DONEIEN;
  	/*
  	 * don't increment local address during transfers
  	 * (we are transferring from a fixed fifo register)
  	 */
  	bits |= PLX_DMAMODE_LACONST;
  	/* route dma interrupt to pci bus */
  	bits |= PLX_DMAMODE_INTRPCI;
  	/* enable demand mode */
  	bits |= PLX_DMAMODE_DEMAND;
  	/* enable local burst mode */
  	bits |= PLX_DMAMODE_BURSTEN;
  	/* 4020 uses 32 bit dma */
  	if (board->layout == LAYOUT_4020)
  		bits |= PLX_DMAMODE_WIDTH_32;
  	else				/* localspace0 bus is 16 bits wide */
  		bits |= PLX_DMAMODE_WIDTH_16;
  	writel(bits, plx_iobase + PLX_REG_DMAMODE1);
  	if (ao_cmd_is_supported(board))
  		writel(bits, plx_iobase + PLX_REG_DMAMODE0);
  
  	/* enable interrupts on plx 9080 */
  	devpriv->plx_intcsr_bits |=
  	    PLX_INTCSR_LSEABORTEN | PLX_INTCSR_LSEPARITYEN | PLX_INTCSR_PIEN |
  	    PLX_INTCSR_PLIEN | PLX_INTCSR_PABORTIEN | PLX_INTCSR_LIOEN |
  	    PLX_INTCSR_DMA0IEN | PLX_INTCSR_DMA1IEN;
  	writel(devpriv->plx_intcsr_bits,
  	       devpriv->plx9080_iobase + PLX_REG_INTCSR);
  }
  
  static void disable_ai_pacing(struct comedi_device *dev)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned long flags;
  
  	disable_ai_interrupts(dev);
  
  	spin_lock_irqsave(&dev->spinlock, flags);
  	devpriv->adc_control1_bits &= ~ADC_SW_GATE_BIT;
  	writew(devpriv->adc_control1_bits,
  	       devpriv->main_iobase + ADC_CONTROL1_REG);
  	spin_unlock_irqrestore(&dev->spinlock, flags);
  
  	/* disable pacing, triggering, etc */
  	writew(ADC_DMA_DISABLE_BIT | ADC_SOFT_GATE_BITS | ADC_GATE_LEVEL_BIT,
  	       devpriv->main_iobase + ADC_CONTROL0_REG);
  }
  
  static int set_ai_fifo_segment_length(struct comedi_device *dev,
  				      unsigned int num_entries)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	static const int increment_size = 0x100;
  	const struct hw_fifo_info *const fifo = board->ai_fifo;
  	unsigned int num_increments;
  	u16 bits;
  
  	if (num_entries < increment_size)
  		num_entries = increment_size;
  	if (num_entries > fifo->max_segment_length)
  		num_entries = fifo->max_segment_length;
  
  	/* 1 == 256 entries, 2 == 512 entries, etc */
  	num_increments = DIV_ROUND_CLOSEST(num_entries, increment_size);
  
  	bits = (~(num_increments - 1)) & fifo->fifo_size_reg_mask;
  	devpriv->fifo_size_bits &= ~fifo->fifo_size_reg_mask;
  	devpriv->fifo_size_bits |= bits;
  	writew(devpriv->fifo_size_bits,
  	       devpriv->main_iobase + FIFO_SIZE_REG);
  
  	devpriv->ai_fifo_segment_length = num_increments * increment_size;
  
  	return devpriv->ai_fifo_segment_length;
  }
  
  /*
   * adjusts the size of hardware fifo (which determines block size for dma xfers)
   */
  static int set_ai_fifo_size(struct comedi_device *dev, unsigned int num_samples)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	unsigned int num_fifo_entries;
  	int retval;
  	const struct hw_fifo_info *const fifo = board->ai_fifo;
  
  	num_fifo_entries = num_samples / fifo->sample_packing_ratio;
  
  	retval = set_ai_fifo_segment_length(dev,
  					    num_fifo_entries /
  					    fifo->num_segments);
  	if (retval < 0)
  		return retval;
  
  	return retval * fifo->num_segments * fifo->sample_packing_ratio;
  }
  
  /* query length of fifo */
  static unsigned int ai_fifo_size(struct comedi_device *dev)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  
  	return devpriv->ai_fifo_segment_length *
  	       board->ai_fifo->num_segments *
  	       board->ai_fifo->sample_packing_ratio;
  }
  
  static void init_stc_registers(struct comedi_device *dev)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	u16 bits;
  	unsigned long flags;
  
  	spin_lock_irqsave(&dev->spinlock, flags);
  
  	/*
  	 * bit should be set for 6025,
  	 * although docs say boards with <= 16 chans should be cleared XXX
  	 */
  	if (1)
  		devpriv->adc_control1_bits |= ADC_QUEUE_CONFIG_BIT;
  	writew(devpriv->adc_control1_bits,
  	       devpriv->main_iobase + ADC_CONTROL1_REG);
  
  	/* 6402/16 manual says this register must be initialized to 0xff? */
  	writew(0xff, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
  
  	bits = SLOW_DAC_BIT | DMA_CH_SELECT_BIT;
  	if (board->layout == LAYOUT_4020)
  		bits |= INTERNAL_CLOCK_4020_BITS;
  	devpriv->hw_config_bits |= bits;
  	writew(devpriv->hw_config_bits,
  	       devpriv->main_iobase + HW_CONFIG_REG);
  
  	writew(0, devpriv->main_iobase + DAQ_SYNC_REG);
  	writew(0, devpriv->main_iobase + CALIBRATION_REG);
  
  	spin_unlock_irqrestore(&dev->spinlock, flags);
  
  	/* set fifos to maximum size */
  	devpriv->fifo_size_bits |= DAC_FIFO_BITS;
  	set_ai_fifo_segment_length(dev, board->ai_fifo->max_segment_length);
  
  	devpriv->dac_control1_bits = DAC_OUTPUT_ENABLE_BIT;
  	devpriv->intr_enable_bits =
  		/* EN_DAC_INTR_SRC_BIT | DAC_INTR_QEMPTY_BITS | */
  		EN_DAC_DONE_INTR_BIT | EN_DAC_UNDERRUN_BIT;
  	writew(devpriv->intr_enable_bits,
  	       devpriv->main_iobase + INTR_ENABLE_REG);
  
  	disable_ai_pacing(dev);
  };
  
  static int alloc_and_init_dma_members(struct comedi_device *dev)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
  	struct pcidas64_private *devpriv = dev->private;
  	int i;
  
  	/* allocate pci dma buffers */
  	for (i = 0; i < ai_dma_ring_count(board); i++) {
  		devpriv->ai_buffer[i] =
  			dma_alloc_coherent(&pcidev->dev, DMA_BUFFER_SIZE,
  					   &devpriv->ai_buffer_bus_addr[i],
  					   GFP_KERNEL);
  		if (!devpriv->ai_buffer[i])
  			return -ENOMEM;
  	}
  	for (i = 0; i < AO_DMA_RING_COUNT; i++) {
  		if (ao_cmd_is_supported(board)) {
  			devpriv->ao_buffer[i] =
  				dma_alloc_coherent(&pcidev->dev,
  						   DMA_BUFFER_SIZE,
  						   &devpriv->
  						   ao_buffer_bus_addr[i],
  						   GFP_KERNEL);
  			if (!devpriv->ao_buffer[i])
  				return -ENOMEM;
  		}
  	}
  	/* allocate dma descriptors */
  	devpriv->ai_dma_desc =
  		dma_alloc_coherent(&pcidev->dev, sizeof(struct plx_dma_desc) *
  				   ai_dma_ring_count(board),
  				   &devpriv->ai_dma_desc_bus_addr, GFP_KERNEL);
  	if (!devpriv->ai_dma_desc)
  		return -ENOMEM;
  
  	if (ao_cmd_is_supported(board)) {
  		devpriv->ao_dma_desc =
  			dma_alloc_coherent(&pcidev->dev,
  					   sizeof(struct plx_dma_desc) *
  					   AO_DMA_RING_COUNT,
  					   &devpriv->ao_dma_desc_bus_addr,
  					   GFP_KERNEL);
  		if (!devpriv->ao_dma_desc)
  			return -ENOMEM;
  	}
  	/* initialize dma descriptors */
  	for (i = 0; i < ai_dma_ring_count(board); i++) {
  		devpriv->ai_dma_desc[i].pci_start_addr =
  			cpu_to_le32(devpriv->ai_buffer_bus_addr[i]);
  		if (board->layout == LAYOUT_4020)
  			devpriv->ai_dma_desc[i].local_start_addr =
  				cpu_to_le32(devpriv->local1_iobase +
  					    ADC_FIFO_REG);
  		else
  			devpriv->ai_dma_desc[i].local_start_addr =
  				cpu_to_le32(devpriv->local0_iobase +
  					    ADC_FIFO_REG);
  		devpriv->ai_dma_desc[i].transfer_size = cpu_to_le32(0);
  		devpriv->ai_dma_desc[i].next =
  			cpu_to_le32((devpriv->ai_dma_desc_bus_addr +
  				     ((i + 1) % ai_dma_ring_count(board)) *
  				     sizeof(devpriv->ai_dma_desc[0])) |
  				    PLX_DMADPR_DESCPCI | PLX_DMADPR_TCINTR |
  				    PLX_DMADPR_XFERL2P);
  	}
  	if (ao_cmd_is_supported(board)) {
  		for (i = 0; i < AO_DMA_RING_COUNT; i++) {
  			devpriv->ao_dma_desc[i].pci_start_addr =
  				cpu_to_le32(devpriv->ao_buffer_bus_addr[i]);
  			devpriv->ao_dma_desc[i].local_start_addr =
  				cpu_to_le32(devpriv->local0_iobase +
  					    DAC_FIFO_REG);
  			devpriv->ao_dma_desc[i].transfer_size = cpu_to_le32(0);
  			devpriv->ao_dma_desc[i].next =
  				cpu_to_le32((devpriv->ao_dma_desc_bus_addr +
  					     ((i + 1) % (AO_DMA_RING_COUNT)) *
  					     sizeof(devpriv->ao_dma_desc[0])) |
  					    PLX_DMADPR_DESCPCI |
  					    PLX_DMADPR_TCINTR);
  		}
  	}
  	return 0;
  }
  
  static void cb_pcidas64_free_dma(struct comedi_device *dev)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
  	struct pcidas64_private *devpriv = dev->private;
  	int i;
  
  	if (!devpriv)
  		return;
  
  	/* free pci dma buffers */
  	for (i = 0; i < ai_dma_ring_count(board); i++) {
  		if (devpriv->ai_buffer[i])
  			dma_free_coherent(&pcidev->dev,
  					  DMA_BUFFER_SIZE,
  					  devpriv->ai_buffer[i],
  					  devpriv->ai_buffer_bus_addr[i]);
  	}
  	for (i = 0; i < AO_DMA_RING_COUNT; i++) {
  		if (devpriv->ao_buffer[i])
  			dma_free_coherent(&pcidev->dev,
  					  DMA_BUFFER_SIZE,
  					  devpriv->ao_buffer[i],
  					  devpriv->ao_buffer_bus_addr[i]);
  	}
  	/* free dma descriptors */
  	if (devpriv->ai_dma_desc)
  		dma_free_coherent(&pcidev->dev,
  				  sizeof(struct plx_dma_desc) *
  				  ai_dma_ring_count(board),
  				  devpriv->ai_dma_desc,
  				  devpriv->ai_dma_desc_bus_addr);
  	if (devpriv->ao_dma_desc)
  		dma_free_coherent(&pcidev->dev,
  				  sizeof(struct plx_dma_desc) *
  				  AO_DMA_RING_COUNT,
  				  devpriv->ao_dma_desc,
  				  devpriv->ao_dma_desc_bus_addr);
  }
  
  static inline void warn_external_queue(struct comedi_device *dev)
  {
  	dev_err(dev->class_dev,
  		"AO command and AI external channel queue cannot be used simultaneously\n");
  	dev_err(dev->class_dev,
  		"Use internal AI channel queue (channels must be consecutive and use same range/aref)\n");
  }
  
  /*
   * their i2c requires a huge delay on setting clock or data high for some reason
   */
  static const int i2c_high_udelay = 1000;
  static const int i2c_low_udelay = 10;
  
  /* set i2c data line high or low */
  static void i2c_set_sda(struct comedi_device *dev, int state)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	static const int data_bit = PLX_CNTRL_EEWB;
  	void __iomem *plx_control_addr = devpriv->plx9080_iobase +
  					 PLX_REG_CNTRL;
  
  	if (state) {				/* set data line high */
  		devpriv->plx_control_bits &= ~data_bit;
  		writel(devpriv->plx_control_bits, plx_control_addr);
  		udelay(i2c_high_udelay);
  	} else {				/* set data line low */
  		devpriv->plx_control_bits |= data_bit;
  		writel(devpriv->plx_control_bits, plx_control_addr);
  		udelay(i2c_low_udelay);
  	}
  }
  
  /* set i2c clock line high or low */
  static void i2c_set_scl(struct comedi_device *dev, int state)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	static const int clock_bit = PLX_CNTRL_USERO;
  	void __iomem *plx_control_addr = devpriv->plx9080_iobase +
  					 PLX_REG_CNTRL;
  
  	if (state) {				/* set clock line high */
  		devpriv->plx_control_bits &= ~clock_bit;
  		writel(devpriv->plx_control_bits, plx_control_addr);
  		udelay(i2c_high_udelay);
  	} else {				/* set clock line low */
  		devpriv->plx_control_bits |= clock_bit;
  		writel(devpriv->plx_control_bits, plx_control_addr);
  		udelay(i2c_low_udelay);
  	}
  }
  
  static void i2c_write_byte(struct comedi_device *dev, u8 byte)
  {
  	u8 bit;
  	unsigned int num_bits = 8;
  
  	for (bit = 1 << (num_bits - 1); bit; bit >>= 1) {
  		i2c_set_scl(dev, 0);
  		if ((byte & bit))
  			i2c_set_sda(dev, 1);
  		else
  			i2c_set_sda(dev, 0);
  		i2c_set_scl(dev, 1);
  	}
  }
  
  /* we can't really read the lines, so fake it */
  static int i2c_read_ack(struct comedi_device *dev)
  {
  	i2c_set_scl(dev, 0);
  	i2c_set_sda(dev, 1);
  	i2c_set_scl(dev, 1);
  
  	return 0;		/* return fake acknowledge bit */
  }
  
  /* send start bit */
  static void i2c_start(struct comedi_device *dev)
  {
  	i2c_set_scl(dev, 1);
  	i2c_set_sda(dev, 1);
  	i2c_set_sda(dev, 0);
  }
  
  /* send stop bit */
  static void i2c_stop(struct comedi_device *dev)
  {
  	i2c_set_scl(dev, 0);
  	i2c_set_sda(dev, 0);
  	i2c_set_scl(dev, 1);
  	i2c_set_sda(dev, 1);
  }
  
  static void i2c_write(struct comedi_device *dev, unsigned int address,
  		      const u8 *data, unsigned int length)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int i;
  	u8 bitstream;
  	static const int read_bit = 0x1;
  
  	/*
  	 * XXX need mutex to prevent simultaneous attempts to access
  	 * eeprom and i2c bus
  	 */
  
  	/* make sure we dont send anything to eeprom */
  	devpriv->plx_control_bits &= ~PLX_CNTRL_EECS;
  
  	i2c_stop(dev);
  	i2c_start(dev);
  
  	/* send address and write bit */
  	bitstream = (address << 1) & ~read_bit;
  	i2c_write_byte(dev, bitstream);
  
  	/* get acknowledge */
  	if (i2c_read_ack(dev) != 0) {
  		dev_err(dev->class_dev, "failed: no acknowledge\n");
  		i2c_stop(dev);
  		return;
  	}
  	/* write data bytes */
  	for (i = 0; i < length; i++) {
  		i2c_write_byte(dev, data[i]);
  		if (i2c_read_ack(dev) != 0) {
  			dev_err(dev->class_dev, "failed: no acknowledge\n");
  			i2c_stop(dev);
  			return;
  		}
  	}
  	i2c_stop(dev);
  }
  
  static int cb_pcidas64_ai_eoc(struct comedi_device *dev,
  			      struct comedi_subdevice *s,
  			      struct comedi_insn *insn,
  			      unsigned long context)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int status;
  
  	status = readw(devpriv->main_iobase + HW_STATUS_REG);
  	if (board->layout == LAYOUT_4020) {
  		status = readw(devpriv->main_iobase + ADC_WRITE_PNTR_REG);
  		if (status)
  			return 0;
  	} else {
  		if (pipe_full_bits(status))
  			return 0;
  	}
  	return -EBUSY;
  }
  
  static int ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
  		    struct comedi_insn *insn, unsigned int *data)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int bits = 0, n;
  	unsigned int channel, range, aref;
  	unsigned long flags;
  	int ret;
  
  	channel = CR_CHAN(insn->chanspec);
  	range = CR_RANGE(insn->chanspec);
  	aref = CR_AREF(insn->chanspec);
  
  	/* disable card's analog input interrupt sources and pacing */
  	/* 4020 generates dac done interrupts even though they are disabled */
  	disable_ai_pacing(dev);
  
  	spin_lock_irqsave(&dev->spinlock, flags);
  	if (insn->chanspec & CR_ALT_FILTER)
  		devpriv->adc_control1_bits |= ADC_DITHER_BIT;
  	else
  		devpriv->adc_control1_bits &= ~ADC_DITHER_BIT;
  	writew(devpriv->adc_control1_bits,
  	       devpriv->main_iobase + ADC_CONTROL1_REG);
  	spin_unlock_irqrestore(&dev->spinlock, flags);
  
  	if (board->layout != LAYOUT_4020) {
  		/* use internal queue */
  		devpriv->hw_config_bits &= ~EXT_QUEUE_BIT;
  		writew(devpriv->hw_config_bits,
  		       devpriv->main_iobase + HW_CONFIG_REG);
  
  		/* ALT_SOURCE is internal calibration reference */
  		if (insn->chanspec & CR_ALT_SOURCE) {
  			unsigned int cal_en_bit;
  
  			if (board->layout == LAYOUT_60XX)
  				cal_en_bit = CAL_EN_60XX_BIT;
  			else
  				cal_en_bit = CAL_EN_64XX_BIT;
  			/*
  			 * select internal reference source to connect
  			 * to channel 0
  			 */
  			writew(cal_en_bit |
  			       adc_src_bits(devpriv->calibration_source),
  			       devpriv->main_iobase + CALIBRATION_REG);
  		} else {
  			/*
  			 * make sure internal calibration source
  			 * is turned off
  			 */
  			writew(0, devpriv->main_iobase + CALIBRATION_REG);
  		}
  		/* load internal queue */
  		bits = 0;
  		/* set gain */
  		bits |= ai_range_bits_6xxx(dev, CR_RANGE(insn->chanspec));
  		/* set single-ended / differential */
  		bits |= se_diff_bit_6xxx(dev, aref == AREF_DIFF);
  		if (aref == AREF_COMMON)
  			bits |= ADC_COMMON_BIT;
  		bits |= adc_chan_bits(channel);
  		/* set stop channel */
  		writew(adc_chan_bits(channel),
  		       devpriv->main_iobase + ADC_QUEUE_HIGH_REG);
  		/* set start channel, and rest of settings */
  		writew(bits, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
  	} else {
  		u8 old_cal_range_bits = devpriv->i2c_cal_range_bits;
  
  		devpriv->i2c_cal_range_bits &= ~ADC_SRC_4020_MASK;
  		if (insn->chanspec & CR_ALT_SOURCE) {
  			devpriv->i2c_cal_range_bits |=
  				adc_src_4020_bits(devpriv->calibration_source);
  		} else {	/* select BNC inputs */
  			devpriv->i2c_cal_range_bits |= adc_src_4020_bits(4);
  		}
  		/* select range */
  		if (range == 0)
  			devpriv->i2c_cal_range_bits |= attenuate_bit(channel);
  		else
  			devpriv->i2c_cal_range_bits &= ~attenuate_bit(channel);
  		/*
  		 * update calibration/range i2c register only if necessary,
  		 * as it is very slow
  		 */
  		if (old_cal_range_bits != devpriv->i2c_cal_range_bits) {
  			u8 i2c_data = devpriv->i2c_cal_range_bits;
  
  			i2c_write(dev, RANGE_CAL_I2C_ADDR, &i2c_data,
  				  sizeof(i2c_data));
  		}
  
  		/*
  		 * 4020 manual asks that sample interval register to be set
  		 * before writing to convert register.
  		 * Using somewhat arbitrary setting of 4 master clock ticks
  		 * = 0.1 usec
  		 */
  		writew(0, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
  		writew(2, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG);
  	}
  
  	for (n = 0; n < insn->n; n++) {
  		/* clear adc buffer (inside loop for 4020 sake) */
  		writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);
  
  		/* trigger conversion, bits sent only matter for 4020 */
  		writew(adc_convert_chan_4020_bits(CR_CHAN(insn->chanspec)),
  		       devpriv->main_iobase + ADC_CONVERT_REG);
  
  		/* wait for data */
  		ret = comedi_timeout(dev, s, insn, cb_pcidas64_ai_eoc, 0);
  		if (ret)
  			return ret;
  
  		if (board->layout == LAYOUT_4020)
  			data[n] = readl(dev->mmio + ADC_FIFO_REG) & 0xffff;
  		else
  			data[n] = readw(devpriv->main_iobase + PIPE1_READ_REG);
  	}
  
  	return n;
  }
  
  static int ai_config_calibration_source(struct comedi_device *dev,
  					unsigned int *data)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int source = data[1];
  	int num_calibration_sources;
  
  	if (board->layout == LAYOUT_60XX)
  		num_calibration_sources = 16;
  	else
  		num_calibration_sources = 8;
  	if (source >= num_calibration_sources) {
  		dev_dbg(dev->class_dev, "invalid calibration source: %i\n",
  			source);
  		return -EINVAL;
  	}
  
  	devpriv->calibration_source = source;
  
  	return 2;
  }
  
  static int ai_config_block_size(struct comedi_device *dev, unsigned int *data)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	int fifo_size;
  	const struct hw_fifo_info *const fifo = board->ai_fifo;
  	unsigned int block_size, requested_block_size;
  	int retval;
  
  	requested_block_size = data[1];
  
  	if (requested_block_size) {
  		fifo_size = requested_block_size * fifo->num_segments /
  			    bytes_in_sample;
  
  		retval = set_ai_fifo_size(dev, fifo_size);
  		if (retval < 0)
  			return retval;
  	}
  
  	block_size = ai_fifo_size(dev) / fifo->num_segments * bytes_in_sample;
  
  	data[1] = block_size;
  
  	return 2;
  }
  
  static int ai_config_master_clock_4020(struct comedi_device *dev,
  				       unsigned int *data)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int divisor = data[4];
  	int retval = 0;
  
  	if (divisor < 2) {
  		divisor = 2;
  		retval = -EAGAIN;
  	}
  
  	switch (data[1]) {
  	case COMEDI_EV_SCAN_BEGIN:
  		devpriv->ext_clock.divisor = divisor;
  		devpriv->ext_clock.chanspec = data[2];
  		break;
  	default:
  		return -EINVAL;
  	}
  
  	data[4] = divisor;
  
  	return retval ? retval : 5;
  }
  
  /* XXX could add support for 60xx series */
  static int ai_config_master_clock(struct comedi_device *dev, unsigned int *data)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  
  	switch (board->layout) {
  	case LAYOUT_4020:
  		return ai_config_master_clock_4020(dev, data);
  	default:
  		return -EINVAL;
  	}
  
  	return -EINVAL;
  }
  
  static int ai_config_insn(struct comedi_device *dev, struct comedi_subdevice *s,
  			  struct comedi_insn *insn, unsigned int *data)
  {
  	int id = data[0];
  
  	switch (id) {
  	case INSN_CONFIG_ALT_SOURCE:
  		return ai_config_calibration_source(dev, data);
  	case INSN_CONFIG_BLOCK_SIZE:
  		return ai_config_block_size(dev, data);
  	case INSN_CONFIG_TIMER_1:
  		return ai_config_master_clock(dev, data);
  	default:
  		return -EINVAL;
  	}
  	return -EINVAL;
  }
  
  /*
   * Gets nearest achievable timing given master clock speed, does not
   * take into account possible minimum/maximum divisor values.  Used
   * by other timing checking functions.
   */
  static unsigned int get_divisor(unsigned int ns, unsigned int flags)
  {
  	unsigned int divisor;
  
  	switch (flags & CMDF_ROUND_MASK) {
  	case CMDF_ROUND_UP:
  		divisor = DIV_ROUND_UP(ns, TIMER_BASE);
  		break;
  	case CMDF_ROUND_DOWN:
  		divisor = ns / TIMER_BASE;
  		break;
  	case CMDF_ROUND_NEAREST:
  	default:
  		divisor = DIV_ROUND_CLOSEST(ns, TIMER_BASE);
  		break;
  	}
  	return divisor;
  }
  
  /*
   * utility function that rounds desired timing to an achievable time, and
   * sets cmd members appropriately.
   * adc paces conversions from master clock by dividing by (x + 3) where x is
   * 24 bit number
   */
  static void check_adc_timing(struct comedi_device *dev, struct comedi_cmd *cmd)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	unsigned long long convert_divisor = 0;
  	unsigned int scan_divisor;
  	static const int min_convert_divisor = 3;
  	static const int max_convert_divisor =
  		max_counter_value + min_convert_divisor;
  	static const int min_scan_divisor_4020 = 2;
  	unsigned long long max_scan_divisor, min_scan_divisor;
  
  	if (cmd->convert_src == TRIG_TIMER) {
  		if (board->layout == LAYOUT_4020) {
  			cmd->convert_arg = 0;
  		} else {
  			convert_divisor = get_divisor(cmd->convert_arg,
  						      cmd->flags);
  			if (convert_divisor > max_convert_divisor)
  				convert_divisor = max_convert_divisor;
  			if (convert_divisor < min_convert_divisor)
  				convert_divisor = min_convert_divisor;
  			cmd->convert_arg = convert_divisor * TIMER_BASE;
  		}
  	} else if (cmd->convert_src == TRIG_NOW) {
  		cmd->convert_arg = 0;
  	}
  
  	if (cmd->scan_begin_src == TRIG_TIMER) {
  		scan_divisor = get_divisor(cmd->scan_begin_arg, cmd->flags);
  		if (cmd->convert_src == TRIG_TIMER) {
  			min_scan_divisor = convert_divisor * cmd->chanlist_len;
  			max_scan_divisor =
  				(convert_divisor * cmd->chanlist_len - 1) +
  				max_counter_value;
  		} else {
  			min_scan_divisor = min_scan_divisor_4020;
  			max_scan_divisor = max_counter_value + min_scan_divisor;
  		}
  		if (scan_divisor > max_scan_divisor)
  			scan_divisor = max_scan_divisor;
  		if (scan_divisor < min_scan_divisor)
  			scan_divisor = min_scan_divisor;
  		cmd->scan_begin_arg = scan_divisor * TIMER_BASE;
  	}
  }
  
  static int cb_pcidas64_ai_check_chanlist(struct comedi_device *dev,
  					 struct comedi_subdevice *s,
  					 struct comedi_cmd *cmd)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
  	int i;
  
  	for (i = 1; i < cmd->chanlist_len; i++) {
  		unsigned int aref = CR_AREF(cmd->chanlist[i]);
  
  		if (aref != aref0) {
  			dev_dbg(dev->class_dev,
  				"all elements in chanlist must use the same analog reference\n");
  			return -EINVAL;
  		}
  	}
  
  	if (board->layout == LAYOUT_4020) {
  		unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
  
  		for (i = 1; i < cmd->chanlist_len; i++) {
  			unsigned int chan = CR_CHAN(cmd->chanlist[i]);
  
  			if (chan != (chan0 + i)) {
  				dev_dbg(dev->class_dev,
  					"chanlist must use consecutive channels\n");
  				return -EINVAL;
  			}
  		}
  		if (cmd->chanlist_len == 3) {
  			dev_dbg(dev->class_dev,
  				"chanlist cannot be 3 channels long, use 1, 2, or 4 channels\n");
  			return -EINVAL;
  		}
  	}
  
  	return 0;
  }
  
  static int ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
  		      struct comedi_cmd *cmd)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	int err = 0;
  	unsigned int tmp_arg, tmp_arg2;
  	unsigned int triggers;
  
  	/* Step 1 : check if triggers are trivially valid */
  
  	err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
  
  	triggers = TRIG_TIMER;
  	if (board->layout == LAYOUT_4020)
  		triggers |= TRIG_OTHER;
  	else
  		triggers |= TRIG_FOLLOW;
  	err |= comedi_check_trigger_src(&cmd->scan_begin_src, triggers);
  
  	triggers = TRIG_TIMER;
  	if (board->layout == LAYOUT_4020)
  		triggers |= TRIG_NOW;
  	else
  		triggers |= TRIG_EXT;
  	err |= comedi_check_trigger_src(&cmd->convert_src, triggers);
  	err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
  	err |= comedi_check_trigger_src(&cmd->stop_src,
  					TRIG_COUNT | TRIG_EXT | TRIG_NONE);
  
  	if (err)
  		return 1;
  
  	/* Step 2a : make sure trigger sources are unique */
  
  	err |= comedi_check_trigger_is_unique(cmd->start_src);
  	err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
  	err |= comedi_check_trigger_is_unique(cmd->convert_src);
  	err |= comedi_check_trigger_is_unique(cmd->stop_src);
  
  	/* Step 2b : and mutually compatible */
  
  	if (cmd->convert_src == TRIG_EXT && cmd->scan_begin_src == TRIG_TIMER)
  		err |= -EINVAL;
  
  	if (err)
  		return 2;
  
  	/* Step 3: check if arguments are trivially valid */
  
  	switch (cmd->start_src) {
  	case TRIG_NOW:
  		err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
  		break;
  	case TRIG_EXT:
  		/*
  		 * start_arg is the CR_CHAN | CR_INVERT of the
  		 * external trigger.
  		 */
  		break;
  	}
  
  	if (cmd->convert_src == TRIG_TIMER) {
  		if (board->layout == LAYOUT_4020) {
  			err |= comedi_check_trigger_arg_is(&cmd->convert_arg,
  							   0);
  		} else {
  			err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
  							    board->ai_speed);
  			/*
  			 * if scans are timed faster than conversion rate
  			 * allows
  			 */
  			if (cmd->scan_begin_src == TRIG_TIMER) {
  				err |= comedi_check_trigger_arg_min(
  						&cmd->scan_begin_arg,
  						cmd->convert_arg *
  						cmd->chanlist_len);
  			}
  		}
  	}
  
  	err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
  	err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
  					   cmd->chanlist_len);
  
  	switch (cmd->stop_src) {
  	case TRIG_EXT:
  		break;
  	case TRIG_COUNT:
  		err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
  		break;
  	case TRIG_NONE:
  		err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
  		break;
  	default:
  		break;
  	}
  
  	if (err)
  		return 3;
  
  	/* step 4: fix up any arguments */
  
  	if (cmd->convert_src == TRIG_TIMER) {
  		tmp_arg = cmd->convert_arg;
  		tmp_arg2 = cmd->scan_begin_arg;
  		check_adc_timing(dev, cmd);
  		if (tmp_arg != cmd->convert_arg)
  			err++;
  		if (tmp_arg2 != cmd->scan_begin_arg)
  			err++;
  	}
  
  	if (err)
  		return 4;
  
  	/* Step 5: check channel list if it exists */
  	if (cmd->chanlist && cmd->chanlist_len > 0)
  		err |= cb_pcidas64_ai_check_chanlist(dev, s, cmd);
  
  	if (err)
  		return 5;
  
  	return 0;
  }
  
  static int use_hw_sample_counter(struct comedi_cmd *cmd)
  {
  /* disable for now until I work out a race */
  	return 0;
  
  	if (cmd->stop_src == TRIG_COUNT && cmd->stop_arg <= max_counter_value)
  		return 1;
  
  	return 0;
  }
  
  static void setup_sample_counters(struct comedi_device *dev,
  				  struct comedi_cmd *cmd)
  {
  	struct pcidas64_private *devpriv = dev->private;
  
  	/* load hardware conversion counter */
  	if (use_hw_sample_counter(cmd)) {
  		writew(cmd->stop_arg & 0xffff,
  		       devpriv->main_iobase + ADC_COUNT_LOWER_REG);
  		writew((cmd->stop_arg >> 16) & 0xff,
  		       devpriv->main_iobase + ADC_COUNT_UPPER_REG);
  	} else {
  		writew(1, devpriv->main_iobase + ADC_COUNT_LOWER_REG);
  	}
  }
  
  static inline unsigned int dma_transfer_size(struct comedi_device *dev)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int num_samples;
  
  	num_samples = devpriv->ai_fifo_segment_length *
  		      board->ai_fifo->sample_packing_ratio;
  	if (num_samples > DMA_BUFFER_SIZE / sizeof(u16))
  		num_samples = DMA_BUFFER_SIZE / sizeof(u16);
  
  	return num_samples;
  }
  
  static u32 ai_convert_counter_6xxx(const struct comedi_device *dev,
  					const struct comedi_cmd *cmd)
  {
  	/* supposed to load counter with desired divisor minus 3 */
  	return cmd->convert_arg / TIMER_BASE - 3;
  }
  
  static u32 ai_scan_counter_6xxx(struct comedi_device *dev,
  				     struct comedi_cmd *cmd)
  {
  	u32 count;
  
  	/* figure out how long we need to delay at end of scan */
  	switch (cmd->scan_begin_src) {
  	case TRIG_TIMER:
  		count = (cmd->scan_begin_arg -
  			 (cmd->convert_arg * (cmd->chanlist_len - 1))) /
  			TIMER_BASE;
  		break;
  	case TRIG_FOLLOW:
  		count = cmd->convert_arg / TIMER_BASE;
  		break;
  	default:
  		return 0;
  	}
  	return count - 3;
  }
  
  static u32 ai_convert_counter_4020(struct comedi_device *dev,
  					struct comedi_cmd *cmd)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int divisor;
  
  	switch (cmd->scan_begin_src) {
  	case TRIG_TIMER:
  		divisor = cmd->scan_begin_arg / TIMER_BASE;
  		break;
  	case TRIG_OTHER:
  		divisor = devpriv->ext_clock.divisor;
  		break;
  	default:		/* should never happen */
  		dev_err(dev->class_dev, "bug! failed to set ai pacing!\n");
  		divisor = 1000;
  		break;
  	}
  
  	/* supposed to load counter with desired divisor minus 2 for 4020 */
  	return divisor - 2;
  }
  
  static void select_master_clock_4020(struct comedi_device *dev,
  				     const struct comedi_cmd *cmd)
  {
  	struct pcidas64_private *devpriv = dev->private;
  
  	/* select internal/external master clock */
  	devpriv->hw_config_bits &= ~MASTER_CLOCK_4020_MASK;
  	if (cmd->scan_begin_src == TRIG_OTHER) {
  		int chanspec = devpriv->ext_clock.chanspec;
  
  		if (CR_CHAN(chanspec))
  			devpriv->hw_config_bits |= BNC_CLOCK_4020_BITS;
  		else
  			devpriv->hw_config_bits |= EXT_CLOCK_4020_BITS;
  	} else {
  		devpriv->hw_config_bits |= INTERNAL_CLOCK_4020_BITS;
  	}
  	writew(devpriv->hw_config_bits,
  	       devpriv->main_iobase + HW_CONFIG_REG);
  }
  
  static void select_master_clock(struct comedi_device *dev,
  				const struct comedi_cmd *cmd)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  
  	switch (board->layout) {
  	case LAYOUT_4020:
  		select_master_clock_4020(dev, cmd);
  		break;
  	default:
  		break;
  	}
  }
  
  static inline void dma_start_sync(struct comedi_device *dev,
  				  unsigned int channel)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned long flags;
  
  	/* spinlock for plx dma control/status reg */
  	spin_lock_irqsave(&dev->spinlock, flags);
  	writeb(PLX_DMACSR_ENABLE | PLX_DMACSR_START | PLX_DMACSR_CLEARINTR,
  	       devpriv->plx9080_iobase + PLX_REG_DMACSR(channel));
  	spin_unlock_irqrestore(&dev->spinlock, flags);
  }
  
  static void set_ai_pacing(struct comedi_device *dev, struct comedi_cmd *cmd)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	u32 convert_counter = 0, scan_counter = 0;
  
  	check_adc_timing(dev, cmd);
  
  	select_master_clock(dev, cmd);
  
  	if (board->layout == LAYOUT_4020) {
  		convert_counter = ai_convert_counter_4020(dev, cmd);
  	} else {
  		convert_counter = ai_convert_counter_6xxx(dev, cmd);
  		scan_counter = ai_scan_counter_6xxx(dev, cmd);
  	}
  
  	/* load lower 16 bits of convert interval */
  	writew(convert_counter & 0xffff,
  	       devpriv->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG);
  	/* load upper 8 bits of convert interval */
  	writew((convert_counter >> 16) & 0xff,
  	       devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
  	/* load lower 16 bits of scan delay */
  	writew(scan_counter & 0xffff,
  	       devpriv->main_iobase + ADC_DELAY_INTERVAL_LOWER_REG);
  	/* load upper 8 bits of scan delay */
  	writew((scan_counter >> 16) & 0xff,
  	       devpriv->main_iobase + ADC_DELAY_INTERVAL_UPPER_REG);
  }
  
  static int use_internal_queue_6xxx(const struct comedi_cmd *cmd)
  {
  	int i;
  
  	for (i = 0; i + 1 < cmd->chanlist_len; i++) {
  		if (CR_CHAN(cmd->chanlist[i + 1]) !=
  		    CR_CHAN(cmd->chanlist[i]) + 1)
  			return 0;
  		if (CR_RANGE(cmd->chanlist[i + 1]) !=
  		    CR_RANGE(cmd->chanlist[i]))
  			return 0;
  		if (CR_AREF(cmd->chanlist[i + 1]) != CR_AREF(cmd->chanlist[i]))
  			return 0;
  	}
  	return 1;
  }
  
  static int setup_channel_queue(struct comedi_device *dev,
  			       const struct comedi_cmd *cmd)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned short bits;
  	int i;
  
  	if (board->layout != LAYOUT_4020) {
  		if (use_internal_queue_6xxx(cmd)) {
  			devpriv->hw_config_bits &= ~EXT_QUEUE_BIT;
  			writew(devpriv->hw_config_bits,
  			       devpriv->main_iobase + HW_CONFIG_REG);
  			bits = 0;
  			/* set channel */
  			bits |= adc_chan_bits(CR_CHAN(cmd->chanlist[0]));
  			/* set gain */
  			bits |= ai_range_bits_6xxx(dev,
  						   CR_RANGE(cmd->chanlist[0]));
  			/* set single-ended / differential */
  			bits |= se_diff_bit_6xxx(dev,
  						 CR_AREF(cmd->chanlist[0]) ==
  						 AREF_DIFF);
  			if (CR_AREF(cmd->chanlist[0]) == AREF_COMMON)
  				bits |= ADC_COMMON_BIT;
  			/* set stop channel */
  			writew(adc_chan_bits
  			       (CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1])),
  			       devpriv->main_iobase + ADC_QUEUE_HIGH_REG);
  			/* set start channel, and rest of settings */
  			writew(bits,
  			       devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
  		} else {
  			/* use external queue */
  			if (dev->write_subdev && dev->write_subdev->busy) {
  				warn_external_queue(dev);
  				return -EBUSY;
  			}
  			devpriv->hw_config_bits |= EXT_QUEUE_BIT;
  			writew(devpriv->hw_config_bits,
  			       devpriv->main_iobase + HW_CONFIG_REG);
  			/* clear DAC buffer to prevent weird interactions */
  			writew(0,
  			       devpriv->main_iobase + DAC_BUFFER_CLEAR_REG);
  			/* clear queue pointer */
  			writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG);
  			/* load external queue */
  			for (i = 0; i < cmd->chanlist_len; i++) {
  				bits = 0;
  				/* set channel */
  				bits |= adc_chan_bits(CR_CHAN(cmd->
  							      chanlist[i]));
  				/* set gain */
  				bits |= ai_range_bits_6xxx(dev,
  							   CR_RANGE(cmd->
  								    chanlist
  								    [i]));
  				/* set single-ended / differential */
  				bits |= se_diff_bit_6xxx(dev,
  							 CR_AREF(cmd->
  								 chanlist[i]) ==
  							 AREF_DIFF);
  				if (CR_AREF(cmd->chanlist[i]) == AREF_COMMON)
  					bits |= ADC_COMMON_BIT;
  				/* mark end of queue */
  				if (i == cmd->chanlist_len - 1)
  					bits |= QUEUE_EOSCAN_BIT |
  						QUEUE_EOSEQ_BIT;
  				writew(bits,
  				       devpriv->main_iobase +
  				       ADC_QUEUE_FIFO_REG);
  			}
  			/*
  			 * doing a queue clear is not specified in board docs,
  			 * but required for reliable operation
  			 */
  			writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG);
  			/* prime queue holding register */
  			writew(0, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
  		}
  	} else {
  		unsigned short old_cal_range_bits = devpriv->i2c_cal_range_bits;
  
  		devpriv->i2c_cal_range_bits &= ~ADC_SRC_4020_MASK;
  		/* select BNC inputs */
  		devpriv->i2c_cal_range_bits |= adc_src_4020_bits(4);
  		/* select ranges */
  		for (i = 0; i < cmd->chanlist_len; i++) {
  			unsigned int channel = CR_CHAN(cmd->chanlist[i]);
  			unsigned int range = CR_RANGE(cmd->chanlist[i]);
  
  			if (range == 0)
  				devpriv->i2c_cal_range_bits |=
  					attenuate_bit(channel);
  			else
  				devpriv->i2c_cal_range_bits &=
  					~attenuate_bit(channel);
  		}
  		/*
  		 * update calibration/range i2c register only if necessary,
  		 * as it is very slow
  		 */
  		if (old_cal_range_bits != devpriv->i2c_cal_range_bits) {
  			u8 i2c_data = devpriv->i2c_cal_range_bits;
  
  			i2c_write(dev, RANGE_CAL_I2C_ADDR, &i2c_data,
  				  sizeof(i2c_data));
  		}
  	}
  	return 0;
  }
  
  static inline void load_first_dma_descriptor(struct comedi_device *dev,
  					     unsigned int dma_channel,
  					     unsigned int descriptor_bits)
  {
  	struct pcidas64_private *devpriv = dev->private;
  
  	/*
  	 * The transfer size, pci address, and local address registers
  	 * are supposedly unused during chained dma,
  	 * but I have found that left over values from last operation
  	 * occasionally cause problems with transfer of first dma
  	 * block.  Initializing them to zero seems to fix the problem.
  	 */
  	if (dma_channel) {
  		writel(0, devpriv->plx9080_iobase + PLX_REG_DMASIZ1);
  		writel(0, devpriv->plx9080_iobase + PLX_REG_DMAPADR1);
  		writel(0, devpriv->plx9080_iobase + PLX_REG_DMALADR1);
  		writel(descriptor_bits,
  		       devpriv->plx9080_iobase + PLX_REG_DMADPR1);
  	} else {
  		writel(0, devpriv->plx9080_iobase + PLX_REG_DMASIZ0);
  		writel(0, devpriv->plx9080_iobase + PLX_REG_DMAPADR0);
  		writel(0, devpriv->plx9080_iobase + PLX_REG_DMALADR0);
  		writel(descriptor_bits,
  		       devpriv->plx9080_iobase + PLX_REG_DMADPR0);
  	}
  }
  
  static int ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	struct comedi_async *async = s->async;
  	struct comedi_cmd *cmd = &async->cmd;
  	u32 bits;
  	unsigned int i;
  	unsigned long flags;
  	int retval;
  
  	disable_ai_pacing(dev);
  	abort_dma(dev, 1);
  
  	retval = setup_channel_queue(dev, cmd);
  	if (retval < 0)
  		return retval;
  
  	/* make sure internal calibration source is turned off */
  	writew(0, devpriv->main_iobase + CALIBRATION_REG);
  
  	set_ai_pacing(dev, cmd);
  
  	setup_sample_counters(dev, cmd);
  
  	enable_ai_interrupts(dev, cmd);
  
  	spin_lock_irqsave(&dev->spinlock, flags);
  	/* set mode, allow conversions through software gate */
  	devpriv->adc_control1_bits |= ADC_SW_GATE_BIT;
  	devpriv->adc_control1_bits &= ~ADC_DITHER_BIT;
  	if (board->layout != LAYOUT_4020) {
  		devpriv->adc_control1_bits &= ~ADC_MODE_MASK;
  		if (cmd->convert_src == TRIG_EXT)
  			/* good old mode 13 */
  			devpriv->adc_control1_bits |= adc_mode_bits(13);
  		else
  			/* mode 8.  What else could you need? */
  			devpriv->adc_control1_bits |= adc_mode_bits(8);
  	} else {
  		devpriv->adc_control1_bits &= ~CHANNEL_MODE_4020_MASK;
  		if (cmd->chanlist_len == 4)
  			devpriv->adc_control1_bits |= FOUR_CHANNEL_4020_BITS;
  		else if (cmd->chanlist_len == 2)
  			devpriv->adc_control1_bits |= TWO_CHANNEL_4020_BITS;
  		devpriv->adc_control1_bits &= ~ADC_LO_CHANNEL_4020_MASK;
  		devpriv->adc_control1_bits |=
  			adc_lo_chan_4020_bits(CR_CHAN(cmd->chanlist[0]));
  		devpriv->adc_control1_bits &= ~ADC_HI_CHANNEL_4020_MASK;
  		devpriv->adc_control1_bits |=
  			adc_hi_chan_4020_bits(CR_CHAN(cmd->chanlist
  						      [cmd->chanlist_len - 1]));
  	}
  	writew(devpriv->adc_control1_bits,
  	       devpriv->main_iobase + ADC_CONTROL1_REG);
  	spin_unlock_irqrestore(&dev->spinlock, flags);
  
  	/* clear adc buffer */
  	writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);
  
  	if ((cmd->flags & CMDF_WAKE_EOS) == 0 ||
  	    board->layout == LAYOUT_4020) {
  		devpriv->ai_dma_index = 0;
  
  		/* set dma transfer size */
  		for (i = 0; i < ai_dma_ring_count(board); i++)
  			devpriv->ai_dma_desc[i].transfer_size =
  				cpu_to_le32(dma_transfer_size(dev) *
  					    sizeof(u16));
  
  		/* give location of first dma descriptor */
  		load_first_dma_descriptor(dev, 1,
  					  devpriv->ai_dma_desc_bus_addr |
  					  PLX_DMADPR_DESCPCI |
  					  PLX_DMADPR_TCINTR |
  					  PLX_DMADPR_XFERL2P);
  
  		dma_start_sync(dev, 1);
  	}
  
  	if (board->layout == LAYOUT_4020) {
  		/* set source for external triggers */
  		bits = 0;
  		if (cmd->start_src == TRIG_EXT && CR_CHAN(cmd->start_arg))
  			bits |= EXT_START_TRIG_BNC_BIT;
  		if (cmd->stop_src == TRIG_EXT && CR_CHAN(cmd->stop_arg))
  			bits |= EXT_STOP_TRIG_BNC_BIT;
  		writew(bits, devpriv->main_iobase + DAQ_ATRIG_LOW_4020_REG);
  	}
  
  	spin_lock_irqsave(&dev->spinlock, flags);
  
  	/* enable pacing, triggering, etc */
  	bits = ADC_ENABLE_BIT | ADC_SOFT_GATE_BITS | ADC_GATE_LEVEL_BIT;
  	if (cmd->flags & CMDF_WAKE_EOS)
  		bits |= ADC_DMA_DISABLE_BIT;
  	/* set start trigger */
  	if (cmd->start_src == TRIG_EXT) {
  		bits |= ADC_START_TRIG_EXT_BITS;
  		if (cmd->start_arg & CR_INVERT)
  			bits |= ADC_START_TRIG_FALLING_BIT;
  	} else if (cmd->start_src == TRIG_NOW) {
  		bits |= ADC_START_TRIG_SOFT_BITS;
  	}
  	if (use_hw_sample_counter(cmd))
  		bits |= ADC_SAMPLE_COUNTER_EN_BIT;
  	writew(bits, devpriv->main_iobase + ADC_CONTROL0_REG);
  
  	devpriv->ai_cmd_running = 1;
  
  	spin_unlock_irqrestore(&dev->spinlock, flags);
  
  	/* start acquisition */
  	if (cmd->start_src == TRIG_NOW)
  		writew(0, devpriv->main_iobase + ADC_START_REG);
  
  	return 0;
  }
  
  /* read num_samples from 16 bit wide ai fifo */
  static void pio_drain_ai_fifo_16(struct comedi_device *dev)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	struct comedi_subdevice *s = dev->read_subdev;
  	unsigned int i;
  	u16 prepost_bits;
  	int read_segment, read_index, write_segment, write_index;
  	int num_samples;
  
  	do {
  		/* get least significant 15 bits */
  		read_index = readw(devpriv->main_iobase + ADC_READ_PNTR_REG) &
  			     0x7fff;
  		write_index = readw(devpriv->main_iobase + ADC_WRITE_PNTR_REG) &
  			      0x7fff;
  		/*
  		 * Get most significant bits (grey code).
  		 * Different boards use different code so use a scheme
  		 * that doesn't depend on encoding.  This read must
  		 * occur after reading least significant 15 bits to avoid race
  		 * with fifo switching to next segment.
  		 */
  		prepost_bits = readw(devpriv->main_iobase + PREPOST_REG);
  
  		/*
  		 * if read and write pointers are not on the same fifo segment,
  		 * read to the end of the read segment
  		 */
  		read_segment = adc_upper_read_ptr_code(prepost_bits);
  		write_segment = adc_upper_write_ptr_code(prepost_bits);
  
  		if (read_segment != write_segment)
  			num_samples =
  				devpriv->ai_fifo_segment_length - read_index;
  		else
  			num_samples = write_index - read_index;
  		if (num_samples < 0) {
  			dev_err(dev->class_dev,
  				"cb_pcidas64: bug! num_samples < 0\n");
  			break;
  		}
  
  		num_samples = comedi_nsamples_left(s, num_samples);
  		if (num_samples == 0)
  			break;
  
  		for (i = 0; i < num_samples; i++) {
  			unsigned short val;
  
  			val = readw(devpriv->main_iobase + ADC_FIFO_REG);
  			comedi_buf_write_samples(s, &val, 1);
  		}
  
  	} while (read_segment != write_segment);
  }
  
  /*
   * Read from 32 bit wide ai fifo of 4020 - deal with insane grey coding of
   * pointers.  The pci-4020 hardware only supports dma transfers (it only
   * supports the use of pio for draining the last remaining points from the
   * fifo when a data acquisition operation has completed).
   */
  static void pio_drain_ai_fifo_32(struct comedi_device *dev)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	struct comedi_subdevice *s = dev->read_subdev;
  	unsigned int nsamples;
  	unsigned int i;
  	u32 fifo_data;
  	int write_code =
  		readw(devpriv->main_iobase + ADC_WRITE_PNTR_REG) & 0x7fff;
  	int read_code =
  		readw(devpriv->main_iobase + ADC_READ_PNTR_REG) & 0x7fff;
  
  	nsamples = comedi_nsamples_left(s, 100000);
  	for (i = 0; read_code != write_code && i < nsamples;) {
  		unsigned short val;
  
  		fifo_data = readl(dev->mmio + ADC_FIFO_REG);
  		val = fifo_data & 0xffff;
  		comedi_buf_write_samples(s, &val, 1);
  		i++;
  		if (i < nsamples) {
  			val = (fifo_data >> 16) & 0xffff;
  			comedi_buf_write_samples(s, &val, 1);
  			i++;
  		}
  		read_code = readw(devpriv->main_iobase + ADC_READ_PNTR_REG) &
  			    0x7fff;
  	}
  }
  
  /* empty fifo */
  static void pio_drain_ai_fifo(struct comedi_device *dev)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  
  	if (board->layout == LAYOUT_4020)
  		pio_drain_ai_fifo_32(dev);
  	else
  		pio_drain_ai_fifo_16(dev);
  }
  
  static void drain_dma_buffers(struct comedi_device *dev, unsigned int channel)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	struct comedi_subdevice *s = dev->read_subdev;
  	u32 next_transfer_addr;
  	int j;
  	int num_samples = 0;
  	void __iomem *pci_addr_reg;
  
  	pci_addr_reg = devpriv->plx9080_iobase + PLX_REG_DMAPADR(channel);
  
  	/* loop until we have read all the full buffers */
  	for (j = 0, next_transfer_addr = readl(pci_addr_reg);
  	     (next_transfer_addr <
  	      devpriv->ai_buffer_bus_addr[devpriv->ai_dma_index] ||
  	      next_transfer_addr >=
  	      devpriv->ai_buffer_bus_addr[devpriv->ai_dma_index] +
  	      DMA_BUFFER_SIZE) && j < ai_dma_ring_count(board); j++) {
  		/* transfer data from dma buffer to comedi buffer */
  		num_samples = comedi_nsamples_left(s, dma_transfer_size(dev));
  		comedi_buf_write_samples(s,
  				devpriv->ai_buffer[devpriv->ai_dma_index],
  				num_samples);
  		devpriv->ai_dma_index = (devpriv->ai_dma_index + 1) %
  					ai_dma_ring_count(board);
  	}
  	/*
  	 * XXX check for dma ring buffer overrun
  	 * (use end-of-chain bit to mark last unused buffer)
  	 */
  }
  
  static void handle_ai_interrupt(struct comedi_device *dev,
  				unsigned short status,
  				unsigned int plx_status)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	struct comedi_subdevice *s = dev->read_subdev;
  	struct comedi_async *async = s->async;
  	struct comedi_cmd *cmd = &async->cmd;
  	u8 dma1_status;
  	unsigned long flags;
  
  	/* check for fifo overrun */
  	if (status & ADC_OVERRUN_BIT) {
  		dev_err(dev->class_dev, "fifo overrun\n");
  		async->events |= COMEDI_CB_ERROR;
  	}
  	/* spin lock makes sure no one else changes plx dma control reg */
  	spin_lock_irqsave(&dev->spinlock, flags);
  	dma1_status = readb(devpriv->plx9080_iobase + PLX_REG_DMACSR1);
  	if (plx_status & PLX_INTCSR_DMA1IA) {	/* dma chan 1 interrupt */
  		writeb((dma1_status & PLX_DMACSR_ENABLE) | PLX_DMACSR_CLEARINTR,
  		       devpriv->plx9080_iobase + PLX_REG_DMACSR1);
  
  		if (dma1_status & PLX_DMACSR_ENABLE)
  			drain_dma_buffers(dev, 1);
  	}
  	spin_unlock_irqrestore(&dev->spinlock, flags);
  
  	/* drain fifo with pio */
  	if ((status & ADC_DONE_BIT) ||
  	    ((cmd->flags & CMDF_WAKE_EOS) &&
  	     (status & ADC_INTR_PENDING_BIT) &&
  	     (board->layout != LAYOUT_4020))) {
  		spin_lock_irqsave(&dev->spinlock, flags);
  		if (devpriv->ai_cmd_running) {
  			spin_unlock_irqrestore(&dev->spinlock, flags);
  			pio_drain_ai_fifo(dev);
  		} else {
  			spin_unlock_irqrestore(&dev->spinlock, flags);
  		}
  	}
  	/* if we are have all the data, then quit */
  	if ((cmd->stop_src == TRIG_COUNT &&
  	     async->scans_done >= cmd->stop_arg) ||
  	    (cmd->stop_src == TRIG_EXT && (status & ADC_STOP_BIT)))
  		async->events |= COMEDI_CB_EOA;
  
  	comedi_handle_events(dev, s);
  }
  
  static inline unsigned int prev_ao_dma_index(struct comedi_device *dev)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int buffer_index;
  
  	if (devpriv->ao_dma_index == 0)
  		buffer_index = AO_DMA_RING_COUNT - 1;
  	else
  		buffer_index = devpriv->ao_dma_index - 1;
  	return buffer_index;
  }
  
  static int last_ao_dma_load_completed(struct comedi_device *dev)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int buffer_index;
  	unsigned int transfer_address;
  	unsigned short dma_status;
  
  	buffer_index = prev_ao_dma_index(dev);
  	dma_status = readb(devpriv->plx9080_iobase + PLX_REG_DMACSR0);
  	if ((dma_status & PLX_DMACSR_DONE) == 0)
  		return 0;
  
  	transfer_address =
  		readl(devpriv->plx9080_iobase + PLX_REG_DMAPADR0);
  	if (transfer_address != devpriv->ao_buffer_bus_addr[buffer_index])
  		return 0;
  
  	return 1;
  }
  
  static inline int ao_dma_needs_restart(struct comedi_device *dev,
  				       unsigned short dma_status)
  {
  	if ((dma_status & PLX_DMACSR_DONE) == 0 ||
  	    (dma_status & PLX_DMACSR_ENABLE) == 0)
  		return 0;
  	if (last_ao_dma_load_completed(dev))
  		return 0;
  
  	return 1;
  }
  
  static void restart_ao_dma(struct comedi_device *dev)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int dma_desc_bits;
  
  	dma_desc_bits = readl(devpriv->plx9080_iobase + PLX_REG_DMADPR0);
  	dma_desc_bits &= ~PLX_DMADPR_CHAINEND;
  	load_first_dma_descriptor(dev, 0, dma_desc_bits);
  
  	dma_start_sync(dev, 0);
  }
  
  static unsigned int cb_pcidas64_ao_fill_buffer(struct comedi_device *dev,
  					       struct comedi_subdevice *s,
  					       unsigned short *dest,
  					       unsigned int max_bytes)
  {
  	unsigned int nsamples = comedi_bytes_to_samples(s, max_bytes);
  	unsigned int actual_bytes;
  
  	nsamples = comedi_nsamples_left(s, nsamples);
  	actual_bytes = comedi_buf_read_samples(s, dest, nsamples);
  
  	return comedi_bytes_to_samples(s, actual_bytes);
  }
  
  static unsigned int load_ao_dma_buffer(struct comedi_device *dev,
  				       const struct comedi_cmd *cmd)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	struct comedi_subdevice *s = dev->write_subdev;
  	unsigned int buffer_index = devpriv->ao_dma_index;
  	unsigned int prev_buffer_index = prev_ao_dma_index(dev);
  	unsigned int nsamples;
  	unsigned int nbytes;
  	unsigned int next_bits;
  
  	nsamples = cb_pcidas64_ao_fill_buffer(dev, s,
  					      devpriv->ao_buffer[buffer_index],
  					      DMA_BUFFER_SIZE);
  	if (nsamples == 0)
  		return 0;
  
  	nbytes = comedi_samples_to_bytes(s, nsamples);
  	devpriv->ao_dma_desc[buffer_index].transfer_size = cpu_to_le32(nbytes);
  	/* set end of chain bit so we catch underruns */
  	next_bits = le32_to_cpu(devpriv->ao_dma_desc[buffer_index].next);
  	next_bits |= PLX_DMADPR_CHAINEND;
  	devpriv->ao_dma_desc[buffer_index].next = cpu_to_le32(next_bits);
  	/*
  	 * clear end of chain bit on previous buffer now that we have set it
  	 * for the last buffer
  	 */
  	next_bits = le32_to_cpu(devpriv->ao_dma_desc[prev_buffer_index].next);
  	next_bits &= ~PLX_DMADPR_CHAINEND;
  	devpriv->ao_dma_desc[prev_buffer_index].next = cpu_to_le32(next_bits);
  
  	devpriv->ao_dma_index = (buffer_index + 1) % AO_DMA_RING_COUNT;
  
  	return nbytes;
  }
  
  static void load_ao_dma(struct comedi_device *dev, const struct comedi_cmd *cmd)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int num_bytes;
  	unsigned int next_transfer_addr;
  	void __iomem *pci_addr_reg = devpriv->plx9080_iobase + PLX_REG_DMAPADR0;
  	unsigned int buffer_index;
  
  	do {
  		buffer_index = devpriv->ao_dma_index;
  		/* don't overwrite data that hasn't been transferred yet */
  		next_transfer_addr = readl(pci_addr_reg);
  		if (next_transfer_addr >=
  		    devpriv->ao_buffer_bus_addr[buffer_index] &&
  		    next_transfer_addr <
  		    devpriv->ao_buffer_bus_addr[buffer_index] +
  		    DMA_BUFFER_SIZE)
  			return;
  		num_bytes = load_ao_dma_buffer(dev, cmd);
  	} while (num_bytes >= DMA_BUFFER_SIZE);
  }
  
  static void handle_ao_interrupt(struct comedi_device *dev,
  				unsigned short status, unsigned int plx_status)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	struct comedi_subdevice *s = dev->write_subdev;
  	struct comedi_async *async;
  	struct comedi_cmd *cmd;
  	u8 dma0_status;
  	unsigned long flags;
  
  	/* board might not support ao, in which case write_subdev is NULL */
  	if (!s)
  		return;
  	async = s->async;
  	cmd = &async->cmd;
  
  	/* spin lock makes sure no one else changes plx dma control reg */
  	spin_lock_irqsave(&dev->spinlock, flags);
  	dma0_status = readb(devpriv->plx9080_iobase + PLX_REG_DMACSR0);
  	if (plx_status & PLX_INTCSR_DMA0IA) {	/*  dma chan 0 interrupt */
  		if ((dma0_status & PLX_DMACSR_ENABLE) &&
  		    !(dma0_status & PLX_DMACSR_DONE)) {
  			writeb(PLX_DMACSR_ENABLE | PLX_DMACSR_CLEARINTR,
  			       devpriv->plx9080_iobase + PLX_REG_DMACSR0);
  		} else {
  			writeb(PLX_DMACSR_CLEARINTR,
  			       devpriv->plx9080_iobase + PLX_REG_DMACSR0);
  		}
  		spin_unlock_irqrestore(&dev->spinlock, flags);
  		if (dma0_status & PLX_DMACSR_ENABLE) {
  			load_ao_dma(dev, cmd);
  			/* try to recover from dma end-of-chain event */
  			if (ao_dma_needs_restart(dev, dma0_status))
  				restart_ao_dma(dev);
  		}
  	} else {
  		spin_unlock_irqrestore(&dev->spinlock, flags);
  	}
  
  	if ((status & DAC_DONE_BIT)) {
  		if ((cmd->stop_src == TRIG_COUNT &&
  		     async->scans_done >= cmd->stop_arg) ||
  		    last_ao_dma_load_completed(dev))
  			async->events |= COMEDI_CB_EOA;
  		else
  			async->events |= COMEDI_CB_ERROR;
  	}
  	comedi_handle_events(dev, s);
  }
  
  static irqreturn_t handle_interrupt(int irq, void *d)
  {
  	struct comedi_device *dev = d;
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned short status;
  	u32 plx_status;
  	u32 plx_bits;
  
  	plx_status = readl(devpriv->plx9080_iobase + PLX_REG_INTCSR);
  	status = readw(devpriv->main_iobase + HW_STATUS_REG);
  
  	/*
  	 * an interrupt before all the postconfig stuff gets done could
  	 * cause a NULL dereference if we continue through the
  	 * interrupt handler
  	 */
  	if (!dev->attached)
  		return IRQ_HANDLED;
  
  	handle_ai_interrupt(dev, status, plx_status);
  	handle_ao_interrupt(dev, status, plx_status);
  
  	/* clear possible plx9080 interrupt sources */
  	if (plx_status & PLX_INTCSR_LDBIA) {
  		/* clear local doorbell interrupt */
  		plx_bits = readl(devpriv->plx9080_iobase + PLX_REG_L2PDBELL);
  		writel(plx_bits, devpriv->plx9080_iobase + PLX_REG_L2PDBELL);
  	}
  
  	return IRQ_HANDLED;
  }
  
  static int ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned long flags;
  
  	spin_lock_irqsave(&dev->spinlock, flags);
  	if (devpriv->ai_cmd_running == 0) {
  		spin_unlock_irqrestore(&dev->spinlock, flags);
  		return 0;
  	}
  	devpriv->ai_cmd_running = 0;
  	spin_unlock_irqrestore(&dev->spinlock, flags);
  
  	disable_ai_pacing(dev);
  
  	abort_dma(dev, 1);
  
  	return 0;
  }
  
  static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
  		    struct comedi_insn *insn, unsigned int *data)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	int chan = CR_CHAN(insn->chanspec);
  	int range = CR_RANGE(insn->chanspec);
  
  	/* do some initializing */
  	writew(0, devpriv->main_iobase + DAC_CONTROL0_REG);
  
  	/* set range */
  	set_dac_range_bits(dev, &devpriv->dac_control1_bits, chan, range);
  	writew(devpriv->dac_control1_bits,
  	       devpriv->main_iobase + DAC_CONTROL1_REG);
  
  	/* write to channel */
  	if (board->layout == LAYOUT_4020) {
  		writew(data[0] & 0xff,
  		       devpriv->main_iobase + dac_lsb_4020_reg(chan));
  		writew((data[0] >> 8) & 0xf,
  		       devpriv->main_iobase + dac_msb_4020_reg(chan));
  	} else {
  		writew(data[0], devpriv->main_iobase + dac_convert_reg(chan));
  	}
  
  	/* remember output value */
  	s->readback[chan] = data[0];
  
  	return 1;
  }
  
  static void set_dac_control0_reg(struct comedi_device *dev,
  				 const struct comedi_cmd *cmd)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int bits = DAC_ENABLE_BIT | WAVEFORM_GATE_LEVEL_BIT |
  			    WAVEFORM_GATE_ENABLE_BIT | WAVEFORM_GATE_SELECT_BIT;
  
  	if (cmd->start_src == TRIG_EXT) {
  		bits |= WAVEFORM_TRIG_EXT_BITS;
  		if (cmd->start_arg & CR_INVERT)
  			bits |= WAVEFORM_TRIG_FALLING_BIT;
  	} else {
  		bits |= WAVEFORM_TRIG_SOFT_BITS;
  	}
  	if (cmd->scan_begin_src == TRIG_EXT) {
  		bits |= DAC_EXT_UPDATE_ENABLE_BIT;
  		if (cmd->scan_begin_arg & CR_INVERT)
  			bits |= DAC_EXT_UPDATE_FALLING_BIT;
  	}
  	writew(bits, devpriv->main_iobase + DAC_CONTROL0_REG);
  }
  
  static void set_dac_control1_reg(struct comedi_device *dev,
  				 const struct comedi_cmd *cmd)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	int i;
  
  	for (i = 0; i < cmd->chanlist_len; i++) {
  		int channel, range;
  
  		channel = CR_CHAN(cmd->chanlist[i]);
  		range = CR_RANGE(cmd->chanlist[i]);
  		set_dac_range_bits(dev, &devpriv->dac_control1_bits, channel,
  				   range);
  	}
  	devpriv->dac_control1_bits |= DAC_SW_GATE_BIT;
  	writew(devpriv->dac_control1_bits,
  	       devpriv->main_iobase + DAC_CONTROL1_REG);
  }
  
  static void set_dac_select_reg(struct comedi_device *dev,
  			       const struct comedi_cmd *cmd)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	u16 bits;
  	unsigned int first_channel, last_channel;
  
  	first_channel = CR_CHAN(cmd->chanlist[0]);
  	last_channel = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]);
  	if (last_channel < first_channel)
  		dev_err(dev->class_dev,
  			"bug! last ao channel < first ao channel\n");
  
  	bits = (first_channel & 0x7) | (last_channel & 0x7) << 3;
  
  	writew(bits, devpriv->main_iobase + DAC_SELECT_REG);
  }
  
  static unsigned int get_ao_divisor(unsigned int ns, unsigned int flags)
  {
  	return get_divisor(ns, flags) - 2;
  }
  
  static void set_dac_interval_regs(struct comedi_device *dev,
  				  const struct comedi_cmd *cmd)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	unsigned int divisor;
  
  	if (cmd->scan_begin_src != TRIG_TIMER)
  		return;
  
  	divisor = get_ao_divisor(cmd->scan_begin_arg, cmd->flags);
  	if (divisor > max_counter_value) {
  		dev_err(dev->class_dev, "bug! ao divisor too big\n");
  		divisor = max_counter_value;
  	}
  	writew(divisor & 0xffff,
  	       devpriv->main_iobase + DAC_SAMPLE_INTERVAL_LOWER_REG);
  	writew((divisor >> 16) & 0xff,
  	       devpriv->main_iobase + DAC_SAMPLE_INTERVAL_UPPER_REG);
  }
  
  static int prep_ao_dma(struct comedi_device *dev, const struct comedi_cmd *cmd)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	struct comedi_subdevice *s = dev->write_subdev;
  	unsigned int nsamples;
  	unsigned int nbytes;
  	int i;
  
  	/*
  	 * clear queue pointer too, since external queue has
  	 * weird interactions with ao fifo
  	 */
  	writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG);
  	writew(0, devpriv->main_iobase + DAC_BUFFER_CLEAR_REG);
  
  	nsamples = cb_pcidas64_ao_fill_buffer(dev, s,
  					      devpriv->ao_bounce_buffer,
  					      DAC_FIFO_SIZE);
  	if (nsamples == 0)
  		return -1;
  
  	for (i = 0; i < nsamples; i++) {
  		writew(devpriv->ao_bounce_buffer[i],
  		       devpriv->main_iobase + DAC_FIFO_REG);
  	}
  
  	if (cmd->stop_src == TRIG_COUNT &&
  	    s->async->scans_done >= cmd->stop_arg)
  		return 0;
  
  	nbytes = load_ao_dma_buffer(dev, cmd);
  	if (nbytes == 0)
  		return -1;
  	load_ao_dma(dev, cmd);
  
  	dma_start_sync(dev, 0);
  
  	return 0;
  }
  
  static inline int external_ai_queue_in_use(struct comedi_device *dev,
  					   struct comedi_subdevice *s,
  					   struct comedi_cmd *cmd)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  
  	if (s->busy)
  		return 0;
  	if (board->layout == LAYOUT_4020)
  		return 0;
  	else if (use_internal_queue_6xxx(cmd))
  		return 0;
  	return 1;
  }
  
  static int ao_inttrig(struct comedi_device *dev, struct comedi_subdevice *s,
  		      unsigned int trig_num)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	struct comedi_cmd *cmd = &s->async->cmd;
  	int retval;
  
  	if (trig_num != cmd->start_arg)
  		return -EINVAL;
  
  	retval = prep_ao_dma(dev, cmd);
  	if (retval < 0)
  		return -EPIPE;
  
  	set_dac_control0_reg(dev, cmd);
  
  	if (cmd->start_src == TRIG_INT)
  		writew(0, devpriv->main_iobase + DAC_START_REG);
  
  	s->async->inttrig = NULL;
  
  	return 0;
  }
  
  static int ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	struct comedi_cmd *cmd = &s->async->cmd;
  
  	if (external_ai_queue_in_use(dev, s, cmd)) {
  		warn_external_queue(dev);
  		return -EBUSY;
  	}
  	/* disable analog output system during setup */
  	writew(0x0, devpriv->main_iobase + DAC_CONTROL0_REG);
  
  	devpriv->ao_dma_index = 0;
  
  	set_dac_select_reg(dev, cmd);
  	set_dac_interval_regs(dev, cmd);
  	load_first_dma_descriptor(dev, 0, devpriv->ao_dma_desc_bus_addr |
  				  PLX_DMADPR_DESCPCI | PLX_DMADPR_TCINTR);
  
  	set_dac_control1_reg(dev, cmd);
  	s->async->inttrig = ao_inttrig;
  
  	return 0;
  }
  
  static int cb_pcidas64_ao_check_chanlist(struct comedi_device *dev,
  					 struct comedi_subdevice *s,
  					 struct comedi_cmd *cmd)
  {
  	unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
  	int i;
  
  	for (i = 1; i < cmd->chanlist_len; i++) {
  		unsigned int chan = CR_CHAN(cmd->chanlist[i]);
  
  		if (chan != (chan0 + i)) {
  			dev_dbg(dev->class_dev,
  				"chanlist must use consecutive channels\n");
  			return -EINVAL;
  		}
  	}
  
  	return 0;
  }
  
  static int ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
  		      struct comedi_cmd *cmd)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	int err = 0;
  	unsigned int tmp_arg;
  
  	/* Step 1 : check if triggers are trivially valid */
  
  	err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT);
  	err |= comedi_check_trigger_src(&cmd->scan_begin_src,
  					TRIG_TIMER | TRIG_EXT);
  	err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
  	err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
  	err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
  
  	if (err)
  		return 1;
  
  	/* Step 2a : make sure trigger sources are unique */
  
  	err |= comedi_check_trigger_is_unique(cmd->start_src);
  	err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
  
  	/* Step 2b : and mutually compatible */
  
  	if (cmd->convert_src == TRIG_EXT && cmd->scan_begin_src == TRIG_TIMER)
  		err |= -EINVAL;
  	if (cmd->stop_src != TRIG_COUNT &&
  	    cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_EXT)
  		err |= -EINVAL;
  
  	if (err)
  		return 2;
  
  	/* Step 3: check if arguments are trivially valid */
  
  	err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
  
  	if (cmd->scan_begin_src == TRIG_TIMER) {
  		err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
  						    board->ao_scan_speed);
  		if (get_ao_divisor(cmd->scan_begin_arg, cmd->flags) >
  		    max_counter_value) {
  			cmd->scan_begin_arg = (max_counter_value + 2) *
  					      TIMER_BASE;
  			err |= -EINVAL;
  		}
  	}
  
  	err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
  	err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
  					   cmd->chanlist_len);
  
  	if (err)
  		return 3;
  
  	/* step 4: fix up any arguments */
  
  	if (cmd->scan_begin_src == TRIG_TIMER) {
  		tmp_arg = cmd->scan_begin_arg;
  		cmd->scan_begin_arg = get_divisor(cmd->scan_begin_arg,
  						  cmd->flags) * TIMER_BASE;
  		if (tmp_arg != cmd->scan_begin_arg)
  			err++;
  	}
  
  	if (err)
  		return 4;
  
  	/* Step 5: check channel list if it exists */
  	if (cmd->chanlist && cmd->chanlist_len > 0)
  		err |= cb_pcidas64_ao_check_chanlist(dev, s, cmd);
  
  	if (err)
  		return 5;
  
  	return 0;
  }
  
  static int ao_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
  {
  	struct pcidas64_private *devpriv = dev->private;
  
  	writew(0x0, devpriv->main_iobase + DAC_CONTROL0_REG);
  	abort_dma(dev, 0);
  	return 0;
  }
  
  static int dio_callback_4020(struct comedi_device *dev,
  			     int dir, int port, int data, unsigned long iobase)
  {
  	struct pcidas64_private *devpriv = dev->private;
  
  	if (dir) {
  		writew(data, devpriv->main_iobase + iobase + 2 * port);
  		return 0;
  	}
  	return readw(devpriv->main_iobase + iobase + 2 * port);
  }
  
  static int di_rbits(struct comedi_device *dev, struct comedi_subdevice *s,
  		    struct comedi_insn *insn, unsigned int *data)
  {
  	unsigned int bits;
  
  	bits = readb(dev->mmio + DI_REG);
  	bits &= 0xf;
  	data[1] = bits;
  	data[0] = 0;
  
  	return insn->n;
  }
  
  static int do_wbits(struct comedi_device *dev,
  		    struct comedi_subdevice *s,
  		    struct comedi_insn *insn,
  		    unsigned int *data)
  {
  	if (comedi_dio_update_state(s, data))
  		writeb(s->state, dev->mmio + DO_REG);
  
  	data[1] = s->state;
  
  	return insn->n;
  }
  
  static int dio_60xx_config_insn(struct comedi_device *dev,
  				struct comedi_subdevice *s,
  				struct comedi_insn *insn,
  				unsigned int *data)
  {
  	int ret;
  
  	ret = comedi_dio_insn_config(dev, s, insn, data, 0);
  	if (ret)
  		return ret;
  
  	writeb(s->io_bits, dev->mmio + DIO_DIRECTION_60XX_REG);
  
  	return insn->n;
  }
  
  static int dio_60xx_wbits(struct comedi_device *dev,
  			  struct comedi_subdevice *s,
  			  struct comedi_insn *insn,
  			  unsigned int *data)
  {
  	if (comedi_dio_update_state(s, data))
  		writeb(s->state, dev->mmio + DIO_DATA_60XX_REG);
  
  	data[1] = readb(dev->mmio + DIO_DATA_60XX_REG);
  
  	return insn->n;
  }
  
  /*
   * pci-6025 8800 caldac:
   * address 0 == dac channel 0 offset
   * address 1 == dac channel 0 gain
   * address 2 == dac channel 1 offset
   * address 3 == dac channel 1 gain
   * address 4 == fine adc offset
   * address 5 == coarse adc offset
   * address 6 == coarse adc gain
   * address 7 == fine adc gain
   */
  /*
   * pci-6402/16 uses all 8 channels for dac:
   * address 0 == dac channel 0 fine gain
   * address 1 == dac channel 0 coarse gain
   * address 2 == dac channel 0 coarse offset
   * address 3 == dac channel 1 coarse offset
   * address 4 == dac channel 1 fine gain
   * address 5 == dac channel 1 coarse gain
   * address 6 == dac channel 0 fine offset
   * address 7 == dac channel 1 fine offset
   */
  
  static int caldac_8800_write(struct comedi_device *dev, unsigned int address,
  			     u8 value)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	static const int num_caldac_channels = 8;
  	static const int bitstream_length = 11;
  	unsigned int bitstream = ((address & 0x7) << 8) | value;
  	unsigned int bit, register_bits;
  	static const int caldac_8800_udelay = 1;
  
  	if (address >= num_caldac_channels) {
  		dev_err(dev->class_dev, "illegal caldac channel\n");
  		return -1;
  	}
  	for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) {
  		register_bits = 0;
  		if (bitstream & bit)
  			register_bits |= SERIAL_DATA_IN_BIT;
  		udelay(caldac_8800_udelay);
  		writew(register_bits, devpriv->main_iobase + CALIBRATION_REG);
  		register_bits |= SERIAL_CLOCK_BIT;
  		udelay(caldac_8800_udelay);
  		writew(register_bits, devpriv->main_iobase + CALIBRATION_REG);
  	}
  	udelay(caldac_8800_udelay);
  	writew(SELECT_8800_BIT, devpriv->main_iobase + CALIBRATION_REG);
  	udelay(caldac_8800_udelay);
  	writew(0, devpriv->main_iobase + CALIBRATION_REG);
  	udelay(caldac_8800_udelay);
  	return 0;
  }
  
  /* 4020 caldacs */
  static int caldac_i2c_write(struct comedi_device *dev,
  			    unsigned int caldac_channel, unsigned int value)
  {
  	u8 serial_bytes[3];
  	u8 i2c_addr;
  	enum pointer_bits {
  		/* manual has gain and offset bits switched */
  		OFFSET_0_2 = 0x1,
  		GAIN_0_2 = 0x2,
  		OFFSET_1_3 = 0x4,
  		GAIN_1_3 = 0x8,
  	};
  	enum data_bits {
  		NOT_CLEAR_REGISTERS = 0x20,
  	};
  
  	switch (caldac_channel) {
  	case 0:					/* chan 0 offset */
  		i2c_addr = CALDAC0_I2C_ADDR;
  		serial_bytes[0] = OFFSET_0_2;
  		break;
  	case 1:					/* chan 1 offset */
  		i2c_addr = CALDAC0_I2C_ADDR;
  		serial_bytes[0] = OFFSET_1_3;
  		break;
  	case 2:					/* chan 2 offset */
  		i2c_addr = CALDAC1_I2C_ADDR;
  		serial_bytes[0] = OFFSET_0_2;
  		break;
  	case 3:					/* chan 3 offset */
  		i2c_addr = CALDAC1_I2C_ADDR;
  		serial_bytes[0] = OFFSET_1_3;
  		break;
  	case 4:					/* chan 0 gain */
  		i2c_addr = CALDAC0_I2C_ADDR;
  		serial_bytes[0] = GAIN_0_2;
  		break;
  	case 5:					/* chan 1 gain */
  		i2c_addr = CALDAC0_I2C_ADDR;
  		serial_bytes[0] = GAIN_1_3;
  		break;
  	case 6:					/* chan 2 gain */
  		i2c_addr = CALDAC1_I2C_ADDR;
  		serial_bytes[0] = GAIN_0_2;
  		break;
  	case 7:					/* chan 3 gain */
  		i2c_addr = CALDAC1_I2C_ADDR;
  		serial_bytes[0] = GAIN_1_3;
  		break;
  	default:
  		dev_err(dev->class_dev, "invalid caldac channel\n");
  		return -1;
  	}
  	serial_bytes[1] = NOT_CLEAR_REGISTERS | ((value >> 8) & 0xf);
  	serial_bytes[2] = value & 0xff;
  	i2c_write(dev, i2c_addr, serial_bytes, 3);
  	return 0;
  }
  
  static void caldac_write(struct comedi_device *dev, unsigned int channel,
  			 unsigned int value)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  
  	switch (board->layout) {
  	case LAYOUT_60XX:
  	case LAYOUT_64XX:
  		caldac_8800_write(dev, channel, value);
  		break;
  	case LAYOUT_4020:
  		caldac_i2c_write(dev, channel, value);
  		break;
  	default:
  		break;
  	}
  }
  
  static int cb_pcidas64_calib_insn_write(struct comedi_device *dev,
  					struct comedi_subdevice *s,
  					struct comedi_insn *insn,
  					unsigned int *data)
  {
  	unsigned int chan = CR_CHAN(insn->chanspec);
  
  	/*
  	 * Programming the calib device is slow. Only write the
  	 * last data value if the value has changed.
  	 */
  	if (insn->n) {
  		unsigned int val = data[insn->n - 1];
  
  		if (s->readback[chan] != val) {
  			caldac_write(dev, chan, val);
  			s->readback[chan] = val;
  		}
  	}
  
  	return insn->n;
  }
  
  static void ad8402_write(struct comedi_device *dev, unsigned int channel,
  			 unsigned int value)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	static const int bitstream_length = 10;
  	unsigned int bit, register_bits;
  	unsigned int bitstream = ((channel & 0x3) << 8) | (value & 0xff);
  	static const int ad8402_udelay = 1;
  
  	register_bits = SELECT_8402_64XX_BIT;
  	udelay(ad8402_udelay);
  	writew(register_bits, devpriv->main_iobase + CALIBRATION_REG);
  
  	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;
  		udelay(ad8402_udelay);
  		writew(register_bits, devpriv->main_iobase + CALIBRATION_REG);
  		udelay(ad8402_udelay);
  		writew(register_bits | SERIAL_CLOCK_BIT,
  		       devpriv->main_iobase + CALIBRATION_REG);
  	}
  
  	udelay(ad8402_udelay);
  	writew(0, devpriv->main_iobase + CALIBRATION_REG);
  }
  
  /* for pci-das6402/16, channel 0 is analog input gain and channel 1 is offset */
  static int cb_pcidas64_ad8402_insn_write(struct comedi_device *dev,
  					 struct comedi_subdevice *s,
  					 struct comedi_insn *insn,
  					 unsigned int *data)
  {
  	unsigned int chan = CR_CHAN(insn->chanspec);
  
  	/*
  	 * Programming the calib device is slow. Only write the
  	 * last data value if the value has changed.
  	 */
  	if (insn->n) {
  		unsigned int val = data[insn->n - 1];
  
  		if (s->readback[chan] != val) {
  			ad8402_write(dev, chan, val);
  			s->readback[chan] = val;
  		}
  	}
  
  	return insn->n;
  }
  
  static u16 read_eeprom(struct comedi_device *dev, u8 address)
  {
  	struct pcidas64_private *devpriv = dev->private;
  	static const int bitstream_length = 11;
  	static const int read_command = 0x6;
  	unsigned int bitstream = (read_command << 8) | address;
  	unsigned int bit;
  	void __iomem * const plx_control_addr =
  		devpriv->plx9080_iobase + PLX_REG_CNTRL;
  	u16 value;
  	static const int value_length = 16;
  	static const int eeprom_udelay = 1;
  
  	udelay(eeprom_udelay);
  	devpriv->plx_control_bits &= ~PLX_CNTRL_EESK & ~PLX_CNTRL_EECS;
  	/* make sure we don't send anything to the i2c bus on 4020 */
  	devpriv->plx_control_bits |= PLX_CNTRL_USERO;
  	writel(devpriv->plx_control_bits, plx_control_addr);
  	/* activate serial eeprom */
  	udelay(eeprom_udelay);
  	devpriv->plx_control_bits |= PLX_CNTRL_EECS;
  	writel(devpriv->plx_control_bits, plx_control_addr);
  
  	/* write read command and desired memory address */
  	for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) {
  		/* set bit to be written */
  		udelay(eeprom_udelay);
  		if (bitstream & bit)
  			devpriv->plx_control_bits |= PLX_CNTRL_EEWB;
  		else
  			devpriv->plx_control_bits &= ~PLX_CNTRL_EEWB;
  		writel(devpriv->plx_control_bits, plx_control_addr);
  		/* clock in bit */
  		udelay(eeprom_udelay);
  		devpriv->plx_control_bits |= PLX_CNTRL_EESK;
  		writel(devpriv->plx_control_bits, plx_control_addr);
  		udelay(eeprom_udelay);
  		devpriv->plx_control_bits &= ~PLX_CNTRL_EESK;
  		writel(devpriv->plx_control_bits, plx_control_addr);
  	}
  	/* read back value from eeprom memory location */
  	value = 0;
  	for (bit = 1 << (value_length - 1); bit; bit >>= 1) {
  		/* clock out bit */
  		udelay(eeprom_udelay);
  		devpriv->plx_control_bits |= PLX_CNTRL_EESK;
  		writel(devpriv->plx_control_bits, plx_control_addr);
  		udelay(eeprom_udelay);
  		devpriv->plx_control_bits &= ~PLX_CNTRL_EESK;
  		writel(devpriv->plx_control_bits, plx_control_addr);
  		udelay(eeprom_udelay);
  		if (readl(plx_control_addr) & PLX_CNTRL_EERB)
  			value |= bit;
  	}
  
  	/* deactivate eeprom serial input */
  	udelay(eeprom_udelay);
  	devpriv->plx_control_bits &= ~PLX_CNTRL_EECS;
  	writel(devpriv->plx_control_bits, plx_control_addr);
  
  	return value;
  }
  
  static int eeprom_read_insn(struct comedi_device *dev,
  			    struct comedi_subdevice *s,
  			    struct comedi_insn *insn, unsigned int *data)
  {
  	data[0] = read_eeprom(dev, CR_CHAN(insn->chanspec));
  
  	return 1;
  }
  
  /* Allocate and initialize the subdevice structures. */
  static int setup_subdevices(struct comedi_device *dev)
  {
  	const struct pcidas64_board *board = dev->board_ptr;
  	struct pcidas64_private *devpriv = dev->private;
  	struct comedi_subdevice *s;
  	int i;
  	int ret;
  
  	ret = comedi_alloc_subdevices(dev, 10);
  	if (ret)
  		return ret;
  
  	s = &dev->subdevices[0];
  	/* analog input subdevice */
  	dev->read_subdev = s;
  	s->type = COMEDI_SUBD_AI;
  	s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DITHER | SDF_CMD_READ;
  	if (board->layout == LAYOUT_60XX)
  		s->subdev_flags |= SDF_COMMON | SDF_DIFF;
  	else if (board->layout == LAYOUT_64XX)
  		s->subdev_flags |= SDF_DIFF;
  	/* XXX Number of inputs in differential mode is ignored */
  	s->n_chan = board->ai_se_chans;
  	s->len_chanlist = 0x2000;
  	s->maxdata = (1 << board->ai_bits) - 1;
  	s->range_table = board->ai_range_table;
  	s->insn_read = ai_rinsn;
  	s->insn_config = ai_config_insn;
  	s->do_cmd = ai_cmd;
  	s->do_cmdtest = ai_cmdtest;
  	s->cancel = ai_cancel;
  	if (board->layout == LAYOUT_4020) {
  		u8 data;
  		/*
  		 * set adc to read from inputs
  		 * (not internal calibration sources)
  		 */
  		devpriv->i2c_cal_range_bits = adc_src_4020_bits(4);
  		/* set channels to +-5 volt input ranges */
  		for (i = 0; i < s->n_chan; i++)
  			devpriv->i2c_cal_range_bits |= attenuate_bit(i);
  		data = devpriv->i2c_cal_range_bits;
  		i2c_write(dev, RANGE_CAL_I2C_ADDR, &data, sizeof(data));
  	}
  
  	/* analog output subdevice */
  	s = &dev->subdevices[1];
  	if (board->ao_nchan) {
  		s->type = COMEDI_SUBD_AO;
  		s->subdev_flags = SDF_READABLE | SDF_WRITABLE |
  				  SDF_GROUND | SDF_CMD_WRITE;
  		s->n_chan = board->ao_nchan;
  		s->maxdata = (1 << board->ao_bits) - 1;
  		s->range_table = board->ao_range_table;
  		s->insn_write = ao_winsn;
  
  		ret = comedi_alloc_subdev_readback(s);
  		if (ret)
  			return ret;
  
  		if (ao_cmd_is_supported(board)) {
  			dev->write_subdev = s;
  			s->do_cmdtest = ao_cmdtest;
  			s->do_cmd = ao_cmd;
  			s->len_chanlist = board->ao_nchan;
  			s->cancel = ao_cancel;
  		}
  	} else {
  		s->type = COMEDI_SUBD_UNUSED;
  	}
  
  	/* digital input */
  	s = &dev->subdevices[2];
  	if (board->layout == LAYOUT_64XX) {
  		s->type = COMEDI_SUBD_DI;
  		s->subdev_flags = SDF_READABLE;
  		s->n_chan = 4;
  		s->maxdata = 1;
  		s->range_table = &range_digital;
  		s->insn_bits = di_rbits;
  	} else {
  		s->type = COMEDI_SUBD_UNUSED;
  	}
  
  	/* digital output */
  	if (board->layout == LAYOUT_64XX) {
  		s = &dev->subdevices[3];
  		s->type = COMEDI_SUBD_DO;
  		s->subdev_flags = SDF_WRITABLE;
  		s->n_chan = 4;
  		s->maxdata = 1;
  		s->range_table = &range_digital;
  		s->insn_bits = do_wbits;
  	} else {
  		s->type = COMEDI_SUBD_UNUSED;
  	}
  
  	/* 8255 */
  	s = &dev->subdevices[4];
  	if (board->has_8255) {
  		if (board->layout == LAYOUT_4020) {
  			ret = subdev_8255_init(dev, s, dio_callback_4020,
  					       I8255_4020_REG);
  		} else {
  			ret = subdev_8255_mm_init(dev, s, NULL,
  						  DIO_8255_OFFSET);
  		}
  		if (ret)
  			return ret;
  	} else {
  		s->type = COMEDI_SUBD_UNUSED;
  	}
  
  	/* 8 channel dio for 60xx */
  	s = &dev->subdevices[5];
  	if (board->layout == LAYOUT_60XX) {
  		s->type = COMEDI_SUBD_DIO;
  		s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
  		s->n_chan = 8;
  		s->maxdata = 1;
  		s->range_table = &range_digital;
  		s->insn_config = dio_60xx_config_insn;
  		s->insn_bits = dio_60xx_wbits;
  	} else {
  		s->type = COMEDI_SUBD_UNUSED;
  	}
  
  	/* caldac */
  	s = &dev->subdevices[6];
  	s->type = COMEDI_SUBD_CALIB;
  	s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
  	s->n_chan = 8;
  	if (board->layout == LAYOUT_4020)
  		s->maxdata = 0xfff;
  	else
  		s->maxdata = 0xff;
  	s->insn_write = cb_pcidas64_calib_insn_write;
  
  	ret = comedi_alloc_subdev_readback(s);
  	if (ret)
  		return ret;
  
  	for (i = 0; i < s->n_chan; i++) {
  		caldac_write(dev, i, s->maxdata / 2);
  		s->readback[i] = s->maxdata / 2;
  	}
  
  	/* 2 channel ad8402 potentiometer */
  	s = &dev->subdevices[7];
  	if (board->layout == LAYOUT_64XX) {
  		s->type = COMEDI_SUBD_CALIB;
  		s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
  		s->n_chan = 2;
  		s->maxdata = 0xff;
  		s->insn_write = cb_pcidas64_ad8402_insn_write;
  
  		ret = comedi_alloc_subdev_readback(s);
  		if (ret)
  			return ret;
  
  		for (i = 0; i < s->n_chan; i++) {
  			ad8402_write(dev, i, s->maxdata / 2);
  			s->readback[i] = s->maxdata / 2;
  		}
  	} else {
  		s->type = COMEDI_SUBD_UNUSED;
  	}
  
  	/* serial EEPROM, if present */
  	s = &dev->subdevices[8];
  	if (readl(devpriv->plx9080_iobase + PLX_REG_CNTRL) &
  	    PLX_CNTRL_EEPRESENT) {
  		s->type = COMEDI_SUBD_MEMORY;
  		s->subdev_flags = SDF_READABLE | SDF_INTERNAL;
  		s->n_chan = 128;
  		s->maxdata = 0xffff;
  		s->insn_read = eeprom_read_insn;
  	} else {
  		s->type = COMEDI_SUBD_UNUSED;
  	}
  
  	/* user counter subd XXX */
  	s = &dev->subdevices[9];
  	s->type = COMEDI_SUBD_UNUSED;
  
  	return 0;
  }
  
  static int auto_attach(struct comedi_device *dev,
  		       unsigned long context)
  {
  	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
  	const struct pcidas64_board *board = NULL;
  	struct pcidas64_private *devpriv;
  	u32 local_range, local_decode;
  	int retval;
  
  	if (context < ARRAY_SIZE(pcidas64_boards))
  		board = &pcidas64_boards[context];
  	if (!board)
  		return -ENODEV;
  	dev->board_ptr = board;
  
  	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
  	if (!devpriv)
  		return -ENOMEM;
  
  	retval = comedi_pci_enable(dev);
  	if (retval)
  		return retval;
  	pci_set_master(pcidev);
  
  	/* Initialize dev->board_name */
  	dev->board_name = board->name;
  
  	devpriv->main_phys_iobase = pci_resource_start(pcidev, 2);
  	devpriv->dio_counter_phys_iobase = pci_resource_start(pcidev, 3);
  
  	devpriv->plx9080_iobase = pci_ioremap_bar(pcidev, 0);
  	devpriv->main_iobase = pci_ioremap_bar(pcidev, 2);
  	dev->mmio = pci_ioremap_bar(pcidev, 3);
  
  	if (!devpriv->plx9080_iobase || !devpriv->main_iobase || !dev->mmio) {
  		dev_warn(dev->class_dev, "failed to remap io memory\n");
  		return -ENOMEM;
  	}
  
  	/* figure out what local addresses are */
  	local_range = readl(devpriv->plx9080_iobase + PLX_REG_LAS0RR) &
  		      PLX_LASRR_MEM_MASK;
  	local_decode = readl(devpriv->plx9080_iobase + PLX_REG_LAS0BA) &
  		       local_range & PLX_LASBA_MEM_MASK;
  	devpriv->local0_iobase = ((u32)devpriv->main_phys_iobase &
  				  ~local_range) | local_decode;
  	local_range = readl(devpriv->plx9080_iobase + PLX_REG_LAS1RR) &
  		      PLX_LASRR_MEM_MASK;
  	local_decode = readl(devpriv->plx9080_iobase + PLX_REG_LAS1BA) &
  		       local_range & PLX_LASBA_MEM_MASK;
  	devpriv->local1_iobase = ((u32)devpriv->dio_counter_phys_iobase &
  				  ~local_range) | local_decode;
  
  	retval = alloc_and_init_dma_members(dev);
  	if (retval < 0)
  		return retval;
  
  	devpriv->hw_revision =
  		hw_revision(dev, readw(devpriv->main_iobase + HW_STATUS_REG));
  	dev_dbg(dev->class_dev, "stc hardware revision %i\n",
  		devpriv->hw_revision);
  	init_plx9080(dev);
  	init_stc_registers(dev);
  
  	retval = request_irq(pcidev->irq, handle_interrupt, IRQF_SHARED,
  			     dev->board_name, dev);
  	if (retval) {
  		dev_dbg(dev->class_dev, "unable to allocate irq %u\n",
  			pcidev->irq);
  		return retval;
  	}
  	dev->irq = pcidev->irq;
  	dev_dbg(dev->class_dev, "irq %u\n", dev->irq);
  
  	retval = setup_subdevices(dev);
  	if (retval < 0)
  		return retval;
  
  	return 0;
  }
  
  static void detach(struct comedi_device *dev)
  {
  	struct pcidas64_private *devpriv = dev->private;
  
  	if (dev->irq)
  		free_irq(dev->irq, dev);
  	if (devpriv) {
  		if (devpriv->plx9080_iobase) {
  			disable_plx_interrupts(dev);
  			iounmap(devpriv->plx9080_iobase);
  		}
  		if (devpriv->main_iobase)
  			iounmap(devpriv->main_iobase);
  		if (dev->mmio)
  			iounmap(dev->mmio);
  	}
  	comedi_pci_disable(dev);
  	cb_pcidas64_free_dma(dev);
  }
  
  static struct comedi_driver cb_pcidas64_driver = {
  	.driver_name	= "cb_pcidas64",
  	.module		= THIS_MODULE,
  	.auto_attach	= auto_attach,
  	.detach		= detach,
  };
  
  static int cb_pcidas64_pci_probe(struct pci_dev *dev,
  				 const struct pci_device_id *id)
  {
  	return comedi_pci_auto_config(dev, &cb_pcidas64_driver,
  				      id->driver_data);
  }
  
  static const struct pci_device_id cb_pcidas64_pci_table[] = {
  	{ PCI_VDEVICE(CB, 0x001d), BOARD_PCIDAS6402_16 },
  	{ PCI_VDEVICE(CB, 0x001e), BOARD_PCIDAS6402_12 },
  	{ PCI_VDEVICE(CB, 0x0035), BOARD_PCIDAS64_M1_16 },
  	{ PCI_VDEVICE(CB, 0x0036), BOARD_PCIDAS64_M2_16 },
  	{ PCI_VDEVICE(CB, 0x0037), BOARD_PCIDAS64_M3_16 },
  	{ PCI_VDEVICE(CB, 0x0052), BOARD_PCIDAS4020_12 },
  	{ PCI_VDEVICE(CB, 0x005d), BOARD_PCIDAS6023 },
  	{ PCI_VDEVICE(CB, 0x005e), BOARD_PCIDAS6025 },
  	{ PCI_VDEVICE(CB, 0x005f), BOARD_PCIDAS6030 },
  	{ PCI_VDEVICE(CB, 0x0060), BOARD_PCIDAS6031 },
  	{ PCI_VDEVICE(CB, 0x0061), BOARD_PCIDAS6032 },
  	{ PCI_VDEVICE(CB, 0x0062), BOARD_PCIDAS6033 },
  	{ PCI_VDEVICE(CB, 0x0063), BOARD_PCIDAS6034 },
  	{ PCI_VDEVICE(CB, 0x0064), BOARD_PCIDAS6035 },
  	{ PCI_VDEVICE(CB, 0x0065), BOARD_PCIDAS6040 },
  	{ PCI_VDEVICE(CB, 0x0066), BOARD_PCIDAS6052 },
  	{ PCI_VDEVICE(CB, 0x0067), BOARD_PCIDAS6070 },
  	{ PCI_VDEVICE(CB, 0x0068), BOARD_PCIDAS6071 },
  	{ PCI_VDEVICE(CB, 0x006f), BOARD_PCIDAS6036 },
  	{ PCI_VDEVICE(CB, 0x0078), BOARD_PCIDAS6013 },
  	{ PCI_VDEVICE(CB, 0x0079), BOARD_PCIDAS6014 },
  	{ 0 }
  };
  MODULE_DEVICE_TABLE(pci, cb_pcidas64_pci_table);
  
  static struct pci_driver cb_pcidas64_pci_driver = {
  	.name		= "cb_pcidas64",
  	.id_table	= cb_pcidas64_pci_table,
  	.probe		= cb_pcidas64_pci_probe,
  	.remove		= comedi_pci_auto_unconfig,
  };
  module_comedi_pci_driver(cb_pcidas64_driver, cb_pcidas64_pci_driver);
  
  MODULE_AUTHOR("Comedi http://www.comedi.org");
  MODULE_DESCRIPTION("Comedi low-level driver");
  MODULE_LICENSE("GPL");
  