diff options
Diffstat (limited to 'drivers/gpio')
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 |
