Commit | Line | Data |
---|---|---|
3c443716 DS |
1 | /* |
2 | comedi/drivers/dt2811.c | |
3 | Hardware driver for Data Translation DT2811 | |
4 | ||
5 | COMEDI - Linux Control and Measurement Device Interface | |
6 | History: | |
7 | Base Version - David A. Schleef <ds@schleef.org> | |
8 | December 1998 - Updated to work. David does not have a DT2811 | |
9 | board any longer so this was suffering from bitrot. | |
10 | Updated performed by ... | |
11 | ||
12 | This program is free software; you can redistribute it and/or modify | |
13 | it under the terms of the GNU General Public License as published by | |
14 | the Free Software Foundation; either version 2 of the License, or | |
15 | (at your option) any later version. | |
16 | ||
17 | This program is distributed in the hope that it will be useful, | |
18 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | GNU General Public License for more details. | |
21 | ||
22 | You should have received a copy of the GNU General Public License | |
23 | along with this program; if not, write to the Free Software | |
24 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
25 | */ | |
26 | /* | |
27 | Driver: dt2811 | |
28 | Description: Data Translation DT2811 | |
29 | Author: ds | |
30 | Devices: [Data Translation] DT2811-PGL (dt2811-pgl), DT2811-PGH (dt2811-pgh) | |
31 | Status: works | |
32 | ||
33 | Configuration options: | |
34 | [0] - I/O port base address | |
35 | [1] - IRQ, although this is currently unused | |
36 | [2] - A/D reference | |
37 | 0 = signle-ended | |
38 | 1 = differential | |
39 | 2 = pseudo-differential (common reference) | |
40 | [3] - A/D range | |
41 | 0 = [-5,5] | |
42 | 1 = [-2.5,2.5] | |
43 | 2 = [0,5] | |
44 | [4] - D/A 0 range (same choices) | |
45 | [4] - D/A 1 range (same choices) | |
46 | */ | |
47 | ||
48 | #include "../comedidev.h" | |
49 | ||
50 | #include <linux/ioport.h> | |
51 | ||
52 | static const char *driver_name = "dt2811"; | |
53 | ||
9ced1de6 | 54 | static const struct comedi_lrange range_dt2811_pgh_ai_5_unipolar = { 4, { |
3c443716 DS |
55 | RANGE(0, 5), |
56 | RANGE(0, 2.5), | |
57 | RANGE(0, 1.25), | |
58 | RANGE(0, 0.625) | |
59 | } | |
60 | }; | |
9ced1de6 | 61 | static const struct comedi_lrange range_dt2811_pgh_ai_2_5_bipolar = { 4, { |
3c443716 DS |
62 | RANGE(-2.5, 2.5), |
63 | RANGE(-1.25, 1.25), | |
64 | RANGE(-0.625, 0.625), | |
65 | RANGE(-0.3125, 0.3125) | |
66 | } | |
67 | }; | |
9ced1de6 | 68 | static const struct comedi_lrange range_dt2811_pgh_ai_5_bipolar = { 4, { |
3c443716 DS |
69 | RANGE(-5, 5), |
70 | RANGE(-2.5, 2.5), | |
71 | RANGE(-1.25, 1.25), | |
72 | RANGE(-0.625, 0.625) | |
73 | } | |
74 | }; | |
9ced1de6 | 75 | static const struct comedi_lrange range_dt2811_pgl_ai_5_unipolar = { 4, { |
3c443716 DS |
76 | RANGE(0, 5), |
77 | RANGE(0, 0.5), | |
78 | RANGE(0, 0.05), | |
79 | RANGE(0, 0.01) | |
80 | } | |
81 | }; | |
9ced1de6 | 82 | static const struct comedi_lrange range_dt2811_pgl_ai_2_5_bipolar = { 4, { |
3c443716 DS |
83 | RANGE(-2.5, 2.5), |
84 | RANGE(-0.25, 0.25), | |
85 | RANGE(-0.025, 0.025), | |
86 | RANGE(-0.005, 0.005) | |
87 | } | |
88 | }; | |
9ced1de6 | 89 | static const struct comedi_lrange range_dt2811_pgl_ai_5_bipolar = { 4, { |
3c443716 DS |
90 | RANGE(-5, 5), |
91 | RANGE(-0.5, 0.5), | |
92 | RANGE(-0.05, 0.05), | |
93 | RANGE(-0.01, 0.01) | |
94 | } | |
95 | }; | |
96 | ||
97 | /* | |
98 | ||
99 | 0x00 ADCSR R/W A/D Control/Status Register | |
100 | bit 7 - (R) 1 indicates A/D conversion done | |
101 | reading ADDAT clears bit | |
102 | (W) ignored | |
103 | bit 6 - (R) 1 indicates A/D error | |
104 | (W) ignored | |
105 | bit 5 - (R) 1 indicates A/D busy, cleared at end | |
106 | of conversion | |
107 | (W) ignored | |
108 | bit 4 - (R) 0 | |
109 | (W) | |
110 | bit 3 - (R) 0 | |
111 | bit 2 - (R/W) 1 indicates interrupts enabled | |
112 | bits 1,0 - (R/W) mode bits | |
113 | 00 single conversion on ADGCR load | |
114 | 01 continuous conversion, internal clock, | |
115 | (clock enabled on ADGCR load) | |
116 | 10 continuous conversion, internal clock, | |
117 | external trigger | |
118 | 11 continuous conversion, external clock, | |
119 | external trigger | |
120 | ||
121 | 0x01 ADGCR R/W A/D Gain/Channel Register | |
122 | bit 6,7 - (R/W) gain select | |
123 | 00 gain=1, both PGH, PGL models | |
124 | 01 gain=2 PGH, 10 PGL | |
125 | 10 gain=4 PGH, 100 PGL | |
126 | 11 gain=8 PGH, 500 PGL | |
127 | bit 4,5 - reserved | |
128 | bit 3-0 - (R/W) channel select | |
129 | channel number from 0-15 | |
130 | ||
131 | 0x02,0x03 (R) ADDAT A/D Data Register | |
132 | (W) DADAT0 D/A Data Register 0 | |
133 | 0x02 low byte | |
134 | 0x03 high byte | |
135 | ||
136 | 0x04,0x05 (W) DADAT0 D/A Data Register 1 | |
137 | ||
138 | 0x06 (R) DIO0 Digital Input Port 0 | |
139 | (W) DIO1 Digital Output Port 1 | |
140 | ||
141 | 0x07 TMRCTR (R/W) Timer/Counter Register | |
142 | bits 6,7 - reserved | |
143 | bits 5-3 - Timer frequency control (mantissa) | |
144 | 543 divisor freqency (kHz) | |
145 | 000 1 600 | |
146 | 001 10 60 | |
147 | 010 2 300 | |
148 | 011 3 200 | |
149 | 100 4 150 | |
150 | 101 5 120 | |
151 | 110 6 100 | |
152 | 111 12 50 | |
153 | bits 2-0 - Timer frequency control (exponent) | |
154 | 210 multiply divisor/divide frequency by | |
155 | 000 1 | |
156 | 001 10 | |
157 | 010 100 | |
158 | 011 1000 | |
159 | 100 10000 | |
160 | 101 100000 | |
161 | 110 1000000 | |
162 | 111 10000000 | |
163 | ||
164 | */ | |
165 | ||
166 | #define TIMEOUT 10000 | |
167 | ||
168 | #define DT2811_SIZE 8 | |
169 | ||
170 | #define DT2811_ADCSR 0 | |
171 | #define DT2811_ADGCR 1 | |
172 | #define DT2811_ADDATLO 2 | |
173 | #define DT2811_ADDATHI 3 | |
174 | #define DT2811_DADAT0LO 2 | |
175 | #define DT2811_DADAT0HI 3 | |
176 | #define DT2811_DADAT1LO 4 | |
177 | #define DT2811_DADAT1HI 5 | |
178 | #define DT2811_DIO 6 | |
179 | #define DT2811_TMRCTR 7 | |
180 | ||
181 | /* | |
182 | * flags | |
183 | */ | |
184 | ||
185 | /* ADCSR */ | |
186 | ||
187 | #define DT2811_ADDONE 0x80 | |
188 | #define DT2811_ADERROR 0x40 | |
189 | #define DT2811_ADBUSY 0x20 | |
190 | #define DT2811_CLRERROR 0x10 | |
191 | #define DT2811_INTENB 0x04 | |
192 | #define DT2811_ADMODE 0x03 | |
193 | ||
194 | typedef struct { | |
195 | const char *name; | |
9ced1de6 BP |
196 | const struct comedi_lrange *bip_5; |
197 | const struct comedi_lrange *bip_2_5; | |
198 | const struct comedi_lrange *unip_5; | |
3c443716 DS |
199 | } boardtype; |
200 | static const boardtype boardtypes[] = { | |
201 | {"dt2811-pgh", | |
202 | &range_dt2811_pgh_ai_5_bipolar, | |
203 | &range_dt2811_pgh_ai_2_5_bipolar, | |
204 | &range_dt2811_pgh_ai_5_unipolar, | |
205 | }, | |
206 | {"dt2811-pgl", | |
207 | &range_dt2811_pgl_ai_5_bipolar, | |
208 | &range_dt2811_pgl_ai_2_5_bipolar, | |
209 | &range_dt2811_pgl_ai_5_unipolar, | |
210 | }, | |
211 | }; | |
212 | ||
213 | #define this_board ((const boardtype *)dev->board_ptr) | |
214 | ||
71b5f4f1 BP |
215 | static int dt2811_attach(struct comedi_device * dev, comedi_devconfig * it); |
216 | static int dt2811_detach(struct comedi_device * dev); | |
139dfbdf | 217 | static struct comedi_driver driver_dt2811 = { |
3c443716 DS |
218 | driver_name:"dt2811", |
219 | module:THIS_MODULE, | |
220 | attach:dt2811_attach, | |
221 | detach:dt2811_detach, | |
222 | board_name:&boardtypes[0].name, | |
223 | num_names:sizeof(boardtypes) / sizeof(boardtype), | |
224 | offset:sizeof(boardtype), | |
225 | }; | |
226 | ||
227 | COMEDI_INITCLEANUP(driver_dt2811); | |
228 | ||
34c43922 | 229 | static int dt2811_ai_insn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 230 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 231 | static int dt2811_ao_insn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 232 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 233 | static int dt2811_ao_insn_read(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 234 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 235 | static int dt2811_di_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 236 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 237 | static int dt2811_do_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 238 | struct comedi_insn * insn, unsigned int * data); |
3c443716 DS |
239 | |
240 | enum { card_2811_pgh, card_2811_pgl }; | |
241 | typedef struct { | |
242 | int ntrig; | |
243 | int curadchan; | |
244 | enum { | |
245 | adc_singleended, adc_diff, adc_pseudo_diff | |
246 | } adc_mux; | |
247 | enum { | |
248 | dac_bipolar_5, dac_bipolar_2_5, dac_unipolar_5 | |
249 | } dac_range[2]; | |
9ced1de6 | 250 | const struct comedi_lrange *range_type_list[2]; |
790c5541 | 251 | unsigned int ao_readback[2]; |
3c443716 DS |
252 | } dt2811_private; |
253 | ||
254 | #define devpriv ((dt2811_private *)dev->private) | |
255 | ||
9ced1de6 | 256 | static const struct comedi_lrange *dac_range_types[] = { |
3c443716 DS |
257 | &range_bipolar5, |
258 | &range_bipolar2_5, | |
259 | &range_unipolar5 | |
260 | }; | |
261 | ||
262 | #define DT2811_TIMEOUT 5 | |
263 | ||
264 | #if 0 | |
265 | static irqreturn_t dt2811_interrupt(int irq, void *d PT_REGS_ARG) | |
266 | { | |
267 | int lo, hi; | |
268 | int data; | |
71b5f4f1 | 269 | struct comedi_device *dev = d; |
3c443716 DS |
270 | |
271 | if (!dev->attached) { | |
272 | comedi_error(dev, "spurious interrupt"); | |
273 | return IRQ_HANDLED; | |
274 | } | |
275 | ||
276 | lo = inb(dev->iobase + DT2811_ADDATLO); | |
277 | hi = inb(dev->iobase + DT2811_ADDATHI); | |
278 | ||
279 | data = lo + (hi << 8); | |
280 | ||
281 | if (!(--devpriv->ntrig)) { | |
282 | /* how to turn off acquisition */ | |
283 | s->async->events |= COMEDI_SB_EOA; | |
284 | } | |
285 | comedi_event(dev, s); | |
286 | return IRQ_HANDLED; | |
287 | } | |
288 | #endif | |
289 | ||
290 | /* | |
291 | options[0] Board base address | |
292 | options[1] IRQ | |
293 | options[2] Input configuration | |
294 | 0 == single-ended | |
295 | 1 == differential | |
296 | 2 == pseudo-differential | |
297 | options[3] Analog input range configuration | |
298 | 0 == bipolar 5 (-5V -- +5V) | |
299 | 1 == bipolar 2.5V (-2.5V -- +2.5V) | |
300 | 2 == unipolar 5V (0V -- +5V) | |
301 | options[4] Analog output 0 range configuration | |
302 | 0 == bipolar 5 (-5V -- +5V) | |
303 | 1 == bipolar 2.5V (-2.5V -- +2.5V) | |
304 | 2 == unipolar 5V (0V -- +5V) | |
305 | options[5] Analog output 1 range configuration | |
306 | 0 == bipolar 5 (-5V -- +5V) | |
307 | 1 == bipolar 2.5V (-2.5V -- +2.5V) | |
308 | 2 == unipolar 5V (0V -- +5V) | |
309 | */ | |
310 | ||
71b5f4f1 | 311 | static int dt2811_attach(struct comedi_device * dev, comedi_devconfig * it) |
3c443716 DS |
312 | { |
313 | //int i, irq; | |
314 | //unsigned long irqs; | |
315 | //long flags; | |
316 | int ret; | |
34c43922 | 317 | struct comedi_subdevice *s; |
3c443716 DS |
318 | unsigned long iobase; |
319 | ||
320 | iobase = it->options[0]; | |
321 | ||
322 | printk("comedi%d: dt2811: base=0x%04lx\n", dev->minor, iobase); | |
323 | ||
324 | if (!request_region(iobase, DT2811_SIZE, driver_name)) { | |
325 | printk("I/O port conflict\n"); | |
326 | return -EIO; | |
327 | } | |
328 | ||
329 | dev->iobase = iobase; | |
330 | dev->board_name = this_board->name; | |
331 | ||
332 | #if 0 | |
333 | outb(0, dev->iobase + DT2811_ADCSR); | |
334 | comedi_udelay(100); | |
335 | i = inb(dev->iobase + DT2811_ADDATLO); | |
336 | i = inb(dev->iobase + DT2811_ADDATHI); | |
337 | #endif | |
338 | ||
339 | #if 0 | |
340 | irq = it->options[1]; | |
341 | if (irq < 0) { | |
342 | save_flags(flags); | |
343 | sti(); | |
344 | irqs = probe_irq_on(); | |
345 | ||
346 | outb(DT2811_CLRERROR | DT2811_INTENB, | |
347 | dev->iobase + DT2811_ADCSR); | |
348 | outb(0, dev->iobase + DT2811_ADGCR); | |
349 | ||
350 | comedi_udelay(100); | |
351 | ||
352 | irq = probe_irq_off(irqs); | |
353 | restore_flags(flags); | |
354 | ||
355 | /*outb(DT2811_CLRERROR|DT2811_INTENB,dev->iobase+DT2811_ADCSR); */ | |
356 | ||
357 | if (inb(dev->iobase + DT2811_ADCSR) & DT2811_ADERROR) { | |
358 | printk("error probing irq (bad) \n"); | |
359 | } | |
360 | dev->irq = 0; | |
361 | if (irq > 0) { | |
362 | i = inb(dev->iobase + DT2811_ADDATLO); | |
363 | i = inb(dev->iobase + DT2811_ADDATHI); | |
364 | printk("(irq = %d)\n", irq); | |
365 | ret = comedi_request_irq(irq, dt2811_interrupt, 0, | |
366 | driver_name, dev); | |
367 | if (ret < 0) | |
368 | return -EIO; | |
369 | dev->irq = irq; | |
370 | } else if (irq == 0) { | |
371 | printk("(no irq)\n"); | |
372 | } else { | |
373 | printk("( multiple irq's -- this is bad! )\n"); | |
374 | } | |
375 | } | |
376 | #endif | |
377 | ||
378 | if ((ret = alloc_subdevices(dev, 4)) < 0) | |
379 | return ret; | |
380 | if ((ret = alloc_private(dev, sizeof(dt2811_private))) < 0) | |
381 | return ret; | |
382 | switch (it->options[2]) { | |
383 | case 0: | |
384 | devpriv->adc_mux = adc_singleended; | |
385 | break; | |
386 | case 1: | |
387 | devpriv->adc_mux = adc_diff; | |
388 | break; | |
389 | case 2: | |
390 | devpriv->adc_mux = adc_pseudo_diff; | |
391 | break; | |
392 | default: | |
393 | devpriv->adc_mux = adc_singleended; | |
394 | break; | |
395 | } | |
396 | switch (it->options[4]) { | |
397 | case 0: | |
398 | devpriv->dac_range[0] = dac_bipolar_5; | |
399 | break; | |
400 | case 1: | |
401 | devpriv->dac_range[0] = dac_bipolar_2_5; | |
402 | break; | |
403 | case 2: | |
404 | devpriv->dac_range[0] = dac_unipolar_5; | |
405 | break; | |
406 | default: | |
407 | devpriv->dac_range[0] = dac_bipolar_5; | |
408 | break; | |
409 | } | |
410 | switch (it->options[5]) { | |
411 | case 0: | |
412 | devpriv->dac_range[1] = dac_bipolar_5; | |
413 | break; | |
414 | case 1: | |
415 | devpriv->dac_range[1] = dac_bipolar_2_5; | |
416 | break; | |
417 | case 2: | |
418 | devpriv->dac_range[1] = dac_unipolar_5; | |
419 | break; | |
420 | default: | |
421 | devpriv->dac_range[1] = dac_bipolar_5; | |
422 | break; | |
423 | } | |
424 | ||
425 | s = dev->subdevices + 0; | |
426 | /* initialize the ADC subdevice */ | |
427 | s->type = COMEDI_SUBD_AI; | |
428 | s->subdev_flags = SDF_READABLE | SDF_GROUND; | |
429 | s->n_chan = devpriv->adc_mux == adc_diff ? 8 : 16; | |
430 | s->insn_read = dt2811_ai_insn; | |
431 | s->maxdata = 0xfff; | |
432 | switch (it->options[3]) { | |
433 | case 0: | |
434 | default: | |
435 | s->range_table = this_board->bip_5; | |
436 | break; | |
437 | case 1: | |
438 | s->range_table = this_board->bip_2_5; | |
439 | break; | |
440 | case 2: | |
441 | s->range_table = this_board->unip_5; | |
442 | break; | |
443 | } | |
444 | ||
445 | s = dev->subdevices + 1; | |
446 | /* ao subdevice */ | |
447 | s->type = COMEDI_SUBD_AO; | |
448 | s->subdev_flags = SDF_WRITABLE; | |
449 | s->n_chan = 2; | |
450 | s->insn_write = dt2811_ao_insn; | |
451 | s->insn_read = dt2811_ao_insn_read; | |
452 | s->maxdata = 0xfff; | |
453 | s->range_table_list = devpriv->range_type_list; | |
454 | devpriv->range_type_list[0] = dac_range_types[devpriv->dac_range[0]]; | |
455 | devpriv->range_type_list[1] = dac_range_types[devpriv->dac_range[1]]; | |
456 | ||
457 | s = dev->subdevices + 2; | |
458 | /* di subdevice */ | |
459 | s->type = COMEDI_SUBD_DI; | |
460 | s->subdev_flags = SDF_READABLE; | |
461 | s->n_chan = 8; | |
462 | s->insn_bits = dt2811_di_insn_bits; | |
463 | s->maxdata = 1; | |
464 | s->range_table = &range_digital; | |
465 | ||
466 | s = dev->subdevices + 3; | |
467 | /* do subdevice */ | |
468 | s->type = COMEDI_SUBD_DO; | |
469 | s->subdev_flags = SDF_WRITABLE; | |
470 | s->n_chan = 8; | |
471 | s->insn_bits = dt2811_do_insn_bits; | |
472 | s->maxdata = 1; | |
473 | s->state = 0; | |
474 | s->range_table = &range_digital; | |
475 | ||
476 | return 0; | |
477 | } | |
478 | ||
71b5f4f1 | 479 | static int dt2811_detach(struct comedi_device * dev) |
3c443716 DS |
480 | { |
481 | printk("comedi%d: dt2811: remove\n", dev->minor); | |
482 | ||
483 | if (dev->irq) { | |
484 | comedi_free_irq(dev->irq, dev); | |
485 | } | |
486 | if (dev->iobase) { | |
487 | release_region(dev->iobase, DT2811_SIZE); | |
488 | } | |
489 | ||
490 | return 0; | |
491 | } | |
492 | ||
34c43922 | 493 | static int dt2811_ai_insn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 494 | struct comedi_insn * insn, unsigned int * data) |
3c443716 DS |
495 | { |
496 | int chan = CR_CHAN(insn->chanspec); | |
497 | int timeout = DT2811_TIMEOUT; | |
498 | int i; | |
499 | ||
500 | for (i = 0; i < insn->n; i++) { | |
501 | outb(chan, dev->iobase + DT2811_ADGCR); | |
502 | ||
503 | while (timeout | |
504 | && inb(dev->iobase + DT2811_ADCSR) & DT2811_ADBUSY) | |
505 | timeout--; | |
506 | if (!timeout) | |
507 | return -ETIME; | |
508 | ||
509 | data[i] = inb(dev->iobase + DT2811_ADDATLO); | |
510 | data[i] |= inb(dev->iobase + DT2811_ADDATHI) << 8; | |
511 | data[i] &= 0xfff; | |
512 | } | |
513 | ||
514 | return i; | |
515 | } | |
516 | ||
517 | #if 0 | |
518 | /* Wow. This is code from the Comedi stone age. But it hasn't been | |
519 | * replaced, so I'll let it stay. */ | |
520 | int dt2811_adtrig(kdev_t minor, comedi_adtrig * adtrig) | |
521 | { | |
71b5f4f1 | 522 | struct comedi_device *dev = comedi_devices + minor; |
3c443716 DS |
523 | |
524 | if (adtrig->n < 1) | |
525 | return 0; | |
526 | dev->curadchan = adtrig->chan; | |
527 | switch (dev->i_admode) { | |
528 | case COMEDI_MDEMAND: | |
529 | dev->ntrig = adtrig->n - 1; | |
530 | /*printk("dt2811: AD soft trigger\n"); */ | |
531 | /*outb(DT2811_CLRERROR|DT2811_INTENB,dev->iobase+DT2811_ADCSR); *//* not neccessary */ | |
532 | outb(dev->curadchan, dev->iobase + DT2811_ADGCR); | |
533 | do_gettimeofday(&trigtime); | |
534 | break; | |
535 | case COMEDI_MCONTS: | |
536 | dev->ntrig = adtrig->n; | |
537 | break; | |
538 | } | |
539 | ||
540 | return 0; | |
541 | } | |
542 | #endif | |
543 | ||
34c43922 | 544 | static int dt2811_ao_insn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 545 | struct comedi_insn * insn, unsigned int * data) |
3c443716 DS |
546 | { |
547 | int i; | |
548 | int chan; | |
549 | ||
550 | chan = CR_CHAN(insn->chanspec); | |
551 | ||
552 | for (i = 0; i < insn->n; i++) { | |
553 | outb(data[i] & 0xff, dev->iobase + DT2811_DADAT0LO + 2 * chan); | |
554 | outb((data[i] >> 8) & 0xff, | |
555 | dev->iobase + DT2811_DADAT0HI + 2 * chan); | |
556 | devpriv->ao_readback[chan] = data[i]; | |
557 | } | |
558 | ||
559 | return i; | |
560 | } | |
561 | ||
34c43922 | 562 | static int dt2811_ao_insn_read(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 563 | struct comedi_insn * insn, unsigned int * data) |
3c443716 DS |
564 | { |
565 | int i; | |
566 | int chan; | |
567 | ||
568 | chan = CR_CHAN(insn->chanspec); | |
569 | ||
570 | for (i = 0; i < insn->n; i++) { | |
571 | data[i] = devpriv->ao_readback[chan]; | |
572 | } | |
573 | ||
574 | return i; | |
575 | } | |
576 | ||
34c43922 | 577 | static int dt2811_di_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 578 | struct comedi_insn * insn, unsigned int * data) |
3c443716 DS |
579 | { |
580 | if (insn->n != 2) | |
581 | return -EINVAL; | |
582 | ||
583 | data[1] = inb(dev->iobase + DT2811_DIO); | |
584 | ||
585 | return 2; | |
586 | } | |
587 | ||
34c43922 | 588 | static int dt2811_do_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 589 | struct comedi_insn * insn, unsigned int * data) |
3c443716 DS |
590 | { |
591 | if (insn->n != 2) | |
592 | return -EINVAL; | |
593 | ||
594 | s->state &= ~data[0]; | |
595 | s->state |= data[0] & data[1]; | |
596 | outb(s->state, dev->iobase + DT2811_DIO); | |
597 | ||
598 | data[1] = s->state; | |
599 | ||
600 | return 2; | |
601 | } |