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