1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * comedi/drivers/das800.c
4 * Driver for Keitley das800 series boards and compatibles
5 * Copyright (C) 2000 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 * Driver: das800
12 * Description: Keithley Metrabyte DAS800 (& compatibles)
13 * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
14 * Devices: [Keithley Metrabyte] DAS-800 (das-800), DAS-801 (das-801),
15 * DAS-802 (das-802),
16 * [Measurement Computing] CIO-DAS800 (cio-das800),
17 * CIO-DAS801 (cio-das801), CIO-DAS802 (cio-das802),
18 * CIO-DAS802/16 (cio-das802/16)
19 * Status: works, cio-das802/16 untested - email me if you have tested it
20 *
21 * Configuration options:
22 * [0] - I/O port base address
23 * [1] - IRQ (optional, required for timed or externally triggered conversions)
24 *
25 * Notes:
26 * IRQ can be omitted, although the cmd interface will not work without it.
27 *
28 * All entries in the channel/gain list must use the same gain and be
29 * consecutive channels counting upwards in channel number (these are
30 * hardware limitations.)
31 *
32 * I've never tested the gain setting stuff since I only have a
33 * DAS-800 board with fixed gain.
34 *
35 * The cio-das802/16 does not have a fifo-empty status bit! Therefore
36 * only fifo-half-full transfers are possible with this card.
37 *
38 * cmd triggers supported:
39 * start_src: TRIG_NOW | TRIG_EXT
40 * scan_begin_src: TRIG_FOLLOW
41 * scan_end_src: TRIG_COUNT
42 * convert_src: TRIG_TIMER | TRIG_EXT
43 * stop_src: TRIG_NONE | TRIG_COUNT
44 */
45
46#include <linux/module.h>
47#include <linux/interrupt.h>
48#include <linux/delay.h>
49#include <linux/comedi/comedidev.h>
50#include <linux/comedi/comedi_8254.h>
51
52#define N_CHAN_AI 8 /* number of analog input channels */
53
54/* Registers for the das800 */
55
56#define DAS800_LSB 0
57#define FIFO_EMPTY 0x1
58#define FIFO_OVF 0x2
59#define DAS800_MSB 1
60#define DAS800_CONTROL1 2
61#define CONTROL1_INTE 0x8
62#define DAS800_CONV_CONTROL 2
63#define ITE 0x1
64#define CASC 0x2
65#define DTEN 0x4
66#define IEOC 0x8
67#define EACS 0x10
68#define CONV_HCEN 0x80
69#define DAS800_SCAN_LIMITS 2
70#define DAS800_STATUS 2
71#define IRQ 0x8
72#define BUSY 0x80
73#define DAS800_GAIN 3
74#define CIO_FFOV 0x8 /* cio-das802/16 fifo overflow */
75#define CIO_ENHF 0x90 /* cio-das802/16 fifo half full int ena */
76#define CONTROL1 0x80
77#define CONV_CONTROL 0xa0
78#define SCAN_LIMITS 0xc0
79#define ID 0xe0
80#define DAS800_8254 4
81#define DAS800_STATUS2 7
82#define STATUS2_HCEN 0x80
83#define STATUS2_INTE 0X20
84#define DAS800_ID 7
85
86#define DAS802_16_HALF_FIFO_SZ 128
87
88struct das800_board {
89 const char *name;
90 int ai_speed;
91 const struct comedi_lrange *ai_range;
92 int resolution;
93};
94
95static const struct comedi_lrange range_das801_ai = {
96 9, {
97 BIP_RANGE(5),
98 BIP_RANGE(10),
99 UNI_RANGE(10),
100 BIP_RANGE(0.5),
101 UNI_RANGE(1),
102 BIP_RANGE(0.05),
103 UNI_RANGE(0.1),
104 BIP_RANGE(0.01),
105 UNI_RANGE(0.02)
106 }
107};
108
109static const struct comedi_lrange range_cio_das801_ai = {
110 9, {
111 BIP_RANGE(5),
112 BIP_RANGE(10),
113 UNI_RANGE(10),
114 BIP_RANGE(0.5),
115 UNI_RANGE(1),
116 BIP_RANGE(0.05),
117 UNI_RANGE(0.1),
118 BIP_RANGE(0.005),
119 UNI_RANGE(0.01)
120 }
121};
122
123static const struct comedi_lrange range_das802_ai = {
124 9, {
125 BIP_RANGE(5),
126 BIP_RANGE(10),
127 UNI_RANGE(10),
128 BIP_RANGE(2.5),
129 UNI_RANGE(5),
130 BIP_RANGE(1.25),
131 UNI_RANGE(2.5),
132 BIP_RANGE(0.625),
133 UNI_RANGE(1.25)
134 }
135};
136
137static const struct comedi_lrange range_das80216_ai = {
138 8, {
139 BIP_RANGE(10),
140 UNI_RANGE(10),
141 BIP_RANGE(5),
142 UNI_RANGE(5),
143 BIP_RANGE(2.5),
144 UNI_RANGE(2.5),
145 BIP_RANGE(1.25),
146 UNI_RANGE(1.25)
147 }
148};
149
150enum das800_boardinfo {
151 BOARD_DAS800,
152 BOARD_CIODAS800,
153 BOARD_DAS801,
154 BOARD_CIODAS801,
155 BOARD_DAS802,
156 BOARD_CIODAS802,
157 BOARD_CIODAS80216,
158};
159
160static const struct das800_board das800_boards[] = {
161 [BOARD_DAS800] = {
162 .name = "das-800",
163 .ai_speed = 25000,
164 .ai_range = &range_bipolar5,
165 .resolution = 12,
166 },
167 [BOARD_CIODAS800] = {
168 .name = "cio-das800",
169 .ai_speed = 20000,
170 .ai_range = &range_bipolar5,
171 .resolution = 12,
172 },
173 [BOARD_DAS801] = {
174 .name = "das-801",
175 .ai_speed = 25000,
176 .ai_range = &range_das801_ai,
177 .resolution = 12,
178 },
179 [BOARD_CIODAS801] = {
180 .name = "cio-das801",
181 .ai_speed = 20000,
182 .ai_range = &range_cio_das801_ai,
183 .resolution = 12,
184 },
185 [BOARD_DAS802] = {
186 .name = "das-802",
187 .ai_speed = 25000,
188 .ai_range = &range_das802_ai,
189 .resolution = 12,
190 },
191 [BOARD_CIODAS802] = {
192 .name = "cio-das802",
193 .ai_speed = 20000,
194 .ai_range = &range_das802_ai,
195 .resolution = 12,
196 },
197 [BOARD_CIODAS80216] = {
198 .name = "cio-das802/16",
199 .ai_speed = 10000,
200 .ai_range = &range_das80216_ai,
201 .resolution = 16,
202 },
203};
204
205struct das800_private {
206 unsigned int do_bits; /* digital output bits */
207};
208
209static void das800_ind_write(struct comedi_device *dev,
210 unsigned int val, unsigned int reg)
211{
212 /*
213 * Select dev->iobase + 2 to be desired register
214 * then write to that register.
215 */
216 outb(value: reg, port: dev->iobase + DAS800_GAIN);
217 outb(value: val, port: dev->iobase + 2);
218}
219
220static unsigned int das800_ind_read(struct comedi_device *dev, unsigned int reg)
221{
222 /*
223 * Select dev->iobase + 7 to be desired register
224 * then read from that register.
225 */
226 outb(value: reg, port: dev->iobase + DAS800_GAIN);
227 return inb(port: dev->iobase + 7);
228}
229
230static void das800_enable(struct comedi_device *dev)
231{
232 const struct das800_board *board = dev->board_ptr;
233 struct das800_private *devpriv = dev->private;
234 unsigned long irq_flags;
235
236 spin_lock_irqsave(&dev->spinlock, irq_flags);
237 /* enable fifo-half full interrupts for cio-das802/16 */
238 if (board->resolution == 16)
239 outb(CIO_ENHF, port: dev->iobase + DAS800_GAIN);
240 /* enable hardware triggering */
241 das800_ind_write(dev, CONV_HCEN, CONV_CONTROL);
242 /* enable card's interrupt */
243 das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits, CONTROL1);
244 spin_unlock_irqrestore(lock: &dev->spinlock, flags: irq_flags);
245}
246
247static void das800_disable(struct comedi_device *dev)
248{
249 unsigned long irq_flags;
250
251 spin_lock_irqsave(&dev->spinlock, irq_flags);
252 /* disable hardware triggering of conversions */
253 das800_ind_write(dev, val: 0x0, CONV_CONTROL);
254 spin_unlock_irqrestore(lock: &dev->spinlock, flags: irq_flags);
255}
256
257static int das800_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
258{
259 das800_disable(dev);
260 return 0;
261}
262
263static int das800_ai_check_chanlist(struct comedi_device *dev,
264 struct comedi_subdevice *s,
265 struct comedi_cmd *cmd)
266{
267 unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
268 unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
269 int i;
270
271 for (i = 1; i < cmd->chanlist_len; i++) {
272 unsigned int chan = CR_CHAN(cmd->chanlist[i]);
273 unsigned int range = CR_RANGE(cmd->chanlist[i]);
274
275 if (chan != (chan0 + i) % s->n_chan) {
276 dev_dbg(dev->class_dev,
277 "chanlist must be consecutive, counting upwards\n");
278 return -EINVAL;
279 }
280
281 if (range != range0) {
282 dev_dbg(dev->class_dev,
283 "chanlist must all have the same gain\n");
284 return -EINVAL;
285 }
286 }
287
288 return 0;
289}
290
291static int das800_ai_do_cmdtest(struct comedi_device *dev,
292 struct comedi_subdevice *s,
293 struct comedi_cmd *cmd)
294{
295 const struct das800_board *board = dev->board_ptr;
296 int err = 0;
297
298 /* Step 1 : check if triggers are trivially valid */
299
300 err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW | TRIG_EXT);
301 err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_FOLLOW);
302 err |= comedi_check_trigger_src(src: &cmd->convert_src,
303 TRIG_TIMER | TRIG_EXT);
304 err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT);
305 err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE);
306
307 if (err)
308 return 1;
309
310 /* Step 2a : make sure trigger sources are unique */
311
312 err |= comedi_check_trigger_is_unique(src: cmd->start_src);
313 err |= comedi_check_trigger_is_unique(src: cmd->convert_src);
314 err |= comedi_check_trigger_is_unique(src: cmd->stop_src);
315
316 /* Step 2b : and mutually compatible */
317
318 if (err)
319 return 2;
320
321 /* Step 3: check if arguments are trivially valid */
322
323 err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0);
324
325 if (cmd->convert_src == TRIG_TIMER) {
326 err |= comedi_check_trigger_arg_min(arg: &cmd->convert_arg,
327 val: board->ai_speed);
328 }
329
330 err |= comedi_check_trigger_arg_min(arg: &cmd->chanlist_len, val: 1);
331 err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg,
332 val: cmd->chanlist_len);
333
334 if (cmd->stop_src == TRIG_COUNT)
335 err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1);
336 else /* TRIG_NONE */
337 err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0);
338
339 if (err)
340 return 3;
341
342 /* step 4: fix up any arguments */
343
344 if (cmd->convert_src == TRIG_TIMER) {
345 unsigned int arg = cmd->convert_arg;
346
347 comedi_8254_cascade_ns_to_timer(i8254: dev->pacer, nanosec: &arg, flags: cmd->flags);
348 err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: arg);
349 }
350
351 if (err)
352 return 4;
353
354 /* Step 5: check channel list if it exists */
355 if (cmd->chanlist && cmd->chanlist_len > 0)
356 err |= das800_ai_check_chanlist(dev, s, cmd);
357
358 if (err)
359 return 5;
360
361 return 0;
362}
363
364static int das800_ai_do_cmd(struct comedi_device *dev,
365 struct comedi_subdevice *s)
366{
367 const struct das800_board *board = dev->board_ptr;
368 struct comedi_async *async = s->async;
369 struct comedi_cmd *cmd = &async->cmd;
370 unsigned int gain = CR_RANGE(cmd->chanlist[0]);
371 unsigned int start_chan = CR_CHAN(cmd->chanlist[0]);
372 unsigned int end_chan = (start_chan + cmd->chanlist_len - 1) % 8;
373 unsigned int scan_chans = (end_chan << 3) | start_chan;
374 int conv_bits;
375 unsigned long irq_flags;
376
377 das800_disable(dev);
378
379 spin_lock_irqsave(&dev->spinlock, irq_flags);
380 /* set scan limits */
381 das800_ind_write(dev, val: scan_chans, SCAN_LIMITS);
382 spin_unlock_irqrestore(lock: &dev->spinlock, flags: irq_flags);
383
384 /* set gain */
385 if (board->resolution == 12 && gain > 0)
386 gain += 0x7;
387 gain &= 0xf;
388 outb(value: gain, port: dev->iobase + DAS800_GAIN);
389
390 /* enable auto channel scan, send interrupts on end of conversion
391 * and set clock source to internal or external
392 */
393 conv_bits = 0;
394 conv_bits |= EACS | IEOC;
395 if (cmd->start_src == TRIG_EXT)
396 conv_bits |= DTEN;
397 if (cmd->convert_src == TRIG_TIMER) {
398 conv_bits |= CASC | ITE;
399 comedi_8254_update_divisors(i8254: dev->pacer);
400 comedi_8254_pacer_enable(i8254: dev->pacer, counter1: 1, counter2: 2, enable: true);
401 }
402
403 spin_lock_irqsave(&dev->spinlock, irq_flags);
404 das800_ind_write(dev, val: conv_bits, CONV_CONTROL);
405 spin_unlock_irqrestore(lock: &dev->spinlock, flags: irq_flags);
406
407 das800_enable(dev);
408 return 0;
409}
410
411static unsigned int das800_ai_get_sample(struct comedi_device *dev)
412{
413 unsigned int lsb = inb(port: dev->iobase + DAS800_LSB);
414 unsigned int msb = inb(port: dev->iobase + DAS800_MSB);
415
416 return (msb << 8) | lsb;
417}
418
419static irqreturn_t das800_interrupt(int irq, void *d)
420{
421 struct comedi_device *dev = d;
422 struct das800_private *devpriv = dev->private;
423 struct comedi_subdevice *s = dev->read_subdev;
424 struct comedi_async *async;
425 struct comedi_cmd *cmd;
426 unsigned long irq_flags;
427 unsigned int status;
428 unsigned short val;
429 bool fifo_empty;
430 bool fifo_overflow;
431 int i;
432
433 status = inb(port: dev->iobase + DAS800_STATUS);
434 if (!(status & IRQ))
435 return IRQ_NONE;
436 if (!dev->attached)
437 return IRQ_HANDLED;
438
439 async = s->async;
440 cmd = &async->cmd;
441
442 spin_lock_irqsave(&dev->spinlock, irq_flags);
443 status = das800_ind_read(dev, CONTROL1) & STATUS2_HCEN;
444 /*
445 * Don't release spinlock yet since we want to make sure
446 * no one else disables hardware conversions.
447 */
448
449 /* if hardware conversions are not enabled, then quit */
450 if (status == 0) {
451 spin_unlock_irqrestore(lock: &dev->spinlock, flags: irq_flags);
452 return IRQ_HANDLED;
453 }
454
455 for (i = 0; i < DAS802_16_HALF_FIFO_SZ; i++) {
456 val = das800_ai_get_sample(dev);
457 if (s->maxdata == 0x0fff) {
458 fifo_empty = !!(val & FIFO_EMPTY);
459 fifo_overflow = !!(val & FIFO_OVF);
460 } else {
461 /* cio-das802/16 has no fifo empty status bit */
462 fifo_empty = false;
463 fifo_overflow = !!(inb(port: dev->iobase + DAS800_GAIN) &
464 CIO_FFOV);
465 }
466 if (fifo_empty || fifo_overflow)
467 break;
468
469 if (s->maxdata == 0x0fff)
470 val >>= 4; /* 12-bit sample */
471
472 val &= s->maxdata;
473 comedi_buf_write_samples(s, data: &val, nsamples: 1);
474
475 if (cmd->stop_src == TRIG_COUNT &&
476 async->scans_done >= cmd->stop_arg) {
477 async->events |= COMEDI_CB_EOA;
478 break;
479 }
480 }
481
482 if (fifo_overflow) {
483 spin_unlock_irqrestore(lock: &dev->spinlock, flags: irq_flags);
484 async->events |= COMEDI_CB_ERROR;
485 comedi_handle_events(dev, s);
486 return IRQ_HANDLED;
487 }
488
489 if (!(async->events & COMEDI_CB_CANCEL_MASK)) {
490 /*
491 * Re-enable card's interrupt.
492 * We already have spinlock, so indirect addressing is safe
493 */
494 das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits,
495 CONTROL1);
496 spin_unlock_irqrestore(lock: &dev->spinlock, flags: irq_flags);
497 } else {
498 /* otherwise, stop taking data */
499 spin_unlock_irqrestore(lock: &dev->spinlock, flags: irq_flags);
500 das800_disable(dev);
501 }
502 comedi_handle_events(dev, s);
503 return IRQ_HANDLED;
504}
505
506static int das800_ai_eoc(struct comedi_device *dev,
507 struct comedi_subdevice *s,
508 struct comedi_insn *insn,
509 unsigned long context)
510{
511 unsigned int status;
512
513 status = inb(port: dev->iobase + DAS800_STATUS);
514 if ((status & BUSY) == 0)
515 return 0;
516 return -EBUSY;
517}
518
519static int das800_ai_insn_read(struct comedi_device *dev,
520 struct comedi_subdevice *s,
521 struct comedi_insn *insn,
522 unsigned int *data)
523{
524 struct das800_private *devpriv = dev->private;
525 unsigned int chan = CR_CHAN(insn->chanspec);
526 unsigned int range = CR_RANGE(insn->chanspec);
527 unsigned long irq_flags;
528 unsigned int val;
529 int ret;
530 int i;
531
532 das800_disable(dev);
533
534 /* set multiplexer */
535 spin_lock_irqsave(&dev->spinlock, irq_flags);
536 das800_ind_write(dev, val: chan | devpriv->do_bits, CONTROL1);
537 spin_unlock_irqrestore(lock: &dev->spinlock, flags: irq_flags);
538
539 /* set gain / range */
540 if (s->maxdata == 0x0fff && range)
541 range += 0x7;
542 range &= 0xf;
543 outb(value: range, port: dev->iobase + DAS800_GAIN);
544
545 udelay(5);
546
547 for (i = 0; i < insn->n; i++) {
548 /* trigger conversion */
549 outb_p(value: 0, port: dev->iobase + DAS800_MSB);
550
551 ret = comedi_timeout(dev, s, insn, cb: das800_ai_eoc, context: 0);
552 if (ret)
553 return ret;
554
555 val = das800_ai_get_sample(dev);
556 if (s->maxdata == 0x0fff)
557 val >>= 4; /* 12-bit sample */
558 data[i] = val & s->maxdata;
559 }
560
561 return insn->n;
562}
563
564static int das800_di_insn_bits(struct comedi_device *dev,
565 struct comedi_subdevice *s,
566 struct comedi_insn *insn,
567 unsigned int *data)
568{
569 data[1] = (inb(port: dev->iobase + DAS800_STATUS) >> 4) & 0x7;
570
571 return insn->n;
572}
573
574static int das800_do_insn_bits(struct comedi_device *dev,
575 struct comedi_subdevice *s,
576 struct comedi_insn *insn,
577 unsigned int *data)
578{
579 struct das800_private *devpriv = dev->private;
580 unsigned long irq_flags;
581
582 if (comedi_dio_update_state(s, data)) {
583 devpriv->do_bits = s->state << 4;
584
585 spin_lock_irqsave(&dev->spinlock, irq_flags);
586 das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits,
587 CONTROL1);
588 spin_unlock_irqrestore(lock: &dev->spinlock, flags: irq_flags);
589 }
590
591 data[1] = s->state;
592
593 return insn->n;
594}
595
596static const struct das800_board *das800_probe(struct comedi_device *dev)
597{
598 const struct das800_board *board = dev->board_ptr;
599 int index = board ? board - das800_boards : -EINVAL;
600 int id_bits;
601 unsigned long irq_flags;
602
603 /*
604 * The dev->board_ptr will be set by comedi_device_attach() if the
605 * board name provided by the user matches a board->name in this
606 * driver. If so, this function sanity checks the id_bits to verify
607 * that the board is correct.
608 *
609 * If the dev->board_ptr is not set, the user is trying to attach
610 * an unspecified board to this driver. In this case the id_bits
611 * are used to 'probe' for the correct dev->board_ptr.
612 */
613 spin_lock_irqsave(&dev->spinlock, irq_flags);
614 id_bits = das800_ind_read(dev, ID) & 0x3;
615 spin_unlock_irqrestore(lock: &dev->spinlock, flags: irq_flags);
616
617 switch (id_bits) {
618 case 0x0:
619 if (index == BOARD_DAS800 || index == BOARD_CIODAS800)
620 return board;
621 index = BOARD_DAS800;
622 break;
623 case 0x2:
624 if (index == BOARD_DAS801 || index == BOARD_CIODAS801)
625 return board;
626 index = BOARD_DAS801;
627 break;
628 case 0x3:
629 if (index == BOARD_DAS802 || index == BOARD_CIODAS802 ||
630 index == BOARD_CIODAS80216)
631 return board;
632 index = BOARD_DAS802;
633 break;
634 default:
635 dev_dbg(dev->class_dev, "Board model: 0x%x (unknown)\n",
636 id_bits);
637 return NULL;
638 }
639 dev_dbg(dev->class_dev, "Board model (probed): %s series\n",
640 das800_boards[index].name);
641
642 return &das800_boards[index];
643}
644
645static int das800_attach(struct comedi_device *dev, struct comedi_devconfig *it)
646{
647 const struct das800_board *board;
648 struct das800_private *devpriv;
649 struct comedi_subdevice *s;
650 unsigned int irq = it->options[1];
651 unsigned long irq_flags;
652 int ret;
653
654 devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv));
655 if (!devpriv)
656 return -ENOMEM;
657
658 ret = comedi_request_region(dev, start: it->options[0], len: 0x8);
659 if (ret)
660 return ret;
661
662 board = das800_probe(dev);
663 if (!board)
664 return -ENODEV;
665 dev->board_ptr = board;
666 dev->board_name = board->name;
667
668 if (irq > 1 && irq <= 7) {
669 ret = request_irq(irq, handler: das800_interrupt, flags: 0, name: "das800",
670 dev);
671 if (ret == 0)
672 dev->irq = irq;
673 }
674
675 dev->pacer = comedi_8254_io_alloc(iobase: dev->iobase + DAS800_8254,
676 I8254_OSC_BASE_1MHZ, I8254_IO8, regshift: 0);
677 if (IS_ERR(ptr: dev->pacer))
678 return PTR_ERR(ptr: dev->pacer);
679
680 ret = comedi_alloc_subdevices(dev, num_subdevices: 3);
681 if (ret)
682 return ret;
683
684 /* Analog Input subdevice */
685 s = &dev->subdevices[0];
686 dev->read_subdev = s;
687 s->type = COMEDI_SUBD_AI;
688 s->subdev_flags = SDF_READABLE | SDF_GROUND;
689 s->n_chan = 8;
690 s->maxdata = (1 << board->resolution) - 1;
691 s->range_table = board->ai_range;
692 s->insn_read = das800_ai_insn_read;
693 if (dev->irq) {
694 s->subdev_flags |= SDF_CMD_READ;
695 s->len_chanlist = 8;
696 s->do_cmdtest = das800_ai_do_cmdtest;
697 s->do_cmd = das800_ai_do_cmd;
698 s->cancel = das800_cancel;
699 }
700
701 /* Digital Input subdevice */
702 s = &dev->subdevices[1];
703 s->type = COMEDI_SUBD_DI;
704 s->subdev_flags = SDF_READABLE;
705 s->n_chan = 3;
706 s->maxdata = 1;
707 s->range_table = &range_digital;
708 s->insn_bits = das800_di_insn_bits;
709
710 /* Digital Output subdevice */
711 s = &dev->subdevices[2];
712 s->type = COMEDI_SUBD_DO;
713 s->subdev_flags = SDF_WRITABLE;
714 s->n_chan = 4;
715 s->maxdata = 1;
716 s->range_table = &range_digital;
717 s->insn_bits = das800_do_insn_bits;
718
719 das800_disable(dev);
720
721 /* initialize digital out channels */
722 spin_lock_irqsave(&dev->spinlock, irq_flags);
723 das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits, CONTROL1);
724 spin_unlock_irqrestore(lock: &dev->spinlock, flags: irq_flags);
725
726 return 0;
727};
728
729static struct comedi_driver driver_das800 = {
730 .driver_name = "das800",
731 .module = THIS_MODULE,
732 .attach = das800_attach,
733 .detach = comedi_legacy_detach,
734 .num_names = ARRAY_SIZE(das800_boards),
735 .board_name = &das800_boards[0].name,
736 .offset = sizeof(struct das800_board),
737};
738module_comedi_driver(driver_das800);
739
740MODULE_AUTHOR("Comedi https://www.comedi.org");
741MODULE_DESCRIPTION("Comedi low-level driver");
742MODULE_LICENSE("GPL");
743

source code of linux/drivers/comedi/drivers/das800.c