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 | |
24 | static 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 | |
37 | static const struct comedi_lrange apci3xxx_ao_range = { |
38 | 2, { |
39 | BIP_RANGE(10), |
40 | UNI_RANGE(10) |
41 | } |
42 | }; |
43 | |
44 | enum 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 | |
72 | struct 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 | |
85 | static 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 | |
342 | struct apci3xxx_private { |
343 | unsigned int ai_timer; |
344 | unsigned char ai_time_base; |
345 | }; |
346 | |
347 | static 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 | |
371 | static 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 | |
379 | static 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 | |
418 | static 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 | |
431 | static 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 | |
459 | static 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 | |
509 | static 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 | |
566 | static 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 | |
589 | static int apci3xxx_ai_cancel(struct comedi_device *dev, |
590 | struct comedi_subdevice *s) |
591 | { |
592 | return 0; |
593 | } |
594 | |
595 | static 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 | |
608 | static 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 | |
638 | static 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 | |
648 | static 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 | |
663 | static 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 | |
696 | static 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 | |
724 | static 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 | |
752 | static 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 | |
900 | static void apci3xxx_detach(struct comedi_device *dev) |
901 | { |
902 | if (dev->iobase) |
903 | apci3xxx_reset(dev); |
904 | comedi_pci_detach(dev); |
905 | } |
906 | |
907 | static 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 | |
914 | static 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 | |
920 | static 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 | }; |
948 | MODULE_DEVICE_TABLE(pci, apci3xxx_pci_table); |
949 | |
950 | static 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 | }; |
956 | module_comedi_pci_driver(apci3xxx_driver, apci3xxx_pci_driver); |
957 | |
958 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
959 | MODULE_DESCRIPTION("Comedi low-level driver" ); |
960 | MODULE_LICENSE("GPL" ); |
961 | |