Commit | Line | Data |
---|---|---|
3726e56b FMH |
1 | /* |
2 | comedi/drivers/das800.c | |
3 | Driver for Keitley das800 series boards and compatibles | |
4 | Copyright (C) 2000 Frank Mori Hess <fmhess@users.sourceforge.net> | |
5 | ||
6 | COMEDI - Linux Control and Measurement Device Interface | |
7 | Copyright (C) 2000 David A. Schleef <ds@schleef.org> | |
8 | ||
9 | This program is free software; you can redistribute it and/or modify | |
10 | it under the terms of the GNU General Public License as published by | |
11 | the Free Software Foundation; either version 2 of the License, or | |
12 | (at your option) any later version. | |
13 | ||
14 | This program is distributed in the hope that it will be useful, | |
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | GNU General Public License for more details. | |
18 | ||
19 | You should have received a copy of the GNU General Public License | |
20 | along with this program; if not, write to the Free Software | |
21 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
22 | ||
23 | ************************************************************************ | |
24 | */ | |
25 | /* | |
26 | Driver: das800 | |
27 | Description: Keithley Metrabyte DAS800 (& compatibles) | |
28 | Author: Frank Mori Hess <fmhess@users.sourceforge.net> | |
29 | Devices: [Keithley Metrabyte] DAS-800 (das-800), DAS-801 (das-801), | |
30 | DAS-802 (das-802), | |
31 | [Measurement Computing] CIO-DAS800 (cio-das800), | |
32 | CIO-DAS801 (cio-das801), CIO-DAS802 (cio-das802), | |
33 | CIO-DAS802/16 (cio-das802/16) | |
34 | Status: works, cio-das802/16 untested - email me if you have tested it | |
35 | ||
36 | Configuration options: | |
37 | [0] - I/O port base address | |
38 | [1] - IRQ (optional, required for timed or externally triggered conversions) | |
39 | ||
40 | Notes: | |
41 | IRQ can be omitted, although the cmd interface will not work without it. | |
42 | ||
43 | All entries in the channel/gain list must use the same gain and be | |
44 | consecutive channels counting upwards in channel number (these are | |
45 | hardware limitations.) | |
46 | ||
47 | I've never tested the gain setting stuff since I only have a | |
48 | DAS-800 board with fixed gain. | |
49 | ||
50 | The cio-das802/16 does not have a fifo-empty status bit! Therefore | |
51 | only fifo-half-full transfers are possible with this card. | |
52 | */ | |
53 | /* | |
54 | ||
55 | cmd triggers supported: | |
56 | start_src: TRIG_NOW | TRIG_EXT | |
57 | scan_begin_src: TRIG_FOLLOW | |
58 | scan_end_src: TRIG_COUNT | |
59 | convert_src: TRIG_TIMER | TRIG_EXT | |
60 | stop_src: TRIG_NONE | TRIG_COUNT | |
61 | ||
62 | ||
63 | */ | |
64 | ||
65 | #include "../comedidev.h" | |
66 | ||
67 | #include <linux/ioport.h> | |
68 | #include <linux/delay.h> | |
69 | ||
70 | #include "8253.h" | |
71 | #include "comedi_fc.h" | |
72 | ||
73 | #define DAS800_SIZE 8 | |
74 | #define TIMER_BASE 1000 | |
75 | #define N_CHAN_AI 8 // number of analog input channels | |
76 | ||
77 | /* Registers for the das800 */ | |
78 | ||
79 | #define DAS800_LSB 0 | |
80 | #define FIFO_EMPTY 0x1 | |
81 | #define FIFO_OVF 0x2 | |
82 | #define DAS800_MSB 1 | |
83 | #define DAS800_CONTROL1 2 | |
84 | #define CONTROL1_INTE 0x8 | |
85 | #define DAS800_CONV_CONTROL 2 | |
86 | #define ITE 0x1 | |
87 | #define CASC 0x2 | |
88 | #define DTEN 0x4 | |
89 | #define IEOC 0x8 | |
90 | #define EACS 0x10 | |
91 | #define CONV_HCEN 0x80 | |
92 | #define DAS800_SCAN_LIMITS 2 | |
93 | #define DAS800_STATUS 2 | |
94 | #define IRQ 0x8 | |
95 | #define BUSY 0x80 | |
96 | #define DAS800_GAIN 3 | |
97 | #define CIO_FFOV 0x8 // fifo overflow for cio-das802/16 | |
98 | #define CIO_ENHF 0x90 // interrupt fifo half full for cio-das802/16 | |
99 | #define CONTROL1 0x80 | |
100 | #define CONV_CONTROL 0xa0 | |
101 | #define SCAN_LIMITS 0xc0 | |
102 | #define ID 0xe0 | |
103 | #define DAS800_8254 4 | |
104 | #define DAS800_STATUS2 7 | |
105 | #define STATUS2_HCEN 0x80 | |
106 | #define STATUS2_INTE 0X20 | |
107 | #define DAS800_ID 7 | |
108 | ||
febc2ed6 | 109 | struct das800_board { |
3726e56b FMH |
110 | const char *name; |
111 | int ai_speed; | |
9ced1de6 | 112 | const struct comedi_lrange *ai_range; |
3726e56b | 113 | int resolution; |
febc2ed6 | 114 | }; |
3726e56b FMH |
115 | |
116 | //analog input ranges | |
9ced1de6 | 117 | static const struct comedi_lrange range_das800_ai = { |
3726e56b FMH |
118 | 1, |
119 | { | |
120 | RANGE(-5, 5), | |
121 | } | |
122 | }; | |
123 | ||
9ced1de6 | 124 | static const struct comedi_lrange range_das801_ai = { |
3726e56b FMH |
125 | 9, |
126 | { | |
127 | RANGE(-5, 5), | |
128 | RANGE(-10, 10), | |
129 | RANGE(0, 10), | |
130 | RANGE(-0.5, 0.5), | |
131 | RANGE(0, 1), | |
132 | RANGE(-0.05, 0.05), | |
133 | RANGE(0, 0.1), | |
134 | RANGE(-0.01, 0.01), | |
135 | RANGE(0, 0.02), | |
136 | } | |
137 | }; | |
138 | ||
9ced1de6 | 139 | static const struct comedi_lrange range_cio_das801_ai = { |
3726e56b FMH |
140 | 9, |
141 | { | |
142 | RANGE(-5, 5), | |
143 | RANGE(-10, 10), | |
144 | RANGE(0, 10), | |
145 | RANGE(-0.5, 0.5), | |
146 | RANGE(0, 1), | |
147 | RANGE(-0.05, 0.05), | |
148 | RANGE(0, 0.1), | |
149 | RANGE(-0.005, 0.005), | |
150 | RANGE(0, 0.01), | |
151 | } | |
152 | }; | |
153 | ||
9ced1de6 | 154 | static const struct comedi_lrange range_das802_ai = { |
3726e56b FMH |
155 | 9, |
156 | { | |
157 | RANGE(-5, 5), | |
158 | RANGE(-10, 10), | |
159 | RANGE(0, 10), | |
160 | RANGE(-2.5, 2.5), | |
161 | RANGE(0, 5), | |
162 | RANGE(-1.25, 1.25), | |
163 | RANGE(0, 2.5), | |
164 | RANGE(-0.625, 0.625), | |
165 | RANGE(0, 1.25), | |
166 | } | |
167 | }; | |
168 | ||
9ced1de6 | 169 | static const struct comedi_lrange range_das80216_ai = { |
3726e56b FMH |
170 | 8, |
171 | { | |
172 | RANGE(-10, 10), | |
173 | RANGE(0, 10), | |
174 | RANGE(-5, 5), | |
175 | RANGE(0, 5), | |
176 | RANGE(-2.5, 2.5), | |
177 | RANGE(0, 2.5), | |
178 | RANGE(-1.25, 1.25), | |
179 | RANGE(0, 1.25), | |
180 | } | |
181 | }; | |
182 | ||
183 | enum { das800, ciodas800, das801, ciodas801, das802, ciodas802, ciodas80216 }; | |
184 | ||
febc2ed6 | 185 | static const struct das800_board das800_boards[] = { |
3726e56b FMH |
186 | { |
187 | name: "das-800", | |
188 | ai_speed:25000, | |
189 | ai_range:&range_das800_ai, | |
190 | resolution:12, | |
191 | }, | |
192 | { | |
193 | name: "cio-das800", | |
194 | ai_speed:20000, | |
195 | ai_range:&range_das800_ai, | |
196 | resolution:12, | |
197 | }, | |
198 | { | |
199 | name: "das-801", | |
200 | ai_speed:25000, | |
201 | ai_range:&range_das801_ai, | |
202 | resolution:12, | |
203 | }, | |
204 | { | |
205 | name: "cio-das801", | |
206 | ai_speed:20000, | |
207 | ai_range:&range_cio_das801_ai, | |
208 | resolution:12, | |
209 | }, | |
210 | { | |
211 | name: "das-802", | |
212 | ai_speed:25000, | |
213 | ai_range:&range_das802_ai, | |
214 | resolution:12, | |
215 | }, | |
216 | { | |
217 | name: "cio-das802", | |
218 | ai_speed:20000, | |
219 | ai_range:&range_das802_ai, | |
220 | resolution:12, | |
221 | }, | |
222 | { | |
223 | name: "cio-das802/16", | |
224 | ai_speed:10000, | |
225 | ai_range:&range_das80216_ai, | |
226 | resolution:16, | |
227 | }, | |
228 | }; | |
229 | ||
230 | /* | |
231 | * Useful for shorthand access to the particular board structure | |
232 | */ | |
febc2ed6 | 233 | #define thisboard ((const struct das800_board *)dev->board_ptr) |
3726e56b | 234 | |
938f185d | 235 | struct das800_private { |
3726e56b FMH |
236 | volatile unsigned int count; /* number of data points left to be taken */ |
237 | volatile int forever; /* flag indicating whether we should take data forever */ | |
238 | unsigned int divisor1; /* value to load into board's counter 1 for timed conversions */ | |
239 | unsigned int divisor2; /* value to load into board's counter 2 for timed conversions */ | |
240 | volatile int do_bits; /* digital output bits */ | |
938f185d | 241 | }; |
3726e56b | 242 | |
938f185d | 243 | #define devpriv ((struct das800_private *)dev->private) |
3726e56b | 244 | |
0707bb04 | 245 | static int das800_attach(struct comedi_device * dev, struct comedi_devconfig * it); |
71b5f4f1 | 246 | static int das800_detach(struct comedi_device * dev); |
34c43922 | 247 | static int das800_cancel(struct comedi_device * dev, struct comedi_subdevice * s); |
3726e56b | 248 | |
139dfbdf | 249 | static struct comedi_driver driver_das800 = { |
3726e56b FMH |
250 | driver_name:"das800", |
251 | module:THIS_MODULE, | |
252 | attach:das800_attach, | |
253 | detach:das800_detach, | |
febc2ed6 | 254 | num_names:sizeof(das800_boards) / sizeof(struct das800_board), |
3726e56b | 255 | board_name:&das800_boards[0].name, |
febc2ed6 | 256 | offset:sizeof(struct das800_board), |
3726e56b FMH |
257 | }; |
258 | ||
70265d24 | 259 | static irqreturn_t das800_interrupt(int irq, void *d); |
71b5f4f1 BP |
260 | static void enable_das800(struct comedi_device * dev); |
261 | static void disable_das800(struct comedi_device * dev); | |
34c43922 | 262 | static int das800_ai_do_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s, |
ea6d0d4c | 263 | struct comedi_cmd * cmd); |
34c43922 BP |
264 | static int das800_ai_do_cmd(struct comedi_device * dev, struct comedi_subdevice * s); |
265 | static int das800_ai_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, | |
90035c08 | 266 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 267 | static int das800_di_rbits(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 268 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 269 | static int das800_do_wbits(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 270 | struct comedi_insn * insn, unsigned int * data); |
71b5f4f1 BP |
271 | static int das800_probe(struct comedi_device * dev); |
272 | static int das800_set_frequency(struct comedi_device * dev); | |
3726e56b FMH |
273 | |
274 | /* checks and probes das-800 series board type */ | |
71b5f4f1 | 275 | static int das800_probe(struct comedi_device * dev) |
3726e56b FMH |
276 | { |
277 | int id_bits; | |
278 | unsigned long irq_flags; | |
279 | int board; | |
280 | ||
281 | // 'comedi spin lock irqsave' disables even rt interrupts, we use them to protect indirect addressing | |
282 | comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); | |
283 | outb(ID, dev->iobase + DAS800_GAIN); /* select base address + 7 to be ID register */ | |
284 | id_bits = inb(dev->iobase + DAS800_ID) & 0x3; /* get id bits */ | |
285 | comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); | |
286 | ||
287 | board = thisboard - das800_boards; | |
288 | ||
289 | switch (id_bits) { | |
290 | case 0x0: | |
291 | if (board == das800) { | |
292 | printk(" Board model: DAS-800\n"); | |
293 | return board; | |
294 | } | |
295 | if (board == ciodas800) { | |
296 | printk(" Board model: CIO-DAS800\n"); | |
297 | return board; | |
298 | } | |
299 | printk(" Board model (probed): DAS-800\n"); | |
300 | return das800; | |
301 | break; | |
302 | case 0x2: | |
303 | if (board == das801) { | |
304 | printk(" Board model: DAS-801\n"); | |
305 | return board; | |
306 | } | |
307 | if (board == ciodas801) { | |
308 | printk(" Board model: CIO-DAS801\n"); | |
309 | return board; | |
310 | } | |
311 | printk(" Board model (probed): DAS-801\n"); | |
312 | return das801; | |
313 | break; | |
314 | case 0x3: | |
315 | if (board == das802) { | |
316 | printk(" Board model: DAS-802\n"); | |
317 | return board; | |
318 | } | |
319 | if (board == ciodas802) { | |
320 | printk(" Board model: CIO-DAS802\n"); | |
321 | return board; | |
322 | } | |
323 | if (board == ciodas80216) { | |
324 | printk(" Board model: CIO-DAS802/16\n"); | |
325 | return board; | |
326 | } | |
327 | printk(" Board model (probed): DAS-802\n"); | |
328 | return das802; | |
329 | break; | |
330 | default: | |
331 | printk(" Board model: probe returned 0x%x (unknown)\n", | |
332 | id_bits); | |
333 | return board; | |
334 | break; | |
335 | } | |
336 | return -1; | |
337 | } | |
338 | ||
339 | /* | |
340 | * A convenient macro that defines init_module() and cleanup_module(), | |
341 | * as necessary. | |
342 | */ | |
343 | COMEDI_INITCLEANUP(driver_das800); | |
344 | ||
345 | /* interrupt service routine */ | |
70265d24 | 346 | static irqreturn_t das800_interrupt(int irq, void *d) |
3726e56b FMH |
347 | { |
348 | short i; /* loop index */ | |
790c5541 | 349 | short dataPoint = 0; |
71b5f4f1 | 350 | struct comedi_device *dev = d; |
34c43922 | 351 | struct comedi_subdevice *s = dev->read_subdev; /* analog input subdevice */ |
d163679c | 352 | struct comedi_async *async; |
3726e56b FMH |
353 | int status; |
354 | unsigned long irq_flags; | |
355 | static const int max_loops = 128; // half-fifo size for cio-das802/16 | |
356 | // flags | |
357 | int fifo_empty = 0; | |
358 | int fifo_overflow = 0; | |
359 | ||
360 | status = inb(dev->iobase + DAS800_STATUS); | |
361 | /* if interrupt was not generated by board or driver not attached, quit */ | |
362 | if (!(status & IRQ)) | |
363 | return IRQ_NONE; | |
364 | if (!(dev->attached)) | |
365 | return IRQ_HANDLED; | |
366 | ||
367 | /* wait until here to initialize async, since we will get null dereference | |
368 | * if interrupt occurs before driver is fully attached! | |
369 | */ | |
370 | async = s->async; | |
371 | ||
372 | // if hardware conversions are not enabled, then quit | |
373 | comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); | |
374 | outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select base address + 7 to be STATUS2 register */ | |
375 | status = inb(dev->iobase + DAS800_STATUS2) & STATUS2_HCEN; | |
376 | /* don't release spinlock yet since we want to make sure noone else disables hardware conversions */ | |
377 | if (status == 0) { | |
378 | comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); | |
379 | return IRQ_HANDLED; | |
380 | } | |
381 | ||
382 | /* loop while card's fifo is not empty (and limit to half fifo for cio-das802/16) */ | |
383 | for (i = 0; i < max_loops; i++) { | |
384 | /* read 16 bits from dev->iobase and dev->iobase + 1 */ | |
385 | dataPoint = inb(dev->iobase + DAS800_LSB); | |
386 | dataPoint += inb(dev->iobase + DAS800_MSB) << 8; | |
387 | if (thisboard->resolution == 12) { | |
388 | fifo_empty = dataPoint & FIFO_EMPTY; | |
389 | fifo_overflow = dataPoint & FIFO_OVF; | |
390 | if (fifo_overflow) | |
391 | break; | |
392 | } else { | |
393 | fifo_empty = 0; // cio-das802/16 has no fifo empty status bit | |
394 | } | |
395 | if (fifo_empty) { | |
396 | break; | |
397 | } | |
398 | /* strip off extraneous bits for 12 bit cards */ | |
399 | if (thisboard->resolution == 12) | |
400 | dataPoint = (dataPoint >> 4) & 0xfff; | |
401 | /* if there are more data points to collect */ | |
402 | if (devpriv->count > 0 || devpriv->forever == 1) { | |
403 | /* write data point to buffer */ | |
404 | cfc_write_to_buffer(s, dataPoint); | |
405 | if (devpriv->count > 0) | |
406 | devpriv->count--; | |
407 | } | |
408 | } | |
409 | async->events |= COMEDI_CB_BLOCK; | |
410 | /* check for fifo overflow */ | |
411 | if (thisboard->resolution == 12) { | |
412 | fifo_overflow = dataPoint & FIFO_OVF; | |
413 | // else cio-das802/16 | |
414 | } else { | |
415 | fifo_overflow = inb(dev->iobase + DAS800_GAIN) & CIO_FFOV; | |
416 | } | |
417 | if (fifo_overflow) { | |
418 | comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); | |
419 | comedi_error(dev, "DAS800 FIFO overflow"); | |
420 | das800_cancel(dev, dev->subdevices + 0); | |
421 | async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; | |
422 | comedi_event(dev, s); | |
423 | async->events = 0; | |
424 | return IRQ_HANDLED; | |
425 | } | |
426 | if (devpriv->count > 0 || devpriv->forever == 1) { | |
427 | /* Re-enable card's interrupt. | |
428 | * We already have spinlock, so indirect addressing is safe */ | |
429 | outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ | |
430 | outb(CONTROL1_INTE | devpriv->do_bits, | |
431 | dev->iobase + DAS800_CONTROL1); | |
432 | comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); | |
433 | /* otherwise, stop taking data */ | |
434 | } else { | |
435 | comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); | |
436 | disable_das800(dev); /* diable hardware triggered conversions */ | |
437 | async->events |= COMEDI_CB_EOA; | |
438 | } | |
439 | comedi_event(dev, s); | |
440 | async->events = 0; | |
441 | return IRQ_HANDLED; | |
442 | } | |
443 | ||
0707bb04 | 444 | static int das800_attach(struct comedi_device * dev, struct comedi_devconfig * it) |
3726e56b | 445 | { |
34c43922 | 446 | struct comedi_subdevice *s; |
3726e56b FMH |
447 | unsigned long iobase = it->options[0]; |
448 | unsigned int irq = it->options[1]; | |
449 | unsigned long irq_flags; | |
450 | int board; | |
451 | ||
452 | printk("comedi%d: das800: io 0x%lx", dev->minor, iobase); | |
453 | if (irq) { | |
454 | printk(", irq %u", irq); | |
455 | } | |
456 | printk("\n"); | |
457 | ||
458 | /* allocate and initialize dev->private */ | |
938f185d | 459 | if (alloc_private(dev, sizeof(struct das800_private)) < 0) |
3726e56b FMH |
460 | return -ENOMEM; |
461 | ||
462 | if (iobase == 0) { | |
463 | printk("io base address required for das800\n"); | |
464 | return -EINVAL; | |
465 | } | |
466 | ||
467 | /* check if io addresses are available */ | |
468 | if (!request_region(iobase, DAS800_SIZE, "das800")) { | |
469 | printk("I/O port conflict\n"); | |
470 | return -EIO; | |
471 | } | |
472 | dev->iobase = iobase; | |
473 | ||
474 | board = das800_probe(dev); | |
475 | if (board < 0) { | |
476 | printk("unable to determine board type\n"); | |
477 | return -ENODEV; | |
478 | } | |
479 | dev->board_ptr = das800_boards + board; | |
480 | ||
481 | /* grab our IRQ */ | |
482 | if (irq == 1 || irq > 7) { | |
483 | printk("irq out of range\n"); | |
484 | return -EINVAL; | |
485 | } | |
486 | if (irq) { | |
487 | if (comedi_request_irq(irq, das800_interrupt, 0, "das800", dev)) { | |
488 | printk("unable to allocate irq %u\n", irq); | |
489 | return -EINVAL; | |
490 | } | |
491 | } | |
492 | dev->irq = irq; | |
493 | ||
494 | dev->board_name = thisboard->name; | |
495 | ||
496 | if (alloc_subdevices(dev, 3) < 0) | |
497 | return -ENOMEM; | |
498 | ||
499 | /* analog input subdevice */ | |
500 | s = dev->subdevices + 0; | |
501 | dev->read_subdev = s; | |
502 | s->type = COMEDI_SUBD_AI; | |
503 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; | |
504 | s->n_chan = 8; | |
505 | s->len_chanlist = 8; | |
506 | s->maxdata = (1 << thisboard->resolution) - 1; | |
507 | s->range_table = thisboard->ai_range; | |
508 | s->do_cmd = das800_ai_do_cmd; | |
509 | s->do_cmdtest = das800_ai_do_cmdtest; | |
510 | s->insn_read = das800_ai_rinsn; | |
511 | s->cancel = das800_cancel; | |
512 | ||
513 | /* di */ | |
514 | s = dev->subdevices + 1; | |
515 | s->type = COMEDI_SUBD_DI; | |
516 | s->subdev_flags = SDF_READABLE; | |
517 | s->n_chan = 3; | |
518 | s->maxdata = 1; | |
519 | s->range_table = &range_digital; | |
520 | s->insn_bits = das800_di_rbits; | |
521 | ||
522 | /* do */ | |
523 | s = dev->subdevices + 2; | |
524 | s->type = COMEDI_SUBD_DO; | |
525 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE; | |
526 | s->n_chan = 4; | |
527 | s->maxdata = 1; | |
528 | s->range_table = &range_digital; | |
529 | s->insn_bits = das800_do_wbits; | |
530 | ||
531 | disable_das800(dev); | |
532 | ||
533 | /* initialize digital out channels */ | |
534 | comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); | |
535 | outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ | |
536 | outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); | |
537 | comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); | |
538 | ||
539 | return 0; | |
540 | }; | |
541 | ||
71b5f4f1 | 542 | static int das800_detach(struct comedi_device * dev) |
3726e56b FMH |
543 | { |
544 | printk("comedi%d: das800: remove\n", dev->minor); | |
545 | ||
546 | /* only free stuff if it has been allocated by _attach */ | |
547 | if (dev->iobase) | |
548 | release_region(dev->iobase, DAS800_SIZE); | |
549 | if (dev->irq) | |
550 | comedi_free_irq(dev->irq, dev); | |
551 | return 0; | |
552 | }; | |
553 | ||
34c43922 | 554 | static int das800_cancel(struct comedi_device * dev, struct comedi_subdevice * s) |
3726e56b FMH |
555 | { |
556 | devpriv->forever = 0; | |
557 | devpriv->count = 0; | |
558 | disable_das800(dev); | |
559 | return 0; | |
560 | } | |
561 | ||
562 | /* enable_das800 makes the card start taking hardware triggered conversions */ | |
71b5f4f1 | 563 | static void enable_das800(struct comedi_device * dev) |
3726e56b FMH |
564 | { |
565 | unsigned long irq_flags; | |
566 | comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); | |
567 | // enable fifo-half full interrupts for cio-das802/16 | |
568 | if (thisboard->resolution == 16) | |
569 | outb(CIO_ENHF, dev->iobase + DAS800_GAIN); | |
570 | outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */ | |
571 | outb(CONV_HCEN, dev->iobase + DAS800_CONV_CONTROL); /* enable hardware triggering */ | |
572 | outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ | |
573 | outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); /* enable card's interrupt */ | |
574 | comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); | |
575 | } | |
576 | ||
577 | /* disable_das800 stops hardware triggered conversions */ | |
71b5f4f1 | 578 | static void disable_das800(struct comedi_device * dev) |
3726e56b FMH |
579 | { |
580 | unsigned long irq_flags; | |
581 | comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); | |
582 | outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */ | |
583 | outb(0x0, dev->iobase + DAS800_CONV_CONTROL); /* disable hardware triggering of conversions */ | |
584 | comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); | |
585 | } | |
586 | ||
34c43922 | 587 | static int das800_ai_do_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s, |
ea6d0d4c | 588 | struct comedi_cmd * cmd) |
3726e56b FMH |
589 | { |
590 | int err = 0; | |
591 | int tmp; | |
592 | int gain, startChan; | |
593 | int i; | |
594 | ||
595 | /* step 1: make sure trigger sources are trivially valid */ | |
596 | ||
597 | tmp = cmd->start_src; | |
598 | cmd->start_src &= TRIG_NOW | TRIG_EXT; | |
599 | if (!cmd->start_src || tmp != cmd->start_src) | |
600 | err++; | |
601 | ||
602 | tmp = cmd->scan_begin_src; | |
603 | cmd->scan_begin_src &= TRIG_FOLLOW; | |
604 | if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) | |
605 | err++; | |
606 | ||
607 | tmp = cmd->convert_src; | |
608 | cmd->convert_src &= TRIG_TIMER | TRIG_EXT; | |
609 | if (!cmd->convert_src || tmp != cmd->convert_src) | |
610 | err++; | |
611 | ||
612 | tmp = cmd->scan_end_src; | |
613 | cmd->scan_end_src &= TRIG_COUNT; | |
614 | if (!cmd->scan_end_src || tmp != cmd->scan_end_src) | |
615 | err++; | |
616 | ||
617 | tmp = cmd->stop_src; | |
618 | cmd->stop_src &= TRIG_COUNT | TRIG_NONE; | |
619 | if (!cmd->stop_src || tmp != cmd->stop_src) | |
620 | err++; | |
621 | ||
622 | if (err) | |
623 | return 1; | |
624 | ||
625 | /* step 2: make sure trigger sources are unique and mutually compatible */ | |
626 | ||
627 | if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) | |
628 | err++; | |
629 | if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) | |
630 | err++; | |
631 | if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) | |
632 | err++; | |
633 | ||
634 | if (err) | |
635 | return 2; | |
636 | ||
637 | /* step 3: make sure arguments are trivially compatible */ | |
638 | ||
639 | if (cmd->start_arg != 0) { | |
640 | cmd->start_arg = 0; | |
641 | err++; | |
642 | } | |
643 | if (cmd->convert_src == TRIG_TIMER) { | |
644 | if (cmd->convert_arg < thisboard->ai_speed) { | |
645 | cmd->convert_arg = thisboard->ai_speed; | |
646 | err++; | |
647 | } | |
648 | } | |
649 | if (!cmd->chanlist_len) { | |
650 | cmd->chanlist_len = 1; | |
651 | err++; | |
652 | } | |
653 | if (cmd->scan_end_arg != cmd->chanlist_len) { | |
654 | cmd->scan_end_arg = cmd->chanlist_len; | |
655 | err++; | |
656 | } | |
657 | if (cmd->stop_src == TRIG_COUNT) { | |
658 | if (!cmd->stop_arg) { | |
659 | cmd->stop_arg = 1; | |
660 | err++; | |
661 | } | |
662 | } else { /* TRIG_NONE */ | |
663 | if (cmd->stop_arg != 0) { | |
664 | cmd->stop_arg = 0; | |
665 | err++; | |
666 | } | |
667 | } | |
668 | ||
669 | if (err) | |
670 | return 3; | |
671 | ||
672 | /* step 4: fix up any arguments */ | |
673 | ||
674 | if (cmd->convert_src == TRIG_TIMER) { | |
675 | tmp = cmd->convert_arg; | |
676 | /* calculate counter values that give desired timing */ | |
677 | i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1), | |
678 | &(devpriv->divisor2), &(cmd->convert_arg), | |
679 | cmd->flags & TRIG_ROUND_MASK); | |
680 | if (tmp != cmd->convert_arg) | |
681 | err++; | |
682 | } | |
683 | ||
684 | if (err) | |
685 | return 4; | |
686 | ||
687 | // check channel/gain list against card's limitations | |
688 | if (cmd->chanlist) { | |
689 | gain = CR_RANGE(cmd->chanlist[0]); | |
690 | startChan = CR_CHAN(cmd->chanlist[0]); | |
691 | for (i = 1; i < cmd->chanlist_len; i++) { | |
692 | if (CR_CHAN(cmd->chanlist[i]) != | |
693 | (startChan + i) % N_CHAN_AI) { | |
694 | comedi_error(dev, | |
695 | "entries in chanlist must be consecutive channels, counting upwards\n"); | |
696 | err++; | |
697 | } | |
698 | if (CR_RANGE(cmd->chanlist[i]) != gain) { | |
699 | comedi_error(dev, | |
700 | "entries in chanlist must all have the same gain\n"); | |
701 | err++; | |
702 | } | |
703 | } | |
704 | } | |
705 | ||
706 | if (err) | |
707 | return 5; | |
708 | ||
709 | return 0; | |
710 | } | |
711 | ||
34c43922 | 712 | static int das800_ai_do_cmd(struct comedi_device * dev, struct comedi_subdevice * s) |
3726e56b FMH |
713 | { |
714 | int startChan, endChan, scan, gain; | |
715 | int conv_bits; | |
716 | unsigned long irq_flags; | |
d163679c | 717 | struct comedi_async *async = s->async; |
3726e56b FMH |
718 | |
719 | if (!dev->irq) { | |
720 | comedi_error(dev, | |
721 | "no irq assigned for das-800, cannot do hardware conversions"); | |
722 | return -1; | |
723 | } | |
724 | ||
725 | disable_das800(dev); | |
726 | ||
727 | /* set channel scan limits */ | |
728 | startChan = CR_CHAN(async->cmd.chanlist[0]); | |
729 | endChan = (startChan + async->cmd.chanlist_len - 1) % 8; | |
730 | scan = (endChan << 3) | startChan; | |
731 | ||
732 | comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); | |
733 | outb(SCAN_LIMITS, dev->iobase + DAS800_GAIN); /* select base address + 2 to be scan limits register */ | |
734 | outb(scan, dev->iobase + DAS800_SCAN_LIMITS); /* set scan limits */ | |
735 | comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); | |
736 | ||
737 | /* set gain */ | |
738 | gain = CR_RANGE(async->cmd.chanlist[0]); | |
739 | if (thisboard->resolution == 12 && gain > 0) | |
740 | gain += 0x7; | |
741 | gain &= 0xf; | |
742 | outb(gain, dev->iobase + DAS800_GAIN); | |
743 | ||
744 | switch (async->cmd.stop_src) { | |
745 | case TRIG_COUNT: | |
746 | devpriv->count = async->cmd.stop_arg * async->cmd.chanlist_len; | |
747 | devpriv->forever = 0; | |
748 | break; | |
749 | case TRIG_NONE: | |
750 | devpriv->forever = 1; | |
751 | devpriv->count = 0; | |
752 | break; | |
753 | default: | |
754 | break; | |
755 | } | |
756 | ||
757 | /* enable auto channel scan, send interrupts on end of conversion | |
758 | * and set clock source to internal or external | |
759 | */ | |
760 | conv_bits = 0; | |
761 | conv_bits |= EACS | IEOC; | |
762 | if (async->cmd.start_src == TRIG_EXT) | |
763 | conv_bits |= DTEN; | |
764 | switch (async->cmd.convert_src) { | |
765 | case TRIG_TIMER: | |
766 | conv_bits |= CASC | ITE; | |
767 | /* set conversion frequency */ | |
768 | i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1), | |
769 | &(devpriv->divisor2), &(async->cmd.convert_arg), | |
770 | async->cmd.flags & TRIG_ROUND_MASK); | |
771 | if (das800_set_frequency(dev) < 0) { | |
772 | comedi_error(dev, "Error setting up counters"); | |
773 | return -1; | |
774 | } | |
775 | break; | |
776 | case TRIG_EXT: | |
777 | break; | |
778 | default: | |
779 | break; | |
780 | } | |
781 | ||
782 | comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); | |
783 | outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */ | |
784 | outb(conv_bits, dev->iobase + DAS800_CONV_CONTROL); | |
785 | comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); | |
786 | async->events = 0; | |
787 | enable_das800(dev); | |
788 | return 0; | |
789 | } | |
790 | ||
34c43922 | 791 | static int das800_ai_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 792 | struct comedi_insn * insn, unsigned int * data) |
3726e56b FMH |
793 | { |
794 | int i, n; | |
795 | int chan; | |
796 | int range; | |
797 | int lsb, msb; | |
798 | int timeout = 1000; | |
799 | unsigned long irq_flags; | |
800 | ||
801 | disable_das800(dev); /* disable hardware conversions (enables software conversions) */ | |
802 | ||
803 | /* set multiplexer */ | |
804 | chan = CR_CHAN(insn->chanspec); | |
805 | ||
806 | comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); | |
807 | outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ | |
808 | outb(chan | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); | |
809 | comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); | |
810 | ||
811 | /* set gain / range */ | |
812 | range = CR_RANGE(insn->chanspec); | |
813 | if (thisboard->resolution == 12 && range) | |
814 | range += 0x7; | |
815 | range &= 0xf; | |
816 | outb(range, dev->iobase + DAS800_GAIN); | |
817 | ||
818 | comedi_udelay(5); | |
819 | ||
820 | for (n = 0; n < insn->n; n++) { | |
821 | /* trigger conversion */ | |
822 | outb_p(0, dev->iobase + DAS800_MSB); | |
823 | ||
824 | for (i = 0; i < timeout; i++) { | |
825 | if (!(inb(dev->iobase + DAS800_STATUS) & BUSY)) | |
826 | break; | |
827 | } | |
828 | if (i == timeout) { | |
829 | comedi_error(dev, "timeout"); | |
830 | return -ETIME; | |
831 | } | |
832 | lsb = inb(dev->iobase + DAS800_LSB); | |
833 | msb = inb(dev->iobase + DAS800_MSB); | |
834 | if (thisboard->resolution == 12) { | |
835 | data[n] = (lsb >> 4) & 0xff; | |
836 | data[n] |= (msb << 4); | |
837 | } else { | |
838 | data[n] = (msb << 8) | lsb; | |
839 | } | |
840 | } | |
841 | ||
842 | return n; | |
843 | } | |
844 | ||
34c43922 | 845 | static int das800_di_rbits(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 846 | struct comedi_insn * insn, unsigned int * data) |
3726e56b | 847 | { |
790c5541 | 848 | unsigned int bits; |
3726e56b FMH |
849 | |
850 | bits = inb(dev->iobase + DAS800_STATUS) >> 4; | |
851 | bits &= 0x7; | |
852 | data[1] = bits; | |
853 | data[0] = 0; | |
854 | ||
855 | return 2; | |
856 | } | |
857 | ||
34c43922 | 858 | static int das800_do_wbits(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 859 | struct comedi_insn * insn, unsigned int * data) |
3726e56b FMH |
860 | { |
861 | int wbits; | |
862 | unsigned long irq_flags; | |
863 | ||
864 | // only set bits that have been masked | |
865 | data[0] &= 0xf; | |
866 | wbits = devpriv->do_bits >> 4; | |
867 | wbits &= ~data[0]; | |
868 | wbits |= data[0] & data[1]; | |
869 | devpriv->do_bits = wbits << 4; | |
870 | ||
871 | comedi_spin_lock_irqsave(&dev->spinlock, irq_flags); | |
872 | outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ | |
873 | outb(devpriv->do_bits | CONTROL1_INTE, dev->iobase + DAS800_CONTROL1); | |
874 | comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags); | |
875 | ||
876 | data[1] = wbits; | |
877 | ||
878 | return 2; | |
879 | } | |
880 | ||
881 | /* loads counters with divisor1, divisor2 from private structure */ | |
71b5f4f1 | 882 | static int das800_set_frequency(struct comedi_device * dev) |
3726e56b FMH |
883 | { |
884 | int err = 0; | |
885 | ||
886 | if (i8254_load(dev->iobase + DAS800_8254, 0, 1, devpriv->divisor1, 2)) | |
887 | err++; | |
888 | if (i8254_load(dev->iobase + DAS800_8254, 0, 2, devpriv->divisor2, 2)) | |
889 | err++; | |
890 | if (err) | |
891 | return -1; | |
892 | ||
893 | return 0; | |
894 | } |