text: // SPDX-License-Identifier: GPL-2.0
  /*
   *  comedi/drivers/adl_pci9118.c
   *
   *  hardware driver for ADLink cards:
   *   card:   PCI-9118DG, PCI-9118HG, PCI-9118HR
   *   driver: pci9118dg,  pci9118hg,  pci9118hr
   *
   * Author: Michal Dobes <dobes@tesnet.cz>
   *
   */
  
  /*
   * Driver: adl_pci9118
   * Description: Adlink PCI-9118DG, PCI-9118HG, PCI-9118HR
   * Author: Michal Dobes <dobes@tesnet.cz>
   * Devices: [ADLink] PCI-9118DG (pci9118dg), PCI-9118HG (pci9118hg),
   * PCI-9118HR (pci9118hr)
   * Status: works
   *
   * This driver supports AI, AO, DI and DO subdevices.
   * AI subdevice supports cmd and insn interface,
   * other subdevices support only insn interface.
   * For AI:
   * - If cmd->scan_begin_src=TRIG_EXT then trigger input is TGIN (pin 46).
   * - If cmd->convert_src=TRIG_EXT then trigger input is EXTTRG (pin 44).
   * - If cmd->start_src/stop_src=TRIG_EXT then trigger input is TGIN (pin 46).
   * - It is not necessary to have cmd.scan_end_arg=cmd.chanlist_len but
   * cmd.scan_end_arg modulo cmd.chanlist_len must by 0.
   * - If return value of cmdtest is 5 then you've bad channel list
   * (it isn't possible mixture S.E. and DIFF inputs or bipolar and unipolar
   * ranges).
   *
   * There are some hardware limitations:
   * a) You cann't use mixture of unipolar/bipoar ranges or differencial/single
   *  ended inputs.
   * b) DMA transfers must have the length aligned to two samples (32 bit),
   *  so there is some problems if cmd->chanlist_len is odd. This driver tries
   *  bypass this with adding one sample to the end of the every scan and discard
   *  it on output but this can't be used if cmd->scan_begin_src=TRIG_FOLLOW
   *  and is used flag CMDF_WAKE_EOS, then driver switch to interrupt driven mode
   *  with interrupt after every sample.
   * c) If isn't used DMA then you can use only mode where
   *  cmd->scan_begin_src=TRIG_FOLLOW.
   *
   * Configuration options:
   * [0] - PCI bus of device (optional)
   * [1] - PCI slot of device (optional)
   *	 If bus/slot is not specified, then first available PCI
   *	 card will be used.
   * [2] - 0= standard 8 DIFF/16 SE channels configuration
   *	 n = external multiplexer connected, 1 <= n <= 256
   * [3] - ignored
   * [4] - sample&hold signal - card can generate signal for external S&H board
   *	 0 = use SSHO(pin 45) signal is generated in onboard hardware S&H logic
   *	 0 != use ADCHN7(pin 23) signal is generated from driver, number say how
   *		long delay is requested in ns and sign polarity of the hold
   *		(in this case external multiplexor can serve only 128 channels)
   * [5] - ignored
   */
  
  /*
   * FIXME
   *
   * All the supported boards have the same PCI vendor and device IDs, so
   * auto-attachment of PCI devices will always find the first board type.
   *
   * Perhaps the boards have different subdevice IDs that we could use to
   * distinguish them?
   *
   * Need some device attributes so the board type can be corrected after
   * attachment if necessary, and possibly to set other options supported by
   * manual attachment.
   */
  
  #include <linux/module.h>
  #include <linux/delay.h>
  #include <linux/gfp.h>
  #include <linux/interrupt.h>
  #include <linux/io.h>
  
  #include "../comedi_pci.h"
  
  #include "amcc_s5933.h"
  #include "comedi_8254.h"
  
  /*
   * PCI BAR2 Register map (dev->iobase)
   */
  #define PCI9118_TIMER_BASE		0x00
  #define PCI9118_AI_FIFO_REG		0x10
  #define PCI9118_AO_REG(x)		(0x10 + ((x) * 4))
  #define PCI9118_AI_STATUS_REG		0x18
  #define PCI9118_AI_STATUS_NFULL		BIT(8)	/* 0=FIFO full (fatal) */
  #define PCI9118_AI_STATUS_NHFULL	BIT(7)	/* 0=FIFO half full */
  #define PCI9118_AI_STATUS_NEPTY		BIT(6)	/* 0=FIFO empty */
  #define PCI9118_AI_STATUS_ACMP		BIT(5)	/* 1=about trigger complete */
  #define PCI9118_AI_STATUS_DTH		BIT(4)	/* 1=ext. digital trigger */
  #define PCI9118_AI_STATUS_BOVER		BIT(3)	/* 1=burst overrun (fatal) */
  #define PCI9118_AI_STATUS_ADOS		BIT(2)	/* 1=A/D over speed (warn) */
  #define PCI9118_AI_STATUS_ADOR		BIT(1)	/* 1=A/D overrun (fatal) */
  #define PCI9118_AI_STATUS_ADRDY		BIT(0)	/* 1=A/D ready */
  #define PCI9118_AI_CTRL_REG		0x18
  #define PCI9118_AI_CTRL_UNIP		BIT(7)	/* 1=unipolar */
  #define PCI9118_AI_CTRL_DIFF		BIT(6)	/* 1=differential inputs */
  #define PCI9118_AI_CTRL_SOFTG		BIT(5)	/* 1=8254 software gate */
  #define PCI9118_AI_CTRL_EXTG		BIT(4)	/* 1=8254 TGIN(pin 46) gate */
  #define PCI9118_AI_CTRL_EXTM		BIT(3)	/* 1=ext. trigger (pin 44) */
  #define PCI9118_AI_CTRL_TMRTR		BIT(2)	/* 1=8254 is trigger source */
  #define PCI9118_AI_CTRL_INT		BIT(1)	/* 1=enable interrupt */
  #define PCI9118_AI_CTRL_DMA		BIT(0)	/* 1=enable DMA */
  #define PCI9118_DIO_REG			0x1c
  #define PCI9118_SOFTTRG_REG		0x20
  #define PCI9118_AI_CHANLIST_REG		0x24
  #define PCI9118_AI_CHANLIST_RANGE(x)	(((x) & 0x3) << 8)
  #define PCI9118_AI_CHANLIST_CHAN(x)	((x) << 0)
  #define PCI9118_AI_BURST_NUM_REG	0x28
  #define PCI9118_AI_AUTOSCAN_MODE_REG	0x2c
  #define PCI9118_AI_CFG_REG		0x30
  #define PCI9118_AI_CFG_PDTRG		BIT(7)	/* 1=positive trigger */
  #define PCI9118_AI_CFG_PETRG		BIT(6)	/* 1=positive ext. trigger */
  #define PCI9118_AI_CFG_BSSH		BIT(5)	/* 1=with sample & hold */
  #define PCI9118_AI_CFG_BM		BIT(4)	/* 1=burst mode */
  #define PCI9118_AI_CFG_BS		BIT(3)	/* 1=burst mode start */
  #define PCI9118_AI_CFG_PM		BIT(2)	/* 1=post trigger */
  #define PCI9118_AI_CFG_AM		BIT(1)	/* 1=about trigger */
  #define PCI9118_AI_CFG_START		BIT(0)	/* 1=trigger start */
  #define PCI9118_FIFO_RESET_REG		0x34
  #define PCI9118_INT_CTRL_REG		0x38
  #define PCI9118_INT_CTRL_TIMER		BIT(3)	/* timer interrupt */
  #define PCI9118_INT_CTRL_ABOUT		BIT(2)	/* about trigger complete */
  #define PCI9118_INT_CTRL_HFULL		BIT(1)	/* A/D FIFO half full */
  #define PCI9118_INT_CTRL_DTRG		BIT(0)	/* ext. digital trigger */
  
  #define START_AI_EXT	0x01	/* start measure on external trigger */
  #define STOP_AI_EXT	0x02	/* stop measure on external trigger */
  #define STOP_AI_INT	0x08	/* stop measure on internal trigger */
  
  static const struct comedi_lrange pci9118_ai_range = {
  	8, {
  		BIP_RANGE(5),
  		BIP_RANGE(2.5),
  		BIP_RANGE(1.25),
  		BIP_RANGE(0.625),
  		UNI_RANGE(10),
  		UNI_RANGE(5),
  		UNI_RANGE(2.5),
  		UNI_RANGE(1.25)
  	}
  };
  
  static const struct comedi_lrange pci9118hg_ai_range = {
  	8, {
  		BIP_RANGE(5),
  		BIP_RANGE(0.5),
  		BIP_RANGE(0.05),
  		BIP_RANGE(0.005),
  		UNI_RANGE(10),
  		UNI_RANGE(1),
  		UNI_RANGE(0.1),
  		UNI_RANGE(0.01)
  	}
  };
  
  enum pci9118_boardid {
  	BOARD_PCI9118DG,
  	BOARD_PCI9118HG,
  	BOARD_PCI9118HR,
  };
  
  struct pci9118_boardinfo {
  	const char *name;
  	unsigned int ai_is_16bit:1;
  	unsigned int is_hg:1;
  };
  
  static const struct pci9118_boardinfo pci9118_boards[] = {
  	[BOARD_PCI9118DG] = {
  		.name		= "pci9118dg",
  	},
  	[BOARD_PCI9118HG] = {
  		.name		= "pci9118hg",
  		.is_hg		= 1,
  	},
  	[BOARD_PCI9118HR] = {
  		.name		= "pci9118hr",
  		.ai_is_16bit	= 1,
  	},
  };
  
  struct pci9118_dmabuf {
  	unsigned short *virt;	/* virtual address of buffer */
  	dma_addr_t hw;		/* hardware (bus) address of buffer */
  	unsigned int size;	/* size of dma buffer in bytes */
  	unsigned int use_size;	/* which size we may now use for transfer */
  };
  
  struct pci9118_private {
  	unsigned long iobase_a;	/* base+size for AMCC chip */
  	unsigned int master:1;
  	unsigned int dma_doublebuf:1;
  	unsigned int ai_neverending:1;
  	unsigned int usedma:1;
  	unsigned int usemux:1;
  	unsigned char ai_ctrl;
  	unsigned char int_ctrl;
  	unsigned char ai_cfg;
  	unsigned int ai_do;		/* what do AI? 0=nothing, 1 to 4 mode */
  	unsigned int ai_n_realscanlen;	/*
  					 * what we must transfer for one
  					 * outgoing scan include front/back adds
  					 */
  	unsigned int ai_act_dmapos;	/* position in actual real stream */
  	unsigned int ai_add_front;	/*
  					 * how many channels we must add
  					 * before scan to satisfy S&H?
  					 */
  	unsigned int ai_add_back;	/*
  					 * how many channels we must add
  					 * before scan to satisfy DMA?
  					 */
  	unsigned int ai_flags;
  	char ai12_startstop;		/*
  					 * measure can start/stop
  					 * on external trigger
  					 */
  	unsigned int dma_actbuf;		/* which buffer is used now */
  	struct pci9118_dmabuf dmabuf[2];
  	int softsshdelay;		/*
  					 * >0 use software S&H,
  					 * numer is requested delay in ns
  					 */
  	unsigned char softsshsample;	/*
  					 * polarity of S&H signal
  					 * in sample state
  					 */
  	unsigned char softsshhold;	/*
  					 * polarity of S&H signal
  					 * in hold state
  					 */
  	unsigned int ai_ns_min;
  };
  
  static void pci9118_amcc_setup_dma(struct comedi_device *dev, unsigned int buf)
  {
  	struct pci9118_private *devpriv = dev->private;
  	struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[buf];
  
  	/* set the master write address and transfer count */
  	outl(dmabuf->hw, devpriv->iobase_a + AMCC_OP_REG_MWAR);
  	outl(dmabuf->use_size, devpriv->iobase_a + AMCC_OP_REG_MWTC);
  }
  
  static void pci9118_amcc_dma_ena(struct comedi_device *dev, bool enable)
  {
  	struct pci9118_private *devpriv = dev->private;
  	unsigned int mcsr;
  
  	mcsr = inl(devpriv->iobase_a + AMCC_OP_REG_MCSR);
  	if (enable)
  		mcsr |= RESET_A2P_FLAGS | A2P_HI_PRIORITY | EN_A2P_TRANSFERS;
  	else
  		mcsr &= ~EN_A2P_TRANSFERS;
  	outl(mcsr, devpriv->iobase_a + AMCC_OP_REG_MCSR);
  }
  
  static void pci9118_amcc_int_ena(struct comedi_device *dev, bool enable)
  {
  	struct pci9118_private *devpriv = dev->private;
  	unsigned int intcsr;
  
  	/* enable/disable interrupt for AMCC Incoming Mailbox 4 (32-bit) */
  	intcsr = inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR);
  	if (enable)
  		intcsr |= 0x1f00;
  	else
  		intcsr &= ~0x1f00;
  	outl(intcsr, devpriv->iobase_a + AMCC_OP_REG_INTCSR);
  }
  
  static void pci9118_ai_reset_fifo(struct comedi_device *dev)
  {
  	/* writing any value resets the A/D FIFO */
  	outl(0, dev->iobase + PCI9118_FIFO_RESET_REG);
  }
  
  static int pci9118_ai_check_chanlist(struct comedi_device *dev,
  				     struct comedi_subdevice *s,
  				     struct comedi_cmd *cmd)
  {
  	struct pci9118_private *devpriv = dev->private;
  	unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
  	unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
  	int i;
  
  	/* single channel scans are always ok */
  	if (cmd->chanlist_len == 1)
  		return 0;
  
  	for (i = 1; 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_err(dev->class_dev,
  				"Differential and single ended inputs can't be mixed!\n");
  			return -EINVAL;
  		}
  		if (comedi_range_is_bipolar(s, range) !=
  		    comedi_range_is_bipolar(s, range0)) {
  			dev_err(dev->class_dev,
  				"Bipolar and unipolar ranges can't be mixed!\n");
  			return -EINVAL;
  		}
  		if (!devpriv->usemux && aref == AREF_DIFF &&
  		    (chan >= (s->n_chan / 2))) {
  			dev_err(dev->class_dev,
  				"AREF_DIFF is only available for the first 8 channels!\n");
  			return -EINVAL;
  		}
  	}
  
  	return 0;
  }
  
  static void pci9118_set_chanlist(struct comedi_device *dev,
  				 struct comedi_subdevice *s,
  				 int n_chan, unsigned int *chanlist,
  				 int frontadd, int backadd)
  {
  	struct pci9118_private *devpriv = dev->private;
  	unsigned int chan0 = CR_CHAN(chanlist[0]);
  	unsigned int range0 = CR_RANGE(chanlist[0]);
  	unsigned int aref0 = CR_AREF(chanlist[0]);
  	unsigned int ssh = 0x00;
  	unsigned int val;
  	int i;
  
  	/*
  	 * Configure analog input based on the first chanlist entry.
  	 * All entries are either unipolar or bipolar and single-ended
  	 * or differential.
  	 */
  	devpriv->ai_ctrl = 0;
  	if (comedi_range_is_unipolar(s, range0))
  		devpriv->ai_ctrl |= PCI9118_AI_CTRL_UNIP;
  	if (aref0 == AREF_DIFF)
  		devpriv->ai_ctrl |= PCI9118_AI_CTRL_DIFF;
  	outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);
  
  	/* gods know why this sequence! */
  	outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
  	outl(0, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
  	outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
  
  	/* insert channels for S&H */
  	if (frontadd) {
  		val = PCI9118_AI_CHANLIST_CHAN(chan0) |
  		      PCI9118_AI_CHANLIST_RANGE(range0);
  		ssh = devpriv->softsshsample;
  		for (i = 0; i < frontadd; i++) {
  			outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG);
  			ssh = devpriv->softsshhold;
  		}
  	}
  
  	/* store chanlist */
  	for (i = 0; i < n_chan; i++) {
  		unsigned int chan = CR_CHAN(chanlist[i]);
  		unsigned int range = CR_RANGE(chanlist[i]);
  
  		val = PCI9118_AI_CHANLIST_CHAN(chan) |
  		      PCI9118_AI_CHANLIST_RANGE(range);
  		outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG);
  	}
  
  	/* insert channels to fit onto 32bit DMA */
  	if (backadd) {
  		val = PCI9118_AI_CHANLIST_CHAN(chan0) |
  		      PCI9118_AI_CHANLIST_RANGE(range0);
  		for (i = 0; i < backadd; i++)
  			outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG);
  	}
  	/* close scan queue */
  	outl(0, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
  	/* udelay(100); important delay, or first sample will be crippled */
  }
  
  static void pci9118_ai_mode4_switch(struct comedi_device *dev,
  				    unsigned int next_buf)
  {
  	struct pci9118_private *devpriv = dev->private;
  	struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[next_buf];
  
  	devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG |
  			  PCI9118_AI_CFG_AM;
  	outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
  	comedi_8254_load(dev->pacer, 0, dmabuf->hw >> 1,
  			 I8254_MODE0 | I8254_BINARY);
  	devpriv->ai_cfg |= PCI9118_AI_CFG_START;
  	outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
  }
  
  static unsigned int pci9118_ai_samples_ready(struct comedi_device *dev,
  					     struct comedi_subdevice *s,
  					     unsigned int n_raw_samples)
  {
  	struct pci9118_private *devpriv = dev->private;
  	struct comedi_cmd *cmd = &s->async->cmd;
  	unsigned int start_pos = devpriv->ai_add_front;
  	unsigned int stop_pos = start_pos + cmd->chanlist_len;
  	unsigned int span_len = stop_pos + devpriv->ai_add_back;
  	unsigned int dma_pos = devpriv->ai_act_dmapos;
  	unsigned int whole_spans, n_samples, x;
  
  	if (span_len == cmd->chanlist_len)
  		return n_raw_samples;	/* use all samples */
  
  	/*
  	 * Not all samples are to be used.  Buffer contents consist of a
  	 * possibly non-whole number of spans and a region of each span
  	 * is to be used.
  	 *
  	 * Account for samples in whole number of spans.
  	 */
  	whole_spans = n_raw_samples / span_len;
  	n_samples = whole_spans * cmd->chanlist_len;
  	n_raw_samples -= whole_spans * span_len;
  
  	/*
  	 * Deal with remaining samples which could overlap up to two spans.
  	 */
  	while (n_raw_samples) {
  		if (dma_pos < start_pos) {
  			/* Skip samples before start position. */
  			x = start_pos - dma_pos;
  			if (x > n_raw_samples)
  				x = n_raw_samples;
  			dma_pos += x;
  			n_raw_samples -= x;
  			if (!n_raw_samples)
  				break;
  		}
  		if (dma_pos < stop_pos) {
  			/* Include samples before stop position. */
  			x = stop_pos - dma_pos;
  			if (x > n_raw_samples)
  				x = n_raw_samples;
  			n_samples += x;
  			dma_pos += x;
  			n_raw_samples -= x;
  		}
  		/* Advance to next span. */
  		start_pos += span_len;
  		stop_pos += span_len;
  	}
  	return n_samples;
  }
  
  static void pci9118_ai_dma_xfer(struct comedi_device *dev,
  				struct comedi_subdevice *s,
  				unsigned short *dma_buffer,
  				unsigned int n_raw_samples)
  {
  	struct pci9118_private *devpriv = dev->private;
  	struct comedi_cmd *cmd = &s->async->cmd;
  	unsigned int start_pos = devpriv->ai_add_front;
  	unsigned int stop_pos = start_pos + cmd->chanlist_len;
  	unsigned int span_len = stop_pos + devpriv->ai_add_back;
  	unsigned int dma_pos = devpriv->ai_act_dmapos;
  	unsigned int x;
  
  	if (span_len == cmd->chanlist_len) {
  		/* All samples are to be copied. */
  		comedi_buf_write_samples(s, dma_buffer, n_raw_samples);
  		dma_pos += n_raw_samples;
  	} else {
  		/*
  		 * Not all samples are to be copied.  Buffer contents consist
  		 * of a possibly non-whole number of spans and a region of
  		 * each span is to be copied.
  		 */
  		while (n_raw_samples) {
  			if (dma_pos < start_pos) {
  				/* Skip samples before start position. */
  				x = start_pos - dma_pos;
  				if (x > n_raw_samples)
  					x = n_raw_samples;
  				dma_pos += x;
  				n_raw_samples -= x;
  				if (!n_raw_samples)
  					break;
  			}
  			if (dma_pos < stop_pos) {
  				/* Copy samples before stop position. */
  				x = stop_pos - dma_pos;
  				if (x > n_raw_samples)
  					x = n_raw_samples;
  				comedi_buf_write_samples(s, dma_buffer, x);
  				dma_pos += x;
  				n_raw_samples -= x;
  			}
  			/* Advance to next span. */
  			start_pos += span_len;
  			stop_pos += span_len;
  		}
  	}
  	/* Update position in span for next time. */
  	devpriv->ai_act_dmapos = dma_pos % span_len;
  }
  
  static void pci9118_exttrg_enable(struct comedi_device *dev, bool enable)
  {
  	struct pci9118_private *devpriv = dev->private;
  
  	if (enable)
  		devpriv->int_ctrl |= PCI9118_INT_CTRL_DTRG;
  	else
  		devpriv->int_ctrl &= ~PCI9118_INT_CTRL_DTRG;
  	outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG);
  
  	if (devpriv->int_ctrl)
  		pci9118_amcc_int_ena(dev, true);
  	else
  		pci9118_amcc_int_ena(dev, false);
  }
  
  static void pci9118_calc_divisors(struct comedi_device *dev,
  				  struct comedi_subdevice *s,
  				  unsigned int *tim1, unsigned int *tim2,
  				  unsigned int flags, int chans,
  				  unsigned int *div1, unsigned int *div2,
  				  unsigned int chnsshfront)
  {
  	struct comedi_8254 *pacer = dev->pacer;
  	struct comedi_cmd *cmd = &s->async->cmd;
  
  	*div1 = *tim2 / pacer->osc_base;	/* convert timer (burst) */
  	*div2 = *tim1 / pacer->osc_base;	/* scan timer */
  	*div2 = *div2 / *div1;			/* major timer is c1*c2 */
  	if (*div2 < chans)
  		*div2 = chans;
  
  	*tim2 = *div1 * pacer->osc_base;	/* real convert timer */
  
  	if (cmd->convert_src == TRIG_NOW && !chnsshfront) {
  		/* use BSSH signal */
  		if (*div2 < (chans + 2))
  			*div2 = chans + 2;
  	}
  
  	*tim1 = *div1 * *div2 * pacer->osc_base;
  }
  
  static void pci9118_start_pacer(struct comedi_device *dev, int mode)
  {
  	if (mode == 1 || mode == 2 || mode == 4)
  		comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
  }
  
  static int pci9118_ai_cancel(struct comedi_device *dev,
  			     struct comedi_subdevice *s)
  {
  	struct pci9118_private *devpriv = dev->private;
  
  	if (devpriv->usedma)
  		pci9118_amcc_dma_ena(dev, false);
  	pci9118_exttrg_enable(dev, false);
  	comedi_8254_pacer_enable(dev->pacer, 1, 2, false);
  	/* set default config (disable burst and triggers) */
  	devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
  	outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
  	/* reset acquisition control */
  	devpriv->ai_ctrl = 0;
  	outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);
  	outl(0, dev->iobase + PCI9118_AI_BURST_NUM_REG);
  	/* reset scan queue */
  	outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
  	outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
  	pci9118_ai_reset_fifo(dev);
  
  	devpriv->int_ctrl = 0;
  	outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG);
  	pci9118_amcc_int_ena(dev, false);
  
  	devpriv->ai_do = 0;
  	devpriv->usedma = 0;
  
  	devpriv->ai_act_dmapos = 0;
  	s->async->inttrig = NULL;
  	devpriv->ai_neverending = 0;
  	devpriv->dma_actbuf = 0;
  
  	return 0;
  }
  
  static void pci9118_ai_munge(struct comedi_device *dev,
  			     struct comedi_subdevice *s, void *data,
  			     unsigned int num_bytes,
  			     unsigned int start_chan_index)
  {
  	struct pci9118_private *devpriv = dev->private;
  	unsigned short *array = data;
  	unsigned int num_samples = comedi_bytes_to_samples(s, num_bytes);
  	unsigned int i;
  	__be16 *barray = data;
  
  	for (i = 0; i < num_samples; i++) {
  		if (devpriv->usedma)
  			array[i] = be16_to_cpu(barray[i]);
  		if (s->maxdata == 0xffff)
  			array[i] ^= 0x8000;
  		else
  			array[i] = (array[i] >> 4) & 0x0fff;
  	}
  }
  
  static void pci9118_ai_get_onesample(struct comedi_device *dev,
  				     struct comedi_subdevice *s)
  {
  	struct pci9118_private *devpriv = dev->private;
  	struct comedi_cmd *cmd = &s->async->cmd;
  	unsigned short sampl;
  
  	sampl = inl(dev->iobase + PCI9118_AI_FIFO_REG);
  
  	comedi_buf_write_samples(s, &sampl, 1);
  
  	if (!devpriv->ai_neverending) {
  		if (s->async->scans_done >= cmd->stop_arg)
  			s->async->events |= COMEDI_CB_EOA;
  	}
  }
  
  static void pci9118_ai_get_dma(struct comedi_device *dev,
  			       struct comedi_subdevice *s)
  {
  	struct pci9118_private *devpriv = dev->private;
  	struct comedi_cmd *cmd = &s->async->cmd;
  	struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[devpriv->dma_actbuf];
  	unsigned int n_all = comedi_bytes_to_samples(s, dmabuf->use_size);
  	unsigned int n_valid;
  	bool more_dma;
  
  	/* determine whether more DMA buffers to do after this one */
  	n_valid = pci9118_ai_samples_ready(dev, s, n_all);
  	more_dma = n_valid < comedi_nsamples_left(s, n_valid + 1);
  
  	/* switch DMA buffers and restart DMA if double buffering */
  	if (more_dma && devpriv->dma_doublebuf) {
  		devpriv->dma_actbuf = 1 - devpriv->dma_actbuf;
  		pci9118_amcc_setup_dma(dev, devpriv->dma_actbuf);
  		if (devpriv->ai_do == 4)
  			pci9118_ai_mode4_switch(dev, devpriv->dma_actbuf);
  	}
  
  	if (n_all)
  		pci9118_ai_dma_xfer(dev, s, dmabuf->virt, n_all);
  
  	if (!devpriv->ai_neverending) {
  		if (s->async->scans_done >= cmd->stop_arg)
  			s->async->events |= COMEDI_CB_EOA;
  	}
  
  	if (s->async->events & COMEDI_CB_CANCEL_MASK)
  		more_dma = false;
  
  	/* restart DMA if not double buffering */
  	if (more_dma && !devpriv->dma_doublebuf) {
  		pci9118_amcc_setup_dma(dev, 0);
  		if (devpriv->ai_do == 4)
  			pci9118_ai_mode4_switch(dev, 0);
  	}
  }
  
  static irqreturn_t pci9118_interrupt(int irq, void *d)
  {
  	struct comedi_device *dev = d;
  	struct comedi_subdevice *s = dev->read_subdev;
  	struct pci9118_private *devpriv = dev->private;
  	unsigned int intsrc;	/* IRQ reasons from card */
  	unsigned int intcsr;	/* INT register from AMCC chip */
  	unsigned int adstat;	/* STATUS register */
  
  	if (!dev->attached)
  		return IRQ_NONE;
  
  	intsrc = inl(dev->iobase + PCI9118_INT_CTRL_REG) & 0xf;
  	intcsr = inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR);
  
  	if (!intsrc && !(intcsr & ANY_S593X_INT))
  		return IRQ_NONE;
  
  	outl(intcsr | 0x00ff0000, devpriv->iobase_a + AMCC_OP_REG_INTCSR);
  
  	if (intcsr & MASTER_ABORT_INT) {
  		dev_err(dev->class_dev, "AMCC IRQ - MASTER DMA ABORT!\n");
  		s->async->events |= COMEDI_CB_ERROR;
  		goto interrupt_exit;
  	}
  
  	if (intcsr & TARGET_ABORT_INT) {
  		dev_err(dev->class_dev, "AMCC IRQ - TARGET DMA ABORT!\n");
  		s->async->events |= COMEDI_CB_ERROR;
  		goto interrupt_exit;
  	}
  
  	adstat = inl(dev->iobase + PCI9118_AI_STATUS_REG);
  	if ((adstat & PCI9118_AI_STATUS_NFULL) == 0) {
  		dev_err(dev->class_dev,
  			"A/D FIFO Full status (Fatal Error!)\n");
  		s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW;
  		goto interrupt_exit;
  	}
  	if (adstat & PCI9118_AI_STATUS_BOVER) {
  		dev_err(dev->class_dev,
  			"A/D Burst Mode Overrun Status (Fatal Error!)\n");
  		s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW;
  		goto interrupt_exit;
  	}
  	if (adstat & PCI9118_AI_STATUS_ADOS) {
  		dev_err(dev->class_dev, "A/D Over Speed Status (Warning!)\n");
  		s->async->events |= COMEDI_CB_ERROR;
  		goto interrupt_exit;
  	}
  	if (adstat & PCI9118_AI_STATUS_ADOR) {
  		dev_err(dev->class_dev, "A/D Overrun Status (Fatal Error!)\n");
  		s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW;
  		goto interrupt_exit;
  	}
  
  	if (!devpriv->ai_do)
  		return IRQ_HANDLED;
  
  	if (devpriv->ai12_startstop) {
  		if ((adstat & PCI9118_AI_STATUS_DTH) &&
  		    (intsrc & PCI9118_INT_CTRL_DTRG)) {
  			/* start/stop of measure */
  			if (devpriv->ai12_startstop & START_AI_EXT) {
  				/* deactivate EXT trigger */
  				devpriv->ai12_startstop &= ~START_AI_EXT;
  				if (!(devpriv->ai12_startstop & STOP_AI_EXT))
  					pci9118_exttrg_enable(dev, false);
  
  				/* start pacer */
  				pci9118_start_pacer(dev, devpriv->ai_do);
  				outl(devpriv->ai_ctrl,
  				     dev->iobase + PCI9118_AI_CTRL_REG);
  			} else if (devpriv->ai12_startstop & STOP_AI_EXT) {
  				/* deactivate EXT trigger */
  				devpriv->ai12_startstop &= ~STOP_AI_EXT;
  				pci9118_exttrg_enable(dev, false);
  
  				/* on next interrupt measure will stop */
  				devpriv->ai_neverending = 0;
  			}
  		}
  	}
  
  	if (devpriv->usedma)
  		pci9118_ai_get_dma(dev, s);
  	else
  		pci9118_ai_get_onesample(dev, s);
  
  interrupt_exit:
  	comedi_handle_events(dev, s);
  	return IRQ_HANDLED;
  }
  
  static void pci9118_ai_cmd_start(struct comedi_device *dev)
  {
  	struct pci9118_private *devpriv = dev->private;
  
  	outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG);
  	outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
  	if (devpriv->ai_do != 3) {
  		pci9118_start_pacer(dev, devpriv->ai_do);
  		devpriv->ai_ctrl |= PCI9118_AI_CTRL_SOFTG;
  	}
  	outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);
  }
  
  static int pci9118_ai_inttrig(struct comedi_device *dev,
  			      struct comedi_subdevice *s,
  			      unsigned int trig_num)
  {
  	struct comedi_cmd *cmd = &s->async->cmd;
  
  	if (trig_num != cmd->start_arg)
  		return -EINVAL;
  
  	s->async->inttrig = NULL;
  	pci9118_ai_cmd_start(dev);
  
  	return 1;
  }
  
  static int pci9118_ai_setup_dma(struct comedi_device *dev,
  				struct comedi_subdevice *s)
  {
  	struct pci9118_private *devpriv = dev->private;
  	struct comedi_cmd *cmd = &s->async->cmd;
  	struct pci9118_dmabuf *dmabuf0 = &devpriv->dmabuf[0];
  	struct pci9118_dmabuf *dmabuf1 = &devpriv->dmabuf[1];
  	unsigned int dmalen0 = dmabuf0->size;
  	unsigned int dmalen1 = dmabuf1->size;
  	unsigned int scan_bytes = devpriv->ai_n_realscanlen *
  				  comedi_bytes_per_sample(s);
  
  	/* isn't output buff smaller that our DMA buff? */
  	if (dmalen0 > s->async->prealloc_bufsz) {
  		/* align to 32bit down */
  		dmalen0 = s->async->prealloc_bufsz & ~3L;
  	}
  	if (dmalen1 > s->async->prealloc_bufsz) {
  		/* align to 32bit down */
  		dmalen1 = s->async->prealloc_bufsz & ~3L;
  	}
  
  	/* we want wake up every scan? */
  	if (devpriv->ai_flags & CMDF_WAKE_EOS) {
  		if (dmalen0 < scan_bytes) {
  			/* uff, too short DMA buffer, disable EOS support! */
  			devpriv->ai_flags &= (~CMDF_WAKE_EOS);
  			dev_info(dev->class_dev,
  				 "WAR: DMA0 buf too short, can't support CMDF_WAKE_EOS (%d<%d)\n",
  				  dmalen0, scan_bytes);
  		} else {
  			/* short first DMA buffer to one scan */
  			dmalen0 = scan_bytes;
  			if (dmalen0 < 4) {
  				dev_info(dev->class_dev,
  					 "ERR: DMA0 buf len bug? (%d<4)\n",
  					 dmalen0);
  				dmalen0 = 4;
  			}
  		}
  	}
  	if (devpriv->ai_flags & CMDF_WAKE_EOS) {
  		if (dmalen1 < scan_bytes) {
  			/* uff, too short DMA buffer, disable EOS support! */
  			devpriv->ai_flags &= (~CMDF_WAKE_EOS);
  			dev_info(dev->class_dev,
  				 "WAR: DMA1 buf too short, can't support CMDF_WAKE_EOS (%d<%d)\n",
  				 dmalen1, scan_bytes);
  		} else {
  			/* short second DMA buffer to one scan */
  			dmalen1 = scan_bytes;
  			if (dmalen1 < 4) {
  				dev_info(dev->class_dev,
  					 "ERR: DMA1 buf len bug? (%d<4)\n",
  					 dmalen1);
  				dmalen1 = 4;
  			}
  		}
  	}
  
  	/* transfer without CMDF_WAKE_EOS */
  	if (!(devpriv->ai_flags & CMDF_WAKE_EOS)) {
  		unsigned int tmp;
  
  		/* if it's possible then align DMA buffers to length of scan */
  		tmp = dmalen0;
  		dmalen0 = (dmalen0 / scan_bytes) * scan_bytes;
  		dmalen0 &= ~3L;
  		if (!dmalen0)
  			dmalen0 = tmp;	/* uff. very long scan? */
  		tmp = dmalen1;
  		dmalen1 = (dmalen1 / scan_bytes) * scan_bytes;
  		dmalen1 &= ~3L;
  		if (!dmalen1)
  			dmalen1 = tmp;	/* uff. very long scan? */
  		/*
  		 * if measure isn't neverending then test, if it fits whole
  		 * into one or two DMA buffers
  		 */
  		if (!devpriv->ai_neverending) {
  			unsigned long long scanlen;
  
  			scanlen = (unsigned long long)scan_bytes *
  				  cmd->stop_arg;
  
  			/* fits whole measure into one DMA buffer? */
  			if (dmalen0 > scanlen) {
  				dmalen0 = scanlen;
  				dmalen0 &= ~3L;
  			} else {
  				/* fits whole measure into two DMA buffer? */
  				if (dmalen1 > (scanlen - dmalen0)) {
  					dmalen1 = scanlen - dmalen0;
  					dmalen1 &= ~3L;
  				}
  			}
  		}
  	}
  
  	/* these DMA buffer size will be used */
  	devpriv->dma_actbuf = 0;
  	dmabuf0->use_size = dmalen0;
  	dmabuf1->use_size = dmalen1;
  
  	pci9118_amcc_dma_ena(dev, false);
  	pci9118_amcc_setup_dma(dev, 0);
  	/* init DMA transfer */
  	outl(0x00000000 | AINT_WRITE_COMPL,
  	     devpriv->iobase_a + AMCC_OP_REG_INTCSR);
  /* outl(0x02000000|AINT_WRITE_COMPL, devpriv->iobase_a+AMCC_OP_REG_INTCSR); */
  	pci9118_amcc_dma_ena(dev, true);
  	outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) | EN_A2P_TRANSFERS,
  	     devpriv->iobase_a + AMCC_OP_REG_INTCSR);
  						/* allow bus mastering */
  
  	return 0;
  }
  
  static int pci9118_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
  {
  	struct pci9118_private *devpriv = dev->private;
  	struct comedi_8254 *pacer = dev->pacer;
  	struct comedi_cmd *cmd = &s->async->cmd;
  	unsigned int addchans = 0;
  	unsigned int scanlen;
  
  	devpriv->ai12_startstop = 0;
  	devpriv->ai_flags = cmd->flags;
  	devpriv->ai_add_front = 0;
  	devpriv->ai_add_back = 0;
  
  	/* prepare for start/stop conditions */
  	if (cmd->start_src == TRIG_EXT)
  		devpriv->ai12_startstop |= START_AI_EXT;
  	if (cmd->stop_src == TRIG_EXT) {
  		devpriv->ai_neverending = 1;
  		devpriv->ai12_startstop |= STOP_AI_EXT;
  	}
  	if (cmd->stop_src == TRIG_NONE)
  		devpriv->ai_neverending = 1;
  	if (cmd->stop_src == TRIG_COUNT)
  		devpriv->ai_neverending = 0;
  
  	/*
  	 * use additional sample at end of every scan
  	 * to satisty DMA 32 bit transfer?
  	 */
  	devpriv->ai_add_front = 0;
  	devpriv->ai_add_back = 0;
  	if (devpriv->master) {
  		devpriv->usedma = 1;
  		if ((cmd->flags & CMDF_WAKE_EOS) &&
  		    (cmd->scan_end_arg == 1)) {
  			if (cmd->convert_src == TRIG_NOW)
  				devpriv->ai_add_back = 1;
  			if (cmd->convert_src == TRIG_TIMER) {
  				devpriv->usedma = 0;
  					/*
  					 * use INT transfer if scanlist
  					 * have only one channel
  					 */
  			}
  		}
  		if ((cmd->flags & CMDF_WAKE_EOS) &&
  		    (cmd->scan_end_arg & 1) &&
  		    (cmd->scan_end_arg > 1)) {
  			if (cmd->scan_begin_src == TRIG_FOLLOW) {
  				devpriv->usedma = 0;
  				/*
  				 * XXX maybe can be corrected to use 16 bit DMA
  				 */
  			} else {	/*
  					 * well, we must insert one sample
  					 * to end of EOS to meet 32 bit transfer
  					 */
  				devpriv->ai_add_back = 1;
  			}
  		}
  	} else {	/* interrupt transfer don't need any correction */
  		devpriv->usedma = 0;
  	}
  
  	/*
  	 * we need software S&H signal?
  	 * It adds two samples before every scan as minimum
  	 */
  	if (cmd->convert_src == TRIG_NOW && devpriv->softsshdelay) {
  		devpriv->ai_add_front = 2;
  		if ((devpriv->usedma == 1) && (devpriv->ai_add_back == 1)) {
  							/* move it to front */
  			devpriv->ai_add_front++;
  			devpriv->ai_add_back = 0;
  		}
  		if (cmd->convert_arg < devpriv->ai_ns_min)
  			cmd->convert_arg = devpriv->ai_ns_min;
  		addchans = devpriv->softsshdelay / cmd->convert_arg;
  		if (devpriv->softsshdelay % cmd->convert_arg)
  			addchans++;
  		if (addchans > (devpriv->ai_add_front - 1)) {
  							/* uff, still short */
  			devpriv->ai_add_front = addchans + 1;
  			if (devpriv->usedma == 1)
  				if ((devpriv->ai_add_front +
  				     cmd->chanlist_len +
  				     devpriv->ai_add_back) & 1)
  					devpriv->ai_add_front++;
  							/* round up to 32 bit */
  		}
  	}
  	/* well, we now know what must be all added */
  	scanlen = devpriv->ai_add_front + cmd->chanlist_len +
  		  devpriv->ai_add_back;
  	/*
  	 * what we must take from card in real to have cmd->scan_end_arg
  	 * on output?
  	 */
  	devpriv->ai_n_realscanlen = scanlen *
  				    (cmd->scan_end_arg / cmd->chanlist_len);
  
  	if (scanlen > s->len_chanlist) {
  		dev_err(dev->class_dev,
  			"range/channel list is too long for actual configuration!\n");
  		return -EINVAL;
  	}
  
  	/*
  	 * Configure analog input and load the chanlist.
  	 * The acquisition control bits are enabled later.
  	 */
  	pci9118_set_chanlist(dev, s, cmd->chanlist_len, cmd->chanlist,
  			     devpriv->ai_add_front, devpriv->ai_add_back);
  
  	/* Determine acquisition mode and calculate timing */
  	devpriv->ai_do = 0;
  	if (cmd->scan_begin_src != TRIG_TIMER &&
  	    cmd->convert_src == TRIG_TIMER) {
  		/* cascaded timers 1 and 2 are used for convert timing */
  		if (cmd->scan_begin_src == TRIG_EXT)
  			devpriv->ai_do = 4;
  		else
  			devpriv->ai_do = 1;
  
  		comedi_8254_cascade_ns_to_timer(pacer, &cmd->convert_arg,
  						devpriv->ai_flags &
  						CMDF_ROUND_NEAREST);
  		comedi_8254_update_divisors(pacer);
  
  		devpriv->ai_ctrl |= PCI9118_AI_CTRL_TMRTR;
  
  		if (!devpriv->usedma) {
  			devpriv->ai_ctrl |= PCI9118_AI_CTRL_INT;
  			devpriv->int_ctrl |= PCI9118_INT_CTRL_TIMER;
  		}
  
  		if (cmd->scan_begin_src == TRIG_EXT) {
  			struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[0];
  
  			devpriv->ai_cfg |= PCI9118_AI_CFG_AM;
  			outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
  			comedi_8254_load(pacer, 0, dmabuf->hw >> 1,
  					 I8254_MODE0 | I8254_BINARY);
  			devpriv->ai_cfg |= PCI9118_AI_CFG_START;
  		}
  	}
  
  	if (cmd->scan_begin_src == TRIG_TIMER &&
  	    cmd->convert_src != TRIG_EXT) {
  		if (!devpriv->usedma) {
  			dev_err(dev->class_dev,
  				"cmd->scan_begin_src=TRIG_TIMER works only with bus mastering!\n");
  			return -EIO;
  		}
  
  		/* double timed action */
  		devpriv->ai_do = 2;
  
  		pci9118_calc_divisors(dev, s,
  				      &cmd->scan_begin_arg, &cmd->convert_arg,
  				      devpriv->ai_flags,
  				      devpriv->ai_n_realscanlen,
  				      &pacer->divisor1,
  				      &pacer->divisor2,
  				      devpriv->ai_add_front);
  
  		devpriv->ai_ctrl |= PCI9118_AI_CTRL_TMRTR;
  		devpriv->ai_cfg |= PCI9118_AI_CFG_BM | PCI9118_AI_CFG_BS;
  		if (cmd->convert_src == TRIG_NOW && !devpriv->softsshdelay)
  			devpriv->ai_cfg |= PCI9118_AI_CFG_BSSH;
  		outl(devpriv->ai_n_realscanlen,
  		     dev->iobase + PCI9118_AI_BURST_NUM_REG);
  	}
  
  	if (cmd->scan_begin_src == TRIG_FOLLOW &&
  	    cmd->convert_src == TRIG_EXT) {
  		/* external trigger conversion */
  		devpriv->ai_do = 3;
  
  		devpriv->ai_ctrl |= PCI9118_AI_CTRL_EXTM;
  	}
  
  	if (devpriv->ai_do == 0) {
  		dev_err(dev->class_dev,
  			"Unable to determine acquisition mode! BUG in (*do_cmdtest)?\n");
  		return -EINVAL;
  	}
  
  	if (devpriv->usedma)
  		devpriv->ai_ctrl |= PCI9118_AI_CTRL_DMA;
  
  	/* set default config (disable burst and triggers) */
  	devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
  	outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
  	udelay(1);
  	pci9118_ai_reset_fifo(dev);
  
  	/* clear A/D and INT status registers */
  	inl(dev->iobase + PCI9118_AI_STATUS_REG);
  	inl(dev->iobase + PCI9118_INT_CTRL_REG);
  
  	devpriv->ai_act_dmapos = 0;
  
  	if (devpriv->usedma) {
  		pci9118_ai_setup_dma(dev, s);
  
  		outl(0x02000000 | AINT_WRITE_COMPL,
  		     devpriv->iobase_a + AMCC_OP_REG_INTCSR);
  	} else {
  		pci9118_amcc_int_ena(dev, true);
  	}
  
  	/* start async command now or wait for internal trigger */
  	if (cmd->start_src == TRIG_NOW)
  		pci9118_ai_cmd_start(dev);
  	else if (cmd->start_src == TRIG_INT)
  		s->async->inttrig = pci9118_ai_inttrig;
  
  	/* enable external trigger for command start/stop */
  	if (cmd->start_src == TRIG_EXT || cmd->stop_src == TRIG_EXT)
  		pci9118_exttrg_enable(dev, true);
  
  	return 0;
  }
  
  static int pci9118_ai_cmdtest(struct comedi_device *dev,
  			      struct comedi_subdevice *s,
  			      struct comedi_cmd *cmd)
  {
  	struct pci9118_private *devpriv = dev->private;
  	int err = 0;
  	unsigned int flags;
  	unsigned int arg;
  
  	/* Step 1 : check if triggers are trivially valid */
  
  	err |= comedi_check_trigger_src(&cmd->start_src,
  					TRIG_NOW | TRIG_EXT | TRIG_INT);
  
  	flags = TRIG_FOLLOW;
  	if (devpriv->master)
  		flags |= TRIG_TIMER | TRIG_EXT;
  	err |= comedi_check_trigger_src(&cmd->scan_begin_src, flags);
  
  	flags = TRIG_TIMER | TRIG_EXT;
  	if (devpriv->master)
  		flags |= TRIG_NOW;
  	err |= comedi_check_trigger_src(&cmd->convert_src, flags);
  
  	err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
  	err |= comedi_check_trigger_src(&cmd->stop_src,
  					TRIG_COUNT | TRIG_NONE | TRIG_EXT);
  
  	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->start_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT)
  		err |= -EINVAL;
  
  	if ((cmd->scan_begin_src & (TRIG_TIMER | TRIG_EXT)) &&
  	    (!(cmd->convert_src & (TRIG_TIMER | TRIG_NOW))))
  		err |= -EINVAL;
  
  	if ((cmd->scan_begin_src == TRIG_FOLLOW) &&
  	    (!(cmd->convert_src & (TRIG_TIMER | TRIG_EXT))))
  		err |= -EINVAL;
  
  	if (cmd->stop_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT)
  		err |= -EINVAL;
  
  	if (err)
  		return 2;
  
  	/* Step 3: check if arguments are trivially valid */
  
  	switch (cmd->start_src) {
  	case TRIG_NOW:
  	case TRIG_EXT:
  		err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
  		break;
  	case TRIG_INT:
  		/* start_arg is the internal trigger (any value) */
  		break;
  	}
  
  	if (cmd->scan_begin_src & (TRIG_FOLLOW | TRIG_EXT))
  		err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
  
  	if ((cmd->scan_begin_src == TRIG_TIMER) &&
  	    (cmd->convert_src == TRIG_TIMER) && (cmd->scan_end_arg == 1)) {
  		cmd->scan_begin_src = TRIG_FOLLOW;
  		cmd->convert_arg = cmd->scan_begin_arg;
  		cmd->scan_begin_arg = 0;
  	}
  
  	if (cmd->scan_begin_src == TRIG_TIMER) {
  		err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
  						    devpriv->ai_ns_min);
  	}
  
  	if (cmd->scan_begin_src == TRIG_EXT) {
  		if (cmd->scan_begin_arg) {
  			cmd->scan_begin_arg = 0;
  			err |= -EINVAL;
  			err |= comedi_check_trigger_arg_max(&cmd->scan_end_arg,
  							    65535);
  		}
  	}
  
  	if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) {
  		err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
  						    devpriv->ai_ns_min);
  	}
  
  	if (cmd->convert_src == TRIG_EXT)
  		err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
  
  	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);
  
  	err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
  
  	err |= comedi_check_trigger_arg_min(&cmd->scan_end_arg,
  					    cmd->chanlist_len);
  
  	if ((cmd->scan_end_arg % cmd->chanlist_len)) {
  		cmd->scan_end_arg =
  		    cmd->chanlist_len * (cmd->scan_end_arg / cmd->chanlist_len);
  		err |= -EINVAL;
  	}
  
  	if (err)
  		return 3;
  
  	/* step 4: fix up any arguments */
  
  	if (cmd->scan_begin_src == TRIG_TIMER) {
  		arg = cmd->scan_begin_arg;
  		comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
  		err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
  	}
  
  	if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) {
  		arg = cmd->convert_arg;
  		comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
  		err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
  
  		if (cmd->scan_begin_src == TRIG_TIMER &&
  		    cmd->convert_src == TRIG_NOW) {
  			if (cmd->convert_arg == 0) {
  				arg = devpriv->ai_ns_min *
  				      (cmd->scan_end_arg + 2);
  			} else {
  				arg = cmd->convert_arg * cmd->chanlist_len;
  			}
  			err |= comedi_check_trigger_arg_min(
  				&cmd->scan_begin_arg, arg);
  		}
  	}
  
  	if (err)
  		return 4;
  
  	/* Step 5: check channel list if it exists */
  
  	if (cmd->chanlist)
  		err |= pci9118_ai_check_chanlist(dev, s, cmd);
  
  	if (err)
  		return 5;
  
  	return 0;
  }
  
  static int pci9118_ai_eoc(struct comedi_device *dev,
  			  struct comedi_subdevice *s,
  			  struct comedi_insn *insn,
  			  unsigned long context)
  {
  	unsigned int status;
  
  	status = inl(dev->iobase + PCI9118_AI_STATUS_REG);
  	if (status & PCI9118_AI_STATUS_ADRDY)
  		return 0;
  	return -EBUSY;
  }
  
  static void pci9118_ai_start_conv(struct comedi_device *dev)
  {
  	/* writing any value triggers an A/D conversion */
  	outl(0, dev->iobase + PCI9118_SOFTTRG_REG);
  }
  
  static int pci9118_ai_insn_read(struct comedi_device *dev,
  				struct comedi_subdevice *s,
  				struct comedi_insn *insn,
  				unsigned int *data)
  {
  	struct pci9118_private *devpriv = dev->private;
  	unsigned int val;
  	int ret;
  	int i;
  
         /*
  	* Configure analog input based on the chanspec.
  	* Acqusition is software controlled without interrupts.
  	*/
  	pci9118_set_chanlist(dev, s, 1, &insn->chanspec, 0, 0);
  
  	/* set default config (disable burst and triggers) */
  	devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
  	outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
  
  	pci9118_ai_reset_fifo(dev);
  
  	for (i = 0; i < insn->n; i++) {
  		pci9118_ai_start_conv(dev);
  
  		ret = comedi_timeout(dev, s, insn, pci9118_ai_eoc, 0);
  		if (ret)
  			return ret;
  
  		val = inl(dev->iobase + PCI9118_AI_FIFO_REG);
  		if (s->maxdata == 0xffff)
  			data[i] = (val & 0xffff) ^ 0x8000;
  		else
  			data[i] = (val >> 4) & 0xfff;
  	}
  
  	return insn->n;
  }
  
  static int pci9118_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 val = s->readback[chan];
  	int i;
  
  	for (i = 0; i < insn->n; i++) {
  		val = data[i];
  		outl(val, dev->iobase + PCI9118_AO_REG(chan));
  	}
  	s->readback[chan] = val;
  
  	return insn->n;
  }
  
  static int pci9118_di_insn_bits(struct comedi_device *dev,
  				struct comedi_subdevice *s,
  				struct comedi_insn *insn,
  				unsigned int *data)
  {
  	/*
  	 * The digital inputs and outputs share the read register.
  	 * bits [7:4] are the digital outputs
  	 * bits [3:0] are the digital inputs
  	 */
  	data[1] = inl(dev->iobase + PCI9118_DIO_REG) & 0xf;
  
  	return insn->n;
  }
  
  static int pci9118_do_insn_bits(struct comedi_device *dev,
  				struct comedi_subdevice *s,
  				struct comedi_insn *insn,
  				unsigned int *data)
  {
  	/*
  	 * The digital outputs are set with the same register that
  	 * the digital inputs and outputs are read from. But the
  	 * outputs are set with bits [3:0] so we can simply write
  	 * the s->state to set them.
  	 */
  	if (comedi_dio_update_state(s, data))
  		outl(s->state, dev->iobase + PCI9118_DIO_REG);
  
  	data[1] = s->state;
  
  	return insn->n;
  }
  
  static void pci9118_reset(struct comedi_device *dev)
  {
  	/* reset analog input subsystem */
  	outl(0, dev->iobase + PCI9118_INT_CTRL_REG);
  	outl(0, dev->iobase + PCI9118_AI_CTRL_REG);
  	outl(0, dev->iobase + PCI9118_AI_CFG_REG);
  	pci9118_ai_reset_fifo(dev);
  
  	/* clear any pending interrupts and status */
  	inl(dev->iobase + PCI9118_INT_CTRL_REG);
  	inl(dev->iobase + PCI9118_AI_STATUS_REG);
  
  	/* reset DMA and scan queue */
  	outl(0, dev->iobase + PCI9118_AI_BURST_NUM_REG);
  	outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
  	outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
  
  	/* reset analog outputs to 0V */
  	outl(2047, dev->iobase + PCI9118_AO_REG(0));
  	outl(2047, dev->iobase + PCI9118_AO_REG(1));
  }
  
  static struct pci_dev *pci9118_find_pci(struct comedi_device *dev,
  					struct comedi_devconfig *it)
  {
  	struct pci_dev *pcidev = NULL;
  	int bus = it->options[0];
  	int slot = it->options[1];
  
  	for_each_pci_dev(pcidev) {
  		if (pcidev->vendor != PCI_VENDOR_ID_AMCC)
  			continue;
  		if (pcidev->device != 0x80d9)
  			continue;
  		if (bus || slot) {
  			/* requested particular bus/slot */
  			if (pcidev->bus->number != bus ||
  			    PCI_SLOT(pcidev->devfn) != slot)
  				continue;
  		}
  		return pcidev;
  	}
  	dev_err(dev->class_dev,
  		"no supported board found! (req. bus/slot : %d/%d)\n",
  		bus, slot);
  	return NULL;
  }
  
  static void pci9118_alloc_dma(struct comedi_device *dev)
  {
  	struct pci9118_private *devpriv = dev->private;
  	struct pci9118_dmabuf *dmabuf;
  	int order;
  	int i;
  
  	for (i = 0; i < 2; i++) {
  		dmabuf = &devpriv->dmabuf[i];
  		for (order = 2; order >= 0; order--) {
  			dmabuf->virt =
  			    dma_alloc_coherent(dev->hw_dev, PAGE_SIZE << order,
  					       &dmabuf->hw, GFP_KERNEL);
  			if (dmabuf->virt)
  				break;
  		}
  		if (!dmabuf->virt)
  			break;
  		dmabuf->size = PAGE_SIZE << order;
  
  		if (i == 0)
  			devpriv->master = 1;
  		if (i == 1)
  			devpriv->dma_doublebuf = 1;
  	}
  }
  
  static void pci9118_free_dma(struct comedi_device *dev)
  {
  	struct pci9118_private *devpriv = dev->private;
  	struct pci9118_dmabuf *dmabuf;
  	int i;
  
  	if (!devpriv)
  		return;
  
  	for (i = 0; i < 2; i++) {
  		dmabuf = &devpriv->dmabuf[i];
  		if (dmabuf->virt) {
  			dma_free_coherent(dev->hw_dev, dmabuf->size,
  					  dmabuf->virt, dmabuf->hw);
  		}
  	}
  }
  
  static int pci9118_common_attach(struct comedi_device *dev,
  				 int ext_mux, int softsshdelay)
  {
  	const struct pci9118_boardinfo *board = dev->board_ptr;
  	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
  	struct pci9118_private *devpriv;
  	struct comedi_subdevice *s;
  	int ret;
  	int i;
  	u16 u16w;
  
  	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
  	if (!devpriv)
  		return -ENOMEM;
  
  	ret = comedi_pci_enable(dev);
  	if (ret)
  		return ret;
  	pci_set_master(pcidev);
  
  	devpriv->iobase_a = pci_resource_start(pcidev, 0);
  	dev->iobase = pci_resource_start(pcidev, 2);
  
  	dev->pacer = comedi_8254_init(dev->iobase + PCI9118_TIMER_BASE,
  				      I8254_OSC_BASE_4MHZ, I8254_IO32, 0);
  	if (!dev->pacer)
  		return -ENOMEM;
  
  	pci9118_reset(dev);
  
  	if (pcidev->irq) {
  		ret = request_irq(pcidev->irq, pci9118_interrupt, IRQF_SHARED,
  				  dev->board_name, dev);
  		if (ret == 0) {
  			dev->irq = pcidev->irq;
  
  			pci9118_alloc_dma(dev);
  		}
  	}
  
  	if (ext_mux > 0) {
  		if (ext_mux > 256)
  			ext_mux = 256;	/* max 256 channels! */
  		if (softsshdelay > 0)
  			if (ext_mux > 128)
  				ext_mux = 128;
  		devpriv->usemux = 1;
  	} else {
  		devpriv->usemux = 0;
  	}
  
  	if (softsshdelay < 0) {
  		/* select sample&hold signal polarity */
  		devpriv->softsshdelay = -softsshdelay;
  		devpriv->softsshsample = 0x80;
  		devpriv->softsshhold = 0x00;
  	} else {
  		devpriv->softsshdelay = softsshdelay;
  		devpriv->softsshsample = 0x00;
  		devpriv->softsshhold = 0x80;
  	}
  
  	pci_read_config_word(pcidev, PCI_COMMAND, &u16w);
  	pci_write_config_word(pcidev, PCI_COMMAND, u16w | 64);
  				/* Enable parity check for parity error */
  
  	ret = comedi_alloc_subdevices(dev, 4);
  	if (ret)
  		return ret;
  
  	/* Analog Input subdevice */
  	s = &dev->subdevices[0];
  	s->type		= COMEDI_SUBD_AI;
  	s->subdev_flags	= SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
  	s->n_chan	= (devpriv->usemux) ? ext_mux : 16;
  	s->maxdata	= board->ai_is_16bit ? 0xffff : 0x0fff;
  	s->range_table	= board->is_hg ? &pci9118hg_ai_range
  				       : &pci9118_ai_range;
  	s->insn_read	= pci9118_ai_insn_read;
  	if (dev->irq) {
  		dev->read_subdev = s;
  		s->subdev_flags	|= SDF_CMD_READ;
  		s->len_chanlist	= 255;
  		s->do_cmdtest	= pci9118_ai_cmdtest;
  		s->do_cmd	= pci9118_ai_cmd;
  		s->cancel	= pci9118_ai_cancel;
  		s->munge	= pci9118_ai_munge;
  	}
  
  	if (s->maxdata == 0xffff) {
  		/*
  		 * 16-bit samples are from an ADS7805 A/D converter.
  		 * Minimum sampling rate is 10us.
  		 */
  		devpriv->ai_ns_min = 10000;
  	} else {
  		/*
  		 * 12-bit samples are from an ADS7800 A/D converter.
  		 * Minimum sampling rate is 3us.
  		 */
  		devpriv->ai_ns_min = 3000;
  	}
  
  	/* Analog Output subdevice */
  	s = &dev->subdevices[1];
  	s->type		= COMEDI_SUBD_AO;
  	s->subdev_flags	= SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
  	s->n_chan	= 2;
  	s->maxdata	= 0x0fff;
  	s->range_table	= &range_bipolar10;
  	s->insn_write	= pci9118_ao_insn_write;
  
  	ret = comedi_alloc_subdev_readback(s);
  	if (ret)
  		return ret;
  
  	/* the analog outputs were reset to 0V, make the readback match */
  	for (i = 0; i < s->n_chan; i++)
  		s->readback[i] = 2047;
  
  	/* Digital Input subdevice */
  	s = &dev->subdevices[2];
  	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	= pci9118_di_insn_bits;
  
  	/* Digital Output subdevice */
  	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	= pci9118_do_insn_bits;
  
  	/* get the current state of the digital outputs */
  	s->state = inl(dev->iobase + PCI9118_DIO_REG) >> 4;
  
  	return 0;
  }
  
  static int pci9118_attach(struct comedi_device *dev,
  			  struct comedi_devconfig *it)
  {
  	struct pci_dev *pcidev;
  	int ext_mux, softsshdelay;
  
  	ext_mux = it->options[2];
  	softsshdelay = it->options[4];
  
  	pcidev = pci9118_find_pci(dev, it);
  	if (!pcidev)
  		return -EIO;
  	comedi_set_hw_dev(dev, &pcidev->dev);
  
  	return pci9118_common_attach(dev, ext_mux, softsshdelay);
  }
  
  static int pci9118_auto_attach(struct comedi_device *dev,
  			       unsigned long context)
  {
  	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
  	const struct pci9118_boardinfo *board = NULL;
  
  	if (context < ARRAY_SIZE(pci9118_boards))
  		board = &pci9118_boards[context];
  	if (!board)
  		return -ENODEV;
  	dev->board_ptr = board;
  	dev->board_name = board->name;
  
  	/*
  	 * Need to 'get' the PCI device to match the 'put' in pci9118_detach().
  	 * (The 'put' also matches the implicit 'get' by pci9118_find_pci().)
  	 */
  	pci_dev_get(pcidev);
  	/* no external mux, no sample-hold delay */
  	return pci9118_common_attach(dev, 0, 0);
  }
  
  static void pci9118_detach(struct comedi_device *dev)
  {
  	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
  
  	if (dev->iobase)
  		pci9118_reset(dev);
  	comedi_pci_detach(dev);
  	pci9118_free_dma(dev);
  	pci_dev_put(pcidev);
  }
  
  static struct comedi_driver adl_pci9118_driver = {
  	.driver_name	= "adl_pci9118",
  	.module		= THIS_MODULE,
  	.attach		= pci9118_attach,
  	.auto_attach	= pci9118_auto_attach,
  	.detach		= pci9118_detach,
  	.num_names	= ARRAY_SIZE(pci9118_boards),
  	.board_name	= &pci9118_boards[0].name,
  	.offset		= sizeof(struct pci9118_boardinfo),
  };
  
  static int adl_pci9118_pci_probe(struct pci_dev *dev,
  				 const struct pci_device_id *id)
  {
  	return comedi_pci_auto_config(dev, &adl_pci9118_driver,
  				      id->driver_data);
  }
  
  /* FIXME: All the supported board types have the same device ID! */
  static const struct pci_device_id adl_pci9118_pci_table[] = {
  	{ PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118DG },
  /*	{ PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118HG }, */
  /*	{ PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118HR }, */
  	{ 0 }
  };
  MODULE_DEVICE_TABLE(pci, adl_pci9118_pci_table);
  
  static struct pci_driver adl_pci9118_pci_driver = {
  	.name		= "adl_pci9118",
  	.id_table	= adl_pci9118_pci_table,
  	.probe		= adl_pci9118_pci_probe,
  	.remove		= comedi_pci_auto_unconfig,
  };
  module_comedi_pci_driver(adl_pci9118_driver, adl_pci9118_pci_driver);
  
  MODULE_AUTHOR("Comedi https://www.comedi.org");
  MODULE_DESCRIPTION("Comedi low-level driver");
  MODULE_LICENSE("GPL");
  