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
driver_amplc_pc236_pci_probe(struct pci_dev * dev,const struct pci_device_id * ent)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
driver_amplc_pc236_pci_remove(struct pci_dev * dev)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
driver_amplc_pc236_init_module(void)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
driver_amplc_pc236_cleanup_module(void)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
driver_amplc_pc236_init_module(void)223 static int __init driver_amplc_pc236_init_module(void)
224 {
225 return comedi_driver_register(&driver_amplc_pc236);
226 }
227
driver_amplc_pc236_cleanup_module(void)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
pc236_find_pci(struct comedi_device * dev,int bus,int slot,struct pci_dev ** pci_dev_p)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 */
pc236_attach(struct comedi_device * dev,struct comedi_devconfig * it)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 */
pc236_detach(struct comedi_device * dev)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 */
pc236_request_region(unsigned minor,unsigned long from,unsigned long extent)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 */
pc236_intr_disable(struct comedi_device * dev)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 */
pc236_intr_enable(struct comedi_device * dev)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 */
pc236_intr_check(struct comedi_device * dev)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 */
pc236_intr_insn(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)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 */
pc236_intr_cmdtest(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_cmd * cmd)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 */
pc236_intr_cmd(struct comedi_device * dev,struct comedi_subdevice * s)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 */
pc236_intr_cancel(struct comedi_device * dev,struct comedi_subdevice * s)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 */
pc236_interrupt(int irq,void * d)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");
718