1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * comedi/drivers/dt2801.c |
4 | * Device Driver for DataTranslation DT2801 |
5 | * |
6 | */ |
7 | /* |
8 | * Driver: dt2801 |
9 | * Description: Data Translation DT2801 series and DT01-EZ |
10 | * Author: ds |
11 | * Status: works |
12 | * Devices: [Data Translation] DT2801 (dt2801), DT2801-A, DT2801/5716A, |
13 | * DT2805, DT2805/5716A, DT2808, DT2818, DT2809, DT01-EZ |
14 | * |
15 | * This driver can autoprobe the type of board. |
16 | * |
17 | * Configuration options: |
18 | * [0] - I/O port base address |
19 | * [1] - unused |
20 | * [2] - A/D reference 0=differential, 1=single-ended |
21 | * [3] - A/D range |
22 | * 0 = [-10, 10] |
23 | * 1 = [0,10] |
24 | * [4] - D/A 0 range |
25 | * 0 = [-10, 10] |
26 | * 1 = [-5,5] |
27 | * 2 = [-2.5,2.5] |
28 | * 3 = [0,10] |
29 | * 4 = [0,5] |
30 | * [5] - D/A 1 range (same choices) |
31 | */ |
32 | |
33 | #include <linux/module.h> |
34 | #include <linux/comedi/comedidev.h> |
35 | #include <linux/delay.h> |
36 | |
37 | #define DT2801_TIMEOUT 1000 |
38 | |
39 | /* Hardware Configuration */ |
40 | /* ====================== */ |
41 | |
42 | #define DT2801_MAX_DMA_SIZE (64 * 1024) |
43 | |
44 | /* define's */ |
45 | /* ====================== */ |
46 | |
47 | /* Commands */ |
48 | #define DT_C_RESET 0x0 |
49 | #define DT_C_CLEAR_ERR 0x1 |
50 | #define DT_C_READ_ERRREG 0x2 |
51 | #define DT_C_SET_CLOCK 0x3 |
52 | |
53 | #define DT_C_TEST 0xb |
54 | #define DT_C_STOP 0xf |
55 | |
56 | #define DT_C_SET_DIGIN 0x4 |
57 | #define DT_C_SET_DIGOUT 0x5 |
58 | #define DT_C_READ_DIG 0x6 |
59 | #define DT_C_WRITE_DIG 0x7 |
60 | |
61 | #define DT_C_WRITE_DAIM 0x8 |
62 | #define DT_C_SET_DA 0x9 |
63 | #define DT_C_WRITE_DA 0xa |
64 | |
65 | #define DT_C_READ_ADIM 0xc |
66 | #define DT_C_SET_AD 0xd |
67 | #define DT_C_READ_AD 0xe |
68 | |
69 | /* |
70 | * Command modifiers (only used with read/write), EXTTRIG can be |
71 | * used with some other commands. |
72 | */ |
73 | #define DT_MOD_DMA BIT(4) |
74 | #define DT_MOD_CONT BIT(5) |
75 | #define DT_MOD_EXTCLK BIT(6) |
76 | #define DT_MOD_EXTTRIG BIT(7) |
77 | |
78 | /* Bits in status register */ |
79 | #define DT_S_DATA_OUT_READY BIT(0) |
80 | #define DT_S_DATA_IN_FULL BIT(1) |
81 | #define DT_S_READY BIT(2) |
82 | #define DT_S_COMMAND BIT(3) |
83 | #define DT_S_COMPOSITE_ERROR BIT(7) |
84 | |
85 | /* registers */ |
86 | #define DT2801_DATA 0 |
87 | #define DT2801_STATUS 1 |
88 | #define DT2801_CMD 1 |
89 | |
90 | #if 0 |
91 | /* ignore 'defined but not used' warning */ |
92 | static const struct comedi_lrange range_dt2801_ai_pgh_bipolar = { |
93 | 4, { |
94 | BIP_RANGE(10), |
95 | BIP_RANGE(5), |
96 | BIP_RANGE(2.5), |
97 | BIP_RANGE(1.25) |
98 | } |
99 | }; |
100 | #endif |
101 | static const struct comedi_lrange range_dt2801_ai_pgl_bipolar = { |
102 | 4, { |
103 | BIP_RANGE(10), |
104 | BIP_RANGE(1), |
105 | BIP_RANGE(0.1), |
106 | BIP_RANGE(0.02) |
107 | } |
108 | }; |
109 | |
110 | #if 0 |
111 | /* ignore 'defined but not used' warning */ |
112 | static const struct comedi_lrange range_dt2801_ai_pgh_unipolar = { |
113 | 4, { |
114 | UNI_RANGE(10), |
115 | UNI_RANGE(5), |
116 | UNI_RANGE(2.5), |
117 | UNI_RANGE(1.25) |
118 | } |
119 | }; |
120 | #endif |
121 | static const struct comedi_lrange range_dt2801_ai_pgl_unipolar = { |
122 | 4, { |
123 | UNI_RANGE(10), |
124 | UNI_RANGE(1), |
125 | UNI_RANGE(0.1), |
126 | UNI_RANGE(0.02) |
127 | } |
128 | }; |
129 | |
130 | struct dt2801_board { |
131 | const char *name; |
132 | int boardcode; |
133 | int ad_diff; |
134 | int ad_chan; |
135 | int adbits; |
136 | int adrangetype; |
137 | int dabits; |
138 | }; |
139 | |
140 | /* |
141 | * Typeid's for the different boards of the DT2801-series |
142 | * (taken from the test-software, that comes with the board) |
143 | */ |
144 | static const struct dt2801_board boardtypes[] = { |
145 | { |
146 | .name = "dt2801" , |
147 | .boardcode = 0x09, |
148 | .ad_diff = 2, |
149 | .ad_chan = 16, |
150 | .adbits = 12, |
151 | .adrangetype = 0, |
152 | .dabits = 12}, |
153 | { |
154 | .name = "dt2801-a" , |
155 | .boardcode = 0x52, |
156 | .ad_diff = 2, |
157 | .ad_chan = 16, |
158 | .adbits = 12, |
159 | .adrangetype = 0, |
160 | .dabits = 12}, |
161 | { |
162 | .name = "dt2801/5716a" , |
163 | .boardcode = 0x82, |
164 | .ad_diff = 1, |
165 | .ad_chan = 16, |
166 | .adbits = 16, |
167 | .adrangetype = 1, |
168 | .dabits = 12}, |
169 | { |
170 | .name = "dt2805" , |
171 | .boardcode = 0x12, |
172 | .ad_diff = 1, |
173 | .ad_chan = 16, |
174 | .adbits = 12, |
175 | .adrangetype = 0, |
176 | .dabits = 12}, |
177 | { |
178 | .name = "dt2805/5716a" , |
179 | .boardcode = 0x92, |
180 | .ad_diff = 1, |
181 | .ad_chan = 16, |
182 | .adbits = 16, |
183 | .adrangetype = 1, |
184 | .dabits = 12}, |
185 | { |
186 | .name = "dt2808" , |
187 | .boardcode = 0x20, |
188 | .ad_diff = 0, |
189 | .ad_chan = 16, |
190 | .adbits = 12, |
191 | .adrangetype = 2, |
192 | .dabits = 8}, |
193 | { |
194 | .name = "dt2818" , |
195 | .boardcode = 0xa2, |
196 | .ad_diff = 0, |
197 | .ad_chan = 4, |
198 | .adbits = 12, |
199 | .adrangetype = 0, |
200 | .dabits = 12}, |
201 | { |
202 | .name = "dt2809" , |
203 | .boardcode = 0xb0, |
204 | .ad_diff = 0, |
205 | .ad_chan = 8, |
206 | .adbits = 12, |
207 | .adrangetype = 1, |
208 | .dabits = 12}, |
209 | }; |
210 | |
211 | struct dt2801_private { |
212 | const struct comedi_lrange *dac_range_types[2]; |
213 | }; |
214 | |
215 | /* |
216 | * These are the low-level routines: |
217 | * writecommand: write a command to the board |
218 | * writedata: write data byte |
219 | * readdata: read data byte |
220 | */ |
221 | |
222 | /* |
223 | * Only checks DataOutReady-flag, not the Ready-flag as it is done |
224 | * in the examples of the manual. I don't see why this should be |
225 | * necessary. |
226 | */ |
227 | static int dt2801_readdata(struct comedi_device *dev, int *data) |
228 | { |
229 | int stat = 0; |
230 | int timeout = DT2801_TIMEOUT; |
231 | |
232 | do { |
233 | stat = inb_p(port: dev->iobase + DT2801_STATUS); |
234 | if (stat & (DT_S_COMPOSITE_ERROR | DT_S_READY)) |
235 | return stat; |
236 | if (stat & DT_S_DATA_OUT_READY) { |
237 | *data = inb_p(port: dev->iobase + DT2801_DATA); |
238 | return 0; |
239 | } |
240 | } while (--timeout > 0); |
241 | |
242 | return -ETIME; |
243 | } |
244 | |
245 | static int dt2801_readdata2(struct comedi_device *dev, int *data) |
246 | { |
247 | int lb = 0; |
248 | int hb = 0; |
249 | int ret; |
250 | |
251 | ret = dt2801_readdata(dev, data: &lb); |
252 | if (ret) |
253 | return ret; |
254 | ret = dt2801_readdata(dev, data: &hb); |
255 | if (ret) |
256 | return ret; |
257 | |
258 | *data = (hb << 8) + lb; |
259 | return 0; |
260 | } |
261 | |
262 | static int dt2801_writedata(struct comedi_device *dev, unsigned int data) |
263 | { |
264 | int stat = 0; |
265 | int timeout = DT2801_TIMEOUT; |
266 | |
267 | do { |
268 | stat = inb_p(port: dev->iobase + DT2801_STATUS); |
269 | |
270 | if (stat & DT_S_COMPOSITE_ERROR) |
271 | return stat; |
272 | if (!(stat & DT_S_DATA_IN_FULL)) { |
273 | outb_p(value: data & 0xff, port: dev->iobase + DT2801_DATA); |
274 | return 0; |
275 | } |
276 | } while (--timeout > 0); |
277 | |
278 | return -ETIME; |
279 | } |
280 | |
281 | static int dt2801_writedata2(struct comedi_device *dev, unsigned int data) |
282 | { |
283 | int ret; |
284 | |
285 | ret = dt2801_writedata(dev, data: data & 0xff); |
286 | if (ret < 0) |
287 | return ret; |
288 | ret = dt2801_writedata(dev, data: data >> 8); |
289 | if (ret < 0) |
290 | return ret; |
291 | |
292 | return 0; |
293 | } |
294 | |
295 | static int dt2801_wait_for_ready(struct comedi_device *dev) |
296 | { |
297 | int timeout = DT2801_TIMEOUT; |
298 | int stat; |
299 | |
300 | stat = inb_p(port: dev->iobase + DT2801_STATUS); |
301 | if (stat & DT_S_READY) |
302 | return 0; |
303 | do { |
304 | stat = inb_p(port: dev->iobase + DT2801_STATUS); |
305 | |
306 | if (stat & DT_S_COMPOSITE_ERROR) |
307 | return stat; |
308 | if (stat & DT_S_READY) |
309 | return 0; |
310 | } while (--timeout > 0); |
311 | |
312 | return -ETIME; |
313 | } |
314 | |
315 | static void dt2801_writecmd(struct comedi_device *dev, int command) |
316 | { |
317 | int stat; |
318 | |
319 | dt2801_wait_for_ready(dev); |
320 | |
321 | stat = inb_p(port: dev->iobase + DT2801_STATUS); |
322 | if (stat & DT_S_COMPOSITE_ERROR) { |
323 | dev_dbg(dev->class_dev, |
324 | "composite-error in %s, ignoring\n" , __func__); |
325 | } |
326 | if (!(stat & DT_S_READY)) |
327 | dev_dbg(dev->class_dev, "!ready in %s, ignoring\n" , __func__); |
328 | outb_p(value: command, port: dev->iobase + DT2801_CMD); |
329 | } |
330 | |
331 | static int dt2801_reset(struct comedi_device *dev) |
332 | { |
333 | int board_code = 0; |
334 | unsigned int stat; |
335 | int timeout; |
336 | |
337 | /* pull random data from data port */ |
338 | inb_p(port: dev->iobase + DT2801_DATA); |
339 | inb_p(port: dev->iobase + DT2801_DATA); |
340 | inb_p(port: dev->iobase + DT2801_DATA); |
341 | inb_p(port: dev->iobase + DT2801_DATA); |
342 | |
343 | /* dt2801_writecmd(dev,DT_C_STOP); */ |
344 | outb_p(DT_C_STOP, port: dev->iobase + DT2801_CMD); |
345 | |
346 | /* dt2801_wait_for_ready(dev); */ |
347 | usleep_range(min: 100, max: 200); |
348 | timeout = 10000; |
349 | do { |
350 | stat = inb_p(port: dev->iobase + DT2801_STATUS); |
351 | if (stat & DT_S_READY) |
352 | break; |
353 | } while (timeout--); |
354 | if (!timeout) |
355 | dev_dbg(dev->class_dev, "timeout 1 status=0x%02x\n" , stat); |
356 | |
357 | /* dt2801_readdata(dev,&board_code); */ |
358 | |
359 | outb_p(DT_C_RESET, port: dev->iobase + DT2801_CMD); |
360 | /* dt2801_writecmd(dev,DT_C_RESET); */ |
361 | |
362 | usleep_range(min: 100, max: 200); |
363 | timeout = 10000; |
364 | do { |
365 | stat = inb_p(port: dev->iobase + DT2801_STATUS); |
366 | if (stat & DT_S_READY) |
367 | break; |
368 | } while (timeout--); |
369 | if (!timeout) |
370 | dev_dbg(dev->class_dev, "timeout 2 status=0x%02x\n" , stat); |
371 | |
372 | dt2801_readdata(dev, data: &board_code); |
373 | |
374 | return board_code; |
375 | } |
376 | |
377 | static int probe_number_of_ai_chans(struct comedi_device *dev) |
378 | { |
379 | int n_chans; |
380 | int stat; |
381 | int data; |
382 | |
383 | for (n_chans = 0; n_chans < 16; n_chans++) { |
384 | dt2801_writecmd(dev, DT_C_READ_ADIM); |
385 | dt2801_writedata(dev, data: 0); |
386 | dt2801_writedata(dev, data: n_chans); |
387 | stat = dt2801_readdata2(dev, data: &data); |
388 | |
389 | if (stat) |
390 | break; |
391 | } |
392 | |
393 | dt2801_reset(dev); |
394 | dt2801_reset(dev); |
395 | |
396 | return n_chans; |
397 | } |
398 | |
399 | static const struct comedi_lrange *dac_range_table[] = { |
400 | &range_bipolar10, |
401 | &range_bipolar5, |
402 | &range_bipolar2_5, |
403 | &range_unipolar10, |
404 | &range_unipolar5 |
405 | }; |
406 | |
407 | static const struct comedi_lrange *dac_range_lkup(int opt) |
408 | { |
409 | if (opt < 0 || opt >= 5) |
410 | return &range_unknown; |
411 | return dac_range_table[opt]; |
412 | } |
413 | |
414 | static const struct comedi_lrange *ai_range_lkup(int type, int opt) |
415 | { |
416 | switch (type) { |
417 | case 0: |
418 | return (opt) ? |
419 | &range_dt2801_ai_pgl_unipolar : |
420 | &range_dt2801_ai_pgl_bipolar; |
421 | case 1: |
422 | return (opt) ? &range_unipolar10 : &range_bipolar10; |
423 | case 2: |
424 | return &range_unipolar5; |
425 | } |
426 | return &range_unknown; |
427 | } |
428 | |
429 | static int dt2801_error(struct comedi_device *dev, int stat) |
430 | { |
431 | if (stat < 0) { |
432 | if (stat == -ETIME) |
433 | dev_dbg(dev->class_dev, "timeout\n" ); |
434 | else |
435 | dev_dbg(dev->class_dev, "error %d\n" , stat); |
436 | return stat; |
437 | } |
438 | dev_dbg(dev->class_dev, "error status 0x%02x, resetting...\n" , stat); |
439 | |
440 | dt2801_reset(dev); |
441 | dt2801_reset(dev); |
442 | |
443 | return -EIO; |
444 | } |
445 | |
446 | static int dt2801_ai_insn_read(struct comedi_device *dev, |
447 | struct comedi_subdevice *s, |
448 | struct comedi_insn *insn, unsigned int *data) |
449 | { |
450 | int d; |
451 | int stat; |
452 | int i; |
453 | |
454 | for (i = 0; i < insn->n; i++) { |
455 | dt2801_writecmd(dev, DT_C_READ_ADIM); |
456 | dt2801_writedata(dev, CR_RANGE(insn->chanspec)); |
457 | dt2801_writedata(dev, CR_CHAN(insn->chanspec)); |
458 | stat = dt2801_readdata2(dev, data: &d); |
459 | |
460 | if (stat != 0) |
461 | return dt2801_error(dev, stat); |
462 | |
463 | data[i] = d; |
464 | } |
465 | |
466 | return i; |
467 | } |
468 | |
469 | static int dt2801_ao_insn_write(struct comedi_device *dev, |
470 | struct comedi_subdevice *s, |
471 | struct comedi_insn *insn, |
472 | unsigned int *data) |
473 | { |
474 | unsigned int chan = CR_CHAN(insn->chanspec); |
475 | |
476 | dt2801_writecmd(dev, DT_C_WRITE_DAIM); |
477 | dt2801_writedata(dev, data: chan); |
478 | dt2801_writedata2(dev, data: data[0]); |
479 | |
480 | s->readback[chan] = data[0]; |
481 | |
482 | return 1; |
483 | } |
484 | |
485 | static int dt2801_dio_insn_bits(struct comedi_device *dev, |
486 | struct comedi_subdevice *s, |
487 | struct comedi_insn *insn, |
488 | unsigned int *data) |
489 | { |
490 | int which = (s == &dev->subdevices[3]) ? 1 : 0; |
491 | unsigned int val = 0; |
492 | |
493 | if (comedi_dio_update_state(s, data)) { |
494 | dt2801_writecmd(dev, DT_C_WRITE_DIG); |
495 | dt2801_writedata(dev, data: which); |
496 | dt2801_writedata(dev, data: s->state); |
497 | } |
498 | |
499 | dt2801_writecmd(dev, DT_C_READ_DIG); |
500 | dt2801_writedata(dev, data: which); |
501 | dt2801_readdata(dev, data: &val); |
502 | |
503 | data[1] = val; |
504 | |
505 | return insn->n; |
506 | } |
507 | |
508 | static int dt2801_dio_insn_config(struct comedi_device *dev, |
509 | struct comedi_subdevice *s, |
510 | struct comedi_insn *insn, |
511 | unsigned int *data) |
512 | { |
513 | int ret; |
514 | |
515 | ret = comedi_dio_insn_config(dev, s, insn, data, mask: 0xff); |
516 | if (ret) |
517 | return ret; |
518 | |
519 | dt2801_writecmd(dev, command: s->io_bits ? DT_C_SET_DIGOUT : DT_C_SET_DIGIN); |
520 | dt2801_writedata(dev, data: (s == &dev->subdevices[3]) ? 1 : 0); |
521 | |
522 | return insn->n; |
523 | } |
524 | |
525 | /* |
526 | * options: |
527 | * [0] - i/o base |
528 | * [1] - unused |
529 | * [2] - a/d 0=differential, 1=single-ended |
530 | * [3] - a/d range 0=[-10,10], 1=[0,10] |
531 | * [4] - dac0 range 0=[-10,10], 1=[-5,5], 2=[-2.5,2.5] 3=[0,10], 4=[0,5] |
532 | * [5] - dac1 range 0=[-10,10], 1=[-5,5], 2=[-2.5,2.5] 3=[0,10], 4=[0,5] |
533 | */ |
534 | static int dt2801_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
535 | { |
536 | const struct dt2801_board *board; |
537 | struct dt2801_private *devpriv; |
538 | struct comedi_subdevice *s; |
539 | int board_code, type; |
540 | int ret = 0; |
541 | int n_ai_chans; |
542 | |
543 | ret = comedi_request_region(dev, start: it->options[0], len: 0x2); |
544 | if (ret) |
545 | return ret; |
546 | |
547 | /* do some checking */ |
548 | |
549 | board_code = dt2801_reset(dev); |
550 | |
551 | /* heh. if it didn't work, try it again. */ |
552 | if (!board_code) |
553 | board_code = dt2801_reset(dev); |
554 | |
555 | for (type = 0; type < ARRAY_SIZE(boardtypes); type++) { |
556 | if (boardtypes[type].boardcode == board_code) |
557 | goto havetype; |
558 | } |
559 | dev_dbg(dev->class_dev, |
560 | "unrecognized board code=0x%02x, contact author\n" , board_code); |
561 | type = 0; |
562 | |
563 | havetype: |
564 | dev->board_ptr = boardtypes + type; |
565 | board = dev->board_ptr; |
566 | |
567 | n_ai_chans = probe_number_of_ai_chans(dev); |
568 | |
569 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
570 | if (ret) |
571 | goto out; |
572 | |
573 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
574 | if (!devpriv) |
575 | return -ENOMEM; |
576 | |
577 | dev->board_name = board->name; |
578 | |
579 | s = &dev->subdevices[0]; |
580 | /* ai subdevice */ |
581 | s->type = COMEDI_SUBD_AI; |
582 | s->subdev_flags = SDF_READABLE | SDF_GROUND; |
583 | #if 1 |
584 | s->n_chan = n_ai_chans; |
585 | #else |
586 | if (it->options[2]) |
587 | s->n_chan = board->ad_chan; |
588 | else |
589 | s->n_chan = board->ad_chan / 2; |
590 | #endif |
591 | s->maxdata = (1 << board->adbits) - 1; |
592 | s->range_table = ai_range_lkup(type: board->adrangetype, opt: it->options[3]); |
593 | s->insn_read = dt2801_ai_insn_read; |
594 | |
595 | s = &dev->subdevices[1]; |
596 | /* ao subdevice */ |
597 | s->type = COMEDI_SUBD_AO; |
598 | s->subdev_flags = SDF_WRITABLE; |
599 | s->n_chan = 2; |
600 | s->maxdata = (1 << board->dabits) - 1; |
601 | s->range_table_list = devpriv->dac_range_types; |
602 | devpriv->dac_range_types[0] = dac_range_lkup(opt: it->options[4]); |
603 | devpriv->dac_range_types[1] = dac_range_lkup(opt: it->options[5]); |
604 | s->insn_write = dt2801_ao_insn_write; |
605 | |
606 | ret = comedi_alloc_subdev_readback(s); |
607 | if (ret) |
608 | return ret; |
609 | |
610 | s = &dev->subdevices[2]; |
611 | /* 1st digital subdevice */ |
612 | s->type = COMEDI_SUBD_DIO; |
613 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
614 | s->n_chan = 8; |
615 | s->maxdata = 1; |
616 | s->range_table = &range_digital; |
617 | s->insn_bits = dt2801_dio_insn_bits; |
618 | s->insn_config = dt2801_dio_insn_config; |
619 | |
620 | s = &dev->subdevices[3]; |
621 | /* 2nd digital subdevice */ |
622 | s->type = COMEDI_SUBD_DIO; |
623 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
624 | s->n_chan = 8; |
625 | s->maxdata = 1; |
626 | s->range_table = &range_digital; |
627 | s->insn_bits = dt2801_dio_insn_bits; |
628 | s->insn_config = dt2801_dio_insn_config; |
629 | |
630 | ret = 0; |
631 | out: |
632 | return ret; |
633 | } |
634 | |
635 | static struct comedi_driver dt2801_driver = { |
636 | .driver_name = "dt2801" , |
637 | .module = THIS_MODULE, |
638 | .attach = dt2801_attach, |
639 | .detach = comedi_legacy_detach, |
640 | }; |
641 | module_comedi_driver(dt2801_driver); |
642 | |
643 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
644 | MODULE_DESCRIPTION("Comedi low-level driver" ); |
645 | MODULE_LICENSE("GPL" ); |
646 | |