162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * adv_pci1710.c
462306a36Sopenharmony_ci * Comedi driver for Advantech PCI-1710 series boards
562306a36Sopenharmony_ci * Author: Michal Dobes <dobes@tesnet.cz>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Thanks to ZhenGang Shang <ZhenGang.Shang@Advantech.com.cn>
862306a36Sopenharmony_ci * for testing and information.
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci/*
1262306a36Sopenharmony_ci * Driver: adv_pci1710
1362306a36Sopenharmony_ci * Description: Comedi driver for Advantech PCI-1710 series boards
1462306a36Sopenharmony_ci * Devices: [Advantech] PCI-1710 (adv_pci1710), PCI-1710HG, PCI-1711,
1562306a36Sopenharmony_ci *   PCI-1713, PCI-1731
1662306a36Sopenharmony_ci * Author: Michal Dobes <dobes@tesnet.cz>
1762306a36Sopenharmony_ci * Updated: Fri, 29 Oct 2015 17:19:35 -0700
1862306a36Sopenharmony_ci * Status: works
1962306a36Sopenharmony_ci *
2062306a36Sopenharmony_ci * Configuration options: not applicable, uses PCI auto config
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci * This driver supports AI, AO, DI and DO subdevices.
2362306a36Sopenharmony_ci * AI subdevice supports cmd and insn interface,
2462306a36Sopenharmony_ci * other subdevices support only insn interface.
2562306a36Sopenharmony_ci *
2662306a36Sopenharmony_ci * The PCI-1710 and PCI-1710HG have the same PCI device ID, so the
2762306a36Sopenharmony_ci * driver cannot distinguish between them, as would be normal for a
2862306a36Sopenharmony_ci * PCI driver.
2962306a36Sopenharmony_ci */
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#include <linux/module.h>
3262306a36Sopenharmony_ci#include <linux/interrupt.h>
3362306a36Sopenharmony_ci#include <linux/comedi/comedi_pci.h>
3462306a36Sopenharmony_ci#include <linux/comedi/comedi_8254.h>
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#include "amcc_s5933.h"
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/*
3962306a36Sopenharmony_ci * PCI BAR2 Register map (dev->iobase)
4062306a36Sopenharmony_ci */
4162306a36Sopenharmony_ci#define PCI171X_AD_DATA_REG	0x00	/* R:   A/D data */
4262306a36Sopenharmony_ci#define PCI171X_SOFTTRG_REG	0x00	/* W:   soft trigger for A/D */
4362306a36Sopenharmony_ci#define PCI171X_RANGE_REG	0x02	/* W:   A/D gain/range register */
4462306a36Sopenharmony_ci#define PCI171X_RANGE_DIFF	BIT(5)
4562306a36Sopenharmony_ci#define PCI171X_RANGE_UNI	BIT(4)
4662306a36Sopenharmony_ci#define PCI171X_RANGE_GAIN(x)	(((x) & 0x7) << 0)
4762306a36Sopenharmony_ci#define PCI171X_MUX_REG		0x04	/* W:   A/D multiplexor control */
4862306a36Sopenharmony_ci#define PCI171X_MUX_CHANH(x)	(((x) & 0xff) << 8)
4962306a36Sopenharmony_ci#define PCI171X_MUX_CHANL(x)	(((x) & 0xff) << 0)
5062306a36Sopenharmony_ci#define PCI171X_MUX_CHAN(x)	(PCI171X_MUX_CHANH(x) | PCI171X_MUX_CHANL(x))
5162306a36Sopenharmony_ci#define PCI171X_STATUS_REG	0x06	/* R:   status register */
5262306a36Sopenharmony_ci#define PCI171X_STATUS_IRQ	BIT(11)	/* 1=IRQ occurred */
5362306a36Sopenharmony_ci#define PCI171X_STATUS_FF	BIT(10)	/* 1=FIFO is full, fatal error */
5462306a36Sopenharmony_ci#define PCI171X_STATUS_FH	BIT(9)	/* 1=FIFO is half full */
5562306a36Sopenharmony_ci#define PCI171X_STATUS_FE	BIT(8)	/* 1=FIFO is empty */
5662306a36Sopenharmony_ci#define PCI171X_CTRL_REG	0x06	/* W:   control register */
5762306a36Sopenharmony_ci#define PCI171X_CTRL_CNT0	BIT(6)	/* 1=ext. clk, 0=int. 100kHz clk */
5862306a36Sopenharmony_ci#define PCI171X_CTRL_ONEFH	BIT(5)	/* 1=on FIFO half full, 0=on sample */
5962306a36Sopenharmony_ci#define PCI171X_CTRL_IRQEN	BIT(4)	/* 1=enable IRQ */
6062306a36Sopenharmony_ci#define PCI171X_CTRL_GATE	BIT(3)	/* 1=enable ext. trigger GATE (8254?) */
6162306a36Sopenharmony_ci#define PCI171X_CTRL_EXT	BIT(2)	/* 1=enable ext. trigger source */
6262306a36Sopenharmony_ci#define PCI171X_CTRL_PACER	BIT(1)	/* 1=enable int. 8254 trigger source */
6362306a36Sopenharmony_ci#define PCI171X_CTRL_SW		BIT(0)	/* 1=enable software trigger source */
6462306a36Sopenharmony_ci#define PCI171X_CLRINT_REG	0x08	/* W:   clear interrupts request */
6562306a36Sopenharmony_ci#define PCI171X_CLRFIFO_REG	0x09	/* W:   clear FIFO */
6662306a36Sopenharmony_ci#define PCI171X_DA_REG(x)	(0x0a + ((x) * 2)) /* W:   D/A register */
6762306a36Sopenharmony_ci#define PCI171X_DAREF_REG	0x0e	/* W:   D/A reference control */
6862306a36Sopenharmony_ci#define PCI171X_DAREF(c, r)	(((r) & 0x3) << ((c) * 2))
6962306a36Sopenharmony_ci#define PCI171X_DAREF_MASK(c)	PCI171X_DAREF((c), 0x3)
7062306a36Sopenharmony_ci#define PCI171X_DI_REG		0x10	/* R:   digital inputs */
7162306a36Sopenharmony_ci#define PCI171X_DO_REG		0x10	/* W:   digital outputs */
7262306a36Sopenharmony_ci#define PCI171X_TIMER_BASE	0x18	/* R/W: 8254 timer */
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistatic const struct comedi_lrange pci1710_ai_range = {
7562306a36Sopenharmony_ci	9, {
7662306a36Sopenharmony_ci		BIP_RANGE(5),		/* gain 1   (0x00) */
7762306a36Sopenharmony_ci		BIP_RANGE(2.5),		/* gain 2   (0x01) */
7862306a36Sopenharmony_ci		BIP_RANGE(1.25),	/* gain 4   (0x02) */
7962306a36Sopenharmony_ci		BIP_RANGE(0.625),	/* gain 8   (0x03) */
8062306a36Sopenharmony_ci		BIP_RANGE(10),		/* gain 0.5 (0x04) */
8162306a36Sopenharmony_ci		UNI_RANGE(10),		/* gain 1   (0x00 | UNI) */
8262306a36Sopenharmony_ci		UNI_RANGE(5),		/* gain 2   (0x01 | UNI) */
8362306a36Sopenharmony_ci		UNI_RANGE(2.5),		/* gain 4   (0x02 | UNI) */
8462306a36Sopenharmony_ci		UNI_RANGE(1.25)		/* gain 8   (0x03 | UNI) */
8562306a36Sopenharmony_ci	}
8662306a36Sopenharmony_ci};
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic const struct comedi_lrange pci1710hg_ai_range = {
8962306a36Sopenharmony_ci	12, {
9062306a36Sopenharmony_ci		BIP_RANGE(5),		/* gain 1    (0x00) */
9162306a36Sopenharmony_ci		BIP_RANGE(0.5),		/* gain 10   (0x01) */
9262306a36Sopenharmony_ci		BIP_RANGE(0.05),	/* gain 100  (0x02) */
9362306a36Sopenharmony_ci		BIP_RANGE(0.005),	/* gain 1000 (0x03) */
9462306a36Sopenharmony_ci		BIP_RANGE(10),		/* gain 0.5  (0x04) */
9562306a36Sopenharmony_ci		BIP_RANGE(1),		/* gain 5    (0x05) */
9662306a36Sopenharmony_ci		BIP_RANGE(0.1),		/* gain 50   (0x06) */
9762306a36Sopenharmony_ci		BIP_RANGE(0.01),	/* gain 500  (0x07) */
9862306a36Sopenharmony_ci		UNI_RANGE(10),		/* gain 1    (0x00 | UNI) */
9962306a36Sopenharmony_ci		UNI_RANGE(1),		/* gain 10   (0x01 | UNI) */
10062306a36Sopenharmony_ci		UNI_RANGE(0.1),		/* gain 100  (0x02 | UNI) */
10162306a36Sopenharmony_ci		UNI_RANGE(0.01)		/* gain 1000 (0x03 | UNI) */
10262306a36Sopenharmony_ci	}
10362306a36Sopenharmony_ci};
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic const struct comedi_lrange pci1711_ai_range = {
10662306a36Sopenharmony_ci	5, {
10762306a36Sopenharmony_ci		BIP_RANGE(10),		/* gain 1  (0x00) */
10862306a36Sopenharmony_ci		BIP_RANGE(5),		/* gain 2  (0x01) */
10962306a36Sopenharmony_ci		BIP_RANGE(2.5),		/* gain 4  (0x02) */
11062306a36Sopenharmony_ci		BIP_RANGE(1.25),	/* gain 8  (0x03) */
11162306a36Sopenharmony_ci		BIP_RANGE(0.625)	/* gain 16 (0x04) */
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci};
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic const struct comedi_lrange pci171x_ao_range = {
11662306a36Sopenharmony_ci	3, {
11762306a36Sopenharmony_ci		UNI_RANGE(5),		/* internal -5V ref */
11862306a36Sopenharmony_ci		UNI_RANGE(10),		/* internal -10V ref */
11962306a36Sopenharmony_ci		RANGE_ext(0, 1)		/* external -Vref (+/-10V max) */
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci};
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cienum pci1710_boardid {
12462306a36Sopenharmony_ci	BOARD_PCI1710,
12562306a36Sopenharmony_ci	BOARD_PCI1710HG,
12662306a36Sopenharmony_ci	BOARD_PCI1711,
12762306a36Sopenharmony_ci	BOARD_PCI1713,
12862306a36Sopenharmony_ci	BOARD_PCI1731,
12962306a36Sopenharmony_ci};
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistruct boardtype {
13262306a36Sopenharmony_ci	const char *name;
13362306a36Sopenharmony_ci	const struct comedi_lrange *ai_range;
13462306a36Sopenharmony_ci	unsigned int is_pci1711:1;
13562306a36Sopenharmony_ci	unsigned int is_pci1713:1;
13662306a36Sopenharmony_ci	unsigned int has_ao:1;
13762306a36Sopenharmony_ci};
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic const struct boardtype boardtypes[] = {
14062306a36Sopenharmony_ci	[BOARD_PCI1710] = {
14162306a36Sopenharmony_ci		.name		= "pci1710",
14262306a36Sopenharmony_ci		.ai_range	= &pci1710_ai_range,
14362306a36Sopenharmony_ci		.has_ao		= 1,
14462306a36Sopenharmony_ci	},
14562306a36Sopenharmony_ci	[BOARD_PCI1710HG] = {
14662306a36Sopenharmony_ci		.name		= "pci1710hg",
14762306a36Sopenharmony_ci		.ai_range	= &pci1710hg_ai_range,
14862306a36Sopenharmony_ci		.has_ao		= 1,
14962306a36Sopenharmony_ci	},
15062306a36Sopenharmony_ci	[BOARD_PCI1711] = {
15162306a36Sopenharmony_ci		.name		= "pci1711",
15262306a36Sopenharmony_ci		.ai_range	= &pci1711_ai_range,
15362306a36Sopenharmony_ci		.is_pci1711	= 1,
15462306a36Sopenharmony_ci		.has_ao		= 1,
15562306a36Sopenharmony_ci	},
15662306a36Sopenharmony_ci	[BOARD_PCI1713] = {
15762306a36Sopenharmony_ci		.name		= "pci1713",
15862306a36Sopenharmony_ci		.ai_range	= &pci1710_ai_range,
15962306a36Sopenharmony_ci		.is_pci1713	= 1,
16062306a36Sopenharmony_ci	},
16162306a36Sopenharmony_ci	[BOARD_PCI1731] = {
16262306a36Sopenharmony_ci		.name		= "pci1731",
16362306a36Sopenharmony_ci		.ai_range	= &pci1711_ai_range,
16462306a36Sopenharmony_ci		.is_pci1711	= 1,
16562306a36Sopenharmony_ci	},
16662306a36Sopenharmony_ci};
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistruct pci1710_private {
16962306a36Sopenharmony_ci	unsigned int max_samples;
17062306a36Sopenharmony_ci	unsigned int ctrl;	/* control register value */
17162306a36Sopenharmony_ci	unsigned int ctrl_ext;	/* used to switch from TRIG_EXT to TRIG_xxx */
17262306a36Sopenharmony_ci	unsigned int mux_scan;	/* used to set the channel interval to scan */
17362306a36Sopenharmony_ci	unsigned char ai_et;
17462306a36Sopenharmony_ci	unsigned int act_chanlist[32];	/*  list of scanned channel */
17562306a36Sopenharmony_ci	unsigned char saved_seglen;	/* len of the non-repeating chanlist */
17662306a36Sopenharmony_ci	unsigned char da_ranges;	/*  copy of D/A outpit range register */
17762306a36Sopenharmony_ci	unsigned char unipolar_gain;	/* adjust for unipolar gain codes */
17862306a36Sopenharmony_ci};
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic int pci1710_ai_check_chanlist(struct comedi_device *dev,
18162306a36Sopenharmony_ci				     struct comedi_subdevice *s,
18262306a36Sopenharmony_ci				     struct comedi_cmd *cmd)
18362306a36Sopenharmony_ci{
18462306a36Sopenharmony_ci	struct pci1710_private *devpriv = dev->private;
18562306a36Sopenharmony_ci	unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
18662306a36Sopenharmony_ci	unsigned int last_aref = CR_AREF(cmd->chanlist[0]);
18762306a36Sopenharmony_ci	unsigned int next_chan = (chan0 + 1) % s->n_chan;
18862306a36Sopenharmony_ci	unsigned int chansegment[32];
18962306a36Sopenharmony_ci	unsigned int seglen;
19062306a36Sopenharmony_ci	int i;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	if (cmd->chanlist_len == 1) {
19362306a36Sopenharmony_ci		devpriv->saved_seglen = cmd->chanlist_len;
19462306a36Sopenharmony_ci		return 0;
19562306a36Sopenharmony_ci	}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	/* first channel is always ok */
19862306a36Sopenharmony_ci	chansegment[0] = cmd->chanlist[0];
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	for (i = 1; i < cmd->chanlist_len; i++) {
20162306a36Sopenharmony_ci		unsigned int chan = CR_CHAN(cmd->chanlist[i]);
20262306a36Sopenharmony_ci		unsigned int aref = CR_AREF(cmd->chanlist[i]);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci		if (cmd->chanlist[0] == cmd->chanlist[i])
20562306a36Sopenharmony_ci			break;	/*  we detected a loop, stop */
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci		if (aref == AREF_DIFF && (chan & 1)) {
20862306a36Sopenharmony_ci			dev_err(dev->class_dev,
20962306a36Sopenharmony_ci				"Odd channel cannot be differential input!\n");
21062306a36Sopenharmony_ci			return -EINVAL;
21162306a36Sopenharmony_ci		}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci		if (last_aref == AREF_DIFF)
21462306a36Sopenharmony_ci			next_chan = (next_chan + 1) % s->n_chan;
21562306a36Sopenharmony_ci		if (chan != next_chan) {
21662306a36Sopenharmony_ci			dev_err(dev->class_dev,
21762306a36Sopenharmony_ci				"channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n",
21862306a36Sopenharmony_ci				i, chan, next_chan, chan0);
21962306a36Sopenharmony_ci			return -EINVAL;
22062306a36Sopenharmony_ci		}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci		/* next correct channel in list */
22362306a36Sopenharmony_ci		chansegment[i] = cmd->chanlist[i];
22462306a36Sopenharmony_ci		last_aref = aref;
22562306a36Sopenharmony_ci	}
22662306a36Sopenharmony_ci	seglen = i;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	for (i = 0; i < cmd->chanlist_len; i++) {
22962306a36Sopenharmony_ci		if (cmd->chanlist[i] != chansegment[i % seglen]) {
23062306a36Sopenharmony_ci			dev_err(dev->class_dev,
23162306a36Sopenharmony_ci				"bad channel, reference or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n",
23262306a36Sopenharmony_ci				i, CR_CHAN(chansegment[i]),
23362306a36Sopenharmony_ci				CR_RANGE(chansegment[i]),
23462306a36Sopenharmony_ci				CR_AREF(chansegment[i]),
23562306a36Sopenharmony_ci				CR_CHAN(cmd->chanlist[i % seglen]),
23662306a36Sopenharmony_ci				CR_RANGE(cmd->chanlist[i % seglen]),
23762306a36Sopenharmony_ci				CR_AREF(chansegment[i % seglen]));
23862306a36Sopenharmony_ci			return -EINVAL;
23962306a36Sopenharmony_ci		}
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci	devpriv->saved_seglen = seglen;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	return 0;
24462306a36Sopenharmony_ci}
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cistatic void pci1710_ai_setup_chanlist(struct comedi_device *dev,
24762306a36Sopenharmony_ci				      struct comedi_subdevice *s,
24862306a36Sopenharmony_ci				      unsigned int *chanlist,
24962306a36Sopenharmony_ci				      unsigned int n_chan,
25062306a36Sopenharmony_ci				      unsigned int seglen)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	struct pci1710_private *devpriv = dev->private;
25362306a36Sopenharmony_ci	unsigned int first_chan = CR_CHAN(chanlist[0]);
25462306a36Sopenharmony_ci	unsigned int last_chan = CR_CHAN(chanlist[seglen - 1]);
25562306a36Sopenharmony_ci	unsigned int i;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	for (i = 0; i < seglen; i++) {	/*  store range list to card */
25862306a36Sopenharmony_ci		unsigned int chan = CR_CHAN(chanlist[i]);
25962306a36Sopenharmony_ci		unsigned int range = CR_RANGE(chanlist[i]);
26062306a36Sopenharmony_ci		unsigned int aref = CR_AREF(chanlist[i]);
26162306a36Sopenharmony_ci		unsigned int rangeval = 0;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci		if (aref == AREF_DIFF)
26462306a36Sopenharmony_ci			rangeval |= PCI171X_RANGE_DIFF;
26562306a36Sopenharmony_ci		if (comedi_range_is_unipolar(s, range)) {
26662306a36Sopenharmony_ci			rangeval |= PCI171X_RANGE_UNI;
26762306a36Sopenharmony_ci			range -= devpriv->unipolar_gain;
26862306a36Sopenharmony_ci		}
26962306a36Sopenharmony_ci		rangeval |= PCI171X_RANGE_GAIN(range);
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci		/* select channel and set range */
27262306a36Sopenharmony_ci		outw(PCI171X_MUX_CHAN(chan), dev->iobase + PCI171X_MUX_REG);
27362306a36Sopenharmony_ci		outw(rangeval, dev->iobase + PCI171X_RANGE_REG);
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci		devpriv->act_chanlist[i] = chan;
27662306a36Sopenharmony_ci	}
27762306a36Sopenharmony_ci	for ( ; i < n_chan; i++)	/* store remainder of channel list */
27862306a36Sopenharmony_ci		devpriv->act_chanlist[i] = CR_CHAN(chanlist[i]);
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	/* select channel interval to scan */
28162306a36Sopenharmony_ci	devpriv->mux_scan = PCI171X_MUX_CHANL(first_chan) |
28262306a36Sopenharmony_ci			    PCI171X_MUX_CHANH(last_chan);
28362306a36Sopenharmony_ci	outw(devpriv->mux_scan, dev->iobase + PCI171X_MUX_REG);
28462306a36Sopenharmony_ci}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_cistatic int pci1710_ai_eoc(struct comedi_device *dev,
28762306a36Sopenharmony_ci			  struct comedi_subdevice *s,
28862306a36Sopenharmony_ci			  struct comedi_insn *insn,
28962306a36Sopenharmony_ci			  unsigned long context)
29062306a36Sopenharmony_ci{
29162306a36Sopenharmony_ci	unsigned int status;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	status = inw(dev->iobase + PCI171X_STATUS_REG);
29462306a36Sopenharmony_ci	if ((status & PCI171X_STATUS_FE) == 0)
29562306a36Sopenharmony_ci		return 0;
29662306a36Sopenharmony_ci	return -EBUSY;
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_cistatic int pci1710_ai_read_sample(struct comedi_device *dev,
30062306a36Sopenharmony_ci				  struct comedi_subdevice *s,
30162306a36Sopenharmony_ci				  unsigned int cur_chan,
30262306a36Sopenharmony_ci				  unsigned short *val)
30362306a36Sopenharmony_ci{
30462306a36Sopenharmony_ci	const struct boardtype *board = dev->board_ptr;
30562306a36Sopenharmony_ci	struct pci1710_private *devpriv = dev->private;
30662306a36Sopenharmony_ci	unsigned short sample;
30762306a36Sopenharmony_ci	unsigned int chan;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	sample = inw(dev->iobase + PCI171X_AD_DATA_REG);
31062306a36Sopenharmony_ci	if (!board->is_pci1713) {
31162306a36Sopenharmony_ci		/*
31262306a36Sopenharmony_ci		 * The upper 4 bits of the 16-bit sample are the channel number
31362306a36Sopenharmony_ci		 * that the sample was acquired from. Verify that this channel
31462306a36Sopenharmony_ci		 * number matches the expected channel number.
31562306a36Sopenharmony_ci		 */
31662306a36Sopenharmony_ci		chan = sample >> 12;
31762306a36Sopenharmony_ci		if (chan != devpriv->act_chanlist[cur_chan]) {
31862306a36Sopenharmony_ci			dev_err(dev->class_dev,
31962306a36Sopenharmony_ci				"A/D data dropout: received from channel %d, expected %d\n",
32062306a36Sopenharmony_ci				chan, devpriv->act_chanlist[cur_chan]);
32162306a36Sopenharmony_ci			return -ENODATA;
32262306a36Sopenharmony_ci		}
32362306a36Sopenharmony_ci	}
32462306a36Sopenharmony_ci	*val = sample & s->maxdata;
32562306a36Sopenharmony_ci	return 0;
32662306a36Sopenharmony_ci}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_cistatic int pci1710_ai_insn_read(struct comedi_device *dev,
32962306a36Sopenharmony_ci				struct comedi_subdevice *s,
33062306a36Sopenharmony_ci				struct comedi_insn *insn,
33162306a36Sopenharmony_ci				unsigned int *data)
33262306a36Sopenharmony_ci{
33362306a36Sopenharmony_ci	struct pci1710_private *devpriv = dev->private;
33462306a36Sopenharmony_ci	int ret = 0;
33562306a36Sopenharmony_ci	int i;
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci	/* enable software trigger */
33862306a36Sopenharmony_ci	devpriv->ctrl |= PCI171X_CTRL_SW;
33962306a36Sopenharmony_ci	outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG);
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRFIFO_REG);
34262306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRINT_REG);
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	pci1710_ai_setup_chanlist(dev, s, &insn->chanspec, 1, 1);
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	for (i = 0; i < insn->n; i++) {
34762306a36Sopenharmony_ci		unsigned short val;
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci		/* start conversion */
35062306a36Sopenharmony_ci		outw(0, dev->iobase + PCI171X_SOFTTRG_REG);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci		ret = comedi_timeout(dev, s, insn, pci1710_ai_eoc, 0);
35362306a36Sopenharmony_ci		if (ret)
35462306a36Sopenharmony_ci			break;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci		ret = pci1710_ai_read_sample(dev, s, 0, &val);
35762306a36Sopenharmony_ci		if (ret)
35862306a36Sopenharmony_ci			break;
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci		data[i] = val;
36162306a36Sopenharmony_ci	}
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	/* disable software trigger */
36462306a36Sopenharmony_ci	devpriv->ctrl &= ~PCI171X_CTRL_SW;
36562306a36Sopenharmony_ci	outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG);
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRFIFO_REG);
36862306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRINT_REG);
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	return ret ? ret : insn->n;
37162306a36Sopenharmony_ci}
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_cistatic int pci1710_ai_cancel(struct comedi_device *dev,
37462306a36Sopenharmony_ci			     struct comedi_subdevice *s)
37562306a36Sopenharmony_ci{
37662306a36Sopenharmony_ci	struct pci1710_private *devpriv = dev->private;
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	/* disable A/D triggers and interrupt sources */
37962306a36Sopenharmony_ci	devpriv->ctrl &= PCI171X_CTRL_CNT0;	/* preserve counter 0 clk src */
38062306a36Sopenharmony_ci	outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG);
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	/* disable pacer */
38362306a36Sopenharmony_ci	comedi_8254_pacer_enable(dev->pacer, 1, 2, false);
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	/* clear A/D FIFO and any pending interrutps */
38662306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRFIFO_REG);
38762306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRINT_REG);
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	return 0;
39062306a36Sopenharmony_ci}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_cistatic void pci1710_handle_every_sample(struct comedi_device *dev,
39362306a36Sopenharmony_ci					struct comedi_subdevice *s)
39462306a36Sopenharmony_ci{
39562306a36Sopenharmony_ci	struct comedi_cmd *cmd = &s->async->cmd;
39662306a36Sopenharmony_ci	unsigned int status;
39762306a36Sopenharmony_ci	unsigned short val;
39862306a36Sopenharmony_ci	int ret;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	status = inw(dev->iobase + PCI171X_STATUS_REG);
40162306a36Sopenharmony_ci	if (status & PCI171X_STATUS_FE) {
40262306a36Sopenharmony_ci		dev_dbg(dev->class_dev, "A/D FIFO empty (%4x)\n", status);
40362306a36Sopenharmony_ci		s->async->events |= COMEDI_CB_ERROR;
40462306a36Sopenharmony_ci		return;
40562306a36Sopenharmony_ci	}
40662306a36Sopenharmony_ci	if (status & PCI171X_STATUS_FF) {
40762306a36Sopenharmony_ci		dev_dbg(dev->class_dev,
40862306a36Sopenharmony_ci			"A/D FIFO Full status (Fatal Error!) (%4x)\n", status);
40962306a36Sopenharmony_ci		s->async->events |= COMEDI_CB_ERROR;
41062306a36Sopenharmony_ci		return;
41162306a36Sopenharmony_ci	}
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRINT_REG);
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	for (; !(inw(dev->iobase + PCI171X_STATUS_REG) & PCI171X_STATUS_FE);) {
41662306a36Sopenharmony_ci		ret = pci1710_ai_read_sample(dev, s, s->async->cur_chan, &val);
41762306a36Sopenharmony_ci		if (ret) {
41862306a36Sopenharmony_ci			s->async->events |= COMEDI_CB_ERROR;
41962306a36Sopenharmony_ci			break;
42062306a36Sopenharmony_ci		}
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci		comedi_buf_write_samples(s, &val, 1);
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci		if (cmd->stop_src == TRIG_COUNT &&
42562306a36Sopenharmony_ci		    s->async->scans_done >= cmd->stop_arg) {
42662306a36Sopenharmony_ci			s->async->events |= COMEDI_CB_EOA;
42762306a36Sopenharmony_ci			break;
42862306a36Sopenharmony_ci		}
42962306a36Sopenharmony_ci	}
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRINT_REG);
43262306a36Sopenharmony_ci}
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_cistatic void pci1710_handle_fifo(struct comedi_device *dev,
43562306a36Sopenharmony_ci				struct comedi_subdevice *s)
43662306a36Sopenharmony_ci{
43762306a36Sopenharmony_ci	struct pci1710_private *devpriv = dev->private;
43862306a36Sopenharmony_ci	struct comedi_async *async = s->async;
43962306a36Sopenharmony_ci	struct comedi_cmd *cmd = &async->cmd;
44062306a36Sopenharmony_ci	unsigned int status;
44162306a36Sopenharmony_ci	int i;
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	status = inw(dev->iobase + PCI171X_STATUS_REG);
44462306a36Sopenharmony_ci	if (!(status & PCI171X_STATUS_FH)) {
44562306a36Sopenharmony_ci		dev_dbg(dev->class_dev, "A/D FIFO not half full!\n");
44662306a36Sopenharmony_ci		async->events |= COMEDI_CB_ERROR;
44762306a36Sopenharmony_ci		return;
44862306a36Sopenharmony_ci	}
44962306a36Sopenharmony_ci	if (status & PCI171X_STATUS_FF) {
45062306a36Sopenharmony_ci		dev_dbg(dev->class_dev,
45162306a36Sopenharmony_ci			"A/D FIFO Full status (Fatal Error!)\n");
45262306a36Sopenharmony_ci		async->events |= COMEDI_CB_ERROR;
45362306a36Sopenharmony_ci		return;
45462306a36Sopenharmony_ci	}
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	for (i = 0; i < devpriv->max_samples; i++) {
45762306a36Sopenharmony_ci		unsigned short val;
45862306a36Sopenharmony_ci		int ret;
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci		ret = pci1710_ai_read_sample(dev, s, s->async->cur_chan, &val);
46162306a36Sopenharmony_ci		if (ret) {
46262306a36Sopenharmony_ci			s->async->events |= COMEDI_CB_ERROR;
46362306a36Sopenharmony_ci			break;
46462306a36Sopenharmony_ci		}
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_ci		if (!comedi_buf_write_samples(s, &val, 1))
46762306a36Sopenharmony_ci			break;
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci		if (cmd->stop_src == TRIG_COUNT &&
47062306a36Sopenharmony_ci		    async->scans_done >= cmd->stop_arg) {
47162306a36Sopenharmony_ci			async->events |= COMEDI_CB_EOA;
47262306a36Sopenharmony_ci			break;
47362306a36Sopenharmony_ci		}
47462306a36Sopenharmony_ci	}
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRINT_REG);
47762306a36Sopenharmony_ci}
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_cistatic irqreturn_t pci1710_irq_handler(int irq, void *d)
48062306a36Sopenharmony_ci{
48162306a36Sopenharmony_ci	struct comedi_device *dev = d;
48262306a36Sopenharmony_ci	struct pci1710_private *devpriv = dev->private;
48362306a36Sopenharmony_ci	struct comedi_subdevice *s;
48462306a36Sopenharmony_ci	struct comedi_cmd *cmd;
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	if (!dev->attached)	/*  is device attached? */
48762306a36Sopenharmony_ci		return IRQ_NONE;	/*  no, exit */
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	s = dev->read_subdev;
49062306a36Sopenharmony_ci	cmd = &s->async->cmd;
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	/*  is this interrupt from our board? */
49362306a36Sopenharmony_ci	if (!(inw(dev->iobase + PCI171X_STATUS_REG) & PCI171X_STATUS_IRQ))
49462306a36Sopenharmony_ci		return IRQ_NONE;	/*  no, exit */
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	if (devpriv->ai_et) {	/*  Switch from initial TRIG_EXT to TRIG_xxx. */
49762306a36Sopenharmony_ci		devpriv->ai_et = 0;
49862306a36Sopenharmony_ci		devpriv->ctrl &= PCI171X_CTRL_CNT0;
49962306a36Sopenharmony_ci		devpriv->ctrl |= PCI171X_CTRL_SW; /* set software trigger */
50062306a36Sopenharmony_ci		outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG);
50162306a36Sopenharmony_ci		devpriv->ctrl = devpriv->ctrl_ext;
50262306a36Sopenharmony_ci		outb(0, dev->iobase + PCI171X_CLRFIFO_REG);
50362306a36Sopenharmony_ci		outb(0, dev->iobase + PCI171X_CLRINT_REG);
50462306a36Sopenharmony_ci		/* no sample on this interrupt; reset the channel interval */
50562306a36Sopenharmony_ci		outw(devpriv->mux_scan, dev->iobase + PCI171X_MUX_REG);
50662306a36Sopenharmony_ci		outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG);
50762306a36Sopenharmony_ci		comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
50862306a36Sopenharmony_ci		return IRQ_HANDLED;
50962306a36Sopenharmony_ci	}
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci	if (cmd->flags & CMDF_WAKE_EOS)
51262306a36Sopenharmony_ci		pci1710_handle_every_sample(dev, s);
51362306a36Sopenharmony_ci	else
51462306a36Sopenharmony_ci		pci1710_handle_fifo(dev, s);
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci	comedi_handle_events(dev, s);
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	return IRQ_HANDLED;
51962306a36Sopenharmony_ci}
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_cistatic int pci1710_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
52262306a36Sopenharmony_ci{
52362306a36Sopenharmony_ci	struct pci1710_private *devpriv = dev->private;
52462306a36Sopenharmony_ci	struct comedi_cmd *cmd = &s->async->cmd;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	pci1710_ai_setup_chanlist(dev, s, cmd->chanlist, cmd->chanlist_len,
52762306a36Sopenharmony_ci				  devpriv->saved_seglen);
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRFIFO_REG);
53062306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRINT_REG);
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	devpriv->ctrl &= PCI171X_CTRL_CNT0;
53362306a36Sopenharmony_ci	if ((cmd->flags & CMDF_WAKE_EOS) == 0)
53462306a36Sopenharmony_ci		devpriv->ctrl |= PCI171X_CTRL_ONEFH;
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci	if (cmd->convert_src == TRIG_TIMER) {
53762306a36Sopenharmony_ci		comedi_8254_update_divisors(dev->pacer);
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci		devpriv->ctrl |= PCI171X_CTRL_PACER | PCI171X_CTRL_IRQEN;
54062306a36Sopenharmony_ci		if (cmd->start_src == TRIG_EXT) {
54162306a36Sopenharmony_ci			devpriv->ctrl_ext = devpriv->ctrl;
54262306a36Sopenharmony_ci			devpriv->ctrl &= ~(PCI171X_CTRL_PACER |
54362306a36Sopenharmony_ci					   PCI171X_CTRL_ONEFH |
54462306a36Sopenharmony_ci					   PCI171X_CTRL_GATE);
54562306a36Sopenharmony_ci			devpriv->ctrl |= PCI171X_CTRL_EXT;
54662306a36Sopenharmony_ci			devpriv->ai_et = 1;
54762306a36Sopenharmony_ci		} else {	/* TRIG_NOW */
54862306a36Sopenharmony_ci			devpriv->ai_et = 0;
54962306a36Sopenharmony_ci		}
55062306a36Sopenharmony_ci		outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG);
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci		if (cmd->start_src == TRIG_NOW)
55362306a36Sopenharmony_ci			comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
55462306a36Sopenharmony_ci	} else {	/* TRIG_EXT */
55562306a36Sopenharmony_ci		devpriv->ctrl |= PCI171X_CTRL_EXT | PCI171X_CTRL_IRQEN;
55662306a36Sopenharmony_ci		outw(devpriv->ctrl, dev->iobase + PCI171X_CTRL_REG);
55762306a36Sopenharmony_ci	}
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	return 0;
56062306a36Sopenharmony_ci}
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_cistatic int pci1710_ai_cmdtest(struct comedi_device *dev,
56362306a36Sopenharmony_ci			      struct comedi_subdevice *s,
56462306a36Sopenharmony_ci			      struct comedi_cmd *cmd)
56562306a36Sopenharmony_ci{
56662306a36Sopenharmony_ci	int err = 0;
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci	/* Step 1 : check if triggers are trivially valid */
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
57162306a36Sopenharmony_ci	err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
57262306a36Sopenharmony_ci	err |= comedi_check_trigger_src(&cmd->convert_src,
57362306a36Sopenharmony_ci					TRIG_TIMER | TRIG_EXT);
57462306a36Sopenharmony_ci	err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
57562306a36Sopenharmony_ci	err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	if (err)
57862306a36Sopenharmony_ci		return 1;
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	/* step 2a: make sure trigger sources are unique */
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_ci	err |= comedi_check_trigger_is_unique(cmd->start_src);
58362306a36Sopenharmony_ci	err |= comedi_check_trigger_is_unique(cmd->convert_src);
58462306a36Sopenharmony_ci	err |= comedi_check_trigger_is_unique(cmd->stop_src);
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci	/* step 2b: and mutually compatible */
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_ci	if (err)
58962306a36Sopenharmony_ci		return 2;
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci	/* Step 3: check if arguments are trivially valid */
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
59462306a36Sopenharmony_ci	err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ci	if (cmd->convert_src == TRIG_TIMER)
59762306a36Sopenharmony_ci		err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000);
59862306a36Sopenharmony_ci	else	/* TRIG_FOLLOW */
59962306a36Sopenharmony_ci		err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
60262306a36Sopenharmony_ci					   cmd->chanlist_len);
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci	if (cmd->stop_src == TRIG_COUNT)
60562306a36Sopenharmony_ci		err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
60662306a36Sopenharmony_ci	else	/* TRIG_NONE */
60762306a36Sopenharmony_ci		err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_ci	if (err)
61062306a36Sopenharmony_ci		return 3;
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	/* step 4: fix up any arguments */
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	if (cmd->convert_src == TRIG_TIMER) {
61562306a36Sopenharmony_ci		unsigned int arg = cmd->convert_arg;
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ci		comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
61862306a36Sopenharmony_ci		err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
61962306a36Sopenharmony_ci	}
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci	if (err)
62262306a36Sopenharmony_ci		return 4;
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	/* Step 5: check channel list */
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_ci	err |= pci1710_ai_check_chanlist(dev, s, cmd);
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	if (err)
62962306a36Sopenharmony_ci		return 5;
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci	return 0;
63262306a36Sopenharmony_ci}
63362306a36Sopenharmony_ci
63462306a36Sopenharmony_cistatic int pci1710_ao_insn_write(struct comedi_device *dev,
63562306a36Sopenharmony_ci				 struct comedi_subdevice *s,
63662306a36Sopenharmony_ci				 struct comedi_insn *insn,
63762306a36Sopenharmony_ci				 unsigned int *data)
63862306a36Sopenharmony_ci{
63962306a36Sopenharmony_ci	struct pci1710_private *devpriv = dev->private;
64062306a36Sopenharmony_ci	unsigned int chan = CR_CHAN(insn->chanspec);
64162306a36Sopenharmony_ci	unsigned int range = CR_RANGE(insn->chanspec);
64262306a36Sopenharmony_ci	unsigned int val = s->readback[chan];
64362306a36Sopenharmony_ci	int i;
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	devpriv->da_ranges &= ~PCI171X_DAREF_MASK(chan);
64662306a36Sopenharmony_ci	devpriv->da_ranges |= PCI171X_DAREF(chan, range);
64762306a36Sopenharmony_ci	outw(devpriv->da_ranges, dev->iobase + PCI171X_DAREF_REG);
64862306a36Sopenharmony_ci
64962306a36Sopenharmony_ci	for (i = 0; i < insn->n; i++) {
65062306a36Sopenharmony_ci		val = data[i];
65162306a36Sopenharmony_ci		outw(val, dev->iobase + PCI171X_DA_REG(chan));
65262306a36Sopenharmony_ci	}
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci	s->readback[chan] = val;
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	return insn->n;
65762306a36Sopenharmony_ci}
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_cistatic int pci1710_di_insn_bits(struct comedi_device *dev,
66062306a36Sopenharmony_ci				struct comedi_subdevice *s,
66162306a36Sopenharmony_ci				struct comedi_insn *insn,
66262306a36Sopenharmony_ci				unsigned int *data)
66362306a36Sopenharmony_ci{
66462306a36Sopenharmony_ci	data[1] = inw(dev->iobase + PCI171X_DI_REG);
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci	return insn->n;
66762306a36Sopenharmony_ci}
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_cistatic int pci1710_do_insn_bits(struct comedi_device *dev,
67062306a36Sopenharmony_ci				struct comedi_subdevice *s,
67162306a36Sopenharmony_ci				struct comedi_insn *insn,
67262306a36Sopenharmony_ci				unsigned int *data)
67362306a36Sopenharmony_ci{
67462306a36Sopenharmony_ci	if (comedi_dio_update_state(s, data))
67562306a36Sopenharmony_ci		outw(s->state, dev->iobase + PCI171X_DO_REG);
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_ci	data[1] = s->state;
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_ci	return insn->n;
68062306a36Sopenharmony_ci}
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_cistatic int pci1710_counter_insn_config(struct comedi_device *dev,
68362306a36Sopenharmony_ci				       struct comedi_subdevice *s,
68462306a36Sopenharmony_ci				       struct comedi_insn *insn,
68562306a36Sopenharmony_ci				       unsigned int *data)
68662306a36Sopenharmony_ci{
68762306a36Sopenharmony_ci	struct pci1710_private *devpriv = dev->private;
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_ci	switch (data[0]) {
69062306a36Sopenharmony_ci	case INSN_CONFIG_SET_CLOCK_SRC:
69162306a36Sopenharmony_ci		switch (data[1]) {
69262306a36Sopenharmony_ci		case 0:	/* internal */
69362306a36Sopenharmony_ci			devpriv->ctrl_ext &= ~PCI171X_CTRL_CNT0;
69462306a36Sopenharmony_ci			break;
69562306a36Sopenharmony_ci		case 1:	/* external */
69662306a36Sopenharmony_ci			devpriv->ctrl_ext |= PCI171X_CTRL_CNT0;
69762306a36Sopenharmony_ci			break;
69862306a36Sopenharmony_ci		default:
69962306a36Sopenharmony_ci			return -EINVAL;
70062306a36Sopenharmony_ci		}
70162306a36Sopenharmony_ci		outw(devpriv->ctrl_ext, dev->iobase + PCI171X_CTRL_REG);
70262306a36Sopenharmony_ci		break;
70362306a36Sopenharmony_ci	case INSN_CONFIG_GET_CLOCK_SRC:
70462306a36Sopenharmony_ci		if (devpriv->ctrl_ext & PCI171X_CTRL_CNT0) {
70562306a36Sopenharmony_ci			data[1] = 1;
70662306a36Sopenharmony_ci			data[2] = 0;
70762306a36Sopenharmony_ci		} else {
70862306a36Sopenharmony_ci			data[1] = 0;
70962306a36Sopenharmony_ci			data[2] = I8254_OSC_BASE_1MHZ;
71062306a36Sopenharmony_ci		}
71162306a36Sopenharmony_ci		break;
71262306a36Sopenharmony_ci	default:
71362306a36Sopenharmony_ci		return -EINVAL;
71462306a36Sopenharmony_ci	}
71562306a36Sopenharmony_ci
71662306a36Sopenharmony_ci	return insn->n;
71762306a36Sopenharmony_ci}
71862306a36Sopenharmony_ci
71962306a36Sopenharmony_cistatic void pci1710_reset(struct comedi_device *dev)
72062306a36Sopenharmony_ci{
72162306a36Sopenharmony_ci	const struct boardtype *board = dev->board_ptr;
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_ci	/*
72462306a36Sopenharmony_ci	 * Disable A/D triggers and interrupt sources, set counter 0
72562306a36Sopenharmony_ci	 * to use internal 1 MHz clock.
72662306a36Sopenharmony_ci	 */
72762306a36Sopenharmony_ci	outw(0, dev->iobase + PCI171X_CTRL_REG);
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ci	/* clear A/D FIFO and any pending interrutps */
73062306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRFIFO_REG);
73162306a36Sopenharmony_ci	outb(0, dev->iobase + PCI171X_CLRINT_REG);
73262306a36Sopenharmony_ci
73362306a36Sopenharmony_ci	if (board->has_ao) {
73462306a36Sopenharmony_ci		/* set DACs to 0..5V and outputs to 0V */
73562306a36Sopenharmony_ci		outb(0, dev->iobase + PCI171X_DAREF_REG);
73662306a36Sopenharmony_ci		outw(0, dev->iobase + PCI171X_DA_REG(0));
73762306a36Sopenharmony_ci		outw(0, dev->iobase + PCI171X_DA_REG(1));
73862306a36Sopenharmony_ci	}
73962306a36Sopenharmony_ci
74062306a36Sopenharmony_ci	/* set digital outputs to 0 */
74162306a36Sopenharmony_ci	outw(0, dev->iobase + PCI171X_DO_REG);
74262306a36Sopenharmony_ci}
74362306a36Sopenharmony_ci
74462306a36Sopenharmony_cistatic int pci1710_auto_attach(struct comedi_device *dev,
74562306a36Sopenharmony_ci			       unsigned long context)
74662306a36Sopenharmony_ci{
74762306a36Sopenharmony_ci	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
74862306a36Sopenharmony_ci	const struct boardtype *board = NULL;
74962306a36Sopenharmony_ci	struct pci1710_private *devpriv;
75062306a36Sopenharmony_ci	struct comedi_subdevice *s;
75162306a36Sopenharmony_ci	int ret, subdev, n_subdevices;
75262306a36Sopenharmony_ci	int i;
75362306a36Sopenharmony_ci
75462306a36Sopenharmony_ci	if (context < ARRAY_SIZE(boardtypes))
75562306a36Sopenharmony_ci		board = &boardtypes[context];
75662306a36Sopenharmony_ci	if (!board)
75762306a36Sopenharmony_ci		return -ENODEV;
75862306a36Sopenharmony_ci	dev->board_ptr = board;
75962306a36Sopenharmony_ci	dev->board_name = board->name;
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_ci	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
76262306a36Sopenharmony_ci	if (!devpriv)
76362306a36Sopenharmony_ci		return -ENOMEM;
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_ci	ret = comedi_pci_enable(dev);
76662306a36Sopenharmony_ci	if (ret)
76762306a36Sopenharmony_ci		return ret;
76862306a36Sopenharmony_ci	dev->iobase = pci_resource_start(pcidev, 2);
76962306a36Sopenharmony_ci
77062306a36Sopenharmony_ci	dev->pacer = comedi_8254_init(dev->iobase + PCI171X_TIMER_BASE,
77162306a36Sopenharmony_ci				      I8254_OSC_BASE_10MHZ, I8254_IO16, 0);
77262306a36Sopenharmony_ci	if (!dev->pacer)
77362306a36Sopenharmony_ci		return -ENOMEM;
77462306a36Sopenharmony_ci
77562306a36Sopenharmony_ci	n_subdevices = 1;	/* all boards have analog inputs */
77662306a36Sopenharmony_ci	if (board->has_ao)
77762306a36Sopenharmony_ci		n_subdevices++;
77862306a36Sopenharmony_ci	if (!board->is_pci1713) {
77962306a36Sopenharmony_ci		/*
78062306a36Sopenharmony_ci		 * All other boards have digital inputs and outputs as
78162306a36Sopenharmony_ci		 * well as a user counter.
78262306a36Sopenharmony_ci		 */
78362306a36Sopenharmony_ci		n_subdevices += 3;
78462306a36Sopenharmony_ci	}
78562306a36Sopenharmony_ci
78662306a36Sopenharmony_ci	ret = comedi_alloc_subdevices(dev, n_subdevices);
78762306a36Sopenharmony_ci	if (ret)
78862306a36Sopenharmony_ci		return ret;
78962306a36Sopenharmony_ci
79062306a36Sopenharmony_ci	pci1710_reset(dev);
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci	if (pcidev->irq) {
79362306a36Sopenharmony_ci		ret = request_irq(pcidev->irq, pci1710_irq_handler,
79462306a36Sopenharmony_ci				  IRQF_SHARED, dev->board_name, dev);
79562306a36Sopenharmony_ci		if (ret == 0)
79662306a36Sopenharmony_ci			dev->irq = pcidev->irq;
79762306a36Sopenharmony_ci	}
79862306a36Sopenharmony_ci
79962306a36Sopenharmony_ci	subdev = 0;
80062306a36Sopenharmony_ci
80162306a36Sopenharmony_ci	/* Analog Input subdevice */
80262306a36Sopenharmony_ci	s = &dev->subdevices[subdev++];
80362306a36Sopenharmony_ci	s->type		= COMEDI_SUBD_AI;
80462306a36Sopenharmony_ci	s->subdev_flags	= SDF_READABLE | SDF_GROUND;
80562306a36Sopenharmony_ci	if (!board->is_pci1711)
80662306a36Sopenharmony_ci		s->subdev_flags	|= SDF_DIFF;
80762306a36Sopenharmony_ci	s->n_chan	= board->is_pci1713 ? 32 : 16;
80862306a36Sopenharmony_ci	s->maxdata	= 0x0fff;
80962306a36Sopenharmony_ci	s->range_table	= board->ai_range;
81062306a36Sopenharmony_ci	s->insn_read	= pci1710_ai_insn_read;
81162306a36Sopenharmony_ci	if (dev->irq) {
81262306a36Sopenharmony_ci		dev->read_subdev = s;
81362306a36Sopenharmony_ci		s->subdev_flags	|= SDF_CMD_READ;
81462306a36Sopenharmony_ci		s->len_chanlist	= s->n_chan;
81562306a36Sopenharmony_ci		s->do_cmdtest	= pci1710_ai_cmdtest;
81662306a36Sopenharmony_ci		s->do_cmd	= pci1710_ai_cmd;
81762306a36Sopenharmony_ci		s->cancel	= pci1710_ai_cancel;
81862306a36Sopenharmony_ci	}
81962306a36Sopenharmony_ci
82062306a36Sopenharmony_ci	/* find the value needed to adjust for unipolar gain codes */
82162306a36Sopenharmony_ci	for (i = 0; i < s->range_table->length; i++) {
82262306a36Sopenharmony_ci		if (comedi_range_is_unipolar(s, i)) {
82362306a36Sopenharmony_ci			devpriv->unipolar_gain = i;
82462306a36Sopenharmony_ci			break;
82562306a36Sopenharmony_ci		}
82662306a36Sopenharmony_ci	}
82762306a36Sopenharmony_ci
82862306a36Sopenharmony_ci	if (board->has_ao) {
82962306a36Sopenharmony_ci		/* Analog Output subdevice */
83062306a36Sopenharmony_ci		s = &dev->subdevices[subdev++];
83162306a36Sopenharmony_ci		s->type		= COMEDI_SUBD_AO;
83262306a36Sopenharmony_ci		s->subdev_flags	= SDF_WRITABLE | SDF_GROUND;
83362306a36Sopenharmony_ci		s->n_chan	= 2;
83462306a36Sopenharmony_ci		s->maxdata	= 0x0fff;
83562306a36Sopenharmony_ci		s->range_table	= &pci171x_ao_range;
83662306a36Sopenharmony_ci		s->insn_write	= pci1710_ao_insn_write;
83762306a36Sopenharmony_ci
83862306a36Sopenharmony_ci		ret = comedi_alloc_subdev_readback(s);
83962306a36Sopenharmony_ci		if (ret)
84062306a36Sopenharmony_ci			return ret;
84162306a36Sopenharmony_ci	}
84262306a36Sopenharmony_ci
84362306a36Sopenharmony_ci	if (!board->is_pci1713) {
84462306a36Sopenharmony_ci		/* Digital Input subdevice */
84562306a36Sopenharmony_ci		s = &dev->subdevices[subdev++];
84662306a36Sopenharmony_ci		s->type		= COMEDI_SUBD_DI;
84762306a36Sopenharmony_ci		s->subdev_flags	= SDF_READABLE;
84862306a36Sopenharmony_ci		s->n_chan	= 16;
84962306a36Sopenharmony_ci		s->maxdata	= 1;
85062306a36Sopenharmony_ci		s->range_table	= &range_digital;
85162306a36Sopenharmony_ci		s->insn_bits	= pci1710_di_insn_bits;
85262306a36Sopenharmony_ci
85362306a36Sopenharmony_ci		/* Digital Output subdevice */
85462306a36Sopenharmony_ci		s = &dev->subdevices[subdev++];
85562306a36Sopenharmony_ci		s->type		= COMEDI_SUBD_DO;
85662306a36Sopenharmony_ci		s->subdev_flags	= SDF_WRITABLE;
85762306a36Sopenharmony_ci		s->n_chan	= 16;
85862306a36Sopenharmony_ci		s->maxdata	= 1;
85962306a36Sopenharmony_ci		s->range_table	= &range_digital;
86062306a36Sopenharmony_ci		s->insn_bits	= pci1710_do_insn_bits;
86162306a36Sopenharmony_ci
86262306a36Sopenharmony_ci		/* Counter subdevice (8254) */
86362306a36Sopenharmony_ci		s = &dev->subdevices[subdev++];
86462306a36Sopenharmony_ci		comedi_8254_subdevice_init(s, dev->pacer);
86562306a36Sopenharmony_ci
86662306a36Sopenharmony_ci		dev->pacer->insn_config = pci1710_counter_insn_config;
86762306a36Sopenharmony_ci
86862306a36Sopenharmony_ci		/* counters 1 and 2 are used internally for the pacer */
86962306a36Sopenharmony_ci		comedi_8254_set_busy(dev->pacer, 1, true);
87062306a36Sopenharmony_ci		comedi_8254_set_busy(dev->pacer, 2, true);
87162306a36Sopenharmony_ci	}
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_ci	/* max_samples is half the FIFO size (2 bytes/sample) */
87462306a36Sopenharmony_ci	devpriv->max_samples = (board->is_pci1711) ? 512 : 2048;
87562306a36Sopenharmony_ci
87662306a36Sopenharmony_ci	return 0;
87762306a36Sopenharmony_ci}
87862306a36Sopenharmony_ci
87962306a36Sopenharmony_cistatic struct comedi_driver adv_pci1710_driver = {
88062306a36Sopenharmony_ci	.driver_name	= "adv_pci1710",
88162306a36Sopenharmony_ci	.module		= THIS_MODULE,
88262306a36Sopenharmony_ci	.auto_attach	= pci1710_auto_attach,
88362306a36Sopenharmony_ci	.detach		= comedi_pci_detach,
88462306a36Sopenharmony_ci};
88562306a36Sopenharmony_ci
88662306a36Sopenharmony_cistatic int adv_pci1710_pci_probe(struct pci_dev *dev,
88762306a36Sopenharmony_ci				 const struct pci_device_id *id)
88862306a36Sopenharmony_ci{
88962306a36Sopenharmony_ci	return comedi_pci_auto_config(dev, &adv_pci1710_driver,
89062306a36Sopenharmony_ci				      id->driver_data);
89162306a36Sopenharmony_ci}
89262306a36Sopenharmony_ci
89362306a36Sopenharmony_cistatic const struct pci_device_id adv_pci1710_pci_table[] = {
89462306a36Sopenharmony_ci	{
89562306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
89662306a36Sopenharmony_ci			       PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050),
89762306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710,
89862306a36Sopenharmony_ci	}, {
89962306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
90062306a36Sopenharmony_ci			       PCI_VENDOR_ID_ADVANTECH, 0x0000),
90162306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710,
90262306a36Sopenharmony_ci	}, {
90362306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
90462306a36Sopenharmony_ci			       PCI_VENDOR_ID_ADVANTECH, 0xb100),
90562306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710,
90662306a36Sopenharmony_ci	}, {
90762306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
90862306a36Sopenharmony_ci			       PCI_VENDOR_ID_ADVANTECH, 0xb200),
90962306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710,
91062306a36Sopenharmony_ci	}, {
91162306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
91262306a36Sopenharmony_ci			       PCI_VENDOR_ID_ADVANTECH, 0xc100),
91362306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710,
91462306a36Sopenharmony_ci	}, {
91562306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
91662306a36Sopenharmony_ci			       PCI_VENDOR_ID_ADVANTECH, 0xc200),
91762306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710,
91862306a36Sopenharmony_ci	}, {
91962306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, 0x1000, 0xd100),
92062306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710,
92162306a36Sopenharmony_ci	}, {
92262306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
92362306a36Sopenharmony_ci			       PCI_VENDOR_ID_ADVANTECH, 0x0002),
92462306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710HG,
92562306a36Sopenharmony_ci	}, {
92662306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
92762306a36Sopenharmony_ci			       PCI_VENDOR_ID_ADVANTECH, 0xb102),
92862306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710HG,
92962306a36Sopenharmony_ci	}, {
93062306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
93162306a36Sopenharmony_ci			       PCI_VENDOR_ID_ADVANTECH, 0xb202),
93262306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710HG,
93362306a36Sopenharmony_ci	}, {
93462306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
93562306a36Sopenharmony_ci			       PCI_VENDOR_ID_ADVANTECH, 0xc102),
93662306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710HG,
93762306a36Sopenharmony_ci	}, {
93862306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
93962306a36Sopenharmony_ci			       PCI_VENDOR_ID_ADVANTECH, 0xc202),
94062306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710HG,
94162306a36Sopenharmony_ci	}, {
94262306a36Sopenharmony_ci		PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, 0x1000, 0xd102),
94362306a36Sopenharmony_ci		.driver_data = BOARD_PCI1710HG,
94462306a36Sopenharmony_ci	},
94562306a36Sopenharmony_ci	{ PCI_VDEVICE(ADVANTECH, 0x1711), BOARD_PCI1711 },
94662306a36Sopenharmony_ci	{ PCI_VDEVICE(ADVANTECH, 0x1713), BOARD_PCI1713 },
94762306a36Sopenharmony_ci	{ PCI_VDEVICE(ADVANTECH, 0x1731), BOARD_PCI1731 },
94862306a36Sopenharmony_ci	{ 0 }
94962306a36Sopenharmony_ci};
95062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, adv_pci1710_pci_table);
95162306a36Sopenharmony_ci
95262306a36Sopenharmony_cistatic struct pci_driver adv_pci1710_pci_driver = {
95362306a36Sopenharmony_ci	.name		= "adv_pci1710",
95462306a36Sopenharmony_ci	.id_table	= adv_pci1710_pci_table,
95562306a36Sopenharmony_ci	.probe		= adv_pci1710_pci_probe,
95662306a36Sopenharmony_ci	.remove		= comedi_pci_auto_unconfig,
95762306a36Sopenharmony_ci};
95862306a36Sopenharmony_cimodule_comedi_pci_driver(adv_pci1710_driver, adv_pci1710_pci_driver);
95962306a36Sopenharmony_ci
96062306a36Sopenharmony_ciMODULE_AUTHOR("Comedi https://www.comedi.org");
96162306a36Sopenharmony_ciMODULE_DESCRIPTION("Comedi: Advantech PCI-1710 Series Multifunction DAS Cards");
96262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
963