staging: comedi: addi_apci_2032: use channel list
authorIan Abbott <abbotti@mev.co.uk>
Mon, 3 Dec 2012 18:15:45 +0000 (18:15 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 7 Jan 2013 22:10:02 +0000 (14:10 -0800)
When setting up asynchronous commands for the special interrupt
subdevice, use the channel list to decide which interrupt sources to
enable.  Set the maximum length of the channel list to be the same as
the number of channels (2).  Normally, the channel list would include
channel 0, channel 1 or both.

When reading the scan data in the interrupt routine, the readings from
each channel in the channel list will be packed into a single unsigned
short data value.  Make each bit in this value correspond to an index in
the channel list.

Since all the channels in the channel list are read at the same time,
insist that the scan end argument is the length of the channel list and
that the conversion source is `TRIG_NOW`.

Allocate some private data for the special interrupt subdevice to hold a
spin-lock, the channels to be enabled and an indication of whether the
command is still active.  Stop the command if a buffer overflow occurs.

Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/comedi/drivers/addi_apci_2032.c

index 98691fd..dd81ddc 100644 (file)
@@ -58,6 +58,12 @@ struct apci2032_private {
        unsigned int wdog_ctrl;
 };
 
+struct apci2032_int_private {
+       spinlock_t spinlock;
+       bool active;
+       unsigned char enabled_isns;
+};
+
 static int apci2032_do_insn_bits(struct comedi_device *dev,
                                 struct comedi_subdevice *s,
                                 struct comedi_insn *insn,
@@ -163,6 +169,16 @@ static int apci2032_int_insn_bits(struct comedi_device *dev,
        return insn->n;
 }
 
+static void apci2032_int_stop(struct comedi_device *dev,
+                             struct comedi_subdevice *s)
+{
+       struct apci2032_int_private *subpriv = s->private;
+
+       subpriv->active = false;
+       subpriv->enabled_isns = 0;
+       outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG);
+}
+
 static int apci2032_int_cmdtest(struct comedi_device *dev,
                                struct comedi_subdevice *s,
                                struct comedi_cmd *cmd)
@@ -172,8 +188,8 @@ static int apci2032_int_cmdtest(struct comedi_device *dev,
        /* Step 1 : check if triggers are trivially valid */
 
        err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
-       err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_OTHER);
-       err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+       err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+       err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
        err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
        err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
 
@@ -189,17 +205,9 @@ static int apci2032_int_cmdtest(struct comedi_device *dev,
        /* Step 3: check if arguments are trivially valid */
 
        err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
-
-       /*
-        * 0 == no trigger
-        * 1 == trigger on VCC interrupt
-        * 2 == trigger on CC interrupt
-        * 3 == trigger on either VCC or CC interrupt
-        */
-       err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 3);
-
+       err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
        err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
-       err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, 1);
+       err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
        err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
 
        if (err)
@@ -217,8 +225,20 @@ static int apci2032_int_cmd(struct comedi_device *dev,
                            struct comedi_subdevice *s)
 {
        struct comedi_cmd *cmd = &s->async->cmd;
+       struct apci2032_int_private *subpriv = s->private;
+       unsigned char enabled_isns;
+       unsigned int n;
+       unsigned long flags;
+
+       enabled_isns = 0;
+       for (n = 0; n < cmd->chanlist_len; n++)
+               enabled_isns |= 1 << CR_CHAN(cmd->chanlist[n]);
 
-       outl(cmd->scan_begin_arg, dev->iobase + APCI2032_INT_CTRL_REG);
+       spin_lock_irqsave(&subpriv->spinlock, flags);
+       subpriv->enabled_isns = enabled_isns;
+       subpriv->active = true;
+       outl(subpriv->enabled_isns, dev->iobase + APCI2032_INT_CTRL_REG);
+       spin_unlock_irqrestore(&subpriv->spinlock, flags);
 
        return 0;
 }
@@ -226,7 +246,13 @@ static int apci2032_int_cmd(struct comedi_device *dev,
 static int apci2032_int_cancel(struct comedi_device *dev,
                               struct comedi_subdevice *s)
 {
-       outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG);
+       struct apci2032_int_private *subpriv = s->private;
+       unsigned long flags;
+
+       spin_lock_irqsave(&subpriv->spinlock, flags);
+       if (subpriv->active)
+               apci2032_int_stop(dev, s);
+       spin_unlock_irqrestore(&subpriv->spinlock, flags);
 
        return 0;
 }
@@ -235,7 +261,9 @@ static irqreturn_t apci2032_interrupt(int irq, void *d)
 {
        struct comedi_device *dev = d;
        struct comedi_subdevice *s = dev->read_subdev;
+       struct apci2032_int_private *subpriv;
        unsigned int val;
+       bool do_event = false;
 
        if (!dev->attached)
                return IRQ_NONE;
@@ -245,6 +273,9 @@ static irqreturn_t apci2032_interrupt(int irq, void *d)
        if (!val)
                return IRQ_NONE;
 
+       subpriv = s->private;
+       spin_lock(&subpriv->spinlock);
+
        val = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3;
        /* Disable triggered interrupt sources. */
        outl(~val & 3, dev->iobase + APCI2032_INT_CTRL_REG);
@@ -254,11 +285,31 @@ static irqreturn_t apci2032_interrupt(int irq, void *d)
         * they'd keep triggering interrupts repeatedly.
         */
 
-       if (comedi_buf_put(s->async, val))
-               s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
-       else
-               s->async->events |= COMEDI_CB_OVERFLOW;
-       comedi_event(dev, s);
+       if (subpriv->active && (val & subpriv->enabled_isns) != 0) {
+               unsigned short bits;
+               unsigned int n, len;
+               unsigned int *chanlist;
+
+               /* Bits in scan data correspond to indices in channel list. */
+               bits = 0;
+               len = s->async->cmd.chanlist_len;
+               chanlist = &s->async->cmd.chanlist[0];
+               for (n = 0; n < len; n++)
+                       if ((val & (1U << CR_CHAN(chanlist[n]))) != 0)
+                               bits |= 1U << n;
+
+               if (comedi_buf_put(s->async, bits)) {
+                       s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
+               } else {
+                       apci2032_int_stop(dev, s);
+                       s->async->events |= COMEDI_CB_OVERFLOW;
+               }
+               do_event = true;
+       }
+
+       spin_unlock(&subpriv->spinlock);
+       if (do_event)
+               comedi_event(dev, s);
 
        return IRQ_HANDLED;
 }
@@ -327,10 +378,18 @@ static int apci2032_auto_attach(struct comedi_device *dev,
        /* Initialize the interrupt subdevice */
        s = &dev->subdevices[2];
        if (dev->irq) {
+               struct apci2032_int_private *subpriv;
+
                dev->read_subdev = s;
+               subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
+               if (!subpriv)
+                       return -ENOMEM;
+               spin_lock_init(&subpriv->spinlock);
+               s->private      = subpriv;
                s->type         = COMEDI_SUBD_DI;
                s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
                s->n_chan       = 2;
+               s->len_chanlist = 2;
                s->maxdata      = 1;
                s->range_table  = &range_digital;
                s->insn_bits    = apci2032_int_insn_bits;
@@ -352,6 +411,8 @@ static void apci2032_detach(struct comedi_device *dev)
                apci2032_reset(dev);
        if (dev->irq)
                free_irq(dev->irq, dev);
+       if (dev->read_subdev)
+               kfree(dev->read_subdev->private);
        if (pcidev) {
                if (dev->iobase)
                        comedi_pci_disable(pcidev);