summaryrefslogtreecommitdiff
path: root/drivers/gpio
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpio')
-rw-r--r--drivers/gpio/Kconfig35
-rw-r--r--drivers/gpio/Makefile3
-rw-r--r--drivers/gpio/devres.c32
-rw-r--r--drivers/gpio/gpio-74xx-mmio.c170
-rw-r--r--drivers/gpio/gpio-amd8111.c1
-rw-r--r--drivers/gpio/gpio-bcm-kona.c4
-rw-r--r--drivers/gpio/gpio-cs5535.c11
-rw-r--r--drivers/gpio/gpio-davinci.c5
-rw-r--r--drivers/gpio/gpio-dln2.c553
-rw-r--r--drivers/gpio/gpio-dwapb.c4
-rw-r--r--drivers/gpio/gpio-em.c11
-rw-r--r--drivers/gpio/gpio-grgpio.c1
-rw-r--r--drivers/gpio/gpio-mcp23s08.c41
-rw-r--r--drivers/gpio/gpio-mpc8xxx.c27
-rw-r--r--drivers/gpio/gpio-msm-v1.c2
-rw-r--r--drivers/gpio/gpio-mvebu.c99
-rw-r--r--drivers/gpio/gpio-mxs.c13
-rw-r--r--drivers/gpio/gpio-omap.c10
-rw-r--r--drivers/gpio/gpio-pl061.c20
-rw-r--r--drivers/gpio/gpio-rcar.c27
-rw-r--r--drivers/gpio/gpio-sch.c293
-rw-r--r--drivers/gpio/gpio-spear-spics.c2
-rw-r--r--drivers/gpio/gpio-stp-xway.c8
-rw-r--r--drivers/gpio/gpio-tb10x.c7
-rw-r--r--drivers/gpio/gpio-tc3589x.c2
-rw-r--r--drivers/gpio/gpio-tegra.c4
-rw-r--r--drivers/gpio/gpio-tz1090.c2
-rw-r--r--drivers/gpio/gpio-vf610.c295
-rw-r--r--drivers/gpio/gpio-vr41xx.c4
-rw-r--r--drivers/gpio/gpio-zynq.c2
-rw-r--r--drivers/gpio/gpiolib-acpi.c185
-rw-r--r--drivers/gpio/gpiolib-legacy.c12
-rw-r--r--drivers/gpio/gpiolib-sysfs.c6
-rw-r--r--drivers/gpio/gpiolib.c326
-rw-r--r--drivers/gpio/gpiolib.h7
35 files changed, 1907 insertions, 317 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 0959ca9b6b27..633ec216e185 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -112,6 +112,20 @@ config GPIO_MAX730X
comment "Memory mapped GPIO drivers:"
+config GPIO_74XX_MMIO
+ tristate "GPIO driver for 74xx-ICs with MMIO access"
+ depends on OF_GPIO
+ select GPIO_GENERIC
+ help
+ Say yes here to support GPIO functionality for 74xx-compatible ICs
+ with MMIO access. Compatible models include:
+ 1 bit: 741G125 (Input), 741G74 (Output)
+ 2 bits: 742G125 (Input), 7474 (Output)
+ 4 bits: 74125 (Input), 74175 (Output)
+ 6 bits: 74365 (Input), 74174 (Output)
+ 8 bits: 74244 (Input), 74273 (Output)
+ 16 bits: 741624 (Input), 7416374 (Output)
+
config GPIO_CLPS711X
tristate "CLPS711X GPIO support"
depends on ARCH_CLPS711X || COMPILE_TEST
@@ -134,6 +148,8 @@ config GPIO_GENERIC_PLATFORM
config GPIO_DWAPB
tristate "Synopsys DesignWare APB GPIO driver"
+ depends on ARM
+ depends on OF_GPIO
select GPIO_GENERIC
select GENERIC_IRQ_CHIP
help
@@ -333,6 +349,13 @@ config GPIO_TZ1090_PDC
help
Say yes here to support Toumaz Xenif TZ1090 PDC GPIOs.
+config GPIO_VF610
+ def_bool y
+ depends on ARCH_MXC && SOC_VF610
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support Vybrid vf610 GPIOs.
+
config GPIO_XGENE
bool "APM X-Gene GPIO controller support"
depends on ARM64 && OF_GPIO
@@ -905,4 +928,16 @@ config GPIO_VIPERBOARD
River Tech's viperboard.h for detailed meaning
of the module parameters.
+config GPIO_DLN2
+ tristate "Diolan DLN2 GPIO support"
+ depends on MFD_DLN2
+ select GPIOLIB_IRQCHIP
+
+ help
+ Select this option to enable GPIO driver for the Diolan DLN2
+ board.
+
+ This driver can also be built as a module. If so, the module
+ will be called gpio-dln2.
+
endif
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index e5d346cf3b6e..81755f1305e6 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_GPIO_ACPI) += gpiolib-acpi.o
obj-$(CONFIG_GPIO_GENERIC) += gpio-generic.o
obj-$(CONFIG_GPIO_74X164) += gpio-74x164.o
+obj-$(CONFIG_GPIO_74XX_MMIO) += gpio-74xx-mmio.o
obj-$(CONFIG_GPIO_ADNP) += gpio-adnp.o
obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o
obj-$(CONFIG_GPIO_ADP5588) += gpio-adp5588.o
@@ -26,6 +27,7 @@ obj-$(CONFIG_GPIO_CRYSTAL_COVE) += gpio-crystalcove.o
obj-$(CONFIG_GPIO_DA9052) += gpio-da9052.o
obj-$(CONFIG_GPIO_DA9055) += gpio-da9055.o
obj-$(CONFIG_GPIO_DAVINCI) += gpio-davinci.o
+obj-$(CONFIG_GPIO_DLN2) += gpio-dln2.o
obj-$(CONFIG_GPIO_DWAPB) += gpio-dwapb.o
obj-$(CONFIG_GPIO_EM) += gpio-em.o
obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o
@@ -95,6 +97,7 @@ obj-$(CONFIG_GPIO_TWL6040) += gpio-twl6040.o
obj-$(CONFIG_GPIO_TZ1090) += gpio-tz1090.o
obj-$(CONFIG_GPIO_TZ1090_PDC) += gpio-tz1090-pdc.o
obj-$(CONFIG_GPIO_UCB1400) += gpio-ucb1400.o
+obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o
obj-$(CONFIG_GPIO_VIPERBOARD) += gpio-viperboard.o
obj-$(CONFIG_GPIO_VR41XX) += gpio-vr41xx.o
obj-$(CONFIG_GPIO_VX855) += gpio-vx855.o
diff --git a/drivers/gpio/devres.c b/drivers/gpio/devres.c
index 954b9f6b0ef8..13dbd3dfc33a 100644
--- a/drivers/gpio/devres.c
+++ b/drivers/gpio/devres.c
@@ -109,6 +109,38 @@ struct gpio_desc *__must_check __devm_gpiod_get_index(struct device *dev,
EXPORT_SYMBOL(__devm_gpiod_get_index);
/**
+ * devm_get_gpiod_from_child - get a GPIO descriptor from a device's child node
+ * @dev: GPIO consumer
+ * @child: firmware node (child of @dev)
+ *
+ * GPIO descriptors returned from this function are automatically disposed on
+ * driver detach.
+ */
+struct gpio_desc *devm_get_gpiod_from_child(struct device *dev,
+ struct fwnode_handle *child)
+{
+ struct gpio_desc **dr;
+ struct gpio_desc *desc;
+
+ dr = devres_alloc(devm_gpiod_release, sizeof(struct gpio_desc *),
+ GFP_KERNEL);
+ if (!dr)
+ return ERR_PTR(-ENOMEM);
+
+ desc = fwnode_get_named_gpiod(child, "gpios");
+ if (IS_ERR(desc)) {
+ devres_free(dr);
+ return desc;
+ }
+
+ *dr = desc;
+ devres_add(dev, dr);
+
+ return desc;
+}
+EXPORT_SYMBOL(devm_get_gpiod_from_child);
+
+/**
* devm_gpiod_get_index_optional - Resource-managed gpiod_get_index_optional()
* @dev: GPIO consumer
* @con_id: function within the GPIO consumer
diff --git a/drivers/gpio/gpio-74xx-mmio.c b/drivers/gpio/gpio-74xx-mmio.c
new file mode 100644
index 000000000000..0763655cca6c
--- /dev/null
+++ b/drivers/gpio/gpio-74xx-mmio.c
@@ -0,0 +1,170 @@
+/*
+ * 74xx MMIO GPIO driver
+ *
+ * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/basic_mmio_gpio.h>
+#include <linux/platform_device.h>
+
+#define MMIO_74XX_DIR_IN (0 << 8)
+#define MMIO_74XX_DIR_OUT (1 << 8)
+#define MMIO_74XX_BIT_CNT(x) ((x) & 0xff)
+
+struct mmio_74xx_gpio_priv {
+ struct bgpio_chip bgc;
+ unsigned flags;
+};
+
+static const struct of_device_id mmio_74xx_gpio_ids[] = {
+ {
+ .compatible = "ti,741g125",
+ .data = (const void *)(MMIO_74XX_DIR_IN | 1),
+ },
+ {
+ .compatible = "ti,742g125",
+ .data = (const void *)(MMIO_74XX_DIR_IN | 2),
+ },
+ {
+ .compatible = "ti,74125",
+ .data = (const void *)(MMIO_74XX_DIR_IN | 4),
+ },
+ {
+ .compatible = "ti,74365",
+ .data = (const void *)(MMIO_74XX_DIR_IN | 6),
+ },
+ {
+ .compatible = "ti,74244",
+ .data = (const void *)(MMIO_74XX_DIR_IN | 8),
+ },
+ {
+ .compatible = "ti,741624",
+ .data = (const void *)(MMIO_74XX_DIR_IN | 16),
+ },
+ {
+ .compatible = "ti,741g74",
+ .data = (const void *)(MMIO_74XX_DIR_OUT | 1),
+ },
+ {
+ .compatible = "ti,7474",
+ .data = (const void *)(MMIO_74XX_DIR_OUT | 2),
+ },
+ {
+ .compatible = "ti,74175",
+ .data = (const void *)(MMIO_74XX_DIR_OUT | 4),
+ },
+ {
+ .compatible = "ti,74174",
+ .data = (const void *)(MMIO_74XX_DIR_OUT | 6),
+ },
+ {
+ .compatible = "ti,74273",
+ .data = (const void *)(MMIO_74XX_DIR_OUT | 8),
+ },
+ {
+ .compatible = "ti,7416374",
+ .data = (const void *)(MMIO_74XX_DIR_OUT | 16),
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mmio_74xx_gpio_ids);
+
+static inline struct mmio_74xx_gpio_priv *to_74xx_gpio(struct gpio_chip *gc)
+{
+ struct bgpio_chip *bgc = to_bgpio_chip(gc);
+
+ return container_of(bgc, struct mmio_74xx_gpio_priv, bgc);
+}
+
+static int mmio_74xx_get_direction(struct gpio_chip *gc, unsigned offset)
+{
+ struct mmio_74xx_gpio_priv *priv = to_74xx_gpio(gc);
+
+ return (priv->flags & MMIO_74XX_DIR_OUT) ? GPIOF_DIR_OUT : GPIOF_DIR_IN;
+}
+
+static int mmio_74xx_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct mmio_74xx_gpio_priv *priv = to_74xx_gpio(gc);
+
+ return (priv->flags & MMIO_74XX_DIR_OUT) ? -ENOTSUPP : 0;
+}
+
+static int mmio_74xx_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ struct mmio_74xx_gpio_priv *priv = to_74xx_gpio(gc);
+
+ if (priv->flags & MMIO_74XX_DIR_OUT) {
+ gc->set(gc, gpio, val);
+ return 0;
+ }
+
+ return -ENOTSUPP;
+}
+
+static int mmio_74xx_gpio_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *of_id =
+ of_match_device(mmio_74xx_gpio_ids, &pdev->dev);
+ struct mmio_74xx_gpio_priv *priv;
+ struct resource *res;
+ void __iomem *dat;
+ int err;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dat = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dat))
+ return PTR_ERR(dat);
+
+ priv->flags = (unsigned)of_id->data;
+
+ err = bgpio_init(&priv->bgc, &pdev->dev,
+ DIV_ROUND_UP(MMIO_74XX_BIT_CNT(priv->flags), 8),
+ dat, NULL, NULL, NULL, NULL, 0);
+ if (err)
+ return err;
+
+ priv->bgc.gc.direction_input = mmio_74xx_dir_in;
+ priv->bgc.gc.direction_output = mmio_74xx_dir_out;
+ priv->bgc.gc.get_direction = mmio_74xx_get_direction;
+ priv->bgc.gc.ngpio = MMIO_74XX_BIT_CNT(priv->flags);
+ priv->bgc.gc.owner = THIS_MODULE;
+
+ platform_set_drvdata(pdev, priv);
+
+ return gpiochip_add(&priv->bgc.gc);
+}
+
+static int mmio_74xx_gpio_remove(struct platform_device *pdev)
+{
+ struct mmio_74xx_gpio_priv *priv = platform_get_drvdata(pdev);
+
+ return bgpio_remove(&priv->bgc);
+}
+
+static struct platform_driver mmio_74xx_gpio_driver = {
+ .driver = {
+ .name = "74xx-mmio-gpio",
+ .of_match_table = mmio_74xx_gpio_ids,
+ },
+ .probe = mmio_74xx_gpio_probe,
+ .remove = mmio_74xx_gpio_remove,
+};
+module_platform_driver(mmio_74xx_gpio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
+MODULE_DESCRIPTION("74xx MMIO GPIO driver");
diff --git a/drivers/gpio/gpio-amd8111.c b/drivers/gpio/gpio-amd8111.c
index 3c09f1a6872a..d3d2d1099f64 100644
--- a/drivers/gpio/gpio-amd8111.c
+++ b/drivers/gpio/gpio-amd8111.c
@@ -223,6 +223,7 @@ found:
if (err) {
printk(KERN_ERR "GPIO registering failed (%d)\n",
err);
+ ioport_unmap(gp.pm);
release_region(gp.pmbase + PMBASE_OFFSET, PMBASE_SIZE);
goto out;
}
diff --git a/drivers/gpio/gpio-bcm-kona.c b/drivers/gpio/gpio-bcm-kona.c
index 9fc05581cbd6..b164ce837b43 100644
--- a/drivers/gpio/gpio-bcm-kona.c
+++ b/drivers/gpio/gpio-bcm-kona.c
@@ -470,7 +470,7 @@ static int bcm_kona_gpio_irq_reqres(struct irq_data *d)
{
struct bcm_kona_gpio *kona_gpio = irq_data_get_irq_chip_data(d);
- if (gpio_lock_as_irq(&kona_gpio->gpio_chip, d->hwirq)) {
+ if (gpiochip_lock_as_irq(&kona_gpio->gpio_chip, d->hwirq)) {
dev_err(kona_gpio->gpio_chip.dev,
"unable to lock HW IRQ %lu for IRQ\n",
d->hwirq);
@@ -483,7 +483,7 @@ static void bcm_kona_gpio_irq_relres(struct irq_data *d)
{
struct bcm_kona_gpio *kona_gpio = irq_data_get_irq_chip_data(d);
- gpio_unlock_as_irq(&kona_gpio->gpio_chip, d->hwirq);
+ gpiochip_unlock_as_irq(&kona_gpio->gpio_chip, d->hwirq);
}
static struct irq_chip bcm_gpio_irq_chip = {
diff --git a/drivers/gpio/gpio-cs5535.c b/drivers/gpio/gpio-cs5535.c
index feb4cecfcb01..7b0b198a563d 100644
--- a/drivers/gpio/gpio-cs5535.c
+++ b/drivers/gpio/gpio-cs5535.c
@@ -322,7 +322,8 @@ static int cs5535_gpio_probe(struct platform_device *pdev)
goto done;
}
- if (!request_region(res->start, resource_size(res), pdev->name)) {
+ if (!devm_request_region(&pdev->dev, res->start, resource_size(res),
+ pdev->name)) {
dev_err(&pdev->dev, "can't request region\n");
goto done;
}
@@ -348,24 +349,18 @@ static int cs5535_gpio_probe(struct platform_device *pdev)
/* finally, register with the generic GPIO API */
err = gpiochip_add(&cs5535_gpio_chip.chip);
if (err)
- goto release_region;
+ goto done;
return 0;
-release_region:
- release_region(res->start, resource_size(res));
done:
return err;
}
static int cs5535_gpio_remove(struct platform_device *pdev)
{
- struct resource *r;
-
gpiochip_remove(&cs5535_gpio_chip.chip);
- r = platform_get_resource(pdev, IORESOURCE_IO, 0);
- release_region(r->start, resource_size(r));
return 0;
}
diff --git a/drivers/gpio/gpio-davinci.c b/drivers/gpio/gpio-davinci.c
index ab06a92faac6..c5e05c82d67c 100644
--- a/drivers/gpio/gpio-davinci.c
+++ b/drivers/gpio/gpio-davinci.c
@@ -234,11 +234,6 @@ static int davinci_gpio_probe(struct platform_device *pdev)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
- dev_err(dev, "Invalid memory resource\n");
- return -EBUSY;
- }
-
gpio_base = devm_ioremap_resource(dev, res);
if (IS_ERR(gpio_base))
return PTR_ERR(gpio_base);
diff --git a/drivers/gpio/gpio-dln2.c b/drivers/gpio/gpio-dln2.c
new file mode 100644
index 000000000000..978b51eae2ec
--- /dev/null
+++ b/drivers/gpio/gpio-dln2.c
@@ -0,0 +1,553 @@
+/*
+ * Driver for the Diolan DLN-2 USB-GPIO adapter
+ *
+ * Copyright (c) 2014 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/irqdomain.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/dln2.h>
+
+#define DLN2_GPIO_ID 0x01
+
+#define DLN2_GPIO_GET_PIN_COUNT DLN2_CMD(0x01, DLN2_GPIO_ID)
+#define DLN2_GPIO_SET_DEBOUNCE DLN2_CMD(0x04, DLN2_GPIO_ID)
+#define DLN2_GPIO_GET_DEBOUNCE DLN2_CMD(0x05, DLN2_GPIO_ID)
+#define DLN2_GPIO_PORT_GET_VAL DLN2_CMD(0x06, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_GET_VAL DLN2_CMD(0x0B, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_SET_OUT_VAL DLN2_CMD(0x0C, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_GET_OUT_VAL DLN2_CMD(0x0D, DLN2_GPIO_ID)
+#define DLN2_GPIO_CONDITION_MET_EV DLN2_CMD(0x0F, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_ENABLE DLN2_CMD(0x10, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_DISABLE DLN2_CMD(0x11, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_SET_DIRECTION DLN2_CMD(0x13, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_GET_DIRECTION DLN2_CMD(0x14, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_SET_EVENT_CFG DLN2_CMD(0x1E, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_GET_EVENT_CFG DLN2_CMD(0x1F, DLN2_GPIO_ID)
+
+#define DLN2_GPIO_EVENT_NONE 0
+#define DLN2_GPIO_EVENT_CHANGE 1
+#define DLN2_GPIO_EVENT_LVL_HIGH 2
+#define DLN2_GPIO_EVENT_LVL_LOW 3
+#define DLN2_GPIO_EVENT_CHANGE_RISING 0x11
+#define DLN2_GPIO_EVENT_CHANGE_FALLING 0x21
+#define DLN2_GPIO_EVENT_MASK 0x0F
+
+#define DLN2_GPIO_MAX_PINS 32
+
+struct dln2_irq_work {
+ struct work_struct work;
+ struct dln2_gpio *dln2;
+ int pin;
+ int type;
+};
+
+struct dln2_gpio {
+ struct platform_device *pdev;
+ struct gpio_chip gpio;
+
+ /*
+ * Cache pin direction to save us one transfer, since the hardware has
+ * separate commands to read the in and out values.
+ */
+ DECLARE_BITMAP(output_enabled, DLN2_GPIO_MAX_PINS);
+
+ DECLARE_BITMAP(irqs_masked, DLN2_GPIO_MAX_PINS);
+ DECLARE_BITMAP(irqs_enabled, DLN2_GPIO_MAX_PINS);
+ DECLARE_BITMAP(irqs_pending, DLN2_GPIO_MAX_PINS);
+ struct dln2_irq_work *irq_work;
+};
+
+struct dln2_gpio_pin {
+ __le16 pin;
+};
+
+struct dln2_gpio_pin_val {
+ __le16 pin __packed;
+ u8 value;
+};
+
+static int dln2_gpio_get_pin_count(struct platform_device *pdev)
+{
+ int ret;
+ __le16 count;
+ int len = sizeof(count);
+
+ ret = dln2_transfer_rx(pdev, DLN2_GPIO_GET_PIN_COUNT, &count, &len);
+ if (ret < 0)
+ return ret;
+ if (len < sizeof(count))
+ return -EPROTO;
+
+ return le16_to_cpu(count);
+}
+
+static int dln2_gpio_pin_cmd(struct dln2_gpio *dln2, int cmd, unsigned pin)
+{
+ struct dln2_gpio_pin req = {
+ .pin = cpu_to_le16(pin),
+ };
+
+ return dln2_transfer_tx(dln2->pdev, cmd, &req, sizeof(req));
+}
+
+static int dln2_gpio_pin_val(struct dln2_gpio *dln2, int cmd, unsigned int pin)
+{
+ int ret;
+ struct dln2_gpio_pin req = {
+ .pin = cpu_to_le16(pin),
+ };
+ struct dln2_gpio_pin_val rsp;
+ int len = sizeof(rsp);
+
+ ret = dln2_transfer(dln2->pdev, cmd, &req, sizeof(req), &rsp, &len);
+ if (ret < 0)
+ return ret;
+ if (len < sizeof(rsp) || req.pin != rsp.pin)
+ return -EPROTO;
+
+ return rsp.value;
+}
+
+static int dln2_gpio_pin_get_in_val(struct dln2_gpio *dln2, unsigned int pin)
+{
+ int ret;
+
+ ret = dln2_gpio_pin_val(dln2, DLN2_GPIO_PIN_GET_VAL, pin);
+ if (ret < 0)
+ return ret;
+ return !!ret;
+}
+
+static int dln2_gpio_pin_get_out_val(struct dln2_gpio *dln2, unsigned int pin)
+{
+ int ret;
+
+ ret = dln2_gpio_pin_val(dln2, DLN2_GPIO_PIN_GET_OUT_VAL, pin);
+ if (ret < 0)
+ return ret;
+ return !!ret;
+}
+
+static void dln2_gpio_pin_set_out_val(struct dln2_gpio *dln2,
+ unsigned int pin, int value)
+{
+ struct dln2_gpio_pin_val req = {
+ .pin = cpu_to_le16(pin),
+ .value = value,
+ };
+
+ dln2_transfer_tx(dln2->pdev, DLN2_GPIO_PIN_SET_OUT_VAL, &req,
+ sizeof(req));
+}
+
+#define DLN2_GPIO_DIRECTION_IN 0
+#define DLN2_GPIO_DIRECTION_OUT 1
+
+static int dln2_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+ struct dln2_gpio_pin req = {
+ .pin = cpu_to_le16(offset),
+ };
+ struct dln2_gpio_pin_val rsp;
+ int len = sizeof(rsp);
+ int ret;
+
+ ret = dln2_gpio_pin_cmd(dln2, DLN2_GPIO_PIN_ENABLE, offset);
+ if (ret < 0)
+ return ret;
+
+ /* cache the pin direction */
+ ret = dln2_transfer(dln2->pdev, DLN2_GPIO_PIN_GET_DIRECTION,
+ &req, sizeof(req), &rsp, &len);
+ if (ret < 0)
+ return ret;
+ if (len < sizeof(rsp) || req.pin != rsp.pin) {
+ ret = -EPROTO;
+ goto out_disable;
+ }
+
+ switch (rsp.value) {
+ case DLN2_GPIO_DIRECTION_IN:
+ clear_bit(offset, dln2->output_enabled);
+ return 0;
+ case DLN2_GPIO_DIRECTION_OUT:
+ set_bit(offset, dln2->output_enabled);
+ return 0;
+ default:
+ ret = -EPROTO;
+ goto out_disable;
+ }
+
+out_disable:
+ dln2_gpio_pin_cmd(dln2, DLN2_GPIO_PIN_DISABLE, offset);
+ return ret;
+}
+
+static void dln2_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+ struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+
+ dln2_gpio_pin_cmd(dln2, DLN2_GPIO_PIN_DISABLE, offset);
+}
+
+static int dln2_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+ struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+
+ if (test_bit(offset, dln2->output_enabled))
+ return GPIOF_DIR_OUT;
+
+ return GPIOF_DIR_IN;
+}
+
+static int dln2_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+ int dir;
+
+ dir = dln2_gpio_get_direction(chip, offset);
+ if (dir < 0)
+ return dir;
+
+ if (dir == GPIOF_DIR_IN)
+ return dln2_gpio_pin_get_in_val(dln2, offset);
+
+ return dln2_gpio_pin_get_out_val(dln2, offset);
+}
+
+static void dln2_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+
+ dln2_gpio_pin_set_out_val(dln2, offset, value);
+}
+
+static int dln2_gpio_set_direction(struct gpio_chip *chip, unsigned offset,
+ unsigned dir)
+{
+ struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+ struct dln2_gpio_pin_val req = {
+ .pin = cpu_to_le16(offset),
+ .value = dir,
+ };
+ int ret;
+
+ ret = dln2_transfer_tx(dln2->pdev, DLN2_GPIO_PIN_SET_DIRECTION,
+ &req, sizeof(req));
+ if (ret < 0)
+ return ret;
+
+ if (dir == DLN2_GPIO_DIRECTION_OUT)
+ set_bit(offset, dln2->output_enabled);
+ else
+ clear_bit(offset, dln2->output_enabled);
+
+ return ret;
+}
+
+static int dln2_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ return dln2_gpio_set_direction(chip, offset, DLN2_GPIO_DIRECTION_IN);
+}
+
+static int dln2_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ return dln2_gpio_set_direction(chip, offset, DLN2_GPIO_DIRECTION_OUT);
+}
+
+static int dln2_gpio_set_debounce(struct gpio_chip *chip, unsigned offset,
+ unsigned debounce)
+{
+ struct dln2_gpio *dln2 = container_of(chip, struct dln2_gpio, gpio);
+ __le32 duration = cpu_to_le32(debounce);
+
+ return dln2_transfer_tx(dln2->pdev, DLN2_GPIO_SET_DEBOUNCE,
+ &duration, sizeof(duration));
+}
+
+static int dln2_gpio_set_event_cfg(struct dln2_gpio *dln2, unsigned pin,
+ unsigned type, unsigned period)
+{
+ struct {
+ __le16 pin;
+ u8 type;
+ __le16 period;
+ } __packed req = {
+ .pin = cpu_to_le16(pin),
+ .type = type,
+ .period = cpu_to_le16(period),
+ };
+
+ return dln2_transfer_tx(dln2->pdev, DLN2_GPIO_PIN_SET_EVENT_CFG,
+ &req, sizeof(req));
+}
+
+static void dln2_irq_work(struct work_struct *w)
+{
+ struct dln2_irq_work *iw = container_of(w, struct dln2_irq_work, work);
+ struct dln2_gpio *dln2 = iw->dln2;
+ u8 type = iw->type & DLN2_GPIO_EVENT_MASK;
+
+ if (test_bit(iw->pin, dln2->irqs_enabled))
+ dln2_gpio_set_event_cfg(dln2, iw->pin, type, 0);
+ else
+ dln2_gpio_set_event_cfg(dln2, iw->pin, DLN2_GPIO_EVENT_NONE, 0);
+}
+
+static void dln2_irq_enable(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
+ int pin = irqd_to_hwirq(irqd);
+
+ set_bit(pin, dln2->irqs_enabled);
+ schedule_work(&dln2->irq_work[pin].work);
+}
+
+static void dln2_irq_disable(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
+ int pin = irqd_to_hwirq(irqd);
+
+ clear_bit(pin, dln2->irqs_enabled);
+ schedule_work(&dln2->irq_work[pin].work);
+}
+
+static void dln2_irq_mask(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
+ int pin = irqd_to_hwirq(irqd);
+
+ set_bit(pin, dln2->irqs_masked);
+}
+
+static void dln2_irq_unmask(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
+ struct device *dev = dln2->gpio.dev;
+ int pin = irqd_to_hwirq(irqd);
+
+ if (test_and_clear_bit(pin, dln2->irqs_pending)) {
+ int irq;
+
+ irq = irq_find_mapping(dln2->gpio.irqdomain, pin);
+ if (!irq) {
+ dev_err(dev, "pin %d not mapped to IRQ\n", pin);
+ return;
+ }
+
+ generic_handle_irq(irq);
+ }
+}
+
+static int dln2_irq_set_type(struct irq_data *irqd, unsigned type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct dln2_gpio *dln2 = container_of(gc, struct dln2_gpio, gpio);
+ int pin = irqd_to_hwirq(irqd);
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ dln2->irq_work[pin].type = DLN2_GPIO_EVENT_LVL_HIGH;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ dln2->irq_work[pin].type = DLN2_GPIO_EVENT_LVL_LOW;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ dln2->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ dln2->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE_RISING;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ dln2->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE_FALLING;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct irq_chip dln2_gpio_irqchip = {
+ .name = "dln2-irq",
+ .irq_enable = dln2_irq_enable,
+ .irq_disable = dln2_irq_disable,
+ .irq_mask = dln2_irq_mask,
+ .irq_unmask = dln2_irq_unmask,
+ .irq_set_type = dln2_irq_set_type,
+};
+
+static void dln2_gpio_event(struct platform_device *pdev, u16 echo,
+ const void *data, int len)
+{
+ int pin, irq;
+ const struct {
+ __le16 count;
+ __u8 type;
+ __le16 pin;
+ __u8 value;
+ } __packed *event = data;
+ struct dln2_gpio *dln2 = platform_get_drvdata(pdev);
+
+ if (len < sizeof(*event)) {
+ dev_err(dln2->gpio.dev, "short event message\n");
+ return;
+ }
+
+ pin = le16_to_cpu(event->pin);
+ if (pin >= dln2->gpio.ngpio) {
+ dev_err(dln2->gpio.dev, "out of bounds pin %d\n", pin);
+ return;
+ }
+
+ irq = irq