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