]>
Commit | Line | Data |
---|---|---|
3c501880 PP |
1 | /* |
2 | comedi/drivers/dmm32at.c | |
3 | Diamond Systems mm32at code for a Comedi driver | |
4 | ||
5 | COMEDI - Linux Control and Measurement Device Interface | |
6 | Copyright (C) 2000 David A. Schleef <ds@schleef.org> | |
7 | ||
8 | This program is free software; you can redistribute it and/or modify | |
9 | it under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation; either version 2 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | This program is distributed in the hope that it will be useful, | |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | GNU General Public License for more details. | |
3c501880 PP |
17 | */ |
18 | /* | |
19 | Driver: dmm32at | |
20 | Description: Diamond Systems mm32at driver. | |
21 | Devices: | |
22 | Author: Perry J. Piplani <perry.j.piplani@nasa.gov> | |
23 | Updated: Fri Jun 4 09:13:24 CDT 2004 | |
24 | Status: experimental | |
25 | ||
26 | This driver is for the Diamond Systems MM-32-AT board | |
27 | http://www.diamondsystems.com/products/diamondmm32at It is being used | |
28 | on serveral projects inside NASA, without problems so far. For analog | |
29 | input commands, TRIG_EXT is not yet supported at all.. | |
30 | ||
31 | Configuration Options: | |
32 | comedi_config /dev/comedi0 dmm32at baseaddr,irq | |
33 | */ | |
34 | ||
ce157f80 HS |
35 | #include <linux/module.h> |
36 | #include <linux/delay.h> | |
25436dc9 | 37 | #include <linux/interrupt.h> |
3c501880 | 38 | #include "../comedidev.h" |
3c501880 | 39 | |
27020ffe HS |
40 | #include "comedi_fc.h" |
41 | ||
3c501880 | 42 | /* Board register addresses */ |
3c501880 PP |
43 | #define DMM32AT_CONV 0x00 |
44 | #define DMM32AT_AILSB 0x00 | |
45 | #define DMM32AT_AUXDOUT 0x01 | |
46 | #define DMM32AT_AIMSB 0x01 | |
47 | #define DMM32AT_AILOW 0x02 | |
48 | #define DMM32AT_AIHIGH 0x03 | |
49 | ||
3c501880 | 50 | #define DMM32AT_DACSTAT 0x04 |
bf8e3e3a HS |
51 | #define DMM32AT_DACLSB_REG 0x04 |
52 | #define DMM32AT_DACMSB_REG 0x05 | |
53 | #define DMM32AT_DACMSB_CHAN(x) ((x) << 6) | |
3c501880 PP |
54 | |
55 | #define DMM32AT_FIFOCNTRL 0x07 | |
56 | #define DMM32AT_FIFOSTAT 0x07 | |
57 | ||
58 | #define DMM32AT_CNTRL 0x08 | |
59 | #define DMM32AT_AISTAT 0x08 | |
60 | ||
61 | #define DMM32AT_INTCLOCK 0x09 | |
62 | ||
63 | #define DMM32AT_CNTRDIO 0x0a | |
64 | ||
65 | #define DMM32AT_AICONF 0x0b | |
66 | #define DMM32AT_AIRBACK 0x0b | |
67 | ||
68 | #define DMM32AT_CLK1 0x0d | |
69 | #define DMM32AT_CLK2 0x0e | |
70 | #define DMM32AT_CLKCT 0x0f | |
71 | ||
72 | #define DMM32AT_DIOA 0x0c | |
73 | #define DMM32AT_DIOB 0x0d | |
74 | #define DMM32AT_DIOC 0x0e | |
75 | #define DMM32AT_DIOCONF 0x0f | |
76 | ||
3c501880 PP |
77 | /* Board register values. */ |
78 | ||
79 | /* DMM32AT_DACSTAT 0x04 */ | |
80 | #define DMM32AT_DACBUSY 0x80 | |
81 | ||
82 | /* DMM32AT_FIFOCNTRL 0x07 */ | |
83 | #define DMM32AT_FIFORESET 0x02 | |
84 | #define DMM32AT_SCANENABLE 0x04 | |
85 | ||
86 | /* DMM32AT_CNTRL 0x08 */ | |
87 | #define DMM32AT_RESET 0x20 | |
88 | #define DMM32AT_INTRESET 0x08 | |
89 | #define DMM32AT_CLKACC 0x00 | |
90 | #define DMM32AT_DIOACC 0x01 | |
91 | ||
92 | /* DMM32AT_AISTAT 0x08 */ | |
93 | #define DMM32AT_STATUS 0x80 | |
94 | ||
95 | /* DMM32AT_INTCLOCK 0x09 */ | |
96 | #define DMM32AT_ADINT 0x80 | |
97 | #define DMM32AT_CLKSEL 0x03 | |
98 | ||
99 | /* DMM32AT_CNTRDIO 0x0a */ | |
100 | #define DMM32AT_FREQ12 0x80 | |
101 | ||
102 | /* DMM32AT_AICONF 0x0b */ | |
103 | #define DMM32AT_RANGE_U10 0x0c | |
104 | #define DMM32AT_RANGE_U5 0x0d | |
105 | #define DMM32AT_RANGE_B10 0x08 | |
106 | #define DMM32AT_RANGE_B5 0x00 | |
107 | #define DMM32AT_SCINT_20 0x00 | |
108 | #define DMM32AT_SCINT_15 0x10 | |
109 | #define DMM32AT_SCINT_10 0x20 | |
110 | #define DMM32AT_SCINT_5 0x30 | |
111 | ||
112 | /* DMM32AT_CLKCT 0x0f */ | |
113 | #define DMM32AT_CLKCT1 0x56 /* mode3 counter 1 - write low byte only */ | |
114 | #define DMM32AT_CLKCT2 0xb6 /* mode3 counter 2 - write high and low byte */ | |
115 | ||
116 | /* DMM32AT_DIOCONF 0x0f */ | |
117 | #define DMM32AT_DIENABLE 0x80 | |
118 | #define DMM32AT_DIRA 0x10 | |
119 | #define DMM32AT_DIRB 0x02 | |
120 | #define DMM32AT_DIRCL 0x01 | |
121 | #define DMM32AT_DIRCH 0x08 | |
122 | ||
123 | /* board AI ranges in comedi structure */ | |
9ced1de6 | 124 | static const struct comedi_lrange dmm32at_airanges = { |
45ddfc5a HS |
125 | 4, { |
126 | UNI_RANGE(10), | |
127 | UNI_RANGE(5), | |
128 | BIP_RANGE(10), | |
129 | BIP_RANGE(5) | |
130 | } | |
3c501880 PP |
131 | }; |
132 | ||
133 | /* register values for above ranges */ | |
134 | static const unsigned char dmm32at_rangebits[] = { | |
135 | DMM32AT_RANGE_U10, | |
136 | DMM32AT_RANGE_U5, | |
137 | DMM32AT_RANGE_B10, | |
138 | DMM32AT_RANGE_B5, | |
139 | }; | |
140 | ||
141 | /* only one of these ranges is valid, as set by a jumper on the | |
142 | * board. The application should only use the range set by the jumper | |
143 | */ | |
9ced1de6 | 144 | static const struct comedi_lrange dmm32at_aoranges = { |
45ddfc5a HS |
145 | 4, { |
146 | UNI_RANGE(10), | |
147 | UNI_RANGE(5), | |
148 | BIP_RANGE(10), | |
149 | BIP_RANGE(5) | |
150 | } | |
3c501880 PP |
151 | }; |
152 | ||
39d31e09 | 153 | struct dmm32at_private { |
3c501880 PP |
154 | int data; |
155 | int ai_inuse; | |
156 | unsigned int ai_scans_left; | |
3c501880 | 157 | unsigned char dio_config; |
39d31e09 | 158 | }; |
3c501880 | 159 | |
f7b3d79a HS |
160 | static int dmm32at_ai_status(struct comedi_device *dev, |
161 | struct comedi_subdevice *s, | |
162 | struct comedi_insn *insn, | |
163 | unsigned long context) | |
164 | { | |
165 | unsigned char status; | |
166 | ||
167 | status = inb(dev->iobase + context); | |
168 | if ((status & DMM32AT_STATUS) == 0) | |
169 | return 0; | |
170 | return -EBUSY; | |
171 | } | |
172 | ||
0a85b6f0 MT |
173 | static int dmm32at_ai_rinsn(struct comedi_device *dev, |
174 | struct comedi_subdevice *s, | |
175 | struct comedi_insn *insn, unsigned int *data) | |
3c501880 | 176 | { |
f7b3d79a | 177 | int n; |
3c501880 | 178 | unsigned int d; |
3c501880 PP |
179 | unsigned short msb, lsb; |
180 | unsigned char chan; | |
181 | int range; | |
f7b3d79a | 182 | int ret; |
3c501880 PP |
183 | |
184 | /* get the channel and range number */ | |
185 | ||
186 | chan = CR_CHAN(insn->chanspec) & (s->n_chan - 1); | |
187 | range = CR_RANGE(insn->chanspec); | |
188 | ||
3c501880 | 189 | /* zero scan and fifo control and reset fifo */ |
29f747c2 | 190 | outb(DMM32AT_FIFORESET, dev->iobase + DMM32AT_FIFOCNTRL); |
3c501880 PP |
191 | |
192 | /* write the ai channel range regs */ | |
29f747c2 HS |
193 | outb(chan, dev->iobase + DMM32AT_AILOW); |
194 | outb(chan, dev->iobase + DMM32AT_AIHIGH); | |
3c501880 | 195 | /* set the range bits */ |
29f747c2 | 196 | outb(dmm32at_rangebits[range], dev->iobase + DMM32AT_AICONF); |
3c501880 PP |
197 | |
198 | /* wait for circuit to settle */ | |
f7b3d79a HS |
199 | ret = comedi_timeout(dev, s, insn, dmm32at_ai_status, DMM32AT_AIRBACK); |
200 | if (ret) | |
201 | return ret; | |
3c501880 PP |
202 | |
203 | /* convert n samples */ | |
204 | for (n = 0; n < insn->n; n++) { | |
205 | /* trigger conversion */ | |
29f747c2 | 206 | outb(0xff, dev->iobase + DMM32AT_CONV); |
f7b3d79a | 207 | |
3c501880 | 208 | /* wait for conversion to end */ |
f7b3d79a HS |
209 | ret = comedi_timeout(dev, s, insn, dmm32at_ai_status, |
210 | DMM32AT_AISTAT); | |
211 | if (ret) | |
212 | return ret; | |
3c501880 PP |
213 | |
214 | /* read data */ | |
99953ea1 HS |
215 | lsb = inb(dev->iobase + DMM32AT_AILSB); |
216 | msb = inb(dev->iobase + DMM32AT_AIMSB); | |
3c501880 PP |
217 | |
218 | /* invert sign bit to make range unsigned, this is an | |
25985edc | 219 | idiosyncrasy of the diamond board, it return |
3c501880 PP |
220 | conversions as a signed value, i.e. -32768 to |
221 | 32767, flipping the bit and interpreting it as | |
222 | signed gives you a range of 0 to 65535 which is | |
223 | used by comedi */ | |
224 | d = ((msb ^ 0x0080) << 8) + lsb; | |
225 | ||
226 | data[n] = d; | |
227 | } | |
228 | ||
229 | /* return the number of samples read/written */ | |
230 | return n; | |
231 | } | |
232 | ||
a207c12f | 233 | static int dmm32at_ns_to_timer(unsigned int *ns, unsigned int flags) |
47ae6a72 HS |
234 | { |
235 | /* trivial timer */ | |
47ae6a72 HS |
236 | return *ns; |
237 | } | |
238 | ||
86914996 HS |
239 | static int dmm32at_ai_check_chanlist(struct comedi_device *dev, |
240 | struct comedi_subdevice *s, | |
241 | struct comedi_cmd *cmd) | |
242 | { | |
243 | unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); | |
244 | unsigned int range0 = CR_RANGE(cmd->chanlist[0]); | |
245 | int i; | |
246 | ||
247 | for (i = 1; i < cmd->chanlist_len; i++) { | |
248 | unsigned int chan = CR_CHAN(cmd->chanlist[i]); | |
249 | unsigned int range = CR_RANGE(cmd->chanlist[i]); | |
250 | ||
251 | if (chan != (chan0 + i) % s->n_chan) { | |
252 | dev_dbg(dev->class_dev, | |
253 | "entries in chanlist must be consecutive channels, counting upwards\n"); | |
254 | return -EINVAL; | |
255 | } | |
256 | if (range != range0) { | |
257 | dev_dbg(dev->class_dev, | |
258 | "entries in chanlist must all have the same gain\n"); | |
259 | return -EINVAL; | |
260 | } | |
261 | } | |
262 | ||
263 | return 0; | |
264 | } | |
265 | ||
0a85b6f0 MT |
266 | static int dmm32at_ai_cmdtest(struct comedi_device *dev, |
267 | struct comedi_subdevice *s, | |
268 | struct comedi_cmd *cmd) | |
3c501880 PP |
269 | { |
270 | int err = 0; | |
d579392a | 271 | unsigned int arg; |
3c501880 | 272 | |
27020ffe | 273 | /* Step 1 : check if triggers are trivially valid */ |
3c501880 | 274 | |
27020ffe HS |
275 | err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); |
276 | err |= cfc_check_trigger_src(&cmd->scan_begin_src, | |
277 | TRIG_TIMER /*| TRIG_EXT */); | |
278 | err |= cfc_check_trigger_src(&cmd->convert_src, | |
279 | TRIG_TIMER /*| TRIG_EXT */); | |
280 | err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
281 | err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); | |
3c501880 PP |
282 | |
283 | if (err) | |
284 | return 1; | |
285 | ||
27020ffe | 286 | /* Step 2a : make sure trigger sources are unique */ |
3c501880 | 287 | |
27020ffe HS |
288 | err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); |
289 | err |= cfc_check_trigger_is_unique(cmd->convert_src); | |
290 | err |= cfc_check_trigger_is_unique(cmd->stop_src); | |
291 | ||
292 | /* Step 2b : and mutually compatible */ | |
3c501880 PP |
293 | |
294 | if (err) | |
295 | return 2; | |
296 | ||
e43ed5fa HS |
297 | /* Step 3: check if arguments are trivially valid */ |
298 | ||
299 | err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); | |
3c501880 | 300 | |
3c501880 PP |
301 | #define MAX_SCAN_SPEED 1000000 /* in nanoseconds */ |
302 | #define MIN_SCAN_SPEED 1000000000 /* in nanoseconds */ | |
303 | ||
304 | if (cmd->scan_begin_src == TRIG_TIMER) { | |
e43ed5fa HS |
305 | err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, |
306 | MAX_SCAN_SPEED); | |
307 | err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, | |
308 | MIN_SCAN_SPEED); | |
3c501880 PP |
309 | } else { |
310 | /* external trigger */ | |
311 | /* should be level/edge, hi/lo specification here */ | |
312 | /* should specify multiple external triggers */ | |
e43ed5fa | 313 | err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9); |
3c501880 | 314 | } |
e43ed5fa | 315 | |
3c501880 PP |
316 | if (cmd->convert_src == TRIG_TIMER) { |
317 | if (cmd->convert_arg >= 17500) | |
318 | cmd->convert_arg = 20000; | |
319 | else if (cmd->convert_arg >= 12500) | |
320 | cmd->convert_arg = 15000; | |
321 | else if (cmd->convert_arg >= 7500) | |
322 | cmd->convert_arg = 10000; | |
323 | else | |
324 | cmd->convert_arg = 5000; | |
3c501880 PP |
325 | } else { |
326 | /* external trigger */ | |
327 | /* see above */ | |
e43ed5fa | 328 | err |= cfc_check_trigger_arg_max(&cmd->convert_arg, 9); |
3c501880 PP |
329 | } |
330 | ||
e43ed5fa HS |
331 | err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); |
332 | ||
3c501880 | 333 | if (cmd->stop_src == TRIG_COUNT) { |
e43ed5fa HS |
334 | err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0xfffffff0); |
335 | err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); | |
3c501880 PP |
336 | } else { |
337 | /* TRIG_NONE */ | |
e43ed5fa | 338 | err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); |
3c501880 PP |
339 | } |
340 | ||
341 | if (err) | |
342 | return 3; | |
343 | ||
344 | /* step 4: fix up any arguments */ | |
345 | ||
346 | if (cmd->scan_begin_src == TRIG_TIMER) { | |
d579392a | 347 | arg = cmd->scan_begin_arg; |
a207c12f | 348 | dmm32at_ns_to_timer(&arg, cmd->flags); |
d579392a | 349 | err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg); |
3c501880 PP |
350 | } |
351 | if (cmd->convert_src == TRIG_TIMER) { | |
d579392a | 352 | arg = cmd->convert_arg; |
a207c12f | 353 | dmm32at_ns_to_timer(&arg, cmd->flags); |
d579392a HS |
354 | err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg); |
355 | ||
356 | if (cmd->scan_begin_src == TRIG_TIMER) { | |
357 | arg = cmd->convert_arg * cmd->scan_end_arg; | |
358 | err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, | |
359 | arg); | |
3c501880 PP |
360 | } |
361 | } | |
362 | ||
363 | if (err) | |
364 | return 4; | |
365 | ||
86914996 HS |
366 | /* Step 5: check channel list if it exists */ |
367 | if (cmd->chanlist && cmd->chanlist_len > 0) | |
368 | err |= dmm32at_ai_check_chanlist(dev, s, cmd); | |
3c501880 PP |
369 | |
370 | if (err) | |
371 | return 5; | |
372 | ||
373 | return 0; | |
374 | } | |
375 | ||
47ae6a72 HS |
376 | static void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec) |
377 | { | |
378 | unsigned char lo1, lo2, hi2; | |
379 | unsigned short both2; | |
380 | ||
381 | /* based on 10mhz clock */ | |
382 | lo1 = 200; | |
383 | both2 = nansec / 20000; | |
384 | hi2 = (both2 & 0xff00) >> 8; | |
385 | lo2 = both2 & 0x00ff; | |
386 | ||
387 | /* set the counter frequency to 10mhz */ | |
29f747c2 | 388 | outb(0, dev->iobase + DMM32AT_CNTRDIO); |
47ae6a72 HS |
389 | |
390 | /* get access to the clock regs */ | |
29f747c2 | 391 | outb(DMM32AT_CLKACC, dev->iobase + DMM32AT_CNTRL); |
47ae6a72 HS |
392 | |
393 | /* write the counter 1 control word and low byte to counter */ | |
29f747c2 HS |
394 | outb(DMM32AT_CLKCT1, dev->iobase + DMM32AT_CLKCT); |
395 | outb(lo1, dev->iobase + DMM32AT_CLK1); | |
47ae6a72 HS |
396 | |
397 | /* write the counter 2 control word and low byte then to counter */ | |
29f747c2 HS |
398 | outb(DMM32AT_CLKCT2, dev->iobase + DMM32AT_CLKCT); |
399 | outb(lo2, dev->iobase + DMM32AT_CLK2); | |
400 | outb(hi2, dev->iobase + DMM32AT_CLK2); | |
47ae6a72 HS |
401 | |
402 | /* enable the ai conversion interrupt and the clock to start scans */ | |
29f747c2 | 403 | outb(DMM32AT_ADINT | DMM32AT_CLKSEL, dev->iobase + DMM32AT_INTCLOCK); |
47ae6a72 HS |
404 | } |
405 | ||
da91b269 | 406 | static int dmm32at_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
3c501880 | 407 | { |
3eff0174 | 408 | struct dmm32at_private *devpriv = dev->private; |
ea6d0d4c | 409 | struct comedi_cmd *cmd = &s->async->cmd; |
f7b3d79a HS |
410 | int range; |
411 | unsigned char chanlo, chanhi; | |
412 | int ret; | |
3c501880 PP |
413 | |
414 | if (!cmd->chanlist) | |
415 | return -EINVAL; | |
416 | ||
417 | /* get the channel list and range */ | |
418 | chanlo = CR_CHAN(cmd->chanlist[0]) & (s->n_chan - 1); | |
419 | chanhi = chanlo + cmd->chanlist_len - 1; | |
420 | if (chanhi >= s->n_chan) | |
421 | return -EINVAL; | |
422 | range = CR_RANGE(cmd->chanlist[0]); | |
423 | ||
424 | /* reset fifo */ | |
29f747c2 | 425 | outb(DMM32AT_FIFORESET, dev->iobase + DMM32AT_FIFOCNTRL); |
3c501880 PP |
426 | |
427 | /* set scan enable */ | |
29f747c2 | 428 | outb(DMM32AT_SCANENABLE, dev->iobase + DMM32AT_FIFOCNTRL); |
3c501880 PP |
429 | |
430 | /* write the ai channel range regs */ | |
29f747c2 HS |
431 | outb(chanlo, dev->iobase + DMM32AT_AILOW); |
432 | outb(chanhi, dev->iobase + DMM32AT_AIHIGH); | |
3c501880 PP |
433 | |
434 | /* set the range bits */ | |
29f747c2 | 435 | outb(dmm32at_rangebits[range], dev->iobase + DMM32AT_AICONF); |
3c501880 PP |
436 | |
437 | /* reset the interrupt just in case */ | |
29f747c2 | 438 | outb(DMM32AT_INTRESET, dev->iobase + DMM32AT_CNTRL); |
3c501880 PP |
439 | |
440 | if (cmd->stop_src == TRIG_COUNT) | |
441 | devpriv->ai_scans_left = cmd->stop_arg; | |
442 | else { /* TRIG_NONE */ | |
27bf0bc9 M |
443 | devpriv->ai_scans_left = 0xffffffff; /* indicates TRIG_NONE to |
444 | * isr */ | |
3c501880 PP |
445 | } |
446 | ||
f7b3d79a HS |
447 | /* |
448 | * wait for circuit to settle | |
449 | * we don't have the 'insn' here but it's not needed | |
450 | */ | |
451 | ret = comedi_timeout(dev, s, NULL, dmm32at_ai_status, DMM32AT_AIRBACK); | |
452 | if (ret) | |
453 | return ret; | |
3c501880 PP |
454 | |
455 | if (devpriv->ai_scans_left > 1) { | |
456 | /* start the clock and enable the interrupts */ | |
457 | dmm32at_setaitimer(dev, cmd->scan_begin_arg); | |
458 | } else { | |
459 | /* start the interrups and initiate a single scan */ | |
29f747c2 HS |
460 | outb(DMM32AT_ADINT, dev->iobase + DMM32AT_INTCLOCK); |
461 | outb(0xff, dev->iobase + DMM32AT_CONV); | |
3c501880 PP |
462 | } |
463 | ||
3c501880 PP |
464 | return 0; |
465 | ||
466 | } | |
467 | ||
0a85b6f0 MT |
468 | static int dmm32at_ai_cancel(struct comedi_device *dev, |
469 | struct comedi_subdevice *s) | |
3c501880 | 470 | { |
3eff0174 HS |
471 | struct dmm32at_private *devpriv = dev->private; |
472 | ||
3c501880 PP |
473 | devpriv->ai_scans_left = 1; |
474 | return 0; | |
475 | } | |
476 | ||
70265d24 | 477 | static irqreturn_t dmm32at_isr(int irq, void *d) |
3c501880 | 478 | { |
3eff0174 HS |
479 | struct comedi_device *dev = d; |
480 | struct dmm32at_private *devpriv = dev->private; | |
3c501880 PP |
481 | unsigned char intstat; |
482 | unsigned int samp; | |
483 | unsigned short msb, lsb; | |
484 | int i; | |
3c501880 PP |
485 | |
486 | if (!dev->attached) { | |
2d60dd4a | 487 | dev_err(dev->class_dev, "spurious interrupt\n"); |
3c501880 PP |
488 | return IRQ_HANDLED; |
489 | } | |
490 | ||
99953ea1 | 491 | intstat = inb(dev->iobase + DMM32AT_INTCLOCK); |
3c501880 PP |
492 | |
493 | if (intstat & DMM32AT_ADINT) { | |
34c43922 | 494 | struct comedi_subdevice *s = dev->read_subdev; |
ea6d0d4c | 495 | struct comedi_cmd *cmd = &s->async->cmd; |
3c501880 PP |
496 | |
497 | for (i = 0; i < cmd->chanlist_len; i++) { | |
498 | /* read data */ | |
99953ea1 HS |
499 | lsb = inb(dev->iobase + DMM32AT_AILSB); |
500 | msb = inb(dev->iobase + DMM32AT_AIMSB); | |
3c501880 PP |
501 | |
502 | /* invert sign bit to make range unsigned */ | |
503 | samp = ((msb ^ 0x0080) << 8) + lsb; | |
1700529b | 504 | comedi_buf_write_samples(s, &samp, 1); |
3c501880 PP |
505 | } |
506 | ||
507 | if (devpriv->ai_scans_left != 0xffffffff) { /* TRIG_COUNT */ | |
508 | devpriv->ai_scans_left--; | |
509 | if (devpriv->ai_scans_left == 0) { | |
510 | /* disable further interrupts and clocks */ | |
29f747c2 | 511 | outb(0x0, dev->iobase + DMM32AT_INTCLOCK); |
3c501880 PP |
512 | /* set the buffer to be flushed with an EOF */ |
513 | s->async->events |= COMEDI_CB_EOA; | |
514 | } | |
515 | ||
516 | } | |
285d1ff1 | 517 | comedi_handle_events(dev, s); |
3c501880 PP |
518 | } |
519 | ||
520 | /* reset the interrupt */ | |
29f747c2 | 521 | outb(DMM32AT_INTRESET, dev->iobase + DMM32AT_CNTRL); |
3c501880 PP |
522 | return IRQ_HANDLED; |
523 | } | |
524 | ||
f7b3d79a HS |
525 | static int dmm32at_ao_eoc(struct comedi_device *dev, |
526 | struct comedi_subdevice *s, | |
527 | struct comedi_insn *insn, | |
528 | unsigned long context) | |
529 | { | |
530 | unsigned char status; | |
531 | ||
532 | status = inb(dev->iobase + DMM32AT_DACSTAT); | |
533 | if ((status & DMM32AT_DACBUSY) == 0) | |
534 | return 0; | |
535 | return -EBUSY; | |
536 | } | |
537 | ||
bf8e3e3a HS |
538 | static int dmm32at_ao_insn_write(struct comedi_device *dev, |
539 | struct comedi_subdevice *s, | |
540 | struct comedi_insn *insn, | |
541 | unsigned int *data) | |
3c501880 | 542 | { |
bf8e3e3a | 543 | unsigned int chan = CR_CHAN(insn->chanspec); |
bf8e3e3a | 544 | int i; |
3c501880 | 545 | |
3c501880 | 546 | for (i = 0; i < insn->n; i++) { |
b328ad30 HS |
547 | unsigned int val = data[i]; |
548 | int ret; | |
3c501880 | 549 | |
bf8e3e3a HS |
550 | /* write LSB then MSB + chan to load DAC */ |
551 | outb(val & 0xff, dev->iobase + DMM32AT_DACLSB_REG); | |
552 | outb((val >> 8) | DMM32AT_DACMSB_CHAN(chan), | |
553 | dev->iobase + DMM32AT_DACMSB_REG); | |
3c501880 PP |
554 | |
555 | /* wait for circuit to settle */ | |
f7b3d79a HS |
556 | ret = comedi_timeout(dev, s, insn, dmm32at_ao_eoc, 0); |
557 | if (ret) | |
558 | return ret; | |
65e2618f | 559 | |
bf8e3e3a HS |
560 | /* dummy read to update DAC */ |
561 | inb(dev->iobase + DMM32AT_DACMSB_REG); | |
3c501880 | 562 | |
b328ad30 | 563 | s->readback[chan] = val; |
3c501880 PP |
564 | } |
565 | ||
bf8e3e3a | 566 | return insn->n; |
3c501880 PP |
567 | } |
568 | ||
0a85b6f0 MT |
569 | static int dmm32at_dio_insn_bits(struct comedi_device *dev, |
570 | struct comedi_subdevice *s, | |
b3ff824a HS |
571 | struct comedi_insn *insn, |
572 | unsigned int *data) | |
3c501880 | 573 | { |
3eff0174 | 574 | struct dmm32at_private *devpriv = dev->private; |
b3ff824a HS |
575 | unsigned int mask; |
576 | unsigned int val; | |
577 | ||
578 | mask = comedi_dio_update_state(s, data); | |
579 | if (mask) { | |
580 | /* get access to the DIO regs */ | |
581 | outb(DMM32AT_DIOACC, dev->iobase + DMM32AT_CNTRL); | |
582 | ||
583 | /* if either part of dio is set for output */ | |
584 | if (((devpriv->dio_config & DMM32AT_DIRCL) == 0) || | |
585 | ((devpriv->dio_config & DMM32AT_DIRCH) == 0)) { | |
586 | val = (s->state & 0x00ff0000) >> 16; | |
587 | outb(val, dev->iobase + DMM32AT_DIOC); | |
588 | } | |
589 | if ((devpriv->dio_config & DMM32AT_DIRB) == 0) { | |
590 | val = (s->state & 0x0000ff00) >> 8; | |
591 | outb(val, dev->iobase + DMM32AT_DIOB); | |
592 | } | |
593 | if ((devpriv->dio_config & DMM32AT_DIRA) == 0) { | |
594 | val = (s->state & 0x000000ff); | |
595 | outb(val, dev->iobase + DMM32AT_DIOA); | |
596 | } | |
3c501880 PP |
597 | } |
598 | ||
b3ff824a HS |
599 | val = inb(dev->iobase + DMM32AT_DIOA); |
600 | val |= inb(dev->iobase + DMM32AT_DIOB) << 8; | |
601 | val |= inb(dev->iobase + DMM32AT_DIOC) << 16; | |
602 | s->state = val; | |
3c501880 | 603 | |
b3ff824a | 604 | data[1] = val; |
3c501880 | 605 | |
a2714e3e | 606 | return insn->n; |
3c501880 PP |
607 | } |
608 | ||
0a85b6f0 MT |
609 | static int dmm32at_dio_insn_config(struct comedi_device *dev, |
610 | struct comedi_subdevice *s, | |
49b71eba HS |
611 | struct comedi_insn *insn, |
612 | unsigned int *data) | |
3c501880 | 613 | { |
3eff0174 | 614 | struct dmm32at_private *devpriv = dev->private; |
49b71eba HS |
615 | unsigned int chan = CR_CHAN(insn->chanspec); |
616 | unsigned int mask; | |
3c501880 | 617 | unsigned char chanbit; |
49b71eba | 618 | int ret; |
3c501880 | 619 | |
49b71eba HS |
620 | if (chan < 8) { |
621 | mask = 0x0000ff; | |
3c501880 | 622 | chanbit = DMM32AT_DIRA; |
49b71eba HS |
623 | } else if (chan < 16) { |
624 | mask = 0x00ff00; | |
3c501880 | 625 | chanbit = DMM32AT_DIRB; |
49b71eba HS |
626 | } else if (chan < 20) { |
627 | mask = 0x0f0000; | |
3c501880 | 628 | chanbit = DMM32AT_DIRCL; |
49b71eba HS |
629 | } else { |
630 | mask = 0xf00000; | |
3c501880 | 631 | chanbit = DMM32AT_DIRCH; |
49b71eba | 632 | } |
3c501880 | 633 | |
49b71eba HS |
634 | ret = comedi_dio_insn_config(dev, s, insn, data, mask); |
635 | if (ret) | |
636 | return ret; | |
3c501880 | 637 | |
49b71eba | 638 | if (data[0] == INSN_CONFIG_DIO_OUTPUT) |
3c501880 | 639 | devpriv->dio_config &= ~chanbit; |
20962c10 | 640 | else |
3c501880 | 641 | devpriv->dio_config |= chanbit; |
3c501880 | 642 | /* get access to the DIO regs */ |
29f747c2 | 643 | outb(DMM32AT_DIOACC, dev->iobase + DMM32AT_CNTRL); |
3c501880 | 644 | /* set the DIO's to the new configuration setting */ |
29f747c2 | 645 | outb(devpriv->dio_config, dev->iobase + DMM32AT_DIOCONF); |
3c501880 | 646 | |
49b71eba | 647 | return insn->n; |
3c501880 PP |
648 | } |
649 | ||
4f793db3 HS |
650 | static int dmm32at_attach(struct comedi_device *dev, |
651 | struct comedi_devconfig *it) | |
652 | { | |
3eff0174 | 653 | struct dmm32at_private *devpriv; |
4f793db3 HS |
654 | int ret; |
655 | struct comedi_subdevice *s; | |
656 | unsigned char aihi, ailo, fifostat, aistat, intstat, airback; | |
4f793db3 | 657 | |
88634cd8 | 658 | ret = comedi_request_region(dev, it->options[0], 0x10); |
2dd11a81 HS |
659 | if (ret) |
660 | return ret; | |
4f793db3 HS |
661 | |
662 | /* the following just makes sure the board is there and gets | |
663 | it to a known state */ | |
664 | ||
665 | /* reset the board */ | |
29f747c2 | 666 | outb(DMM32AT_RESET, dev->iobase + DMM32AT_CNTRL); |
4f793db3 HS |
667 | |
668 | /* allow a millisecond to reset */ | |
669 | udelay(1000); | |
670 | ||
671 | /* zero scan and fifo control */ | |
29f747c2 | 672 | outb(0x0, dev->iobase + DMM32AT_FIFOCNTRL); |
4f793db3 HS |
673 | |
674 | /* zero interrupt and clock control */ | |
29f747c2 | 675 | outb(0x0, dev->iobase + DMM32AT_INTCLOCK); |
4f793db3 HS |
676 | |
677 | /* write a test channel range, the high 3 bits should drop */ | |
29f747c2 HS |
678 | outb(0x80, dev->iobase + DMM32AT_AILOW); |
679 | outb(0xff, dev->iobase + DMM32AT_AIHIGH); | |
4f793db3 HS |
680 | |
681 | /* set the range at 10v unipolar */ | |
29f747c2 | 682 | outb(DMM32AT_RANGE_U10, dev->iobase + DMM32AT_AICONF); |
4f793db3 HS |
683 | |
684 | /* should take 10 us to settle, here's a hundred */ | |
685 | udelay(100); | |
686 | ||
687 | /* read back the values */ | |
99953ea1 HS |
688 | ailo = inb(dev->iobase + DMM32AT_AILOW); |
689 | aihi = inb(dev->iobase + DMM32AT_AIHIGH); | |
690 | fifostat = inb(dev->iobase + DMM32AT_FIFOSTAT); | |
691 | aistat = inb(dev->iobase + DMM32AT_AISTAT); | |
692 | intstat = inb(dev->iobase + DMM32AT_INTCLOCK); | |
693 | airback = inb(dev->iobase + DMM32AT_AIRBACK); | |
4f793db3 | 694 | |
4f793db3 HS |
695 | if ((ailo != 0x00) || (aihi != 0x1f) || (fifostat != 0x80) || |
696 | (aistat != 0x60 || (intstat != 0x00) || airback != 0x0c)) { | |
45d35103 | 697 | dev_err(dev->class_dev, "board detection failed\n"); |
4f793db3 HS |
698 | return -EIO; |
699 | } | |
700 | ||
0c2e5532 HS |
701 | if (it->options[1]) { |
702 | ret = request_irq(it->options[1], dmm32at_isr, 0, | |
703 | dev->board_name, dev); | |
704 | if (ret == 0) | |
705 | dev->irq = it->options[1]; | |
4f793db3 HS |
706 | } |
707 | ||
0bdab509 | 708 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
c34fa261 HS |
709 | if (!devpriv) |
710 | return -ENOMEM; | |
4f793db3 HS |
711 | |
712 | ret = comedi_alloc_subdevices(dev, 3); | |
713 | if (ret) | |
714 | return ret; | |
715 | ||
2930d0ba | 716 | s = &dev->subdevices[0]; |
4f793db3 HS |
717 | /* analog input subdevice */ |
718 | s->type = COMEDI_SUBD_AI; | |
719 | /* we support single-ended (ground) and differential */ | |
0c2e5532 | 720 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; |
27921828 HS |
721 | s->n_chan = 32; |
722 | s->maxdata = 0xffff; | |
723 | s->range_table = &dmm32at_airanges; | |
4f793db3 | 724 | s->insn_read = dmm32at_ai_rinsn; |
0c2e5532 HS |
725 | if (dev->irq) { |
726 | dev->read_subdev = s; | |
727 | s->subdev_flags |= SDF_CMD_READ; | |
728 | s->len_chanlist = 32; | |
729 | s->do_cmd = dmm32at_ai_cmd; | |
730 | s->do_cmdtest = dmm32at_ai_cmdtest; | |
731 | s->cancel = dmm32at_ai_cancel; | |
732 | } | |
4f793db3 | 733 | |
2930d0ba | 734 | s = &dev->subdevices[1]; |
4f793db3 HS |
735 | /* analog output subdevice */ |
736 | s->type = COMEDI_SUBD_AO; | |
737 | s->subdev_flags = SDF_WRITABLE; | |
27921828 HS |
738 | s->n_chan = 4; |
739 | s->maxdata = 0x0fff; | |
740 | s->range_table = &dmm32at_aoranges; | |
bf8e3e3a | 741 | s->insn_write = dmm32at_ao_insn_write; |
b328ad30 HS |
742 | s->insn_read = comedi_readback_insn_read; |
743 | ||
744 | ret = comedi_alloc_subdev_readback(s); | |
745 | if (ret) | |
746 | return ret; | |
4f793db3 | 747 | |
2930d0ba | 748 | s = &dev->subdevices[2]; |
4f793db3 | 749 | /* digital i/o subdevice */ |
27921828 HS |
750 | |
751 | /* get access to the DIO regs */ | |
752 | outb(DMM32AT_DIOACC, dev->iobase + DMM32AT_CNTRL); | |
753 | /* set the DIO's to the defualt input setting */ | |
754 | devpriv->dio_config = DMM32AT_DIRA | DMM32AT_DIRB | | |
755 | DMM32AT_DIRCL | DMM32AT_DIRCH | DMM32AT_DIENABLE; | |
756 | outb(devpriv->dio_config, dev->iobase + DMM32AT_DIOCONF); | |
757 | ||
758 | /* set up the subdevice */ | |
759 | s->type = COMEDI_SUBD_DIO; | |
760 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
761 | s->n_chan = 24; | |
762 | s->maxdata = 1; | |
763 | s->state = 0; | |
764 | s->range_table = &range_digital; | |
765 | s->insn_bits = dmm32at_dio_insn_bits; | |
766 | s->insn_config = dmm32at_dio_insn_config; | |
4f793db3 | 767 | |
9b35db02 | 768 | return 0; |
4f793db3 HS |
769 | } |
770 | ||
17f49dd4 HS |
771 | static struct comedi_driver dmm32at_driver = { |
772 | .driver_name = "dmm32at", | |
773 | .module = THIS_MODULE, | |
774 | .attach = dmm32at_attach, | |
3d1fe3f7 | 775 | .detach = comedi_legacy_detach, |
17f49dd4 HS |
776 | }; |
777 | module_comedi_driver(dmm32at_driver); | |
90f703d3 AT |
778 | |
779 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
780 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
781 | MODULE_LICENSE("GPL"); |