amplc_pc236.c revision 1bb6dfc407b7c29d22dc9b5d21db86f50653b46e
1/*
2    comedi/drivers/amplc_pc236.c
3    Driver for Amplicon PC36AT and PCI236 DIO boards.
4
5    Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
6
7    COMEDI - Linux Control and Measurement Device Interface
8    Copyright (C) 2000 David A. Schleef <ds@schleef.org>
9
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 2 of the License, or
13    (at your option) any later version.
14
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23
24*/
25/*
26Driver: amplc_pc236
27Description: Amplicon PC36AT, PCI236
28Author: Ian Abbott <abbotti@mev.co.uk>
29Devices: [Amplicon] PC36AT (pc36at), PCI236 (pci236 or amplc_pc236)
30Updated: Wed, 01 Apr 2009 15:41:25 +0100
31Status: works
32
33Configuration options - PC36AT:
34  [0] - I/O port base address
35  [1] - IRQ (optional)
36
37Configuration options - PCI236:
38  [0] - PCI bus of device (optional)
39  [1] - PCI slot of device (optional)
40  If bus/slot is not specified, the first available PCI device will be
41  used.
42
43The PC36AT ISA board and PCI236 PCI board have a single 8255 appearing
44as subdevice 0.
45
46Subdevice 1 pretends to be a digital input device, but it always returns
470 when read. However, if you run a command with scan_begin_src=TRIG_EXT,
48a rising edge on port C bit 3 acts as an external trigger, which can be
49used to wake up tasks.  This is like the comedi_parport device, but the
50only way to physically disable the interrupt on the PC36AT is to remove
51the IRQ jumper.  If no interrupt is connected, then subdevice 1 is
52unused.
53*/
54
55#include <linux/interrupt.h>
56
57#include "../comedidev.h"
58
59#include "8255.h"
60#include "plx9052.h"
61
62#define PC236_DRIVER_NAME	"amplc_pc236"
63
64/* PCI236 PCI configuration register information */
65#define PCI_VENDOR_ID_AMPLICON 0x14dc
66#define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009
67#define PCI_DEVICE_ID_INVALID 0xffff
68
69/* PC36AT / PCI236 registers */
70
71#define PC236_IO_SIZE		4
72#define PC236_LCR_IO_SIZE	128
73
74/*
75 * INTCSR values for PCI236.
76 */
77/* Disable interrupt, also clear any interrupt there */
78#define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1ENAB_DISABLED \
79	| PLX9052_INTCSR_LI1POL_HIGH \
80	| PLX9052_INTCSR_LI2POL_HIGH \
81	| PLX9052_INTCSR_PCIENAB_DISABLED \
82	| PLX9052_INTCSR_LI1SEL_EDGE \
83	| PLX9052_INTCSR_LI1CLRINT_ASSERTED)
84/* Enable interrupt, also clear any interrupt there. */
85#define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB_ENABLED \
86	| PLX9052_INTCSR_LI1POL_HIGH \
87	| PLX9052_INTCSR_LI2POL_HIGH \
88	| PLX9052_INTCSR_PCIENAB_ENABLED \
89	| PLX9052_INTCSR_LI1SEL_EDGE \
90	| PLX9052_INTCSR_LI1CLRINT_ASSERTED)
91
92/*
93 * Board descriptions for Amplicon PC36AT and PCI236.
94 */
95
96enum pc236_bustype { isa_bustype, pci_bustype };
97enum pc236_model { pc36at_model, pci236_model, anypci_model };
98
99struct pc236_board {
100	const char *name;
101	const char *fancy_name;
102	unsigned short devid;
103	enum pc236_bustype bustype;
104	enum pc236_model model;
105};
106static const struct pc236_board pc236_boards[] = {
107#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
108	{
109	 .name = "pc36at",
110	 .fancy_name = "PC36AT",
111	 .bustype = isa_bustype,
112	 .model = pc36at_model,
113	 },
114#endif
115#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
116	{
117	 .name = "pci236",
118	 .fancy_name = "PCI236",
119	 .devid = PCI_DEVICE_ID_AMPLICON_PCI236,
120	 .bustype = pci_bustype,
121	 .model = pci236_model,
122	 },
123	{
124	 .name = PC236_DRIVER_NAME,
125	 .fancy_name = PC236_DRIVER_NAME,
126	 .devid = PCI_DEVICE_ID_INVALID,
127	 .bustype = pci_bustype,
128	 .model = anypci_model,	/* wildcard */
129	 },
130#endif
131};
132
133#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
134static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = {
135	{ PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
136	{0}
137};
138
139MODULE_DEVICE_TABLE(pci, pc236_pci_table);
140#endif /* CONFIG_COMEDI_AMPLC_PC236_PCI */
141
142/*
143 * Useful for shorthand access to the particular board structure
144 */
145#define thisboard ((const struct pc236_board *)dev->board_ptr)
146
147/* this structure is for data unique to this hardware driver.  If
148   several hardware drivers keep similar information in this structure,
149   feel free to suggest moving the variable to the struct comedi_device struct.
150 */
151struct pc236_private {
152#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
153	/* PCI device */
154	struct pci_dev *pci_dev;
155	unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
156#endif
157	int enable_irq;
158};
159
160#define devpriv ((struct pc236_private *)dev->private)
161
162/*
163 * The struct comedi_driver structure tells the Comedi core module
164 * which functions to call to configure/deconfigure (attach/detach)
165 * the board, and also about the kernel module that contains
166 * the device code.
167 */
168static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it);
169static void pc236_detach(struct comedi_device *dev);
170static struct comedi_driver driver_amplc_pc236 = {
171	.driver_name = PC236_DRIVER_NAME,
172	.module = THIS_MODULE,
173	.attach = pc236_attach,
174	.detach = pc236_detach,
175	.board_name = &pc236_boards[0].name,
176	.offset = sizeof(struct pc236_board),
177	.num_names = ARRAY_SIZE(pc236_boards),
178};
179
180#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
181static int __devinit driver_amplc_pc236_pci_probe(struct pci_dev *dev,
182						  const struct pci_device_id
183						  *ent)
184{
185	return comedi_pci_auto_config(dev, &driver_amplc_pc236);
186}
187
188static void __devexit driver_amplc_pc236_pci_remove(struct pci_dev *dev)
189{
190	comedi_pci_auto_unconfig(dev);
191}
192
193static struct pci_driver driver_amplc_pc236_pci_driver = {
194	.id_table = pc236_pci_table,
195	.probe = &driver_amplc_pc236_pci_probe,
196	.remove = __devexit_p(&driver_amplc_pc236_pci_remove)
197};
198
199static int __init driver_amplc_pc236_init_module(void)
200{
201	int retval;
202
203	retval = comedi_driver_register(&driver_amplc_pc236);
204	if (retval < 0)
205		return retval;
206
207	driver_amplc_pc236_pci_driver.name =
208	    (char *)driver_amplc_pc236.driver_name;
209	return pci_register_driver(&driver_amplc_pc236_pci_driver);
210}
211
212static void __exit driver_amplc_pc236_cleanup_module(void)
213{
214	pci_unregister_driver(&driver_amplc_pc236_pci_driver);
215	comedi_driver_unregister(&driver_amplc_pc236);
216}
217
218module_init(driver_amplc_pc236_init_module);
219module_exit(driver_amplc_pc236_cleanup_module);
220#else
221static int __init driver_amplc_pc236_init_module(void)
222{
223	return comedi_driver_register(&driver_amplc_pc236);
224}
225
226static void __exit driver_amplc_pc236_cleanup_module(void)
227{
228	comedi_driver_unregister(&driver_amplc_pc236);
229}
230
231module_init(driver_amplc_pc236_init_module);
232module_exit(driver_amplc_pc236_cleanup_module);
233#endif
234
235#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
236static int pc236_request_region(unsigned minor, unsigned long from,
237				unsigned long extent);
238#endif
239static void pc236_intr_disable(struct comedi_device *dev);
240static void pc236_intr_enable(struct comedi_device *dev);
241static int pc236_intr_check(struct comedi_device *dev);
242static int pc236_intr_insn(struct comedi_device *dev,
243			   struct comedi_subdevice *s, struct comedi_insn *insn,
244			   unsigned int *data);
245static int pc236_intr_cmdtest(struct comedi_device *dev,
246			      struct comedi_subdevice *s,
247			      struct comedi_cmd *cmd);
248static int pc236_intr_cmd(struct comedi_device *dev,
249			  struct comedi_subdevice *s);
250static int pc236_intr_cancel(struct comedi_device *dev,
251			     struct comedi_subdevice *s);
252static irqreturn_t pc236_interrupt(int irq, void *d);
253
254/*
255 * This function looks for a PCI device matching the requested board name,
256 * bus and slot.
257 */
258#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
259static int
260pc236_find_pci(struct comedi_device *dev, int bus, int slot,
261	       struct pci_dev **pci_dev_p)
262{
263	struct pci_dev *pci_dev = NULL;
264
265	*pci_dev_p = NULL;
266
267	/* Look for matching PCI device. */
268	for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL);
269	     pci_dev != NULL;
270	     pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON,
271				      PCI_ANY_ID, pci_dev)) {
272		/* If bus/slot specified, check them. */
273		if (bus || slot) {
274			if (bus != pci_dev->bus->number
275			    || slot != PCI_SLOT(pci_dev->devfn))
276				continue;
277		}
278		if (thisboard->model == anypci_model) {
279			/* Match any supported model. */
280			int i;
281
282			for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) {
283				if (pc236_boards[i].bustype != pci_bustype)
284					continue;
285				if (pci_dev->device == pc236_boards[i].devid) {
286					/* Change board_ptr to matched board. */
287					dev->board_ptr = &pc236_boards[i];
288					break;
289				}
290			}
291			if (i == ARRAY_SIZE(pc236_boards))
292				continue;
293		} else {
294			/* Match specific model name. */
295			if (pci_dev->device != thisboard->devid)
296				continue;
297		}
298
299		/* Found a match. */
300		*pci_dev_p = pci_dev;
301		return 0;
302	}
303	/* No match found. */
304	if (bus || slot) {
305		printk(KERN_ERR
306		       "comedi%d: error! no %s found at pci %02x:%02x!\n",
307		       dev->minor, thisboard->name, bus, slot);
308	} else {
309		printk(KERN_ERR "comedi%d: error! no %s found!\n",
310		       dev->minor, thisboard->name);
311	}
312	return -EIO;
313}
314#endif
315
316/*
317 * Attach is called by the Comedi core to configure the driver
318 * for a particular board.  If you specified a board_name array
319 * in the driver structure, dev->board_ptr contains that
320 * address.
321 */
322static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
323{
324	struct comedi_subdevice *s;
325	unsigned long iobase = 0;
326	unsigned int irq = 0;
327#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
328	struct pci_dev *pci_dev = NULL;
329	int bus = 0, slot = 0;
330#endif
331	int share_irq = 0;
332	int ret;
333
334	printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor,
335	       PC236_DRIVER_NAME);
336/*
337 * Allocate the private structure area.  alloc_private() is a
338 * convenient macro defined in comedidev.h.
339 */
340	ret = alloc_private(dev, sizeof(struct pc236_private));
341	if (ret < 0) {
342		printk(KERN_ERR "comedi%d: error! out of memory!\n",
343		       dev->minor);
344		return ret;
345	}
346	/* Process options. */
347	switch (thisboard->bustype) {
348#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
349	case isa_bustype:
350		iobase = it->options[0];
351		irq = it->options[1];
352		share_irq = 0;
353		break;
354#endif
355#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
356	case pci_bustype:
357		bus = it->options[0];
358		slot = it->options[1];
359		share_irq = 1;
360
361		ret = pc236_find_pci(dev, bus, slot, &pci_dev);
362		if (ret < 0)
363			return ret;
364		devpriv->pci_dev = pci_dev;
365		break;
366#endif
367	default:
368		printk(KERN_ERR
369		       "comedi%d: %s: BUG! cannot determine board type!\n",
370		       dev->minor, PC236_DRIVER_NAME);
371		return -EINVAL;
372		break;
373	}
374
375/*
376 * Initialize dev->board_name.
377 */
378	dev->board_name = thisboard->name;
379
380	/* Enable device and reserve I/O spaces. */
381#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
382	if (pci_dev) {
383
384		ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
385		if (ret < 0) {
386			printk(KERN_ERR
387			       "comedi%d: error! cannot enable PCI device and request regions!\n",
388			       dev->minor);
389			return ret;
390		}
391		devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
392		iobase = pci_resource_start(pci_dev, 2);
393		irq = pci_dev->irq;
394	} else
395#endif
396	{
397#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
398		ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE);
399		if (ret < 0)
400			return ret;
401#endif
402	}
403	dev->iobase = iobase;
404
405/*
406 * Allocate the subdevice structures.  alloc_subdevice() is a
407 * convenient macro defined in comedidev.h.
408 */
409	ret = alloc_subdevices(dev, 2);
410	if (ret < 0) {
411		printk(KERN_ERR "comedi%d: error! out of memory!\n",
412		       dev->minor);
413		return ret;
414	}
415
416	s = dev->subdevices + 0;
417	/* digital i/o subdevice (8255) */
418	ret = subdev_8255_init(dev, s, NULL, iobase);
419	if (ret < 0) {
420		printk(KERN_ERR "comedi%d: error! out of memory!\n",
421		       dev->minor);
422		return ret;
423	}
424	s = dev->subdevices + 1;
425	dev->read_subdev = s;
426	s->type = COMEDI_SUBD_UNUSED;
427	pc236_intr_disable(dev);
428	if (irq) {
429		unsigned long flags = share_irq ? IRQF_SHARED : 0;
430
431		if (request_irq(irq, pc236_interrupt, flags,
432				PC236_DRIVER_NAME, dev) >= 0) {
433			dev->irq = irq;
434			s->type = COMEDI_SUBD_DI;
435			s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
436			s->n_chan = 1;
437			s->maxdata = 1;
438			s->range_table = &range_digital;
439			s->insn_bits = pc236_intr_insn;
440			s->do_cmdtest = pc236_intr_cmdtest;
441			s->do_cmd = pc236_intr_cmd;
442			s->cancel = pc236_intr_cancel;
443		}
444	}
445	printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
446	switch (thisboard->bustype) {
447#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
448	case isa_bustype:
449		printk("(base %#lx) ", iobase);
450		break;
451#endif
452#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
453	case pci_bustype:
454		printk("(pci %s) ", pci_name(pci_dev));
455		break;
456#endif
457	default:
458		break;
459	}
460	if (irq)
461		printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
462	else
463		printk("(no irq) ");
464
465	printk("attached\n");
466
467	return 1;
468}
469
470static void pc236_detach(struct comedi_device *dev)
471{
472	if (devpriv)
473		pc236_intr_disable(dev);
474	if (dev->irq)
475		free_irq(dev->irq, dev);
476	if (dev->subdevices)
477		subdev_8255_cleanup(dev, dev->subdevices + 0);
478	if (devpriv) {
479#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
480		if (devpriv->pci_dev) {
481			if (dev->iobase)
482				comedi_pci_disable(devpriv->pci_dev);
483			pci_dev_put(devpriv->pci_dev);
484		} else
485#endif
486		{
487#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
488			if (dev->iobase)
489				release_region(dev->iobase, PC236_IO_SIZE);
490#endif
491		}
492	}
493}
494
495/*
496 * This function checks and requests an I/O region, reporting an error
497 * if there is a conflict.
498 */
499#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
500static int pc236_request_region(unsigned minor, unsigned long from,
501				unsigned long extent)
502{
503	if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
504		printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
505		       minor, from, extent);
506		return -EIO;
507	}
508	return 0;
509}
510#endif
511
512/*
513 * This function is called to mark the interrupt as disabled (no command
514 * configured on subdevice 1) and to physically disable the interrupt
515 * (not possible on the PC36AT, except by removing the IRQ jumper!).
516 */
517static void pc236_intr_disable(struct comedi_device *dev)
518{
519	unsigned long flags;
520
521	spin_lock_irqsave(&dev->spinlock, flags);
522	devpriv->enable_irq = 0;
523#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
524	if (devpriv->lcr_iobase)
525		outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
526#endif
527	spin_unlock_irqrestore(&dev->spinlock, flags);
528}
529
530/*
531 * This function is called to mark the interrupt as enabled (a command
532 * configured on subdevice 1) and to physically enable the interrupt
533 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
534 */
535static void pc236_intr_enable(struct comedi_device *dev)
536{
537	unsigned long flags;
538
539	spin_lock_irqsave(&dev->spinlock, flags);
540	devpriv->enable_irq = 1;
541#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
542	if (devpriv->lcr_iobase)
543		outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
544#endif
545	spin_unlock_irqrestore(&dev->spinlock, flags);
546}
547
548/*
549 * This function is called when an interrupt occurs to check whether
550 * the interrupt has been marked as enabled and was generated by the
551 * board.  If so, the function prepares the hardware for the next
552 * interrupt.
553 * Returns 0 if the interrupt should be ignored.
554 */
555static int pc236_intr_check(struct comedi_device *dev)
556{
557	int retval = 0;
558	unsigned long flags;
559
560	spin_lock_irqsave(&dev->spinlock, flags);
561	if (devpriv->enable_irq) {
562		retval = 1;
563#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
564		if (devpriv->lcr_iobase) {
565			if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
566			     & PLX9052_INTCSR_LI1STAT_MASK)
567			    == PLX9052_INTCSR_LI1STAT_INACTIVE) {
568				retval = 0;
569			} else {
570				/* Clear interrupt and keep it enabled. */
571				outl(PCI236_INTR_ENABLE,
572				     devpriv->lcr_iobase + PLX9052_INTCSR);
573			}
574		}
575#endif
576	}
577	spin_unlock_irqrestore(&dev->spinlock, flags);
578
579	return retval;
580}
581
582/*
583 * Input from subdevice 1.
584 * Copied from the comedi_parport driver.
585 */
586static int pc236_intr_insn(struct comedi_device *dev,
587			   struct comedi_subdevice *s, struct comedi_insn *insn,
588			   unsigned int *data)
589{
590	data[1] = 0;
591	return 2;
592}
593
594/*
595 * Subdevice 1 command test.
596 * Copied from the comedi_parport driver.
597 */
598static int pc236_intr_cmdtest(struct comedi_device *dev,
599			      struct comedi_subdevice *s,
600			      struct comedi_cmd *cmd)
601{
602	int err = 0;
603	int tmp;
604
605	/* step 1 */
606
607	tmp = cmd->start_src;
608	cmd->start_src &= TRIG_NOW;
609	if (!cmd->start_src || tmp != cmd->start_src)
610		err++;
611
612	tmp = cmd->scan_begin_src;
613	cmd->scan_begin_src &= TRIG_EXT;
614	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
615		err++;
616
617	tmp = cmd->convert_src;
618	cmd->convert_src &= TRIG_FOLLOW;
619	if (!cmd->convert_src || tmp != cmd->convert_src)
620		err++;
621
622	tmp = cmd->scan_end_src;
623	cmd->scan_end_src &= TRIG_COUNT;
624	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
625		err++;
626
627	tmp = cmd->stop_src;
628	cmd->stop_src &= TRIG_NONE;
629	if (!cmd->stop_src || tmp != cmd->stop_src)
630		err++;
631
632	if (err)
633		return 1;
634
635	/* step 2: ignored */
636
637	if (err)
638		return 2;
639
640	/* step 3: */
641
642	if (cmd->start_arg != 0) {
643		cmd->start_arg = 0;
644		err++;
645	}
646	if (cmd->scan_begin_arg != 0) {
647		cmd->scan_begin_arg = 0;
648		err++;
649	}
650	if (cmd->convert_arg != 0) {
651		cmd->convert_arg = 0;
652		err++;
653	}
654	if (cmd->scan_end_arg != 1) {
655		cmd->scan_end_arg = 1;
656		err++;
657	}
658	if (cmd->stop_arg != 0) {
659		cmd->stop_arg = 0;
660		err++;
661	}
662
663	if (err)
664		return 3;
665
666	/* step 4: ignored */
667
668	if (err)
669		return 4;
670
671	return 0;
672}
673
674/*
675 * Subdevice 1 command.
676 */
677static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
678{
679	pc236_intr_enable(dev);
680
681	return 0;
682}
683
684/*
685 * Subdevice 1 cancel command.
686 */
687static int pc236_intr_cancel(struct comedi_device *dev,
688			     struct comedi_subdevice *s)
689{
690	pc236_intr_disable(dev);
691
692	return 0;
693}
694
695/*
696 * Interrupt service routine.
697 * Based on the comedi_parport driver.
698 */
699static irqreturn_t pc236_interrupt(int irq, void *d)
700{
701	struct comedi_device *dev = d;
702	struct comedi_subdevice *s = dev->subdevices + 1;
703	int handled;
704
705	handled = pc236_intr_check(dev);
706	if (dev->attached && handled) {
707		comedi_buf_put(s->async, 0);
708		s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
709		comedi_event(dev, s);
710	}
711	return IRQ_RETVAL(handled);
712}
713
714MODULE_AUTHOR("Comedi http://www.comedi.org");
715MODULE_DESCRIPTION("Comedi low-level driver");
716MODULE_LICENSE("GPL");
717