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        { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
 138        {0}
 139};
 140
 141MODULE_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 */
 153struct 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 */
 170static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it);
 171static int pc236_detach(struct comedi_device *dev);
 172static 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
 183static 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
 190static void __devexit driver_amplc_pc236_pci_remove(struct pci_dev *dev)
 191{
 192        comedi_pci_auto_unconfig(dev);
 193}
 194
 195static 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
 201static 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
 214static 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
 220module_init(driver_amplc_pc236_init_module);
 221module_exit(driver_amplc_pc236_cleanup_module);
 222#else
 223static int __init driver_amplc_pc236_init_module(void)
 224{
 225        return comedi_driver_register(&driver_amplc_pc236);
 226}
 227
 228static void __exit driver_amplc_pc236_cleanup_module(void)
 229{
 230        comedi_driver_unregister(&driver_amplc_pc236);
 231}
 232
 233module_init(driver_amplc_pc236_init_module);
 234module_exit(driver_amplc_pc236_cleanup_module);
 235#endif
 236
 237static int pc236_request_region(unsigned minor, unsigned long from,
 238                                unsigned long extent);
 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#ifdef CONFIG_COMEDI_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#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 */
 467static 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 */
 502static 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 */
 518static 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 */
 536static 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 */
 556static 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 */
 587static 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 */
 599static 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 */
 678static 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 */
 688static 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 */
 700static 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
 715MODULE_AUTHOR("Comedi http://www.comedi.org");
 716MODULE_DESCRIPTION("Comedi low-level driver");
 717MODULE_LICENSE("GPL");
 718