Commit | Line | Data |
---|---|---|
9afbebe4 FMH |
1 | /* |
2 | comedi/drivers/das16m1.c | |
3 | CIO-DAS16/M1 driver | |
4 | Author: Frank Mori Hess, based on code from the das16 | |
5 | driver. | |
6 | Copyright (C) 2001 Frank Mori Hess <fmhess@users.sourceforge.net> | |
7 | ||
8 | COMEDI - Linux Control and Measurement Device Interface | |
9 | Copyright (C) 2000 David A. Schleef <ds@schleef.org> | |
10 | ||
11 | This program is free software; you can redistribute it and/or modify | |
12 | it under the terms of the GNU General Public License as published by | |
13 | the Free Software Foundation; either version 2 of the License, or | |
14 | (at your option) any later version. | |
15 | ||
16 | This program is distributed in the hope that it will be useful, | |
17 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | GNU General Public License for more details. | |
9afbebe4 FMH |
20 | */ |
21 | /* | |
22 | Driver: das16m1 | |
23 | Description: CIO-DAS16/M1 | |
24 | Author: Frank Mori Hess <fmhess@users.sourceforge.net> | |
34702518 | 25 | Devices: [Measurement Computing] CIO-DAS16/M1 (das16m1) |
9afbebe4 FMH |
26 | Status: works |
27 | ||
28 | This driver supports a single board - the CIO-DAS16/M1. | |
29 | As far as I know, there are no other boards that have | |
30 | the same register layout. Even the CIO-DAS16/M1/16 is | |
31 | significantly different. | |
32 | ||
33 | I was _barely_ able to reach the full 1 MHz capability | |
34 | of this board, using a hard real-time interrupt | |
ea6d0d4c | 35 | (set the TRIG_RT flag in your struct comedi_cmd and use |
9afbebe4 FMH |
36 | rtlinux or RTAI). The board can't do dma, so the bottleneck is |
37 | pulling the data across the ISA bus. I timed the interrupt | |
38 | handler, and it took my computer ~470 microseconds to pull 512 | |
39 | samples from the board. So at 1 Mhz sampling rate, | |
40 | expect your CPU to be spending almost all of its | |
41 | time in the interrupt handler. | |
42 | ||
43 | This board has some unusual restrictions for its channel/gain list. If the | |
44 | list has 2 or more channels in it, then two conditions must be satisfied: | |
45 | (1) - even/odd channels must appear at even/odd indices in the list | |
46 | (2) - the list must have an even number of entries. | |
47 | ||
48 | Options: | |
49 | [0] - base io address | |
50 | [1] - irq (optional, but you probably want it) | |
51 | ||
52 | irq can be omitted, although the cmd interface will not work without it. | |
53 | */ | |
54 | ||
ce157f80 | 55 | #include <linux/module.h> |
25436dc9 | 56 | #include <linux/interrupt.h> |
9afbebe4 FMH |
57 | #include "../comedidev.h" |
58 | ||
59 | #include "8255.h" | |
60 | #include "8253.h" | |
61 | #include "comedi_fc.h" | |
62 | ||
9afbebe4 FMH |
63 | #define DAS16M1_SIZE2 8 |
64 | ||
2696fb57 | 65 | #define FIFO_SIZE 1024 /* 1024 sample fifo */ |
9afbebe4 FMH |
66 | |
67 | /* | |
68 | CIO-DAS16_M1.pdf | |
69 | ||
70 | "cio-das16/m1" | |
71 | ||
72 | 0 a/d bits 0-3, mux start 12 bit | |
73 | 1 a/d bits 4-11 unused | |
74 | 2 status control | |
75 | 3 di 4 bit do 4 bit | |
76 | 4 unused clear interrupt | |
77 | 5 interrupt, pacer | |
78 | 6 channel/gain queue address | |
79 | 7 channel/gain queue data | |
80 | 89ab 8254 | |
81 | cdef 8254 | |
82 | 400 8255 | |
83 | 404-407 8254 | |
84 | ||
85 | */ | |
86 | ||
2696fb57 | 87 | #define DAS16M1_AI 0 /* 16-bit wide register */ |
9afbebe4 FMH |
88 | #define AI_CHAN(x) ((x) & 0xf) |
89 | #define DAS16M1_CS 2 | |
90 | #define EXT_TRIG_BIT 0x1 | |
91 | #define OVRUN 0x20 | |
92 | #define IRQDATA 0x80 | |
93 | #define DAS16M1_DIO 3 | |
94 | #define DAS16M1_CLEAR_INTR 4 | |
95 | #define DAS16M1_INTR_CONTROL 5 | |
96 | #define EXT_PACER 0x2 | |
97 | #define INT_PACER 0x3 | |
98 | #define PACER_MASK 0x3 | |
99 | #define INTE 0x80 | |
100 | #define DAS16M1_QUEUE_ADDR 6 | |
101 | #define DAS16M1_QUEUE_DATA 7 | |
102 | #define Q_CHAN(x) ((x) & 0x7) | |
103 | #define Q_RANGE(x) (((x) & 0xf) << 4) | |
104 | #define UNIPOLAR 0x40 | |
105 | #define DAS16M1_8254_FIRST 0x8 | |
106 | #define DAS16M1_8254_FIRST_CNTRL 0xb | |
107 | #define TOTAL_CLEAR 0x30 | |
108 | #define DAS16M1_8254_SECOND 0xc | |
109 | #define DAS16M1_82C55 0x400 | |
110 | #define DAS16M1_8254_THIRD 0x404 | |
111 | ||
0b4c89d8 HS |
112 | static const struct comedi_lrange range_das16m1 = { |
113 | 9, { | |
114 | BIP_RANGE(5), | |
115 | BIP_RANGE(2.5), | |
116 | BIP_RANGE(1.25), | |
117 | BIP_RANGE(0.625), | |
118 | UNI_RANGE(10), | |
119 | UNI_RANGE(5), | |
120 | UNI_RANGE(2.5), | |
121 | UNI_RANGE(1.25), | |
122 | BIP_RANGE(10) | |
123 | } | |
9afbebe4 FMH |
124 | }; |
125 | ||
9afbebe4 FMH |
126 | struct das16m1_private_struct { |
127 | unsigned int control_state; | |
e3ebc55f | 128 | unsigned int adc_count; /* number of samples completed */ |
9afbebe4 FMH |
129 | /* initial value in lower half of hardware conversion counter, |
130 | * needed to keep track of whether new count has been loaded into | |
131 | * counter yet (loaded by first sample conversion) */ | |
132 | u16 initial_hw_count; | |
eabbf3f4 | 133 | unsigned short ai_buffer[FIFO_SIZE]; |
2696fb57 BP |
134 | unsigned int divisor1; /* divides master clock to obtain conversion speed */ |
135 | unsigned int divisor2; /* divides master clock to obtain conversion speed */ | |
a0b4bccc | 136 | unsigned long extra_iobase; |
9afbebe4 | 137 | }; |
9afbebe4 | 138 | |
eabbf3f4 | 139 | static inline unsigned short munge_sample(unsigned short data) |
7114a280 | 140 | { |
72022184 | 141 | return (data >> 4) & 0xfff; |
7114a280 AT |
142 | } |
143 | ||
eabbf3f4 | 144 | static void munge_sample_array(unsigned short *array, unsigned int num_elements) |
7114a280 | 145 | { |
72022184 | 146 | unsigned int i; |
9afbebe4 | 147 | |
72022184 HS |
148 | for (i = 0; i < num_elements; i++) |
149 | array[i] = munge_sample(array[i]); | |
9afbebe4 FMH |
150 | } |
151 | ||
f71a2832 HS |
152 | static int das16m1_ai_check_chanlist(struct comedi_device *dev, |
153 | struct comedi_subdevice *s, | |
154 | struct comedi_cmd *cmd) | |
155 | { | |
156 | int i; | |
157 | ||
158 | if (cmd->chanlist_len == 1) | |
159 | return 0; | |
160 | ||
161 | if ((cmd->chanlist_len % 2) != 0) { | |
162 | dev_dbg(dev->class_dev, | |
163 | "chanlist must be of even length or length 1\n"); | |
164 | return -EINVAL; | |
165 | } | |
166 | ||
167 | for (i = 0; i < cmd->chanlist_len; i++) { | |
168 | unsigned int chan = CR_CHAN(cmd->chanlist[i]); | |
169 | ||
170 | if ((i % 2) != (chan % 2)) { | |
171 | dev_dbg(dev->class_dev, | |
172 | "even/odd channels must go have even/odd chanlist indices\n"); | |
173 | return -EINVAL; | |
174 | } | |
175 | } | |
176 | ||
177 | return 0; | |
178 | } | |
179 | ||
0a85b6f0 MT |
180 | static int das16m1_cmd_test(struct comedi_device *dev, |
181 | struct comedi_subdevice *s, struct comedi_cmd *cmd) | |
9afbebe4 | 182 | { |
9a1a6cf8 | 183 | struct das16m1_private_struct *devpriv = dev->private; |
e9ace43c HS |
184 | int err = 0; |
185 | unsigned int arg; | |
9afbebe4 | 186 | |
27020ffe | 187 | /* Step 1 : check if triggers are trivially valid */ |
9afbebe4 | 188 | |
27020ffe HS |
189 | err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); |
190 | err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); | |
191 | err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT); | |
192 | err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
193 | err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); | |
9afbebe4 FMH |
194 | |
195 | if (err) | |
196 | return 1; | |
197 | ||
27020ffe HS |
198 | /* Step 2a : make sure trigger sources are unique */ |
199 | ||
200 | err |= cfc_check_trigger_is_unique(cmd->start_src); | |
201 | err |= cfc_check_trigger_is_unique(cmd->convert_src); | |
202 | err |= cfc_check_trigger_is_unique(cmd->stop_src); | |
203 | ||
204 | /* Step 2b : and mutually compatible */ | |
9afbebe4 FMH |
205 | |
206 | if (err) | |
207 | return 2; | |
208 | ||
050b3b18 | 209 | /* Step 3: check if arguments are trivially valid */ |
9afbebe4 | 210 | |
050b3b18 | 211 | err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); |
9afbebe4 | 212 | |
050b3b18 HS |
213 | if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ |
214 | err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); | |
9afbebe4 | 215 | |
050b3b18 HS |
216 | if (cmd->convert_src == TRIG_TIMER) |
217 | err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 1000); | |
218 | ||
219 | err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); | |
9afbebe4 | 220 | |
1a021d63 HS |
221 | if (cmd->stop_src == TRIG_COUNT) |
222 | err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); | |
223 | else /* TRIG_NONE */ | |
050b3b18 | 224 | err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); |
9afbebe4 FMH |
225 | |
226 | if (err) | |
227 | return 3; | |
228 | ||
229 | /* step 4: fix up arguments */ | |
230 | ||
231 | if (cmd->convert_src == TRIG_TIMER) { | |
e9ace43c | 232 | arg = cmd->convert_arg; |
cb9cfd7e HS |
233 | i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ, |
234 | &devpriv->divisor1, | |
235 | &devpriv->divisor2, | |
e9ace43c HS |
236 | &arg, cmd->flags); |
237 | err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg); | |
9afbebe4 FMH |
238 | } |
239 | ||
240 | if (err) | |
241 | return 4; | |
242 | ||
f71a2832 HS |
243 | /* Step 5: check channel list if it exists */ |
244 | if (cmd->chanlist && cmd->chanlist_len > 0) | |
245 | err |= das16m1_ai_check_chanlist(dev, s, cmd); | |
9afbebe4 FMH |
246 | |
247 | if (err) | |
248 | return 5; | |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
863e07c9 | 253 | static void das16m1_set_pacer(struct comedi_device *dev) |
72022184 | 254 | { |
9a1a6cf8 | 255 | struct das16m1_private_struct *devpriv = dev->private; |
863e07c9 | 256 | unsigned long timer_base = dev->iobase + DAS16M1_8254_SECOND; |
9a1a6cf8 | 257 | |
863e07c9 HS |
258 | i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); |
259 | i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); | |
72022184 | 260 | |
863e07c9 HS |
261 | i8254_write(timer_base, 0, 1, devpriv->divisor1); |
262 | i8254_write(timer_base, 0, 2, devpriv->divisor2); | |
72022184 HS |
263 | } |
264 | ||
0a85b6f0 MT |
265 | static int das16m1_cmd_exec(struct comedi_device *dev, |
266 | struct comedi_subdevice *s) | |
9afbebe4 | 267 | { |
9a1a6cf8 | 268 | struct das16m1_private_struct *devpriv = dev->private; |
d163679c | 269 | struct comedi_async *async = s->async; |
ea6d0d4c | 270 | struct comedi_cmd *cmd = &async->cmd; |
bef74ba8 | 271 | unsigned long timer_base = dev->iobase + DAS16M1_8254_FIRST; |
9afbebe4 FMH |
272 | unsigned int byte, i; |
273 | ||
9afbebe4 FMH |
274 | /* disable interrupts and internal pacer */ |
275 | devpriv->control_state &= ~INTE & ~PACER_MASK; | |
276 | outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); | |
277 | ||
2696fb57 | 278 | /* set software count */ |
9afbebe4 FMH |
279 | devpriv->adc_count = 0; |
280 | /* Initialize lower half of hardware counter, used to determine how | |
281 | * many samples are in fifo. Value doesn't actually load into counter | |
282 | * until counter's next clock (the next a/d conversion) */ | |
bef74ba8 HS |
283 | i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); |
284 | i8254_write(timer_base, 0, 1, 0); | |
9afbebe4 FMH |
285 | /* remember current reading of counter so we know when counter has |
286 | * actually been loaded */ | |
bef74ba8 | 287 | devpriv->initial_hw_count = i8254_read(timer_base, 0, 1); |
9afbebe4 FMH |
288 | /* setup channel/gain queue */ |
289 | for (i = 0; i < cmd->chanlist_len; i++) { | |
290 | outb(i, dev->iobase + DAS16M1_QUEUE_ADDR); | |
0a85b6f0 MT |
291 | byte = |
292 | Q_CHAN(CR_CHAN(cmd->chanlist[i])) | | |
293 | Q_RANGE(CR_RANGE(cmd->chanlist[i])); | |
9afbebe4 FMH |
294 | outb(byte, dev->iobase + DAS16M1_QUEUE_DATA); |
295 | } | |
296 | ||
863e07c9 HS |
297 | /* enable interrupts and set internal pacer counter mode and counts */ |
298 | devpriv->control_state &= ~PACER_MASK; | |
299 | if (cmd->convert_src == TRIG_TIMER) { | |
300 | das16m1_set_pacer(dev); | |
301 | devpriv->control_state |= INT_PACER; | |
302 | } else { /* TRIG_EXT */ | |
303 | devpriv->control_state |= EXT_PACER; | |
304 | } | |
9afbebe4 | 305 | |
2696fb57 | 306 | /* set control & status register */ |
9afbebe4 FMH |
307 | byte = 0; |
308 | /* if we are using external start trigger (also board dislikes having | |
309 | * both start and conversion triggers external simultaneously) */ | |
4547251f | 310 | if (cmd->start_src == TRIG_EXT && cmd->convert_src != TRIG_EXT) |
9afbebe4 | 311 | byte |= EXT_TRIG_BIT; |
4547251f | 312 | |
9afbebe4 FMH |
313 | outb(byte, dev->iobase + DAS16M1_CS); |
314 | /* clear interrupt bit */ | |
315 | outb(0, dev->iobase + DAS16M1_CLEAR_INTR); | |
316 | ||
9afbebe4 FMH |
317 | devpriv->control_state |= INTE; |
318 | outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); | |
319 | ||
320 | return 0; | |
321 | } | |
322 | ||
da91b269 | 323 | static int das16m1_cancel(struct comedi_device *dev, struct comedi_subdevice *s) |
9afbebe4 | 324 | { |
9a1a6cf8 HS |
325 | struct das16m1_private_struct *devpriv = dev->private; |
326 | ||
9afbebe4 FMH |
327 | devpriv->control_state &= ~INTE & ~PACER_MASK; |
328 | outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); | |
329 | ||
330 | return 0; | |
331 | } | |
332 | ||
ab984177 HS |
333 | static int das16m1_ai_eoc(struct comedi_device *dev, |
334 | struct comedi_subdevice *s, | |
335 | struct comedi_insn *insn, | |
336 | unsigned long context) | |
337 | { | |
338 | unsigned int status; | |
339 | ||
340 | status = inb(dev->iobase + DAS16M1_CS); | |
341 | if (status & IRQDATA) | |
342 | return 0; | |
343 | return -EBUSY; | |
344 | } | |
345 | ||
0a85b6f0 MT |
346 | static int das16m1_ai_rinsn(struct comedi_device *dev, |
347 | struct comedi_subdevice *s, | |
348 | struct comedi_insn *insn, unsigned int *data) | |
9afbebe4 | 349 | { |
9a1a6cf8 | 350 | struct das16m1_private_struct *devpriv = dev->private; |
ab984177 HS |
351 | int ret; |
352 | int n; | |
9afbebe4 | 353 | int byte; |
9afbebe4 FMH |
354 | |
355 | /* disable interrupts and internal pacer */ | |
356 | devpriv->control_state &= ~INTE & ~PACER_MASK; | |
357 | outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); | |
358 | ||
359 | /* setup channel/gain queue */ | |
360 | outb(0, dev->iobase + DAS16M1_QUEUE_ADDR); | |
0a85b6f0 MT |
361 | byte = |
362 | Q_CHAN(CR_CHAN(insn->chanspec)) | Q_RANGE(CR_RANGE(insn->chanspec)); | |
9afbebe4 FMH |
363 | outb(byte, dev->iobase + DAS16M1_QUEUE_DATA); |
364 | ||
365 | for (n = 0; n < insn->n; n++) { | |
366 | /* clear IRQDATA bit */ | |
367 | outb(0, dev->iobase + DAS16M1_CLEAR_INTR); | |
368 | /* trigger conversion */ | |
369 | outb(0, dev->iobase); | |
370 | ||
ab984177 | 371 | ret = comedi_timeout(dev, s, insn, das16m1_ai_eoc, 0); |
22ca19d9 | 372 | if (ret) |
ab984177 | 373 | return ret; |
ab984177 | 374 | |
9afbebe4 FMH |
375 | data[n] = munge_sample(inw(dev->iobase)); |
376 | } | |
377 | ||
378 | return n; | |
379 | } | |
380 | ||
0a85b6f0 MT |
381 | static int das16m1_di_rbits(struct comedi_device *dev, |
382 | struct comedi_subdevice *s, | |
383 | struct comedi_insn *insn, unsigned int *data) | |
9afbebe4 | 384 | { |
790c5541 | 385 | unsigned int bits; |
9afbebe4 FMH |
386 | |
387 | bits = inb(dev->iobase + DAS16M1_DIO) & 0xf; | |
388 | data[1] = bits; | |
389 | data[0] = 0; | |
390 | ||
a2714e3e | 391 | return insn->n; |
9afbebe4 FMH |
392 | } |
393 | ||
0a85b6f0 MT |
394 | static int das16m1_do_wbits(struct comedi_device *dev, |
395 | struct comedi_subdevice *s, | |
e49242a4 HS |
396 | struct comedi_insn *insn, |
397 | unsigned int *data) | |
9afbebe4 | 398 | { |
e49242a4 HS |
399 | if (comedi_dio_update_state(s, data)) |
400 | outb(s->state, dev->iobase + DAS16M1_DIO); | |
9afbebe4 | 401 | |
e49242a4 | 402 | data[1] = s->state; |
9afbebe4 | 403 | |
a2714e3e | 404 | return insn->n; |
9afbebe4 FMH |
405 | } |
406 | ||
da91b269 | 407 | static void das16m1_handler(struct comedi_device *dev, unsigned int status) |
9afbebe4 | 408 | { |
9a1a6cf8 | 409 | struct das16m1_private_struct *devpriv = dev->private; |
34c43922 | 410 | struct comedi_subdevice *s; |
d163679c | 411 | struct comedi_async *async; |
ea6d0d4c | 412 | struct comedi_cmd *cmd; |
9afbebe4 FMH |
413 | u16 num_samples; |
414 | u16 hw_counter; | |
415 | ||
416 | s = dev->read_subdev; | |
417 | async = s->async; | |
9afbebe4 FMH |
418 | cmd = &async->cmd; |
419 | ||
2696fb57 | 420 | /* figure out how many samples are in fifo */ |
9afbebe4 FMH |
421 | hw_counter = i8254_read(dev->iobase + DAS16M1_8254_FIRST, 0, 1); |
422 | /* make sure hardware counter reading is not bogus due to initial value | |
423 | * not having been loaded yet */ | |
424 | if (devpriv->adc_count == 0 && hw_counter == devpriv->initial_hw_count) { | |
425 | num_samples = 0; | |
426 | } else { | |
427 | /* The calculation of num_samples looks odd, but it uses the following facts. | |
428 | * 16 bit hardware counter is initialized with value of zero (which really | |
429 | * means 0x1000). The counter decrements by one on each conversion | |
430 | * (when the counter decrements from zero it goes to 0xffff). num_samples | |
431 | * is a 16 bit variable, so it will roll over in a similar fashion to the | |
432 | * hardware counter. Work it out, and this is what you get. */ | |
433 | num_samples = -hw_counter - devpriv->adc_count; | |
434 | } | |
2696fb57 | 435 | /* check if we only need some of the points */ |
9afbebe4 FMH |
436 | if (cmd->stop_src == TRIG_COUNT) { |
437 | if (num_samples > cmd->stop_arg * cmd->chanlist_len) | |
438 | num_samples = cmd->stop_arg * cmd->chanlist_len; | |
439 | } | |
2696fb57 | 440 | /* make sure we dont try to get too many points if fifo has overrun */ |
9afbebe4 FMH |
441 | if (num_samples > FIFO_SIZE) |
442 | num_samples = FIFO_SIZE; | |
443 | insw(dev->iobase, devpriv->ai_buffer, num_samples); | |
444 | munge_sample_array(devpriv->ai_buffer, num_samples); | |
445 | cfc_write_array_to_buffer(s, devpriv->ai_buffer, | |
0a85b6f0 | 446 | num_samples * sizeof(short)); |
9afbebe4 FMH |
447 | devpriv->adc_count += num_samples; |
448 | ||
449 | if (cmd->stop_src == TRIG_COUNT) { | |
6278c5d8 HS |
450 | if (devpriv->adc_count >= cmd->stop_arg * cmd->chanlist_len) { |
451 | /* end of acquisition */ | |
9afbebe4 FMH |
452 | async->events |= COMEDI_CB_EOA; |
453 | } | |
454 | } | |
455 | ||
456 | /* this probably won't catch overruns since the card doesn't generate | |
457 | * overrun interrupts, but we might as well try */ | |
458 | if (status & OVRUN) { | |
9afbebe4 | 459 | async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; |
b07262b0 | 460 | dev_err(dev->class_dev, "fifo overflow\n"); |
9afbebe4 FMH |
461 | } |
462 | ||
6278c5d8 | 463 | cfc_handle_events(dev, s); |
9afbebe4 FMH |
464 | } |
465 | ||
72022184 | 466 | static int das16m1_poll(struct comedi_device *dev, struct comedi_subdevice *s) |
9afbebe4 | 467 | { |
72022184 HS |
468 | unsigned long flags; |
469 | unsigned int status; | |
9afbebe4 | 470 | |
72022184 HS |
471 | /* prevent race with interrupt handler */ |
472 | spin_lock_irqsave(&dev->spinlock, flags); | |
473 | status = inb(dev->iobase + DAS16M1_CS); | |
474 | das16m1_handler(dev, status); | |
475 | spin_unlock_irqrestore(&dev->spinlock, flags); | |
9afbebe4 | 476 | |
f4f3f7cf | 477 | return comedi_buf_n_bytes_ready(s); |
72022184 HS |
478 | } |
479 | ||
480 | static irqreturn_t das16m1_interrupt(int irq, void *d) | |
481 | { | |
482 | int status; | |
483 | struct comedi_device *dev = d; | |
484 | ||
a7401cdd | 485 | if (!dev->attached) { |
b07262b0 | 486 | dev_err(dev->class_dev, "premature interrupt\n"); |
72022184 HS |
487 | return IRQ_HANDLED; |
488 | } | |
489 | /* prevent race with comedi_poll() */ | |
490 | spin_lock(&dev->spinlock); | |
491 | ||
492 | status = inb(dev->iobase + DAS16M1_CS); | |
493 | ||
494 | if ((status & (IRQDATA | OVRUN)) == 0) { | |
b07262b0 | 495 | dev_err(dev->class_dev, "spurious interrupt\n"); |
72022184 HS |
496 | spin_unlock(&dev->spinlock); |
497 | return IRQ_NONE; | |
498 | } | |
499 | ||
500 | das16m1_handler(dev, status); | |
501 | ||
502 | /* clear interrupt */ | |
503 | outb(0, dev->iobase + DAS16M1_CLEAR_INTR); | |
504 | ||
505 | spin_unlock(&dev->spinlock); | |
506 | return IRQ_HANDLED; | |
9afbebe4 FMH |
507 | } |
508 | ||
509 | static int das16m1_irq_bits(unsigned int irq) | |
510 | { | |
9afbebe4 FMH |
511 | switch (irq) { |
512 | case 10: | |
72998850 | 513 | return 0x0; |
9afbebe4 | 514 | case 11: |
72998850 | 515 | return 0x1; |
9afbebe4 | 516 | case 12: |
72998850 | 517 | return 0x2; |
9afbebe4 | 518 | case 15: |
72998850 | 519 | return 0x3; |
9afbebe4 | 520 | case 2: |
72998850 | 521 | return 0x4; |
9afbebe4 | 522 | case 3: |
72998850 | 523 | return 0x5; |
9afbebe4 | 524 | case 5: |
72998850 | 525 | return 0x6; |
9afbebe4 | 526 | case 7: |
72998850 | 527 | return 0x7; |
9afbebe4 | 528 | default: |
72998850 | 529 | return 0x0; |
9afbebe4 | 530 | } |
9afbebe4 FMH |
531 | } |
532 | ||
533 | /* | |
534 | * Options list: | |
535 | * 0 I/O base | |
536 | * 1 IRQ | |
537 | */ | |
0a85b6f0 MT |
538 | static int das16m1_attach(struct comedi_device *dev, |
539 | struct comedi_devconfig *it) | |
9afbebe4 | 540 | { |
9a1a6cf8 | 541 | struct das16m1_private_struct *devpriv; |
34c43922 | 542 | struct comedi_subdevice *s; |
9afbebe4 | 543 | int ret; |
9afbebe4 | 544 | |
0bdab509 | 545 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
c34fa261 HS |
546 | if (!devpriv) |
547 | return -ENOMEM; | |
9afbebe4 | 548 | |
862755ec | 549 | ret = comedi_request_region(dev, it->options[0], 0x10); |
a983834e HS |
550 | if (ret) |
551 | return ret; | |
552 | /* Request an additional region for the 8255 */ | |
5950ae51 HS |
553 | ret = __comedi_request_region(dev, dev->iobase + DAS16M1_82C55, |
554 | DAS16M1_SIZE2); | |
a0b4bccc HS |
555 | if (ret) |
556 | return ret; | |
557 | devpriv->extra_iobase = dev->iobase + DAS16M1_82C55; | |
9afbebe4 | 558 | |
72998850 HS |
559 | /* only irqs 2, 3, 4, 5, 6, 7, 10, 11, 12, 14, and 15 are valid */ |
560 | if ((1 << it->options[1]) & 0xdcfc) { | |
561 | ret = request_irq(it->options[1], das16m1_interrupt, 0, | |
562 | dev->board_name, dev); | |
563 | if (ret == 0) | |
564 | dev->irq = it->options[1]; | |
9afbebe4 FMH |
565 | } |
566 | ||
2f0b9d08 | 567 | ret = comedi_alloc_subdevices(dev, 4); |
8b6c5694 | 568 | if (ret) |
9afbebe4 FMH |
569 | return ret; |
570 | ||
9b7beb66 | 571 | s = &dev->subdevices[0]; |
9afbebe4 FMH |
572 | /* ai */ |
573 | s->type = COMEDI_SUBD_AI; | |
72998850 | 574 | s->subdev_flags = SDF_READABLE | SDF_DIFF; |
9afbebe4 | 575 | s->n_chan = 8; |
9afbebe4 FMH |
576 | s->maxdata = (1 << 12) - 1; |
577 | s->range_table = &range_das16m1; | |
578 | s->insn_read = das16m1_ai_rinsn; | |
72998850 HS |
579 | if (dev->irq) { |
580 | dev->read_subdev = s; | |
581 | s->subdev_flags |= SDF_CMD_READ; | |
582 | s->len_chanlist = 256; | |
583 | s->do_cmdtest = das16m1_cmd_test; | |
584 | s->do_cmd = das16m1_cmd_exec; | |
585 | s->cancel = das16m1_cancel; | |
586 | s->poll = das16m1_poll; | |
587 | } | |
9afbebe4 | 588 | |
9b7beb66 | 589 | s = &dev->subdevices[1]; |
9afbebe4 FMH |
590 | /* di */ |
591 | s->type = COMEDI_SUBD_DI; | |
592 | s->subdev_flags = SDF_READABLE; | |
593 | s->n_chan = 4; | |
594 | s->maxdata = 1; | |
595 | s->range_table = &range_digital; | |
596 | s->insn_bits = das16m1_di_rbits; | |
597 | ||
9b7beb66 | 598 | s = &dev->subdevices[2]; |
9afbebe4 FMH |
599 | /* do */ |
600 | s->type = COMEDI_SUBD_DO; | |
601 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE; | |
602 | s->n_chan = 4; | |
603 | s->maxdata = 1; | |
604 | s->range_table = &range_digital; | |
605 | s->insn_bits = das16m1_do_wbits; | |
606 | ||
9b7beb66 | 607 | s = &dev->subdevices[3]; |
9afbebe4 | 608 | /* 8255 */ |
4085e93b | 609 | ret = subdev_8255_init(dev, s, NULL, DAS16M1_82C55); |
4b3fb0ff HS |
610 | if (ret) |
611 | return ret; | |
9afbebe4 | 612 | |
2696fb57 | 613 | /* disable upper half of hardware conversion counter so it doesn't mess with us */ |
9afbebe4 FMH |
614 | outb(TOTAL_CLEAR, dev->iobase + DAS16M1_8254_FIRST_CNTRL); |
615 | ||
2696fb57 | 616 | /* initialize digital output lines */ |
e49242a4 | 617 | outb(0, dev->iobase + DAS16M1_DIO); |
9afbebe4 FMH |
618 | |
619 | /* set the interrupt level */ | |
72998850 | 620 | devpriv->control_state = das16m1_irq_bits(dev->irq) << 4; |
9afbebe4 FMH |
621 | outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); |
622 | ||
623 | return 0; | |
624 | } | |
625 | ||
484ecc95 | 626 | static void das16m1_detach(struct comedi_device *dev) |
9afbebe4 | 627 | { |
a0b4bccc HS |
628 | struct das16m1_private_struct *devpriv = dev->private; |
629 | ||
a0b4bccc HS |
630 | if (devpriv && devpriv->extra_iobase) |
631 | release_region(devpriv->extra_iobase, DAS16M1_SIZE2); | |
632 | comedi_legacy_detach(dev); | |
9afbebe4 | 633 | } |
90f703d3 | 634 | |
72022184 HS |
635 | static struct comedi_driver das16m1_driver = { |
636 | .driver_name = "das16m1", | |
637 | .module = THIS_MODULE, | |
638 | .attach = das16m1_attach, | |
639 | .detach = das16m1_detach, | |
72022184 HS |
640 | }; |
641 | module_comedi_driver(das16m1_driver); | |
642 | ||
90f703d3 AT |
643 | MODULE_AUTHOR("Comedi http://www.comedi.org"); |
644 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
645 | MODULE_LICENSE("GPL"); |