18ffdff6aSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
28ffdff6aSGreg Kroah-Hartman /*
38ffdff6aSGreg Kroah-Hartman  * ii_pci20kc.c
48ffdff6aSGreg Kroah-Hartman  * Driver for Intelligent Instruments PCI-20001C carrier board and modules.
58ffdff6aSGreg Kroah-Hartman  *
68ffdff6aSGreg Kroah-Hartman  * Copyright (C) 2000 Markus Kempf <kempf@matsci.uni-sb.de>
78ffdff6aSGreg Kroah-Hartman  * with suggestions from David Schleef		16.06.2000
88ffdff6aSGreg Kroah-Hartman  */
98ffdff6aSGreg Kroah-Hartman 
108ffdff6aSGreg Kroah-Hartman /*
118ffdff6aSGreg Kroah-Hartman  * Driver: ii_pci20kc
128ffdff6aSGreg Kroah-Hartman  * Description: Intelligent Instruments PCI-20001C carrier board
138ffdff6aSGreg Kroah-Hartman  * Devices: [Intelligent Instrumentation] PCI-20001C (ii_pci20kc)
148ffdff6aSGreg Kroah-Hartman  * Author: Markus Kempf <kempf@matsci.uni-sb.de>
158ffdff6aSGreg Kroah-Hartman  * Status: works
168ffdff6aSGreg Kroah-Hartman  *
178ffdff6aSGreg Kroah-Hartman  * Supports the PCI-20001C-1a and PCI-20001C-2a carrier boards. The
188ffdff6aSGreg Kroah-Hartman  * -2a version has 32 on-board DIO channels. Three add-on modules
198ffdff6aSGreg Kroah-Hartman  * can be added to the carrier board for additional functionality.
208ffdff6aSGreg Kroah-Hartman  *
218ffdff6aSGreg Kroah-Hartman  * Supported add-on modules:
228ffdff6aSGreg Kroah-Hartman  *	PCI-20006M-1   1 channel, 16-bit analog output module
238ffdff6aSGreg Kroah-Hartman  *	PCI-20006M-2   2 channel, 16-bit analog output module
248ffdff6aSGreg Kroah-Hartman  *	PCI-20341M-1A  4 channel, 16-bit analog input module
258ffdff6aSGreg Kroah-Hartman  *
268ffdff6aSGreg Kroah-Hartman  * Options:
278ffdff6aSGreg Kroah-Hartman  *   0   Board base address
288ffdff6aSGreg Kroah-Hartman  *   1   IRQ (not-used)
298ffdff6aSGreg Kroah-Hartman  */
308ffdff6aSGreg Kroah-Hartman 
318ffdff6aSGreg Kroah-Hartman #include <linux/module.h>
328ffdff6aSGreg Kroah-Hartman #include <linux/io.h>
33*df0e68c1SIan Abbott #include <linux/comedi/comedidev.h>
348ffdff6aSGreg Kroah-Hartman 
358ffdff6aSGreg Kroah-Hartman /*
368ffdff6aSGreg Kroah-Hartman  * Register I/O map
378ffdff6aSGreg Kroah-Hartman  */
388ffdff6aSGreg Kroah-Hartman #define II20K_SIZE			0x400
398ffdff6aSGreg Kroah-Hartman #define II20K_MOD_OFFSET		0x100
408ffdff6aSGreg Kroah-Hartman #define II20K_ID_REG			0x00
418ffdff6aSGreg Kroah-Hartman #define II20K_ID_MOD1_EMPTY		BIT(7)
428ffdff6aSGreg Kroah-Hartman #define II20K_ID_MOD2_EMPTY		BIT(6)
438ffdff6aSGreg Kroah-Hartman #define II20K_ID_MOD3_EMPTY		BIT(5)
448ffdff6aSGreg Kroah-Hartman #define II20K_ID_MASK			0x1f
458ffdff6aSGreg Kroah-Hartman #define II20K_ID_PCI20001C_1A		0x1b	/* no on-board DIO */
468ffdff6aSGreg Kroah-Hartman #define II20K_ID_PCI20001C_2A		0x1d	/* on-board DIO */
478ffdff6aSGreg Kroah-Hartman #define II20K_MOD_STATUS_REG		0x40
488ffdff6aSGreg Kroah-Hartman #define II20K_MOD_STATUS_IRQ_MOD1	BIT(7)
498ffdff6aSGreg Kroah-Hartman #define II20K_MOD_STATUS_IRQ_MOD2	BIT(6)
508ffdff6aSGreg Kroah-Hartman #define II20K_MOD_STATUS_IRQ_MOD3	BIT(5)
518ffdff6aSGreg Kroah-Hartman #define II20K_DIO0_REG			0x80
528ffdff6aSGreg Kroah-Hartman #define II20K_DIO1_REG			0x81
538ffdff6aSGreg Kroah-Hartman #define II20K_DIR_ENA_REG		0x82
548ffdff6aSGreg Kroah-Hartman #define II20K_DIR_DIO3_OUT		BIT(7)
558ffdff6aSGreg Kroah-Hartman #define II20K_DIR_DIO2_OUT		BIT(6)
568ffdff6aSGreg Kroah-Hartman #define II20K_BUF_DISAB_DIO3		BIT(5)
578ffdff6aSGreg Kroah-Hartman #define II20K_BUF_DISAB_DIO2		BIT(4)
588ffdff6aSGreg Kroah-Hartman #define II20K_DIR_DIO1_OUT		BIT(3)
598ffdff6aSGreg Kroah-Hartman #define II20K_DIR_DIO0_OUT		BIT(2)
608ffdff6aSGreg Kroah-Hartman #define II20K_BUF_DISAB_DIO1		BIT(1)
618ffdff6aSGreg Kroah-Hartman #define II20K_BUF_DISAB_DIO0		BIT(0)
628ffdff6aSGreg Kroah-Hartman #define II20K_CTRL01_REG		0x83
638ffdff6aSGreg Kroah-Hartman #define II20K_CTRL01_SET		BIT(7)
648ffdff6aSGreg Kroah-Hartman #define II20K_CTRL01_DIO0_IN		BIT(4)
658ffdff6aSGreg Kroah-Hartman #define II20K_CTRL01_DIO1_IN		BIT(1)
668ffdff6aSGreg Kroah-Hartman #define II20K_DIO2_REG			0xc0
678ffdff6aSGreg Kroah-Hartman #define II20K_DIO3_REG			0xc1
688ffdff6aSGreg Kroah-Hartman #define II20K_CTRL23_REG		0xc3
698ffdff6aSGreg Kroah-Hartman #define II20K_CTRL23_SET		BIT(7)
708ffdff6aSGreg Kroah-Hartman #define II20K_CTRL23_DIO2_IN		BIT(4)
718ffdff6aSGreg Kroah-Hartman #define II20K_CTRL23_DIO3_IN		BIT(1)
728ffdff6aSGreg Kroah-Hartman 
738ffdff6aSGreg Kroah-Hartman #define II20K_ID_PCI20006M_1		0xe2	/* 1 AO channels */
748ffdff6aSGreg Kroah-Hartman #define II20K_ID_PCI20006M_2		0xe3	/* 2 AO channels */
758ffdff6aSGreg Kroah-Hartman #define II20K_AO_STRB_REG(x)		(0x0b + ((x) * 0x08))
768ffdff6aSGreg Kroah-Hartman #define II20K_AO_LSB_REG(x)		(0x0d + ((x) * 0x08))
778ffdff6aSGreg Kroah-Hartman #define II20K_AO_MSB_REG(x)		(0x0e + ((x) * 0x08))
788ffdff6aSGreg Kroah-Hartman #define II20K_AO_STRB_BOTH_REG		0x1b
798ffdff6aSGreg Kroah-Hartman 
808ffdff6aSGreg Kroah-Hartman #define II20K_ID_PCI20341M_1		0x77	/* 4 AI channels */
818ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_CMD_REG		0x01
828ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_CMD_BUSY	BIT(7)
838ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_CMD_HW_ENA	BIT(1)
848ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_CMD_EXT_START	BIT(0)
858ffdff6aSGreg Kroah-Hartman #define II20K_AI_LSB_REG		0x02
868ffdff6aSGreg Kroah-Hartman #define II20K_AI_MSB_REG		0x03
878ffdff6aSGreg Kroah-Hartman #define II20K_AI_PACER_RESET_REG	0x04
888ffdff6aSGreg Kroah-Hartman #define II20K_AI_16BIT_DATA_REG		0x06
898ffdff6aSGreg Kroah-Hartman #define II20K_AI_CONF_REG		0x10
908ffdff6aSGreg Kroah-Hartman #define II20K_AI_CONF_ENA		BIT(2)
918ffdff6aSGreg Kroah-Hartman #define II20K_AI_OPT_REG		0x11
928ffdff6aSGreg Kroah-Hartman #define II20K_AI_OPT_TRIG_ENA		BIT(5)
938ffdff6aSGreg Kroah-Hartman #define II20K_AI_OPT_TRIG_INV		BIT(4)
948ffdff6aSGreg Kroah-Hartman #define II20K_AI_OPT_TIMEBASE(x)	(((x) & 0x3) << 1)
958ffdff6aSGreg Kroah-Hartman #define II20K_AI_OPT_BURST_MODE		BIT(0)
968ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_REG		0x12
978ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_INT		BIT(7)
988ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_TRIG		BIT(6)
998ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_TRIG_ENA	BIT(5)
1008ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_PACER_ERR	BIT(2)
1018ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_DATA_ERR	BIT(1)
1028ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_SET_TIME_ERR	BIT(0)
1038ffdff6aSGreg Kroah-Hartman #define II20K_AI_LAST_CHAN_ADDR_REG	0x13
1048ffdff6aSGreg Kroah-Hartman #define II20K_AI_CUR_ADDR_REG		0x14
1058ffdff6aSGreg Kroah-Hartman #define II20K_AI_SET_TIME_REG		0x15
1068ffdff6aSGreg Kroah-Hartman #define II20K_AI_DELAY_LSB_REG		0x16
1078ffdff6aSGreg Kroah-Hartman #define II20K_AI_DELAY_MSB_REG		0x17
1088ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHAN_ADV_REG		0x18
1098ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHAN_RESET_REG		0x19
1108ffdff6aSGreg Kroah-Hartman #define II20K_AI_START_TRIG_REG		0x1a
1118ffdff6aSGreg Kroah-Hartman #define II20K_AI_COUNT_RESET_REG	0x1b
1128ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHANLIST_REG		0x80
1138ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHANLIST_ONBOARD_ONLY	BIT(5)
1148ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHANLIST_GAIN(x)	(((x) & 0x3) << 3)
1158ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHANLIST_MUX_ENA	BIT(2)
1168ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHANLIST_CHAN(x)	(((x) & 0x3) << 0)
1178ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHANLIST_LEN		0x80
1188ffdff6aSGreg Kroah-Hartman 
1198ffdff6aSGreg Kroah-Hartman /* the AO range is set by jumpers on the 20006M module */
1208ffdff6aSGreg Kroah-Hartman static const struct comedi_lrange ii20k_ao_ranges = {
1218ffdff6aSGreg Kroah-Hartman 	3, {
1228ffdff6aSGreg Kroah-Hartman 		BIP_RANGE(5),	/* Chan 0 - W1/W3 in   Chan 1 - W2/W4 in  */
1238ffdff6aSGreg Kroah-Hartman 		UNI_RANGE(10),	/* Chan 0 - W1/W3 out  Chan 1 - W2/W4 in  */
1248ffdff6aSGreg Kroah-Hartman 		BIP_RANGE(10)	/* Chan 0 - W1/W3 in   Chan 1 - W2/W4 out */
1258ffdff6aSGreg Kroah-Hartman 	}
1268ffdff6aSGreg Kroah-Hartman };
1278ffdff6aSGreg Kroah-Hartman 
1288ffdff6aSGreg Kroah-Hartman static const struct comedi_lrange ii20k_ai_ranges = {
1298ffdff6aSGreg Kroah-Hartman 	4, {
1308ffdff6aSGreg Kroah-Hartman 		BIP_RANGE(5),		/* gain 1 */
1318ffdff6aSGreg Kroah-Hartman 		BIP_RANGE(0.5),		/* gain 10 */
1328ffdff6aSGreg Kroah-Hartman 		BIP_RANGE(0.05),	/* gain 100 */
1338ffdff6aSGreg Kroah-Hartman 		BIP_RANGE(0.025)	/* gain 200 */
1348ffdff6aSGreg Kroah-Hartman 	},
1358ffdff6aSGreg Kroah-Hartman };
1368ffdff6aSGreg Kroah-Hartman 
ii20k_module_iobase(struct comedi_device * dev,struct comedi_subdevice * s)1378ffdff6aSGreg Kroah-Hartman static void __iomem *ii20k_module_iobase(struct comedi_device *dev,
1388ffdff6aSGreg Kroah-Hartman 					 struct comedi_subdevice *s)
1398ffdff6aSGreg Kroah-Hartman {
1408ffdff6aSGreg Kroah-Hartman 	return dev->mmio + (s->index + 1) * II20K_MOD_OFFSET;
1418ffdff6aSGreg Kroah-Hartman }
1428ffdff6aSGreg Kroah-Hartman 
ii20k_ao_insn_write(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)1438ffdff6aSGreg Kroah-Hartman static int ii20k_ao_insn_write(struct comedi_device *dev,
1448ffdff6aSGreg Kroah-Hartman 			       struct comedi_subdevice *s,
1458ffdff6aSGreg Kroah-Hartman 			       struct comedi_insn *insn,
1468ffdff6aSGreg Kroah-Hartman 			       unsigned int *data)
1478ffdff6aSGreg Kroah-Hartman {
1488ffdff6aSGreg Kroah-Hartman 	void __iomem *iobase = ii20k_module_iobase(dev, s);
1498ffdff6aSGreg Kroah-Hartman 	unsigned int chan = CR_CHAN(insn->chanspec);
1508ffdff6aSGreg Kroah-Hartman 	int i;
1518ffdff6aSGreg Kroah-Hartman 
1528ffdff6aSGreg Kroah-Hartman 	for (i = 0; i < insn->n; i++) {
1538ffdff6aSGreg Kroah-Hartman 		unsigned int val = data[i];
1548ffdff6aSGreg Kroah-Hartman 
1558ffdff6aSGreg Kroah-Hartman 		s->readback[chan] = val;
1568ffdff6aSGreg Kroah-Hartman 
1578ffdff6aSGreg Kroah-Hartman 		/* munge the offset binary data to 2's complement */
1588ffdff6aSGreg Kroah-Hartman 		val = comedi_offset_munge(s, val);
1598ffdff6aSGreg Kroah-Hartman 
1608ffdff6aSGreg Kroah-Hartman 		writeb(val & 0xff, iobase + II20K_AO_LSB_REG(chan));
1618ffdff6aSGreg Kroah-Hartman 		writeb((val >> 8) & 0xff, iobase + II20K_AO_MSB_REG(chan));
1628ffdff6aSGreg Kroah-Hartman 		writeb(0x00, iobase + II20K_AO_STRB_REG(chan));
1638ffdff6aSGreg Kroah-Hartman 	}
1648ffdff6aSGreg Kroah-Hartman 
1658ffdff6aSGreg Kroah-Hartman 	return insn->n;
1668ffdff6aSGreg Kroah-Hartman }
1678ffdff6aSGreg Kroah-Hartman 
ii20k_ai_eoc(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned long context)1688ffdff6aSGreg Kroah-Hartman static int ii20k_ai_eoc(struct comedi_device *dev,
1698ffdff6aSGreg Kroah-Hartman 			struct comedi_subdevice *s,
1708ffdff6aSGreg Kroah-Hartman 			struct comedi_insn *insn,
1718ffdff6aSGreg Kroah-Hartman 			unsigned long context)
1728ffdff6aSGreg Kroah-Hartman {
1738ffdff6aSGreg Kroah-Hartman 	void __iomem *iobase = ii20k_module_iobase(dev, s);
1748ffdff6aSGreg Kroah-Hartman 	unsigned char status;
1758ffdff6aSGreg Kroah-Hartman 
1768ffdff6aSGreg Kroah-Hartman 	status = readb(iobase + II20K_AI_STATUS_REG);
1778ffdff6aSGreg Kroah-Hartman 	if ((status & II20K_AI_STATUS_INT) == 0)
1788ffdff6aSGreg Kroah-Hartman 		return 0;
1798ffdff6aSGreg Kroah-Hartman 	return -EBUSY;
1808ffdff6aSGreg Kroah-Hartman }
1818ffdff6aSGreg Kroah-Hartman 
ii20k_ai_setup(struct comedi_device * dev,struct comedi_subdevice * s,unsigned int chanspec)1828ffdff6aSGreg Kroah-Hartman static void ii20k_ai_setup(struct comedi_device *dev,
1838ffdff6aSGreg Kroah-Hartman 			   struct comedi_subdevice *s,
1848ffdff6aSGreg Kroah-Hartman 			   unsigned int chanspec)
1858ffdff6aSGreg Kroah-Hartman {
1868ffdff6aSGreg Kroah-Hartman 	void __iomem *iobase = ii20k_module_iobase(dev, s);
1878ffdff6aSGreg Kroah-Hartman 	unsigned int chan = CR_CHAN(chanspec);
1888ffdff6aSGreg Kroah-Hartman 	unsigned int range = CR_RANGE(chanspec);
1898ffdff6aSGreg Kroah-Hartman 	unsigned char val;
1908ffdff6aSGreg Kroah-Hartman 
1918ffdff6aSGreg Kroah-Hartman 	/* initialize module */
1928ffdff6aSGreg Kroah-Hartman 	writeb(II20K_AI_CONF_ENA, iobase + II20K_AI_CONF_REG);
1938ffdff6aSGreg Kroah-Hartman 
1948ffdff6aSGreg Kroah-Hartman 	/* software conversion */
1958ffdff6aSGreg Kroah-Hartman 	writeb(0, iobase + II20K_AI_STATUS_CMD_REG);
1968ffdff6aSGreg Kroah-Hartman 
1978ffdff6aSGreg Kroah-Hartman 	/* set the time base for the settling time counter based on the gain */
1988ffdff6aSGreg Kroah-Hartman 	val = (range < 3) ? II20K_AI_OPT_TIMEBASE(0) : II20K_AI_OPT_TIMEBASE(2);
1998ffdff6aSGreg Kroah-Hartman 	writeb(val, iobase + II20K_AI_OPT_REG);
2008ffdff6aSGreg Kroah-Hartman 
2018ffdff6aSGreg Kroah-Hartman 	/* set the settling time counter based on the gain */
2028ffdff6aSGreg Kroah-Hartman 	val = (range < 2) ? 0x58 : (range < 3) ? 0x93 : 0x99;
2038ffdff6aSGreg Kroah-Hartman 	writeb(val, iobase + II20K_AI_SET_TIME_REG);
2048ffdff6aSGreg Kroah-Hartman 
2058ffdff6aSGreg Kroah-Hartman 	/* set number of input channels */
2068ffdff6aSGreg Kroah-Hartman 	writeb(1, iobase + II20K_AI_LAST_CHAN_ADDR_REG);
2078ffdff6aSGreg Kroah-Hartman 
2088ffdff6aSGreg Kroah-Hartman 	/* set the channel list byte */
2098ffdff6aSGreg Kroah-Hartman 	val = II20K_AI_CHANLIST_ONBOARD_ONLY |
2108ffdff6aSGreg Kroah-Hartman 	      II20K_AI_CHANLIST_MUX_ENA |
2118ffdff6aSGreg Kroah-Hartman 	      II20K_AI_CHANLIST_GAIN(range) |
2128ffdff6aSGreg Kroah-Hartman 	      II20K_AI_CHANLIST_CHAN(chan);
2138ffdff6aSGreg Kroah-Hartman 	writeb(val, iobase + II20K_AI_CHANLIST_REG);
2148ffdff6aSGreg Kroah-Hartman 
2158ffdff6aSGreg Kroah-Hartman 	/* reset settling time counter and trigger delay counter */
2168ffdff6aSGreg Kroah-Hartman 	writeb(0, iobase + II20K_AI_COUNT_RESET_REG);
2178ffdff6aSGreg Kroah-Hartman 
2188ffdff6aSGreg Kroah-Hartman 	/* reset channel scanner */
2198ffdff6aSGreg Kroah-Hartman 	writeb(0, iobase + II20K_AI_CHAN_RESET_REG);
2208ffdff6aSGreg Kroah-Hartman }
2218ffdff6aSGreg Kroah-Hartman 
ii20k_ai_insn_read(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)2228ffdff6aSGreg Kroah-Hartman static int ii20k_ai_insn_read(struct comedi_device *dev,
2238ffdff6aSGreg Kroah-Hartman 			      struct comedi_subdevice *s,
2248ffdff6aSGreg Kroah-Hartman 			      struct comedi_insn *insn,
2258ffdff6aSGreg Kroah-Hartman 			      unsigned int *data)
2268ffdff6aSGreg Kroah-Hartman {
2278ffdff6aSGreg Kroah-Hartman 	void __iomem *iobase = ii20k_module_iobase(dev, s);
2288ffdff6aSGreg Kroah-Hartman 	int ret;
2298ffdff6aSGreg Kroah-Hartman 	int i;
2308ffdff6aSGreg Kroah-Hartman 
2318ffdff6aSGreg Kroah-Hartman 	ii20k_ai_setup(dev, s, insn->chanspec);
2328ffdff6aSGreg Kroah-Hartman 
2338ffdff6aSGreg Kroah-Hartman 	for (i = 0; i < insn->n; i++) {
2348ffdff6aSGreg Kroah-Hartman 		unsigned int val;
2358ffdff6aSGreg Kroah-Hartman 
2368ffdff6aSGreg Kroah-Hartman 		/* generate a software start convert signal */
2378ffdff6aSGreg Kroah-Hartman 		readb(iobase + II20K_AI_PACER_RESET_REG);
2388ffdff6aSGreg Kroah-Hartman 
2398ffdff6aSGreg Kroah-Hartman 		ret = comedi_timeout(dev, s, insn, ii20k_ai_eoc, 0);
2408ffdff6aSGreg Kroah-Hartman 		if (ret)
2418ffdff6aSGreg Kroah-Hartman 			return ret;
2428ffdff6aSGreg Kroah-Hartman 
2438ffdff6aSGreg Kroah-Hartman 		val = readb(iobase + II20K_AI_LSB_REG);
2448ffdff6aSGreg Kroah-Hartman 		val |= (readb(iobase + II20K_AI_MSB_REG) << 8);
2458ffdff6aSGreg Kroah-Hartman 
2468ffdff6aSGreg Kroah-Hartman 		/* munge the 2's complement data to offset binary */
2478ffdff6aSGreg Kroah-Hartman 		data[i] = comedi_offset_munge(s, val);
2488ffdff6aSGreg Kroah-Hartman 	}
2498ffdff6aSGreg Kroah-Hartman 
2508ffdff6aSGreg Kroah-Hartman 	return insn->n;
2518ffdff6aSGreg Kroah-Hartman }
2528ffdff6aSGreg Kroah-Hartman 
ii20k_dio_config(struct comedi_device * dev,struct comedi_subdevice * s)2538ffdff6aSGreg Kroah-Hartman static void ii20k_dio_config(struct comedi_device *dev,
2548ffdff6aSGreg Kroah-Hartman 			     struct comedi_subdevice *s)
2558ffdff6aSGreg Kroah-Hartman {
2568ffdff6aSGreg Kroah-Hartman 	unsigned char ctrl01 = 0;
2578ffdff6aSGreg Kroah-Hartman 	unsigned char ctrl23 = 0;
2588ffdff6aSGreg Kroah-Hartman 	unsigned char dir_ena = 0;
2598ffdff6aSGreg Kroah-Hartman 
2608ffdff6aSGreg Kroah-Hartman 	/* port 0 - channels 0-7 */
2618ffdff6aSGreg Kroah-Hartman 	if (s->io_bits & 0x000000ff) {
2628ffdff6aSGreg Kroah-Hartman 		/* output port */
2638ffdff6aSGreg Kroah-Hartman 		ctrl01 &= ~II20K_CTRL01_DIO0_IN;
2648ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_BUF_DISAB_DIO0;
2658ffdff6aSGreg Kroah-Hartman 		dir_ena |= II20K_DIR_DIO0_OUT;
2668ffdff6aSGreg Kroah-Hartman 	} else {
2678ffdff6aSGreg Kroah-Hartman 		/* input port */
2688ffdff6aSGreg Kroah-Hartman 		ctrl01 |= II20K_CTRL01_DIO0_IN;
2698ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_DIR_DIO0_OUT;
2708ffdff6aSGreg Kroah-Hartman 	}
2718ffdff6aSGreg Kroah-Hartman 
2728ffdff6aSGreg Kroah-Hartman 	/* port 1 - channels 8-15 */
2738ffdff6aSGreg Kroah-Hartman 	if (s->io_bits & 0x0000ff00) {
2748ffdff6aSGreg Kroah-Hartman 		/* output port */
2758ffdff6aSGreg Kroah-Hartman 		ctrl01 &= ~II20K_CTRL01_DIO1_IN;
2768ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_BUF_DISAB_DIO1;
2778ffdff6aSGreg Kroah-Hartman 		dir_ena |= II20K_DIR_DIO1_OUT;
2788ffdff6aSGreg Kroah-Hartman 	} else {
2798ffdff6aSGreg Kroah-Hartman 		/* input port */
2808ffdff6aSGreg Kroah-Hartman 		ctrl01 |= II20K_CTRL01_DIO1_IN;
2818ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_DIR_DIO1_OUT;
2828ffdff6aSGreg Kroah-Hartman 	}
2838ffdff6aSGreg Kroah-Hartman 
2848ffdff6aSGreg Kroah-Hartman 	/* port 2 - channels 16-23 */
2858ffdff6aSGreg Kroah-Hartman 	if (s->io_bits & 0x00ff0000) {
2868ffdff6aSGreg Kroah-Hartman 		/* output port */
2878ffdff6aSGreg Kroah-Hartman 		ctrl23 &= ~II20K_CTRL23_DIO2_IN;
2888ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_BUF_DISAB_DIO2;
2898ffdff6aSGreg Kroah-Hartman 		dir_ena |= II20K_DIR_DIO2_OUT;
2908ffdff6aSGreg Kroah-Hartman 	} else {
2918ffdff6aSGreg Kroah-Hartman 		/* input port */
2928ffdff6aSGreg Kroah-Hartman 		ctrl23 |= II20K_CTRL23_DIO2_IN;
2938ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_DIR_DIO2_OUT;
2948ffdff6aSGreg Kroah-Hartman 	}
2958ffdff6aSGreg Kroah-Hartman 
2968ffdff6aSGreg Kroah-Hartman 	/* port 3 - channels 24-31 */
2978ffdff6aSGreg Kroah-Hartman 	if (s->io_bits & 0xff000000) {
2988ffdff6aSGreg Kroah-Hartman 		/* output port */
2998ffdff6aSGreg Kroah-Hartman 		ctrl23 &= ~II20K_CTRL23_DIO3_IN;
3008ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_BUF_DISAB_DIO3;
3018ffdff6aSGreg Kroah-Hartman 		dir_ena |= II20K_DIR_DIO3_OUT;
3028ffdff6aSGreg Kroah-Hartman 	} else {
3038ffdff6aSGreg Kroah-Hartman 		/* input port */
3048ffdff6aSGreg Kroah-Hartman 		ctrl23 |= II20K_CTRL23_DIO3_IN;
3058ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_DIR_DIO3_OUT;
3068ffdff6aSGreg Kroah-Hartman 	}
3078ffdff6aSGreg Kroah-Hartman 
3088ffdff6aSGreg Kroah-Hartman 	ctrl23 |= II20K_CTRL01_SET;
3098ffdff6aSGreg Kroah-Hartman 	ctrl23 |= II20K_CTRL23_SET;
3108ffdff6aSGreg Kroah-Hartman 
3118ffdff6aSGreg Kroah-Hartman 	/* order is important */
3128ffdff6aSGreg Kroah-Hartman 	writeb(ctrl01, dev->mmio + II20K_CTRL01_REG);
3138ffdff6aSGreg Kroah-Hartman 	writeb(ctrl23, dev->mmio + II20K_CTRL23_REG);
3148ffdff6aSGreg Kroah-Hartman 	writeb(dir_ena, dev->mmio + II20K_DIR_ENA_REG);
3158ffdff6aSGreg Kroah-Hartman }
3168ffdff6aSGreg Kroah-Hartman 
ii20k_dio_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)3178ffdff6aSGreg Kroah-Hartman static int ii20k_dio_insn_config(struct comedi_device *dev,
3188ffdff6aSGreg Kroah-Hartman 				 struct comedi_subdevice *s,
3198ffdff6aSGreg Kroah-Hartman 				 struct comedi_insn *insn,
3208ffdff6aSGreg Kroah-Hartman 				 unsigned int *data)
3218ffdff6aSGreg Kroah-Hartman {
3228ffdff6aSGreg Kroah-Hartman 	unsigned int chan = CR_CHAN(insn->chanspec);
3238ffdff6aSGreg Kroah-Hartman 	unsigned int mask;
3248ffdff6aSGreg Kroah-Hartman 	int ret;
3258ffdff6aSGreg Kroah-Hartman 
3268ffdff6aSGreg Kroah-Hartman 	if (chan < 8)
3278ffdff6aSGreg Kroah-Hartman 		mask = 0x000000ff;
3288ffdff6aSGreg Kroah-Hartman 	else if (chan < 16)
3298ffdff6aSGreg Kroah-Hartman 		mask = 0x0000ff00;
3308ffdff6aSGreg Kroah-Hartman 	else if (chan < 24)
3318ffdff6aSGreg Kroah-Hartman 		mask = 0x00ff0000;
3328ffdff6aSGreg Kroah-Hartman 	else
3338ffdff6aSGreg Kroah-Hartman 		mask = 0xff000000;
3348ffdff6aSGreg Kroah-Hartman 
3358ffdff6aSGreg Kroah-Hartman 	ret = comedi_dio_insn_config(dev, s, insn, data, mask);
3368ffdff6aSGreg Kroah-Hartman 	if (ret)
3378ffdff6aSGreg Kroah-Hartman 		return ret;
3388ffdff6aSGreg Kroah-Hartman 
3398ffdff6aSGreg Kroah-Hartman 	ii20k_dio_config(dev, s);
3408ffdff6aSGreg Kroah-Hartman 
3418ffdff6aSGreg Kroah-Hartman 	return insn->n;
3428ffdff6aSGreg Kroah-Hartman }
3438ffdff6aSGreg Kroah-Hartman 
ii20k_dio_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)3448ffdff6aSGreg Kroah-Hartman static int ii20k_dio_insn_bits(struct comedi_device *dev,
3458ffdff6aSGreg Kroah-Hartman 			       struct comedi_subdevice *s,
3468ffdff6aSGreg Kroah-Hartman 			       struct comedi_insn *insn,
3478ffdff6aSGreg Kroah-Hartman 			       unsigned int *data)
3488ffdff6aSGreg Kroah-Hartman {
3498ffdff6aSGreg Kroah-Hartman 	unsigned int mask;
3508ffdff6aSGreg Kroah-Hartman 
3518ffdff6aSGreg Kroah-Hartman 	mask = comedi_dio_update_state(s, data);
3528ffdff6aSGreg Kroah-Hartman 	if (mask) {
3538ffdff6aSGreg Kroah-Hartman 		if (mask & 0x000000ff)
3548ffdff6aSGreg Kroah-Hartman 			writeb((s->state >> 0) & 0xff,
3558ffdff6aSGreg Kroah-Hartman 			       dev->mmio + II20K_DIO0_REG);
3568ffdff6aSGreg Kroah-Hartman 		if (mask & 0x0000ff00)
3578ffdff6aSGreg Kroah-Hartman 			writeb((s->state >> 8) & 0xff,
3588ffdff6aSGreg Kroah-Hartman 			       dev->mmio + II20K_DIO1_REG);
3598ffdff6aSGreg Kroah-Hartman 		if (mask & 0x00ff0000)
3608ffdff6aSGreg Kroah-Hartman 			writeb((s->state >> 16) & 0xff,
3618ffdff6aSGreg Kroah-Hartman 			       dev->mmio + II20K_DIO2_REG);
3628ffdff6aSGreg Kroah-Hartman 		if (mask & 0xff000000)
3638ffdff6aSGreg Kroah-Hartman 			writeb((s->state >> 24) & 0xff,
3648ffdff6aSGreg Kroah-Hartman 			       dev->mmio + II20K_DIO3_REG);
3658ffdff6aSGreg Kroah-Hartman 	}
3668ffdff6aSGreg Kroah-Hartman 
3678ffdff6aSGreg Kroah-Hartman 	data[1] = readb(dev->mmio + II20K_DIO0_REG);
3688ffdff6aSGreg Kroah-Hartman 	data[1] |= readb(dev->mmio + II20K_DIO1_REG) << 8;
3698ffdff6aSGreg Kroah-Hartman 	data[1] |= readb(dev->mmio + II20K_DIO2_REG) << 16;
3708ffdff6aSGreg Kroah-Hartman 	data[1] |= readb(dev->mmio + II20K_DIO3_REG) << 24;
3718ffdff6aSGreg Kroah-Hartman 
3728ffdff6aSGreg Kroah-Hartman 	return insn->n;
3738ffdff6aSGreg Kroah-Hartman }
3748ffdff6aSGreg Kroah-Hartman 
ii20k_init_module(struct comedi_device * dev,struct comedi_subdevice * s)3758ffdff6aSGreg Kroah-Hartman static int ii20k_init_module(struct comedi_device *dev,
3768ffdff6aSGreg Kroah-Hartman 			     struct comedi_subdevice *s)
3778ffdff6aSGreg Kroah-Hartman {
3788ffdff6aSGreg Kroah-Hartman 	void __iomem *iobase = ii20k_module_iobase(dev, s);
3798ffdff6aSGreg Kroah-Hartman 	unsigned char id;
3808ffdff6aSGreg Kroah-Hartman 	int ret;
3818ffdff6aSGreg Kroah-Hartman 
3828ffdff6aSGreg Kroah-Hartman 	id = readb(iobase + II20K_ID_REG);
3838ffdff6aSGreg Kroah-Hartman 	switch (id) {
3848ffdff6aSGreg Kroah-Hartman 	case II20K_ID_PCI20006M_1:
3858ffdff6aSGreg Kroah-Hartman 	case II20K_ID_PCI20006M_2:
3868ffdff6aSGreg Kroah-Hartman 		/* Analog Output subdevice */
3878ffdff6aSGreg Kroah-Hartman 		s->type		= COMEDI_SUBD_AO;
3888ffdff6aSGreg Kroah-Hartman 		s->subdev_flags	= SDF_WRITABLE;
3898ffdff6aSGreg Kroah-Hartman 		s->n_chan	= (id == II20K_ID_PCI20006M_2) ? 2 : 1;
3908ffdff6aSGreg Kroah-Hartman 		s->maxdata	= 0xffff;
3918ffdff6aSGreg Kroah-Hartman 		s->range_table	= &ii20k_ao_ranges;
3928ffdff6aSGreg Kroah-Hartman 		s->insn_write	= ii20k_ao_insn_write;
3938ffdff6aSGreg Kroah-Hartman 
3948ffdff6aSGreg Kroah-Hartman 		ret = comedi_alloc_subdev_readback(s);
3958ffdff6aSGreg Kroah-Hartman 		if (ret)
3968ffdff6aSGreg Kroah-Hartman 			return ret;
3978ffdff6aSGreg Kroah-Hartman 		break;
3988ffdff6aSGreg Kroah-Hartman 	case II20K_ID_PCI20341M_1:
3998ffdff6aSGreg Kroah-Hartman 		/* Analog Input subdevice */
4008ffdff6aSGreg Kroah-Hartman 		s->type		= COMEDI_SUBD_AI;
4018ffdff6aSGreg Kroah-Hartman 		s->subdev_flags	= SDF_READABLE | SDF_DIFF;
4028ffdff6aSGreg Kroah-Hartman 		s->n_chan	= 4;
4038ffdff6aSGreg Kroah-Hartman 		s->maxdata	= 0xffff;
4048ffdff6aSGreg Kroah-Hartman 		s->range_table	= &ii20k_ai_ranges;
4058ffdff6aSGreg Kroah-Hartman 		s->insn_read	= ii20k_ai_insn_read;
4068ffdff6aSGreg Kroah-Hartman 		break;
4078ffdff6aSGreg Kroah-Hartman 	default:
4088ffdff6aSGreg Kroah-Hartman 		s->type = COMEDI_SUBD_UNUSED;
4098ffdff6aSGreg Kroah-Hartman 		break;
4108ffdff6aSGreg Kroah-Hartman 	}
4118ffdff6aSGreg Kroah-Hartman 
4128ffdff6aSGreg Kroah-Hartman 	return 0;
4138ffdff6aSGreg Kroah-Hartman }
4148ffdff6aSGreg Kroah-Hartman 
ii20k_attach(struct comedi_device * dev,struct comedi_devconfig * it)4158ffdff6aSGreg Kroah-Hartman static int ii20k_attach(struct comedi_device *dev,
4168ffdff6aSGreg Kroah-Hartman 			struct comedi_devconfig *it)
4178ffdff6aSGreg Kroah-Hartman {
4188ffdff6aSGreg Kroah-Hartman 	struct comedi_subdevice *s;
4198ffdff6aSGreg Kroah-Hartman 	unsigned int membase;
4208ffdff6aSGreg Kroah-Hartman 	unsigned char id;
4218ffdff6aSGreg Kroah-Hartman 	bool has_dio;
4228ffdff6aSGreg Kroah-Hartman 	int ret;
4238ffdff6aSGreg Kroah-Hartman 
4248ffdff6aSGreg Kroah-Hartman 	membase = it->options[0];
4258ffdff6aSGreg Kroah-Hartman 	if (!membase || (membase & ~(0x100000 - II20K_SIZE))) {
4268ffdff6aSGreg Kroah-Hartman 		dev_warn(dev->class_dev,
4278ffdff6aSGreg Kroah-Hartman 			 "%s: invalid memory address specified\n",
4288ffdff6aSGreg Kroah-Hartman 			 dev->board_name);
4298ffdff6aSGreg Kroah-Hartman 		return -EINVAL;
4308ffdff6aSGreg Kroah-Hartman 	}
4318ffdff6aSGreg Kroah-Hartman 
4328ffdff6aSGreg Kroah-Hartman 	if (!request_mem_region(membase, II20K_SIZE, dev->board_name)) {
4338ffdff6aSGreg Kroah-Hartman 		dev_warn(dev->class_dev, "%s: I/O mem conflict (%#x,%u)\n",
4348ffdff6aSGreg Kroah-Hartman 			 dev->board_name, membase, II20K_SIZE);
4358ffdff6aSGreg Kroah-Hartman 		return -EIO;
4368ffdff6aSGreg Kroah-Hartman 	}
4378ffdff6aSGreg Kroah-Hartman 	dev->iobase = membase;	/* actually, a memory address */
4388ffdff6aSGreg Kroah-Hartman 
4398ffdff6aSGreg Kroah-Hartman 	dev->mmio = ioremap(membase, II20K_SIZE);
4408ffdff6aSGreg Kroah-Hartman 	if (!dev->mmio)
4418ffdff6aSGreg Kroah-Hartman 		return -ENOMEM;
4428ffdff6aSGreg Kroah-Hartman 
4438ffdff6aSGreg Kroah-Hartman 	id = readb(dev->mmio + II20K_ID_REG);
4448ffdff6aSGreg Kroah-Hartman 	switch (id & II20K_ID_MASK) {
4458ffdff6aSGreg Kroah-Hartman 	case II20K_ID_PCI20001C_1A:
4468ffdff6aSGreg Kroah-Hartman 		has_dio = false;
4478ffdff6aSGreg Kroah-Hartman 		break;
4488ffdff6aSGreg Kroah-Hartman 	case II20K_ID_PCI20001C_2A:
4498ffdff6aSGreg Kroah-Hartman 		has_dio = true;
4508ffdff6aSGreg Kroah-Hartman 		break;
4518ffdff6aSGreg Kroah-Hartman 	default:
4528ffdff6aSGreg Kroah-Hartman 		return -ENODEV;
4538ffdff6aSGreg Kroah-Hartman 	}
4548ffdff6aSGreg Kroah-Hartman 
4558ffdff6aSGreg Kroah-Hartman 	ret = comedi_alloc_subdevices(dev, 4);
4568ffdff6aSGreg Kroah-Hartman 	if (ret)
4578ffdff6aSGreg Kroah-Hartman 		return ret;
4588ffdff6aSGreg Kroah-Hartman 
4598ffdff6aSGreg Kroah-Hartman 	s = &dev->subdevices[0];
4608ffdff6aSGreg Kroah-Hartman 	if (id & II20K_ID_MOD1_EMPTY) {
4618ffdff6aSGreg Kroah-Hartman 		s->type = COMEDI_SUBD_UNUSED;
4628ffdff6aSGreg Kroah-Hartman 	} else {
4638ffdff6aSGreg Kroah-Hartman 		ret = ii20k_init_module(dev, s);
4648ffdff6aSGreg Kroah-Hartman 		if (ret)
4658ffdff6aSGreg Kroah-Hartman 			return ret;
4668ffdff6aSGreg Kroah-Hartman 	}
4678ffdff6aSGreg Kroah-Hartman 
4688ffdff6aSGreg Kroah-Hartman 	s = &dev->subdevices[1];
4698ffdff6aSGreg Kroah-Hartman 	if (id & II20K_ID_MOD2_EMPTY) {
4708ffdff6aSGreg Kroah-Hartman 		s->type = COMEDI_SUBD_UNUSED;
4718ffdff6aSGreg Kroah-Hartman 	} else {
4728ffdff6aSGreg Kroah-Hartman 		ret = ii20k_init_module(dev, s);
4738ffdff6aSGreg Kroah-Hartman 		if (ret)
4748ffdff6aSGreg Kroah-Hartman 			return ret;
4758ffdff6aSGreg Kroah-Hartman 	}
4768ffdff6aSGreg Kroah-Hartman 
4778ffdff6aSGreg Kroah-Hartman 	s = &dev->subdevices[2];
4788ffdff6aSGreg Kroah-Hartman 	if (id & II20K_ID_MOD3_EMPTY) {
4798ffdff6aSGreg Kroah-Hartman 		s->type = COMEDI_SUBD_UNUSED;
4808ffdff6aSGreg Kroah-Hartman 	} else {
4818ffdff6aSGreg Kroah-Hartman 		ret = ii20k_init_module(dev, s);
4828ffdff6aSGreg Kroah-Hartman 		if (ret)
4838ffdff6aSGreg Kroah-Hartman 			return ret;
4848ffdff6aSGreg Kroah-Hartman 	}
4858ffdff6aSGreg Kroah-Hartman 
4868ffdff6aSGreg Kroah-Hartman 	/* Digital I/O subdevice */
4878ffdff6aSGreg Kroah-Hartman 	s = &dev->subdevices[3];
4888ffdff6aSGreg Kroah-Hartman 	if (has_dio) {
4898ffdff6aSGreg Kroah-Hartman 		s->type		= COMEDI_SUBD_DIO;
4908ffdff6aSGreg Kroah-Hartman 		s->subdev_flags	= SDF_READABLE | SDF_WRITABLE;
4918ffdff6aSGreg Kroah-Hartman 		s->n_chan	= 32;
4928ffdff6aSGreg Kroah-Hartman 		s->maxdata	= 1;
4938ffdff6aSGreg Kroah-Hartman 		s->range_table	= &range_digital;
4948ffdff6aSGreg Kroah-Hartman 		s->insn_bits	= ii20k_dio_insn_bits;
4958ffdff6aSGreg Kroah-Hartman 		s->insn_config	= ii20k_dio_insn_config;
4968ffdff6aSGreg Kroah-Hartman 
4978ffdff6aSGreg Kroah-Hartman 		/* default all channels to input */
4988ffdff6aSGreg Kroah-Hartman 		ii20k_dio_config(dev, s);
4998ffdff6aSGreg Kroah-Hartman 	} else {
5008ffdff6aSGreg Kroah-Hartman 		s->type = COMEDI_SUBD_UNUSED;
5018ffdff6aSGreg Kroah-Hartman 	}
5028ffdff6aSGreg Kroah-Hartman 
5038ffdff6aSGreg Kroah-Hartman 	return 0;
5048ffdff6aSGreg Kroah-Hartman }
5058ffdff6aSGreg Kroah-Hartman 
ii20k_detach(struct comedi_device * dev)5068ffdff6aSGreg Kroah-Hartman static void ii20k_detach(struct comedi_device *dev)
5078ffdff6aSGreg Kroah-Hartman {
5088ffdff6aSGreg Kroah-Hartman 	if (dev->mmio)
5098ffdff6aSGreg Kroah-Hartman 		iounmap(dev->mmio);
5108ffdff6aSGreg Kroah-Hartman 	if (dev->iobase)	/* actually, a memory address */
5118ffdff6aSGreg Kroah-Hartman 		release_mem_region(dev->iobase, II20K_SIZE);
5128ffdff6aSGreg Kroah-Hartman }
5138ffdff6aSGreg Kroah-Hartman 
5148ffdff6aSGreg Kroah-Hartman static struct comedi_driver ii20k_driver = {
5158ffdff6aSGreg Kroah-Hartman 	.driver_name	= "ii_pci20kc",
5168ffdff6aSGreg Kroah-Hartman 	.module		= THIS_MODULE,
5178ffdff6aSGreg Kroah-Hartman 	.attach		= ii20k_attach,
5188ffdff6aSGreg Kroah-Hartman 	.detach		= ii20k_detach,
5198ffdff6aSGreg Kroah-Hartman };
5208ffdff6aSGreg Kroah-Hartman module_comedi_driver(ii20k_driver);
5218ffdff6aSGreg Kroah-Hartman 
5228ffdff6aSGreg Kroah-Hartman MODULE_AUTHOR("Comedi https://www.comedi.org");
5238ffdff6aSGreg Kroah-Hartman MODULE_DESCRIPTION("Comedi driver for Intelligent Instruments PCI-20001C");
5248ffdff6aSGreg Kroah-Hartman MODULE_LICENSE("GPL");
525