diff options
115 files changed, 2363 insertions, 297 deletions
diff --git a/Documentation/devicetree/bindings/gpio/cdns,gpio.yaml b/Documentation/devicetree/bindings/gpio/cdns,gpio.yaml index a84d60b39459..7eacf5af6554 100644 --- a/Documentation/devicetree/bindings/gpio/cdns,gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/cdns,gpio.yaml @@ -14,8 +14,11 @@ properties: oneOf: - const: cdns,gpio-r1p02 - items: - - enum: - - axiado,ax3000-gpio + - const: axiado,ax3000-gpio + - const: cdns,gpio-r1p02 + - items: + - const: axiado,ax3005-gpio + - const: axiado,ax3000-gpio - const: cdns,gpio-r1p02 reg: diff --git a/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml b/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml index 4f955f855e1a..4631388a7d91 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml @@ -23,6 +23,9 @@ properties: - const: diodes,pi4ioe5v6534q - const: nxp,pcal6534 - items: + - const: kinetic,kts1622 + - const: ti,tcal6416 + - items: - enum: - exar,xra1202 - maxim,max7310 diff --git a/Documentation/devicetree/bindings/gpio/realtek,rtd1625-gpio.yaml b/Documentation/devicetree/bindings/gpio/realtek,rtd1625-gpio.yaml new file mode 100644 index 000000000000..f13c910b73c6 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/realtek,rtd1625-gpio.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright 2023 Realtek Semiconductor Corporation +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/realtek,rtd1625-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek DHC RTD1625 GPIO controller + +maintainers: + - Tzuyi Chang <tychang@realtek.com> + +description: | + GPIO controller for the Realtek RTD1625 SoC, featuring a per-pin register + architecture that differs significantly from earlier RTD series controllers. + Each GPIO has dedicated registers for configuration (direction, input/output + values, debounce), and interrupt control supporting edge and level detection + modes. + +properties: + compatible: + enum: + - realtek,rtd1625-iso-gpio + - realtek,rtd1625-isom-gpio + + reg: + maxItems: 1 + + interrupts: + items: + - description: Interrupt number of the assert GPIO interrupt, which is + triggered when there is a rising edge. + - description: Interrupt number of the deassert GPIO interrupt, which is + triggered when there is a falling edge. + - description: Interrupt number of the level-sensitive GPIO interrupt, + triggered by a configured logic level. + + interrupt-controller: true + + "#interrupt-cells": + const: 2 + + gpio-ranges: true + + gpio-controller: true + + "#gpio-cells": + const: 2 + +required: + - compatible + - reg + - gpio-ranges + - gpio-controller + - "#gpio-cells" + +additionalProperties: false + +examples: + - | + gpio@89100 { + compatible = "realtek,rtd1625-isom-gpio"; + reg = <0x89100 0x30>; + interrupt-parent = <&iso_m_irq_mux>; + interrupts = <0>, <1>, <2>; + interrupt-controller; + #interrupt-cells = <2>; + gpio-ranges = <&isom_pinctrl 0 0 4>; + gpio-controller; + #gpio-cells = <2>; + }; diff --git a/Documentation/devicetree/bindings/gpio/rockchip,gpio-bank.yaml b/Documentation/devicetree/bindings/gpio/rockchip,gpio-bank.yaml index bdd83f42615c..774e9c7de606 100644 --- a/Documentation/devicetree/bindings/gpio/rockchip,gpio-bank.yaml +++ b/Documentation/devicetree/bindings/gpio/rockchip,gpio-bank.yaml @@ -44,6 +44,13 @@ properties: power-domains: maxItems: 1 + rockchip,grf: + $ref: /schemas/types.yaml#/definitions/phandle + description: + The phandle of the syscon node managing the IO control registers + of this bank, on SoCs such as the RV1106 where each GPIO bank has + its own IOC block. + patternProperties: "^.+-hog(-[0-9]+)?$": type: object diff --git a/MAINTAINERS b/MAINTAINERS index 9f8d226b305a..d3f266e39800 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25094,10 +25094,12 @@ R: Andy Shevchenko <andriy.shevchenko@linux.intel.com> R: Daniel Scally <djrscally@gmail.com> R: Heikki Krogerus <heikki.krogerus@linux.intel.com> R: Sakari Ailus <sakari.ailus@linux.intel.com> +R: Bartosz Golaszewski <brgl@kernel.org> L: linux-acpi@vger.kernel.org S: Maintained F: drivers/base/property.c F: drivers/base/swnode.c +F: drivers/base/test/swnode-devlink-test.c F: include/linux/fwnode.h F: include/linux/property.h diff --git a/arch/mips/alchemy/board-xxs1500.c b/arch/mips/alchemy/board-xxs1500.c index f175bce2987f..a03762dde4e7 100644 --- a/arch/mips/alchemy/board-xxs1500.c +++ b/arch/mips/alchemy/board-xxs1500.c @@ -11,7 +11,7 @@ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/platform_device.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/delay.h> #include <linux/pm.h> #include <asm/bootinfo.h> diff --git a/arch/mips/alchemy/devboards/db1000.c b/arch/mips/alchemy/devboards/db1000.c index 8fb24b220e3a..5eff34a8683b 100644 --- a/arch/mips/alchemy/devboards/db1000.c +++ b/arch/mips/alchemy/devboards/db1000.c @@ -8,7 +8,7 @@ #include <linux/clk.h> #include <linux/dma-mapping.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/gpio/machine.h> #include <linux/gpio/property.h> #include <linux/init.h> diff --git a/arch/mips/alchemy/devboards/db1200.c b/arch/mips/alchemy/devboards/db1200.c index de2a9083ed9a..539b311fc8af 100644 --- a/arch/mips/alchemy/devboards/db1200.c +++ b/arch/mips/alchemy/devboards/db1200.c @@ -7,7 +7,7 @@ #include <linux/clk.h> #include <linux/dma-mapping.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/i2c.h> #include <linux/init.h> #include <linux/interrupt.h> diff --git a/arch/mips/alchemy/devboards/db1550.c b/arch/mips/alchemy/devboards/db1550.c index b8295a5c2e9a..579fc8f1eaed 100644 --- a/arch/mips/alchemy/devboards/db1550.c +++ b/arch/mips/alchemy/devboards/db1550.c @@ -7,7 +7,7 @@ #include <linux/clk.h> #include <linux/dma-mapping.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/i2c.h> #include <linux/init.h> #include <linux/io.h> diff --git a/arch/mips/bcm47xx/workarounds.c b/arch/mips/bcm47xx/workarounds.c index 745c6228eb2c..dc9e5483347d 100644 --- a/arch/mips/bcm47xx/workarounds.c +++ b/arch/mips/bcm47xx/workarounds.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include "bcm47xx_private.h" -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <bcm47xx_board.h> #include <bcm47xx.h> diff --git a/arch/mips/bcm63xx/boards/board_bcm963xx.c b/arch/mips/bcm63xx/boards/board_bcm963xx.c index c5617b889b1c..71628dac6c26 100644 --- a/arch/mips/bcm63xx/boards/board_bcm963xx.c +++ b/arch/mips/bcm63xx/boards/board_bcm963xx.c @@ -7,6 +7,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/init.h> +#include <linux/gpio/legacy.h> #include <linux/kernel.h> #include <linux/string.h> #include <linux/platform_device.h> diff --git a/arch/mips/include/asm/mach-bcm63xx/board_bcm963xx.h b/arch/mips/include/asm/mach-bcm63xx/board_bcm963xx.h index 830f53f28e3f..428cf4508f9a 100644 --- a/arch/mips/include/asm/mach-bcm63xx/board_bcm963xx.h +++ b/arch/mips/include/asm/mach-bcm63xx/board_bcm963xx.h @@ -3,7 +3,7 @@ #define BOARD_BCM963XX_H_ #include <linux/types.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/leds.h> #include <bcm63xx_dev_enet.h> #include <bcm63xx_dev_usb_usbd.h> diff --git a/arch/sh/Kconfig b/arch/sh/Kconfig index d5795067befa..d60f1d5a94c0 100644 --- a/arch/sh/Kconfig +++ b/arch/sh/Kconfig @@ -462,6 +462,7 @@ config CPU_SUBTYPE_SHX3 select CPU_SHX3 select GENERIC_CLOCKEVENTS_BROADCAST if SMP select GPIOLIB + select GPIOLIB_LEGACY select PINCTRL # SH4AL-DSP Processor Support diff --git a/arch/sh/boards/Kconfig b/arch/sh/boards/Kconfig index 1af93be61b1f..d89b74177233 100644 --- a/arch/sh/boards/Kconfig +++ b/arch/sh/boards/Kconfig @@ -80,6 +80,7 @@ config SH_7724_SOLUTION_ENGINE select SOLUTION_ENGINE depends on CPU_SUBTYPE_SH7724 select GPIOLIB + select GPIOLIB_LEGACY select REGULATOR_FIXED_VOLTAGE if REGULATOR imply SND_SOC_AK4642 if SND_SIMPLE_CARD help @@ -199,6 +200,7 @@ config SH_SH7757LCR bool "SH7757LCR" depends on CPU_SUBTYPE_SH7757 select GPIOLIB + select GPIOLIB_LEGACY select REGULATOR_FIXED_VOLTAGE if REGULATOR config SH_SH7785LCR @@ -226,6 +228,7 @@ config SH_URQUELL bool "Urquell" depends on CPU_SUBTYPE_SH7786 select GPIOLIB + select GPIOLIB_LEGACY select HAVE_PCI select NO_IOPORT_MAP if !PCI @@ -233,6 +236,7 @@ config SH_MIGOR bool "Migo-R" depends on CPU_SUBTYPE_SH7722 select GPIOLIB + select GPIOLIB_LEGACY select REGULATOR_FIXED_VOLTAGE if REGULATOR help Select Migo-R if configuring for the SH7722 Migo-R platform @@ -242,6 +246,7 @@ config SH_AP325RXA bool "AP-325RXA" depends on CPU_SUBTYPE_SH7723 select GPIOLIB + select GPIOLIB_LEGACY select REGULATOR_FIXED_VOLTAGE if REGULATOR help Renesas "AP-325RXA" support. @@ -251,6 +256,7 @@ config SH_KFR2R09 bool "KFR2R09" depends on CPU_SUBTYPE_SH7724 select GPIOLIB + select GPIOLIB_LEGACY select REGULATOR_FIXED_VOLTAGE if REGULATOR help "Kit For R2R for 2009" support. @@ -259,6 +265,7 @@ config SH_ECOVEC bool "EcoVec" depends on CPU_SUBTYPE_SH7724 select GPIOLIB + select GPIOLIB_LEGACY select REGULATOR_FIXED_VOLTAGE if REGULATOR imply SND_SOC_DA7210 if SND_SIMPLE_CARD help @@ -329,6 +336,7 @@ config SH_MAGIC_PANEL_R2 bool "Magic Panel R2" depends on CPU_SUBTYPE_SH7720 select GPIOLIB + select GPIOLIB_LEGACY select REGULATOR_FIXED_VOLTAGE if REGULATOR help Select Magic Panel R2 if configuring for Magic Panel R2. diff --git a/arch/sh/boards/board-magicpanelr2.c b/arch/sh/boards/board-magicpanelr2.c index 75de893152af..3f14118a1015 100644 --- a/arch/sh/boards/board-magicpanelr2.c +++ b/arch/sh/boards/board-magicpanelr2.c @@ -10,7 +10,7 @@ #include <linux/irq.h> #include <linux/platform_device.h> #include <linux/delay.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/regulator/fixed.h> #include <linux/regulator/machine.h> #include <linux/smsc911x.h> diff --git a/arch/sh/boards/board-sh7757lcr.c b/arch/sh/boards/board-sh7757lcr.c index bca54e489e11..6d18f59ef261 100644 --- a/arch/sh/boards/board-sh7757lcr.c +++ b/arch/sh/boards/board-sh7757lcr.c @@ -7,7 +7,7 @@ #include <linux/init.h> #include <linux/platform_device.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/irq.h> #include <linux/regulator/fixed.h> #include <linux/regulator/machine.h> diff --git a/arch/sh/boards/board-urquell.c b/arch/sh/boards/board-urquell.c index dad2b3b40735..1f73c04d341f 100644 --- a/arch/sh/boards/board-urquell.c +++ b/arch/sh/boards/board-urquell.c @@ -14,7 +14,7 @@ #include <linux/smc91x.h> #include <linux/mtd/physmap.h> #include <linux/delay.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/irq.h> #include <linux/clk.h> #include <linux/sh_intc.h> diff --git a/arch/sh/boards/mach-ap325rxa/setup.c b/arch/sh/boards/mach-ap325rxa/setup.c index bb5004a8ac02..17c3f568d92e 100644 --- a/arch/sh/boards/mach-ap325rxa/setup.c +++ b/arch/sh/boards/mach-ap325rxa/setup.c @@ -17,7 +17,7 @@ #include <linux/clkdev.h> #include <linux/delay.h> #include <linux/device.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/gpio/consumer.h> #include <linux/gpio/machine.h> #include <linux/i2c.h> diff --git a/arch/sh/boards/mach-ecovec24/setup.c b/arch/sh/boards/mach-ecovec24/setup.c index a641e26f8fdf..ca4b4dd1ddef 100644 --- a/arch/sh/boards/mach-ecovec24/setup.c +++ b/arch/sh/boards/mach-ecovec24/setup.c @@ -20,7 +20,7 @@ #include <linux/mmc/host.h> #include <linux/platform_data/sh_mmcif.h> #include <linux/mtd/physmap.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/gpio/machine.h> #include <linux/platform_data/gpio_backlight.h> #include <linux/platform_data/tmio.h> diff --git a/arch/sh/boards/mach-highlander/Kconfig b/arch/sh/boards/mach-highlander/Kconfig index b0abd03cac4e..cd3a553ce30c 100644 --- a/arch/sh/boards/mach-highlander/Kconfig +++ b/arch/sh/boards/mach-highlander/Kconfig @@ -20,6 +20,7 @@ config SH_R7785RP bool "R7785RP board support" depends on CPU_SUBTYPE_SH7785 select GPIOLIB + select GPIOLIB_LEGACY endchoice diff --git a/arch/sh/boards/mach-highlander/pinmux-r7785rp.c b/arch/sh/boards/mach-highlander/pinmux-r7785rp.c index 689bd8732d9e..3a1057ee9ace 100644 --- a/arch/sh/boards/mach-highlander/pinmux-r7785rp.c +++ b/arch/sh/boards/mach-highlander/pinmux-r7785rp.c @@ -3,7 +3,7 @@ * Copyright (C) 2008 Paul Mundt */ #include <linux/init.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <cpu/sh7785.h> #include <mach/highlander.h> diff --git a/arch/sh/boards/mach-kfr2r09/lcd_wqvga.c b/arch/sh/boards/mach-kfr2r09/lcd_wqvga.c index f6bbac106d13..68716660de34 100644 --- a/arch/sh/boards/mach-kfr2r09/lcd_wqvga.c +++ b/arch/sh/boards/mach-kfr2r09/lcd_wqvga.c @@ -14,7 +14,7 @@ #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <video/sh_mobile_lcdc.h> #include <mach/kfr2r09.h> #include <cpu/sh7724.h> diff --git a/arch/sh/boards/mach-kfr2r09/setup.c b/arch/sh/boards/mach-kfr2r09/setup.c index 70236859919d..30dd7dae7906 100644 --- a/arch/sh/boards/mach-kfr2r09/setup.c +++ b/arch/sh/boards/mach-kfr2r09/setup.c @@ -14,7 +14,7 @@ #include <linux/clkdev.h> #include <linux/delay.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/gpio/machine.h> #include <linux/i2c.h> #include <linux/init.h> diff --git a/arch/sh/boards/mach-migor/lcd_qvga.c b/arch/sh/boards/mach-migor/lcd_qvga.c index 4ebf130510bc..f72934e370fd 100644 --- a/arch/sh/boards/mach-migor/lcd_qvga.c +++ b/arch/sh/boards/mach-migor/lcd_qvga.c @@ -14,7 +14,7 @@ #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <video/sh_mobile_lcdc.h> #include <cpu/sh7722.h> #include <mach/migor.h> diff --git a/arch/sh/boards/mach-migor/setup.c b/arch/sh/boards/mach-migor/setup.c index 1853e6319a66..b0198e7cb9ab 100644 --- a/arch/sh/boards/mach-migor/setup.c +++ b/arch/sh/boards/mach-migor/setup.c @@ -22,7 +22,7 @@ #include <linux/smc91x.h> #include <linux/delay.h> #include <linux/clk.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/gpio/machine.h> #include <linux/videodev2.h> #include <linux/sh_intc.h> diff --git a/arch/sh/boards/mach-rsk/Kconfig b/arch/sh/boards/mach-rsk/Kconfig index f0299bc4416f..3810937aa5d4 100644 --- a/arch/sh/boards/mach-rsk/Kconfig +++ b/arch/sh/boards/mach-rsk/Kconfig @@ -12,16 +12,19 @@ config SH_RSK7201 config SH_RSK7203 bool "RSK7203" select GPIOLIB + select GPIOLIB_LEGACY depends on CPU_SUBTYPE_SH7203 config SH_RSK7264 bool "RSK2+SH7264" select GPIOLIB + select GPIOLIB_LEGACY depends on CPU_SUBTYPE_SH7264 config SH_RSK7269 bool "RSK2+SH7269" select GPIOLIB + select GPIOLIB_LEGACY depends on CPU_SUBTYPE_SH7269 endchoice diff --git a/arch/sh/boards/mach-rsk/devices-rsk7269.c b/arch/sh/boards/mach-rsk/devices-rsk7269.c index 4b1e386b51dd..54edd9958eca 100644 --- a/arch/sh/boards/mach-rsk/devices-rsk7269.c +++ b/arch/sh/boards/mach-rsk/devices-rsk7269.c @@ -11,7 +11,6 @@ #include <linux/interrupt.h> #include <linux/input.h> #include <linux/smsc911x.h> -#include <linux/gpio.h> #include <asm/machvec.h> #include <asm/io.h> diff --git a/arch/sh/boards/mach-se/7724/setup.c b/arch/sh/boards/mach-se/7724/setup.c index e500feb91053..bed8f44fcecb 100644 --- a/arch/sh/boards/mach-se/7724/setup.c +++ b/arch/sh/boards/mach-se/7724/setup.c @@ -15,7 +15,7 @@ #include <linux/delay.h> #include <linux/device.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/init.h> #include <linux/input.h> #include <linux/input/sh_keysc.h> diff --git a/arch/sh/include/mach-common/mach/magicpanelr2.h b/arch/sh/include/mach-common/mach/magicpanelr2.h index c2d218cea74b..29d86dd21b7b 100644 --- a/arch/sh/include/mach-common/mach/magicpanelr2.h +++ b/arch/sh/include/mach-common/mach/magicpanelr2.h @@ -10,8 +10,6 @@ #ifndef __ASM_SH_MAGICPANELR2_H #define __ASM_SH_MAGICPANELR2_H -#include <linux/gpio.h> - #define __IO_PREFIX mpr2 #include <asm/io_generic.h> diff --git a/arch/sh/kernel/cpu/sh4a/setup-shx3.c b/arch/sh/kernel/cpu/sh4a/setup-shx3.c index 7014d6d199b3..60be8dca54f6 100644 --- a/arch/sh/kernel/cpu/sh4a/setup-shx3.c +++ b/arch/sh/kernel/cpu/sh4a/setup-shx3.c @@ -9,7 +9,7 @@ #include <linux/serial.h> #include <linux/serial_sci.h> #include <linux/io.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/sh_timer.h> #include <linux/sh_intc.h> #include <cpu/shx3.h> diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 485e014330cc..48ccc3e6059d 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -2978,6 +2978,7 @@ config OLPC bool "One Laptop Per Child support" depends on !X86_PAE select GPIOLIB + select GPIOLIB_LEGACY select OF select OF_PROMTREE select IRQ_DOMAIN diff --git a/arch/x86/platform/olpc/olpc-xo1-sci.c b/arch/x86/platform/olpc/olpc-xo1-sci.c index 97eb4738d602..8b5b3626b559 100644 --- a/arch/x86/platform/olpc/olpc-xo1-sci.c +++ b/arch/x86/platform/olpc/olpc-xo1-sci.c @@ -9,7 +9,7 @@ #include <linux/cs5535.h> #include <linux/device.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/input.h> #include <linux/interrupt.h> #include <linux/platform_device.h> diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c index 07234d415b51..99b55b1053ee 100644 --- a/drivers/base/regmap/regmap-irq.c +++ b/drivers/base/regmap/regmap-irq.c @@ -296,6 +296,26 @@ static int regmap_irq_set_wake(struct irq_data *data, unsigned int on) return 0; } +static int regmap_irq_reqres(struct irq_data *data) +{ + struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data); + irq_hw_number_t hwirq = irqd_to_hwirq(data); + + if (d->chip->irq_reqres) + return d->chip->irq_reqres(d->chip->irq_drv_data, hwirq); + + return 0; +} + +static void regmap_irq_relres(struct irq_data *data) +{ + struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data); + irq_hw_number_t hwirq = irqd_to_hwirq(data); + + if (d->chip->irq_relres) + d->chip->irq_relres(d->chip->irq_drv_data, hwirq); +} + static const struct irq_chip regmap_irq_chip = { .irq_bus_lock = regmap_irq_lock, .irq_bus_sync_unlock = regmap_irq_sync_unlock, @@ -303,6 +323,8 @@ static const struct irq_chip regmap_irq_chip = { .irq_enable = regmap_irq_enable, .irq_set_type = regmap_irq_set_type, .irq_set_wake = regmap_irq_set_wake, + .irq_request_resources = regmap_irq_reqres, + .irq_release_resources = regmap_irq_relres, }; static inline int read_sub_irq_data(struct regmap_irq_chip_data *data, diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c index 869228a65cb3..1f2315858cc3 100644 --- a/drivers/base/swnode.c +++ b/drivers/base/swnode.c @@ -699,6 +699,62 @@ software_node_graph_parse_endpoint(const struct fwnode_handle *fwnode, return 0; } +static int software_node_add_links(struct fwnode_handle *fwnode) +{ + const struct software_node_ref_args *ref, *ref_array; + struct swnode *swnode = to_swnode(fwnode); + const struct property_entry *prop; + struct fwnode_handle *refnode; + unsigned int count; + + if (!swnode || !swnode->node->properties) + return 0; + + /* + * Unlike Device Tree, where phandles appear in many non-supplier + * contexts and a curated allowlist is required, a software node only + * carries a DEV_PROP_REF property when the author explicitly describes + * a reference to another node. Every such reference is therefore an + * intentional supplier dependency, so we create fwnode links for all + * of them. + */ + for (prop = swnode->node->properties; prop->name; prop++) { + if (prop->type != DEV_PROP_REF || prop->is_inline) + continue; + + /* + * TODO: Graph "remote-endpoint" references go both ways + * between endpoint child nodes and would create endpoint + * cycles. Let's leave it out for now until we have potential + * users. + */ + if (!strcmp(prop->name, "remote-endpoint")) + continue; + + ref_array = prop->pointer; + count = prop->length / sizeof(*ref_array); + + for (unsigned int i = 0; i < count; i++) { + ref = &ref_array[i]; + + if (ref->swnode) + refnode = software_node_fwnode(ref->swnode); + else if (ref->fwnode) + refnode = ref->fwnode; + else + continue; + + /* Supplier not registered yet, or self-reference. */ + if (!refnode || refnode == &swnode->fwnode) + continue; + + fwnode_link_add(&swnode->fwnode, refnode, 0); + } + } + + return 0; +} + static const struct fwnode_operations software_node_ops = { .get = software_node_get, .put = software_node_put, @@ -716,6 +772,7 @@ static const struct fwnode_operations software_node_ops = { .graph_get_remote_endpoint = software_node_graph_get_remote_endpoint, .graph_get_port_parent = software_node_graph_get_port_parent, .graph_parse_endpoint = software_node_graph_parse_endpoint, + .add_links = software_node_add_links, }; /* -------------------------------------------------------------------------- */ @@ -787,6 +844,8 @@ static void software_node_release(struct kobject *kobj) { struct swnode *swnode = kobj_to_swnode(kobj); + fwnode_links_purge(&swnode->fwnode); + if (swnode->parent) { ida_free(&swnode->parent->child_ids, swnode->id); list_del(&swnode->entry); @@ -1105,6 +1164,17 @@ void software_node_notify(struct device *dev) if (!swnode) return; + /* + * When the software node is the device's secondary firmware node, + * the core only records the owning device on the primary fwnode + * (see device_add()). fw_devlink resolves a supplier device through + * fwnode->dev, so without this a consumer referencing the software + * node could never find the supplier device and would defer forever. + * Make fwnode.dev point to its owner in that case. + */ + if (!device_match_fwnode(dev, &swnode->fwnode) && !swnode->fwnode.dev) + swnode->fwnode.dev = dev; + swnode_get(swnode); ret = sysfs_create_link(&dev->kobj, &swnode->kobj, "software_node"); if (ret) @@ -1127,6 +1197,15 @@ void software_node_notify_remove(struct device *dev) sysfs_remove_link(&swnode->kobj, dev_name(dev)); sysfs_remove_link(&dev->kobj, "software_node"); + + /* + * Drop the device pointer mirrored onto a secondary software node in + * software_node_notify(). For a primary software node the core owns + * fwnode->dev and clears it in device_del(). + */ + if (!device_match_fwnode(dev, &swnode->fwnode) && swnode->fwnode.dev == dev) + swnode->fwnode.dev = NULL; + swnode_put(swnode); if (swnode->managed) { diff --git a/drivers/base/test/Kconfig b/drivers/base/test/Kconfig index 2756870615cc..1ecf0791241a 100644 --- a/drivers/base/test/Kconfig +++ b/drivers/base/test/Kconfig @@ -18,3 +18,8 @@ config DRIVER_PE_KUNIT_TEST tristate "KUnit Tests for property entry API" if !KUNIT_ALL_TESTS depends on KUNIT default KUNIT_ALL_TESTS + +config DRIVER_SWNODE_KUNIT_TEST + tristate "KUnit Tests for software node fw_devlink links" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS diff --git a/drivers/base/test/Makefile b/drivers/base/test/Makefile index e321dfc7e922..9ced7bbd569f 100644 --- a/drivers/base/test/Makefile +++ b/drivers/base/test/Makefile @@ -6,3 +6,5 @@ obj-$(CONFIG_DM_KUNIT_TEST) += platform-device-test.o obj-$(CONFIG_DRIVER_PE_KUNIT_TEST) += property-entry-test.o CFLAGS_property-entry-test.o += $(DISABLE_STRUCTLEAK_PLUGIN) + +obj-$(CONFIG_DRIVER_SWNODE_KUNIT_TEST) += swnode-devlink-test.o diff --git a/drivers/base/test/swnode-devlink-test.c b/drivers/base/test/swnode-devlink-test.c new file mode 100644 index 000000000000..f53549e92611 --- /dev/null +++ b/drivers/base/test/swnode-devlink-test.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) Qualcomm Technologies, Inc. and/or its subsidiaries + */ + +#include <linux/array_size.h> +#include <linux/device.h> +#include <linux/fwnode.h> +#include <linux/list.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/wait.h> + +#include <kunit/fwnode.h> +#include <kunit/platform_device.h> +#include <kunit/test.h> + +static int swnode_count_suppliers(struct fwnode_handle *fwnode) +{ + struct fwnode_link *link; + unsigned int count = 0; + + /* + * The suppliers and consumers lists should typically only be accessed + * with the fwnode_link_lock taken but it's private to the driver core. + * + * These are tests and at this point nobody should be modifying them so + * let's just access the list. + */ + list_for_each_entry(link, &fwnode->suppliers, c_hook) + count++; + + return count; +} + +/* True if a supplier link con->sup exists, checked from both list ends. */ +static bool swnode_has_link(struct fwnode_handle *consumer, + struct fwnode_handle *supplier) +{ + bool from_con = false, from_sup = false; + struct fwnode_link *link; + + list_for_each_entry(link, &consumer->suppliers, c_hook) { + if (link->supplier == supplier && link->consumer == consumer) + from_con = true; + } + + list_for_each_entry(link, &supplier->consumers, s_hook) { + if (link->supplier == supplier && link->consumer == consumer) + from_sup = true; + } + + return from_con && from_sup; +} + +/* A single reference creates exactly one supplier link, on both list ends. */ +static void swnode_devlink_test_single_ref(struct kunit *test) +{ + static const struct software_node supp_swnode = { + .name = "swnode-devlink-test-supplier", + }; + + struct fwnode_handle *cons_fwnode, *supp_fwnode; + int ret; + + const struct property_entry props[] = { + PROPERTY_ENTRY_REF("supplier", &supp_swnode), + { } + }; + + supp_fwnode = kunit_software_node_register(test, &supp_swnode); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, supp_fwnode); + + cons_fwnode = kunit_fwnode_create_software_node(test, props, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons_fwnode); + + ret = fwnode_call_int_op(cons_fwnode, add_links); + KUNIT_EXPECT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, swnode_count_suppliers(cons_fwnode), 1); + KUNIT_EXPECT_TRUE(test, swnode_has_link(cons_fwnode, supp_fwnode)); +} + +/* Multiple distinct references create multiple supplier links. */ +static void swnode_devlink_test_multiple_refs(struct kunit *test) +{ + static const struct software_node supp1_swnode = { + .name = "swnode-devlink-test-supplier-1", + }; + static const struct software_node supp2_swnode = { + .name = "swnode-devlink-test-supplier-2", + }; + static const struct software_node *supp_nodes[] = { + &supp1_swnode, &supp2_swnode, NULL + }; + + const struct property_entry props[] = { + PROPERTY_ENTRY_REF("foo", &supp1_swnode), + PROPERTY_ENTRY_REF("bar", &supp2_swnode), + { } + }; + + struct fwnode_handle *fwnode; + int ret; + + ret = kunit_software_node_register_node_group(test, supp_nodes); + KUNIT_ASSERT_EQ(test, ret, 0); + + fwnode = kunit_fwnode_create_software_node(test, props, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + ret = fwnode_call_int_op(fwnode, add_links); + KUNIT_EXPECT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, swnode_count_suppliers(fwnode), 2); + KUNIT_EXPECT_TRUE(test, swnode_has_link(fwnode, software_node_fwnode(&supp1_swnode))); + KUNIT_EXPECT_TRUE(test, swnode_has_link(fwnode, software_node_fwnode(&supp2_swnode))); +} + +/* A reference to an unregistered node creates no link (graceful skip). */ +static void swnode_devlink_test_unregistered_ref(struct kunit *test) +{ + static const struct software_node supp_swnode = { + .name = "swnode-devlink-test-supplier", + }; + + const struct property_entry props[] = { + PROPERTY_ENTRY_REF("supplier", &supp_swnode), + { } + }; + + struct fwnode_handle *fwnode; + int ret; + + fwnode = kunit_fwnode_create_software_node(test, props, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + ret = fwnode_call_int_op(fwnode, add_links); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, swnode_count_suppliers(fwnode), 0); +} + +/* Graph "remote-endpoint" references are excluded. */ +static void swnode_devlink_test_remote_endpoint_excluded(struct kunit *test) +{ + static const struct software_node ep_swnode = { + .name = "swnode-devlink-test-end-point" + }; + + const struct property_entry props[] = { + PROPERTY_ENTRY_REF("remote-endpoint", &ep_swnode), + { } + }; + + struct fwnode_handle *cons_fwnode, *supp_fwnode; + int ret; + + supp_fwnode = kunit_software_node_register(test, &ep_swnode); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, supp_fwnode); + + cons_fwnode = kunit_fwnode_create_software_node(test, props, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons_fwnode); + + ret = fwnode_call_int_op(cons_fwnode, add_links); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, swnode_count_suppliers(cons_fwnode), 0); +} + +/* A reference array creates one link per registered element. */ +static void swnode_devlink_test_ref_array(struct kunit *test) +{ + static const struct software_node supp1_swnode = { + .name = "swnode-devlink-test-supplier-1", + }; + static const struct software_node supp2_swnode = { + .name = "swnode-devlink-test-supplier-2", + }; + static const struct software_node *supp_nodes[] = { + &supp1_swnode, &supp2_swnode, NULL + }; + static const struct software_node_ref_args refs[] = { + SOFTWARE_NODE_REFERENCE(&supp1_swnode), + SOFTWARE_NODE_REFERENCE(&supp2_swnode, 4, 2), + }; + + const struct property_entry props[] = { + PROPERTY_ENTRY_REF_ARRAY("suppliers", refs), + { } + }; + + struct fwnode_handle *fwnode; + int ret; + + ret = kunit_software_node_register_node_group(test, supp_nodes); + KUNIT_ASSERT_EQ(test, ret, 0); + + fwnode = kunit_fwnode_create_software_node(test, props, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + ret = fwnode_call_int_op(fwnode, add_links); + KUNIT_EXPECT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, swnode_count_suppliers(fwnode), 2); + KUNIT_EXPECT_TRUE(test, swnode_has_link(fwnode, software_node_fwnode(&supp1_swnode))); + KUNIT_EXPECT_TRUE(test, swnode_has_link(fwnode, software_node_fwnode(&supp2_swnode))); +} + +/* + * End-to-end test: fw_devlink must defer a consumer's probe until its + * supplier has probed. + * + * The reference created by software_node_add_links() is only useful if the + * driver core promotes it to a real device_link and uses it to order probing. + * This test drives actual probing through the platform bus and asserts the + * supplier binds before the consumer. + */ + +#define SWNODE_DEVLINK_TEST_SUPPLIER "swnode-link-supplier" +#define SWNODE_DEVLINK_TEST_CONSUMER "swnode-link-consumer" +#define SWNODE_DEVLINK_TEST_TIMEOUT_MS (2 * MSEC_PER_SEC) + +struct swnode_test_probe_order { + /* Names in the order their drivers' .probe ran. */ + const char *probed[2]; + unsigned int count; + wait_queue_head_t wq; +}; + +static int swnode_test_record_probe(struct platform_device *pdev) +{ + struct swnode_test_probe_order *order = platform_get_drvdata(pdev); + + if (order && order->count < ARRAY_SIZE(order->probed)) { + order->probed[order->count++] = dev_name(&pdev->dev); + wake_up_interruptible(&order->wq); + } + + return 0; +} + +static struct platform_driver swnode_test_supplier_driver = { + .probe = swnode_test_record_probe, + .driver = { + .name = SWNODE_DEVLINK_TEST_SUPPLIER, + }, +}; + +static struct platform_driver swnode_test_consumer_driver = { + .probe = swnode_test_record_probe, + .driver = { + .name = SWNODE_DEVLINK_TEST_CONSUMER, + }, +}; + +static void swnode_devlink_test_probe_order(struct kunit *test) +{ + static const struct software_node supplier_swnode = { + .name = "swnode-devlink-test-supplier", + }; + + const struct property_entry consumer_props[] = { + PROPERTY_ENTRY_REF("supplier-ref", &supplier_swnode), + { } + }; + + struct platform_device *supplier, *consumer; + struct swnode_test_probe_order *order; + struct fwnode_handle *fwnode; + int ret; + + order = kunit_kzalloc(test, sizeof(*order), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, order); + init_waitqueue_head(&order->wq); + + fwnode = kunit_software_node_register(test, &supplier_swnode); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + ret = kunit_platform_driver_register(test, &swnode_test_supplier_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + ret = kunit_platform_driver_register(test, &swnode_test_consumer_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + supplier = kunit_platform_device_alloc(test, SWNODE_DEVLINK_TEST_SUPPLIER, + PLATFORM_DEVID_NONE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, supplier); + consumer = kunit_platform_device_alloc(test, SWNODE_DEVLINK_TEST_CONSUMER, + PLATFORM_DEVID_NONE); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, consumer); + + platform_set_drvdata(supplier, order); + platform_set_drvdata(consumer, order); + + ret = kunit_device_add_software_node(test, &supplier->dev, &supplier_swnode); + KUNIT_ASSERT_EQ(test, ret, 0); + ret = device_create_managed_software_node(&consumer->dev, + consumer_props, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_platform_device_add(test, consumer); + KUNIT_ASSERT_EQ(test, ret, 0); + ret = kunit_platform_device_add(test, supplier); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = wait_event_interruptible_timeout(order->wq, + order->count == 2, + msecs_to_jiffies(SWNODE_DEVLINK_TEST_TIMEOUT_MS)); + KUNIT_ASSERT_GT(test, ret, 0); + + KUNIT_EXPECT_STREQ(test, order->probed[0], SWNODE_DEVLINK_TEST_SUPPLIER); + KUNIT_EXPECT_STREQ(test, order->probed[1], SWNODE_DEVLINK_TEST_CONSUMER); + + /* Tear down the consumer (and its device link) before the supplier. */ + kunit_platform_device_unregister(test, consumer); +} + +static struct kunit_case swnode_test_cases[] = { + KUNIT_CASE(swnode_devlink_test_single_ref), + KUNIT_CASE(swnode_devlink_test_multiple_refs), + KUNIT_CASE(swnode_devlink_test_unregistered_ref), + KUNIT_CASE(swnode_devlink_test_remote_endpoint_excluded), + KUNIT_CASE(swnode_devlink_test_ref_array), + KUNIT_CASE(swnode_devlink_test_probe_order), + { } +}; + +static struct kunit_suite swnode_test_suite = { + .name = "software-node-links", + .test_cases = swnode_test_cases, +}; + +kunit_test_suite(swnode_test_suite); + +MODULE_DESCRIPTION("Test module for software node fw_devlink support"); +MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpib/gpio/gpib_bitbang.c b/drivers/gpib/gpio/gpib_bitbang.c index 0e227980b493..34d14b94a0b8 100644 --- a/drivers/gpib/gpio/gpib_bitbang.c +++ b/drivers/gpib/gpio/gpib_bitbang.c @@ -64,7 +64,6 @@ #include <linux/gpio/consumer.h> #include <linux/gpio/driver.h> #include <linux/gpio/machine.h> -#include <linux/gpio.h> #include <linux/irq.h> static int sn7516x_used = 1, sn7516x; diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 7a09e42672b4..a48586bb8edb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -653,7 +653,7 @@ config GPIO_ROCKCHIP config GPIO_RTD tristate "Realtek DHC GPIO support" depends on ARCH_REALTEK || COMPILE_TEST - default y + default ARCH_REALTEK select GPIOLIB_IRQCHIP help This option enables support for GPIOs found on Realtek DHC(Digital @@ -663,6 +663,20 @@ config GPIO_RTD Say yes here to support GPIO functionality and GPIO interrupt on Realtek DHC SoCs. +config GPIO_RTD1625 + tristate "Realtek DHC RTD1625 GPIO support" + depends on ARCH_REALTEK || COMPILE_TEST + default ARCH_REALTEK + select GPIOLIB_IRQCHIP + select GPIO_REGMAP + select REGMAP_MMIO + help + This option enables support for the GPIO controller on Realtek + DHC (Digital Home Center) RTD1625 SoC. + + Say yes here to support both basic GPIO line functionality + and GPIO interrupt handling capabilities for this platform. + config GPIO_SAMA5D2_PIOBU tristate "SAMA5D2 PIOBU GPIO support" depends on OF @@ -949,7 +963,8 @@ config GPIO_IDT3243X endmenu menu "Port-mapped I/O GPIO drivers" - depends on X86 && HAS_IOPORT # I/O space access + depends on X86 || COMPILE_TEST + depends on HAS_IOPORT # I/O space access config GPIO_VX855 tristate "VIA VX855/VX875 GPIO" diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 23aedc107cc5..dc9e6d643b5b 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -161,6 +161,7 @@ obj-$(CONFIG_GPIO_REALTEK_OTTO) += gpio-realtek-otto.o obj-$(CONFIG_GPIO_REG) += gpio-reg.o obj-$(CONFIG_GPIO_ROCKCHIP) += gpio-rockchip.o obj-$(CONFIG_GPIO_RTD) += gpio-rtd.o +obj-$(CONFIG_GPIO_RTD1625) += gpio-rtd1625.o obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o diff --git a/drivers/gpio/gpio-104-idi-48.c b/drivers/gpio/gpio-104-idi-48.c index ba73ee9c0c29..fb017569cfa8 100644 --- a/drivers/gpio/gpio-104-idi-48.c +++ b/drivers/gpio/gpio-104-idi-48.c @@ -36,9 +36,10 @@ MODULE_PARM_DESC(irq, "ACCES 104-IDI-48 interrupt line numbers"); #define IDI48_IRQ_STATUS 0x7 #define IDI48_IRQ_ENABLE IDI48_IRQ_STATUS -static int idi_48_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base, - unsigned int offset, unsigned int *reg, - unsigned int *mask) +static int idi_48_reg_mask_xlate(struct gpio_regmap *gpio, + enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, + unsigned int *reg, unsigned int *mask) { const unsigned int line = offset % 8; const unsigned int stride = offset / 8; diff --git a/drivers/gpio/gpio-amd-fch.c b/drivers/gpio/gpio-amd-fch.c index 9f329938202b..9b9d75acf35a 100644 --- a/drivers/gpio/gpio-amd-fch.c +++ b/drivers/gpio/gpio-amd-fch.c @@ -48,13 +48,11 @@ static void __iomem *amd_fch_gpio_addr(struct amd_fch_gpio_priv *priv, static int amd_fch_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) { - unsigned long flags; struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); void __iomem *ptr = amd_fch_gpio_addr(priv, offset); - spin_lock_irqsave(&priv->lock, flags); + guard(spinlock_irqsave)(&priv->lock); writel_relaxed(readl_relaxed(ptr) & ~AMD_FCH_GPIO_FLAG_DIRECTION, ptr); - spin_unlock_irqrestore(&priv->lock, flags); return 0; } @@ -62,12 +60,11 @@ static int amd_fch_gpio_direction_input(struct gpio_chip *gc, static int amd_fch_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio, int value) { - unsigned long flags; struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); void __iomem *ptr = amd_fch_gpio_addr(priv, gpio); u32 val; - spin_lock_irqsave(&priv->lock, flags); + guard(spinlock_irqsave)(&priv->lock); val = readl_relaxed(ptr); if (value) @@ -77,33 +74,28 @@ static int amd_fch_gpio_direction_output(struct gpio_chip *gc, writel_relaxed(val | AMD_FCH_GPIO_FLAG_DIRECTION, ptr); - spin_unlock_irqrestore(&priv->lock, flags); - return 0; } static int amd_fch_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio) { int ret; - unsigned long flags; struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); void __iomem *ptr = amd_fch_gpio_addr(priv, gpio); - spin_lock_irqsave(&priv->lock, flags); + guard(spinlock_irqsave)(&priv->lock); ret = (readl_relaxed(ptr) & AMD_FCH_GPIO_FLAG_DIRECTION); - spin_unlock_irqrestore(&priv->lock, flags); return ret ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; } static int amd_fch_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) { - unsigned long flags; struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); void __iomem *ptr = amd_fch_gpio_addr(priv, gpio); u32 mask; - spin_lock_irqsave(&priv->lock, flags); + guard(spinlock_irqsave)(&priv->lock); mask = readl_relaxed(ptr); if (value) @@ -112,22 +104,18 @@ static int amd_fch_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) mask &= ~AMD_FCH_GPIO_FLAG_WRITE; writel_relaxed(mask, ptr); - spin_unlock_irqrestore(&priv->lock, flags); - return 0; } static int amd_fch_gpio_get(struct gpio_chip *gc, unsigned int offset) { - unsigned long flags; u32 val; struct amd_fch_gpio_priv *priv = gpiochip_get_data(gc); void __iomem *ptr = amd_fch_gpio_addr(priv, offset); - spin_lock_irqsave(&priv->lock, flags); + guard(spinlock_irqsave)(&priv->lock); val = readl_relaxed(ptr); - spin_unlock_irqrestore(&priv->lock, flags); return FIELD_GET(AMD_FCH_GPIO_FLAG_READ, val); } diff --git a/drivers/gpio/gpio-bd72720.c b/drivers/gpio/gpio-bd72720.c index 306e23411209..c124d964e4cf 100644 --- a/drivers/gpio/gpio-bd72720.c +++ b/drivers/gpio/gpio-bd72720.c @@ -85,9 +85,11 @@ static int bd72720gpi_get(struct bd72720_gpio *bdgpio, unsigned int reg_offset) static int bd72720gpo_get(struct bd72720_gpio *bdgpio, unsigned int offset) { - const int regs[] = { BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL, - BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL, - BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL }; + static const int regs[] = { + BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL, + BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL, + BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL + }; int ret, val; ret = regmap_read(bdgpio->regmap, regs[offset], &val); @@ -111,9 +113,11 @@ static int bd72720gpo_set(struct gpio_chip *chip, unsigned int offset, int value) { struct bd72720_gpio *bdgpio = gpiochip_get_data(chip); - const int regs[] = { BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL, - BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL, - BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL }; + static const int regs[] = { + BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL, + BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL, + BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL + }; if (BIT(offset) & bdgpio->gpio_is_input) { dev_dbg(bdgpio->dev, "pin %d not output.\n", offset); @@ -132,9 +136,11 @@ static int bd72720_gpio_set_config(struct gpio_chip *chip, unsigned int offset, unsigned long config) { struct bd72720_gpio *bdgpio = gpiochip_get_data(chip); - const int regs[] = { BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL, - BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL, - BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL }; + static const int regs[] = { + BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL, + BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL, + BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL + }; /* * We can only set the output mode, which makes sense only when output diff --git a/drivers/gpio/gpio-bt8xx.c b/drivers/gpio/gpio-bt8xx.c index 324eeb77dbd5..f3a2321bc388 100644 --- a/drivers/gpio/gpio-bt8xx.c +++ b/drivers/gpio/gpio-bt8xx.c @@ -154,12 +154,18 @@ static int bt8xxgpio_probe(struct pci_dev *dev, const struct pci_device_id *pci_id) { struct bt8xxgpio *bg; + void __iomem *mmio; int err; + mmio = devm_ioremap_resource(&dev->dev, pci_resource_n(dev, 0)); + if (IS_ERR(mmio)) + return PTR_ERR(mmio); + bg = devm_kzalloc(&dev->dev, sizeof(struct bt8xxgpio), GFP_KERNEL); if (!bg) return -ENOMEM; + bg->mmio = mmio; bg->pdev = dev; spin_lock_init(&bg->lock); @@ -168,24 +174,9 @@ static int bt8xxgpio_probe(struct pci_dev *dev, dev_err(&dev->dev, "can't enable device.\n"); return err; } - if (!devm_request_mem_region(&dev->dev, pci_resource_start(dev, 0), - pci_resource_len(dev, 0), - "bt8xxgpio")) { - dev_warn(&dev->dev, "can't request iomem (0x%llx).\n", - (unsigned long long)pci_resource_start(dev, 0)); - err = -EBUSY; - goto err_disable; - } pci_set_master(dev); pci_set_drvdata(dev, bg); - bg->mmio = devm_ioremap(&dev->dev, pci_resource_start(dev, 0), 0x1000); - if (!bg->mmio) { - dev_err(&dev->dev, "ioremap() failed\n"); - err = -EIO; - goto err_disable; - } - /* Disable interrupts */ bgwrite(0, BT848_INT_MASK); diff --git a/drivers/gpio/gpio-by-pinctrl.c b/drivers/gpio/gpio-by-pinctrl.c index fb8440acb31d..f54efd635f13 100644 --- a/drivers/gpio/gpio-by-pinctrl.c +++ b/drivers/gpio/gpio-by-pinctrl.c @@ -54,11 +54,11 @@ static int pin_control_gpio_direction_output(struct gpio_chip *chip, { int ret; - ret = pinctrl_gpio_direction_output(chip, offset); + ret = pin_control_gpio_set(chip, offset, val); if (ret) return ret; - return pin_control_gpio_set(chip, offset, val); + return pinctrl_gpio_direction_output(chip, offset); } static int pin_control_gpio_probe(struct platform_device *pdev) diff --git a/drivers/gpio/gpio-davinci.c b/drivers/gpio/gpio-davinci.c index 270cd7c88812..cccbaea1dec2 100644 --- a/drivers/gpio/gpio-davinci.c +++ b/drivers/gpio/gpio-davinci.c @@ -285,9 +285,9 @@ static void gpio_irq_unmask(struct irq_data *d) gpiochip_enable_irq(&chips->chip, hwirq); - status &= IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING; + status &= IRQ_TYPE_EDGE_BOTH; if (!status) - status = IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING; + status = IRQ_TYPE_EDGE_BOTH; if (status & IRQ_TYPE_EDGE_FALLING) writel_relaxed(mask, &g->set_falling); @@ -297,7 +297,7 @@ static void gpio_irq_unmask(struct irq_data *d) static int gpio_irq_type(struct irq_data *d, unsigned trigger) { - if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + if (trigger & ~IRQ_TYPE_EDGE_BOTH) return -EINVAL; return 0; @@ -400,7 +400,7 @@ static int gpio_irq_type_unbanked(struct irq_data *data, unsigned trigger) mask = __gpio_mask(i); - if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + if (trigger & ~IRQ_TYPE_EDGE_BOTH) return -EINVAL; writel_relaxed(mask, (trigger & IRQ_TYPE_EDGE_FALLING) diff --git a/drivers/gpio/gpio-i8255.c b/drivers/gpio/gpio-i8255.c index 953018bfa2b1..541a348447cd 100644 --- a/drivers/gpio/gpio-i8255.c +++ b/drivers/gpio/gpio-i8255.c @@ -67,8 +67,8 @@ static int i8255_ppi_init(struct regmap *const map, const unsigned int base) return regmap_write(map, base + I8255_PORTC, 0x00); } -static int i8255_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base, - unsigned int offset, unsigned int *reg, +static int i8255_reg_mask_xlate(struct gpio_regmap *gpio, enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, unsigned int *reg, unsigned int *mask) { const unsigned int ppi = offset / I8255_NGPIO; diff --git a/drivers/gpio/gpio-idio-16.c b/drivers/gpio/gpio-idio-16.c index 4fbae6f6a497..9ac967c6b430 100644 --- a/drivers/gpio/gpio-idio-16.c +++ b/drivers/gpio/gpio-idio-16.c @@ -66,9 +66,9 @@ static int idio_16_handle_mask_sync(const int index, const unsigned int mask_buf return 0; } -static int idio_16_reg_mask_xlate(struct gpio_regmap *const gpio, const unsigned int base, - const unsigned int offset, unsigned int *const reg, - unsigned int *const mask) +static int idio_16_reg_mask_xlate(struct gpio_regmap *const gpio, enum gpio_regmap_operation op, + const unsigned int base, const unsigned int offset, + unsigned int *const reg, unsigned int *const mask) { unsigned int stride; diff --git a/drivers/gpio/gpio-max7360.c b/drivers/gpio/gpio-max7360.c index d12cf1dc8d57..6755d58c4534 100644 --- a/drivers/gpio/gpio-max7360.c +++ b/drivers/gpio/gpio-max7360.c @@ -93,6 +93,7 @@ static int max7360_set_gpos_count(struct device *dev, struct regmap *regmap) } static int max7360_gpio_reg_mask_xlate(struct gpio_regmap *gpio, + enum gpio_regmap_operation op, unsigned int base, unsigned int offset, unsigned int *reg, unsigned int *mask) { @@ -127,7 +128,7 @@ static int max7360_handle_mask_sync(const int index, const unsigned int mask_buf, void *const irq_drv_data) { - struct regmap *regmap = irq_drv_data; + struct regmap *regmap = gpio_regmap_get_drvdata(irq_drv_data); int ret; for (unsigned int i = 0; i < MAX7360_MAX_GPIO; i++) { @@ -170,7 +171,6 @@ static int max7360_gpio_probe(struct platform_device *pdev) /* Create custom IRQ configuration. */ irq_chip = devm_kzalloc(dev, sizeof(*irq_chip), GFP_KERNEL); - gpio_config.regmap_irq_chip = irq_chip; if (!irq_chip) return -ENOMEM; @@ -181,7 +181,9 @@ static int max7360_gpio_probe(struct platform_device *pdev) irq_chip->num_irqs = MAX7360_MAX_GPIO; irq_chip->irqs = max7360_regmap_irqs; irq_chip->handle_mask_sync = max7360_handle_mask_sync; - irq_chip->irq_drv_data = regmap; + + gpio_config.regmap_irq_chip = irq_chip; + gpio_config.drvdata = regmap; for (unsigned int i = 0; i < MAX7360_MAX_GPIO; i++) { ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(i), diff --git a/drivers/gpio/gpio-mt7621.c b/drivers/gpio/gpio-mt7621.c index 1b0b5247d3c9..a86f6b1060fc 100644 --- a/drivers/gpio/gpio-mt7621.c +++ b/drivers/gpio/gpio-mt7621.c @@ -68,7 +68,7 @@ mt7621_gpio_gc_to_priv(struct gpio_chip *gc) } static inline struct mtk_gc * -to_mediatek_gpio(struct gpio_chip *chip) +to_mt7621_gpio(struct gpio_chip *chip) { struct gpio_generic_chip *gen_gc = to_gpio_generic_chip(chip); @@ -137,7 +137,7 @@ mt7621_gpio_hwirq_to_offset(irq_hw_number_t hwirq, struct mtk_gc *bank) } static void -mediatek_gpio_irq_unmask(struct irq_data *d) +mt7621_gpio_irq_unmask(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct mtk_gc *rg = gpiochip_get_data(gc); @@ -159,7 +159,7 @@ mediatek_gpio_irq_unmask(struct irq_data *d) } static void -mediatek_gpio_irq_mask(struct irq_data *d) +mt7621_gpio_irq_mask(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct mtk_gc *rg = gpiochip_get_data(gc); @@ -181,7 +181,7 @@ mediatek_gpio_irq_mask(struct irq_data *d) } static int -mediatek_gpio_irq_type(struct irq_data *d, unsigned int type) +mt7621_gpio_irq_type(struct irq_data *d, unsigned int type) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct mtk_gc *rg = gpiochip_get_data(gc); @@ -194,7 +194,7 @@ mediatek_gpio_irq_type(struct irq_data *d, unsigned int type) rg->hlevel | rg->llevel) & mask) return 0; - type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING; + type = IRQ_TYPE_EDGE_BOTH; } rg->rising &= ~mask; @@ -245,11 +245,11 @@ mt7621_gpio_irq_relres(struct irq_data *d) } static int -mediatek_gpio_xlate(struct gpio_chip *chip, +mt7621_gpio_xlate(struct gpio_chip *chip, const struct of_phandle_args *spec, u32 *flags) { int gpio = spec->args[0]; - struct mtk_gc *rg = to_mediatek_gpio(chip); + struct mtk_gc *rg = to_mt7621_gpio(chip); if (rg->bank != gpio / MTK_BANK_WIDTH) return -EINVAL; @@ -264,10 +264,10 @@ static const struct irq_chip mt7621_irq_chip = { .name = "mt7621-gpio", .irq_request_resources = mt7621_gpio_irq_reqres, .irq_release_resources = mt7621_gpio_irq_relres, - .irq_mask_ack = mediatek_gpio_irq_mask, - .irq_mask = mediatek_gpio_irq_mask, - .irq_unmask = mediatek_gpio_irq_unmask, - .irq_set_type = mediatek_gpio_irq_type, + .irq_mask_ack = mt7621_gpio_irq_mask, + .irq_mask = mt7621_gpio_irq_mask, + .irq_unmask = mt7621_gpio_irq_unmask, + .irq_set_type = mt7621_gpio_irq_type, .flags = IRQCHIP_IMMUTABLE, }; @@ -380,7 +380,7 @@ mt7621_gpio_to_irq(struct gpio_chip *gc, unsigned int offset) } static int -mediatek_gpio_bank_probe(struct device *dev, int bank) +mt7621_gpio_bank_probe(struct device *dev, int bank) { struct gpio_generic_chip_config config; struct mtk *mtk = dev_get_drvdata(dev); @@ -416,7 +416,7 @@ mediatek_gpio_bank_probe(struct device *dev, int bank) } rg->chip.gc.of_gpio_n_cells = 2; - rg->chip.gc.of_xlate = mediatek_gpio_xlate; + rg->chip.gc.of_xlate = mt7621_gpio_xlate; rg->chip.gc.ngpio = MTK_BANK_WIDTH; rg->chip.gc.label = devm_kasprintf(dev, GFP_KERNEL, "%s-bank%d", dev_name(dev), bank); @@ -443,7 +443,7 @@ mediatek_gpio_bank_probe(struct device *dev, int bank) } static int -mediatek_gpio_probe(struct platform_device *pdev) +mt7621_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mtk *mtk; @@ -477,7 +477,7 @@ mediatek_gpio_probe(struct platform_device *pdev) return ret; for (i = 0; i < MTK_BANK_CNT; i++) { - ret = mediatek_gpio_bank_probe(dev, i); + ret = mt7621_gpio_bank_probe(dev, i); if (ret) return ret; } @@ -485,18 +485,18 @@ mediatek_gpio_probe(struct platform_device *pdev) return 0; } -static const struct of_device_id mediatek_gpio_match[] = { +static const struct of_device_id mt7621_gpio_match[] = { { .compatible = "mediatek,mt7621-gpio" }, {}, }; -MODULE_DEVICE_TABLE(of, mediatek_gpio_match); +MODULE_DEVICE_TABLE(of, mt7621_gpio_match); -static struct platform_driver mediatek_gpio_driver = { - .probe = mediatek_gpio_probe, +static struct platform_driver mt7621_gpio_driver = { + .probe = mt7621_gpio_probe, .driver = { .name = "mt7621_gpio", - .of_match_table = mediatek_gpio_match, + .of_match_table = mt7621_gpio_match, }, }; -builtin_platform_driver(mediatek_gpio_driver); +builtin_platform_driver(mt7621_gpio_driver); diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c index a010604e5ff7..93b8a08b04b9 100644 --- a/drivers/gpio/gpio-mvebu.c +++ b/drivers/gpio/gpio-mvebu.c @@ -37,6 +37,7 @@ #include <linux/gpio/consumer.h> #include <linux/gpio/machine.h> #include <linux/init.h> +#include <linux/interrupt.h> #include <linux/io.h> #include <linux/irq.h> #include <linux/irqchip/chained_irq.h> @@ -114,7 +115,7 @@ struct mvebu_gpio_chip { struct regmap *regs; u32 offset; struct regmap *percpu_regs; - int irqbase; + int bank_irq[4]; struct irq_domain *domain; int soc_variant; @@ -604,6 +605,34 @@ static const struct regmap_config mvebu_gpio_regmap_config = { }; /* + * Forward wake-up configuration to the parent bank IRQ. + * @d: interrupt data + * @enable: enable as wake-up if non-zero + * + * Return: 0 on success, or a negative error code. + */ +static int mvebu_gpio_set_wake_irq(struct irq_data *d, unsigned int enable) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct mvebu_gpio_chip *mvchip = gc->private; + int bank; + int irq; + + bank = d->hwirq / 8; + if (bank >= ARRAY_SIZE(mvchip->bank_irq)) + return -EINVAL; + + irq = mvchip->bank_irq[bank]; + if (irq <= 0) + return -EINVAL; + + if (enable) + return enable_irq_wake(irq); + + return disable_irq_wake(irq); +} + +/* * Functions implementing the pwm_chip methods */ static struct mvebu_pwm *to_mvebu_pwm(struct pwm_chip *chip) @@ -1253,7 +1282,7 @@ static int mvebu_gpio_probe(struct platform_device *pdev) err = irq_alloc_domain_generic_chips( mvchip->domain, ngpios, 2, np->name, handle_level_irq, - IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_LEVEL, 0, 0); + IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_LEVEL, 0, IRQ_GC_INIT_NESTED_LOCK); if (err) { dev_err(&pdev->dev, "couldn't allocate irq chips %s (DT).\n", mvchip->chip.label); @@ -1267,18 +1296,22 @@ static int mvebu_gpio_probe(struct platform_device *pdev) gc = irq_get_domain_generic_chip(mvchip->domain, 0); gc->private = mvchip; ct = &gc->chip_types[0]; - ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW; + ct->type = IRQ_TYPE_LEVEL_MASK; ct->chip.irq_mask = mvebu_gpio_level_irq_mask; ct->chip.irq_unmask = mvebu_gpio_level_irq_unmask; ct->chip.irq_set_type = mvebu_gpio_irq_set_type; + ct->chip.irq_set_wake = mvebu_gpio_set_wake_irq; + ct->chip.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND; ct->chip.name = mvchip->chip.label; ct = &gc->chip_types[1]; - ct->type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING; + ct->type = IRQ_TYPE_EDGE_BOTH; ct->chip.irq_ack = mvebu_gpio_irq_ack; ct->chip.irq_mask = mvebu_gpio_edge_irq_mask; ct->chip.irq_unmask = mvebu_gpio_edge_irq_unmask; ct->chip.irq_set_type = mvebu_gpio_irq_set_type; + ct->chip.irq_set_wake = mvebu_gpio_set_wake_irq; + ct->chip.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND; ct->handler = handle_edge_irq; ct->chip.name = mvchip->chip.label; @@ -1287,13 +1320,14 @@ static int mvebu_gpio_probe(struct platform_device *pdev) * interrupt handlers, with each handler dealing with 8 GPIO * pins. */ - for (i = 0; i < 4; i++) { + for (i = 0; i < ARRAY_SIZE(mvchip->bank_irq); i++) { int irq = platform_get_irq_optional(pdev, i); if (irq < 0) continue; irq_set_chained_handler_and_data(irq, mvebu_gpio_irq_handler, mvchip); + mvchip->bank_irq[i] = irq; } return 0; diff --git a/drivers/gpio/gpio-mxs.c b/drivers/gpio/gpio-mxs.c index 5635694bf9f4..900315aba85a 100644 --- a/drivers/gpio/gpio-mxs.c +++ b/drivers/gpio/gpio-mxs.c @@ -198,7 +198,7 @@ static int mxs_gpio_init_gc(struct mxs_gpio_port *port, int irq_base) gc->private = port; ct = &gc->chip_types[0]; - ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW; + ct->type = IRQ_TYPE_LEVEL_MASK; ct->chip.irq_ack = irq_gc_ack_set_bit; ct->chip.irq_mask = irq_gc_mask_disable_reg; ct->chip.irq_unmask = irq_gc_unmask_enable_reg; @@ -210,7 +210,7 @@ static int mxs_gpio_init_gc(struct mxs_gpio_port *port, int irq_base) ct->regs.disable = PINCTRL_PIN2IRQ(port) + MXS_CLR; ct = &gc->chip_types[1]; - ct->type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING; + ct->type = IRQ_TYPE_EDGE_BOTH; ct->chip.irq_ack = irq_gc_ack_set_bit; ct->chip.irq_mask = irq_gc_mask_disable_reg; ct->chip.irq_unmask = irq_gc_unmask_enable_reg; diff --git a/drivers/gpio/gpio-nomadik.c b/drivers/gpio/gpio-nomadik.c index 5dc9f9d5912a..208fed631975 100644 --- a/drivers/gpio/gpio-nomadik.c +++ b/drivers/gpio/gpio-nomadik.c @@ -519,22 +519,23 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode, gpio_dev = bus_find_device_by_fwnode(&platform_bus_type, fwnode); if (!gpio_dev) { - dev_err(dev, "populate \"%pfwP\": device not found\n", fwnode); - return ERR_PTR(-ENODEV); + ret = -ENODEV; + dev_err_probe(dev, ret, "populate \"%pfwP\": device not found\n", fwnode); + return ERR_PTR(ret); } gpio_pdev = to_platform_device(gpio_dev); if (device_property_read_u32(gpio_dev, "gpio-bank", &id)) { - dev_err(dev, "populate: gpio-bank property not found\n"); - platform_device_put(gpio_pdev); - return ERR_PTR(-EINVAL); + ret = -EINVAL; + dev_err_probe(dev, ret, "populate: gpio-bank property not found\n"); + goto err_put_pdev; } #ifdef CONFIG_PINCTRL_NOMADIK if (id >= ARRAY_SIZE(nmk_gpio_chips)) { - dev_err(dev, "populate: invalid id: %u\n", id); - platform_device_put(gpio_pdev); - return ERR_PTR(-EINVAL); + ret = -EINVAL; + dev_err_probe(dev, ret, "populate: invalid id: %u\n", id); + goto err_put_pdev; } /* Already populated? */ nmk_chip = nmk_gpio_chips[id]; @@ -546,8 +547,8 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode, nmk_chip = devm_kzalloc(dev, sizeof(*nmk_chip), GFP_KERNEL); if (!nmk_chip) { - platform_device_put(gpio_pdev); - return ERR_PTR(-ENOMEM); + ret = -ENOMEM; + goto err_put_pdev; } if (device_property_read_u32(gpio_dev, "ngpios", &ngpio)) { @@ -568,16 +569,17 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode, res = platform_get_resource(gpio_pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) { - platform_device_put(gpio_pdev); - return ERR_CAST(base); + ret = PTR_ERR(base); + goto err_put_pdev; } nmk_chip->addr = base; /* NOTE: do not use devm_ here! */ clk = clk_get_optional(gpio_dev, NULL); if (IS_ERR(clk)) { - platform_device_put(gpio_pdev); - return ERR_CAST(clk); + ret = PTR_ERR(clk); + dev_err_probe(dev, ret, "failed getting clock\n"); + goto err_put_pdev; } clk_prepare(clk); nmk_chip->clk = clk; @@ -585,12 +587,9 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode, /* NOTE: do not use devm_ here! */ reset = reset_control_get_optional_shared(gpio_dev, NULL); if (IS_ERR(reset)) { - clk_unprepare(clk); - clk_put(clk); - platform_device_put(gpio_pdev); - dev_err(dev, "failed getting reset control: %pe\n", - reset); - return ERR_CAST(reset); + ret = PTR_ERR(reset); + dev_err_probe(dev, ret, "failed getting reset control\n"); + goto err_unprepare_clk; } /* @@ -600,18 +599,23 @@ struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode, */ ret = reset_control_deassert(reset); if (ret) { - reset_control_put(reset); - clk_unprepare(clk); - clk_put(clk); - platform_device_put(gpio_pdev); - dev_err(dev, "failed reset deassert: %d\n", ret); - return ERR_PTR(ret); + dev_err_probe(dev, ret, "failed reset deassert\n"); + goto err_put_reset; } #ifdef CONFIG_PINCTRL_NOMADIK nmk_gpio_chips[id] = nmk_chip; #endif return nmk_chip; + +err_put_reset: + reset_control_put(reset); +err_unprepare_clk: + clk_unprepare(clk); + clk_put(clk); +err_put_pdev: + platform_device_put(gpio_pdev); + return ERR_PTR(ret); } static void nmk_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p) @@ -647,10 +651,8 @@ static int nmk_gpio_probe(struct platform_device *pdev) int ret; nmk_chip = nmk_gpio_populate_chip(dev_fwnode(dev), pdev); - if (IS_ERR(nmk_chip)) { - dev_err(dev, "could not populate nmk chip struct\n"); + if (IS_ERR(nmk_chip)) return PTR_ERR(nmk_chip); - } supports_sleepmode = device_property_read_bool(dev, "st,supports-sleepmode"); @@ -693,7 +695,7 @@ static int nmk_gpio_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, nmk_gpio_irq_handler, IRQF_SHARED, dev_name(dev), nmk_chip); if (ret) { - dev_err(dev, "failed requesting IRQ\n"); + dev_err_probe(dev, ret, "failed requesting IRQ\n"); return ret; } @@ -709,8 +711,6 @@ static int nmk_gpio_probe(struct platform_device *pdev) platform_set_drvdata(pdev, nmk_chip); - dev_info(dev, "chip registered\n"); - return 0; } diff --git a/drivers/gpio/gpio-omap.c b/drivers/gpio/gpio-omap.c index e39723b5901b..005420baecf4 100644 --- a/drivers/gpio/gpio-omap.c +++ b/drivers/gpio/gpio-omap.c @@ -432,8 +432,7 @@ static int omap_gpio_irq_type(struct irq_data *d, unsigned type) if (type & ~IRQ_TYPE_SENSE_MASK) return -EINVAL; - if (!bank->regs->leveldetect0 && - (type & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH))) + if (!bank->regs->leveldetect0 && (type & IRQ_TYPE_LEVEL_MASK)) return -EINVAL; raw_spin_lock_irqsave(&bank->lock, flags); @@ -450,9 +449,9 @@ static int omap_gpio_irq_type(struct irq_data *d, unsigned type) } raw_spin_unlock_irqrestore(&bank->lock, flags); - if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) + if (type & IRQ_TYPE_LEVEL_MASK) irq_set_handler_locked(d, handle_level_irq); - else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + else if (type & IRQ_TYPE_EDGE_BOTH) /* * Edge IRQs are already cleared/acked in irq_handler and * not need to be masked, as result handle_edge_irq() @@ -702,7 +701,7 @@ static void omap_gpio_unmask_irq(struct irq_data *d) * after enabing the interrupt to clear the wakeup status. */ if (bank->regs->leveldetect0 && bank->regs->wkup_en && - trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) + trigger & IRQ_TYPE_LEVEL_MASK) omap_clear_gpio_irqstatus(bank, offset); if (trigger) diff --git a/drivers/gpio/gpio-pca9570.c b/drivers/gpio/gpio-pca9570.c index 7a47a9aa0414..db027c10494f 100644 --- a/drivers/gpio/gpio-pca9570.c +++ b/drivers/gpio/gpio-pca9570.c @@ -133,7 +133,7 @@ static int pca9570_probe(struct i2c_client *client) gpio->chip.get = pca9570_get; gpio->chip.set = pca9570_set; gpio->chip.base = -1; - gpio->chip_data = device_get_match_data(&client->dev); + gpio->chip_data = i2c_get_match_data(client); gpio->chip.ngpio = gpio->chip_data->ngpio; gpio->chip.can_sleep = true; diff --git a/drivers/gpio/gpio-pcf857x.c b/drivers/gpio/gpio-pcf857x.c index 4196916c4f94..c1f5e10a3c20 100644 --- a/drivers/gpio/gpio-pcf857x.c +++ b/drivers/gpio/gpio-pcf857x.c @@ -18,6 +18,8 @@ #include <linux/slab.h> #include <linux/spinlock.h> +#include <asm/byteorder.h> + static const struct i2c_device_id pcf857x_id[] = { { .name = "pcf8574", .driver_data = 8 }, { .name = "pcf8574a", .driver_data = 8 }, @@ -96,22 +98,23 @@ static int i2c_read_le8(struct i2c_client *client) static int i2c_write_le16(struct i2c_client *client, unsigned int word) { - u8 buf[2] = { word & 0xff, word >> 8, }; + __le16 buf = cpu_to_le16(word); int status; - status = i2c_master_send(client, buf, 2); + status = i2c_master_send(client, (char *)&buf, sizeof(buf)); return (status < 0) ? status : 0; } static int i2c_read_le16(struct i2c_client *client) { - u8 buf[2]; + __le16 buf; int status; - status = i2c_master_recv(client, buf, 2); + status = i2c_master_recv(client, (char *)&buf, sizeof(buf)); if (status < 0) return status; - return (buf[1] << 8) | buf[0]; + + return le16_to_cpu(buf); } /*-------------------------------------------------------------------------*/ diff --git a/drivers/gpio/gpio-pcie-idio-24.c b/drivers/gpio/gpio-pcie-idio-24.c index 80c0ba0afa67..b99f4863443d 100644 --- a/drivers/gpio/gpio-pcie-idio-24.c +++ b/drivers/gpio/gpio-pcie-idio-24.c @@ -225,9 +225,9 @@ exit_unlock: return ret; } -static int idio_24_reg_mask_xlate(struct gpio_regmap *const gpio, const unsigned int base, - const unsigned int offset, unsigned int *const reg, - unsigned int *const mask) +static int idio_24_reg_mask_xlate(struct gpio_regmap *const gpio, enum gpio_regmap_operation op, + const unsigned int base, const unsigned int offset, + unsigned int *const reg, unsigned int *const mask) { const unsigned int out_stride = offset / IDIO_24_NGPIO_PER_REG; const unsigned int in_stride = (offset - 24) / IDIO_24_NGPIO_PER_REG; diff --git a/drivers/gpio/gpio-pl061.c b/drivers/gpio/gpio-pl061.c index 919cf86fd590..fcd44e34f65e 100644 --- a/drivers/gpio/gpio-pl061.c +++ b/drivers/gpio/gpio-pl061.c @@ -132,8 +132,7 @@ static int pl061_irq_type(struct irq_data *d, unsigned trigger) if (offset < 0 || offset >= PL061_GPIO_NR) return -EINVAL; - if ((trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) && - (trigger & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))) + if ((trigger & IRQ_TYPE_LEVEL_MASK) && (trigger & IRQ_TYPE_EDGE_BOTH)) { dev_err(gc->parent, "trying to configure line %d for both level and edge " @@ -149,7 +148,7 @@ static int pl061_irq_type(struct irq_data *d, unsigned trigger) gpiois = readb(pl061->base + GPIOIS); gpioibe = readb(pl061->base + GPIOIBE); - if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) { + if (trigger & IRQ_TYPE_LEVEL_MASK) { bool polarity = trigger & IRQ_TYPE_LEVEL_HIGH; /* Disable edge detection */ diff --git a/drivers/gpio/gpio-pxa.c b/drivers/gpio/gpio-pxa.c index 5d61053e0596..2b5da973f51f 100644 --- a/drivers/gpio/gpio-pxa.c +++ b/drivers/gpio/gpio-pxa.c @@ -406,7 +406,7 @@ static int pxa_gpio_irq_type(struct irq_data *d, unsigned int type) if (__gpio_is_occupied(pchip, gpio)) return 0; - type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING; + type = IRQ_TYPE_EDGE_BOTH; } gpdr = readl_relaxed(c->regbase + GPDR_OFFSET); diff --git a/drivers/gpio/gpio-rcar.c b/drivers/gpio/gpio-rcar.c index 86777e097fd8..09bebde5c426 100644 --- a/drivers/gpio/gpio-rcar.c +++ b/drivers/gpio/gpio-rcar.c @@ -59,7 +59,6 @@ struct gpio_rcar_priv { #define MSKCLR 0x1c /* Interrupt Mask Clear Register */ #define POSNEG 0x20 /* Positive/Negative Logic Select Register */ #define EDGLEVEL 0x24 /* Edge/level Select Register */ -#define FILONOFF 0x28 /* Chattering Prevention On/Off Register */ #define OUTDTSEL 0x40 /* Output Data Select Register */ #define BOTHEDGE 0x4c /* One Edge/Both Edge Select Register */ #define INEN 0x50 /* General Input Enable Register */ diff --git a/drivers/gpio/gpio-rda.c b/drivers/gpio/gpio-rda.c index 7bbc6f0ce4c8..f6c05fdbe63f 100644 --- a/drivers/gpio/gpio-rda.c +++ b/drivers/gpio/gpio-rda.c @@ -169,9 +169,9 @@ static int rda_gpio_irq_set_type(struct irq_data *data, unsigned int flow_type) if (ret) return ret; - if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) + if (flow_type & IRQ_TYPE_LEVEL_MASK) irq_set_handler_locked(data, handle_level_irq); - else if (flow_type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + else if (flow_type & IRQ_TYPE_EDGE_BOTH) irq_set_handler_locked(data, handle_edge_irq); return 0; diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c index 51b4d69b8740..fb21396e1d02 100644 --- a/drivers/gpio/gpio-regmap.c +++ b/drivers/gpio/gpio-regmap.c @@ -6,6 +6,7 @@ */ #include <linux/bits.h> +#include <linux/compiler_attributes.h> #include <linux/device.h> #include <linux/err.h> #include <linux/io.h> @@ -39,9 +40,19 @@ struct gpio_regmap { struct regmap_irq_chip_data *irq_chip_data; #endif - int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base, - unsigned int offset, unsigned int *reg, - unsigned int *mask); + int (*reg_mask_xlate)(struct gpio_regmap *gpio, + enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, + unsigned int *reg, unsigned int *mask); + + int (*value_xlate)(struct gpio_regmap *gpio, + enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, + unsigned int reg, unsigned int *mask, + unsigned int *val); + + int (*set_config)(struct gpio_regmap *gpio, struct gpio_chip *chip, + unsigned int offset, unsigned long config); void *driver_data; }; @@ -55,6 +66,7 @@ static unsigned int gpio_regmap_addr(unsigned int addr) } static int gpio_regmap_simple_xlate(struct gpio_regmap *gpio, + enum gpio_regmap_operation __maybe_unused op, unsigned int base, unsigned int offset, unsigned int *reg, unsigned int *mask) { @@ -79,7 +91,7 @@ static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset) else base = gpio_regmap_addr(gpio->reg_set_base); - ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_GET_OP, base, offset, ®, &mask); if (ret) return ret; @@ -102,7 +114,7 @@ static int gpio_regmap_set(struct gpio_chip *chip, unsigned int offset, unsigned int reg, mask, mask_val; int ret; - ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_OP, base, offset, ®, &mask); if (ret) return ret; @@ -111,6 +123,13 @@ static int gpio_regmap_set(struct gpio_chip *chip, unsigned int offset, else mask_val = 0; + if (gpio->value_xlate) { + ret = gpio->value_xlate(gpio, GPIO_REGMAP_SET_OP, base, offset, + reg, &mask, &mask_val); + if (ret) + return ret; + } + /* ignore input values which shadow the old output value */ if (gpio->reg_dat_base == gpio->reg_set_base) ret = regmap_write_bits(gpio->regmap, reg, mask, mask_val); @@ -124,7 +143,7 @@ static int gpio_regmap_set_with_clear(struct gpio_chip *chip, unsigned int offset, int val) { struct gpio_regmap *gpio = gpiochip_get_data(chip); - unsigned int base, reg, mask; + unsigned int base, reg, mask, value = 0; int ret; if (val) @@ -132,10 +151,17 @@ static int gpio_regmap_set_with_clear(struct gpio_chip *chip, else base = gpio_regmap_addr(gpio->reg_clr_base); - ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_OP, base, offset, ®, &mask); if (ret) return ret; + if (gpio->value_xlate) { + ret = gpio->value_xlate(gpio, GPIO_REGMAP_SET_OP, base, offset, + reg, &mask, &value); + if (ret) + return ret; + } + return regmap_write(gpio->regmap, reg, mask); } @@ -182,7 +208,7 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip, return -ENOTSUPP; } - ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_GET_DIR_OP, base, offset, ®, &mask); if (ret) return ret; @@ -236,7 +262,7 @@ static int gpio_regmap_set_direction(struct gpio_chip *chip, return -ENOTSUPP; } - ret = gpio->reg_mask_xlate(gpio, base, offset, ®, &mask); + ret = gpio->reg_mask_xlate(gpio, GPIO_REGMAP_SET_DIR_OP, base, offset, ®, &mask); if (ret) return ret; @@ -245,6 +271,13 @@ static int gpio_regmap_set_direction(struct gpio_chip *chip, else val = output ? mask : 0; + if (gpio->value_xlate) { + ret = gpio->value_xlate(gpio, GPIO_REGMAP_SET_DIR_OP, base, offset, + reg, &mask, &val); + if (ret) + return ret; + } + return regmap_update_bits(gpio->regmap, reg, mask, val); } @@ -276,12 +309,55 @@ static int gpio_regmap_direction_output(struct gpio_chip *chip, return gpio_regmap_set_direction(chip, offset, true); } +static int gpio_regmap_set_config(struct gpio_chip *chip, + unsigned int offset, + unsigned long cfg) +{ + struct gpio_regmap *gpio = gpiochip_get_data(chip); + + return gpio->set_config(gpio, chip, offset, cfg); +} + +int gpio_regmap_reqres_irq(struct gpio_regmap *gpio, unsigned int offset) +{ + return gpiochip_reqres_irq(&gpio->gpio_chip, offset); +} +EXPORT_SYMBOL_GPL(gpio_regmap_reqres_irq); + +void gpio_regmap_relres_irq(struct gpio_regmap *gpio, unsigned int offset) +{ + gpiochip_relres_irq(&gpio->gpio_chip, offset); +} +EXPORT_SYMBOL_GPL(gpio_regmap_relres_irq); + +static int __maybe_unused gpio_regmap_irq_reqres(void *irq_drv_data, irq_hw_number_t hwirq) +{ + return gpio_regmap_reqres_irq(irq_drv_data, hwirq); +} + +static void __maybe_unused gpio_regmap_irq_relres(void *irq_drv_data, irq_hw_number_t hwirq) +{ + gpio_regmap_relres_irq(irq_drv_data, hwirq); +} + void *gpio_regmap_get_drvdata(struct gpio_regmap *gpio) { return gpio->driver_data; } EXPORT_SYMBOL_GPL(gpio_regmap_get_drvdata); +void gpio_regmap_enable_irq(struct gpio_regmap *gpio, irq_hw_number_t hwirq) +{ + gpiochip_enable_irq(&gpio->gpio_chip, hwirq); +} +EXPORT_SYMBOL_GPL(gpio_regmap_enable_irq); + +void gpio_regmap_disable_irq(struct gpio_regmap *gpio, irq_hw_number_t hwirq) +{ + gpiochip_disable_irq(&gpio->gpio_chip, hwirq); +} +EXPORT_SYMBOL_GPL(gpio_regmap_disable_irq); + /** * gpio_regmap_register() - Register a generic regmap GPIO controller * @config: configuration for gpio_regmap @@ -390,6 +466,13 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config if (!gpio->reg_mask_xlate) gpio->reg_mask_xlate = gpio_regmap_simple_xlate; + gpio->value_xlate = config->value_xlate; + + if (config->set_config) { + gpio->set_config = config->set_config; + chip->set_config = gpio_regmap_set_config; + } + ret = gpiochip_add_data(chip, gpio); if (ret < 0) goto err_free_bitmap_output; @@ -397,6 +480,9 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config #ifdef CONFIG_REGMAP_IRQ if (config->regmap_irq_chip) { gpio->regmap_irq_line = config->regmap_irq_line; + config->regmap_irq_chip->irq_reqres = gpio_regmap_irq_reqres; + config->regmap_irq_chip->irq_relres = gpio_regmap_irq_relres; + config->regmap_irq_chip->irq_drv_data = gpio; ret = regmap_add_irq_chip_fwnode(dev_fwnode(config->parent), config->regmap, config->regmap_irq_line, config->regmap_irq_flags, 0, config->regmap_irq_chip, &gpio->irq_chip_data); diff --git a/drivers/gpio/gpio-rockchip.c b/drivers/gpio/gpio-rockchip.c index 9478a58f1caa..77b239a9a601 100644 --- a/drivers/gpio/gpio-rockchip.c +++ b/drivers/gpio/gpio-rockchip.c @@ -647,21 +647,17 @@ static void rockchip_clk_put(void *data) static int rockchip_get_bank_data(struct rockchip_pin_bank *bank) { - struct resource res; + struct platform_device *pdev = to_platform_device(bank->dev); int id = 0, ret; - if (of_address_to_resource(bank->of_node, 0, &res)) { - dev_err(bank->dev, "cannot find IO resource for bank\n"); - return -ENOENT; - } - - bank->reg_base = devm_ioremap_resource(bank->dev, &res); + bank->reg_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(bank->reg_base)) return PTR_ERR(bank->reg_base); - bank->irq = irq_of_parse_and_map(bank->of_node, 0); - if (!bank->irq) - return -EINVAL; + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + bank->irq = ret; bank->clk = devm_clk_get_enabled(bank->dev, NULL); if (IS_ERR(bank->clk)) diff --git a/drivers/gpio/gpio-rtd1625.c b/drivers/gpio/gpio-rtd1625.c new file mode 100644 index 000000000000..483e44cf5abc --- /dev/null +++ b/drivers/gpio/gpio-rtd1625.c @@ -0,0 +1,677 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Realtek DHC RTD1625 gpio driver + * + * Copyright (c) 2023-2026 Realtek Semiconductor Corp. + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/gpio/driver.h> +#include <linux/gpio/regmap.h> +#include <linux/interrupt.h> +#include <linux/irqchip.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/irqdomain.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#define RTD1625_GPIO_DIR BIT(0) +#define RTD1625_GPIO_OUT BIT(2) +#define RTD1625_GPIO_IN BIT(4) +#define RTD1625_GPIO_EDGE_INT_DP BIT(6) +#define RTD1625_GPIO_EDGE_INT_EN BIT(8) +#define RTD1625_GPIO_LEVEL_INT_EN BIT(16) +#define RTD1625_GPIO_LEVEL_INT_DP BIT(18) +#define RTD1625_GPIO_DEBOUNCE GENMASK(30, 28) +#define RTD1625_GPIO_DEBOUNCE_WREN BIT(31) + +#define RTD1625_GPIO_WREN(x) ((x) << 1) + +/* Write-enable masks for all GPIO configs and reserved hardware bits */ +#define RTD1625_ISO_GPIO_WREN_ALL 0x8000aa8a +#define RTD1625_ISOM_GPIO_WREN_ALL 0x800aaa8a + +#define RTD1625_GPIO_DEBOUNCE_1US 0 +#define RTD1625_GPIO_DEBOUNCE_10US 1 +#define RTD1625_GPIO_DEBOUNCE_100US 2 +#define RTD1625_GPIO_DEBOUNCE_1MS 3 +#define RTD1625_GPIO_DEBOUNCE_10MS 4 +#define RTD1625_GPIO_DEBOUNCE_20MS 5 +#define RTD1625_GPIO_DEBOUNCE_30MS 6 +#define RTD1625_GPIO_DEBOUNCE_50MS 7 + +#define GPIO_CONTROL(gpio) ((gpio) * 4) + +static struct lock_class_key rtd1625_gpio_irq_lock_class; +static struct lock_class_key rtd1625_gpio_irq_request_class; + +enum rtd1625_irq_index { + RTD1625_IRQ_ASSERT, + RTD1625_IRQ_DEASSERT, + RTD1625_IRQ_LEVEL, + RTD1625_MAX_IRQS +}; + +/** + * struct rtd1625_gpio_info - Specific GPIO register information + * @num_gpios: The number of GPIOs + * @irq_type_support: Supported IRQ types + * @base_offset: Offset for GPIO controller register + * @gpa_offset: Offset for GPIO assert interrupt status register + * @gpda_offset: Offset for GPIO deassert interrupt status register + * @level_offset: Offset of level interrupt status register + * @write_en_all: Write-enable mask for all configurable bits + */ +struct rtd1625_gpio_info { + unsigned int num_gpios; + unsigned int irq_type_support; + unsigned int base_offset; + unsigned int gpa_offset; + unsigned int gpda_offset; + unsigned int level_offset; + unsigned int write_en_all; +}; + +struct rtd1625_gpio { + struct gpio_regmap *gpio_reg; + const struct rtd1625_gpio_info *info; + struct regmap *regmap; + unsigned int irqs[RTD1625_MAX_IRQS]; + raw_spinlock_t lock; + struct irq_domain *domain; + unsigned int *save_regs; +}; + +static unsigned int rtd1625_gpio_gpa_offset(struct rtd1625_gpio *data, unsigned int offset) +{ + return data->info->gpa_offset + ((offset / 32) * 4); +} + +static unsigned int rtd1625_gpio_gpda_offset(struct rtd1625_gpio *data, unsigned int offset) +{ + return data->info->gpda_offset + ((offset / 32) * 4); +} + +static unsigned int rtd1625_gpio_level_offset(struct rtd1625_gpio *data, unsigned int offset) +{ + return data->info->level_offset + ((offset / 32) * 4); +} + +static int rtd1625_reg_mask_xlate(struct gpio_regmap *gpio, enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, unsigned int *reg, + unsigned int *mask) +{ + /* Each GPIO has its own dedicated 32-bit register */ + struct rtd1625_gpio *data = gpio_regmap_get_drvdata(gpio); + int val = 0, ret = 0; + *reg = base + offset * 4; + + switch (op) { + case GPIO_REGMAP_SET_OP: + *mask = RTD1625_GPIO_OUT; + return 0; + + case GPIO_REGMAP_GET_OP: + ret = regmap_read(data->regmap, *reg, &val); + if (ret) + return ret; + + if (val & RTD1625_GPIO_DIR) + *mask = RTD1625_GPIO_OUT; + else + *mask = RTD1625_GPIO_IN; + return 0; + + case GPIO_REGMAP_GET_DIR_OP: + case GPIO_REGMAP_SET_DIR_OP: + *mask = RTD1625_GPIO_DIR; + return 0; + + default: + return -ENOTSUPP; + } +} + +static int rtd1625_value_xlate(struct gpio_regmap *gpio, + enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, + unsigned int reg, unsigned int *mask, + unsigned int *val) +{ + switch (op) { + case GPIO_REGMAP_SET_OP: + *val |= RTD1625_GPIO_WREN(RTD1625_GPIO_OUT); + *mask |= RTD1625_GPIO_WREN(RTD1625_GPIO_OUT); + return 0; + + case GPIO_REGMAP_SET_DIR_OP: + *val |= RTD1625_GPIO_WREN(RTD1625_GPIO_DIR); + *mask |= RTD1625_GPIO_WREN(RTD1625_GPIO_DIR); + return 0; + + default: + return -ENOTSUPP; + } +} + +static int rtd1625_gpio_set_debounce(struct rtd1625_gpio *data, unsigned int offset, + unsigned int debounce) +{ + u8 deb_val; + u32 val; + + switch (debounce) { + case 1: + deb_val = RTD1625_GPIO_DEBOUNCE_1US; + break; + case 10: + deb_val = RTD1625_GPIO_DEBOUNCE_10US; + break; + case 100: + deb_val = RTD1625_GPIO_DEBOUNCE_100US; + break; + case 1000: + deb_val = RTD1625_GPIO_DEBOUNCE_1MS; + break; + case 10000: + deb_val = RTD1625_GPIO_DEBOUNCE_10MS; + break; + case 20000: + deb_val = RTD1625_GPIO_DEBOUNCE_20MS; + break; + case 30000: + deb_val = RTD1625_GPIO_DEBOUNCE_30MS; + break; + case 50000: + deb_val = RTD1625_GPIO_DEBOUNCE_50MS; + break; + default: + return -ENOTSUPP; + } + + val = FIELD_PREP(RTD1625_GPIO_DEBOUNCE, deb_val) | RTD1625_GPIO_DEBOUNCE_WREN; + + guard(raw_spinlock_irqsave)(&data->lock); + + return regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(offset), val); +} + +static int rtd1625_gpio_set_config(struct gpio_regmap *gpio, struct gpio_chip *chip, + unsigned int offset, unsigned long config) +{ + struct rtd1625_gpio *data = gpio_regmap_get_drvdata(gpio); + u32 debounce; + + if (pinconf_to_config_param(config) == PIN_CONFIG_INPUT_DEBOUNCE) { + debounce = pinconf_to_config_argument(config); + return rtd1625_gpio_set_debounce(data, offset, debounce); + } + + return gpiochip_generic_config(chip, offset, config); +} + +static void rtd1625_gpio_irq_handle(struct irq_desc *desc) +{ + unsigned int (*get_reg_offset)(struct rtd1625_gpio *gpio, unsigned int offset); + struct rtd1625_gpio *data = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned int irq = irq_desc_get_irq(desc); + struct irq_domain *domain = data->domain; + unsigned int reg_offset, i, j, val; + irq_hw_number_t hwirq; + unsigned long status; + u32 irq_type; + int ret; + + if (irq == data->irqs[RTD1625_IRQ_ASSERT]) + get_reg_offset = &rtd1625_gpio_gpa_offset; + else if (irq == data->irqs[RTD1625_IRQ_DEASSERT]) + get_reg_offset = &rtd1625_gpio_gpda_offset; + else if (irq == data->irqs[RTD1625_IRQ_LEVEL]) + get_reg_offset = &rtd1625_gpio_level_offset; + else + return; + + chained_irq_enter(chip, desc); + + for (i = 0; i < data->info->num_gpios; i += 32) { + reg_offset = get_reg_offset(data, i); + ret = regmap_read(data->regmap, reg_offset, &val); + if (ret) { + pr_err_ratelimited("Failed to read IRQ status for GPIO %u: %d\n", i, ret); + continue; + } + + status = val; + + /* + * Hardware quirk: The controller fires both "assert" and "de-assert" + * interrupts simultaneously on any edge toggle. + * We must pre-clear edge interrupts here. If we drop an unwanted + * de-assert interrupt below, it will never reach the IRQ core + * (generic_handle_domain_irq), meaning ->irq_ack() won't be called. + * Failing to clear it here leads to an interrupt storm. + */ + if (irq != data->irqs[RTD1625_IRQ_LEVEL]) { + ret = regmap_write(data->regmap, reg_offset, status); + if (ret) + pr_err_ratelimited("Failed to clear edge IRQ for GPIO %u: %d\n", + i, ret); + } + + for_each_set_bit(j, &status, 32) { + hwirq = i + j; + irq_type = irq_get_trigger_type(irq_find_mapping(domain, hwirq)); + + /* + * Filter out the hardware-forced de-assert interrupt unless + * the user explicitly requested IRQ_TYPE_EDGE_BOTH. + */ + if (irq == data->irqs[RTD1625_IRQ_DEASSERT] && + irq_type != IRQ_TYPE_EDGE_BOTH) + continue; + + generic_handle_domain_irq(domain, hwirq); + } + } + + chained_irq_exit(chip, desc); +} + +static void rtd1625_gpio_ack_irq(struct irq_data *d) +{ + struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + u32 irq_type = irqd_get_trigger_type(d); + u32 bit_mask = BIT(hwirq % 32); + int reg_offset; + + if (irq_type & IRQ_TYPE_LEVEL_MASK) { + reg_offset = rtd1625_gpio_level_offset(data, hwirq); + regmap_write(data->regmap, reg_offset, bit_mask); + } +} + +static void rtd1625_gpio_enable_edge_irq(struct rtd1625_gpio *data, irq_hw_number_t hwirq) +{ + int gpda_reg_offset = rtd1625_gpio_gpda_offset(data, hwirq); + int gpa_reg_offset = rtd1625_gpio_gpa_offset(data, hwirq); + u32 clr_mask = BIT(hwirq % 32); + u32 val; + + guard(raw_spinlock_irqsave)(&data->lock); + + regmap_write(data->regmap, gpa_reg_offset, clr_mask); + regmap_write(data->regmap, gpda_reg_offset, clr_mask); + val = RTD1625_GPIO_EDGE_INT_EN | RTD1625_GPIO_WREN(RTD1625_GPIO_EDGE_INT_EN); + regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(hwirq), val); +} + +static void rtd1625_gpio_disable_edge_irq(struct rtd1625_gpio *data, irq_hw_number_t hwirq) +{ + u32 val; + + guard(raw_spinlock_irqsave)(&data->lock); + + val = RTD1625_GPIO_WREN(RTD1625_GPIO_EDGE_INT_EN); + regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(hwirq), val); +} + +static void rtd1625_gpio_enable_level_irq(struct rtd1625_gpio *data, irq_hw_number_t hwirq) +{ + int level_reg_offset = rtd1625_gpio_level_offset(data, hwirq); + u32 clr_mask = BIT(hwirq % 32); + u32 val; + + guard(raw_spinlock_irqsave)(&data->lock); + + regmap_write(data->regmap, level_reg_offset, clr_mask); + val = RTD1625_GPIO_LEVEL_INT_EN | RTD1625_GPIO_WREN(RTD1625_GPIO_LEVEL_INT_EN); + regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(hwirq), val); +} + +static void rtd1625_gpio_disable_level_irq(struct rtd1625_gpio *data, irq_hw_number_t hwirq) +{ + u32 val; + + guard(raw_spinlock_irqsave)(&data->lock); + + val = RTD1625_GPIO_WREN(RTD1625_GPIO_LEVEL_INT_EN); + regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(hwirq), val); +} + +static void rtd1625_gpio_enable_irq(struct irq_data *d) +{ + struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + u32 irq_type = irqd_get_trigger_type(d); + + gpio_regmap_enable_irq(data->gpio_reg, hwirq); + + if (irq_type & IRQ_TYPE_EDGE_BOTH) + rtd1625_gpio_enable_edge_irq(data, hwirq); + else if (irq_type & IRQ_TYPE_LEVEL_MASK) + rtd1625_gpio_enable_level_irq(data, hwirq); +} + +static void rtd1625_gpio_disable_irq(struct irq_data *d) +{ + struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + u32 irq_type = irqd_get_trigger_type(d); + + if (irq_type & IRQ_TYPE_EDGE_BOTH) + rtd1625_gpio_disable_edge_irq(data, hwirq); + else if (irq_type & IRQ_TYPE_LEVEL_MASK) + rtd1625_gpio_disable_level_irq(data, hwirq); + + gpio_regmap_disable_irq(data->gpio_reg, hwirq); +} + +static int rtd1625_gpio_irq_set_level_type(struct irq_data *d, bool level) +{ + u32 val = RTD1625_GPIO_WREN(RTD1625_GPIO_LEVEL_INT_DP); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + struct rtd1625_gpio *data; + int ret; + + data = irq_data_get_irq_chip_data(d); + if (!(data->info->irq_type_support & IRQ_TYPE_LEVEL_MASK)) + return -EINVAL; + + if (level) + val |= RTD1625_GPIO_LEVEL_INT_DP; + + scoped_guard(raw_spinlock_irqsave, &data->lock) { + ret = regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(hwirq), + val); + if (ret) + return ret; + } + + irq_set_handler_locked(d, handle_level_irq); + + return 0; +} + +static int rtd1625_gpio_irq_set_edge_type(struct irq_data *d, bool polarity) +{ + struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d); + u32 val = RTD1625_GPIO_WREN(RTD1625_GPIO_EDGE_INT_DP); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + int ret; + + if (!(data->info->irq_type_support & IRQ_TYPE_EDGE_BOTH)) + return -EINVAL; + + if (polarity) + val |= RTD1625_GPIO_EDGE_INT_DP; + + scoped_guard(raw_spinlock_irqsave, &data->lock) { + ret = regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(hwirq), + val); + if (ret) + return ret; + } + + irq_set_handler_locked(d, handle_edge_irq); + + return 0; +} + +static int rtd1625_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_RISING: + return rtd1625_gpio_irq_set_edge_type(d, 1); + + case IRQ_TYPE_EDGE_FALLING: + return rtd1625_gpio_irq_set_edge_type(d, 0); + + case IRQ_TYPE_EDGE_BOTH: + return rtd1625_gpio_irq_set_edge_type(d, 1); + + case IRQ_TYPE_LEVEL_HIGH: + return rtd1625_gpio_irq_set_level_type(d, 0); + + case IRQ_TYPE_LEVEL_LOW: + return rtd1625_gpio_irq_set_level_type(d, 1); + + default: + return -EINVAL; + } +} + +static int rtd1625_gpio_irq_request_resources(struct irq_data *d) +{ + struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d); + + return gpio_regmap_reqres_irq(data->gpio_reg, d->hwirq); +} + +static void rtd1625_gpio_irq_release_resources(struct irq_data *d) +{ + struct rtd1625_gpio *data = irq_data_get_irq_chip_data(d); + + gpio_regmap_relres_irq(data->gpio_reg, d->hwirq); +} + +static struct irq_chip rtd1625_iso_gpio_irq_chip = { + .name = "rtd1625-gpio", + .irq_ack = rtd1625_gpio_ack_irq, + .irq_mask = rtd1625_gpio_disable_irq, + .irq_unmask = rtd1625_gpio_enable_irq, + .irq_set_type = rtd1625_gpio_irq_set_type, + .flags = IRQCHIP_IMMUTABLE | IRQCHIP_SKIP_SET_WAKE, + .irq_request_resources = rtd1625_gpio_irq_request_resources, + .irq_release_resources = rtd1625_gpio_irq_release_resources, +}; + +static int rtd1625_gpio_setup_irq(struct platform_device *pdev, struct rtd1625_gpio *data) +{ + unsigned int num_irqs; + int irq; + + /* IRQ is optional; operate as basic GPIO if absent */ + irq = platform_get_irq_optional(pdev, 0); + if (irq == -ENXIO) + return 0; + if (irq < 0) + return irq; + + num_irqs = (data->info->irq_type_support & IRQ_TYPE_LEVEL_MASK) ? 3 : 2; + + for (unsigned int i = 0; i < num_irqs; i++) { + irq = platform_get_irq(pdev, i); + if (irq < 0) + return irq; + + data->irqs[i] = irq; + irq_set_chained_handler_and_data(data->irqs[i], rtd1625_gpio_irq_handle, data); + } + + return 0; +} + +static int rtd1625_gpio_irq_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct rtd1625_gpio *data = domain->host_data; + + irq_set_chip_data(irq, data); + + irq_set_chip_and_handler(irq, &rtd1625_iso_gpio_irq_chip, handle_bad_irq); + + irq_set_noprobe(irq); + + irq_set_lockdep_class(irq, &rtd1625_gpio_irq_lock_class, + &rtd1625_gpio_irq_request_class); + + return 0; +} + +static const struct irq_domain_ops rtd1625_gpio_irq_domain_ops = { + .map = rtd1625_gpio_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +static const struct regmap_config rtd1625_gpio_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +#ifndef CONFIG_DEBUG_GPIO + .disable_locking = true, +#endif +}; + +static int rtd1625_gpio_probe(struct platform_device *pdev) +{ + struct gpio_regmap_config config = {}; + struct irq_domain_info d_info = {}; + struct device *dev = &pdev->dev; + struct gpio_regmap *gpio_reg; + struct rtd1625_gpio *data; + void __iomem *irq_base; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->info = device_get_match_data(dev); + if (!data->info) + return -ENODATA; + + irq_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(irq_base)) + return PTR_ERR(irq_base); + + data->regmap = devm_regmap_init_mmio(dev, irq_base, &rtd1625_gpio_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + raw_spin_lock_init(&data->lock); + platform_set_drvdata(pdev, data); + + data->save_regs = devm_kcalloc(dev, data->info->num_gpios, sizeof(*data->save_regs), + GFP_KERNEL); + if (!data->save_regs) + return -ENOMEM; + + config.parent = dev; + config.regmap = data->regmap; + config.ngpio = data->info->num_gpios; + config.reg_dat_base = data->info->base_offset; + config.reg_set_base = data->info->base_offset; + config.reg_dir_out_base = data->info->base_offset; + + config.reg_mask_xlate = rtd1625_reg_mask_xlate; + config.set_config = rtd1625_gpio_set_config; + config.value_xlate = rtd1625_value_xlate; + + d_info.fwnode = dev_fwnode(&pdev->dev); + d_info.size = data->info->num_gpios; + d_info.hwirq_max = data->info->num_gpios; + d_info.ops = &rtd1625_gpio_irq_domain_ops; + d_info.host_data = data; + + data->domain = devm_irq_domain_instantiate(dev, &d_info); + if (IS_ERR(data->domain)) + return PTR_ERR(data->domain); + + ret = rtd1625_gpio_setup_irq(pdev, data); + if (ret) + return ret; + + config.irq_domain = data->domain; + config.drvdata = data; + + gpio_reg = devm_gpio_regmap_register(dev, &config); + if (IS_ERR(gpio_reg)) + return PTR_ERR(gpio_reg); + + data->gpio_reg = gpio_reg; + + return 0; +} + +static const struct rtd1625_gpio_info rtd1625_iso_gpio_info = { + .num_gpios = 166, + .irq_type_support = IRQ_TYPE_EDGE_BOTH, + .base_offset = 0x100, + .gpa_offset = 0x000, + .gpda_offset = 0x020, + .write_en_all = RTD1625_ISO_GPIO_WREN_ALL, +}; + +static const struct rtd1625_gpio_info rtd1625_isom_gpio_info = { + .num_gpios = 4, + .irq_type_support = IRQ_TYPE_EDGE_BOTH | IRQ_TYPE_LEVEL_LOW | + IRQ_TYPE_LEVEL_HIGH, + .base_offset = 0x20, + .gpa_offset = 0x00, + .gpda_offset = 0x04, + .level_offset = 0x18, + .write_en_all = RTD1625_ISOM_GPIO_WREN_ALL, +}; + +static int rtd1625_gpio_suspend(struct device *dev) +{ + struct rtd1625_gpio *data = dev_get_drvdata(dev); + const struct rtd1625_gpio_info *info = data->info; + int ret; + + for (unsigned int i = 0; i < info->num_gpios; i++) { + ret = regmap_read(data->regmap, data->info->base_offset + GPIO_CONTROL(i), + &data->save_regs[i]); + if (ret) + return ret; + } + + return 0; +} + +static int rtd1625_gpio_resume(struct device *dev) +{ + struct rtd1625_gpio *data = dev_get_drvdata(dev); + const struct rtd1625_gpio_info *info = data->info; + int ret; + + for (unsigned int i = 0; i < info->num_gpios; i++) { + ret = regmap_write(data->regmap, data->info->base_offset + GPIO_CONTROL(i), + data->save_regs[i] | info->write_en_all); + if (ret) + return ret; + } + + return 0; +} + +static DEFINE_NOIRQ_DEV_PM_OPS(rtd1625_gpio_pm_ops, rtd1625_gpio_suspend, rtd1625_gpio_resume); + +static const struct of_device_id rtd1625_gpio_of_matches[] = { + { .compatible = "realtek,rtd1625-iso-gpio", .data = &rtd1625_iso_gpio_info }, + { .compatible = "realtek,rtd1625-isom-gpio", .data = &rtd1625_isom_gpio_info }, + { } +}; +MODULE_DEVICE_TABLE(of, rtd1625_gpio_of_matches); + +static struct platform_driver rtd1625_gpio_platform_driver = { + .driver = { + .name = "gpio-rtd1625", + .of_match_table = rtd1625_gpio_of_matches, + .pm = pm_sleep_ptr(&rtd1625_gpio_pm_ops), + }, + .probe = rtd1625_gpio_probe, +}; +module_platform_driver(rtd1625_gpio_platform_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Realtek Semiconductor Corporation"); +MODULE_DESCRIPTION("Realtek DHC SoC RTD1625 gpio driver"); diff --git a/drivers/gpio/gpio-sa1100.c b/drivers/gpio/gpio-sa1100.c index ffa73dd3b982..e4f3bf949b87 100644 --- a/drivers/gpio/gpio-sa1100.c +++ b/drivers/gpio/gpio-sa1100.c @@ -139,7 +139,7 @@ static int sa1100_gpio_type(struct irq_data *d, unsigned int type) if (type == IRQ_TYPE_PROBE) { if ((sgc->irqrising | sgc->irqfalling) & mask) return 0; - type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING; + type = IRQ_TYPE_EDGE_BOTH; } if (type & IRQ_TYPE_EDGE_RISING) diff --git a/drivers/gpio/gpio-sifive.c b/drivers/gpio/gpio-sifive.c index 94ef2efbd14f..e9a992cd4b9c 100644 --- a/drivers/gpio/gpio-sifive.c +++ b/drivers/gpio/gpio-sifive.c @@ -260,6 +260,7 @@ static const struct of_device_id sifive_gpio_match[] = { { .compatible = "sifive,fu540-c000-gpio" }, { }, }; +MODULE_DEVICE_TABLE(of, sifive_gpio_match); static struct platform_driver sifive_gpio_driver = { .probe = sifive_gpio_probe, diff --git a/drivers/gpio/gpio-sloppy-logic-analyzer.c b/drivers/gpio/gpio-sloppy-logic-analyzer.c index 2f9a1f0384ad..044d81e7cafb 100644 --- a/drivers/gpio/gpio-sloppy-logic-analyzer.c +++ b/drivers/gpio/gpio-sloppy-logic-analyzer.c @@ -244,6 +244,7 @@ static int gpio_la_poll_probe(struct platform_device *pdev) if (ret) return ret; + /* Initially allocate a buffer. It currently is NULL */ fops_buf_size_set(priv, GPIO_LA_DEFAULT_BUF_SIZE); ret = devm_add_action_or_reset(dev, fops_buf_release, priv); if (ret) diff --git a/drivers/gpio/gpio-sodaville.c b/drivers/gpio/gpio-sodaville.c index 37c133837729..e2a784df275c 100644 --- a/drivers/gpio/gpio-sodaville.c +++ b/drivers/gpio/gpio-sodaville.c @@ -158,7 +158,7 @@ static int sdv_register_irqsupport(struct sdv_gpio_chip_data *sd, sd->gc->private = sd; ct = sd->gc->chip_types; - ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW; + ct->type = IRQ_TYPE_LEVEL_MASK; ct->regs.eoi = GPSTR; ct->regs.mask = GPIO_INT; ct->chip.irq_mask = irq_gc_mask_clr_bit; diff --git a/drivers/gpio/gpio-stmpe.c b/drivers/gpio/gpio-stmpe.c index 6faf30347a36..ccdf13a741b7 100644 --- a/drivers/gpio/gpio-stmpe.c +++ b/drivers/gpio/gpio-stmpe.c @@ -149,7 +149,7 @@ static int stmpe_gpio_irq_set_type(struct irq_data *d, unsigned int type) int regoffset = offset / 8; int mask = BIT(offset % 8); - if (type & IRQ_TYPE_LEVEL_LOW || type & IRQ_TYPE_LEVEL_HIGH) + if (type & IRQ_TYPE_LEVEL_MASK) return -EINVAL; /* STMPE801 and STMPE 1600 don't have RE and FE registers */ diff --git a/drivers/gpio/gpio-tb10x.c b/drivers/gpio/gpio-tb10x.c index 705bfd80a8d0..7fb8e6223bd1 100644 --- a/drivers/gpio/gpio-tb10x.c +++ b/drivers/gpio/gpio-tb10x.c @@ -51,7 +51,7 @@ static inline u32 tb10x_reg_read(struct tb10x_gpio *gpio, unsigned int offs) return ioread32(gpio->base + offs); } -static int tb10x_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +static int tb10x_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) { struct tb10x_gpio *tb10x_gpio = gpiochip_get_data(chip); @@ -167,9 +167,8 @@ static int tb10x_gpio_probe(struct platform_device *pdev) tb10x_gpio->domain = irq_domain_create_linear(dev_fwnode(dev), tb10x_gpio->chip.gc.ngpio, &irq_generic_chip_ops, NULL); - if (!tb10x_gpio->domain) { + if (!tb10x_gpio->domain) return -ENOMEM; - } ret = irq_alloc_domain_generic_chips(tb10x_gpio->domain, tb10x_gpio->chip.gc.ngpio, 1, tb10x_gpio->chip.gc.label, diff --git a/drivers/gpio/gpio-tegra.c b/drivers/gpio/gpio-tegra.c index fa6c8ee92093..ccda3a56d599 100644 --- a/drivers/gpio/gpio-tegra.c +++ b/drivers/gpio/gpio-tegra.c @@ -342,9 +342,9 @@ static int tegra_gpio_irq_set_type(struct irq_data *d, unsigned int type) return ret; } - if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) + if (type & IRQ_TYPE_LEVEL_MASK) irq_set_handler_locked(d, handle_level_irq); - else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + else if (type & IRQ_TYPE_EDGE_BOTH) irq_set_handler_locked(d, handle_edge_irq); if (d->parent_data) diff --git a/drivers/gpio/gpio-timberdale.c b/drivers/gpio/gpio-timberdale.c index ec378a4220a7..727fa1b5ad3b 100644 --- a/drivers/gpio/gpio-timberdale.c +++ b/drivers/gpio/gpio-timberdale.c @@ -148,7 +148,7 @@ static int timbgpio_irq_type(struct irq_data *d, unsigned trigger) if (ver > 2) bflr = ioread32(tgpio->membase + TGPIO_BFLR); - if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) { + if (trigger & IRQ_TYPE_LEVEL_MASK) { bflr &= ~(1 << offset); flr &= ~(1 << offset); if (trigger & IRQ_TYPE_LEVEL_HIGH) diff --git a/drivers/gpio/gpio-vf610.c b/drivers/gpio/gpio-vf610.c index aa8586d8a787..adfa2c2d5873 100644 --- a/drivers/gpio/gpio-vf610.c +++ b/drivers/gpio/gpio-vf610.c @@ -83,6 +83,7 @@ static const struct of_device_id vf610_gpio_dt_ids[] = { { .compatible = "fsl,imx8ulp-gpio", .data = &imx8ulp_data, }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, vf610_gpio_dt_ids); static inline void vf610_gpio_writel(u32 val, void __iomem *reg) { diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 82f27db0b230..9f3b628d5793 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2682,15 +2682,8 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) file->private_data = cdev; cdev->fp = file; - ret = nonseekable_open(inode, file); - if (ret) - goto out_unregister_device_notifier; - - return ret; + return nonseekable_open(inode, file); -out_unregister_device_notifier: - blocking_notifier_chain_unregister(&gdev->device_notifier, - &cdev->device_unregistered_nb); out_unregister_line_notifier: scoped_guard(write_lock_irqsave, &gdev->line_state_lock) raw_notifier_chain_unregister(&gdev->line_state_notifier, diff --git a/drivers/gpio/gpiolib-kunit.c b/drivers/gpio/gpiolib-kunit.c index 380b68f879e5..e6cb43a8df5b 100644 --- a/drivers/gpio/gpiolib-kunit.c +++ b/drivers/gpio/gpiolib-kunit.c @@ -3,6 +3,8 @@ * Copyright (C) Qualcomm Technologies, Inc. and/or its subsidiaries */ +#include <linux/cleanup.h> +#include <linux/err.h> #include <linux/fwnode.h> #include <linux/gpio/consumer.h> #include <linux/gpio/driver.h> @@ -11,26 +13,78 @@ #include <linux/notifier.h> #include <linux/platform_device.h> #include <linux/property.h> +#include <linux/types.h> +#include <kunit/fwnode.h> #include <kunit/platform_device.h> #include <kunit/test.h> #define GPIO_TEST_PROVIDER "gpio-test-provider" #define GPIO_SWNODE_TEST_CONSUMER "gpio-swnode-test-consumer" +#define GPIO_PROBE_ORDER_TEST_CONSUMER "gpio-probe-order-test-consumer" +#define GPIO_PROBE_DEFER_TEST_CONSUMER "gpio-probe-defer-test-consumer" #define GPIO_UNBIND_TEST_CONSUMER "gpio-unbind-test-consumer" +#define GPIO_CONSUMER_NAME "gpio-swnode-consumer-test-device" + +#define GPIO_TEST_PROVIDER_NGPIO 4 + +/* + * The test provider tracks per-line direction and value so that lines can be + * driven as both inputs and outputs - this is needed to exercise input as well + * as output GPIO hogs. + */ +struct gpio_test_provider_data { + DECLARE_BITMAP(is_output, GPIO_TEST_PROVIDER_NGPIO); + DECLARE_BITMAP(values, GPIO_TEST_PROVIDER_NGPIO); +}; static int gpio_test_provider_get_direction(struct gpio_chip *gc, unsigned int offset) { - return GPIO_LINE_DIRECTION_OUT; + struct gpio_test_provider_data *data = gpiochip_get_data(gc); + + return test_bit(offset, data->is_output) ? + GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; +} + +static int gpio_test_provider_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + struct gpio_test_provider_data *data = gpiochip_get_data(gc); + + clear_bit(offset, data->is_output); + + return 0; +} + +static int gpio_test_provider_direction_output(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct gpio_test_provider_data *data = gpiochip_get_data(gc); + + set_bit(offset, data->is_output); + __assign_bit(offset, data->values, value); + + return 0; +} + +static int gpio_test_provider_get(struct gpio_chip *gc, unsigned int offset) +{ + struct gpio_test_provider_data *data = gpiochip_get_data(gc); + + return test_bit(offset, data->values); } static int gpio_test_provider_set(struct gpio_chip *gc, unsigned int offset, int value) { + struct gpio_test_provider_data *data = gpiochip_get_data(gc); + + __assign_bit(offset, data->values, value); + return 0; } static int gpio_test_provider_probe(struct platform_device *pdev) { + struct gpio_test_provider_data *data; struct device *dev = &pdev->dev; struct gpio_chip *gc; @@ -38,16 +92,26 @@ static int gpio_test_provider_probe(struct platform_device *pdev) if (!gc) return -ENOMEM; + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* Lines start as outputs to preserve the default for lookup tests. */ + bitmap_fill(data->is_output, GPIO_TEST_PROVIDER_NGPIO); + gc->base = -1; - gc->ngpio = 4; - gc->label = "gpio-swnode-consumer-test-device"; + gc->ngpio = GPIO_TEST_PROVIDER_NGPIO; + gc->label = GPIO_CONSUMER_NAME; gc->parent = dev; gc->owner = THIS_MODULE; gc->get_direction = gpio_test_provider_get_direction; + gc->direction_input = gpio_test_provider_direction_input; + gc->direction_output = gpio_test_provider_direction_output; + gc->get = gpio_test_provider_get; gc->set = gpio_test_provider_set; - return devm_gpiochip_add_data(dev, gc, NULL); + return devm_gpiochip_add_data(dev, gc, data); } static struct platform_driver gpio_test_provider_driver = { @@ -63,10 +127,12 @@ static const struct software_node gpio_test_provider_swnode = { struct gpio_swnode_consumer_pdata { bool gpio_ok; + int errno; }; static const struct gpio_swnode_consumer_pdata gpio_swnode_pdata_template = { .gpio_ok = false, + .errno = 0, }; static int gpio_swnode_consumer_probe(struct platform_device *pdev) @@ -76,8 +142,10 @@ static int gpio_swnode_consumer_probe(struct platform_device *pdev) struct gpio_desc *desc; desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH); - if (IS_ERR(desc)) + if (IS_ERR(desc)) { + pdata->errno = PTR_ERR(desc); return PTR_ERR(desc); + } pdata->gpio_ok = true; @@ -91,13 +159,8 @@ static struct platform_driver gpio_swnode_consumer_driver = { }, }; -static void gpio_swnode_lookup_by_primary(struct kunit *test) +static int gpio_swnode_register_drivers(struct kunit *test) { - struct gpio_swnode_consumer_pdata *pdata; - struct platform_device_info pdevinfo; - struct property_entry properties[2]; - struct platform_device *pdev; - bool bound = false; int ret; ret = kunit_platform_driver_register(test, &gpio_test_provider_driver); @@ -106,6 +169,17 @@ static void gpio_swnode_lookup_by_primary(struct kunit *test) ret = kunit_platform_driver_register(test, &gpio_swnode_consumer_driver); KUNIT_ASSERT_EQ(test, ret, 0); + return 0; +} + +static void gpio_swnode_lookup_by_primary(struct kunit *test) +{ + struct gpio_swnode_consumer_pdata *pdata; + struct platform_device_info pdevinfo; + struct property_entry properties[2]; + struct platform_device *pdev; + bool bound = false; + pdevinfo = (struct platform_device_info){ .name = GPIO_TEST_PROVIDER, .id = PLATFORM_DEVID_NONE, @@ -149,7 +223,6 @@ static void gpio_swnode_lookup_by_secondary(struct kunit *test) struct fwnode_handle *primary; struct platform_device *pdev; bool bound = false; - int ret; /* * Can't live on the stack as it will still get referenced in cleanup @@ -158,12 +231,6 @@ static void gpio_swnode_lookup_by_secondary(struct kunit *test) primary = kunit_kzalloc(test, sizeof(*primary), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, primary); - ret = kunit_platform_driver_register(test, &gpio_test_provider_driver); - KUNIT_ASSERT_EQ(test, ret, 0); - - ret = kunit_platform_driver_register(test, &gpio_swnode_consumer_driver); - KUNIT_ASSERT_EQ(test, ret, 0); - fwnode_init(primary, NULL); pdevinfo = (struct platform_device_info){ @@ -211,6 +278,266 @@ static struct kunit_case gpio_swnode_lookup_tests[] = { static struct kunit_suite gpio_swnode_lookup_test_suite = { .name = "gpio-swnode-lookup", .test_cases = gpio_swnode_lookup_tests, + .init = gpio_swnode_register_drivers, +}; + +static void gpio_swnode_unregister_swnode(void *data) +{ + software_node_unregister(data); +} + +struct gpio_probe_order_pdata { + unsigned int probe_count; + bool gpio_ok; +}; + +static const struct gpio_probe_order_pdata gpio_probe_order_pdata_template = { + .probe_count = 0, + .gpio_ok = false, +}; + +static int gpio_probe_order_consumer_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_probe_order_pdata *pdata = dev_get_platdata(dev); + struct gpio_desc *desc; + + pdata->probe_count++; + + desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + pdata->gpio_ok = true; + + return 0; +} + +static struct platform_driver gpio_probe_order_consumer_driver = { + .probe = gpio_probe_order_consumer_probe, + .driver = { + .name = GPIO_PROBE_ORDER_TEST_CONSUMER, + }, +}; + +/* + * Verify that fw_devlink orders the probe of a GPIO consumer after its + * provider. The consumer references the provider through a software node and + * is registered first. fw_devlink must defer it before its driver's probe() + * is ever entered, so the consumer probes exactly once - only after the + * provider is added and bound. + */ +static void gpio_swnode_probe_order(struct kunit *test) +{ + struct property_entry properties[2] = { }; + struct gpio_probe_order_pdata *pdata; + struct platform_device_info pdevinfo; + struct platform_device *prvd, *cons; + bool bound = false; + int ret; + + ret = kunit_platform_driver_register(test, &gpio_test_provider_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_platform_driver_register(test, &gpio_probe_order_consumer_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = software_node_register(&gpio_test_provider_swnode); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_add_action_or_reset(test, gpio_swnode_unregister_swnode, + (void *)&gpio_test_provider_swnode); + KUNIT_ASSERT_EQ(test, ret, 0); + + properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios", + &gpio_test_provider_swnode, + 0, GPIO_ACTIVE_HIGH); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_PROBE_ORDER_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + .data = &gpio_probe_order_pdata_template, + .size_data = sizeof(gpio_probe_order_pdata_template), + .properties = properties, + }; + + cons = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + + wait_for_device_probe(); + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + pdata = dev_get_platdata(&cons->dev); + KUNIT_ASSERT_EQ(test, pdata->probe_count, 0); + KUNIT_ASSERT_FALSE(test, pdata->gpio_ok); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_TEST_PROVIDER, + .id = PLATFORM_DEVID_NONE, + .swnode = &gpio_test_provider_swnode, + }; + + prvd = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd); + + wait_for_device_probe(); + + scoped_guard(device, &prvd->dev) + bound = device_is_bound(&prvd->dev); + KUNIT_ASSERT_TRUE(test, bound); + + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + KUNIT_ASSERT_TRUE(test, bound); + + pdata = dev_get_platdata(&cons->dev); + KUNIT_ASSERT_EQ(test, pdata->probe_count, 1); + KUNIT_ASSERT_TRUE(test, pdata->gpio_ok); +} + +struct gpio_probe_defer_pdata { + unsigned int probe_count; + int gpio_err; +}; + +static const struct gpio_probe_defer_pdata gpio_probe_defer_pdata_template = { + .probe_count = 0, + .gpio_err = 0, +}; + +static int gpio_probe_defer_consumer_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_probe_defer_pdata *pdata = dev_get_platdata(dev); + struct gpio_desc *desc; + + pdata->probe_count++; + + desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH); + if (IS_ERR(desc)) { + pdata->gpio_err = PTR_ERR(desc); + return pdata->gpio_err; + } + + pdata->gpio_err = 0; + + return 0; +} + +static struct platform_driver gpio_probe_defer_consumer_driver = { + .probe = gpio_probe_defer_consumer_probe, + .driver = { + .name = GPIO_PROBE_DEFER_TEST_CONSUMER, + }, +}; + +/* + * Verify that a GPIO consumer referencing a provider whose software node is + * not registered yet, defers its probe instead of failing. + * + * The provider software node is deliberately left unregistered when the + * consumer is added. fw_devlink cannot resolve the reference, so it creates no + * supplier link and does not order the consumer - the consumer's probe() runs + * and reaches devm_gpiod_get(). The swnode GPIO lookup returns -ENOTCONN for a + * reference to an unregistered node, which gpiolib maps to -EPROBE_DEFER. Once + * the provider software node and device appear, the deferred consumer probes + * again and binds. + */ +static void gpio_swnode_probe_defer_on_unregistered(struct kunit *test) +{ + struct property_entry properties[2] = { }; + struct gpio_probe_defer_pdata *pdata; + struct platform_device_info pdevinfo; + struct platform_device *prvd, *cons; + struct fwnode_handle *fwnode; + bool bound = false; + int ret; + + ret = kunit_platform_driver_register(test, &gpio_test_provider_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_platform_driver_register(test, &gpio_probe_defer_consumer_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios", + &gpio_test_provider_swnode, + 0, GPIO_ACTIVE_HIGH); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_PROBE_DEFER_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + .data = &gpio_probe_defer_pdata_template, + .size_data = sizeof(gpio_probe_defer_pdata_template), + .properties = properties, + }; + + cons = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + + wait_for_device_probe(); + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + pdata = dev_get_platdata(&cons->dev); + KUNIT_ASSERT_GT(test, pdata->probe_count, 0); + KUNIT_ASSERT_EQ(test, pdata->gpio_err, -EPROBE_DEFER); + + fwnode = kunit_software_node_register(test, &gpio_test_provider_swnode); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_TEST_PROVIDER, + .id = PLATFORM_DEVID_NONE, + .swnode = &gpio_test_provider_swnode, + }; + + prvd = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd); + + wait_for_device_probe(); + + scoped_guard(device, &prvd->dev) + bound = device_is_bound(&prvd->dev); + KUNIT_ASSERT_TRUE(test, bound); + + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + KUNIT_ASSERT_TRUE(test, bound); + + pdata = dev_get_platdata(&cons->dev); + KUNIT_ASSERT_EQ(test, pdata->gpio_err, 0); + + /* Tear down the consumer before the provider to free the GPIO. */ + kunit_platform_device_unregister(test, cons); +} + +static int gpio_swnode_probe_order_test_init(struct kunit *test) +{ + /* + * A prior test may have left a managed device link teardown queued on + * the device_link_mq. Flush it so that software_node_register() + * doesn't spuriously see the node as registered and fail with -EEXIST. + */ + device_link_wait_removal(); + + return 0; +} + +static struct kunit_case gpio_swnode_probe_order_tests[] = { + KUNIT_CASE(gpio_swnode_probe_order), + KUNIT_CASE(gpio_swnode_probe_defer_on_unregistered), + { } +}; + +static struct kunit_suite gpio_swnode_probe_order_test_suite = { + .name = "gpio-swnode-probe-order", + .test_cases = gpio_swnode_probe_order_tests, + .init = gpio_swnode_probe_order_test_init, }; static BLOCKING_NOTIFIER_HEAD(gpio_unbind_notifier); @@ -310,15 +637,24 @@ static void gpio_unbind_with_consumers(struct kunit *test) 0, GPIO_ACTIVE_HIGH); properties[1] = (struct property_entry){ }; - pdevinfo = (struct platform_device_info){ - .name = GPIO_UNBIND_TEST_CONSUMER, - .id = PLATFORM_DEVID_NONE, - .properties = properties, - }; - - cons = kunit_platform_device_register_full(test, &pdevinfo); + /* + * This test deliberately keeps the consumer bound while the provider + * is unregistered. fw_devlink would force-unbind the consumer before + * the provider so use the FWNODE_FLAG_LINKS_ADDED flag to opt out of + * it as a workaround. + */ + cons = kunit_platform_device_alloc(test, GPIO_UNBIND_TEST_CONSUMER, + PLATFORM_DEVID_NONE); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + ret = device_create_managed_software_node(&cons->dev, properties, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + fwnode_set_flag(dev_fwnode(&cons->dev), FWNODE_FLAG_LINKS_ADDED); + + ret = kunit_platform_device_add(test, cons); + KUNIT_ASSERT_EQ(test, ret, 0); + wait_for_device_probe(); scoped_guard(device, &cons->dev) bound = device_is_bound(&cons->dev); @@ -346,11 +682,196 @@ static struct kunit_case gpio_unbind_with_consumers_tests[] = { static struct kunit_suite gpio_unbind_with_consumers_test_suite = { .name = "gpio-unbind-with-consumers", .test_cases = gpio_unbind_with_consumers_tests, + /* We need this here too to clean any left over links. */ + .init = gpio_swnode_probe_order_test_init, +}; + +/* + * GPIO line hogs are described by child software nodes of the provider + * carrying the "gpio-hog" property. They are picked up automatically when the + * gpiochip is registered. Each hog below sits on a distinct line of the + * provider. + */ +#define GPIO_HOG_OUTPUT_HIGH_OFFSET 0 +#define GPIO_HOG_OUTPUT_LOW_OFFSET 1 +#define GPIO_HOG_INPUT_OFFSET 2 + +static const u32 gpio_hog_output_high_gpios[] = { + GPIO_HOG_OUTPUT_HIGH_OFFSET, GPIO_ACTIVE_HIGH, +}; + +static const struct property_entry gpio_hog_output_high_properties[] = { + PROPERTY_ENTRY_U32_ARRAY("gpios", gpio_hog_output_high_gpios), + PROPERTY_ENTRY_STRING("line-name", "hog-output-high"), + PROPERTY_ENTRY_BOOL("output-high"), + PROPERTY_ENTRY_BOOL("gpio-hog"), + { } +}; + +static const struct software_node gpio_hog_output_high_swnode = + SOFTWARE_NODE("hog-output-high", gpio_hog_output_high_properties, + &gpio_test_provider_swnode); + +static const u32 gpio_hog_output_low_gpios[] = { + GPIO_HOG_OUTPUT_LOW_OFFSET, GPIO_ACTIVE_HIGH, +}; + +static const struct property_entry gpio_hog_output_low_properties[] = { + PROPERTY_ENTRY_U32_ARRAY("gpios", gpio_hog_output_low_gpios), + PROPERTY_ENTRY_STRING("line-name", "hog-output-low"), + PROPERTY_ENTRY_BOOL("output-low"), + PROPERTY_ENTRY_BOOL("gpio-hog"), + { } +}; + +static const struct software_node gpio_hog_output_low_swnode = + SOFTWARE_NODE("hog-output-low", gpio_hog_output_low_properties, + &gpio_test_provider_swnode); + +static const u32 gpio_hog_input_gpios[] = { + GPIO_HOG_INPUT_OFFSET, GPIO_ACTIVE_HIGH, +}; + +static const struct property_entry gpio_hog_input_properties[] = { + PROPERTY_ENTRY_U32_ARRAY("gpios", gpio_hog_input_gpios), + PROPERTY_ENTRY_STRING("line-name", "hog-input"), + PROPERTY_ENTRY_BOOL("input"), + PROPERTY_ENTRY_BOOL("gpio-hog"), + { } +}; + +static const struct software_node gpio_hog_input_swnode = + SOFTWARE_NODE("hog-input", gpio_hog_input_properties, + &gpio_test_provider_swnode); + +static const struct software_node *const gpio_hog_swnodes[] = { + &gpio_test_provider_swnode, + &gpio_hog_output_high_swnode, + &gpio_hog_output_low_swnode, + &gpio_hog_input_swnode, + NULL +}; + +/* + * Bring up the provider with a single hog child registered and verify both + * that the line was configured with the expected direction and that it is now + * exclusively owned (a consumer asking for the same line fails to bind). + * + * The provider node is referenced by the device through its fwnode rather than + * being handed to .swnode, so the device takes no software node reference of + * its own. Both the provider and the hog child are therefore test-managed and + * torn down (child first) once the test case completes. + */ +static void gpio_hog_assert(struct kunit *test, unsigned int offset, + int expected_direction) +{ + struct gpio_swnode_consumer_pdata *pdata; + struct platform_device_info pdevinfo; + struct property_entry properties[2]; + struct platform_device *pdev; + struct fwnode_handle *fwnode; + struct gpio_desc *desc; + bool bound = true; + int ret; + + fwnode = software_node_fwnode(&gpio_test_provider_swnode); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_TEST_PROVIDER, + .id = PLATFORM_DEVID_NONE, + .fwnode = fwnode, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + wait_for_device_probe(); + + /* The hog must have configured the line with the expected direction. */ + struct gpio_device *gdev __free(gpio_device_put) = + gpio_device_find_by_label(GPIO_CONSUMER_NAME); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, gdev); + + desc = gpio_device_get_desc(gdev, offset); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); + + ret = gpiod_get_direction(desc); + KUNIT_ASSERT_EQ(test, ret, expected_direction); + + /* A hogged line is owned exclusively, so a consumer must fail to bind. */ + properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios", + &gpio_test_provider_swnode, + offset, GPIO_ACTIVE_HIGH); + properties[1] = (struct property_entry){ }; + + pdevinfo = (struct platform_device_info){ + .name = GPIO_SWNODE_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + .data = &gpio_swnode_pdata_template, + .size_data = sizeof(gpio_swnode_pdata_template), + .properties = properties, + }; + + pdev = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + wait_for_device_probe(); + scoped_guard(device, &pdev->dev) + bound = device_is_bound(&pdev->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + pdata = dev_get_platdata(&pdev->dev); + KUNIT_ASSERT_FALSE(test, pdata->gpio_ok); + KUNIT_ASSERT_EQ(test, pdata->errno, -EBUSY); +} + +static void gpio_hog_output_high(struct kunit *test) +{ + gpio_hog_assert(test, GPIO_HOG_OUTPUT_HIGH_OFFSET, GPIO_LINE_DIRECTION_OUT); +} + +static void gpio_hog_output_low(struct kunit *test) +{ + gpio_hog_assert(test, GPIO_HOG_OUTPUT_LOW_OFFSET, GPIO_LINE_DIRECTION_OUT); +} + +static void gpio_hog_input(struct kunit *test) +{ + gpio_hog_assert(test, GPIO_HOG_INPUT_OFFSET, GPIO_LINE_DIRECTION_IN); +} + +static int gpio_hog_suite_init(struct kunit_suite *suite) +{ + return software_node_register_node_group(gpio_hog_swnodes); +} + +static void gpio_hog_suite_exit(struct kunit_suite *suite) +{ + software_node_unregister_node_group(gpio_hog_swnodes); +} + +static struct kunit_case gpio_swnode_hog_tests[] = { + KUNIT_CASE(gpio_hog_output_high), + KUNIT_CASE(gpio_hog_output_low), + KUNIT_CASE(gpio_hog_input), + { } +}; + +static struct kunit_suite gpio_swnode_hog_test_suite = { + .name = "gpio-swnode-hog", + .test_cases = gpio_swnode_hog_tests, + .suite_init = gpio_hog_suite_init, + .suite_exit = gpio_hog_suite_exit, + .init = gpio_swnode_register_drivers, }; kunit_test_suites( &gpio_swnode_lookup_test_suite, + &gpio_swnode_probe_order_test_suite, &gpio_unbind_with_consumers_test_suite, + &gpio_swnode_hog_test_suite, ); MODULE_DESCRIPTION("Test module for the GPIO subsystem"); diff --git a/drivers/gpio/gpiolib-swnode.c b/drivers/gpio/gpiolib-swnode.c index 8d9591aa9304..304994c5c7d0 100644 --- a/drivers/gpio/gpiolib-swnode.c +++ b/drivers/gpio/gpiolib-swnode.c @@ -26,7 +26,6 @@ static struct gpio_device *swnode_get_gpio_device(struct fwnode_handle *fwnode) { const struct software_node *gdev_node; - struct gpio_device *gdev; gdev_node = to_software_node(fwnode); if (!gdev_node) @@ -41,27 +40,7 @@ static struct gpio_device *swnode_get_gpio_device(struct fwnode_handle *fwnode) return ERR_PTR(-ENOENT); fwnode_lookup: - gdev = gpio_device_find_by_fwnode(fwnode); - if (!gdev && gdev_node && gdev_node->name) - /* - * FIXME: We shouldn't need to compare the GPIO controller's - * label against the software node that is supposedly attached - * to it. However there are currently GPIO users that - knowing - * the expected label of the GPIO chip whose pins they want to - * control - set up dummy software nodes named after those GPIO - * controllers, which aren't actually attached to them. In this - * case gpio_device_find_by_fwnode() will fail as no device on - * the GPIO bus is actually associated with the fwnode we're - * looking for. - * - * As a fallback: continue checking the label if we have no - * match. However, the situation described above is an abuse - * of the software node API and should be phased out and the - * following line - eventually removed. - */ - gdev = gpio_device_find_by_label(gdev_node->name); - - return gdev ?: ERR_PTR(-EPROBE_DEFER); + return gpio_device_find_by_fwnode(fwnode) ?: ERR_PTR(-EPROBE_DEFER); } static int swnode_gpio_get_reference(const struct fwnode_handle *fwnode, diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c index 9ee65d63c525..f78e758706ed 100644 --- a/drivers/iio/adc/ad7173.c +++ b/drivers/iio/adc/ad7173.c @@ -560,8 +560,8 @@ out: return ret; } -static int ad7173_mask_xlate(struct gpio_regmap *gpio, unsigned int base, - unsigned int offset, unsigned int *reg, +static int ad7173_mask_xlate(struct gpio_regmap *gpio, enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, unsigned int *reg, unsigned int *mask) { *mask = AD7173_GPO_DATA(offset); @@ -569,8 +569,8 @@ static int ad7173_mask_xlate(struct gpio_regmap *gpio, unsigned int base, return 0; } -static int ad4111_mask_xlate(struct gpio_regmap *gpio, unsigned int base, - unsigned int offset, unsigned int *reg, +static int ad4111_mask_xlate(struct gpio_regmap *gpio, enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, unsigned int *reg, unsigned int *mask) { *mask = AD4111_GPO01_DATA(offset); diff --git a/drivers/iio/addac/stx104.c b/drivers/iio/addac/stx104.c index 7bdf2cb94176..6207eb2fac74 100644 --- a/drivers/iio/addac/stx104.c +++ b/drivers/iio/addac/stx104.c @@ -349,9 +349,9 @@ static const struct iio_chan_spec stx104_channels_diff[] = { STX104_IN_CHAN(6, 1), STX104_IN_CHAN(7, 1) }; -static int stx104_reg_mask_xlate(struct gpio_regmap *const gpio, const unsigned int base, - unsigned int offset, unsigned int *const reg, - unsigned int *const mask) +static int stx104_reg_mask_xlate(struct gpio_regmap *const gpio, enum gpio_regmap_operation op, + const unsigned int base, unsigned int offset, + unsigned int *const reg, unsigned int *const mask) { /* Output lines are located at same register bit offsets as input lines */ if (offset >= 4) diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c index e19617485679..129fc4212a5e 100644 --- a/drivers/input/keyboard/gpio_keys.c +++ b/drivers/input/keyboard/gpio_keys.c @@ -23,8 +23,8 @@ #include <linux/input.h> #include <linux/gpio_keys.h> #include <linux/workqueue.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> +#include <linux/gpio/legacy.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/spinlock.h> @@ -528,6 +528,7 @@ static int gpio_keys_setup_key(struct platform_device *pdev, */ bdata->gpiod = NULL; } +#ifdef CONFIG_GPIOLIB_LEGACY } else if (gpio_is_valid(button->gpio)) { /* * Legacy GPIO number, so request the GPIO here and @@ -546,6 +547,7 @@ static int gpio_keys_setup_key(struct platform_device *pdev, if (button->active_low ^ gpiod_is_active_low(bdata->gpiod)) gpiod_toggle_active_low(bdata->gpiod); +#endif } if (bdata->gpiod) { @@ -583,8 +585,7 @@ static int gpio_keys_setup_key(struct platform_device *pdev, if (irq < 0) { error = irq; dev_err_probe(dev, error, - "Unable to get irq number for GPIO %d\n", - button->gpio); + "Unable to get irq number for GPIO\n"); return error; } bdata->irq = irq; diff --git a/drivers/input/keyboard/gpio_keys_polled.c b/drivers/input/keyboard/gpio_keys_polled.c index e6707d72210e..4e7a366ff05b 100644 --- a/drivers/input/keyboard/gpio_keys_polled.c +++ b/drivers/input/keyboard/gpio_keys_polled.c @@ -18,8 +18,8 @@ #include <linux/input.h> #include <linux/ioport.h> #include <linux/platform_device.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> +#include <linux/gpio/legacy.h> #include <linux/gpio_keys.h> #include <linux/property.h> @@ -301,6 +301,7 @@ static int gpio_keys_polled_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(bdata->gpiod), "failed to get gpio\n"); } +#ifdef CONFIG_GPIOLIB_LEGACY } else if (gpio_is_valid(button->gpio)) { /* * Legacy GPIO number so request the GPIO here and @@ -323,6 +324,7 @@ static int gpio_keys_polled_probe(struct platform_device *pdev) if (button->active_low ^ gpiod_is_active_low(bdata->gpiod)) gpiod_toggle_active_low(bdata->gpiod); +#endif } bdata->last_state = -1; diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 7154eaf5a60b..abee43158d84 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -904,6 +904,9 @@ config INPUT_IDEAPAD_SLIDEBAR config INPUT_SOC_BUTTON_ARRAY tristate "Windows-compatible SoC Button Array" depends on KEYBOARD_GPIO && ACPI + depends on X86 + depends on GPIOLIB + select GPIOLIB_LEGACY help Say Y here if you have a SoC-based tablet that originally runs Windows 8 or a Microsoft Surface Book 2, Pro 5, Laptop 1 or later. diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c index b8cad415c62c..a6c984205123 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -15,7 +15,6 @@ #include <linux/dmi.h> #include <linux/gpio/consumer.h> #include <linux/gpio_keys.h> -#include <linux/gpio.h> #include <linux/platform_device.h> static bool use_low_level_irq; diff --git a/drivers/mfd/aat2870-core.c b/drivers/mfd/aat2870-core.c index 34d66ba9646a..0d56cd6fbc6a 100644 --- a/drivers/mfd/aat2870-core.c +++ b/drivers/mfd/aat2870-core.c @@ -13,7 +13,7 @@ #include <linux/uaccess.h> #include <linux/i2c.h> #include <linux/delay.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/mfd/core.h> #include <linux/mfd/aat2870.h> #include <linux/regulator/machine.h> diff --git a/drivers/mfd/arizona-irq.c b/drivers/mfd/arizona-irq.c index 544016d420fe..14f9cb2c4b67 100644 --- a/drivers/mfd/arizona-irq.c +++ b/drivers/mfd/arizona-irq.c @@ -8,7 +8,7 @@ */ #include <linux/delay.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/irqdomain.h> diff --git a/drivers/mfd/lp3943.c b/drivers/mfd/lp3943.c index 6764553147e4..1918b5c7a5e7 100644 --- a/drivers/mfd/lp3943.c +++ b/drivers/mfd/lp3943.c @@ -28,7 +28,7 @@ */ #include <linux/err.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/mfd/core.h> #include <linux/mfd/lp3943.h> diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index b5bda477ebfc..9d5bb67e8084 100644 --- a/drivers/mfd/sm501.c +++ b/drivers/mfd/sm501.c @@ -35,7 +35,7 @@ struct sm501_device { struct sm501_gpio; #ifdef CONFIG_MFD_SM501_GPIO -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> struct sm501_gpio_chip { struct gpio_chip gpio; diff --git a/drivers/mfd/tps6105x.c b/drivers/mfd/tps6105x.c index e2f6858d101e..b11cd2c03311 100644 --- a/drivers/mfd/tps6105x.c +++ b/drivers/mfd/tps6105x.c @@ -16,7 +16,7 @@ #include <linux/init.h> #include <linux/i2c.h> #include <linux/regmap.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/spinlock.h> #include <linux/slab.h> #include <linux/err.h> diff --git a/drivers/mfd/tps65911-comparator.c b/drivers/mfd/tps65911-comparator.c index 7098712ea008..cc8545a972bc 100644 --- a/drivers/mfd/tps65911-comparator.c +++ b/drivers/mfd/tps65911-comparator.c @@ -14,7 +14,7 @@ #include <linux/err.h> #include <linux/platform_device.h> #include <linux/debugfs.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/mfd/tps65910.h> #define COMP1 0 diff --git a/drivers/mfd/wm8994-irq.c b/drivers/mfd/wm8994-irq.c index a46cea948763..a15483489b9d 100644 --- a/drivers/mfd/wm8994-irq.c +++ b/drivers/mfd/wm8994-irq.c @@ -9,7 +9,7 @@ #include <linux/kernel.h> #include <linux/module.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/irq.h> #include <linux/mfd/core.h> diff --git a/drivers/pcmcia/bcm63xx_pcmcia.c b/drivers/pcmcia/bcm63xx_pcmcia.c index 724fd6ee0fd0..1612b5d76975 100644 --- a/drivers/pcmcia/bcm63xx_pcmcia.c +++ b/drivers/pcmcia/bcm63xx_pcmcia.c @@ -14,7 +14,7 @@ #include <linux/slab.h> #include <linux/delay.h> #include <linux/pci.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <bcm63xx_regs.h> #include <bcm63xx_io.h> diff --git a/drivers/pcmcia/db1xxx_ss.c b/drivers/pcmcia/db1xxx_ss.c index 7b896d7dbc9f..410d24762f13 100644 --- a/drivers/pcmcia/db1xxx_ss.c +++ b/drivers/pcmcia/db1xxx_ss.c @@ -23,7 +23,7 @@ */ #include <linux/delay.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/interrupt.h> #include <linux/pm.h> #include <linux/module.h> diff --git a/drivers/pcmcia/sa1100_h3600.c b/drivers/pcmcia/sa1100_h3600.c index 10cb99c20a7f..6c52d90795eb 100644 --- a/drivers/pcmcia/sa1100_h3600.c +++ b/drivers/pcmcia/sa1100_h3600.c @@ -11,7 +11,7 @@ #include <linux/interrupt.h> #include <linux/init.h> #include <linux/delay.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <mach/hardware.h> #include <asm/irq.h> diff --git a/drivers/pcmcia/soc_common.c b/drivers/pcmcia/soc_common.c index 87aa3f667117..23585e3968e9 100644 --- a/drivers/pcmcia/soc_common.c +++ b/drivers/pcmcia/soc_common.c @@ -32,8 +32,8 @@ #include <linux/cpufreq.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> +#include <linux/gpio/legacy.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/io.h> diff --git a/drivers/pcmcia/xxs1500_ss.c b/drivers/pcmcia/xxs1500_ss.c index 8a8aae1843b5..ba1bd0f9d9f8 100644 --- a/drivers/pcmcia/xxs1500_ss.c +++ b/drivers/pcmcia/xxs1500_ss.c @@ -7,7 +7,7 @@ */ #include <linux/delay.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/ioport.h> diff --git a/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c b/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c index 8473fa574529..171bf097a8b8 100644 --- a/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c +++ b/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c @@ -3,7 +3,6 @@ #include <linux/delay.h> #include <linux/extcon-provider.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> #include <linux/init.h> #include <linux/interrupt.h> diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c index f68de14366db..07a839e41db7 100644 --- a/drivers/phy/rockchip/phy-rockchip-usbdp.c +++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c @@ -11,7 +11,7 @@ #include <linux/bits.h> #include <linux/clk.h> #include <linux/delay.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/mutex.h> diff --git a/drivers/phy/ti/phy-j721e-wiz.c b/drivers/phy/ti/phy-j721e-wiz.c index 7531a8a04912..1f5dba49ace4 100644 --- a/drivers/phy/ti/phy-j721e-wiz.c +++ b/drivers/phy/ti/phy-j721e-wiz.c @@ -11,7 +11,6 @@ #include <linux/slab.h> #include <linux/clk.h> #include <linux/clk-provider.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> #include <linux/io.h> #include <linux/module.h> diff --git a/drivers/pinctrl/bcm/pinctrl-bcm63xx.c b/drivers/pinctrl/bcm/pinctrl-bcm63xx.c index a4aa4146b530..b41a3d16aabe 100644 --- a/drivers/pinctrl/bcm/pinctrl-bcm63xx.c +++ b/drivers/pinctrl/bcm/pinctrl-bcm63xx.c @@ -19,6 +19,7 @@ #define BCM63XX_DATA_REG 0x0c static int bcm63xx_reg_mask_xlate(struct gpio_regmap *gpio, + enum gpio_regmap_operation op, unsigned int base, unsigned int offset, unsigned int *reg, unsigned int *mask) { diff --git a/drivers/pinctrl/pinctrl-tps6594.c b/drivers/pinctrl/pinctrl-tps6594.c index 456a3cfc8de9..937fc9cb54b7 100644 --- a/drivers/pinctrl/pinctrl-tps6594.c +++ b/drivers/pinctrl/pinctrl-tps6594.c @@ -346,6 +346,7 @@ static struct tps6594_pinctrl tps6594_template_pinctrl = { }; static int tps6594_gpio_regmap_xlate(struct gpio_regmap *gpio, + enum gpio_regmap_operation op, unsigned int base, unsigned int offset, unsigned int *reg, unsigned int *mask) { diff --git a/include/kunit/fwnode.h b/include/kunit/fwnode.h new file mode 100644 index 000000000000..e250bb87e144 --- /dev/null +++ b/include/kunit/fwnode.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * KUnit resource management helpers for firmware nodes. + * + * Copyright (C) Qualcomm Technologies, Inc. and/or its subsidiaries + */ + +#ifndef _KUNIT_FWNODE_H +#define _KUNIT_FWNODE_H + +struct device; +struct fwnode_handle; +struct kunit; +struct property_entry; +struct software_node; + +struct fwnode_handle * +kunit_fwnode_create_software_node(struct kunit *test, + const struct property_entry *properties, + const struct fwnode_handle *parent); +struct fwnode_handle * +kunit_software_node_register(struct kunit *test, + const struct software_node *node); +int kunit_software_node_register_node_group(struct kunit *test, + const struct software_node *const *nodes); +int kunit_device_add_software_node(struct kunit *test, struct device *dev, + const struct software_node *node); + +#endif /* _KUNIT_FWNODE_H */ diff --git a/include/linux/gpio/machine.h b/include/linux/gpio/machine.h index 5eb88f5d0630..1a141056716f 100644 --- a/include/linux/gpio/machine.h +++ b/include/linux/gpio/machine.h @@ -54,7 +54,7 @@ static struct gpiod_lookup_table _name = { \ .dev_id = _dev_id, \ .table = { \ GPIO_LOOKUP(_key, _chip_hwnum, _con_id, _flags), \ - {}, \ + { } \ }, \ } diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h index 06255756710d..6f52c140e50d 100644 --- a/include/linux/gpio/regmap.h +++ b/include/linux/gpio/regmap.h @@ -3,6 +3,8 @@ #ifndef _LINUX_GPIO_REGMAP_H #define _LINUX_GPIO_REGMAP_H +#include <linux/types.h> + struct device; struct fwnode_handle; struct gpio_regmap; @@ -14,6 +16,32 @@ struct regmap; #define GPIO_REGMAP_ADDR(addr) ((addr) ? : GPIO_REGMAP_ADDR_ZERO) /** + * enum gpio_regmap_operation - Operation type for gpio_regmap callbacks + * + * Traditionally, the operation type was inferred from the base register. + * However, that approach does not always work — for example, when all control + * bits of a single GPIO reside in the same register. This enum allows the + * callbacks (reg_mask_xlate and value_xlate) to explicitly distinguish between + * operation types. The user is free to choose which method to use. + * + * Read operation: + * @GPIO_REGMAP_GET_OP: Indicates a read operation to get the current GPIO value. + * + * Write operation: + * @GPIO_REGMAP_SET_OP: Indicates a write operation to set the GPIO output value. + * + * Direction operations: + * @GPIO_REGMAP_GET_DIR_OP: Indicates a read operation to get the GPIO direction. + * @GPIO_REGMAP_SET_DIR_OP: Indicates a write operation to set the GPIO direction. + */ +enum gpio_regmap_operation { + GPIO_REGMAP_GET_OP, + GPIO_REGMAP_SET_OP, + GPIO_REGMAP_GET_DIR_OP, + GPIO_REGMAP_SET_DIR_OP, +}; + +/** * struct gpio_regmap_config - Description of a generic regmap gpio_chip. * @parent: The parent device * @regmap: The regmap used to access the registers @@ -34,10 +62,6 @@ struct regmap; * @ngpio_per_reg: (Optional) Number of GPIOs per register * @irq_domain: (Optional) IRQ domain if the controller is * interrupt-capable - * @reg_mask_xlate: (Optional) Translates base address and GPIO - * offset to a register/bitmask pair. If not - * given the default gpio_regmap_simple_xlate() - * is used. * @fixed_direction_mask: * (Optional) Bitmap representing the GPIO lines that * make use of the @fixed_direction_output list to @@ -48,16 +72,28 @@ struct regmap; * (Optional) Bitmap representing the fixed direction of * the GPIO lines. Useful when there are GPIO lines with a * fixed direction mixed together in the same register. - * @drvdata: (Optional) Pointer to driver specific data which is - * not used by gpio-remap but is provided "as is" to the - * driver callback(s). - * @init_valid_mask: (Optional) Routine to initialize @valid_mask, to be used - * if not all GPIOs are valid. * @regmap_irq_chip: (Optional) Pointer on an regmap_irq_chip structure. If * set, a regmap-irq device will be created and the IRQ * domain will be set accordingly. * @regmap_irq_line: (Optional) The IRQ the device uses to signal interrupts. * @regmap_irq_flags: (Optional) The IRQF_ flags to use for the interrupt. + * @reg_mask_xlate: (Optional) Translates base address and GPIO + * offset to a register/bitmask pair. If not + * given the default gpio_regmap_simple_xlate() + * is used. + * @init_valid_mask: (Optional) Routine to initialize @valid_mask, to be used + * if not all GPIOs are valid. + * @value_xlate: (Optional) Routine to translate the register value and + * mask before writing. This allows driver-specific logic + * to append additional bits (like write-enable masks) + * dynamically based on the current operation + * (GPIO_REGMAP_SET_OP and GPIO_REGMAP_SET_DIR_OP). + * @set_config: (Optional) Callback for setting GPIO configuration such + * as debounce, drive strength, or other hardware specific + * settings. + * @drvdata: (Optional) Pointer to driver specific data which is + * not used by gpio-remap but is provided "as is" to the + * driver callback(s). * * The ->reg_mask_xlate translates a given base address and GPIO offset to * register and mask pair. The base address is one of the given register @@ -104,14 +140,21 @@ struct gpio_regmap_config { unsigned long regmap_irq_flags; #endif - int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base, - unsigned int offset, unsigned int *reg, - unsigned int *mask); + int (*reg_mask_xlate)(struct gpio_regmap *gpio, enum gpio_regmap_operation, + unsigned int base, unsigned int offset, + unsigned int *reg, unsigned int *mask); int (*init_valid_mask)(struct gpio_chip *gc, unsigned long *valid_mask, unsigned int ngpios); + int (*value_xlate)(struct gpio_regmap *gpio, enum gpio_regmap_operation, + unsigned int base, unsigned int offset, unsigned int reg, + unsigned int *mask, unsigned int *val); + + int (*set_config)(struct gpio_regmap *gpio, struct gpio_chip *chip, + unsigned int offset, unsigned long config); + void *drvdata; }; @@ -121,4 +164,10 @@ struct gpio_regmap *devm_gpio_regmap_register(struct device *dev, const struct gpio_regmap_config *config); void *gpio_regmap_get_drvdata(struct gpio_regmap *gpio); +int gpio_regmap_reqres_irq(struct gpio_regmap *gpio, unsigned int offset); +void gpio_regmap_relres_irq(struct gpio_regmap *gpio, unsigned int offset); + +void gpio_regmap_enable_irq(struct gpio_regmap *gpio, irq_hw_number_t hwirq); +void gpio_regmap_disable_irq(struct gpio_regmap *gpio, irq_hw_number_t hwirq); + #endif /* _LINUX_GPIO_REGMAP_H */ diff --git a/include/linux/gpio_keys.h b/include/linux/gpio_keys.h index 80fa930b04c6..e8d6dc290efb 100644 --- a/include/linux/gpio_keys.h +++ b/include/linux/gpio_keys.h @@ -25,7 +25,9 @@ struct device; */ struct gpio_keys_button { unsigned int code; +#ifdef CONFIG_GPIOLIB_LEGACY int gpio; +#endif int active_low; const char *desc; unsigned int type; diff --git a/include/linux/mfd/lp3943.h b/include/linux/mfd/lp3943.h index 402f01078fcc..5d2d172d3598 100644 --- a/include/linux/mfd/lp3943.h +++ b/include/linux/mfd/lp3943.h @@ -10,7 +10,7 @@ #ifndef __MFD_LP3943_H__ #define __MFD_LP3943_H__ -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/regmap.h> /* Registers */ diff --git a/include/linux/mfd/ti-lmu.h b/include/linux/mfd/ti-lmu.h index 0bc0e8199798..2089ec5124e8 100644 --- a/include/linux/mfd/ti-lmu.h +++ b/include/linux/mfd/ti-lmu.h @@ -10,7 +10,6 @@ #ifndef __MFD_TI_LMU_H__ #define __MFD_TI_LMU_H__ -#include <linux/gpio.h> #include <linux/notifier.h> #include <linux/regmap.h> #include <linux/gpio/consumer.h> diff --git a/include/linux/mfd/tps65910.h b/include/linux/mfd/tps65910.h index f67ef0a4e041..3813fc9c2b55 100644 --- a/include/linux/mfd/tps65910.h +++ b/include/linux/mfd/tps65910.h @@ -12,7 +12,7 @@ #ifndef __LINUX_MFD_TPS65910_H #define __LINUX_MFD_TPS65910_H -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/regmap.h> /* TPS chip id list */ diff --git a/include/linux/mfd/ucb1x00.h b/include/linux/mfd/ucb1x00.h index ede237384723..4ad54e22ed33 100644 --- a/include/linux/mfd/ucb1x00.h +++ b/include/linux/mfd/ucb1x00.h @@ -9,7 +9,7 @@ #include <linux/device.h> #include <linux/mfd/mcp.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/gpio/driver.h> #include <linux/mutex.h> diff --git a/include/linux/regmap.h b/include/linux/regmap.h index df44cb30f53b..370baa19db87 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -1770,6 +1770,8 @@ struct regmap_irq_chip { void *irq_drv_data); unsigned int (*get_irq_reg)(struct regmap_irq_chip_data *data, unsigned int base, int index); + int (*irq_reqres)(void *irq_drv_data, irq_hw_number_t hwirq); + void (*irq_relres)(void *irq_drv_data, irq_hw_number_t hwirq); void *irq_drv_data; }; diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile index 2e8a6b71a2ab..204e02b10eba 100644 --- a/lib/kunit/Makefile +++ b/lib/kunit/Makefile @@ -11,6 +11,7 @@ kunit-objs += test.o \ attributes.o \ device.o \ platform.o \ + fwnode.o \ bug.o ifeq ($(CONFIG_KUNIT_DEBUGFS),y) diff --git a/lib/kunit/fwnode.c b/lib/kunit/fwnode.c new file mode 100644 index 000000000000..a58ce0984d76 --- /dev/null +++ b/lib/kunit/fwnode.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Qualcomm Technologies, Inc. and/or its subsidiaries + */ + +#include <kunit/fwnode.h> +#include <kunit/test.h> + +#include <linux/fwnode.h> +#include <linux/property.h> + +KUNIT_DEFINE_ACTION_WRAPPER(fwnode_remove_software_node_wrapper, + fwnode_remove_software_node, + struct fwnode_handle *); + +/** + * kunit_fwnode_create_software_node() - Create a kunit-managed software node + * @test: Test context + * @properties: Properties to use to create the new software node + * @parent: Parent of this software node + * + * Create a test-managed software node and return its firmware node handle. + * The software node is removed after the test case completes. + * + * Returns: + * Firmware node handle of the newly created software node or IS_ERR() on + * failure. + */ +struct fwnode_handle * +kunit_fwnode_create_software_node(struct kunit *test, + const struct property_entry *properties, + const struct fwnode_handle *parent) +{ + struct fwnode_handle *fwnode; + int ret; + + fwnode = fwnode_create_software_node(properties, parent); + if (IS_ERR(fwnode)) + return fwnode; + + ret = kunit_add_action_or_reset(test, fwnode_remove_software_node_wrapper, + fwnode); + if (ret) + return ERR_PTR(ret); + + return fwnode; +} +EXPORT_SYMBOL_GPL(kunit_fwnode_create_software_node); + +KUNIT_DEFINE_ACTION_WRAPPER(software_node_unregister_wrapper, + software_node_unregister, + const struct software_node *); + +/** + * kunit_software_node_register() - Register a kunit-managed software node + * @test: Test context + * @swnode: Software node to register + * + * Register a test-managed software node and return its firmware node handle. + * The software node is unregistered after the test case completes. + * + * Returns: + * Firmware node handle of the registered software node or IS_ERR() on failure. + */ +struct fwnode_handle * +kunit_software_node_register(struct kunit *test, + const struct software_node *swnode) +{ + struct fwnode_handle *fwnode; + int ret; + + ret = software_node_register(swnode); + if (ret) + return ERR_PTR(ret); + + fwnode = software_node_fwnode(swnode); + if (WARN_ON(!fwnode)) + return ERR_PTR(-ENOENT); + + ret = kunit_add_action_or_reset(test, software_node_unregister_wrapper, + (void *)swnode); + if (ret) + return ERR_PTR(ret); + + return fwnode; +} +EXPORT_SYMBOL_GPL(kunit_software_node_register); + +KUNIT_DEFINE_ACTION_WRAPPER(software_node_unregister_node_group_wrapper, + software_node_unregister_node_group, + const struct software_node *const *); + +/** + * kunit_software_node_register_node_group() - Register a kunit-managed software node group + * @test: Test context + * @nodes: Software node group to register + * + * Register a test-managed software node group. The nodes are unregistered + * after the test case completes. + * + * Returns: + * 0 on success, negative error number on failure. + */ +int kunit_software_node_register_node_group(struct kunit *test, + const struct software_node *const *nodes) +{ + int ret; + + ret = software_node_register_node_group(nodes); + if (ret) + return ret; + + return kunit_add_action_or_reset(test, software_node_unregister_node_group_wrapper, + (void *)nodes); +} +EXPORT_SYMBOL_GPL(kunit_software_node_register_node_group); + +KUNIT_DEFINE_ACTION_WRAPPER(device_remove_software_node_wrapper, + device_remove_software_node, + struct device *); + +/** + * kunit_device_add_software_node() - Assign a kunit-managed software node to a device + * @test: Test context + * @dev: Device to assign the software node for + * @node: The software node to assign + * + * Make @node the secondary firmware node of @dev. If @dev has no primary + * firmware node, @node will become the primary node. The software node will + * be automatically removed from @dev when the test case completes. + * + * Returns: + * 0 on success, negative error number on failure. + */ +int kunit_device_add_software_node(struct kunit *test, struct device *dev, + const struct software_node *node) +{ + int ret; + + ret = device_add_software_node(dev, node); + if (ret) + return ret; + + return kunit_add_action_or_reset(test, device_remove_software_node_wrapper, dev); +} +EXPORT_SYMBOL_GPL(kunit_device_add_software_node); diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index e0996a9d90b0..6366f72b3667 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -300,6 +300,7 @@ config SND_CS5535AUDIO tristate "CS5535/CS5536 Audio" depends on X86_32 || MIPS || COMPILE_TEST depends on HAS_IOPORT + depends on GPIOLIB_LEGACY || !OLPC select SND_PCM select SND_AC97_CODEC help diff --git a/sound/pci/cs5535audio/cs5535audio_olpc.c b/sound/pci/cs5535audio/cs5535audio_olpc.c index 122170a410d9..cfdcc5bf4341 100644 --- a/sound/pci/cs5535audio/cs5535audio_olpc.c +++ b/sound/pci/cs5535audio/cs5535audio_olpc.c @@ -9,7 +9,7 @@ #include <sound/info.h> #include <sound/control.h> #include <sound/ac97_codec.h> -#include <linux/gpio.h> +#include <linux/gpio/legacy.h> #include <asm/olpc.h> #include "cs5535audio.h" diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c index f2a58163de0e..f2448b4c11fc 100644 --- a/sound/soc/codecs/cs42l84.c +++ b/sound/soc/codecs/cs42l84.c @@ -16,7 +16,6 @@ #include <linux/init.h> #include <linux/delay.h> #include <linux/i2c.h> -#include <linux/gpio.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/acpi.h> diff --git a/sound/soc/codecs/cx2072x.c b/sound/soc/codecs/cx2072x.c index 83c6cbd40804..0dd35fa86cc5 100644 --- a/sound/soc/codecs/cx2072x.c +++ b/sound/soc/codecs/cx2072x.c @@ -11,7 +11,7 @@ #include <linux/acpi.h> #include <linux/clk.h> #include <linux/delay.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/init.h> #include <linux/i2c.h> #include <linux/module.h> diff --git a/sound/soc/codecs/dmic.c b/sound/soc/codecs/dmic.c index 61e1bf1b3c9e..cbed11136935 100644 --- a/sound/soc/codecs/dmic.c +++ b/sound/soc/codecs/dmic.c @@ -6,7 +6,6 @@ */ #include <linux/delay.h> -#include <linux/gpio.h> #include <linux/gpio/consumer.h> #include <linux/platform_device.h> #include <linux/regulator/consumer.h> |
