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