amplc_pc236.c revision 727b286b44ea359d66f47d241cc2cdad36ed7bdc
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 "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 98enum pc236_bustype { isa_bustype, pci_bustype }; 99enum pc236_model { pc36at_model, pci236_model, anypci_model }; 100 101struct 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}; 108static 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 136static 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 143MODULE_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 */ 155struct 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 */ 172static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it); 173static int pc236_detach(struct comedi_device *dev); 174static 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 185static 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 192static void __devexit driver_amplc_pc236_pci_remove(struct pci_dev *dev) 193{ 194 comedi_pci_auto_unconfig(dev); 195} 196 197static 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 203static 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 216static 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 222module_init(driver_amplc_pc236_init_module); 223module_exit(driver_amplc_pc236_cleanup_module); 224#else 225static int __init driver_amplc_pc236_init_module(void) 226{ 227 return comedi_driver_register(&driver_amplc_pc236); 228} 229 230static void __exit driver_amplc_pc236_cleanup_module(void) 231{ 232 comedi_driver_unregister(&driver_amplc_pc236); 233} 234 235module_init(driver_amplc_pc236_init_module); 236module_exit(driver_amplc_pc236_cleanup_module); 237#endif 238 239static int pc236_request_region(unsigned minor, unsigned long from, 240 unsigned long extent); 241static void pc236_intr_disable(struct comedi_device *dev); 242static void pc236_intr_enable(struct comedi_device *dev); 243static int pc236_intr_check(struct comedi_device *dev); 244static int pc236_intr_insn(struct comedi_device *dev, 245 struct comedi_subdevice *s, struct comedi_insn *insn, 246 unsigned int *data); 247static int pc236_intr_cmdtest(struct comedi_device *dev, 248 struct comedi_subdevice *s, 249 struct comedi_cmd *cmd); 250static int pc236_intr_cmd(struct comedi_device *dev, 251 struct comedi_subdevice *s); 252static int pc236_intr_cancel(struct comedi_device *dev, 253 struct comedi_subdevice *s); 254static 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 261static int 262pc236_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 */ 324static 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 */ 469static 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 */ 504static 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 */ 520static 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 */ 538static 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 */ 558static 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 */ 589static 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 */ 601static 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 */ 680static 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 */ 690static 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 */ 702static 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 717MODULE_AUTHOR("Comedi http://www.comedi.org"); 718MODULE_DESCRIPTION("Comedi low-level driver"); 719MODULE_LICENSE("GPL"); 720