1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * addi_apci_3xxx.c
4 * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
5 * Project manager: S. Weber
6 *
7 * ADDI-DATA GmbH
8 * Dieselstrasse 3
9 * D-77833 Ottersweier
10 * Tel: +19(0)7223/9493-0
11 * Fax: +49(0)7223/9493-92
12 * http://www.addi-data.com
13 * info@addi-data.com
14 */
15
16#include <linux/module.h>
17#include <linux/interrupt.h>
18#include <linux/comedi/comedi_pci.h>
19
20#define CONV_UNIT_NS BIT(0)
21#define CONV_UNIT_US BIT(1)
22#define CONV_UNIT_MS BIT(2)
23
24static const struct comedi_lrange apci3xxx_ai_range = {
25 8, {
26 BIP_RANGE(10),
27 BIP_RANGE(5),
28 BIP_RANGE(2),
29 BIP_RANGE(1),
30 UNI_RANGE(10),
31 UNI_RANGE(5),
32 UNI_RANGE(2),
33 UNI_RANGE(1)
34 }
35};
36
37static const struct comedi_lrange apci3xxx_ao_range = {
38 2, {
39 BIP_RANGE(10),
40 UNI_RANGE(10)
41 }
42};
43
44enum apci3xxx_boardid {
45 BOARD_APCI3000_16,
46 BOARD_APCI3000_8,
47 BOARD_APCI3000_4,
48 BOARD_APCI3006_16,
49 BOARD_APCI3006_8,
50 BOARD_APCI3006_4,
51 BOARD_APCI3010_16,
52 BOARD_APCI3010_8,
53 BOARD_APCI3010_4,
54 BOARD_APCI3016_16,
55 BOARD_APCI3016_8,
56 BOARD_APCI3016_4,
57 BOARD_APCI3100_16_4,
58 BOARD_APCI3100_8_4,
59 BOARD_APCI3106_16_4,
60 BOARD_APCI3106_8_4,
61 BOARD_APCI3110_16_4,
62 BOARD_APCI3110_8_4,
63 BOARD_APCI3116_16_4,
64 BOARD_APCI3116_8_4,
65 BOARD_APCI3003,
66 BOARD_APCI3002_16,
67 BOARD_APCI3002_8,
68 BOARD_APCI3002_4,
69 BOARD_APCI3500,
70};
71
72struct apci3xxx_boardinfo {
73 const char *name;
74 int ai_subdev_flags;
75 int ai_n_chan;
76 unsigned int ai_maxdata;
77 unsigned char ai_conv_units;
78 unsigned int ai_min_acq_ns;
79 unsigned int has_ao:1;
80 unsigned int has_dig_in:1;
81 unsigned int has_dig_out:1;
82 unsigned int has_ttl_io:1;
83};
84
85static const struct apci3xxx_boardinfo apci3xxx_boardtypes[] = {
86 [BOARD_APCI3000_16] = {
87 .name = "apci3000-16",
88 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
89 .ai_n_chan = 16,
90 .ai_maxdata = 0x0fff,
91 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
92 .ai_min_acq_ns = 10000,
93 .has_ttl_io = 1,
94 },
95 [BOARD_APCI3000_8] = {
96 .name = "apci3000-8",
97 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
98 .ai_n_chan = 8,
99 .ai_maxdata = 0x0fff,
100 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
101 .ai_min_acq_ns = 10000,
102 .has_ttl_io = 1,
103 },
104 [BOARD_APCI3000_4] = {
105 .name = "apci3000-4",
106 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
107 .ai_n_chan = 4,
108 .ai_maxdata = 0x0fff,
109 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
110 .ai_min_acq_ns = 10000,
111 .has_ttl_io = 1,
112 },
113 [BOARD_APCI3006_16] = {
114 .name = "apci3006-16",
115 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
116 .ai_n_chan = 16,
117 .ai_maxdata = 0xffff,
118 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
119 .ai_min_acq_ns = 10000,
120 .has_ttl_io = 1,
121 },
122 [BOARD_APCI3006_8] = {
123 .name = "apci3006-8",
124 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
125 .ai_n_chan = 8,
126 .ai_maxdata = 0xffff,
127 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
128 .ai_min_acq_ns = 10000,
129 .has_ttl_io = 1,
130 },
131 [BOARD_APCI3006_4] = {
132 .name = "apci3006-4",
133 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
134 .ai_n_chan = 4,
135 .ai_maxdata = 0xffff,
136 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
137 .ai_min_acq_ns = 10000,
138 .has_ttl_io = 1,
139 },
140 [BOARD_APCI3010_16] = {
141 .name = "apci3010-16",
142 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
143 .ai_n_chan = 16,
144 .ai_maxdata = 0x0fff,
145 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
146 .ai_min_acq_ns = 5000,
147 .has_dig_in = 1,
148 .has_dig_out = 1,
149 .has_ttl_io = 1,
150 },
151 [BOARD_APCI3010_8] = {
152 .name = "apci3010-8",
153 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
154 .ai_n_chan = 8,
155 .ai_maxdata = 0x0fff,
156 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
157 .ai_min_acq_ns = 5000,
158 .has_dig_in = 1,
159 .has_dig_out = 1,
160 .has_ttl_io = 1,
161 },
162 [BOARD_APCI3010_4] = {
163 .name = "apci3010-4",
164 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
165 .ai_n_chan = 4,
166 .ai_maxdata = 0x0fff,
167 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
168 .ai_min_acq_ns = 5000,
169 .has_dig_in = 1,
170 .has_dig_out = 1,
171 .has_ttl_io = 1,
172 },
173 [BOARD_APCI3016_16] = {
174 .name = "apci3016-16",
175 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
176 .ai_n_chan = 16,
177 .ai_maxdata = 0xffff,
178 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
179 .ai_min_acq_ns = 5000,
180 .has_dig_in = 1,
181 .has_dig_out = 1,
182 .has_ttl_io = 1,
183 },
184 [BOARD_APCI3016_8] = {
185 .name = "apci3016-8",
186 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
187 .ai_n_chan = 8,
188 .ai_maxdata = 0xffff,
189 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
190 .ai_min_acq_ns = 5000,
191 .has_dig_in = 1,
192 .has_dig_out = 1,
193 .has_ttl_io = 1,
194 },
195 [BOARD_APCI3016_4] = {
196 .name = "apci3016-4",
197 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
198 .ai_n_chan = 4,
199 .ai_maxdata = 0xffff,
200 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
201 .ai_min_acq_ns = 5000,
202 .has_dig_in = 1,
203 .has_dig_out = 1,
204 .has_ttl_io = 1,
205 },
206 [BOARD_APCI3100_16_4] = {
207 .name = "apci3100-16-4",
208 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
209 .ai_n_chan = 16,
210 .ai_maxdata = 0x0fff,
211 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
212 .ai_min_acq_ns = 10000,
213 .has_ao = 1,
214 .has_ttl_io = 1,
215 },
216 [BOARD_APCI3100_8_4] = {
217 .name = "apci3100-8-4",
218 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
219 .ai_n_chan = 8,
220 .ai_maxdata = 0x0fff,
221 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
222 .ai_min_acq_ns = 10000,
223 .has_ao = 1,
224 .has_ttl_io = 1,
225 },
226 [BOARD_APCI3106_16_4] = {
227 .name = "apci3106-16-4",
228 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
229 .ai_n_chan = 16,
230 .ai_maxdata = 0xffff,
231 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
232 .ai_min_acq_ns = 10000,
233 .has_ao = 1,
234 .has_ttl_io = 1,
235 },
236 [BOARD_APCI3106_8_4] = {
237 .name = "apci3106-8-4",
238 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
239 .ai_n_chan = 8,
240 .ai_maxdata = 0xffff,
241 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
242 .ai_min_acq_ns = 10000,
243 .has_ao = 1,
244 .has_ttl_io = 1,
245 },
246 [BOARD_APCI3110_16_4] = {
247 .name = "apci3110-16-4",
248 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
249 .ai_n_chan = 16,
250 .ai_maxdata = 0x0fff,
251 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
252 .ai_min_acq_ns = 5000,
253 .has_ao = 1,
254 .has_dig_in = 1,
255 .has_dig_out = 1,
256 .has_ttl_io = 1,
257 },
258 [BOARD_APCI3110_8_4] = {
259 .name = "apci3110-8-4",
260 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
261 .ai_n_chan = 8,
262 .ai_maxdata = 0x0fff,
263 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
264 .ai_min_acq_ns = 5000,
265 .has_ao = 1,
266 .has_dig_in = 1,
267 .has_dig_out = 1,
268 .has_ttl_io = 1,
269 },
270 [BOARD_APCI3116_16_4] = {
271 .name = "apci3116-16-4",
272 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
273 .ai_n_chan = 16,
274 .ai_maxdata = 0xffff,
275 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
276 .ai_min_acq_ns = 5000,
277 .has_ao = 1,
278 .has_dig_in = 1,
279 .has_dig_out = 1,
280 .has_ttl_io = 1,
281 },
282 [BOARD_APCI3116_8_4] = {
283 .name = "apci3116-8-4",
284 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
285 .ai_n_chan = 8,
286 .ai_maxdata = 0xffff,
287 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
288 .ai_min_acq_ns = 5000,
289 .has_ao = 1,
290 .has_dig_in = 1,
291 .has_dig_out = 1,
292 .has_ttl_io = 1,
293 },
294 [BOARD_APCI3003] = {
295 .name = "apci3003",
296 .ai_subdev_flags = SDF_DIFF,
297 .ai_n_chan = 4,
298 .ai_maxdata = 0xffff,
299 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US |
300 CONV_UNIT_NS,
301 .ai_min_acq_ns = 2500,
302 .has_dig_in = 1,
303 .has_dig_out = 1,
304 },
305 [BOARD_APCI3002_16] = {
306 .name = "apci3002-16",
307 .ai_subdev_flags = SDF_DIFF,
308 .ai_n_chan = 16,
309 .ai_maxdata = 0xffff,
310 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
311 .ai_min_acq_ns = 5000,
312 .has_dig_in = 1,
313 .has_dig_out = 1,
314 },
315 [BOARD_APCI3002_8] = {
316 .name = "apci3002-8",
317 .ai_subdev_flags = SDF_DIFF,
318 .ai_n_chan = 8,
319 .ai_maxdata = 0xffff,
320 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
321 .ai_min_acq_ns = 5000,
322 .has_dig_in = 1,
323 .has_dig_out = 1,
324 },
325 [BOARD_APCI3002_4] = {
326 .name = "apci3002-4",
327 .ai_subdev_flags = SDF_DIFF,
328 .ai_n_chan = 4,
329 .ai_maxdata = 0xffff,
330 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
331 .ai_min_acq_ns = 5000,
332 .has_dig_in = 1,
333 .has_dig_out = 1,
334 },
335 [BOARD_APCI3500] = {
336 .name = "apci3500",
337 .has_ao = 1,
338 .has_ttl_io = 1,
339 },
340};
341
342struct apci3xxx_private {
343 unsigned int ai_timer;
344 unsigned char ai_time_base;
345};
346
347static irqreturn_t apci3xxx_irq_handler(int irq, void *d)
348{
349 struct comedi_device *dev = d;
350 struct comedi_subdevice *s = dev->read_subdev;
351 unsigned int status;
352 unsigned int val;
353
354 /* Test if interrupt occur */
355 status = readl(addr: dev->mmio + 16);
356 if ((status & 0x2) == 0x2) {
357 /* Reset the interrupt */
358 writel(val: status, addr: dev->mmio + 16);
359
360 val = readl(addr: dev->mmio + 28);
361 comedi_buf_write_samples(s, data: &val, nsamples: 1);
362
363 s->async->events |= COMEDI_CB_EOA;
364 comedi_handle_events(dev, s);
365
366 return IRQ_HANDLED;
367 }
368 return IRQ_NONE;
369}
370
371static int apci3xxx_ai_started(struct comedi_device *dev)
372{
373 if ((readl(addr: dev->mmio + 8) & 0x80000) == 0x80000)
374 return 1;
375
376 return 0;
377}
378
379static int apci3xxx_ai_setup(struct comedi_device *dev, unsigned int chanspec)
380{
381 unsigned int chan = CR_CHAN(chanspec);
382 unsigned int range = CR_RANGE(chanspec);
383 unsigned int aref = CR_AREF(chanspec);
384 unsigned int delay_mode;
385 unsigned int val;
386
387 if (apci3xxx_ai_started(dev))
388 return -EBUSY;
389
390 /* Clear the FIFO */
391 writel(val: 0x10000, addr: dev->mmio + 12);
392
393 /* Get and save the delay mode */
394 delay_mode = readl(addr: dev->mmio + 4);
395 delay_mode &= 0xfffffef0;
396
397 /* Channel configuration selection */
398 writel(val: delay_mode, addr: dev->mmio + 4);
399
400 /* Make the configuration */
401 val = (range & 3) | ((range >> 2) << 6) |
402 ((aref == AREF_DIFF) << 7);
403 writel(val, addr: dev->mmio + 0);
404
405 /* Channel selection */
406 writel(val: delay_mode | 0x100, addr: dev->mmio + 4);
407 writel(val: chan, addr: dev->mmio + 0);
408
409 /* Restore delay mode */
410 writel(val: delay_mode, addr: dev->mmio + 4);
411
412 /* Set the number of sequence to 1 */
413 writel(val: 1, addr: dev->mmio + 48);
414
415 return 0;
416}
417
418static int apci3xxx_ai_eoc(struct comedi_device *dev,
419 struct comedi_subdevice *s,
420 struct comedi_insn *insn,
421 unsigned long context)
422{
423 unsigned int status;
424
425 status = readl(addr: dev->mmio + 20);
426 if (status & 0x1)
427 return 0;
428 return -EBUSY;
429}
430
431static int apci3xxx_ai_insn_read(struct comedi_device *dev,
432 struct comedi_subdevice *s,
433 struct comedi_insn *insn,
434 unsigned int *data)
435{
436 int ret;
437 int i;
438
439 ret = apci3xxx_ai_setup(dev, chanspec: insn->chanspec);
440 if (ret)
441 return ret;
442
443 for (i = 0; i < insn->n; i++) {
444 /* Start the conversion */
445 writel(val: 0x80000, addr: dev->mmio + 8);
446
447 /* Wait the EOS */
448 ret = comedi_timeout(dev, s, insn, cb: apci3xxx_ai_eoc, context: 0);
449 if (ret)
450 return ret;
451
452 /* Read the analog value */
453 data[i] = readl(addr: dev->mmio + 28);
454 }
455
456 return insn->n;
457}
458
459static int apci3xxx_ai_ns_to_timer(struct comedi_device *dev,
460 unsigned int *ns, unsigned int flags)
461{
462 const struct apci3xxx_boardinfo *board = dev->board_ptr;
463 struct apci3xxx_private *devpriv = dev->private;
464 unsigned int base;
465 unsigned int timer;
466 int time_base;
467
468 /* time_base: 0 = ns, 1 = us, 2 = ms */
469 for (time_base = 0; time_base < 3; time_base++) {
470 /* skip unsupported time bases */
471 if (!(board->ai_conv_units & (1 << time_base)))
472 continue;
473
474 switch (time_base) {
475 case 0:
476 base = 1;
477 break;
478 case 1:
479 base = 1000;
480 break;
481 case 2:
482 base = 1000000;
483 break;
484 }
485
486 switch (flags & CMDF_ROUND_MASK) {
487 case CMDF_ROUND_NEAREST:
488 default:
489 timer = DIV_ROUND_CLOSEST(*ns, base);
490 break;
491 case CMDF_ROUND_DOWN:
492 timer = *ns / base;
493 break;
494 case CMDF_ROUND_UP:
495 timer = DIV_ROUND_UP(*ns, base);
496 break;
497 }
498
499 if (timer < 0x10000) {
500 devpriv->ai_time_base = time_base;
501 devpriv->ai_timer = timer;
502 *ns = timer * time_base;
503 return 0;
504 }
505 }
506 return -EINVAL;
507}
508
509static int apci3xxx_ai_cmdtest(struct comedi_device *dev,
510 struct comedi_subdevice *s,
511 struct comedi_cmd *cmd)
512{
513 const struct apci3xxx_boardinfo *board = dev->board_ptr;
514 int err = 0;
515 unsigned int arg;
516
517 /* Step 1 : check if triggers are trivially valid */
518
519 err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW);
520 err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_FOLLOW);
521 err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_TIMER);
522 err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT);
523 err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE);
524
525 if (err)
526 return 1;
527
528 /* Step 2a : make sure trigger sources are unique */
529
530 err |= comedi_check_trigger_is_unique(src: cmd->stop_src);
531
532 /* Step 2b : and mutually compatible */
533
534 if (err)
535 return 2;
536
537 /* Step 3: check if arguments are trivially valid */
538
539 err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0);
540 err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0);
541 err |= comedi_check_trigger_arg_min(arg: &cmd->convert_arg,
542 val: board->ai_min_acq_ns);
543 err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg,
544 val: cmd->chanlist_len);
545
546 if (cmd->stop_src == TRIG_COUNT)
547 err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1);
548 else /* TRIG_NONE */
549 err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0);
550
551 if (err)
552 return 3;
553
554 /* step 4: fix up any arguments */
555
556 arg = cmd->convert_arg;
557 err |= apci3xxx_ai_ns_to_timer(dev, ns: &arg, flags: cmd->flags);
558 err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: arg);
559
560 if (err)
561 return 4;
562
563 return 0;
564}
565
566static int apci3xxx_ai_cmd(struct comedi_device *dev,
567 struct comedi_subdevice *s)
568{
569 struct apci3xxx_private *devpriv = dev->private;
570 struct comedi_cmd *cmd = &s->async->cmd;
571 int ret;
572
573 ret = apci3xxx_ai_setup(dev, chanspec: cmd->chanlist[0]);
574 if (ret)
575 return ret;
576
577 /* Set the convert timing unit */
578 writel(val: devpriv->ai_time_base, addr: dev->mmio + 36);
579
580 /* Set the convert timing */
581 writel(val: devpriv->ai_timer, addr: dev->mmio + 32);
582
583 /* Start the conversion */
584 writel(val: 0x180000, addr: dev->mmio + 8);
585
586 return 0;
587}
588
589static int apci3xxx_ai_cancel(struct comedi_device *dev,
590 struct comedi_subdevice *s)
591{
592 return 0;
593}
594
595static int apci3xxx_ao_eoc(struct comedi_device *dev,
596 struct comedi_subdevice *s,
597 struct comedi_insn *insn,
598 unsigned long context)
599{
600 unsigned int status;
601
602 status = readl(addr: dev->mmio + 96);
603 if (status & 0x100)
604 return 0;
605 return -EBUSY;
606}
607
608static int apci3xxx_ao_insn_write(struct comedi_device *dev,
609 struct comedi_subdevice *s,
610 struct comedi_insn *insn,
611 unsigned int *data)
612{
613 unsigned int chan = CR_CHAN(insn->chanspec);
614 unsigned int range = CR_RANGE(insn->chanspec);
615 int ret;
616 int i;
617
618 for (i = 0; i < insn->n; i++) {
619 unsigned int val = data[i];
620
621 /* Set the range selection */
622 writel(val: range, addr: dev->mmio + 96);
623
624 /* Write the analog value to the selected channel */
625 writel(val: (val << 8) | chan, addr: dev->mmio + 100);
626
627 /* Wait the end of transfer */
628 ret = comedi_timeout(dev, s, insn, cb: apci3xxx_ao_eoc, context: 0);
629 if (ret)
630 return ret;
631
632 s->readback[chan] = val;
633 }
634
635 return insn->n;
636}
637
638static int apci3xxx_di_insn_bits(struct comedi_device *dev,
639 struct comedi_subdevice *s,
640 struct comedi_insn *insn,
641 unsigned int *data)
642{
643 data[1] = inl(port: dev->iobase + 32) & 0xf;
644
645 return insn->n;
646}
647
648static int apci3xxx_do_insn_bits(struct comedi_device *dev,
649 struct comedi_subdevice *s,
650 struct comedi_insn *insn,
651 unsigned int *data)
652{
653 s->state = inl(port: dev->iobase + 48) & 0xf;
654
655 if (comedi_dio_update_state(s, data))
656 outl(value: s->state, port: dev->iobase + 48);
657
658 data[1] = s->state;
659
660 return insn->n;
661}
662
663static int apci3xxx_dio_insn_config(struct comedi_device *dev,
664 struct comedi_subdevice *s,
665 struct comedi_insn *insn,
666 unsigned int *data)
667{
668 unsigned int chan = CR_CHAN(insn->chanspec);
669 unsigned int mask = 0;
670 int ret;
671
672 /*
673 * Port 0 (channels 0-7) are always inputs
674 * Port 1 (channels 8-15) are always outputs
675 * Port 2 (channels 16-23) are programmable i/o
676 */
677 if (data[0] != INSN_CONFIG_DIO_QUERY) {
678 /* ignore all other instructions for ports 0 and 1 */
679 if (chan < 16)
680 return -EINVAL;
681
682 /* changing any channel in port 2 changes the entire port */
683 mask = 0xff0000;
684 }
685
686 ret = comedi_dio_insn_config(dev, s, insn, data, mask);
687 if (ret)
688 return ret;
689
690 /* update port 2 configuration */
691 outl(value: (s->io_bits >> 24) & 0xff, port: dev->iobase + 224);
692
693 return insn->n;
694}
695
696static int apci3xxx_dio_insn_bits(struct comedi_device *dev,
697 struct comedi_subdevice *s,
698 struct comedi_insn *insn,
699 unsigned int *data)
700{
701 unsigned int mask;
702 unsigned int val;
703
704 mask = comedi_dio_update_state(s, data);
705 if (mask) {
706 if (mask & 0xff)
707 outl(value: s->state & 0xff, port: dev->iobase + 80);
708 if (mask & 0xff0000)
709 outl(value: (s->state >> 16) & 0xff, port: dev->iobase + 112);
710 }
711
712 val = inl(port: dev->iobase + 80);
713 val |= (inl(port: dev->iobase + 64) << 8);
714 if (s->io_bits & 0xff0000)
715 val |= (inl(port: dev->iobase + 112) << 16);
716 else
717 val |= (inl(port: dev->iobase + 96) << 16);
718
719 data[1] = val;
720
721 return insn->n;
722}
723
724static int apci3xxx_reset(struct comedi_device *dev)
725{
726 unsigned int val;
727 int i;
728
729 /* Disable the interrupt */
730 disable_irq(irq: dev->irq);
731
732 /* Clear the start command */
733 writel(val: 0, addr: dev->mmio + 8);
734
735 /* Reset the interrupt flags */
736 val = readl(addr: dev->mmio + 16);
737 writel(val, addr: dev->mmio + 16);
738
739 /* clear the EOS */
740 readl(addr: dev->mmio + 20);
741
742 /* Clear the FIFO */
743 for (i = 0; i < 16; i++)
744 val = readl(addr: dev->mmio + 28);
745
746 /* Enable the interrupt */
747 enable_irq(irq: dev->irq);
748
749 return 0;
750}
751
752static int apci3xxx_auto_attach(struct comedi_device *dev,
753 unsigned long context)
754{
755 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
756 const struct apci3xxx_boardinfo *board = NULL;
757 struct apci3xxx_private *devpriv;
758 struct comedi_subdevice *s;
759 int n_subdevices;
760 int subdev;
761 int ret;
762
763 if (context < ARRAY_SIZE(apci3xxx_boardtypes))
764 board = &apci3xxx_boardtypes[context];
765 if (!board)
766 return -ENODEV;
767 dev->board_ptr = board;
768 dev->board_name = board->name;
769
770 devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv));
771 if (!devpriv)
772 return -ENOMEM;
773
774 ret = comedi_pci_enable(dev);
775 if (ret)
776 return ret;
777
778 dev->iobase = pci_resource_start(pcidev, 2);
779 dev->mmio = pci_ioremap_bar(pdev: pcidev, bar: 3);
780 if (!dev->mmio)
781 return -ENOMEM;
782
783 if (pcidev->irq > 0) {
784 ret = request_irq(irq: pcidev->irq, handler: apci3xxx_irq_handler,
785 IRQF_SHARED, name: dev->board_name, dev);
786 if (ret == 0)
787 dev->irq = pcidev->irq;
788 }
789
790 n_subdevices = (board->ai_n_chan ? 0 : 1) + board->has_ao +
791 board->has_dig_in + board->has_dig_out +
792 board->has_ttl_io;
793 ret = comedi_alloc_subdevices(dev, num_subdevices: n_subdevices);
794 if (ret)
795 return ret;
796
797 subdev = 0;
798
799 /* Analog Input subdevice */
800 if (board->ai_n_chan) {
801 s = &dev->subdevices[subdev];
802 s->type = COMEDI_SUBD_AI;
803 s->subdev_flags = SDF_READABLE | board->ai_subdev_flags;
804 s->n_chan = board->ai_n_chan;
805 s->maxdata = board->ai_maxdata;
806 s->range_table = &apci3xxx_ai_range;
807 s->insn_read = apci3xxx_ai_insn_read;
808 if (dev->irq) {
809 /*
810 * FIXME: The hardware supports multiple scan modes
811 * but the original addi-data driver only supported
812 * reading a single channel with interrupts. Need a
813 * proper datasheet to fix this.
814 *
815 * The following scan modes are supported by the
816 * hardware:
817 * 1) Single software scan
818 * 2) Single hardware triggered scan
819 * 3) Continuous software scan
820 * 4) Continuous software scan with timer delay
821 * 5) Continuous hardware triggered scan
822 * 6) Continuous hardware triggered scan with timer
823 * delay
824 *
825 * For now, limit the chanlist to a single channel.
826 */
827 dev->read_subdev = s;
828 s->subdev_flags |= SDF_CMD_READ;
829 s->len_chanlist = 1;
830 s->do_cmdtest = apci3xxx_ai_cmdtest;
831 s->do_cmd = apci3xxx_ai_cmd;
832 s->cancel = apci3xxx_ai_cancel;
833 }
834
835 subdev++;
836 }
837
838 /* Analog Output subdevice */
839 if (board->has_ao) {
840 s = &dev->subdevices[subdev];
841 s->type = COMEDI_SUBD_AO;
842 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
843 s->n_chan = 4;
844 s->maxdata = 0x0fff;
845 s->range_table = &apci3xxx_ao_range;
846 s->insn_write = apci3xxx_ao_insn_write;
847
848 ret = comedi_alloc_subdev_readback(s);
849 if (ret)
850 return ret;
851
852 subdev++;
853 }
854
855 /* Digital Input subdevice */
856 if (board->has_dig_in) {
857 s = &dev->subdevices[subdev];
858 s->type = COMEDI_SUBD_DI;
859 s->subdev_flags = SDF_READABLE;
860 s->n_chan = 4;
861 s->maxdata = 1;
862 s->range_table = &range_digital;
863 s->insn_bits = apci3xxx_di_insn_bits;
864
865 subdev++;
866 }
867
868 /* Digital Output subdevice */
869 if (board->has_dig_out) {
870 s = &dev->subdevices[subdev];
871 s->type = COMEDI_SUBD_DO;
872 s->subdev_flags = SDF_WRITABLE;
873 s->n_chan = 4;
874 s->maxdata = 1;
875 s->range_table = &range_digital;
876 s->insn_bits = apci3xxx_do_insn_bits;
877
878 subdev++;
879 }
880
881 /* TTL Digital I/O subdevice */
882 if (board->has_ttl_io) {
883 s = &dev->subdevices[subdev];
884 s->type = COMEDI_SUBD_DIO;
885 s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
886 s->n_chan = 24;
887 s->maxdata = 1;
888 s->io_bits = 0xff; /* channels 0-7 are always outputs */
889 s->range_table = &range_digital;
890 s->insn_config = apci3xxx_dio_insn_config;
891 s->insn_bits = apci3xxx_dio_insn_bits;
892
893 subdev++;
894 }
895
896 apci3xxx_reset(dev);
897 return 0;
898}
899
900static void apci3xxx_detach(struct comedi_device *dev)
901{
902 if (dev->iobase)
903 apci3xxx_reset(dev);
904 comedi_pci_detach(dev);
905}
906
907static struct comedi_driver apci3xxx_driver = {
908 .driver_name = "addi_apci_3xxx",
909 .module = THIS_MODULE,
910 .auto_attach = apci3xxx_auto_attach,
911 .detach = apci3xxx_detach,
912};
913
914static int apci3xxx_pci_probe(struct pci_dev *dev,
915 const struct pci_device_id *id)
916{
917 return comedi_pci_auto_config(pcidev: dev, driver: &apci3xxx_driver, context: id->driver_data);
918}
919
920static const struct pci_device_id apci3xxx_pci_table[] = {
921 { PCI_VDEVICE(ADDIDATA, 0x3010), BOARD_APCI3000_16 },
922 { PCI_VDEVICE(ADDIDATA, 0x300f), BOARD_APCI3000_8 },
923 { PCI_VDEVICE(ADDIDATA, 0x300e), BOARD_APCI3000_4 },
924 { PCI_VDEVICE(ADDIDATA, 0x3013), BOARD_APCI3006_16 },
925 { PCI_VDEVICE(ADDIDATA, 0x3014), BOARD_APCI3006_8 },
926 { PCI_VDEVICE(ADDIDATA, 0x3015), BOARD_APCI3006_4 },
927 { PCI_VDEVICE(ADDIDATA, 0x3016), BOARD_APCI3010_16 },
928 { PCI_VDEVICE(ADDIDATA, 0x3017), BOARD_APCI3010_8 },
929 { PCI_VDEVICE(ADDIDATA, 0x3018), BOARD_APCI3010_4 },
930 { PCI_VDEVICE(ADDIDATA, 0x3019), BOARD_APCI3016_16 },
931 { PCI_VDEVICE(ADDIDATA, 0x301a), BOARD_APCI3016_8 },
932 { PCI_VDEVICE(ADDIDATA, 0x301b), BOARD_APCI3016_4 },
933 { PCI_VDEVICE(ADDIDATA, 0x301c), BOARD_APCI3100_16_4 },
934 { PCI_VDEVICE(ADDIDATA, 0x301d), BOARD_APCI3100_8_4 },
935 { PCI_VDEVICE(ADDIDATA, 0x301e), BOARD_APCI3106_16_4 },
936 { PCI_VDEVICE(ADDIDATA, 0x301f), BOARD_APCI3106_8_4 },
937 { PCI_VDEVICE(ADDIDATA, 0x3020), BOARD_APCI3110_16_4 },
938 { PCI_VDEVICE(ADDIDATA, 0x3021), BOARD_APCI3110_8_4 },
939 { PCI_VDEVICE(ADDIDATA, 0x3022), BOARD_APCI3116_16_4 },
940 { PCI_VDEVICE(ADDIDATA, 0x3023), BOARD_APCI3116_8_4 },
941 { PCI_VDEVICE(ADDIDATA, 0x300B), BOARD_APCI3003 },
942 { PCI_VDEVICE(ADDIDATA, 0x3002), BOARD_APCI3002_16 },
943 { PCI_VDEVICE(ADDIDATA, 0x3003), BOARD_APCI3002_8 },
944 { PCI_VDEVICE(ADDIDATA, 0x3004), BOARD_APCI3002_4 },
945 { PCI_VDEVICE(ADDIDATA, 0x3024), BOARD_APCI3500 },
946 { 0 }
947};
948MODULE_DEVICE_TABLE(pci, apci3xxx_pci_table);
949
950static struct pci_driver apci3xxx_pci_driver = {
951 .name = "addi_apci_3xxx",
952 .id_table = apci3xxx_pci_table,
953 .probe = apci3xxx_pci_probe,
954 .remove = comedi_pci_auto_unconfig,
955};
956module_comedi_pci_driver(apci3xxx_driver, apci3xxx_pci_driver);
957
958MODULE_AUTHOR("Comedi https://www.comedi.org");
959MODULE_DESCRIPTION("Comedi low-level driver");
960MODULE_LICENSE("GPL");
961

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