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