// SPDX-License-Identifier: GPL-2.0+
/*
 * Hardware driver for NI 660x devices
 */

/*
 * Driver: ni_660x
 * Description: National Instruments 660x counter/timer boards
 * Devices: [National Instruments] PCI-6601 (ni_660x), PCI-6602, PXI-6602,
 *   PCI-6608, PXI-6608, PCI-6624, PXI-6624
 * Author: J.P. Mellor <jpmellor@rose-hulman.edu>,
 *   Herman.Bruyninckx@mech.kuleuven.ac.be,
 *   Wim.Meeussen@mech.kuleuven.ac.be,
 *   Klaas.Gadeyne@mech.kuleuven.ac.be,
 *   Frank Mori Hess <fmhess@users.sourceforge.net>
 * Updated: Mon, 16 Jan 2017 14:00:43 +0000
 * Status: experimental
 *
 * Encoders work.  PulseGeneration (both single pulse and pulse train)
 * works.  Buffered commands work for input but not output.
 *
 * References:
 * DAQ 660x Register-Level Programmer Manual  (NI 370505A-01)
 * DAQ 6601/6602 User Manual (NI 322137B-01)
 */

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

#include "mite.h"
#include "ni_tio.h"
#include "ni_routes.h"

/* See Register-Level Programmer Manual page 3.1 */
enum ni_660x_register {
	/* see enum ni_gpct_register */
	NI660X_STC_DIO_PARALLEL_INPUT = NITIO_NUM_REGS,
	NI660X_STC_DIO_OUTPUT,
	NI660X_STC_DIO_CONTROL,
	NI660X_STC_DIO_SERIAL_INPUT,
	NI660X_DIO32_INPUT,
	NI660X_DIO32_OUTPUT,
	NI660X_CLK_CFG,
	NI660X_GLOBAL_INT_STATUS,
	NI660X_DMA_CFG,
	NI660X_GLOBAL_INT_CFG,
	NI660X_IO_CFG_0_1,
	NI660X_IO_CFG_2_3,
	NI660X_IO_CFG_4_5,
	NI660X_IO_CFG_6_7,
	NI660X_IO_CFG_8_9,
	NI660X_IO_CFG_10_11,
	NI660X_IO_CFG_12_13,
	NI660X_IO_CFG_14_15,
	NI660X_IO_CFG_16_17,
	NI660X_IO_CFG_18_19,
	NI660X_IO_CFG_20_21,
	NI660X_IO_CFG_22_23,
	NI660X_IO_CFG_24_25,
	NI660X_IO_CFG_26_27,
	NI660X_IO_CFG_28_29,
	NI660X_IO_CFG_30_31,
	NI660X_IO_CFG_32_33,
	NI660X_IO_CFG_34_35,
	NI660X_IO_CFG_36_37,
	NI660X_IO_CFG_38_39,
	NI660X_NUM_REGS,
};

#define NI660X_CLK_CFG_COUNTER_SWAP	BIT(21)

#define NI660X_GLOBAL_INT_COUNTER0	BIT(8)
#define NI660X_GLOBAL_INT_COUNTER1	BIT(9)
#define NI660X_GLOBAL_INT_COUNTER2	BIT(10)
#define NI660X_GLOBAL_INT_COUNTER3	BIT(11)
#define NI660X_GLOBAL_INT_CASCADE	BIT(29)
#define NI660X_GLOBAL_INT_GLOBAL_POL	BIT(30)
#define NI660X_GLOBAL_INT_GLOBAL	BIT(31)

#define NI660X_DMA_CFG_SEL(_c, _s)	(((_s) & 0x1f) << (8 * (_c)))
#define NI660X_DMA_CFG_SEL_MASK(_c)	NI660X_DMA_CFG_SEL((_c), 0x1f)
#define NI660X_DMA_CFG_SEL_NONE(_c)	NI660X_DMA_CFG_SEL((_c), 0x1f)
#define NI660X_DMA_CFG_RESET(_c)	NI660X_DMA_CFG_SEL((_c), 0x80)

#define NI660X_IO_CFG(x)		(NI660X_IO_CFG_0_1 + ((x) / 2))
#define NI660X_IO_CFG_OUT_SEL(_c, _s)	(((_s) & 0x3) << (((_c) % 2) ? 0 : 8))
#define NI660X_IO_CFG_OUT_SEL_MASK(_c)	NI660X_IO_CFG_OUT_SEL((_c), 0x3)
#define NI660X_IO_CFG_IN_SEL(_c, _s)	(((_s) & 0x7) << (((_c) % 2) ? 4 : 12))
#define NI660X_IO_CFG_IN_SEL_MASK(_c)	NI660X_IO_CFG_IN_SEL((_c), 0x7)

struct ni_660x_register_data {
	int offset;		/*  Offset from base address from GPCT chip */
	char size;		/* 2 or 4 bytes */
};

static const struct ni_660x_register_data ni_660x_reg_data[NI660X_NUM_REGS] = {
	[NITIO_G0_INT_ACK]		= { 0x004, 2 },	/* write */
	[NITIO_G0_STATUS]		= { 0x004, 2 },	/* read */
	[NITIO_G1_INT_ACK]		= { 0x006, 2 },	/* write */
	[NITIO_G1_STATUS]		= { 0x006, 2 },	/* read */
	[NITIO_G01_STATUS]		= { 0x008, 2 },	/* read */
	[NITIO_G0_CMD]			= { 0x00c, 2 },	/* write */
	[NI660X_STC_DIO_PARALLEL_INPUT]	= { 0x00e, 2 },	/* read */
	[NITIO_G1_CMD]			= { 0x00e, 2 },	/* write */
	[NITIO_G0_HW_SAVE]		= { 0x010, 4 },	/* read */
	[NITIO_G1_HW_SAVE]		= { 0x014, 4 },	/* read */
	[NI660X_STC_DIO_OUTPUT]		= { 0x014, 2 },	/* write */
	[NI660X_STC_DIO_CONTROL]	= { 0x016, 2 },	/* write */
	[NITIO_G0_SW_SAVE]		= { 0x018, 4 },	/* read */
	[NITIO_G1_SW_SAVE]		= { 0x01c, 4 },	/* read */
	[NITIO_G0_MODE]			= { 0x034, 2 },	/* write */
	[NITIO_G01_STATUS1]		= { 0x036, 2 },	/* read */
	[NITIO_G1_MODE]			= { 0x036, 2 },	/* write */
	[NI660X_STC_DIO_SERIAL_INPUT]	= { 0x038, 2 },	/* read */
	[NITIO_G0_LOADA]		= { 0x038, 4 },	/* write */
	[NITIO_G01_STATUS2]		= { 0x03a, 2 },	/* read */
	[NITIO_G0_LOADB]		= { 0x03c, 4 },	/* write */
	[NITIO_G1_LOADA]		= { 0x040, 4 },	/* write */
	[NITIO_G1_LOADB]		= { 0x044, 4 },	/* write */
	[NITIO_G0_INPUT_SEL]		= { 0x048, 2 },	/* write */
	[NITIO_G1_INPUT_SEL]		= { 0x04a, 2 },	/* write */
	[NITIO_G0_AUTO_INC]		= { 0x088, 2 },	/* write */
	[NITIO_G1_AUTO_INC]		= { 0x08a, 2 },	/* write */
	[NITIO_G01_RESET]		= { 0x090, 2 },	/* write */
	[NITIO_G0_INT_ENA]		= { 0x092, 2 },	/* write */
	[NITIO_G1_INT_ENA]		= { 0x096, 2 },	/* write */
	[NITIO_G0_CNT_MODE]		= { 0x0b0, 2 },	/* write */
	[NITIO_G1_CNT_MODE]		= { 0x0b2, 2 },	/* write */
	[NITIO_G0_GATE2]		= { 0x0b4, 2 },	/* write */
	[NITIO_G1_GATE2]		= { 0x0b6, 2 },	/* write */
	[NITIO_G0_DMA_CFG]		= { 0x0b8, 2 },	/* write */
	[NITIO_G0_DMA_STATUS]		= { 0x0b8, 2 },	/* read */
	[NITIO_G1_DMA_CFG]		= { 0x0ba, 2 },	/* write */
	[NITIO_G1_DMA_STATUS]		= { 0x0ba, 2 },	/* read */
	[NITIO_G2_INT_ACK]		= { 0x104, 2 },	/* write */
	[NITIO_G2_STATUS]		= { 0x104, 2 },	/* read */
	[NITIO_G3_INT_ACK]		= { 0x106, 2 },	/* write */
	[NITIO_G3_STATUS]		= { 0x106, 2 },	/* read */
	[NITIO_G23_STATUS]		= { 0x108, 2 },	/* read */
	[NITIO_G2_CMD]			= { 0x10c, 2 },	/* write */
	[NITIO_G3_CMD]			= { 0x10e, 2 },	/* write */
	[NITIO_G2_HW_SAVE]		= { 0x110, 4 },	/* read */
	[NITIO_G3_HW_SAVE]		= { 0x114, 4 },	/* read */
	[NITIO_G2_SW_SAVE]		= { 0x118, 4 },	/* read */
	[NITIO_G3_SW_SAVE]		= { 0x11c, 4 },	/* read */
	[NITIO_G2_MODE]			= { 0x134, 2 },	/* write */
	[NITIO_G23_STATUS1]		= { 0x136, 2 },	/* read */
	[NITIO_G3_MODE]			= { 0x136, 2 },	/* write */
	[NITIO_G2_LOADA]		= { 0x138, 4 },	/* write */
	[NITIO_G23_STATUS2]		= { 0x13a, 2 },	/* read */
	[NITIO_G2_LOADB]		= { 0x13c, 4 },	/* write */
	[NITIO_G3_LOADA]		= { 0x140, 4 },	/* write */
	[NITIO_G3_LOADB]		= { 0x144, 4 },	/* write */
	[NITIO_G2_INPUT_SEL]		= { 0x148, 2 },	/* write */
	[NITIO_G3_INPUT_SEL]		= { 0x14a, 2 },	/* write */
	[NITIO_G2_AUTO_INC]		= { 0x188, 2 },	/* write */
	[NITIO_G3_AUTO_INC]		= { 0x18a, 2 },	/* write */
	[NITIO_G23_RESET]		= { 0x190, 2 },	/* write */
	[NITIO_G2_INT_ENA]		= { 0x192, 2 },	/* write */
	[NITIO_G3_INT_ENA]		= { 0x196, 2 },	/* write */
	[NITIO_G2_CNT_MODE]		= { 0x1b0, 2 },	/* write */
	[NITIO_G3_CNT_MODE]		= { 0x1b2, 2 },	/* write */
	[NITIO_G2_GATE2]		= { 0x1b4, 2 },	/* write */
	[NITIO_G3_GATE2]		= { 0x1b6, 2 },	/* write */
	[NITIO_G2_DMA_CFG]		= { 0x1b8, 2 },	/* write */
	[NITIO_G2_DMA_STATUS]		= { 0x1b8, 2 },	/* read */
	[NITIO_G3_DMA_CFG]		= { 0x1ba, 2 },	/* write */
	[NITIO_G3_DMA_STATUS]		= { 0x1ba, 2 },	/* read */
	[NI660X_DIO32_INPUT]		= { 0x414, 4 },	/* read */
	[NI660X_DIO32_OUTPUT]		= { 0x510, 4 },	/* write */
	[NI660X_CLK_CFG]		= { 0x73c, 4 },	/* write */
	[NI660X_GLOBAL_INT_STATUS]	= { 0x754, 4 },	/* read */
	[NI660X_DMA_CFG]		= { 0x76c, 4 },	/* write */
	[NI660X_GLOBAL_INT_CFG]		= { 0x770, 4 },	/* write */
	[NI660X_IO_CFG_0_1]		= { 0x77c, 2 },	/* read/write */
	[NI660X_IO_CFG_2_3]		= { 0x77e, 2 },	/* read/write */
	[NI660X_IO_CFG_4_5]		= { 0x780, 2 },	/* read/write */
	[NI660X_IO_CFG_6_7]		= { 0x782, 2 },	/* read/write */
	[NI660X_IO_CFG_8_9]		= { 0x784, 2 },	/* read/write */
	[NI660X_IO_CFG_10_11]		= { 0x786, 2 },	/* read/write */
	[NI660X_IO_CFG_12_13]		= { 0x788, 2 },	/* read/write */
	[NI660X_IO_CFG_14_15]		= { 0x78a, 2 },	/* read/write */
	[NI660X_IO_CFG_16_17]		= { 0x78c, 2 },	/* read/write */
	[NI660X_IO_CFG_18_19]		= { 0x78e, 2 },	/* read/write */
	[NI660X_IO_CFG_20_21]		= { 0x790, 2 },	/* read/write */
	[NI660X_IO_CFG_22_23]		= { 0x792, 2 },	/* read/write */
	[NI660X_IO_CFG_24_25]		= { 0x794, 2 },	/* read/write */
	[NI660X_IO_CFG_26_27]		= { 0x796, 2 },	/* read/write */
	[NI660X_IO_CFG_28_29]		= { 0x798, 2 },	/* read/write */
	[NI660X_IO_CFG_30_31]		= { 0x79a, 2 },	/* read/write */
	[NI660X_IO_CFG_32_33]		= { 0x79c, 2 },	/* read/write */
	[NI660X_IO_CFG_34_35]		= { 0x79e, 2 },	/* read/write */
	[NI660X_IO_CFG_36_37]		= { 0x7a0, 2 },	/* read/write */
	[NI660X_IO_CFG_38_39]		= { 0x7a2, 2 }	/* read/write */
};

#define NI660X_CHIP_OFFSET		0x800

enum ni_660x_boardid {
	BOARD_PCI6601,
	BOARD_PCI6602,
	BOARD_PXI6602,
	BOARD_PCI6608,
	BOARD_PXI6608,
	BOARD_PCI6624,
	BOARD_PXI6624
};

struct ni_660x_board {
	const char *name;
	unsigned int n_chips;	/* total number of TIO chips */
};

static const struct ni_660x_board ni_660x_boards[] = {
	[BOARD_PCI6601] = {
		.name		= "PCI-6601",
		.n_chips	= 1,
	},
	[BOARD_PCI6602] = {
		.name		= "PCI-6602",
		.n_chips	= 2,
	},
	[BOARD_PXI6602] = {
		.name		= "PXI-6602",
		.n_chips	= 2,
	},
	[BOARD_PCI6608] = {
		.name		= "PCI-6608",
		.n_chips	= 2,
	},
	[BOARD_PXI6608] = {
		.name		= "PXI-6608",
		.n_chips	= 2,
	},
	[BOARD_PCI6624] = {
		.name		= "PCI-6624",
		.n_chips	= 2,
	},
	[BOARD_PXI6624] = {
		.name		= "PXI-6624",
		.n_chips	= 2,
	},
};

#define NI660X_NUM_PFI_CHANNELS		40

/* there are only up to 3 dma channels, but the register layout allows for 4 */
#define NI660X_MAX_DMA_CHANNEL		4

#define NI660X_COUNTERS_PER_CHIP	4
#define NI660X_MAX_CHIPS		2
#define NI660X_MAX_COUNTERS		(NI660X_MAX_CHIPS *	\
					 NI660X_COUNTERS_PER_CHIP)

struct ni_660x_private {
	struct mite *mite;
	struct ni_gpct_device *counter_dev;
	struct mite_ring *ring[NI660X_MAX_CHIPS][NI660X_COUNTERS_PER_CHIP];
	/* protects mite channel request/release */
	spinlock_t mite_channel_lock;
	/* prevents races between interrupt and comedi_poll */
	spinlock_t interrupt_lock;
	unsigned int dma_cfg[NI660X_MAX_CHIPS];
	unsigned int io_cfg[NI660X_NUM_PFI_CHANNELS];
	u64 io_dir;
	struct ni_route_tables routing_tables;
};

static void ni_660x_write(struct comedi_device *dev, unsigned int chip,
			  unsigned int bits, unsigned int reg)
{
	unsigned int addr = (chip * NI660X_CHIP_OFFSET) +
			    ni_660x_reg_data[reg].offset;

	if (ni_660x_reg_data[reg].size == 2)
		writew(bits, dev->mmio + addr);
	else
		writel(bits, dev->mmio + addr);
}

static unsigned int ni_660x_read(struct comedi_device *dev,
				 unsigned int chip, unsigned int reg)
{
	unsigned int addr = (chip * NI660X_CHIP_OFFSET) +
			    ni_660x_reg_data[reg].offset;

	if (ni_660x_reg_data[reg].size == 2)
		return readw(dev->mmio + addr);
	return readl(dev->mmio + addr);
}

static void ni_660x_gpct_write(struct ni_gpct *counter, unsigned int bits,
			       enum ni_gpct_register reg)
{
	struct comedi_device *dev = counter->counter_dev->dev;

	ni_660x_write(dev, counter->chip_index, bits, reg);
}

static unsigned int ni_660x_gpct_read(struct ni_gpct *counter,
				      enum ni_gpct_register reg)
{
	struct comedi_device *dev = counter->counter_dev->dev;

	return ni_660x_read(dev, counter->chip_index, reg);
}

static inline void ni_660x_set_dma_channel(struct comedi_device *dev,
					   unsigned int mite_channel,
					   struct ni_gpct *counter)
{
	struct ni_660x_private *devpriv = dev->private;
	unsigned int chip = counter->chip_index;

	devpriv->dma_cfg[chip] &= ~NI660X_DMA_CFG_SEL_MASK(mite_channel);
	devpriv->dma_cfg[chip] |= NI660X_DMA_CFG_SEL(mite_channel,
						     counter->counter_index);
	ni_660x_write(dev, chip, devpriv->dma_cfg[chip] |
		      NI660X_DMA_CFG_RESET(mite_channel),
		      NI660X_DMA_CFG);
}

static inline void ni_660x_unset_dma_channel(struct comedi_device *dev,
					     unsigned int mite_channel,
					     struct ni_gpct *counter)
{
	struct ni_660x_private *devpriv = dev->private;
	unsigned int chip = counter->chip_index;

	devpriv->dma_cfg[chip] &= ~NI660X_DMA_CFG_SEL_MASK(mite_channel);
	devpriv->dma_cfg[chip] |= NI660X_DMA_CFG_SEL_NONE(mite_channel);
	ni_660x_write(dev, chip, devpriv->dma_cfg[chip], NI660X_DMA_CFG);
}

static int ni_660x_request_mite_channel(struct comedi_device *dev,
					struct ni_gpct *counter,
					enum comedi_io_direction direction)
{
	struct ni_660x_private *devpriv = dev->private;
	struct mite_ring *ring;
	struct mite_channel *mite_chan;
	unsigned long flags;

	spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
	ring = devpriv->ring[counter->chip_index][counter->counter_index];
	mite_chan = mite_request_channel(devpriv->mite, ring);
	if (!mite_chan) {
		spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
		dev_err(dev->class_dev,
			"failed to reserve mite dma channel for counter\n");
		return -EBUSY;
	}
	mite_chan->dir = direction;
	ni_tio_set_mite_channel(counter, mite_chan);
	ni_660x_set_dma_channel(dev, mite_chan->channel, counter);
	spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
	return 0;
}

static void ni_660x_release_mite_channel(struct comedi_device *dev,
					 struct ni_gpct *counter)
{
	struct ni_660x_private *devpriv = dev->private;
	unsigned long flags;

	spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
	if (counter->mite_chan) {
		struct mite_channel *mite_chan = counter->mite_chan;

		ni_660x_unset_dma_channel(dev, mite_chan->channel, counter);
		ni_tio_set_mite_channel(counter, NULL);
		mite_release_channel(mite_chan);
	}
	spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
}

static int ni_660x_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
{
	struct ni_gpct *counter = s->private;
	int retval;

	retval = ni_660x_request_mite_channel(dev, counter, COMEDI_INPUT);
	if (retval) {
		dev_err(dev->class_dev,
			"no dma channel available for use by counter\n");
		return retval;
	}
	ni_tio_acknowledge(counter);

	return ni_tio_cmd(dev, s);
}

static int ni_660x_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
{
	struct ni_gpct *counter = s->private;
	int retval;

	retval = ni_tio_cancel(counter);
	ni_660x_release_mite_channel(dev, counter);
	return retval;
}

static void set_tio_counterswap(struct comedi_device *dev, int chip)
{
	unsigned int bits = 0;

	/*
	 * See P. 3.5 of the Register-Level Programming manual.
	 * The CounterSwap bit has to be set on the second chip,
	 * otherwise it will try to use the same pins as the
	 * first chip.
	 */
	if (chip)
		bits = NI660X_CLK_CFG_COUNTER_SWAP;

	ni_660x_write(dev, chip, bits, NI660X_CLK_CFG);
}

static void ni_660x_handle_gpct_interrupt(struct comedi_device *dev,
					  struct comedi_subdevice *s)
{
	struct ni_gpct *counter = s->private;

	ni_tio_handle_interrupt(counter, s);
	comedi_handle_events(dev, s);
}

static irqreturn_t ni_660x_interrupt(int irq, void *d)
{
	struct comedi_device *dev = d;
	struct ni_660x_private *devpriv = dev->private;
	struct comedi_subdevice *s;
	unsigned int i;
	unsigned long flags;

	if (!dev->attached)
		return IRQ_NONE;
	/* make sure dev->attached is checked before doing anything else */
	smp_mb();

	/* lock to avoid race with comedi_poll */
	spin_lock_irqsave(&devpriv->interrupt_lock, flags);
	for (i = 0; i < dev->n_subdevices; ++i) {
		s = &dev->subdevices[i];
		if (s->type == COMEDI_SUBD_COUNTER)
			ni_660x_handle_gpct_interrupt(dev, s);
	}
	spin_unlock_irqrestore(&devpriv->interrupt_lock, flags);
	return IRQ_HANDLED;
}

static int ni_660x_input_poll(struct comedi_device *dev,
			      struct comedi_subdevice *s)
{
	struct ni_660x_private *devpriv = dev->private;
	struct ni_gpct *counter = s->private;
	unsigned long flags;

	/* lock to avoid race with comedi_poll */
	spin_lock_irqsave(&devpriv->interrupt_lock, flags);
	mite_sync_dma(counter->mite_chan, s);
	spin_unlock_irqrestore(&devpriv->interrupt_lock, flags);
	return comedi_buf_read_n_available(s);
}

static int ni_660x_buf_change(struct comedi_device *dev,
			      struct comedi_subdevice *s)
{
	struct ni_660x_private *devpriv = dev->private;
	struct ni_gpct *counter = s->private;
	struct mite_ring *ring;
	int ret;

	ring = devpriv->ring[counter->chip_index][counter->counter_index];
	ret = mite_buf_change(ring, s);
	if (ret < 0)
		return ret;

	return 0;
}

static int ni_660x_allocate_private(struct comedi_device *dev)
{
	struct ni_660x_private *devpriv;
	unsigned int i;

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

	spin_lock_init(&devpriv->mite_channel_lock);
	spin_lock_init(&devpriv->interrupt_lock);
	for (i = 0; i < NI660X_NUM_PFI_CHANNELS; ++i)
		devpriv->io_cfg[i] = NI_660X_PFI_OUTPUT_COUNTER;

	return 0;
}

static int ni_660x_alloc_mite_rings(struct comedi_device *dev)
{
	const struct ni_660x_board *board = dev->board_ptr;
	struct ni_660x_private *devpriv = dev->private;
	unsigned int i;
	unsigned int j;

	for (i = 0; i < board->n_chips; ++i) {
		for (j = 0; j < NI660X_COUNTERS_PER_CHIP; ++j) {
			devpriv->ring[i][j] = mite_alloc_ring(devpriv->mite);
			if (!devpriv->ring[i][j])
				return -ENOMEM;
		}
	}
	return 0;
}

static void ni_660x_free_mite_rings(struct comedi_device *dev)
{
	const struct ni_660x_board *board = dev->board_ptr;
	struct ni_660x_private *devpriv = dev->private;
	unsigned int i;
	unsigned int j;

	for (i = 0; i < board->n_chips; ++i) {
		for (j = 0; j < NI660X_COUNTERS_PER_CHIP; ++j)
			mite_free_ring(devpriv->ring[i][j]);
	}
}

static int ni_660x_dio_insn_bits(struct comedi_device *dev,
				 struct comedi_subdevice *s,
				 struct comedi_insn *insn,
				 unsigned int *data)
{
	unsigned int shift = CR_CHAN(insn->chanspec);
	unsigned int mask = data[0] << shift;
	unsigned int bits = data[1] << shift;

	/*
	 * There are 40 channels in this subdevice but only 32 are usable
	 * as DIO. The shift adjusts the mask/bits to account for the base
	 * channel in insn->chanspec. The state update can then be handled
	 * normally for the 32 usable channels.
	 */
	if (mask) {
		s->state &= ~mask;
		s->state |= (bits & mask);
		ni_660x_write(dev, 0, s->state, NI660X_DIO32_OUTPUT);
	}

	/*
	 * Return the input channels, shifted back to account for the base
	 * channel.
	 */
	data[1] = ni_660x_read(dev, 0, NI660X_DIO32_INPUT) >> shift;

	return insn->n;
}

static void ni_660x_select_pfi_output(struct comedi_device *dev,
				      unsigned int chan, unsigned int out_sel)
{
	const struct ni_660x_board *board = dev->board_ptr;
	unsigned int active_chip = 0;
	unsigned int idle_chip = 0;
	unsigned int bits;

	if (chan >= NI_PFI(0))
		/* allow new and old names of pfi channels to work. */
		chan -= NI_PFI(0);

	if (board->n_chips > 1) {
		if (out_sel == NI_660X_PFI_OUTPUT_COUNTER &&
		    chan >= 8 && chan <= 23) {
			/* counters 4-7 pfi channels */
			active_chip = 1;
			idle_chip = 0;
		} else {
			/* counters 0-3 pfi channels */
			active_chip = 0;
			idle_chip = 1;
		}
	}

	if (idle_chip != active_chip) {
		/* set the pfi channel to high-z on the inactive chip */
		bits = ni_660x_read(dev, idle_chip, NI660X_IO_CFG(chan));
		bits &= ~NI660X_IO_CFG_OUT_SEL_MASK(chan);
		bits |= NI660X_IO_CFG_OUT_SEL(chan, 0);		/* high-z */
		ni_660x_write(dev, idle_chip, bits, NI660X_IO_CFG(chan));
	}

	/* set the pfi channel output on the active chip */
	bits = ni_660x_read(dev, active_chip, NI660X_IO_CFG(chan));
	bits &= ~NI660X_IO_CFG_OUT_SEL_MASK(chan);
	bits |= NI660X_IO_CFG_OUT_SEL(chan, out_sel);
	ni_660x_write(dev, active_chip, bits, NI660X_IO_CFG(chan));
}

static void ni_660x_set_pfi_direction(struct comedi_device *dev,
				      unsigned int chan,
				      unsigned int direction)
{
	struct ni_660x_private *devpriv = dev->private;
	u64 bit;

	if (chan >= NI_PFI(0))
		/* allow new and old names of pfi channels to work. */
		chan -= NI_PFI(0);

	bit = 1ULL << chan;

	if (direction == COMEDI_OUTPUT) {
		devpriv->io_dir |= bit;
		/* reset the output to currently assigned output value */
		ni_660x_select_pfi_output(dev, chan, devpriv->io_cfg[chan]);
	} else {
		devpriv->io_dir &= ~bit;
		/* set pin to high-z; do not change currently assigned route */
		ni_660x_select_pfi_output(dev, chan, 0);
	}
}

static unsigned int ni_660x_get_pfi_direction(struct comedi_device *dev,
					      unsigned int chan)
{
	struct ni_660x_private *devpriv = dev->private;
	u64 bit;

	if (chan >= NI_PFI(0))
		/* allow new and old names of pfi channels to work. */
		chan -= NI_PFI(0);

	bit = 1ULL << chan;

	return (devpriv->io_dir & bit) ? COMEDI_OUTPUT : COMEDI_INPUT;
}

static int ni_660x_set_pfi_routing(struct comedi_device *dev,
				   unsigned int chan, unsigned int source)
{
	struct ni_660x_private *devpriv = dev->private;

	if (chan >= NI_PFI(0))
		/* allow new and old names of pfi channels to work. */
		chan -= NI_PFI(0);

	switch (source) {
	case NI_660X_PFI_OUTPUT_COUNTER:
		if (chan < 8)
			return -EINVAL;
		break;
	case NI_660X_PFI_OUTPUT_DIO:
		if (chan > 31)
			return -EINVAL;
		break;
	default:
		return -EINVAL;
	}

	devpriv->io_cfg[chan] = source;
	if (ni_660x_get_pfi_direction(dev, chan) == COMEDI_OUTPUT)
		ni_660x_select_pfi_output(dev, chan, devpriv->io_cfg[chan]);
	return 0;
}

static int ni_660x_get_pfi_routing(struct comedi_device *dev, unsigned int chan)
{
	struct ni_660x_private *devpriv = dev->private;

	if (chan >= NI_PFI(0))
		/* allow new and old names of pfi channels to work. */
		chan -= NI_PFI(0);

	return devpriv->io_cfg[chan];
}

static void ni_660x_set_pfi_filter(struct comedi_device *dev,
				   unsigned int chan, unsigned int value)
{
	unsigned int val;

	if (chan >= NI_PFI(0))
		/* allow new and old names of pfi channels to work. */
		chan -= NI_PFI(0);

	val = ni_660x_read(dev, 0, NI660X_IO_CFG(chan));
	val &= ~NI660X_IO_CFG_IN_SEL_MASK(chan);
	val |= NI660X_IO_CFG_IN_SEL(chan, value);
	ni_660x_write(dev, 0, val, NI660X_IO_CFG(chan));
}

static int ni_660x_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);
	int ret;

	switch (data[0]) {
	case INSN_CONFIG_DIO_OUTPUT:
		ni_660x_set_pfi_direction(dev, chan, COMEDI_OUTPUT);
		break;

	case INSN_CONFIG_DIO_INPUT:
		ni_660x_set_pfi_direction(dev, chan, COMEDI_INPUT);
		break;

	case INSN_CONFIG_DIO_QUERY:
		data[1] = ni_660x_get_pfi_direction(dev, chan);
		break;

	case INSN_CONFIG_SET_ROUTING:
		ret = ni_660x_set_pfi_routing(dev, chan, data[1]);
		if (ret)
			return ret;
		break;

	case INSN_CONFIG_GET_ROUTING:
		data[1] = ni_660x_get_pfi_routing(dev, chan);
		break;

	case INSN_CONFIG_FILTER:
		ni_660x_set_pfi_filter(dev, chan, data[1]);
		break;

	default:
		return -EINVAL;
	}

	return insn->n;
}

static unsigned int _ni_get_valid_routes(struct comedi_device *dev,
					 unsigned int n_pairs,
					 unsigned int *pair_data)
{
	struct ni_660x_private *devpriv = dev->private;

	return ni_get_valid_routes(&devpriv->routing_tables, n_pairs,
				   pair_data);
}

/*
 * Retrieves the current source of the output selector for the given
 * destination.  If the terminal for the destination is not already configured
 * as an output, this function returns -EINVAL as error.
 *
 * Return: The register value of the destination output selector;
 *	   -EINVAL if terminal is not configured for output.
 */
static inline int get_output_select_source(int dest, struct comedi_device *dev)
{
	struct ni_660x_private *devpriv = dev->private;
	int reg = -1;

	if (channel_is_pfi(dest)) {
		if (ni_660x_get_pfi_direction(dev, dest) == COMEDI_OUTPUT)
			reg = ni_660x_get_pfi_routing(dev, dest);
	} else if (channel_is_rtsi(dest)) {
		dev_dbg(dev->class_dev,
			"%s: unhandled rtsi destination (%d) queried\n",
			__func__, dest);
		/*
		 * The following can be enabled when RTSI routing info is
		 * determined (not currently documented):
		 * if (ni_get_rtsi_direction(dev, dest) == COMEDI_OUTPUT) {
		 *	reg = ni_get_rtsi_routing(dev, dest);

		 *	if (reg == NI_RTSI_OUTPUT_RGOUT0) {
		 *		dest = NI_RGOUT0; ** prepare for lookup below **
		 *		reg = get_rgout0_reg(dev);
		 *	} else if (reg >= NI_RTSI_OUTPUT_RTSI_BRD(0) &&
		 *		   reg <= NI_RTSI_OUTPUT_RTSI_BRD(3)) {
		 *		const int i = reg - NI_RTSI_OUTPUT_RTSI_BRD(0);

		 *		dest = NI_RTSI_BRD(i); ** prepare for lookup **
		 *		reg = get_ith_rtsi_brd_reg(i, dev);
		 *	}
		 * }
		 */
	} else if (channel_is_ctr(dest)) {
		reg = ni_tio_get_routing(devpriv->counter_dev, dest);
	} else {
		dev_dbg(dev->class_dev,
			"%s: unhandled destination (%d) queried\n",
			__func__, dest);
	}

	if (reg >= 0)
		return ni_find_route_source(CR_CHAN(reg), dest,
					    &devpriv->routing_tables);
	return -EINVAL;
}

/*
 * Test a route:
 *
 * Return: -1 if not connectible;
 *	    0 if connectible and not connected;
 *	    1 if connectible and connected.
 */
static inline int test_route(unsigned int src, unsigned int dest,
			     struct comedi_device *dev)
{
	struct ni_660x_private *devpriv = dev->private;
	s8 reg = ni_route_to_register(CR_CHAN(src), dest,
				      &devpriv->routing_tables);

	if (reg < 0)
		return -1;
	if (get_output_select_source(dest, dev) != CR_CHAN(src))
		return 0;
	return 1;
}

/* Connect the actual route.  */
static inline int connect_route(unsigned int src, unsigned int dest,
				struct comedi_device *dev)
{
	struct ni_660x_private *devpriv = dev->private;
	s8 reg = ni_route_to_register(CR_CHAN(src), dest,
				      &devpriv->routing_tables);
	s8 current_src;

	if (reg < 0)
		/* route is not valid */
		return -EINVAL;

	current_src = get_output_select_source(dest, dev);
	if (current_src == CR_CHAN(src))
		return -EALREADY;
	if (current_src >= 0)
		/* destination mux is already busy. complain, don't overwrite */
		return -EBUSY;

	/* The route is valid and available. Now connect... */
	if (channel_is_pfi(CR_CHAN(dest))) {
		/*
		 * set routing and then direction so that the output does not
		 * first get generated with the wrong pin
		 */
		ni_660x_set_pfi_routing(dev, dest, reg);
		ni_660x_set_pfi_direction(dev, dest, COMEDI_OUTPUT);
	} else if (channel_is_rtsi(CR_CHAN(dest))) {
		dev_dbg(dev->class_dev, "%s: unhandled rtsi destination (%d)\n",
			__func__, dest);
		return -EINVAL;
		/*
		 * The following can be enabled when RTSI routing info is
		 * determined (not currently documented):
		 * if (reg == NI_RTSI_OUTPUT_RGOUT0) {
		 *	int ret = incr_rgout0_src_use(src, dev);

		 *	if (ret < 0)
		 *		return ret;
		 * } else if (ni_rtsi_route_requires_mux(reg)) {
		 *	** Attempt to allocate and  route (src->brd) **
		 *	int brd = incr_rtsi_brd_src_use(src, dev);

		 *	if (brd < 0)
		 *		return brd;

		 *	** Now lookup the register value for (brd->dest) **
		 *	reg = ni_lookup_route_register(brd, CR_CHAN(dest),
		 *				       &devpriv->routing_tables);
		 * }

		 * ni_set_rtsi_direction(dev, dest, COMEDI_OUTPUT);
		 * ni_set_rtsi_routing(dev, dest, reg);
		 */
	} else if (channel_is_ctr(CR_CHAN(dest))) {
		/*
		 * we are adding back the channel modifier info to set
		 * invert/edge info passed by the user
		 */
		ni_tio_set_routing(devpriv->counter_dev, dest,
				   reg | (src & ~CR_CHAN(-1)));
	} else {
		return -EINVAL;
	}
	return 0;
}

static inline int disconnect_route(unsigned int src, unsigned int dest,
				   struct comedi_device *dev)
{
	struct ni_660x_private *devpriv = dev->private;
	s8 reg = ni_route_to_register(CR_CHAN(src), CR_CHAN(dest),
				      &devpriv->routing_tables);

	if (reg < 0)
		/* route is not valid */
		return -EINVAL;
	if (get_output_select_source(dest, dev) != CR_CHAN(src))
		/* cannot disconnect something not connected */
		return -EINVAL;

	/* The route is valid and is connected.  Now disconnect... */
	if (channel_is_pfi(CR_CHAN(dest))) {
		unsigned int source = ((CR_CHAN(dest) - NI_PFI(0)) < 8)
					? NI_660X_PFI_OUTPUT_DIO
					: NI_660X_PFI_OUTPUT_COUNTER;

		/* set the pfi to high impedance, and disconnect */
		ni_660x_set_pfi_direction(dev, dest, COMEDI_INPUT);
		ni_660x_set_pfi_routing(dev, dest, source);
	} else if (channel_is_rtsi(CR_CHAN(dest))) {
		dev_dbg(dev->class_dev, "%s: unhandled rtsi destination (%d)\n",
			__func__, dest);
		return -EINVAL;
		/*
		 * The following can be enabled when RTSI routing info is
		 * determined (not currently documented):
		 * if (reg == NI_RTSI_OUTPUT_RGOUT0) {
		 *	int ret = decr_rgout0_src_use(src, dev);

		 *	if (ret < 0)
		 *		return ret;
		 * } else if (ni_rtsi_route_requires_mux(reg)) {
		 *	** find which RTSI_BRD line is source for rtsi pin **
		 *	int brd = ni_find_route_source(
		 *		ni_get_rtsi_routing(dev, dest), CR_CHAN(dest),
		 *		&devpriv->routing_tables);

		 *	if (brd < 0)
		 *		return brd;

		 *	** decrement/disconnect RTSI_BRD line from source **
		 *	decr_rtsi_brd_src_use(src, brd, dev);
		 * }

		 * ** set rtsi output selector to default state **
		 * reg = default_rtsi_routing[CR_CHAN(dest) - TRIGGER_LINE(0)];
		 * ni_set_rtsi_direction(dev, dest, COMEDI_INPUT);
		 * ni_set_rtsi_routing(dev, dest, reg);
		 */
	} else if (channel_is_ctr(CR_CHAN(dest))) {
		ni_tio_unset_routing(devpriv->counter_dev, dest);
	} else {
		return -EINVAL;
	}
	return 0;
}

static int ni_global_insn_config(struct comedi_device *dev,
				 struct comedi_insn *insn,
				 unsigned int *data)
{
	switch (data[0]) {
	case INSN_DEVICE_CONFIG_TEST_ROUTE:
		data[0] = test_route(data[1], data[2], dev);
		return 2;
	case INSN_DEVICE_CONFIG_CONNECT_ROUTE:
		return connect_route(data[1], data[2], dev);
	case INSN_DEVICE_CONFIG_DISCONNECT_ROUTE:
		return disconnect_route(data[1], data[2], dev);
	/*
	 * This case is already handled one level up.
	 * case INSN_DEVICE_CONFIG_GET_ROUTES:
	 */
	default:
		return -EINVAL;
	}
	return 1;
}

static void ni_660x_init_tio_chips(struct comedi_device *dev,
				   unsigned int n_chips)
{
	struct ni_660x_private *devpriv = dev->private;
	unsigned int chip;
	unsigned int chan;

	/*
	 * We use the ioconfig registers to control dio direction, so zero
	 * output enables in stc dio control reg.
	 */
	ni_660x_write(dev, 0, 0, NI660X_STC_DIO_CONTROL);

	for (chip = 0; chip < n_chips; ++chip) {
		/* init dma configuration register */
		devpriv->dma_cfg[chip] = 0;
		for (chan = 0; chan < NI660X_MAX_DMA_CHANNEL; ++chan)
			devpriv->dma_cfg[chip] |= NI660X_DMA_CFG_SEL_NONE(chan);
		ni_660x_write(dev, chip, devpriv->dma_cfg[chip],
			      NI660X_DMA_CFG);

		/* init ioconfig registers */
		for (chan = 0; chan < NI660X_NUM_PFI_CHANNELS; ++chan)
			ni_660x_write(dev, chip, 0, NI660X_IO_CFG(chan));
	}
}

static int ni_660x_auto_attach(struct comedi_device *dev,
			       unsigned long context)
{
	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
	const struct ni_660x_board *board = NULL;
	struct ni_660x_private *devpriv;
	struct comedi_subdevice *s;
	struct ni_gpct_device *gpct_dev;
	unsigned int n_counters;
	int subdev;
	int ret;
	unsigned int i;
	unsigned int global_interrupt_config_bits;

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

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

	ret = ni_660x_allocate_private(dev);
	if (ret < 0)
		return ret;
	devpriv = dev->private;

	devpriv->mite = mite_attach(dev, true);		/* use win1 */
	if (!devpriv->mite)
		return -ENOMEM;

	ret = ni_660x_alloc_mite_rings(dev);
	if (ret < 0)
		return ret;

	ni_660x_init_tio_chips(dev, board->n_chips);

	/* prepare the device for globally-named routes. */
	if (ni_assign_device_routes("ni_660x", board->name, NULL,
				    &devpriv->routing_tables) < 0) {
		dev_warn(dev->class_dev, "%s: %s device has no signal routing table.\n",
			 __func__, board->name);
		dev_warn(dev->class_dev, "%s: High level NI signal names will not be available for this %s board.\n",
			 __func__, board->name);
	} else {
		/*
		 * only(?) assign insn_device_config if we have global names for
		 * this device.
		 */
		dev->insn_device_config = ni_global_insn_config;
		dev->get_valid_routes = _ni_get_valid_routes;
	}

	n_counters = board->n_chips * NI660X_COUNTERS_PER_CHIP;
	gpct_dev = ni_gpct_device_construct(dev,
					    ni_660x_gpct_write,
					    ni_660x_gpct_read,
					    ni_gpct_variant_660x,
					    n_counters,
					    NI660X_COUNTERS_PER_CHIP,
					    &devpriv->routing_tables);
	if (!gpct_dev)
		return -ENOMEM;
	devpriv->counter_dev = gpct_dev;

	ret = comedi_alloc_subdevices(dev, 2 + NI660X_MAX_COUNTERS);
	if (ret)
		return ret;

	subdev = 0;

	s = &dev->subdevices[subdev++];
	/* Old GENERAL-PURPOSE COUNTER/TIME (GPCT) subdevice, no longer used */
	s->type = COMEDI_SUBD_UNUSED;

	/*
	 * Digital I/O subdevice
	 *
	 * There are 40 channels but only the first 32 can be digital I/Os.
	 * The last 8 are dedicated to counters 0 and 1.
	 *
	 * Counter 0-3 signals are from the first TIO chip.
	 * Counter 4-7 signals are from the second TIO chip.
	 *
	 * Comedi	External
	 * PFI Chan	DIO Chan        Counter Signal
	 * -------	--------	--------------
	 *     0	    0
	 *     1	    1
	 *     2	    2
	 *     3	    3
	 *     4	    4
	 *     5	    5
	 *     6	    6
	 *     7	    7
	 *     8	    8		CTR 7 OUT
	 *     9	    9		CTR 7 AUX
	 *    10	   10		CTR 7 GATE
	 *    11	   11		CTR 7 SOURCE
	 *    12	   12		CTR 6 OUT
	 *    13	   13		CTR 6 AUX
	 *    14	   14		CTR 6 GATE
	 *    15	   15		CTR 6 SOURCE
	 *    16	   16		CTR 5 OUT
	 *    17	   17		CTR 5 AUX
	 *    18	   18		CTR 5 GATE
	 *    19	   19		CTR 5 SOURCE
	 *    20	   20		CTR 4 OUT
	 *    21	   21		CTR 4 AUX
	 *    22	   22		CTR 4 GATE
	 *    23	   23		CTR 4 SOURCE
	 *    24	   24		CTR 3 OUT
	 *    25	   25		CTR 3 AUX
	 *    26	   26		CTR 3 GATE
	 *    27	   27		CTR 3 SOURCE
	 *    28	   28		CTR 2 OUT
	 *    29	   29		CTR 2 AUX
	 *    30	   30		CTR 2 GATE
	 *    31	   31		CTR 2 SOURCE
	 *    32			CTR 1 OUT
	 *    33			CTR 1 AUX
	 *    34			CTR 1 GATE
	 *    35			CTR 1 SOURCE
	 *    36			CTR 0 OUT
	 *    37			CTR 0 AUX
	 *    38			CTR 0 GATE
	 *    39			CTR 0 SOURCE
	 */
	s = &dev->subdevices[subdev++];
	s->type		= COMEDI_SUBD_DIO;
	s->subdev_flags	= SDF_READABLE | SDF_WRITABLE;
	s->n_chan	= NI660X_NUM_PFI_CHANNELS;
	s->maxdata	= 1;
	s->range_table	= &range_digital;
	s->insn_bits	= ni_660x_dio_insn_bits;
	s->insn_config	= ni_660x_dio_insn_config;

	 /*
	  * Default the DIO channels as:
	  *   chan 0-7:  DIO inputs
	  *   chan 8-39: counter signal inputs
	  */
	for (i = 0; i < s->n_chan; ++i) {
		unsigned int source = (i < 8) ? NI_660X_PFI_OUTPUT_DIO
					      : NI_660X_PFI_OUTPUT_COUNTER;

		ni_660x_set_pfi_routing(dev, i, source);
		ni_660x_set_pfi_direction(dev, i, COMEDI_INPUT);/* high-z */
	}

	/* Counter subdevices (4 NI TIO General Purpose Counters per chip) */
	for (i = 0; i < NI660X_MAX_COUNTERS; ++i) {
		s = &dev->subdevices[subdev++];
		if (i < n_counters) {
			struct ni_gpct *counter = &gpct_dev->counters[i];

			s->type		= COMEDI_SUBD_COUNTER;
			s->subdev_flags	= SDF_READABLE | SDF_WRITABLE |
					  SDF_LSAMPL | SDF_CMD_READ;
			s->n_chan	= 3;
			s->maxdata	= 0xffffffff;
			s->insn_read	= ni_tio_insn_read;
			s->insn_write	= ni_tio_insn_write;
			s->insn_config	= ni_tio_insn_config;
			s->len_chanlist	= 1;
			s->do_cmd	= ni_660x_cmd;
			s->do_cmdtest	= ni_tio_cmdtest;
			s->cancel	= ni_660x_cancel;
			s->poll		= ni_660x_input_poll;
			s->buf_change	= ni_660x_buf_change;
			s->async_dma_dir = DMA_BIDIRECTIONAL;
			s->private	= counter;

			ni_tio_init_counter(counter);
		} else {
			s->type		= COMEDI_SUBD_UNUSED;
		}
	}

	/*
	 * To be safe, set counterswap bits on tio chips after all the counter
	 * outputs have been set to high impedance mode.
	 */
	for (i = 0; i < board->n_chips; ++i)
		set_tio_counterswap(dev, i);

	ret = request_irq(pcidev->irq, ni_660x_interrupt, IRQF_SHARED,
			  dev->board_name, dev);
	if (ret < 0) {
		dev_warn(dev->class_dev, " irq not available\n");
		return ret;
	}
	dev->irq = pcidev->irq;
	global_interrupt_config_bits = NI660X_GLOBAL_INT_GLOBAL;
	if (board->n_chips > 1)
		global_interrupt_config_bits |= NI660X_GLOBAL_INT_CASCADE;
	ni_660x_write(dev, 0, global_interrupt_config_bits,
		      NI660X_GLOBAL_INT_CFG);

	return 0;
}

static void ni_660x_detach(struct comedi_device *dev)
{
	struct ni_660x_private *devpriv = dev->private;

	if (dev->irq) {
		ni_660x_write(dev, 0, 0, NI660X_GLOBAL_INT_CFG);
		free_irq(dev->irq, dev);
	}
	if (devpriv) {
		ni_gpct_device_destroy(devpriv->counter_dev);
		ni_660x_free_mite_rings(dev);
		mite_detach(devpriv->mite);
	}
	if (dev->mmio)
		iounmap(dev->mmio);
	comedi_pci_disable(dev);
}

static struct comedi_driver ni_660x_driver = {
	.driver_name	= "ni_660x",
	.module		= THIS_MODULE,
	.auto_attach	= ni_660x_auto_attach,
	.detach		= ni_660x_detach,
};

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

static const struct pci_device_id ni_660x_pci_table[] = {
	{ PCI_VDEVICE(NI, 0x1310), BOARD_PCI6602 },
	{ PCI_VDEVICE(NI, 0x1360), BOARD_PXI6602 },
	{ PCI_VDEVICE(NI, 0x2c60), BOARD_PCI6601 },
	{ PCI_VDEVICE(NI, 0x2db0), BOARD_PCI6608 },
	{ PCI_VDEVICE(NI, 0x2cc0), BOARD_PXI6608 },
	{ PCI_VDEVICE(NI, 0x1e30), BOARD_PCI6624 },
	{ PCI_VDEVICE(NI, 0x1e40), BOARD_PXI6624 },
	{ 0 }
};
MODULE_DEVICE_TABLE(pci, ni_660x_pci_table);

static struct pci_driver ni_660x_pci_driver = {
	.name		= "ni_660x",
	.id_table	= ni_660x_pci_table,
	.probe		= ni_660x_pci_probe,
	.remove		= comedi_pci_auto_unconfig,
};
module_comedi_pci_driver(ni_660x_driver, ni_660x_pci_driver);

MODULE_AUTHOR("Comedi https://www.comedi.org");
MODULE_DESCRIPTION("Comedi driver for NI 660x counter/timer boards");
MODULE_LICENSE("GPL"