1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * das6402.c |
4 | * Comedi driver for DAS6402 compatible boards |
5 | * Copyright(c) 2014 H Hartley Sweeten <hsweeten@visionengravers.com> |
6 | * |
7 | * Rewrite of an experimental driver by: |
8 | * Copyright (C) 1999 Oystein Svendsen <svendsen@pvv.org> |
9 | */ |
10 | |
11 | /* |
12 | * Driver: das6402 |
13 | * Description: Keithley Metrabyte DAS6402 (& compatibles) |
14 | * Devices: [Keithley Metrabyte] DAS6402-12 (das6402-12), |
15 | * DAS6402-16 (das6402-16) |
16 | * Author: H Hartley Sweeten <hsweeten@visionengravers.com> |
17 | * Updated: Fri, 14 Mar 2014 10:18:43 -0700 |
18 | * Status: unknown |
19 | * |
20 | * Configuration Options: |
21 | * [0] - I/O base address |
22 | * [1] - IRQ (optional, needed for async command support) |
23 | */ |
24 | |
25 | #include <linux/module.h> |
26 | #include <linux/interrupt.h> |
27 | #include <linux/comedi/comedidev.h> |
28 | #include <linux/comedi/comedi_8254.h> |
29 | |
30 | /* |
31 | * Register I/O map |
32 | */ |
33 | #define DAS6402_AI_DATA_REG 0x00 |
34 | #define DAS6402_AI_MUX_REG 0x02 |
35 | #define DAS6402_AI_MUX_LO(x) (((x) & 0x3f) << 0) |
36 | #define DAS6402_AI_MUX_HI(x) (((x) & 0x3f) << 8) |
37 | #define DAS6402_DI_DO_REG 0x03 |
38 | #define DAS6402_AO_DATA_REG(x) (0x04 + ((x) * 2)) |
39 | #define DAS6402_AO_LSB_REG(x) (0x04 + ((x) * 2)) |
40 | #define DAS6402_AO_MSB_REG(x) (0x05 + ((x) * 2)) |
41 | #define DAS6402_STATUS_REG 0x08 |
42 | #define DAS6402_STATUS_FFNE BIT(0) |
43 | #define DAS6402_STATUS_FHALF BIT(1) |
44 | #define DAS6402_STATUS_FFULL BIT(2) |
45 | #define DAS6402_STATUS_XINT BIT(3) |
46 | #define DAS6402_STATUS_INT BIT(4) |
47 | #define DAS6402_STATUS_XTRIG BIT(5) |
48 | #define DAS6402_STATUS_INDGT BIT(6) |
49 | #define DAS6402_STATUS_10MHZ BIT(7) |
50 | #define DAS6402_STATUS_W_CLRINT BIT(0) |
51 | #define DAS6402_STATUS_W_CLRXTR BIT(1) |
52 | #define DAS6402_STATUS_W_CLRXIN BIT(2) |
53 | #define DAS6402_STATUS_W_EXTEND BIT(4) |
54 | #define DAS6402_STATUS_W_ARMED BIT(5) |
55 | #define DAS6402_STATUS_W_POSTMODE BIT(6) |
56 | #define DAS6402_STATUS_W_10MHZ BIT(7) |
57 | #define DAS6402_CTRL_REG 0x09 |
58 | #define DAS6402_CTRL_TRIG(x) ((x) << 0) |
59 | #define DAS6402_CTRL_SOFT_TRIG DAS6402_CTRL_TRIG(0) |
60 | #define DAS6402_CTRL_EXT_FALL_TRIG DAS6402_CTRL_TRIG(1) |
61 | #define DAS6402_CTRL_EXT_RISE_TRIG DAS6402_CTRL_TRIG(2) |
62 | #define DAS6402_CTRL_PACER_TRIG DAS6402_CTRL_TRIG(3) |
63 | #define DAS6402_CTRL_BURSTEN BIT(2) |
64 | #define DAS6402_CTRL_XINTE BIT(3) |
65 | #define DAS6402_CTRL_IRQ(x) ((x) << 4) |
66 | #define DAS6402_CTRL_INTE BIT(7) |
67 | #define DAS6402_TRIG_REG 0x0a |
68 | #define DAS6402_TRIG_TGEN BIT(0) |
69 | #define DAS6402_TRIG_TGSEL BIT(1) |
70 | #define DAS6402_TRIG_TGPOL BIT(2) |
71 | #define DAS6402_TRIG_PRETRIG BIT(3) |
72 | #define DAS6402_AO_RANGE(_chan, _range) ((_range) << ((_chan) ? 6 : 4)) |
73 | #define DAS6402_AO_RANGE_MASK(_chan) (3 << ((_chan) ? 6 : 4)) |
74 | #define DAS6402_MODE_REG 0x0b |
75 | #define DAS6402_MODE_RANGE(x) ((x) << 2) |
76 | #define DAS6402_MODE_POLLED DAS6402_MODE_RANGE(0) |
77 | #define DAS6402_MODE_FIFONEPTY DAS6402_MODE_RANGE(1) |
78 | #define DAS6402_MODE_FIFOHFULL DAS6402_MODE_RANGE(2) |
79 | #define DAS6402_MODE_EOB DAS6402_MODE_RANGE(3) |
80 | #define DAS6402_MODE_ENHANCED BIT(4) |
81 | #define DAS6402_MODE_SE BIT(5) |
82 | #define DAS6402_MODE_UNI BIT(6) |
83 | #define DAS6402_MODE_DMA(x) ((x) << 7) |
84 | #define DAS6402_MODE_DMA1 DAS6402_MODE_DMA(0) |
85 | #define DAS6402_MODE_DMA3 DAS6402_MODE_DMA(1) |
86 | #define DAS6402_TIMER_BASE 0x0c |
87 | |
88 | static const struct comedi_lrange das6402_ai_ranges = { |
89 | 8, { |
90 | BIP_RANGE(10), |
91 | BIP_RANGE(5), |
92 | BIP_RANGE(2.5), |
93 | BIP_RANGE(1.25), |
94 | UNI_RANGE(10), |
95 | UNI_RANGE(5), |
96 | UNI_RANGE(2.5), |
97 | UNI_RANGE(1.25) |
98 | } |
99 | }; |
100 | |
101 | /* |
102 | * Analog output ranges are programmable on the DAS6402/12. |
103 | * For the DAS6402/16 the range bits have no function, the |
104 | * DAC ranges are selected by switches on the board. |
105 | */ |
106 | static const struct comedi_lrange das6402_ao_ranges = { |
107 | 4, { |
108 | BIP_RANGE(5), |
109 | BIP_RANGE(10), |
110 | UNI_RANGE(5), |
111 | UNI_RANGE(10) |
112 | } |
113 | }; |
114 | |
115 | struct das6402_boardinfo { |
116 | const char *name; |
117 | unsigned int maxdata; |
118 | }; |
119 | |
120 | static struct das6402_boardinfo das6402_boards[] = { |
121 | { |
122 | .name = "das6402-12" , |
123 | .maxdata = 0x0fff, |
124 | }, { |
125 | .name = "das6402-16" , |
126 | .maxdata = 0xffff, |
127 | }, |
128 | }; |
129 | |
130 | struct das6402_private { |
131 | unsigned int irq; |
132 | unsigned int ao_range; |
133 | }; |
134 | |
135 | static void das6402_set_mode(struct comedi_device *dev, |
136 | unsigned int mode) |
137 | { |
138 | outb(DAS6402_MODE_ENHANCED | mode, port: dev->iobase + DAS6402_MODE_REG); |
139 | } |
140 | |
141 | static void das6402_set_extended(struct comedi_device *dev, |
142 | unsigned int val) |
143 | { |
144 | outb(DAS6402_STATUS_W_EXTEND, port: dev->iobase + DAS6402_STATUS_REG); |
145 | outb(DAS6402_STATUS_W_EXTEND | val, port: dev->iobase + DAS6402_STATUS_REG); |
146 | outb(value: val, port: dev->iobase + DAS6402_STATUS_REG); |
147 | } |
148 | |
149 | static void das6402_clear_all_interrupts(struct comedi_device *dev) |
150 | { |
151 | outb(DAS6402_STATUS_W_CLRINT | |
152 | DAS6402_STATUS_W_CLRXTR | |
153 | DAS6402_STATUS_W_CLRXIN, port: dev->iobase + DAS6402_STATUS_REG); |
154 | } |
155 | |
156 | static void das6402_ai_clear_eoc(struct comedi_device *dev) |
157 | { |
158 | outb(DAS6402_STATUS_W_CLRINT, port: dev->iobase + DAS6402_STATUS_REG); |
159 | } |
160 | |
161 | static unsigned int das6402_ai_read_sample(struct comedi_device *dev, |
162 | struct comedi_subdevice *s) |
163 | { |
164 | unsigned int val; |
165 | |
166 | val = inw(port: dev->iobase + DAS6402_AI_DATA_REG); |
167 | if (s->maxdata == 0x0fff) |
168 | val >>= 4; |
169 | return val; |
170 | } |
171 | |
172 | static irqreturn_t das6402_interrupt(int irq, void *d) |
173 | { |
174 | struct comedi_device *dev = d; |
175 | struct comedi_subdevice *s = dev->read_subdev; |
176 | struct comedi_async *async = s->async; |
177 | struct comedi_cmd *cmd = &async->cmd; |
178 | unsigned int status; |
179 | |
180 | status = inb(port: dev->iobase + DAS6402_STATUS_REG); |
181 | if ((status & DAS6402_STATUS_INT) == 0) |
182 | return IRQ_NONE; |
183 | |
184 | if (status & DAS6402_STATUS_FFULL) { |
185 | async->events |= COMEDI_CB_OVERFLOW; |
186 | } else if (status & DAS6402_STATUS_FFNE) { |
187 | unsigned short val; |
188 | |
189 | val = das6402_ai_read_sample(dev, s); |
190 | comedi_buf_write_samples(s, data: &val, nsamples: 1); |
191 | |
192 | if (cmd->stop_src == TRIG_COUNT && |
193 | async->scans_done >= cmd->stop_arg) |
194 | async->events |= COMEDI_CB_EOA; |
195 | } |
196 | |
197 | das6402_clear_all_interrupts(dev); |
198 | |
199 | comedi_handle_events(dev, s); |
200 | |
201 | return IRQ_HANDLED; |
202 | } |
203 | |
204 | static void das6402_ai_set_mode(struct comedi_device *dev, |
205 | struct comedi_subdevice *s, |
206 | unsigned int chanspec, |
207 | unsigned int mode) |
208 | { |
209 | unsigned int range = CR_RANGE(chanspec); |
210 | unsigned int aref = CR_AREF(chanspec); |
211 | |
212 | mode |= DAS6402_MODE_RANGE(range); |
213 | if (aref == AREF_GROUND) |
214 | mode |= DAS6402_MODE_SE; |
215 | if (comedi_range_is_unipolar(s, range)) |
216 | mode |= DAS6402_MODE_UNI; |
217 | |
218 | das6402_set_mode(dev, mode); |
219 | } |
220 | |
221 | static int das6402_ai_cmd(struct comedi_device *dev, |
222 | struct comedi_subdevice *s) |
223 | { |
224 | struct das6402_private *devpriv = dev->private; |
225 | struct comedi_cmd *cmd = &s->async->cmd; |
226 | unsigned int chan_lo = CR_CHAN(cmd->chanlist[0]); |
227 | unsigned int chan_hi = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]); |
228 | |
229 | das6402_ai_set_mode(dev, s, chanspec: cmd->chanlist[0], DAS6402_MODE_FIFONEPTY); |
230 | |
231 | /* load the mux for chanlist conversion */ |
232 | outw(DAS6402_AI_MUX_HI(chan_hi) | DAS6402_AI_MUX_LO(chan_lo), |
233 | port: dev->iobase + DAS6402_AI_MUX_REG); |
234 | |
235 | comedi_8254_update_divisors(i8254: dev->pacer); |
236 | comedi_8254_pacer_enable(i8254: dev->pacer, counter1: 1, counter2: 2, enable: true); |
237 | |
238 | /* enable interrupt and pacer trigger */ |
239 | outb(DAS6402_CTRL_INTE | |
240 | DAS6402_CTRL_IRQ(devpriv->irq) | |
241 | DAS6402_CTRL_PACER_TRIG, port: dev->iobase + DAS6402_CTRL_REG); |
242 | |
243 | return 0; |
244 | } |
245 | |
246 | static int das6402_ai_check_chanlist(struct comedi_device *dev, |
247 | struct comedi_subdevice *s, |
248 | struct comedi_cmd *cmd) |
249 | { |
250 | unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); |
251 | unsigned int range0 = CR_RANGE(cmd->chanlist[0]); |
252 | unsigned int aref0 = CR_AREF(cmd->chanlist[0]); |
253 | int i; |
254 | |
255 | for (i = 1; i < cmd->chanlist_len; i++) { |
256 | unsigned int chan = CR_CHAN(cmd->chanlist[i]); |
257 | unsigned int range = CR_RANGE(cmd->chanlist[i]); |
258 | unsigned int aref = CR_AREF(cmd->chanlist[i]); |
259 | |
260 | if (chan != chan0 + i) { |
261 | dev_dbg(dev->class_dev, |
262 | "chanlist must be consecutive\n" ); |
263 | return -EINVAL; |
264 | } |
265 | |
266 | if (range != range0) { |
267 | dev_dbg(dev->class_dev, |
268 | "chanlist must have the same range\n" ); |
269 | return -EINVAL; |
270 | } |
271 | |
272 | if (aref != aref0) { |
273 | dev_dbg(dev->class_dev, |
274 | "chanlist must have the same reference\n" ); |
275 | return -EINVAL; |
276 | } |
277 | |
278 | if (aref0 == AREF_DIFF && chan > (s->n_chan / 2)) { |
279 | dev_dbg(dev->class_dev, |
280 | "chanlist differential channel too large\n" ); |
281 | return -EINVAL; |
282 | } |
283 | } |
284 | return 0; |
285 | } |
286 | |
287 | static int das6402_ai_cmdtest(struct comedi_device *dev, |
288 | struct comedi_subdevice *s, |
289 | struct comedi_cmd *cmd) |
290 | { |
291 | int err = 0; |
292 | unsigned int arg; |
293 | |
294 | /* Step 1 : check if triggers are trivially valid */ |
295 | |
296 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW); |
297 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_FOLLOW); |
298 | err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_TIMER); |
299 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
300 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
301 | |
302 | if (err) |
303 | return 1; |
304 | |
305 | /* Step 2a : make sure trigger sources are unique */ |
306 | |
307 | err |= comedi_check_trigger_is_unique(src: cmd->stop_src); |
308 | |
309 | /* Step 2b : and mutually compatible */ |
310 | |
311 | if (err) |
312 | return 2; |
313 | |
314 | /* Step 3: check if arguments are trivially valid */ |
315 | |
316 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
317 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
318 | err |= comedi_check_trigger_arg_min(arg: &cmd->convert_arg, val: 10000); |
319 | err |= comedi_check_trigger_arg_min(arg: &cmd->chanlist_len, val: 1); |
320 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
321 | val: cmd->chanlist_len); |
322 | |
323 | if (cmd->stop_src == TRIG_COUNT) |
324 | err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1); |
325 | else /* TRIG_NONE */ |
326 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
327 | |
328 | if (err) |
329 | return 3; |
330 | |
331 | /* step 4: fix up any arguments */ |
332 | |
333 | arg = cmd->convert_arg; |
334 | comedi_8254_cascade_ns_to_timer(i8254: dev->pacer, nanosec: &arg, flags: cmd->flags); |
335 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: arg); |
336 | |
337 | if (err) |
338 | return 4; |
339 | |
340 | /* Step 5: check channel list if it exists */ |
341 | if (cmd->chanlist && cmd->chanlist_len > 0) |
342 | err |= das6402_ai_check_chanlist(dev, s, cmd); |
343 | |
344 | if (err) |
345 | return 5; |
346 | |
347 | return 0; |
348 | } |
349 | |
350 | static int das6402_ai_cancel(struct comedi_device *dev, |
351 | struct comedi_subdevice *s) |
352 | { |
353 | outb(DAS6402_CTRL_SOFT_TRIG, port: dev->iobase + DAS6402_CTRL_REG); |
354 | |
355 | return 0; |
356 | } |
357 | |
358 | static void das6402_ai_soft_trig(struct comedi_device *dev) |
359 | { |
360 | outw(value: 0, port: dev->iobase + DAS6402_AI_DATA_REG); |
361 | } |
362 | |
363 | static int das6402_ai_eoc(struct comedi_device *dev, |
364 | struct comedi_subdevice *s, |
365 | struct comedi_insn *insn, |
366 | unsigned long context) |
367 | { |
368 | unsigned int status; |
369 | |
370 | status = inb(port: dev->iobase + DAS6402_STATUS_REG); |
371 | if (status & DAS6402_STATUS_FFNE) |
372 | return 0; |
373 | return -EBUSY; |
374 | } |
375 | |
376 | static int das6402_ai_insn_read(struct comedi_device *dev, |
377 | struct comedi_subdevice *s, |
378 | struct comedi_insn *insn, |
379 | unsigned int *data) |
380 | { |
381 | unsigned int chan = CR_CHAN(insn->chanspec); |
382 | unsigned int aref = CR_AREF(insn->chanspec); |
383 | int ret; |
384 | int i; |
385 | |
386 | if (aref == AREF_DIFF && chan > (s->n_chan / 2)) |
387 | return -EINVAL; |
388 | |
389 | /* enable software conversion trigger */ |
390 | outb(DAS6402_CTRL_SOFT_TRIG, port: dev->iobase + DAS6402_CTRL_REG); |
391 | |
392 | das6402_ai_set_mode(dev, s, chanspec: insn->chanspec, DAS6402_MODE_POLLED); |
393 | |
394 | /* load the mux for single channel conversion */ |
395 | outw(DAS6402_AI_MUX_HI(chan) | DAS6402_AI_MUX_LO(chan), |
396 | port: dev->iobase + DAS6402_AI_MUX_REG); |
397 | |
398 | for (i = 0; i < insn->n; i++) { |
399 | das6402_ai_clear_eoc(dev); |
400 | das6402_ai_soft_trig(dev); |
401 | |
402 | ret = comedi_timeout(dev, s, insn, cb: das6402_ai_eoc, context: 0); |
403 | if (ret) |
404 | break; |
405 | |
406 | data[i] = das6402_ai_read_sample(dev, s); |
407 | } |
408 | |
409 | das6402_ai_clear_eoc(dev); |
410 | |
411 | return insn->n; |
412 | } |
413 | |
414 | static int das6402_ao_insn_write(struct comedi_device *dev, |
415 | struct comedi_subdevice *s, |
416 | struct comedi_insn *insn, |
417 | unsigned int *data) |
418 | { |
419 | struct das6402_private *devpriv = dev->private; |
420 | unsigned int chan = CR_CHAN(insn->chanspec); |
421 | unsigned int range = CR_RANGE(insn->chanspec); |
422 | unsigned int val; |
423 | int i; |
424 | |
425 | /* set the range for this channel */ |
426 | val = devpriv->ao_range; |
427 | val &= ~DAS6402_AO_RANGE_MASK(chan); |
428 | val |= DAS6402_AO_RANGE(chan, range); |
429 | if (val != devpriv->ao_range) { |
430 | devpriv->ao_range = val; |
431 | outb(value: val, port: dev->iobase + DAS6402_TRIG_REG); |
432 | } |
433 | |
434 | /* |
435 | * The DAS6402/16 has a jumper to select either individual |
436 | * update (UPDATE) or simultaneous updating (XFER) of both |
437 | * DAC's. In UPDATE mode, when the MSB is written, that DAC |
438 | * is updated. In XFER mode, after both DAC's are loaded, |
439 | * a read cycle of any DAC register will update both DAC's |
440 | * simultaneously. |
441 | * |
442 | * If you have XFER mode enabled a (*insn_read) will need |
443 | * to be performed in order to update the DAC's with the |
444 | * last value written. |
445 | */ |
446 | for (i = 0; i < insn->n; i++) { |
447 | val = data[i]; |
448 | |
449 | s->readback[chan] = val; |
450 | |
451 | if (s->maxdata == 0x0fff) { |
452 | /* |
453 | * DAS6402/12 has the two 8-bit DAC registers, left |
454 | * justified (the 4 LSB bits are don't care). Data |
455 | * can be written as one word. |
456 | */ |
457 | val <<= 4; |
458 | outw(value: val, port: dev->iobase + DAS6402_AO_DATA_REG(chan)); |
459 | } else { |
460 | /* |
461 | * DAS6402/16 uses both 8-bit DAC registers and needs |
462 | * to be written LSB then MSB. |
463 | */ |
464 | outb(value: val & 0xff, |
465 | port: dev->iobase + DAS6402_AO_LSB_REG(chan)); |
466 | outb(value: (val >> 8) & 0xff, |
467 | port: dev->iobase + DAS6402_AO_LSB_REG(chan)); |
468 | } |
469 | } |
470 | |
471 | return insn->n; |
472 | } |
473 | |
474 | static int das6402_ao_insn_read(struct comedi_device *dev, |
475 | struct comedi_subdevice *s, |
476 | struct comedi_insn *insn, |
477 | unsigned int *data) |
478 | { |
479 | unsigned int chan = CR_CHAN(insn->chanspec); |
480 | |
481 | /* |
482 | * If XFER mode is enabled, reading any DAC register |
483 | * will update both DAC's simultaneously. |
484 | */ |
485 | inw(port: dev->iobase + DAS6402_AO_LSB_REG(chan)); |
486 | |
487 | return comedi_readback_insn_read(dev, s, insn, data); |
488 | } |
489 | |
490 | static int das6402_di_insn_bits(struct comedi_device *dev, |
491 | struct comedi_subdevice *s, |
492 | struct comedi_insn *insn, |
493 | unsigned int *data) |
494 | { |
495 | data[1] = inb(port: dev->iobase + DAS6402_DI_DO_REG); |
496 | |
497 | return insn->n; |
498 | } |
499 | |
500 | static int das6402_do_insn_bits(struct comedi_device *dev, |
501 | struct comedi_subdevice *s, |
502 | struct comedi_insn *insn, |
503 | unsigned int *data) |
504 | { |
505 | if (comedi_dio_update_state(s, data)) |
506 | outb(value: s->state, port: dev->iobase + DAS6402_DI_DO_REG); |
507 | |
508 | data[1] = s->state; |
509 | |
510 | return insn->n; |
511 | } |
512 | |
513 | static void das6402_reset(struct comedi_device *dev) |
514 | { |
515 | struct das6402_private *devpriv = dev->private; |
516 | |
517 | /* enable "Enhanced" mode */ |
518 | outb(DAS6402_MODE_ENHANCED, port: dev->iobase + DAS6402_MODE_REG); |
519 | |
520 | /* enable 10MHz pacer clock */ |
521 | das6402_set_extended(dev, DAS6402_STATUS_W_10MHZ); |
522 | |
523 | /* enable software conversion trigger */ |
524 | outb(DAS6402_CTRL_SOFT_TRIG, port: dev->iobase + DAS6402_CTRL_REG); |
525 | |
526 | /* default ADC to single-ended unipolar 10V inputs */ |
527 | das6402_set_mode(dev, DAS6402_MODE_RANGE(0) | |
528 | DAS6402_MODE_POLLED | |
529 | DAS6402_MODE_SE | |
530 | DAS6402_MODE_UNI); |
531 | |
532 | /* default mux for single channel conversion (channel 0) */ |
533 | outw(DAS6402_AI_MUX_HI(0) | DAS6402_AI_MUX_LO(0), |
534 | port: dev->iobase + DAS6402_AI_MUX_REG); |
535 | |
536 | /* set both DAC's for unipolar 5V output range */ |
537 | devpriv->ao_range = DAS6402_AO_RANGE(0, 2) | DAS6402_AO_RANGE(1, 2); |
538 | outb(value: devpriv->ao_range, port: dev->iobase + DAS6402_TRIG_REG); |
539 | |
540 | /* set both DAC's to 0V */ |
541 | outw(value: 0, port: dev->iobase + DAS6402_AO_DATA_REG(0)); |
542 | outw(value: 0, port: dev->iobase + DAS6402_AO_DATA_REG(0)); |
543 | inw(port: dev->iobase + DAS6402_AO_LSB_REG(0)); |
544 | |
545 | /* set all digital outputs low */ |
546 | outb(value: 0, port: dev->iobase + DAS6402_DI_DO_REG); |
547 | |
548 | das6402_clear_all_interrupts(dev); |
549 | } |
550 | |
551 | static int das6402_attach(struct comedi_device *dev, |
552 | struct comedi_devconfig *it) |
553 | { |
554 | const struct das6402_boardinfo *board = dev->board_ptr; |
555 | struct das6402_private *devpriv; |
556 | struct comedi_subdevice *s; |
557 | int ret; |
558 | |
559 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
560 | if (!devpriv) |
561 | return -ENOMEM; |
562 | |
563 | ret = comedi_request_region(dev, start: it->options[0], len: 0x10); |
564 | if (ret) |
565 | return ret; |
566 | |
567 | das6402_reset(dev); |
568 | |
569 | /* IRQs 2,3,5,6,7, 10,11,15 are valid for "enhanced" mode */ |
570 | if ((1 << it->options[1]) & 0x8cec) { |
571 | ret = request_irq(irq: it->options[1], handler: das6402_interrupt, flags: 0, |
572 | name: dev->board_name, dev); |
573 | if (ret == 0) { |
574 | dev->irq = it->options[1]; |
575 | |
576 | switch (dev->irq) { |
577 | case 10: |
578 | devpriv->irq = 4; |
579 | break; |
580 | case 11: |
581 | devpriv->irq = 1; |
582 | break; |
583 | case 15: |
584 | devpriv->irq = 6; |
585 | break; |
586 | default: |
587 | devpriv->irq = dev->irq; |
588 | break; |
589 | } |
590 | } |
591 | } |
592 | |
593 | dev->pacer = comedi_8254_io_alloc(iobase: dev->iobase + DAS6402_TIMER_BASE, |
594 | I8254_OSC_BASE_10MHZ, I8254_IO8, regshift: 0); |
595 | if (IS_ERR(ptr: dev->pacer)) |
596 | return PTR_ERR(ptr: dev->pacer); |
597 | |
598 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
599 | if (ret) |
600 | return ret; |
601 | |
602 | /* Analog Input subdevice */ |
603 | s = &dev->subdevices[0]; |
604 | s->type = COMEDI_SUBD_AI; |
605 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; |
606 | s->n_chan = 64; |
607 | s->maxdata = board->maxdata; |
608 | s->range_table = &das6402_ai_ranges; |
609 | s->insn_read = das6402_ai_insn_read; |
610 | if (dev->irq) { |
611 | dev->read_subdev = s; |
612 | s->subdev_flags |= SDF_CMD_READ; |
613 | s->len_chanlist = s->n_chan; |
614 | s->do_cmdtest = das6402_ai_cmdtest; |
615 | s->do_cmd = das6402_ai_cmd; |
616 | s->cancel = das6402_ai_cancel; |
617 | } |
618 | |
619 | /* Analog Output subdevice */ |
620 | s = &dev->subdevices[1]; |
621 | s->type = COMEDI_SUBD_AO; |
622 | s->subdev_flags = SDF_WRITABLE; |
623 | s->n_chan = 2; |
624 | s->maxdata = board->maxdata; |
625 | s->range_table = &das6402_ao_ranges; |
626 | s->insn_write = das6402_ao_insn_write; |
627 | s->insn_read = das6402_ao_insn_read; |
628 | |
629 | ret = comedi_alloc_subdev_readback(s); |
630 | if (ret) |
631 | return ret; |
632 | |
633 | /* Digital Input subdevice */ |
634 | s = &dev->subdevices[2]; |
635 | s->type = COMEDI_SUBD_DI; |
636 | s->subdev_flags = SDF_READABLE; |
637 | s->n_chan = 8; |
638 | s->maxdata = 1; |
639 | s->range_table = &range_digital; |
640 | s->insn_bits = das6402_di_insn_bits; |
641 | |
642 | /* Digital Input subdevice */ |
643 | s = &dev->subdevices[3]; |
644 | s->type = COMEDI_SUBD_DO; |
645 | s->subdev_flags = SDF_WRITABLE; |
646 | s->n_chan = 8; |
647 | s->maxdata = 1; |
648 | s->range_table = &range_digital; |
649 | s->insn_bits = das6402_do_insn_bits; |
650 | |
651 | return 0; |
652 | } |
653 | |
654 | static struct comedi_driver das6402_driver = { |
655 | .driver_name = "das6402" , |
656 | .module = THIS_MODULE, |
657 | .attach = das6402_attach, |
658 | .detach = comedi_legacy_detach, |
659 | .board_name = &das6402_boards[0].name, |
660 | .num_names = ARRAY_SIZE(das6402_boards), |
661 | .offset = sizeof(struct das6402_boardinfo), |
662 | }; |
663 | module_comedi_driver(das6402_driver) |
664 | |
665 | MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>" ); |
666 | MODULE_DESCRIPTION("Comedi driver for DAS6402 compatible boards" ); |
667 | MODULE_LICENSE("GPL" ); |
668 | |