1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Comedi driver for CIO-DAS16/M1 |
4 | * Author: Frank Mori Hess, based on code from the das16 driver. |
5 | * Copyright (C) 2001 Frank Mori Hess <fmhess@users.sourceforge.net> |
6 | * |
7 | * COMEDI - Linux Control and Measurement Device Interface |
8 | * Copyright (C) 2000 David A. Schleef <ds@schleef.org> |
9 | */ |
10 | |
11 | /* |
12 | * Driver: das16m1 |
13 | * Description: CIO-DAS16/M1 |
14 | * Author: Frank Mori Hess <fmhess@users.sourceforge.net> |
15 | * Devices: [Measurement Computing] CIO-DAS16/M1 (das16m1) |
16 | * Status: works |
17 | * |
18 | * This driver supports a single board - the CIO-DAS16/M1. As far as I know, |
19 | * there are no other boards that have the same register layout. Even the |
20 | * CIO-DAS16/M1/16 is significantly different. |
21 | * |
22 | * I was _barely_ able to reach the full 1 MHz capability of this board, using |
23 | * a hard real-time interrupt (set the TRIG_RT flag in your struct comedi_cmd |
24 | * and use rtlinux or RTAI). The board can't do dma, so the bottleneck is |
25 | * pulling the data across the ISA bus. I timed the interrupt handler, and it |
26 | * took my computer ~470 microseconds to pull 512 samples from the board. So |
27 | * at 1 Mhz sampling rate, expect your CPU to be spending almost all of its |
28 | * time in the interrupt handler. |
29 | * |
30 | * This board has some unusual restrictions for its channel/gain list. If the |
31 | * list has 2 or more channels in it, then two conditions must be satisfied: |
32 | * (1) - even/odd channels must appear at even/odd indices in the list |
33 | * (2) - the list must have an even number of entries. |
34 | * |
35 | * Configuration options: |
36 | * [0] - base io address |
37 | * [1] - irq (optional, but you probably want it) |
38 | * |
39 | * irq can be omitted, although the cmd interface will not work without it. |
40 | */ |
41 | |
42 | #include <linux/module.h> |
43 | #include <linux/slab.h> |
44 | #include <linux/interrupt.h> |
45 | #include <linux/comedi/comedidev.h> |
46 | #include <linux/comedi/comedi_8255.h> |
47 | #include <linux/comedi/comedi_8254.h> |
48 | |
49 | /* |
50 | * Register map (dev->iobase) |
51 | */ |
52 | #define DAS16M1_AI_REG 0x00 /* 16-bit register */ |
53 | #define DAS16M1_AI_TO_CHAN(x) (((x) >> 0) & 0xf) |
54 | #define DAS16M1_AI_TO_SAMPLE(x) (((x) >> 4) & 0xfff) |
55 | #define DAS16M1_CS_REG 0x02 |
56 | #define DAS16M1_CS_EXT_TRIG BIT(0) |
57 | #define DAS16M1_CS_OVRUN BIT(5) |
58 | #define DAS16M1_CS_IRQDATA BIT(7) |
59 | #define DAS16M1_DI_REG 0x03 |
60 | #define DAS16M1_DO_REG 0x03 |
61 | #define DAS16M1_CLR_INTR_REG 0x04 |
62 | #define DAS16M1_INTR_CTRL_REG 0x05 |
63 | #define DAS16M1_INTR_CTRL_PACER(x) (((x) & 0x3) << 0) |
64 | #define DAS16M1_INTR_CTRL_PACER_EXT DAS16M1_INTR_CTRL_PACER(2) |
65 | #define DAS16M1_INTR_CTRL_PACER_INT DAS16M1_INTR_CTRL_PACER(3) |
66 | #define DAS16M1_INTR_CTRL_PACER_MASK DAS16M1_INTR_CTRL_PACER(3) |
67 | #define DAS16M1_INTR_CTRL_IRQ(x) (((x) & 0x7) << 4) |
68 | #define DAS16M1_INTR_CTRL_INTE BIT(7) |
69 | #define DAS16M1_Q_ADDR_REG 0x06 |
70 | #define DAS16M1_Q_REG 0x07 |
71 | #define DAS16M1_Q_CHAN(x) (((x) & 0x7) << 0) |
72 | #define DAS16M1_Q_RANGE(x) (((x) & 0xf) << 4) |
73 | #define DAS16M1_8254_IOBASE1 0x08 |
74 | #define DAS16M1_8254_IOBASE2 0x0c |
75 | #define DAS16M1_8255_IOBASE 0x400 |
76 | #define DAS16M1_8254_IOBASE3 0x404 |
77 | |
78 | #define DAS16M1_SIZE2 0x08 |
79 | |
80 | #define DAS16M1_AI_FIFO_SZ 1024 /* # samples */ |
81 | |
82 | static const struct comedi_lrange range_das16m1 = { |
83 | 9, { |
84 | BIP_RANGE(5), |
85 | BIP_RANGE(2.5), |
86 | BIP_RANGE(1.25), |
87 | BIP_RANGE(0.625), |
88 | UNI_RANGE(10), |
89 | UNI_RANGE(5), |
90 | UNI_RANGE(2.5), |
91 | UNI_RANGE(1.25), |
92 | BIP_RANGE(10) |
93 | } |
94 | }; |
95 | |
96 | struct das16m1_private { |
97 | struct comedi_8254 *counter; |
98 | unsigned int intr_ctrl; |
99 | unsigned int adc_count; |
100 | u16 initial_hw_count; |
101 | unsigned short ai_buffer[DAS16M1_AI_FIFO_SZ]; |
102 | unsigned long ; |
103 | }; |
104 | |
105 | static void das16m1_ai_set_queue(struct comedi_device *dev, |
106 | unsigned int *chanspec, unsigned int len) |
107 | { |
108 | unsigned int i; |
109 | |
110 | for (i = 0; i < len; i++) { |
111 | unsigned int chan = CR_CHAN(chanspec[i]); |
112 | unsigned int range = CR_RANGE(chanspec[i]); |
113 | |
114 | outb(value: i, port: dev->iobase + DAS16M1_Q_ADDR_REG); |
115 | outb(DAS16M1_Q_CHAN(chan) | DAS16M1_Q_RANGE(range), |
116 | port: dev->iobase + DAS16M1_Q_REG); |
117 | } |
118 | } |
119 | |
120 | static void das16m1_ai_munge(struct comedi_device *dev, |
121 | struct comedi_subdevice *s, |
122 | void *data, unsigned int num_bytes, |
123 | unsigned int start_chan_index) |
124 | { |
125 | unsigned short *array = data; |
126 | unsigned int nsamples = comedi_bytes_to_samples(s, nbytes: num_bytes); |
127 | unsigned int i; |
128 | |
129 | /* |
130 | * The fifo values have the channel number in the lower 4-bits and |
131 | * the sample in the upper 12-bits. This just shifts the values |
132 | * to remove the channel numbers. |
133 | */ |
134 | for (i = 0; i < nsamples; i++) |
135 | array[i] = DAS16M1_AI_TO_SAMPLE(array[i]); |
136 | } |
137 | |
138 | static int das16m1_ai_check_chanlist(struct comedi_device *dev, |
139 | struct comedi_subdevice *s, |
140 | struct comedi_cmd *cmd) |
141 | { |
142 | int i; |
143 | |
144 | if (cmd->chanlist_len == 1) |
145 | return 0; |
146 | |
147 | if ((cmd->chanlist_len % 2) != 0) { |
148 | dev_dbg(dev->class_dev, |
149 | "chanlist must be of even length or length 1\n" ); |
150 | return -EINVAL; |
151 | } |
152 | |
153 | for (i = 0; i < cmd->chanlist_len; i++) { |
154 | unsigned int chan = CR_CHAN(cmd->chanlist[i]); |
155 | |
156 | if ((i % 2) != (chan % 2)) { |
157 | dev_dbg(dev->class_dev, |
158 | "even/odd channels must go have even/odd chanlist indices\n" ); |
159 | return -EINVAL; |
160 | } |
161 | } |
162 | |
163 | return 0; |
164 | } |
165 | |
166 | static int das16m1_ai_cmdtest(struct comedi_device *dev, |
167 | struct comedi_subdevice *s, |
168 | struct comedi_cmd *cmd) |
169 | { |
170 | int err = 0; |
171 | |
172 | /* Step 1 : check if triggers are trivially valid */ |
173 | |
174 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW | TRIG_EXT); |
175 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_FOLLOW); |
176 | err |= comedi_check_trigger_src(src: &cmd->convert_src, |
177 | TRIG_TIMER | TRIG_EXT); |
178 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
179 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
180 | |
181 | if (err) |
182 | return 1; |
183 | |
184 | /* Step 2a : make sure trigger sources are unique */ |
185 | |
186 | err |= comedi_check_trigger_is_unique(src: cmd->start_src); |
187 | err |= comedi_check_trigger_is_unique(src: cmd->convert_src); |
188 | err |= comedi_check_trigger_is_unique(src: cmd->stop_src); |
189 | |
190 | /* Step 2b : and mutually compatible */ |
191 | |
192 | if (err) |
193 | return 2; |
194 | |
195 | /* Step 3: check if arguments are trivially valid */ |
196 | |
197 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
198 | |
199 | if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ |
200 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
201 | |
202 | if (cmd->convert_src == TRIG_TIMER) |
203 | err |= comedi_check_trigger_arg_min(arg: &cmd->convert_arg, val: 1000); |
204 | |
205 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
206 | val: cmd->chanlist_len); |
207 | |
208 | if (cmd->stop_src == TRIG_COUNT) |
209 | err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1); |
210 | else /* TRIG_NONE */ |
211 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
212 | |
213 | if (err) |
214 | return 3; |
215 | |
216 | /* step 4: fix up arguments */ |
217 | |
218 | if (cmd->convert_src == TRIG_TIMER) { |
219 | unsigned int arg = cmd->convert_arg; |
220 | |
221 | comedi_8254_cascade_ns_to_timer(i8254: dev->pacer, nanosec: &arg, flags: cmd->flags); |
222 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: arg); |
223 | } |
224 | |
225 | if (err) |
226 | return 4; |
227 | |
228 | /* Step 5: check channel list if it exists */ |
229 | if (cmd->chanlist && cmd->chanlist_len > 0) |
230 | err |= das16m1_ai_check_chanlist(dev, s, cmd); |
231 | |
232 | if (err) |
233 | return 5; |
234 | |
235 | return 0; |
236 | } |
237 | |
238 | static int das16m1_ai_cmd(struct comedi_device *dev, |
239 | struct comedi_subdevice *s) |
240 | { |
241 | struct das16m1_private *devpriv = dev->private; |
242 | struct comedi_async *async = s->async; |
243 | struct comedi_cmd *cmd = &async->cmd; |
244 | unsigned int byte; |
245 | |
246 | /* set software count */ |
247 | devpriv->adc_count = 0; |
248 | |
249 | /* |
250 | * Initialize lower half of hardware counter, used to determine how |
251 | * many samples are in fifo. Value doesn't actually load into counter |
252 | * until counter's next clock (the next a/d conversion). |
253 | */ |
254 | comedi_8254_set_mode(i8254: devpriv->counter, counter: 1, mode: I8254_MODE2 | I8254_BINARY); |
255 | comedi_8254_write(i8254: devpriv->counter, counter: 1, val: 0); |
256 | |
257 | /* |
258 | * Remember current reading of counter so we know when counter has |
259 | * actually been loaded. |
260 | */ |
261 | devpriv->initial_hw_count = comedi_8254_read(i8254: devpriv->counter, counter: 1); |
262 | |
263 | das16m1_ai_set_queue(dev, chanspec: cmd->chanlist, len: cmd->chanlist_len); |
264 | |
265 | /* enable interrupts and set internal pacer counter mode and counts */ |
266 | devpriv->intr_ctrl &= ~DAS16M1_INTR_CTRL_PACER_MASK; |
267 | if (cmd->convert_src == TRIG_TIMER) { |
268 | comedi_8254_update_divisors(i8254: dev->pacer); |
269 | comedi_8254_pacer_enable(i8254: dev->pacer, counter1: 1, counter2: 2, enable: true); |
270 | devpriv->intr_ctrl |= DAS16M1_INTR_CTRL_PACER_INT; |
271 | } else { /* TRIG_EXT */ |
272 | devpriv->intr_ctrl |= DAS16M1_INTR_CTRL_PACER_EXT; |
273 | } |
274 | |
275 | /* set control & status register */ |
276 | byte = 0; |
277 | /* |
278 | * If we are using external start trigger (also board dislikes having |
279 | * both start and conversion triggers external simultaneously). |
280 | */ |
281 | if (cmd->start_src == TRIG_EXT && cmd->convert_src != TRIG_EXT) |
282 | byte |= DAS16M1_CS_EXT_TRIG; |
283 | |
284 | outb(value: byte, port: dev->iobase + DAS16M1_CS_REG); |
285 | |
286 | /* clear interrupt */ |
287 | outb(value: 0, port: dev->iobase + DAS16M1_CLR_INTR_REG); |
288 | |
289 | devpriv->intr_ctrl |= DAS16M1_INTR_CTRL_INTE; |
290 | outb(value: devpriv->intr_ctrl, port: dev->iobase + DAS16M1_INTR_CTRL_REG); |
291 | |
292 | return 0; |
293 | } |
294 | |
295 | static int das16m1_ai_cancel(struct comedi_device *dev, |
296 | struct comedi_subdevice *s) |
297 | { |
298 | struct das16m1_private *devpriv = dev->private; |
299 | |
300 | /* disable interrupts and pacer */ |
301 | devpriv->intr_ctrl &= ~(DAS16M1_INTR_CTRL_INTE | |
302 | DAS16M1_INTR_CTRL_PACER_MASK); |
303 | outb(value: devpriv->intr_ctrl, port: dev->iobase + DAS16M1_INTR_CTRL_REG); |
304 | |
305 | return 0; |
306 | } |
307 | |
308 | static int das16m1_ai_eoc(struct comedi_device *dev, |
309 | struct comedi_subdevice *s, |
310 | struct comedi_insn *insn, |
311 | unsigned long context) |
312 | { |
313 | unsigned int status; |
314 | |
315 | status = inb(port: dev->iobase + DAS16M1_CS_REG); |
316 | if (status & DAS16M1_CS_IRQDATA) |
317 | return 0; |
318 | return -EBUSY; |
319 | } |
320 | |
321 | static int das16m1_ai_insn_read(struct comedi_device *dev, |
322 | struct comedi_subdevice *s, |
323 | struct comedi_insn *insn, |
324 | unsigned int *data) |
325 | { |
326 | int ret; |
327 | int i; |
328 | |
329 | das16m1_ai_set_queue(dev, chanspec: &insn->chanspec, len: 1); |
330 | |
331 | for (i = 0; i < insn->n; i++) { |
332 | unsigned short val; |
333 | |
334 | /* clear interrupt */ |
335 | outb(value: 0, port: dev->iobase + DAS16M1_CLR_INTR_REG); |
336 | /* trigger conversion */ |
337 | outb(value: 0, port: dev->iobase + DAS16M1_AI_REG); |
338 | |
339 | ret = comedi_timeout(dev, s, insn, cb: das16m1_ai_eoc, context: 0); |
340 | if (ret) |
341 | return ret; |
342 | |
343 | val = inw(port: dev->iobase + DAS16M1_AI_REG); |
344 | data[i] = DAS16M1_AI_TO_SAMPLE(val); |
345 | } |
346 | |
347 | return insn->n; |
348 | } |
349 | |
350 | static int das16m1_di_insn_bits(struct comedi_device *dev, |
351 | struct comedi_subdevice *s, |
352 | struct comedi_insn *insn, |
353 | unsigned int *data) |
354 | { |
355 | data[1] = inb(port: dev->iobase + DAS16M1_DI_REG) & 0xf; |
356 | |
357 | return insn->n; |
358 | } |
359 | |
360 | static int das16m1_do_insn_bits(struct comedi_device *dev, |
361 | struct comedi_subdevice *s, |
362 | struct comedi_insn *insn, |
363 | unsigned int *data) |
364 | { |
365 | if (comedi_dio_update_state(s, data)) |
366 | outb(value: s->state, port: dev->iobase + DAS16M1_DO_REG); |
367 | |
368 | data[1] = s->state; |
369 | |
370 | return insn->n; |
371 | } |
372 | |
373 | static void das16m1_handler(struct comedi_device *dev, unsigned int status) |
374 | { |
375 | struct das16m1_private *devpriv = dev->private; |
376 | struct comedi_subdevice *s = dev->read_subdev; |
377 | struct comedi_async *async = s->async; |
378 | struct comedi_cmd *cmd = &async->cmd; |
379 | u16 num_samples; |
380 | u16 hw_counter; |
381 | |
382 | /* figure out how many samples are in fifo */ |
383 | hw_counter = comedi_8254_read(i8254: devpriv->counter, counter: 1); |
384 | /* |
385 | * Make sure hardware counter reading is not bogus due to initial |
386 | * value not having been loaded yet. |
387 | */ |
388 | if (devpriv->adc_count == 0 && |
389 | hw_counter == devpriv->initial_hw_count) { |
390 | num_samples = 0; |
391 | } else { |
392 | /* |
393 | * The calculation of num_samples looks odd, but it uses the |
394 | * following facts. 16 bit hardware counter is initialized with |
395 | * value of zero (which really means 0x1000). The counter |
396 | * decrements by one on each conversion (when the counter |
397 | * decrements from zero it goes to 0xffff). num_samples is a |
398 | * 16 bit variable, so it will roll over in a similar fashion |
399 | * to the hardware counter. Work it out, and this is what you |
400 | * get. |
401 | */ |
402 | num_samples = -hw_counter - devpriv->adc_count; |
403 | } |
404 | /* check if we only need some of the points */ |
405 | if (cmd->stop_src == TRIG_COUNT) { |
406 | if (num_samples > cmd->stop_arg * cmd->chanlist_len) |
407 | num_samples = cmd->stop_arg * cmd->chanlist_len; |
408 | } |
409 | /* make sure we don't try to get too many points if fifo has overrun */ |
410 | if (num_samples > DAS16M1_AI_FIFO_SZ) |
411 | num_samples = DAS16M1_AI_FIFO_SZ; |
412 | insw(port: dev->iobase, addr: devpriv->ai_buffer, count: num_samples); |
413 | comedi_buf_write_samples(s, data: devpriv->ai_buffer, nsamples: num_samples); |
414 | devpriv->adc_count += num_samples; |
415 | |
416 | if (cmd->stop_src == TRIG_COUNT) { |
417 | if (devpriv->adc_count >= cmd->stop_arg * cmd->chanlist_len) { |
418 | /* end of acquisition */ |
419 | async->events |= COMEDI_CB_EOA; |
420 | } |
421 | } |
422 | |
423 | /* |
424 | * This probably won't catch overruns since the card doesn't generate |
425 | * overrun interrupts, but we might as well try. |
426 | */ |
427 | if (status & DAS16M1_CS_OVRUN) { |
428 | async->events |= COMEDI_CB_ERROR; |
429 | dev_err(dev->class_dev, "fifo overflow\n" ); |
430 | } |
431 | |
432 | comedi_handle_events(dev, s); |
433 | } |
434 | |
435 | static int das16m1_ai_poll(struct comedi_device *dev, |
436 | struct comedi_subdevice *s) |
437 | { |
438 | unsigned long flags; |
439 | unsigned int status; |
440 | |
441 | /* prevent race with interrupt handler */ |
442 | spin_lock_irqsave(&dev->spinlock, flags); |
443 | status = inb(port: dev->iobase + DAS16M1_CS_REG); |
444 | das16m1_handler(dev, status); |
445 | spin_unlock_irqrestore(lock: &dev->spinlock, flags); |
446 | |
447 | return comedi_buf_n_bytes_ready(s); |
448 | } |
449 | |
450 | static irqreturn_t das16m1_interrupt(int irq, void *d) |
451 | { |
452 | int status; |
453 | struct comedi_device *dev = d; |
454 | |
455 | if (!dev->attached) { |
456 | dev_err(dev->class_dev, "premature interrupt\n" ); |
457 | return IRQ_HANDLED; |
458 | } |
459 | /* prevent race with comedi_poll() */ |
460 | spin_lock(lock: &dev->spinlock); |
461 | |
462 | status = inb(port: dev->iobase + DAS16M1_CS_REG); |
463 | |
464 | if ((status & (DAS16M1_CS_IRQDATA | DAS16M1_CS_OVRUN)) == 0) { |
465 | dev_err(dev->class_dev, "spurious interrupt\n" ); |
466 | spin_unlock(lock: &dev->spinlock); |
467 | return IRQ_NONE; |
468 | } |
469 | |
470 | das16m1_handler(dev, status); |
471 | |
472 | /* clear interrupt */ |
473 | outb(value: 0, port: dev->iobase + DAS16M1_CLR_INTR_REG); |
474 | |
475 | spin_unlock(lock: &dev->spinlock); |
476 | return IRQ_HANDLED; |
477 | } |
478 | |
479 | static int das16m1_irq_bits(unsigned int irq) |
480 | { |
481 | switch (irq) { |
482 | case 10: |
483 | return 0x0; |
484 | case 11: |
485 | return 0x1; |
486 | case 12: |
487 | return 0x2; |
488 | case 15: |
489 | return 0x3; |
490 | case 2: |
491 | return 0x4; |
492 | case 3: |
493 | return 0x5; |
494 | case 5: |
495 | return 0x6; |
496 | case 7: |
497 | return 0x7; |
498 | default: |
499 | return 0x0; |
500 | } |
501 | } |
502 | |
503 | static int das16m1_attach(struct comedi_device *dev, |
504 | struct comedi_devconfig *it) |
505 | { |
506 | struct das16m1_private *devpriv; |
507 | struct comedi_subdevice *s; |
508 | int ret; |
509 | |
510 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
511 | if (!devpriv) |
512 | return -ENOMEM; |
513 | |
514 | ret = comedi_request_region(dev, start: it->options[0], len: 0x10); |
515 | if (ret) |
516 | return ret; |
517 | /* Request an additional region for the 8255 and 3rd 8254 */ |
518 | ret = __comedi_request_region(dev, start: dev->iobase + DAS16M1_8255_IOBASE, |
519 | DAS16M1_SIZE2); |
520 | if (ret) |
521 | return ret; |
522 | devpriv->extra_iobase = dev->iobase + DAS16M1_8255_IOBASE; |
523 | |
524 | /* only irqs 2, 3, 4, 5, 6, 7, 10, 11, 12, 14, and 15 are valid */ |
525 | if ((1 << it->options[1]) & 0xdcfc) { |
526 | ret = request_irq(irq: it->options[1], handler: das16m1_interrupt, flags: 0, |
527 | name: dev->board_name, dev); |
528 | if (ret == 0) |
529 | dev->irq = it->options[1]; |
530 | } |
531 | |
532 | dev->pacer = comedi_8254_io_alloc(iobase: dev->iobase + DAS16M1_8254_IOBASE2, |
533 | I8254_OSC_BASE_10MHZ, I8254_IO8, regshift: 0); |
534 | if (IS_ERR(ptr: dev->pacer)) |
535 | return PTR_ERR(ptr: dev->pacer); |
536 | |
537 | devpriv->counter = |
538 | comedi_8254_io_alloc(iobase: dev->iobase + DAS16M1_8254_IOBASE1, |
539 | osc_base: 0, I8254_IO8, regshift: 0); |
540 | if (IS_ERR(ptr: devpriv->counter)) |
541 | return PTR_ERR(ptr: devpriv->counter); |
542 | |
543 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
544 | if (ret) |
545 | return ret; |
546 | |
547 | /* Analog Input subdevice */ |
548 | s = &dev->subdevices[0]; |
549 | s->type = COMEDI_SUBD_AI; |
550 | s->subdev_flags = SDF_READABLE | SDF_DIFF; |
551 | s->n_chan = 8; |
552 | s->maxdata = 0x0fff; |
553 | s->range_table = &range_das16m1; |
554 | s->insn_read = das16m1_ai_insn_read; |
555 | if (dev->irq) { |
556 | dev->read_subdev = s; |
557 | s->subdev_flags |= SDF_CMD_READ; |
558 | s->len_chanlist = 256; |
559 | s->do_cmdtest = das16m1_ai_cmdtest; |
560 | s->do_cmd = das16m1_ai_cmd; |
561 | s->cancel = das16m1_ai_cancel; |
562 | s->poll = das16m1_ai_poll; |
563 | s->munge = das16m1_ai_munge; |
564 | } |
565 | |
566 | /* Digital Input subdevice */ |
567 | s = &dev->subdevices[1]; |
568 | s->type = COMEDI_SUBD_DI; |
569 | s->subdev_flags = SDF_READABLE; |
570 | s->n_chan = 4; |
571 | s->maxdata = 1; |
572 | s->range_table = &range_digital; |
573 | s->insn_bits = das16m1_di_insn_bits; |
574 | |
575 | /* Digital Output subdevice */ |
576 | s = &dev->subdevices[2]; |
577 | s->type = COMEDI_SUBD_DO; |
578 | s->subdev_flags = SDF_WRITABLE; |
579 | s->n_chan = 4; |
580 | s->maxdata = 1; |
581 | s->range_table = &range_digital; |
582 | s->insn_bits = das16m1_do_insn_bits; |
583 | |
584 | /* Digital I/O subdevice (8255) */ |
585 | s = &dev->subdevices[3]; |
586 | ret = subdev_8255_io_init(dev, s, DAS16M1_8255_IOBASE); |
587 | if (ret) |
588 | return ret; |
589 | |
590 | /* initialize digital output lines */ |
591 | outb(value: 0, port: dev->iobase + DAS16M1_DO_REG); |
592 | |
593 | /* set the interrupt level */ |
594 | devpriv->intr_ctrl = DAS16M1_INTR_CTRL_IRQ(das16m1_irq_bits(dev->irq)); |
595 | outb(value: devpriv->intr_ctrl, port: dev->iobase + DAS16M1_INTR_CTRL_REG); |
596 | |
597 | return 0; |
598 | } |
599 | |
600 | static void das16m1_detach(struct comedi_device *dev) |
601 | { |
602 | struct das16m1_private *devpriv = dev->private; |
603 | |
604 | if (devpriv) { |
605 | if (devpriv->extra_iobase) |
606 | release_region(devpriv->extra_iobase, DAS16M1_SIZE2); |
607 | if (!IS_ERR(ptr: devpriv->counter)) |
608 | kfree(objp: devpriv->counter); |
609 | } |
610 | comedi_legacy_detach(dev); |
611 | } |
612 | |
613 | static struct comedi_driver das16m1_driver = { |
614 | .driver_name = "das16m1" , |
615 | .module = THIS_MODULE, |
616 | .attach = das16m1_attach, |
617 | .detach = das16m1_detach, |
618 | }; |
619 | module_comedi_driver(das16m1_driver); |
620 | |
621 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
622 | MODULE_DESCRIPTION("Comedi driver for CIO-DAS16/M1 ISA cards" ); |
623 | MODULE_LICENSE("GPL" ); |
624 | |