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