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 <linux/comedi/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 */
120static 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
128static 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
137static 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
143static 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
168static 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
182static 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
222static 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
253static 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
317static 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
344static 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
375static 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
415static 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
506static 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
514static struct comedi_driver ii20k_driver = {
515	.driver_name	= "ii_pci20kc",
516	.module		= THIS_MODULE,
517	.attach		= ii20k_attach,
518	.detach		= ii20k_detach,
519};
520module_comedi_driver(ii20k_driver);
521
522MODULE_AUTHOR("Comedi https://www.comedi.org");
523MODULE_DESCRIPTION("Comedi driver for Intelligent Instruments PCI-20001C");
524MODULE_LICENSE("GPL");
525