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