gpio: Add GPIO support for the ACCES PCI-IDIO-16
authorWilliam Breathitt Gray <vilhelm.gray@gmail.com>
Wed, 1 Feb 2017 20:37:55 +0000 (15:37 -0500)
committerLinus Walleij <linus.walleij@linaro.org>
Mon, 6 Feb 2017 10:01:06 +0000 (11:01 +0100)
The ACCES PCI-IDIO-16 device provides 32 lines of digital I/O (16 lines
of optically-isolated digital inputs for AC and DC control signals, and
16 lines of solid state switch digital outputs). An interrupt is
generated when any of the inputs change state (low to high or high to
low). Input filter control is not supported by this driver, and input
filters are deactivated by this driver.

Signed-off-by: William Breathitt Gray <vilhelm.gray@gmail.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
MAINTAINERS
drivers/gpio/Kconfig
drivers/gpio/Makefile
drivers/gpio/gpio-pci-idio-16.c [new file with mode: 0644]

index cfff2c9e3d9470550fd47dcd7b2638c77121c607..25bf100967de2ade1965251be1990199a20ce6e2 100644 (file)
@@ -266,6 +266,12 @@ L: linux-iio@vger.kernel.org
 S:     Maintained
 F:     drivers/iio/counter/104-quad-8.c
 
+ACCES PCI-IDIO-16 GPIO DRIVER
+M:     William Breathitt Gray <vilhelm.gray@gmail.com>
+L:     linux-gpio@vger.kernel.org
+S:     Maintained
+F:     drivers/gpio/gpio-pci-idio-16.c
+
 ACENIC DRIVER
 M:     Jes Sorensen <jes@trained-monkey.org>
 L:     linux-acenic@sunsite.dk
index 28e621900a14a4a426413898e759e104df54c15c..d4cec615a71ec4450dd34b284e92436567d838d8 100644 (file)
@@ -1157,6 +1157,15 @@ config GPIO_PCH
          ML7223/ML7831 is companion chip for Intel Atom E6xx series.
          ML7223/ML7831 is completely compatible for Intel EG20T PCH.
 
+config GPIO_PCI_IDIO_16
+       tristate "ACCES PCI-IDIO-16 GPIO support"
+       select GPIOLIB_IRQCHIP
+       help
+         Enables GPIO support for the ACCES PCI-IDIO-16. An interrupt is
+         generated when any of the inputs change state (low to high or high to
+         low). Input filter control is not supported by this driver, and the
+         input filters are deactivated by this driver.
+
 config GPIO_RDC321X
        tristate "RDC R-321x GPIO support"
        select MFD_CORE
index ee8b0dcac623b625e9e23ea86538fa5227fcb6cf..becb96c724fef59bf770c89268771c10acf6e48f 100644 (file)
@@ -92,6 +92,7 @@ obj-$(CONFIG_GPIO_OMAP)               += gpio-omap.o
 obj-$(CONFIG_GPIO_PCA953X)     += gpio-pca953x.o
 obj-$(CONFIG_GPIO_PCF857X)     += gpio-pcf857x.o
 obj-$(CONFIG_GPIO_PCH)         += gpio-pch.o
+obj-$(CONFIG_GPIO_PCI_IDIO_16) += gpio-pci-idio-16.o
 obj-$(CONFIG_GPIO_PISOSR)      += gpio-pisosr.o
 obj-$(CONFIG_GPIO_PL061)       += gpio-pl061.o
 obj-$(CONFIG_GPIO_PXA)         += gpio-pxa.o
diff --git a/drivers/gpio/gpio-pci-idio-16.c b/drivers/gpio/gpio-pci-idio-16.c
new file mode 100644 (file)
index 0000000..0220787
--- /dev/null
@@ -0,0 +1,348 @@
+/*
+ * GPIO driver for the ACCES PCI-IDIO-16
+ * Copyright (C) 2017 William Breathitt Gray
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/irqdesc.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+/**
+ * struct idio_16_gpio_reg - GPIO device registers structure
+ * @out0_7:    Read: FET Drive Outputs 0-7
+ *             Write: FET Drive Outputs 0-7
+ * @in0_7:     Read: Isolated Inputs 0-7
+ *             Write: Clear Interrupt
+ * @irq_ctl:   Read: Enable IRQ
+ *             Write: Disable IRQ
+ * @filter_ctl:        Read: Activate Input Filters 0-15
+ *             Write: Deactivate Input Filters 0-15
+ * @out8_15:   Read: FET Drive Outputs 8-15
+ *             Write: FET Drive Outputs 8-15
+ * @in8_15:    Read: Isolated Inputs 8-15
+ *             Write: Unused
+ * @irq_status:        Read: Interrupt status
+ *             Write: Unused
+ */
+struct idio_16_gpio_reg {
+       u8 out0_7;
+       u8 in0_7;
+       u8 irq_ctl;
+       u8 filter_ctl;
+       u8 out8_15;
+       u8 in8_15;
+       u8 irq_status;
+};
+
+/**
+ * struct idio_16_gpio - GPIO device private data structure
+ * @chip:      instance of the gpio_chip
+ * @lock:      synchronization lock to prevent I/O race conditions
+ * @reg:       I/O address offset for the GPIO device registers
+ * @irq_mask:  I/O bits affected by interrupts
+ */
+struct idio_16_gpio {
+       struct gpio_chip chip;
+       spinlock_t lock;
+       struct idio_16_gpio_reg __iomem *reg;
+       unsigned long irq_mask;
+};
+
+static int idio_16_gpio_get_direction(struct gpio_chip *chip,
+       unsigned int offset)
+{
+       if (offset > 15)
+               return 1;
+
+       return 0;
+}
+
+static int idio_16_gpio_direction_input(struct gpio_chip *chip,
+       unsigned int offset)
+{
+       return 0;
+}
+
+static int idio_16_gpio_direction_output(struct gpio_chip *chip,
+       unsigned int offset, int value)
+{
+       chip->set(chip, offset, value);
+       return 0;
+}
+
+static int idio_16_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+       struct idio_16_gpio *const idio16gpio = gpiochip_get_data(chip);
+       unsigned long mask = BIT(offset);
+
+       if (offset < 8)
+               return !!(ioread8(&idio16gpio->reg->out0_7) & mask);
+
+       if (offset < 16)
+               return !!(ioread8(&idio16gpio->reg->out8_15) & (mask >> 8));
+
+       if (offset < 24)
+               return !!(ioread8(&idio16gpio->reg->in0_7) & (mask >> 16));
+
+       return !!(ioread8(&idio16gpio->reg->in8_15) & (mask >> 24));
+}
+
+static void idio_16_gpio_set(struct gpio_chip *chip, unsigned int offset,
+       int value)
+{
+       struct idio_16_gpio *const idio16gpio = gpiochip_get_data(chip);
+       unsigned int mask = BIT(offset);
+       void __iomem *base;
+       unsigned long flags;
+       unsigned int out_state;
+
+       if (offset > 15)
+               return;
+
+       if (offset > 7) {
+               mask >>= 8;
+               base = &idio16gpio->reg->out8_15;
+       } else
+               base = &idio16gpio->reg->out0_7;
+
+       spin_lock_irqsave(&idio16gpio->lock, flags);
+
+       if (value)
+               out_state = ioread8(base) | mask;
+       else
+               out_state = ioread8(base) & ~mask;
+
+       iowrite8(out_state, base);
+
+       spin_unlock_irqrestore(&idio16gpio->lock, flags);
+}
+
+static void idio_16_gpio_set_multiple(struct gpio_chip *chip,
+       unsigned long *mask, unsigned long *bits)
+{
+       struct idio_16_gpio *const idio16gpio = gpiochip_get_data(chip);
+       unsigned long flags;
+       unsigned int out_state;
+
+       spin_lock_irqsave(&idio16gpio->lock, flags);
+
+       /* process output lines 0-7 */
+       if (*mask & 0xFF) {
+               out_state = ioread8(&idio16gpio->reg->out0_7) & ~*mask;
+               out_state |= *mask & *bits;
+               iowrite8(out_state, &idio16gpio->reg->out0_7);
+       }
+
+       /* shift to next output line word */
+       *mask >>= 8;
+
+       /* process output lines 8-15 */
+       if (*mask & 0xFF) {
+               *bits >>= 8;
+               out_state = ioread8(&idio16gpio->reg->out8_15) & ~*mask;
+               out_state |= *mask & *bits;
+               iowrite8(out_state, &idio16gpio->reg->out8_15);
+       }
+
+       spin_unlock_irqrestore(&idio16gpio->lock, flags);
+}
+
+static void idio_16_irq_ack(struct irq_data *data)
+{
+}
+
+static void idio_16_irq_mask(struct irq_data *data)
+{
+       struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+       struct idio_16_gpio *const idio16gpio = gpiochip_get_data(chip);
+       const unsigned long mask = BIT(irqd_to_hwirq(data));
+       unsigned long flags;
+
+       idio16gpio->irq_mask &= ~mask;
+
+       if (!idio16gpio->irq_mask) {
+               spin_lock_irqsave(&idio16gpio->lock, flags);
+
+               iowrite8(0, &idio16gpio->reg->irq_ctl);
+
+               spin_unlock_irqrestore(&idio16gpio->lock, flags);
+       }
+}
+
+static void idio_16_irq_unmask(struct irq_data *data)
+{
+       struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+       struct idio_16_gpio *const idio16gpio = gpiochip_get_data(chip);
+       const unsigned long mask = BIT(irqd_to_hwirq(data));
+       const unsigned long prev_irq_mask = idio16gpio->irq_mask;
+       unsigned long flags;
+
+       idio16gpio->irq_mask |= mask;
+
+       if (!prev_irq_mask) {
+               spin_lock_irqsave(&idio16gpio->lock, flags);
+
+               ioread8(&idio16gpio->reg->irq_ctl);
+
+               spin_unlock_irqrestore(&idio16gpio->lock, flags);
+       }
+}
+
+static int idio_16_irq_set_type(struct irq_data *data, unsigned int flow_type)
+{
+       /* The only valid irq types are none and both-edges */
+       if (flow_type != IRQ_TYPE_NONE &&
+               (flow_type & IRQ_TYPE_EDGE_BOTH) != IRQ_TYPE_EDGE_BOTH)
+               return -EINVAL;
+
+       return 0;
+}
+
+static struct irq_chip idio_16_irqchip = {
+       .name = "pci-idio-16",
+       .irq_ack = idio_16_irq_ack,
+       .irq_mask = idio_16_irq_mask,
+       .irq_unmask = idio_16_irq_unmask,
+       .irq_set_type = idio_16_irq_set_type
+};
+
+static irqreturn_t idio_16_irq_handler(int irq, void *dev_id)
+{
+       struct idio_16_gpio *const idio16gpio = dev_id;
+       unsigned int irq_status;
+       struct gpio_chip *const chip = &idio16gpio->chip;
+       int gpio;
+
+       spin_lock(&idio16gpio->lock);
+
+       irq_status = ioread8(&idio16gpio->reg->irq_status);
+
+       spin_unlock(&idio16gpio->lock);
+
+       /* Make sure our device generated IRQ */
+       if (!(irq_status & 0x3) || !(irq_status & 0x4))
+               return IRQ_NONE;
+
+       for_each_set_bit(gpio, &idio16gpio->irq_mask, chip->ngpio)
+               generic_handle_irq(irq_find_mapping(chip->irqdomain, gpio));
+
+       spin_lock(&idio16gpio->lock);
+
+       /* Clear interrupt */
+       iowrite8(0, &idio16gpio->reg->in0_7);
+
+       spin_unlock(&idio16gpio->lock);
+
+       return IRQ_HANDLED;
+}
+
+#define IDIO_16_NGPIO 32
+static const char *idio_16_names[IDIO_16_NGPIO] = {
+       "OUT0", "OUT1", "OUT2", "OUT3", "OUT4", "OUT5", "OUT6", "OUT7",
+       "OUT8", "OUT9", "OUT10", "OUT11", "OUT12", "OUT13", "OUT14", "OUT15",
+       "IIN0", "IIN1", "IIN2", "IIN3", "IIN4", "IIN5", "IIN6", "IIN7",
+       "IIN8", "IIN9", "IIN10", "IIN11", "IIN12", "IIN13", "IIN14", "IIN15"
+};
+
+static int idio_16_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+       struct device *const dev = &pdev->dev;
+       struct idio_16_gpio *idio16gpio;
+       int err;
+       const char *const name = pci_name(pdev);
+
+       idio16gpio = devm_kzalloc(dev, sizeof(*idio16gpio), GFP_KERNEL);
+       if (!idio16gpio)
+               return -ENOMEM;
+
+       err = pcim_enable_device(pdev);
+       if (err) {
+               dev_err(dev, "Failed to enable PCI device (%d)\n", err);
+               return err;
+       }
+
+       err = pcim_iomap_regions(pdev, 0x1, name);
+       if (err) {
+               dev_err(dev, "Unable to map PCI I/O addresses (%d)\n", err);
+               return err;
+       }
+
+       idio16gpio->reg = pcim_iomap_table(pdev)[0];
+
+       /* Deactivate input filters */
+       iowrite8(0, &idio16gpio->reg->filter_ctl);
+
+       idio16gpio->chip.label = name;
+       idio16gpio->chip.parent = dev;
+       idio16gpio->chip.owner = THIS_MODULE;
+       idio16gpio->chip.base = -1;
+       idio16gpio->chip.ngpio = IDIO_16_NGPIO;
+       idio16gpio->chip.names = idio_16_names;
+       idio16gpio->chip.get_direction = idio_16_gpio_get_direction;
+       idio16gpio->chip.direction_input = idio_16_gpio_direction_input;
+       idio16gpio->chip.direction_output = idio_16_gpio_direction_output;
+       idio16gpio->chip.get = idio_16_gpio_get;
+       idio16gpio->chip.set = idio_16_gpio_set;
+       idio16gpio->chip.set_multiple = idio_16_gpio_set_multiple;
+
+       spin_lock_init(&idio16gpio->lock);
+
+       err = devm_gpiochip_add_data(dev, &idio16gpio->chip, idio16gpio);
+       if (err) {
+               dev_err(dev, "GPIO registering failed (%d)\n", err);
+               return err;
+       }
+
+       /* Disable IRQ by default and clear any pending interrupt */
+       iowrite8(0, &idio16gpio->reg->irq_ctl);
+       iowrite8(0, &idio16gpio->reg->in0_7);
+
+       err = gpiochip_irqchip_add(&idio16gpio->chip, &idio_16_irqchip, 0,
+               handle_edge_irq, IRQ_TYPE_NONE);
+       if (err) {
+               dev_err(dev, "Could not add irqchip (%d)\n", err);
+               return err;
+       }
+
+       err = devm_request_irq(dev, pdev->irq, idio_16_irq_handler, IRQF_SHARED,
+               name, idio16gpio);
+       if (err) {
+               dev_err(dev, "IRQ handler registering failed (%d)\n", err);
+               return err;
+       }
+
+       return 0;
+}
+
+static const struct pci_device_id idio_16_pci_dev_id[] = {
+       { PCI_DEVICE(0x494F, 0x0F00) }, { 0 }
+};
+MODULE_DEVICE_TABLE(pci, idio_16_pci_dev_id);
+
+static struct pci_driver idio_16_driver = {
+       .name = "pci-idio-16",
+       .id_table = idio_16_pci_dev_id,
+       .probe = idio_16_probe
+};
+
+module_pci_driver(idio_16_driver);
+
+MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
+MODULE_DESCRIPTION("ACCES PCI-IDIO-16 GPIO driver");
+MODULE_LICENSE("GPL v2");