staging: comedi: ni_65xx: refactor edge detection configuration
[firefly-linux-kernel-4.4.55.git] / drivers / staging / comedi / drivers / ni_65xx.c
index e547c2f80726415c093f94ff55e3bf7a4e0a255c..f7566fe461b42f748df1090fcf25705361f554d3 100644 (file)
  *         (National Instruments) PXI-6521 [ni_65xx]
  *         (National Instruments) PCI-6528 [ni_65xx]
  *         (National Instruments) PXI-6528 [ni_65xx]
- * Updated: Wed Oct 18 08:59:11 EDT 2006
+ * Updated: Mon, 21 Jul 2014 12:49:58 +0000
  *
  * Configuration Options: not applicable, uses PCI auto config
  *
  * Based on the PCI-6527 driver by ds.
  * The interrupt subdevice (subdevice 3) is probably broken for all
  * boards except maybe the 6514.
+ *
+ * This driver previously inverted the outputs on PCI-6513 through to
+ * PCI-6519 and on PXI-6513 through to PXI-6515.  It no longer inverts
+ * outputs on those cards by default as it didn't make much sense.  If
+ * you require the outputs to be inverted on those cards for legacy
+ * reasons, set the module parameter "legacy_invert_outputs=true" when
+ * loading the module, or set "ni_65xx.legacy_invert_outputs=true" on
+ * the kernel command line if the driver is built in to the kernel.
  */
 
 /*
 #define NI_65XX_WDOG_HI_LO_REG(x)      (0x48 + NI_65XX_PORT(x))
 #define NI_65XX_RTSI_ENA(x)            (0x49 + NI_65XX_PORT(x))
 
-#define NI_65XX_MAX_NUM_PORTS          12
 #define NI_65XX_PORT_TO_CHAN(x)                ((x) * 8)
 #define NI_65XX_CHAN_TO_PORT(x)                ((x) / 8)
 #define NI_65XX_CHAN_TO_MASK(x)                (1 << ((x) % 8))
@@ -164,7 +171,7 @@ struct ni_65xx_board {
        unsigned num_dio_ports;
        unsigned num_di_ports;
        unsigned num_do_ports;
-       unsigned invert_outputs:1;
+       unsigned legacy_invert:1;
 };
 
 static const struct ni_65xx_board ni_65xx_boards[] = {
@@ -199,58 +206,58 @@ static const struct ni_65xx_board ni_65xx_boards[] = {
        [BOARD_PCI6513] = {
                .name           = "pci-6513",
                .num_do_ports   = 8,
-               .invert_outputs = 1,
+               .legacy_invert  = 1,
        },
        [BOARD_PXI6513] = {
                .name           = "pxi-6513",
                .num_do_ports   = 8,
-               .invert_outputs = 1,
+               .legacy_invert  = 1,
        },
        [BOARD_PCI6514] = {
                .name           = "pci-6514",
                .num_di_ports   = 4,
                .num_do_ports   = 4,
-               .invert_outputs = 1,
+               .legacy_invert  = 1,
        },
        [BOARD_PXI6514] = {
                .name           = "pxi-6514",
                .num_di_ports   = 4,
                .num_do_ports   = 4,
-               .invert_outputs = 1,
+               .legacy_invert  = 1,
        },
        [BOARD_PCI6515] = {
                .name           = "pci-6515",
                .num_di_ports   = 4,
                .num_do_ports   = 4,
-               .invert_outputs = 1,
+               .legacy_invert  = 1,
        },
        [BOARD_PXI6515] = {
                .name           = "pxi-6515",
                .num_di_ports   = 4,
                .num_do_ports   = 4,
-               .invert_outputs = 1,
+               .legacy_invert  = 1,
        },
        [BOARD_PCI6516] = {
                .name           = "pci-6516",
                .num_do_ports   = 4,
-               .invert_outputs = 1,
+               .legacy_invert  = 1,
        },
        [BOARD_PCI6517] = {
                .name           = "pci-6517",
                .num_do_ports   = 4,
-               .invert_outputs = 1,
+               .legacy_invert  = 1,
        },
        [BOARD_PCI6518] = {
                .name           = "pci-6518",
                .num_di_ports   = 2,
                .num_do_ports   = 2,
-               .invert_outputs = 1,
+               .legacy_invert  = 1,
        },
        [BOARD_PCI6519] = {
                .name           = "pci-6519",
                .num_di_ports   = 2,
                .num_do_ports   = 2,
-               .invert_outputs = 1,
+               .legacy_invert  = 1,
        },
        [BOARD_PCI6520] = {
                .name           = "pci-6520",
@@ -279,17 +286,85 @@ static const struct ni_65xx_board ni_65xx_boards[] = {
        },
 };
 
-static inline unsigned ni_65xx_total_num_ports(const struct ni_65xx_board
-                                              *board)
-{
-       return board->num_dio_ports + board->num_di_ports + board->num_do_ports;
-}
+static bool ni_65xx_legacy_invert_outputs;
+module_param_named(legacy_invert_outputs, ni_65xx_legacy_invert_outputs,
+                  bool, 0444);
+MODULE_PARM_DESC(legacy_invert_outputs,
+                "invert outputs of PCI/PXI-6513/6514/6515/6516/6517/6518/6519 for compatibility with old user code");
 
 struct ni_65xx_private {
        void __iomem *mmio;
-       unsigned short output_bits[NI_65XX_MAX_NUM_PORTS];
 };
 
+static unsigned int ni_65xx_num_ports(struct comedi_device *dev)
+{
+       const struct ni_65xx_board *board = comedi_board(dev);
+
+       return board->num_dio_ports + board->num_di_ports + board->num_do_ports;
+}
+
+static void ni_65xx_disable_input_filters(struct comedi_device *dev)
+{
+       struct ni_65xx_private *devpriv = dev->private;
+       unsigned int num_ports = ni_65xx_num_ports(dev);
+       int i;
+
+       /* disable input filtering on all ports */
+       for (i = 0; i < num_ports; ++i)
+               writeb(0x00, devpriv->mmio + NI_65XX_FILTER_ENA(i));
+
+       /* set filter interval to 0 (32bit reg) */
+       writel(0x00000000, devpriv->mmio + NI_65XX_FILTER_REG);
+}
+
+/* updates edge detection for base_chan to base_chan+31 */
+static void ni_65xx_update_edge_detection(struct comedi_device *dev,
+                                         unsigned int base_chan,
+                                         unsigned int rising,
+                                         unsigned int falling)
+{
+       struct ni_65xx_private *devpriv = dev->private;
+       unsigned int num_ports = ni_65xx_num_ports(dev);
+       unsigned int port;
+
+       if (base_chan >= NI_65XX_PORT_TO_CHAN(num_ports))
+               return;
+
+       for (port = NI_65XX_CHAN_TO_PORT(base_chan); port < num_ports; port++) {
+               int bitshift = (int)(NI_65XX_PORT_TO_CHAN(port) - base_chan);
+               unsigned int port_mask, port_rising, port_falling;
+
+               if (bitshift >= 32)
+                       break;
+
+               if (bitshift >= 0) {
+                       port_mask = ~0U >> bitshift;
+                       port_rising = rising >> bitshift;
+                       port_falling = falling >> bitshift;
+               } else {
+                       port_mask = ~0U << -bitshift;
+                       port_rising = rising << -bitshift;
+                       port_falling = falling << -bitshift;
+               }
+               if (port_mask & 0xff) {
+                       if (~port_mask & 0xff) {
+                               port_rising |=
+                                   readb(devpriv->mmio +
+                                         NI_65XX_RISE_EDGE_ENA_REG(port)) &
+                                   ~port_mask;
+                               port_falling |=
+                                   readb(devpriv->mmio +
+                                         NI_65XX_FALL_EDGE_ENA_REG(port)) &
+                                   ~port_mask;
+                       }
+                       writeb(port_rising & 0xff,
+                              devpriv->mmio + NI_65XX_RISE_EDGE_ENA_REG(port));
+                       writeb(port_falling & 0xff,
+                              devpriv->mmio + NI_65XX_FALL_EDGE_ENA_REG(port));
+               }
+       }
+}
+
 static int ni_65xx_dio_insn_config(struct comedi_device *dev,
                                   struct comedi_subdevice *s,
                                   struct comedi_insn *insn,
@@ -364,7 +439,6 @@ static int ni_65xx_dio_insn_bits(struct comedi_device *dev,
                                 struct comedi_insn *insn,
                                 unsigned int *data)
 {
-       const struct ni_65xx_board *board = comedi_board(dev);
        struct ni_65xx_private *devpriv = dev->private;
        unsigned long base_port = (unsigned long)s->private;
        unsigned int base_chan = CR_CHAN(insn->chanspec);
@@ -376,7 +450,7 @@ static int ni_65xx_dio_insn_bits(struct comedi_device *dev,
             port_offset <= last_port_offset; port_offset++) {
                unsigned port = base_port + port_offset;
                int base_port_channel = NI_65XX_PORT_TO_CHAN(port_offset);
-               unsigned port_mask, port_data, port_read_bits;
+               unsigned port_mask, port_data, bits;
                int bitshift = base_port_channel - base_chan;
 
                if (bitshift >= 32)
@@ -392,30 +466,26 @@ static int ni_65xx_dio_insn_bits(struct comedi_device *dev,
                }
                port_mask &= 0xff;
                port_data &= 0xff;
+
+               /* update the outputs */
                if (port_mask) {
-                       unsigned bits;
-                       devpriv->output_bits[port] &= ~port_mask;
-                       devpriv->output_bits[port] |=
-                           port_data & port_mask;
-                       bits = devpriv->output_bits[port];
-                       if (board->invert_outputs)
-                               bits = ~bits;
+                       bits = readb(devpriv->mmio + NI_65XX_IO_DATA_REG(port));
+                       bits ^= s->io_bits;     /* invert if necessary */
+                       bits &= ~port_mask;
+                       bits |= (port_data & port_mask);
+                       bits ^= s->io_bits;     /* invert back */
                        writeb(bits, devpriv->mmio + NI_65XX_IO_DATA_REG(port));
                }
-               port_read_bits = readb(devpriv->mmio +
-                                      NI_65XX_IO_DATA_REG(port));
-               if (s->type == COMEDI_SUBD_DO && board->invert_outputs) {
-                       /* Outputs inverted, so invert value read back from
-                        * DO subdevice.  (Does not apply to boards with DIO
-                        * subdevice.) */
-                       port_read_bits ^= 0xFF;
-               }
+
+               /* read back the actual state */
+               bits = readb(devpriv->mmio + NI_65XX_IO_DATA_REG(port));
+               bits ^= s->io_bits;     /* invert if necessary */
                if (bitshift > 0)
-                       port_read_bits <<= bitshift;
+                       bits <<= bitshift;
                else
-                       port_read_bits >>= -bitshift;
+                       bits >>= -bitshift;
 
-               read_bits |= port_read_bits;
+               read_bits |= bits;
        }
        data[1] = read_bits;
        return insn->n;
@@ -511,7 +581,8 @@ static int ni_65xx_intr_cancel(struct comedi_device *dev,
 
 static int ni_65xx_intr_insn_bits(struct comedi_device *dev,
                                  struct comedi_subdevice *s,
-                                 struct comedi_insn *insn, unsigned int *data)
+                                 struct comedi_insn *insn,
+                                 unsigned int *data)
 {
        data[1] = 0;
        return insn->n;
@@ -522,8 +593,6 @@ static int ni_65xx_intr_insn_config(struct comedi_device *dev,
                                    struct comedi_insn *insn,
                                    unsigned int *data)
 {
-       struct ni_65xx_private *devpriv = dev->private;
-
        switch (data[0]) {
        case INSN_CONFIG_CHANGE_NOTIFY:
                /* add instruction to check_insn_config_length() */
@@ -533,26 +602,7 @@ static int ni_65xx_intr_insn_config(struct comedi_device *dev,
                /*
                 * This only works for the first 4 ports (32 channels)!
                 */
-
-               /* set the channels to monitor for rising edges */
-               writeb(data[1] & 0xff,
-                      devpriv->mmio + NI_65XX_RISE_EDGE_ENA_REG(0));
-               writeb((data[1] >> 8) & 0xff,
-                      devpriv->mmio + NI_65XX_RISE_EDGE_ENA_REG(1));
-               writeb((data[1] >> 16) & 0xff,
-                      devpriv->mmio + NI_65XX_RISE_EDGE_ENA_REG(2));
-               writeb((data[1] >> 24) & 0xff,
-                      devpriv->mmio + NI_65XX_RISE_EDGE_ENA_REG(3));
-
-               /* set the channels to monitor for falling edges */
-               writeb(data[2] & 0xff,
-                      devpriv->mmio + NI_65XX_FALL_EDGE_ENA_REG(0));
-               writeb((data[2] >> 8) & 0xff,
-                      devpriv->mmio + NI_65XX_FALL_EDGE_ENA_REG(1));
-               writeb((data[2] >> 16) & 0xff,
-                      devpriv->mmio + NI_65XX_FALL_EDGE_ENA_REG(2));
-               writeb((data[2] >> 24) & 0xff,
-                      devpriv->mmio + NI_65XX_FALL_EDGE_ENA_REG(3));
+               ni_65xx_update_edge_detection(dev, 0, data[1], data[2]);
                break;
        default:
                return -EINVAL;
@@ -662,6 +712,21 @@ static int ni_65xx_auto_attach(struct comedi_device *dev,
 
                /* the output ports always start after the input ports */
                s->private = (void *)(unsigned long)board->num_di_ports;
+
+               /*
+                * Use the io_bits to handle the inverted outputs.  Inverted
+                * outputs are only supported if the "legacy_invert_outputs"
+                * module parameter is set to "true".
+                */
+               if (ni_65xx_legacy_invert_outputs && board->legacy_invert)
+                       s->io_bits = 0xff;
+
+               /* reset all output ports to comedi '0' */
+               for (i = 0; i < board->num_do_ports; ++i) {
+                       writeb(s->io_bits,      /* inverted if necessary */
+                              devpriv->mmio +
+                              NI_65XX_IO_DATA_REG(board->num_di_ports + i));
+               }
        } else {
                s->type         = COMEDI_SUBD_UNUSED;
        }
@@ -705,16 +770,7 @@ static int ni_65xx_auto_attach(struct comedi_device *dev,
                s->cancel       = ni_65xx_intr_cancel;
        }
 
-       for (i = 0; i < ni_65xx_total_num_ports(board); ++i) {
-               writeb(0x00, devpriv->mmio + NI_65XX_FILTER_ENA(i));
-               if (board->invert_outputs)
-                       writeb(0x01, devpriv->mmio + NI_65XX_IO_DATA_REG(i));
-               else
-                       writeb(0x00, devpriv->mmio + NI_65XX_IO_DATA_REG(i));
-       }
-
-       /* Set filter interval to 0  (32bit reg) */
-       writel(0x00000000, devpriv->mmio + NI_65XX_FILTER_REG);
+       ni_65xx_disable_input_filters(dev);
 
        return 0;
 }