root/drivers/comedi/drivers/cb_pcidas64.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * 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>
 */

/*
 * 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 <linux/comedi/comedi_pci.h>
#include <linux/comedi/comedi_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,
};

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 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 don't 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++) {
                                unsigned int chanspec = cmd->chanlist[i];
                                int use_differential;

                                bits = 0;
                                /* set channel */
                                bits |= adc_chan_bits(CR_CHAN(chanspec));
                                /* set gain */
                                bits |= ai_range_bits_6xxx(dev,
                                                           CR_RANGE(chanspec));
                                /* set single-ended / differential */
                                use_differential = 0;
                                if (CR_AREF(chanspec) == AREF_DIFF)
                                        use_differential = 1;
                                bits |= se_diff_bit_6xxx(dev, use_differential);

                                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;
        unsigned int chan = CR_CHAN(insn->chanspec);
        unsigned int range = CR_RANGE(insn->chanspec);
        unsigned int val = s->readback[chan];
        unsigned int i;

        /* 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);

        for (i = 0; i < insn->n; i++) {
                /* write to channel */
                val = data[i];
                if (board->layout == LAYOUT_4020) {
                        writew(val & 0xff,
                               devpriv->main_iobase + dac_lsb_4020_reg(chan));
                        writew((val >> 8) & 0xf,
                               devpriv->main_iobase + dac_msb_4020_reg(chan));
                } else {
                        writew(val,
                               devpriv->main_iobase + dac_convert_reg(chan));
                }
        }

        /* remember last output value */
        s->readback[chan] = val;

        return insn->n;
}

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)
{
        const struct pcidas64_board *board = dev->board_ptr;

        if (!dev->read_subdev->busy)
                return 0;
        if (board->layout == LAYOUT_4020)
                return 0;
        else if (use_internal_queue_6xxx(&dev->read_subdev->async->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)) {
                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)
{
        unsigned int val;
        unsigned int i;

        if (insn->n) {
                /* No point reading the same EEPROM location more than once. */
                val = read_eeprom(dev, CR_CHAN(insn->chanspec));
                for (i = 0; i < insn->n; i++)
                        data[i] = val;
        }

        return insn->n;
}

/* 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_cb_init(dev, s, dio_callback_4020,
                                                  I8255_4020_REG);
                } else {
                        ret = subdev_8255_mm_init(dev, s, 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,
                             "cb_pcidas64", 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 https://www.comedi.org");
MODULE_DESCRIPTION("Comedi low-level driver");
MODULE_LICENSE("GPL");