staging/comedi/amplc: Convert pci_table entries to PCI_DEVICE (if PCI_ANY_ID is used)
[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         { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
138         {0}
139 };
140
141 MODULE_DEVICE_TABLE(pci, pc236_pci_table);
142 #endif /* CONFIG_COMEDI_PCI */
143
144 /*
145  * Useful for shorthand access to the particular board structure
146  */
147 #define thisboard ((const struct pc236_board *)dev->board_ptr)
148
149 /* this structure is for data unique to this hardware driver.  If
150    several hardware drivers keep similar information in this structure,
151    feel free to suggest moving the variable to the struct comedi_device struct.
152  */
153 struct pc236_private {
154 #ifdef CONFIG_COMEDI_PCI
155         /* PCI device */
156         struct pci_dev *pci_dev;
157         unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
158 #endif
159         int enable_irq;
160 };
161
162 #define devpriv ((struct pc236_private *)dev->private)
163
164 /*
165  * The struct comedi_driver structure tells the Comedi core module
166  * which functions to call to configure/deconfigure (attach/detach)
167  * the board, and also about the kernel module that contains
168  * the device code.
169  */
170 static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it);
171 static int pc236_detach(struct comedi_device *dev);
172 static struct comedi_driver driver_amplc_pc236 = {
173         .driver_name = PC236_DRIVER_NAME,
174         .module = THIS_MODULE,
175         .attach = pc236_attach,
176         .detach = pc236_detach,
177         .board_name = &pc236_boards[0].name,
178         .offset = sizeof(struct pc236_board),
179         .num_names = ARRAY_SIZE(pc236_boards),
180 };
181
182 #ifdef CONFIG_COMEDI_PCI
183 static int __devinit driver_amplc_pc236_pci_probe(struct pci_dev *dev,
184                                                   const struct pci_device_id
185                                                   *ent)
186 {
187         return comedi_pci_auto_config(dev, driver_amplc_pc236.driver_name);
188 }
189
190 static void __devexit driver_amplc_pc236_pci_remove(struct pci_dev *dev)
191 {
192         comedi_pci_auto_unconfig(dev);
193 }
194
195 static struct pci_driver driver_amplc_pc236_pci_driver = {
196         .id_table = pc236_pci_table,
197         .probe = &driver_amplc_pc236_pci_probe,
198         .remove = __devexit_p(&driver_amplc_pc236_pci_remove)
199 };
200
201 static int __init driver_amplc_pc236_init_module(void)
202 {
203         int retval;
204
205         retval = comedi_driver_register(&driver_amplc_pc236);
206         if (retval < 0)
207                 return retval;
208
209         driver_amplc_pc236_pci_driver.name =
210             (char *)driver_amplc_pc236.driver_name;
211         return pci_register_driver(&driver_amplc_pc236_pci_driver);
212 }
213
214 static void __exit driver_amplc_pc236_cleanup_module(void)
215 {
216         pci_unregister_driver(&driver_amplc_pc236_pci_driver);
217         comedi_driver_unregister(&driver_amplc_pc236);
218 }
219
220 module_init(driver_amplc_pc236_init_module);
221 module_exit(driver_amplc_pc236_cleanup_module);
222 #else
223 static int __init driver_amplc_pc236_init_module(void)
224 {
225         return comedi_driver_register(&driver_amplc_pc236);
226 }
227
228 static void __exit driver_amplc_pc236_cleanup_module(void)
229 {
230         comedi_driver_unregister(&driver_amplc_pc236);
231 }
232
233 module_init(driver_amplc_pc236_init_module);
234 module_exit(driver_amplc_pc236_cleanup_module);
235 #endif
236
237 static int pc236_request_region(unsigned minor, unsigned long from,
238                                 unsigned long extent);
239 static void pc236_intr_disable(struct comedi_device *dev);
240 static void pc236_intr_enable(struct comedi_device *dev);
241 static int pc236_intr_check(struct comedi_device *dev);
242 static int pc236_intr_insn(struct comedi_device *dev,
243                            struct comedi_subdevice *s, struct comedi_insn *insn,
244                            unsigned int *data);
245 static int pc236_intr_cmdtest(struct comedi_device *dev,
246                               struct comedi_subdevice *s,
247                               struct comedi_cmd *cmd);
248 static int pc236_intr_cmd(struct comedi_device *dev,
249                           struct comedi_subdevice *s);
250 static int pc236_intr_cancel(struct comedi_device *dev,
251                              struct comedi_subdevice *s);
252 static 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 #ifdef CONFIG_COMEDI_PCI
259 static int
260 pc236_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  */
322 static 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 #ifdef CONFIG_COMEDI_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         case isa_bustype:
349                 iobase = it->options[0];
350                 irq = it->options[1];
351                 share_irq = 0;
352                 break;
353 #ifdef CONFIG_COMEDI_PCI
354         case pci_bustype:
355                 bus = it->options[0];
356                 slot = it->options[1];
357                 share_irq = 1;
358
359                 ret = pc236_find_pci(dev, bus, slot, &pci_dev);
360                 if (ret < 0)
361                         return ret;
362                 devpriv->pci_dev = pci_dev;
363                 break;
364 #endif /* CONFIG_COMEDI_PCI */
365         default:
366                 printk(KERN_ERR
367                        "comedi%d: %s: BUG! cannot determine board type!\n",
368                        dev->minor, PC236_DRIVER_NAME);
369                 return -EINVAL;
370                 break;
371         }
372
373 /*
374  * Initialize dev->board_name.
375  */
376         dev->board_name = thisboard->name;
377
378         /* Enable device and reserve I/O spaces. */
379 #ifdef CONFIG_COMEDI_PCI
380         if (pci_dev) {
381
382                 ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
383                 if (ret < 0) {
384                         printk(KERN_ERR
385                                "comedi%d: error! cannot enable PCI device and request regions!\n",
386                                dev->minor);
387                         return ret;
388                 }
389                 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
390                 iobase = pci_resource_start(pci_dev, 2);
391                 irq = pci_dev->irq;
392         } else
393 #endif
394         {
395                 ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE);
396                 if (ret < 0)
397                         return ret;
398         }
399         dev->iobase = iobase;
400
401 /*
402  * Allocate the subdevice structures.  alloc_subdevice() is a
403  * convenient macro defined in comedidev.h.
404  */
405         ret = alloc_subdevices(dev, 2);
406         if (ret < 0) {
407                 printk(KERN_ERR "comedi%d: error! out of memory!\n",
408                        dev->minor);
409                 return ret;
410         }
411
412         s = dev->subdevices + 0;
413         /* digital i/o subdevice (8255) */
414         ret = subdev_8255_init(dev, s, NULL, iobase);
415         if (ret < 0) {
416                 printk(KERN_ERR "comedi%d: error! out of memory!\n",
417                        dev->minor);
418                 return ret;
419         }
420         s = dev->subdevices + 1;
421         dev->read_subdev = s;
422         s->type = COMEDI_SUBD_UNUSED;
423         pc236_intr_disable(dev);
424         if (irq) {
425                 unsigned long flags = share_irq ? IRQF_SHARED : 0;
426
427                 if (request_irq(irq, pc236_interrupt, flags,
428                                 PC236_DRIVER_NAME, dev) >= 0) {
429                         dev->irq = irq;
430                         s->type = COMEDI_SUBD_DI;
431                         s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
432                         s->n_chan = 1;
433                         s->maxdata = 1;
434                         s->range_table = &range_digital;
435                         s->insn_bits = pc236_intr_insn;
436                         s->do_cmdtest = pc236_intr_cmdtest;
437                         s->do_cmd = pc236_intr_cmd;
438                         s->cancel = pc236_intr_cancel;
439                 }
440         }
441         printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
442         if (thisboard->bustype == isa_bustype) {
443                 printk("(base %#lx) ", iobase);
444         } else {
445 #ifdef CONFIG_COMEDI_PCI
446                 printk("(pci %s) ", pci_name(pci_dev));
447 #endif
448         }
449         if (irq)
450                 printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
451         else
452                 printk("(no irq) ");
453
454         printk("attached\n");
455
456         return 1;
457 }
458
459 /*
460  * _detach is called to deconfigure a device.  It should deallocate
461  * resources.
462  * This function is also called when _attach() fails, so it should be
463  * careful not to release resources that were not necessarily
464  * allocated by _attach().  dev->private and dev->subdevices are
465  * deallocated automatically by the core.
466  */
467 static int pc236_detach(struct comedi_device *dev)
468 {
469         printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor,
470                PC236_DRIVER_NAME);
471         if (devpriv)
472                 pc236_intr_disable(dev);
473
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 #ifdef CONFIG_COMEDI_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 (dev->iobase)
488                                 release_region(dev->iobase, PC236_IO_SIZE);
489                 }
490         }
491         if (dev->board_name) {
492                 printk(KERN_INFO "comedi%d: %s removed\n",
493                        dev->minor, dev->board_name);
494         }
495         return 0;
496 }
497
498 /*
499  * This function checks and requests an I/O region, reporting an error
500  * if there is a conflict.
501  */
502 static int pc236_request_region(unsigned minor, unsigned long from,
503                                 unsigned long extent)
504 {
505         if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
506                 printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
507                        minor, from, extent);
508                 return -EIO;
509         }
510         return 0;
511 }
512
513 /*
514  * This function is called to mark the interrupt as disabled (no command
515  * configured on subdevice 1) and to physically disable the interrupt
516  * (not possible on the PC36AT, except by removing the IRQ jumper!).
517  */
518 static void pc236_intr_disable(struct comedi_device *dev)
519 {
520         unsigned long flags;
521
522         spin_lock_irqsave(&dev->spinlock, flags);
523         devpriv->enable_irq = 0;
524 #ifdef CONFIG_COMEDI_PCI
525         if (devpriv->lcr_iobase)
526                 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
527 #endif
528         spin_unlock_irqrestore(&dev->spinlock, flags);
529 }
530
531 /*
532  * This function is called to mark the interrupt as enabled (a command
533  * configured on subdevice 1) and to physically enable the interrupt
534  * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
535  */
536 static void pc236_intr_enable(struct comedi_device *dev)
537 {
538         unsigned long flags;
539
540         spin_lock_irqsave(&dev->spinlock, flags);
541         devpriv->enable_irq = 1;
542 #ifdef CONFIG_COMEDI_PCI
543         if (devpriv->lcr_iobase)
544                 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
545 #endif
546         spin_unlock_irqrestore(&dev->spinlock, flags);
547 }
548
549 /*
550  * This function is called when an interrupt occurs to check whether
551  * the interrupt has been marked as enabled and was generated by the
552  * board.  If so, the function prepares the hardware for the next
553  * interrupt.
554  * Returns 0 if the interrupt should be ignored.
555  */
556 static int pc236_intr_check(struct comedi_device *dev)
557 {
558         int retval = 0;
559         unsigned long flags;
560
561         spin_lock_irqsave(&dev->spinlock, flags);
562         if (devpriv->enable_irq) {
563                 retval = 1;
564 #ifdef CONFIG_COMEDI_PCI
565                 if (devpriv->lcr_iobase) {
566                         if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
567                              & PLX9052_INTCSR_LI1STAT_MASK)
568                             == PLX9052_INTCSR_LI1STAT_INACTIVE) {
569                                 retval = 0;
570                         } else {
571                                 /* Clear interrupt and keep it enabled. */
572                                 outl(PCI236_INTR_ENABLE,
573                                      devpriv->lcr_iobase + PLX9052_INTCSR);
574                         }
575                 }
576 #endif
577         }
578         spin_unlock_irqrestore(&dev->spinlock, flags);
579
580         return retval;
581 }
582
583 /*
584  * Input from subdevice 1.
585  * Copied from the comedi_parport driver.
586  */
587 static int pc236_intr_insn(struct comedi_device *dev,
588                            struct comedi_subdevice *s, struct comedi_insn *insn,
589                            unsigned int *data)
590 {
591         data[1] = 0;
592         return 2;
593 }
594
595 /*
596  * Subdevice 1 command test.
597  * Copied from the comedi_parport driver.
598  */
599 static int pc236_intr_cmdtest(struct comedi_device *dev,
600                               struct comedi_subdevice *s,
601                               struct comedi_cmd *cmd)
602 {
603         int err = 0;
604         int tmp;
605
606         /* step 1 */
607
608         tmp = cmd->start_src;
609         cmd->start_src &= TRIG_NOW;
610         if (!cmd->start_src || tmp != cmd->start_src)
611                 err++;
612
613         tmp = cmd->scan_begin_src;
614         cmd->scan_begin_src &= TRIG_EXT;
615         if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
616                 err++;
617
618         tmp = cmd->convert_src;
619         cmd->convert_src &= TRIG_FOLLOW;
620         if (!cmd->convert_src || tmp != cmd->convert_src)
621                 err++;
622
623         tmp = cmd->scan_end_src;
624         cmd->scan_end_src &= TRIG_COUNT;
625         if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
626                 err++;
627
628         tmp = cmd->stop_src;
629         cmd->stop_src &= TRIG_NONE;
630         if (!cmd->stop_src || tmp != cmd->stop_src)
631                 err++;
632
633         if (err)
634                 return 1;
635
636         /* step 2: ignored */
637
638         if (err)
639                 return 2;
640
641         /* step 3: */
642
643         if (cmd->start_arg != 0) {
644                 cmd->start_arg = 0;
645                 err++;
646         }
647         if (cmd->scan_begin_arg != 0) {
648                 cmd->scan_begin_arg = 0;
649                 err++;
650         }
651         if (cmd->convert_arg != 0) {
652                 cmd->convert_arg = 0;
653                 err++;
654         }
655         if (cmd->scan_end_arg != 1) {
656                 cmd->scan_end_arg = 1;
657                 err++;
658         }
659         if (cmd->stop_arg != 0) {
660                 cmd->stop_arg = 0;
661                 err++;
662         }
663
664         if (err)
665                 return 3;
666
667         /* step 4: ignored */
668
669         if (err)
670                 return 4;
671
672         return 0;
673 }
674
675 /*
676  * Subdevice 1 command.
677  */
678 static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
679 {
680         pc236_intr_enable(dev);
681
682         return 0;
683 }
684
685 /*
686  * Subdevice 1 cancel command.
687  */
688 static int pc236_intr_cancel(struct comedi_device *dev,
689                              struct comedi_subdevice *s)
690 {
691         pc236_intr_disable(dev);
692
693         return 0;
694 }
695
696 /*
697  * Interrupt service routine.
698  * Based on the comedi_parport driver.
699  */
700 static irqreturn_t pc236_interrupt(int irq, void *d)
701 {
702         struct comedi_device *dev = d;
703         struct comedi_subdevice *s = dev->subdevices + 1;
704         int handled;
705
706         handled = pc236_intr_check(dev);
707         if (dev->attached && handled) {
708                 comedi_buf_put(s->async, 0);
709                 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
710                 comedi_event(dev, s);
711         }
712         return IRQ_RETVAL(handled);
713 }
714
715 MODULE_AUTHOR("Comedi http://www.comedi.org");
716 MODULE_DESCRIPTION("Comedi low-level driver");
717 MODULE_LICENSE("GPL");