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