das16m1.c revision 0a85b6f0ab0d2edb0d41b32697111ce0e4f43496
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.
20
21    You should have received a copy of the GNU General Public License
22    along with this program; if not, write to the Free Software
23    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24
25************************************************************************
26*/
27/*
28Driver: das16m1
29Description: CIO-DAS16/M1
30Author: Frank Mori Hess <fmhess@users.sourceforge.net>
31Devices: [Measurement Computing] CIO-DAS16/M1 (cio-das16/m1)
32Status: works
33
34This driver supports a single board - the CIO-DAS16/M1.
35As far as I know, there are no other boards that have
36the same register layout.  Even the CIO-DAS16/M1/16 is
37significantly different.
38
39I was _barely_ able to reach the full 1 MHz capability
40of this board, using a hard real-time interrupt
41(set the TRIG_RT flag in your struct comedi_cmd and use
42rtlinux or RTAI).  The board can't do dma, so the bottleneck is
43pulling the data across the ISA bus.  I timed the interrupt
44handler, and it took my computer ~470 microseconds to pull 512
45samples from the board.  So at 1 Mhz sampling rate,
46expect your CPU to be spending almost all of its
47time in the interrupt handler.
48
49This board has some unusual restrictions for its channel/gain list.  If the
50list has 2 or more channels in it, then two conditions must be satisfied:
51(1) - even/odd channels must appear at even/odd indices in the list
52(2) - the list must have an even number of entries.
53
54Options:
55        [0] - base io address
56        [1] - irq (optional, but you probably want it)
57
58irq can be omitted, although the cmd interface will not work without it.
59*/
60
61#include <linux/ioport.h>
62#include <linux/interrupt.h>
63#include "../comedidev.h"
64
65#include "8255.h"
66#include "8253.h"
67#include "comedi_fc.h"
68
69#define DAS16M1_SIZE 16
70#define DAS16M1_SIZE2 8
71
72#define DAS16M1_XTAL 100	/* 10 MHz master clock */
73
74#define FIFO_SIZE 1024		/*  1024 sample fifo */
75
76/*
77    CIO-DAS16_M1.pdf
78
79    "cio-das16/m1"
80
81  0	a/d bits 0-3, mux		start 12 bit
82  1	a/d bits 4-11		unused
83  2	status		control
84  3	di 4 bit		do 4 bit
85  4	unused			clear interrupt
86  5	interrupt, pacer
87  6	channel/gain queue address
88  7	channel/gain queue data
89  89ab	8254
90  cdef	8254
91  400	8255
92  404-407 	8254
93
94*/
95
96#define DAS16M1_AI             0	/*  16-bit wide register */
97#define   AI_CHAN(x)             ((x) & 0xf)
98#define DAS16M1_CS             2
99#define   EXT_TRIG_BIT           0x1
100#define   OVRUN                  0x20
101#define   IRQDATA                0x80
102#define DAS16M1_DIO            3
103#define DAS16M1_CLEAR_INTR     4
104#define DAS16M1_INTR_CONTROL   5
105#define   EXT_PACER              0x2
106#define   INT_PACER              0x3
107#define   PACER_MASK             0x3
108#define   INTE                   0x80
109#define DAS16M1_QUEUE_ADDR     6
110#define DAS16M1_QUEUE_DATA     7
111#define   Q_CHAN(x)              ((x) & 0x7)
112#define   Q_RANGE(x)             (((x) & 0xf) << 4)
113#define   UNIPOLAR               0x40
114#define DAS16M1_8254_FIRST             0x8
115#define DAS16M1_8254_FIRST_CNTRL       0xb
116#define   TOTAL_CLEAR                    0x30
117#define DAS16M1_8254_SECOND            0xc
118#define DAS16M1_82C55                  0x400
119#define DAS16M1_8254_THIRD             0x404
120
121static const struct comedi_lrange range_das16m1 = { 9,
122	{
123	 BIP_RANGE(5),
124	 BIP_RANGE(2.5),
125	 BIP_RANGE(1.25),
126	 BIP_RANGE(0.625),
127	 UNI_RANGE(10),
128	 UNI_RANGE(5),
129	 UNI_RANGE(2.5),
130	 UNI_RANGE(1.25),
131	 BIP_RANGE(10),
132	 }
133};
134
135static int das16m1_do_wbits(struct comedi_device *dev,
136			    struct comedi_subdevice *s,
137			    struct comedi_insn *insn, unsigned int *data);
138static int das16m1_di_rbits(struct comedi_device *dev,
139			    struct comedi_subdevice *s,
140			    struct comedi_insn *insn, unsigned int *data);
141static int das16m1_ai_rinsn(struct comedi_device *dev,
142			    struct comedi_subdevice *s,
143			    struct comedi_insn *insn, unsigned int *data);
144
145static int das16m1_cmd_test(struct comedi_device *dev,
146			    struct comedi_subdevice *s, struct comedi_cmd *cmd);
147static int das16m1_cmd_exec(struct comedi_device *dev,
148			    struct comedi_subdevice *s);
149static int das16m1_cancel(struct comedi_device *dev,
150			  struct comedi_subdevice *s);
151
152static int das16m1_poll(struct comedi_device *dev, struct comedi_subdevice *s);
153static irqreturn_t das16m1_interrupt(int irq, void *d);
154static void das16m1_handler(struct comedi_device *dev, unsigned int status);
155
156static unsigned int das16m1_set_pacer(struct comedi_device *dev,
157				      unsigned int ns, int round_flag);
158
159static int das16m1_irq_bits(unsigned int irq);
160
161struct das16m1_board {
162	const char *name;
163	unsigned int ai_speed;
164};
165
166static const struct das16m1_board das16m1_boards[] = {
167	{
168	 .name = "cio-das16/m1",	/*  CIO-DAS16_M1.pdf */
169	 .ai_speed = 1000,	/*  1MHz max speed */
170	 },
171};
172
173static int das16m1_attach(struct comedi_device *dev,
174			  struct comedi_devconfig *it);
175static int das16m1_detach(struct comedi_device *dev);
176static struct comedi_driver driver_das16m1 = {
177	.driver_name = "das16m1",
178	.module = THIS_MODULE,
179	.attach = das16m1_attach,
180	.detach = das16m1_detach,
181	.board_name = &das16m1_boards[0].name,
182	.num_names = ARRAY_SIZE(das16m1_boards),
183	.offset = sizeof(das16m1_boards[0]),
184};
185
186struct das16m1_private_struct {
187	unsigned int control_state;
188	volatile unsigned int adc_count;	/*  number of samples completed */
189	/* initial value in lower half of hardware conversion counter,
190	 * needed to keep track of whether new count has been loaded into
191	 * counter yet (loaded by first sample conversion) */
192	u16 initial_hw_count;
193	short ai_buffer[FIFO_SIZE];
194	unsigned int do_bits;	/*  saves status of digital output bits */
195	unsigned int divisor1;	/*  divides master clock to obtain conversion speed */
196	unsigned int divisor2;	/*  divides master clock to obtain conversion speed */
197};
198#define devpriv ((struct das16m1_private_struct *)(dev->private))
199#define thisboard ((const struct das16m1_board *)(dev->board_ptr))
200
201COMEDI_INITCLEANUP(driver_das16m1);
202
203static inline short munge_sample(short data)
204{
205	return (data >> 4) & 0xfff;
206}
207
208static int das16m1_cmd_test(struct comedi_device *dev,
209			    struct comedi_subdevice *s, struct comedi_cmd *cmd)
210{
211	unsigned int err = 0, tmp, i;
212
213	/* make sure triggers are valid */
214	tmp = cmd->start_src;
215	cmd->start_src &= TRIG_NOW | TRIG_EXT;
216	if (!cmd->start_src || tmp != cmd->start_src)
217		err++;
218
219	tmp = cmd->scan_begin_src;
220	cmd->scan_begin_src &= TRIG_FOLLOW;
221	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
222		err++;
223
224	tmp = cmd->convert_src;
225	cmd->convert_src &= TRIG_TIMER | TRIG_EXT;
226	if (!cmd->convert_src || tmp != cmd->convert_src)
227		err++;
228
229	tmp = cmd->scan_end_src;
230	cmd->scan_end_src &= TRIG_COUNT;
231	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
232		err++;
233
234	tmp = cmd->stop_src;
235	cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
236	if (!cmd->stop_src || tmp != cmd->stop_src)
237		err++;
238
239	if (err)
240		return 1;
241
242	/* step 2: make sure trigger sources are unique and mutually compatible */
243	if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
244		err++;
245	if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT)
246		err++;
247	if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT)
248		err++;
249
250	if (err)
251		return 2;
252
253	/* step 3: make sure arguments are trivially compatible */
254	if (cmd->start_arg != 0) {
255		cmd->start_arg = 0;
256		err++;
257	}
258
259	if (cmd->scan_begin_src == TRIG_FOLLOW) {
260		/* internal trigger */
261		if (cmd->scan_begin_arg != 0) {
262			cmd->scan_begin_arg = 0;
263			err++;
264		}
265	}
266
267	if (cmd->convert_src == TRIG_TIMER) {
268		if (cmd->convert_arg < thisboard->ai_speed) {
269			cmd->convert_arg = thisboard->ai_speed;
270			err++;
271		}
272	}
273
274	if (cmd->scan_end_arg != cmd->chanlist_len) {
275		cmd->scan_end_arg = cmd->chanlist_len;
276		err++;
277	}
278
279	if (cmd->stop_src == TRIG_COUNT) {
280		/* any count is allowed */
281	} else {
282		/* TRIG_NONE */
283		if (cmd->stop_arg != 0) {
284			cmd->stop_arg = 0;
285			err++;
286		}
287	}
288
289	if (err)
290		return 3;
291
292	/* step 4: fix up arguments */
293
294	if (cmd->convert_src == TRIG_TIMER) {
295		tmp = cmd->convert_arg;
296		/* calculate counter values that give desired timing */
297		i8253_cascade_ns_to_timer_2div(DAS16M1_XTAL,
298					       &(devpriv->divisor1),
299					       &(devpriv->divisor2),
300					       &(cmd->convert_arg),
301					       cmd->flags & TRIG_ROUND_MASK);
302		if (tmp != cmd->convert_arg)
303			err++;
304	}
305
306	if (err)
307		return 4;
308
309	/*  check chanlist against board's peculiarities */
310	if (cmd->chanlist && cmd->chanlist_len > 1) {
311		for (i = 0; i < cmd->chanlist_len; i++) {
312			/*  even/odd channels must go into even/odd queue addresses */
313			if ((i % 2) != (CR_CHAN(cmd->chanlist[i]) % 2)) {
314				comedi_error(dev, "bad chanlist:\n"
315					     " even/odd channels must go have even/odd chanlist indices");
316				err++;
317			}
318		}
319		if ((cmd->chanlist_len % 2) != 0) {
320			comedi_error(dev,
321				     "chanlist must be of even length or length 1");
322			err++;
323		}
324	}
325
326	if (err)
327		return 5;
328
329	return 0;
330}
331
332static int das16m1_cmd_exec(struct comedi_device *dev,
333			    struct comedi_subdevice *s)
334{
335	struct comedi_async *async = s->async;
336	struct comedi_cmd *cmd = &async->cmd;
337	unsigned int byte, i;
338
339	if (dev->irq == 0) {
340		comedi_error(dev, "irq required to execute comedi_cmd");
341		return -1;
342	}
343
344	/* disable interrupts and internal pacer */
345	devpriv->control_state &= ~INTE & ~PACER_MASK;
346	outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
347
348	/*  set software count */
349	devpriv->adc_count = 0;
350	/* Initialize lower half of hardware counter, used to determine how
351	 * many samples are in fifo.  Value doesn't actually load into counter
352	 * until counter's next clock (the next a/d conversion) */
353	i8254_load(dev->iobase + DAS16M1_8254_FIRST, 0, 1, 0, 2);
354	/* remember current reading of counter so we know when counter has
355	 * actually been loaded */
356	devpriv->initial_hw_count =
357	    i8254_read(dev->iobase + DAS16M1_8254_FIRST, 0, 1);
358	/* setup channel/gain queue */
359	for (i = 0; i < cmd->chanlist_len; i++) {
360		outb(i, dev->iobase + DAS16M1_QUEUE_ADDR);
361		byte =
362		    Q_CHAN(CR_CHAN(cmd->chanlist[i])) |
363		    Q_RANGE(CR_RANGE(cmd->chanlist[i]));
364		outb(byte, dev->iobase + DAS16M1_QUEUE_DATA);
365	}
366
367	/* set counter mode and counts */
368	cmd->convert_arg =
369	    das16m1_set_pacer(dev, cmd->convert_arg,
370			      cmd->flags & TRIG_ROUND_MASK);
371
372	/*  set control & status register */
373	byte = 0;
374	/* if we are using external start trigger (also board dislikes having
375	 * both start and conversion triggers external simultaneously) */
376	if (cmd->start_src == TRIG_EXT && cmd->convert_src != TRIG_EXT) {
377		byte |= EXT_TRIG_BIT;
378	}
379	outb(byte, dev->iobase + DAS16M1_CS);
380	/* clear interrupt bit */
381	outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
382
383	/* enable interrupts and internal pacer */
384	devpriv->control_state &= ~PACER_MASK;
385	if (cmd->convert_src == TRIG_TIMER) {
386		devpriv->control_state |= INT_PACER;
387	} else {
388		devpriv->control_state |= EXT_PACER;
389	}
390	devpriv->control_state |= INTE;
391	outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
392
393	return 0;
394}
395
396static int das16m1_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
397{
398	devpriv->control_state &= ~INTE & ~PACER_MASK;
399	outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
400
401	return 0;
402}
403
404static int das16m1_ai_rinsn(struct comedi_device *dev,
405			    struct comedi_subdevice *s,
406			    struct comedi_insn *insn, unsigned int *data)
407{
408	int i, n;
409	int byte;
410	const int timeout = 1000;
411
412	/* disable interrupts and internal pacer */
413	devpriv->control_state &= ~INTE & ~PACER_MASK;
414	outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
415
416	/* setup channel/gain queue */
417	outb(0, dev->iobase + DAS16M1_QUEUE_ADDR);
418	byte =
419	    Q_CHAN(CR_CHAN(insn->chanspec)) | Q_RANGE(CR_RANGE(insn->chanspec));
420	outb(byte, dev->iobase + DAS16M1_QUEUE_DATA);
421
422	for (n = 0; n < insn->n; n++) {
423		/* clear IRQDATA bit */
424		outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
425		/* trigger conversion */
426		outb(0, dev->iobase);
427
428		for (i = 0; i < timeout; i++) {
429			if (inb(dev->iobase + DAS16M1_CS) & IRQDATA)
430				break;
431		}
432		if (i == timeout) {
433			comedi_error(dev, "timeout");
434			return -ETIME;
435		}
436		data[n] = munge_sample(inw(dev->iobase));
437	}
438
439	return n;
440}
441
442static int das16m1_di_rbits(struct comedi_device *dev,
443			    struct comedi_subdevice *s,
444			    struct comedi_insn *insn, unsigned int *data)
445{
446	unsigned int bits;
447
448	bits = inb(dev->iobase + DAS16M1_DIO) & 0xf;
449	data[1] = bits;
450	data[0] = 0;
451
452	return 2;
453}
454
455static int das16m1_do_wbits(struct comedi_device *dev,
456			    struct comedi_subdevice *s,
457			    struct comedi_insn *insn, unsigned int *data)
458{
459	unsigned int wbits;
460
461	/*  only set bits that have been masked */
462	data[0] &= 0xf;
463	wbits = devpriv->do_bits;
464	/*  zero bits that have been masked */
465	wbits &= ~data[0];
466	/*  set masked bits */
467	wbits |= data[0] & data[1];
468	devpriv->do_bits = wbits;
469	data[1] = wbits;
470
471	outb(devpriv->do_bits, dev->iobase + DAS16M1_DIO);
472
473	return 2;
474}
475
476static int das16m1_poll(struct comedi_device *dev, struct comedi_subdevice *s)
477{
478	unsigned long flags;
479	unsigned int status;
480
481	/*  prevent race with interrupt handler */
482	spin_lock_irqsave(&dev->spinlock, flags);
483	status = inb(dev->iobase + DAS16M1_CS);
484	das16m1_handler(dev, status);
485	spin_unlock_irqrestore(&dev->spinlock, flags);
486
487	return s->async->buf_write_count - s->async->buf_read_count;
488}
489
490static irqreturn_t das16m1_interrupt(int irq, void *d)
491{
492	int status;
493	struct comedi_device *dev = d;
494
495	if (dev->attached == 0) {
496		comedi_error(dev, "premature interrupt");
497		return IRQ_HANDLED;
498	}
499	/*  prevent race with comedi_poll() */
500	spin_lock(&dev->spinlock);
501
502	status = inb(dev->iobase + DAS16M1_CS);
503
504	if ((status & (IRQDATA | OVRUN)) == 0) {
505		comedi_error(dev, "spurious interrupt");
506		spin_unlock(&dev->spinlock);
507		return IRQ_NONE;
508	}
509
510	das16m1_handler(dev, status);
511
512	/* clear interrupt */
513	outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
514
515	spin_unlock(&dev->spinlock);
516	return IRQ_HANDLED;
517}
518
519static void munge_sample_array(short *array, unsigned int num_elements)
520{
521	unsigned int i;
522
523	for (i = 0; i < num_elements; i++) {
524		array[i] = munge_sample(array[i]);
525	}
526}
527
528static void das16m1_handler(struct comedi_device *dev, unsigned int status)
529{
530	struct comedi_subdevice *s;
531	struct comedi_async *async;
532	struct comedi_cmd *cmd;
533	u16 num_samples;
534	u16 hw_counter;
535
536	s = dev->read_subdev;
537	async = s->async;
538	async->events = 0;
539	cmd = &async->cmd;
540
541	/*  figure out how many samples are in fifo */
542	hw_counter = i8254_read(dev->iobase + DAS16M1_8254_FIRST, 0, 1);
543	/* make sure hardware counter reading is not bogus due to initial value
544	 * not having been loaded yet */
545	if (devpriv->adc_count == 0 && hw_counter == devpriv->initial_hw_count) {
546		num_samples = 0;
547	} else {
548		/* The calculation of num_samples looks odd, but it uses the following facts.
549		 * 16 bit hardware counter is initialized with value of zero (which really
550		 * means 0x1000).  The counter decrements by one on each conversion
551		 * (when the counter decrements from zero it goes to 0xffff).  num_samples
552		 * is a 16 bit variable, so it will roll over in a similar fashion to the
553		 * hardware counter.  Work it out, and this is what you get. */
554		num_samples = -hw_counter - devpriv->adc_count;
555	}
556	/*  check if we only need some of the points */
557	if (cmd->stop_src == TRIG_COUNT) {
558		if (num_samples > cmd->stop_arg * cmd->chanlist_len)
559			num_samples = cmd->stop_arg * cmd->chanlist_len;
560	}
561	/*  make sure we dont try to get too many points if fifo has overrun */
562	if (num_samples > FIFO_SIZE)
563		num_samples = FIFO_SIZE;
564	insw(dev->iobase, devpriv->ai_buffer, num_samples);
565	munge_sample_array(devpriv->ai_buffer, num_samples);
566	cfc_write_array_to_buffer(s, devpriv->ai_buffer,
567				  num_samples * sizeof(short));
568	devpriv->adc_count += num_samples;
569
570	if (cmd->stop_src == TRIG_COUNT) {
571		if (devpriv->adc_count >= cmd->stop_arg * cmd->chanlist_len) {	/* end of acquisition */
572			das16m1_cancel(dev, s);
573			async->events |= COMEDI_CB_EOA;
574		}
575	}
576
577	/* this probably won't catch overruns since the card doesn't generate
578	 * overrun interrupts, but we might as well try */
579	if (status & OVRUN) {
580		das16m1_cancel(dev, s);
581		async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
582		comedi_error(dev, "fifo overflow");
583	}
584
585	comedi_event(dev, s);
586
587}
588
589/* This function takes a time in nanoseconds and sets the     *
590 * 2 pacer clocks to the closest frequency possible. It also  *
591 * returns the actual sampling period.                        */
592static unsigned int das16m1_set_pacer(struct comedi_device *dev,
593				      unsigned int ns, int rounding_flags)
594{
595	i8253_cascade_ns_to_timer_2div(DAS16M1_XTAL, &(devpriv->divisor1),
596				       &(devpriv->divisor2), &ns,
597				       rounding_flags & TRIG_ROUND_MASK);
598
599	/* Write the values of ctr1 and ctr2 into counters 1 and 2 */
600	i8254_load(dev->iobase + DAS16M1_8254_SECOND, 0, 1, devpriv->divisor1,
601		   2);
602	i8254_load(dev->iobase + DAS16M1_8254_SECOND, 0, 2, devpriv->divisor2,
603		   2);
604
605	return ns;
606}
607
608static int das16m1_irq_bits(unsigned int irq)
609{
610	int ret;
611
612	switch (irq) {
613	case 10:
614		ret = 0x0;
615		break;
616	case 11:
617		ret = 0x1;
618		break;
619	case 12:
620		ret = 0x2;
621		break;
622	case 15:
623		ret = 0x3;
624		break;
625	case 2:
626		ret = 0x4;
627		break;
628	case 3:
629		ret = 0x5;
630		break;
631	case 5:
632		ret = 0x6;
633		break;
634	case 7:
635		ret = 0x7;
636		break;
637	default:
638		return -1;
639		break;
640	}
641	return ret << 4;
642}
643
644/*
645 * Options list:
646 *   0  I/O base
647 *   1  IRQ
648 */
649
650static int das16m1_attach(struct comedi_device *dev,
651			  struct comedi_devconfig *it)
652{
653	struct comedi_subdevice *s;
654	int ret;
655	unsigned int irq;
656	unsigned long iobase;
657
658	iobase = it->options[0];
659
660	printk("comedi%d: das16m1:", dev->minor);
661
662	ret = alloc_private(dev, sizeof(struct das16m1_private_struct));
663	if (ret < 0)
664		return ret;
665
666	dev->board_name = thisboard->name;
667
668	printk(" io 0x%lx-0x%lx 0x%lx-0x%lx",
669	       iobase, iobase + DAS16M1_SIZE,
670	       iobase + DAS16M1_82C55, iobase + DAS16M1_82C55 + DAS16M1_SIZE2);
671	if (!request_region(iobase, DAS16M1_SIZE, driver_das16m1.driver_name)) {
672		printk(" I/O port conflict\n");
673		return -EIO;
674	}
675	if (!request_region(iobase + DAS16M1_82C55, DAS16M1_SIZE2,
676			    driver_das16m1.driver_name)) {
677		release_region(iobase, DAS16M1_SIZE);
678		printk(" I/O port conflict\n");
679		return -EIO;
680	}
681	dev->iobase = iobase;
682
683	/* now for the irq */
684	irq = it->options[1];
685	/*  make sure it is valid */
686	if (das16m1_irq_bits(irq) >= 0) {
687		ret = request_irq(irq, das16m1_interrupt, 0,
688				  driver_das16m1.driver_name, dev);
689		if (ret < 0) {
690			printk(", irq unavailable\n");
691			return ret;
692		}
693		dev->irq = irq;
694		printk(", irq %u\n", irq);
695	} else if (irq == 0) {
696		printk(", no irq\n");
697	} else {
698		printk(", invalid irq\n"
699		       " valid irqs are 2, 3, 5, 7, 10, 11, 12, or 15\n");
700		return -EINVAL;
701	}
702
703	ret = alloc_subdevices(dev, 4);
704	if (ret < 0)
705		return ret;
706
707	s = dev->subdevices + 0;
708	dev->read_subdev = s;
709	/* ai */
710	s->type = COMEDI_SUBD_AI;
711	s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
712	s->n_chan = 8;
713	s->subdev_flags = SDF_DIFF;
714	s->len_chanlist = 256;
715	s->maxdata = (1 << 12) - 1;
716	s->range_table = &range_das16m1;
717	s->insn_read = das16m1_ai_rinsn;
718	s->do_cmdtest = das16m1_cmd_test;
719	s->do_cmd = das16m1_cmd_exec;
720	s->cancel = das16m1_cancel;
721	s->poll = das16m1_poll;
722
723	s = dev->subdevices + 1;
724	/* di */
725	s->type = COMEDI_SUBD_DI;
726	s->subdev_flags = SDF_READABLE;
727	s->n_chan = 4;
728	s->maxdata = 1;
729	s->range_table = &range_digital;
730	s->insn_bits = das16m1_di_rbits;
731
732	s = dev->subdevices + 2;
733	/* do */
734	s->type = COMEDI_SUBD_DO;
735	s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
736	s->n_chan = 4;
737	s->maxdata = 1;
738	s->range_table = &range_digital;
739	s->insn_bits = das16m1_do_wbits;
740
741	s = dev->subdevices + 3;
742	/* 8255 */
743	subdev_8255_init(dev, s, NULL, dev->iobase + DAS16M1_82C55);
744
745	/*  disable upper half of hardware conversion counter so it doesn't mess with us */
746	outb(TOTAL_CLEAR, dev->iobase + DAS16M1_8254_FIRST_CNTRL);
747
748	/*  initialize digital output lines */
749	outb(devpriv->do_bits, dev->iobase + DAS16M1_DIO);
750
751	/* set the interrupt level */
752	if (dev->irq)
753		devpriv->control_state = das16m1_irq_bits(dev->irq);
754	else
755		devpriv->control_state = 0;
756	outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
757
758	return 0;
759}
760
761static int das16m1_detach(struct comedi_device *dev)
762{
763	printk("comedi%d: das16m1: remove\n", dev->minor);
764
765/* das16m1_reset(dev); */
766
767	if (dev->subdevices)
768		subdev_8255_cleanup(dev, dev->subdevices + 3);
769
770	if (dev->irq)
771		free_irq(dev->irq, dev);
772
773	if (dev->iobase) {
774		release_region(dev->iobase, DAS16M1_SIZE);
775		release_region(dev->iobase + DAS16M1_82C55, DAS16M1_SIZE2);
776	}
777
778	return 0;
779}
780