staging: comedi: pcmuio: fix interrupt requests
authorH Hartley Sweeten <hsweeten@visionengravers.com>
Thu, 5 Dec 2013 23:54:03 +0000 (16:54 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 6 Dec 2013 21:10:02 +0000 (13:10 -0800)
Legacy (ISA) interrupts are not sharable so this driver should not
be passing the IRQF_SHARED flag when requesting the interrupts.

This driver supports two board types:
  PCM-UIO48 with one asic (one interrupt source)
  PCM-UIO96 with two asics (two interrupt sources)

The PCM-UIO96 has a jumper that allows the two interrupt sources to
share an interrupt. This is safe for legacy interrupts as long as
the "shared" interrupt is handled by a single driver.

Modify the request_irq() code in this driver to correctly request the
interrupts. For the PCM-UI048 case (one asic) only one request_irq()
is needed. For the PCM-UIO96 (two asics) there are three cases:

  1) irq is shared, one request_irq() call
  2) only one asic has an irq, one request_irq() call
  3) two irqs, two request_irq() calls

The irq for the first asic (dev->irq) will be requested during the
attach if required. The comedi core will handle the freeing of this
irq during the detach.

The irq for the second asic (devpriv->irq2) will also be requested
during the attach if required. The freeing of this irq will be
handled by the driver during the detach.

Move the board reset and interrupt request code so it occurs early
in the attach. We can then check dev->irq and devpriv->irq2 to see
if the subdevice command support actually needs to be initialized.

This also simplifies the interrupt handler. The irq can be simply
checked against dev->irq and devpriv->irq2 to see which asic caused
the interrupt.

Add a call to pcmuio_reset() in the (*detach) to make sure the
interrupts are disabled before freeing the irqs.

Signed-off-by: H Hartley Sweeten <hsweeten@visionengravers.com>
Cc: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/comedi/drivers/pcmuio.c

index 954fa96a50ac766df3ed8d257704e46442302293..dfa72805226557e8b24f2c43de0a50fbdb1ce8ad 100644 (file)
@@ -146,10 +146,10 @@ struct pcmuio_subdev_private {
 
 struct pcmuio_private {
        struct {
-               unsigned int irq;
                spinlock_t spinlock;
        } asics[PCMUIO_MAX_ASICS];
        struct pcmuio_subdev_private *sprivs;
+       unsigned int irq2;
 };
 
 static void pcmuio_write(struct comedi_device *dev, unsigned int val,
@@ -390,19 +390,14 @@ static irqreturn_t pcmuio_interrupt(int irq, void *d)
 {
        struct comedi_device *dev = d;
        struct pcmuio_private *devpriv = dev->private;
-       int got1 = 0;
-       int asic;
+       int handled = 0;
 
-       for (asic = 0; asic < PCMUIO_MAX_ASICS; ++asic) {
-               if (irq == devpriv->asics[asic].irq) {
-                       /* it is an interrupt for ASIC #asic */
-                       if (pcmuio_handle_asic_interrupt(dev, asic))
-                               got1++;
-               }
-       }
-       if (!got1)
-               return IRQ_NONE;        /* interrupt from other source */
-       return IRQ_HANDLED;
+       if (irq == dev->irq)
+               handled += pcmuio_handle_asic_interrupt(dev, 0);
+       if (irq == devpriv->irq2)
+               handled += pcmuio_handle_asic_interrupt(dev, 1);
+
+       return handled ? IRQ_HANDLED : IRQ_NONE;
 }
 
 static int pcmuio_start_intr(struct comedi_device *dev,
@@ -591,12 +586,8 @@ static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
        struct pcmuio_private *devpriv;
        struct pcmuio_subdev_private *subpriv;
        int sdev_no, n_subdevs, asic;
-       unsigned int irq[PCMUIO_MAX_ASICS];
        int ret;
 
-       irq[0] = it->options[1];
-       irq[1] = it->options[2];
-
        ret = comedi_request_region(dev, it->options[0],
                                    board->num_asics * PCMUIO_ASIC_IOSIZE);
        if (ret)
@@ -609,6 +600,29 @@ static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
        for (asic = 0; asic < PCMUIO_MAX_ASICS; ++asic)
                spin_lock_init(&devpriv->asics[asic].spinlock);
 
+       pcmuio_reset(dev);
+
+       if (it->options[1]) {
+               /* request the irq for the 1st asic */
+               ret = request_irq(it->options[1], pcmuio_interrupt, 0,
+                                 dev->board_name, dev);
+               if (ret == 0)
+                       dev->irq = it->options[1];
+       }
+
+       if (board->num_asics == 2) {
+               if (it->options[2] == dev->irq) {
+                       /* the same irq (or none) is used by both asics */
+                       devpriv->irq2 = it->options[2];
+               } else if (it->options[2]) {
+                       /* request the irq for the 2nd asic */
+                       ret = request_irq(it->options[2], pcmuio_interrupt, 0,
+                                       dev->board_name, dev);
+                       if (ret == 0)
+                               devpriv->irq2 = it->options[2];
+               }
+       }
+
        n_subdevs = board->num_asics * 2;
        devpriv->sprivs = kcalloc(n_subdevs, sizeof(*subpriv), GFP_KERNEL);
        if (!devpriv->sprivs)
@@ -630,8 +644,9 @@ static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
                s->insn_config = pcmuio_dio_insn_config;
                s->n_chan = 24;
 
-               /* subdevices 0 and 2 suppport interrupts */
-               if ((sdev_no % 2) == 0) {
+               /* subdevices 0 and 2 can suppport interrupts */
+               if ((sdev_no == 0 && dev->irq) ||
+                   (sdev_no == 2 && devpriv->irq2)) {
                        /* setup the interrupt subdevice */
                        subpriv->intr.asic = sdev_no / 2;
                        dev->read_subdev = s;
@@ -647,36 +662,20 @@ static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
                spin_lock_init(&subpriv->intr.spinlock);
        }
 
-       pcmuio_reset(dev);
-
-       for (asic = 0; irq[0] && asic < PCMUIO_MAX_ASICS; ++asic) {
-               if (irq[asic]
-                   && request_irq(irq[asic], pcmuio_interrupt,
-                                  IRQF_SHARED, board->name, dev)) {
-                       int i;
-                       /* unroll the allocated irqs.. */
-                       for (i = asic - 1; i >= 0; --i) {
-                               free_irq(irq[i], dev);
-                               devpriv->asics[i].irq = irq[i] = 0;
-                       }
-                       irq[asic] = 0;
-               }
-               devpriv->asics[asic].irq = irq[asic];
-       }
-
        return 0;
 }
 
 static void pcmuio_detach(struct comedi_device *dev)
 {
        struct pcmuio_private *devpriv = dev->private;
-       int i;
 
        if (devpriv) {
-               for (i = 0; i < PCMUIO_MAX_ASICS; ++i) {
-                       if (devpriv->asics[i].irq)
-                               free_irq(devpriv->asics[i].irq, dev);
-               }
+               pcmuio_reset(dev);
+
+               /* free the 2nd irq if used, the core will free the 1st one */
+               if (devpriv->irq2 && devpriv->irq2 != dev->irq)
+                       free_irq(devpriv->irq2, dev);
+
                kfree(devpriv->sprivs);
        }
        comedi_legacy_detach(dev);