linux/drivers/staging/comedi/drivers/amplc_pc236.c
<<
>>
Prefs
   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.  */
 154struct pc236_private {
 155#ifdef CONFIG_COMEDI_PCI
 156        /* PCI device */
 157        struct pci_dev *pci_dev;
 158        unsigned long lcr_iobase;       /* PLX PCI9052 config registers in PCIBAR1 */
 159#endif
 160        int enable_irq;
 161};
 162
 163#define devpriv ((struct pc236_private *)dev->private)
 164
 165/*
 166 * The struct comedi_driver structure tells the Comedi core module
 167 * which functions to call to configure/deconfigure (attach/detach)
 168 * the board, and also about the kernel module that contains
 169 * the device code.
 170 */
 171static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it);
 172static int pc236_detach(struct comedi_device *dev);
 173static struct comedi_driver driver_amplc_pc236 = {
 174        .driver_name = PC236_DRIVER_NAME,
 175        .module = THIS_MODULE,
 176        .attach = pc236_attach,
 177        .detach = pc236_detach,
 178        .board_name = &pc236_boards[0].name,
 179        .offset = sizeof(struct pc236_board),
 180        .num_names = ARRAY_SIZE(pc236_boards),
 181};
 182
 183#ifdef CONFIG_COMEDI_PCI
 184COMEDI_PCI_INITCLEANUP(driver_amplc_pc236, pc236_pci_table);
 185#else
 186COMEDI_INITCLEANUP(driver_amplc_pc236);
 187#endif
 188
 189static int pc236_request_region(unsigned minor, unsigned long from,
 190                                unsigned long extent);
 191static void pc236_intr_disable(struct comedi_device *dev);
 192static void pc236_intr_enable(struct comedi_device *dev);
 193static int pc236_intr_check(struct comedi_device *dev);
 194static int pc236_intr_insn(struct comedi_device *dev,
 195                           struct comedi_subdevice *s, struct comedi_insn *insn,
 196                           unsigned int *data);
 197static int pc236_intr_cmdtest(struct comedi_device *dev,
 198                              struct comedi_subdevice *s,
 199                              struct comedi_cmd *cmd);
 200static int pc236_intr_cmd(struct comedi_device *dev,
 201                          struct comedi_subdevice *s);
 202static int pc236_intr_cancel(struct comedi_device *dev,
 203                             struct comedi_subdevice *s);
 204static irqreturn_t pc236_interrupt(int irq, void *d);
 205
 206/*
 207 * This function looks for a PCI device matching the requested board name,
 208 * bus and slot.
 209 */
 210#ifdef CONFIG_COMEDI_PCI
 211static int
 212pc236_find_pci(struct comedi_device *dev, int bus, int slot,
 213               struct pci_dev **pci_dev_p)
 214{
 215        struct pci_dev *pci_dev = NULL;
 216
 217        *pci_dev_p = NULL;
 218
 219        /* Look for matching PCI device. */
 220        for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL);
 221             pci_dev != NULL;
 222             pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON,
 223                                      PCI_ANY_ID, pci_dev)) {
 224                /* If bus/slot specified, check them. */
 225                if (bus || slot) {
 226                        if (bus != pci_dev->bus->number
 227                            || slot != PCI_SLOT(pci_dev->devfn))
 228                                continue;
 229                }
 230                if (thisboard->model == anypci_model) {
 231                        /* Match any supported model. */
 232                        int i;
 233
 234                        for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) {
 235                                if (pc236_boards[i].bustype != pci_bustype)
 236                                        continue;
 237                                if (pci_dev->device == pc236_boards[i].devid) {
 238                                        /* Change board_ptr to matched board. */
 239                                        dev->board_ptr = &pc236_boards[i];
 240                                        break;
 241                                }
 242                        }
 243                        if (i == ARRAY_SIZE(pc236_boards))
 244                                continue;
 245                } else {
 246                        /* Match specific model name. */
 247                        if (pci_dev->device != thisboard->devid)
 248                                continue;
 249                }
 250
 251                /* Found a match. */
 252                *pci_dev_p = pci_dev;
 253                return 0;
 254        }
 255        /* No match found. */
 256        if (bus || slot) {
 257                printk(KERN_ERR
 258                       "comedi%d: error! no %s found at pci %02x:%02x!\n",
 259                       dev->minor, thisboard->name, bus, slot);
 260        } else {
 261                printk(KERN_ERR "comedi%d: error! no %s found!\n",
 262                       dev->minor, thisboard->name);
 263        }
 264        return -EIO;
 265}
 266#endif
 267
 268/*
 269 * Attach is called by the Comedi core to configure the driver
 270 * for a particular board.  If you specified a board_name array
 271 * in the driver structure, dev->board_ptr contains that
 272 * address.
 273 */
 274static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
 275{
 276        struct comedi_subdevice *s;
 277        unsigned long iobase = 0;
 278        unsigned int irq = 0;
 279#ifdef CONFIG_COMEDI_PCI
 280        struct pci_dev *pci_dev = NULL;
 281        int bus = 0, slot = 0;
 282#endif
 283        int share_irq = 0;
 284        int ret;
 285
 286        printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor,
 287               PC236_DRIVER_NAME);
 288/*
 289 * Allocate the private structure area.  alloc_private() is a
 290 * convenient macro defined in comedidev.h.
 291 */
 292        ret = alloc_private(dev, sizeof(struct pc236_private));
 293        if (ret < 0) {
 294                printk(KERN_ERR "comedi%d: error! out of memory!\n",
 295                       dev->minor);
 296                return ret;
 297        }
 298        /* Process options. */
 299        switch (thisboard->bustype) {
 300        case isa_bustype:
 301                iobase = it->options[0];
 302                irq = it->options[1];
 303                share_irq = 0;
 304                break;
 305#ifdef CONFIG_COMEDI_PCI
 306        case pci_bustype:
 307                bus = it->options[0];
 308                slot = it->options[1];
 309                share_irq = 1;
 310
 311                ret = pc236_find_pci(dev, bus, slot, &pci_dev);
 312                if (ret < 0)
 313                        return ret;
 314                devpriv->pci_dev = pci_dev;
 315                break;
 316#endif /* CONFIG_COMEDI_PCI */
 317        default:
 318                printk(KERN_ERR
 319                       "comedi%d: %s: BUG! cannot determine board type!\n",
 320                       dev->minor, PC236_DRIVER_NAME);
 321                return -EINVAL;
 322                break;
 323        }
 324
 325/*
 326 * Initialize dev->board_name.
 327 */
 328        dev->board_name = thisboard->name;
 329
 330        /* Enable device and reserve I/O spaces. */
 331#ifdef CONFIG_COMEDI_PCI
 332        if (pci_dev) {
 333
 334                ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
 335                if (ret < 0) {
 336                        printk(KERN_ERR
 337                               "comedi%d: error! cannot enable PCI device and request regions!\n",
 338                               dev->minor);
 339                        return ret;
 340                }
 341                devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
 342                iobase = pci_resource_start(pci_dev, 2);
 343                irq = pci_dev->irq;
 344        } else
 345#endif
 346        {
 347                ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE);
 348                if (ret < 0) {
 349                        return ret;
 350                }
 351        }
 352        dev->iobase = iobase;
 353
 354/*
 355 * Allocate the subdevice structures.  alloc_subdevice() is a
 356 * convenient macro defined in comedidev.h.
 357 */
 358        ret = alloc_subdevices(dev, 2);
 359        if (ret < 0) {
 360                printk(KERN_ERR "comedi%d: error! out of memory!\n",
 361                       dev->minor);
 362                return ret;
 363        }
 364
 365        s = dev->subdevices + 0;
 366        /* digital i/o subdevice (8255) */
 367        ret = subdev_8255_init(dev, s, NULL, iobase);
 368        if (ret < 0) {
 369                printk(KERN_ERR "comedi%d: error! out of memory!\n",
 370                       dev->minor);
 371                return ret;
 372        }
 373        s = dev->subdevices + 1;
 374        dev->read_subdev = s;
 375        s->type = COMEDI_SUBD_UNUSED;
 376        pc236_intr_disable(dev);
 377        if (irq) {
 378                unsigned long flags = share_irq ? IRQF_SHARED : 0;
 379
 380                if (request_irq(irq, pc236_interrupt, flags,
 381                                PC236_DRIVER_NAME, dev) >= 0) {
 382                        dev->irq = irq;
 383                        s->type = COMEDI_SUBD_DI;
 384                        s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
 385                        s->n_chan = 1;
 386                        s->maxdata = 1;
 387                        s->range_table = &range_digital;
 388                        s->insn_bits = pc236_intr_insn;
 389                        s->do_cmdtest = pc236_intr_cmdtest;
 390                        s->do_cmd = pc236_intr_cmd;
 391                        s->cancel = pc236_intr_cancel;
 392                }
 393        }
 394        printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
 395        if (thisboard->bustype == isa_bustype) {
 396                printk("(base %#lx) ", iobase);
 397        } else {
 398#ifdef CONFIG_COMEDI_PCI
 399                printk("(pci %s) ", pci_name(pci_dev));
 400#endif
 401        }
 402        if (irq) {
 403                printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
 404        } else {
 405                printk("(no irq) ");
 406        }
 407
 408        printk("attached\n");
 409
 410        return 1;
 411}
 412
 413/*
 414 * _detach is called to deconfigure a device.  It should deallocate
 415 * resources.
 416 * This function is also called when _attach() fails, so it should be
 417 * careful not to release resources that were not necessarily
 418 * allocated by _attach().  dev->private and dev->subdevices are
 419 * deallocated automatically by the core.
 420 */
 421static int pc236_detach(struct comedi_device *dev)
 422{
 423        printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor,
 424               PC236_DRIVER_NAME);
 425        if (devpriv) {
 426                pc236_intr_disable(dev);
 427        }
 428        if (dev->irq)
 429                free_irq(dev->irq, dev);
 430        if (dev->subdevices) {
 431                subdev_8255_cleanup(dev, dev->subdevices + 0);
 432        }
 433        if (devpriv) {
 434#ifdef CONFIG_COMEDI_PCI
 435                if (devpriv->pci_dev) {
 436                        if (dev->iobase) {
 437                                comedi_pci_disable(devpriv->pci_dev);
 438                        }
 439                        pci_dev_put(devpriv->pci_dev);
 440                } else
 441#endif
 442                {
 443                        if (dev->iobase) {
 444                                release_region(dev->iobase, PC236_IO_SIZE);
 445                        }
 446                }
 447        }
 448        if (dev->board_name) {
 449                printk(KERN_INFO "comedi%d: %s removed\n",
 450                       dev->minor, dev->board_name);
 451        }
 452        return 0;
 453}
 454
 455/*
 456 * This function checks and requests an I/O region, reporting an error
 457 * if there is a conflict.
 458 */
 459static int pc236_request_region(unsigned minor, unsigned long from,
 460                                unsigned long extent)
 461{
 462        if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
 463                printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
 464                       minor, from, extent);
 465                return -EIO;
 466        }
 467        return 0;
 468}
 469
 470/*
 471 * This function is called to mark the interrupt as disabled (no command
 472 * configured on subdevice 1) and to physically disable the interrupt
 473 * (not possible on the PC36AT, except by removing the IRQ jumper!).
 474 */
 475static void pc236_intr_disable(struct comedi_device *dev)
 476{
 477        unsigned long flags;
 478
 479        spin_lock_irqsave(&dev->spinlock, flags);
 480        devpriv->enable_irq = 0;
 481#ifdef CONFIG_COMEDI_PCI
 482        if (devpriv->lcr_iobase)
 483                outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
 484#endif
 485        spin_unlock_irqrestore(&dev->spinlock, flags);
 486}
 487
 488/*
 489 * This function is called to mark the interrupt as enabled (a command
 490 * configured on subdevice 1) and to physically enable the interrupt
 491 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
 492 */
 493static void pc236_intr_enable(struct comedi_device *dev)
 494{
 495        unsigned long flags;
 496
 497        spin_lock_irqsave(&dev->spinlock, flags);
 498        devpriv->enable_irq = 1;
 499#ifdef CONFIG_COMEDI_PCI
 500        if (devpriv->lcr_iobase)
 501                outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
 502#endif
 503        spin_unlock_irqrestore(&dev->spinlock, flags);
 504}
 505
 506/*
 507 * This function is called when an interrupt occurs to check whether
 508 * the interrupt has been marked as enabled and was generated by the
 509 * board.  If so, the function prepares the hardware for the next
 510 * interrupt.
 511 * Returns 0 if the interrupt should be ignored.
 512 */
 513static int pc236_intr_check(struct comedi_device *dev)
 514{
 515        int retval = 0;
 516        unsigned long flags;
 517
 518        spin_lock_irqsave(&dev->spinlock, flags);
 519        if (devpriv->enable_irq) {
 520                retval = 1;
 521#ifdef CONFIG_COMEDI_PCI
 522                if (devpriv->lcr_iobase) {
 523                        if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
 524                             & PLX9052_INTCSR_LI1STAT_MASK)
 525                            == PLX9052_INTCSR_LI1STAT_INACTIVE) {
 526                                retval = 0;
 527                        } else {
 528                                /* Clear interrupt and keep it enabled. */
 529                                outl(PCI236_INTR_ENABLE,
 530                                     devpriv->lcr_iobase + PLX9052_INTCSR);
 531                        }
 532                }
 533#endif
 534        }
 535        spin_unlock_irqrestore(&dev->spinlock, flags);
 536
 537        return retval;
 538}
 539
 540/*
 541 * Input from subdevice 1.
 542 * Copied from the comedi_parport driver.
 543 */
 544static int pc236_intr_insn(struct comedi_device *dev,
 545                           struct comedi_subdevice *s, struct comedi_insn *insn,
 546                           unsigned int *data)
 547{
 548        data[1] = 0;
 549        return 2;
 550}
 551
 552/*
 553 * Subdevice 1 command test.
 554 * Copied from the comedi_parport driver.
 555 */
 556static int pc236_intr_cmdtest(struct comedi_device *dev,
 557                              struct comedi_subdevice *s,
 558                              struct comedi_cmd *cmd)
 559{
 560        int err = 0;
 561        int tmp;
 562
 563        /* step 1 */
 564
 565        tmp = cmd->start_src;
 566        cmd->start_src &= TRIG_NOW;
 567        if (!cmd->start_src || tmp != cmd->start_src)
 568                err++;
 569
 570        tmp = cmd->scan_begin_src;
 571        cmd->scan_begin_src &= TRIG_EXT;
 572        if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
 573                err++;
 574
 575        tmp = cmd->convert_src;
 576        cmd->convert_src &= TRIG_FOLLOW;
 577        if (!cmd->convert_src || tmp != cmd->convert_src)
 578                err++;
 579
 580        tmp = cmd->scan_end_src;
 581        cmd->scan_end_src &= TRIG_COUNT;
 582        if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
 583                err++;
 584
 585        tmp = cmd->stop_src;
 586        cmd->stop_src &= TRIG_NONE;
 587        if (!cmd->stop_src || tmp != cmd->stop_src)
 588                err++;
 589
 590        if (err)
 591                return 1;
 592
 593        /* step 2: ignored */
 594
 595        if (err)
 596                return 2;
 597
 598        /* step 3: */
 599
 600        if (cmd->start_arg != 0) {
 601                cmd->start_arg = 0;
 602                err++;
 603        }
 604        if (cmd->scan_begin_arg != 0) {
 605                cmd->scan_begin_arg = 0;
 606                err++;
 607        }
 608        if (cmd->convert_arg != 0) {
 609                cmd->convert_arg = 0;
 610                err++;
 611        }
 612        if (cmd->scan_end_arg != 1) {
 613                cmd->scan_end_arg = 1;
 614                err++;
 615        }
 616        if (cmd->stop_arg != 0) {
 617                cmd->stop_arg = 0;
 618                err++;
 619        }
 620
 621        if (err)
 622                return 3;
 623
 624        /* step 4: ignored */
 625
 626        if (err)
 627                return 4;
 628
 629        return 0;
 630}
 631
 632/*
 633 * Subdevice 1 command.
 634 */
 635static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
 636{
 637        pc236_intr_enable(dev);
 638
 639        return 0;
 640}
 641
 642/*
 643 * Subdevice 1 cancel command.
 644 */
 645static int pc236_intr_cancel(struct comedi_device *dev,
 646                             struct comedi_subdevice *s)
 647{
 648        pc236_intr_disable(dev);
 649
 650        return 0;
 651}
 652
 653/*
 654 * Interrupt service routine.
 655 * Based on the comedi_parport driver.
 656 */
 657static irqreturn_t pc236_interrupt(int irq, void *d)
 658{
 659        struct comedi_device *dev = d;
 660        struct comedi_subdevice *s = dev->subdevices + 1;
 661        int handled;
 662
 663        handled = pc236_intr_check(dev);
 664        if (dev->attached && handled) {
 665                comedi_buf_put(s->async, 0);
 666                s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
 667                comedi_event(dev, s);
 668        }
 669        return IRQ_RETVAL(handled);
 670}
 671