1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Comedi driver for Data Translation DT2811 |
4 | * |
5 | * COMEDI - Linux Control and Measurement Device Interface |
6 | * Copyright (C) David A. Schleef <ds@schleef.org> |
7 | */ |
8 | |
9 | /* |
10 | * Driver: dt2811 |
11 | * Description: Data Translation DT2811 |
12 | * Author: ds |
13 | * Devices: [Data Translation] DT2811-PGL (dt2811-pgl), DT2811-PGH (dt2811-pgh) |
14 | * Status: works |
15 | * |
16 | * Configuration options: |
17 | * [0] - I/O port base address |
18 | * [1] - IRQ (optional, needed for async command support) |
19 | * [2] - A/D reference (# of analog inputs) |
20 | * 0 = single-ended (16 channels) |
21 | * 1 = differential (8 channels) |
22 | * 2 = pseudo-differential (16 channels) |
23 | * [3] - A/D range (deprecated, see below) |
24 | * [4] - D/A 0 range (deprecated, see below) |
25 | * [5] - D/A 1 range (deprecated, see below) |
26 | * |
27 | * Notes: |
28 | * - A/D ranges are not programmable but the gain is. The AI subdevice has |
29 | * a range_table containing all the possible analog input range/gain |
30 | * options for the dt2811-pgh or dt2811-pgl. Use the range that matches |
31 | * your board configuration and the desired gain to correctly convert |
32 | * between data values and physical units and to set the correct output |
33 | * gain. |
34 | * - D/A ranges are not programmable. The AO subdevice has a range_table |
35 | * containing all the possible analog output ranges. Use the range |
36 | * that matches your board configuration to convert between data |
37 | * values and physical units. |
38 | */ |
39 | |
40 | #include <linux/module.h> |
41 | #include <linux/interrupt.h> |
42 | #include <linux/delay.h> |
43 | #include <linux/comedi/comedidev.h> |
44 | |
45 | /* |
46 | * Register I/O map |
47 | */ |
48 | #define DT2811_ADCSR_REG 0x00 /* r/w A/D Control/Status */ |
49 | #define DT2811_ADCSR_ADDONE BIT(7) /* r 1=A/D conv done */ |
50 | #define DT2811_ADCSR_ADERROR BIT(6) /* r 1=A/D error */ |
51 | #define DT2811_ADCSR_ADBUSY BIT(5) /* r 1=A/D busy */ |
52 | #define DT2811_ADCSR_CLRERROR BIT(4) |
53 | #define DT2811_ADCSR_DMAENB BIT(3) /* r/w 1=dma ena */ |
54 | #define DT2811_ADCSR_INTENB BIT(2) /* r/w 1=interrupts ena */ |
55 | #define DT2811_ADCSR_ADMODE(x) (((x) & 0x3) << 0) |
56 | |
57 | #define DT2811_ADGCR_REG 0x01 /* r/w A/D Gain/Channel */ |
58 | #define DT2811_ADGCR_GAIN(x) (((x) & 0x3) << 6) |
59 | #define DT2811_ADGCR_CHAN(x) (((x) & 0xf) << 0) |
60 | |
61 | #define DT2811_ADDATA_LO_REG 0x02 /* r A/D Data low byte */ |
62 | #define DT2811_ADDATA_HI_REG 0x03 /* r A/D Data high byte */ |
63 | |
64 | #define DT2811_DADATA_LO_REG(x) (0x02 + ((x) * 2)) /* w D/A Data low */ |
65 | #define DT2811_DADATA_HI_REG(x) (0x03 + ((x) * 2)) /* w D/A Data high */ |
66 | |
67 | #define DT2811_DI_REG 0x06 /* r Digital Input Port 0 */ |
68 | #define DT2811_DO_REG 0x06 /* w Digital Output Port 1 */ |
69 | |
70 | #define DT2811_TMRCTR_REG 0x07 /* r/w Timer/Counter */ |
71 | #define DT2811_TMRCTR_MANTISSA(x) (((x) & 0x7) << 3) |
72 | #define DT2811_TMRCTR_EXPONENT(x) (((x) & 0x7) << 0) |
73 | |
74 | #define DT2811_OSC_BASE 1666 /* 600 kHz = 1666.6667ns */ |
75 | |
76 | /* |
77 | * Timer frequency control: |
78 | * DT2811_TMRCTR_MANTISSA DT2811_TMRCTR_EXPONENT |
79 | * val divisor frequency val multiply divisor/divide frequency by |
80 | * 0 1 600 kHz 0 1 |
81 | * 1 10 60 kHz 1 10 |
82 | * 2 2 300 kHz 2 100 |
83 | * 3 3 200 kHz 3 1000 |
84 | * 4 4 150 kHz 4 10000 |
85 | * 5 5 120 kHz 5 100000 |
86 | * 6 6 100 kHz 6 1000000 |
87 | * 7 12 50 kHz 7 10000000 |
88 | */ |
89 | static const unsigned int dt2811_clk_dividers[] = { |
90 | 1, 10, 2, 3, 4, 5, 6, 12 |
91 | }; |
92 | |
93 | static const unsigned int dt2811_clk_multipliers[] = { |
94 | 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 |
95 | }; |
96 | |
97 | /* |
98 | * The Analog Input range is set using jumpers on the board. |
99 | * |
100 | * Input Range W9 W10 |
101 | * -5V to +5V In Out |
102 | * -2.5V to +2.5V In In |
103 | * 0V to +5V Out In |
104 | * |
105 | * The gain may be set to 1, 2, 4, or 8 (on the dt2811-pgh) or to |
106 | * 1, 10, 100, 500 (on the dt2811-pgl). |
107 | */ |
108 | static const struct comedi_lrange dt2811_pgh_ai_ranges = { |
109 | 12, { |
110 | BIP_RANGE(5), /* range 0: gain=1 */ |
111 | BIP_RANGE(2.5), /* range 1: gain=2 */ |
112 | BIP_RANGE(1.25), /* range 2: gain=4 */ |
113 | BIP_RANGE(0.625), /* range 3: gain=8 */ |
114 | |
115 | BIP_RANGE(2.5), /* range 0+4: gain=1 */ |
116 | BIP_RANGE(1.25), /* range 1+4: gain=2 */ |
117 | BIP_RANGE(0.625), /* range 2+4: gain=4 */ |
118 | BIP_RANGE(0.3125), /* range 3+4: gain=8 */ |
119 | |
120 | UNI_RANGE(5), /* range 0+8: gain=1 */ |
121 | UNI_RANGE(2.5), /* range 1+8: gain=2 */ |
122 | UNI_RANGE(1.25), /* range 2+8: gain=4 */ |
123 | UNI_RANGE(0.625) /* range 3+8: gain=8 */ |
124 | } |
125 | }; |
126 | |
127 | static const struct comedi_lrange dt2811_pgl_ai_ranges = { |
128 | 12, { |
129 | BIP_RANGE(5), /* range 0: gain=1 */ |
130 | BIP_RANGE(0.5), /* range 1: gain=10 */ |
131 | BIP_RANGE(0.05), /* range 2: gain=100 */ |
132 | BIP_RANGE(0.01), /* range 3: gain=500 */ |
133 | |
134 | BIP_RANGE(2.5), /* range 0+4: gain=1 */ |
135 | BIP_RANGE(0.25), /* range 1+4: gain=10 */ |
136 | BIP_RANGE(0.025), /* range 2+4: gain=100 */ |
137 | BIP_RANGE(0.005), /* range 3+4: gain=500 */ |
138 | |
139 | UNI_RANGE(5), /* range 0+8: gain=1 */ |
140 | UNI_RANGE(0.5), /* range 1+8: gain=10 */ |
141 | UNI_RANGE(0.05), /* range 2+8: gain=100 */ |
142 | UNI_RANGE(0.01) /* range 3+8: gain=500 */ |
143 | } |
144 | }; |
145 | |
146 | /* |
147 | * The Analog Output range is set per-channel using jumpers on the board. |
148 | * |
149 | * DAC0 Jumpers DAC1 Jumpers |
150 | * Output Range W5 W6 W7 W8 W1 W2 W3 W4 |
151 | * -5V to +5V In Out In Out In Out In Out |
152 | * -2.5V to +2.5V In Out Out In In Out Out In |
153 | * 0 to +5V Out In Out In Out In Out In |
154 | */ |
155 | static const struct comedi_lrange dt2811_ao_ranges = { |
156 | 3, { |
157 | BIP_RANGE(5), /* default setting from factory */ |
158 | BIP_RANGE(2.5), |
159 | UNI_RANGE(5) |
160 | } |
161 | }; |
162 | |
163 | struct dt2811_board { |
164 | const char *name; |
165 | unsigned int is_pgh:1; |
166 | }; |
167 | |
168 | static const struct dt2811_board dt2811_boards[] = { |
169 | { |
170 | .name = "dt2811-pgh" , |
171 | .is_pgh = 1, |
172 | }, { |
173 | .name = "dt2811-pgl" , |
174 | }, |
175 | }; |
176 | |
177 | struct dt2811_private { |
178 | unsigned int ai_divisor; |
179 | }; |
180 | |
181 | static unsigned int dt2811_ai_read_sample(struct comedi_device *dev, |
182 | struct comedi_subdevice *s) |
183 | { |
184 | unsigned int val; |
185 | |
186 | val = inb(port: dev->iobase + DT2811_ADDATA_LO_REG) | |
187 | (inb(port: dev->iobase + DT2811_ADDATA_HI_REG) << 8); |
188 | |
189 | return val & s->maxdata; |
190 | } |
191 | |
192 | static irqreturn_t dt2811_interrupt(int irq, void *d) |
193 | { |
194 | struct comedi_device *dev = d; |
195 | struct comedi_subdevice *s = dev->read_subdev; |
196 | struct comedi_async *async = s->async; |
197 | struct comedi_cmd *cmd = &async->cmd; |
198 | unsigned int status; |
199 | |
200 | if (!dev->attached) |
201 | return IRQ_NONE; |
202 | |
203 | status = inb(port: dev->iobase + DT2811_ADCSR_REG); |
204 | |
205 | if (status & DT2811_ADCSR_ADERROR) { |
206 | async->events |= COMEDI_CB_OVERFLOW; |
207 | |
208 | outb(value: status | DT2811_ADCSR_CLRERROR, |
209 | port: dev->iobase + DT2811_ADCSR_REG); |
210 | } |
211 | |
212 | if (status & DT2811_ADCSR_ADDONE) { |
213 | unsigned short val; |
214 | |
215 | val = dt2811_ai_read_sample(dev, s); |
216 | comedi_buf_write_samples(s, data: &val, nsamples: 1); |
217 | } |
218 | |
219 | if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) |
220 | async->events |= COMEDI_CB_EOA; |
221 | |
222 | comedi_handle_events(dev, s); |
223 | |
224 | return IRQ_HANDLED; |
225 | } |
226 | |
227 | static int dt2811_ai_cancel(struct comedi_device *dev, |
228 | struct comedi_subdevice *s) |
229 | { |
230 | /* |
231 | * Mode 0 |
232 | * Single conversion |
233 | * |
234 | * Loading a chanspec will trigger a conversion. |
235 | */ |
236 | outb(DT2811_ADCSR_ADMODE(0), port: dev->iobase + DT2811_ADCSR_REG); |
237 | |
238 | return 0; |
239 | } |
240 | |
241 | static void dt2811_ai_set_chanspec(struct comedi_device *dev, |
242 | unsigned int chanspec) |
243 | { |
244 | unsigned int chan = CR_CHAN(chanspec); |
245 | unsigned int range = CR_RANGE(chanspec); |
246 | |
247 | outb(DT2811_ADGCR_CHAN(chan) | DT2811_ADGCR_GAIN(range), |
248 | port: dev->iobase + DT2811_ADGCR_REG); |
249 | } |
250 | |
251 | static int dt2811_ai_cmd(struct comedi_device *dev, |
252 | struct comedi_subdevice *s) |
253 | { |
254 | struct dt2811_private *devpriv = dev->private; |
255 | struct comedi_cmd *cmd = &s->async->cmd; |
256 | unsigned int mode; |
257 | |
258 | if (cmd->start_src == TRIG_NOW) { |
259 | /* |
260 | * Mode 1 |
261 | * Continuous conversion, internal trigger and clock |
262 | * |
263 | * This resets the trigger flip-flop, disabling A/D strobes. |
264 | * The timer/counter register is loaded with the division |
265 | * ratio which will give the required sample rate. |
266 | * |
267 | * Loading the first chanspec sets the trigger flip-flop, |
268 | * enabling the timer/counter. A/D strobes are then generated |
269 | * at the rate set by the internal clock/divider. |
270 | */ |
271 | mode = DT2811_ADCSR_ADMODE(1); |
272 | } else { /* TRIG_EXT */ |
273 | if (cmd->convert_src == TRIG_TIMER) { |
274 | /* |
275 | * Mode 2 |
276 | * Continuous conversion, external trigger |
277 | * |
278 | * Similar to Mode 1, with the exception that the |
279 | * trigger flip-flop must be set by a negative edge |
280 | * on the external trigger input. |
281 | */ |
282 | mode = DT2811_ADCSR_ADMODE(2); |
283 | } else { /* TRIG_EXT */ |
284 | /* |
285 | * Mode 3 |
286 | * Continuous conversion, external trigger, clock |
287 | * |
288 | * Similar to Mode 2, with the exception that the |
289 | * conversion rate is set by the frequency on the |
290 | * external clock/divider. |
291 | */ |
292 | mode = DT2811_ADCSR_ADMODE(3); |
293 | } |
294 | } |
295 | outb(value: mode | DT2811_ADCSR_INTENB, port: dev->iobase + DT2811_ADCSR_REG); |
296 | |
297 | /* load timer */ |
298 | outb(value: devpriv->ai_divisor, port: dev->iobase + DT2811_TMRCTR_REG); |
299 | |
300 | /* load chanspec - enables timer */ |
301 | dt2811_ai_set_chanspec(dev, chanspec: cmd->chanlist[0]); |
302 | |
303 | return 0; |
304 | } |
305 | |
306 | static unsigned int dt2811_ns_to_timer(unsigned int *nanosec, |
307 | unsigned int flags) |
308 | { |
309 | unsigned long long ns; |
310 | unsigned int ns_lo = COMEDI_MIN_SPEED; |
311 | unsigned int ns_hi = 0; |
312 | unsigned int divisor_hi = 0; |
313 | unsigned int divisor_lo = 0; |
314 | unsigned int _div; |
315 | unsigned int _mult; |
316 | |
317 | /* |
318 | * Work through all the divider/multiplier values to find the two |
319 | * closest divisors to generate the requested nanosecond timing. |
320 | */ |
321 | for (_div = 0; _div <= 7; _div++) { |
322 | for (_mult = 0; _mult <= 7; _mult++) { |
323 | unsigned int div = dt2811_clk_dividers[_div]; |
324 | unsigned int mult = dt2811_clk_multipliers[_mult]; |
325 | unsigned long long divider = div * mult; |
326 | unsigned int divisor = DT2811_TMRCTR_MANTISSA(_div) | |
327 | DT2811_TMRCTR_EXPONENT(_mult); |
328 | |
329 | /* |
330 | * The timer can be configured to run at a slowest |
331 | * speed of 0.005hz (600 Khz/120000000), which requires |
332 | * 37-bits to represent the nanosecond value. Limit the |
333 | * slowest timing to what comedi handles (32-bits). |
334 | */ |
335 | ns = divider * DT2811_OSC_BASE; |
336 | if (ns > COMEDI_MIN_SPEED) |
337 | continue; |
338 | |
339 | /* Check for fastest found timing */ |
340 | if (ns <= *nanosec && ns > ns_hi) { |
341 | ns_hi = ns; |
342 | divisor_hi = divisor; |
343 | } |
344 | /* Check for slowest found timing */ |
345 | if (ns >= *nanosec && ns < ns_lo) { |
346 | ns_lo = ns; |
347 | divisor_lo = divisor; |
348 | } |
349 | } |
350 | } |
351 | |
352 | /* |
353 | * The slowest found timing will be invalid if the requested timing |
354 | * is faster than what can be generated by the timer. Fix it so that |
355 | * CMDF_ROUND_UP returns valid timing. |
356 | */ |
357 | if (ns_lo == COMEDI_MIN_SPEED) { |
358 | ns_lo = ns_hi; |
359 | divisor_lo = divisor_hi; |
360 | } |
361 | /* |
362 | * The fastest found timing will be invalid if the requested timing |
363 | * is less than what can be generated by the timer. Fix it so that |
364 | * CMDF_ROUND_NEAREST and CMDF_ROUND_DOWN return valid timing. |
365 | */ |
366 | if (ns_hi == 0) { |
367 | ns_hi = ns_lo; |
368 | divisor_hi = divisor_lo; |
369 | } |
370 | |
371 | switch (flags & CMDF_ROUND_MASK) { |
372 | case CMDF_ROUND_NEAREST: |
373 | default: |
374 | if (ns_hi - *nanosec < *nanosec - ns_lo) { |
375 | *nanosec = ns_lo; |
376 | return divisor_lo; |
377 | } |
378 | *nanosec = ns_hi; |
379 | return divisor_hi; |
380 | case CMDF_ROUND_UP: |
381 | *nanosec = ns_lo; |
382 | return divisor_lo; |
383 | case CMDF_ROUND_DOWN: |
384 | *nanosec = ns_hi; |
385 | return divisor_hi; |
386 | } |
387 | } |
388 | |
389 | static int dt2811_ai_cmdtest(struct comedi_device *dev, |
390 | struct comedi_subdevice *s, |
391 | struct comedi_cmd *cmd) |
392 | { |
393 | struct dt2811_private *devpriv = dev->private; |
394 | unsigned int arg; |
395 | int err = 0; |
396 | |
397 | /* Step 1 : check if triggers are trivially valid */ |
398 | |
399 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW | TRIG_EXT); |
400 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_FOLLOW); |
401 | err |= comedi_check_trigger_src(src: &cmd->convert_src, |
402 | TRIG_TIMER | TRIG_EXT); |
403 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
404 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
405 | |
406 | if (err) |
407 | return 1; |
408 | |
409 | /* Step 2a : make sure trigger sources are unique */ |
410 | |
411 | err |= comedi_check_trigger_is_unique(src: cmd->start_src); |
412 | err |= comedi_check_trigger_is_unique(src: cmd->convert_src); |
413 | err |= comedi_check_trigger_is_unique(src: cmd->stop_src); |
414 | |
415 | /* Step 2b : and mutually compatible */ |
416 | |
417 | if (cmd->convert_src == TRIG_EXT && cmd->start_src != TRIG_EXT) |
418 | err |= -EINVAL; |
419 | |
420 | if (err) |
421 | return 2; |
422 | |
423 | /* Step 3: check if arguments are trivially valid */ |
424 | |
425 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
426 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
427 | if (cmd->convert_src == TRIG_TIMER) |
428 | err |= comedi_check_trigger_arg_min(arg: &cmd->convert_arg, val: 12500); |
429 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
430 | val: cmd->chanlist_len); |
431 | if (cmd->stop_src == TRIG_COUNT) |
432 | err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1); |
433 | else /* TRIG_NONE */ |
434 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
435 | |
436 | if (err) |
437 | return 3; |
438 | |
439 | /* Step 4: fix up any arguments */ |
440 | |
441 | if (cmd->convert_src == TRIG_TIMER) { |
442 | arg = cmd->convert_arg; |
443 | devpriv->ai_divisor = dt2811_ns_to_timer(nanosec: &arg, flags: cmd->flags); |
444 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: arg); |
445 | } else { /* TRIG_EXT */ |
446 | /* The convert_arg is used to set the divisor. */ |
447 | devpriv->ai_divisor = cmd->convert_arg; |
448 | } |
449 | |
450 | if (err) |
451 | return 4; |
452 | |
453 | /* Step 5: check channel list if it exists */ |
454 | |
455 | return 0; |
456 | } |
457 | |
458 | static int dt2811_ai_eoc(struct comedi_device *dev, |
459 | struct comedi_subdevice *s, |
460 | struct comedi_insn *insn, |
461 | unsigned long context) |
462 | { |
463 | unsigned int status; |
464 | |
465 | status = inb(port: dev->iobase + DT2811_ADCSR_REG); |
466 | if ((status & DT2811_ADCSR_ADBUSY) == 0) |
467 | return 0; |
468 | return -EBUSY; |
469 | } |
470 | |
471 | static int dt2811_ai_insn_read(struct comedi_device *dev, |
472 | struct comedi_subdevice *s, |
473 | struct comedi_insn *insn, |
474 | unsigned int *data) |
475 | { |
476 | int ret; |
477 | int i; |
478 | |
479 | /* We will already be in Mode 0 */ |
480 | for (i = 0; i < insn->n; i++) { |
481 | /* load chanspec and trigger conversion */ |
482 | dt2811_ai_set_chanspec(dev, chanspec: insn->chanspec); |
483 | |
484 | ret = comedi_timeout(dev, s, insn, cb: dt2811_ai_eoc, context: 0); |
485 | if (ret) |
486 | return ret; |
487 | |
488 | data[i] = dt2811_ai_read_sample(dev, s); |
489 | } |
490 | |
491 | return insn->n; |
492 | } |
493 | |
494 | static int dt2811_ao_insn_write(struct comedi_device *dev, |
495 | struct comedi_subdevice *s, |
496 | struct comedi_insn *insn, |
497 | unsigned int *data) |
498 | { |
499 | unsigned int chan = CR_CHAN(insn->chanspec); |
500 | unsigned int val = s->readback[chan]; |
501 | int i; |
502 | |
503 | for (i = 0; i < insn->n; i++) { |
504 | val = data[i]; |
505 | outb(value: val & 0xff, port: dev->iobase + DT2811_DADATA_LO_REG(chan)); |
506 | outb(value: (val >> 8) & 0xff, |
507 | port: dev->iobase + DT2811_DADATA_HI_REG(chan)); |
508 | } |
509 | s->readback[chan] = val; |
510 | |
511 | return insn->n; |
512 | } |
513 | |
514 | static int dt2811_di_insn_bits(struct comedi_device *dev, |
515 | struct comedi_subdevice *s, |
516 | struct comedi_insn *insn, |
517 | unsigned int *data) |
518 | { |
519 | data[1] = inb(port: dev->iobase + DT2811_DI_REG); |
520 | |
521 | return insn->n; |
522 | } |
523 | |
524 | static int dt2811_do_insn_bits(struct comedi_device *dev, |
525 | struct comedi_subdevice *s, |
526 | struct comedi_insn *insn, |
527 | unsigned int *data) |
528 | { |
529 | if (comedi_dio_update_state(s, data)) |
530 | outb(value: s->state, port: dev->iobase + DT2811_DO_REG); |
531 | |
532 | data[1] = s->state; |
533 | |
534 | return insn->n; |
535 | } |
536 | |
537 | static void dt2811_reset(struct comedi_device *dev) |
538 | { |
539 | /* This is the initialization sequence from the users manual */ |
540 | outb(DT2811_ADCSR_ADMODE(0), port: dev->iobase + DT2811_ADCSR_REG); |
541 | usleep_range(min: 100, max: 1000); |
542 | inb(port: dev->iobase + DT2811_ADDATA_LO_REG); |
543 | inb(port: dev->iobase + DT2811_ADDATA_HI_REG); |
544 | outb(DT2811_ADCSR_ADMODE(0) | DT2811_ADCSR_CLRERROR, |
545 | port: dev->iobase + DT2811_ADCSR_REG); |
546 | } |
547 | |
548 | static int dt2811_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
549 | { |
550 | const struct dt2811_board *board = dev->board_ptr; |
551 | struct dt2811_private *devpriv; |
552 | struct comedi_subdevice *s; |
553 | int ret; |
554 | |
555 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
556 | if (!devpriv) |
557 | return -ENOMEM; |
558 | |
559 | ret = comedi_request_region(dev, start: it->options[0], len: 0x8); |
560 | if (ret) |
561 | return ret; |
562 | |
563 | dt2811_reset(dev); |
564 | |
565 | /* IRQ's 2,3,5,7 are valid for async command support */ |
566 | if (it->options[1] <= 7 && (BIT(it->options[1]) & 0xac)) { |
567 | ret = request_irq(irq: it->options[1], handler: dt2811_interrupt, flags: 0, |
568 | name: dev->board_name, dev); |
569 | if (ret == 0) |
570 | dev->irq = it->options[1]; |
571 | } |
572 | |
573 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
574 | if (ret) |
575 | return ret; |
576 | |
577 | /* Analog Input subdevice */ |
578 | s = &dev->subdevices[0]; |
579 | s->type = COMEDI_SUBD_AI; |
580 | s->subdev_flags = SDF_READABLE | |
581 | ((it->options[2] == 1) ? SDF_DIFF : |
582 | (it->options[2] == 2) ? SDF_COMMON : SDF_GROUND); |
583 | s->n_chan = (it->options[2] == 1) ? 8 : 16; |
584 | s->maxdata = 0x0fff; |
585 | s->range_table = board->is_pgh ? &dt2811_pgh_ai_ranges |
586 | : &dt2811_pgl_ai_ranges; |
587 | s->insn_read = dt2811_ai_insn_read; |
588 | if (dev->irq) { |
589 | dev->read_subdev = s; |
590 | s->subdev_flags |= SDF_CMD_READ; |
591 | s->len_chanlist = 1; |
592 | s->do_cmdtest = dt2811_ai_cmdtest; |
593 | s->do_cmd = dt2811_ai_cmd; |
594 | s->cancel = dt2811_ai_cancel; |
595 | } |
596 | |
597 | /* Analog Output subdevice */ |
598 | s = &dev->subdevices[1]; |
599 | s->type = COMEDI_SUBD_AO; |
600 | s->subdev_flags = SDF_WRITABLE; |
601 | s->n_chan = 2; |
602 | s->maxdata = 0x0fff; |
603 | s->range_table = &dt2811_ao_ranges; |
604 | s->insn_write = dt2811_ao_insn_write; |
605 | |
606 | ret = comedi_alloc_subdev_readback(s); |
607 | if (ret) |
608 | return ret; |
609 | |
610 | /* Digital Input subdevice */ |
611 | s = &dev->subdevices[2]; |
612 | s->type = COMEDI_SUBD_DI; |
613 | s->subdev_flags = SDF_READABLE; |
614 | s->n_chan = 8; |
615 | s->maxdata = 1; |
616 | s->range_table = &range_digital; |
617 | s->insn_bits = dt2811_di_insn_bits; |
618 | |
619 | /* Digital Output subdevice */ |
620 | s = &dev->subdevices[3]; |
621 | s->type = COMEDI_SUBD_DO; |
622 | s->subdev_flags = SDF_WRITABLE; |
623 | s->n_chan = 8; |
624 | s->maxdata = 1; |
625 | s->range_table = &range_digital; |
626 | s->insn_bits = dt2811_do_insn_bits; |
627 | |
628 | return 0; |
629 | } |
630 | |
631 | static struct comedi_driver dt2811_driver = { |
632 | .driver_name = "dt2811" , |
633 | .module = THIS_MODULE, |
634 | .attach = dt2811_attach, |
635 | .detach = comedi_legacy_detach, |
636 | .board_name = &dt2811_boards[0].name, |
637 | .num_names = ARRAY_SIZE(dt2811_boards), |
638 | .offset = sizeof(struct dt2811_board), |
639 | }; |
640 | module_comedi_driver(dt2811_driver); |
641 | |
642 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
643 | MODULE_DESCRIPTION("Comedi driver for Data Translation DT2811 series boards" ); |
644 | MODULE_LICENSE("GPL" ); |
645 | |