1*8ffdff6aSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2*8ffdff6aSGreg Kroah-Hartman /*
3*8ffdff6aSGreg Kroah-Hartman  * ii_pci20kc.c
4*8ffdff6aSGreg Kroah-Hartman  * Driver for Intelligent Instruments PCI-20001C carrier board and modules.
5*8ffdff6aSGreg Kroah-Hartman  *
6*8ffdff6aSGreg Kroah-Hartman  * Copyright (C) 2000 Markus Kempf <kempf@matsci.uni-sb.de>
7*8ffdff6aSGreg Kroah-Hartman  * with suggestions from David Schleef		16.06.2000
8*8ffdff6aSGreg Kroah-Hartman  */
9*8ffdff6aSGreg Kroah-Hartman 
10*8ffdff6aSGreg Kroah-Hartman /*
11*8ffdff6aSGreg Kroah-Hartman  * Driver: ii_pci20kc
12*8ffdff6aSGreg Kroah-Hartman  * Description: Intelligent Instruments PCI-20001C carrier board
13*8ffdff6aSGreg Kroah-Hartman  * Devices: [Intelligent Instrumentation] PCI-20001C (ii_pci20kc)
14*8ffdff6aSGreg Kroah-Hartman  * Author: Markus Kempf <kempf@matsci.uni-sb.de>
15*8ffdff6aSGreg Kroah-Hartman  * Status: works
16*8ffdff6aSGreg Kroah-Hartman  *
17*8ffdff6aSGreg Kroah-Hartman  * Supports the PCI-20001C-1a and PCI-20001C-2a carrier boards. The
18*8ffdff6aSGreg Kroah-Hartman  * -2a version has 32 on-board DIO channels. Three add-on modules
19*8ffdff6aSGreg Kroah-Hartman  * can be added to the carrier board for additional functionality.
20*8ffdff6aSGreg Kroah-Hartman  *
21*8ffdff6aSGreg Kroah-Hartman  * Supported add-on modules:
22*8ffdff6aSGreg Kroah-Hartman  *	PCI-20006M-1   1 channel, 16-bit analog output module
23*8ffdff6aSGreg Kroah-Hartman  *	PCI-20006M-2   2 channel, 16-bit analog output module
24*8ffdff6aSGreg Kroah-Hartman  *	PCI-20341M-1A  4 channel, 16-bit analog input module
25*8ffdff6aSGreg Kroah-Hartman  *
26*8ffdff6aSGreg Kroah-Hartman  * Options:
27*8ffdff6aSGreg Kroah-Hartman  *   0   Board base address
28*8ffdff6aSGreg Kroah-Hartman  *   1   IRQ (not-used)
29*8ffdff6aSGreg Kroah-Hartman  */
30*8ffdff6aSGreg Kroah-Hartman 
31*8ffdff6aSGreg Kroah-Hartman #include <linux/module.h>
32*8ffdff6aSGreg Kroah-Hartman #include <linux/io.h>
33*8ffdff6aSGreg Kroah-Hartman #include "../comedidev.h"
34*8ffdff6aSGreg Kroah-Hartman 
35*8ffdff6aSGreg Kroah-Hartman /*
36*8ffdff6aSGreg Kroah-Hartman  * Register I/O map
37*8ffdff6aSGreg Kroah-Hartman  */
38*8ffdff6aSGreg Kroah-Hartman #define II20K_SIZE			0x400
39*8ffdff6aSGreg Kroah-Hartman #define II20K_MOD_OFFSET		0x100
40*8ffdff6aSGreg Kroah-Hartman #define II20K_ID_REG			0x00
41*8ffdff6aSGreg Kroah-Hartman #define II20K_ID_MOD1_EMPTY		BIT(7)
42*8ffdff6aSGreg Kroah-Hartman #define II20K_ID_MOD2_EMPTY		BIT(6)
43*8ffdff6aSGreg Kroah-Hartman #define II20K_ID_MOD3_EMPTY		BIT(5)
44*8ffdff6aSGreg Kroah-Hartman #define II20K_ID_MASK			0x1f
45*8ffdff6aSGreg Kroah-Hartman #define II20K_ID_PCI20001C_1A		0x1b	/* no on-board DIO */
46*8ffdff6aSGreg Kroah-Hartman #define II20K_ID_PCI20001C_2A		0x1d	/* on-board DIO */
47*8ffdff6aSGreg Kroah-Hartman #define II20K_MOD_STATUS_REG		0x40
48*8ffdff6aSGreg Kroah-Hartman #define II20K_MOD_STATUS_IRQ_MOD1	BIT(7)
49*8ffdff6aSGreg Kroah-Hartman #define II20K_MOD_STATUS_IRQ_MOD2	BIT(6)
50*8ffdff6aSGreg Kroah-Hartman #define II20K_MOD_STATUS_IRQ_MOD3	BIT(5)
51*8ffdff6aSGreg Kroah-Hartman #define II20K_DIO0_REG			0x80
52*8ffdff6aSGreg Kroah-Hartman #define II20K_DIO1_REG			0x81
53*8ffdff6aSGreg Kroah-Hartman #define II20K_DIR_ENA_REG		0x82
54*8ffdff6aSGreg Kroah-Hartman #define II20K_DIR_DIO3_OUT		BIT(7)
55*8ffdff6aSGreg Kroah-Hartman #define II20K_DIR_DIO2_OUT		BIT(6)
56*8ffdff6aSGreg Kroah-Hartman #define II20K_BUF_DISAB_DIO3		BIT(5)
57*8ffdff6aSGreg Kroah-Hartman #define II20K_BUF_DISAB_DIO2		BIT(4)
58*8ffdff6aSGreg Kroah-Hartman #define II20K_DIR_DIO1_OUT		BIT(3)
59*8ffdff6aSGreg Kroah-Hartman #define II20K_DIR_DIO0_OUT		BIT(2)
60*8ffdff6aSGreg Kroah-Hartman #define II20K_BUF_DISAB_DIO1		BIT(1)
61*8ffdff6aSGreg Kroah-Hartman #define II20K_BUF_DISAB_DIO0		BIT(0)
62*8ffdff6aSGreg Kroah-Hartman #define II20K_CTRL01_REG		0x83
63*8ffdff6aSGreg Kroah-Hartman #define II20K_CTRL01_SET		BIT(7)
64*8ffdff6aSGreg Kroah-Hartman #define II20K_CTRL01_DIO0_IN		BIT(4)
65*8ffdff6aSGreg Kroah-Hartman #define II20K_CTRL01_DIO1_IN		BIT(1)
66*8ffdff6aSGreg Kroah-Hartman #define II20K_DIO2_REG			0xc0
67*8ffdff6aSGreg Kroah-Hartman #define II20K_DIO3_REG			0xc1
68*8ffdff6aSGreg Kroah-Hartman #define II20K_CTRL23_REG		0xc3
69*8ffdff6aSGreg Kroah-Hartman #define II20K_CTRL23_SET		BIT(7)
70*8ffdff6aSGreg Kroah-Hartman #define II20K_CTRL23_DIO2_IN		BIT(4)
71*8ffdff6aSGreg Kroah-Hartman #define II20K_CTRL23_DIO3_IN		BIT(1)
72*8ffdff6aSGreg Kroah-Hartman 
73*8ffdff6aSGreg Kroah-Hartman #define II20K_ID_PCI20006M_1		0xe2	/* 1 AO channels */
74*8ffdff6aSGreg Kroah-Hartman #define II20K_ID_PCI20006M_2		0xe3	/* 2 AO channels */
75*8ffdff6aSGreg Kroah-Hartman #define II20K_AO_STRB_REG(x)		(0x0b + ((x) * 0x08))
76*8ffdff6aSGreg Kroah-Hartman #define II20K_AO_LSB_REG(x)		(0x0d + ((x) * 0x08))
77*8ffdff6aSGreg Kroah-Hartman #define II20K_AO_MSB_REG(x)		(0x0e + ((x) * 0x08))
78*8ffdff6aSGreg Kroah-Hartman #define II20K_AO_STRB_BOTH_REG		0x1b
79*8ffdff6aSGreg Kroah-Hartman 
80*8ffdff6aSGreg Kroah-Hartman #define II20K_ID_PCI20341M_1		0x77	/* 4 AI channels */
81*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_CMD_REG		0x01
82*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_CMD_BUSY	BIT(7)
83*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_CMD_HW_ENA	BIT(1)
84*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_CMD_EXT_START	BIT(0)
85*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_LSB_REG		0x02
86*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_MSB_REG		0x03
87*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_PACER_RESET_REG	0x04
88*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_16BIT_DATA_REG		0x06
89*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_CONF_REG		0x10
90*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_CONF_ENA		BIT(2)
91*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_OPT_REG		0x11
92*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_OPT_TRIG_ENA		BIT(5)
93*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_OPT_TRIG_INV		BIT(4)
94*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_OPT_TIMEBASE(x)	(((x) & 0x3) << 1)
95*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_OPT_BURST_MODE		BIT(0)
96*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_REG		0x12
97*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_INT		BIT(7)
98*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_TRIG		BIT(6)
99*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_TRIG_ENA	BIT(5)
100*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_PACER_ERR	BIT(2)
101*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_DATA_ERR	BIT(1)
102*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_STATUS_SET_TIME_ERR	BIT(0)
103*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_LAST_CHAN_ADDR_REG	0x13
104*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_CUR_ADDR_REG		0x14
105*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_SET_TIME_REG		0x15
106*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_DELAY_LSB_REG		0x16
107*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_DELAY_MSB_REG		0x17
108*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHAN_ADV_REG		0x18
109*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHAN_RESET_REG		0x19
110*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_START_TRIG_REG		0x1a
111*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_COUNT_RESET_REG	0x1b
112*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHANLIST_REG		0x80
113*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHANLIST_ONBOARD_ONLY	BIT(5)
114*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHANLIST_GAIN(x)	(((x) & 0x3) << 3)
115*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHANLIST_MUX_ENA	BIT(2)
116*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHANLIST_CHAN(x)	(((x) & 0x3) << 0)
117*8ffdff6aSGreg Kroah-Hartman #define II20K_AI_CHANLIST_LEN		0x80
118*8ffdff6aSGreg Kroah-Hartman 
119*8ffdff6aSGreg Kroah-Hartman /* the AO range is set by jumpers on the 20006M module */
120*8ffdff6aSGreg Kroah-Hartman static const struct comedi_lrange ii20k_ao_ranges = {
121*8ffdff6aSGreg Kroah-Hartman 	3, {
122*8ffdff6aSGreg Kroah-Hartman 		BIP_RANGE(5),	/* Chan 0 - W1/W3 in   Chan 1 - W2/W4 in  */
123*8ffdff6aSGreg Kroah-Hartman 		UNI_RANGE(10),	/* Chan 0 - W1/W3 out  Chan 1 - W2/W4 in  */
124*8ffdff6aSGreg Kroah-Hartman 		BIP_RANGE(10)	/* Chan 0 - W1/W3 in   Chan 1 - W2/W4 out */
125*8ffdff6aSGreg Kroah-Hartman 	}
126*8ffdff6aSGreg Kroah-Hartman };
127*8ffdff6aSGreg Kroah-Hartman 
128*8ffdff6aSGreg Kroah-Hartman static const struct comedi_lrange ii20k_ai_ranges = {
129*8ffdff6aSGreg Kroah-Hartman 	4, {
130*8ffdff6aSGreg Kroah-Hartman 		BIP_RANGE(5),		/* gain 1 */
131*8ffdff6aSGreg Kroah-Hartman 		BIP_RANGE(0.5),		/* gain 10 */
132*8ffdff6aSGreg Kroah-Hartman 		BIP_RANGE(0.05),	/* gain 100 */
133*8ffdff6aSGreg Kroah-Hartman 		BIP_RANGE(0.025)	/* gain 200 */
134*8ffdff6aSGreg Kroah-Hartman 	},
135*8ffdff6aSGreg Kroah-Hartman };
136*8ffdff6aSGreg Kroah-Hartman 
137*8ffdff6aSGreg Kroah-Hartman static void __iomem *ii20k_module_iobase(struct comedi_device *dev,
138*8ffdff6aSGreg Kroah-Hartman 					 struct comedi_subdevice *s)
139*8ffdff6aSGreg Kroah-Hartman {
140*8ffdff6aSGreg Kroah-Hartman 	return dev->mmio + (s->index + 1) * II20K_MOD_OFFSET;
141*8ffdff6aSGreg Kroah-Hartman }
142*8ffdff6aSGreg Kroah-Hartman 
143*8ffdff6aSGreg Kroah-Hartman static int ii20k_ao_insn_write(struct comedi_device *dev,
144*8ffdff6aSGreg Kroah-Hartman 			       struct comedi_subdevice *s,
145*8ffdff6aSGreg Kroah-Hartman 			       struct comedi_insn *insn,
146*8ffdff6aSGreg Kroah-Hartman 			       unsigned int *data)
147*8ffdff6aSGreg Kroah-Hartman {
148*8ffdff6aSGreg Kroah-Hartman 	void __iomem *iobase = ii20k_module_iobase(dev, s);
149*8ffdff6aSGreg Kroah-Hartman 	unsigned int chan = CR_CHAN(insn->chanspec);
150*8ffdff6aSGreg Kroah-Hartman 	int i;
151*8ffdff6aSGreg Kroah-Hartman 
152*8ffdff6aSGreg Kroah-Hartman 	for (i = 0; i < insn->n; i++) {
153*8ffdff6aSGreg Kroah-Hartman 		unsigned int val = data[i];
154*8ffdff6aSGreg Kroah-Hartman 
155*8ffdff6aSGreg Kroah-Hartman 		s->readback[chan] = val;
156*8ffdff6aSGreg Kroah-Hartman 
157*8ffdff6aSGreg Kroah-Hartman 		/* munge the offset binary data to 2's complement */
158*8ffdff6aSGreg Kroah-Hartman 		val = comedi_offset_munge(s, val);
159*8ffdff6aSGreg Kroah-Hartman 
160*8ffdff6aSGreg Kroah-Hartman 		writeb(val & 0xff, iobase + II20K_AO_LSB_REG(chan));
161*8ffdff6aSGreg Kroah-Hartman 		writeb((val >> 8) & 0xff, iobase + II20K_AO_MSB_REG(chan));
162*8ffdff6aSGreg Kroah-Hartman 		writeb(0x00, iobase + II20K_AO_STRB_REG(chan));
163*8ffdff6aSGreg Kroah-Hartman 	}
164*8ffdff6aSGreg Kroah-Hartman 
165*8ffdff6aSGreg Kroah-Hartman 	return insn->n;
166*8ffdff6aSGreg Kroah-Hartman }
167*8ffdff6aSGreg Kroah-Hartman 
168*8ffdff6aSGreg Kroah-Hartman static int ii20k_ai_eoc(struct comedi_device *dev,
169*8ffdff6aSGreg Kroah-Hartman 			struct comedi_subdevice *s,
170*8ffdff6aSGreg Kroah-Hartman 			struct comedi_insn *insn,
171*8ffdff6aSGreg Kroah-Hartman 			unsigned long context)
172*8ffdff6aSGreg Kroah-Hartman {
173*8ffdff6aSGreg Kroah-Hartman 	void __iomem *iobase = ii20k_module_iobase(dev, s);
174*8ffdff6aSGreg Kroah-Hartman 	unsigned char status;
175*8ffdff6aSGreg Kroah-Hartman 
176*8ffdff6aSGreg Kroah-Hartman 	status = readb(iobase + II20K_AI_STATUS_REG);
177*8ffdff6aSGreg Kroah-Hartman 	if ((status & II20K_AI_STATUS_INT) == 0)
178*8ffdff6aSGreg Kroah-Hartman 		return 0;
179*8ffdff6aSGreg Kroah-Hartman 	return -EBUSY;
180*8ffdff6aSGreg Kroah-Hartman }
181*8ffdff6aSGreg Kroah-Hartman 
182*8ffdff6aSGreg Kroah-Hartman static void ii20k_ai_setup(struct comedi_device *dev,
183*8ffdff6aSGreg Kroah-Hartman 			   struct comedi_subdevice *s,
184*8ffdff6aSGreg Kroah-Hartman 			   unsigned int chanspec)
185*8ffdff6aSGreg Kroah-Hartman {
186*8ffdff6aSGreg Kroah-Hartman 	void __iomem *iobase = ii20k_module_iobase(dev, s);
187*8ffdff6aSGreg Kroah-Hartman 	unsigned int chan = CR_CHAN(chanspec);
188*8ffdff6aSGreg Kroah-Hartman 	unsigned int range = CR_RANGE(chanspec);
189*8ffdff6aSGreg Kroah-Hartman 	unsigned char val;
190*8ffdff6aSGreg Kroah-Hartman 
191*8ffdff6aSGreg Kroah-Hartman 	/* initialize module */
192*8ffdff6aSGreg Kroah-Hartman 	writeb(II20K_AI_CONF_ENA, iobase + II20K_AI_CONF_REG);
193*8ffdff6aSGreg Kroah-Hartman 
194*8ffdff6aSGreg Kroah-Hartman 	/* software conversion */
195*8ffdff6aSGreg Kroah-Hartman 	writeb(0, iobase + II20K_AI_STATUS_CMD_REG);
196*8ffdff6aSGreg Kroah-Hartman 
197*8ffdff6aSGreg Kroah-Hartman 	/* set the time base for the settling time counter based on the gain */
198*8ffdff6aSGreg Kroah-Hartman 	val = (range < 3) ? II20K_AI_OPT_TIMEBASE(0) : II20K_AI_OPT_TIMEBASE(2);
199*8ffdff6aSGreg Kroah-Hartman 	writeb(val, iobase + II20K_AI_OPT_REG);
200*8ffdff6aSGreg Kroah-Hartman 
201*8ffdff6aSGreg Kroah-Hartman 	/* set the settling time counter based on the gain */
202*8ffdff6aSGreg Kroah-Hartman 	val = (range < 2) ? 0x58 : (range < 3) ? 0x93 : 0x99;
203*8ffdff6aSGreg Kroah-Hartman 	writeb(val, iobase + II20K_AI_SET_TIME_REG);
204*8ffdff6aSGreg Kroah-Hartman 
205*8ffdff6aSGreg Kroah-Hartman 	/* set number of input channels */
206*8ffdff6aSGreg Kroah-Hartman 	writeb(1, iobase + II20K_AI_LAST_CHAN_ADDR_REG);
207*8ffdff6aSGreg Kroah-Hartman 
208*8ffdff6aSGreg Kroah-Hartman 	/* set the channel list byte */
209*8ffdff6aSGreg Kroah-Hartman 	val = II20K_AI_CHANLIST_ONBOARD_ONLY |
210*8ffdff6aSGreg Kroah-Hartman 	      II20K_AI_CHANLIST_MUX_ENA |
211*8ffdff6aSGreg Kroah-Hartman 	      II20K_AI_CHANLIST_GAIN(range) |
212*8ffdff6aSGreg Kroah-Hartman 	      II20K_AI_CHANLIST_CHAN(chan);
213*8ffdff6aSGreg Kroah-Hartman 	writeb(val, iobase + II20K_AI_CHANLIST_REG);
214*8ffdff6aSGreg Kroah-Hartman 
215*8ffdff6aSGreg Kroah-Hartman 	/* reset settling time counter and trigger delay counter */
216*8ffdff6aSGreg Kroah-Hartman 	writeb(0, iobase + II20K_AI_COUNT_RESET_REG);
217*8ffdff6aSGreg Kroah-Hartman 
218*8ffdff6aSGreg Kroah-Hartman 	/* reset channel scanner */
219*8ffdff6aSGreg Kroah-Hartman 	writeb(0, iobase + II20K_AI_CHAN_RESET_REG);
220*8ffdff6aSGreg Kroah-Hartman }
221*8ffdff6aSGreg Kroah-Hartman 
222*8ffdff6aSGreg Kroah-Hartman static int ii20k_ai_insn_read(struct comedi_device *dev,
223*8ffdff6aSGreg Kroah-Hartman 			      struct comedi_subdevice *s,
224*8ffdff6aSGreg Kroah-Hartman 			      struct comedi_insn *insn,
225*8ffdff6aSGreg Kroah-Hartman 			      unsigned int *data)
226*8ffdff6aSGreg Kroah-Hartman {
227*8ffdff6aSGreg Kroah-Hartman 	void __iomem *iobase = ii20k_module_iobase(dev, s);
228*8ffdff6aSGreg Kroah-Hartman 	int ret;
229*8ffdff6aSGreg Kroah-Hartman 	int i;
230*8ffdff6aSGreg Kroah-Hartman 
231*8ffdff6aSGreg Kroah-Hartman 	ii20k_ai_setup(dev, s, insn->chanspec);
232*8ffdff6aSGreg Kroah-Hartman 
233*8ffdff6aSGreg Kroah-Hartman 	for (i = 0; i < insn->n; i++) {
234*8ffdff6aSGreg Kroah-Hartman 		unsigned int val;
235*8ffdff6aSGreg Kroah-Hartman 
236*8ffdff6aSGreg Kroah-Hartman 		/* generate a software start convert signal */
237*8ffdff6aSGreg Kroah-Hartman 		readb(iobase + II20K_AI_PACER_RESET_REG);
238*8ffdff6aSGreg Kroah-Hartman 
239*8ffdff6aSGreg Kroah-Hartman 		ret = comedi_timeout(dev, s, insn, ii20k_ai_eoc, 0);
240*8ffdff6aSGreg Kroah-Hartman 		if (ret)
241*8ffdff6aSGreg Kroah-Hartman 			return ret;
242*8ffdff6aSGreg Kroah-Hartman 
243*8ffdff6aSGreg Kroah-Hartman 		val = readb(iobase + II20K_AI_LSB_REG);
244*8ffdff6aSGreg Kroah-Hartman 		val |= (readb(iobase + II20K_AI_MSB_REG) << 8);
245*8ffdff6aSGreg Kroah-Hartman 
246*8ffdff6aSGreg Kroah-Hartman 		/* munge the 2's complement data to offset binary */
247*8ffdff6aSGreg Kroah-Hartman 		data[i] = comedi_offset_munge(s, val);
248*8ffdff6aSGreg Kroah-Hartman 	}
249*8ffdff6aSGreg Kroah-Hartman 
250*8ffdff6aSGreg Kroah-Hartman 	return insn->n;
251*8ffdff6aSGreg Kroah-Hartman }
252*8ffdff6aSGreg Kroah-Hartman 
253*8ffdff6aSGreg Kroah-Hartman static void ii20k_dio_config(struct comedi_device *dev,
254*8ffdff6aSGreg Kroah-Hartman 			     struct comedi_subdevice *s)
255*8ffdff6aSGreg Kroah-Hartman {
256*8ffdff6aSGreg Kroah-Hartman 	unsigned char ctrl01 = 0;
257*8ffdff6aSGreg Kroah-Hartman 	unsigned char ctrl23 = 0;
258*8ffdff6aSGreg Kroah-Hartman 	unsigned char dir_ena = 0;
259*8ffdff6aSGreg Kroah-Hartman 
260*8ffdff6aSGreg Kroah-Hartman 	/* port 0 - channels 0-7 */
261*8ffdff6aSGreg Kroah-Hartman 	if (s->io_bits & 0x000000ff) {
262*8ffdff6aSGreg Kroah-Hartman 		/* output port */
263*8ffdff6aSGreg Kroah-Hartman 		ctrl01 &= ~II20K_CTRL01_DIO0_IN;
264*8ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_BUF_DISAB_DIO0;
265*8ffdff6aSGreg Kroah-Hartman 		dir_ena |= II20K_DIR_DIO0_OUT;
266*8ffdff6aSGreg Kroah-Hartman 	} else {
267*8ffdff6aSGreg Kroah-Hartman 		/* input port */
268*8ffdff6aSGreg Kroah-Hartman 		ctrl01 |= II20K_CTRL01_DIO0_IN;
269*8ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_DIR_DIO0_OUT;
270*8ffdff6aSGreg Kroah-Hartman 	}
271*8ffdff6aSGreg Kroah-Hartman 
272*8ffdff6aSGreg Kroah-Hartman 	/* port 1 - channels 8-15 */
273*8ffdff6aSGreg Kroah-Hartman 	if (s->io_bits & 0x0000ff00) {
274*8ffdff6aSGreg Kroah-Hartman 		/* output port */
275*8ffdff6aSGreg Kroah-Hartman 		ctrl01 &= ~II20K_CTRL01_DIO1_IN;
276*8ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_BUF_DISAB_DIO1;
277*8ffdff6aSGreg Kroah-Hartman 		dir_ena |= II20K_DIR_DIO1_OUT;
278*8ffdff6aSGreg Kroah-Hartman 	} else {
279*8ffdff6aSGreg Kroah-Hartman 		/* input port */
280*8ffdff6aSGreg Kroah-Hartman 		ctrl01 |= II20K_CTRL01_DIO1_IN;
281*8ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_DIR_DIO1_OUT;
282*8ffdff6aSGreg Kroah-Hartman 	}
283*8ffdff6aSGreg Kroah-Hartman 
284*8ffdff6aSGreg Kroah-Hartman 	/* port 2 - channels 16-23 */
285*8ffdff6aSGreg Kroah-Hartman 	if (s->io_bits & 0x00ff0000) {
286*8ffdff6aSGreg Kroah-Hartman 		/* output port */
287*8ffdff6aSGreg Kroah-Hartman 		ctrl23 &= ~II20K_CTRL23_DIO2_IN;
288*8ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_BUF_DISAB_DIO2;
289*8ffdff6aSGreg Kroah-Hartman 		dir_ena |= II20K_DIR_DIO2_OUT;
290*8ffdff6aSGreg Kroah-Hartman 	} else {
291*8ffdff6aSGreg Kroah-Hartman 		/* input port */
292*8ffdff6aSGreg Kroah-Hartman 		ctrl23 |= II20K_CTRL23_DIO2_IN;
293*8ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_DIR_DIO2_OUT;
294*8ffdff6aSGreg Kroah-Hartman 	}
295*8ffdff6aSGreg Kroah-Hartman 
296*8ffdff6aSGreg Kroah-Hartman 	/* port 3 - channels 24-31 */
297*8ffdff6aSGreg Kroah-Hartman 	if (s->io_bits & 0xff000000) {
298*8ffdff6aSGreg Kroah-Hartman 		/* output port */
299*8ffdff6aSGreg Kroah-Hartman 		ctrl23 &= ~II20K_CTRL23_DIO3_IN;
300*8ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_BUF_DISAB_DIO3;
301*8ffdff6aSGreg Kroah-Hartman 		dir_ena |= II20K_DIR_DIO3_OUT;
302*8ffdff6aSGreg Kroah-Hartman 	} else {
303*8ffdff6aSGreg Kroah-Hartman 		/* input port */
304*8ffdff6aSGreg Kroah-Hartman 		ctrl23 |= II20K_CTRL23_DIO3_IN;
305*8ffdff6aSGreg Kroah-Hartman 		dir_ena &= ~II20K_DIR_DIO3_OUT;
306*8ffdff6aSGreg Kroah-Hartman 	}
307*8ffdff6aSGreg Kroah-Hartman 
308*8ffdff6aSGreg Kroah-Hartman 	ctrl23 |= II20K_CTRL01_SET;
309*8ffdff6aSGreg Kroah-Hartman 	ctrl23 |= II20K_CTRL23_SET;
310*8ffdff6aSGreg Kroah-Hartman 
311*8ffdff6aSGreg Kroah-Hartman 	/* order is important */
312*8ffdff6aSGreg Kroah-Hartman 	writeb(ctrl01, dev->mmio + II20K_CTRL01_REG);
313*8ffdff6aSGreg Kroah-Hartman 	writeb(ctrl23, dev->mmio + II20K_CTRL23_REG);
314*8ffdff6aSGreg Kroah-Hartman 	writeb(dir_ena, dev->mmio + II20K_DIR_ENA_REG);
315*8ffdff6aSGreg Kroah-Hartman }
316*8ffdff6aSGreg Kroah-Hartman 
317*8ffdff6aSGreg Kroah-Hartman static int ii20k_dio_insn_config(struct comedi_device *dev,
318*8ffdff6aSGreg Kroah-Hartman 				 struct comedi_subdevice *s,
319*8ffdff6aSGreg Kroah-Hartman 				 struct comedi_insn *insn,
320*8ffdff6aSGreg Kroah-Hartman 				 unsigned int *data)
321*8ffdff6aSGreg Kroah-Hartman {
322*8ffdff6aSGreg Kroah-Hartman 	unsigned int chan = CR_CHAN(insn->chanspec);
323*8ffdff6aSGreg Kroah-Hartman 	unsigned int mask;
324*8ffdff6aSGreg Kroah-Hartman 	int ret;
325*8ffdff6aSGreg Kroah-Hartman 
326*8ffdff6aSGreg Kroah-Hartman 	if (chan < 8)
327*8ffdff6aSGreg Kroah-Hartman 		mask = 0x000000ff;
328*8ffdff6aSGreg Kroah-Hartman 	else if (chan < 16)
329*8ffdff6aSGreg Kroah-Hartman 		mask = 0x0000ff00;
330*8ffdff6aSGreg Kroah-Hartman 	else if (chan < 24)
331*8ffdff6aSGreg Kroah-Hartman 		mask = 0x00ff0000;
332*8ffdff6aSGreg Kroah-Hartman 	else
333*8ffdff6aSGreg Kroah-Hartman 		mask = 0xff000000;
334*8ffdff6aSGreg Kroah-Hartman 
335*8ffdff6aSGreg Kroah-Hartman 	ret = comedi_dio_insn_config(dev, s, insn, data, mask);
336*8ffdff6aSGreg Kroah-Hartman 	if (ret)
337*8ffdff6aSGreg Kroah-Hartman 		return ret;
338*8ffdff6aSGreg Kroah-Hartman 
339*8ffdff6aSGreg Kroah-Hartman 	ii20k_dio_config(dev, s);
340*8ffdff6aSGreg Kroah-Hartman 
341*8ffdff6aSGreg Kroah-Hartman 	return insn->n;
342*8ffdff6aSGreg Kroah-Hartman }
343*8ffdff6aSGreg Kroah-Hartman 
344*8ffdff6aSGreg Kroah-Hartman static int ii20k_dio_insn_bits(struct comedi_device *dev,
345*8ffdff6aSGreg Kroah-Hartman 			       struct comedi_subdevice *s,
346*8ffdff6aSGreg Kroah-Hartman 			       struct comedi_insn *insn,
347*8ffdff6aSGreg Kroah-Hartman 			       unsigned int *data)
348*8ffdff6aSGreg Kroah-Hartman {
349*8ffdff6aSGreg Kroah-Hartman 	unsigned int mask;
350*8ffdff6aSGreg Kroah-Hartman 
351*8ffdff6aSGreg Kroah-Hartman 	mask = comedi_dio_update_state(s, data);
352*8ffdff6aSGreg Kroah-Hartman 	if (mask) {
353*8ffdff6aSGreg Kroah-Hartman 		if (mask & 0x000000ff)
354*8ffdff6aSGreg Kroah-Hartman 			writeb((s->state >> 0) & 0xff,
355*8ffdff6aSGreg Kroah-Hartman 			       dev->mmio + II20K_DIO0_REG);
356*8ffdff6aSGreg Kroah-Hartman 		if (mask & 0x0000ff00)
357*8ffdff6aSGreg Kroah-Hartman 			writeb((s->state >> 8) & 0xff,
358*8ffdff6aSGreg Kroah-Hartman 			       dev->mmio + II20K_DIO1_REG);
359*8ffdff6aSGreg Kroah-Hartman 		if (mask & 0x00ff0000)
360*8ffdff6aSGreg Kroah-Hartman 			writeb((s->state >> 16) & 0xff,
361*8ffdff6aSGreg Kroah-Hartman 			       dev->mmio + II20K_DIO2_REG);
362*8ffdff6aSGreg Kroah-Hartman 		if (mask & 0xff000000)
363*8ffdff6aSGreg Kroah-Hartman 			writeb((s->state >> 24) & 0xff,
364*8ffdff6aSGreg Kroah-Hartman 			       dev->mmio + II20K_DIO3_REG);
365*8ffdff6aSGreg Kroah-Hartman 	}
366*8ffdff6aSGreg Kroah-Hartman 
367*8ffdff6aSGreg Kroah-Hartman 	data[1] = readb(dev->mmio + II20K_DIO0_REG);
368*8ffdff6aSGreg Kroah-Hartman 	data[1] |= readb(dev->mmio + II20K_DIO1_REG) << 8;
369*8ffdff6aSGreg Kroah-Hartman 	data[1] |= readb(dev->mmio + II20K_DIO2_REG) << 16;
370*8ffdff6aSGreg Kroah-Hartman 	data[1] |= readb(dev->mmio + II20K_DIO3_REG) << 24;
371*8ffdff6aSGreg Kroah-Hartman 
372*8ffdff6aSGreg Kroah-Hartman 	return insn->n;
373*8ffdff6aSGreg Kroah-Hartman }
374*8ffdff6aSGreg Kroah-Hartman 
375*8ffdff6aSGreg Kroah-Hartman static int ii20k_init_module(struct comedi_device *dev,
376*8ffdff6aSGreg Kroah-Hartman 			     struct comedi_subdevice *s)
377*8ffdff6aSGreg Kroah-Hartman {
378*8ffdff6aSGreg Kroah-Hartman 	void __iomem *iobase = ii20k_module_iobase(dev, s);
379*8ffdff6aSGreg Kroah-Hartman 	unsigned char id;
380*8ffdff6aSGreg Kroah-Hartman 	int ret;
381*8ffdff6aSGreg Kroah-Hartman 
382*8ffdff6aSGreg Kroah-Hartman 	id = readb(iobase + II20K_ID_REG);
383*8ffdff6aSGreg Kroah-Hartman 	switch (id) {
384*8ffdff6aSGreg Kroah-Hartman 	case II20K_ID_PCI20006M_1:
385*8ffdff6aSGreg Kroah-Hartman 	case II20K_ID_PCI20006M_2:
386*8ffdff6aSGreg Kroah-Hartman 		/* Analog Output subdevice */
387*8ffdff6aSGreg Kroah-Hartman 		s->type		= COMEDI_SUBD_AO;
388*8ffdff6aSGreg Kroah-Hartman 		s->subdev_flags	= SDF_WRITABLE;
389*8ffdff6aSGreg Kroah-Hartman 		s->n_chan	= (id == II20K_ID_PCI20006M_2) ? 2 : 1;
390*8ffdff6aSGreg Kroah-Hartman 		s->maxdata	= 0xffff;
391*8ffdff6aSGreg Kroah-Hartman 		s->range_table	= &ii20k_ao_ranges;
392*8ffdff6aSGreg Kroah-Hartman 		s->insn_write	= ii20k_ao_insn_write;
393*8ffdff6aSGreg Kroah-Hartman 
394*8ffdff6aSGreg Kroah-Hartman 		ret = comedi_alloc_subdev_readback(s);
395*8ffdff6aSGreg Kroah-Hartman 		if (ret)
396*8ffdff6aSGreg Kroah-Hartman 			return ret;
397*8ffdff6aSGreg Kroah-Hartman 		break;
398*8ffdff6aSGreg Kroah-Hartman 	case II20K_ID_PCI20341M_1:
399*8ffdff6aSGreg Kroah-Hartman 		/* Analog Input subdevice */
400*8ffdff6aSGreg Kroah-Hartman 		s->type		= COMEDI_SUBD_AI;
401*8ffdff6aSGreg Kroah-Hartman 		s->subdev_flags	= SDF_READABLE | SDF_DIFF;
402*8ffdff6aSGreg Kroah-Hartman 		s->n_chan	= 4;
403*8ffdff6aSGreg Kroah-Hartman 		s->maxdata	= 0xffff;
404*8ffdff6aSGreg Kroah-Hartman 		s->range_table	= &ii20k_ai_ranges;
405*8ffdff6aSGreg Kroah-Hartman 		s->insn_read	= ii20k_ai_insn_read;
406*8ffdff6aSGreg Kroah-Hartman 		break;
407*8ffdff6aSGreg Kroah-Hartman 	default:
408*8ffdff6aSGreg Kroah-Hartman 		s->type = COMEDI_SUBD_UNUSED;
409*8ffdff6aSGreg Kroah-Hartman 		break;
410*8ffdff6aSGreg Kroah-Hartman 	}
411*8ffdff6aSGreg Kroah-Hartman 
412*8ffdff6aSGreg Kroah-Hartman 	return 0;
413*8ffdff6aSGreg Kroah-Hartman }
414*8ffdff6aSGreg Kroah-Hartman 
415*8ffdff6aSGreg Kroah-Hartman static int ii20k_attach(struct comedi_device *dev,
416*8ffdff6aSGreg Kroah-Hartman 			struct comedi_devconfig *it)
417*8ffdff6aSGreg Kroah-Hartman {
418*8ffdff6aSGreg Kroah-Hartman 	struct comedi_subdevice *s;
419*8ffdff6aSGreg Kroah-Hartman 	unsigned int membase;
420*8ffdff6aSGreg Kroah-Hartman 	unsigned char id;
421*8ffdff6aSGreg Kroah-Hartman 	bool has_dio;
422*8ffdff6aSGreg Kroah-Hartman 	int ret;
423*8ffdff6aSGreg Kroah-Hartman 
424*8ffdff6aSGreg Kroah-Hartman 	membase = it->options[0];
425*8ffdff6aSGreg Kroah-Hartman 	if (!membase || (membase & ~(0x100000 - II20K_SIZE))) {
426*8ffdff6aSGreg Kroah-Hartman 		dev_warn(dev->class_dev,
427*8ffdff6aSGreg Kroah-Hartman 			 "%s: invalid memory address specified\n",
428*8ffdff6aSGreg Kroah-Hartman 			 dev->board_name);
429*8ffdff6aSGreg Kroah-Hartman 		return -EINVAL;
430*8ffdff6aSGreg Kroah-Hartman 	}
431*8ffdff6aSGreg Kroah-Hartman 
432*8ffdff6aSGreg Kroah-Hartman 	if (!request_mem_region(membase, II20K_SIZE, dev->board_name)) {
433*8ffdff6aSGreg Kroah-Hartman 		dev_warn(dev->class_dev, "%s: I/O mem conflict (%#x,%u)\n",
434*8ffdff6aSGreg Kroah-Hartman 			 dev->board_name, membase, II20K_SIZE);
435*8ffdff6aSGreg Kroah-Hartman 		return -EIO;
436*8ffdff6aSGreg Kroah-Hartman 	}
437*8ffdff6aSGreg Kroah-Hartman 	dev->iobase = membase;	/* actually, a memory address */
438*8ffdff6aSGreg Kroah-Hartman 
439*8ffdff6aSGreg Kroah-Hartman 	dev->mmio = ioremap(membase, II20K_SIZE);
440*8ffdff6aSGreg Kroah-Hartman 	if (!dev->mmio)
441*8ffdff6aSGreg Kroah-Hartman 		return -ENOMEM;
442*8ffdff6aSGreg Kroah-Hartman 
443*8ffdff6aSGreg Kroah-Hartman 	id = readb(dev->mmio + II20K_ID_REG);
444*8ffdff6aSGreg Kroah-Hartman 	switch (id & II20K_ID_MASK) {
445*8ffdff6aSGreg Kroah-Hartman 	case II20K_ID_PCI20001C_1A:
446*8ffdff6aSGreg Kroah-Hartman 		has_dio = false;
447*8ffdff6aSGreg Kroah-Hartman 		break;
448*8ffdff6aSGreg Kroah-Hartman 	case II20K_ID_PCI20001C_2A:
449*8ffdff6aSGreg Kroah-Hartman 		has_dio = true;
450*8ffdff6aSGreg Kroah-Hartman 		break;
451*8ffdff6aSGreg Kroah-Hartman 	default:
452*8ffdff6aSGreg Kroah-Hartman 		return -ENODEV;
453*8ffdff6aSGreg Kroah-Hartman 	}
454*8ffdff6aSGreg Kroah-Hartman 
455*8ffdff6aSGreg Kroah-Hartman 	ret = comedi_alloc_subdevices(dev, 4);
456*8ffdff6aSGreg Kroah-Hartman 	if (ret)
457*8ffdff6aSGreg Kroah-Hartman 		return ret;
458*8ffdff6aSGreg Kroah-Hartman 
459*8ffdff6aSGreg Kroah-Hartman 	s = &dev->subdevices[0];
460*8ffdff6aSGreg Kroah-Hartman 	if (id & II20K_ID_MOD1_EMPTY) {
461*8ffdff6aSGreg Kroah-Hartman 		s->type = COMEDI_SUBD_UNUSED;
462*8ffdff6aSGreg Kroah-Hartman 	} else {
463*8ffdff6aSGreg Kroah-Hartman 		ret = ii20k_init_module(dev, s);
464*8ffdff6aSGreg Kroah-Hartman 		if (ret)
465*8ffdff6aSGreg Kroah-Hartman 			return ret;
466*8ffdff6aSGreg Kroah-Hartman 	}
467*8ffdff6aSGreg Kroah-Hartman 
468*8ffdff6aSGreg Kroah-Hartman 	s = &dev->subdevices[1];
469*8ffdff6aSGreg Kroah-Hartman 	if (id & II20K_ID_MOD2_EMPTY) {
470*8ffdff6aSGreg Kroah-Hartman 		s->type = COMEDI_SUBD_UNUSED;
471*8ffdff6aSGreg Kroah-Hartman 	} else {
472*8ffdff6aSGreg Kroah-Hartman 		ret = ii20k_init_module(dev, s);
473*8ffdff6aSGreg Kroah-Hartman 		if (ret)
474*8ffdff6aSGreg Kroah-Hartman 			return ret;
475*8ffdff6aSGreg Kroah-Hartman 	}
476*8ffdff6aSGreg Kroah-Hartman 
477*8ffdff6aSGreg Kroah-Hartman 	s = &dev->subdevices[2];
478*8ffdff6aSGreg Kroah-Hartman 	if (id & II20K_ID_MOD3_EMPTY) {
479*8ffdff6aSGreg Kroah-Hartman 		s->type = COMEDI_SUBD_UNUSED;
480*8ffdff6aSGreg Kroah-Hartman 	} else {
481*8ffdff6aSGreg Kroah-Hartman 		ret = ii20k_init_module(dev, s);
482*8ffdff6aSGreg Kroah-Hartman 		if (ret)
483*8ffdff6aSGreg Kroah-Hartman 			return ret;
484*8ffdff6aSGreg Kroah-Hartman 	}
485*8ffdff6aSGreg Kroah-Hartman 
486*8ffdff6aSGreg Kroah-Hartman 	/* Digital I/O subdevice */
487*8ffdff6aSGreg Kroah-Hartman 	s = &dev->subdevices[3];
488*8ffdff6aSGreg Kroah-Hartman 	if (has_dio) {
489*8ffdff6aSGreg Kroah-Hartman 		s->type		= COMEDI_SUBD_DIO;
490*8ffdff6aSGreg Kroah-Hartman 		s->subdev_flags	= SDF_READABLE | SDF_WRITABLE;
491*8ffdff6aSGreg Kroah-Hartman 		s->n_chan	= 32;
492*8ffdff6aSGreg Kroah-Hartman 		s->maxdata	= 1;
493*8ffdff6aSGreg Kroah-Hartman 		s->range_table	= &range_digital;
494*8ffdff6aSGreg Kroah-Hartman 		s->insn_bits	= ii20k_dio_insn_bits;
495*8ffdff6aSGreg Kroah-Hartman 		s->insn_config	= ii20k_dio_insn_config;
496*8ffdff6aSGreg Kroah-Hartman 
497*8ffdff6aSGreg Kroah-Hartman 		/* default all channels to input */
498*8ffdff6aSGreg Kroah-Hartman 		ii20k_dio_config(dev, s);
499*8ffdff6aSGreg Kroah-Hartman 	} else {
500*8ffdff6aSGreg Kroah-Hartman 		s->type = COMEDI_SUBD_UNUSED;
501*8ffdff6aSGreg Kroah-Hartman 	}
502*8ffdff6aSGreg Kroah-Hartman 
503*8ffdff6aSGreg Kroah-Hartman 	return 0;
504*8ffdff6aSGreg Kroah-Hartman }
505*8ffdff6aSGreg Kroah-Hartman 
506*8ffdff6aSGreg Kroah-Hartman static void ii20k_detach(struct comedi_device *dev)
507*8ffdff6aSGreg Kroah-Hartman {
508*8ffdff6aSGreg Kroah-Hartman 	if (dev->mmio)
509*8ffdff6aSGreg Kroah-Hartman 		iounmap(dev->mmio);
510*8ffdff6aSGreg Kroah-Hartman 	if (dev->iobase)	/* actually, a memory address */
511*8ffdff6aSGreg Kroah-Hartman 		release_mem_region(dev->iobase, II20K_SIZE);
512*8ffdff6aSGreg Kroah-Hartman }
513*8ffdff6aSGreg Kroah-Hartman 
514*8ffdff6aSGreg Kroah-Hartman static struct comedi_driver ii20k_driver = {
515*8ffdff6aSGreg Kroah-Hartman 	.driver_name	= "ii_pci20kc",
516*8ffdff6aSGreg Kroah-Hartman 	.module		= THIS_MODULE,
517*8ffdff6aSGreg Kroah-Hartman 	.attach		= ii20k_attach,
518*8ffdff6aSGreg Kroah-Hartman 	.detach		= ii20k_detach,
519*8ffdff6aSGreg Kroah-Hartman };
520*8ffdff6aSGreg Kroah-Hartman module_comedi_driver(ii20k_driver);
521*8ffdff6aSGreg Kroah-Hartman 
522*8ffdff6aSGreg Kroah-Hartman MODULE_AUTHOR("Comedi https://www.comedi.org");
523*8ffdff6aSGreg Kroah-Hartman MODULE_DESCRIPTION("Comedi driver for Intelligent Instruments PCI-20001C");
524*8ffdff6aSGreg Kroah-Hartman MODULE_LICENSE("GPL");
525