pcmuio.c revision 0bdab509bf9c6d838dc0a3b1d68bbf841fc20b5a
1/*
2 * pcmuio.c
3 * Comedi driver for Winsystems PC-104 based 48/96-channel DIO boards.
4 *
5 * COMEDI - Linux Control and Measurement Device Interface
6 * Copyright (C) 2006 Calin A. Culianu <calin@ajvar.org>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 */
18
19/*
20 * Driver: pcmuio
21 * Description: Winsystems PC-104 based 48/96-channel DIO boards.
22 * Devices: (Winsystems) PCM-UIO48A [pcmuio48]
23 *	    (Winsystems) PCM-UIO96A [pcmuio96]
24 * Author: Calin Culianu <calin@ajvar.org>
25 * Updated: Fri, 13 Jan 2006 12:01:01 -0500
26 * Status: works
27 *
28 * A driver for the relatively straightforward-to-program PCM-UIO48A and
29 * PCM-UIO96A boards from Winsystems. These boards use either one or two
30 * (in the 96-DIO version) WS16C48 ASIC HighDensity I/O Chips (HDIO). This
31 * chip is interesting in that each I/O line is individually programmable
32 * for INPUT or OUTPUT (thus comedi_dio_config can be done on a per-channel
33 * basis). Also, each chip supports edge-triggered interrupts for the first
34 * 24 I/O lines. Of course, since the 96-channel version of the board has
35 * two ASICs, it can detect polarity changes on up to 48 I/O lines. Since
36 * this is essentially an (non-PnP) ISA board, I/O Address and IRQ selection
37 * are done through jumpers on the board. You need to pass that information
38 * to this driver as the first and second comedi_config option, respectively.
39 * Note that the 48-channel version uses 16 bytes of IO memory and the 96-
40 * channel version uses 32-bytes (in case you are worried about conflicts).
41 * The 48-channel board is split into two 24-channel comedi subdevices. The
42 * 96-channel board is split into 4 24-channel DIO subdevices.
43 *
44 * Note that IRQ support has been added, but it is untested.
45 *
46 * To use edge-detection IRQ support, pass the IRQs of both ASICS (for the
47 * 96 channel version) or just 1 ASIC (for 48-channel version). Then, use
48 * comedi_commands with TRIG_NOW. Your callback will be called each time an
49 * edge is triggered, and the data values will be two sample_t's, which
50 * should be concatenated to form one 32-bit unsigned int.  This value is
51 * the mask of channels that had edges detected from your channel list. Note
52 * that the bits positions in the mask correspond to positions in your
53 * chanlist when you specified the command and *not* channel id's!
54 *
55 * To set the polarity of the edge-detection interrupts pass a nonzero value
56 * for either CR_RANGE or CR_AREF for edge-up polarity, or a zero value for
57 * both CR_RANGE and CR_AREF if you want edge-down polarity.
58 *
59 * In the 48-channel version:
60 *
61 * On subdev 0, the first 24 channels channels are edge-detect channels.
62 *
63 * In the 96-channel board you have the following channels that can do edge
64 * detection:
65 *
66 * subdev 0, channels 0-24  (first 24 channels of 1st ASIC)
67 * subdev 2, channels 0-24  (first 24 channels of 2nd ASIC)
68 *
69 * Configuration Options:
70 *  [0] - I/O port base address
71 *  [1] - IRQ (for first ASIC, or first 24 channels)
72 *  [2] - IRQ (for second ASIC, pcmuio96 only - IRQ for chans 48-72
73 *             can be the same as first irq!)
74 */
75
76#include <linux/interrupt.h>
77#include <linux/slab.h>
78
79#include "../comedidev.h"
80
81#include "comedi_fc.h"
82
83/*
84 * Register I/O map
85 *
86 * Offset    Page 0       Page 1       Page 2       Page 3
87 * ------  -----------  -----------  -----------  -----------
88 *  0x00   Port 0 I/O   Port 0 I/O   Port 0 I/O   Port 0 I/O
89 *  0x01   Port 1 I/O   Port 1 I/O   Port 1 I/O   Port 1 I/O
90 *  0x02   Port 2 I/O   Port 2 I/O   Port 2 I/O   Port 2 I/O
91 *  0x03   Port 3 I/O   Port 3 I/O   Port 3 I/O   Port 3 I/O
92 *  0x04   Port 4 I/O   Port 4 I/O   Port 4 I/O   Port 4 I/O
93 *  0x05   Port 5 I/O   Port 5 I/O   Port 5 I/O   Port 5 I/O
94 *  0x06   INT_PENDING  INT_PENDING  INT_PENDING  INT_PENDING
95 *  0x07    Page/Lock    Page/Lock    Page/Lock    Page/Lock
96 *  0x08       N/A         POL_0       ENAB_0       INT_ID0
97 *  0x09       N/A         POL_1       ENAB_1       INT_ID1
98 *  0x0a       N/A         POL_2       ENAB_2       INT_ID2
99 */
100#define PCMUIO_PORT_REG(x)		(0x00 + (x))
101#define PCMUIO_INT_PENDING_REG		0x06
102#define PCMUIO_PAGE_LOCK_REG		0x07
103#define PCMUIO_LOCK_PORT(x)		((1 << (x)) & 0x3f)
104#define PCMUIO_PAGE(x)			(((x) & 0x3) << 6)
105#define PCMUIO_PAGE_MASK		PCMUIO_PAGE(3)
106#define PCMUIO_PAGE_POL			1
107#define PCMUIO_PAGE_ENAB		2
108#define PCMUIO_PAGE_INT_ID		3
109#define PCMUIO_PAGE_REG(x)		(0x08 + (x))
110
111#define PCMUIO_ASIC_IOSIZE		0x10
112#define PCMUIO_MAX_ASICS		2
113
114struct pcmuio_board {
115	const char *name;
116	const int num_asics;
117};
118
119static const struct pcmuio_board pcmuio_boards[] = {
120	{
121		.name		= "pcmuio48",
122		.num_asics	= 1,
123	}, {
124		.name		= "pcmuio96",
125		.num_asics	= 2,
126	},
127};
128
129struct pcmuio_subdev_private {
130	/* The below is only used for intr subdevices */
131	struct {
132		/* if non-negative, this subdev has an interrupt asic */
133		int asic;
134		/*
135		 * subdev-relative channel mask for channels
136		 * we are interested in
137		 */
138		int enabled_mask;
139		int active;
140		int stop_count;
141		int continuous;
142		spinlock_t spinlock;
143	} intr;
144};
145
146struct pcmuio_private {
147	struct {
148		unsigned int irq;
149		spinlock_t spinlock;
150	} asics[PCMUIO_MAX_ASICS];
151	struct pcmuio_subdev_private *sprivs;
152};
153
154static void pcmuio_write(struct comedi_device *dev, unsigned int val,
155			 int asic, int page, int port)
156{
157	unsigned long iobase = dev->iobase + (asic * PCMUIO_ASIC_IOSIZE);
158
159	if (page == 0) {
160		/* Port registers are valid for any page */
161		outb(val & 0xff, iobase + PCMUIO_PORT_REG(port + 0));
162		outb((val >> 8) & 0xff, iobase + PCMUIO_PORT_REG(port + 1));
163		outb((val >> 16) & 0xff, iobase + PCMUIO_PORT_REG(port + 2));
164	} else {
165		outb(PCMUIO_PAGE(page), iobase + PCMUIO_PAGE_LOCK_REG);
166		outb(val & 0xff, iobase + PCMUIO_PAGE_REG(0));
167		outb((val >> 8) & 0xff, iobase + PCMUIO_PAGE_REG(1));
168		outb((val >> 16) & 0xff, iobase + PCMUIO_PAGE_REG(2));
169	}
170}
171
172static unsigned int pcmuio_read(struct comedi_device *dev,
173				int asic, int page, int port)
174{
175	unsigned long iobase = dev->iobase + (asic * PCMUIO_ASIC_IOSIZE);
176	unsigned int val;
177
178	if (page == 0) {
179		/* Port registers are valid for any page */
180		val = inb(iobase + PCMUIO_PORT_REG(port + 0));
181		val |= (inb(iobase + PCMUIO_PORT_REG(port + 1)) << 8);
182		val |= (inb(iobase + PCMUIO_PORT_REG(port + 2)) << 16);
183	} else {
184		outb(PCMUIO_PAGE(page), iobase + PCMUIO_PAGE_LOCK_REG);
185		val = inb(iobase + PCMUIO_PAGE_REG(0));
186		val |= (inb(iobase + PCMUIO_PAGE_REG(1)) << 8);
187		val |= (inb(iobase + PCMUIO_PAGE_REG(2)) << 16);
188	}
189
190	return val;
191}
192
193/*
194 * Each channel can be individually programmed for input or output.
195 * Writing a '0' to a channel causes the corresponding output pin
196 * to go to a high-z state (pulled high by an external 10K resistor).
197 * This allows it to be used as an input. When used in the input mode,
198 * a read reflects the inverted state of the I/O pin, such that a
199 * high on the pin will read as a '0' in the register. Writing a '1'
200 * to a bit position causes the pin to sink current (up to 12mA),
201 * effectively pulling it low.
202 */
203static int pcmuio_dio_insn_bits(struct comedi_device *dev,
204				struct comedi_subdevice *s,
205				struct comedi_insn *insn, unsigned int *data)
206{
207	unsigned int mask = data[0] & s->io_bits;	/* outputs only */
208	unsigned int bits = data[1];
209	int asic = s->index / 2;
210	int port = (s->index % 2) ? 3 : 0;
211	unsigned int val;
212
213	/* get inverted state of the channels from the port */
214	val = pcmuio_read(dev, asic, 0, port);
215
216	/* get the true state of the channels */
217	s->state = val ^ ((0x1 << s->n_chan) - 1);
218
219	if (mask) {
220		s->state &= ~mask;
221		s->state |= (mask & bits);
222
223		/* invert the state and update the channels */
224		val = s->state ^ ((0x1 << s->n_chan) - 1);
225		pcmuio_write(dev, val, asic, 0, port);
226	}
227
228	data[1] = s->state;
229
230	return insn->n;
231}
232
233static int pcmuio_dio_insn_config(struct comedi_device *dev,
234				  struct comedi_subdevice *s,
235				  struct comedi_insn *insn, unsigned int *data)
236{
237	unsigned int chan_mask = 1 << CR_CHAN(insn->chanspec);
238	int asic = s->index / 2;
239	int port = (s->index % 2) ? 3 : 0;
240
241	switch (data[0]) {
242	case INSN_CONFIG_DIO_OUTPUT:
243		s->io_bits |= chan_mask;
244		break;
245	case INSN_CONFIG_DIO_INPUT:
246		s->io_bits &= ~chan_mask;
247		pcmuio_write(dev, s->io_bits, asic, 0, port);
248		break;
249	case INSN_CONFIG_DIO_QUERY:
250		data[1] = (s->io_bits & chan_mask) ? COMEDI_OUTPUT : COMEDI_INPUT;
251		break;
252	default:
253		return -EINVAL;
254		break;
255	}
256
257	return insn->n;
258}
259
260static void pcmuio_reset(struct comedi_device *dev)
261{
262	const struct pcmuio_board *board = comedi_board(dev);
263	int asic;
264
265	for (asic = 0; asic < board->num_asics; ++asic) {
266		/* first, clear all the DIO port bits */
267		pcmuio_write(dev, 0, asic, 0, 0);
268		pcmuio_write(dev, 0, asic, 0, 3);
269
270		/* Next, clear all the paged registers for each page */
271		pcmuio_write(dev, 0, asic, PCMUIO_PAGE_POL, 0);
272		pcmuio_write(dev, 0, asic, PCMUIO_PAGE_ENAB, 0);
273		pcmuio_write(dev, 0, asic, PCMUIO_PAGE_INT_ID, 0);
274	}
275}
276
277static void pcmuio_stop_intr(struct comedi_device *dev,
278			     struct comedi_subdevice *s)
279{
280	struct pcmuio_subdev_private *subpriv = s->private;
281	int asic;
282
283	asic = subpriv->intr.asic;
284	if (asic < 0)
285		return;		/* not an interrupt subdev */
286
287	subpriv->intr.enabled_mask = 0;
288	subpriv->intr.active = 0;
289	s->async->inttrig = NULL;
290
291	/* disable all intrs for this subdev.. */
292	pcmuio_write(dev, 0, asic, PCMUIO_PAGE_ENAB, 0);
293}
294
295static void pcmuio_handle_intr_subdev(struct comedi_device *dev,
296				      struct comedi_subdevice *s,
297				      unsigned triggered)
298{
299	struct pcmuio_subdev_private *subpriv = s->private;
300	unsigned int len = s->async->cmd.chanlist_len;
301	unsigned oldevents = s->async->events;
302	unsigned int val = 0;
303	unsigned long flags;
304	unsigned mytrig;
305	unsigned int i;
306
307	spin_lock_irqsave(&subpriv->intr.spinlock, flags);
308
309	if (!subpriv->intr.active)
310		goto done;
311
312	mytrig = triggered;
313	mytrig &= ((0x1 << s->n_chan) - 1);
314
315	if (!(mytrig & subpriv->intr.enabled_mask))
316		goto done;
317
318	for (i = 0; i < len; i++) {
319		unsigned int chan = CR_CHAN(s->async->cmd.chanlist[i]);
320		if (mytrig & (1U << chan))
321			val |= (1U << i);
322	}
323
324	/* Write the scan to the buffer. */
325	if (comedi_buf_put(s->async, ((short *)&val)[0]) &&
326	    comedi_buf_put(s->async, ((short *)&val)[1])) {
327		s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS);
328	} else {
329		/* Overflow! Stop acquisition!! */
330		/* TODO: STOP_ACQUISITION_CALL_HERE!! */
331		pcmuio_stop_intr(dev, s);
332	}
333
334	/* Check for end of acquisition. */
335	if (!subpriv->intr.continuous) {
336		/* stop_src == TRIG_COUNT */
337		if (subpriv->intr.stop_count > 0) {
338			subpriv->intr.stop_count--;
339			if (subpriv->intr.stop_count == 0) {
340				s->async->events |= COMEDI_CB_EOA;
341				/* TODO: STOP_ACQUISITION_CALL_HERE!! */
342				pcmuio_stop_intr(dev, s);
343			}
344		}
345	}
346
347done:
348	spin_unlock_irqrestore(&subpriv->intr.spinlock, flags);
349
350	if (oldevents != s->async->events)
351		comedi_event(dev, s);
352}
353
354static int pcmuio_handle_asic_interrupt(struct comedi_device *dev, int asic)
355{
356	struct pcmuio_private *devpriv = dev->private;
357	struct pcmuio_subdev_private *subpriv;
358	unsigned long iobase = dev->iobase + (asic * PCMUIO_ASIC_IOSIZE);
359	unsigned int triggered = 0;
360	int got1 = 0;
361	unsigned long flags;
362	unsigned char int_pend;
363	int i;
364
365	spin_lock_irqsave(&devpriv->asics[asic].spinlock, flags);
366
367	int_pend = inb(iobase + PCMUIO_INT_PENDING_REG) & 0x07;
368	if (int_pend) {
369		triggered = pcmuio_read(dev, asic, PCMUIO_PAGE_INT_ID, 0);
370		pcmuio_write(dev, 0, asic, PCMUIO_PAGE_INT_ID, 0);
371
372		++got1;
373	}
374
375	spin_unlock_irqrestore(&devpriv->asics[asic].spinlock, flags);
376
377	if (triggered) {
378		struct comedi_subdevice *s;
379		/* TODO here: dispatch io lines to subdevs with commands.. */
380		for (i = 0; i < dev->n_subdevices; i++) {
381			s = &dev->subdevices[i];
382			subpriv = s->private;
383			if (subpriv->intr.asic == asic) {
384				/*
385				 * This is an interrupt subdev, and it
386				 * matches this asic!
387				 */
388				pcmuio_handle_intr_subdev(dev, s,
389							  triggered);
390			}
391		}
392	}
393	return got1;
394}
395
396static irqreturn_t pcmuio_interrupt(int irq, void *d)
397{
398	struct comedi_device *dev = d;
399	struct pcmuio_private *devpriv = dev->private;
400	int got1 = 0;
401	int asic;
402
403	for (asic = 0; asic < PCMUIO_MAX_ASICS; ++asic) {
404		if (irq == devpriv->asics[asic].irq) {
405			/* it is an interrupt for ASIC #asic */
406			if (pcmuio_handle_asic_interrupt(dev, asic))
407				got1++;
408		}
409	}
410	if (!got1)
411		return IRQ_NONE;	/* interrupt from other source */
412	return IRQ_HANDLED;
413}
414
415static int pcmuio_start_intr(struct comedi_device *dev,
416			     struct comedi_subdevice *s)
417{
418	struct pcmuio_subdev_private *subpriv = s->private;
419
420	if (!subpriv->intr.continuous && subpriv->intr.stop_count == 0) {
421		/* An empty acquisition! */
422		s->async->events |= COMEDI_CB_EOA;
423		subpriv->intr.active = 0;
424		return 1;
425	} else {
426		unsigned bits = 0, pol_bits = 0, n;
427		int asic;
428		struct comedi_cmd *cmd = &s->async->cmd;
429
430		asic = subpriv->intr.asic;
431		if (asic < 0)
432			return 1;	/* not an interrupt
433					   subdev */
434		subpriv->intr.enabled_mask = 0;
435		subpriv->intr.active = 1;
436		if (cmd->chanlist) {
437			for (n = 0; n < cmd->chanlist_len; n++) {
438				bits |= (1U << CR_CHAN(cmd->chanlist[n]));
439				pol_bits |= (CR_AREF(cmd->chanlist[n])
440					     || CR_RANGE(cmd->
441							 chanlist[n]) ? 1U : 0U)
442				    << CR_CHAN(cmd->chanlist[n]);
443			}
444		}
445		bits &= ((0x1 << s->n_chan) - 1);
446		subpriv->intr.enabled_mask = bits;
447
448		/* set pol and enab intrs for this subdev.. */
449		pcmuio_write(dev, pol_bits, asic, PCMUIO_PAGE_POL, 0);
450		pcmuio_write(dev, bits, asic, PCMUIO_PAGE_ENAB, 0);
451	}
452	return 0;
453}
454
455static int pcmuio_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
456{
457	struct pcmuio_subdev_private *subpriv = s->private;
458	unsigned long flags;
459
460	spin_lock_irqsave(&subpriv->intr.spinlock, flags);
461	if (subpriv->intr.active)
462		pcmuio_stop_intr(dev, s);
463	spin_unlock_irqrestore(&subpriv->intr.spinlock, flags);
464
465	return 0;
466}
467
468/*
469 * Internal trigger function to start acquisition for an 'INTERRUPT' subdevice.
470 */
471static int
472pcmuio_inttrig_start_intr(struct comedi_device *dev, struct comedi_subdevice *s,
473			  unsigned int trignum)
474{
475	struct pcmuio_subdev_private *subpriv = s->private;
476	unsigned long flags;
477	int event = 0;
478
479	if (trignum != 0)
480		return -EINVAL;
481
482	spin_lock_irqsave(&subpriv->intr.spinlock, flags);
483	s->async->inttrig = NULL;
484	if (subpriv->intr.active)
485		event = pcmuio_start_intr(dev, s);
486
487	spin_unlock_irqrestore(&subpriv->intr.spinlock, flags);
488
489	if (event)
490		comedi_event(dev, s);
491
492	return 1;
493}
494
495/*
496 * 'do_cmd' function for an 'INTERRUPT' subdevice.
497 */
498static int pcmuio_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
499{
500	struct pcmuio_subdev_private *subpriv = s->private;
501	struct comedi_cmd *cmd = &s->async->cmd;
502	unsigned long flags;
503	int event = 0;
504
505	spin_lock_irqsave(&subpriv->intr.spinlock, flags);
506	subpriv->intr.active = 1;
507
508	/* Set up end of acquisition. */
509	switch (cmd->stop_src) {
510	case TRIG_COUNT:
511		subpriv->intr.continuous = 0;
512		subpriv->intr.stop_count = cmd->stop_arg;
513		break;
514	default:
515		/* TRIG_NONE */
516		subpriv->intr.continuous = 1;
517		subpriv->intr.stop_count = 0;
518		break;
519	}
520
521	/* Set up start of acquisition. */
522	switch (cmd->start_src) {
523	case TRIG_INT:
524		s->async->inttrig = pcmuio_inttrig_start_intr;
525		break;
526	default:
527		/* TRIG_NOW */
528		event = pcmuio_start_intr(dev, s);
529		break;
530	}
531	spin_unlock_irqrestore(&subpriv->intr.spinlock, flags);
532
533	if (event)
534		comedi_event(dev, s);
535
536	return 0;
537}
538
539static int pcmuio_cmdtest(struct comedi_device *dev,
540			  struct comedi_subdevice *s,
541			  struct comedi_cmd *cmd)
542{
543	int err = 0;
544
545	/* Step 1 : check if triggers are trivially valid */
546
547	err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
548	err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
549	err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
550	err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
551	err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
552
553	if (err)
554		return 1;
555
556	/* Step 2a : make sure trigger sources are unique */
557
558	err |= cfc_check_trigger_is_unique(cmd->start_src);
559	err |= cfc_check_trigger_is_unique(cmd->stop_src);
560
561	/* Step 2b : and mutually compatible */
562
563	if (err)
564		return 2;
565
566	/* Step 3: check if arguments are trivially valid */
567
568	err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
569	err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
570	err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
571	err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
572
573	switch (cmd->stop_src) {
574	case TRIG_COUNT:
575		/* any count allowed */
576		break;
577	case TRIG_NONE:
578		err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
579		break;
580	default:
581		break;
582	}
583
584	if (err)
585		return 3;
586
587	/* step 4: fix up any arguments */
588
589	/* if (err) return 4; */
590
591	return 0;
592}
593
594static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
595{
596	const struct pcmuio_board *board = comedi_board(dev);
597	struct comedi_subdevice *s;
598	struct pcmuio_private *devpriv;
599	struct pcmuio_subdev_private *subpriv;
600	int sdev_no, n_subdevs, asic;
601	unsigned int irq[PCMUIO_MAX_ASICS];
602	int ret;
603
604	irq[0] = it->options[1];
605	irq[1] = it->options[2];
606
607	ret = comedi_request_region(dev, it->options[0],
608				    board->num_asics * PCMUIO_ASIC_IOSIZE);
609	if (ret)
610		return ret;
611
612	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
613	if (!devpriv)
614		return -ENOMEM;
615
616	for (asic = 0; asic < PCMUIO_MAX_ASICS; ++asic)
617		spin_lock_init(&devpriv->asics[asic].spinlock);
618
619	n_subdevs = board->num_asics * 2;
620	devpriv->sprivs = kcalloc(n_subdevs, sizeof(*subpriv), GFP_KERNEL);
621	if (!devpriv->sprivs)
622		return -ENOMEM;
623
624	ret = comedi_alloc_subdevices(dev, n_subdevs);
625	if (ret)
626		return ret;
627
628	for (sdev_no = 0; sdev_no < (int)dev->n_subdevices; ++sdev_no) {
629		s = &dev->subdevices[sdev_no];
630		subpriv = &devpriv->sprivs[sdev_no];
631		s->private = subpriv;
632		s->maxdata = 1;
633		s->range_table = &range_digital;
634		s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
635		s->type = COMEDI_SUBD_DIO;
636		s->insn_bits = pcmuio_dio_insn_bits;
637		s->insn_config = pcmuio_dio_insn_config;
638		s->n_chan = 24;
639
640		/* subdevices 0 and 2 suppport interrupts */
641		if ((sdev_no % 2) == 0) {
642			/* setup the interrupt subdevice */
643			subpriv->intr.asic = sdev_no / 2;
644			dev->read_subdev = s;
645			s->subdev_flags |= SDF_CMD_READ;
646			s->cancel = pcmuio_cancel;
647			s->do_cmd = pcmuio_cmd;
648			s->do_cmdtest = pcmuio_cmdtest;
649			s->len_chanlist = s->n_chan;
650		} else {
651			subpriv->intr.asic = -1;
652			s->len_chanlist = 1;
653		}
654		spin_lock_init(&subpriv->intr.spinlock);
655	}
656
657	pcmuio_reset(dev);
658
659	for (asic = 0; irq[0] && asic < PCMUIO_MAX_ASICS; ++asic) {
660		if (irq[asic]
661		    && request_irq(irq[asic], pcmuio_interrupt,
662				   IRQF_SHARED, board->name, dev)) {
663			int i;
664			/* unroll the allocated irqs.. */
665			for (i = asic - 1; i >= 0; --i) {
666				free_irq(irq[i], dev);
667				devpriv->asics[i].irq = irq[i] = 0;
668			}
669			irq[asic] = 0;
670		}
671		devpriv->asics[asic].irq = irq[asic];
672	}
673
674	return 0;
675}
676
677static void pcmuio_detach(struct comedi_device *dev)
678{
679	struct pcmuio_private *devpriv = dev->private;
680	int i;
681
682	for (i = 0; i < PCMUIO_MAX_ASICS; ++i) {
683		if (devpriv->asics[i].irq)
684			free_irq(devpriv->asics[i].irq, dev);
685	}
686	if (devpriv && devpriv->sprivs)
687		kfree(devpriv->sprivs);
688	comedi_legacy_detach(dev);
689}
690
691static struct comedi_driver pcmuio_driver = {
692	.driver_name	= "pcmuio",
693	.module		= THIS_MODULE,
694	.attach		= pcmuio_attach,
695	.detach		= pcmuio_detach,
696	.board_name	= &pcmuio_boards[0].name,
697	.offset		= sizeof(struct pcmuio_board),
698	.num_names	= ARRAY_SIZE(pcmuio_boards),
699};
700module_comedi_driver(pcmuio_driver);
701
702MODULE_AUTHOR("Comedi http://www.comedi.org");
703MODULE_DESCRIPTION("Comedi low-level driver");
704MODULE_LICENSE("GPL");
705