linux/drivers/comedi/drivers/me4000.c

// SPDX-License-Identifier: GPL-2.0+
/*
 * me4000.c
 * Source code for the Meilhaus ME-4000 board family.
 *
 * COMEDI - Linux Control and Measurement Device Interface
 * Copyright (C) 2000 David A. Schleef <[email protected]>
 */

/*
 * Driver: me4000
 * Description: Meilhaus ME-4000 series boards
 * Devices: [Meilhaus] ME-4650 (me4000), ME-4670i, ME-4680, ME-4680i,
 *	    ME-4680is
 * Author: gg (Guenter Gebhardt <[email protected]>)
 * Updated: Mon, 18 Mar 2002 15:34:01 -0800
 * Status: untested
 *
 * Supports:
 *	- Analog Input
 *	- Analog Output
 *	- Digital I/O
 *	- Counter
 *
 * Configuration Options: not applicable, uses PCI auto config
 *
 * The firmware required by these boards is available in the
 * comedi_nonfree_firmware tarball available from
 * https://www.comedi.org.
 */

#include <linux/module.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/comedi/comedi_pci.h>
#include <linux/comedi/comedi_8254.h>

#include "plx9052.h"

#define ME4000_FIRMWARE		"me4000_firmware.bin"

/*
 * ME4000 Register map and bit defines
 */
#define ME4000_AO_CHAN(x)			((x) * 0x18)

#define ME4000_AO_CTRL_REG(x)			(0x00 + ME4000_AO_CHAN(x))
#define ME4000_AO_CTRL_MODE_0			BIT(0)
#define ME4000_AO_CTRL_MODE_1			BIT(1)
#define ME4000_AO_CTRL_STOP			BIT(2)
#define ME4000_AO_CTRL_ENABLE_FIFO		BIT(3)
#define ME4000_AO_CTRL_ENABLE_EX_TRIG		BIT(4)
#define ME4000_AO_CTRL_EX_TRIG_EDGE		BIT(5)
#define ME4000_AO_CTRL_IMMEDIATE_STOP		BIT(7)
#define ME4000_AO_CTRL_ENABLE_DO		BIT(8)
#define ME4000_AO_CTRL_ENABLE_IRQ		BIT(9)
#define ME4000_AO_CTRL_RESET_IRQ		BIT(10)
#define ME4000_AO_STATUS_REG(x)			(0x04 + ME4000_AO_CHAN(x))
#define ME4000_AO_STATUS_FSM			BIT(0)
#define ME4000_AO_STATUS_FF			BIT(1)
#define ME4000_AO_STATUS_HF			BIT(2)
#define ME4000_AO_STATUS_EF			BIT(3)
#define ME4000_AO_FIFO_REG(x)			(0x08 + ME4000_AO_CHAN(x))
#define ME4000_AO_SINGLE_REG(x)			(0x0c + ME4000_AO_CHAN(x))
#define ME4000_AO_TIMER_REG(x)			(0x10 + ME4000_AO_CHAN(x))
#define ME4000_AI_CTRL_REG			0x74
#define ME4000_AI_STATUS_REG			0x74
#define ME4000_AI_CTRL_MODE_0			BIT(0)
#define ME4000_AI_CTRL_MODE_1			BIT(1)
#define ME4000_AI_CTRL_MODE_2			BIT(2)
#define ME4000_AI_CTRL_SAMPLE_HOLD		BIT(3)
#define ME4000_AI_CTRL_IMMEDIATE_STOP		BIT(4)
#define ME4000_AI_CTRL_STOP			BIT(5)
#define ME4000_AI_CTRL_CHANNEL_FIFO		BIT(6)
#define ME4000_AI_CTRL_DATA_FIFO		BIT(7)
#define ME4000_AI_CTRL_FULLSCALE		BIT(8)
#define ME4000_AI_CTRL_OFFSET			BIT(9)
#define ME4000_AI_CTRL_EX_TRIG_ANALOG		BIT(10)
#define ME4000_AI_CTRL_EX_TRIG			BIT(11)
#define ME4000_AI_CTRL_EX_TRIG_FALLING		BIT(12)
#define ME4000_AI_CTRL_EX_IRQ			BIT(13)
#define ME4000_AI_CTRL_EX_IRQ_RESET		BIT(14)
#define ME4000_AI_CTRL_LE_IRQ			BIT(15)
#define ME4000_AI_CTRL_LE_IRQ_RESET		BIT(16)
#define ME4000_AI_CTRL_HF_IRQ			BIT(17)
#define ME4000_AI_CTRL_HF_IRQ_RESET		BIT(18)
#define ME4000_AI_CTRL_SC_IRQ			BIT(19)
#define ME4000_AI_CTRL_SC_IRQ_RESET		BIT(20)
#define ME4000_AI_CTRL_SC_RELOAD		BIT(21)
#define ME4000_AI_STATUS_EF_CHANNEL		BIT(22)
#define ME4000_AI_STATUS_HF_CHANNEL		BIT(23)
#define ME4000_AI_STATUS_FF_CHANNEL		BIT(24)
#define ME4000_AI_STATUS_EF_DATA		BIT(25)
#define ME4000_AI_STATUS_HF_DATA		BIT(26)
#define ME4000_AI_STATUS_FF_DATA		BIT(27)
#define ME4000_AI_STATUS_LE			BIT(28)
#define ME4000_AI_STATUS_FSM			BIT(29)
#define ME4000_AI_CTRL_EX_TRIG_BOTH		BIT(31)
#define ME4000_AI_CHANNEL_LIST_REG		0x78
#define ME4000_AI_LIST_INPUT_DIFFERENTIAL	BIT(5)
#define ME4000_AI_LIST_RANGE(x)			((3 - ((x) & 3)) << 6)
#define ME4000_AI_LIST_LAST_ENTRY		BIT(8)
#define ME4000_AI_DATA_REG			0x7c
#define ME4000_AI_CHAN_TIMER_REG		0x80
#define ME4000_AI_CHAN_PRE_TIMER_REG		0x84
#define ME4000_AI_SCAN_TIMER_LOW_REG		0x88
#define ME4000_AI_SCAN_TIMER_HIGH_REG		0x8c
#define ME4000_AI_SCAN_PRE_TIMER_LOW_REG	0x90
#define ME4000_AI_SCAN_PRE_TIMER_HIGH_REG	0x94
#define ME4000_AI_START_REG			0x98
#define ME4000_IRQ_STATUS_REG			0x9c
#define ME4000_IRQ_STATUS_EX			BIT(0)
#define ME4000_IRQ_STATUS_LE			BIT(1)
#define ME4000_IRQ_STATUS_AI_HF			BIT(2)
#define ME4000_IRQ_STATUS_AO_0_HF		BIT(3)
#define ME4000_IRQ_STATUS_AO_1_HF		BIT(4)
#define ME4000_IRQ_STATUS_AO_2_HF		BIT(5)
#define ME4000_IRQ_STATUS_AO_3_HF		BIT(6)
#define ME4000_IRQ_STATUS_SC			BIT(7)
#define ME4000_DIO_PORT_0_REG			0xa0
#define ME4000_DIO_PORT_1_REG			0xa4
#define ME4000_DIO_PORT_2_REG			0xa8
#define ME4000_DIO_PORT_3_REG			0xac
#define ME4000_DIO_DIR_REG			0xb0
#define ME4000_AO_LOADSETREG_XX			0xb4
#define ME4000_DIO_CTRL_REG			0xb8
#define ME4000_DIO_CTRL_MODE_0			BIT(0)
#define ME4000_DIO_CTRL_MODE_1			BIT(1)
#define ME4000_DIO_CTRL_MODE_2			BIT(2)
#define ME4000_DIO_CTRL_MODE_3			BIT(3)
#define ME4000_DIO_CTRL_MODE_4			BIT(4)
#define ME4000_DIO_CTRL_MODE_5			BIT(5)
#define ME4000_DIO_CTRL_MODE_6			BIT(6)
#define ME4000_DIO_CTRL_MODE_7			BIT(7)
#define ME4000_DIO_CTRL_FUNCTION_0		BIT(8)
#define ME4000_DIO_CTRL_FUNCTION_1		BIT(9)
#define ME4000_DIO_CTRL_FIFO_HIGH_0		BIT(10)
#define ME4000_DIO_CTRL_FIFO_HIGH_1		BIT(11)
#define ME4000_DIO_CTRL_FIFO_HIGH_2		BIT(12)
#define ME4000_DIO_CTRL_FIFO_HIGH_3		BIT(13)
#define ME4000_AO_DEMUX_ADJUST_REG		0xbc
#define ME4000_AO_DEMUX_ADJUST_VALUE		0x4c
#define ME4000_AI_SAMPLE_COUNTER_REG		0xc0

#define ME4000_AI_FIFO_COUNT			2048

#define ME4000_AI_MIN_TICKS			66
#define ME4000_AI_MIN_SAMPLE_TIME		2000

#define ME4000_AI_CHANNEL_LIST_COUNT		1024

struct me4000_private {
	unsigned long plx_regbase;
	unsigned int ai_ctrl_mode;
	unsigned int ai_init_ticks;
	unsigned int ai_scan_ticks;
	unsigned int ai_chan_ticks;
};

enum me4000_boardid {
	BOARD_ME4650,
	BOARD_ME4660,
	BOARD_ME4660I,
	BOARD_ME4660S,
	BOARD_ME4660IS,
	BOARD_ME4670,
	BOARD_ME4670I,
	BOARD_ME4670S,
	BOARD_ME4670IS,
	BOARD_ME4680,
	BOARD_ME4680I,
	BOARD_ME4680S,
	BOARD_ME4680IS,
};

struct me4000_board {
	const char *name;
	int ai_nchan;
	unsigned int can_do_diff_ai:1;
	unsigned int can_do_sh_ai:1;	/* sample & hold (8 channels) */
	unsigned int ex_trig_analog:1;
	unsigned int has_ao:1;
	unsigned int has_ao_fifo:1;
	unsigned int has_counter:1;
};

static const struct me4000_board me4000_boards[] = {
	[BOARD_ME4650] = {
		.name		= "ME-4650",
		.ai_nchan	= 16,
	},
	[BOARD_ME4660] = {
		.name		= "ME-4660",
		.ai_nchan	= 32,
		.can_do_diff_ai	= 1,
		.has_counter	= 1,
	},
	[BOARD_ME4660I] = {
		.name		= "ME-4660i",
		.ai_nchan	= 32,
		.can_do_diff_ai	= 1,
		.has_counter	= 1,
	},
	[BOARD_ME4660S] = {
		.name		= "ME-4660s",
		.ai_nchan	= 32,
		.can_do_diff_ai	= 1,
		.can_do_sh_ai	= 1,
		.has_counter	= 1,
	},
	[BOARD_ME4660IS] = {
		.name		= "ME-4660is",
		.ai_nchan	= 32,
		.can_do_diff_ai	= 1,
		.can_do_sh_ai	= 1,
		.has_counter	= 1,
	},
	[BOARD_ME4670] = {
		.name		= "ME-4670",
		.ai_nchan	= 32,
		.can_do_diff_ai	= 1,
		.ex_trig_analog	= 1,
		.has_ao		= 1,
		.has_counter	= 1,
	},
	[BOARD_ME4670I] = {
		.name		= "ME-4670i",
		.ai_nchan	= 32,
		.can_do_diff_ai	= 1,
		.ex_trig_analog	= 1,
		.has_ao		= 1,
		.has_counter	= 1,
	},
	[BOARD_ME4670S] = {
		.name		= "ME-4670s",
		.ai_nchan	= 32,
		.can_do_diff_ai	= 1,
		.can_do_sh_ai	= 1,
		.ex_trig_analog	= 1,
		.has_ao		= 1,
		.has_counter	= 1,
	},
	[BOARD_ME4670IS] = {
		.name		= "ME-4670is",
		.ai_nchan	= 32,
		.can_do_diff_ai	= 1,
		.can_do_sh_ai	= 1,
		.ex_trig_analog	= 1,
		.has_ao		= 1,
		.has_counter	= 1,
	},
	[BOARD_ME4680] = {
		.name		= "ME-4680",
		.ai_nchan	= 32,
		.can_do_diff_ai	= 1,
		.ex_trig_analog	= 1,
		.has_ao		= 1,
		.has_ao_fifo	= 1,
		.has_counter	= 1,
	},
	[BOARD_ME4680I] = {
		.name		= "ME-4680i",
		.ai_nchan	= 32,
		.can_do_diff_ai	= 1,
		.ex_trig_analog	= 1,
		.has_ao		= 1,
		.has_ao_fifo	= 1,
		.has_counter	= 1,
	},
	[BOARD_ME4680S] = {
		.name		= "ME-4680s",
		.ai_nchan	= 32,
		.can_do_diff_ai	= 1,
		.can_do_sh_ai	= 1,
		.ex_trig_analog	= 1,
		.has_ao		= 1,
		.has_ao_fifo	= 1,
		.has_counter	= 1,
	},
	[BOARD_ME4680IS] = {
		.name		= "ME-4680is",
		.ai_nchan	= 32,
		.can_do_diff_ai	= 1,
		.can_do_sh_ai	= 1,
		.ex_trig_analog	= 1,
		.has_ao		= 1,
		.has_ao_fifo	= 1,
		.has_counter	= 1,
	},
};

/*
 * NOTE: the ranges here are inverted compared to the values
 * written to the ME4000_AI_CHANNEL_LIST_REG,
 *
 * The ME4000_AI_LIST_RANGE() macro handles the inversion.
 */
static const struct comedi_lrange me4000_ai_range = {
	4, {
		UNI_RANGE(2.5),
		UNI_RANGE(10),
		BIP_RANGE(2.5),
		BIP_RANGE(10)
	}
};

static int me4000_xilinx_download(struct comedi_device *dev,
				  const u8 *data, size_t size,
				  unsigned long context)
{
	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
	struct me4000_private *devpriv = dev->private;
	unsigned long xilinx_iobase = pci_resource_start(pcidev, 5);
	unsigned int file_length;
	unsigned int val;
	unsigned int i;

	if (!xilinx_iobase)
		return -ENODEV;

	/*
	 * Set PLX local interrupt 2 polarity to high.
	 * Interrupt is thrown by init pin of xilinx.
	 */
	outl(PLX9052_INTCSR_LI2POL, devpriv->plx_regbase + PLX9052_INTCSR);

	/* Set /CS and /WRITE of the Xilinx */
	val = inl(devpriv->plx_regbase + PLX9052_CNTRL);
	val |= PLX9052_CNTRL_UIO2_DATA;
	outl(val, devpriv->plx_regbase + PLX9052_CNTRL);

	/* Init Xilinx with CS1 */
	inb(xilinx_iobase + 0xC8);

	/* Wait until /INIT pin is set */
	usleep_range(20, 1000);
	val = inl(devpriv->plx_regbase + PLX9052_INTCSR);
	if (!(val & PLX9052_INTCSR_LI2STAT)) {
		dev_err(dev->class_dev, "Can't init Xilinx\n");
		return -EIO;
	}

	/* Reset /CS and /WRITE of the Xilinx */
	val = inl(devpriv->plx_regbase + PLX9052_CNTRL);
	val &= ~PLX9052_CNTRL_UIO2_DATA;
	outl(val, devpriv->plx_regbase + PLX9052_CNTRL);

	/* Download Xilinx firmware */
	file_length = (((unsigned int)data[0] & 0xff) << 24) +
		      (((unsigned int)data[1] & 0xff) << 16) +
		      (((unsigned int)data[2] & 0xff) << 8) +
		      ((unsigned int)data[3] & 0xff);
	usleep_range(10, 1000);

	for (i = 0; i < file_length; i++) {
		outb(data[16 + i], xilinx_iobase);
		usleep_range(10, 1000);

		/* Check if BUSY flag is low */
		val = inl(devpriv->plx_regbase + PLX9052_CNTRL);
		if (val & PLX9052_CNTRL_UIO1_DATA) {
			dev_err(dev->class_dev,
				"Xilinx is still busy (i = %d)\n", i);
			return -EIO;
		}
	}

	/* If done flag is high download was successful */
	val = inl(devpriv->plx_regbase + PLX9052_CNTRL);
	if (!(val & PLX9052_CNTRL_UIO0_DATA)) {
		dev_err(dev->class_dev, "DONE flag is not set\n");
		dev_err(dev->class_dev, "Download not successful\n");
		return -EIO;
	}

	/* Set /CS and /WRITE */
	val = inl(devpriv->plx_regbase + PLX9052_CNTRL);
	val |= PLX9052_CNTRL_UIO2_DATA;
	outl(val, devpriv->plx_regbase + PLX9052_CNTRL);

	return 0;
}

static void me4000_ai_reset(struct comedi_device *dev)
{
	unsigned int ctrl;

	/* Stop any running conversion */
	ctrl = inl(dev->iobase + ME4000_AI_CTRL_REG);
	ctrl |= ME4000_AI_CTRL_STOP | ME4000_AI_CTRL_IMMEDIATE_STOP;
	outl(ctrl, dev->iobase + ME4000_AI_CTRL_REG);

	/* Clear the control register */
	outl(0x0, dev->iobase + ME4000_AI_CTRL_REG);
}

static void me4000_reset(struct comedi_device *dev)
{
	struct me4000_private *devpriv = dev->private;
	unsigned int val;
	int chan;

	/* Disable interrupts on the PLX */
	outl(0, devpriv->plx_regbase + PLX9052_INTCSR);

	/* Software reset the PLX */
	val = inl(devpriv->plx_regbase + PLX9052_CNTRL);
	val |= PLX9052_CNTRL_PCI_RESET;
	outl(val, devpriv->plx_regbase + PLX9052_CNTRL);
	val &= ~PLX9052_CNTRL_PCI_RESET;
	outl(val, devpriv->plx_regbase + PLX9052_CNTRL);

	/* 0x8000 to the DACs means an output voltage of 0V */
	for (chan = 0; chan < 4; chan++)
		outl(0x8000, dev->iobase + ME4000_AO_SINGLE_REG(chan));

	me4000_ai_reset(dev);

	/* Set both stop bits in the analog output control register */
	val = ME4000_AO_CTRL_IMMEDIATE_STOP | ME4000_AO_CTRL_STOP;
	for (chan = 0; chan < 4; chan++)
		outl(val, dev->iobase + ME4000_AO_CTRL_REG(chan));

	/* Set the adustment register for AO demux */
	outl(ME4000_AO_DEMUX_ADJUST_VALUE,
	     dev->iobase + ME4000_AO_DEMUX_ADJUST_REG);

	/*
	 * Set digital I/O direction for port 0
	 * to output on isolated versions
	 */
	if (!(inl(dev->iobase + ME4000_DIO_DIR_REG) & 0x1))
		outl(0x1, dev->iobase + ME4000_DIO_CTRL_REG);
}

static unsigned int me4000_ai_get_sample(struct comedi_device *dev,
					 struct comedi_subdevice *s)
{
	unsigned int val;

	/* read two's complement value and munge to offset binary */
	val = inl(dev->iobase + ME4000_AI_DATA_REG);
	return comedi_offset_munge(s, val);
}

static int me4000_ai_eoc(struct comedi_device *dev,
			 struct comedi_subdevice *s,
			 struct comedi_insn *insn,
			 unsigned long context)
{
	unsigned int status;

	status = inl(dev->iobase + ME4000_AI_STATUS_REG);
	if (status & ME4000_AI_STATUS_EF_DATA)
		return 0;
	return -EBUSY;
}

static int me4000_ai_insn_read(struct comedi_device *dev,
			       struct comedi_subdevice *s,
			       struct comedi_insn *insn,
			       unsigned int *data)
{
	unsigned int chan = CR_CHAN(insn->chanspec);
	unsigned int range = CR_RANGE(insn->chanspec);
	unsigned int aref = CR_AREF(insn->chanspec);
	unsigned int entry;
	int ret = 0;
	int i;

	entry = chan | ME4000_AI_LIST_RANGE(range);
	if (aref == AREF_DIFF) {
		if (!(s->subdev_flags & SDF_DIFF)) {
			dev_err(dev->class_dev,
				"Differential inputs are not available\n");
			return -EINVAL;
		}

		if (!comedi_range_is_bipolar(s, range)) {
			dev_err(dev->class_dev,
				"Range must be bipolar when aref = diff\n");
			return -EINVAL;
		}

		if (chan >= (s->n_chan / 2)) {
			dev_err(dev->class_dev,
				"Analog input is not available\n");
			return -EINVAL;
		}
		entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL;
	}

	entry |= ME4000_AI_LIST_LAST_ENTRY;

	/* Enable channel list and data fifo for single acquisition mode */
	outl(ME4000_AI_CTRL_CHANNEL_FIFO | ME4000_AI_CTRL_DATA_FIFO,
	     dev->iobase + ME4000_AI_CTRL_REG);

	/* Generate channel list entry */
	outl(entry, dev->iobase + ME4000_AI_CHANNEL_LIST_REG);

	/* Set the timer to maximum sample rate */
	outl(ME4000_AI_MIN_TICKS, dev->iobase + ME4000_AI_CHAN_TIMER_REG);
	outl(ME4000_AI_MIN_TICKS, dev->iobase + ME4000_AI_CHAN_PRE_TIMER_REG);

	for (i = 0; i < insn->n; i++) {
		unsigned int val;

		/* start conversion by dummy read */
		inl(dev->iobase + ME4000_AI_START_REG);

		ret = comedi_timeout(dev, s, insn, me4000_ai_eoc, 0);
		if (ret)
			break;

		val = me4000_ai_get_sample(dev, s);
		data[i] = comedi_offset_munge(s, val);
	}

	me4000_ai_reset(dev);

	return ret ? ret : insn->n;
}

static int me4000_ai_cancel(struct comedi_device *dev,
			    struct comedi_subdevice *s)
{
	me4000_ai_reset(dev);

	return 0;
}

static int me4000_ai_check_chanlist(struct comedi_device *dev,
				    struct comedi_subdevice *s,
				    struct comedi_cmd *cmd)
{
	unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
	int i;

	for (i = 0; i < cmd->chanlist_len; i++) {
		unsigned int chan = CR_CHAN(cmd->chanlist[i]);
		unsigned int range = CR_RANGE(cmd->chanlist[i]);
		unsigned int aref = CR_AREF(cmd->chanlist[i]);

		if (aref != aref0) {
			dev_dbg(dev->class_dev,
				"Mode is not equal for all entries\n");
			return -EINVAL;
		}

		if (aref == AREF_DIFF) {
			if (!(s->subdev_flags & SDF_DIFF)) {
				dev_err(dev->class_dev,
					"Differential inputs are not available\n");
				return -EINVAL;
			}

			if (chan >= (s->n_chan / 2)) {
				dev_dbg(dev->class_dev,
					"Channel number to high\n");
				return -EINVAL;
			}

			if (!comedi_range_is_bipolar(s, range)) {
				dev_dbg(dev->class_dev,
					"Bipolar is not selected in differential mode\n");
				return -EINVAL;
			}
		}
	}

	return 0;
}

static void me4000_ai_round_cmd_args(struct comedi_device *dev,
				     struct comedi_subdevice *s,
				     struct comedi_cmd *cmd)
{
	struct me4000_private *devpriv = dev->private;
	int rest;

	devpriv->ai_init_ticks = 0;
	devpriv->ai_scan_ticks = 0;
	devpriv->ai_chan_ticks = 0;

	if (cmd->start_arg) {
		devpriv->ai_init_ticks = (cmd->start_arg * 33) / 1000;
		rest = (cmd->start_arg * 33) % 1000;

		if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_NEAREST) {
			if (rest > 33)
				devpriv->ai_init_ticks++;
		} else if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_UP) {
			if (rest)
				devpriv->ai_init_ticks++;
		}
	}

	if (cmd->scan_begin_arg) {
		devpriv->ai_scan_ticks = (cmd->scan_begin_arg * 33) / 1000;
		rest = (cmd->scan_begin_arg * 33) % 1000;

		if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_NEAREST) {
			if (rest > 33)
				devpriv->ai_scan_ticks++;
		} else if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_UP) {
			if (rest)
				devpriv->ai_scan_ticks++;
		}
	}

	if (cmd->convert_arg) {
		devpriv->ai_chan_ticks = (cmd->convert_arg * 33) / 1000;
		rest = (cmd->convert_arg * 33) % 1000;

		if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_NEAREST) {
			if (rest > 33)
				devpriv->ai_chan_ticks++;
		} else if ((cmd->flags & CMDF_ROUND_MASK) == CMDF_ROUND_UP) {
			if (rest)
				devpriv->ai_chan_ticks++;
		}
	}
}

static void me4000_ai_write_chanlist(struct comedi_device *dev,
				     struct comedi_subdevice *s,
				     struct comedi_cmd *cmd)
{
	int i;

	for (i = 0; i < cmd->chanlist_len; i++) {
		unsigned int chan = CR_CHAN(cmd->chanlist[i]);
		unsigned int range = CR_RANGE(cmd->chanlist[i]);
		unsigned int aref = CR_AREF(cmd->chanlist[i]);
		unsigned int entry;

		entry = chan | ME4000_AI_LIST_RANGE(range);

		if (aref == AREF_DIFF)
			entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL;

		if (i == (cmd->chanlist_len - 1))
			entry |= ME4000_AI_LIST_LAST_ENTRY;

		outl(entry, dev->iobase + ME4000_AI_CHANNEL_LIST_REG);
	}
}

static int me4000_ai_do_cmd(struct comedi_device *dev,
			    struct comedi_subdevice *s)
{
	struct me4000_private *devpriv = dev->private;
	struct comedi_cmd *cmd = &s->async->cmd;
	unsigned int ctrl;

	/* Write timer arguments */
	outl(devpriv->ai_init_ticks - 1,
	     dev->iobase + ME4000_AI_SCAN_PRE_TIMER_LOW_REG);
	outl(0x0, dev->iobase + ME4000_AI_SCAN_PRE_TIMER_HIGH_REG);

	if (devpriv->ai_scan_ticks) {
		outl(devpriv->ai_scan_ticks - 1,
		     dev->iobase + ME4000_AI_SCAN_TIMER_LOW_REG);
		outl(0x0, dev->iobase + ME4000_AI_SCAN_TIMER_HIGH_REG);
	}

	outl(devpriv->ai_chan_ticks - 1,
	     dev->iobase + ME4000_AI_CHAN_PRE_TIMER_REG);
	outl(devpriv->ai_chan_ticks - 1,
	     dev->iobase + ME4000_AI_CHAN_TIMER_REG);

	/* Start sources */
	ctrl = devpriv->ai_ctrl_mode |
	       ME4000_AI_CTRL_CHANNEL_FIFO |
	       ME4000_AI_CTRL_DATA_FIFO;

	/* Stop triggers */
	if (cmd->stop_src == TRIG_COUNT) {
		outl(cmd->chanlist_len * cmd->stop_arg,
		     dev->iobase + ME4000_AI_SAMPLE_COUNTER_REG);
		ctrl |= ME4000_AI_CTRL_SC_IRQ;
	} else if (cmd->stop_src == TRIG_NONE &&
		   cmd->scan_end_src == TRIG_COUNT) {
		outl(cmd->scan_end_arg,
		     dev->iobase + ME4000_AI_SAMPLE_COUNTER_REG);
		ctrl |= ME4000_AI_CTRL_SC_IRQ;
	}
	ctrl |= ME4000_AI_CTRL_HF_IRQ;

	/* Write the setup to the control register */
	outl(ctrl, dev->iobase + ME4000_AI_CTRL_REG);

	/* Write the channel list */
	me4000_ai_write_chanlist(dev, s, cmd);

	/* Start acquistion by dummy read */
	inl(dev->iobase + ME4000_AI_START_REG);

	return 0;
}

static int me4000_ai_do_cmd_test(struct comedi_device *dev,
				 struct comedi_subdevice *s,
				 struct comedi_cmd *cmd)
{
	struct me4000_private *devpriv = dev->private;
	int err = 0;

	/* Step 1 : check if triggers are trivially valid */

	err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
	err |= comedi_check_trigger_src(&cmd->scan_begin_src,
					TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT);
	err |= comedi_check_trigger_src(&cmd->convert_src,
					TRIG_TIMER | TRIG_EXT);
	err |= comedi_check_trigger_src(&cmd->scan_end_src,
					TRIG_NONE | TRIG_COUNT);
	err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE | TRIG_COUNT);

	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->scan_end_src);
	err |= comedi_check_trigger_is_unique(cmd->stop_src);

	/* Step 2b : and mutually compatible */

	if (cmd->start_src == TRIG_NOW &&
	    cmd->scan_begin_src == TRIG_TIMER &&
	    cmd->convert_src == TRIG_TIMER) {
		devpriv->ai_ctrl_mode = ME4000_AI_CTRL_MODE_0;
	} else if (cmd->start_src == TRIG_NOW &&
		   cmd->scan_begin_src == TRIG_FOLLOW &&
		   cmd->convert_src == TRIG_TIMER) {
		devpriv->ai_ctrl_mode = ME4000_AI_CTRL_MODE_0;
	} else if (cmd->start_src == TRIG_EXT &&
		   cmd->scan_begin_src == TRIG_TIMER &&
		   cmd->convert_src == TRIG_TIMER) {
		devpriv->ai_ctrl_mode = ME4000_AI_CTRL_MODE_1;
	} else if (cmd->start_src == TRIG_EXT &&
		   cmd->scan_begin_src == TRIG_FOLLOW &&
		   cmd->convert_src == TRIG_TIMER) {
		devpriv->ai_ctrl_mode = ME4000_AI_CTRL_MODE_1;
	} else if (cmd->start_src == TRIG_EXT &&
		   cmd->scan_begin_src == TRIG_EXT &&
		   cmd->convert_src == TRIG_TIMER) {
		devpriv->ai_ctrl_mode = ME4000_AI_CTRL_MODE_2;
	} else if (cmd->start_src == TRIG_EXT &&
		   cmd->scan_begin_src == TRIG_EXT &&
		   cmd->convert_src == TRIG_EXT) {
		devpriv->ai_ctrl_mode = ME4000_AI_CTRL_MODE_0 |
					ME4000_AI_CTRL_MODE_1;
	} else {
		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->chanlist_len < 1) {
		cmd->chanlist_len = 1;
		err |= -EINVAL;
	}

	/* Round the timer arguments */
	me4000_ai_round_cmd_args(dev, s, cmd);

	if (devpriv->ai_init_ticks < 66) {
		cmd->start_arg = 2000;
		err |= -EINVAL;
	}
	if (devpriv->ai_scan_ticks && devpriv->ai_scan_ticks < 67) {
		cmd->scan_begin_arg = 2031;
		err |= -EINVAL;
	}
	if (devpriv->ai_chan_ticks < 66) {
		cmd->convert_arg = 2000;
		err |= -EINVAL;
	}

	if (cmd->stop_src == TRIG_COUNT)
		err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
	else	/* TRIG_NONE */
		err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);

	if (err)
		return 3;

	/*
	 * Stage 4. Check for argument conflicts.
	 */
	if (cmd->start_src == TRIG_NOW &&
	    cmd->scan_begin_src == TRIG_TIMER &&
	    cmd->convert_src == TRIG_TIMER) {
		/* Check timer arguments */
		if (devpriv->ai_init_ticks < ME4000_AI_MIN_TICKS) {
			dev_err(dev->class_dev, "Invalid start arg\n");
			cmd->start_arg = 2000;	/*  66 ticks at least */
			err++;
		}
		if (devpriv->ai_chan_ticks < ME4000_AI_MIN_TICKS) {
			dev_err(dev->class_dev, "Invalid convert arg\n");
			cmd->convert_arg = 2000;	/*  66 ticks at least */
			err++;
		}
		if (devpriv->ai_scan_ticks <=
		    cmd->chanlist_len * devpriv->ai_chan_ticks) {
			dev_err(dev->class_dev, "Invalid scan end arg\n");

			/*  At least one tick more */
			cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31;
			err++;
		}
	} else if (cmd->start_src == TRIG_NOW &&
		   cmd->scan_begin_src == TRIG_FOLLOW &&
		   cmd->convert_src == TRIG_TIMER) {
		/* Check timer arguments */
		if (devpriv->ai_init_ticks < ME4000_AI_MIN_TICKS) {
			dev_err(dev->class_dev, "Invalid start arg\n");
			cmd->start_arg = 2000;	/*  66 ticks at least */
			err++;
		}
		if (devpriv->ai_chan_ticks < ME4000_AI_MIN_TICKS) {
			dev_err(dev->class_dev, "Invalid convert arg\n");
			cmd->convert_arg = 2000;	/*  66 ticks at least */
			err++;
		}
	} else if (cmd->start_src == TRIG_EXT &&
		   cmd->scan_begin_src == TRIG_TIMER &&
		   cmd->convert_src == TRIG_TIMER) {
		/* Check timer arguments */
		if (devpriv->ai_init_ticks < ME4000_AI_MIN_TICKS) {
			dev_err(dev->class_dev, "Invalid start arg\n");
			cmd->start_arg = 2000;	/*  66 ticks at least */
			err++;
		}
		if (devpriv->ai_chan_ticks < ME4000_AI_MIN_TICKS) {
			dev_err(dev->class_dev, "Invalid convert arg\n");
			cmd->convert_arg = 2000;	/*  66 ticks at least */
			err++;
		}
		if (devpriv->ai_scan_ticks <=
		    cmd->chanlist_len * devpriv->ai_chan_ticks) {
			dev_err(dev->class_dev, "Invalid scan end arg\n");

			/*  At least one tick more */
			cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31;
			err++;
		}
	} else if (cmd->start_src == TRIG_EXT &&
		   cmd->scan_begin_src == TRIG_FOLLOW &&
		   cmd->convert_src == TRIG_TIMER) {
		/* Check timer arguments */
		if (devpriv->ai_init_ticks < ME4000_AI_MIN_TICKS) {
			dev_err(dev->class_dev, "Invalid start arg\n");
			cmd->start_arg = 2000;	/*  66 ticks at least */
			err++;
		}
		if (devpriv->ai_chan_ticks < ME4000_AI_MIN_TICKS) {
			dev_err(dev->class_dev, "Invalid convert arg\n");
			cmd->convert_arg = 2000;	/*  66 ticks at least */
			err++;
		}
	} else if (cmd->start_src == TRIG_EXT &&
		   cmd->scan_begin_src == TRIG_EXT &&
		   cmd->convert_src == TRIG_TIMER) {
		/* Check timer arguments */
		if (devpriv->ai_init_ticks < ME4000_AI_MIN_TICKS) {
			dev_err(dev->class_dev, "Invalid start arg\n");
			cmd->start_arg = 2000;	/*  66 ticks at least */
			err++;
		}
		if (devpriv->ai_chan_ticks < ME4000_AI_MIN_TICKS) {
			dev_err(dev->class_dev, "Invalid convert arg\n");
			cmd->convert_arg = 2000;	/*  66 ticks at least */
			err++;
		}
	} else if (cmd->start_src == TRIG_EXT &&
		   cmd->scan_begin_src == TRIG_EXT &&
		   cmd->convert_src == TRIG_EXT) {
		/* Check timer arguments */
		if (devpriv->ai_init_ticks < ME4000_AI_MIN_TICKS) {
			dev_err(dev->class_dev, "Invalid start arg\n");
			cmd->start_arg = 2000;	/*  66 ticks at least */
			err++;
		}
	}
	if (cmd->scan_end_src == TRIG_COUNT) {
		if (cmd->scan_end_arg == 0) {
			dev_err(dev->class_dev, "Invalid scan end arg\n");
			cmd->scan_end_arg = 1;
			err++;
		}
	}

	if (err)
		return 4;

	/* Step 5: check channel list if it exists */
	if (cmd->chanlist && cmd->chanlist_len > 0)
		err |= me4000_ai_check_chanlist(dev, s, cmd);

	if (err)
		return 5;

	return 0;
}

static irqreturn_t me4000_ai_isr(int irq, void *dev_id)
{
	unsigned int tmp;
	struct comedi_device *dev = dev_id;
	struct comedi_subdevice *s = dev->read_subdev;
	int i;
	int c = 0;
	unsigned short lval;

	if (!dev->attached)
		return IRQ_NONE;

	if (inl(dev->iobase + ME4000_IRQ_STATUS_REG) &
	    ME4000_IRQ_STATUS_AI_HF) {
		/* Read status register to find out what happened */
		tmp = inl(dev->iobase + ME4000_AI_STATUS_REG);

		if (!(tmp & ME4000_AI_STATUS_FF_DATA) &&
		    !(tmp & ME4000_AI_STATUS_HF_DATA) &&
		    (tmp & ME4000_AI_STATUS_EF_DATA)) {
			dev_err(dev->class_dev, "FIFO overflow\n");
			s->async->events |= COMEDI_CB_ERROR;
			c = ME4000_AI_FIFO_COUNT;
		} else if ((tmp & ME4000_AI_STATUS_FF_DATA) &&
			   !(tmp & ME4000_AI_STATUS_HF_DATA) &&
			   (tmp & ME4000_AI_STATUS_EF_DATA)) {
			c = ME4000_AI_FIFO_COUNT / 2;
		} else {
			dev_err(dev->class_dev, "Undefined FIFO state\n");
			s->async->events |= COMEDI_CB_ERROR;
			c = 0;
		}

		for (i = 0; i < c; i++) {
			lval = me4000_ai_get_sample(dev, s);
			if (!comedi_buf_write_samples(s, &lval, 1))
				break;
		}

		/* Work is done, so reset the interrupt */
		tmp |= ME4000_AI_CTRL_HF_IRQ_RESET;
		outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
		tmp &= ~ME4000_AI_CTRL_HF_IRQ_RESET;
		outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
	}

	if (inl(dev->iobase + ME4000_IRQ_STATUS_REG) &
	    ME4000_IRQ_STATUS_SC) {
		/* Acquisition is complete */
		s->async->events |= COMEDI_CB_EOA;

		/* Poll data until fifo empty */
		while (inl(dev->iobase + ME4000_AI_STATUS_REG) &
		       ME4000_AI_STATUS_EF_DATA) {
			lval = me4000_ai_get_sample(dev, s);
			if (!comedi_buf_write_samples(s, &lval, 1))
				break;
		}

		/* Work is done, so reset the interrupt */
		tmp = inl(dev->iobase + ME4000_AI_CTRL_REG);
		tmp |= ME4000_AI_CTRL_SC_IRQ_RESET;
		outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
		tmp &= ~ME4000_AI_CTRL_SC_IRQ_RESET;
		outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
	}

	comedi_handle_events(dev, s);

	return IRQ_HANDLED;
}

static int me4000_ao_insn_write(struct comedi_device *dev,
				struct comedi_subdevice *s,
				struct comedi_insn *insn,
				unsigned int *data)
{
	unsigned int chan = CR_CHAN(insn->chanspec);
	unsigned int tmp;

	/* Stop any running conversion */
	tmp = inl(dev->iobase + ME4000_AO_CTRL_REG(chan));
	tmp |= ME4000_AO_CTRL_IMMEDIATE_STOP;
	outl(tmp, dev->iobase + ME4000_AO_CTRL_REG(chan));

	/* Clear control register and set to single mode */
	outl(0x0, dev->iobase + ME4000_AO_CTRL_REG(chan));

	/* Write data value */
	outl(data[0], dev->iobase + ME4000_AO_SINGLE_REG(chan));

	/* Store in the mirror */
	s->readback[chan] = data[0];

	return 1;
}

static int me4000_dio_insn_bits(struct comedi_device *dev,
				struct comedi_subdevice *s,
				struct comedi_insn *insn,
				unsigned int *data)
{
	if (comedi_dio_update_state(s, data)) {
		outl((s->state >> 0) & 0xFF,
		     dev->iobase + ME4000_DIO_PORT_0_REG);
		outl((s->state >> 8) & 0xFF,
		     dev->iobase + ME4000_DIO_PORT_1_REG);
		outl((s->state >> 16) & 0xFF,
		     dev->iobase + ME4000_DIO_PORT_2_REG);
		outl((s->state >> 24) & 0xFF,
		     dev->iobase + ME4000_DIO_PORT_3_REG);
	}

	data[1] = ((inl(dev->iobase + ME4000_DIO_PORT_0_REG) & 0xFF) << 0) |
		  ((inl(dev->iobase + ME4000_DIO_PORT_1_REG) & 0xFF) << 8) |
		  ((inl(dev->iobase + ME4000_DIO_PORT_2_REG) & 0xFF) << 16) |
		  ((inl(dev->iobase + ME4000_DIO_PORT_3_REG) & 0xFF) << 24);

	return insn->n;
}

static int me4000_dio_insn_config(struct comedi_device *dev,
				  struct comedi_subdevice *s,
				  struct comedi_insn *insn,
				  unsigned int *data)
{
	unsigned int chan = CR_CHAN(insn->chanspec);
	unsigned int mask;
	unsigned int tmp;
	int ret;

	if (chan < 8)
		mask = 0x000000ff;
	else if (chan < 16)
		mask = 0x0000ff00;
	else if (chan < 24)
		mask = 0x00ff0000;
	else
		mask = 0xff000000;

	ret = comedi_dio_insn_config(dev, s, insn, data, mask);
	if (ret)
		return ret;

	tmp = inl(dev->iobase + ME4000_DIO_CTRL_REG);
	tmp &= ~(ME4000_DIO_CTRL_MODE_0 | ME4000_DIO_CTRL_MODE_1 |
		 ME4000_DIO_CTRL_MODE_2 | ME4000_DIO_CTRL_MODE_3 |
		 ME4000_DIO_CTRL_MODE_4 | ME4000_DIO_CTRL_MODE_5 |
		 ME4000_DIO_CTRL_MODE_6 | ME4000_DIO_CTRL_MODE_7);
	if (s->io_bits & 0x000000ff)
		tmp |= ME4000_DIO_CTRL_MODE_0;
	if (s->io_bits & 0x0000ff00)
		tmp |= ME4000_DIO_CTRL_MODE_2;
	if (s->io_bits & 0x00ff0000)
		tmp |= ME4000_DIO_CTRL_MODE_4;
	if (s->io_bits & 0xff000000)
		tmp |= ME4000_DIO_CTRL_MODE_6;

	/*
	 * Check for optoisolated ME-4000 version.
	 * If one the first port is a fixed output
	 * port and the second is a fixed input port.
	 */
	if (inl(dev->iobase + ME4000_DIO_DIR_REG)) {
		s->io_bits |= 0x000000ff;
		s->io_bits &= ~0x0000ff00;
		tmp |= ME4000_DIO_CTRL_MODE_0;
		tmp &= ~(ME4000_DIO_CTRL_MODE_2 | ME4000_DIO_CTRL_MODE_3);
	}

	outl(tmp, dev->iobase + ME4000_DIO_CTRL_REG);

	return insn->n;
}

static int me4000_auto_attach(struct comedi_device *dev,
			      unsigned long context)
{
	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
	const struct me4000_board *board = NULL;
	struct me4000_private *devpriv;
	struct comedi_subdevice *s;
	int result;

	if (context < ARRAY_SIZE(me4000_boards))
		board = &me4000_boards[context];
	if (!board)
		return -ENODEV;
	dev->board_ptr = board;
	dev->board_name = board->name;

	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
	if (!devpriv)
		return -ENOMEM;

	result = comedi_pci_enable(dev);
	if (result)
		return result;

	devpriv->plx_regbase = pci_resource_start(pcidev, 1);
	dev->iobase = pci_resource_start(pcidev, 2);
	if (!devpriv->plx_regbase || !dev->iobase)
		return -ENODEV;

	result = comedi_load_firmware(dev, &pcidev->dev, ME4000_FIRMWARE,
				      me4000_xilinx_download, 0);
	if (result < 0)
		return result;

	me4000_reset(dev);

	if (pcidev->irq > 0) {
		result = request_irq(pcidev->irq, me4000_ai_isr, IRQF_SHARED,
				     dev->board_name, dev);
		if (result == 0) {
			dev->irq = pcidev->irq;

			/* Enable interrupts on the PLX */
			outl(PLX9052_INTCSR_LI1ENAB | PLX9052_INTCSR_LI1POL |
			     PLX9052_INTCSR_PCIENAB,
			     devpriv->plx_regbase + PLX9052_INTCSR);
		}
	}

	result = comedi_alloc_subdevices(dev, 4);
	if (result)
		return result;

	/* Analog Input subdevice */
	s = &dev->subdevices[0];
	s->type		= COMEDI_SUBD_AI;
	s->subdev_flags	= SDF_READABLE | SDF_COMMON | SDF_GROUND;
	if (board->can_do_diff_ai)
		s->subdev_flags	|= SDF_DIFF;
	s->n_chan	= board->ai_nchan;
	s->maxdata	= 0xffff;
	s->len_chanlist	= ME4000_AI_CHANNEL_LIST_COUNT;
	s->range_table	= &me4000_ai_range;
	s->insn_read	= me4000_ai_insn_read;

	if (dev->irq) {
		dev->read_subdev = s;
		s->subdev_flags	|= SDF_CMD_READ;
		s->cancel	= me4000_ai_cancel;
		s->do_cmdtest	= me4000_ai_do_cmd_test;
		s->do_cmd	= me4000_ai_do_cmd;
	}

	/* Analog Output subdevice */
	s = &dev->subdevices[1];
	if (board->has_ao) {
		s->type		= COMEDI_SUBD_AO;
		s->subdev_flags	= SDF_WRITABLE | SDF_COMMON | SDF_GROUND;
		s->n_chan	= 4;
		s->maxdata	= 0xffff;
		s->range_table	= &range_bipolar10;
		s->insn_write	= me4000_ao_insn_write;

		result = comedi_alloc_subdev_readback(s);
		if (result)
			return result;
	} else {
		s->type		= COMEDI_SUBD_UNUSED;
	}

	/* Digital I/O subdevice */
	s = &dev->subdevices[2];
	s->type		= COMEDI_SUBD_DIO;
	s->subdev_flags	= SDF_READABLE | SDF_WRITABLE;
	s->n_chan	= 32;
	s->maxdata	= 1;
	s->range_table	= &range_digital;
	s->insn_bits	= me4000_dio_insn_bits;
	s->insn_config	= me4000_dio_insn_config;

	/*
	 * Check for optoisolated ME-4000 version. If one the first
	 * port is a fixed output port and the second is a fixed input port.
	 */
	if (!inl(dev->iobase + ME4000_DIO_DIR_REG)) {
		s->io_bits |= 0xFF;
		outl(ME4000_DIO_CTRL_MODE_0,
		     dev->iobase + ME4000_DIO_DIR_REG);
	}

	/* Counter subdevice (8254) */
	s = &dev->subdevices[3];
	if (board->has_counter) {
		unsigned long timer_base = pci_resource_start(pcidev, 3);

		if (!timer_base)
			return -ENODEV;

		dev->pacer = comedi_8254_io_alloc(timer_base, 0, I8254_IO8, 0);
		if (IS_ERR(dev->pacer))
			return PTR_ERR(dev->pacer);

		comedi_8254_subdevice_init(s, dev->pacer);
	} else {
		s->type = COMEDI_SUBD_UNUSED;
	}

	return 0;
}

static void me4000_detach(struct comedi_device *dev)
{
	if (dev->irq) {
		struct me4000_private *devpriv = dev->private;

		/* Disable interrupts on the PLX */
		outl(0, devpriv->plx_regbase + PLX9052_INTCSR);
	}
	comedi_pci_detach(dev);
}

static struct comedi_driver me4000_driver = {
	.driver_name	= "me4000",
	.module		= THIS_MODULE,
	.auto_attach	= me4000_auto_attach,
	.detach		= me4000_detach,
};

static int me4000_pci_probe(struct pci_dev *dev,
			    const struct pci_device_id *id)
{
	return comedi_pci_auto_config(dev, &me4000_driver, id->driver_data);
}

static const struct pci_device_id me4000_pci_table[] = {
	{ PCI_VDEVICE(MEILHAUS, 0x4650), BOARD_ME4650 },
	{ PCI_VDEVICE(MEILHAUS, 0x4660), BOARD_ME4660 },
	{ PCI_VDEVICE(MEILHAUS, 0x4661), BOARD_ME4660I },
	{ PCI_VDEVICE(MEILHAUS, 0x4662), BOARD_ME4660S },
	{ PCI_VDEVICE(MEILHAUS, 0x4663), BOARD_ME4660IS },
	{ PCI_VDEVICE(MEILHAUS, 0x4670), BOARD_ME4670 },
	{ PCI_VDEVICE(MEILHAUS, 0x4671), BOARD_ME4670I },
	{ PCI_VDEVICE(MEILHAUS, 0x4672), BOARD_ME4670S },
	{ PCI_VDEVICE(MEILHAUS, 0x4673), BOARD_ME4670IS },
	{ PCI_VDEVICE(MEILHAUS, 0x4680), BOARD_ME4680 },
	{ PCI_VDEVICE(MEILHAUS, 0x4681), BOARD_ME4680I },
	{ PCI_VDEVICE(MEILHAUS, 0x4682), BOARD_ME4680S },
	{ PCI_VDEVICE(MEILHAUS, 0x4683), BOARD_ME4680IS },
	{ 0 }
};
MODULE_DEVICE_TABLE(pci, me4000_pci_table);

static struct pci_driver me4000_pci_driver = {
	.name		= "me4000",
	.id_table	= me4000_pci_table,
	.probe		= me4000_pci_probe,
	.remove		= comedi_pci_auto_unconfig,
};
module_comedi_pci_driver(me4000_driver, me4000_pci_driver);

MODULE_AUTHOR("Comedi https://www.comedi.org");
MODULE_DESCRIPTION("Comedi driver for Meilhaus ME-4000 series boards");
MODULE_LICENSE("GPL");
MODULE_FIRMWARE(ME4000_FIRMWARE);