From d463c6ff88108d187c73396280f5dae8b4a78e65 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Wed, 27 Nov 2013 15:46:06 +0100 Subject: gpio-lynxpoint: Allow building as a module Change CONFIG_GPIO_LYNXPOINT from bool to tristate so that the gpio-lynxpoint driver can be built as a module. Add the required glue: an exit function to unregister the driver, and module information. Signed-off-by: Jean Delvare Cc: Mathias Nyman Cc: Linus Walleij Acked-by: Mika Westerberg Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/gpio/Kconfig') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 0f0444475bf0..58d98dd9e4b9 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -353,7 +353,7 @@ config GPIO_GE_FPGA board computers. config GPIO_LYNXPOINT - bool "Intel Lynxpoint GPIO support" + tristate "Intel Lynxpoint GPIO support" depends on ACPI && X86 select IRQ_DOMAIN help -- cgit v1.2.3 From 0299b77b44531ae0963fe86d8b9cb9b5ddb584b7 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Fri, 29 Nov 2013 12:11:34 +0100 Subject: gpio: Add MOXA ART GPIO driver Add GPIO driver for MOXA ART SoCs. Signed-off-by: Jonas Jensen Acked-by: Arnd Bergmann Signed-off-by: Linus Walleij --- .../devicetree/bindings/gpio/moxa,moxart-gpio.txt | 19 +++ drivers/gpio/Kconfig | 7 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-moxart.c | 162 +++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/moxa,moxart-gpio.txt create mode 100644 drivers/gpio/gpio-moxart.c (limited to 'drivers/gpio/Kconfig') diff --git a/Documentation/devicetree/bindings/gpio/moxa,moxart-gpio.txt b/Documentation/devicetree/bindings/gpio/moxa,moxart-gpio.txt new file mode 100644 index 000000000000..f8e8f185a3db --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/moxa,moxart-gpio.txt @@ -0,0 +1,19 @@ +MOXA ART GPIO Controller + +Required properties: + +- #gpio-cells : Should be 2, The first cell is the pin number, + the second cell is used to specify polarity: + 0 = active high + 1 = active low +- compatible : Must be "moxa,moxart-gpio" +- reg : Should contain registers location and length + +Example: + + gpio: gpio@98700000 { + gpio-controller; + #gpio-cells = <2>; + compatible = "moxa,moxart-gpio"; + reg = <0x98700000 0xC>; + }; diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 58d98dd9e4b9..ae3682d25a3c 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -156,6 +156,13 @@ config GPIO_F7188X To compile this driver as a module, choose M here: the module will be called f7188x-gpio. +config GPIO_MOXART + bool "MOXART GPIO support" + depends on ARCH_MOXART + help + Select this option to enable GPIO driver for + MOXA ART SoC devices. + config GPIO_MPC5200 def_bool y depends on PPC_MPC52xx diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 7971e36b8b12..ee95154cb1d2 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_GPIO_MC9S08DZ60) += gpio-mc9s08dz60.o obj-$(CONFIG_GPIO_MCP23S08) += gpio-mcp23s08.o obj-$(CONFIG_GPIO_ML_IOH) += gpio-ml-ioh.o obj-$(CONFIG_GPIO_MM_LANTIQ) += gpio-mm-lantiq.o +obj-$(CONFIG_GPIO_MOXART) += gpio-moxart.o obj-$(CONFIG_GPIO_MPC5200) += gpio-mpc5200.o obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o obj-$(CONFIG_GPIO_MSIC) += gpio-msic.o diff --git a/drivers/gpio/gpio-moxart.c b/drivers/gpio/gpio-moxart.c new file mode 100644 index 000000000000..d662cdededac --- /dev/null +++ b/drivers/gpio/gpio-moxart.c @@ -0,0 +1,162 @@ +/* + * MOXA ART SoCs GPIO driver. + * + * Copyright (C) 2013 Jonas Jensen + * + * Jonas Jensen + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPIO_DATA_OUT 0x00 +#define GPIO_DATA_IN 0x04 +#define GPIO_PIN_DIRECTION 0x08 + +struct moxart_gpio_chip { + struct gpio_chip gpio; + void __iomem *moxart_gpio_base; +}; + +static inline struct moxart_gpio_chip *to_moxart_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct moxart_gpio_chip, gpio); +} + +static int moxart_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + return pinctrl_request_gpio(offset); +} + +static void moxart_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + pinctrl_free_gpio(offset); +} + +static int moxart_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct moxart_gpio_chip *gc = to_moxart_gpio(chip); + void __iomem *ioaddr = gc->moxart_gpio_base + GPIO_PIN_DIRECTION; + + writel(readl(ioaddr) & ~BIT(offset), ioaddr); + return 0; +} + +static int moxart_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct moxart_gpio_chip *gc = to_moxart_gpio(chip); + void __iomem *ioaddr = gc->moxart_gpio_base + GPIO_PIN_DIRECTION; + + writel(readl(ioaddr) | BIT(offset), ioaddr); + return 0; +} + +static void moxart_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct moxart_gpio_chip *gc = to_moxart_gpio(chip); + void __iomem *ioaddr = gc->moxart_gpio_base + GPIO_DATA_OUT; + u32 reg = readl(ioaddr); + + if (value) + reg = reg | BIT(offset); + else + reg = reg & ~BIT(offset); + + + writel(reg, ioaddr); +} + +static int moxart_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct moxart_gpio_chip *gc = to_moxart_gpio(chip); + u32 ret = readl(gc->moxart_gpio_base + GPIO_PIN_DIRECTION); + + if (ret & BIT(offset)) + return !!(readl(gc->moxart_gpio_base + GPIO_DATA_OUT) & + BIT(offset)); + else + return !!(readl(gc->moxart_gpio_base + GPIO_DATA_IN) & + BIT(offset)); +} + +static struct gpio_chip moxart_template_chip = { + .label = "moxart-gpio", + .request = moxart_gpio_request, + .free = moxart_gpio_free, + .direction_input = moxart_gpio_direction_input, + .direction_output = moxart_gpio_direction_output, + .set = moxart_gpio_set, + .get = moxart_gpio_get, + .base = 0, + .ngpio = 32, + .can_sleep = 0, +}; + +static int moxart_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct moxart_gpio_chip *mgc; + int ret; + + mgc = devm_kzalloc(dev, sizeof(*mgc), GFP_KERNEL); + if (!mgc) { + dev_err(dev, "can't allocate GPIO chip container\n"); + return -ENOMEM; + } + mgc->gpio = moxart_template_chip; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mgc->moxart_gpio_base = devm_ioremap_resource(dev, res); + if (IS_ERR(mgc->moxart_gpio_base)) { + dev_err(dev, "%s: devm_ioremap_resource res_gpio failed\n", + dev->of_node->full_name); + return PTR_ERR(mgc->moxart_gpio_base); + } + + mgc->gpio.dev = dev; + + ret = gpiochip_add(&mgc->gpio); + if (ret) { + dev_err(dev, "%s: gpiochip_add failed\n", + dev->of_node->full_name); + return ret; + } + + return 0; +} + +static const struct of_device_id moxart_gpio_match[] = { + { .compatible = "moxa,moxart-gpio" }, + { } +}; + +static struct platform_driver moxart_gpio_driver = { + .driver = { + .name = "moxart-gpio", + .owner = THIS_MODULE, + .of_match_table = moxart_gpio_match, + }, + .probe = moxart_gpio_probe, +}; +module_platform_driver(moxart_gpio_driver); + +MODULE_DESCRIPTION("MOXART GPIO chip driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jonas Jensen "); -- cgit v1.2.3 From 61e7380403efbaf5f796e8cf979827c602bf6c50 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Sat, 7 Dec 2013 14:08:22 +0400 Subject: gpio: 74x164: Remove non-DT support Commit 20bc4d5d565159eb2b942bf4b7fae86fba94e32c (gpio: 74x164: Add support for the daisy-chaining) introduce check for DT for the driver, so driver cannot be used without DT. There are no in-tree users of this driver, so remove non-DT support completely. Signed-off-by: Alexander Shiyan Acked-by: Mark Brown Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 6 +++--- drivers/gpio/gpio-74x164.c | 14 +------------- include/linux/spi/74x164.h | 9 --------- 3 files changed, 4 insertions(+), 25 deletions(-) delete mode 100644 include/linux/spi/74x164.h (limited to 'drivers/gpio/Kconfig') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index ae3682d25a3c..4127f68412eb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -714,10 +714,10 @@ config GPIO_MC33880 config GPIO_74X164 tristate "74x164 serial-in/parallel-out 8-bits shift register" - depends on SPI_MASTER + depends on SPI_MASTER && OF help - Platform driver for 74x164 compatible serial-in/parallel-out - 8-outputs shift registers. This driver can be used to provide access + Driver for 74x164 compatible serial-in/parallel-out 8-outputs + shift registers. This driver can be used to provide access to more gpio outputs. comment "AC97 GPIO expanders:" diff --git a/drivers/gpio/gpio-74x164.c b/drivers/gpio/gpio-74x164.c index ddb831232407..4d4e15ca67e0 100644 --- a/drivers/gpio/gpio-74x164.c +++ b/drivers/gpio/gpio-74x164.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -108,14 +107,8 @@ static int gen_74x164_direction_output(struct gpio_chip *gc, static int gen_74x164_probe(struct spi_device *spi) { struct gen_74x164_chip *chip; - struct gen_74x164_chip_platform_data *pdata; int ret; - if (!spi->dev.of_node) { - dev_err(&spi->dev, "No device tree data available.\n"); - return -EINVAL; - } - /* * bits_per_word cannot be configured in platform data */ @@ -129,12 +122,6 @@ static int gen_74x164_probe(struct spi_device *spi) if (!chip) return -ENOMEM; - pdata = dev_get_platdata(&spi->dev); - if (pdata && pdata->base) - chip->gpio_chip.base = pdata->base; - else - chip->gpio_chip.base = -1; - mutex_init(&chip->lock); spi_set_drvdata(spi, chip); @@ -145,6 +132,7 @@ static int gen_74x164_probe(struct spi_device *spi) chip->gpio_chip.direction_output = gen_74x164_direction_output; chip->gpio_chip.get = gen_74x164_get_value; chip->gpio_chip.set = gen_74x164_set_value; + chip->gpio_chip.base = -1; if (of_property_read_u32(spi->dev.of_node, "registers-number", &chip->registers)) { dev_err(&spi->dev, "Missing registers-number property in the DT.\n"); diff --git a/include/linux/spi/74x164.h b/include/linux/spi/74x164.h deleted file mode 100644 index 0aa6acc73317..000000000000 --- a/include/linux/spi/74x164.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef LINUX_SPI_74X164_H -#define LINUX_SPI_74X164_H - -struct gen_74x164_chip_platform_data { - /* number assigned to the first GPIO */ - unsigned base; -}; - -#endif -- cgit v1.2.3 From 3b31d0eca5fd8d7d485c7cb7319a5cd6a3207726 Mon Sep 17 00:00:00 2001 From: Baruch Siach Date: Thu, 12 Dec 2013 11:18:41 +0200 Subject: gpio: driver for Xtensa GPIO32 GPIO32 is a standard optional extension to the Xtensa architecture core that provides preconfigured output and input ports for intra SoC signaling. The GPIO32 option is implemented as 32bit Tensilica Instruction Extension (TIE) output state called EXPSTATE, and 32bit input wire called IMPWIRE. This driver treats input and output states as two distinct devices. v3: * Use BUG() in xtensa_impwire_set_value() to indicate that it should never be called (Linus Walleij) v2: * Address the comments of Linus Walleij: - Add a few comments - Expand commit log message - Use the BIT() macro for bit offsets - Rewrite CPENABLE handling as static inlines - Use device_initcall() * Depend on !SMP for reason explained in the comments (Marc Gauthier) * Use XCHAL_CP_ID_XTIOP to enable/disable GPIO32 only Signed-off-by: Baruch Siach Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 8 +++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-xtensa.c | 163 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 drivers/gpio/gpio-xtensa.c (limited to 'drivers/gpio/Kconfig') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 4127f68412eb..02e8d5350f8b 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -288,6 +288,14 @@ config GPIO_XILINX help Say yes here to support the Xilinx FPGA GPIO device +config GPIO_XTENSA + bool "Xtensa GPIO32 support" + depends on XTENSA + depends on !SMP + help + Say yes here to support the Xtensa internal GPIO32 IMPWIRE (input) + and EXPSTATE (output) ports + config GPIO_VR41XX tristate "NEC VR4100 series General-purpose I/O Uint support" depends on CPU_VR41XX diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index ee95154cb1d2..699e7cd96584 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -96,3 +96,4 @@ obj-$(CONFIG_GPIO_WM831X) += gpio-wm831x.o obj-$(CONFIG_GPIO_WM8350) += gpio-wm8350.o obj-$(CONFIG_GPIO_WM8994) += gpio-wm8994.o obj-$(CONFIG_GPIO_XILINX) += gpio-xilinx.o +obj-$(CONFIG_GPIO_XTENSA) += gpio-xtensa.o diff --git a/drivers/gpio/gpio-xtensa.c b/drivers/gpio/gpio-xtensa.c new file mode 100644 index 000000000000..1d136eceda62 --- /dev/null +++ b/drivers/gpio/gpio-xtensa.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2013 TangoTec Ltd. + * Author: Baruch Siach + * + * 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. + * + * Driver for the Xtensa LX4 GPIO32 Option + * + * Documentation: Xtensa LX4 Microprocessor Data Book, Section 2.22 + * + * GPIO32 is a standard optional extension to the Xtensa architecture core that + * provides preconfigured output and input ports for intra SoC signaling. The + * GPIO32 option is implemented as 32bit Tensilica Instruction Extension (TIE) + * output state called EXPSTATE, and 32bit input wire called IMPWIRE. This + * driver treats input and output states as two distinct devices. + * + * Access to GPIO32 specific instructions is controlled by the CPENABLE + * (Coprocessor Enable Bits) register. By default Xtensa Linux startup code + * disables access to all coprocessors. This driver sets the CPENABLE bit + * corresponding to GPIO32 before any GPIO32 specific instruction, and restores + * CPENABLE state after that. + * + * This driver is currently incompatible with SMP. The GPIO32 extension is not + * guaranteed to be available in all cores. Moreover, each core controls a + * different set of IO wires. A theoretical SMP aware version of this driver + * would need to have a per core workqueue to do the actual GPIO manipulation. + */ + +#include +#include +#include +#include +#include + +#include /* CPENABLE read/write macros */ + +#ifndef XCHAL_CP_ID_XTIOP +#error GPIO32 option is not enabled for your xtensa core variant +#endif + +static inline unsigned long enable_cp(unsigned long *cpenable) +{ + unsigned long flags; + + local_irq_save(flags); + RSR_CPENABLE(*cpenable); + WSR_CPENABLE(*cpenable | BIT(XCHAL_CP_ID_XTIOP)); + + return flags; +} + +static inline void disable_cp(unsigned long flags, unsigned long cpenable) +{ + WSR_CPENABLE(cpenable); + local_irq_restore(flags); +} + +static int xtensa_impwire_get_direction(struct gpio_chip *gc, unsigned offset) +{ + return 1; /* input only */ +} + +static int xtensa_impwire_get_value(struct gpio_chip *gc, unsigned offset) +{ + unsigned long flags, saved_cpenable; + u32 impwire; + + flags = enable_cp(&saved_cpenable); + __asm__ __volatile__("read_impwire %0" : "=a" (impwire)); + disable_cp(flags, saved_cpenable); + + return !!(impwire & BIT(offset)); +} + +static void xtensa_impwire_set_value(struct gpio_chip *gc, unsigned offset, + int value) +{ + BUG(); /* output only; should never be called */ +} + +static int xtensa_expstate_get_direction(struct gpio_chip *gc, unsigned offset) +{ + return 0; /* output only */ +} + +static int xtensa_expstate_get_value(struct gpio_chip *gc, unsigned offset) +{ + unsigned long flags, saved_cpenable; + u32 expstate; + + flags = enable_cp(&saved_cpenable); + __asm__ __volatile__("rur.expstate %0" : "=a" (expstate)); + disable_cp(flags, saved_cpenable); + + return !!(expstate & BIT(offset)); +} + +static void xtensa_expstate_set_value(struct gpio_chip *gc, unsigned offset, + int value) +{ + unsigned long flags, saved_cpenable; + u32 mask = BIT(offset); + u32 val = value ? BIT(offset) : 0; + + flags = enable_cp(&saved_cpenable); + __asm__ __volatile__("wrmsk_expstate %0, %1" + :: "a" (val), "a" (mask)); + disable_cp(flags, saved_cpenable); +} + +static struct gpio_chip impwire_chip = { + .label = "impwire", + .base = -1, + .ngpio = 32, + .get_direction = xtensa_impwire_get_direction, + .get = xtensa_impwire_get_value, + .set = xtensa_impwire_set_value, +}; + +static struct gpio_chip expstate_chip = { + .label = "expstate", + .base = -1, + .ngpio = 32, + .get_direction = xtensa_expstate_get_direction, + .get = xtensa_expstate_get_value, + .set = xtensa_expstate_set_value, +}; + +static int xtensa_gpio_probe(struct platform_device *pdev) +{ + int ret; + + ret = gpiochip_add(&impwire_chip); + if (ret) + return ret; + return gpiochip_add(&expstate_chip); +} + +static struct platform_driver xtensa_gpio_driver = { + .driver = { + .name = "xtensa-gpio", + .owner = THIS_MODULE, + }, + .probe = xtensa_gpio_probe, +}; + +static int __init xtensa_gpio_init(void) +{ + struct platform_device *pdev; + + pdev = platform_device_register_simple("xtensa-gpio", 0, NULL, 0); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + return platform_driver_register(&xtensa_gpio_driver); +} +device_initcall(xtensa_gpio_init); + +MODULE_AUTHOR("Baruch Siach "); +MODULE_DESCRIPTION("Xtensa LX4 GPIO32 driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 12262bef8f4614df7af8b2963de5beddee18ae5e Mon Sep 17 00:00:00 2001 From: Bruno Randolf Date: Wed, 4 Dec 2013 23:56:43 +0000 Subject: gpio: add GPIO support for SMSC SCH311x This patch adds support for the GPIOs found on the SMSC "Super I/ SCH311x. The chip detection and I/O functions are copied from sch311x_wdt. Signed-off-by: Bruno Randolf Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 9 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-sch311x.c | 431 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 441 insertions(+) create mode 100644 drivers/gpio/gpio-sch311x.c (limited to 'drivers/gpio/Kconfig') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 02e8d5350f8b..f456b93bb953 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -244,6 +244,15 @@ config GPIO_SAMSUNG Legacy GPIO support. Use only for platforms without support for pinctrl. +config GPIO_SCH311X + tristate "SMSC SCH311x SuperI/O GPIO" + help + Driver to enable the GPIOs found on SMSC SMSC SCH3112, SCH3114 and + SCH3116 "Super I/O" chipsets. + + To compile this driver as a module, choose M here: the module will + be called gpio-sch311x. + config GPIO_SPEAR_SPICS bool "ST SPEAr13xx SPI Chip Select as GPIO support" depends on PLAT_SPEAR diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 699e7cd96584..c44fffac9519 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_GPIO_RCAR) += gpio-rcar.o obj-$(CONFIG_GPIO_SAMSUNG) += gpio-samsung.o obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o obj-$(CONFIG_GPIO_SCH) += gpio-sch.o +obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o obj-$(CONFIG_GPIO_SPEAR_SPICS) += gpio-spear-spics.o obj-$(CONFIG_GPIO_STA2X11) += gpio-sta2x11.o diff --git a/drivers/gpio/gpio-sch311x.c b/drivers/gpio/gpio-sch311x.c new file mode 100644 index 000000000000..483a6f8bd958 --- /dev/null +++ b/drivers/gpio/gpio-sch311x.c @@ -0,0 +1,431 @@ +/* + * GPIO driver for the SMSC SCH311x Super-I/O chips + * + * Copyright (C) 2013 Bruno Randolf + * + * SuperIO functions and chip detection: + * (c) Copyright 2008 Wim Van Sebroeck . + * + * 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 +#include +#include +#include +#include +#include + +#define DRV_NAME "gpio-sch311x" + +#define SCH311X_GPIO_CONF_OUT 0x00 +#define SCH311X_GPIO_CONF_IN 0x01 +#define SCH311X_GPIO_CONF_INVERT 0x02 +#define SCH311X_GPIO_CONF_OPEN_DRAIN 0x80 + +#define SIO_CONFIG_KEY_ENTER 0x55 +#define SIO_CONFIG_KEY_EXIT 0xaa + +#define GP1 0x4b + +static int sch311x_ioports[] = { 0x2e, 0x4e, 0x162e, 0x164e }; + +static struct platform_device *sch311x_gpio_pdev; + +struct sch311x_pdev_data { /* platform device data */ + unsigned short runtime_reg; /* runtime register base address */ +}; + +struct sch311x_gpio_block { /* one GPIO block runtime data */ + struct gpio_chip chip; + unsigned short data_reg; /* from definition below */ + unsigned short *config_regs; /* pointer to definition below */ + unsigned short runtime_reg; /* runtime register */ + spinlock_t lock; /* lock for this GPIO block */ +}; + +struct sch311x_gpio_priv { /* driver private data */ + struct sch311x_gpio_block blocks[6]; +}; + +struct sch311x_gpio_block_def { /* register address definitions */ + unsigned short data_reg; + unsigned short config_regs[8]; + unsigned short base; +}; + +/* Note: some GPIOs are not available, these are marked with 0x00 */ + +static struct sch311x_gpio_block_def sch311x_gpio_blocks[] = { + { + .data_reg = 0x4b, /* GP1 */ + .config_regs = {0x23, 0x24, 0x25, 0x26, 0x27, 0x29, 0x2a, 0x2b}, + .base = 10, + }, + { + .data_reg = 0x4c, /* GP2 */ + .config_regs = {0x00, 0x2c, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x32}, + .base = 20, + }, + { + .data_reg = 0x4d, /* GP3 */ + .config_regs = {0x33, 0x34, 0x35, 0x36, 0x37, 0x00, 0x39, 0x3a}, + .base = 30, + }, + { + .data_reg = 0x4e, /* GP4 */ + .config_regs = {0x3b, 0x00, 0x3d, 0x00, 0x6e, 0x6f, 0x72, 0x73}, + .base = 40, + }, + { + .data_reg = 0x4f, /* GP5 */ + .config_regs = {0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46}, + .base = 50, + }, + { + .data_reg = 0x50, /* GP6 */ + .config_regs = {0x47, 0x48, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59}, + .base = 60, + }, +}; + +static inline struct sch311x_gpio_block * +to_sch311x_gpio_block(struct gpio_chip *chip) +{ + return container_of(chip, struct sch311x_gpio_block, chip); +} + + +/* + * Super-IO functions + */ + +static inline int sch311x_sio_enter(int sio_config_port) +{ + /* Don't step on other drivers' I/O space by accident. */ + if (!request_muxed_region(sio_config_port, 2, DRV_NAME)) { + pr_err(DRV_NAME "I/O address 0x%04x already in use\n", + sio_config_port); + return -EBUSY; + } + + outb(SIO_CONFIG_KEY_ENTER, sio_config_port); + return 0; +} + +static inline void sch311x_sio_exit(int sio_config_port) +{ + outb(SIO_CONFIG_KEY_EXIT, sio_config_port); + release_region(sio_config_port, 2); +} + +static inline int sch311x_sio_inb(int sio_config_port, int reg) +{ + outb(reg, sio_config_port); + return inb(sio_config_port + 1); +} + +static inline void sch311x_sio_outb(int sio_config_port, int reg, int val) +{ + outb(reg, sio_config_port); + outb(val, sio_config_port + 1); +} + + +/* + * GPIO functions + */ + +static int sch311x_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct sch311x_gpio_block *block = to_sch311x_gpio_block(chip); + + if (block->config_regs[offset] == 0) /* GPIO is not available */ + return -ENODEV; + + if (!request_region(block->runtime_reg + block->config_regs[offset], + 1, DRV_NAME)) { + dev_err(chip->dev, "Failed to request region 0x%04x.\n", + block->runtime_reg + block->config_regs[offset]); + return -EBUSY; + } + return 0; +} + +static void sch311x_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + struct sch311x_gpio_block *block = to_sch311x_gpio_block(chip); + + if (block->config_regs[offset] == 0) /* GPIO is not available */ + return; + + release_region(block->runtime_reg + block->config_regs[offset], 1); +} + +static int sch311x_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct sch311x_gpio_block *block = to_sch311x_gpio_block(chip); + unsigned char data; + + spin_lock(&block->lock); + data = inb(block->runtime_reg + block->data_reg); + spin_unlock(&block->lock); + + return !!(data & BIT(offset)); +} + +static void __sch311x_gpio_set(struct sch311x_gpio_block *block, + unsigned offset, int value) +{ + unsigned char data = inb(block->runtime_reg + block->data_reg); + if (value) + data |= BIT(offset); + else + data &= ~BIT(offset); + outb(data, block->runtime_reg + block->data_reg); +} + +static void sch311x_gpio_set(struct gpio_chip *chip, unsigned offset, + int value) +{ + struct sch311x_gpio_block *block = to_sch311x_gpio_block(chip); + + spin_lock(&block->lock); + __sch311x_gpio_set(block, offset, value); + spin_unlock(&block->lock); +} + +static int sch311x_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct sch311x_gpio_block *block = to_sch311x_gpio_block(chip); + + spin_lock(&block->lock); + outb(SCH311X_GPIO_CONF_IN, block->runtime_reg + + block->config_regs[offset]); + spin_unlock(&block->lock); + + return 0; +} + +static int sch311x_gpio_direction_out(struct gpio_chip *chip, unsigned offset, + int value) +{ + struct sch311x_gpio_block *block = to_sch311x_gpio_block(chip); + + spin_lock(&block->lock); + + outb(SCH311X_GPIO_CONF_OUT, block->runtime_reg + + block->config_regs[offset]); + + __sch311x_gpio_set(block, offset, value); + + spin_unlock(&block->lock); + return 0; +} + +static int sch311x_gpio_probe(struct platform_device *pdev) +{ + struct sch311x_pdev_data *pdata = pdev->dev.platform_data; + struct sch311x_gpio_priv *priv; + struct sch311x_gpio_block *block; + int err, i; + + /* we can register all GPIO data registers at once */ + if (!request_region(pdata->runtime_reg + GP1, 6, DRV_NAME)) { + dev_err(&pdev->dev, "Failed to request region 0x%04x-0x%04x.\n", + pdata->runtime_reg + GP1, pdata->runtime_reg + GP1 + 5); + return -EBUSY; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + for (i = 0; i < ARRAY_SIZE(priv->blocks); i++) { + block = &priv->blocks[i]; + + spin_lock_init(&block->lock); + + block->chip.label = DRV_NAME; + block->chip.owner = THIS_MODULE; + block->chip.request = sch311x_gpio_request; + block->chip.free = sch311x_gpio_free; + block->chip.direction_input = sch311x_gpio_direction_in; + block->chip.direction_output = sch311x_gpio_direction_out; + block->chip.get = sch311x_gpio_get; + block->chip.set = sch311x_gpio_set; + block->chip.ngpio = 8; + block->chip.dev = &pdev->dev; + block->chip.base = sch311x_gpio_blocks[i].base; + block->config_regs = sch311x_gpio_blocks[i].config_regs; + block->data_reg = sch311x_gpio_blocks[i].data_reg; + block->runtime_reg = pdata->runtime_reg; + + err = gpiochip_add(&block->chip); + if (err < 0) { + dev_err(&pdev->dev, + "Could not register gpiochip, %d\n", err); + goto exit_err; + } + dev_info(&pdev->dev, + "SMSC SCH311x GPIO block %d registered.\n", i); + } + + return 0; + +exit_err: + release_region(pdata->runtime_reg + GP1, 6); + /* release already registered chips */ + for (--i; i >= 0; i--) + gpiochip_remove(&priv->blocks[i].chip); + return err; +} + +static int sch311x_gpio_remove(struct platform_device *pdev) +{ + struct sch311x_pdev_data *pdata = pdev->dev.platform_data; + struct sch311x_gpio_priv *priv = platform_get_drvdata(pdev); + int err, i; + + release_region(pdata->runtime_reg + GP1, 6); + + for (i = 0; i < ARRAY_SIZE(priv->blocks); i++) { + err = gpiochip_remove(&priv->blocks[i].chip); + if (err) + return err; + dev_info(&pdev->dev, + "SMSC SCH311x GPIO block %d unregistered.\n", i); + } + return 0; +} + +static struct platform_driver sch311x_gpio_driver = { + .driver.name = DRV_NAME, + .driver.owner = THIS_MODULE, + .probe = sch311x_gpio_probe, + .remove = sch311x_gpio_remove, +}; + + +/* + * Init & exit routines + */ + +static int __init sch311x_detect(int sio_config_port, unsigned short *addr) +{ + int err = 0, reg; + unsigned short base_addr; + unsigned char dev_id; + + err = sch311x_sio_enter(sio_config_port); + if (err) + return err; + + /* Check device ID. We currently know about: + * SCH3112 (0x7c), SCH3114 (0x7d), and SCH3116 (0x7f). */ + reg = sch311x_sio_inb(sio_config_port, 0x20); + if (!(reg == 0x7c || reg == 0x7d || reg == 0x7f)) { + err = -ENODEV; + goto exit; + } + dev_id = reg == 0x7c ? 2 : reg == 0x7d ? 4 : 6; + + /* Select logical device A (runtime registers) */ + sch311x_sio_outb(sio_config_port, 0x07, 0x0a); + + /* Check if Logical Device Register is currently active */ + if ((sch311x_sio_inb(sio_config_port, 0x30) & 0x01) == 0) + pr_info("Seems that LDN 0x0a is not active...\n"); + + /* Get the base address of the runtime registers */ + base_addr = (sch311x_sio_inb(sio_config_port, 0x60) << 8) | + sch311x_sio_inb(sio_config_port, 0x61); + if (!base_addr) { + pr_err("Base address not set\n"); + err = -ENODEV; + goto exit; + } + *addr = base_addr; + + pr_info("Found an SMSC SCH311%d chip at 0x%04x\n", dev_id, base_addr); + +exit: + sch311x_sio_exit(sio_config_port); + return err; +} + +static int __init sch311x_gpio_pdev_add(const unsigned short addr) +{ + struct sch311x_pdev_data pdata; + int err; + + pdata.runtime_reg = addr; + + sch311x_gpio_pdev = platform_device_alloc(DRV_NAME, -1); + if (!sch311x_gpio_pdev) + return -ENOMEM; + + err = platform_device_add_data(sch311x_gpio_pdev, + &pdata, sizeof(pdata)); + if (err) { + pr_err(DRV_NAME "Platform data allocation failed\n"); + goto err; + } + + err = platform_device_add(sch311x_gpio_pdev); + if (err) { + pr_err(DRV_NAME "Device addition failed\n"); + goto err; + } + return 0; + +err: + platform_device_put(sch311x_gpio_pdev); + return err; +} + +static int __init sch311x_gpio_init(void) +{ + int err, i; + unsigned short addr = 0; + + for (i = 0; i < ARRAY_SIZE(sch311x_ioports); i++) + if (sch311x_detect(sch311x_ioports[i], &addr) == 0) + break; + + if (!addr) + return -ENODEV; + + err = platform_driver_register(&sch311x_gpio_driver); + if (err) + return err; + + err = sch311x_gpio_pdev_add(addr); + if (err) + goto unreg_platform_driver; + + return 0; + +unreg_platform_driver: + platform_driver_unregister(&sch311x_gpio_driver); + return err; +} + +static void __exit sch311x_gpio_exit(void) +{ + platform_device_unregister(sch311x_gpio_pdev); + platform_driver_unregister(&sch311x_gpio_driver); +} + +module_init(sch311x_gpio_init); +module_exit(sch311x_gpio_exit); + +MODULE_AUTHOR("Bruno Randolf "); +MODULE_DESCRIPTION("SMSC SCH311x GPIO Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio-sch311x"); -- cgit v1.2.3 From ffd4bf1a9e8a88c4b2a47007b1a9e69be28599fe Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Tue, 24 Dec 2013 18:08:54 +0400 Subject: gpio: clps711x: Enable driver compilation with COMPILE_TEST This helps increasing build testing coverage. Signed-off-by: Alexander Shiyan Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/gpio/Kconfig') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index f456b93bb953..1a07c4044839 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -110,7 +110,7 @@ comment "Memory mapped GPIO drivers:" config GPIO_CLPS711X tristate "CLPS711X GPIO support" - depends on ARCH_CLPS711X + depends on ARCH_CLPS711X || COMPILE_TEST select GPIO_GENERIC help Say yes here to support GPIO on CLPS711X SoCs. -- cgit v1.2.3 From a1a2bdec4772228274dfb3f9a41ac5234b93a5e9 Mon Sep 17 00:00:00 2001 From: Baruch Siach Date: Wed, 18 Dec 2013 09:10:29 +0200 Subject: gpio: xtensa: depend on HAVE_XTENSA_GPIO32 Prevent build failure when the selected variant does not support GPIO32. Acked-by: Max Filippov Signed-off-by: Baruch Siach Signed-off-by: Linus Walleij --- arch/xtensa/Kconfig | 6 ++++++ drivers/gpio/Kconfig | 1 + 2 files changed, 7 insertions(+) (limited to 'drivers/gpio/Kconfig') diff --git a/arch/xtensa/Kconfig b/arch/xtensa/Kconfig index 8d24dcb7cdac..f8df0cc70cb6 100644 --- a/arch/xtensa/Kconfig +++ b/arch/xtensa/Kconfig @@ -64,6 +64,9 @@ config MMU config VARIANT_IRQ_SWITCH def_bool n +config HAVE_XTENSA_GPIO32 + def_bool n + menu "Processor type and features" choice @@ -73,16 +76,19 @@ choice config XTENSA_VARIANT_FSF bool "fsf - default (not generic) configuration" select MMU + select HAVE_XTENSA_GPIO32 config XTENSA_VARIANT_DC232B bool "dc232b - Diamond 232L Standard Core Rev.B (LE)" select MMU + select HAVE_XTENSA_GPIO32 help This variant refers to Tensilica's Diamond 232L Standard core Rev.B (LE). config XTENSA_VARIANT_DC233C bool "dc233c - Diamond 233L Standard Core Rev.C (LE)" select MMU + select HAVE_XTENSA_GPIO32 help This variant refers to Tensilica's Diamond 233L Standard core Rev.C (LE). diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 1a07c4044839..858d489f6bae 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -300,6 +300,7 @@ config GPIO_XILINX config GPIO_XTENSA bool "Xtensa GPIO32 support" depends on XTENSA + depends on HAVE_XTENSA_GPIO32 depends on !SMP help Say yes here to support the Xtensa internal GPIO32 IMPWIRE (input) -- cgit v1.2.3 From 4e47f91bf741e011a90ceb6241b8d78141709733 Mon Sep 17 00:00:00 2001 From: Lars Poeschel Date: Thu, 16 Jan 2014 11:44:15 +0100 Subject: gpio: mcp23s08: Add irq functionality for i2c chips This adds interrupt functionality for i2c chips to the driver. They can act as a interrupt-controller and generate interrupts, if the inputs change. This is tested with a mcp23017 chip on an arm based platform. v3: - be a bit more clear that the irq functionality is also available on spi versions of the chips, but the linux driver does not support this yet v2: - some more word about irq-mirror property in binding doc - use of_read_bool instead of of_find_property for "interrupt-contrller" and "irq-mirror" - cache the "interrupt-controller" for remove function - do set the irq-mirror bit only if device is marked as interrupt-controller - do create the irq mapping and setup of irq_desc of all possible interrupts in probe path instead of in gpio_to_irq - mark gpios as in use as interrupts in irq in irq_startup and unlock it in irq_shutdown - rename virq to child_irq - remove dev argument from mcp23s08_irq_setup function - move gpiochip_add before mcp23s08_irq_setup in probe path Signed-off-by: Lars Poeschel Signed-off-by: Linus Walleij --- .../devicetree/bindings/gpio/gpio-mcp23s08.txt | 28 ++- drivers/gpio/Kconfig | 1 + drivers/gpio/gpio-mcp23s08.c | 248 ++++++++++++++++++++- 3 files changed, 271 insertions(+), 6 deletions(-) (limited to 'drivers/gpio/Kconfig') diff --git a/Documentation/devicetree/bindings/gpio/gpio-mcp23s08.txt b/Documentation/devicetree/bindings/gpio/gpio-mcp23s08.txt index daa30174bcc1..3ddc7ccfe5f3 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-mcp23s08.txt +++ b/Documentation/devicetree/bindings/gpio/gpio-mcp23s08.txt @@ -38,12 +38,38 @@ Required device specific properties (only for SPI chips): removed. - spi-max-frequency = The maximum frequency this chip is able to handle -Example I2C: +Optional properties: +- #interrupt-cells : Should be two. + - first cell is the pin number + - second cell is used to specify flags. +- interrupt-controller: Marks the device node as a interrupt controller. +NOTE: The interrupt functionality is only supported for i2c versions of the +chips. The spi chips can also do the interrupts, but this is not supported by +the linux driver yet. + +Optional device specific properties: +- microchip,irq-mirror: Sets the mirror flag in the IOCON register. Devices + with two interrupt outputs (these are the devices ending with 17 and + those that have 16 IOs) have two IO banks: IO 0-7 form bank 1 and + IO 8-15 are bank 2. These chips have two different interrupt outputs: + One for bank 1 and another for bank 2. If irq-mirror is set, both + interrupts are generated regardless of the bank that an input change + occured on. If it is not set, the interrupt are only generated for the + bank they belong to. + On devices with only one interrupt output this property is useless. + +Example I2C (with interrupt): gpiom1: gpio@20 { compatible = "microchip,mcp23017"; gpio-controller; #gpio-cells = <2>; reg = <0x20>; + + interrupt-parent = <&gpio1>; + interrupts = <17 IRQ_TYPE_LEVEL_LOW>; + interrupt-controller; + #interrupt-cells=<2>; + microchip,irq-mirror; }; Example SPI: diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 858d489f6bae..2d49784109b5 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -722,6 +722,7 @@ config GPIO_MCP23S08 SPI/I2C driver for Microchip MCP23S08/MCP23S17/MCP23008/MCP23017 I/O expanders. This provides a GPIO interface supporting inputs and outputs. + The I2C versions of the chips can be used as interrupt-controller. config GPIO_MC33880 tristate "Freescale MC33880 high-side/low-side switch" diff --git a/drivers/gpio/gpio-mcp23s08.c b/drivers/gpio/gpio-mcp23s08.c index b16401ee4766..ef3fd48aca3b 100644 --- a/drivers/gpio/gpio-mcp23s08.c +++ b/drivers/gpio/gpio-mcp23s08.c @@ -1,5 +1,13 @@ /* - * MCP23S08 SPI/GPIO gpio expander driver + * MCP23S08 SPI/I2C GPIO gpio expander driver + * + * The inputs and outputs of the mcp23s08, mcp23s17, mcp23008 and mcp23017 are + * supported. + * For the I2C versions of the chips (mcp23008 and mcp23017) generation of + * interrupts is also supported. + * The hardware of the SPI versions of the chips (mcp23s08 and mcp23s17) is + * also capable of generating interrupts, but the linux driver does not + * support that yet. */ #include @@ -12,7 +20,8 @@ #include #include #include -#include +#include +#include #include /** @@ -34,6 +43,7 @@ #define MCP_DEFVAL 0x03 #define MCP_INTCON 0x04 #define MCP_IOCON 0x05 +# define IOCON_MIRROR (1 << 6) # define IOCON_SEQOP (1 << 5) # define IOCON_HAEN (1 << 3) # define IOCON_ODR (1 << 2) @@ -57,8 +67,14 @@ struct mcp23s08 { u8 addr; u16 cache[11]; + u16 irq_rise; + u16 irq_fall; + int irq; + bool irq_controller; /* lock protects the cached values */ struct mutex lock; + struct mutex irq_lock; + struct irq_domain *irq_domain; struct gpio_chip chip; @@ -77,6 +93,11 @@ struct mcp23s08_driver_data { struct mcp23s08 chip[]; }; +/* This lock class tells lockdep that GPIO irqs are in a different + * category than their parents, so it won't report false recursion. + */ +static struct lock_class_key gpio_lock_class; + /*----------------------------------------------------------------------*/ #if IS_ENABLED(CONFIG_I2C) @@ -315,6 +336,195 @@ mcp23s08_direction_output(struct gpio_chip *chip, unsigned offset, int value) return status; } +/*----------------------------------------------------------------------*/ +static irqreturn_t mcp23s08_irq(int irq, void *data) +{ + struct mcp23s08 *mcp = data; + int intcap, intf, i; + unsigned int child_irq; + + mutex_lock(&mcp->lock); + intf = mcp->ops->read(mcp, MCP_INTF); + if (intf < 0) { + mutex_unlock(&mcp->lock); + return IRQ_HANDLED; + } + + mcp->cache[MCP_INTF] = intf; + + intcap = mcp->ops->read(mcp, MCP_INTCAP); + if (intcap < 0) { + mutex_unlock(&mcp->lock); + return IRQ_HANDLED; + } + + mcp->cache[MCP_INTCAP] = intcap; + mutex_unlock(&mcp->lock); + + + for (i = 0; i < mcp->chip.ngpio; i++) { + if ((BIT(i) & mcp->cache[MCP_INTF]) && + ((BIT(i) & intcap & mcp->irq_rise) || + (mcp->irq_fall & ~intcap & BIT(i)))) { + child_irq = irq_find_mapping(mcp->irq_domain, i); + handle_nested_irq(child_irq); + } + } + + return IRQ_HANDLED; +} + +static int mcp23s08_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); + + return irq_find_mapping(mcp->irq_domain, offset); +} + +static void mcp23s08_irq_mask(struct irq_data *data) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + unsigned int pos = data->hwirq; + + mcp->cache[MCP_GPINTEN] &= ~BIT(pos); +} + +static void mcp23s08_irq_unmask(struct irq_data *data) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + unsigned int pos = data->hwirq; + + mcp->cache[MCP_GPINTEN] |= BIT(pos); +} + +static int mcp23s08_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + unsigned int pos = data->hwirq; + int status = 0; + + if ((type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) { + mcp->cache[MCP_INTCON] &= ~BIT(pos); + mcp->irq_rise |= BIT(pos); + mcp->irq_fall |= BIT(pos); + } else if (type & IRQ_TYPE_EDGE_RISING) { + mcp->cache[MCP_INTCON] &= ~BIT(pos); + mcp->irq_rise |= BIT(pos); + mcp->irq_fall &= ~BIT(pos); + } else if (type & IRQ_TYPE_EDGE_FALLING) { + mcp->cache[MCP_INTCON] &= ~BIT(pos); + mcp->irq_rise &= ~BIT(pos); + mcp->irq_fall |= BIT(pos); + } else + return -EINVAL; + + return status; +} + +static void mcp23s08_irq_bus_lock(struct irq_data *data) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + + mutex_lock(&mcp->irq_lock); +} + +static void mcp23s08_irq_bus_unlock(struct irq_data *data) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + + mutex_lock(&mcp->lock); + mcp->ops->write(mcp, MCP_GPINTEN, mcp->cache[MCP_GPINTEN]); + mcp->ops->write(mcp, MCP_DEFVAL, mcp->cache[MCP_DEFVAL]); + mcp->ops->write(mcp, MCP_INTCON, mcp->cache[MCP_INTCON]); + mutex_unlock(&mcp->lock); + mutex_unlock(&mcp->irq_lock); +} + +static unsigned int mcp23s08_irq_startup(struct irq_data *data) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + + if (gpio_lock_as_irq(&mcp->chip, data->hwirq)) + dev_err(mcp->chip.dev, + "unable to lock HW IRQ %lu for IRQ usage\n", + data->hwirq); + + mcp23s08_irq_unmask(data); + return 0; +} + +static void mcp23s08_irq_shutdown(struct irq_data *data) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + + mcp23s08_irq_mask(data); + gpio_unlock_as_irq(&mcp->chip, data->hwirq); +} + +static struct irq_chip mcp23s08_irq_chip = { + .name = "gpio-mcp23xxx", + .irq_mask = mcp23s08_irq_mask, + .irq_unmask = mcp23s08_irq_unmask, + .irq_set_type = mcp23s08_irq_set_type, + .irq_bus_lock = mcp23s08_irq_bus_lock, + .irq_bus_sync_unlock = mcp23s08_irq_bus_unlock, + .irq_startup = mcp23s08_irq_startup, + .irq_shutdown = mcp23s08_irq_shutdown, +}; + +static int mcp23s08_irq_setup(struct mcp23s08 *mcp) +{ + struct gpio_chip *chip = &mcp->chip; + int err, irq, j; + + mutex_init(&mcp->irq_lock); + + mcp->irq_domain = irq_domain_add_linear(chip->of_node, chip->ngpio, + &irq_domain_simple_ops, mcp); + if (!mcp->irq_domain) + return -ENODEV; + + err = devm_request_threaded_irq(chip->dev, mcp->irq, NULL, mcp23s08_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + dev_name(chip->dev), mcp); + if (err != 0) { + dev_err(chip->dev, "unable to request IRQ#%d: %d\n", + mcp->irq, err); + return err; + } + + chip->to_irq = mcp23s08_gpio_to_irq; + + for (j = 0; j < mcp->chip.ngpio; j++) { + irq = irq_create_mapping(mcp->irq_domain, j); + irq_set_lockdep_class(irq, &gpio_lock_class); + irq_set_chip_data(irq, mcp); + irq_set_chip(irq, &mcp23s08_irq_chip); + irq_set_nested_thread(irq, true); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + irq_set_noprobe(irq); +#endif + } + return 0; +} + +static void mcp23s08_irq_teardown(struct mcp23s08 *mcp) +{ + unsigned int irq, i; + + free_irq(mcp->irq, mcp); + + for (i = 0; i < mcp->chip.ngpio; i++) { + irq = irq_find_mapping(mcp->irq_domain, i); + if (irq > 0) + irq_dispose_mapping(irq); + } + + irq_domain_remove(mcp->irq_domain); +} + /*----------------------------------------------------------------------*/ #ifdef CONFIG_DEBUG_FS @@ -370,10 +580,11 @@ done: /*----------------------------------------------------------------------*/ static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev, - void *data, unsigned addr, - unsigned type, unsigned base, unsigned pullups) + void *data, unsigned addr, unsigned type, + unsigned base, unsigned pullups) { int status; + bool mirror = false; mutex_init(&mcp->lock); @@ -432,13 +643,25 @@ static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev, /* verify MCP_IOCON.SEQOP = 0, so sequential reads work, * and MCP_IOCON.HAEN = 1, so we work with all chips. */ + status = mcp->ops->read(mcp, MCP_IOCON); if (status < 0) goto fail; - if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN)) { + + mcp->irq_controller = of_property_read_bool(mcp->chip.of_node, + "interrupt-controller"); + if (mcp->irq && mcp->irq_controller && (type == MCP_TYPE_017)) + mirror = of_property_read_bool(mcp->chip.of_node, + "microchip,irq-mirror"); + + if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN) || mirror) { /* mcp23s17 has IOCON twice, make sure they are in sync */ status &= ~(IOCON_SEQOP | (IOCON_SEQOP << 8)); status |= IOCON_HAEN | (IOCON_HAEN << 8); + status &= ~(IOCON_INTPOL | (IOCON_INTPOL << 8)); + if (mirror) + status |= IOCON_MIRROR | (IOCON_MIRROR << 8); + status = mcp->ops->write(mcp, MCP_IOCON, status); if (status < 0) goto fail; @@ -470,6 +693,16 @@ static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev, } status = gpiochip_add(&mcp->chip); + if (status < 0) + goto fail; + + if (mcp->irq && mcp->irq_controller) { + status = mcp23s08_irq_setup(mcp); + if (status) { + mcp23s08_irq_teardown(mcp); + goto fail; + } + } fail: if (status < 0) dev_dbg(dev, "can't setup chip %d, --> %d\n", @@ -546,6 +779,7 @@ static int mcp230xx_probe(struct i2c_client *client, if (match || !pdata) { base = -1; pullups = 0; + client->irq = irq_of_parse_and_map(client->dev.of_node, 0); } else { if (!gpio_is_valid(pdata->base)) { dev_dbg(&client->dev, "invalid platform data\n"); @@ -559,6 +793,7 @@ static int mcp230xx_probe(struct i2c_client *client, if (!mcp) return -ENOMEM; + mcp->irq = client->irq; status = mcp23s08_probe_one(mcp, &client->dev, client, client->addr, id->driver_data, base, pullups); if (status) @@ -579,6 +814,9 @@ static int mcp230xx_remove(struct i2c_client *client) struct mcp23s08 *mcp = i2c_get_clientdata(client); int status; + if (client->irq && mcp->irq_controller) + mcp23s08_irq_teardown(mcp); + status = gpiochip_remove(&mcp->chip); if (status == 0) kfree(mcp); -- cgit v1.2.3 From 01d7004181c8ff6d63bf5e8e2b771022c4f78289 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 20 Jan 2014 09:07:34 +0100 Subject: gpio: mcp23s08: depend on OF_GPIO The MCP drivers fails to compile on trial builds due to missing Kconfig dependency on OF_GPIO. Fix it. Cc: Lars Poeschel Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/gpio/Kconfig') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 2d49784109b5..d5bd9eece408 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -717,7 +717,7 @@ config GPIO_MAX7301 config GPIO_MCP23S08 tristate "Microchip MCP23xxx I/O expander" - depends on (SPI_MASTER && !I2C) || I2C + depends on (SPI_MASTER && !I2C) || I2C && OF_GPIO help SPI/I2C driver for Microchip MCP23S08/MCP23S17/MCP23008/MCP23017 I/O expanders. -- cgit v1.2.3