summaryrefslogtreecommitdiff
path: root/drivers/staging/comedi/drivers/addi_apci_1032.c
diff options
context:
space:
mode:
authorH Hartley Sweeten <hartleys@visionengravers.com>2012-11-05 14:41:52 -0700
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-11-06 09:30:58 +0100
commit4cde0a6d51807b5b956f2cc7878a8c4872d8bde5 (patch)
tree66ce6e17347264eeec850a70b54fd9fd98fd06e1 /drivers/staging/comedi/drivers/addi_apci_1032.c
parent14696bbec450bbc13773a8a8f85577469435fa77 (diff)
staging: comedi: addi_apci_1032: hook-up the interrupt subdevice
The board supported by this driver can generate an interrupt based on the state of input channels 0-15. The apci1032_intr_insn_config() function is used to configure which inputs are used to generate the interrupt. Currently this function is broken since it does not follow the comedi API for insn_config functions. Fix this function by implementing the, currently unused, config instruction INSN_CONFIG_DIGITAL_TRIG. Add the remaining subdevice operations necessary for the interrupt subdevice to support async commands. Fix the subdevice initialization so that if the interrupt is not available the subdevice is set as COMEDI_SUBD_UNUSED. Signed-off-by: H Hartley Sweeten <hsweeten@visionengravers.com> Cc: Ian Abbott <abbotti@mev.co.uk> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/staging/comedi/drivers/addi_apci_1032.c')
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_1032.c250
1 files changed, 184 insertions, 66 deletions
diff --git a/drivers/staging/comedi/drivers/addi_apci_1032.c b/drivers/staging/comedi/drivers/addi_apci_1032.c
index 5a300d93ce18..b2736356d850 100644
--- a/drivers/staging/comedi/drivers/addi_apci_1032.c
+++ b/drivers/staging/comedi/drivers/addi_apci_1032.c
@@ -33,8 +33,6 @@
#include "comedi_fc.h"
#include "amcc_s5933.h"
-#include "addi-data/addi_common.h"
-
/*
* I/O Register Map
*/
@@ -47,72 +45,191 @@
#define APCI1032_CTRL_INT_AND (1 << 1)
#define APCI1032_CTRL_INT_ENA (1 << 2)
-/* Digital Input IRQ Function Selection */
-#define ADDIDATA_OR 0
-#define ADDIDATA_AND 1
+struct apci1032_private {
+ unsigned int mode1; /* rising-edge/high level channels */
+ unsigned int mode2; /* falling-edge/low level channels */
+ unsigned int ctrl; /* interrupt mode OR (edge) . AND (level) */
+};
-static unsigned int ui_InterruptStatus;
+static int apci1032_reset(struct comedi_device *dev)
+{
+ /* disable the interrupts */
+ outl(0x0, dev->iobase + APCI1032_CTRL_REG);
+ /* Reset the interrupt status register */
+ inl(dev->iobase + APCI1032_STATUS_REG);
+ /* Disable the and/or interrupt */
+ outl(0x0, dev->iobase + APCI1032_MODE1_REG);
+ outl(0x0, dev->iobase + APCI1032_MODE2_REG);
+
+ return 0;
+}
/*
- * data[0] : 1 Enable Digital Input Interrupt
- * 0 Disable Digital Input Interrupt
- * data[1] : 0 ADDIDATA Interrupt OR LOGIC
- * : 1 ADDIDATA Interrupt AND LOGIC
- * data[2] : Interrupt mask for the mode 1
- * data[3] : Interrupt mask for the mode 2
+ * Change-Of-State (COS) interrupt configuration
+ *
+ * Channels 0 to 15 are interruptible. These channels can be configured
+ * to generate interrupts based on AND/OR logic for the desired channels.
+ *
+ * OR logic
+ * - reacts to rising or falling edges
+ * - interrupt is generated when any enabled channel
+ * meet the desired interrupt condition
+ *
+ * AND logic
+ * - reacts to changes in level of the selected inputs
+ * - interrupt is generated when all enabled channels
+ * meet the desired interrupt condition
+ * - after an interrupt, a change in level must occur on
+ * the selected inputs to release the IRQ logic
+ *
+ * The COS interrupt must be configured before it can be enabled.
+ *
+ * data[0] : INSN_CONFIG_DIGITAL_TRIG
+ * data[1] : 0 = OR (edge) interrupts
+ * 1 = AND (level) interrupts
+ * data[2] : rising-edge/high level channels
+ * data[3] : falling-edge/low level channels
*/
-static int apci1032_intr_insn_config(struct comedi_device *dev,
- struct comedi_subdevice *s,
- struct comedi_insn *insn,
- unsigned int *data)
+static int apci1032_cos_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
{
- unsigned int ui_TmpValue;
- unsigned int ul_Command1 = 0;
- unsigned int ul_Command2 = 0;
-
- /*******************************/
- /* Set the digital input logic */
- /*******************************/
- if (data[0] == ADDIDATA_ENABLE) {
- ul_Command1 = ul_Command1 | data[2];
- ul_Command2 = ul_Command2 | data[3];
- outl(ul_Command1, dev->iobase + APCI1032_MODE1_REG);
- outl(ul_Command2, dev->iobase + APCI1032_MODE2_REG);
- if (data[1] == ADDIDATA_OR) {
- outl(APCI1032_CTRL_INT_ENA |
- APCI1032_CTRL_INT_OR,
- dev->iobase + APCI1032_CTRL_REG);
- ui_TmpValue =
- inl(dev->iobase + APCI1032_CTRL_REG);
- } /* if (data[1] == ADDIDATA_OR) */
- else
- outl(APCI1032_CTRL_INT_ENA |
- APCI1032_CTRL_INT_AND,
- dev->iobase + APCI1032_CTRL_REG);
- /* else if(data[1] == ADDIDATA_OR) */
- } /* if( data[0] == ADDIDATA_ENABLE) */
- else {
- ul_Command1 = ul_Command1 & 0xFFFF0000;
- ul_Command2 = ul_Command2 & 0xFFFF0000;
- outl(ul_Command1, dev->iobase + APCI1032_MODE1_REG);
- outl(ul_Command2, dev->iobase + APCI1032_MODE2_REG);
- outl(0x0, dev->iobase + APCI1032_CTRL_REG);
- } /* else if ( data[0] == ADDIDATA_ENABLE) */
+ struct apci1032_private *devpriv = dev->private;
+
+ switch (data[0]) {
+ case INSN_CONFIG_DIGITAL_TRIG:
+ devpriv->mode1 = data[2];
+ devpriv->mode2 = data[3];
+
+ if (devpriv->mode1 || devpriv->mode2) {
+ devpriv->ctrl = APCI1032_CTRL_INT_ENA;
+ if (data[1] == 1)
+ devpriv->ctrl = APCI1032_CTRL_INT_AND;
+ else
+ devpriv->ctrl = APCI1032_CTRL_INT_OR;
+ } else {
+ devpriv->ctrl = 0;
+ apci1032_reset(dev);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
return insn->n;
}
+static int apci1032_cos_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = s->state;
+
+ return 0;
+}
+
+static int apci1032_cos_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* 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_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* step 3: */
+
+ if (cmd->start_arg != 0) {
+ cmd->start_arg = 0;
+ err++;
+ }
+ if (cmd->scan_begin_arg != 0) {
+ cmd->scan_begin_arg = 0;
+ err++;
+ }
+ if (cmd->convert_arg != 0) {
+ cmd->convert_arg = 0;
+ err++;
+ }
+ if (cmd->scan_end_arg != 1) {
+ cmd->scan_end_arg = 1;
+ err++;
+ }
+ if (cmd->stop_arg != 0) {
+ cmd->stop_arg = 0;
+ err++;
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: ignored */
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+/*
+ * Change-Of-State (COS) 'do_cmd' operation
+ *
+ * Enable the COS interrupt as configured by apci1032_cos_insn_config().
+ */
+static int apci1032_cos_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci1032_private *devpriv = dev->private;
+
+ if (!devpriv->ctrl) {
+ dev_warn(dev->class_dev,
+ "Interrupts disabled due to mode configuration!\n");
+ return -EINVAL;
+ }
+
+ outl(devpriv->mode1, dev->iobase + APCI1032_MODE1_REG);
+ outl(devpriv->mode2, dev->iobase + APCI1032_MODE2_REG);
+ outl(devpriv->ctrl, dev->iobase + APCI1032_CTRL_REG);
+
+ return 0;
+}
+
+static int apci1032_cos_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ return apci1032_reset(dev);
+}
+
static irqreturn_t apci1032_interrupt(int irq, void *d)
{
struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
unsigned int ctrl;
/* disable the interrupt */
ctrl = inl(dev->iobase + APCI1032_CTRL_REG);
outl(ctrl & ~APCI1032_CTRL_INT_ENA, dev->iobase + APCI1032_CTRL_REG);
- ui_InterruptStatus = inl(dev->iobase + APCI1032_STATUS_REG);
- ui_InterruptStatus = ui_InterruptStatus & 0X0000FFFF;
+ s->state = inl(dev->iobase + APCI1032_STATUS_REG) & 0xffff;
+ comedi_buf_put(s->async, s->state);
+ s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
+ comedi_event(dev, s);
/* enable the interrupt */
outl(ctrl, dev->iobase + APCI1032_CTRL_REG);
@@ -130,27 +247,20 @@ static int apci1032_di_insn_bits(struct comedi_device *dev,
return insn->n;
}
-static int apci1032_reset(struct comedi_device *dev)
-{
- /* disable the interrupts */
- outl(0x0, dev->iobase + APCI1032_CTRL_REG);
- /* Reset the interrupt status register */
- inl(dev->iobase + APCI1032_STATUS_REG);
- /* Disable the and/or interrupt */
- outl(0x0, dev->iobase + APCI1032_MODE1_REG);
- outl(0x0, dev->iobase + APCI1032_MODE2_REG);
-
- return 0;
-}
-
static int apci1032_attach_pci(struct comedi_device *dev,
struct pci_dev *pcidev)
{
+ struct apci1032_private *devpriv;
struct comedi_subdevice *s;
int ret;
dev->board_name = dev->driver->driver_name;
+ devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
+ if (!devpriv)
+ return -ENOMEM;
+ dev->private = devpriv;
+
ret = comedi_pci_enable(pcidev, dev->board_name);
if (ret)
return ret;
@@ -178,14 +288,22 @@ static int apci1032_attach_pci(struct comedi_device *dev,
s->range_table = &range_digital;
s->insn_bits = apci1032_di_insn_bits;
+ /* Change-Of-State (COS) interrupt subdevice */
+ s = &dev->subdevices[1];
if (dev->irq) {
- s = &dev->subdevices[1];
- s->type = COMEDI_SUBD_DI;
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_DI | SDF_CMD_READ;
s->subdev_flags = SDF_READABLE;
s->n_chan = 1;
s->maxdata = 1;
s->range_table = &range_digital;
- s->insn_config = apci1032_intr_insn_config;
+ s->insn_config = apci1032_cos_insn_config;
+ s->insn_bits = apci1032_cos_insn_bits;
+ s->do_cmdtest = apci1032_cos_cmdtest;
+ s->do_cmd = apci1032_cos_cmd;
+ s->cancel = apci1032_cos_cancel;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
}
apci1032_reset(dev);