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: val & 0xff, addr: iobase + II20K_AO_LSB_REG(chan));
161 writeb(val: (val >> 8) & 0xff, addr: iobase + II20K_AO_MSB_REG(chan));
162 writeb(val: 0x00, addr: 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(addr: 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, addr: iobase + II20K_AI_CONF_REG);
193
194 /* software conversion */
195 writeb(val: 0, addr: 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, addr: 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, addr: iobase + II20K_AI_SET_TIME_REG);
204
205 /* set number of input channels */
206 writeb(val: 1, addr: 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, addr: iobase + II20K_AI_CHANLIST_REG);
214
215 /* reset settling time counter and trigger delay counter */
216 writeb(val: 0, addr: iobase + II20K_AI_COUNT_RESET_REG);
217
218 /* reset channel scanner */
219 writeb(val: 0, addr: 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, chanspec: 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(addr: iobase + II20K_AI_PACER_RESET_REG);
238
239 ret = comedi_timeout(dev, s, insn, cb: ii20k_ai_eoc, context: 0);
240 if (ret)
241 return ret;
242
243 val = readb(addr: iobase + II20K_AI_LSB_REG);
244 val |= (readb(addr: 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(val: ctrl01, addr: dev->mmio + II20K_CTRL01_REG);
313 writeb(val: ctrl23, addr: dev->mmio + II20K_CTRL23_REG);
314 writeb(val: dir_ena, addr: 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(val: (s->state >> 0) & 0xff,
355 addr: dev->mmio + II20K_DIO0_REG);
356 if (mask & 0x0000ff00)
357 writeb(val: (s->state >> 8) & 0xff,
358 addr: dev->mmio + II20K_DIO1_REG);
359 if (mask & 0x00ff0000)
360 writeb(val: (s->state >> 16) & 0xff,
361 addr: dev->mmio + II20K_DIO2_REG);
362 if (mask & 0xff000000)
363 writeb(val: (s->state >> 24) & 0xff,
364 addr: dev->mmio + II20K_DIO3_REG);
365 }
366
367 data[1] = readb(addr: dev->mmio + II20K_DIO0_REG);
368 data[1] |= readb(addr: dev->mmio + II20K_DIO1_REG) << 8;
369 data[1] |= readb(addr: dev->mmio + II20K_DIO2_REG) << 16;
370 data[1] |= readb(addr: 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(addr: 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(offset: membase, II20K_SIZE);
440 if (!dev->mmio)
441 return -ENOMEM;
442
443 id = readb(addr: 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, num_subdevices: 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(addr: 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

source code of linux/drivers/comedi/drivers/ii_pci20kc.c