summaryrefslogtreecommitdiffstats
path: root/drivers/gpio
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/gpio
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpio')
-rw-r--r--drivers/gpio/Kconfig1822
-rw-r--r--drivers/gpio/Makefile197
-rw-r--r--drivers/gpio/TODO211
-rw-r--r--drivers/gpio/gpio-104-dio-48e.c342
-rw-r--r--drivers/gpio/gpio-104-idi-48.c189
-rw-r--r--drivers/gpio/gpio-104-idio-16.c129
-rw-r--r--drivers/gpio/gpio-74x164.c203
-rw-r--r--drivers/gpio/gpio-74xx-mmio.c152
-rw-r--r--drivers/gpio/gpio-adnp.c545
-rw-r--r--drivers/gpio/gpio-adp5520.c167
-rw-r--r--drivers/gpio/gpio-aggregator.c636
-rw-r--r--drivers/gpio/gpio-altera-a10sr.c115
-rw-r--r--drivers/gpio/gpio-altera.c358
-rw-r--r--drivers/gpio/gpio-amd-fch.c192
-rw-r--r--drivers/gpio/gpio-amd8111.c248
-rw-r--r--drivers/gpio/gpio-amdpt.c155
-rw-r--r--drivers/gpio/gpio-arizona.c212
-rw-r--r--drivers/gpio/gpio-aspeed-sgpio.c639
-rw-r--r--drivers/gpio/gpio-aspeed.c1280
-rw-r--r--drivers/gpio/gpio-ath79.c308
-rw-r--r--drivers/gpio/gpio-bcm-kona.c656
-rw-r--r--drivers/gpio/gpio-bd71815.c185
-rw-r--r--drivers/gpio/gpio-bd71828.c143
-rw-r--r--drivers/gpio/gpio-bd9571mwv.c130
-rw-r--r--drivers/gpio/gpio-brcmstb.c767
-rw-r--r--drivers/gpio/gpio-bt8xx.c311
-rw-r--r--drivers/gpio/gpio-cadence.c300
-rw-r--r--drivers/gpio/gpio-clps711x.c88
-rw-r--r--drivers/gpio/gpio-creg-snps.c189
-rw-r--r--drivers/gpio/gpio-crystalcove.c397
-rw-r--r--drivers/gpio/gpio-cs5535.c364
-rw-r--r--drivers/gpio/gpio-da9052.c225
-rw-r--r--drivers/gpio/gpio-da9055.c173
-rw-r--r--drivers/gpio/gpio-davinci.c734
-rw-r--r--drivers/gpio/gpio-dln2.c525
-rw-r--r--drivers/gpio/gpio-ds4520.c80
-rw-r--r--drivers/gpio/gpio-dwapb.c852
-rw-r--r--drivers/gpio/gpio-eic-sprd.c717
-rw-r--r--drivers/gpio/gpio-elkhartlake.c90
-rw-r--r--drivers/gpio/gpio-em.c390
-rw-r--r--drivers/gpio/gpio-en7523.c137
-rw-r--r--drivers/gpio/gpio-ep93xx.c467
-rw-r--r--drivers/gpio/gpio-exar.c235
-rw-r--r--drivers/gpio/gpio-f7188x.c666
-rw-r--r--drivers/gpio/gpio-ftgpio010.c357
-rw-r--r--drivers/gpio/gpio-fxl6408.c158
-rw-r--r--drivers/gpio/gpio-ge.c99
-rw-r--r--drivers/gpio/gpio-gpio-mm.c101
-rw-r--r--drivers/gpio/gpio-grgpio.c466
-rw-r--r--drivers/gpio/gpio-gw-pld.c134
-rw-r--r--drivers/gpio/gpio-hisi.c335
-rw-r--r--drivers/gpio/gpio-hlwd.c319
-rw-r--r--drivers/gpio/gpio-htc-egpio.c409
-rw-r--r--drivers/gpio/gpio-i8255.c141
-rw-r--r--drivers/gpio/gpio-i8255.h34
-rw-r--r--drivers/gpio/gpio-ich.c484
-rw-r--r--drivers/gpio/gpio-idio-16.c172
-rw-r--r--drivers/gpio/gpio-idio-16.h32
-rw-r--r--drivers/gpio/gpio-idt3243x.c211
-rw-r--r--drivers/gpio/gpio-imx-scu.c139
-rw-r--r--drivers/gpio/gpio-it87.c412
-rw-r--r--drivers/gpio/gpio-ixp4xx.c309
-rw-r--r--drivers/gpio/gpio-janz-ttl.c200
-rw-r--r--drivers/gpio/gpio-kempld.c200
-rw-r--r--drivers/gpio/gpio-latch.c219
-rw-r--r--drivers/gpio/gpio-ljca.c454
-rw-r--r--drivers/gpio/gpio-logicvc.c165
-rw-r--r--drivers/gpio/gpio-loongson-64bit.c238
-rw-r--r--drivers/gpio/gpio-loongson.c135
-rw-r--r--drivers/gpio/gpio-loongson1.c110
-rw-r--r--drivers/gpio/gpio-lp3943.c224
-rw-r--r--drivers/gpio/gpio-lp873x.c175
-rw-r--r--drivers/gpio/gpio-lp87565.c187
-rw-r--r--drivers/gpio/gpio-lpc18xx.c415
-rw-r--r--drivers/gpio/gpio-lpc32xx.c545
-rw-r--r--drivers/gpio/gpio-madera.c213
-rw-r--r--drivers/gpio/gpio-max3191x.c495
-rw-r--r--drivers/gpio/gpio-max7300.c84
-rw-r--r--drivers/gpio/gpio-max7301.c104
-rw-r--r--drivers/gpio/gpio-max730x.c236
-rw-r--r--drivers/gpio/gpio-max732x.c731
-rw-r--r--drivers/gpio/gpio-max77620.c369
-rw-r--r--drivers/gpio/gpio-max77650.c191
-rw-r--r--drivers/gpio/gpio-mb86s7x.c246
-rw-r--r--drivers/gpio/gpio-mc33880.c172
-rw-r--r--drivers/gpio/gpio-menz127.c214
-rw-r--r--drivers/gpio/gpio-merrifield.c144
-rw-r--r--drivers/gpio/gpio-ml-ioh.c526
-rw-r--r--drivers/gpio/gpio-mlxbf.c152
-rw-r--r--drivers/gpio/gpio-mlxbf2.c478
-rw-r--r--drivers/gpio/gpio-mlxbf3.c274
-rw-r--r--drivers/gpio/gpio-mm-lantiq.c159
-rw-r--r--drivers/gpio/gpio-mmio.c829
-rw-r--r--drivers/gpio/gpio-mockup.c615
-rw-r--r--drivers/gpio/gpio-moxtet.c179
-rw-r--r--drivers/gpio/gpio-mpc5200.c366
-rw-r--r--drivers/gpio/gpio-mpc8xxx.c457
-rw-r--r--drivers/gpio/gpio-msc313.c726
-rw-r--r--drivers/gpio/gpio-mt7621.c343
-rw-r--r--drivers/gpio/gpio-mvebu.c1318
-rw-r--r--drivers/gpio/gpio-mxc.c722
-rw-r--r--drivers/gpio/gpio-mxs.c365
-rw-r--r--drivers/gpio/gpio-octeon.c137
-rw-r--r--drivers/gpio/gpio-omap.c1590
-rw-r--r--drivers/gpio/gpio-palmas.c199
-rw-r--r--drivers/gpio/gpio-pca953x.c1390
-rw-r--r--drivers/gpio/gpio-pca9570.c185
-rw-r--r--drivers/gpio/gpio-pcf857x.c442
-rw-r--r--drivers/gpio/gpio-pch.c453
-rw-r--r--drivers/gpio/gpio-pci-idio-16.c122
-rw-r--r--drivers/gpio/gpio-pcie-idio-24.c404
-rw-r--r--drivers/gpio/gpio-pisosr.c188
-rw-r--r--drivers/gpio/gpio-pl061.c441
-rw-r--r--drivers/gpio/gpio-pmic-eic-sprd.c387
-rw-r--r--drivers/gpio/gpio-pxa.c804
-rw-r--r--drivers/gpio/gpio-raspberrypi-exp.c255
-rw-r--r--drivers/gpio/gpio-rc5t583.c139
-rw-r--r--drivers/gpio/gpio-rcar.c673
-rw-r--r--drivers/gpio/gpio-rda.c292
-rw-r--r--drivers/gpio/gpio-rdc321x.c197
-rw-r--r--drivers/gpio/gpio-realtek-otto.c459
-rw-r--r--drivers/gpio/gpio-reg.c191
-rw-r--r--drivers/gpio/gpio-regmap.c345
-rw-r--r--drivers/gpio/gpio-rockchip.c821
-rw-r--r--drivers/gpio/gpio-sa1100.c329
-rw-r--r--drivers/gpio/gpio-sama5d2-piobu.c247
-rw-r--r--drivers/gpio/gpio-sch.c414
-rw-r--r--drivers/gpio/gpio-sch311x.c449
-rw-r--r--drivers/gpio/gpio-sifive.c274
-rw-r--r--drivers/gpio/gpio-sim.c1517
-rw-r--r--drivers/gpio/gpio-siox.c270
-rw-r--r--drivers/gpio/gpio-sl28cpld.c160
-rw-r--r--drivers/gpio/gpio-sodaville.c245
-rw-r--r--drivers/gpio/gpio-spear-spics.c184
-rw-r--r--drivers/gpio/gpio-sprd.c279
-rw-r--r--drivers/gpio/gpio-stmpe.c544
-rw-r--r--drivers/gpio/gpio-stp-xway.c341
-rw-r--r--drivers/gpio/gpio-syscon.c265
-rw-r--r--drivers/gpio/gpio-tangier.c538
-rw-r--r--drivers/gpio/gpio-tangier.h117
-rw-r--r--drivers/gpio/gpio-tb10x.c249
-rw-r--r--drivers/gpio/gpio-tc3589x.c375
-rw-r--r--drivers/gpio/gpio-tegra.c846
-rw-r--r--drivers/gpio/gpio-tegra186.c1321
-rw-r--r--drivers/gpio/gpio-thunderx.c602
-rw-r--r--drivers/gpio/gpio-timberdale.c288
-rw-r--r--drivers/gpio/gpio-tn48m.c100
-rw-r--r--drivers/gpio/gpio-tpic2810.c137
-rw-r--r--drivers/gpio/gpio-tps65086.c112
-rw-r--r--drivers/gpio/gpio-tps65218.c229
-rw-r--r--drivers/gpio/gpio-tps65219.c185
-rw-r--r--drivers/gpio/gpio-tps6586x.c120
-rw-r--r--drivers/gpio/gpio-tps65910.c181
-rw-r--r--drivers/gpio/gpio-tps65912.c131
-rw-r--r--drivers/gpio/gpio-tps68470.c161
-rw-r--r--drivers/gpio/gpio-tqmx86.c354
-rw-r--r--drivers/gpio/gpio-ts4800.c77
-rw-r--r--drivers/gpio/gpio-ts4900.c195
-rw-r--r--drivers/gpio/gpio-ts5500.c446
-rw-r--r--drivers/gpio/gpio-twl4030.c639
-rw-r--r--drivers/gpio/gpio-twl6040.c117
-rw-r--r--drivers/gpio/gpio-uniphier.c496
-rw-r--r--drivers/gpio/gpio-vf610.c361
-rw-r--r--drivers/gpio/gpio-viperboard.c471
-rw-r--r--drivers/gpio/gpio-virtio.c664
-rw-r--r--drivers/gpio/gpio-visconti.c232
-rw-r--r--drivers/gpio/gpio-vx855.c283
-rw-r--r--drivers/gpio/gpio-wcd934x.c124
-rw-r--r--drivers/gpio/gpio-wcove.c514
-rw-r--r--drivers/gpio/gpio-winbond.c733
-rw-r--r--drivers/gpio/gpio-wm831x.c304
-rw-r--r--drivers/gpio/gpio-wm8350.c145
-rw-r--r--drivers/gpio/gpio-wm8994.c304
-rw-r--r--drivers/gpio/gpio-ws16c48.c327
-rw-r--r--drivers/gpio/gpio-xgene-sb.c337
-rw-r--r--drivers/gpio/gpio-xgene.c209
-rw-r--r--drivers/gpio/gpio-xgs-iproc.c327
-rw-r--r--drivers/gpio/gpio-xilinx.c741
-rw-r--r--drivers/gpio/gpio-xlp.c328
-rw-r--r--drivers/gpio/gpio-xra1403.c217
-rw-r--r--drivers/gpio/gpio-xtensa.c174
-rw-r--r--drivers/gpio/gpio-zevio.c218
-rw-r--r--drivers/gpio/gpio-zynq.c1042
-rw-r--r--drivers/gpio/gpio-zynqmp-modepin.c162
-rw-r--r--drivers/gpio/gpiolib-acpi.c1721
-rw-r--r--drivers/gpio/gpiolib-acpi.h60
-rw-r--r--drivers/gpio/gpiolib-cdev.c2823
-rw-r--r--drivers/gpio/gpiolib-cdev.h13
-rw-r--r--drivers/gpio/gpiolib-devres.c429
-rw-r--r--drivers/gpio/gpiolib-legacy.c101
-rw-r--r--drivers/gpio/gpiolib-of.c1113
-rw-r--r--drivers/gpio/gpiolib-of.h43
-rw-r--r--drivers/gpio/gpiolib-swnode.c124
-rw-r--r--drivers/gpio/gpiolib-swnode.h14
-rw-r--r--drivers/gpio/gpiolib-sysfs.c832
-rw-r--r--drivers/gpio/gpiolib-sysfs.h26
-rw-r--r--drivers/gpio/gpiolib.c4719
-rw-r--r--drivers/gpio/gpiolib.h261
198 files changed, 79352 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
new file mode 100644
index 0000000000..673bafb8be
--- /dev/null
+++ b/drivers/gpio/Kconfig
@@ -0,0 +1,1822 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# GPIO infrastructure and drivers
+#
+
+menuconfig GPIOLIB
+ bool "GPIO Support"
+ help
+ This enables GPIO support through the generic GPIO library.
+ You only need to enable this if you also want to enable
+ one or more of the GPIO drivers below.
+
+ If unsure, say N.
+
+if GPIOLIB
+
+config GPIOLIB_FASTPATH_LIMIT
+ int "Maximum number of GPIOs for fast path"
+ range 32 512
+ default 512
+ help
+ This adjusts the point at which certain APIs will switch from
+ using a stack allocated buffer to a dynamically allocated buffer.
+
+ You shouldn't need to change this unless you really need to
+ optimize either stack space or performance. Change this carefully
+ since setting an incorrect value could cause stack corruption.
+
+config OF_GPIO
+ def_bool y
+ depends on OF
+ depends on HAS_IOMEM
+
+config GPIO_ACPI
+ def_bool y
+ depends on ACPI
+
+config GPIOLIB_IRQCHIP
+ select IRQ_DOMAIN
+ bool
+
+config OF_GPIO_MM_GPIOCHIP
+ bool
+ help
+ This adds support for the legacy 'struct of_mm_gpio_chip' interface
+ from PowerPC. Existing drivers using this interface need to select
+ this symbol, but new drivers should use the generic gpio-regmap
+ infrastructure instead.
+
+config DEBUG_GPIO
+ bool "Debug GPIO calls"
+ depends on DEBUG_KERNEL
+ help
+ Say Y here to add some extra checks and diagnostics to GPIO calls.
+ These checks help ensure that GPIOs have been properly initialized
+ before they are used, and that sleeping calls are not made from
+ non-sleeping contexts. They can make bitbanged serial protocols
+ slower. The diagnostics help catch the type of setup errors
+ that are most common when setting up new platforms or boards.
+
+config GPIO_SYSFS
+ bool "/sys/class/gpio/... (sysfs interface)" if EXPERT
+ depends on SYSFS
+ select GPIO_CDEV # We need to encourage the new ABI
+ help
+ Say Y here to add the legacy sysfs interface for GPIOs.
+
+ This ABI is deprecated. If you want to use GPIO from userspace,
+ use the character device /dev/gpiochipN with the appropriate
+ ioctl() operations instead.
+
+config GPIO_CDEV
+ bool
+ prompt "Character device (/dev/gpiochipN) support" if EXPERT
+ default y
+ help
+ Say Y here to add the character device /dev/gpiochipN interface
+ for GPIOs. The character device allows userspace to control GPIOs
+ using ioctl() operations.
+
+ Only say N if you are sure that the GPIO character device is not
+ required.
+
+ If unsure, say Y.
+
+config GPIO_CDEV_V1
+ bool "Support GPIO ABI Version 1"
+ default y
+ depends on GPIO_CDEV
+ help
+ Say Y here to support version 1 of the GPIO CDEV ABI.
+
+ This ABI version is deprecated.
+ Please use the latest ABI for new developments.
+
+ If unsure, say Y.
+
+config GPIO_GENERIC
+ depends on HAS_IOMEM # Only for IOMEM drivers
+ tristate
+
+config GPIO_REGMAP
+ select REGMAP
+ tristate
+
+# put drivers in the right section, in alphabetical order
+
+# This symbol is selected by both I2C and SPI expanders
+config GPIO_MAX730X
+ tristate
+
+config GPIO_IDIO_16
+ tristate
+ select REGMAP_IRQ
+ select GPIOLIB_IRQCHIP
+ select GPIO_REGMAP
+ help
+ Enables support for the idio-16 library functions. The idio-16 library
+ provides functions to facilitate communication with devices within the
+ ACCES IDIO-16 family such as the 104-IDIO-16 and the PCI-IDIO-16.
+
+ If built as a module its name will be gpio-idio-16.
+
+menu "Memory mapped GPIO drivers"
+ depends on HAS_IOMEM
+
+config GPIO_74XX_MMIO
+ tristate "GPIO driver for 74xx-ICs with MMIO access"
+ depends on OF_GPIO
+ select GPIO_GENERIC
+ help
+ Say yes here to support GPIO functionality for 74xx-compatible ICs
+ with MMIO access. Compatible models include:
+ 1 bit: 741G125 (Input), 741G74 (Output)
+ 2 bits: 742G125 (Input), 7474 (Output)
+ 4 bits: 74125 (Input), 74175 (Output)
+ 6 bits: 74365 (Input), 74174 (Output)
+ 8 bits: 74244 (Input), 74273 (Output)
+ 16 bits: 741624 (Input), 7416374 (Output)
+
+config GPIO_ALTERA
+ tristate "Altera GPIO"
+ depends on OF_GPIO
+ select GPIOLIB_IRQCHIP
+ select OF_GPIO_MM_GPIOCHIP
+ help
+ Say Y or M here to build support for the Altera PIO device.
+
+ If driver is built as a module it will be called gpio-altera.
+
+config GPIO_AMDPT
+ tristate "AMD Promontory GPIO support"
+ depends on ACPI
+ select GPIO_GENERIC
+ help
+ Driver for GPIO functionality on Promontory IOHub.
+ Requires ACPI ASL code to enumerate as a platform device.
+
+config GPIO_ASPEED
+ tristate "Aspeed GPIO support"
+ depends on (ARCH_ASPEED || COMPILE_TEST) && OF_GPIO
+ select GPIOLIB_IRQCHIP
+ help
+ Say Y here to support Aspeed AST2400 and AST2500 GPIO controllers.
+
+config GPIO_ASPEED_SGPIO
+ bool "Aspeed SGPIO support"
+ depends on (ARCH_ASPEED || COMPILE_TEST) && OF_GPIO
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ Say Y here to support Aspeed AST2500 SGPIO functionality.
+
+config GPIO_ATH79
+ tristate "Atheros AR71XX/AR724X/AR913X GPIO support"
+ default y if ATH79
+ depends on ATH79 || COMPILE_TEST
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ Select this option to enable GPIO driver for
+ Atheros AR71XX/AR724X/AR913X SoC devices.
+
+config GPIO_RASPBERRYPI_EXP
+ tristate "Raspberry Pi 3 GPIO Expander"
+ default RASPBERRYPI_FIRMWARE
+ depends on OF_GPIO
+ # Make sure not 'y' when RASPBERRYPI_FIRMWARE is 'm'. This can only
+ # happen when COMPILE_TEST=y, hence the added !RASPBERRYPI_FIRMWARE.
+ depends on (ARCH_BCM2835 && RASPBERRYPI_FIRMWARE) || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE)
+ help
+ Turn on GPIO support for the expander on Raspberry Pi 3 boards, using
+ the firmware mailbox to communicate with VideoCore on BCM283x chips.
+
+config GPIO_BCM_KONA
+ bool "Broadcom Kona GPIO"
+ depends on ARCH_BCM_MOBILE || COMPILE_TEST
+ help
+ Turn on GPIO support for Broadcom "Kona" chips.
+
+config GPIO_BCM_XGS_IPROC
+ tristate "BRCM XGS iProc GPIO support"
+ depends on OF_GPIO && (ARCH_BCM_IPROC || COMPILE_TEST)
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ default ARCH_BCM_IPROC
+ help
+ Say yes here to enable GPIO support for Broadcom XGS iProc SoCs.
+
+config GPIO_BRCMSTB
+ tristate "BRCMSTB GPIO support"
+ default y if (ARCH_BRCMSTB || BMIPS_GENERIC)
+ depends on OF_GPIO && (ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST)
+ select GPIO_GENERIC
+ select IRQ_DOMAIN
+ help
+ Say yes here to enable GPIO support for Broadcom STB (BCM7XXX) SoCs.
+
+config GPIO_CADENCE
+ tristate "Cadence GPIO support"
+ depends on OF_GPIO
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to enable support for Cadence GPIO controller.
+
+config GPIO_CLPS711X
+ tristate "CLPS711X GPIO support"
+ depends on ARCH_CLPS711X || COMPILE_TEST
+ select GPIO_GENERIC
+ help
+ Say yes here to support GPIO on CLPS711X SoCs.
+
+config GPIO_DAVINCI
+ tristate "TI Davinci/Keystone GPIO support"
+ default y if ARCH_DAVINCI
+ depends on (ARM || ARM64) && (ARCH_DAVINCI || ARCH_KEYSTONE || ARCH_K3)
+ help
+ Say yes here to enable GPIO support for TI Davinci/Keystone SoCs.
+
+config GPIO_DWAPB
+ tristate "Synopsys DesignWare APB GPIO driver"
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ Say Y or M here to build support for the Synopsys DesignWare APB
+ GPIO block.
+
+config GPIO_EIC_SPRD
+ tristate "Spreadtrum EIC support"
+ depends on ARCH_SPRD || COMPILE_TEST
+ depends on OF_GPIO
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support Spreadtrum EIC device.
+
+config GPIO_EM
+ tristate "Emma Mobile GPIO"
+ depends on (ARCH_EMEV2 || COMPILE_TEST) && OF_GPIO
+ help
+ Say yes here to support GPIO on Renesas Emma Mobile SoCs.
+
+config GPIO_EN7523
+ tristate "Airoha GPIO support"
+ depends on ARCH_AIROHA
+ default ARCH_AIROHA
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ Say Y or M here to support the GPIO controller block on the
+ Airoha EN7523 SoC. It supports two banks of 32 GPIOs.
+
+config GPIO_EP93XX
+ def_bool y
+ depends on ARCH_EP93XX
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+
+config GPIO_EXAR
+ tristate "Support for GPIO pins on XR17V352/354/358"
+ depends on SERIAL_8250_EXAR
+ select REGMAP_MMIO
+ help
+ Selecting this option will enable handling of GPIO pins present
+ on Exar XR17V352/354/358 chips.
+
+config GPIO_GE_FPGA
+ bool "GE FPGA based GPIO"
+ depends on GE_FPGA || COMPILE_TEST
+ select GPIO_GENERIC
+ help
+ Support for common GPIO functionality provided on some GE Single Board
+ Computers.
+
+ This driver provides basic support (configure as input or output, read
+ and write pin state) for GPIO implemented in a number of GE single
+ board computers.
+
+config GPIO_FTGPIO010
+ bool "Faraday FTGPIO010 GPIO"
+ depends on OF_GPIO
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ default (ARCH_GEMINI || ARCH_MOXART)
+ help
+ Support for common GPIOs from the Faraday FTGPIO010 IP core, found in
+ Cortina systems Gemini platforms, Moxa ART and others.
+
+config GPIO_GENERIC_PLATFORM
+ tristate "Generic memory-mapped GPIO controller support (MMIO platform device)"
+ select GPIO_GENERIC
+ help
+ Say yes here to support basic platform_device memory-mapped GPIO controllers.
+
+config GPIO_GRGPIO
+ tristate "Aeroflex Gaisler GRGPIO support"
+ depends on OF_GPIO
+ select GPIO_GENERIC
+ select IRQ_DOMAIN
+ help
+ Select this to support Aeroflex Gaisler GRGPIO cores from the GRLIB
+ VHDL IP core library.
+
+config GPIO_HISI
+ tristate "HiSilicon GPIO controller driver"
+ depends on ARM64 || COMPILE_TEST
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ Say Y or M here to build support for the HiSilicon GPIO controller
+ driver GPIO block.
+ This GPIO controller supports double-edge interrupt and multi-core
+ concurrent access.
+
+config GPIO_HLWD
+ tristate "Nintendo Wii (Hollywood) GPIO"
+ depends on OF_GPIO
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ Select this to support the GPIO controller of the Nintendo Wii.
+
+ If unsure, say N.
+
+config GPIO_ICH
+ tristate "Intel ICH GPIO"
+ depends on X86
+ depends on LPC_ICH
+ help
+ Say yes here to support the GPIO functionality of a number of Intel
+ ICH-based chipsets. Currently supported devices: ICH6, ICH7, ICH8
+ ICH9, ICH10, Series 5/3400 (e.g. Ibex Peak), Series 6/C200 (e.g.
+ Cougar Point), NM10 (Tiger Point), and 3100 (Whitmore Lake).
+
+ If unsure, say N.
+
+config GPIO_IMX_SCU
+ def_bool y
+ depends on IMX_SCU
+
+config GPIO_IXP4XX
+ bool "Intel IXP4xx GPIO"
+ depends on ARCH_IXP4XX
+ depends on OF
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Say yes here to support the GPIO functionality of a number of Intel
+ IXP4xx series of chips.
+
+ If unsure, say N.
+
+config GPIO_LOGICVC
+ tristate "Xylon LogiCVC GPIO support"
+ depends on MFD_SYSCON && OF
+ help
+ Say yes here to support GPIO functionality of the Xylon LogiCVC
+ programmable logic block.
+
+config GPIO_LOONGSON
+ bool "Loongson-2/3 GPIO support"
+ depends on CPU_LOONGSON2EF || CPU_LOONGSON64
+ help
+ Driver for GPIO functionality on Loongson-2F/3A/3B processors.
+
+config GPIO_LOONGSON_64BIT
+ tristate "Loongson 64 bit GPIO support"
+ depends on LOONGARCH || COMPILE_TEST
+ depends on OF_GPIO
+ select GPIO_GENERIC
+ help
+ Say yes here to support the GPIO functionality of a number of
+ Loongson series of chips. The Loongson GPIO controller supports
+ up to 60 GPIOS in total, 4 of which are dedicated GPIO pins, and
+ the remaining 56 are reused with other functions, with edge or
+ level triggered interrupts.
+
+config GPIO_LPC18XX
+ tristate "NXP LPC18XX/43XX GPIO support"
+ default y if ARCH_LPC18XX
+ depends on OF_GPIO && (ARCH_LPC18XX || COMPILE_TEST)
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Select this option to enable GPIO driver for
+ NXP LPC18XX/43XX devices.
+
+config GPIO_LPC32XX
+ tristate "NXP LPC32XX GPIO support"
+ depends on OF_GPIO && (ARCH_LPC32XX || COMPILE_TEST)
+ help
+ Select this option to enable GPIO driver for
+ NXP LPC32XX devices.
+
+config GPIO_MB86S7X
+ tristate "GPIO support for Fujitsu MB86S7x Platforms"
+ help
+ Say yes here to support the GPIO controller in Fujitsu MB86S70 SoCs.
+
+config GPIO_MENZ127
+ tristate "MEN 16Z127 GPIO support"
+ depends on MCB
+ select GPIO_GENERIC
+ help
+ Say yes here to support the MEN 16Z127 GPIO Controller.
+
+config GPIO_MM_LANTIQ
+ bool "Lantiq Memory mapped GPIOs"
+ depends on LANTIQ && SOC_XWAY
+ select OF_GPIO_MM_GPIOCHIP
+ help
+ This enables support for memory mapped GPIOs on the External Bus Unit
+ (EBU) found on Lantiq SoCs. The GPIOs are output only as they are
+ created by attaching a 16-bit latch to the bus.
+
+config GPIO_MPC5200
+ def_bool y
+ depends on PPC_MPC52xx
+ select OF_GPIO_MM_GPIOCHIP
+
+config GPIO_MPC8XXX
+ bool "MPC512x/MPC8xxx/QorIQ GPIO support"
+ depends on PPC_MPC512x || PPC_MPC831x || PPC_MPC834x || PPC_MPC837x || \
+ FSL_SOC_BOOKE || PPC_86xx || ARCH_LAYERSCAPE || ARM || \
+ COMPILE_TEST
+ select GPIO_GENERIC
+ select IRQ_DOMAIN
+ help
+ Say Y here if you're going to use hardware that connects to the
+ MPC512x/831x/834x/837x/8572/8610/QorIQ GPIOs.
+
+config GPIO_MT7621
+ bool "Mediatek MT7621 GPIO Support"
+ depends on SOC_MT7620 || SOC_MT7621 || COMPILE_TEST
+ depends on OF_GPIO
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support the Mediatek MT7621 SoC GPIO device.
+
+config GPIO_MVEBU
+ def_bool y
+ depends on PLAT_ORION || ARCH_MVEBU
+ depends on OF_GPIO
+ select GENERIC_IRQ_CHIP
+ select REGMAP_MMIO
+
+config GPIO_MXC
+ tristate "i.MX GPIO support"
+ depends on ARCH_MXC || COMPILE_TEST
+ select GPIO_GENERIC
+ select GENERIC_IRQ_CHIP
+
+config GPIO_MXS
+ bool "Freescale MXS GPIO support" if COMPILE_TEST
+ depends on ARCH_MXS || COMPILE_TEST
+ default y if ARCH_MXS
+ select GPIO_GENERIC
+ select GENERIC_IRQ_CHIP
+
+config GPIO_OCTEON
+ tristate "Cavium OCTEON GPIO"
+ depends on CAVIUM_OCTEON_SOC
+ default y
+ help
+ Say yes here to support the on-chip GPIO lines on the OCTEON
+ family of SOCs.
+
+config GPIO_OMAP
+ tristate "TI OMAP GPIO support" if ARCH_OMAP2PLUS || COMPILE_TEST
+ default y if ARCH_OMAP
+ depends on ARM
+ select GENERIC_IRQ_CHIP
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to enable GPIO support for TI OMAP SoCs.
+
+config GPIO_PL061
+ tristate "PrimeCell PL061 GPIO support"
+ depends on ARM_AMBA
+ select IRQ_DOMAIN
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support the PrimeCell PL061 GPIO device.
+
+config GPIO_PXA
+ bool "PXA GPIO support"
+ depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST
+ help
+ Say yes here to support the PXA GPIO device.
+
+config GPIO_RCAR
+ tristate "Renesas R-Car and RZ/G GPIO support"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support GPIO on Renesas R-Car or RZ/G SoCs.
+
+config GPIO_RDA
+ bool "RDA Micro GPIO controller support"
+ depends on ARCH_RDA || COMPILE_TEST
+ depends on OF_GPIO
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ Say Y here to support RDA Micro GPIO controller.
+
+config GPIO_REALTEK_OTTO
+ tristate "Realtek Otto GPIO support"
+ depends on MACH_REALTEK_RTL
+ default MACH_REALTEK_RTL
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ The GPIO controller on the Otto MIPS platform supports up to two
+ banks of 32 GPIOs, with edge triggered interrupts. The 32 GPIOs
+ are grouped in four 8-bit wide ports.
+
+ When built as a module, the module will be called realtek_otto_gpio.
+
+config GPIO_REG
+ bool
+ help
+ A 32-bit single register GPIO fixed in/out implementation. This
+ can be used to represent any register as a set of GPIO signals.
+
+config GPIO_ROCKCHIP
+ tristate "Rockchip GPIO support"
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
+ select GENERIC_IRQ_CHIP
+ select GPIOLIB_IRQCHIP
+ default ARCH_ROCKCHIP
+ help
+ Say yes here to support GPIO on Rockchip SoCs.
+
+config GPIO_SAMA5D2_PIOBU
+ tristate "SAMA5D2 PIOBU GPIO support"
+ depends on MFD_SYSCON
+ depends on OF_GPIO
+ depends on ARCH_AT91 || COMPILE_TEST
+ select GPIO_SYSCON
+ help
+ Say yes here to use the PIOBU pins as GPIOs.
+
+ PIOBU pins on the SAMA5D2 can be used as GPIOs.
+ The difference from regular GPIOs is that they
+ maintain their value during backup/self-refresh.
+
+config GPIO_SIFIVE
+ tristate "SiFive GPIO support"
+ depends on OF_GPIO
+ select IRQ_DOMAIN_HIERARCHY
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ select REGMAP_MMIO
+ help
+ Say yes here to support the GPIO device on SiFive SoCs.
+
+config GPIO_SIOX
+ tristate "SIOX GPIO support"
+ depends on SIOX
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support SIOX I/O devices. These are units connected
+ via a SIOX bus and have a number of fixed-direction I/O lines.
+
+config GPIO_SNPS_CREG
+ bool "Synopsys GPIO via CREG (Control REGisters) driver"
+ depends on ARC || COMPILE_TEST
+ depends on OF_GPIO
+ help
+ This driver supports GPIOs via CREG on various Synopsys SoCs.
+ This is a single-register MMIO GPIO driver for complex cases
+ where only several fields in register belong to GPIO lines and
+ each GPIO line owns a field with different length and on/off value.
+
+config GPIO_SPEAR_SPICS
+ bool "ST SPEAr13xx SPI Chip Select as GPIO support"
+ depends on PLAT_SPEAR
+ select GENERIC_IRQ_CHIP
+ help
+ Say yes here to support ST SPEAr SPI Chip Select as GPIO device.
+
+config GPIO_SPRD
+ tristate "Spreadtrum GPIO support"
+ depends on ARCH_SPRD || COMPILE_TEST
+ depends on OF_GPIO
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support Spreadtrum GPIO device.
+
+config GPIO_STP_XWAY
+ bool "XWAY STP GPIOs"
+ depends on SOC_XWAY || COMPILE_TEST
+ depends on OF_GPIO
+ help
+ This enables support for the Serial To Parallel (STP) unit found on
+ XWAY SoC. The STP allows the SoC to drive a shift registers cascade,
+ that can be up to 24 bits. This peripheral is aimed at driving LEDs.
+ Some of the GPIOs/LEDs can be auto updated by the SoC with DSL and
+ phy status.
+
+config GPIO_SYSCON
+ tristate "GPIO based on SYSCON"
+ depends on MFD_SYSCON && OF
+ help
+ Say yes here to support GPIO functionality though SYSCON driver.
+
+config GPIO_TANGIER
+ tristate
+ select GPIOLIB_IRQCHIP
+ help
+ GPIO support for Intel Tangier and compatible platforms.
+ Currently supported:
+ - Elkhart Lake
+ - Merrifield
+
+ If built as a module its name will be gpio-tangier.
+
+config GPIO_TB10X
+ bool
+ select GPIO_GENERIC
+ select GENERIC_IRQ_CHIP
+ select OF_GPIO
+
+config GPIO_TEGRA
+ tristate "NVIDIA Tegra GPIO support"
+ default ARCH_TEGRA
+ depends on ARCH_TEGRA || COMPILE_TEST
+ depends on OF_GPIO
+ select GPIOLIB_IRQCHIP
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Say yes here to support GPIO pins on NVIDIA Tegra SoCs.
+
+config GPIO_TEGRA186
+ tristate "NVIDIA Tegra186 GPIO support"
+ default ARCH_TEGRA_186_SOC || ARCH_TEGRA_194_SOC
+ depends on ARCH_TEGRA_186_SOC || ARCH_TEGRA_194_SOC || COMPILE_TEST
+ depends on OF_GPIO
+ select GPIOLIB_IRQCHIP
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Say yes here to support GPIO pins on NVIDIA Tegra186 SoCs.
+
+config GPIO_TS4800
+ tristate "TS-4800 DIO blocks and compatibles"
+ depends on OF_GPIO
+ depends on SOC_IMX51 || COMPILE_TEST
+ select GPIO_GENERIC
+ help
+ This driver support TS-4800 FPGA GPIO controllers.
+
+config GPIO_THUNDERX
+ tristate "Cavium ThunderX/OCTEON-TX GPIO"
+ depends on ARCH_THUNDER || (64BIT && COMPILE_TEST)
+ depends on PCI_MSI
+ select GPIOLIB_IRQCHIP
+ select IRQ_DOMAIN_HIERARCHY
+ select IRQ_FASTEOI_HIERARCHY_HANDLERS
+ help
+ Say yes here to support the on-chip GPIO lines on the ThunderX
+ and OCTEON-TX families of SoCs.
+
+config GPIO_UNIPHIER
+ tristate "UniPhier GPIO support"
+ depends on ARCH_UNIPHIER || COMPILE_TEST
+ depends on OF_GPIO
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Say yes here to support UniPhier GPIOs.
+
+config GPIO_VF610
+ def_bool y
+ depends on ARCH_MXC
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support i.MX or Vybrid vf610 GPIOs.
+
+config GPIO_VISCONTI
+ tristate "Toshiba Visconti GPIO support"
+ depends on ARCH_VISCONTI || COMPILE_TEST
+ depends on OF_GPIO
+ select GPIOLIB_IRQCHIP
+ select GPIO_GENERIC
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Say yes here to support GPIO on Tohisba Visconti.
+
+config GPIO_WCD934X
+ tristate "Qualcomm Technologies Inc WCD9340/WCD9341 GPIO controller driver"
+ depends on MFD_WCD934X && OF_GPIO
+ help
+ This driver is to support GPIO block found on the Qualcomm Technologies
+ Inc WCD9340/WCD9341 Audio Codec.
+
+config GPIO_XGENE
+ bool "APM X-Gene GPIO controller support"
+ depends on ARM64 && OF_GPIO
+ help
+ This driver is to support the GPIO block within the APM X-Gene SoC
+ platform's generic flash controller. The GPIO pins are muxed with
+ the generic flash controller's address and data pins. Say yes
+ here to enable the GFC GPIO functionality.
+
+config GPIO_XGENE_SB
+ tristate "APM X-Gene GPIO standby controller support"
+ depends on (ARCH_XGENE || COMPILE_TEST)
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ This driver supports the GPIO block within the APM X-Gene
+ Standby Domain. Say yes here to enable the GPIO functionality.
+
+config GPIO_XILINX
+ tristate "Xilinx GPIO support"
+ select GPIOLIB_IRQCHIP
+ depends on OF_GPIO
+ help
+ Say yes here to support the Xilinx FPGA GPIO device.
+
+config GPIO_XLP
+ tristate "Cavium ThunderX2 GPIO support"
+ depends on ARCH_THUNDER2 || COMPILE_TEST
+ select GPIOLIB_IRQCHIP
+ help
+ This driver provides support for GPIO interface on Cavium's ThunderX2
+ CN99XX SoCs (Originally from Netlogic XLP).
+
+ If unsure, say N.
+
+config GPIO_XTENSA
+ bool "Xtensa GPIO32 support"
+ depends on XTENSA
+ depends on HAVE_XTENSA_GPIO32
+ depends on !SMP
+ help
+ Say yes here to support the Xtensa internal GPIO32 IMPWIRE (input)
+ and EXPSTATE (output) ports.
+
+config GPIO_ZEVIO
+ bool "LSI ZEVIO SoC memory mapped GPIOs"
+ depends on ARM
+ help
+ Say yes here to support the GPIO controller in LSI ZEVIO SoCs.
+
+config GPIO_ZYNQ
+ tristate "Xilinx Zynq GPIO support"
+ depends on ARCH_ZYNQ || ARCH_ZYNQMP
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support Xilinx Zynq GPIO controller.
+
+config GPIO_ZYNQMP_MODEPIN
+ tristate "ZynqMP ps-mode pin GPIO configuration driver"
+ depends on ZYNQMP_FIRMWARE
+ default ZYNQMP_FIRMWARE
+ help
+ Say yes here to support the ZynqMP ps-mode pin GPIO configuration
+ driver.
+
+ This ps-mode pin GPIO driver is based on GPIO framework. PS_MODE
+ is 4-bits boot mode pins. It sets and gets the status of
+ the ps-mode pin. Every pin can be configured as input/output.
+
+config GPIO_LOONGSON1
+ tristate "Loongson1 GPIO support"
+ depends on MACH_LOONGSON32
+ select GPIO_GENERIC
+ help
+ Say Y or M here to support GPIO on Loongson1 SoCs.
+
+config GPIO_AMD_FCH
+ tristate "GPIO support for AMD Fusion Controller Hub (G-series SOCs)"
+ help
+ This option enables driver for GPIO on AMD's Fusion Controller Hub,
+ as found on G-series SOCs (e.g. GX-412TC).
+
+ Note: This driver doesn't register itself automatically, as it
+ needs to be provided with platform-specific configuration.
+ (See e.g. CONFIG_PCENGINES_APU2.)
+
+config GPIO_MSC313
+ bool "MStar MSC313 GPIO support"
+ depends on ARCH_MSTARV7
+ default ARCH_MSTARV7
+ select GPIOLIB_IRQCHIP
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Say Y here to support the main GPIO block on MStar/SigmaStar
+ ARMv7-based SoCs.
+
+config GPIO_IDT3243X
+ tristate "IDT 79RC3243X GPIO support"
+ depends on MIKROTIK_RB532 || COMPILE_TEST
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ Select this option to enable GPIO driver for
+ IDT 79RC3243X-based devices like Mikrotik RB532.
+
+ To compile this driver as a module, choose M here: the module will
+ be called gpio-idt3243x.
+
+endmenu
+
+menu "Port-mapped I/O GPIO drivers"
+ depends on X86 && HAS_IOPORT # I/O space access
+
+config GPIO_VX855
+ tristate "VIA VX855/VX875 GPIO"
+ depends on PCI
+ select MFD_CORE
+ select MFD_VX855
+ help
+ Support access to the VX855/VX875 GPIO lines through the GPIO library.
+
+ This driver provides common support for accessing the device.
+ Additional drivers must be enabled in order to use the
+ functionality of the device.
+
+config GPIO_I8255
+ tristate
+ select GPIO_REGMAP
+ help
+ Enables support for the i8255 interface library functions. The i8255
+ interface library provides functions to facilitate communication with
+ interfaces compatible with the venerable Intel 8255 Programmable
+ Peripheral Interface (PPI). The Intel 8255 PPI chip was first released
+ in the early 1970s but compatible interfaces are nowadays typically
+ found embedded in larger VLSI processing chips and FPGA components.
+
+ If built as a module its name will be gpio-i8255.
+
+config GPIO_104_DIO_48E
+ tristate "ACCES 104-DIO-48E GPIO support"
+ depends on PC104
+ select ISA_BUS_API
+ select REGMAP_MMIO
+ select REGMAP_IRQ
+ select GPIOLIB_IRQCHIP
+ select GPIO_I8255
+ select I8254
+ help
+ Enables GPIO support for the ACCES 104-DIO-48E series (104-DIO-48E,
+ 104-DIO-24E). The base port addresses for the devices may be
+ configured via the base module parameter. The interrupt line numbers
+ for the devices may be configured via the irq module parameter.
+
+config GPIO_104_IDIO_16
+ tristate "ACCES 104-IDIO-16 GPIO support"
+ depends on PC104
+ select ISA_BUS_API
+ select REGMAP_MMIO
+ select GPIO_IDIO_16
+ help
+ Enables GPIO support for the ACCES 104-IDIO-16 family (104-IDIO-16,
+ 104-IDIO-16E, 104-IDO-16, 104-IDIO-8, 104-IDIO-8E, 104-IDO-8). The
+ base port addresses for the devices may be configured via the base
+ module parameter. The interrupt line numbers for the devices may be
+ configured via the irq module parameter.
+
+config GPIO_104_IDI_48
+ tristate "ACCES 104-IDI-48 GPIO support"
+ depends on PC104
+ select ISA_BUS_API
+ select REGMAP_MMIO
+ select REGMAP_IRQ
+ select GPIOLIB_IRQCHIP
+ select GPIO_REGMAP
+ help
+ Enables GPIO support for the ACCES 104-IDI-48 family (104-IDI-48A,
+ 104-IDI-48AC, 104-IDI-48B, 104-IDI-48BC). The base port addresses for
+ the devices may be configured via the base module parameter. The
+ interrupt line numbers for the devices may be configured via the irq
+ module parameter.
+
+config GPIO_F7188X
+ tristate "Fintek and Nuvoton Super-I/O GPIO support"
+ help
+ This option enables support for GPIOs found on Fintek Super-I/O
+ chips F71869, F71869A, F71882FG, F71889F and F81866.
+ As well as Nuvoton Super-I/O chip NCT6126D.
+
+ To compile this driver as a module, choose M here: the module will
+ be called f7188x-gpio.
+
+config GPIO_GPIO_MM
+ tristate "Diamond Systems GPIO-MM GPIO support"
+ depends on PC104
+ select ISA_BUS_API
+ select REGMAP_MMIO
+ select GPIO_I8255
+ help
+ Enables GPIO support for the Diamond Systems GPIO-MM and GPIO-MM-12.
+
+ The Diamond Systems GPIO-MM device features 48 lines of digital I/O
+ via the emulation of dual 82C55A PPI chips. This driver provides GPIO
+ support for these 48 channels of digital I/O.
+
+ The base port addresses for the devices may be configured via the base
+ array module parameter.
+
+config GPIO_IT87
+ tristate "IT87xx GPIO support"
+ help
+ Say yes here to support GPIO functionality of IT87xx Super I/O chips.
+
+ This driver is tested with ITE IT8728 and IT8732 Super I/O chips, and
+ supports the IT8761E, IT8613, IT8620E, and IT8628E Super I/O chips as
+ well.
+
+ To compile this driver as a module, choose M here: the module will
+ be called gpio_it87.
+
+config GPIO_SCH
+ tristate "Intel SCH/TunnelCreek/Centerton/Quark X1000 GPIO"
+ depends on (X86 || COMPILE_TEST) && ACPI
+ depends on LPC_SCH
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support GPIO interface on Intel Poulsbo SCH,
+ Intel Tunnel Creek processor, Intel Centerton processor or
+ Intel Quark X1000 SoC.
+
+ The Intel SCH contains a total of 14 GPIO pins. Ten GPIOs are
+ powered by the core power rail and are turned off during sleep
+ modes (S3 and higher). The remaining four GPIOs are powered by
+ the Intel SCH suspend power supply. These GPIOs remain
+ active during S3. The suspend-powered GPIOs can be used to wake the
+ system from the Suspend-to-RAM state.
+
+ The Intel Tunnel Creek processor has 5 GPIOs powered by the
+ core power rail and 9 from suspend power supply.
+
+ The Intel Centerton processor has a total of 30 GPIO pins.
+ Twenty-one are powered by the core power rail and 9 from the
+ suspend power supply.
+
+ The Intel Quark X1000 SoC has 2 GPIOs powered by the core
+ power well and 6 from the suspend power well.
+
+config GPIO_SCH311X
+ tristate "SMSC SCH311x SuperI/O GPIO"
+ help
+ Driver to enable the GPIOs found on SMSC SMSC SCH3112, SCH3114 and
+ SCH3116 "Super I/O" chipsets.
+
+ To compile this driver as a module, choose M here: the module will
+ be called gpio-sch311x.
+
+config GPIO_TS5500
+ tristate "TS-5500 DIO blocks and compatibles"
+ depends on TS5500 || COMPILE_TEST
+ help
+ This driver supports Digital I/O exposed by pin blocks found on some
+ Technologic Systems platforms. It includes, but is not limited to, 3
+ blocks of the TS-5500: DIO1, DIO2 and the LCD port, and the TS-5600
+ LCD port.
+
+config GPIO_WINBOND
+ tristate "Winbond Super I/O GPIO support"
+ select ISA_BUS_API
+ help
+ This option enables support for GPIOs found on Winbond Super I/O
+ chips.
+ Currently, only W83627UHG (also known as Nuvoton NCT6627UD) is
+ supported.
+
+ You will need to provide a module parameter "gpios", or a
+ boot-time parameter "gpio_winbond.gpios" with a bitmask of GPIO
+ ports to enable (bit 0 is GPIO1, bit 1 is GPIO2, etc.).
+
+ To compile this driver as a module, choose M here: the module will
+ be called gpio-winbond.
+
+config GPIO_WS16C48
+ tristate "WinSystems WS16C48 GPIO support"
+ select ISA_BUS_API
+ select REGMAP_IRQ
+ select REGMAP_MMIO
+ select GPIOLIB_IRQCHIP
+ select GPIO_REGMAP
+ help
+ Enables GPIO support for the WinSystems WS16C48. The base port
+ addresses for the devices may be configured via the base module
+ parameter. The interrupt line numbers for the devices may be
+ configured via the irq module parameter.
+
+endmenu
+
+menu "I2C GPIO expanders"
+ depends on I2C
+
+config GPIO_ADNP
+ tristate "Avionic Design N-bit GPIO expander"
+ depends on OF_GPIO
+ select GPIOLIB_IRQCHIP
+ help
+ This option enables support for N GPIOs found on Avionic Design
+ I2C GPIO expanders. The register space will be extended by powers
+ of two, so the controller will need to accommodate for that. For
+ example: if a controller provides 48 pins, 6 registers will be
+ enough to represent all pins, but the driver will assume a
+ register layout for 64 pins (8 registers).
+
+config GPIO_FXL6408
+ tristate "FXL6408 I2C GPIO expander"
+ select GPIO_REGMAP
+ select REGMAP_I2C
+ help
+ GPIO driver for Fairchild Semiconductor FXL6408 GPIO expander.
+
+ To compile this driver as a module, choose M here: the module will
+ be called gpio-fxl6408.
+
+config GPIO_DS4520
+ tristate "DS4520 I2C GPIO expander"
+ select REGMAP_I2C
+ select GPIO_REGMAP
+ help
+ GPIO driver for ADI DS4520 I2C-based GPIO expander.
+ Say yes here to enable the GPIO driver for the ADI DS4520 chip.
+
+ To compile this driver as a module, choose M here: the module will
+ be called gpio-ds4520.
+
+config GPIO_GW_PLD
+ tristate "Gateworks PLD GPIO Expander"
+ depends on OF_GPIO
+ help
+ Say yes here to provide access to the Gateworks I2C PLD GPIO
+ Expander. This is used at least on the Cambria GW2358-4.
+
+config GPIO_MAX7300
+ tristate "Maxim MAX7300 GPIO expander"
+ select GPIO_MAX730X
+ help
+ GPIO driver for Maxim MAX7300 I2C-based GPIO expander.
+
+config GPIO_MAX732X
+ tristate "MAX7319, MAX7320-7327 I2C Port Expanders"
+ help
+ Say yes here to support the MAX7319, MAX7320-7327 series of I2C
+ Port Expanders. Each IO port on these chips has a fixed role of
+ Input (designated by 'I'), Push-Pull Output ('O'), or Open-Drain
+ Input and Output (designed by 'P'). The combinations are listed
+ below:
+
+ 8 bits: max7319 (8I), max7320 (8O), max7321 (8P),
+ max7322 (4I4O), max7323 (4P4O)
+
+ 16 bits: max7324 (8I8O), max7325 (8P8O),
+ max7326 (4I12O), max7327 (4P12O)
+
+ Board setup code must specify the model to use, and the start
+ number for these GPIOs.
+
+config GPIO_MAX732X_IRQ
+ bool "Interrupt controller support for MAX732x"
+ depends on GPIO_MAX732X=y
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to enable the max732x to be used as an interrupt
+ controller. It requires the driver to be built in the kernel.
+
+config GPIO_PCA953X
+ tristate "PCA95[357]x, PCA9698, TCA64xx, and MAX7310 I/O ports"
+ select REGMAP_I2C
+ help
+ Say yes here to provide access to several register-oriented
+ SMBus I/O expanders, made mostly by NXP or TI. Compatible
+ models include:
+
+ 4 bits: pca9536, pca9537
+
+ 8 bits: max7310, max7315, pca6107, pca9534, pca9538, pca9554,
+ pca9556, pca9557, pca9574, tca6408, tca9554, xra1202
+
+ 16 bits: max7312, max7313, pca9535, pca9539, pca9555, pca9575,
+ tca6416
+
+ 24 bits: tca6424
+
+ 40 bits: pca9505, pca9698
+
+config GPIO_PCA953X_IRQ
+ bool "Interrupt controller support for PCA953x"
+ depends on GPIO_PCA953X
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to enable the pca953x to be used as an interrupt
+ controller.
+
+config GPIO_PCA9570
+ tristate "PCA9570 4-Bit I2C GPO expander"
+ help
+ Say yes here to enable the GPO driver for the NXP PCA9570 chip.
+
+ To compile this driver as a module, choose M here: the module will
+ be called gpio-pca9570.
+
+config GPIO_PCF857X
+ tristate "PCF857x, PCA{85,96}7x, and MAX732[89] I2C GPIO expanders"
+ select GPIOLIB_IRQCHIP
+ select IRQ_DOMAIN
+ help
+ Say yes here to provide access to most "quasi-bidirectional" I2C
+ GPIO expanders used for additional digital outputs or inputs.
+ Most of these parts are from NXP, though TI is a second source for
+ some of them. Compatible models include:
+
+ 8 bits: pcf8574, pcf8574a, pca8574, pca8574a,
+ pca9670, pca9672, pca9674, pca9674a,
+ max7328, max7329
+
+ 16 bits: pcf8575, pcf8575c, pca8575,
+ pca9671, pca9673, pca9675
+
+ Your board setup code will need to declare the expanders in
+ use, and assign numbers to the GPIOs they expose. Those GPIOs
+ can then be used from drivers and other kernel code, just like
+ other GPIOs, but only accessible from task contexts.
+
+ This driver provides an in-kernel interface to those GPIOs using
+ platform-neutral GPIO calls.
+
+config GPIO_TPIC2810
+ tristate "TPIC2810 8-Bit I2C GPO expander"
+ help
+ Say yes here to enable the GPO driver for the TI TPIC2810 chip.
+
+ To compile this driver as a module, choose M here: the module will
+ be called gpio-tpic2810.
+
+config GPIO_TS4900
+ tristate "Technologic Systems FPGA I2C GPIO"
+ depends on SOC_IMX6 || COMPILE_TEST
+ select REGMAP_I2C
+ help
+ Say yes here to enabled the GPIO driver for Technologic's FPGA core.
+ Series supported include TS-4100, TS-4900, TS-7970 and TS-7990.
+
+endmenu
+
+menu "MFD GPIO expanders"
+
+config GPIO_ADP5520
+ tristate "GPIO Support for ADP5520 PMIC"
+ depends on PMIC_ADP5520
+ help
+ This option enables support for on-chip GPIO found
+ on Analog Devices ADP5520 PMICs.
+
+config GPIO_ALTERA_A10SR
+ tristate "Altera Arria10 System Resource GPIO"
+ depends on MFD_ALTERA_A10SR
+ help
+ Driver for Arria10 Development Kit GPIO expansion which
+ includes reads of pushbuttons and DIP switches as well
+ as writes to LEDs.
+
+config GPIO_ARIZONA
+ tristate "Wolfson Microelectronics Arizona class devices"
+ depends on MFD_ARIZONA
+ help
+ Support for GPIOs on Wolfson Arizona class devices.
+
+config GPIO_BD71815
+ tristate "ROHM BD71815 PMIC GPIO support"
+ depends on MFD_ROHM_BD71828
+ help
+ Support for GPO(s) on ROHM BD71815 PMIC. There are two GPOs
+ available on the ROHM PMIC.
+
+ This driver can also be built as a module. If so, the module
+ will be called gpio-bd71815.
+
+config GPIO_BD71828
+ tristate "ROHM BD71828 GPIO support"
+ depends on MFD_ROHM_BD71828
+ help
+ Support for GPIOs on ROHM BD71828 PMIC. There are three GPIOs
+ available on the ROHM PMIC in total. The GPIOs are limited to
+ outputs only and pins must be configured to GPIO outputs by
+ OTP. Enable this only if you want to use these pins as outputs.
+
+ This driver can also be built as a module. If so, the module
+ will be called gpio-bd71828.
+
+config GPIO_BD9571MWV
+ tristate "ROHM BD9571 GPIO support"
+ depends on MFD_BD9571MWV
+ help
+ Support for GPIOs on ROHM BD9571 PMIC. There are two GPIOs
+ available on the ROHM PMIC in total, both of which can also
+ generate interrupts.
+
+ This driver can also be built as a module. If so, the module
+ will be called gpio-bd9571mwv.
+
+config GPIO_CRYSTAL_COVE
+ tristate "GPIO support for Crystal Cove PMIC"
+ depends on (X86 || COMPILE_TEST) && INTEL_SOC_PMIC
+ select GPIOLIB_IRQCHIP
+ help
+ Support for GPIO pins on Crystal Cove PMIC.
+
+ Say Yes if you have a Intel SoC-based tablet with Crystal Cove PMIC
+ inside.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-crystalcove.
+
+config GPIO_CS5535
+ tristate "AMD CS5535/CS5536 GPIO support"
+ depends on X86 || MIPS || COMPILE_TEST
+ depends on MFD_CS5535
+ help
+ The AMD CS5535 and CS5536 southbridges support 28 GPIO pins that
+ can be used for quite a number of things. The CS5535/6 is found on
+ AMD Geode and Lemote Yeeloong devices.
+
+ If unsure, say N.
+
+config GPIO_DA9052
+ tristate "Dialog DA9052 GPIO"
+ depends on PMIC_DA9052
+ help
+ Say yes here to enable the GPIO driver for the DA9052 chip.
+
+config GPIO_DA9055
+ tristate "Dialog Semiconductor DA9055 GPIO"
+ depends on MFD_DA9055
+ help
+ Say yes here to enable the GPIO driver for the DA9055 chip.
+
+ The Dialog DA9055 PMIC chip has 3 GPIO pins that can be
+ be controlled by this driver.
+
+ If driver is built as a module it will be called gpio-da9055.
+
+config GPIO_DLN2
+ tristate "Diolan DLN2 GPIO support"
+ depends on MFD_DLN2
+ select GPIOLIB_IRQCHIP
+
+ help
+ Select this option to enable GPIO driver for the Diolan DLN2
+ board.
+
+ This driver can also be built as a module. If so, the module
+ will be called gpio-dln2.
+
+config HTC_EGPIO
+ bool "HTC EGPIO support"
+ depends on ARM
+ help
+ This driver supports the CPLD egpio chip present on
+ several HTC phones. It provides basic support for input
+ pins, output pins, and IRQs.
+
+config GPIO_ELKHARTLAKE
+ tristate "Intel Elkhart Lake PSE GPIO support"
+ depends on X86 || COMPILE_TEST
+ select GPIO_TANGIER
+ help
+ Select this option to enable GPIO support for Intel Elkhart Lake
+ PSE GPIO IP.
+
+ To compile this driver as a module, choose M here: the module will
+ be called gpio-elkhartlake.
+
+config GPIO_JANZ_TTL
+ tristate "Janz VMOD-TTL Digital IO Module"
+ depends on MFD_JANZ_CMODIO
+ help
+ This enables support for the Janz VMOD-TTL Digital IO module.
+ This driver provides support for driving the pins in output
+ mode only. Input mode is not supported.
+
+config GPIO_KEMPLD
+ tristate "Kontron ETX / COMexpress GPIO"
+ depends on MFD_KEMPLD
+ help
+ This enables support for the PLD GPIO interface on some Kontron ETX
+ and COMexpress (ETXexpress) modules.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-kempld.
+
+config GPIO_LJCA
+ tristate "INTEL La Jolla Cove Adapter GPIO support"
+ depends on MFD_LJCA
+ select GPIOLIB_IRQCHIP
+ default MFD_LJCA
+ help
+ Select this option to enable GPIO driver for the INTEL
+ La Jolla Cove Adapter (LJCA) board.
+
+ This driver can also be built as a module. If so, the module
+ will be called gpio-ljca.
+
+config GPIO_LP3943
+ tristate "TI/National Semiconductor LP3943 GPIO expander"
+ depends on MFD_LP3943
+ help
+ GPIO driver for LP3943 MFD.
+ LP3943 can be used as a GPIO expander which provides up to 16 GPIOs.
+ Open drain outputs are required for this usage.
+
+config GPIO_LP873X
+ tristate "TI LP873X GPO"
+ depends on MFD_TI_LP873X
+ help
+ This driver supports the GPO on TI Lp873x PMICs. 2 GPOs are present
+ on LP873X PMICs.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-lp873x.
+
+config GPIO_LP87565
+ tristate "TI LP87565 GPIO"
+ depends on MFD_TI_LP87565
+ help
+ This driver supports the GPIO on TI Lp873565 PMICs. 3 GPIOs are present
+ on LP87565 PMICs.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-lp87565.
+
+config GPIO_MADERA
+ tristate "Cirrus Logic Madera class codecs"
+ depends on PINCTRL_MADERA
+ help
+ Support for GPIOs on Cirrus Logic Madera class codecs.
+
+config GPIO_MAX77620
+ tristate "GPIO support for PMIC MAX77620 and MAX20024"
+ depends on MFD_MAX77620
+ select GPIOLIB_IRQCHIP
+ help
+ GPIO driver for MAX77620 and MAX20024 PMIC from Maxim Semiconductor.
+ MAX77620 PMIC has 8 pins that can be configured as GPIOs. The
+ driver also provides interrupt support for each of the GPIOs.
+ Say yes here to enable the max77620 to be used as GPIO controller.
+
+config GPIO_MAX77650
+ tristate "Maxim MAX77650/77651 GPIO support"
+ depends on MFD_MAX77650
+ help
+ GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor.
+ These chips have a single pin that can be configured as GPIO.
+
+config GPIO_PALMAS
+ bool "TI PALMAS series PMICs GPIO"
+ depends on MFD_PALMAS
+ help
+ Select this option to enable GPIO driver for the TI PALMAS
+ series chip family.
+
+config GPIO_PMIC_EIC_SPRD
+ tristate "Spreadtrum PMIC EIC support"
+ depends on MFD_SC27XX_PMIC || COMPILE_TEST
+ depends on OF_GPIO
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support Spreadtrum PMIC EIC device.
+
+config GPIO_RC5T583
+ bool "RICOH RC5T583 GPIO"
+ depends on MFD_RC5T583
+ help
+ Select this option to enable GPIO driver for the Ricoh RC5T583
+ chip family.
+ This driver provides the support for driving/reading the GPIO pins
+ of RC5T583 device through standard GPIO library.
+
+config GPIO_SL28CPLD
+ tristate "Kontron sl28cpld GPIO support"
+ depends on MFD_SL28CPLD || COMPILE_TEST
+ select GPIO_REGMAP
+ select GPIOLIB_IRQCHIP
+ select REGMAP_IRQ
+ help
+ This enables support for the GPIOs found on the Kontron sl28 CPLD.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-sl28cpld.
+
+config GPIO_STMPE
+ bool "STMPE GPIOs"
+ depends on MFD_STMPE
+ depends on OF_GPIO
+ select GPIOLIB_IRQCHIP
+ help
+ This enables support for the GPIOs found on the STMPE I/O
+ Expanders.
+
+config GPIO_TC3589X
+ bool "TC3589X GPIOs"
+ depends on MFD_TC3589X
+ depends on OF_GPIO
+ select GPIOLIB_IRQCHIP
+ help
+ This enables support for the GPIOs found on the TC3589X
+ I/O Expander.
+
+config GPIO_TIMBERDALE
+ bool "Support for timberdale GPIO IP"
+ depends on MFD_TIMBERDALE
+ help
+ Add support for the GPIO IP in the timberdale FPGA.
+
+config GPIO_TN48M_CPLD
+ tristate "Delta Networks TN48M switch CPLD GPIO driver"
+ depends on MFD_TN48M_CPLD
+ select GPIO_REGMAP
+ help
+ This enables support for the GPIOs found on the Delta
+ Networks TN48M switch Lattice CPLD. It provides 12 pins in total,
+ they are input-only or output-only type.
+
+ This driver can also be built as a module. If so, the
+ module will be called gpio-tn48m.
+
+config GPIO_TPS65086
+ tristate "TI TPS65086 GPO"
+ depends on MFD_TPS65086
+ help
+ This driver supports the GPO on TI TPS65086x PMICs.
+
+config GPIO_TPS65218
+ tristate "TPS65218 GPIO"
+ depends on MFD_TPS65218
+ help
+ Select this option to enable GPIO driver for the TPS65218
+ chip family.
+
+config GPIO_TPS65219
+ tristate "TPS65219 GPIO"
+ depends on MFD_TPS65219
+ default MFD_TPS65219
+ help
+ Select this option to enable GPIO driver for the TPS65219 chip
+ family.
+ GPIO0 is statically configured as either input or output prior to
+ Linux boot. It is used for MULTI_DEVICE_ENABLE function. This setting
+ is statically configured by NVM. GPIO0 can't be used as a generic
+ GPIO. It's either a GPO when MULTI_DEVICE_EN=0 or a GPI when
+ MULTI_DEVICE_EN=1.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio_tps65219.
+
+config GPIO_TPS6586X
+ bool "TPS6586X GPIO"
+ depends on MFD_TPS6586X
+ help
+ Select this option to enable GPIO driver for the TPS6586X
+ chip family.
+
+config GPIO_TPS65910
+ bool "TPS65910 GPIO"
+ depends on MFD_TPS65910
+ help
+ Select this option to enable GPIO driver for the TPS65910
+ chip family.
+
+config GPIO_TPS65912
+ tristate "TI TPS65912 GPIO"
+ depends on MFD_TPS65912
+ help
+ This driver supports TPS65912 GPIO chip.
+
+config GPIO_TPS68470
+ tristate "TPS68470 GPIO"
+ depends on INTEL_SKL_INT3472
+ help
+ Select this option to enable GPIO driver for the TPS68470
+ chip family.
+ There are 7 GPIOs and few sensor-related GPIOs supported
+ by the TPS68470. While the 7 GPIOs can be configured as
+ input or output as appropriate, the sensor related GPIOs
+ are "output only" GPIOs.
+
+config GPIO_TQMX86
+ tristate "TQ-Systems QTMX86 GPIO"
+ depends on MFD_TQMX86 || COMPILE_TEST
+ depends on HAS_IOPORT_MAP
+ select GPIOLIB_IRQCHIP
+ help
+ This driver supports GPIO on the TQMX86 IO controller.
+
+config GPIO_TWL4030
+ tristate "TWL4030, TWL5030, and TPS659x0 GPIOs"
+ depends on TWL4030_CORE
+ help
+ Say yes here to access the GPIO signals of various multi-function
+ power management chips from Texas Instruments.
+
+config GPIO_TWL6040
+ tristate "TWL6040 GPO"
+ depends on TWL6040_CORE
+ help
+ Say yes here to access the GPO signals of twl6040
+ audio chip from Texas Instruments.
+
+config GPIO_WHISKEY_COVE
+ tristate "GPIO support for Whiskey Cove PMIC"
+ depends on (X86 || COMPILE_TEST) && INTEL_SOC_PMIC_BXTWC
+ select GPIOLIB_IRQCHIP
+ help
+ Support for GPIO pins on Whiskey Cove PMIC.
+
+ Say Yes if you have an Intel SoC-based tablet with Whiskey Cove PMIC
+ inside.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-wcove.
+
+config GPIO_WM831X
+ tristate "WM831x GPIOs"
+ depends on MFD_WM831X
+ help
+ Say yes here to access the GPIO signals of WM831x power management
+ chips from Wolfson Microelectronics.
+
+config GPIO_WM8350
+ tristate "WM8350 GPIOs"
+ depends on MFD_WM8350
+ help
+ Say yes here to access the GPIO signals of WM8350 power management
+ chips from Wolfson Microelectronics.
+
+config GPIO_WM8994
+ tristate "WM8994 GPIOs"
+ depends on MFD_WM8994
+ help
+ Say yes here to access the GPIO signals of WM8994 audio hub
+ CODECs from Wolfson Microelectronics.
+
+endmenu
+
+menu "PCI GPIO expanders"
+ depends on PCI
+
+config GPIO_AMD8111
+ tristate "AMD 8111 GPIO driver"
+ depends on X86 || COMPILE_TEST
+ depends on HAS_IOPORT_MAP
+ help
+ The AMD 8111 southbridge contains 32 GPIO pins which can be used.
+
+ Note that usually system firmware/ACPI handles GPIO pins on their
+ own and users might easily break their systems with uncareful usage
+ of this driver!
+
+ If unsure, say N
+
+config GPIO_BT8XX
+ tristate "BT8XX GPIO abuser"
+ depends on VIDEO_BT848=n
+ help
+ The BT8xx frame grabber chip has 24 GPIO pins that can be abused
+ as a cheap PCI GPIO card.
+
+ This chip can be found on Miro, Hauppauge and STB TV-cards.
+
+ The card needs to be physically altered for using it as a
+ GPIO card. For more information on how to build a GPIO card
+ from a BT8xx TV card, see the documentation file at
+ Documentation/driver-api/gpio/bt8xxgpio.rst
+
+ If unsure, say N.
+
+config GPIO_MERRIFIELD
+ tristate "Intel Merrifield GPIO support"
+ depends on X86_INTEL_MID
+ select GPIO_TANGIER
+ help
+ Say Y here to support Intel Merrifield GPIO.
+
+config GPIO_MLXBF
+ tristate "Mellanox BlueField SoC GPIO"
+ depends on (MELLANOX_PLATFORM && ARM64 && ACPI) || (64BIT && COMPILE_TEST)
+ select GPIO_GENERIC
+ help
+ Say Y here if you want GPIO support on Mellanox BlueField SoC.
+
+config GPIO_MLXBF2
+ tristate "Mellanox BlueField 2 SoC GPIO"
+ depends on (MELLANOX_PLATFORM && ARM64 && ACPI) || (64BIT && COMPILE_TEST)
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ Say Y here if you want GPIO support on Mellanox BlueField 2 SoC.
+
+config GPIO_MLXBF3
+ tristate "Mellanox BlueField 3 SoC GPIO"
+ depends on (MELLANOX_PLATFORM && ARM64) || COMPILE_TEST
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
+ help
+ Say Y if you want GPIO support on Mellanox BlueField 3 SoC.
+ This GPIO controller supports interrupt handling and enables the
+ manipulation of certain GPIO pins.
+ This controller should be used in parallel with pinctrl-mlxbf3 to
+ control the desired GPIOs.
+ This driver can also be built as a module called mlxbf3-gpio.
+
+config GPIO_ML_IOH
+ tristate "OKI SEMICONDUCTOR ML7213 IOH GPIO support"
+ depends on X86 || COMPILE_TEST
+ select GENERIC_IRQ_CHIP
+ help
+ ML7213 is companion chip for Intel Atom E6xx series.
+ This driver can be used for OKI SEMICONDUCTOR ML7213 IOH (Input/Output
+ Hub) which is for IVI (In-Vehicle Infotainment) use.
+ This driver can access the IOH's GPIO device.
+
+config GPIO_PCH
+ tristate "Intel EG20T PCH/LAPIS Semiconductor IOH (ML7223/ML7831) GPIO"
+ depends on X86_32 || MIPS || COMPILE_TEST
+ select GENERIC_IRQ_CHIP
+ help
+ This driver is for PCH (Platform Controller Hub) GPIO of Intel Topcliff,
+ which is an IOH (Input/Output Hub) for x86 embedded processor.
+ This driver can access PCH GPIO device.
+
+ This driver also can be used for LAPIS Semiconductor IOH (Input/
+ Output Hub), ML7223 and ML7831.
+ ML7223 IOH is for MP (Media Phone) use.
+ ML7831 IOH is for general purpose use.
+ ML7223/ML7831 is companion chip for Intel Atom E6xx series.
+ ML7223/ML7831 is completely compatible for Intel EG20T PCH.
+
+config GPIO_PCI_IDIO_16
+ tristate "ACCES PCI-IDIO-16 GPIO support"
+ select REGMAP_MMIO
+ select GPIO_IDIO_16
+ help
+ Enables GPIO support for the ACCES PCI-IDIO-16. An interrupt is
+ generated when any of the inputs change state (low to high or high to
+ low). Input filter control is not supported by this driver, and the
+ input filters are deactivated by this driver.
+
+config GPIO_PCIE_IDIO_24
+ tristate "ACCES PCIe-IDIO-24 GPIO support"
+ select REGMAP_IRQ
+ select REGMAP_MMIO
+ select GPIOLIB_IRQCHIP
+ select GPIO_REGMAP
+ help
+ Enables GPIO support for the ACCES PCIe-IDIO-24 family (PCIe-IDIO-24,
+ PCIe-IDI-24, PCIe-IDO-24, PCIe-IDIO-12). An interrupt is generated
+ when any of the inputs change state (low to high or high to low).
+ Input filter control is not supported by this driver, and the input
+ filters are deactivated by this driver.
+
+config GPIO_RDC321X
+ tristate "RDC R-321x GPIO support"
+ select MFD_CORE
+ select MFD_RDC321X
+ help
+ Support for the RDC R321x SoC GPIOs over southbridge
+ PCI configuration space.
+
+config GPIO_SODAVILLE
+ bool "Intel Sodaville GPIO support"
+ depends on X86 && OF
+ select GPIO_GENERIC
+ select GENERIC_IRQ_CHIP
+ help
+ Say Y here to support Intel Sodaville GPIO.
+
+endmenu
+
+menu "SPI GPIO expanders"
+ depends on SPI_MASTER
+
+config GPIO_74X164
+ tristate "74x164 serial-in/parallel-out 8-bits shift register"
+ depends on OF_GPIO
+ help
+ Driver for 74x164 compatible serial-in/parallel-out 8-outputs
+ shift registers. This driver can be used to provide access
+ to more GPIO outputs.
+
+config GPIO_MAX3191X
+ tristate "Maxim MAX3191x industrial serializer"
+ select CRC8
+ help
+ GPIO driver for Maxim MAX31910, MAX31911, MAX31912, MAX31913,
+ MAX31953 and MAX31963 industrial serializer, a daisy-chainable
+ chip to make 8 digital 24V inputs available via SPI. Supports
+ CRC checksums to guard against electromagnetic interference,
+ as well as undervoltage and overtemperature detection.
+
+config GPIO_MAX7301
+ tristate "Maxim MAX7301 GPIO expander"
+ select GPIO_MAX730X
+ help
+ GPIO driver for Maxim MAX7301 SPI-based GPIO expander.
+
+config GPIO_MC33880
+ tristate "Freescale MC33880 high-side/low-side switch"
+ help
+ SPI driver for Freescale MC33880 high-side/low-side switch.
+ This provides GPIO interface supporting inputs and outputs.
+
+config GPIO_PISOSR
+ tristate "Generic parallel-in/serial-out shift register"
+ help
+ GPIO driver for SPI compatible parallel-in/serial-out shift
+ registers. These are input only devices.
+
+config GPIO_XRA1403
+ tristate "EXAR XRA1403 16-bit GPIO expander"
+ select REGMAP_SPI
+ help
+ GPIO driver for EXAR XRA1403 16-bit SPI-based GPIO expander.
+
+config GPIO_MOXTET
+ tristate "Turris Mox Moxtet bus GPIO expander"
+ depends on MOXTET
+ help
+ Say yes here if you are building for the Turris Mox router.
+ This is the driver needed for configuring the GPIOs via the Moxtet
+ bus. For example the Mox module with SFP cage needs this driver
+ so that phylink can use corresponding GPIOs.
+
+endmenu
+
+menu "USB GPIO expanders"
+ depends on USB
+
+config GPIO_VIPERBOARD
+ tristate "Viperboard GPIO a & b support"
+ depends on MFD_VIPERBOARD
+ help
+ Say yes here to access the GPIO signals of Nano River
+ Technologies Viperboard. There are two GPIO chips on the
+ board: gpioa and gpiob.
+ See viperboard API specification and Nano
+ River Tech's viperboard.h for detailed meaning
+ of the module parameters.
+
+endmenu
+
+menu "Virtual GPIO drivers"
+
+config GPIO_AGGREGATOR
+ tristate "GPIO Aggregator"
+ help
+ Say yes here to enable the GPIO Aggregator, which provides a way to
+ aggregate existing GPIO lines into a new virtual GPIO chip.
+ This can serve the following purposes:
+ - Assign permissions for a collection of GPIO lines to a user,
+ - Export a collection of GPIO lines to a virtual machine,
+ - Provide a generic driver for a GPIO-operated device in an
+ industrial control context, to be operated from userspace using
+ the GPIO chardev interface.
+
+config GPIO_LATCH
+ tristate "GPIO latch driver"
+ help
+ Say yes here to enable a driver for GPIO multiplexers based on latches
+ connected to other GPIOs.
+
+config GPIO_MOCKUP
+ tristate "GPIO Testing Driver"
+ select IRQ_SIM
+ help
+ This enables GPIO Testing driver, which provides a way to test GPIO
+ subsystem through sysfs (or char device) and debugfs.
+ User could use it through the script in
+ tools/testing/selftests/gpio/gpio-mockup.sh. Reference the usage in
+ it.
+
+config GPIO_VIRTIO
+ tristate "VirtIO GPIO support"
+ depends on VIRTIO
+ select GPIOLIB_IRQCHIP
+ help
+ Say Y here to enable guest support for virtio-based GPIO controllers.
+
+ These virtual GPIOs can be routed to real GPIOs or attached to
+ simulators on the host (like QEMU).
+
+config GPIO_SIM
+ tristate "GPIO Simulator Module"
+ select IRQ_SIM
+ select CONFIGFS_FS
+ help
+ This enables the GPIO simulator - a configfs-based GPIO testing
+ driver.
+
+endmenu
+
+endif
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
new file mode 100644
index 0000000000..eb73b5d633
--- /dev/null
+++ b/drivers/gpio/Makefile
@@ -0,0 +1,197 @@
+# SPDX-License-Identifier: GPL-2.0
+# generic gpio support: platform drivers, dedicated expander chips, etc
+
+ccflags-$(CONFIG_DEBUG_GPIO) += -DDEBUG
+
+obj-$(CONFIG_GPIOLIB) += gpiolib.o
+obj-$(CONFIG_GPIOLIB) += gpiolib-devres.o
+obj-$(CONFIG_GPIOLIB) += gpiolib-legacy.o
+obj-$(CONFIG_OF_GPIO) += gpiolib-of.o
+obj-$(CONFIG_GPIO_CDEV) += gpiolib-cdev.o
+obj-$(CONFIG_GPIO_SYSFS) += gpiolib-sysfs.o
+obj-$(CONFIG_GPIO_ACPI) += gpiolib-acpi.o
+obj-$(CONFIG_GPIOLIB) += gpiolib-swnode.o
+
+# Device drivers. Generally keep list sorted alphabetically
+obj-$(CONFIG_GPIO_REGMAP) += gpio-regmap.o
+obj-$(CONFIG_GPIO_GENERIC) += gpio-generic.o
+
+# directly supported by gpio-generic
+gpio-generic-$(CONFIG_GPIO_GENERIC) += gpio-mmio.o
+
+obj-$(CONFIG_GPIO_104_DIO_48E) += gpio-104-dio-48e.o
+obj-$(CONFIG_GPIO_104_IDI_48) += gpio-104-idi-48.o
+obj-$(CONFIG_GPIO_104_IDIO_16) += gpio-104-idio-16.o
+obj-$(CONFIG_GPIO_74X164) += gpio-74x164.o
+obj-$(CONFIG_GPIO_74XX_MMIO) += gpio-74xx-mmio.o
+obj-$(CONFIG_GPIO_ADNP) += gpio-adnp.o
+obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o
+obj-$(CONFIG_GPIO_AGGREGATOR) += gpio-aggregator.o
+obj-$(CONFIG_GPIO_ALTERA_A10SR) += gpio-altera-a10sr.o
+obj-$(CONFIG_GPIO_ALTERA) += gpio-altera.o
+obj-$(CONFIG_GPIO_AMD8111) += gpio-amd8111.o
+obj-$(CONFIG_GPIO_AMD_FCH) += gpio-amd-fch.o
+obj-$(CONFIG_GPIO_AMDPT) += gpio-amdpt.o
+obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o
+obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o
+obj-$(CONFIG_GPIO_ASPEED_SGPIO) += gpio-aspeed-sgpio.o
+obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.o
+obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o
+obj-$(CONFIG_GPIO_BCM_XGS_IPROC) += gpio-xgs-iproc.o
+obj-$(CONFIG_GPIO_BD71815) += gpio-bd71815.o
+obj-$(CONFIG_GPIO_BD71828) += gpio-bd71828.o
+obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o
+obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o
+obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o
+obj-$(CONFIG_GPIO_CADENCE) += gpio-cadence.o
+obj-$(CONFIG_GPIO_CLPS711X) += gpio-clps711x.o
+obj-$(CONFIG_GPIO_SNPS_CREG) += gpio-creg-snps.o
+obj-$(CONFIG_GPIO_CRYSTAL_COVE) += gpio-crystalcove.o
+obj-$(CONFIG_GPIO_CS5535) += gpio-cs5535.o
+obj-$(CONFIG_GPIO_DA9052) += gpio-da9052.o
+obj-$(CONFIG_GPIO_DA9055) += gpio-da9055.o
+obj-$(CONFIG_GPIO_DAVINCI) += gpio-davinci.o
+obj-$(CONFIG_GPIO_DLN2) += gpio-dln2.o
+obj-$(CONFIG_GPIO_DS4520) += gpio-ds4520.o
+obj-$(CONFIG_GPIO_DWAPB) += gpio-dwapb.o
+obj-$(CONFIG_GPIO_EIC_SPRD) += gpio-eic-sprd.o
+obj-$(CONFIG_GPIO_ELKHARTLAKE) += gpio-elkhartlake.o
+obj-$(CONFIG_GPIO_EM) += gpio-em.o
+obj-$(CONFIG_GPIO_EN7523) += gpio-en7523.o
+obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o
+obj-$(CONFIG_GPIO_EXAR) += gpio-exar.o
+obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o
+obj-$(CONFIG_GPIO_FTGPIO010) += gpio-ftgpio010.o
+obj-$(CONFIG_GPIO_FXL6408) += gpio-fxl6408.o
+obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o
+obj-$(CONFIG_GPIO_GPIO_MM) += gpio-gpio-mm.o
+obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o
+obj-$(CONFIG_GPIO_GW_PLD) += gpio-gw-pld.o
+obj-$(CONFIG_GPIO_HISI) += gpio-hisi.o
+obj-$(CONFIG_GPIO_HLWD) += gpio-hlwd.o
+obj-$(CONFIG_HTC_EGPIO) += gpio-htc-egpio.o
+obj-$(CONFIG_GPIO_I8255) += gpio-i8255.o
+obj-$(CONFIG_GPIO_ICH) += gpio-ich.o
+obj-$(CONFIG_GPIO_IDIO_16) += gpio-idio-16.o
+obj-$(CONFIG_GPIO_IDT3243X) += gpio-idt3243x.o
+obj-$(CONFIG_GPIO_IMX_SCU) += gpio-imx-scu.o
+obj-$(CONFIG_GPIO_IT87) += gpio-it87.o
+obj-$(CONFIG_GPIO_IXP4XX) += gpio-ixp4xx.o
+obj-$(CONFIG_GPIO_JANZ_TTL) += gpio-janz-ttl.o
+obj-$(CONFIG_GPIO_KEMPLD) += gpio-kempld.o
+obj-$(CONFIG_GPIO_LATCH) += gpio-latch.o
+obj-$(CONFIG_GPIO_LJCA) += gpio-ljca.o
+obj-$(CONFIG_GPIO_LOGICVC) += gpio-logicvc.o
+obj-$(CONFIG_GPIO_LOONGSON1) += gpio-loongson1.o
+obj-$(CONFIG_GPIO_LOONGSON) += gpio-loongson.o
+obj-$(CONFIG_GPIO_LOONGSON_64BIT) += gpio-loongson-64bit.o
+obj-$(CONFIG_GPIO_LP3943) += gpio-lp3943.o
+obj-$(CONFIG_GPIO_LP873X) += gpio-lp873x.o
+obj-$(CONFIG_GPIO_LP87565) += gpio-lp87565.o
+obj-$(CONFIG_GPIO_LPC18XX) += gpio-lpc18xx.o
+obj-$(CONFIG_GPIO_LPC32XX) += gpio-lpc32xx.o
+obj-$(CONFIG_GPIO_MADERA) += gpio-madera.o
+obj-$(CONFIG_GPIO_MAX3191X) += gpio-max3191x.o
+obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o
+obj-$(CONFIG_GPIO_MAX7301) += gpio-max7301.o
+obj-$(CONFIG_GPIO_MAX730X) += gpio-max730x.o
+obj-$(CONFIG_GPIO_MAX732X) += gpio-max732x.o
+obj-$(CONFIG_GPIO_MAX77620) += gpio-max77620.o
+obj-$(CONFIG_GPIO_MAX77650) += gpio-max77650.o
+obj-$(CONFIG_GPIO_MB86S7X) += gpio-mb86s7x.o
+obj-$(CONFIG_GPIO_MC33880) += gpio-mc33880.o
+obj-$(CONFIG_GPIO_MENZ127) += gpio-menz127.o
+obj-$(CONFIG_GPIO_MERRIFIELD) += gpio-merrifield.o
+obj-$(CONFIG_GPIO_ML_IOH) += gpio-ml-ioh.o
+obj-$(CONFIG_GPIO_MLXBF) += gpio-mlxbf.o
+obj-$(CONFIG_GPIO_MLXBF2) += gpio-mlxbf2.o
+obj-$(CONFIG_GPIO_MLXBF3) += gpio-mlxbf3.o
+obj-$(CONFIG_GPIO_MM_LANTIQ) += gpio-mm-lantiq.o
+obj-$(CONFIG_GPIO_MOCKUP) += gpio-mockup.o
+obj-$(CONFIG_GPIO_MOXTET) += gpio-moxtet.o
+obj-$(CONFIG_GPIO_MPC5200) += gpio-mpc5200.o
+obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o
+obj-$(CONFIG_GPIO_MSC313) += gpio-msc313.o
+obj-$(CONFIG_GPIO_MT7621) += gpio-mt7621.o
+obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o
+obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o
+obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o
+obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o
+obj-$(CONFIG_GPIO_OMAP) += gpio-omap.o
+obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.o
+obj-$(CONFIG_GPIO_PCA953X) += gpio-pca953x.o
+obj-$(CONFIG_GPIO_PCA9570) += gpio-pca9570.o
+obj-$(CONFIG_GPIO_PCF857X) += gpio-pcf857x.o
+obj-$(CONFIG_GPIO_PCH) += gpio-pch.o
+obj-$(CONFIG_GPIO_PCIE_IDIO_24) += gpio-pcie-idio-24.o
+obj-$(CONFIG_GPIO_PCI_IDIO_16) += gpio-pci-idio-16.o
+obj-$(CONFIG_GPIO_PISOSR) += gpio-pisosr.o
+obj-$(CONFIG_GPIO_PL061) += gpio-pl061.o
+obj-$(CONFIG_GPIO_PMIC_EIC_SPRD) += gpio-pmic-eic-sprd.o
+obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o
+obj-$(CONFIG_GPIO_RASPBERRYPI_EXP) += gpio-raspberrypi-exp.o
+obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t583.o
+obj-$(CONFIG_GPIO_RCAR) += gpio-rcar.o
+obj-$(CONFIG_GPIO_RDA) += gpio-rda.o
+obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o
+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_ARCH_SA1100) += gpio-sa1100.o
+obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o
+obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o
+obj-$(CONFIG_GPIO_SCH) += gpio-sch.o
+obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o
+obj-$(CONFIG_GPIO_SIM) += gpio-sim.o
+obj-$(CONFIG_GPIO_SIOX) += gpio-siox.o
+obj-$(CONFIG_GPIO_SL28CPLD) += gpio-sl28cpld.o
+obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o
+obj-$(CONFIG_GPIO_SPEAR_SPICS) += gpio-spear-spics.o
+obj-$(CONFIG_GPIO_SPRD) += gpio-sprd.o
+obj-$(CONFIG_GPIO_STMPE) += gpio-stmpe.o
+obj-$(CONFIG_GPIO_STP_XWAY) += gpio-stp-xway.o
+obj-$(CONFIG_GPIO_SYSCON) += gpio-syscon.o
+obj-$(CONFIG_GPIO_TANGIER) += gpio-tangier.o
+obj-$(CONFIG_GPIO_TB10X) += gpio-tb10x.o
+obj-$(CONFIG_GPIO_TC3589X) += gpio-tc3589x.o
+obj-$(CONFIG_GPIO_TEGRA186) += gpio-tegra186.o
+obj-$(CONFIG_GPIO_TEGRA) += gpio-tegra.o
+obj-$(CONFIG_GPIO_THUNDERX) += gpio-thunderx.o
+obj-$(CONFIG_GPIO_TIMBERDALE) += gpio-timberdale.o
+obj-$(CONFIG_GPIO_TN48M_CPLD) += gpio-tn48m.o
+obj-$(CONFIG_GPIO_TPIC2810) += gpio-tpic2810.o
+obj-$(CONFIG_GPIO_TPS65086) += gpio-tps65086.o
+obj-$(CONFIG_GPIO_TPS65218) += gpio-tps65218.o
+obj-$(CONFIG_GPIO_TPS65219) += gpio-tps65219.o
+obj-$(CONFIG_GPIO_TPS6586X) += gpio-tps6586x.o
+obj-$(CONFIG_GPIO_TPS65910) += gpio-tps65910.o
+obj-$(CONFIG_GPIO_TPS65912) += gpio-tps65912.o
+obj-$(CONFIG_GPIO_TPS68470) += gpio-tps68470.o
+obj-$(CONFIG_GPIO_TQMX86) += gpio-tqmx86.o
+obj-$(CONFIG_GPIO_TS4800) += gpio-ts4800.o
+obj-$(CONFIG_GPIO_TS4900) += gpio-ts4900.o
+obj-$(CONFIG_GPIO_TS5500) += gpio-ts5500.o
+obj-$(CONFIG_GPIO_TWL4030) += gpio-twl4030.o
+obj-$(CONFIG_GPIO_TWL6040) += gpio-twl6040.o
+obj-$(CONFIG_GPIO_UNIPHIER) += gpio-uniphier.o
+obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o
+obj-$(CONFIG_GPIO_VIPERBOARD) += gpio-viperboard.o
+obj-$(CONFIG_GPIO_VIRTIO) += gpio-virtio.o
+obj-$(CONFIG_GPIO_VISCONTI) += gpio-visconti.o
+obj-$(CONFIG_GPIO_VX855) += gpio-vx855.o
+obj-$(CONFIG_GPIO_WCD934X) += gpio-wcd934x.o
+obj-$(CONFIG_GPIO_WHISKEY_COVE) += gpio-wcove.o
+obj-$(CONFIG_GPIO_WINBOND) += gpio-winbond.o
+obj-$(CONFIG_GPIO_WM831X) += gpio-wm831x.o
+obj-$(CONFIG_GPIO_WM8350) += gpio-wm8350.o
+obj-$(CONFIG_GPIO_WM8994) += gpio-wm8994.o
+obj-$(CONFIG_GPIO_WS16C48) += gpio-ws16c48.o
+obj-$(CONFIG_GPIO_XGENE) += gpio-xgene.o
+obj-$(CONFIG_GPIO_XGENE_SB) += gpio-xgene-sb.o
+obj-$(CONFIG_GPIO_XILINX) += gpio-xilinx.o
+obj-$(CONFIG_GPIO_XLP) += gpio-xlp.o
+obj-$(CONFIG_GPIO_XRA1403) += gpio-xra1403.o
+obj-$(CONFIG_GPIO_XTENSA) += gpio-xtensa.o
+obj-$(CONFIG_GPIO_ZEVIO) += gpio-zevio.o
+obj-$(CONFIG_GPIO_ZYNQ) += gpio-zynq.o
+obj-$(CONFIG_GPIO_ZYNQMP_MODEPIN) += gpio-zynqmp-modepin.o
diff --git a/drivers/gpio/TODO b/drivers/gpio/TODO
new file mode 100644
index 0000000000..189c3abe7e
--- /dev/null
+++ b/drivers/gpio/TODO
@@ -0,0 +1,211 @@
+This is a place for planning the ongoing long-term work in the GPIO
+subsystem.
+
+
+GPIO descriptors
+
+Starting with commit 79a9becda894 the GPIO subsystem embarked on a journey
+to move away from the global GPIO numberspace and toward a descriptor-based
+approach. This means that GPIO consumers, drivers and machine descriptions
+ideally have no use or idea of the global GPIO numberspace that has/was
+used in the inception of the GPIO subsystem.
+
+The numberspace issue is the same as to why irq is moving away from irq
+numbers to IRQ descriptors.
+
+The underlying motivation for this is that the GPIO numberspace has become
+unmanageable: machine board files tend to become full of macros trying to
+establish the numberspace at compile-time, making it hard to add any numbers
+in the middle (such as if you missed a pin on a chip) without the numberspace
+breaking.
+
+Machine descriptions such as device tree or ACPI does not have a concept of the
+Linux GPIO number as those descriptions are external to the Linux kernel
+and treat GPIO lines as abstract entities.
+
+The runtime-assigned GPIO numberspace (what you get if you assign the GPIO
+base as -1 in struct gpio_chip) has also became unpredictable due to factors
+such as probe ordering and the introduction of -EPROBE_DEFER making probe
+ordering of independent GPIO chips essentially unpredictable, as their base
+number will be assigned on a first come first serve basis.
+
+The best way to get out of the problem is to make the global GPIO numbers
+unimportant by simply not using them. GPIO descriptors deal with this.
+
+Work items:
+
+- Convert all GPIO device drivers to only #include <linux/gpio/driver.h>
+
+- Convert all consumer drivers to only #include <linux/gpio/consumer.h>
+
+- Convert all machine descriptors in "boardfiles" to only
+ #include <linux/gpio/machine.h>, the other option being to convert it
+ to a machine description such as device tree, ACPI or fwnode that
+ implicitly does not use global GPIO numbers.
+
+- When this work is complete (will require some of the items in the
+ following ongoing work as well) we can delete the old global
+ numberspace accessors from <linux/gpio.h> and eventually delete
+ <linux/gpio.h> altogether.
+
+
+Get rid of <linux/of_gpio.h>
+
+This header and helpers appeared at one point when there was no proper
+driver infrastructure for doing simpler MMIO GPIO devices and there was
+no core support for parsing device tree GPIOs from the core library with
+the [devm_]gpiod_get() calls we have today that will implicitly go into
+the device tree back-end. It is legacy and should not be used in new code.
+
+Work items:
+
+- Change all consumer drivers that #include <linux/of_gpio.h> to
+ #include <linux/gpio/consumer.h> and stop doing custom parsing of the
+ GPIO lines from the device tree. This can be tricky and often ivolves
+ changing boardfiles, etc.
+
+- Pull semantics for legacy device tree (OF) GPIO lookups into
+ gpiolib-of.c: in some cases subsystems are doing custom flags and
+ lookups for polarity inversion, open drain and what not. As we now
+ handle this with generic OF bindings, pull all legacy handling into
+ gpiolib so the library API becomes narrow and deep and handle all
+ legacy bindings internally. (See e.g. commits 6953c57ab172,
+ 6a537d48461d etc)
+
+- Delete <linux/of_gpio.h> when all the above is complete and everything
+ uses <linux/gpio/consumer.h> or <linux/gpio/driver.h> instead.
+
+
+Get rid of <linux/gpio/legacy-of-mm-gpiochip.h>
+
+Work items:
+
+- Get rid of struct of_mm_gpio_chip altogether: use the generic MMIO
+ GPIO for all current users (see below). Delete struct of_mm_gpio_chip,
+ to_of_mm_gpio_chip(), of_mm_gpiochip_add_data(), of_mm_gpiochip_remove(),
+ CONFIG_OF_GPIO_MM_GPIOCHIP from the kernel.
+
+
+Get rid of <linux/gpio.h>
+
+This legacy header is a one stop shop for anything GPIO is closely tied
+to the global GPIO numberspace. The endgame of the above refactorings will
+be the removal of <linux/gpio.h> and from that point only the specialized
+headers under <linux/gpio/*.h> will be used. This requires all the above to
+be completed and is expected to take a long time.
+
+
+Collect drivers
+
+Collect GPIO drivers from arch/* and other places that should be placed
+in drivers/gpio/gpio-*. Augment platforms to create platform devices or
+similar and probe a proper driver in the gpiolib subsystem.
+
+In some cases it makes sense to create a GPIO chip from the local driver
+for a few GPIOs. Those should stay where they are.
+
+At the same time it makes sense to get rid of code duplication in existing or
+new coming drivers. For example, gpio-ml-ioh should be incorporated into
+gpio-pch.
+
+
+Generic MMIO GPIO
+
+The GPIO drivers can utilize the generic MMIO helper library in many
+cases, and the helper library should be as helpful as possible for MMIO
+drivers. (drivers/gpio/gpio-mmio.c)
+
+Work items:
+
+- Look over and identify any remaining easily converted drivers and
+ dry-code conversions to MMIO GPIO for maintainers to test
+
+- Expand the MMIO GPIO or write a new library for regmap-based I/O
+ helpers for GPIO drivers on regmap that simply use offsets
+ 0..n in some register to drive GPIO lines
+
+- Expand the MMIO GPIO or write a new library for port-mapped I/O
+ helpers (x86 inb()/outb()) and convert port-mapped I/O drivers to use
+ this with dry-coding and sending to maintainers to test
+
+
+Generic regmap GPIO
+
+In the very similar way to Generic MMIO GPIO convert the users which can
+take advantage of using regmap over direct IO accessors. Note, even in
+MMIO case the regmap MMIO with gpio-regmap.c is preferable over gpio-mmio.c.
+
+
+GPIOLIB irqchip
+
+The GPIOLIB irqchip is a helper irqchip for "simple cases" that should
+try to cover any generic kind of irqchip cascaded from a GPIO.
+
+- Look over and identify any remaining easily converted drivers and
+ dry-code conversions to gpiolib irqchip for maintainers to test
+
+
+Increase integration with pin control
+
+There are already ways to use pin control as back-end for GPIO and
+it may make sense to bring these subsystems closer. One reason for
+creating pin control as its own subsystem was that we could avoid any
+use of the global GPIO numbers. Once the above is complete, it may
+make sense to simply join the subsystems into one and make pin
+multiplexing, pin configuration, GPIO, etc selectable options in one
+and the same pin control and GPIO subsystem.
+
+
+Debugfs in place of sysfs
+
+The old sysfs code that enables simple uses of GPIOs from the
+command line is still popular despite the existance of the proper
+character device. The reason is that it is simple to use on
+root filesystems where you only have a minimal set of tools such
+as "cat", "echo" etc.
+
+The old sysfs still need to be strongly deprecated and removed
+as it relies on the global GPIO numberspace that assume a strict
+order of global GPIO numbers that do not change between boots
+and is independent of probe order.
+
+To solve this and provide an ABI that people can use for hacks
+and development, implement a debugfs interface to manipulate
+GPIO lines that can do everything that sysfs can do today: one
+directory per gpiochip and one file entry per line:
+
+/sys/kernel/debug/gpiochip/gpiochip0
+/sys/kernel/debug/gpiochip/gpiochip0/gpio0
+/sys/kernel/debug/gpiochip/gpiochip0/gpio1
+/sys/kernel/debug/gpiochip/gpiochip0/gpio2
+/sys/kernel/debug/gpiochip/gpiochip0/gpio3
+...
+/sys/kernel/debug/gpiochip/gpiochip1
+/sys/kernel/debug/gpiochip/gpiochip1/gpio0
+/sys/kernel/debug/gpiochip/gpiochip1/gpio1
+...
+
+The exact files and design of the debugfs interface can be
+discussed but the idea is to provide a low-level access point
+for debugging and hacking and to expose all lines without the
+need of any exporting. Also provide ample ammunition to shoot
+oneself in the foot, because this is debugfs after all.
+
+
+Moving over to immutable irq_chip structures
+
+Most of the gpio chips implementing interrupt support rely on gpiolib
+intercepting some of the irq_chip callbacks, preventing the structures
+from being made read-only and forcing duplication of structures that
+should otherwise be unique.
+
+The solution is to call into the gpiolib code when needed (resource
+management, enable/disable or unmask/mask callbacks), and to let the
+core code know about that by exposing a flag (IRQCHIP_IMMUTABLE) in
+the irq_chip structure. The irq_chip structure can then be made unique
+and const.
+
+A small number of drivers have been converted (pl061, tegra186, msm,
+amd, apple), and can be used as examples of how to proceed with this
+conversion. Note that drivers using the generic irqchip framework
+cannot be converted yet, but watch this space!
diff --git a/drivers/gpio/gpio-104-dio-48e.c b/drivers/gpio/gpio-104-dio-48e.c
new file mode 100644
index 0000000000..4df9becaf3
--- /dev/null
+++ b/drivers/gpio/gpio-104-dio-48e.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for the ACCES 104-DIO-48E series
+ * Copyright (C) 2016 William Breathitt Gray
+ *
+ * This driver supports the following ACCES devices: 104-DIO-48E and
+ * 104-DIO-24E.
+ */
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/i8254.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/isa.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include "gpio-i8255.h"
+
+MODULE_IMPORT_NS(I8255);
+
+#define DIO48E_EXTENT 16
+#define MAX_NUM_DIO48E max_num_isa_dev(DIO48E_EXTENT)
+
+static unsigned int base[MAX_NUM_DIO48E];
+static unsigned int num_dio48e;
+module_param_hw_array(base, uint, ioport, &num_dio48e, 0);
+MODULE_PARM_DESC(base, "ACCES 104-DIO-48E base addresses");
+
+static unsigned int irq[MAX_NUM_DIO48E];
+static unsigned int num_irq;
+module_param_hw_array(irq, uint, irq, &num_irq, 0);
+MODULE_PARM_DESC(irq, "ACCES 104-DIO-48E interrupt line numbers");
+
+#define DIO48E_ENABLE_INTERRUPT 0xB
+#define DIO48E_DISABLE_INTERRUPT DIO48E_ENABLE_INTERRUPT
+#define DIO48E_ENABLE_COUNTER_TIMER_ADDRESSING 0xD
+#define DIO48E_DISABLE_COUNTER_TIMER_ADDRESSING DIO48E_ENABLE_COUNTER_TIMER_ADDRESSING
+#define DIO48E_CLEAR_INTERRUPT 0xF
+
+#define DIO48E_NUM_PPI 2
+
+static const struct regmap_range dio48e_wr_ranges[] = {
+ regmap_reg_range(0x0, 0x9), regmap_reg_range(0xB, 0xB),
+ regmap_reg_range(0xD, 0xD), regmap_reg_range(0xF, 0xF),
+};
+static const struct regmap_range dio48e_rd_ranges[] = {
+ regmap_reg_range(0x0, 0x2), regmap_reg_range(0x4, 0x6),
+ regmap_reg_range(0xB, 0xB), regmap_reg_range(0xD, 0xD),
+ regmap_reg_range(0xF, 0xF),
+};
+static const struct regmap_range dio48e_volatile_ranges[] = {
+ i8255_volatile_regmap_range(0x0), i8255_volatile_regmap_range(0x4),
+ regmap_reg_range(0xB, 0xB), regmap_reg_range(0xD, 0xD),
+ regmap_reg_range(0xF, 0xF),
+};
+static const struct regmap_range dio48e_precious_ranges[] = {
+ regmap_reg_range(0xB, 0xB), regmap_reg_range(0xD, 0xD),
+ regmap_reg_range(0xF, 0xF),
+};
+static const struct regmap_access_table dio48e_wr_table = {
+ .yes_ranges = dio48e_wr_ranges,
+ .n_yes_ranges = ARRAY_SIZE(dio48e_wr_ranges),
+};
+static const struct regmap_access_table dio48e_rd_table = {
+ .yes_ranges = dio48e_rd_ranges,
+ .n_yes_ranges = ARRAY_SIZE(dio48e_rd_ranges),
+};
+static const struct regmap_access_table dio48e_volatile_table = {
+ .yes_ranges = dio48e_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(dio48e_volatile_ranges),
+};
+static const struct regmap_access_table dio48e_precious_table = {
+ .yes_ranges = dio48e_precious_ranges,
+ .n_yes_ranges = ARRAY_SIZE(dio48e_precious_ranges),
+};
+
+static const struct regmap_range pit_wr_ranges[] = {
+ regmap_reg_range(0x0, 0x3),
+};
+static const struct regmap_range pit_rd_ranges[] = {
+ regmap_reg_range(0x0, 0x2),
+};
+static const struct regmap_access_table pit_wr_table = {
+ .yes_ranges = pit_wr_ranges,
+ .n_yes_ranges = ARRAY_SIZE(pit_wr_ranges),
+};
+static const struct regmap_access_table pit_rd_table = {
+ .yes_ranges = pit_rd_ranges,
+ .n_yes_ranges = ARRAY_SIZE(pit_rd_ranges),
+};
+
+/* only bit 3 on each respective Port C supports interrupts */
+#define DIO48E_REGMAP_IRQ(_ppi) \
+ [19 + (_ppi) * 24] = { \
+ .mask = BIT(_ppi), \
+ .type = { .types_supported = IRQ_TYPE_EDGE_RISING }, \
+ }
+
+static const struct regmap_irq dio48e_regmap_irqs[] = {
+ DIO48E_REGMAP_IRQ(0), DIO48E_REGMAP_IRQ(1),
+};
+
+/**
+ * struct dio48e_gpio - GPIO device private data structure
+ * @lock: synchronization lock to prevent I/O race conditions
+ * @map: Regmap for the device
+ * @regs: virtual mapping for device registers
+ * @flags: IRQ flags saved during locking
+ * @irq_mask: Current IRQ mask state on the device
+ */
+struct dio48e_gpio {
+ raw_spinlock_t lock;
+ struct regmap *map;
+ void __iomem *regs;
+ unsigned long flags;
+ unsigned int irq_mask;
+};
+
+static void dio48e_regmap_lock(void *lock_arg) __acquires(&dio48egpio->lock)
+{
+ struct dio48e_gpio *const dio48egpio = lock_arg;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&dio48egpio->lock, flags);
+ dio48egpio->flags = flags;
+}
+
+static void dio48e_regmap_unlock(void *lock_arg) __releases(&dio48egpio->lock)
+{
+ struct dio48e_gpio *const dio48egpio = lock_arg;
+
+ raw_spin_unlock_irqrestore(&dio48egpio->lock, dio48egpio->flags);
+}
+
+static void pit_regmap_lock(void *lock_arg) __acquires(&dio48egpio->lock)
+{
+ struct dio48e_gpio *const dio48egpio = lock_arg;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&dio48egpio->lock, flags);
+ dio48egpio->flags = flags;
+
+ iowrite8(0x00, dio48egpio->regs + DIO48E_ENABLE_COUNTER_TIMER_ADDRESSING);
+}
+
+static void pit_regmap_unlock(void *lock_arg) __releases(&dio48egpio->lock)
+{
+ struct dio48e_gpio *const dio48egpio = lock_arg;
+
+ ioread8(dio48egpio->regs + DIO48E_DISABLE_COUNTER_TIMER_ADDRESSING);
+
+ raw_spin_unlock_irqrestore(&dio48egpio->lock, dio48egpio->flags);
+}
+
+static int dio48e_handle_mask_sync(const int index,
+ const unsigned int mask_buf_def,
+ const unsigned int mask_buf,
+ void *const irq_drv_data)
+{
+ struct dio48e_gpio *const dio48egpio = irq_drv_data;
+ const unsigned int prev_mask = dio48egpio->irq_mask;
+ int err;
+ unsigned int val;
+
+ /* exit early if no change since the previous mask */
+ if (mask_buf == prev_mask)
+ return 0;
+
+ /* remember the current mask for the next mask sync */
+ dio48egpio->irq_mask = mask_buf;
+
+ /* if all previously masked, enable interrupts when unmasking */
+ if (prev_mask == mask_buf_def) {
+ err = regmap_write(dio48egpio->map, DIO48E_CLEAR_INTERRUPT, 0x00);
+ if (err)
+ return err;
+ return regmap_write(dio48egpio->map, DIO48E_ENABLE_INTERRUPT, 0x00);
+ }
+
+ /* if all are currently masked, disable interrupts */
+ if (mask_buf == mask_buf_def)
+ return regmap_read(dio48egpio->map, DIO48E_DISABLE_INTERRUPT, &val);
+
+ return 0;
+}
+
+#define DIO48E_NGPIO 48
+static const char *dio48e_names[DIO48E_NGPIO] = {
+ "PPI Group 0 Port A 0", "PPI Group 0 Port A 1", "PPI Group 0 Port A 2",
+ "PPI Group 0 Port A 3", "PPI Group 0 Port A 4", "PPI Group 0 Port A 5",
+ "PPI Group 0 Port A 6", "PPI Group 0 Port A 7", "PPI Group 0 Port B 0",
+ "PPI Group 0 Port B 1", "PPI Group 0 Port B 2", "PPI Group 0 Port B 3",
+ "PPI Group 0 Port B 4", "PPI Group 0 Port B 5", "PPI Group 0 Port B 6",
+ "PPI Group 0 Port B 7", "PPI Group 0 Port C 0", "PPI Group 0 Port C 1",
+ "PPI Group 0 Port C 2", "PPI Group 0 Port C 3", "PPI Group 0 Port C 4",
+ "PPI Group 0 Port C 5", "PPI Group 0 Port C 6", "PPI Group 0 Port C 7",
+ "PPI Group 1 Port A 0", "PPI Group 1 Port A 1", "PPI Group 1 Port A 2",
+ "PPI Group 1 Port A 3", "PPI Group 1 Port A 4", "PPI Group 1 Port A 5",
+ "PPI Group 1 Port A 6", "PPI Group 1 Port A 7", "PPI Group 1 Port B 0",
+ "PPI Group 1 Port B 1", "PPI Group 1 Port B 2", "PPI Group 1 Port B 3",
+ "PPI Group 1 Port B 4", "PPI Group 1 Port B 5", "PPI Group 1 Port B 6",
+ "PPI Group 1 Port B 7", "PPI Group 1 Port C 0", "PPI Group 1 Port C 1",
+ "PPI Group 1 Port C 2", "PPI Group 1 Port C 3", "PPI Group 1 Port C 4",
+ "PPI Group 1 Port C 5", "PPI Group 1 Port C 6", "PPI Group 1 Port C 7"
+};
+
+static int dio48e_irq_init_hw(struct regmap *const map)
+{
+ unsigned int val;
+
+ /* Disable IRQ by default */
+ return regmap_read(map, DIO48E_DISABLE_INTERRUPT, &val);
+}
+
+static int dio48e_probe(struct device *dev, unsigned int id)
+{
+ const char *const name = dev_name(dev);
+ struct i8255_regmap_config config = {};
+ void __iomem *regs;
+ struct regmap *map;
+ struct regmap_config dio48e_regmap_config;
+ struct regmap_config pit_regmap_config;
+ struct i8254_regmap_config pit_config;
+ int err;
+ struct regmap_irq_chip *chip;
+ struct dio48e_gpio *dio48egpio;
+ struct regmap_irq_chip_data *chip_data;
+
+ if (!devm_request_region(dev, base[id], DIO48E_EXTENT, name)) {
+ dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n",
+ base[id], base[id] + DIO48E_EXTENT);
+ return -EBUSY;
+ }
+
+ dio48egpio = devm_kzalloc(dev, sizeof(*dio48egpio), GFP_KERNEL);
+ if (!dio48egpio)
+ return -ENOMEM;
+
+ regs = devm_ioport_map(dev, base[id], DIO48E_EXTENT);
+ if (!regs)
+ return -ENOMEM;
+
+ dio48egpio->regs = regs;
+
+ raw_spin_lock_init(&dio48egpio->lock);
+
+ dio48e_regmap_config = (struct regmap_config) {
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .val_bits = 8,
+ .lock = dio48e_regmap_lock,
+ .unlock = dio48e_regmap_unlock,
+ .lock_arg = dio48egpio,
+ .io_port = true,
+ .wr_table = &dio48e_wr_table,
+ .rd_table = &dio48e_rd_table,
+ .volatile_table = &dio48e_volatile_table,
+ .precious_table = &dio48e_precious_table,
+ .cache_type = REGCACHE_FLAT,
+ };
+
+ map = devm_regmap_init_mmio(dev, regs, &dio48e_regmap_config);
+ if (IS_ERR(map))
+ return dev_err_probe(dev, PTR_ERR(map),
+ "Unable to initialize register map\n");
+
+ dio48egpio->map = map;
+
+ pit_regmap_config = (struct regmap_config) {
+ .name = "i8254",
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .val_bits = 8,
+ .lock = pit_regmap_lock,
+ .unlock = pit_regmap_unlock,
+ .lock_arg = dio48egpio,
+ .io_port = true,
+ .wr_table = &pit_wr_table,
+ .rd_table = &pit_rd_table,
+ };
+
+ pit_config.map = devm_regmap_init_mmio(dev, regs, &pit_regmap_config);
+ if (IS_ERR(pit_config.map))
+ return dev_err_probe(dev, PTR_ERR(pit_config.map),
+ "Unable to initialize i8254 register map\n");
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->name = name;
+ chip->mask_base = DIO48E_ENABLE_INTERRUPT;
+ chip->ack_base = DIO48E_CLEAR_INTERRUPT;
+ chip->no_status = true;
+ chip->num_regs = 1;
+ chip->irqs = dio48e_regmap_irqs;
+ chip->num_irqs = ARRAY_SIZE(dio48e_regmap_irqs);
+ chip->handle_mask_sync = dio48e_handle_mask_sync;
+ chip->irq_drv_data = dio48egpio;
+
+ /* Initialize to prevent spurious interrupts before we're ready */
+ err = dio48e_irq_init_hw(map);
+ if (err)
+ return err;
+
+ err = devm_regmap_add_irq_chip(dev, map, irq[id], 0, 0, chip, &chip_data);
+ if (err)
+ return dev_err_probe(dev, err, "IRQ registration failed\n");
+
+ pit_config.parent = dev;
+
+ err = devm_i8254_regmap_register(dev, &pit_config);
+ if (err)
+ return err;
+
+ config.parent = dev;
+ config.map = map;
+ config.num_ppi = DIO48E_NUM_PPI;
+ config.names = dio48e_names;
+ config.domain = regmap_irq_get_domain(chip_data);
+
+ return devm_i8255_regmap_register(dev, &config);
+}
+
+static struct isa_driver dio48e_driver = {
+ .probe = dio48e_probe,
+ .driver = {
+ .name = "104-dio-48e"
+ },
+};
+module_isa_driver_with_irq(dio48e_driver, num_dio48e, num_irq);
+
+MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
+MODULE_DESCRIPTION("ACCES 104-DIO-48E GPIO driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(I8254);
diff --git a/drivers/gpio/gpio-104-idi-48.c b/drivers/gpio/gpio-104-idi-48.c
new file mode 100644
index 0000000000..ba73ee9c0c
--- /dev/null
+++ b/drivers/gpio/gpio-104-idi-48.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for the ACCES 104-IDI-48 family
+ * Copyright (C) 2015 William Breathitt Gray
+ *
+ * This driver supports the following ACCES devices: 104-IDI-48A,
+ * 104-IDI-48AC, 104-IDI-48B, and 104-IDI-48BC.
+ */
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/regmap.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/isa.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define IDI_48_EXTENT 8
+#define MAX_NUM_IDI_48 max_num_isa_dev(IDI_48_EXTENT)
+
+static unsigned int base[MAX_NUM_IDI_48];
+static unsigned int num_idi_48;
+module_param_hw_array(base, uint, ioport, &num_idi_48, 0);
+MODULE_PARM_DESC(base, "ACCES 104-IDI-48 base addresses");
+
+static unsigned int irq[MAX_NUM_IDI_48];
+static unsigned int num_irq;
+module_param_hw_array(irq, uint, irq, &num_irq, 0);
+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)
+{
+ const unsigned int line = offset % 8;
+ const unsigned int stride = offset / 8;
+ const unsigned int port = (stride / 3) * 4;
+ const unsigned int port_stride = stride % 3;
+
+ *reg = base + port + port_stride;
+ *mask = BIT(line);
+
+ return 0;
+}
+
+static const struct regmap_range idi_48_wr_ranges[] = {
+ regmap_reg_range(0x0, 0x6),
+};
+static const struct regmap_range idi_48_rd_ranges[] = {
+ regmap_reg_range(0x0, 0x2), regmap_reg_range(0x4, 0x7),
+};
+static const struct regmap_range idi_48_precious_ranges[] = {
+ regmap_reg_range(0x7, 0x7),
+};
+static const struct regmap_access_table idi_48_wr_table = {
+ .no_ranges = idi_48_wr_ranges,
+ .n_no_ranges = ARRAY_SIZE(idi_48_wr_ranges),
+};
+static const struct regmap_access_table idi_48_rd_table = {
+ .yes_ranges = idi_48_rd_ranges,
+ .n_yes_ranges = ARRAY_SIZE(idi_48_rd_ranges),
+};
+static const struct regmap_access_table idi_48_precious_table = {
+ .yes_ranges = idi_48_precious_ranges,
+ .n_yes_ranges = ARRAY_SIZE(idi_48_precious_ranges),
+};
+static const struct regmap_config idi48_regmap_config = {
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .val_bits = 8,
+ .io_port = true,
+ .max_register = 0x6,
+ .wr_table = &idi_48_wr_table,
+ .rd_table = &idi_48_rd_table,
+ .precious_table = &idi_48_precious_table,
+ .use_raw_spinlock = true,
+};
+
+#define IDI48_NGPIO 48
+
+#define IDI48_REGMAP_IRQ(_id) \
+ [_id] = { \
+ .mask = BIT((_id) / 8), \
+ .type = { .types_supported = IRQ_TYPE_EDGE_BOTH }, \
+ }
+
+static const struct regmap_irq idi48_regmap_irqs[IDI48_NGPIO] = {
+ IDI48_REGMAP_IRQ(0), IDI48_REGMAP_IRQ(1), IDI48_REGMAP_IRQ(2), /* 0-2 */
+ IDI48_REGMAP_IRQ(3), IDI48_REGMAP_IRQ(4), IDI48_REGMAP_IRQ(5), /* 3-5 */
+ IDI48_REGMAP_IRQ(6), IDI48_REGMAP_IRQ(7), IDI48_REGMAP_IRQ(8), /* 6-8 */
+ IDI48_REGMAP_IRQ(9), IDI48_REGMAP_IRQ(10), IDI48_REGMAP_IRQ(11), /* 9-11 */
+ IDI48_REGMAP_IRQ(12), IDI48_REGMAP_IRQ(13), IDI48_REGMAP_IRQ(14), /* 12-14 */
+ IDI48_REGMAP_IRQ(15), IDI48_REGMAP_IRQ(16), IDI48_REGMAP_IRQ(17), /* 15-17 */
+ IDI48_REGMAP_IRQ(18), IDI48_REGMAP_IRQ(19), IDI48_REGMAP_IRQ(20), /* 18-20 */
+ IDI48_REGMAP_IRQ(21), IDI48_REGMAP_IRQ(22), IDI48_REGMAP_IRQ(23), /* 21-23 */
+ IDI48_REGMAP_IRQ(24), IDI48_REGMAP_IRQ(25), IDI48_REGMAP_IRQ(26), /* 24-26 */
+ IDI48_REGMAP_IRQ(27), IDI48_REGMAP_IRQ(28), IDI48_REGMAP_IRQ(29), /* 27-29 */
+ IDI48_REGMAP_IRQ(30), IDI48_REGMAP_IRQ(31), IDI48_REGMAP_IRQ(32), /* 30-32 */
+ IDI48_REGMAP_IRQ(33), IDI48_REGMAP_IRQ(34), IDI48_REGMAP_IRQ(35), /* 33-35 */
+ IDI48_REGMAP_IRQ(36), IDI48_REGMAP_IRQ(37), IDI48_REGMAP_IRQ(38), /* 36-38 */
+ IDI48_REGMAP_IRQ(39), IDI48_REGMAP_IRQ(40), IDI48_REGMAP_IRQ(41), /* 39-41 */
+ IDI48_REGMAP_IRQ(42), IDI48_REGMAP_IRQ(43), IDI48_REGMAP_IRQ(44), /* 42-44 */
+ IDI48_REGMAP_IRQ(45), IDI48_REGMAP_IRQ(46), IDI48_REGMAP_IRQ(47), /* 45-47 */
+};
+
+static const char *idi48_names[IDI48_NGPIO] = {
+ "Bit 0 A", "Bit 1 A", "Bit 2 A", "Bit 3 A", "Bit 4 A", "Bit 5 A",
+ "Bit 6 A", "Bit 7 A", "Bit 8 A", "Bit 9 A", "Bit 10 A", "Bit 11 A",
+ "Bit 12 A", "Bit 13 A", "Bit 14 A", "Bit 15 A", "Bit 16 A", "Bit 17 A",
+ "Bit 18 A", "Bit 19 A", "Bit 20 A", "Bit 21 A", "Bit 22 A", "Bit 23 A",
+ "Bit 0 B", "Bit 1 B", "Bit 2 B", "Bit 3 B", "Bit 4 B", "Bit 5 B",
+ "Bit 6 B", "Bit 7 B", "Bit 8 B", "Bit 9 B", "Bit 10 B", "Bit 11 B",
+ "Bit 12 B", "Bit 13 B", "Bit 14 B", "Bit 15 B", "Bit 16 B", "Bit 17 B",
+ "Bit 18 B", "Bit 19 B", "Bit 20 B", "Bit 21 B", "Bit 22 B", "Bit 23 B"
+};
+
+static int idi_48_probe(struct device *dev, unsigned int id)
+{
+ const char *const name = dev_name(dev);
+ struct gpio_regmap_config config = {};
+ void __iomem *regs;
+ struct regmap *map;
+ struct regmap_irq_chip *chip;
+ struct regmap_irq_chip_data *chip_data;
+ int err;
+
+ if (!devm_request_region(dev, base[id], IDI_48_EXTENT, name)) {
+ dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n",
+ base[id], base[id] + IDI_48_EXTENT);
+ return -EBUSY;
+ }
+
+ regs = devm_ioport_map(dev, base[id], IDI_48_EXTENT);
+ if (!regs)
+ return -ENOMEM;
+
+ map = devm_regmap_init_mmio(dev, regs, &idi48_regmap_config);
+ if (IS_ERR(map))
+ return dev_err_probe(dev, PTR_ERR(map),
+ "Unable to initialize register map\n");
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->name = name;
+ chip->status_base = IDI48_IRQ_STATUS;
+ chip->unmask_base = IDI48_IRQ_ENABLE;
+ chip->clear_on_unmask = true;
+ chip->num_regs = 1;
+ chip->irqs = idi48_regmap_irqs;
+ chip->num_irqs = ARRAY_SIZE(idi48_regmap_irqs);
+
+ err = devm_regmap_add_irq_chip(dev, map, irq[id], IRQF_SHARED, 0, chip,
+ &chip_data);
+ if (err)
+ return dev_err_probe(dev, err, "IRQ registration failed\n");
+
+ config.parent = dev;
+ config.regmap = map;
+ config.ngpio = IDI48_NGPIO;
+ config.names = idi48_names;
+ config.reg_dat_base = GPIO_REGMAP_ADDR(0x0);
+ config.ngpio_per_reg = 8;
+ config.reg_mask_xlate = idi_48_reg_mask_xlate;
+ config.irq_domain = regmap_irq_get_domain(chip_data);
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &config));
+}
+
+static struct isa_driver idi_48_driver = {
+ .probe = idi_48_probe,
+ .driver = {
+ .name = "104-idi-48"
+ },
+};
+module_isa_driver_with_irq(idi_48_driver, num_idi_48, num_irq);
+
+MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
+MODULE_DESCRIPTION("ACCES 104-IDI-48 GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-104-idio-16.c b/drivers/gpio/gpio-104-idio-16.c
new file mode 100644
index 0000000000..f03ccd0f53
--- /dev/null
+++ b/drivers/gpio/gpio-104-idio-16.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for the ACCES 104-IDIO-16 family
+ * Copyright (C) 2015 William Breathitt Gray
+ *
+ * This driver supports the following ACCES devices: 104-IDIO-16,
+ * 104-IDIO-16E, 104-IDO-16, 104-IDIO-8, 104-IDIO-8E, and 104-IDO-8.
+ */
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/isa.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include "gpio-idio-16.h"
+
+#define IDIO_16_EXTENT 8
+#define MAX_NUM_IDIO_16 max_num_isa_dev(IDIO_16_EXTENT)
+
+static unsigned int base[MAX_NUM_IDIO_16];
+static unsigned int num_idio_16;
+module_param_hw_array(base, uint, ioport, &num_idio_16, 0);
+MODULE_PARM_DESC(base, "ACCES 104-IDIO-16 base addresses");
+
+static unsigned int irq[MAX_NUM_IDIO_16];
+static unsigned int num_irq;
+module_param_hw_array(irq, uint, irq, &num_irq, 0);
+MODULE_PARM_DESC(irq, "ACCES 104-IDIO-16 interrupt line numbers");
+
+static const struct regmap_range idio_16_wr_ranges[] = {
+ regmap_reg_range(0x0, 0x2), regmap_reg_range(0x4, 0x4),
+};
+static const struct regmap_range idio_16_rd_ranges[] = {
+ regmap_reg_range(0x1, 0x2), regmap_reg_range(0x5, 0x5),
+};
+static const struct regmap_range idio_16_precious_ranges[] = {
+ regmap_reg_range(0x2, 0x2),
+};
+static const struct regmap_access_table idio_16_wr_table = {
+ .yes_ranges = idio_16_wr_ranges,
+ .n_yes_ranges = ARRAY_SIZE(idio_16_wr_ranges),
+};
+static const struct regmap_access_table idio_16_rd_table = {
+ .yes_ranges = idio_16_rd_ranges,
+ .n_yes_ranges = ARRAY_SIZE(idio_16_rd_ranges),
+};
+static const struct regmap_access_table idio_16_precious_table = {
+ .yes_ranges = idio_16_precious_ranges,
+ .n_yes_ranges = ARRAY_SIZE(idio_16_precious_ranges),
+};
+static const struct regmap_config idio_16_regmap_config = {
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .val_bits = 8,
+ .io_port = true,
+ .wr_table = &idio_16_wr_table,
+ .rd_table = &idio_16_rd_table,
+ .volatile_table = &idio_16_rd_table,
+ .precious_table = &idio_16_precious_table,
+ .cache_type = REGCACHE_FLAT,
+ .use_raw_spinlock = true,
+};
+
+/* Only input lines (GPIO 16-31) support interrupts */
+#define IDIO_16_REGMAP_IRQ(_id) \
+ [16 + _id] = { \
+ .mask = BIT(_id), \
+ .type = { .types_supported = IRQ_TYPE_EDGE_BOTH }, \
+ }
+
+static const struct regmap_irq idio_16_regmap_irqs[] = {
+ IDIO_16_REGMAP_IRQ(0), IDIO_16_REGMAP_IRQ(1), IDIO_16_REGMAP_IRQ(2), /* 0-2 */
+ IDIO_16_REGMAP_IRQ(3), IDIO_16_REGMAP_IRQ(4), IDIO_16_REGMAP_IRQ(5), /* 3-5 */
+ IDIO_16_REGMAP_IRQ(6), IDIO_16_REGMAP_IRQ(7), IDIO_16_REGMAP_IRQ(8), /* 6-8 */
+ IDIO_16_REGMAP_IRQ(9), IDIO_16_REGMAP_IRQ(10), IDIO_16_REGMAP_IRQ(11), /* 9-11 */
+ IDIO_16_REGMAP_IRQ(12), IDIO_16_REGMAP_IRQ(13), IDIO_16_REGMAP_IRQ(14), /* 12-14 */
+ IDIO_16_REGMAP_IRQ(15), /* 15 */
+};
+
+static int idio_16_probe(struct device *dev, unsigned int id)
+{
+ const char *const name = dev_name(dev);
+ struct idio_16_regmap_config config = {};
+ void __iomem *regs;
+ struct regmap *map;
+
+ if (!devm_request_region(dev, base[id], IDIO_16_EXTENT, name)) {
+ dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n",
+ base[id], base[id] + IDIO_16_EXTENT);
+ return -EBUSY;
+ }
+
+ regs = devm_ioport_map(dev, base[id], IDIO_16_EXTENT);
+ if (!regs)
+ return -ENOMEM;
+
+ map = devm_regmap_init_mmio(dev, regs, &idio_16_regmap_config);
+ if (IS_ERR(map))
+ return dev_err_probe(dev, PTR_ERR(map), "Unable to initialize register map\n");
+
+ config.parent = dev;
+ config.map = map;
+ config.regmap_irqs = idio_16_regmap_irqs;
+ config.num_regmap_irqs = ARRAY_SIZE(idio_16_regmap_irqs);
+ config.irq = irq[id];
+ config.no_status = true;
+
+ return devm_idio_16_regmap_register(dev, &config);
+}
+
+static struct isa_driver idio_16_driver = {
+ .probe = idio_16_probe,
+ .driver = {
+ .name = "104-idio-16"
+ },
+};
+
+module_isa_driver_with_irq(idio_16_driver, num_idio_16, num_irq);
+
+MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
+MODULE_DESCRIPTION("ACCES 104-IDIO-16 GPIO driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(GPIO_IDIO_16);
diff --git a/drivers/gpio/gpio-74x164.c b/drivers/gpio/gpio-74x164.c
new file mode 100644
index 0000000000..e00c333105
--- /dev/null
+++ b/drivers/gpio/gpio-74x164.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 74Hx164 - Generic serial-in/parallel-out 8-bits shift register GPIO driver
+ *
+ * Copyright (C) 2010 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2010 Miguel Gaio <miguel.gaio@efixo.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+
+#define GEN_74X164_NUMBER_GPIOS 8
+
+struct gen_74x164_chip {
+ struct gpio_chip gpio_chip;
+ struct mutex lock;
+ struct gpio_desc *gpiod_oe;
+ u32 registers;
+ /*
+ * Since the registers are chained, every byte sent will make
+ * the previous byte shift to the next register in the
+ * chain. Thus, the first byte sent will end up in the last
+ * register at the end of the transfer. So, to have a logical
+ * numbering, store the bytes in reverse order.
+ */
+ u8 buffer[];
+};
+
+static int __gen_74x164_write_config(struct gen_74x164_chip *chip)
+{
+ return spi_write(to_spi_device(chip->gpio_chip.parent), chip->buffer,
+ chip->registers);
+}
+
+static int gen_74x164_get_value(struct gpio_chip *gc, unsigned offset)
+{
+ struct gen_74x164_chip *chip = gpiochip_get_data(gc);
+ u8 bank = chip->registers - 1 - offset / 8;
+ u8 pin = offset % 8;
+ int ret;
+
+ mutex_lock(&chip->lock);
+ ret = (chip->buffer[bank] >> pin) & 0x1;
+ mutex_unlock(&chip->lock);
+
+ return ret;
+}
+
+static void gen_74x164_set_value(struct gpio_chip *gc,
+ unsigned offset, int val)
+{
+ struct gen_74x164_chip *chip = gpiochip_get_data(gc);
+ u8 bank = chip->registers - 1 - offset / 8;
+ u8 pin = offset % 8;
+
+ mutex_lock(&chip->lock);
+ if (val)
+ chip->buffer[bank] |= (1 << pin);
+ else
+ chip->buffer[bank] &= ~(1 << pin);
+
+ __gen_74x164_write_config(chip);
+ mutex_unlock(&chip->lock);
+}
+
+static void gen_74x164_set_multiple(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct gen_74x164_chip *chip = gpiochip_get_data(gc);
+ unsigned long offset;
+ unsigned long bankmask;
+ size_t bank;
+ unsigned long bitmask;
+
+ mutex_lock(&chip->lock);
+ for_each_set_clump8(offset, bankmask, mask, chip->registers * 8) {
+ bank = chip->registers - 1 - offset / 8;
+ bitmask = bitmap_get_value8(bits, offset) & bankmask;
+
+ chip->buffer[bank] &= ~bankmask;
+ chip->buffer[bank] |= bitmask;
+ }
+ __gen_74x164_write_config(chip);
+ mutex_unlock(&chip->lock);
+}
+
+static int gen_74x164_direction_output(struct gpio_chip *gc,
+ unsigned offset, int val)
+{
+ gen_74x164_set_value(gc, offset, val);
+ return 0;
+}
+
+static int gen_74x164_probe(struct spi_device *spi)
+{
+ struct gen_74x164_chip *chip;
+ u32 nregs;
+ int ret;
+
+ /*
+ * bits_per_word cannot be configured in platform data
+ */
+ spi->bits_per_word = 8;
+
+ ret = spi_setup(spi);
+ if (ret < 0)
+ return ret;
+
+ ret = device_property_read_u32(&spi->dev, "registers-number", &nregs);
+ if (ret) {
+ dev_err(&spi->dev, "Missing 'registers-number' property.\n");
+ return -EINVAL;
+ }
+
+ chip = devm_kzalloc(&spi->dev, sizeof(*chip) + nregs, GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->gpiod_oe = devm_gpiod_get_optional(&spi->dev, "enable",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(chip->gpiod_oe))
+ return PTR_ERR(chip->gpiod_oe);
+
+ gpiod_set_value_cansleep(chip->gpiod_oe, 1);
+
+ spi_set_drvdata(spi, chip);
+
+ chip->gpio_chip.label = spi->modalias;
+ chip->gpio_chip.direction_output = gen_74x164_direction_output;
+ chip->gpio_chip.get = gen_74x164_get_value;
+ chip->gpio_chip.set = gen_74x164_set_value;
+ chip->gpio_chip.set_multiple = gen_74x164_set_multiple;
+ chip->gpio_chip.base = -1;
+
+ chip->registers = nregs;
+ chip->gpio_chip.ngpio = GEN_74X164_NUMBER_GPIOS * chip->registers;
+
+ chip->gpio_chip.can_sleep = true;
+ chip->gpio_chip.parent = &spi->dev;
+ chip->gpio_chip.owner = THIS_MODULE;
+
+ mutex_init(&chip->lock);
+
+ ret = __gen_74x164_write_config(chip);
+ if (ret) {
+ dev_err(&spi->dev, "Failed writing: %d\n", ret);
+ goto exit_destroy;
+ }
+
+ ret = gpiochip_add_data(&chip->gpio_chip, chip);
+ if (!ret)
+ return 0;
+
+exit_destroy:
+ mutex_destroy(&chip->lock);
+
+ return ret;
+}
+
+static void gen_74x164_remove(struct spi_device *spi)
+{
+ struct gen_74x164_chip *chip = spi_get_drvdata(spi);
+
+ gpiod_set_value_cansleep(chip->gpiod_oe, 0);
+ gpiochip_remove(&chip->gpio_chip);
+ mutex_destroy(&chip->lock);
+}
+
+static const struct spi_device_id gen_74x164_spi_ids[] = {
+ { .name = "74hc595" },
+ { .name = "74lvc594" },
+ {},
+};
+MODULE_DEVICE_TABLE(spi, gen_74x164_spi_ids);
+
+static const struct of_device_id gen_74x164_dt_ids[] = {
+ { .compatible = "fairchild,74hc595" },
+ { .compatible = "nxp,74lvc594" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, gen_74x164_dt_ids);
+
+static struct spi_driver gen_74x164_driver = {
+ .driver = {
+ .name = "74x164",
+ .of_match_table = gen_74x164_dt_ids,
+ },
+ .probe = gen_74x164_probe,
+ .remove = gen_74x164_remove,
+ .id_table = gen_74x164_spi_ids,
+};
+module_spi_driver(gen_74x164_driver);
+
+MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
+MODULE_AUTHOR("Miguel Gaio <miguel.gaio@efixo.com>");
+MODULE_DESCRIPTION("GPIO expander driver for 74X164 8-bits shift register");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-74xx-mmio.c b/drivers/gpio/gpio-74xx-mmio.c
new file mode 100644
index 0000000000..c7ac5a9ffb
--- /dev/null
+++ b/drivers/gpio/gpio-74xx-mmio.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 74xx MMIO GPIO driver
+ *
+ * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
+ */
+
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+#define MMIO_74XX_DIR_IN BIT(8)
+#define MMIO_74XX_DIR_OUT BIT(9)
+#define MMIO_74XX_BIT_CNT(x) ((x) & GENMASK(7, 0))
+
+struct mmio_74xx_gpio_priv {
+ struct gpio_chip gc;
+ unsigned flags;
+};
+
+static const struct of_device_id mmio_74xx_gpio_ids[] = {
+ {
+ .compatible = "ti,741g125",
+ .data = (const void *)(MMIO_74XX_DIR_IN | 1),
+ },
+ {
+ .compatible = "ti,742g125",
+ .data = (const void *)(MMIO_74XX_DIR_IN | 2),
+ },
+ {
+ .compatible = "ti,74125",
+ .data = (const void *)(MMIO_74XX_DIR_IN | 4),
+ },
+ {
+ .compatible = "ti,74365",
+ .data = (const void *)(MMIO_74XX_DIR_IN | 6),
+ },
+ {
+ .compatible = "ti,74244",
+ .data = (const void *)(MMIO_74XX_DIR_IN | 8),
+ },
+ {
+ .compatible = "ti,741624",
+ .data = (const void *)(MMIO_74XX_DIR_IN | 16),
+ },
+ {
+ .compatible = "ti,741g74",
+ .data = (const void *)(MMIO_74XX_DIR_OUT | 1),
+ },
+ {
+ .compatible = "ti,7474",
+ .data = (const void *)(MMIO_74XX_DIR_OUT | 2),
+ },
+ {
+ .compatible = "ti,74175",
+ .data = (const void *)(MMIO_74XX_DIR_OUT | 4),
+ },
+ {
+ .compatible = "ti,74174",
+ .data = (const void *)(MMIO_74XX_DIR_OUT | 6),
+ },
+ {
+ .compatible = "ti,74273",
+ .data = (const void *)(MMIO_74XX_DIR_OUT | 8),
+ },
+ {
+ .compatible = "ti,7416374",
+ .data = (const void *)(MMIO_74XX_DIR_OUT | 16),
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mmio_74xx_gpio_ids);
+
+static int mmio_74xx_get_direction(struct gpio_chip *gc, unsigned offset)
+{
+ struct mmio_74xx_gpio_priv *priv = gpiochip_get_data(gc);
+
+ if (priv->flags & MMIO_74XX_DIR_OUT)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int mmio_74xx_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct mmio_74xx_gpio_priv *priv = gpiochip_get_data(gc);
+
+ if (priv->flags & MMIO_74XX_DIR_IN)
+ return 0;
+
+ return -ENOTSUPP;
+}
+
+static int mmio_74xx_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ struct mmio_74xx_gpio_priv *priv = gpiochip_get_data(gc);
+
+ if (priv->flags & MMIO_74XX_DIR_OUT) {
+ gc->set(gc, gpio, val);
+ return 0;
+ }
+
+ return -ENOTSUPP;
+}
+
+static int mmio_74xx_gpio_probe(struct platform_device *pdev)
+{
+ struct mmio_74xx_gpio_priv *priv;
+ void __iomem *dat;
+ int err;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->flags = (uintptr_t)device_get_match_data(&pdev->dev);
+
+ dat = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dat))
+ return PTR_ERR(dat);
+
+ err = bgpio_init(&priv->gc, &pdev->dev,
+ DIV_ROUND_UP(MMIO_74XX_BIT_CNT(priv->flags), 8),
+ dat, NULL, NULL, NULL, NULL, 0);
+ if (err)
+ return err;
+
+ priv->gc.direction_input = mmio_74xx_dir_in;
+ priv->gc.direction_output = mmio_74xx_dir_out;
+ priv->gc.get_direction = mmio_74xx_get_direction;
+ priv->gc.ngpio = MMIO_74XX_BIT_CNT(priv->flags);
+ priv->gc.owner = THIS_MODULE;
+
+ return devm_gpiochip_add_data(&pdev->dev, &priv->gc, priv);
+}
+
+static struct platform_driver mmio_74xx_gpio_driver = {
+ .driver = {
+ .name = "74xx-mmio-gpio",
+ .of_match_table = mmio_74xx_gpio_ids,
+ },
+ .probe = mmio_74xx_gpio_probe,
+};
+module_platform_driver(mmio_74xx_gpio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
+MODULE_DESCRIPTION("74xx MMIO GPIO driver");
diff --git a/drivers/gpio/gpio-adnp.c b/drivers/gpio/gpio-adnp.c
new file mode 100644
index 0000000000..6dafab0cf9
--- /dev/null
+++ b/drivers/gpio/gpio-adnp.c
@@ -0,0 +1,545 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2011-2012 Avionic Design GmbH
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+
+#define GPIO_DDR(gpio) (0x00 << (gpio)->reg_shift)
+#define GPIO_PLR(gpio) (0x01 << (gpio)->reg_shift)
+#define GPIO_IER(gpio) (0x02 << (gpio)->reg_shift)
+#define GPIO_ISR(gpio) (0x03 << (gpio)->reg_shift)
+#define GPIO_PTR(gpio) (0x04 << (gpio)->reg_shift)
+
+struct adnp {
+ struct i2c_client *client;
+ struct gpio_chip gpio;
+ unsigned int reg_shift;
+
+ struct mutex i2c_lock;
+ struct mutex irq_lock;
+
+ u8 *irq_enable;
+ u8 *irq_level;
+ u8 *irq_rise;
+ u8 *irq_fall;
+ u8 *irq_high;
+ u8 *irq_low;
+};
+
+static int adnp_read(struct adnp *adnp, unsigned offset, uint8_t *value)
+{
+ int err;
+
+ err = i2c_smbus_read_byte_data(adnp->client, offset);
+ if (err < 0) {
+ dev_err(adnp->gpio.parent, "%s failed: %d\n",
+ "i2c_smbus_read_byte_data()", err);
+ return err;
+ }
+
+ *value = err;
+ return 0;
+}
+
+static int adnp_write(struct adnp *adnp, unsigned offset, uint8_t value)
+{
+ int err;
+
+ err = i2c_smbus_write_byte_data(adnp->client, offset, value);
+ if (err < 0) {
+ dev_err(adnp->gpio.parent, "%s failed: %d\n",
+ "i2c_smbus_write_byte_data()", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int adnp_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct adnp *adnp = gpiochip_get_data(chip);
+ unsigned int reg = offset >> adnp->reg_shift;
+ unsigned int pos = offset & 7;
+ u8 value;
+ int err;
+
+ err = adnp_read(adnp, GPIO_PLR(adnp) + reg, &value);
+ if (err < 0)
+ return err;
+
+ return (value & BIT(pos)) ? 1 : 0;
+}
+
+static void __adnp_gpio_set(struct adnp *adnp, unsigned offset, int value)
+{
+ unsigned int reg = offset >> adnp->reg_shift;
+ unsigned int pos = offset & 7;
+ int err;
+ u8 val;
+
+ err = adnp_read(adnp, GPIO_PLR(adnp) + reg, &val);
+ if (err < 0)
+ return;
+
+ if (value)
+ val |= BIT(pos);
+ else
+ val &= ~BIT(pos);
+
+ adnp_write(adnp, GPIO_PLR(adnp) + reg, val);
+}
+
+static void adnp_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct adnp *adnp = gpiochip_get_data(chip);
+
+ mutex_lock(&adnp->i2c_lock);
+ __adnp_gpio_set(adnp, offset, value);
+ mutex_unlock(&adnp->i2c_lock);
+}
+
+static int adnp_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ struct adnp *adnp = gpiochip_get_data(chip);
+ unsigned int reg = offset >> adnp->reg_shift;
+ unsigned int pos = offset & 7;
+ u8 value;
+ int err;
+
+ mutex_lock(&adnp->i2c_lock);
+
+ err = adnp_read(adnp, GPIO_DDR(adnp) + reg, &value);
+ if (err < 0)
+ goto out;
+
+ value &= ~BIT(pos);
+
+ err = adnp_write(adnp, GPIO_DDR(adnp) + reg, value);
+ if (err < 0)
+ goto out;
+
+ err = adnp_read(adnp, GPIO_DDR(adnp) + reg, &value);
+ if (err < 0)
+ goto out;
+
+ if (value & BIT(pos)) {
+ err = -EPERM;
+ goto out;
+ }
+
+ err = 0;
+
+out:
+ mutex_unlock(&adnp->i2c_lock);
+ return err;
+}
+
+static int adnp_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct adnp *adnp = gpiochip_get_data(chip);
+ unsigned int reg = offset >> adnp->reg_shift;
+ unsigned int pos = offset & 7;
+ int err;
+ u8 val;
+
+ mutex_lock(&adnp->i2c_lock);
+
+ err = adnp_read(adnp, GPIO_DDR(adnp) + reg, &val);
+ if (err < 0)
+ goto out;
+
+ val |= BIT(pos);
+
+ err = adnp_write(adnp, GPIO_DDR(adnp) + reg, val);
+ if (err < 0)
+ goto out;
+
+ err = adnp_read(adnp, GPIO_DDR(adnp) + reg, &val);
+ if (err < 0)
+ goto out;
+
+ if (!(val & BIT(pos))) {
+ err = -EPERM;
+ goto out;
+ }
+
+ __adnp_gpio_set(adnp, offset, value);
+ err = 0;
+
+out:
+ mutex_unlock(&adnp->i2c_lock);
+ return err;
+}
+
+static void adnp_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ struct adnp *adnp = gpiochip_get_data(chip);
+ unsigned int num_regs = 1 << adnp->reg_shift, i, j;
+ int err;
+
+ for (i = 0; i < num_regs; i++) {
+ u8 ddr, plr, ier, isr;
+
+ mutex_lock(&adnp->i2c_lock);
+
+ err = adnp_read(adnp, GPIO_DDR(adnp) + i, &ddr);
+ if (err < 0)
+ goto unlock;
+
+ err = adnp_read(adnp, GPIO_PLR(adnp) + i, &plr);
+ if (err < 0)
+ goto unlock;
+
+ err = adnp_read(adnp, GPIO_IER(adnp) + i, &ier);
+ if (err < 0)
+ goto unlock;
+
+ err = adnp_read(adnp, GPIO_ISR(adnp) + i, &isr);
+ if (err < 0)
+ goto unlock;
+
+ mutex_unlock(&adnp->i2c_lock);
+
+ for (j = 0; j < 8; j++) {
+ unsigned int bit = (i << adnp->reg_shift) + j;
+ const char *direction = "input ";
+ const char *level = "low ";
+ const char *interrupt = "disabled";
+ const char *pending = "";
+
+ if (ddr & BIT(j))
+ direction = "output";
+
+ if (plr & BIT(j))
+ level = "high";
+
+ if (ier & BIT(j))
+ interrupt = "enabled ";
+
+ if (isr & BIT(j))
+ pending = "pending";
+
+ seq_printf(s, "%2u: %s %s IRQ %s %s\n", bit,
+ direction, level, interrupt, pending);
+ }
+ }
+
+ return;
+
+unlock:
+ mutex_unlock(&adnp->i2c_lock);
+}
+
+static irqreturn_t adnp_irq(int irq, void *data)
+{
+ struct adnp *adnp = data;
+ unsigned int num_regs, i;
+
+ num_regs = 1 << adnp->reg_shift;
+
+ for (i = 0; i < num_regs; i++) {
+ unsigned int base = i << adnp->reg_shift, bit;
+ u8 changed, level, isr, ier;
+ unsigned long pending;
+ int err;
+
+ mutex_lock(&adnp->i2c_lock);
+
+ err = adnp_read(adnp, GPIO_PLR(adnp) + i, &level);
+ if (err < 0) {
+ mutex_unlock(&adnp->i2c_lock);
+ continue;
+ }
+
+ err = adnp_read(adnp, GPIO_ISR(adnp) + i, &isr);
+ if (err < 0) {
+ mutex_unlock(&adnp->i2c_lock);
+ continue;
+ }
+
+ err = adnp_read(adnp, GPIO_IER(adnp) + i, &ier);
+ if (err < 0) {
+ mutex_unlock(&adnp->i2c_lock);
+ continue;
+ }
+
+ mutex_unlock(&adnp->i2c_lock);
+
+ /* determine pins that changed levels */
+ changed = level ^ adnp->irq_level[i];
+
+ /* compute edge-triggered interrupts */
+ pending = changed & ((adnp->irq_fall[i] & ~level) |
+ (adnp->irq_rise[i] & level));
+
+ /* add in level-triggered interrupts */
+ pending |= (adnp->irq_high[i] & level) |
+ (adnp->irq_low[i] & ~level);
+
+ /* mask out non-pending and disabled interrupts */
+ pending &= isr & ier;
+
+ for_each_set_bit(bit, &pending, 8) {
+ unsigned int child_irq;
+ child_irq = irq_find_mapping(adnp->gpio.irq.domain,
+ base + bit);
+ handle_nested_irq(child_irq);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void adnp_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct adnp *adnp = gpiochip_get_data(gc);
+ unsigned int reg = d->hwirq >> adnp->reg_shift;
+ unsigned int pos = d->hwirq & 7;
+
+ adnp->irq_enable[reg] &= ~BIT(pos);
+ gpiochip_disable_irq(gc, irqd_to_hwirq(d));
+}
+
+static void adnp_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct adnp *adnp = gpiochip_get_data(gc);
+ unsigned int reg = d->hwirq >> adnp->reg_shift;
+ unsigned int pos = d->hwirq & 7;
+
+ gpiochip_enable_irq(gc, irqd_to_hwirq(d));
+ adnp->irq_enable[reg] |= BIT(pos);
+}
+
+static int adnp_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct adnp *adnp = gpiochip_get_data(gc);
+ unsigned int reg = d->hwirq >> adnp->reg_shift;
+ unsigned int pos = d->hwirq & 7;
+
+ if (type & IRQ_TYPE_EDGE_RISING)
+ adnp->irq_rise[reg] |= BIT(pos);
+ else
+ adnp->irq_rise[reg] &= ~BIT(pos);
+
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ adnp->irq_fall[reg] |= BIT(pos);
+ else
+ adnp->irq_fall[reg] &= ~BIT(pos);
+
+ if (type & IRQ_TYPE_LEVEL_HIGH)
+ adnp->irq_high[reg] |= BIT(pos);
+ else
+ adnp->irq_high[reg] &= ~BIT(pos);
+
+ if (type & IRQ_TYPE_LEVEL_LOW)
+ adnp->irq_low[reg] |= BIT(pos);
+ else
+ adnp->irq_low[reg] &= ~BIT(pos);
+
+ return 0;
+}
+
+static void adnp_irq_bus_lock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct adnp *adnp = gpiochip_get_data(gc);
+
+ mutex_lock(&adnp->irq_lock);
+}
+
+static void adnp_irq_bus_unlock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct adnp *adnp = gpiochip_get_data(gc);
+ unsigned int num_regs = 1 << adnp->reg_shift, i;
+
+ mutex_lock(&adnp->i2c_lock);
+
+ for (i = 0; i < num_regs; i++)
+ adnp_write(adnp, GPIO_IER(adnp) + i, adnp->irq_enable[i]);
+
+ mutex_unlock(&adnp->i2c_lock);
+ mutex_unlock(&adnp->irq_lock);
+}
+
+static const struct irq_chip adnp_irq_chip = {
+ .name = "gpio-adnp",
+ .irq_mask = adnp_irq_mask,
+ .irq_unmask = adnp_irq_unmask,
+ .irq_set_type = adnp_irq_set_type,
+ .irq_bus_lock = adnp_irq_bus_lock,
+ .irq_bus_sync_unlock = adnp_irq_bus_unlock,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int adnp_irq_setup(struct adnp *adnp)
+{
+ unsigned int num_regs = 1 << adnp->reg_shift, i;
+ struct gpio_chip *chip = &adnp->gpio;
+ int err;
+
+ mutex_init(&adnp->irq_lock);
+
+ /*
+ * Allocate memory to keep track of the current level and trigger
+ * modes of the interrupts. To avoid multiple allocations, a single
+ * large buffer is allocated and pointers are setup to point at the
+ * corresponding offsets. For consistency, the layout of the buffer
+ * is chosen to match the register layout of the hardware in that
+ * each segment contains the corresponding bits for all interrupts.
+ */
+ adnp->irq_enable = devm_kcalloc(chip->parent, num_regs, 6,
+ GFP_KERNEL);
+ if (!adnp->irq_enable)
+ return -ENOMEM;
+
+ adnp->irq_level = adnp->irq_enable + (num_regs * 1);
+ adnp->irq_rise = adnp->irq_enable + (num_regs * 2);
+ adnp->irq_fall = adnp->irq_enable + (num_regs * 3);
+ adnp->irq_high = adnp->irq_enable + (num_regs * 4);
+ adnp->irq_low = adnp->irq_enable + (num_regs * 5);
+
+ for (i = 0; i < num_regs; i++) {
+ /*
+ * Read the initial level of all pins to allow the emulation
+ * of edge triggered interrupts.
+ */
+ err = adnp_read(adnp, GPIO_PLR(adnp) + i, &adnp->irq_level[i]);
+ if (err < 0)
+ return err;
+
+ /* disable all interrupts */
+ err = adnp_write(adnp, GPIO_IER(adnp) + i, 0);
+ if (err < 0)
+ return err;
+
+ adnp->irq_enable[i] = 0x00;
+ }
+
+ err = devm_request_threaded_irq(chip->parent, adnp->client->irq,
+ NULL, adnp_irq,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ dev_name(chip->parent), adnp);
+ if (err != 0) {
+ dev_err(chip->parent, "can't request IRQ#%d: %d\n",
+ adnp->client->irq, err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int adnp_gpio_setup(struct adnp *adnp, unsigned int num_gpios,
+ bool is_irq_controller)
+{
+ struct gpio_chip *chip = &adnp->gpio;
+ int err;
+
+ adnp->reg_shift = get_count_order(num_gpios) - 3;
+
+ chip->direction_input = adnp_gpio_direction_input;
+ chip->direction_output = adnp_gpio_direction_output;
+ chip->get = adnp_gpio_get;
+ chip->set = adnp_gpio_set;
+ chip->can_sleep = true;
+
+ if (IS_ENABLED(CONFIG_DEBUG_FS))
+ chip->dbg_show = adnp_gpio_dbg_show;
+
+ chip->base = -1;
+ chip->ngpio = num_gpios;
+ chip->label = adnp->client->name;
+ chip->parent = &adnp->client->dev;
+ chip->owner = THIS_MODULE;
+
+ if (is_irq_controller) {
+ struct gpio_irq_chip *girq;
+
+ err = adnp_irq_setup(adnp);
+ if (err)
+ return err;
+
+ girq = &chip->irq;
+ gpio_irq_chip_set_chip(girq, &adnp_irq_chip);
+
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ girq->threaded = true;
+ }
+
+ err = devm_gpiochip_add_data(&adnp->client->dev, chip, adnp);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int adnp_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct adnp *adnp;
+ u32 num_gpios;
+ int err;
+
+ err = device_property_read_u32(dev, "nr-gpios", &num_gpios);
+ if (err < 0)
+ return err;
+
+ adnp = devm_kzalloc(&client->dev, sizeof(*adnp), GFP_KERNEL);
+ if (!adnp)
+ return -ENOMEM;
+
+ mutex_init(&adnp->i2c_lock);
+ adnp->client = client;
+
+ err = adnp_gpio_setup(adnp, num_gpios, device_property_read_bool(dev, "interrupt-controller"));
+ if (err)
+ return err;
+
+ i2c_set_clientdata(client, adnp);
+
+ return 0;
+}
+
+static const struct i2c_device_id adnp_i2c_id[] = {
+ { "gpio-adnp" },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, adnp_i2c_id);
+
+static const struct of_device_id adnp_of_match[] = {
+ { .compatible = "ad,gpio-adnp", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, adnp_of_match);
+
+static struct i2c_driver adnp_i2c_driver = {
+ .driver = {
+ .name = "gpio-adnp",
+ .of_match_table = adnp_of_match,
+ },
+ .probe = adnp_i2c_probe,
+ .id_table = adnp_i2c_id,
+};
+module_i2c_driver(adnp_i2c_driver);
+
+MODULE_DESCRIPTION("Avionic Design N-bit GPIO expander");
+MODULE_AUTHOR("Thierry Reding <thierry.reding@avionic-design.de>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-adp5520.c b/drivers/gpio/gpio-adp5520.c
new file mode 100644
index 0000000000..c55e821c63
--- /dev/null
+++ b/drivers/gpio/gpio-adp5520.c
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * GPIO driver for Analog Devices ADP5520 MFD PMICs
+ *
+ * Copyright 2009 Analog Devices Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/adp5520.h>
+#include <linux/gpio/driver.h>
+
+struct adp5520_gpio {
+ struct device *master;
+ struct gpio_chip gpio_chip;
+ unsigned char lut[ADP5520_MAXGPIOS];
+ unsigned long output;
+};
+
+static int adp5520_gpio_get_value(struct gpio_chip *chip, unsigned off)
+{
+ struct adp5520_gpio *dev;
+ uint8_t reg_val;
+
+ dev = gpiochip_get_data(chip);
+
+ /*
+ * There are dedicated registers for GPIO IN/OUT.
+ * Make sure we return the right value, even when configured as output
+ */
+
+ if (test_bit(off, &dev->output))
+ adp5520_read(dev->master, ADP5520_GPIO_OUT, &reg_val);
+ else
+ adp5520_read(dev->master, ADP5520_GPIO_IN, &reg_val);
+
+ return !!(reg_val & dev->lut[off]);
+}
+
+static void adp5520_gpio_set_value(struct gpio_chip *chip,
+ unsigned off, int val)
+{
+ struct adp5520_gpio *dev;
+ dev = gpiochip_get_data(chip);
+
+ if (val)
+ adp5520_set_bits(dev->master, ADP5520_GPIO_OUT, dev->lut[off]);
+ else
+ adp5520_clr_bits(dev->master, ADP5520_GPIO_OUT, dev->lut[off]);
+}
+
+static int adp5520_gpio_direction_input(struct gpio_chip *chip, unsigned off)
+{
+ struct adp5520_gpio *dev;
+ dev = gpiochip_get_data(chip);
+
+ clear_bit(off, &dev->output);
+
+ return adp5520_clr_bits(dev->master, ADP5520_GPIO_CFG_2,
+ dev->lut[off]);
+}
+
+static int adp5520_gpio_direction_output(struct gpio_chip *chip,
+ unsigned off, int val)
+{
+ struct adp5520_gpio *dev;
+ int ret = 0;
+ dev = gpiochip_get_data(chip);
+
+ set_bit(off, &dev->output);
+
+ if (val)
+ ret |= adp5520_set_bits(dev->master, ADP5520_GPIO_OUT,
+ dev->lut[off]);
+ else
+ ret |= adp5520_clr_bits(dev->master, ADP5520_GPIO_OUT,
+ dev->lut[off]);
+
+ ret |= adp5520_set_bits(dev->master, ADP5520_GPIO_CFG_2,
+ dev->lut[off]);
+
+ return ret;
+}
+
+static int adp5520_gpio_probe(struct platform_device *pdev)
+{
+ struct adp5520_gpio_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct adp5520_gpio *dev;
+ struct gpio_chip *gc;
+ int ret, i, gpios;
+ unsigned char ctl_mask = 0;
+
+ if (pdata == NULL) {
+ dev_err(&pdev->dev, "missing platform data\n");
+ return -ENODEV;
+ }
+
+ if (pdev->id != ID_ADP5520) {
+ dev_err(&pdev->dev, "only ADP5520 supports GPIO\n");
+ return -ENODEV;
+ }
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+ if (dev == NULL)
+ return -ENOMEM;
+
+ dev->master = pdev->dev.parent;
+
+ for (gpios = 0, i = 0; i < ADP5520_MAXGPIOS; i++)
+ if (pdata->gpio_en_mask & (1 << i))
+ dev->lut[gpios++] = 1 << i;
+
+ if (gpios < 1)
+ return -EINVAL;
+
+ gc = &dev->gpio_chip;
+ gc->direction_input = adp5520_gpio_direction_input;
+ gc->direction_output = adp5520_gpio_direction_output;
+ gc->get = adp5520_gpio_get_value;
+ gc->set = adp5520_gpio_set_value;
+ gc->can_sleep = true;
+
+ gc->base = pdata->gpio_start;
+ gc->ngpio = gpios;
+ gc->label = pdev->name;
+ gc->owner = THIS_MODULE;
+
+ ret = adp5520_clr_bits(dev->master, ADP5520_GPIO_CFG_1,
+ pdata->gpio_en_mask);
+
+ if (pdata->gpio_en_mask & ADP5520_GPIO_C3)
+ ctl_mask |= ADP5520_C3_MODE;
+
+ if (pdata->gpio_en_mask & ADP5520_GPIO_R3)
+ ctl_mask |= ADP5520_R3_MODE;
+
+ if (ctl_mask)
+ ret = adp5520_set_bits(dev->master, ADP5520_LED_CONTROL,
+ ctl_mask);
+
+ ret |= adp5520_set_bits(dev->master, ADP5520_GPIO_PULLUP,
+ pdata->gpio_pullup_mask);
+
+ if (ret) {
+ dev_err(&pdev->dev, "failed to write\n");
+ return ret;
+ }
+
+ return devm_gpiochip_add_data(&pdev->dev, &dev->gpio_chip, dev);
+}
+
+static struct platform_driver adp5520_gpio_driver = {
+ .driver = {
+ .name = "adp5520-gpio",
+ },
+ .probe = adp5520_gpio_probe,
+};
+
+module_platform_driver(adp5520_gpio_driver);
+
+MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
+MODULE_DESCRIPTION("GPIO ADP5520 Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:adp5520-gpio");
diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c
new file mode 100644
index 0000000000..38e0fff9af
--- /dev/null
+++ b/drivers/gpio/gpio-aggregator.c
@@ -0,0 +1,636 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// GPIO Aggregator
+//
+// Copyright (C) 2019-2020 Glider bv
+
+#define DRV_NAME "gpio-aggregator"
+#define pr_fmt(fmt) DRV_NAME ": " fmt
+
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/idr.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/machine.h>
+
+#define AGGREGATOR_MAX_GPIOS 512
+
+/*
+ * GPIO Aggregator sysfs interface
+ */
+
+struct gpio_aggregator {
+ struct gpiod_lookup_table *lookups;
+ struct platform_device *pdev;
+ char args[];
+};
+
+static DEFINE_MUTEX(gpio_aggregator_lock); /* protects idr */
+static DEFINE_IDR(gpio_aggregator_idr);
+
+static int aggr_add_gpio(struct gpio_aggregator *aggr, const char *key,
+ int hwnum, unsigned int *n)
+{
+ struct gpiod_lookup_table *lookups;
+
+ lookups = krealloc(aggr->lookups, struct_size(lookups, table, *n + 2),
+ GFP_KERNEL);
+ if (!lookups)
+ return -ENOMEM;
+
+ lookups->table[*n] = GPIO_LOOKUP_IDX(key, hwnum, NULL, *n, 0);
+
+ (*n)++;
+ memset(&lookups->table[*n], 0, sizeof(lookups->table[*n]));
+
+ aggr->lookups = lookups;
+ return 0;
+}
+
+static int aggr_parse(struct gpio_aggregator *aggr)
+{
+ char *args = skip_spaces(aggr->args);
+ char *name, *offsets, *p;
+ unsigned long *bitmap;
+ unsigned int i, n = 0;
+ int error = 0;
+
+ bitmap = bitmap_alloc(AGGREGATOR_MAX_GPIOS, GFP_KERNEL);
+ if (!bitmap)
+ return -ENOMEM;
+
+ args = next_arg(args, &name, &p);
+ while (*args) {
+ args = next_arg(args, &offsets, &p);
+
+ p = get_options(offsets, 0, &error);
+ if (error == 0 || *p) {
+ /* Named GPIO line */
+ error = aggr_add_gpio(aggr, name, U16_MAX, &n);
+ if (error)
+ goto free_bitmap;
+
+ name = offsets;
+ continue;
+ }
+
+ /* GPIO chip + offset(s) */
+ error = bitmap_parselist(offsets, bitmap, AGGREGATOR_MAX_GPIOS);
+ if (error) {
+ pr_err("Cannot parse %s: %d\n", offsets, error);
+ goto free_bitmap;
+ }
+
+ for_each_set_bit(i, bitmap, AGGREGATOR_MAX_GPIOS) {
+ error = aggr_add_gpio(aggr, name, i, &n);
+ if (error)
+ goto free_bitmap;
+ }
+
+ args = next_arg(args, &name, &p);
+ }
+
+ if (!n) {
+ pr_err("No GPIOs specified\n");
+ error = -EINVAL;
+ }
+
+free_bitmap:
+ bitmap_free(bitmap);
+ return error;
+}
+
+static ssize_t new_device_store(struct device_driver *driver, const char *buf,
+ size_t count)
+{
+ struct gpio_aggregator *aggr;
+ struct platform_device *pdev;
+ int res, id;
+
+ /* kernfs guarantees string termination, so count + 1 is safe */
+ aggr = kzalloc(sizeof(*aggr) + count + 1, GFP_KERNEL);
+ if (!aggr)
+ return -ENOMEM;
+
+ memcpy(aggr->args, buf, count + 1);
+
+ aggr->lookups = kzalloc(struct_size(aggr->lookups, table, 1),
+ GFP_KERNEL);
+ if (!aggr->lookups) {
+ res = -ENOMEM;
+ goto free_ga;
+ }
+
+ mutex_lock(&gpio_aggregator_lock);
+ id = idr_alloc(&gpio_aggregator_idr, aggr, 0, 0, GFP_KERNEL);
+ mutex_unlock(&gpio_aggregator_lock);
+
+ if (id < 0) {
+ res = id;
+ goto free_table;
+ }
+
+ aggr->lookups->dev_id = kasprintf(GFP_KERNEL, "%s.%d", DRV_NAME, id);
+ if (!aggr->lookups->dev_id) {
+ res = -ENOMEM;
+ goto remove_idr;
+ }
+
+ res = aggr_parse(aggr);
+ if (res)
+ goto free_dev_id;
+
+ gpiod_add_lookup_table(aggr->lookups);
+
+ pdev = platform_device_register_simple(DRV_NAME, id, NULL, 0);
+ if (IS_ERR(pdev)) {
+ res = PTR_ERR(pdev);
+ goto remove_table;
+ }
+
+ aggr->pdev = pdev;
+ return count;
+
+remove_table:
+ gpiod_remove_lookup_table(aggr->lookups);
+free_dev_id:
+ kfree(aggr->lookups->dev_id);
+remove_idr:
+ mutex_lock(&gpio_aggregator_lock);
+ idr_remove(&gpio_aggregator_idr, id);
+ mutex_unlock(&gpio_aggregator_lock);
+free_table:
+ kfree(aggr->lookups);
+free_ga:
+ kfree(aggr);
+ return res;
+}
+
+static DRIVER_ATTR_WO(new_device);
+
+static void gpio_aggregator_free(struct gpio_aggregator *aggr)
+{
+ platform_device_unregister(aggr->pdev);
+ gpiod_remove_lookup_table(aggr->lookups);
+ kfree(aggr->lookups->dev_id);
+ kfree(aggr->lookups);
+ kfree(aggr);
+}
+
+static ssize_t delete_device_store(struct device_driver *driver,
+ const char *buf, size_t count)
+{
+ struct gpio_aggregator *aggr;
+ unsigned int id;
+ int error;
+
+ if (!str_has_prefix(buf, DRV_NAME "."))
+ return -EINVAL;
+
+ error = kstrtouint(buf + strlen(DRV_NAME "."), 10, &id);
+ if (error)
+ return error;
+
+ mutex_lock(&gpio_aggregator_lock);
+ aggr = idr_remove(&gpio_aggregator_idr, id);
+ mutex_unlock(&gpio_aggregator_lock);
+ if (!aggr)
+ return -ENOENT;
+
+ gpio_aggregator_free(aggr);
+ return count;
+}
+static DRIVER_ATTR_WO(delete_device);
+
+static struct attribute *gpio_aggregator_attrs[] = {
+ &driver_attr_new_device.attr,
+ &driver_attr_delete_device.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(gpio_aggregator);
+
+static int __exit gpio_aggregator_idr_remove(int id, void *p, void *data)
+{
+ gpio_aggregator_free(p);
+ return 0;
+}
+
+static void __exit gpio_aggregator_remove_all(void)
+{
+ mutex_lock(&gpio_aggregator_lock);
+ idr_for_each(&gpio_aggregator_idr, gpio_aggregator_idr_remove, NULL);
+ idr_destroy(&gpio_aggregator_idr);
+ mutex_unlock(&gpio_aggregator_lock);
+}
+
+
+/*
+ * GPIO Forwarder
+ */
+
+struct gpiochip_fwd_timing {
+ u32 ramp_up_us;
+ u32 ramp_down_us;
+};
+
+struct gpiochip_fwd {
+ struct gpio_chip chip;
+ struct gpio_desc **descs;
+ union {
+ struct mutex mlock; /* protects tmp[] if can_sleep */
+ spinlock_t slock; /* protects tmp[] if !can_sleep */
+ };
+ struct gpiochip_fwd_timing *delay_timings;
+ unsigned long tmp[]; /* values and descs for multiple ops */
+};
+
+#define fwd_tmp_values(fwd) &(fwd)->tmp[0]
+#define fwd_tmp_descs(fwd) (void *)&(fwd)->tmp[BITS_TO_LONGS((fwd)->chip.ngpio)]
+
+#define fwd_tmp_size(ngpios) (BITS_TO_LONGS((ngpios)) + (ngpios))
+
+static int gpio_fwd_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
+
+ return gpiod_get_direction(fwd->descs[offset]);
+}
+
+static int gpio_fwd_direction_input(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
+
+ return gpiod_direction_input(fwd->descs[offset]);
+}
+
+static int gpio_fwd_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
+
+ return gpiod_direction_output(fwd->descs[offset], value);
+}
+
+static int gpio_fwd_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
+
+ return chip->can_sleep ? gpiod_get_value_cansleep(fwd->descs[offset])
+ : gpiod_get_value(fwd->descs[offset]);
+}
+
+static int gpio_fwd_get_multiple(struct gpiochip_fwd *fwd, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct gpio_desc **descs = fwd_tmp_descs(fwd);
+ unsigned long *values = fwd_tmp_values(fwd);
+ unsigned int i, j = 0;
+ int error;
+
+ bitmap_clear(values, 0, fwd->chip.ngpio);
+ for_each_set_bit(i, mask, fwd->chip.ngpio)
+ descs[j++] = fwd->descs[i];
+
+ if (fwd->chip.can_sleep)
+ error = gpiod_get_array_value_cansleep(j, descs, NULL, values);
+ else
+ error = gpiod_get_array_value(j, descs, NULL, values);
+ if (error)
+ return error;
+
+ j = 0;
+ for_each_set_bit(i, mask, fwd->chip.ngpio)
+ __assign_bit(i, bits, test_bit(j++, values));
+
+ return 0;
+}
+
+static int gpio_fwd_get_multiple_locked(struct gpio_chip *chip,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
+ unsigned long flags;
+ int error;
+
+ if (chip->can_sleep) {
+ mutex_lock(&fwd->mlock);
+ error = gpio_fwd_get_multiple(fwd, mask, bits);
+ mutex_unlock(&fwd->mlock);
+ } else {
+ spin_lock_irqsave(&fwd->slock, flags);
+ error = gpio_fwd_get_multiple(fwd, mask, bits);
+ spin_unlock_irqrestore(&fwd->slock, flags);
+ }
+
+ return error;
+}
+
+static void gpio_fwd_delay(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
+ const struct gpiochip_fwd_timing *delay_timings;
+ bool is_active_low = gpiod_is_active_low(fwd->descs[offset]);
+ u32 delay_us;
+
+ delay_timings = &fwd->delay_timings[offset];
+ if ((!is_active_low && value) || (is_active_low && !value))
+ delay_us = delay_timings->ramp_up_us;
+ else
+ delay_us = delay_timings->ramp_down_us;
+ if (!delay_us)
+ return;
+
+ if (chip->can_sleep)
+ fsleep(delay_us);
+ else
+ udelay(delay_us);
+}
+
+static void gpio_fwd_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
+
+ if (chip->can_sleep)
+ gpiod_set_value_cansleep(fwd->descs[offset], value);
+ else
+ gpiod_set_value(fwd->descs[offset], value);
+
+ if (fwd->delay_timings)
+ gpio_fwd_delay(chip, offset, value);
+}
+
+static void gpio_fwd_set_multiple(struct gpiochip_fwd *fwd, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct gpio_desc **descs = fwd_tmp_descs(fwd);
+ unsigned long *values = fwd_tmp_values(fwd);
+ unsigned int i, j = 0;
+
+ for_each_set_bit(i, mask, fwd->chip.ngpio) {
+ __assign_bit(j, values, test_bit(i, bits));
+ descs[j++] = fwd->descs[i];
+ }
+
+ if (fwd->chip.can_sleep)
+ gpiod_set_array_value_cansleep(j, descs, NULL, values);
+ else
+ gpiod_set_array_value(j, descs, NULL, values);
+}
+
+static void gpio_fwd_set_multiple_locked(struct gpio_chip *chip,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
+ unsigned long flags;
+
+ if (chip->can_sleep) {
+ mutex_lock(&fwd->mlock);
+ gpio_fwd_set_multiple(fwd, mask, bits);
+ mutex_unlock(&fwd->mlock);
+ } else {
+ spin_lock_irqsave(&fwd->slock, flags);
+ gpio_fwd_set_multiple(fwd, mask, bits);
+ spin_unlock_irqrestore(&fwd->slock, flags);
+ }
+}
+
+static int gpio_fwd_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
+
+ return gpiod_set_config(fwd->descs[offset], config);
+}
+
+static int gpio_fwd_to_irq(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
+
+ return gpiod_to_irq(fwd->descs[offset]);
+}
+
+/*
+ * The GPIO delay provides a way to configure platform specific delays
+ * for the GPIO ramp-up or ramp-down delays. This can serve the following
+ * purposes:
+ * - Open-drain output using an RC filter
+ */
+#define FWD_FEATURE_DELAY BIT(0)
+
+#ifdef CONFIG_OF_GPIO
+static int gpiochip_fwd_delay_of_xlate(struct gpio_chip *chip,
+ const struct of_phandle_args *gpiospec,
+ u32 *flags)
+{
+ struct gpiochip_fwd *fwd = gpiochip_get_data(chip);
+ struct gpiochip_fwd_timing *timings;
+ u32 line;
+
+ if (gpiospec->args_count != chip->of_gpio_n_cells)
+ return -EINVAL;
+
+ line = gpiospec->args[0];
+ if (line >= chip->ngpio)
+ return -EINVAL;
+
+ timings = &fwd->delay_timings[line];
+ timings->ramp_up_us = gpiospec->args[1];
+ timings->ramp_down_us = gpiospec->args[2];
+
+ return line;
+}
+
+static int gpiochip_fwd_setup_delay_line(struct device *dev, struct gpio_chip *chip,
+ struct gpiochip_fwd *fwd)
+{
+ fwd->delay_timings = devm_kcalloc(dev, chip->ngpio,
+ sizeof(*fwd->delay_timings),
+ GFP_KERNEL);
+ if (!fwd->delay_timings)
+ return -ENOMEM;
+
+ chip->of_xlate = gpiochip_fwd_delay_of_xlate;
+ chip->of_gpio_n_cells = 3;
+
+ return 0;
+}
+#else
+static int gpiochip_fwd_setup_delay_line(struct device *dev, struct gpio_chip *chip,
+ struct gpiochip_fwd *fwd)
+{
+ return 0;
+}
+#endif /* !CONFIG_OF_GPIO */
+
+/**
+ * gpiochip_fwd_create() - Create a new GPIO forwarder
+ * @dev: Parent device pointer
+ * @ngpios: Number of GPIOs in the forwarder.
+ * @descs: Array containing the GPIO descriptors to forward to.
+ * This array must contain @ngpios entries, and must not be deallocated
+ * before the forwarder has been destroyed again.
+ * @features: Bitwise ORed features as defined with FWD_FEATURE_*.
+ *
+ * This function creates a new gpiochip, which forwards all GPIO operations to
+ * the passed GPIO descriptors.
+ *
+ * Return: An opaque object pointer, or an ERR_PTR()-encoded negative error
+ * code on failure.
+ */
+static struct gpiochip_fwd *gpiochip_fwd_create(struct device *dev,
+ unsigned int ngpios,
+ struct gpio_desc *descs[],
+ unsigned long features)
+{
+ const char *label = dev_name(dev);
+ struct gpiochip_fwd *fwd;
+ struct gpio_chip *chip;
+ unsigned int i;
+ int error;
+
+ fwd = devm_kzalloc(dev, struct_size(fwd, tmp, fwd_tmp_size(ngpios)),
+ GFP_KERNEL);
+ if (!fwd)
+ return ERR_PTR(-ENOMEM);
+
+ chip = &fwd->chip;
+
+ /*
+ * If any of the GPIO lines are sleeping, then the entire forwarder
+ * will be sleeping.
+ * If any of the chips support .set_config(), then the forwarder will
+ * support setting configs.
+ */
+ for (i = 0; i < ngpios; i++) {
+ struct gpio_chip *parent = gpiod_to_chip(descs[i]);
+
+ dev_dbg(dev, "%u => gpio %d irq %d\n", i,
+ desc_to_gpio(descs[i]), gpiod_to_irq(descs[i]));
+
+ if (gpiod_cansleep(descs[i]))
+ chip->can_sleep = true;
+ if (parent && parent->set_config)
+ chip->set_config = gpio_fwd_set_config;
+ }
+
+ chip->label = label;
+ chip->parent = dev;
+ chip->owner = THIS_MODULE;
+ chip->get_direction = gpio_fwd_get_direction;
+ chip->direction_input = gpio_fwd_direction_input;
+ chip->direction_output = gpio_fwd_direction_output;
+ chip->get = gpio_fwd_get;
+ chip->get_multiple = gpio_fwd_get_multiple_locked;
+ chip->set = gpio_fwd_set;
+ chip->set_multiple = gpio_fwd_set_multiple_locked;
+ chip->to_irq = gpio_fwd_to_irq;
+ chip->base = -1;
+ chip->ngpio = ngpios;
+ fwd->descs = descs;
+
+ if (chip->can_sleep)
+ mutex_init(&fwd->mlock);
+ else
+ spin_lock_init(&fwd->slock);
+
+ if (features & FWD_FEATURE_DELAY) {
+ error = gpiochip_fwd_setup_delay_line(dev, chip, fwd);
+ if (error)
+ return ERR_PTR(error);
+ }
+
+ error = devm_gpiochip_add_data(dev, chip, fwd);
+ if (error)
+ return ERR_PTR(error);
+
+ return fwd;
+}
+
+
+/*
+ * GPIO Aggregator platform device
+ */
+
+static int gpio_aggregator_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct gpio_desc **descs;
+ struct gpiochip_fwd *fwd;
+ unsigned long features;
+ int i, n;
+
+ n = gpiod_count(dev, NULL);
+ if (n < 0)
+ return n;
+
+ descs = devm_kmalloc_array(dev, n, sizeof(*descs), GFP_KERNEL);
+ if (!descs)
+ return -ENOMEM;
+
+ for (i = 0; i < n; i++) {
+ descs[i] = devm_gpiod_get_index(dev, NULL, i, GPIOD_ASIS);
+ if (IS_ERR(descs[i]))
+ return PTR_ERR(descs[i]);
+ }
+
+ features = (uintptr_t)device_get_match_data(dev);
+ fwd = gpiochip_fwd_create(dev, n, descs, features);
+ if (IS_ERR(fwd))
+ return PTR_ERR(fwd);
+
+ platform_set_drvdata(pdev, fwd);
+ return 0;
+}
+
+static const struct of_device_id gpio_aggregator_dt_ids[] = {
+ {
+ .compatible = "gpio-delay",
+ .data = (void *)FWD_FEATURE_DELAY,
+ },
+ /*
+ * Add GPIO-operated devices controlled from userspace below,
+ * or use "driver_override" in sysfs.
+ */
+ {}
+};
+MODULE_DEVICE_TABLE(of, gpio_aggregator_dt_ids);
+
+static struct platform_driver gpio_aggregator_driver = {
+ .probe = gpio_aggregator_probe,
+ .driver = {
+ .name = DRV_NAME,
+ .groups = gpio_aggregator_groups,
+ .of_match_table = gpio_aggregator_dt_ids,
+ },
+};
+
+static int __init gpio_aggregator_init(void)
+{
+ return platform_driver_register(&gpio_aggregator_driver);
+}
+module_init(gpio_aggregator_init);
+
+static void __exit gpio_aggregator_exit(void)
+{
+ gpio_aggregator_remove_all();
+ platform_driver_unregister(&gpio_aggregator_driver);
+}
+module_exit(gpio_aggregator_exit);
+
+MODULE_AUTHOR("Geert Uytterhoeven <geert+renesas@glider.be>");
+MODULE_DESCRIPTION("GPIO Aggregator");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-altera-a10sr.c b/drivers/gpio/gpio-altera-a10sr.c
new file mode 100644
index 0000000000..11edf1fe6c
--- /dev/null
+++ b/drivers/gpio/gpio-altera-a10sr.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright Intel Corporation (C) 2014-2016. All Rights Reserved
+ *
+ * GPIO driver for Altera Arria10 MAX5 System Resource Chip
+ *
+ * Adapted from gpio-tps65910.c
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/mfd/altera-a10sr.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+
+/**
+ * struct altr_a10sr_gpio - Altera Max5 GPIO device private data structure
+ * @gp: : instance of the gpio_chip
+ * @regmap: the regmap from the parent device.
+ */
+struct altr_a10sr_gpio {
+ struct gpio_chip gp;
+ struct regmap *regmap;
+};
+
+static int altr_a10sr_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct altr_a10sr_gpio *gpio = gpiochip_get_data(chip);
+ int ret, val;
+
+ ret = regmap_read(gpio->regmap, ALTR_A10SR_PBDSW_REG, &val);
+ if (ret < 0)
+ return ret;
+
+ return !!(val & BIT(offset - ALTR_A10SR_LED_VALID_SHIFT));
+}
+
+static void altr_a10sr_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct altr_a10sr_gpio *gpio = gpiochip_get_data(chip);
+
+ regmap_update_bits(gpio->regmap, ALTR_A10SR_LED_REG,
+ BIT(ALTR_A10SR_LED_VALID_SHIFT + offset),
+ value ? BIT(ALTR_A10SR_LED_VALID_SHIFT + offset)
+ : 0);
+}
+
+static int altr_a10sr_gpio_direction_input(struct gpio_chip *gc,
+ unsigned int nr)
+{
+ if (nr < (ALTR_A10SR_IN_VALID_RANGE_LO - ALTR_A10SR_LED_VALID_SHIFT))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int altr_a10sr_gpio_direction_output(struct gpio_chip *gc,
+ unsigned int nr, int value)
+{
+ if (nr > (ALTR_A10SR_OUT_VALID_RANGE_HI - ALTR_A10SR_LED_VALID_SHIFT))
+ return -EINVAL;
+
+ altr_a10sr_gpio_set(gc, nr, value);
+ return 0;
+}
+
+static const struct gpio_chip altr_a10sr_gc = {
+ .label = "altr_a10sr_gpio",
+ .owner = THIS_MODULE,
+ .get = altr_a10sr_gpio_get,
+ .set = altr_a10sr_gpio_set,
+ .direction_input = altr_a10sr_gpio_direction_input,
+ .direction_output = altr_a10sr_gpio_direction_output,
+ .can_sleep = true,
+ .ngpio = 12,
+ .base = -1,
+};
+
+static int altr_a10sr_gpio_probe(struct platform_device *pdev)
+{
+ struct altr_a10sr_gpio *gpio;
+ struct altr_a10sr *a10sr = dev_get_drvdata(pdev->dev.parent);
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->regmap = a10sr->regmap;
+
+ gpio->gp = altr_a10sr_gc;
+ gpio->gp.parent = pdev->dev.parent;
+ gpio->gp.fwnode = dev_fwnode(&pdev->dev);
+
+ return devm_gpiochip_add_data(&pdev->dev, &gpio->gp, gpio);
+}
+
+static const struct of_device_id altr_a10sr_gpio_of_match[] = {
+ { .compatible = "altr,a10sr-gpio" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, altr_a10sr_gpio_of_match);
+
+static struct platform_driver altr_a10sr_gpio_driver = {
+ .probe = altr_a10sr_gpio_probe,
+ .driver = {
+ .name = "altr_a10sr_gpio",
+ .of_match_table = altr_a10sr_gpio_of_match,
+ },
+};
+module_platform_driver(altr_a10sr_gpio_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Thor Thayer <tthayer@opensource.altera.com>");
+MODULE_DESCRIPTION("Altera Arria10 System Resource Chip GPIO");
diff --git a/drivers/gpio/gpio-altera.c b/drivers/gpio/gpio-altera.c
new file mode 100644
index 0000000000..54d7c450c5
--- /dev/null
+++ b/drivers/gpio/gpio-altera.c
@@ -0,0 +1,358 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2013 Altera Corporation
+ * Based on gpio-mpc8xxx.c
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/legacy-of-mm-gpiochip.h>
+#include <linux/platform_device.h>
+
+#define ALTERA_GPIO_MAX_NGPIO 32
+#define ALTERA_GPIO_DATA 0x0
+#define ALTERA_GPIO_DIR 0x4
+#define ALTERA_GPIO_IRQ_MASK 0x8
+#define ALTERA_GPIO_EDGE_CAP 0xc
+
+/**
+* struct altera_gpio_chip
+* @mmchip : memory mapped chip structure.
+* @gpio_lock : synchronization lock so that new irq/set/get requests
+* will be blocked until the current one completes.
+* @interrupt_trigger : specifies the hardware configured IRQ trigger type
+* (rising, falling, both, high)
+* @mapped_irq : kernel mapped irq number.
+*/
+struct altera_gpio_chip {
+ struct of_mm_gpio_chip mmchip;
+ raw_spinlock_t gpio_lock;
+ int interrupt_trigger;
+ int mapped_irq;
+};
+
+static void altera_gpio_irq_unmask(struct irq_data *d)
+{
+ struct altera_gpio_chip *altera_gc;
+ struct of_mm_gpio_chip *mm_gc;
+ unsigned long flags;
+ u32 intmask;
+
+ altera_gc = gpiochip_get_data(irq_data_get_irq_chip_data(d));
+ mm_gc = &altera_gc->mmchip;
+ gpiochip_enable_irq(&mm_gc->gc, irqd_to_hwirq(d));
+
+ raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags);
+ intmask = readl(mm_gc->regs + ALTERA_GPIO_IRQ_MASK);
+ /* Set ALTERA_GPIO_IRQ_MASK bit to unmask */
+ intmask |= BIT(irqd_to_hwirq(d));
+ writel(intmask, mm_gc->regs + ALTERA_GPIO_IRQ_MASK);
+ raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags);
+}
+
+static void altera_gpio_irq_mask(struct irq_data *d)
+{
+ struct altera_gpio_chip *altera_gc;
+ struct of_mm_gpio_chip *mm_gc;
+ unsigned long flags;
+ u32 intmask;
+
+ altera_gc = gpiochip_get_data(irq_data_get_irq_chip_data(d));
+ mm_gc = &altera_gc->mmchip;
+
+ raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags);
+ intmask = readl(mm_gc->regs + ALTERA_GPIO_IRQ_MASK);
+ /* Clear ALTERA_GPIO_IRQ_MASK bit to mask */
+ intmask &= ~BIT(irqd_to_hwirq(d));
+ writel(intmask, mm_gc->regs + ALTERA_GPIO_IRQ_MASK);
+ raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags);
+ gpiochip_disable_irq(&mm_gc->gc, irqd_to_hwirq(d));
+}
+
+/*
+ * This controller's IRQ type is synthesized in hardware, so this function
+ * just checks if the requested set_type matches the synthesized IRQ type
+ */
+static int altera_gpio_irq_set_type(struct irq_data *d,
+ unsigned int type)
+{
+ struct altera_gpio_chip *altera_gc;
+
+ altera_gc = gpiochip_get_data(irq_data_get_irq_chip_data(d));
+
+ if (type == IRQ_TYPE_NONE) {
+ irq_set_handler_locked(d, handle_bad_irq);
+ return 0;
+ }
+ if (type == altera_gc->interrupt_trigger) {
+ if (type == IRQ_TYPE_LEVEL_HIGH)
+ irq_set_handler_locked(d, handle_level_irq);
+ else
+ irq_set_handler_locked(d, handle_simple_irq);
+ return 0;
+ }
+ irq_set_handler_locked(d, handle_bad_irq);
+ return -EINVAL;
+}
+
+static unsigned int altera_gpio_irq_startup(struct irq_data *d)
+{
+ altera_gpio_irq_unmask(d);
+
+ return 0;
+}
+
+static int altera_gpio_get(struct gpio_chip *gc, unsigned offset)
+{
+ struct of_mm_gpio_chip *mm_gc;
+
+ mm_gc = to_of_mm_gpio_chip(gc);
+
+ return !!(readl(mm_gc->regs + ALTERA_GPIO_DATA) & BIT(offset));
+}
+
+static void altera_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
+{
+ struct of_mm_gpio_chip *mm_gc;
+ struct altera_gpio_chip *chip;
+ unsigned long flags;
+ unsigned int data_reg;
+
+ mm_gc = to_of_mm_gpio_chip(gc);
+ chip = gpiochip_get_data(gc);
+
+ raw_spin_lock_irqsave(&chip->gpio_lock, flags);
+ data_reg = readl(mm_gc->regs + ALTERA_GPIO_DATA);
+ if (value)
+ data_reg |= BIT(offset);
+ else
+ data_reg &= ~BIT(offset);
+ writel(data_reg, mm_gc->regs + ALTERA_GPIO_DATA);
+ raw_spin_unlock_irqrestore(&chip->gpio_lock, flags);
+}
+
+static int altera_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
+{
+ struct of_mm_gpio_chip *mm_gc;
+ struct altera_gpio_chip *chip;
+ unsigned long flags;
+ unsigned int gpio_ddr;
+
+ mm_gc = to_of_mm_gpio_chip(gc);
+ chip = gpiochip_get_data(gc);
+
+ raw_spin_lock_irqsave(&chip->gpio_lock, flags);
+ /* Set pin as input, assumes software controlled IP */
+ gpio_ddr = readl(mm_gc->regs + ALTERA_GPIO_DIR);
+ gpio_ddr &= ~BIT(offset);
+ writel(gpio_ddr, mm_gc->regs + ALTERA_GPIO_DIR);
+ raw_spin_unlock_irqrestore(&chip->gpio_lock, flags);
+
+ return 0;
+}
+
+static int altera_gpio_direction_output(struct gpio_chip *gc,
+ unsigned offset, int value)
+{
+ struct of_mm_gpio_chip *mm_gc;
+ struct altera_gpio_chip *chip;
+ unsigned long flags;
+ unsigned int data_reg, gpio_ddr;
+
+ mm_gc = to_of_mm_gpio_chip(gc);
+ chip = gpiochip_get_data(gc);
+
+ raw_spin_lock_irqsave(&chip->gpio_lock, flags);
+ /* Sets the GPIO value */
+ data_reg = readl(mm_gc->regs + ALTERA_GPIO_DATA);
+ if (value)
+ data_reg |= BIT(offset);
+ else
+ data_reg &= ~BIT(offset);
+ writel(data_reg, mm_gc->regs + ALTERA_GPIO_DATA);
+
+ /* Set pin as output, assumes software controlled IP */
+ gpio_ddr = readl(mm_gc->regs + ALTERA_GPIO_DIR);
+ gpio_ddr |= BIT(offset);
+ writel(gpio_ddr, mm_gc->regs + ALTERA_GPIO_DIR);
+ raw_spin_unlock_irqrestore(&chip->gpio_lock, flags);
+
+ return 0;
+}
+
+static void altera_gpio_irq_edge_handler(struct irq_desc *desc)
+{
+ struct altera_gpio_chip *altera_gc;
+ struct irq_chip *chip;
+ struct of_mm_gpio_chip *mm_gc;
+ struct irq_domain *irqdomain;
+ unsigned long status;
+ int i;
+
+ altera_gc = gpiochip_get_data(irq_desc_get_handler_data(desc));
+ chip = irq_desc_get_chip(desc);
+ mm_gc = &altera_gc->mmchip;
+ irqdomain = altera_gc->mmchip.gc.irq.domain;
+
+ chained_irq_enter(chip, desc);
+
+ while ((status =
+ (readl(mm_gc->regs + ALTERA_GPIO_EDGE_CAP) &
+ readl(mm_gc->regs + ALTERA_GPIO_IRQ_MASK)))) {
+ writel(status, mm_gc->regs + ALTERA_GPIO_EDGE_CAP);
+ for_each_set_bit(i, &status, mm_gc->gc.ngpio)
+ generic_handle_domain_irq(irqdomain, i);
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static void altera_gpio_irq_leveL_high_handler(struct irq_desc *desc)
+{
+ struct altera_gpio_chip *altera_gc;
+ struct irq_chip *chip;
+ struct of_mm_gpio_chip *mm_gc;
+ struct irq_domain *irqdomain;
+ unsigned long status;
+ int i;
+
+ altera_gc = gpiochip_get_data(irq_desc_get_handler_data(desc));
+ chip = irq_desc_get_chip(desc);
+ mm_gc = &altera_gc->mmchip;
+ irqdomain = altera_gc->mmchip.gc.irq.domain;
+
+ chained_irq_enter(chip, desc);
+
+ status = readl(mm_gc->regs + ALTERA_GPIO_DATA);
+ status &= readl(mm_gc->regs + ALTERA_GPIO_IRQ_MASK);
+
+ for_each_set_bit(i, &status, mm_gc->gc.ngpio)
+ generic_handle_domain_irq(irqdomain, i);
+
+ chained_irq_exit(chip, desc);
+}
+
+static const struct irq_chip altera_gpio_irq_chip = {
+ .name = "altera-gpio",
+ .irq_mask = altera_gpio_irq_mask,
+ .irq_unmask = altera_gpio_irq_unmask,
+ .irq_set_type = altera_gpio_irq_set_type,
+ .irq_startup = altera_gpio_irq_startup,
+ .irq_shutdown = altera_gpio_irq_mask,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int altera_gpio_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ int reg, ret;
+ struct altera_gpio_chip *altera_gc;
+ struct gpio_irq_chip *girq;
+
+ altera_gc = devm_kzalloc(&pdev->dev, sizeof(*altera_gc), GFP_KERNEL);
+ if (!altera_gc)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&altera_gc->gpio_lock);
+
+ if (of_property_read_u32(node, "altr,ngpio", &reg))
+ /* By default assume maximum ngpio */
+ altera_gc->mmchip.gc.ngpio = ALTERA_GPIO_MAX_NGPIO;
+ else
+ altera_gc->mmchip.gc.ngpio = reg;
+
+ if (altera_gc->mmchip.gc.ngpio > ALTERA_GPIO_MAX_NGPIO) {
+ dev_warn(&pdev->dev,
+ "ngpio is greater than %d, defaulting to %d\n",
+ ALTERA_GPIO_MAX_NGPIO, ALTERA_GPIO_MAX_NGPIO);
+ altera_gc->mmchip.gc.ngpio = ALTERA_GPIO_MAX_NGPIO;
+ }
+
+ altera_gc->mmchip.gc.direction_input = altera_gpio_direction_input;
+ altera_gc->mmchip.gc.direction_output = altera_gpio_direction_output;
+ altera_gc->mmchip.gc.get = altera_gpio_get;
+ altera_gc->mmchip.gc.set = altera_gpio_set;
+ altera_gc->mmchip.gc.owner = THIS_MODULE;
+ altera_gc->mmchip.gc.parent = &pdev->dev;
+
+ altera_gc->mapped_irq = platform_get_irq_optional(pdev, 0);
+
+ if (altera_gc->mapped_irq < 0)
+ goto skip_irq;
+
+ if (of_property_read_u32(node, "altr,interrupt-type", &reg)) {
+ dev_err(&pdev->dev,
+ "altr,interrupt-type value not set in device tree\n");
+ return -EINVAL;
+ }
+ altera_gc->interrupt_trigger = reg;
+
+ girq = &altera_gc->mmchip.gc.irq;
+ gpio_irq_chip_set_chip(girq, &altera_gpio_irq_chip);
+
+ if (altera_gc->interrupt_trigger == IRQ_TYPE_LEVEL_HIGH)
+ girq->parent_handler = altera_gpio_irq_leveL_high_handler;
+ else
+ girq->parent_handler = altera_gpio_irq_edge_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(&pdev->dev, 1, sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+ girq->parents[0] = altera_gc->mapped_irq;
+
+skip_irq:
+ ret = of_mm_gpiochip_add_data(node, &altera_gc->mmchip, altera_gc);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, altera_gc);
+
+ return 0;
+}
+
+static int altera_gpio_remove(struct platform_device *pdev)
+{
+ struct altera_gpio_chip *altera_gc = platform_get_drvdata(pdev);
+
+ of_mm_gpiochip_remove(&altera_gc->mmchip);
+
+ return 0;
+}
+
+static const struct of_device_id altera_gpio_of_match[] = {
+ { .compatible = "altr,pio-1.0", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, altera_gpio_of_match);
+
+static struct platform_driver altera_gpio_driver = {
+ .driver = {
+ .name = "altera_gpio",
+ .of_match_table = altera_gpio_of_match,
+ },
+ .probe = altera_gpio_probe,
+ .remove = altera_gpio_remove,
+};
+
+static int __init altera_gpio_init(void)
+{
+ return platform_driver_register(&altera_gpio_driver);
+}
+subsys_initcall(altera_gpio_init);
+
+static void __exit altera_gpio_exit(void)
+{
+ platform_driver_unregister(&altera_gpio_driver);
+}
+module_exit(altera_gpio_exit);
+
+MODULE_AUTHOR("Tien Hock Loh <thloh@altera.com>");
+MODULE_DESCRIPTION("Altera GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-amd-fch.c b/drivers/gpio/gpio-amd-fch.c
new file mode 100644
index 0000000000..2a21354ed6
--- /dev/null
+++ b/drivers/gpio/gpio-amd-fch.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * GPIO driver for the AMD G series FCH (eg. GX-412TC)
+ *
+ * Copyright (C) 2018 metux IT consult
+ * Author: Enrico Weigelt, metux IT consult <info@metux.net>
+ *
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_data/gpio/gpio-amd-fch.h>
+#include <linux/spinlock.h>
+
+#define AMD_FCH_MMIO_BASE 0xFED80000
+#define AMD_FCH_GPIO_BANK0_BASE 0x1500
+#define AMD_FCH_GPIO_SIZE 0x0300
+
+#define AMD_FCH_GPIO_FLAG_DIRECTION BIT(23)
+#define AMD_FCH_GPIO_FLAG_WRITE BIT(22)
+#define AMD_FCH_GPIO_FLAG_READ BIT(16)
+
+static const struct resource amd_fch_gpio_iores =
+ DEFINE_RES_MEM_NAMED(
+ AMD_FCH_MMIO_BASE + AMD_FCH_GPIO_BANK0_BASE,
+ AMD_FCH_GPIO_SIZE,
+ "amd-fch-gpio-iomem");
+
+struct amd_fch_gpio_priv {
+ struct gpio_chip gc;
+ void __iomem *base;
+ struct amd_fch_gpio_pdata *pdata;
+ spinlock_t lock;
+};
+
+static void __iomem *amd_fch_gpio_addr(struct amd_fch_gpio_priv *priv,
+ unsigned int gpio)
+{
+ return priv->base + priv->pdata->gpio_reg[gpio]*sizeof(u32);
+}
+
+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);
+ writel_relaxed(readl_relaxed(ptr) & ~AMD_FCH_GPIO_FLAG_DIRECTION, ptr);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+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);
+
+ val = readl_relaxed(ptr);
+ if (value)
+ val |= AMD_FCH_GPIO_FLAG_WRITE;
+ else
+ val &= ~AMD_FCH_GPIO_FLAG_WRITE;
+
+ 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);
+ 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 void 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);
+
+ mask = readl_relaxed(ptr);
+ if (value)
+ mask |= AMD_FCH_GPIO_FLAG_WRITE;
+ else
+ mask &= ~AMD_FCH_GPIO_FLAG_WRITE;
+ writel_relaxed(mask, ptr);
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static int amd_fch_gpio_get(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ unsigned long flags;
+ int ret;
+ 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);
+ ret = (readl_relaxed(ptr) & AMD_FCH_GPIO_FLAG_READ);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return ret;
+}
+
+static int amd_fch_gpio_request(struct gpio_chip *chip,
+ unsigned int gpio_pin)
+{
+ return 0;
+}
+
+static int amd_fch_gpio_probe(struct platform_device *pdev)
+{
+ struct amd_fch_gpio_priv *priv;
+ struct amd_fch_gpio_pdata *pdata;
+
+ pdata = dev_get_platdata(&pdev->dev);
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform_data\n");
+ return -ENOENT;
+ }
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdata = pdata;
+
+ priv->gc.owner = THIS_MODULE;
+ priv->gc.parent = &pdev->dev;
+ priv->gc.label = dev_name(&pdev->dev);
+ priv->gc.ngpio = priv->pdata->gpio_num;
+ priv->gc.names = priv->pdata->gpio_names;
+ priv->gc.base = -1;
+ priv->gc.request = amd_fch_gpio_request;
+ priv->gc.direction_input = amd_fch_gpio_direction_input;
+ priv->gc.direction_output = amd_fch_gpio_direction_output;
+ priv->gc.get_direction = amd_fch_gpio_get_direction;
+ priv->gc.get = amd_fch_gpio_get;
+ priv->gc.set = amd_fch_gpio_set;
+
+ spin_lock_init(&priv->lock);
+
+ priv->base = devm_ioremap_resource(&pdev->dev, &amd_fch_gpio_iores);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ platform_set_drvdata(pdev, priv);
+
+ return devm_gpiochip_add_data(&pdev->dev, &priv->gc, priv);
+}
+
+static struct platform_driver amd_fch_gpio_driver = {
+ .driver = {
+ .name = AMD_FCH_GPIO_DRIVER_NAME,
+ },
+ .probe = amd_fch_gpio_probe,
+};
+
+module_platform_driver(amd_fch_gpio_driver);
+
+MODULE_AUTHOR("Enrico Weigelt, metux IT consult <info@metux.net>");
+MODULE_DESCRIPTION("AMD G-series FCH GPIO driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" AMD_FCH_GPIO_DRIVER_NAME);
diff --git a/drivers/gpio/gpio-amd8111.c b/drivers/gpio/gpio-amd8111.c
new file mode 100644
index 0000000000..6f3ded619c
--- /dev/null
+++ b/drivers/gpio/gpio-amd8111.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO driver for AMD 8111 south bridges
+ *
+ * Copyright (c) 2012 Dmitry Eremin-Solenikov
+ *
+ * Based on the AMD RNG driver:
+ * Copyright 2005 (c) MontaVista Software, Inc.
+ * with the majority of the code coming from:
+ *
+ * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG)
+ * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com>
+ *
+ * derived from
+ *
+ * Hardware driver for the AMD 768 Random Number Generator (RNG)
+ * (c) Copyright 2001 Red Hat Inc
+ *
+ * derived from
+ *
+ * Hardware driver for Intel i810 Random Number Generator (RNG)
+ * Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com>
+ * Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com>
+ */
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/gpio/driver.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+
+#define PMBASE_OFFSET 0xb0
+#define PMBASE_SIZE 0x30
+
+#define AMD_REG_GPIO(i) (0x10 + (i))
+
+#define AMD_GPIO_LTCH_STS 0x40 /* Latch status, w1 */
+#define AMD_GPIO_RTIN 0x20 /* Real Time in, ro */
+#define AMD_GPIO_DEBOUNCE 0x10 /* Debounce, rw */
+#define AMD_GPIO_MODE_MASK 0x0c /* Pin Mode Select, rw */
+#define AMD_GPIO_MODE_IN 0x00
+#define AMD_GPIO_MODE_OUT 0x04
+/* Enable alternative (e.g. clkout, IRQ, etc) function of the pin */
+#define AMD_GPIO_MODE_ALTFN 0x08 /* Or 0x09 */
+#define AMD_GPIO_X_MASK 0x03 /* In/Out specific, rw */
+#define AMD_GPIO_X_IN_ACTIVEHI 0x01 /* Active High */
+#define AMD_GPIO_X_IN_LATCH 0x02 /* Latched version is selected */
+#define AMD_GPIO_X_OUT_LOW 0x00
+#define AMD_GPIO_X_OUT_HI 0x01
+#define AMD_GPIO_X_OUT_CLK0 0x02
+#define AMD_GPIO_X_OUT_CLK1 0x03
+
+/*
+ * Data for PCI driver interface
+ *
+ * This data only exists for exporting the supported
+ * PCI ids via MODULE_DEVICE_TABLE. We do not actually
+ * register a pci_driver, because someone else might one day
+ * want to register another driver on the same PCI id.
+ */
+static const struct pci_device_id pci_tbl[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS), 0 },
+ { 0, }, /* terminate list */
+};
+MODULE_DEVICE_TABLE(pci, pci_tbl);
+
+struct amd_gpio {
+ struct gpio_chip chip;
+ u32 pmbase;
+ void __iomem *pm;
+ struct pci_dev *pdev;
+ spinlock_t lock; /* guards hw registers and orig table */
+ u8 orig[32];
+};
+
+static int amd_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct amd_gpio *agp = gpiochip_get_data(chip);
+
+ agp->orig[offset] = ioread8(agp->pm + AMD_REG_GPIO(offset)) &
+ (AMD_GPIO_DEBOUNCE | AMD_GPIO_MODE_MASK | AMD_GPIO_X_MASK);
+
+ dev_dbg(&agp->pdev->dev, "Requested gpio %d, data %x\n", offset, agp->orig[offset]);
+
+ return 0;
+}
+
+static void amd_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+ struct amd_gpio *agp = gpiochip_get_data(chip);
+
+ dev_dbg(&agp->pdev->dev, "Freed gpio %d, data %x\n", offset, agp->orig[offset]);
+
+ iowrite8(agp->orig[offset], agp->pm + AMD_REG_GPIO(offset));
+}
+
+static void amd_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct amd_gpio *agp = gpiochip_get_data(chip);
+ u8 temp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&agp->lock, flags);
+ temp = ioread8(agp->pm + AMD_REG_GPIO(offset));
+ temp = (temp & AMD_GPIO_DEBOUNCE) | AMD_GPIO_MODE_OUT | (value ? AMD_GPIO_X_OUT_HI : AMD_GPIO_X_OUT_LOW);
+ iowrite8(temp, agp->pm + AMD_REG_GPIO(offset));
+ spin_unlock_irqrestore(&agp->lock, flags);
+
+ dev_dbg(&agp->pdev->dev, "Setting gpio %d, value %d, reg=%02x\n", offset, !!value, temp);
+}
+
+static int amd_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct amd_gpio *agp = gpiochip_get_data(chip);
+ u8 temp;
+
+ temp = ioread8(agp->pm + AMD_REG_GPIO(offset));
+
+ dev_dbg(&agp->pdev->dev, "Getting gpio %d, reg=%02x\n", offset, temp);
+
+ return (temp & AMD_GPIO_RTIN) ? 1 : 0;
+}
+
+static int amd_gpio_dirout(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct amd_gpio *agp = gpiochip_get_data(chip);
+ u8 temp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&agp->lock, flags);
+ temp = ioread8(agp->pm + AMD_REG_GPIO(offset));
+ temp = (temp & AMD_GPIO_DEBOUNCE) | AMD_GPIO_MODE_OUT | (value ? AMD_GPIO_X_OUT_HI : AMD_GPIO_X_OUT_LOW);
+ iowrite8(temp, agp->pm + AMD_REG_GPIO(offset));
+ spin_unlock_irqrestore(&agp->lock, flags);
+
+ dev_dbg(&agp->pdev->dev, "Dirout gpio %d, value %d, reg=%02x\n", offset, !!value, temp);
+
+ return 0;
+}
+
+static int amd_gpio_dirin(struct gpio_chip *chip, unsigned offset)
+{
+ struct amd_gpio *agp = gpiochip_get_data(chip);
+ u8 temp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&agp->lock, flags);
+ temp = ioread8(agp->pm + AMD_REG_GPIO(offset));
+ temp = (temp & AMD_GPIO_DEBOUNCE) | AMD_GPIO_MODE_IN;
+ iowrite8(temp, agp->pm + AMD_REG_GPIO(offset));
+ spin_unlock_irqrestore(&agp->lock, flags);
+
+ dev_dbg(&agp->pdev->dev, "Dirin gpio %d, reg=%02x\n", offset, temp);
+
+ return 0;
+}
+
+static struct amd_gpio gp = {
+ .chip = {
+ .label = "AMD GPIO",
+ .owner = THIS_MODULE,
+ .base = -1,
+ .ngpio = 32,
+ .request = amd_gpio_request,
+ .free = amd_gpio_free,
+ .set = amd_gpio_set,
+ .get = amd_gpio_get,
+ .direction_output = amd_gpio_dirout,
+ .direction_input = amd_gpio_dirin,
+ },
+};
+
+static int __init amd_gpio_init(void)
+{
+ int err = -ENODEV;
+ struct pci_dev *pdev = NULL;
+ const struct pci_device_id *ent;
+
+ /* We look for our device - AMD South Bridge
+ * I don't know about a system with two such bridges,
+ * so we can assume that there is max. one device.
+ *
+ * We can't use plain pci_driver mechanism,
+ * as the device is really a multiple function device,
+ * main driver that binds to the pci_device is an smbus
+ * driver and have to find & bind to the device this way.
+ */
+ for_each_pci_dev(pdev) {
+ ent = pci_match_id(pci_tbl, pdev);
+ if (ent)
+ goto found;
+ }
+ /* Device not found. */
+ goto out;
+
+found:
+ err = pci_read_config_dword(pdev, 0x58, &gp.pmbase);
+ if (err)
+ goto out;
+ err = -EIO;
+ gp.pmbase &= 0x0000FF00;
+ if (gp.pmbase == 0)
+ goto out;
+ if (!devm_request_region(&pdev->dev, gp.pmbase + PMBASE_OFFSET,
+ PMBASE_SIZE, "AMD GPIO")) {
+ dev_err(&pdev->dev, "AMD GPIO region 0x%x already in use!\n",
+ gp.pmbase + PMBASE_OFFSET);
+ err = -EBUSY;
+ goto out;
+ }
+ gp.pm = ioport_map(gp.pmbase + PMBASE_OFFSET, PMBASE_SIZE);
+ if (!gp.pm) {
+ dev_err(&pdev->dev, "Couldn't map io port into io memory\n");
+ err = -ENOMEM;
+ goto out;
+ }
+ gp.pdev = pdev;
+ gp.chip.parent = &pdev->dev;
+
+ spin_lock_init(&gp.lock);
+
+ dev_info(&pdev->dev, "AMD-8111 GPIO detected\n");
+ err = gpiochip_add_data(&gp.chip, &gp);
+ if (err) {
+ dev_err(&pdev->dev, "GPIO registering failed (%d)\n", err);
+ ioport_unmap(gp.pm);
+ goto out;
+ }
+ return 0;
+
+out:
+ pci_dev_put(pdev);
+ return err;
+}
+
+static void __exit amd_gpio_exit(void)
+{
+ gpiochip_remove(&gp.chip);
+ ioport_unmap(gp.pm);
+ pci_dev_put(gp.pdev);
+}
+
+module_init(amd_gpio_init);
+module_exit(amd_gpio_exit);
+
+MODULE_AUTHOR("The Linux Kernel team");
+MODULE_DESCRIPTION("GPIO driver for AMD chipsets");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-amdpt.c b/drivers/gpio/gpio-amdpt.c
new file mode 100644
index 0000000000..07c6d09005
--- /dev/null
+++ b/drivers/gpio/gpio-amdpt.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AMD Promontory GPIO driver
+ *
+ * Copyright (C) 2015 ASMedia Technology Inc.
+ * Author: YD Tseng <yd_tseng@asmedia.com.tw>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio/driver.h>
+#include <linux/spinlock.h>
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+
+#define PT_TOTAL_GPIO 8
+#define PT_TOTAL_GPIO_EX 24
+
+/* PCI-E MMIO register offsets */
+#define PT_DIRECTION_REG 0x00
+#define PT_INPUTDATA_REG 0x04
+#define PT_OUTPUTDATA_REG 0x08
+#define PT_CLOCKRATE_REG 0x0C
+#define PT_SYNC_REG 0x28
+
+struct pt_gpio_chip {
+ struct gpio_chip gc;
+ void __iomem *reg_base;
+};
+
+static int pt_gpio_request(struct gpio_chip *gc, unsigned offset)
+{
+ struct pt_gpio_chip *pt_gpio = gpiochip_get_data(gc);
+ unsigned long flags;
+ u32 using_pins;
+
+ dev_dbg(gc->parent, "pt_gpio_request offset=%x\n", offset);
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+ using_pins = readl(pt_gpio->reg_base + PT_SYNC_REG);
+ if (using_pins & BIT(offset)) {
+ dev_warn(gc->parent, "PT GPIO pin %x reconfigured\n",
+ offset);
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+ return -EINVAL;
+ }
+
+ writel(using_pins | BIT(offset), pt_gpio->reg_base + PT_SYNC_REG);
+
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+ return 0;
+}
+
+static void pt_gpio_free(struct gpio_chip *gc, unsigned offset)
+{
+ struct pt_gpio_chip *pt_gpio = gpiochip_get_data(gc);
+ unsigned long flags;
+ u32 using_pins;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+ using_pins = readl(pt_gpio->reg_base + PT_SYNC_REG);
+ using_pins &= ~BIT(offset);
+ writel(using_pins, pt_gpio->reg_base + PT_SYNC_REG);
+
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+ dev_dbg(gc->parent, "pt_gpio_free offset=%x\n", offset);
+}
+
+static int pt_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pt_gpio_chip *pt_gpio;
+ int ret = 0;
+
+ if (!ACPI_COMPANION(dev)) {
+ dev_err(dev, "PT GPIO device node not found\n");
+ return -ENODEV;
+ }
+
+ pt_gpio = devm_kzalloc(dev, sizeof(struct pt_gpio_chip), GFP_KERNEL);
+ if (!pt_gpio)
+ return -ENOMEM;
+
+ pt_gpio->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(pt_gpio->reg_base)) {
+ dev_err(dev, "Failed to map MMIO resource for PT GPIO.\n");
+ return PTR_ERR(pt_gpio->reg_base);
+ }
+
+ ret = bgpio_init(&pt_gpio->gc, dev, 4,
+ pt_gpio->reg_base + PT_INPUTDATA_REG,
+ pt_gpio->reg_base + PT_OUTPUTDATA_REG, NULL,
+ pt_gpio->reg_base + PT_DIRECTION_REG, NULL,
+ BGPIOF_READ_OUTPUT_REG_SET);
+ if (ret) {
+ dev_err(dev, "bgpio_init failed\n");
+ return ret;
+ }
+
+ pt_gpio->gc.owner = THIS_MODULE;
+ pt_gpio->gc.request = pt_gpio_request;
+ pt_gpio->gc.free = pt_gpio_free;
+ pt_gpio->gc.ngpio = (uintptr_t)device_get_match_data(dev);
+
+ ret = gpiochip_add_data(&pt_gpio->gc, pt_gpio);
+ if (ret) {
+ dev_err(dev, "Failed to register GPIO lib\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, pt_gpio);
+
+ /* initialize register setting */
+ writel(0, pt_gpio->reg_base + PT_SYNC_REG);
+ writel(0, pt_gpio->reg_base + PT_CLOCKRATE_REG);
+
+ dev_dbg(dev, "PT GPIO driver loaded\n");
+ return ret;
+}
+
+static int pt_gpio_remove(struct platform_device *pdev)
+{
+ struct pt_gpio_chip *pt_gpio = platform_get_drvdata(pdev);
+
+ gpiochip_remove(&pt_gpio->gc);
+
+ return 0;
+}
+
+static const struct acpi_device_id pt_gpio_acpi_match[] = {
+ { "AMDF030", PT_TOTAL_GPIO },
+ { "AMDIF030", PT_TOTAL_GPIO },
+ { "AMDIF031", PT_TOTAL_GPIO_EX },
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, pt_gpio_acpi_match);
+
+static struct platform_driver pt_gpio_driver = {
+ .driver = {
+ .name = "pt-gpio",
+ .acpi_match_table = ACPI_PTR(pt_gpio_acpi_match),
+ },
+ .probe = pt_gpio_probe,
+ .remove = pt_gpio_remove,
+};
+
+module_platform_driver(pt_gpio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("YD Tseng <yd_tseng@asmedia.com.tw>");
+MODULE_DESCRIPTION("AMD Promontory GPIO Driver");
diff --git a/drivers/gpio/gpio-arizona.c b/drivers/gpio/gpio-arizona.c
new file mode 100644
index 0000000000..c15fda9912
--- /dev/null
+++ b/drivers/gpio/gpio-arizona.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * gpiolib support for Wolfson Arizona class devices
+ *
+ * Copyright 2012 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/arizona/core.h>
+#include <linux/mfd/arizona/pdata.h>
+#include <linux/mfd/arizona/registers.h>
+
+struct arizona_gpio {
+ struct arizona *arizona;
+ struct gpio_chip gpio_chip;
+};
+
+static int arizona_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct arizona_gpio *arizona_gpio = gpiochip_get_data(chip);
+ struct arizona *arizona = arizona_gpio->arizona;
+ bool persistent = gpiochip_line_is_persistent(chip, offset);
+ bool change;
+ int ret;
+
+ ret = regmap_update_bits_check(arizona->regmap,
+ ARIZONA_GPIO1_CTRL + offset,
+ ARIZONA_GPN_DIR, ARIZONA_GPN_DIR,
+ &change);
+ if (ret < 0)
+ return ret;
+
+ if (change && persistent) {
+ pm_runtime_mark_last_busy(chip->parent);
+ pm_runtime_put_autosuspend(chip->parent);
+ }
+
+ return 0;
+}
+
+static int arizona_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct arizona_gpio *arizona_gpio = gpiochip_get_data(chip);
+ struct arizona *arizona = arizona_gpio->arizona;
+ unsigned int reg, val;
+ int ret;
+
+ reg = ARIZONA_GPIO1_CTRL + offset;
+ ret = regmap_read(arizona->regmap, reg, &val);
+ if (ret < 0)
+ return ret;
+
+ /* Resume to read actual registers for input pins */
+ if (val & ARIZONA_GPN_DIR) {
+ ret = pm_runtime_get_sync(chip->parent);
+ if (ret < 0) {
+ dev_err(chip->parent, "Failed to resume: %d\n", ret);
+ pm_runtime_put_autosuspend(chip->parent);
+ return ret;
+ }
+
+ /* Register is cached, drop it to ensure a physical read */
+ ret = regcache_drop_region(arizona->regmap, reg, reg);
+ if (ret < 0) {
+ dev_err(chip->parent, "Failed to drop cache: %d\n",
+ ret);
+ pm_runtime_put_autosuspend(chip->parent);
+ return ret;
+ }
+
+ ret = regmap_read(arizona->regmap, reg, &val);
+ if (ret < 0) {
+ pm_runtime_put_autosuspend(chip->parent);
+ return ret;
+ }
+
+ pm_runtime_mark_last_busy(chip->parent);
+ pm_runtime_put_autosuspend(chip->parent);
+ }
+
+ if (val & ARIZONA_GPN_LVL)
+ return 1;
+ else
+ return 0;
+}
+
+static int arizona_gpio_direction_out(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct arizona_gpio *arizona_gpio = gpiochip_get_data(chip);
+ struct arizona *arizona = arizona_gpio->arizona;
+ bool persistent = gpiochip_line_is_persistent(chip, offset);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(arizona->regmap, ARIZONA_GPIO1_CTRL + offset, &val);
+ if (ret < 0)
+ return ret;
+
+ if ((val & ARIZONA_GPN_DIR) && persistent) {
+ ret = pm_runtime_get_sync(chip->parent);
+ if (ret < 0) {
+ dev_err(chip->parent, "Failed to resume: %d\n", ret);
+ pm_runtime_put(chip->parent);
+ return ret;
+ }
+ }
+
+ if (value)
+ value = ARIZONA_GPN_LVL;
+
+ return regmap_update_bits(arizona->regmap, ARIZONA_GPIO1_CTRL + offset,
+ ARIZONA_GPN_DIR | ARIZONA_GPN_LVL, value);
+}
+
+static void arizona_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct arizona_gpio *arizona_gpio = gpiochip_get_data(chip);
+ struct arizona *arizona = arizona_gpio->arizona;
+
+ if (value)
+ value = ARIZONA_GPN_LVL;
+
+ regmap_update_bits(arizona->regmap, ARIZONA_GPIO1_CTRL + offset,
+ ARIZONA_GPN_LVL, value);
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "arizona",
+ .owner = THIS_MODULE,
+ .direction_input = arizona_gpio_direction_in,
+ .get = arizona_gpio_get,
+ .direction_output = arizona_gpio_direction_out,
+ .set = arizona_gpio_set,
+ .can_sleep = true,
+};
+
+static int arizona_gpio_probe(struct platform_device *pdev)
+{
+ struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
+ struct arizona_pdata *pdata = &arizona->pdata;
+ struct arizona_gpio *arizona_gpio;
+ int ret;
+
+ device_set_node(&pdev->dev, dev_fwnode(pdev->dev.parent));
+
+ arizona_gpio = devm_kzalloc(&pdev->dev, sizeof(*arizona_gpio),
+ GFP_KERNEL);
+ if (!arizona_gpio)
+ return -ENOMEM;
+
+ arizona_gpio->arizona = arizona;
+ arizona_gpio->gpio_chip = template_chip;
+ arizona_gpio->gpio_chip.parent = &pdev->dev;
+
+ switch (arizona->type) {
+ case WM5102:
+ case WM5110:
+ case WM8280:
+ case WM8997:
+ case WM8998:
+ case WM1814:
+ arizona_gpio->gpio_chip.ngpio = 5;
+ break;
+ case WM1831:
+ case CS47L24:
+ arizona_gpio->gpio_chip.ngpio = 2;
+ break;
+ default:
+ dev_err(&pdev->dev, "Unknown chip variant %d\n",
+ arizona->type);
+ return -EINVAL;
+ }
+
+ if (pdata->gpio_base)
+ arizona_gpio->gpio_chip.base = pdata->gpio_base;
+ else
+ arizona_gpio->gpio_chip.base = -1;
+
+ pm_runtime_enable(&pdev->dev);
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &arizona_gpio->gpio_chip,
+ arizona_gpio);
+ if (ret < 0) {
+ pm_runtime_disable(&pdev->dev);
+ dev_err(&pdev->dev, "Could not register gpiochip, %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver arizona_gpio_driver = {
+ .driver.name = "arizona-gpio",
+ .probe = arizona_gpio_probe,
+};
+
+module_platform_driver(arizona_gpio_driver);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("GPIO interface for Arizona devices");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:arizona-gpio");
diff --git a/drivers/gpio/gpio-aspeed-sgpio.c b/drivers/gpio/gpio-aspeed-sgpio.c
new file mode 100644
index 0000000000..72755fee64
--- /dev/null
+++ b/drivers/gpio/gpio-aspeed-sgpio.c
@@ -0,0 +1,639 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2019 American Megatrends International LLC.
+ *
+ * Author: Karthikeyan Mani <karthikeyanm@amiindia.co.in>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/gpio/driver.h>
+#include <linux/hashtable.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+
+#define ASPEED_SGPIO_CTRL 0x54
+
+#define ASPEED_SGPIO_CLK_DIV_MASK GENMASK(31, 16)
+#define ASPEED_SGPIO_ENABLE BIT(0)
+#define ASPEED_SGPIO_PINS_SHIFT 6
+
+struct aspeed_sgpio_pdata {
+ const u32 pin_mask;
+};
+
+struct aspeed_sgpio {
+ struct gpio_chip chip;
+ struct device *dev;
+ struct clk *pclk;
+ raw_spinlock_t lock;
+ void __iomem *base;
+ int irq;
+};
+
+struct aspeed_sgpio_bank {
+ u16 val_regs;
+ u16 rdata_reg;
+ u16 irq_regs;
+ u16 tolerance_regs;
+ const char names[4][3];
+};
+
+/*
+ * Note: The "value" register returns the input value when the GPIO is
+ * configured as an input.
+ *
+ * The "rdata" register returns the output value when the GPIO is
+ * configured as an output.
+ */
+static const struct aspeed_sgpio_bank aspeed_sgpio_banks[] = {
+ {
+ .val_regs = 0x0000,
+ .rdata_reg = 0x0070,
+ .irq_regs = 0x0004,
+ .tolerance_regs = 0x0018,
+ .names = { "A", "B", "C", "D" },
+ },
+ {
+ .val_regs = 0x001C,
+ .rdata_reg = 0x0074,
+ .irq_regs = 0x0020,
+ .tolerance_regs = 0x0034,
+ .names = { "E", "F", "G", "H" },
+ },
+ {
+ .val_regs = 0x0038,
+ .rdata_reg = 0x0078,
+ .irq_regs = 0x003C,
+ .tolerance_regs = 0x0050,
+ .names = { "I", "J", "K", "L" },
+ },
+ {
+ .val_regs = 0x0090,
+ .rdata_reg = 0x007C,
+ .irq_regs = 0x0094,
+ .tolerance_regs = 0x00A8,
+ .names = { "M", "N", "O", "P" },
+ },
+};
+
+enum aspeed_sgpio_reg {
+ reg_val,
+ reg_rdata,
+ reg_irq_enable,
+ reg_irq_type0,
+ reg_irq_type1,
+ reg_irq_type2,
+ reg_irq_status,
+ reg_tolerance,
+};
+
+#define GPIO_VAL_VALUE 0x00
+#define GPIO_IRQ_ENABLE 0x00
+#define GPIO_IRQ_TYPE0 0x04
+#define GPIO_IRQ_TYPE1 0x08
+#define GPIO_IRQ_TYPE2 0x0C
+#define GPIO_IRQ_STATUS 0x10
+
+static void __iomem *bank_reg(struct aspeed_sgpio *gpio,
+ const struct aspeed_sgpio_bank *bank,
+ const enum aspeed_sgpio_reg reg)
+{
+ switch (reg) {
+ case reg_val:
+ return gpio->base + bank->val_regs + GPIO_VAL_VALUE;
+ case reg_rdata:
+ return gpio->base + bank->rdata_reg;
+ case reg_irq_enable:
+ return gpio->base + bank->irq_regs + GPIO_IRQ_ENABLE;
+ case reg_irq_type0:
+ return gpio->base + bank->irq_regs + GPIO_IRQ_TYPE0;
+ case reg_irq_type1:
+ return gpio->base + bank->irq_regs + GPIO_IRQ_TYPE1;
+ case reg_irq_type2:
+ return gpio->base + bank->irq_regs + GPIO_IRQ_TYPE2;
+ case reg_irq_status:
+ return gpio->base + bank->irq_regs + GPIO_IRQ_STATUS;
+ case reg_tolerance:
+ return gpio->base + bank->tolerance_regs;
+ default:
+ /* acturally if code runs to here, it's an error case */
+ BUG();
+ }
+}
+
+#define GPIO_BANK(x) ((x) >> 6)
+#define GPIO_OFFSET(x) ((x) & GENMASK(5, 0))
+#define GPIO_BIT(x) BIT(GPIO_OFFSET(x) >> 1)
+
+static const struct aspeed_sgpio_bank *to_bank(unsigned int offset)
+{
+ unsigned int bank;
+
+ bank = GPIO_BANK(offset);
+
+ WARN_ON(bank >= ARRAY_SIZE(aspeed_sgpio_banks));
+ return &aspeed_sgpio_banks[bank];
+}
+
+static int aspeed_sgpio_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask, unsigned int ngpios)
+{
+ bitmap_set(valid_mask, 0, ngpios);
+ return 0;
+}
+
+static void aspeed_sgpio_irq_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask, unsigned int ngpios)
+{
+ unsigned int i;
+
+ /* input GPIOs are even bits */
+ for (i = 0; i < ngpios; i++) {
+ if (i % 2)
+ clear_bit(i, valid_mask);
+ }
+}
+
+static bool aspeed_sgpio_is_input(unsigned int offset)
+{
+ return !(offset % 2);
+}
+
+static int aspeed_sgpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct aspeed_sgpio *gpio = gpiochip_get_data(gc);
+ const struct aspeed_sgpio_bank *bank = to_bank(offset);
+ unsigned long flags;
+ enum aspeed_sgpio_reg reg;
+ int rc = 0;
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ reg = aspeed_sgpio_is_input(offset) ? reg_val : reg_rdata;
+ rc = !!(ioread32(bank_reg(gpio, bank, reg)) & GPIO_BIT(offset));
+
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return rc;
+}
+
+static int sgpio_set_value(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ struct aspeed_sgpio *gpio = gpiochip_get_data(gc);
+ const struct aspeed_sgpio_bank *bank = to_bank(offset);
+ void __iomem *addr_r, *addr_w;
+ u32 reg = 0;
+
+ if (aspeed_sgpio_is_input(offset))
+ return -EINVAL;
+
+ /* Since this is an output, read the cached value from rdata, then
+ * update val. */
+ addr_r = bank_reg(gpio, bank, reg_rdata);
+ addr_w = bank_reg(gpio, bank, reg_val);
+
+ reg = ioread32(addr_r);
+
+ if (val)
+ reg |= GPIO_BIT(offset);
+ else
+ reg &= ~GPIO_BIT(offset);
+
+ iowrite32(reg, addr_w);
+
+ return 0;
+}
+
+static void aspeed_sgpio_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ struct aspeed_sgpio *gpio = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ sgpio_set_value(gc, offset, val);
+
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+}
+
+static int aspeed_sgpio_dir_in(struct gpio_chip *gc, unsigned int offset)
+{
+ return aspeed_sgpio_is_input(offset) ? 0 : -EINVAL;
+}
+
+static int aspeed_sgpio_dir_out(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ struct aspeed_sgpio *gpio = gpiochip_get_data(gc);
+ unsigned long flags;
+ int rc;
+
+ /* No special action is required for setting the direction; we'll
+ * error-out in sgpio_set_value if this isn't an output GPIO */
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+ rc = sgpio_set_value(gc, offset, val);
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return rc;
+}
+
+static int aspeed_sgpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ return !!aspeed_sgpio_is_input(offset);
+}
+
+static void irqd_to_aspeed_sgpio_data(struct irq_data *d,
+ struct aspeed_sgpio **gpio,
+ const struct aspeed_sgpio_bank **bank,
+ u32 *bit, int *offset)
+{
+ struct aspeed_sgpio *internal;
+
+ *offset = irqd_to_hwirq(d);
+ internal = irq_data_get_irq_chip_data(d);
+ WARN_ON(!internal);
+
+ *gpio = internal;
+ *bank = to_bank(*offset);
+ *bit = GPIO_BIT(*offset);
+}
+
+static void aspeed_sgpio_irq_ack(struct irq_data *d)
+{
+ const struct aspeed_sgpio_bank *bank;
+ struct aspeed_sgpio *gpio;
+ unsigned long flags;
+ void __iomem *status_addr;
+ int offset;
+ u32 bit;
+
+ irqd_to_aspeed_sgpio_data(d, &gpio, &bank, &bit, &offset);
+
+ status_addr = bank_reg(gpio, bank, reg_irq_status);
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ iowrite32(bit, status_addr);
+
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+}
+
+static void aspeed_sgpio_irq_set_mask(struct irq_data *d, bool set)
+{
+ const struct aspeed_sgpio_bank *bank;
+ struct aspeed_sgpio *gpio;
+ unsigned long flags;
+ u32 reg, bit;
+ void __iomem *addr;
+ int offset;
+
+ irqd_to_aspeed_sgpio_data(d, &gpio, &bank, &bit, &offset);
+ addr = bank_reg(gpio, bank, reg_irq_enable);
+
+ /* Unmasking the IRQ */
+ if (set)
+ gpiochip_enable_irq(&gpio->chip, irqd_to_hwirq(d));
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ reg = ioread32(addr);
+ if (set)
+ reg |= bit;
+ else
+ reg &= ~bit;
+
+ iowrite32(reg, addr);
+
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ /* Masking the IRQ */
+ if (!set)
+ gpiochip_disable_irq(&gpio->chip, irqd_to_hwirq(d));
+
+
+}
+
+static void aspeed_sgpio_irq_mask(struct irq_data *d)
+{
+ aspeed_sgpio_irq_set_mask(d, false);
+}
+
+static void aspeed_sgpio_irq_unmask(struct irq_data *d)
+{
+ aspeed_sgpio_irq_set_mask(d, true);
+}
+
+static int aspeed_sgpio_set_type(struct irq_data *d, unsigned int type)
+{
+ u32 type0 = 0;
+ u32 type1 = 0;
+ u32 type2 = 0;
+ u32 bit, reg;
+ const struct aspeed_sgpio_bank *bank;
+ irq_flow_handler_t handler;
+ struct aspeed_sgpio *gpio;
+ unsigned long flags;
+ void __iomem *addr;
+ int offset;
+
+ irqd_to_aspeed_sgpio_data(d, &gpio, &bank, &bit, &offset);
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_BOTH:
+ type2 |= bit;
+ fallthrough;
+ case IRQ_TYPE_EDGE_RISING:
+ type0 |= bit;
+ fallthrough;
+ case IRQ_TYPE_EDGE_FALLING:
+ handler = handle_edge_irq;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ type0 |= bit;
+ fallthrough;
+ case IRQ_TYPE_LEVEL_LOW:
+ type1 |= bit;
+ handler = handle_level_irq;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ addr = bank_reg(gpio, bank, reg_irq_type0);
+ reg = ioread32(addr);
+ reg = (reg & ~bit) | type0;
+ iowrite32(reg, addr);
+
+ addr = bank_reg(gpio, bank, reg_irq_type1);
+ reg = ioread32(addr);
+ reg = (reg & ~bit) | type1;
+ iowrite32(reg, addr);
+
+ addr = bank_reg(gpio, bank, reg_irq_type2);
+ reg = ioread32(addr);
+ reg = (reg & ~bit) | type2;
+ iowrite32(reg, addr);
+
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ irq_set_handler_locked(d, handler);
+
+ return 0;
+}
+
+static void aspeed_sgpio_irq_handler(struct irq_desc *desc)
+{
+ struct gpio_chip *gc = irq_desc_get_handler_data(desc);
+ struct irq_chip *ic = irq_desc_get_chip(desc);
+ struct aspeed_sgpio *data = gpiochip_get_data(gc);
+ unsigned int i, p;
+ unsigned long reg;
+
+ chained_irq_enter(ic, desc);
+
+ for (i = 0; i < ARRAY_SIZE(aspeed_sgpio_banks); i++) {
+ const struct aspeed_sgpio_bank *bank = &aspeed_sgpio_banks[i];
+
+ reg = ioread32(bank_reg(data, bank, reg_irq_status));
+
+ for_each_set_bit(p, &reg, 32)
+ generic_handle_domain_irq(gc->irq.domain, (i * 32 + p) * 2);
+ }
+
+ chained_irq_exit(ic, desc);
+}
+
+static void aspeed_sgpio_irq_print_chip(struct irq_data *d, struct seq_file *p)
+{
+ const struct aspeed_sgpio_bank *bank;
+ struct aspeed_sgpio *gpio;
+ u32 bit;
+ int offset;
+
+ irqd_to_aspeed_sgpio_data(d, &gpio, &bank, &bit, &offset);
+ seq_printf(p, dev_name(gpio->dev));
+}
+
+static const struct irq_chip aspeed_sgpio_irq_chip = {
+ .irq_ack = aspeed_sgpio_irq_ack,
+ .irq_mask = aspeed_sgpio_irq_mask,
+ .irq_unmask = aspeed_sgpio_irq_unmask,
+ .irq_set_type = aspeed_sgpio_set_type,
+ .irq_print_chip = aspeed_sgpio_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int aspeed_sgpio_setup_irqs(struct aspeed_sgpio *gpio,
+ struct platform_device *pdev)
+{
+ int rc, i;
+ const struct aspeed_sgpio_bank *bank;
+ struct gpio_irq_chip *irq;
+
+ rc = platform_get_irq(pdev, 0);
+ if (rc < 0)
+ return rc;
+
+ gpio->irq = rc;
+
+ /* Disable IRQ and clear Interrupt status registers for all SGPIO Pins. */
+ for (i = 0; i < ARRAY_SIZE(aspeed_sgpio_banks); i++) {
+ bank = &aspeed_sgpio_banks[i];
+ /* disable irq enable bits */
+ iowrite32(0x00000000, bank_reg(gpio, bank, reg_irq_enable));
+ /* clear status bits */
+ iowrite32(0xffffffff, bank_reg(gpio, bank, reg_irq_status));
+ }
+
+ irq = &gpio->chip.irq;
+ gpio_irq_chip_set_chip(irq, &aspeed_sgpio_irq_chip);
+ irq->init_valid_mask = aspeed_sgpio_irq_init_valid_mask;
+ irq->handler = handle_bad_irq;
+ irq->default_type = IRQ_TYPE_NONE;
+ irq->parent_handler = aspeed_sgpio_irq_handler;
+ irq->parent_handler_data = gpio;
+ irq->parents = &gpio->irq;
+ irq->num_parents = 1;
+
+ /* Apply default IRQ settings */
+ for (i = 0; i < ARRAY_SIZE(aspeed_sgpio_banks); i++) {
+ bank = &aspeed_sgpio_banks[i];
+ /* set falling or level-low irq */
+ iowrite32(0x00000000, bank_reg(gpio, bank, reg_irq_type0));
+ /* trigger type is edge */
+ iowrite32(0x00000000, bank_reg(gpio, bank, reg_irq_type1));
+ /* single edge trigger */
+ iowrite32(0x00000000, bank_reg(gpio, bank, reg_irq_type2));
+ }
+
+ return 0;
+}
+
+static const struct aspeed_sgpio_pdata ast2400_sgpio_pdata = {
+ .pin_mask = GENMASK(9, 6),
+};
+
+static int aspeed_sgpio_reset_tolerance(struct gpio_chip *chip,
+ unsigned int offset, bool enable)
+{
+ struct aspeed_sgpio *gpio = gpiochip_get_data(chip);
+ unsigned long flags;
+ void __iomem *reg;
+ u32 val;
+
+ reg = bank_reg(gpio, to_bank(offset), reg_tolerance);
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ val = readl(reg);
+
+ if (enable)
+ val |= GPIO_BIT(offset);
+ else
+ val &= ~GPIO_BIT(offset);
+
+ writel(val, reg);
+
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return 0;
+}
+
+static int aspeed_sgpio_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ unsigned long param = pinconf_to_config_param(config);
+ u32 arg = pinconf_to_config_argument(config);
+
+ if (param == PIN_CONFIG_PERSIST_STATE)
+ return aspeed_sgpio_reset_tolerance(chip, offset, arg);
+
+ return -ENOTSUPP;
+}
+
+static const struct aspeed_sgpio_pdata ast2600_sgpiom_pdata = {
+ .pin_mask = GENMASK(10, 6),
+};
+
+static const struct of_device_id aspeed_sgpio_of_table[] = {
+ { .compatible = "aspeed,ast2400-sgpio", .data = &ast2400_sgpio_pdata, },
+ { .compatible = "aspeed,ast2500-sgpio", .data = &ast2400_sgpio_pdata, },
+ { .compatible = "aspeed,ast2600-sgpiom", .data = &ast2600_sgpiom_pdata, },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, aspeed_sgpio_of_table);
+
+static int __init aspeed_sgpio_probe(struct platform_device *pdev)
+{
+ u32 nr_gpios, sgpio_freq, sgpio_clk_div, gpio_cnt_regval, pin_mask;
+ const struct aspeed_sgpio_pdata *pdata;
+ struct aspeed_sgpio *gpio;
+ unsigned long apb_freq;
+ int rc;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gpio->base))
+ return PTR_ERR(gpio->base);
+
+ gpio->dev = &pdev->dev;
+
+ pdata = device_get_match_data(&pdev->dev);
+ if (!pdata)
+ return -EINVAL;
+
+ pin_mask = pdata->pin_mask;
+
+ rc = device_property_read_u32(&pdev->dev, "ngpios", &nr_gpios);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Could not read ngpios property\n");
+ return -EINVAL;
+ } else if (nr_gpios % 8) {
+ dev_err(&pdev->dev, "Number of GPIOs not multiple of 8: %d\n",
+ nr_gpios);
+ return -EINVAL;
+ }
+
+ rc = device_property_read_u32(&pdev->dev, "bus-frequency", &sgpio_freq);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Could not read bus-frequency property\n");
+ return -EINVAL;
+ }
+
+ gpio->pclk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(gpio->pclk)) {
+ dev_err(&pdev->dev, "devm_clk_get failed\n");
+ return PTR_ERR(gpio->pclk);
+ }
+
+ apb_freq = clk_get_rate(gpio->pclk);
+
+ /*
+ * From the datasheet,
+ * SGPIO period = 1/PCLK * 2 * (GPIO254[31:16] + 1)
+ * period = 2 * (GPIO254[31:16] + 1) / PCLK
+ * frequency = 1 / (2 * (GPIO254[31:16] + 1) / PCLK)
+ * frequency = PCLK / (2 * (GPIO254[31:16] + 1))
+ * frequency * 2 * (GPIO254[31:16] + 1) = PCLK
+ * GPIO254[31:16] = PCLK / (frequency * 2) - 1
+ */
+ if (sgpio_freq == 0)
+ return -EINVAL;
+
+ sgpio_clk_div = (apb_freq / (sgpio_freq * 2)) - 1;
+
+ if (sgpio_clk_div > (1 << 16) - 1)
+ return -EINVAL;
+
+ gpio_cnt_regval = ((nr_gpios / 8) << ASPEED_SGPIO_PINS_SHIFT) & pin_mask;
+ iowrite32(FIELD_PREP(ASPEED_SGPIO_CLK_DIV_MASK, sgpio_clk_div) | gpio_cnt_regval |
+ ASPEED_SGPIO_ENABLE, gpio->base + ASPEED_SGPIO_CTRL);
+
+ raw_spin_lock_init(&gpio->lock);
+
+ gpio->chip.parent = &pdev->dev;
+ gpio->chip.ngpio = nr_gpios * 2;
+ gpio->chip.init_valid_mask = aspeed_sgpio_init_valid_mask;
+ gpio->chip.direction_input = aspeed_sgpio_dir_in;
+ gpio->chip.direction_output = aspeed_sgpio_dir_out;
+ gpio->chip.get_direction = aspeed_sgpio_get_direction;
+ gpio->chip.request = NULL;
+ gpio->chip.free = NULL;
+ gpio->chip.get = aspeed_sgpio_get;
+ gpio->chip.set = aspeed_sgpio_set;
+ gpio->chip.set_config = aspeed_sgpio_set_config;
+ gpio->chip.label = dev_name(&pdev->dev);
+ gpio->chip.base = -1;
+
+ aspeed_sgpio_setup_irqs(gpio, pdev);
+
+ rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+static struct platform_driver aspeed_sgpio_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = aspeed_sgpio_of_table,
+ },
+};
+
+module_platform_driver_probe(aspeed_sgpio_driver, aspeed_sgpio_probe);
+MODULE_DESCRIPTION("Aspeed Serial GPIO Driver");
diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c
new file mode 100644
index 0000000000..58f107194f
--- /dev/null
+++ b/drivers/gpio/gpio-aspeed.c
@@ -0,0 +1,1280 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 IBM Corp.
+ *
+ * Joel Stanley <joel@jms.id.au>
+ */
+
+#include <linux/clk.h>
+#include <linux/gpio/aspeed.h>
+#include <linux/gpio/driver.h>
+#include <linux/hashtable.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+
+#include <asm/div64.h>
+
+/*
+ * These two headers aren't meant to be used by GPIO drivers. We need
+ * them in order to access gpio_chip_hwgpio() which we need to implement
+ * the aspeed specific API which allows the coprocessor to request
+ * access to some GPIOs and to arbitrate between coprocessor and ARM.
+ */
+#include <linux/gpio/consumer.h>
+#include "gpiolib.h"
+
+struct aspeed_bank_props {
+ unsigned int bank;
+ u32 input;
+ u32 output;
+};
+
+struct aspeed_gpio_config {
+ unsigned int nr_gpios;
+ const struct aspeed_bank_props *props;
+};
+
+/*
+ * @offset_timer: Maps an offset to an @timer_users index, or zero if disabled
+ * @timer_users: Tracks the number of users for each timer
+ *
+ * The @timer_users has four elements but the first element is unused. This is
+ * to simplify accounting and indexing, as a zero value in @offset_timer
+ * represents disabled debouncing for the GPIO. Any other value for an element
+ * of @offset_timer is used as an index into @timer_users. This behaviour of
+ * the zero value aligns with the behaviour of zero built from the timer
+ * configuration registers (i.e. debouncing is disabled).
+ */
+struct aspeed_gpio {
+ struct gpio_chip chip;
+ struct device *dev;
+ raw_spinlock_t lock;
+ void __iomem *base;
+ int irq;
+ const struct aspeed_gpio_config *config;
+
+ u8 *offset_timer;
+ unsigned int timer_users[4];
+ struct clk *clk;
+
+ u32 *dcache;
+ u8 *cf_copro_bankmap;
+};
+
+struct aspeed_gpio_bank {
+ uint16_t val_regs; /* +0: Rd: read input value, Wr: set write latch
+ * +4: Rd/Wr: Direction (0=in, 1=out)
+ */
+ uint16_t rdata_reg; /* Rd: read write latch, Wr: <none> */
+ uint16_t irq_regs;
+ uint16_t debounce_regs;
+ uint16_t tolerance_regs;
+ uint16_t cmdsrc_regs;
+ const char names[4][3];
+};
+
+/*
+ * Note: The "value" register returns the input value sampled on the
+ * line even when the GPIO is configured as an output. Since
+ * that input goes through synchronizers, writing, then reading
+ * back may not return the written value right away.
+ *
+ * The "rdata" register returns the content of the write latch
+ * and thus can be used to read back what was last written
+ * reliably.
+ */
+
+static const int debounce_timers[4] = { 0x00, 0x50, 0x54, 0x58 };
+
+static const struct aspeed_gpio_copro_ops *copro_ops;
+static void *copro_data;
+
+static const struct aspeed_gpio_bank aspeed_gpio_banks[] = {
+ {
+ .val_regs = 0x0000,
+ .rdata_reg = 0x00c0,
+ .irq_regs = 0x0008,
+ .debounce_regs = 0x0040,
+ .tolerance_regs = 0x001c,
+ .cmdsrc_regs = 0x0060,
+ .names = { "A", "B", "C", "D" },
+ },
+ {
+ .val_regs = 0x0020,
+ .rdata_reg = 0x00c4,
+ .irq_regs = 0x0028,
+ .debounce_regs = 0x0048,
+ .tolerance_regs = 0x003c,
+ .cmdsrc_regs = 0x0068,
+ .names = { "E", "F", "G", "H" },
+ },
+ {
+ .val_regs = 0x0070,
+ .rdata_reg = 0x00c8,
+ .irq_regs = 0x0098,
+ .debounce_regs = 0x00b0,
+ .tolerance_regs = 0x00ac,
+ .cmdsrc_regs = 0x0090,
+ .names = { "I", "J", "K", "L" },
+ },
+ {
+ .val_regs = 0x0078,
+ .rdata_reg = 0x00cc,
+ .irq_regs = 0x00e8,
+ .debounce_regs = 0x0100,
+ .tolerance_regs = 0x00fc,
+ .cmdsrc_regs = 0x00e0,
+ .names = { "M", "N", "O", "P" },
+ },
+ {
+ .val_regs = 0x0080,
+ .rdata_reg = 0x00d0,
+ .irq_regs = 0x0118,
+ .debounce_regs = 0x0130,
+ .tolerance_regs = 0x012c,
+ .cmdsrc_regs = 0x0110,
+ .names = { "Q", "R", "S", "T" },
+ },
+ {
+ .val_regs = 0x0088,
+ .rdata_reg = 0x00d4,
+ .irq_regs = 0x0148,
+ .debounce_regs = 0x0160,
+ .tolerance_regs = 0x015c,
+ .cmdsrc_regs = 0x0140,
+ .names = { "U", "V", "W", "X" },
+ },
+ {
+ .val_regs = 0x01E0,
+ .rdata_reg = 0x00d8,
+ .irq_regs = 0x0178,
+ .debounce_regs = 0x0190,
+ .tolerance_regs = 0x018c,
+ .cmdsrc_regs = 0x0170,
+ .names = { "Y", "Z", "AA", "AB" },
+ },
+ {
+ .val_regs = 0x01e8,
+ .rdata_reg = 0x00dc,
+ .irq_regs = 0x01a8,
+ .debounce_regs = 0x01c0,
+ .tolerance_regs = 0x01bc,
+ .cmdsrc_regs = 0x01a0,
+ .names = { "AC", "", "", "" },
+ },
+};
+
+enum aspeed_gpio_reg {
+ reg_val,
+ reg_rdata,
+ reg_dir,
+ reg_irq_enable,
+ reg_irq_type0,
+ reg_irq_type1,
+ reg_irq_type2,
+ reg_irq_status,
+ reg_debounce_sel1,
+ reg_debounce_sel2,
+ reg_tolerance,
+ reg_cmdsrc0,
+ reg_cmdsrc1,
+};
+
+#define GPIO_VAL_VALUE 0x00
+#define GPIO_VAL_DIR 0x04
+
+#define GPIO_IRQ_ENABLE 0x00
+#define GPIO_IRQ_TYPE0 0x04
+#define GPIO_IRQ_TYPE1 0x08
+#define GPIO_IRQ_TYPE2 0x0c
+#define GPIO_IRQ_STATUS 0x10
+
+#define GPIO_DEBOUNCE_SEL1 0x00
+#define GPIO_DEBOUNCE_SEL2 0x04
+
+#define GPIO_CMDSRC_0 0x00
+#define GPIO_CMDSRC_1 0x04
+#define GPIO_CMDSRC_ARM 0
+#define GPIO_CMDSRC_LPC 1
+#define GPIO_CMDSRC_COLDFIRE 2
+#define GPIO_CMDSRC_RESERVED 3
+
+/* This will be resolved at compile time */
+static inline void __iomem *bank_reg(struct aspeed_gpio *gpio,
+ const struct aspeed_gpio_bank *bank,
+ const enum aspeed_gpio_reg reg)
+{
+ switch (reg) {
+ case reg_val:
+ return gpio->base + bank->val_regs + GPIO_VAL_VALUE;
+ case reg_rdata:
+ return gpio->base + bank->rdata_reg;
+ case reg_dir:
+ return gpio->base + bank->val_regs + GPIO_VAL_DIR;
+ case reg_irq_enable:
+ return gpio->base + bank->irq_regs + GPIO_IRQ_ENABLE;
+ case reg_irq_type0:
+ return gpio->base + bank->irq_regs + GPIO_IRQ_TYPE0;
+ case reg_irq_type1:
+ return gpio->base + bank->irq_regs + GPIO_IRQ_TYPE1;
+ case reg_irq_type2:
+ return gpio->base + bank->irq_regs + GPIO_IRQ_TYPE2;
+ case reg_irq_status:
+ return gpio->base + bank->irq_regs + GPIO_IRQ_STATUS;
+ case reg_debounce_sel1:
+ return gpio->base + bank->debounce_regs + GPIO_DEBOUNCE_SEL1;
+ case reg_debounce_sel2:
+ return gpio->base + bank->debounce_regs + GPIO_DEBOUNCE_SEL2;
+ case reg_tolerance:
+ return gpio->base + bank->tolerance_regs;
+ case reg_cmdsrc0:
+ return gpio->base + bank->cmdsrc_regs + GPIO_CMDSRC_0;
+ case reg_cmdsrc1:
+ return gpio->base + bank->cmdsrc_regs + GPIO_CMDSRC_1;
+ }
+ BUG();
+}
+
+#define GPIO_BANK(x) ((x) >> 5)
+#define GPIO_OFFSET(x) ((x) & 0x1f)
+#define GPIO_BIT(x) BIT(GPIO_OFFSET(x))
+
+#define _GPIO_SET_DEBOUNCE(t, o, i) ((!!((t) & BIT(i))) << GPIO_OFFSET(o))
+#define GPIO_SET_DEBOUNCE1(t, o) _GPIO_SET_DEBOUNCE(t, o, 1)
+#define GPIO_SET_DEBOUNCE2(t, o) _GPIO_SET_DEBOUNCE(t, o, 0)
+
+static const struct aspeed_gpio_bank *to_bank(unsigned int offset)
+{
+ unsigned int bank = GPIO_BANK(offset);
+
+ WARN_ON(bank >= ARRAY_SIZE(aspeed_gpio_banks));
+ return &aspeed_gpio_banks[bank];
+}
+
+static inline bool is_bank_props_sentinel(const struct aspeed_bank_props *props)
+{
+ return !(props->input || props->output);
+}
+
+static inline const struct aspeed_bank_props *find_bank_props(
+ struct aspeed_gpio *gpio, unsigned int offset)
+{
+ const struct aspeed_bank_props *props = gpio->config->props;
+
+ while (!is_bank_props_sentinel(props)) {
+ if (props->bank == GPIO_BANK(offset))
+ return props;
+ props++;
+ }
+
+ return NULL;
+}
+
+static inline bool have_gpio(struct aspeed_gpio *gpio, unsigned int offset)
+{
+ const struct aspeed_bank_props *props = find_bank_props(gpio, offset);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ unsigned int group = GPIO_OFFSET(offset) / 8;
+
+ return bank->names[group][0] != '\0' &&
+ (!props || ((props->input | props->output) & GPIO_BIT(offset)));
+}
+
+static inline bool have_input(struct aspeed_gpio *gpio, unsigned int offset)
+{
+ const struct aspeed_bank_props *props = find_bank_props(gpio, offset);
+
+ return !props || (props->input & GPIO_BIT(offset));
+}
+
+#define have_irq(g, o) have_input((g), (o))
+#define have_debounce(g, o) have_input((g), (o))
+
+static inline bool have_output(struct aspeed_gpio *gpio, unsigned int offset)
+{
+ const struct aspeed_bank_props *props = find_bank_props(gpio, offset);
+
+ return !props || (props->output & GPIO_BIT(offset));
+}
+
+static void aspeed_gpio_change_cmd_source(struct aspeed_gpio *gpio,
+ const struct aspeed_gpio_bank *bank,
+ int bindex, int cmdsrc)
+{
+ void __iomem *c0 = bank_reg(gpio, bank, reg_cmdsrc0);
+ void __iomem *c1 = bank_reg(gpio, bank, reg_cmdsrc1);
+ u32 bit, reg;
+
+ /*
+ * Each register controls 4 banks, so take the bottom 2
+ * bits of the bank index, and use them to select the
+ * right control bit (0, 8, 16 or 24).
+ */
+ bit = BIT((bindex & 3) << 3);
+
+ /* Source 1 first to avoid illegal 11 combination */
+ reg = ioread32(c1);
+ if (cmdsrc & 2)
+ reg |= bit;
+ else
+ reg &= ~bit;
+ iowrite32(reg, c1);
+
+ /* Then Source 0 */
+ reg = ioread32(c0);
+ if (cmdsrc & 1)
+ reg |= bit;
+ else
+ reg &= ~bit;
+ iowrite32(reg, c0);
+}
+
+static bool aspeed_gpio_copro_request(struct aspeed_gpio *gpio,
+ unsigned int offset)
+{
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+
+ if (!copro_ops || !gpio->cf_copro_bankmap)
+ return false;
+ if (!gpio->cf_copro_bankmap[offset >> 3])
+ return false;
+ if (!copro_ops->request_access)
+ return false;
+
+ /* Pause the coprocessor */
+ copro_ops->request_access(copro_data);
+
+ /* Change command source back to ARM */
+ aspeed_gpio_change_cmd_source(gpio, bank, offset >> 3, GPIO_CMDSRC_ARM);
+
+ /* Update cache */
+ gpio->dcache[GPIO_BANK(offset)] = ioread32(bank_reg(gpio, bank, reg_rdata));
+
+ return true;
+}
+
+static void aspeed_gpio_copro_release(struct aspeed_gpio *gpio,
+ unsigned int offset)
+{
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+
+ if (!copro_ops || !gpio->cf_copro_bankmap)
+ return;
+ if (!gpio->cf_copro_bankmap[offset >> 3])
+ return;
+ if (!copro_ops->release_access)
+ return;
+
+ /* Change command source back to ColdFire */
+ aspeed_gpio_change_cmd_source(gpio, bank, offset >> 3,
+ GPIO_CMDSRC_COLDFIRE);
+
+ /* Restart the coprocessor */
+ copro_ops->release_access(copro_data);
+}
+
+static int aspeed_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct aspeed_gpio *gpio = gpiochip_get_data(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+
+ return !!(ioread32(bank_reg(gpio, bank, reg_val)) & GPIO_BIT(offset));
+}
+
+static void __aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int val)
+{
+ struct aspeed_gpio *gpio = gpiochip_get_data(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ void __iomem *addr;
+ u32 reg;
+
+ addr = bank_reg(gpio, bank, reg_val);
+ reg = gpio->dcache[GPIO_BANK(offset)];
+
+ if (val)
+ reg |= GPIO_BIT(offset);
+ else
+ reg &= ~GPIO_BIT(offset);
+ gpio->dcache[GPIO_BANK(offset)] = reg;
+
+ iowrite32(reg, addr);
+}
+
+static void aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int val)
+{
+ struct aspeed_gpio *gpio = gpiochip_get_data(gc);
+ unsigned long flags;
+ bool copro;
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+ copro = aspeed_gpio_copro_request(gpio, offset);
+
+ __aspeed_gpio_set(gc, offset, val);
+
+ if (copro)
+ aspeed_gpio_copro_release(gpio, offset);
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+}
+
+static int aspeed_gpio_dir_in(struct gpio_chip *gc, unsigned int offset)
+{
+ struct aspeed_gpio *gpio = gpiochip_get_data(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ void __iomem *addr = bank_reg(gpio, bank, reg_dir);
+ unsigned long flags;
+ bool copro;
+ u32 reg;
+
+ if (!have_input(gpio, offset))
+ return -ENOTSUPP;
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ reg = ioread32(addr);
+ reg &= ~GPIO_BIT(offset);
+
+ copro = aspeed_gpio_copro_request(gpio, offset);
+ iowrite32(reg, addr);
+ if (copro)
+ aspeed_gpio_copro_release(gpio, offset);
+
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return 0;
+}
+
+static int aspeed_gpio_dir_out(struct gpio_chip *gc,
+ unsigned int offset, int val)
+{
+ struct aspeed_gpio *gpio = gpiochip_get_data(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ void __iomem *addr = bank_reg(gpio, bank, reg_dir);
+ unsigned long flags;
+ bool copro;
+ u32 reg;
+
+ if (!have_output(gpio, offset))
+ return -ENOTSUPP;
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ reg = ioread32(addr);
+ reg |= GPIO_BIT(offset);
+
+ copro = aspeed_gpio_copro_request(gpio, offset);
+ __aspeed_gpio_set(gc, offset, val);
+ iowrite32(reg, addr);
+
+ if (copro)
+ aspeed_gpio_copro_release(gpio, offset);
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return 0;
+}
+
+static int aspeed_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct aspeed_gpio *gpio = gpiochip_get_data(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ unsigned long flags;
+ u32 val;
+
+ if (!have_input(gpio, offset))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ if (!have_output(gpio, offset))
+ return GPIO_LINE_DIRECTION_IN;
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ val = ioread32(bank_reg(gpio, bank, reg_dir)) & GPIO_BIT(offset);
+
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return val ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+}
+
+static inline int irqd_to_aspeed_gpio_data(struct irq_data *d,
+ struct aspeed_gpio **gpio,
+ const struct aspeed_gpio_bank **bank,
+ u32 *bit, int *offset)
+{
+ struct aspeed_gpio *internal;
+
+ *offset = irqd_to_hwirq(d);
+
+ internal = irq_data_get_irq_chip_data(d);
+
+ /* This might be a bit of a questionable place to check */
+ if (!have_irq(internal, *offset))
+ return -ENOTSUPP;
+
+ *gpio = internal;
+ *bank = to_bank(*offset);
+ *bit = GPIO_BIT(*offset);
+
+ return 0;
+}
+
+static void aspeed_gpio_irq_ack(struct irq_data *d)
+{
+ const struct aspeed_gpio_bank *bank;
+ struct aspeed_gpio *gpio;
+ unsigned long flags;
+ void __iomem *status_addr;
+ int rc, offset;
+ bool copro;
+ u32 bit;
+
+ rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset);
+ if (rc)
+ return;
+
+ status_addr = bank_reg(gpio, bank, reg_irq_status);
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+ copro = aspeed_gpio_copro_request(gpio, offset);
+
+ iowrite32(bit, status_addr);
+
+ if (copro)
+ aspeed_gpio_copro_release(gpio, offset);
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+}
+
+static void aspeed_gpio_irq_set_mask(struct irq_data *d, bool set)
+{
+ const struct aspeed_gpio_bank *bank;
+ struct aspeed_gpio *gpio;
+ unsigned long flags;
+ u32 reg, bit;
+ void __iomem *addr;
+ int rc, offset;
+ bool copro;
+
+ rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset);
+ if (rc)
+ return;
+
+ addr = bank_reg(gpio, bank, reg_irq_enable);
+
+ /* Unmasking the IRQ */
+ if (set)
+ gpiochip_enable_irq(&gpio->chip, irqd_to_hwirq(d));
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+ copro = aspeed_gpio_copro_request(gpio, offset);
+
+ reg = ioread32(addr);
+ if (set)
+ reg |= bit;
+ else
+ reg &= ~bit;
+ iowrite32(reg, addr);
+
+ if (copro)
+ aspeed_gpio_copro_release(gpio, offset);
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ /* Masking the IRQ */
+ if (!set)
+ gpiochip_disable_irq(&gpio->chip, irqd_to_hwirq(d));
+}
+
+static void aspeed_gpio_irq_mask(struct irq_data *d)
+{
+ aspeed_gpio_irq_set_mask(d, false);
+}
+
+static void aspeed_gpio_irq_unmask(struct irq_data *d)
+{
+ aspeed_gpio_irq_set_mask(d, true);
+}
+
+static int aspeed_gpio_set_type(struct irq_data *d, unsigned int type)
+{
+ u32 type0 = 0;
+ u32 type1 = 0;
+ u32 type2 = 0;
+ u32 bit, reg;
+ const struct aspeed_gpio_bank *bank;
+ irq_flow_handler_t handler;
+ struct aspeed_gpio *gpio;
+ unsigned long flags;
+ void __iomem *addr;
+ int rc, offset;
+ bool copro;
+
+ rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset);
+ if (rc)
+ return -EINVAL;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_BOTH:
+ type2 |= bit;
+ fallthrough;
+ case IRQ_TYPE_EDGE_RISING:
+ type0 |= bit;
+ fallthrough;
+ case IRQ_TYPE_EDGE_FALLING:
+ handler = handle_edge_irq;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ type0 |= bit;
+ fallthrough;
+ case IRQ_TYPE_LEVEL_LOW:
+ type1 |= bit;
+ handler = handle_level_irq;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+ copro = aspeed_gpio_copro_request(gpio, offset);
+
+ addr = bank_reg(gpio, bank, reg_irq_type0);
+ reg = ioread32(addr);
+ reg = (reg & ~bit) | type0;
+ iowrite32(reg, addr);
+
+ addr = bank_reg(gpio, bank, reg_irq_type1);
+ reg = ioread32(addr);
+ reg = (reg & ~bit) | type1;
+ iowrite32(reg, addr);
+
+ addr = bank_reg(gpio, bank, reg_irq_type2);
+ reg = ioread32(addr);
+ reg = (reg & ~bit) | type2;
+ iowrite32(reg, addr);
+
+ if (copro)
+ aspeed_gpio_copro_release(gpio, offset);
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ irq_set_handler_locked(d, handler);
+
+ return 0;
+}
+
+static void aspeed_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct gpio_chip *gc = irq_desc_get_handler_data(desc);
+ struct irq_chip *ic = irq_desc_get_chip(desc);
+ struct aspeed_gpio *data = gpiochip_get_data(gc);
+ unsigned int i, p, banks;
+ unsigned long reg;
+ struct aspeed_gpio *gpio = gpiochip_get_data(gc);
+
+ chained_irq_enter(ic, desc);
+
+ banks = DIV_ROUND_UP(gpio->chip.ngpio, 32);
+ for (i = 0; i < banks; i++) {
+ const struct aspeed_gpio_bank *bank = &aspeed_gpio_banks[i];
+
+ reg = ioread32(bank_reg(data, bank, reg_irq_status));
+
+ for_each_set_bit(p, &reg, 32)
+ generic_handle_domain_irq(gc->irq.domain, i * 32 + p);
+ }
+
+ chained_irq_exit(ic, desc);
+}
+
+static void aspeed_init_irq_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ struct aspeed_gpio *gpio = gpiochip_get_data(gc);
+ const struct aspeed_bank_props *props = gpio->config->props;
+
+ while (!is_bank_props_sentinel(props)) {
+ unsigned int offset;
+ const unsigned long int input = props->input;
+
+ /* Pretty crummy approach, but similar to GPIO core */
+ for_each_clear_bit(offset, &input, 32) {
+ unsigned int i = props->bank * 32 + offset;
+
+ if (i >= gpio->chip.ngpio)
+ break;
+
+ clear_bit(i, valid_mask);
+ }
+
+ props++;
+ }
+}
+
+static int aspeed_gpio_reset_tolerance(struct gpio_chip *chip,
+ unsigned int offset, bool enable)
+{
+ struct aspeed_gpio *gpio = gpiochip_get_data(chip);
+ unsigned long flags;
+ void __iomem *treg;
+ bool copro;
+ u32 val;
+
+ treg = bank_reg(gpio, to_bank(offset), reg_tolerance);
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+ copro = aspeed_gpio_copro_request(gpio, offset);
+
+ val = readl(treg);
+
+ if (enable)
+ val |= GPIO_BIT(offset);
+ else
+ val &= ~GPIO_BIT(offset);
+
+ writel(val, treg);
+
+ if (copro)
+ aspeed_gpio_copro_release(gpio, offset);
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return 0;
+}
+
+static int aspeed_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ if (!have_gpio(gpiochip_get_data(chip), offset))
+ return -ENODEV;
+
+ return pinctrl_gpio_request(chip->base + offset);
+}
+
+static void aspeed_gpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+ pinctrl_gpio_free(chip->base + offset);
+}
+
+static int usecs_to_cycles(struct aspeed_gpio *gpio, unsigned long usecs,
+ u32 *cycles)
+{
+ u64 rate;
+ u64 n;
+ u32 r;
+
+ rate = clk_get_rate(gpio->clk);
+ if (!rate)
+ return -ENOTSUPP;
+
+ n = rate * usecs;
+ r = do_div(n, 1000000);
+
+ if (n >= U32_MAX)
+ return -ERANGE;
+
+ /* At least as long as the requested time */
+ *cycles = n + (!!r);
+
+ return 0;
+}
+
+/* Call under gpio->lock */
+static int register_allocated_timer(struct aspeed_gpio *gpio,
+ unsigned int offset, unsigned int timer)
+{
+ if (WARN(gpio->offset_timer[offset] != 0,
+ "Offset %d already allocated timer %d\n",
+ offset, gpio->offset_timer[offset]))
+ return -EINVAL;
+
+ if (WARN(gpio->timer_users[timer] == UINT_MAX,
+ "Timer user count would overflow\n"))
+ return -EPERM;
+
+ gpio->offset_timer[offset] = timer;
+ gpio->timer_users[timer]++;
+
+ return 0;
+}
+
+/* Call under gpio->lock */
+static int unregister_allocated_timer(struct aspeed_gpio *gpio,
+ unsigned int offset)
+{
+ if (WARN(gpio->offset_timer[offset] == 0,
+ "No timer allocated to offset %d\n", offset))
+ return -EINVAL;
+
+ if (WARN(gpio->timer_users[gpio->offset_timer[offset]] == 0,
+ "No users recorded for timer %d\n",
+ gpio->offset_timer[offset]))
+ return -EINVAL;
+
+ gpio->timer_users[gpio->offset_timer[offset]]--;
+ gpio->offset_timer[offset] = 0;
+
+ return 0;
+}
+
+/* Call under gpio->lock */
+static inline bool timer_allocation_registered(struct aspeed_gpio *gpio,
+ unsigned int offset)
+{
+ return gpio->offset_timer[offset] > 0;
+}
+
+/* Call under gpio->lock */
+static void configure_timer(struct aspeed_gpio *gpio, unsigned int offset,
+ unsigned int timer)
+{
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ const u32 mask = GPIO_BIT(offset);
+ void __iomem *addr;
+ u32 val;
+
+ /* Note: Debounce timer isn't under control of the command
+ * source registers, so no need to sync with the coprocessor
+ */
+ addr = bank_reg(gpio, bank, reg_debounce_sel1);
+ val = ioread32(addr);
+ iowrite32((val & ~mask) | GPIO_SET_DEBOUNCE1(timer, offset), addr);
+
+ addr = bank_reg(gpio, bank, reg_debounce_sel2);
+ val = ioread32(addr);
+ iowrite32((val & ~mask) | GPIO_SET_DEBOUNCE2(timer, offset), addr);
+}
+
+static int enable_debounce(struct gpio_chip *chip, unsigned int offset,
+ unsigned long usecs)
+{
+ struct aspeed_gpio *gpio = gpiochip_get_data(chip);
+ u32 requested_cycles;
+ unsigned long flags;
+ int rc;
+ int i;
+
+ if (!gpio->clk)
+ return -EINVAL;
+
+ rc = usecs_to_cycles(gpio, usecs, &requested_cycles);
+ if (rc < 0) {
+ dev_warn(chip->parent, "Failed to convert %luus to cycles at %luHz: %d\n",
+ usecs, clk_get_rate(gpio->clk), rc);
+ return rc;
+ }
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ if (timer_allocation_registered(gpio, offset)) {
+ rc = unregister_allocated_timer(gpio, offset);
+ if (rc < 0)
+ goto out;
+ }
+
+ /* Try to find a timer already configured for the debounce period */
+ for (i = 1; i < ARRAY_SIZE(debounce_timers); i++) {
+ u32 cycles;
+
+ cycles = ioread32(gpio->base + debounce_timers[i]);
+ if (requested_cycles == cycles)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(debounce_timers)) {
+ int j;
+
+ /*
+ * As there are no timers configured for the requested debounce
+ * period, find an unused timer instead
+ */
+ for (j = 1; j < ARRAY_SIZE(gpio->timer_users); j++) {
+ if (gpio->timer_users[j] == 0)
+ break;
+ }
+
+ if (j == ARRAY_SIZE(gpio->timer_users)) {
+ dev_warn(chip->parent,
+ "Debounce timers exhausted, cannot debounce for period %luus\n",
+ usecs);
+
+ rc = -EPERM;
+
+ /*
+ * We already adjusted the accounting to remove @offset
+ * as a user of its previous timer, so also configure
+ * the hardware so @offset has timers disabled for
+ * consistency.
+ */
+ configure_timer(gpio, offset, 0);
+ goto out;
+ }
+
+ i = j;
+
+ iowrite32(requested_cycles, gpio->base + debounce_timers[i]);
+ }
+
+ if (WARN(i == 0, "Cannot register index of disabled timer\n")) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ register_allocated_timer(gpio, offset, i);
+ configure_timer(gpio, offset, i);
+
+out:
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return rc;
+}
+
+static int disable_debounce(struct gpio_chip *chip, unsigned int offset)
+{
+ struct aspeed_gpio *gpio = gpiochip_get_data(chip);
+ unsigned long flags;
+ int rc;
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ rc = unregister_allocated_timer(gpio, offset);
+ if (!rc)
+ configure_timer(gpio, offset, 0);
+
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return rc;
+}
+
+static int set_debounce(struct gpio_chip *chip, unsigned int offset,
+ unsigned long usecs)
+{
+ struct aspeed_gpio *gpio = gpiochip_get_data(chip);
+
+ if (!have_debounce(gpio, offset))
+ return -ENOTSUPP;
+
+ if (usecs)
+ return enable_debounce(chip, offset, usecs);
+
+ return disable_debounce(chip, offset);
+}
+
+static int aspeed_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ unsigned long param = pinconf_to_config_param(config);
+ u32 arg = pinconf_to_config_argument(config);
+
+ if (param == PIN_CONFIG_INPUT_DEBOUNCE)
+ return set_debounce(chip, offset, arg);
+ else if (param == PIN_CONFIG_BIAS_DISABLE ||
+ param == PIN_CONFIG_BIAS_PULL_DOWN ||
+ param == PIN_CONFIG_DRIVE_STRENGTH)
+ return pinctrl_gpio_set_config(chip->base + offset, config);
+ else if (param == PIN_CONFIG_DRIVE_OPEN_DRAIN ||
+ param == PIN_CONFIG_DRIVE_OPEN_SOURCE)
+ /* Return -ENOTSUPP to trigger emulation, as per datasheet */
+ return -ENOTSUPP;
+ else if (param == PIN_CONFIG_PERSIST_STATE)
+ return aspeed_gpio_reset_tolerance(chip, offset, arg);
+
+ return -ENOTSUPP;
+}
+
+/**
+ * aspeed_gpio_copro_set_ops - Sets the callbacks used for handshaking with
+ * the coprocessor for shared GPIO banks
+ * @ops: The callbacks
+ * @data: Pointer passed back to the callbacks
+ */
+int aspeed_gpio_copro_set_ops(const struct aspeed_gpio_copro_ops *ops, void *data)
+{
+ copro_data = data;
+ copro_ops = ops;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(aspeed_gpio_copro_set_ops);
+
+/**
+ * aspeed_gpio_copro_grab_gpio - Mark a GPIO used by the coprocessor. The entire
+ * bank gets marked and any access from the ARM will
+ * result in handshaking via callbacks.
+ * @desc: The GPIO to be marked
+ * @vreg_offset: If non-NULL, returns the value register offset in the GPIO space
+ * @dreg_offset: If non-NULL, returns the data latch register offset in the GPIO space
+ * @bit: If non-NULL, returns the bit number of the GPIO in the registers
+ */
+int aspeed_gpio_copro_grab_gpio(struct gpio_desc *desc,
+ u16 *vreg_offset, u16 *dreg_offset, u8 *bit)
+{
+ struct gpio_chip *chip = gpiod_to_chip(desc);
+ struct aspeed_gpio *gpio = gpiochip_get_data(chip);
+ int rc = 0, bindex, offset = gpio_chip_hwgpio(desc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ unsigned long flags;
+
+ if (!gpio->cf_copro_bankmap)
+ gpio->cf_copro_bankmap = kzalloc(gpio->chip.ngpio >> 3, GFP_KERNEL);
+ if (!gpio->cf_copro_bankmap)
+ return -ENOMEM;
+ if (offset < 0 || offset > gpio->chip.ngpio)
+ return -EINVAL;
+ bindex = offset >> 3;
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ /* Sanity check, this shouldn't happen */
+ if (gpio->cf_copro_bankmap[bindex] == 0xff) {
+ rc = -EIO;
+ goto bail;
+ }
+ gpio->cf_copro_bankmap[bindex]++;
+
+ /* Switch command source */
+ if (gpio->cf_copro_bankmap[bindex] == 1)
+ aspeed_gpio_change_cmd_source(gpio, bank, bindex,
+ GPIO_CMDSRC_COLDFIRE);
+
+ if (vreg_offset)
+ *vreg_offset = bank->val_regs;
+ if (dreg_offset)
+ *dreg_offset = bank->rdata_reg;
+ if (bit)
+ *bit = GPIO_OFFSET(offset);
+ bail:
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(aspeed_gpio_copro_grab_gpio);
+
+/**
+ * aspeed_gpio_copro_release_gpio - Unmark a GPIO used by the coprocessor.
+ * @desc: The GPIO to be marked
+ */
+int aspeed_gpio_copro_release_gpio(struct gpio_desc *desc)
+{
+ struct gpio_chip *chip = gpiod_to_chip(desc);
+ struct aspeed_gpio *gpio = gpiochip_get_data(chip);
+ int rc = 0, bindex, offset = gpio_chip_hwgpio(desc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ unsigned long flags;
+
+ if (!gpio->cf_copro_bankmap)
+ return -ENXIO;
+
+ if (offset < 0 || offset > gpio->chip.ngpio)
+ return -EINVAL;
+ bindex = offset >> 3;
+
+ raw_spin_lock_irqsave(&gpio->lock, flags);
+
+ /* Sanity check, this shouldn't happen */
+ if (gpio->cf_copro_bankmap[bindex] == 0) {
+ rc = -EIO;
+ goto bail;
+ }
+ gpio->cf_copro_bankmap[bindex]--;
+
+ /* Switch command source */
+ if (gpio->cf_copro_bankmap[bindex] == 0)
+ aspeed_gpio_change_cmd_source(gpio, bank, bindex,
+ GPIO_CMDSRC_ARM);
+ bail:
+ raw_spin_unlock_irqrestore(&gpio->lock, flags);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(aspeed_gpio_copro_release_gpio);
+
+static void aspeed_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p)
+{
+ const struct aspeed_gpio_bank *bank;
+ struct aspeed_gpio *gpio;
+ u32 bit;
+ int rc, offset;
+
+ rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset);
+ if (rc)
+ return;
+
+ seq_printf(p, dev_name(gpio->dev));
+}
+
+static const struct irq_chip aspeed_gpio_irq_chip = {
+ .irq_ack = aspeed_gpio_irq_ack,
+ .irq_mask = aspeed_gpio_irq_mask,
+ .irq_unmask = aspeed_gpio_irq_unmask,
+ .irq_set_type = aspeed_gpio_set_type,
+ .irq_print_chip = aspeed_gpio_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+/*
+ * Any banks not specified in a struct aspeed_bank_props array are assumed to
+ * have the properties:
+ *
+ * { .input = 0xffffffff, .output = 0xffffffff }
+ */
+
+static const struct aspeed_bank_props ast2400_bank_props[] = {
+ /* input output */
+ { 5, 0xffffffff, 0x0000ffff }, /* U/V/W/X */
+ { 6, 0x0000000f, 0x0fffff0f }, /* Y/Z/AA/AB, two 4-GPIO holes */
+ { },
+};
+
+static const struct aspeed_gpio_config ast2400_config =
+ /* 220 for simplicity, really 216 with two 4-GPIO holes, four at end */
+ { .nr_gpios = 220, .props = ast2400_bank_props, };
+
+static const struct aspeed_bank_props ast2500_bank_props[] = {
+ /* input output */
+ { 5, 0xffffffff, 0x0000ffff }, /* U/V/W/X */
+ { 6, 0x0fffffff, 0x0fffffff }, /* Y/Z/AA/AB, 4-GPIO hole */
+ { 7, 0x000000ff, 0x000000ff }, /* AC */
+ { },
+};
+
+static const struct aspeed_gpio_config ast2500_config =
+ /* 232 for simplicity, actual number is 228 (4-GPIO hole in GPIOAB) */
+ { .nr_gpios = 232, .props = ast2500_bank_props, };
+
+static const struct aspeed_bank_props ast2600_bank_props[] = {
+ /* input output */
+ {4, 0xffffffff, 0x00ffffff}, /* Q/R/S/T */
+ {5, 0xffffffff, 0xffffff00}, /* U/V/W/X */
+ {6, 0x0000ffff, 0x0000ffff}, /* Y/Z */
+ { },
+};
+
+static const struct aspeed_gpio_config ast2600_config =
+ /*
+ * ast2600 has two controllers one with 208 GPIOs and one with 36 GPIOs.
+ * We expect ngpio being set in the device tree and this is a fallback
+ * option.
+ */
+ { .nr_gpios = 208, .props = ast2600_bank_props, };
+
+static const struct of_device_id aspeed_gpio_of_table[] = {
+ { .compatible = "aspeed,ast2400-gpio", .data = &ast2400_config, },
+ { .compatible = "aspeed,ast2500-gpio", .data = &ast2500_config, },
+ { .compatible = "aspeed,ast2600-gpio", .data = &ast2600_config, },
+ {}
+};
+MODULE_DEVICE_TABLE(of, aspeed_gpio_of_table);
+
+static int __init aspeed_gpio_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *gpio_id;
+ struct gpio_irq_chip *girq;
+ struct aspeed_gpio *gpio;
+ int rc, irq, i, banks, err;
+ u32 ngpio;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gpio->base))
+ return PTR_ERR(gpio->base);
+
+ gpio->dev = &pdev->dev;
+
+ raw_spin_lock_init(&gpio->lock);
+
+ gpio_id = of_match_node(aspeed_gpio_of_table, pdev->dev.of_node);
+ if (!gpio_id)
+ return -EINVAL;
+
+ gpio->clk = of_clk_get(pdev->dev.of_node, 0);
+ if (IS_ERR(gpio->clk)) {
+ dev_warn(&pdev->dev,
+ "Failed to get clock from devicetree, debouncing disabled\n");
+ gpio->clk = NULL;
+ }
+
+ gpio->config = gpio_id->data;
+
+ gpio->chip.parent = &pdev->dev;
+ err = of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpio);
+ gpio->chip.ngpio = (u16) ngpio;
+ if (err)
+ gpio->chip.ngpio = gpio->config->nr_gpios;
+ gpio->chip.direction_input = aspeed_gpio_dir_in;
+ gpio->chip.direction_output = aspeed_gpio_dir_out;
+ gpio->chip.get_direction = aspeed_gpio_get_direction;
+ gpio->chip.request = aspeed_gpio_request;
+ gpio->chip.free = aspeed_gpio_free;
+ gpio->chip.get = aspeed_gpio_get;
+ gpio->chip.set = aspeed_gpio_set;
+ gpio->chip.set_config = aspeed_gpio_set_config;
+ gpio->chip.label = dev_name(&pdev->dev);
+ gpio->chip.base = -1;
+
+ /* Allocate a cache of the output registers */
+ banks = DIV_ROUND_UP(gpio->chip.ngpio, 32);
+ gpio->dcache = devm_kcalloc(&pdev->dev,
+ banks, sizeof(u32), GFP_KERNEL);
+ if (!gpio->dcache)
+ return -ENOMEM;
+
+ /*
+ * Populate it with initial values read from the HW and switch
+ * all command sources to the ARM by default
+ */
+ for (i = 0; i < banks; i++) {
+ const struct aspeed_gpio_bank *bank = &aspeed_gpio_banks[i];
+ void __iomem *addr = bank_reg(gpio, bank, reg_rdata);
+ gpio->dcache[i] = ioread32(addr);
+ aspeed_gpio_change_cmd_source(gpio, bank, 0, GPIO_CMDSRC_ARM);
+ aspeed_gpio_change_cmd_source(gpio, bank, 1, GPIO_CMDSRC_ARM);
+ aspeed_gpio_change_cmd_source(gpio, bank, 2, GPIO_CMDSRC_ARM);
+ aspeed_gpio_change_cmd_source(gpio, bank, 3, GPIO_CMDSRC_ARM);
+ }
+
+ /* Set up an irqchip */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+ gpio->irq = irq;
+ girq = &gpio->chip.irq;
+ gpio_irq_chip_set_chip(girq, &aspeed_gpio_irq_chip);
+
+ girq->parent_handler = aspeed_gpio_irq_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(&pdev->dev, 1, sizeof(*girq->parents), GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ girq->parents[0] = gpio->irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+ girq->init_valid_mask = aspeed_init_irq_valid_mask;
+
+ gpio->offset_timer =
+ devm_kzalloc(&pdev->dev, gpio->chip.ngpio, GFP_KERNEL);
+ if (!gpio->offset_timer)
+ return -ENOMEM;
+
+ rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+static struct platform_driver aspeed_gpio_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = aspeed_gpio_of_table,
+ },
+};
+
+module_platform_driver_probe(aspeed_gpio_driver, aspeed_gpio_probe);
+
+MODULE_DESCRIPTION("Aspeed GPIO Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-ath79.c b/drivers/gpio/gpio-ath79.c
new file mode 100644
index 0000000000..f0c0c0f77e
--- /dev/null
+++ b/drivers/gpio/gpio-ath79.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Atheros AR71XX/AR724X/AR913X GPIO API support
+ *
+ * Copyright (C) 2015 Alban Bedel <albeu@free.fr>
+ * Copyright (C) 2010-2011 Jaiganesh Narayanan <jnarayanan@atheros.com>
+ * Copyright (C) 2008-2011 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/gpio-ath79.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+
+#define AR71XX_GPIO_REG_OE 0x00
+#define AR71XX_GPIO_REG_IN 0x04
+#define AR71XX_GPIO_REG_SET 0x0c
+#define AR71XX_GPIO_REG_CLEAR 0x10
+
+#define AR71XX_GPIO_REG_INT_ENABLE 0x14
+#define AR71XX_GPIO_REG_INT_TYPE 0x18
+#define AR71XX_GPIO_REG_INT_POLARITY 0x1c
+#define AR71XX_GPIO_REG_INT_PENDING 0x20
+#define AR71XX_GPIO_REG_INT_MASK 0x24
+
+struct ath79_gpio_ctrl {
+ struct gpio_chip gc;
+ void __iomem *base;
+ raw_spinlock_t lock;
+ unsigned long both_edges;
+};
+
+static struct ath79_gpio_ctrl *irq_data_to_ath79_gpio(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+
+ return container_of(gc, struct ath79_gpio_ctrl, gc);
+}
+
+static u32 ath79_gpio_read(struct ath79_gpio_ctrl *ctrl, unsigned reg)
+{
+ return readl(ctrl->base + reg);
+}
+
+static void ath79_gpio_write(struct ath79_gpio_ctrl *ctrl,
+ unsigned reg, u32 val)
+{
+ writel(val, ctrl->base + reg);
+}
+
+static bool ath79_gpio_update_bits(
+ struct ath79_gpio_ctrl *ctrl, unsigned reg, u32 mask, u32 bits)
+{
+ u32 old_val, new_val;
+
+ old_val = ath79_gpio_read(ctrl, reg);
+ new_val = (old_val & ~mask) | (bits & mask);
+
+ if (new_val != old_val)
+ ath79_gpio_write(ctrl, reg, new_val);
+
+ return new_val != old_val;
+}
+
+static void ath79_gpio_irq_unmask(struct irq_data *data)
+{
+ struct ath79_gpio_ctrl *ctrl = irq_data_to_ath79_gpio(data);
+ u32 mask = BIT(irqd_to_hwirq(data));
+ unsigned long flags;
+
+ gpiochip_enable_irq(&ctrl->gc, irqd_to_hwirq(data));
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+ ath79_gpio_update_bits(ctrl, AR71XX_GPIO_REG_INT_MASK, mask, mask);
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+}
+
+static void ath79_gpio_irq_mask(struct irq_data *data)
+{
+ struct ath79_gpio_ctrl *ctrl = irq_data_to_ath79_gpio(data);
+ u32 mask = BIT(irqd_to_hwirq(data));
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+ ath79_gpio_update_bits(ctrl, AR71XX_GPIO_REG_INT_MASK, mask, 0);
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+ gpiochip_disable_irq(&ctrl->gc, irqd_to_hwirq(data));
+}
+
+static void ath79_gpio_irq_enable(struct irq_data *data)
+{
+ struct ath79_gpio_ctrl *ctrl = irq_data_to_ath79_gpio(data);
+ u32 mask = BIT(irqd_to_hwirq(data));
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+ ath79_gpio_update_bits(ctrl, AR71XX_GPIO_REG_INT_ENABLE, mask, mask);
+ ath79_gpio_update_bits(ctrl, AR71XX_GPIO_REG_INT_MASK, mask, mask);
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+}
+
+static void ath79_gpio_irq_disable(struct irq_data *data)
+{
+ struct ath79_gpio_ctrl *ctrl = irq_data_to_ath79_gpio(data);
+ u32 mask = BIT(irqd_to_hwirq(data));
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+ ath79_gpio_update_bits(ctrl, AR71XX_GPIO_REG_INT_MASK, mask, 0);
+ ath79_gpio_update_bits(ctrl, AR71XX_GPIO_REG_INT_ENABLE, mask, 0);
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+}
+
+static int ath79_gpio_irq_set_type(struct irq_data *data,
+ unsigned int flow_type)
+{
+ struct ath79_gpio_ctrl *ctrl = irq_data_to_ath79_gpio(data);
+ u32 mask = BIT(irqd_to_hwirq(data));
+ u32 type = 0, polarity = 0;
+ unsigned long flags;
+ bool disabled;
+
+ switch (flow_type) {
+ case IRQ_TYPE_EDGE_RISING:
+ polarity |= mask;
+ fallthrough;
+ case IRQ_TYPE_EDGE_FALLING:
+ case IRQ_TYPE_EDGE_BOTH:
+ break;
+
+ case IRQ_TYPE_LEVEL_HIGH:
+ polarity |= mask;
+ fallthrough;
+ case IRQ_TYPE_LEVEL_LOW:
+ type |= mask;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+
+ if (flow_type == IRQ_TYPE_EDGE_BOTH) {
+ ctrl->both_edges |= mask;
+ polarity = ~ath79_gpio_read(ctrl, AR71XX_GPIO_REG_IN);
+ } else {
+ ctrl->both_edges &= ~mask;
+ }
+
+ /* As the IRQ configuration can't be loaded atomically we
+ * have to disable the interrupt while the configuration state
+ * is invalid.
+ */
+ disabled = ath79_gpio_update_bits(
+ ctrl, AR71XX_GPIO_REG_INT_ENABLE, mask, 0);
+
+ ath79_gpio_update_bits(
+ ctrl, AR71XX_GPIO_REG_INT_TYPE, mask, type);
+ ath79_gpio_update_bits(
+ ctrl, AR71XX_GPIO_REG_INT_POLARITY, mask, polarity);
+
+ if (disabled)
+ ath79_gpio_update_bits(
+ ctrl, AR71XX_GPIO_REG_INT_ENABLE, mask, mask);
+
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+
+ return 0;
+}
+
+static const struct irq_chip ath79_gpio_irqchip = {
+ .name = "gpio-ath79",
+ .irq_enable = ath79_gpio_irq_enable,
+ .irq_disable = ath79_gpio_irq_disable,
+ .irq_mask = ath79_gpio_irq_mask,
+ .irq_unmask = ath79_gpio_irq_unmask,
+ .irq_set_type = ath79_gpio_irq_set_type,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static void ath79_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct gpio_chip *gc = irq_desc_get_handler_data(desc);
+ struct irq_chip *irqchip = irq_desc_get_chip(desc);
+ struct ath79_gpio_ctrl *ctrl =
+ container_of(gc, struct ath79_gpio_ctrl, gc);
+ unsigned long flags, pending;
+ u32 both_edges, state;
+ int irq;
+
+ chained_irq_enter(irqchip, desc);
+
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+
+ pending = ath79_gpio_read(ctrl, AR71XX_GPIO_REG_INT_PENDING);
+
+ /* Update the polarity of the both edges irqs */
+ both_edges = ctrl->both_edges & pending;
+ if (both_edges) {
+ state = ath79_gpio_read(ctrl, AR71XX_GPIO_REG_IN);
+ ath79_gpio_update_bits(ctrl, AR71XX_GPIO_REG_INT_POLARITY,
+ both_edges, ~state);
+ }
+
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+
+ for_each_set_bit(irq, &pending, gc->ngpio)
+ generic_handle_domain_irq(gc->irq.domain, irq);
+
+ chained_irq_exit(irqchip, desc);
+}
+
+static const struct of_device_id ath79_gpio_of_match[] = {
+ { .compatible = "qca,ar7100-gpio" },
+ { .compatible = "qca,ar9340-gpio" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ath79_gpio_of_match);
+
+static int ath79_gpio_probe(struct platform_device *pdev)
+{
+ struct ath79_gpio_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct ath79_gpio_ctrl *ctrl;
+ struct gpio_irq_chip *girq;
+ u32 ath79_gpio_count;
+ bool oe_inverted;
+ int err;
+
+ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ if (np) {
+ err = of_property_read_u32(np, "ngpios", &ath79_gpio_count);
+ if (err) {
+ dev_err(dev, "ngpios property is not valid\n");
+ return err;
+ }
+ oe_inverted = of_device_is_compatible(np, "qca,ar9340-gpio");
+ } else if (pdata) {
+ ath79_gpio_count = pdata->ngpios;
+ oe_inverted = pdata->oe_inverted;
+ } else {
+ dev_err(dev, "No DT node or platform data found\n");
+ return -EINVAL;
+ }
+
+ if (ath79_gpio_count >= 32) {
+ dev_err(dev, "ngpios must be less than 32\n");
+ return -EINVAL;
+ }
+
+ ctrl->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctrl->base))
+ return PTR_ERR(ctrl->base);
+
+ raw_spin_lock_init(&ctrl->lock);
+ err = bgpio_init(&ctrl->gc, dev, 4,
+ ctrl->base + AR71XX_GPIO_REG_IN,
+ ctrl->base + AR71XX_GPIO_REG_SET,
+ ctrl->base + AR71XX_GPIO_REG_CLEAR,
+ oe_inverted ? NULL : ctrl->base + AR71XX_GPIO_REG_OE,
+ oe_inverted ? ctrl->base + AR71XX_GPIO_REG_OE : NULL,
+ 0);
+ if (err) {
+ dev_err(dev, "bgpio_init failed\n");
+ return err;
+ }
+ /* Use base 0 to stay compatible with legacy platforms */
+ ctrl->gc.base = 0;
+
+ /* Optional interrupt setup */
+ if (!np || of_property_read_bool(np, "interrupt-controller")) {
+ girq = &ctrl->gc.irq;
+ gpio_irq_chip_set_chip(girq, &ath79_gpio_irqchip);
+ girq->parent_handler = ath79_gpio_irq_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(dev, 1, sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ girq->parents[0] = platform_get_irq(pdev, 0);
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ }
+
+ return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl);
+}
+
+static struct platform_driver ath79_gpio_driver = {
+ .driver = {
+ .name = "ath79-gpio",
+ .of_match_table = ath79_gpio_of_match,
+ },
+ .probe = ath79_gpio_probe,
+};
+
+module_platform_driver(ath79_gpio_driver);
+
+MODULE_DESCRIPTION("Atheros AR71XX/AR724X/AR913X GPIO API support");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-bcm-kona.c b/drivers/gpio/gpio-bcm-kona.c
new file mode 100644
index 0000000000..5321ef98f4
--- /dev/null
+++ b/drivers/gpio/gpio-bcm-kona.c
@@ -0,0 +1,656 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Broadcom Kona GPIO Driver
+ *
+ * Author: Broadcom Corporation <bcm-kernel-feedback-list@broadcom.com>
+ * Copyright (C) 2012-2014 Broadcom Corporation
+ */
+
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+#define BCM_GPIO_PASSWD 0x00a5a501
+#define GPIO_PER_BANK 32
+#define GPIO_MAX_BANK_NUM 8
+
+#define GPIO_BANK(gpio) ((gpio) >> 5)
+#define GPIO_BIT(gpio) ((gpio) & (GPIO_PER_BANK - 1))
+
+/* There is a GPIO control register for each GPIO */
+#define GPIO_CONTROL(gpio) (0x00000100 + ((gpio) << 2))
+
+/* The remaining registers are per GPIO bank */
+#define GPIO_OUT_STATUS(bank) (0x00000000 + ((bank) << 2))
+#define GPIO_IN_STATUS(bank) (0x00000020 + ((bank) << 2))
+#define GPIO_OUT_SET(bank) (0x00000040 + ((bank) << 2))
+#define GPIO_OUT_CLEAR(bank) (0x00000060 + ((bank) << 2))
+#define GPIO_INT_STATUS(bank) (0x00000080 + ((bank) << 2))
+#define GPIO_INT_MASK(bank) (0x000000a0 + ((bank) << 2))
+#define GPIO_INT_MSKCLR(bank) (0x000000c0 + ((bank) << 2))
+#define GPIO_PWD_STATUS(bank) (0x00000500 + ((bank) << 2))
+
+#define GPIO_GPPWR_OFFSET 0x00000520
+
+#define GPIO_GPCTR0_DBR_SHIFT 5
+#define GPIO_GPCTR0_DBR_MASK 0x000001e0
+
+#define GPIO_GPCTR0_ITR_SHIFT 3
+#define GPIO_GPCTR0_ITR_MASK 0x00000018
+#define GPIO_GPCTR0_ITR_CMD_RISING_EDGE 0x00000001
+#define GPIO_GPCTR0_ITR_CMD_FALLING_EDGE 0x00000002
+#define GPIO_GPCTR0_ITR_CMD_BOTH_EDGE 0x00000003
+
+#define GPIO_GPCTR0_IOTR_MASK 0x00000001
+#define GPIO_GPCTR0_IOTR_CMD_0UTPUT 0x00000000
+#define GPIO_GPCTR0_IOTR_CMD_INPUT 0x00000001
+
+#define GPIO_GPCTR0_DB_ENABLE_MASK 0x00000100
+
+#define LOCK_CODE 0xffffffff
+#define UNLOCK_CODE 0x00000000
+
+struct bcm_kona_gpio {
+ void __iomem *reg_base;
+ int num_bank;
+ raw_spinlock_t lock;
+ struct gpio_chip gpio_chip;
+ struct irq_domain *irq_domain;
+ struct bcm_kona_gpio_bank *banks;
+};
+
+struct bcm_kona_gpio_bank {
+ int id;
+ int irq;
+ /* Used in the interrupt handler */
+ struct bcm_kona_gpio *kona_gpio;
+};
+
+static inline void bcm_kona_gpio_write_lock_regs(void __iomem *reg_base,
+ int bank_id, u32 lockcode)
+{
+ writel(BCM_GPIO_PASSWD, reg_base + GPIO_GPPWR_OFFSET);
+ writel(lockcode, reg_base + GPIO_PWD_STATUS(bank_id));
+}
+
+static void bcm_kona_gpio_lock_gpio(struct bcm_kona_gpio *kona_gpio,
+ unsigned gpio)
+{
+ u32 val;
+ unsigned long flags;
+ int bank_id = GPIO_BANK(gpio);
+
+ raw_spin_lock_irqsave(&kona_gpio->lock, flags);
+
+ val = readl(kona_gpio->reg_base + GPIO_PWD_STATUS(bank_id));
+ val |= BIT(gpio);
+ bcm_kona_gpio_write_lock_regs(kona_gpio->reg_base, bank_id, val);
+
+ raw_spin_unlock_irqrestore(&kona_gpio->lock, flags);
+}
+
+static void bcm_kona_gpio_unlock_gpio(struct bcm_kona_gpio *kona_gpio,
+ unsigned gpio)
+{
+ u32 val;
+ unsigned long flags;
+ int bank_id = GPIO_BANK(gpio);
+
+ raw_spin_lock_irqsave(&kona_gpio->lock, flags);
+
+ val = readl(kona_gpio->reg_base + GPIO_PWD_STATUS(bank_id));
+ val &= ~BIT(gpio);
+ bcm_kona_gpio_write_lock_regs(kona_gpio->reg_base, bank_id, val);
+
+ raw_spin_unlock_irqrestore(&kona_gpio->lock, flags);
+}
+
+static int bcm_kona_gpio_get_dir(struct gpio_chip *chip, unsigned gpio)
+{
+ struct bcm_kona_gpio *kona_gpio = gpiochip_get_data(chip);
+ void __iomem *reg_base = kona_gpio->reg_base;
+ u32 val;
+
+ val = readl(reg_base + GPIO_CONTROL(gpio)) & GPIO_GPCTR0_IOTR_MASK;
+ return val ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
+}
+
+static void bcm_kona_gpio_set(struct gpio_chip *chip, unsigned gpio, int value)
+{
+ struct bcm_kona_gpio *kona_gpio;
+ void __iomem *reg_base;
+ int bank_id = GPIO_BANK(gpio);
+ int bit = GPIO_BIT(gpio);
+ u32 val, reg_offset;
+ unsigned long flags;
+
+ kona_gpio = gpiochip_get_data(chip);
+ reg_base = kona_gpio->reg_base;
+ raw_spin_lock_irqsave(&kona_gpio->lock, flags);
+
+ /* this function only applies to output pin */
+ if (bcm_kona_gpio_get_dir(chip, gpio) == GPIO_LINE_DIRECTION_IN)
+ goto out;
+
+ reg_offset = value ? GPIO_OUT_SET(bank_id) : GPIO_OUT_CLEAR(bank_id);
+
+ val = readl(reg_base + reg_offset);
+ val |= BIT(bit);
+ writel(val, reg_base + reg_offset);
+
+out:
+ raw_spin_unlock_irqrestore(&kona_gpio->lock, flags);
+}
+
+static int bcm_kona_gpio_get(struct gpio_chip *chip, unsigned gpio)
+{
+ struct bcm_kona_gpio *kona_gpio;
+ void __iomem *reg_base;
+ int bank_id = GPIO_BANK(gpio);
+ int bit = GPIO_BIT(gpio);
+ u32 val, reg_offset;
+ unsigned long flags;
+
+ kona_gpio = gpiochip_get_data(chip);
+ reg_base = kona_gpio->reg_base;
+ raw_spin_lock_irqsave(&kona_gpio->lock, flags);
+
+ if (bcm_kona_gpio_get_dir(chip, gpio) == GPIO_LINE_DIRECTION_IN)
+ reg_offset = GPIO_IN_STATUS(bank_id);
+ else
+ reg_offset = GPIO_OUT_STATUS(bank_id);
+
+ /* read the GPIO bank status */
+ val = readl(reg_base + reg_offset);
+
+ raw_spin_unlock_irqrestore(&kona_gpio->lock, flags);
+
+ /* return the specified bit status */
+ return !!(val & BIT(bit));
+}
+
+static int bcm_kona_gpio_request(struct gpio_chip *chip, unsigned gpio)
+{
+ struct bcm_kona_gpio *kona_gpio = gpiochip_get_data(chip);
+
+ bcm_kona_gpio_unlock_gpio(kona_gpio, gpio);
+ return 0;
+}
+
+static void bcm_kona_gpio_free(struct gpio_chip *chip, unsigned gpio)
+{
+ struct bcm_kona_gpio *kona_gpio = gpiochip_get_data(chip);
+
+ bcm_kona_gpio_lock_gpio(kona_gpio, gpio);
+}
+
+static int bcm_kona_gpio_direction_input(struct gpio_chip *chip, unsigned gpio)
+{
+ struct bcm_kona_gpio *kona_gpio;
+ void __iomem *reg_base;
+ u32 val;
+ unsigned long flags;
+
+ kona_gpio = gpiochip_get_data(chip);
+ reg_base = kona_gpio->reg_base;
+ raw_spin_lock_irqsave(&kona_gpio->lock, flags);
+
+ val = readl(reg_base + GPIO_CONTROL(gpio));
+ val &= ~GPIO_GPCTR0_IOTR_MASK;
+ val |= GPIO_GPCTR0_IOTR_CMD_INPUT;
+ writel(val, reg_base + GPIO_CONTROL(gpio));
+
+ raw_spin_unlock_irqrestore(&kona_gpio->lock, flags);
+
+ return 0;
+}
+
+static int bcm_kona_gpio_direction_output(struct gpio_chip *chip,
+ unsigned gpio, int value)
+{
+ struct bcm_kona_gpio *kona_gpio;
+ void __iomem *reg_base;
+ int bank_id = GPIO_BANK(gpio);
+ int bit = GPIO_BIT(gpio);
+ u32 val, reg_offset;
+ unsigned long flags;
+
+ kona_gpio = gpiochip_get_data(chip);
+ reg_base = kona_gpio->reg_base;
+ raw_spin_lock_irqsave(&kona_gpio->lock, flags);
+
+ val = readl(reg_base + GPIO_CONTROL(gpio));
+ val &= ~GPIO_GPCTR0_IOTR_MASK;
+ val |= GPIO_GPCTR0_IOTR_CMD_0UTPUT;
+ writel(val, reg_base + GPIO_CONTROL(gpio));
+ reg_offset = value ? GPIO_OUT_SET(bank_id) : GPIO_OUT_CLEAR(bank_id);
+
+ val = readl(reg_base + reg_offset);
+ val |= BIT(bit);
+ writel(val, reg_base + reg_offset);
+
+ raw_spin_unlock_irqrestore(&kona_gpio->lock, flags);
+
+ return 0;
+}
+
+static int bcm_kona_gpio_to_irq(struct gpio_chip *chip, unsigned gpio)
+{
+ struct bcm_kona_gpio *kona_gpio;
+
+ kona_gpio = gpiochip_get_data(chip);
+ if (gpio >= kona_gpio->gpio_chip.ngpio)
+ return -ENXIO;
+ return irq_create_mapping(kona_gpio->irq_domain, gpio);
+}
+
+static int bcm_kona_gpio_set_debounce(struct gpio_chip *chip, unsigned gpio,
+ unsigned debounce)
+{
+ struct bcm_kona_gpio *kona_gpio;
+ void __iomem *reg_base;
+ u32 val, res;
+ unsigned long flags;
+
+ kona_gpio = gpiochip_get_data(chip);
+ reg_base = kona_gpio->reg_base;
+ /* debounce must be 1-128ms (or 0) */
+ if ((debounce > 0 && debounce < 1000) || debounce > 128000) {
+ dev_err(chip->parent, "Debounce value %u not in range\n",
+ debounce);
+ return -EINVAL;
+ }
+
+ /* calculate debounce bit value */
+ if (debounce != 0) {
+ /* Convert to ms */
+ debounce /= 1000;
+ /* find the MSB */
+ res = fls(debounce) - 1;
+ /* Check if MSB-1 is set (round up or down) */
+ if (res > 0 && (debounce & BIT(res - 1)))
+ res++;
+ }
+
+ /* spin lock for read-modify-write of the GPIO register */
+ raw_spin_lock_irqsave(&kona_gpio->lock, flags);
+
+ val = readl(reg_base + GPIO_CONTROL(gpio));
+ val &= ~GPIO_GPCTR0_DBR_MASK;
+
+ if (debounce == 0) {
+ /* disable debounce */
+ val &= ~GPIO_GPCTR0_DB_ENABLE_MASK;
+ } else {
+ val |= GPIO_GPCTR0_DB_ENABLE_MASK |
+ (res << GPIO_GPCTR0_DBR_SHIFT);
+ }
+
+ writel(val, reg_base + GPIO_CONTROL(gpio));
+
+ raw_spin_unlock_irqrestore(&kona_gpio->lock, flags);
+
+ return 0;
+}
+
+static int bcm_kona_gpio_set_config(struct gpio_chip *chip, unsigned gpio,
+ unsigned long config)
+{
+ u32 debounce;
+
+ if (pinconf_to_config_param(config) != PIN_CONFIG_INPUT_DEBOUNCE)
+ return -ENOTSUPP;
+
+ debounce = pinconf_to_config_argument(config);
+ return bcm_kona_gpio_set_debounce(chip, gpio, debounce);
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "bcm-kona-gpio",
+ .owner = THIS_MODULE,
+ .request = bcm_kona_gpio_request,
+ .free = bcm_kona_gpio_free,
+ .get_direction = bcm_kona_gpio_get_dir,
+ .direction_input = bcm_kona_gpio_direction_input,
+ .get = bcm_kona_gpio_get,
+ .direction_output = bcm_kona_gpio_direction_output,
+ .set = bcm_kona_gpio_set,
+ .set_config = bcm_kona_gpio_set_config,
+ .to_irq = bcm_kona_gpio_to_irq,
+ .base = 0,
+};
+
+static void bcm_kona_gpio_irq_ack(struct irq_data *d)
+{
+ struct bcm_kona_gpio *kona_gpio;
+ void __iomem *reg_base;
+ unsigned gpio = d->hwirq;
+ int bank_id = GPIO_BANK(gpio);
+ int bit = GPIO_BIT(gpio);
+ u32 val;
+ unsigned long flags;
+
+ kona_gpio = irq_data_get_irq_chip_data(d);
+ reg_base = kona_gpio->reg_base;
+ raw_spin_lock_irqsave(&kona_gpio->lock, flags);
+
+ val = readl(reg_base + GPIO_INT_STATUS(bank_id));
+ val |= BIT(bit);
+ writel(val, reg_base + GPIO_INT_STATUS(bank_id));
+
+ raw_spin_unlock_irqrestore(&kona_gpio->lock, flags);
+}
+
+static void bcm_kona_gpio_irq_mask(struct irq_data *d)
+{
+ struct bcm_kona_gpio *kona_gpio;
+ void __iomem *reg_base;
+ unsigned gpio = d->hwirq;
+ int bank_id = GPIO_BANK(gpio);
+ int bit = GPIO_BIT(gpio);
+ u32 val;
+ unsigned long flags;
+
+ kona_gpio = irq_data_get_irq_chip_data(d);
+ reg_base = kona_gpio->reg_base;
+ raw_spin_lock_irqsave(&kona_gpio->lock, flags);
+
+ val = readl(reg_base + GPIO_INT_MASK(bank_id));
+ val |= BIT(bit);
+ writel(val, reg_base + GPIO_INT_MASK(bank_id));
+ gpiochip_disable_irq(&kona_gpio->gpio_chip, gpio);
+
+ raw_spin_unlock_irqrestore(&kona_gpio->lock, flags);
+}
+
+static void bcm_kona_gpio_irq_unmask(struct irq_data *d)
+{
+ struct bcm_kona_gpio *kona_gpio;
+ void __iomem *reg_base;
+ unsigned gpio = d->hwirq;
+ int bank_id = GPIO_BANK(gpio);
+ int bit = GPIO_BIT(gpio);
+ u32 val;
+ unsigned long flags;
+
+ kona_gpio = irq_data_get_irq_chip_data(d);
+ reg_base = kona_gpio->reg_base;
+ raw_spin_lock_irqsave(&kona_gpio->lock, flags);
+
+ val = readl(reg_base + GPIO_INT_MSKCLR(bank_id));
+ val |= BIT(bit);
+ writel(val, reg_base + GPIO_INT_MSKCLR(bank_id));
+ gpiochip_enable_irq(&kona_gpio->gpio_chip, gpio);
+
+ raw_spin_unlock_irqrestore(&kona_gpio->lock, flags);
+}
+
+static int bcm_kona_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct bcm_kona_gpio *kona_gpio;
+ void __iomem *reg_base;
+ unsigned gpio = d->hwirq;
+ u32 lvl_type;
+ u32 val;
+ unsigned long flags;
+
+ kona_gpio = irq_data_get_irq_chip_data(d);
+ reg_base = kona_gpio->reg_base;
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_RISING:
+ lvl_type = GPIO_GPCTR0_ITR_CMD_RISING_EDGE;
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ lvl_type = GPIO_GPCTR0_ITR_CMD_FALLING_EDGE;
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ lvl_type = GPIO_GPCTR0_ITR_CMD_BOTH_EDGE;
+ break;
+
+ case IRQ_TYPE_LEVEL_HIGH:
+ case IRQ_TYPE_LEVEL_LOW:
+ /* BCM GPIO doesn't support level triggering */
+ default:
+ dev_err(kona_gpio->gpio_chip.parent,
+ "Invalid BCM GPIO irq type 0x%x\n", type);
+ return -EINVAL;
+ }
+
+ raw_spin_lock_irqsave(&kona_gpio->lock, flags);
+
+ val = readl(reg_base + GPIO_CONTROL(gpio));
+ val &= ~GPIO_GPCTR0_ITR_MASK;
+ val |= lvl_type << GPIO_GPCTR0_ITR_SHIFT;
+ writel(val, reg_base + GPIO_CONTROL(gpio));
+
+ raw_spin_unlock_irqrestore(&kona_gpio->lock, flags);
+
+ return 0;
+}
+
+static void bcm_kona_gpio_irq_handler(struct irq_desc *desc)
+{
+ void __iomem *reg_base;
+ int bit, bank_id;
+ unsigned long sta;
+ struct bcm_kona_gpio_bank *bank = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+
+ chained_irq_enter(chip, desc);
+
+ /*
+ * For bank interrupts, we can't use chip_data to store the kona_gpio
+ * pointer, since GIC needs it for its own purposes. Therefore, we get
+ * our pointer from the bank structure.
+ */
+ reg_base = bank->kona_gpio->reg_base;
+ bank_id = bank->id;
+
+ while ((sta = readl(reg_base + GPIO_INT_STATUS(bank_id)) &
+ (~(readl(reg_base + GPIO_INT_MASK(bank_id)))))) {
+ for_each_set_bit(bit, &sta, 32) {
+ int hwirq = GPIO_PER_BANK * bank_id + bit;
+ /*
+ * Clear interrupt before handler is called so we don't
+ * miss any interrupt occurred during executing them.
+ */
+ writel(readl(reg_base + GPIO_INT_STATUS(bank_id)) |
+ BIT(bit), reg_base + GPIO_INT_STATUS(bank_id));
+ /* Invoke interrupt handler */
+ generic_handle_domain_irq(bank->kona_gpio->irq_domain,
+ hwirq);
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static int bcm_kona_gpio_irq_reqres(struct irq_data *d)
+{
+ struct bcm_kona_gpio *kona_gpio = irq_data_get_irq_chip_data(d);
+
+ return gpiochip_reqres_irq(&kona_gpio->gpio_chip, d->hwirq);
+}
+
+static void bcm_kona_gpio_irq_relres(struct irq_data *d)
+{
+ struct bcm_kona_gpio *kona_gpio = irq_data_get_irq_chip_data(d);
+
+ gpiochip_relres_irq(&kona_gpio->gpio_chip, d->hwirq);
+}
+
+static struct irq_chip bcm_gpio_irq_chip = {
+ .name = "bcm-kona-gpio",
+ .irq_ack = bcm_kona_gpio_irq_ack,
+ .irq_mask = bcm_kona_gpio_irq_mask,
+ .irq_unmask = bcm_kona_gpio_irq_unmask,
+ .irq_set_type = bcm_kona_gpio_irq_set_type,
+ .irq_request_resources = bcm_kona_gpio_irq_reqres,
+ .irq_release_resources = bcm_kona_gpio_irq_relres,
+};
+
+static struct of_device_id const bcm_kona_gpio_of_match[] = {
+ { .compatible = "brcm,kona-gpio" },
+ {}
+};
+
+/*
+ * This lock class tells lockdep that GPIO irqs are in a different
+ * category than their parents, so it won't report false recursion.
+ */
+static struct lock_class_key gpio_lock_class;
+static struct lock_class_key gpio_request_class;
+
+static int bcm_kona_gpio_irq_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ int ret;
+
+ ret = irq_set_chip_data(irq, d->host_data);
+ if (ret < 0)
+ return ret;
+ irq_set_lockdep_class(irq, &gpio_lock_class, &gpio_request_class);
+ irq_set_chip_and_handler(irq, &bcm_gpio_irq_chip, handle_simple_irq);
+ irq_set_noprobe(irq);
+
+ return 0;
+}
+
+static void bcm_kona_gpio_irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops bcm_kona_irq_ops = {
+ .map = bcm_kona_gpio_irq_map,
+ .unmap = bcm_kona_gpio_irq_unmap,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+static void bcm_kona_gpio_reset(struct bcm_kona_gpio *kona_gpio)
+{
+ void __iomem *reg_base;
+ int i;
+
+ reg_base = kona_gpio->reg_base;
+ /* disable interrupts and clear status */
+ for (i = 0; i < kona_gpio->num_bank; i++) {
+ /* Unlock the entire bank first */
+ bcm_kona_gpio_write_lock_regs(reg_base, i, UNLOCK_CODE);
+ writel(0xffffffff, reg_base + GPIO_INT_MASK(i));
+ writel(0xffffffff, reg_base + GPIO_INT_STATUS(i));
+ /* Now re-lock the bank */
+ bcm_kona_gpio_write_lock_regs(reg_base, i, LOCK_CODE);
+ }
+}
+
+static int bcm_kona_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct bcm_kona_gpio_bank *bank;
+ struct bcm_kona_gpio *kona_gpio;
+ struct gpio_chip *chip;
+ int ret;
+ int i;
+
+ kona_gpio = devm_kzalloc(dev, sizeof(*kona_gpio), GFP_KERNEL);
+ if (!kona_gpio)
+ return -ENOMEM;
+
+ kona_gpio->gpio_chip = template_chip;
+ chip = &kona_gpio->gpio_chip;
+ ret = platform_irq_count(pdev);
+ if (!ret) {
+ dev_err(dev, "Couldn't determine # GPIO banks\n");
+ return -ENOENT;
+ } else if (ret < 0) {
+ return dev_err_probe(dev, ret, "Couldn't determine GPIO banks\n");
+ }
+ kona_gpio->num_bank = ret;
+
+ if (kona_gpio->num_bank > GPIO_MAX_BANK_NUM) {
+ dev_err(dev, "Too many GPIO banks configured (max=%d)\n",
+ GPIO_MAX_BANK_NUM);
+ return -ENXIO;
+ }
+ kona_gpio->banks = devm_kcalloc(dev,
+ kona_gpio->num_bank,
+ sizeof(*kona_gpio->banks),
+ GFP_KERNEL);
+ if (!kona_gpio->banks)
+ return -ENOMEM;
+
+ chip->parent = dev;
+ chip->ngpio = kona_gpio->num_bank * GPIO_PER_BANK;
+
+ kona_gpio->irq_domain = irq_domain_create_linear(dev_fwnode(dev),
+ chip->ngpio,
+ &bcm_kona_irq_ops,
+ kona_gpio);
+ if (!kona_gpio->irq_domain) {
+ dev_err(dev, "Couldn't allocate IRQ domain\n");
+ return -ENXIO;
+ }
+
+ kona_gpio->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(kona_gpio->reg_base)) {
+ ret = PTR_ERR(kona_gpio->reg_base);
+ goto err_irq_domain;
+ }
+
+ for (i = 0; i < kona_gpio->num_bank; i++) {
+ bank = &kona_gpio->banks[i];
+ bank->id = i;
+ bank->irq = platform_get_irq(pdev, i);
+ bank->kona_gpio = kona_gpio;
+ if (bank->irq < 0) {
+ dev_err(dev, "Couldn't get IRQ for bank %d", i);
+ ret = -ENOENT;
+ goto err_irq_domain;
+ }
+ }
+
+ dev_info(&pdev->dev, "Setting up Kona GPIO\n");
+
+ bcm_kona_gpio_reset(kona_gpio);
+
+ ret = devm_gpiochip_add_data(dev, chip, kona_gpio);
+ if (ret < 0) {
+ dev_err(dev, "Couldn't add GPIO chip -- %d\n", ret);
+ goto err_irq_domain;
+ }
+ for (i = 0; i < kona_gpio->num_bank; i++) {
+ bank = &kona_gpio->banks[i];
+ irq_set_chained_handler_and_data(bank->irq,
+ bcm_kona_gpio_irq_handler,
+ bank);
+ }
+
+ raw_spin_lock_init(&kona_gpio->lock);
+
+ return 0;
+
+err_irq_domain:
+ irq_domain_remove(kona_gpio->irq_domain);
+
+ return ret;
+}
+
+static struct platform_driver bcm_kona_gpio_driver = {
+ .driver = {
+ .name = "bcm-kona-gpio",
+ .of_match_table = bcm_kona_gpio_of_match,
+ },
+ .probe = bcm_kona_gpio_probe,
+};
+builtin_platform_driver(bcm_kona_gpio_driver);
diff --git a/drivers/gpio/gpio-bd71815.c b/drivers/gpio/gpio-bd71815.c
new file mode 100644
index 0000000000..08ff285725
--- /dev/null
+++ b/drivers/gpio/gpio-bd71815.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support to GPOs on ROHM BD71815
+ * Copyright 2021 ROHM Semiconductors.
+ * Author: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
+ *
+ * Copyright 2014 Embest Technology Co. Ltd. Inc.
+ * Author: yanglsh@embest-tech.com
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+/* For the BD71815 register definitions */
+#include <linux/mfd/rohm-bd71815.h>
+
+struct bd71815_gpio {
+ /* chip.parent points the MFD which provides DT node and regmap */
+ struct gpio_chip chip;
+ /* dev points to the platform device for devm and prints */
+ struct device *dev;
+ struct regmap *regmap;
+};
+
+static int bd71815gpo_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct bd71815_gpio *bd71815 = gpiochip_get_data(chip);
+ int ret, val;
+
+ ret = regmap_read(bd71815->regmap, BD71815_REG_GPO, &val);
+ if (ret)
+ return ret;
+
+ return (val >> offset) & 1;
+}
+
+static void bd71815gpo_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct bd71815_gpio *bd71815 = gpiochip_get_data(chip);
+ int ret, bit;
+
+ bit = BIT(offset);
+
+ if (value)
+ ret = regmap_set_bits(bd71815->regmap, BD71815_REG_GPO, bit);
+ else
+ ret = regmap_clear_bits(bd71815->regmap, BD71815_REG_GPO, bit);
+
+ if (ret)
+ dev_warn(bd71815->dev, "failed to toggle GPO\n");
+}
+
+static int bd71815_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ struct bd71815_gpio *bdgpio = gpiochip_get_data(chip);
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ return regmap_update_bits(bdgpio->regmap,
+ BD71815_REG_GPO,
+ BD71815_GPIO_DRIVE_MASK << offset,
+ BD71815_GPIO_OPEN_DRAIN << offset);
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ return regmap_update_bits(bdgpio->regmap,
+ BD71815_REG_GPO,
+ BD71815_GPIO_DRIVE_MASK << offset,
+ BD71815_GPIO_CMOS << offset);
+ default:
+ break;
+ }
+ return -ENOTSUPP;
+}
+
+/* BD71815 GPIO is actually GPO */
+static int bd71815gpo_direction_get(struct gpio_chip *gc, unsigned int offset)
+{
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+/* Template for GPIO chip */
+static const struct gpio_chip bd71815gpo_chip = {
+ .label = "bd71815",
+ .owner = THIS_MODULE,
+ .get = bd71815gpo_get,
+ .get_direction = bd71815gpo_direction_get,
+ .set = bd71815gpo_set,
+ .set_config = bd71815_gpio_set_config,
+ .can_sleep = true,
+};
+
+#define BD71815_TWO_GPIOS GENMASK(1, 0)
+#define BD71815_ONE_GPIO BIT(0)
+
+/*
+ * Sigh. The BD71815 and BD71817 were originally designed to support two GPO
+ * pins. At some point it was noticed the second GPO pin which is the E5 pin
+ * located at the center of IC is hard to use on PCB (due to the location). It
+ * was decided to not promote this second GPO and the pin is marked as GND in
+ * the datasheet. The functionality is still there though! I guess driving a GPO
+ * connected to the ground is a bad idea. Thus we do not support it by default.
+ * OTOH - the original driver written by colleagues at Embest did support
+ * controlling this second GPO. It is thus possible this is used in some of the
+ * products.
+ *
+ * This driver does not by default support configuring this second GPO
+ * but allows using it by providing the DT property
+ * "rohm,enable-hidden-gpo".
+ */
+static int bd71815_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ if (ngpios != 2)
+ return 0;
+
+ if (gc->parent && device_property_present(gc->parent,
+ "rohm,enable-hidden-gpo"))
+ *valid_mask = BD71815_TWO_GPIOS;
+ else
+ *valid_mask = BD71815_ONE_GPIO;
+
+ return 0;
+}
+
+static int gpo_bd71815_probe(struct platform_device *pdev)
+{
+ struct bd71815_gpio *g;
+ struct device *parent, *dev;
+
+ /*
+ * Bind devm lifetime to this platform device => use dev for devm.
+ * also the prints should originate from this device.
+ */
+ dev = &pdev->dev;
+ /* The device-tree and regmap come from MFD => use parent for that */
+ parent = dev->parent;
+
+ g = devm_kzalloc(dev, sizeof(*g), GFP_KERNEL);
+ if (!g)
+ return -ENOMEM;
+
+ g->chip = bd71815gpo_chip;
+
+ /*
+ * FIXME: As writing of this the sysfs interface for GPIO control does
+ * not respect the valid_mask. Do not trust it but rather set the ngpios
+ * to 1 if "rohm,enable-hidden-gpo" is not given.
+ *
+ * This check can be removed later if the sysfs export is fixed and
+ * if the fix is backported.
+ *
+ * For now it is safest to just set the ngpios though.
+ */
+ if (device_property_present(parent, "rohm,enable-hidden-gpo"))
+ g->chip.ngpio = 2;
+ else
+ g->chip.ngpio = 1;
+
+ g->chip.init_valid_mask = bd71815_init_valid_mask;
+ g->chip.base = -1;
+ g->chip.parent = parent;
+ g->regmap = dev_get_regmap(parent, NULL);
+ g->dev = dev;
+
+ return devm_gpiochip_add_data(dev, &g->chip, g);
+}
+
+static struct platform_driver gpo_bd71815_driver = {
+ .driver = {
+ .name = "bd71815-gpo",
+ },
+ .probe = gpo_bd71815_probe,
+};
+module_platform_driver(gpo_bd71815_driver);
+
+MODULE_ALIAS("platform:bd71815-gpo");
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
+MODULE_AUTHOR("Peter Yang <yanglsh@embest-tech.com>");
+MODULE_DESCRIPTION("GPO interface for BD71815");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-bd71828.c b/drivers/gpio/gpio-bd71828.c
new file mode 100644
index 0000000000..b2ccc320c7
--- /dev/null
+++ b/drivers/gpio/gpio-bd71828.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2018 ROHM Semiconductors
+
+#include <linux/gpio/driver.h>
+#include <linux/mfd/rohm-bd71828.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define GPIO_OUT_REG(off) (BD71828_REG_GPIO_CTRL1 + (off))
+#define HALL_GPIO_OFFSET 3
+
+struct bd71828_gpio {
+ struct regmap *regmap;
+ struct device *dev;
+ struct gpio_chip gpio;
+};
+
+static void bd71828_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ int ret;
+ struct bd71828_gpio *bdgpio = gpiochip_get_data(chip);
+ u8 val = (value) ? BD71828_GPIO_OUT_HI : BD71828_GPIO_OUT_LO;
+
+ /*
+ * The HALL input pin can only be used as input. If this is the pin
+ * we are dealing with - then we are done
+ */
+ if (offset == HALL_GPIO_OFFSET)
+ return;
+
+ ret = regmap_update_bits(bdgpio->regmap, GPIO_OUT_REG(offset),
+ BD71828_GPIO_OUT_MASK, val);
+ if (ret)
+ dev_err(bdgpio->dev, "Could not set gpio to %d\n", value);
+}
+
+static int bd71828_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ int ret;
+ unsigned int val;
+ struct bd71828_gpio *bdgpio = gpiochip_get_data(chip);
+
+ if (offset == HALL_GPIO_OFFSET)
+ ret = regmap_read(bdgpio->regmap, BD71828_REG_IO_STAT,
+ &val);
+ else
+ ret = regmap_read(bdgpio->regmap, GPIO_OUT_REG(offset),
+ &val);
+ if (!ret)
+ ret = (val & BD71828_GPIO_OUT_MASK);
+
+ return ret;
+}
+
+static int bd71828_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ struct bd71828_gpio *bdgpio = gpiochip_get_data(chip);
+
+ if (offset == HALL_GPIO_OFFSET)
+ return -ENOTSUPP;
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ return regmap_update_bits(bdgpio->regmap,
+ GPIO_OUT_REG(offset),
+ BD71828_GPIO_DRIVE_MASK,
+ BD71828_GPIO_OPEN_DRAIN);
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ return regmap_update_bits(bdgpio->regmap,
+ GPIO_OUT_REG(offset),
+ BD71828_GPIO_DRIVE_MASK,
+ BD71828_GPIO_PUSH_PULL);
+ default:
+ break;
+ }
+ return -ENOTSUPP;
+}
+
+static int bd71828_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ /*
+ * Pin usage is selected by OTP data. We can't read it runtime. Hence
+ * we trust that if the pin is not excluded by "gpio-reserved-ranges"
+ * the OTP configuration is set to OUT. (Other pins but HALL input pin
+ * on BD71828 can't really be used for general purpose input - input
+ * states are used for specific cases like regulator control or
+ * PMIC_ON_REQ.
+ */
+ if (offset == HALL_GPIO_OFFSET)
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int bd71828_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct bd71828_gpio *bdgpio;
+
+ bdgpio = devm_kzalloc(dev, sizeof(*bdgpio), GFP_KERNEL);
+ if (!bdgpio)
+ return -ENOMEM;
+
+ bdgpio->dev = dev;
+ bdgpio->gpio.parent = dev->parent;
+ bdgpio->gpio.label = "bd71828-gpio";
+ bdgpio->gpio.owner = THIS_MODULE;
+ bdgpio->gpio.get_direction = bd71828_get_direction;
+ bdgpio->gpio.set_config = bd71828_gpio_set_config;
+ bdgpio->gpio.can_sleep = true;
+ bdgpio->gpio.get = bd71828_gpio_get;
+ bdgpio->gpio.set = bd71828_gpio_set;
+ bdgpio->gpio.base = -1;
+
+ /*
+ * See if we need some implementation to mark some PINs as
+ * not controllable based on DT info or if core can handle
+ * "gpio-reserved-ranges" and exclude them from control
+ */
+ bdgpio->gpio.ngpio = 4;
+ bdgpio->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!bdgpio->regmap)
+ return -ENODEV;
+
+ return devm_gpiochip_add_data(dev, &bdgpio->gpio, bdgpio);
+}
+
+static struct platform_driver bd71828_gpio = {
+ .driver = {
+ .name = "bd71828-gpio"
+ },
+ .probe = bd71828_probe,
+};
+
+module_platform_driver(bd71828_gpio);
+
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
+MODULE_DESCRIPTION("BD71828 voltage regulator driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bd71828-gpio");
diff --git a/drivers/gpio/gpio-bd9571mwv.c b/drivers/gpio/gpio-bd9571mwv.c
new file mode 100644
index 0000000000..9a4d55f703
--- /dev/null
+++ b/drivers/gpio/gpio-bd9571mwv.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ROHM BD9571MWV-M and BD9574MWF-M GPIO driver
+ *
+ * Copyright (C) 2017 Marek Vasut <marek.vasut+renesas@gmail.com>
+ *
+ * Based on the TPS65086 driver
+ *
+ * NOTE: Interrupts are not supported yet.
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/mfd/rohm-generic.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <linux/mfd/bd9571mwv.h>
+
+struct bd9571mwv_gpio {
+ struct regmap *regmap;
+ struct gpio_chip chip;
+};
+
+static int bd9571mwv_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct bd9571mwv_gpio *gpio = gpiochip_get_data(chip);
+ int ret, val;
+
+ ret = regmap_read(gpio->regmap, BD9571MWV_GPIO_DIR, &val);
+ if (ret < 0)
+ return ret;
+ if (val & BIT(offset))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int bd9571mwv_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct bd9571mwv_gpio *gpio = gpiochip_get_data(chip);
+
+ regmap_update_bits(gpio->regmap, BD9571MWV_GPIO_DIR, BIT(offset), 0);
+
+ return 0;
+}
+
+static int bd9571mwv_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct bd9571mwv_gpio *gpio = gpiochip_get_data(chip);
+
+ /* Set the initial value */
+ regmap_update_bits(gpio->regmap, BD9571MWV_GPIO_OUT,
+ BIT(offset), value ? BIT(offset) : 0);
+ regmap_update_bits(gpio->regmap, BD9571MWV_GPIO_DIR,
+ BIT(offset), BIT(offset));
+
+ return 0;
+}
+
+static int bd9571mwv_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct bd9571mwv_gpio *gpio = gpiochip_get_data(chip);
+ int ret, val;
+
+ ret = regmap_read(gpio->regmap, BD9571MWV_GPIO_IN, &val);
+ if (ret < 0)
+ return ret;
+
+ return val & BIT(offset);
+}
+
+static void bd9571mwv_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct bd9571mwv_gpio *gpio = gpiochip_get_data(chip);
+
+ regmap_update_bits(gpio->regmap, BD9571MWV_GPIO_OUT,
+ BIT(offset), value ? BIT(offset) : 0);
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "bd9571mwv-gpio",
+ .owner = THIS_MODULE,
+ .get_direction = bd9571mwv_gpio_get_direction,
+ .direction_input = bd9571mwv_gpio_direction_input,
+ .direction_output = bd9571mwv_gpio_direction_output,
+ .get = bd9571mwv_gpio_get,
+ .set = bd9571mwv_gpio_set,
+ .base = -1,
+ .ngpio = 2,
+ .can_sleep = true,
+};
+
+static int bd9571mwv_gpio_probe(struct platform_device *pdev)
+{
+ struct bd9571mwv_gpio *gpio;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ gpio->chip = template_chip;
+ gpio->chip.parent = pdev->dev.parent;
+
+ return devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
+}
+
+static const struct platform_device_id bd9571mwv_gpio_id_table[] = {
+ { "bd9571mwv-gpio", ROHM_CHIP_TYPE_BD9571 },
+ { "bd9574mwf-gpio", ROHM_CHIP_TYPE_BD9574 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, bd9571mwv_gpio_id_table);
+
+static struct platform_driver bd9571mwv_gpio_driver = {
+ .driver = {
+ .name = "bd9571mwv-gpio",
+ },
+ .probe = bd9571mwv_gpio_probe,
+ .id_table = bd9571mwv_gpio_id_table,
+};
+module_platform_driver(bd9571mwv_gpio_driver);
+
+MODULE_AUTHOR("Marek Vasut <marek.vasut+renesas@gmail.com>");
+MODULE_DESCRIPTION("BD9571MWV GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c
new file mode 100644
index 0000000000..bccdbfd5ec
--- /dev/null
+++ b/drivers/gpio/gpio-brcmstb.c
@@ -0,0 +1,767 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2015-2017 Broadcom
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+enum gio_reg_index {
+ GIO_REG_ODEN = 0,
+ GIO_REG_DATA,
+ GIO_REG_IODIR,
+ GIO_REG_EC,
+ GIO_REG_EI,
+ GIO_REG_MASK,
+ GIO_REG_LEVEL,
+ GIO_REG_STAT,
+ NUMBER_OF_GIO_REGISTERS
+};
+
+#define GIO_BANK_SIZE (NUMBER_OF_GIO_REGISTERS * sizeof(u32))
+#define GIO_BANK_OFF(bank, off) (((bank) * GIO_BANK_SIZE) + (off * sizeof(u32)))
+#define GIO_ODEN(bank) GIO_BANK_OFF(bank, GIO_REG_ODEN)
+#define GIO_DATA(bank) GIO_BANK_OFF(bank, GIO_REG_DATA)
+#define GIO_IODIR(bank) GIO_BANK_OFF(bank, GIO_REG_IODIR)
+#define GIO_EC(bank) GIO_BANK_OFF(bank, GIO_REG_EC)
+#define GIO_EI(bank) GIO_BANK_OFF(bank, GIO_REG_EI)
+#define GIO_MASK(bank) GIO_BANK_OFF(bank, GIO_REG_MASK)
+#define GIO_LEVEL(bank) GIO_BANK_OFF(bank, GIO_REG_LEVEL)
+#define GIO_STAT(bank) GIO_BANK_OFF(bank, GIO_REG_STAT)
+
+struct brcmstb_gpio_bank {
+ struct list_head node;
+ int id;
+ struct gpio_chip gc;
+ struct brcmstb_gpio_priv *parent_priv;
+ u32 width;
+ u32 wake_active;
+ u32 saved_regs[GIO_REG_STAT]; /* Don't save and restore GIO_REG_STAT */
+};
+
+struct brcmstb_gpio_priv {
+ struct list_head bank_list;
+ void __iomem *reg_base;
+ struct platform_device *pdev;
+ struct irq_domain *irq_domain;
+ struct irq_chip irq_chip;
+ int parent_irq;
+ int gpio_base;
+ int num_gpios;
+ int parent_wake_irq;
+};
+
+#define MAX_GPIO_PER_BANK 32
+#define GPIO_BANK(gpio) ((gpio) >> 5)
+/* assumes MAX_GPIO_PER_BANK is a multiple of 2 */
+#define GPIO_BIT(gpio) ((gpio) & (MAX_GPIO_PER_BANK - 1))
+
+static inline struct brcmstb_gpio_priv *
+brcmstb_gpio_gc_to_priv(struct gpio_chip *gc)
+{
+ struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc);
+ return bank->parent_priv;
+}
+
+static unsigned long
+__brcmstb_gpio_get_active_irqs(struct brcmstb_gpio_bank *bank)
+{
+ void __iomem *reg_base = bank->parent_priv->reg_base;
+
+ return bank->gc.read_reg(reg_base + GIO_STAT(bank->id)) &
+ bank->gc.read_reg(reg_base + GIO_MASK(bank->id));
+}
+
+static unsigned long
+brcmstb_gpio_get_active_irqs(struct brcmstb_gpio_bank *bank)
+{
+ unsigned long status;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&bank->gc.bgpio_lock, flags);
+ status = __brcmstb_gpio_get_active_irqs(bank);
+ raw_spin_unlock_irqrestore(&bank->gc.bgpio_lock, flags);
+
+ return status;
+}
+
+static int brcmstb_gpio_hwirq_to_offset(irq_hw_number_t hwirq,
+ struct brcmstb_gpio_bank *bank)
+{
+ return hwirq - (bank->gc.base - bank->parent_priv->gpio_base);
+}
+
+static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank,
+ unsigned int hwirq, bool enable)
+{
+ struct gpio_chip *gc = &bank->gc;
+ struct brcmstb_gpio_priv *priv = bank->parent_priv;
+ u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(hwirq, bank));
+ u32 imask;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ imask = gc->read_reg(priv->reg_base + GIO_MASK(bank->id));
+ if (enable)
+ imask |= mask;
+ else
+ imask &= ~mask;
+ gc->write_reg(priv->reg_base + GIO_MASK(bank->id), imask);
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
+static int brcmstb_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
+{
+ struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc);
+ /* gc_offset is relative to this gpio_chip; want real offset */
+ int hwirq = offset + (gc->base - priv->gpio_base);
+
+ if (hwirq >= priv->num_gpios)
+ return -ENXIO;
+ return irq_create_mapping(priv->irq_domain, hwirq);
+}
+
+/* -------------------- IRQ chip functions -------------------- */
+
+static void brcmstb_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc);
+
+ brcmstb_gpio_set_imask(bank, d->hwirq, false);
+}
+
+static void brcmstb_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc);
+
+ brcmstb_gpio_set_imask(bank, d->hwirq, true);
+}
+
+static void brcmstb_gpio_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc);
+ struct brcmstb_gpio_priv *priv = bank->parent_priv;
+ u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank));
+
+ gc->write_reg(priv->reg_base + GIO_STAT(bank->id), mask);
+}
+
+static int brcmstb_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc);
+ struct brcmstb_gpio_priv *priv = bank->parent_priv;
+ u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank));
+ u32 edge_insensitive, iedge_insensitive;
+ u32 edge_config, iedge_config;
+ u32 level, ilevel;
+ unsigned long flags;
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_LOW:
+ level = mask;
+ edge_config = 0;
+ edge_insensitive = 0;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ level = mask;
+ edge_config = mask;
+ edge_insensitive = 0;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ level = 0;
+ edge_config = 0;
+ edge_insensitive = 0;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ level = 0;
+ edge_config = mask;
+ edge_insensitive = 0;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ level = 0;
+ edge_config = 0; /* don't care, but want known value */
+ edge_insensitive = mask;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ raw_spin_lock_irqsave(&bank->gc.bgpio_lock, flags);
+
+ iedge_config = bank->gc.read_reg(priv->reg_base +
+ GIO_EC(bank->id)) & ~mask;
+ iedge_insensitive = bank->gc.read_reg(priv->reg_base +
+ GIO_EI(bank->id)) & ~mask;
+ ilevel = bank->gc.read_reg(priv->reg_base +
+ GIO_LEVEL(bank->id)) & ~mask;
+
+ bank->gc.write_reg(priv->reg_base + GIO_EC(bank->id),
+ iedge_config | edge_config);
+ bank->gc.write_reg(priv->reg_base + GIO_EI(bank->id),
+ iedge_insensitive | edge_insensitive);
+ bank->gc.write_reg(priv->reg_base + GIO_LEVEL(bank->id),
+ ilevel | level);
+
+ raw_spin_unlock_irqrestore(&bank->gc.bgpio_lock, flags);
+ return 0;
+}
+
+static int brcmstb_gpio_priv_set_wake(struct brcmstb_gpio_priv *priv,
+ unsigned int enable)
+{
+ int ret = 0;
+
+ if (enable)
+ ret = enable_irq_wake(priv->parent_wake_irq);
+ else
+ ret = disable_irq_wake(priv->parent_wake_irq);
+ if (ret)
+ dev_err(&priv->pdev->dev, "failed to %s wake-up interrupt\n",
+ enable ? "enable" : "disable");
+ return ret;
+}
+
+static int brcmstb_gpio_irq_set_wake(struct irq_data *d, unsigned int enable)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc);
+ struct brcmstb_gpio_priv *priv = bank->parent_priv;
+ u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank));
+
+ /*
+ * Do not do anything specific for now, suspend/resume callbacks will
+ * configure the interrupt mask appropriately
+ */
+ if (enable)
+ bank->wake_active |= mask;
+ else
+ bank->wake_active &= ~mask;
+
+ return brcmstb_gpio_priv_set_wake(priv, enable);
+}
+
+static irqreturn_t brcmstb_gpio_wake_irq_handler(int irq, void *data)
+{
+ struct brcmstb_gpio_priv *priv = data;
+
+ if (!priv || irq != priv->parent_wake_irq)
+ return IRQ_NONE;
+
+ /* Nothing to do */
+ return IRQ_HANDLED;
+}
+
+static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank)
+{
+ struct brcmstb_gpio_priv *priv = bank->parent_priv;
+ struct irq_domain *domain = priv->irq_domain;
+ int hwbase = bank->gc.base - priv->gpio_base;
+ unsigned long status;
+
+ while ((status = brcmstb_gpio_get_active_irqs(bank))) {
+ unsigned int offset;
+
+ for_each_set_bit(offset, &status, 32) {
+ if (offset >= bank->width)
+ dev_warn(&priv->pdev->dev,
+ "IRQ for invalid GPIO (bank=%d, offset=%d)\n",
+ bank->id, offset);
+ generic_handle_domain_irq(domain, hwbase + offset);
+ }
+ }
+}
+
+/* Each UPG GIO block has one IRQ for all banks */
+static void brcmstb_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct brcmstb_gpio_priv *priv = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct brcmstb_gpio_bank *bank;
+
+ /* Interrupts weren't properly cleared during probe */
+ BUG_ON(!priv || !chip);
+
+ chained_irq_enter(chip, desc);
+ list_for_each_entry(bank, &priv->bank_list, node)
+ brcmstb_gpio_irq_bank_handler(bank);
+ chained_irq_exit(chip, desc);
+}
+
+static struct brcmstb_gpio_bank *brcmstb_gpio_hwirq_to_bank(
+ struct brcmstb_gpio_priv *priv, irq_hw_number_t hwirq)
+{
+ struct brcmstb_gpio_bank *bank;
+ int i = 0;
+
+ /* banks are in descending order */
+ list_for_each_entry_reverse(bank, &priv->bank_list, node) {
+ i += bank->gc.ngpio;
+ if (hwirq < i)
+ return bank;
+ }
+ return NULL;
+}
+
+/*
+ * This lock class tells lockdep that GPIO irqs are in a different
+ * category than their parents, so it won't report false recursion.
+ */
+static struct lock_class_key brcmstb_gpio_irq_lock_class;
+static struct lock_class_key brcmstb_gpio_irq_request_class;
+
+
+static int brcmstb_gpio_irq_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ struct brcmstb_gpio_priv *priv = d->host_data;
+ struct brcmstb_gpio_bank *bank =
+ brcmstb_gpio_hwirq_to_bank(priv, hwirq);
+ struct platform_device *pdev = priv->pdev;
+ int ret;
+
+ if (!bank)
+ return -EINVAL;
+
+ dev_dbg(&pdev->dev, "Mapping irq %d for gpio line %d (bank %d)\n",
+ irq, (int)hwirq, bank->id);
+ ret = irq_set_chip_data(irq, &bank->gc);
+ if (ret < 0)
+ return ret;
+ irq_set_lockdep_class(irq, &brcmstb_gpio_irq_lock_class,
+ &brcmstb_gpio_irq_request_class);
+ irq_set_chip_and_handler(irq, &priv->irq_chip, handle_level_irq);
+ irq_set_noprobe(irq);
+ return 0;
+}
+
+static void brcmstb_gpio_irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops brcmstb_gpio_irq_domain_ops = {
+ .map = brcmstb_gpio_irq_map,
+ .unmap = brcmstb_gpio_irq_unmap,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+/* Make sure that the number of banks matches up between properties */
+static int brcmstb_gpio_sanity_check_banks(struct device *dev,
+ struct device_node *np, struct resource *res)
+{
+ int res_num_banks = resource_size(res) / GIO_BANK_SIZE;
+ int num_banks =
+ of_property_count_u32_elems(np, "brcm,gpio-bank-widths");
+
+ if (res_num_banks != num_banks) {
+ dev_err(dev, "Mismatch in banks: res had %d, bank-widths had %d\n",
+ res_num_banks, num_banks);
+ return -EINVAL;
+ } else {
+ return 0;
+ }
+}
+
+static int brcmstb_gpio_remove(struct platform_device *pdev)
+{
+ struct brcmstb_gpio_priv *priv = platform_get_drvdata(pdev);
+ struct brcmstb_gpio_bank *bank;
+ int offset, virq;
+
+ if (priv->parent_irq > 0)
+ irq_set_chained_handler_and_data(priv->parent_irq, NULL, NULL);
+
+ /* Remove all IRQ mappings and delete the domain */
+ if (priv->irq_domain) {
+ for (offset = 0; offset < priv->num_gpios; offset++) {
+ virq = irq_find_mapping(priv->irq_domain, offset);
+ irq_dispose_mapping(virq);
+ }
+ irq_domain_remove(priv->irq_domain);
+ }
+
+ /*
+ * You can lose return values below, but we report all errors, and it's
+ * more important to actually perform all of the steps.
+ */
+ list_for_each_entry(bank, &priv->bank_list, node)
+ gpiochip_remove(&bank->gc);
+
+ return 0;
+}
+
+static int brcmstb_gpio_of_xlate(struct gpio_chip *gc,
+ const struct of_phandle_args *gpiospec, u32 *flags)
+{
+ struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc);
+ struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc);
+ int offset;
+
+ if (gc->of_gpio_n_cells != 2) {
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ if (WARN_ON(gpiospec->args_count < gc->of_gpio_n_cells))
+ return -EINVAL;
+
+ offset = gpiospec->args[0] - (gc->base - priv->gpio_base);
+ if (offset >= gc->ngpio || offset < 0)
+ return -EINVAL;
+
+ if (unlikely(offset >= bank->width)) {
+ dev_warn_ratelimited(&priv->pdev->dev,
+ "Received request for invalid GPIO offset %d\n",
+ gpiospec->args[0]);
+ }
+
+ if (flags)
+ *flags = gpiospec->args[1];
+
+ return offset;
+}
+
+/* priv->parent_irq and priv->num_gpios must be set before calling */
+static int brcmstb_gpio_irq_setup(struct platform_device *pdev,
+ struct brcmstb_gpio_priv *priv)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ int err;
+
+ priv->irq_domain =
+ irq_domain_add_linear(np, priv->num_gpios,
+ &brcmstb_gpio_irq_domain_ops,
+ priv);
+ if (!priv->irq_domain) {
+ dev_err(dev, "Couldn't allocate IRQ domain\n");
+ return -ENXIO;
+ }
+
+ if (of_property_read_bool(np, "wakeup-source")) {
+ priv->parent_wake_irq = platform_get_irq(pdev, 1);
+ if (priv->parent_wake_irq < 0) {
+ priv->parent_wake_irq = 0;
+ dev_warn(dev,
+ "Couldn't get wake IRQ - GPIOs will not be able to wake from sleep");
+ } else {
+ /*
+ * Set wakeup capability so we can process boot-time
+ * "wakeups" (e.g., from S5 cold boot)
+ */
+ device_set_wakeup_capable(dev, true);
+ device_wakeup_enable(dev);
+ err = devm_request_irq(dev, priv->parent_wake_irq,
+ brcmstb_gpio_wake_irq_handler,
+ IRQF_SHARED,
+ "brcmstb-gpio-wake", priv);
+
+ if (err < 0) {
+ dev_err(dev, "Couldn't request wake IRQ");
+ goto out_free_domain;
+ }
+ }
+ }
+
+ priv->irq_chip.name = dev_name(dev);
+ priv->irq_chip.irq_disable = brcmstb_gpio_irq_mask;
+ priv->irq_chip.irq_mask = brcmstb_gpio_irq_mask;
+ priv->irq_chip.irq_unmask = brcmstb_gpio_irq_unmask;
+ priv->irq_chip.irq_ack = brcmstb_gpio_irq_ack;
+ priv->irq_chip.irq_set_type = brcmstb_gpio_irq_set_type;
+
+ if (priv->parent_wake_irq)
+ priv->irq_chip.irq_set_wake = brcmstb_gpio_irq_set_wake;
+
+ irq_set_chained_handler_and_data(priv->parent_irq,
+ brcmstb_gpio_irq_handler, priv);
+ irq_set_status_flags(priv->parent_irq, IRQ_DISABLE_UNLAZY);
+
+ return 0;
+
+out_free_domain:
+ irq_domain_remove(priv->irq_domain);
+
+ return err;
+}
+
+static void brcmstb_gpio_bank_save(struct brcmstb_gpio_priv *priv,
+ struct brcmstb_gpio_bank *bank)
+{
+ struct gpio_chip *gc = &bank->gc;
+ unsigned int i;
+
+ for (i = 0; i < GIO_REG_STAT; i++)
+ bank->saved_regs[i] = gc->read_reg(priv->reg_base +
+ GIO_BANK_OFF(bank->id, i));
+}
+
+static void brcmstb_gpio_quiesce(struct device *dev, bool save)
+{
+ struct brcmstb_gpio_priv *priv = dev_get_drvdata(dev);
+ struct brcmstb_gpio_bank *bank;
+ struct gpio_chip *gc;
+ u32 imask;
+
+ /* disable non-wake interrupt */
+ if (priv->parent_irq >= 0)
+ disable_irq(priv->parent_irq);
+
+ list_for_each_entry(bank, &priv->bank_list, node) {
+ gc = &bank->gc;
+
+ if (save)
+ brcmstb_gpio_bank_save(priv, bank);
+
+ /* Unmask GPIOs which have been flagged as wake-up sources */
+ if (priv->parent_wake_irq)
+ imask = bank->wake_active;
+ else
+ imask = 0;
+ gc->write_reg(priv->reg_base + GIO_MASK(bank->id),
+ imask);
+ }
+}
+
+static void brcmstb_gpio_shutdown(struct platform_device *pdev)
+{
+ /* Enable GPIO for S5 cold boot */
+ brcmstb_gpio_quiesce(&pdev->dev, false);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void brcmstb_gpio_bank_restore(struct brcmstb_gpio_priv *priv,
+ struct brcmstb_gpio_bank *bank)
+{
+ struct gpio_chip *gc = &bank->gc;
+ unsigned int i;
+
+ for (i = 0; i < GIO_REG_STAT; i++)
+ gc->write_reg(priv->reg_base + GIO_BANK_OFF(bank->id, i),
+ bank->saved_regs[i]);
+}
+
+static int brcmstb_gpio_suspend(struct device *dev)
+{
+ brcmstb_gpio_quiesce(dev, true);
+ return 0;
+}
+
+static int brcmstb_gpio_resume(struct device *dev)
+{
+ struct brcmstb_gpio_priv *priv = dev_get_drvdata(dev);
+ struct brcmstb_gpio_bank *bank;
+ bool need_wakeup_event = false;
+
+ list_for_each_entry(bank, &priv->bank_list, node) {
+ need_wakeup_event |= !!__brcmstb_gpio_get_active_irqs(bank);
+ brcmstb_gpio_bank_restore(priv, bank);
+ }
+
+ if (priv->parent_wake_irq && need_wakeup_event)
+ pm_wakeup_event(dev, 0);
+
+ /* enable non-wake interrupt */
+ if (priv->parent_irq >= 0)
+ enable_irq(priv->parent_irq);
+
+ return 0;
+}
+
+#else
+#define brcmstb_gpio_suspend NULL
+#define brcmstb_gpio_resume NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops brcmstb_gpio_pm_ops = {
+ .suspend_noirq = brcmstb_gpio_suspend,
+ .resume_noirq = brcmstb_gpio_resume,
+};
+
+static int brcmstb_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ void __iomem *reg_base;
+ struct brcmstb_gpio_priv *priv;
+ struct resource *res;
+ struct property *prop;
+ const __be32 *p;
+ u32 bank_width;
+ int num_banks = 0;
+ int err;
+ static int gpio_base;
+ unsigned long flags = 0;
+ bool need_wakeup_event = false;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, priv);
+ INIT_LIST_HEAD(&priv->bank_list);
+
+ reg_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(reg_base))
+ return PTR_ERR(reg_base);
+
+ priv->gpio_base = gpio_base;
+ priv->reg_base = reg_base;
+ priv->pdev = pdev;
+
+ if (of_property_read_bool(np, "interrupt-controller")) {
+ priv->parent_irq = platform_get_irq(pdev, 0);
+ if (priv->parent_irq <= 0)
+ return -ENOENT;
+ } else {
+ priv->parent_irq = -ENOENT;
+ }
+
+ if (brcmstb_gpio_sanity_check_banks(dev, np, res))
+ return -EINVAL;
+
+ /*
+ * MIPS endianness is configured by boot strap, which also reverses all
+ * bus endianness (i.e., big-endian CPU + big endian bus ==> native
+ * endian I/O).
+ *
+ * Other architectures (e.g., ARM) either do not support big endian, or
+ * else leave I/O in little endian mode.
+ */
+#if defined(CONFIG_MIPS) && defined(__BIG_ENDIAN)
+ flags = BGPIOF_BIG_ENDIAN_BYTE_ORDER;
+#endif
+
+ of_property_for_each_u32(np, "brcm,gpio-bank-widths", prop, p,
+ bank_width) {
+ struct brcmstb_gpio_bank *bank;
+ struct gpio_chip *gc;
+
+ /*
+ * If bank_width is 0, then there is an empty bank in the
+ * register block. Special handling for this case.
+ */
+ if (bank_width == 0) {
+ dev_dbg(dev, "Width 0 found: Empty bank @ %d\n",
+ num_banks);
+ num_banks++;
+ gpio_base += MAX_GPIO_PER_BANK;
+ continue;
+ }
+
+ bank = devm_kzalloc(dev, sizeof(*bank), GFP_KERNEL);
+ if (!bank) {
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ bank->parent_priv = priv;
+ bank->id = num_banks;
+ if (bank_width <= 0 || bank_width > MAX_GPIO_PER_BANK) {
+ dev_err(dev, "Invalid bank width %d\n", bank_width);
+ err = -EINVAL;
+ goto fail;
+ } else {
+ bank->width = bank_width;
+ }
+
+ /*
+ * Regs are 4 bytes wide, have data reg, no set/clear regs,
+ * and direction bits have 0 = output and 1 = input
+ */
+ gc = &bank->gc;
+ err = bgpio_init(gc, dev, 4,
+ reg_base + GIO_DATA(bank->id),
+ NULL, NULL, NULL,
+ reg_base + GIO_IODIR(bank->id), flags);
+ if (err) {
+ dev_err(dev, "bgpio_init() failed\n");
+ goto fail;
+ }
+
+ gc->owner = THIS_MODULE;
+ gc->label = devm_kasprintf(dev, GFP_KERNEL, "%pOF", np);
+ if (!gc->label) {
+ err = -ENOMEM;
+ goto fail;
+ }
+ gc->base = gpio_base;
+ gc->of_gpio_n_cells = 2;
+ gc->of_xlate = brcmstb_gpio_of_xlate;
+ /* not all ngpio lines are valid, will use bank width later */
+ gc->ngpio = MAX_GPIO_PER_BANK;
+ gc->offset = bank->id * MAX_GPIO_PER_BANK;
+ if (priv->parent_irq > 0)
+ gc->to_irq = brcmstb_gpio_to_irq;
+
+ /*
+ * Mask all interrupts by default, since wakeup interrupts may
+ * be retained from S5 cold boot
+ */
+ need_wakeup_event |= !!__brcmstb_gpio_get_active_irqs(bank);
+ gc->write_reg(reg_base + GIO_MASK(bank->id), 0);
+
+ err = gpiochip_add_data(gc, bank);
+ if (err) {
+ dev_err(dev, "Could not add gpiochip for bank %d\n",
+ bank->id);
+ goto fail;
+ }
+ gpio_base += gc->ngpio;
+
+ dev_dbg(dev, "bank=%d, base=%d, ngpio=%d, width=%d\n", bank->id,
+ gc->base, gc->ngpio, bank->width);
+
+ /* Everything looks good, so add bank to list */
+ list_add(&bank->node, &priv->bank_list);
+
+ num_banks++;
+ }
+
+ priv->num_gpios = gpio_base - priv->gpio_base;
+ if (priv->parent_irq > 0) {
+ err = brcmstb_gpio_irq_setup(pdev, priv);
+ if (err)
+ goto fail;
+ }
+
+ if (priv->parent_wake_irq && need_wakeup_event)
+ pm_wakeup_event(dev, 0);
+
+ return 0;
+
+fail:
+ (void) brcmstb_gpio_remove(pdev);
+ return err;
+}
+
+static const struct of_device_id brcmstb_gpio_of_match[] = {
+ { .compatible = "brcm,brcmstb-gpio" },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, brcmstb_gpio_of_match);
+
+static struct platform_driver brcmstb_gpio_driver = {
+ .driver = {
+ .name = "brcmstb-gpio",
+ .of_match_table = brcmstb_gpio_of_match,
+ .pm = &brcmstb_gpio_pm_ops,
+ },
+ .probe = brcmstb_gpio_probe,
+ .remove = brcmstb_gpio_remove,
+ .shutdown = brcmstb_gpio_shutdown,
+};
+module_platform_driver(brcmstb_gpio_driver);
+
+MODULE_AUTHOR("Gregory Fong");
+MODULE_DESCRIPTION("Driver for Broadcom BRCMSTB SoC UPG GPIO");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-bt8xx.c b/drivers/gpio/gpio-bt8xx.c
new file mode 100644
index 0000000000..7920cf2567
--- /dev/null
+++ b/drivers/gpio/gpio-bt8xx.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+
+ bt8xx GPIO abuser
+
+ Copyright (C) 2008 Michael Buesch <m@bues.ch>
+
+ Please do _only_ contact the people listed _above_ with issues related to this driver.
+ All the other people listed below are not related to this driver. Their names
+ are only here, because this driver is derived from the bt848 driver.
+
+
+ Derived from the bt848 driver:
+
+ Copyright (C) 1996,97,98 Ralph Metzler
+ & Marcus Metzler
+ (c) 1999-2002 Gerd Knorr
+
+ some v4l2 code lines are taken from Justin's bttv2 driver which is
+ (c) 2000 Justin Schoeman
+
+ V4L1 removal from:
+ (c) 2005-2006 Nickolay V. Shmyrev
+
+ Fixes to be fully V4L2 compliant by
+ (c) 2006 Mauro Carvalho Chehab
+
+ Cropping and overscan support
+ Copyright (C) 2005, 2006 Michael H. Schimek
+ Sponsored by OPQ Systems AB
+
+*/
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/gpio/driver.h>
+#include <linux/slab.h>
+
+/* Steal the hardware definitions from the bttv driver. */
+#include "../media/pci/bt8xx/bt848.h"
+
+
+#define BT8XXGPIO_NR_GPIOS 24 /* We have 24 GPIO pins */
+
+
+struct bt8xxgpio {
+ spinlock_t lock;
+
+ void __iomem *mmio;
+ struct pci_dev *pdev;
+ struct gpio_chip gpio;
+
+#ifdef CONFIG_PM
+ u32 saved_outen;
+ u32 saved_data;
+#endif
+};
+
+#define bgwrite(dat, adr) writel((dat), bg->mmio+(adr))
+#define bgread(adr) readl(bg->mmio+(adr))
+
+
+static int modparam_gpiobase = -1/* dynamic */;
+module_param_named(gpiobase, modparam_gpiobase, int, 0444);
+MODULE_PARM_DESC(gpiobase, "The GPIO number base. -1 means dynamic, which is the default.");
+
+
+static int bt8xxgpio_gpio_direction_input(struct gpio_chip *gpio, unsigned nr)
+{
+ struct bt8xxgpio *bg = gpiochip_get_data(gpio);
+ unsigned long flags;
+ u32 outen, data;
+
+ spin_lock_irqsave(&bg->lock, flags);
+
+ data = bgread(BT848_GPIO_DATA);
+ data &= ~(1 << nr);
+ bgwrite(data, BT848_GPIO_DATA);
+
+ outen = bgread(BT848_GPIO_OUT_EN);
+ outen &= ~(1 << nr);
+ bgwrite(outen, BT848_GPIO_OUT_EN);
+
+ spin_unlock_irqrestore(&bg->lock, flags);
+
+ return 0;
+}
+
+static int bt8xxgpio_gpio_get(struct gpio_chip *gpio, unsigned nr)
+{
+ struct bt8xxgpio *bg = gpiochip_get_data(gpio);
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&bg->lock, flags);
+ val = bgread(BT848_GPIO_DATA);
+ spin_unlock_irqrestore(&bg->lock, flags);
+
+ return !!(val & (1 << nr));
+}
+
+static int bt8xxgpio_gpio_direction_output(struct gpio_chip *gpio,
+ unsigned nr, int val)
+{
+ struct bt8xxgpio *bg = gpiochip_get_data(gpio);
+ unsigned long flags;
+ u32 outen, data;
+
+ spin_lock_irqsave(&bg->lock, flags);
+
+ outen = bgread(BT848_GPIO_OUT_EN);
+ outen |= (1 << nr);
+ bgwrite(outen, BT848_GPIO_OUT_EN);
+
+ data = bgread(BT848_GPIO_DATA);
+ if (val)
+ data |= (1 << nr);
+ else
+ data &= ~(1 << nr);
+ bgwrite(data, BT848_GPIO_DATA);
+
+ spin_unlock_irqrestore(&bg->lock, flags);
+
+ return 0;
+}
+
+static void bt8xxgpio_gpio_set(struct gpio_chip *gpio,
+ unsigned nr, int val)
+{
+ struct bt8xxgpio *bg = gpiochip_get_data(gpio);
+ unsigned long flags;
+ u32 data;
+
+ spin_lock_irqsave(&bg->lock, flags);
+
+ data = bgread(BT848_GPIO_DATA);
+ if (val)
+ data |= (1 << nr);
+ else
+ data &= ~(1 << nr);
+ bgwrite(data, BT848_GPIO_DATA);
+
+ spin_unlock_irqrestore(&bg->lock, flags);
+}
+
+static void bt8xxgpio_gpio_setup(struct bt8xxgpio *bg)
+{
+ struct gpio_chip *c = &bg->gpio;
+
+ c->label = dev_name(&bg->pdev->dev);
+ c->owner = THIS_MODULE;
+ c->direction_input = bt8xxgpio_gpio_direction_input;
+ c->get = bt8xxgpio_gpio_get;
+ c->direction_output = bt8xxgpio_gpio_direction_output;
+ c->set = bt8xxgpio_gpio_set;
+ c->dbg_show = NULL;
+ c->base = modparam_gpiobase;
+ c->ngpio = BT8XXGPIO_NR_GPIOS;
+ c->can_sleep = false;
+}
+
+static int bt8xxgpio_probe(struct pci_dev *dev,
+ const struct pci_device_id *pci_id)
+{
+ struct bt8xxgpio *bg;
+ int err;
+
+ bg = devm_kzalloc(&dev->dev, sizeof(struct bt8xxgpio), GFP_KERNEL);
+ if (!bg)
+ return -ENOMEM;
+
+ bg->pdev = dev;
+ spin_lock_init(&bg->lock);
+
+ err = pci_enable_device(dev);
+ if (err) {
+ 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);
+
+ /* gpio init */
+ bgwrite(0, BT848_GPIO_DMA_CTL);
+ bgwrite(0, BT848_GPIO_REG_INP);
+ bgwrite(0, BT848_GPIO_OUT_EN);
+
+ bt8xxgpio_gpio_setup(bg);
+ err = gpiochip_add_data(&bg->gpio, bg);
+ if (err) {
+ dev_err(&dev->dev, "failed to register GPIOs\n");
+ goto err_disable;
+ }
+
+ return 0;
+
+err_disable:
+ pci_disable_device(dev);
+
+ return err;
+}
+
+static void bt8xxgpio_remove(struct pci_dev *pdev)
+{
+ struct bt8xxgpio *bg = pci_get_drvdata(pdev);
+
+ gpiochip_remove(&bg->gpio);
+
+ bgwrite(0, BT848_INT_MASK);
+ bgwrite(~0x0, BT848_INT_STAT);
+ bgwrite(0x0, BT848_GPIO_OUT_EN);
+
+ pci_disable_device(pdev);
+}
+
+#ifdef CONFIG_PM
+static int bt8xxgpio_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+ struct bt8xxgpio *bg = pci_get_drvdata(pdev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&bg->lock, flags);
+
+ bg->saved_outen = bgread(BT848_GPIO_OUT_EN);
+ bg->saved_data = bgread(BT848_GPIO_DATA);
+
+ bgwrite(0, BT848_INT_MASK);
+ bgwrite(~0x0, BT848_INT_STAT);
+ bgwrite(0x0, BT848_GPIO_OUT_EN);
+
+ spin_unlock_irqrestore(&bg->lock, flags);
+
+ pci_save_state(pdev);
+ pci_disable_device(pdev);
+ pci_set_power_state(pdev, pci_choose_state(pdev, state));
+
+ return 0;
+}
+
+static int bt8xxgpio_resume(struct pci_dev *pdev)
+{
+ struct bt8xxgpio *bg = pci_get_drvdata(pdev);
+ unsigned long flags;
+ int err;
+
+ pci_set_power_state(pdev, PCI_D0);
+ err = pci_enable_device(pdev);
+ if (err)
+ return err;
+ pci_restore_state(pdev);
+
+ spin_lock_irqsave(&bg->lock, flags);
+
+ bgwrite(0, BT848_INT_MASK);
+ bgwrite(0, BT848_GPIO_DMA_CTL);
+ bgwrite(0, BT848_GPIO_REG_INP);
+ bgwrite(bg->saved_outen, BT848_GPIO_OUT_EN);
+ bgwrite(bg->saved_data & bg->saved_outen,
+ BT848_GPIO_DATA);
+
+ spin_unlock_irqrestore(&bg->lock, flags);
+
+ return 0;
+}
+#else
+#define bt8xxgpio_suspend NULL
+#define bt8xxgpio_resume NULL
+#endif /* CONFIG_PM */
+
+static const struct pci_device_id bt8xxgpio_pci_tbl[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT848) },
+ { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT849) },
+ { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT878) },
+ { PCI_DEVICE(PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT879) },
+ { 0, },
+};
+MODULE_DEVICE_TABLE(pci, bt8xxgpio_pci_tbl);
+
+static struct pci_driver bt8xxgpio_pci_driver = {
+ .name = "bt8xxgpio",
+ .id_table = bt8xxgpio_pci_tbl,
+ .probe = bt8xxgpio_probe,
+ .remove = bt8xxgpio_remove,
+ .suspend = bt8xxgpio_suspend,
+ .resume = bt8xxgpio_resume,
+};
+
+module_pci_driver(bt8xxgpio_pci_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Michael Buesch");
+MODULE_DESCRIPTION("Abuse a BT8xx framegrabber card as generic GPIO card");
diff --git a/drivers/gpio/gpio-cadence.c b/drivers/gpio/gpio-cadence.c
new file mode 100644
index 0000000000..3720b90cad
--- /dev/null
+++ b/drivers/gpio/gpio-cadence.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright 2017-2018 Cadence
+ *
+ * Authors:
+ * Jan Kotas <jank@cadence.com>
+ * Boris Brezillon <boris.brezillon@free-electrons.com>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define CDNS_GPIO_BYPASS_MODE 0x00
+#define CDNS_GPIO_DIRECTION_MODE 0x04
+#define CDNS_GPIO_OUTPUT_EN 0x08
+#define CDNS_GPIO_OUTPUT_VALUE 0x0c
+#define CDNS_GPIO_INPUT_VALUE 0x10
+#define CDNS_GPIO_IRQ_MASK 0x14
+#define CDNS_GPIO_IRQ_EN 0x18
+#define CDNS_GPIO_IRQ_DIS 0x1c
+#define CDNS_GPIO_IRQ_STATUS 0x20
+#define CDNS_GPIO_IRQ_TYPE 0x24
+#define CDNS_GPIO_IRQ_VALUE 0x28
+#define CDNS_GPIO_IRQ_ANY_EDGE 0x2c
+
+struct cdns_gpio_chip {
+ struct gpio_chip gc;
+ struct clk *pclk;
+ void __iomem *regs;
+ u32 bypass_orig;
+};
+
+static int cdns_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ struct cdns_gpio_chip *cgpio = gpiochip_get_data(chip);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&chip->bgpio_lock, flags);
+
+ iowrite32(ioread32(cgpio->regs + CDNS_GPIO_BYPASS_MODE) & ~BIT(offset),
+ cgpio->regs + CDNS_GPIO_BYPASS_MODE);
+
+ raw_spin_unlock_irqrestore(&chip->bgpio_lock, flags);
+ return 0;
+}
+
+static void cdns_gpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+ struct cdns_gpio_chip *cgpio = gpiochip_get_data(chip);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&chip->bgpio_lock, flags);
+
+ iowrite32(ioread32(cgpio->regs + CDNS_GPIO_BYPASS_MODE) |
+ (BIT(offset) & cgpio->bypass_orig),
+ cgpio->regs + CDNS_GPIO_BYPASS_MODE);
+
+ raw_spin_unlock_irqrestore(&chip->bgpio_lock, flags);
+}
+
+static void cdns_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct cdns_gpio_chip *cgpio = gpiochip_get_data(chip);
+
+ iowrite32(BIT(d->hwirq), cgpio->regs + CDNS_GPIO_IRQ_DIS);
+ gpiochip_disable_irq(chip, irqd_to_hwirq(d));
+}
+
+static void cdns_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct cdns_gpio_chip *cgpio = gpiochip_get_data(chip);
+
+ gpiochip_enable_irq(chip, irqd_to_hwirq(d));
+ iowrite32(BIT(d->hwirq), cgpio->regs + CDNS_GPIO_IRQ_EN);
+}
+
+static int cdns_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct cdns_gpio_chip *cgpio = gpiochip_get_data(chip);
+ unsigned long flags;
+ u32 int_value;
+ u32 int_type;
+ u32 mask = BIT(d->hwirq);
+ int ret = 0;
+
+ raw_spin_lock_irqsave(&chip->bgpio_lock, flags);
+
+ int_value = ioread32(cgpio->regs + CDNS_GPIO_IRQ_VALUE) & ~mask;
+ int_type = ioread32(cgpio->regs + CDNS_GPIO_IRQ_TYPE) & ~mask;
+
+ /*
+ * The GPIO controller doesn't have an ACK register.
+ * All interrupt statuses are cleared on a status register read.
+ * Don't support edge interrupts for now.
+ */
+
+ if (type == IRQ_TYPE_LEVEL_HIGH) {
+ int_type |= mask;
+ int_value |= mask;
+ } else if (type == IRQ_TYPE_LEVEL_LOW) {
+ int_type |= mask;
+ } else {
+ ret = -EINVAL;
+ goto err_irq_type;
+ }
+
+ iowrite32(int_value, cgpio->regs + CDNS_GPIO_IRQ_VALUE);
+ iowrite32(int_type, cgpio->regs + CDNS_GPIO_IRQ_TYPE);
+
+err_irq_type:
+ raw_spin_unlock_irqrestore(&chip->bgpio_lock, flags);
+ return ret;
+}
+
+static void cdns_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct gpio_chip *chip = irq_desc_get_handler_data(desc);
+ struct cdns_gpio_chip *cgpio = gpiochip_get_data(chip);
+ struct irq_chip *irqchip = irq_desc_get_chip(desc);
+ unsigned long status;
+ int hwirq;
+
+ chained_irq_enter(irqchip, desc);
+
+ status = ioread32(cgpio->regs + CDNS_GPIO_IRQ_STATUS) &
+ ~ioread32(cgpio->regs + CDNS_GPIO_IRQ_MASK);
+
+ for_each_set_bit(hwirq, &status, chip->ngpio)
+ generic_handle_domain_irq(chip->irq.domain, hwirq);
+
+ chained_irq_exit(irqchip, desc);
+}
+
+static const struct irq_chip cdns_gpio_irqchip = {
+ .name = "cdns-gpio",
+ .irq_mask = cdns_gpio_irq_mask,
+ .irq_unmask = cdns_gpio_irq_unmask,
+ .irq_set_type = cdns_gpio_irq_set_type,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int cdns_gpio_probe(struct platform_device *pdev)
+{
+ struct cdns_gpio_chip *cgpio;
+ int ret, irq;
+ u32 dir_prev;
+ u32 num_gpios = 32;
+
+ cgpio = devm_kzalloc(&pdev->dev, sizeof(*cgpio), GFP_KERNEL);
+ if (!cgpio)
+ return -ENOMEM;
+
+ cgpio->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(cgpio->regs))
+ return PTR_ERR(cgpio->regs);
+
+ of_property_read_u32(pdev->dev.of_node, "ngpios", &num_gpios);
+
+ if (num_gpios > 32) {
+ dev_err(&pdev->dev, "ngpios must be less or equal 32\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Set all pins as inputs by default, otherwise:
+ * gpiochip_lock_as_irq:
+ * tried to flag a GPIO set as output for IRQ
+ * Generic GPIO driver stores the direction value internally,
+ * so it needs to be changed before bgpio_init() is called.
+ */
+ dir_prev = ioread32(cgpio->regs + CDNS_GPIO_DIRECTION_MODE);
+ iowrite32(GENMASK(num_gpios - 1, 0),
+ cgpio->regs + CDNS_GPIO_DIRECTION_MODE);
+
+ ret = bgpio_init(&cgpio->gc, &pdev->dev, 4,
+ cgpio->regs + CDNS_GPIO_INPUT_VALUE,
+ cgpio->regs + CDNS_GPIO_OUTPUT_VALUE,
+ NULL,
+ NULL,
+ cgpio->regs + CDNS_GPIO_DIRECTION_MODE,
+ BGPIOF_READ_OUTPUT_REG_SET);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register generic gpio, %d\n",
+ ret);
+ goto err_revert_dir;
+ }
+
+ cgpio->gc.label = dev_name(&pdev->dev);
+ cgpio->gc.ngpio = num_gpios;
+ cgpio->gc.parent = &pdev->dev;
+ cgpio->gc.base = -1;
+ cgpio->gc.owner = THIS_MODULE;
+ cgpio->gc.request = cdns_gpio_request;
+ cgpio->gc.free = cdns_gpio_free;
+
+ cgpio->pclk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(cgpio->pclk)) {
+ ret = PTR_ERR(cgpio->pclk);
+ dev_err(&pdev->dev,
+ "Failed to retrieve peripheral clock, %d\n", ret);
+ goto err_revert_dir;
+ }
+
+ ret = clk_prepare_enable(cgpio->pclk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to enable the peripheral clock, %d\n", ret);
+ goto err_revert_dir;
+ }
+
+ /*
+ * Optional irq_chip support
+ */
+ irq = platform_get_irq(pdev, 0);
+ if (irq >= 0) {
+ struct gpio_irq_chip *girq;
+
+ girq = &cgpio->gc.irq;
+ gpio_irq_chip_set_chip(girq, &cdns_gpio_irqchip);
+ girq->parent_handler = cdns_gpio_irq_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(&pdev->dev, 1,
+ sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents) {
+ ret = -ENOMEM;
+ goto err_disable_clk;
+ }
+ girq->parents[0] = irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_level_irq;
+ }
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &cgpio->gc, cgpio);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret);
+ goto err_disable_clk;
+ }
+
+ cgpio->bypass_orig = ioread32(cgpio->regs + CDNS_GPIO_BYPASS_MODE);
+
+ /*
+ * Enable gpio outputs, ignored for input direction
+ */
+ iowrite32(GENMASK(num_gpios - 1, 0),
+ cgpio->regs + CDNS_GPIO_OUTPUT_EN);
+ iowrite32(0, cgpio->regs + CDNS_GPIO_BYPASS_MODE);
+
+ platform_set_drvdata(pdev, cgpio);
+ return 0;
+
+err_disable_clk:
+ clk_disable_unprepare(cgpio->pclk);
+
+err_revert_dir:
+ iowrite32(dir_prev, cgpio->regs + CDNS_GPIO_DIRECTION_MODE);
+
+ return ret;
+}
+
+static int cdns_gpio_remove(struct platform_device *pdev)
+{
+ struct cdns_gpio_chip *cgpio = platform_get_drvdata(pdev);
+
+ iowrite32(cgpio->bypass_orig, cgpio->regs + CDNS_GPIO_BYPASS_MODE);
+ clk_disable_unprepare(cgpio->pclk);
+
+ return 0;
+}
+
+static const struct of_device_id cdns_of_ids[] = {
+ { .compatible = "cdns,gpio-r1p02" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, cdns_of_ids);
+
+static struct platform_driver cdns_gpio_driver = {
+ .driver = {
+ .name = "cdns-gpio",
+ .of_match_table = cdns_of_ids,
+ },
+ .probe = cdns_gpio_probe,
+ .remove = cdns_gpio_remove,
+};
+module_platform_driver(cdns_gpio_driver);
+
+MODULE_AUTHOR("Jan Kotas <jank@cadence.com>");
+MODULE_DESCRIPTION("Cadence GPIO driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:cdns-gpio");
diff --git a/drivers/gpio/gpio-clps711x.c b/drivers/gpio/gpio-clps711x.c
new file mode 100644
index 0000000000..d69a24dd48
--- /dev/null
+++ b/drivers/gpio/gpio-clps711x.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * CLPS711X GPIO driver
+ *
+ * Copyright (C) 2012,2013 Alexander Shiyan <shc_work@mail.ru>
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+
+static int clps711x_gpio_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ void __iomem *dat, *dir;
+ struct gpio_chip *gc;
+ int err, id;
+
+ if (!np)
+ return -ENODEV;
+
+ id = of_alias_get_id(np, "gpio");
+ if ((id < 0) || (id > 4))
+ return -ENODEV;
+
+ gc = devm_kzalloc(&pdev->dev, sizeof(*gc), GFP_KERNEL);
+ if (!gc)
+ return -ENOMEM;
+
+ dat = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dat))
+ return PTR_ERR(dat);
+
+ dir = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(dir))
+ return PTR_ERR(dir);
+
+ switch (id) {
+ case 3:
+ /* PORTD is inverted logic for direction register */
+ err = bgpio_init(gc, &pdev->dev, 1, dat, NULL, NULL,
+ NULL, dir, 0);
+ break;
+ default:
+ err = bgpio_init(gc, &pdev->dev, 1, dat, NULL, NULL,
+ dir, NULL, 0);
+ break;
+ }
+
+ if (err)
+ return err;
+
+ switch (id) {
+ case 4:
+ /* PORTE is 3 lines only */
+ gc->ngpio = 3;
+ break;
+ default:
+ break;
+ }
+
+ gc->base = -1;
+ gc->owner = THIS_MODULE;
+ platform_set_drvdata(pdev, gc);
+
+ return devm_gpiochip_add_data(&pdev->dev, gc, NULL);
+}
+
+static const struct of_device_id clps711x_gpio_ids[] = {
+ { .compatible = "cirrus,ep7209-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, clps711x_gpio_ids);
+
+static struct platform_driver clps711x_gpio_driver = {
+ .driver = {
+ .name = "clps711x-gpio",
+ .of_match_table = clps711x_gpio_ids,
+ },
+ .probe = clps711x_gpio_probe,
+};
+module_platform_driver(clps711x_gpio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
+MODULE_DESCRIPTION("CLPS711X GPIO driver");
+MODULE_ALIAS("platform:clps711x-gpio");
diff --git a/drivers/gpio/gpio-creg-snps.c b/drivers/gpio/gpio-creg-snps.c
new file mode 100644
index 0000000000..4968232f70
--- /dev/null
+++ b/drivers/gpio/gpio-creg-snps.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Synopsys CREG (Control REGisters) GPIO driver
+//
+// Copyright (C) 2018 Synopsys
+// Author: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>
+
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#define MAX_GPIO 32
+
+struct creg_layout {
+ u8 ngpio;
+ u8 shift[MAX_GPIO];
+ u8 on[MAX_GPIO];
+ u8 off[MAX_GPIO];
+ u8 bit_per_gpio[MAX_GPIO];
+};
+
+struct creg_gpio {
+ struct gpio_chip gc;
+ void __iomem *regs;
+ spinlock_t lock;
+ const struct creg_layout *layout;
+};
+
+static void creg_gpio_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ struct creg_gpio *hcg = gpiochip_get_data(gc);
+ const struct creg_layout *layout = hcg->layout;
+ u32 reg, reg_shift, value;
+ unsigned long flags;
+ int i;
+
+ value = val ? hcg->layout->on[offset] : hcg->layout->off[offset];
+
+ reg_shift = layout->shift[offset];
+ for (i = 0; i < offset; i++)
+ reg_shift += layout->bit_per_gpio[i] + layout->shift[i];
+
+ spin_lock_irqsave(&hcg->lock, flags);
+ reg = readl(hcg->regs);
+ reg &= ~(GENMASK(layout->bit_per_gpio[i] - 1, 0) << reg_shift);
+ reg |= (value << reg_shift);
+ writel(reg, hcg->regs);
+ spin_unlock_irqrestore(&hcg->lock, flags);
+}
+
+static int creg_gpio_dir_out(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ creg_gpio_set(gc, offset, val);
+
+ return 0;
+}
+
+static int creg_gpio_validate_pg(struct device *dev, struct creg_gpio *hcg,
+ int i)
+{
+ const struct creg_layout *layout = hcg->layout;
+
+ if (layout->bit_per_gpio[i] < 1 || layout->bit_per_gpio[i] > 8)
+ return -EINVAL;
+
+ /* Check that on value fits its placeholder */
+ if (GENMASK(31, layout->bit_per_gpio[i]) & layout->on[i])
+ return -EINVAL;
+
+ /* Check that off value fits its placeholder */
+ if (GENMASK(31, layout->bit_per_gpio[i]) & layout->off[i])
+ return -EINVAL;
+
+ if (layout->on[i] == layout->off[i])
+ return -EINVAL;
+
+ return 0;
+}
+
+static int creg_gpio_validate(struct device *dev, struct creg_gpio *hcg,
+ u32 ngpios)
+{
+ u32 reg_len = 0;
+ int i;
+
+ if (hcg->layout->ngpio < 1 || hcg->layout->ngpio > MAX_GPIO)
+ return -EINVAL;
+
+ if (ngpios < 1 || ngpios > hcg->layout->ngpio) {
+ dev_err(dev, "ngpios must be in [1:%u]\n", hcg->layout->ngpio);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < hcg->layout->ngpio; i++) {
+ if (creg_gpio_validate_pg(dev, hcg, i))
+ return -EINVAL;
+
+ reg_len += hcg->layout->shift[i] + hcg->layout->bit_per_gpio[i];
+ }
+
+ /* Check that we fit in 32 bit register */
+ if (reg_len > 32)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct creg_layout hsdk_cs_ctl = {
+ .ngpio = 10,
+ .shift = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ .off = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 },
+ .on = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+ .bit_per_gpio = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }
+};
+
+static const struct creg_layout axs10x_flsh_cs_ctl = {
+ .ngpio = 1,
+ .shift = { 0 },
+ .off = { 1 },
+ .on = { 3 },
+ .bit_per_gpio = { 2 }
+};
+
+static const struct of_device_id creg_gpio_ids[] = {
+ {
+ .compatible = "snps,creg-gpio-axs10x",
+ .data = &axs10x_flsh_cs_ctl
+ }, {
+ .compatible = "snps,creg-gpio-hsdk",
+ .data = &hsdk_cs_ctl
+ }, { /* sentinel */ }
+};
+
+static int creg_gpio_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match;
+ struct device *dev = &pdev->dev;
+ struct creg_gpio *hcg;
+ u32 ngpios;
+ int ret;
+
+ hcg = devm_kzalloc(dev, sizeof(struct creg_gpio), GFP_KERNEL);
+ if (!hcg)
+ return -ENOMEM;
+
+ hcg->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(hcg->regs))
+ return PTR_ERR(hcg->regs);
+
+ match = of_match_node(creg_gpio_ids, pdev->dev.of_node);
+ hcg->layout = match->data;
+ if (!hcg->layout)
+ return -EINVAL;
+
+ ret = of_property_read_u32(dev->of_node, "ngpios", &ngpios);
+ if (ret)
+ return ret;
+
+ ret = creg_gpio_validate(dev, hcg, ngpios);
+ if (ret)
+ return ret;
+
+ spin_lock_init(&hcg->lock);
+
+ hcg->gc.parent = dev;
+ hcg->gc.label = dev_name(dev);
+ hcg->gc.base = -1;
+ hcg->gc.ngpio = ngpios;
+ hcg->gc.set = creg_gpio_set;
+ hcg->gc.direction_output = creg_gpio_dir_out;
+
+ ret = devm_gpiochip_add_data(dev, &hcg->gc, hcg);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "GPIO controller with %d gpios probed\n", ngpios);
+
+ return 0;
+}
+
+static struct platform_driver creg_gpio_snps_driver = {
+ .driver = {
+ .name = "snps-creg-gpio",
+ .of_match_table = creg_gpio_ids,
+ },
+ .probe = creg_gpio_probe,
+};
+builtin_platform_driver(creg_gpio_snps_driver);
diff --git a/drivers/gpio/gpio-crystalcove.c b/drivers/gpio/gpio-crystalcove.c
new file mode 100644
index 0000000000..1ee62cd585
--- /dev/null
+++ b/drivers/gpio/gpio-crystalcove.c
@@ -0,0 +1,397 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Crystal Cove GPIO Driver
+ *
+ * Copyright (C) 2012, 2014 Intel Corporation. All rights reserved.
+ *
+ * Author: Yang, Bin <bin.yang@intel.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/seq_file.h>
+#include <linux/types.h>
+
+#define CRYSTALCOVE_GPIO_NUM 16
+#define CRYSTALCOVE_VGPIO_NUM 95
+
+#define UPDATE_IRQ_TYPE BIT(0)
+#define UPDATE_IRQ_MASK BIT(1)
+
+#define GPIO0IRQ 0x0b
+#define GPIO1IRQ 0x0c
+#define MGPIO0IRQS0 0x19
+#define MGPIO1IRQS0 0x1a
+#define MGPIO0IRQSX 0x1b
+#define MGPIO1IRQSX 0x1c
+#define GPIO0P0CTLO 0x2b
+#define GPIO0P0CTLI 0x33
+#define GPIO1P0CTLO 0x3b
+#define GPIO1P0CTLI 0x43
+#define GPIOPANELCTL 0x52
+
+#define CTLI_INTCNT_DIS (0)
+#define CTLI_INTCNT_NE (1 << 1)
+#define CTLI_INTCNT_PE (2 << 1)
+#define CTLI_INTCNT_BE (3 << 1)
+
+#define CTLO_DIR_IN (0)
+#define CTLO_DIR_OUT (1 << 5)
+
+#define CTLO_DRV_CMOS (0)
+#define CTLO_DRV_OD (1 << 4)
+
+#define CTLO_DRV_REN (1 << 3)
+
+#define CTLO_RVAL_2KDW (0)
+#define CTLO_RVAL_2KUP (1 << 1)
+#define CTLO_RVAL_50KDW (2 << 1)
+#define CTLO_RVAL_50KUP (3 << 1)
+
+#define CTLO_INPUT_SET (CTLO_DRV_CMOS | CTLO_DRV_REN | CTLO_RVAL_2KUP)
+#define CTLO_OUTPUT_SET (CTLO_DIR_OUT | CTLO_INPUT_SET)
+
+enum ctrl_register {
+ CTRL_IN,
+ CTRL_OUT,
+};
+
+/**
+ * struct crystalcove_gpio - Crystal Cove GPIO controller
+ * @buslock: for bus lock/sync and unlock.
+ * @chip: the abstract gpio_chip structure.
+ * @regmap: the regmap from the parent device.
+ * @update: pending IRQ setting update, to be written to the chip upon unlock.
+ * @intcnt_value: the Interrupt Detect value to be written.
+ * @set_irq_mask: true if the IRQ mask needs to be set, false to clear.
+ */
+struct crystalcove_gpio {
+ struct mutex buslock; /* irq_bus_lock */
+ struct gpio_chip chip;
+ struct regmap *regmap;
+ int update;
+ int intcnt_value;
+ bool set_irq_mask;
+};
+
+static inline int to_reg(int gpio, enum ctrl_register reg_type)
+{
+ int reg;
+
+ if (gpio >= CRYSTALCOVE_GPIO_NUM) {
+ /*
+ * Virtual GPIO called from ACPI, for now we only support
+ * the panel ctl.
+ */
+ switch (gpio) {
+ case 0x5e:
+ return GPIOPANELCTL;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ if (reg_type == CTRL_IN) {
+ if (gpio < 8)
+ reg = GPIO0P0CTLI;
+ else
+ reg = GPIO1P0CTLI;
+ } else {
+ if (gpio < 8)
+ reg = GPIO0P0CTLO;
+ else
+ reg = GPIO1P0CTLO;
+ }
+
+ return reg + gpio % 8;
+}
+
+static void crystalcove_update_irq_mask(struct crystalcove_gpio *cg, int gpio)
+{
+ u8 mirqs0 = gpio < 8 ? MGPIO0IRQS0 : MGPIO1IRQS0;
+ int mask = BIT(gpio % 8);
+
+ if (cg->set_irq_mask)
+ regmap_update_bits(cg->regmap, mirqs0, mask, mask);
+ else
+ regmap_update_bits(cg->regmap, mirqs0, mask, 0);
+}
+
+static void crystalcove_update_irq_ctrl(struct crystalcove_gpio *cg, int gpio)
+{
+ int reg = to_reg(gpio, CTRL_IN);
+
+ regmap_update_bits(cg->regmap, reg, CTLI_INTCNT_BE, cg->intcnt_value);
+}
+
+static int crystalcove_gpio_dir_in(struct gpio_chip *chip, unsigned int gpio)
+{
+ struct crystalcove_gpio *cg = gpiochip_get_data(chip);
+ int reg = to_reg(gpio, CTRL_OUT);
+
+ if (reg < 0)
+ return 0;
+
+ return regmap_write(cg->regmap, reg, CTLO_INPUT_SET);
+}
+
+static int crystalcove_gpio_dir_out(struct gpio_chip *chip, unsigned int gpio, int value)
+{
+ struct crystalcove_gpio *cg = gpiochip_get_data(chip);
+ int reg = to_reg(gpio, CTRL_OUT);
+
+ if (reg < 0)
+ return 0;
+
+ return regmap_write(cg->regmap, reg, CTLO_OUTPUT_SET | value);
+}
+
+static int crystalcove_gpio_get(struct gpio_chip *chip, unsigned int gpio)
+{
+ struct crystalcove_gpio *cg = gpiochip_get_data(chip);
+ unsigned int val;
+ int ret, reg = to_reg(gpio, CTRL_IN);
+
+ if (reg < 0)
+ return 0;
+
+ ret = regmap_read(cg->regmap, reg, &val);
+ if (ret)
+ return ret;
+
+ return val & 0x1;
+}
+
+static void crystalcove_gpio_set(struct gpio_chip *chip, unsigned int gpio, int value)
+{
+ struct crystalcove_gpio *cg = gpiochip_get_data(chip);
+ int reg = to_reg(gpio, CTRL_OUT);
+
+ if (reg < 0)
+ return;
+
+ if (value)
+ regmap_update_bits(cg->regmap, reg, 1, 1);
+ else
+ regmap_update_bits(cg->regmap, reg, 1, 0);
+}
+
+static int crystalcove_irq_type(struct irq_data *data, unsigned int type)
+{
+ struct crystalcove_gpio *cg = gpiochip_get_data(irq_data_get_irq_chip_data(data));
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ if (hwirq >= CRYSTALCOVE_GPIO_NUM)
+ return 0;
+
+ switch (type) {
+ case IRQ_TYPE_NONE:
+ cg->intcnt_value = CTLI_INTCNT_DIS;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ cg->intcnt_value = CTLI_INTCNT_BE;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ cg->intcnt_value = CTLI_INTCNT_PE;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ cg->intcnt_value = CTLI_INTCNT_NE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ cg->update |= UPDATE_IRQ_TYPE;
+
+ return 0;
+}
+
+static void crystalcove_bus_lock(struct irq_data *data)
+{
+ struct crystalcove_gpio *cg = gpiochip_get_data(irq_data_get_irq_chip_data(data));
+
+ mutex_lock(&cg->buslock);
+}
+
+static void crystalcove_bus_sync_unlock(struct irq_data *data)
+{
+ struct crystalcove_gpio *cg = gpiochip_get_data(irq_data_get_irq_chip_data(data));
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ if (cg->update & UPDATE_IRQ_TYPE)
+ crystalcove_update_irq_ctrl(cg, hwirq);
+ if (cg->update & UPDATE_IRQ_MASK)
+ crystalcove_update_irq_mask(cg, hwirq);
+ cg->update = 0;
+
+ mutex_unlock(&cg->buslock);
+}
+
+static void crystalcove_irq_unmask(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct crystalcove_gpio *cg = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ if (hwirq >= CRYSTALCOVE_GPIO_NUM)
+ return;
+
+ gpiochip_enable_irq(gc, hwirq);
+
+ cg->set_irq_mask = false;
+ cg->update |= UPDATE_IRQ_MASK;
+}
+
+static void crystalcove_irq_mask(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct crystalcove_gpio *cg = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ if (hwirq >= CRYSTALCOVE_GPIO_NUM)
+ return;
+
+ cg->set_irq_mask = true;
+ cg->update |= UPDATE_IRQ_MASK;
+
+ gpiochip_disable_irq(gc, hwirq);
+}
+
+static const struct irq_chip crystalcove_irqchip = {
+ .name = "Crystal Cove",
+ .irq_mask = crystalcove_irq_mask,
+ .irq_unmask = crystalcove_irq_unmask,
+ .irq_set_type = crystalcove_irq_type,
+ .irq_bus_lock = crystalcove_bus_lock,
+ .irq_bus_sync_unlock = crystalcove_bus_sync_unlock,
+ .flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static irqreturn_t crystalcove_gpio_irq_handler(int irq, void *data)
+{
+ struct crystalcove_gpio *cg = data;
+ unsigned long pending;
+ unsigned int p0, p1;
+ int gpio;
+ unsigned int virq;
+
+ if (regmap_read(cg->regmap, GPIO0IRQ, &p0) ||
+ regmap_read(cg->regmap, GPIO1IRQ, &p1))
+ return IRQ_NONE;
+
+ regmap_write(cg->regmap, GPIO0IRQ, p0);
+ regmap_write(cg->regmap, GPIO1IRQ, p1);
+
+ pending = p0 | p1 << 8;
+
+ for_each_set_bit(gpio, &pending, CRYSTALCOVE_GPIO_NUM) {
+ virq = irq_find_mapping(cg->chip.irq.domain, gpio);
+ handle_nested_irq(virq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void crystalcove_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ struct crystalcove_gpio *cg = gpiochip_get_data(chip);
+ int gpio, offset;
+ unsigned int ctlo, ctli, mirqs0, mirqsx, irq;
+
+ for (gpio = 0; gpio < CRYSTALCOVE_GPIO_NUM; gpio++) {
+ regmap_read(cg->regmap, to_reg(gpio, CTRL_OUT), &ctlo);
+ regmap_read(cg->regmap, to_reg(gpio, CTRL_IN), &ctli);
+ regmap_read(cg->regmap, gpio < 8 ? MGPIO0IRQS0 : MGPIO1IRQS0,
+ &mirqs0);
+ regmap_read(cg->regmap, gpio < 8 ? MGPIO0IRQSX : MGPIO1IRQSX,
+ &mirqsx);
+ regmap_read(cg->regmap, gpio < 8 ? GPIO0IRQ : GPIO1IRQ,
+ &irq);
+
+ offset = gpio % 8;
+ seq_printf(s, " gpio-%-2d %s %s %s %s ctlo=%2x,%s %s %s\n",
+ gpio, ctlo & CTLO_DIR_OUT ? "out" : "in ",
+ ctli & 0x1 ? "hi" : "lo",
+ ctli & CTLI_INTCNT_NE ? "fall" : " ",
+ ctli & CTLI_INTCNT_PE ? "rise" : " ",
+ ctlo,
+ mirqs0 & BIT(offset) ? "s0 mask " : "s0 unmask",
+ mirqsx & BIT(offset) ? "sx mask " : "sx unmask",
+ irq & BIT(offset) ? "pending" : " ");
+ }
+}
+
+static int crystalcove_gpio_probe(struct platform_device *pdev)
+{
+ int irq = platform_get_irq(pdev, 0);
+ struct crystalcove_gpio *cg;
+ int retval;
+ struct device *dev = pdev->dev.parent;
+ struct intel_soc_pmic *pmic = dev_get_drvdata(dev);
+ struct gpio_irq_chip *girq;
+
+ if (irq < 0)
+ return irq;
+
+ cg = devm_kzalloc(&pdev->dev, sizeof(*cg), GFP_KERNEL);
+ if (!cg)
+ return -ENOMEM;
+
+ mutex_init(&cg->buslock);
+ cg->chip.label = KBUILD_MODNAME;
+ cg->chip.direction_input = crystalcove_gpio_dir_in;
+ cg->chip.direction_output = crystalcove_gpio_dir_out;
+ cg->chip.get = crystalcove_gpio_get;
+ cg->chip.set = crystalcove_gpio_set;
+ cg->chip.base = -1;
+ cg->chip.ngpio = CRYSTALCOVE_VGPIO_NUM;
+ cg->chip.can_sleep = true;
+ cg->chip.parent = dev;
+ cg->chip.dbg_show = crystalcove_gpio_dbg_show;
+ cg->regmap = pmic->regmap;
+
+ girq = &cg->chip.irq;
+ gpio_irq_chip_set_chip(girq, &crystalcove_irqchip);
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ girq->threaded = true;
+
+ retval = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ crystalcove_gpio_irq_handler,
+ IRQF_ONESHOT, KBUILD_MODNAME, cg);
+ if (retval) {
+ dev_warn(&pdev->dev, "request irq failed: %d\n", retval);
+ return retval;
+ }
+
+ retval = devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg);
+ if (retval)
+ return retval;
+
+ /* Distuingish IRQ domain from others sharing (MFD) the same fwnode */
+ irq_domain_update_bus_token(cg->chip.irq.domain, DOMAIN_BUS_WIRED);
+
+ return 0;
+}
+
+static struct platform_driver crystalcove_gpio_driver = {
+ .probe = crystalcove_gpio_probe,
+ .driver = {
+ .name = "crystal_cove_gpio",
+ },
+};
+module_platform_driver(crystalcove_gpio_driver);
+
+MODULE_AUTHOR("Yang, Bin <bin.yang@intel.com>");
+MODULE_DESCRIPTION("Intel Crystal Cove GPIO Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-cs5535.c b/drivers/gpio/gpio-cs5535.c
new file mode 100644
index 0000000000..6da3a24761
--- /dev/null
+++ b/drivers/gpio/gpio-cs5535.c
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AMD CS5535/CS5536 GPIO driver
+ * Copyright (C) 2006 Advanced Micro Devices, Inc.
+ * Copyright (C) 2007-2009 Andres Salomon <dilinger@collabora.co.uk>
+ */
+
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/cs5535.h>
+#include <asm/msr.h>
+
+#define DRV_NAME "cs5535-gpio"
+
+/*
+ * Some GPIO pins
+ * 31-29,23 : reserved (always mask out)
+ * 28 : Power Button
+ * 26 : PME#
+ * 22-16 : LPC
+ * 14,15 : SMBus
+ * 9,8 : UART1
+ * 7 : PCI INTB
+ * 3,4 : UART2/DDC
+ * 2 : IDE_IRQ0
+ * 1 : AC_BEEP
+ * 0 : PCI INTA
+ *
+ * If a mask was not specified, allow all except
+ * reserved and Power Button
+ */
+#define GPIO_DEFAULT_MASK 0x0F7FFFFF
+
+static ulong mask = GPIO_DEFAULT_MASK;
+module_param_named(mask, mask, ulong, 0444);
+MODULE_PARM_DESC(mask, "GPIO channel mask.");
+
+/*
+ * FIXME: convert this singleton driver to use the state container
+ * design pattern, see Documentation/driver-api/driver-model/design-patterns.rst
+ */
+static struct cs5535_gpio_chip {
+ struct gpio_chip chip;
+ resource_size_t base;
+
+ struct platform_device *pdev;
+ spinlock_t lock;
+} cs5535_gpio_chip;
+
+/*
+ * The CS5535/CS5536 GPIOs support a number of extra features not defined
+ * by the gpio_chip API, so these are exported. For a full list of the
+ * registers, see include/linux/cs5535.h.
+ */
+
+static void errata_outl(struct cs5535_gpio_chip *chip, u32 val,
+ unsigned int reg)
+{
+ unsigned long addr = chip->base + 0x80 + reg;
+
+ /*
+ * According to the CS5536 errata (#36), after suspend
+ * a write to the high bank GPIO register will clear all
+ * non-selected bits; the recommended workaround is a
+ * read-modify-write operation.
+ *
+ * Don't apply this errata to the edge status GPIOs, as writing
+ * to their lower bits will clear them.
+ */
+ if (reg != GPIO_POSITIVE_EDGE_STS && reg != GPIO_NEGATIVE_EDGE_STS) {
+ if (val & 0xffff)
+ val |= (inl(addr) & 0xffff); /* ignore the high bits */
+ else
+ val |= (inl(addr) ^ (val >> 16));
+ }
+ outl(val, addr);
+}
+
+static void __cs5535_gpio_set(struct cs5535_gpio_chip *chip, unsigned offset,
+ unsigned int reg)
+{
+ if (offset < 16)
+ /* low bank register */
+ outl(1 << offset, chip->base + reg);
+ else
+ /* high bank register */
+ errata_outl(chip, 1 << (offset - 16), reg);
+}
+
+void cs5535_gpio_set(unsigned offset, unsigned int reg)
+{
+ struct cs5535_gpio_chip *chip = &cs5535_gpio_chip;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ __cs5535_gpio_set(chip, offset, reg);
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+EXPORT_SYMBOL_GPL(cs5535_gpio_set);
+
+static void __cs5535_gpio_clear(struct cs5535_gpio_chip *chip, unsigned offset,
+ unsigned int reg)
+{
+ if (offset < 16)
+ /* low bank register */
+ outl(1 << (offset + 16), chip->base + reg);
+ else
+ /* high bank register */
+ errata_outl(chip, 1 << offset, reg);
+}
+
+void cs5535_gpio_clear(unsigned offset, unsigned int reg)
+{
+ struct cs5535_gpio_chip *chip = &cs5535_gpio_chip;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ __cs5535_gpio_clear(chip, offset, reg);
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+EXPORT_SYMBOL_GPL(cs5535_gpio_clear);
+
+int cs5535_gpio_isset(unsigned offset, unsigned int reg)
+{
+ struct cs5535_gpio_chip *chip = &cs5535_gpio_chip;
+ unsigned long flags;
+ long val;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ if (offset < 16)
+ /* low bank register */
+ val = inl(chip->base + reg);
+ else {
+ /* high bank register */
+ val = inl(chip->base + 0x80 + reg);
+ offset -= 16;
+ }
+ spin_unlock_irqrestore(&chip->lock, flags);
+
+ return (val & (1 << offset)) ? 1 : 0;
+}
+EXPORT_SYMBOL_GPL(cs5535_gpio_isset);
+
+int cs5535_gpio_set_irq(unsigned group, unsigned irq)
+{
+ uint32_t lo, hi;
+
+ if (group > 7 || irq > 15)
+ return -EINVAL;
+
+ rdmsr(MSR_PIC_ZSEL_HIGH, lo, hi);
+
+ lo &= ~(0xF << (group * 4));
+ lo |= (irq & 0xF) << (group * 4);
+
+ wrmsr(MSR_PIC_ZSEL_HIGH, lo, hi);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cs5535_gpio_set_irq);
+
+void cs5535_gpio_setup_event(unsigned offset, int pair, int pme)
+{
+ struct cs5535_gpio_chip *chip = &cs5535_gpio_chip;
+ uint32_t shift = (offset % 8) * 4;
+ unsigned long flags;
+ uint32_t val;
+
+ if (offset >= 24)
+ offset = GPIO_MAP_W;
+ else if (offset >= 16)
+ offset = GPIO_MAP_Z;
+ else if (offset >= 8)
+ offset = GPIO_MAP_Y;
+ else
+ offset = GPIO_MAP_X;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ val = inl(chip->base + offset);
+
+ /* Clear whatever was there before */
+ val &= ~(0xF << shift);
+
+ /* Set the new value */
+ val |= ((pair & 7) << shift);
+
+ /* Set the PME bit if this is a PME event */
+ if (pme)
+ val |= (1 << (shift + 3));
+
+ outl(val, chip->base + offset);
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+EXPORT_SYMBOL_GPL(cs5535_gpio_setup_event);
+
+/*
+ * Generic gpio_chip API support.
+ */
+
+static int chip_gpio_request(struct gpio_chip *c, unsigned offset)
+{
+ struct cs5535_gpio_chip *chip = gpiochip_get_data(c);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+
+ /* check if this pin is available */
+ if ((mask & (1 << offset)) == 0) {
+ dev_info(&chip->pdev->dev,
+ "pin %u is not available (check mask)\n", offset);
+ spin_unlock_irqrestore(&chip->lock, flags);
+ return -EINVAL;
+ }
+
+ /* disable output aux 1 & 2 on this pin */
+ __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_AUX1);
+ __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_AUX2);
+
+ /* disable input aux 1 on this pin */
+ __cs5535_gpio_clear(chip, offset, GPIO_INPUT_AUX1);
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+
+ return 0;
+}
+
+static int chip_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ return cs5535_gpio_isset(offset, GPIO_READ_BACK);
+}
+
+static void chip_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
+{
+ if (val)
+ cs5535_gpio_set(offset, GPIO_OUTPUT_VAL);
+ else
+ cs5535_gpio_clear(offset, GPIO_OUTPUT_VAL);
+}
+
+static int chip_direction_input(struct gpio_chip *c, unsigned offset)
+{
+ struct cs5535_gpio_chip *chip = gpiochip_get_data(c);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ __cs5535_gpio_set(chip, offset, GPIO_INPUT_ENABLE);
+ __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_ENABLE);
+ spin_unlock_irqrestore(&chip->lock, flags);
+
+ return 0;
+}
+
+static int chip_direction_output(struct gpio_chip *c, unsigned offset, int val)
+{
+ struct cs5535_gpio_chip *chip = gpiochip_get_data(c);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+
+ __cs5535_gpio_set(chip, offset, GPIO_INPUT_ENABLE);
+ __cs5535_gpio_set(chip, offset, GPIO_OUTPUT_ENABLE);
+ if (val)
+ __cs5535_gpio_set(chip, offset, GPIO_OUTPUT_VAL);
+ else
+ __cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_VAL);
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+
+ return 0;
+}
+
+static const char * const cs5535_gpio_names[] = {
+ "GPIO0", "GPIO1", "GPIO2", "GPIO3",
+ "GPIO4", "GPIO5", "GPIO6", "GPIO7",
+ "GPIO8", "GPIO9", "GPIO10", "GPIO11",
+ "GPIO12", "GPIO13", "GPIO14", "GPIO15",
+ "GPIO16", "GPIO17", "GPIO18", "GPIO19",
+ "GPIO20", "GPIO21", "GPIO22", NULL,
+ "GPIO24", "GPIO25", "GPIO26", "GPIO27",
+ "GPIO28", NULL, NULL, NULL,
+};
+
+static struct cs5535_gpio_chip cs5535_gpio_chip = {
+ .chip = {
+ .owner = THIS_MODULE,
+ .label = DRV_NAME,
+
+ .base = 0,
+ .ngpio = 32,
+ .names = cs5535_gpio_names,
+ .request = chip_gpio_request,
+
+ .get = chip_gpio_get,
+ .set = chip_gpio_set,
+
+ .direction_input = chip_direction_input,
+ .direction_output = chip_direction_output,
+ },
+};
+
+static int cs5535_gpio_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ int err = -EIO;
+ ulong mask_orig = mask;
+
+ /* There are two ways to get the GPIO base address; one is by
+ * fetching it from MSR_LBAR_GPIO, the other is by reading the
+ * PCI BAR info. The latter method is easier (especially across
+ * different architectures), so we'll stick with that for now. If
+ * it turns out to be unreliable in the face of crappy BIOSes, we
+ * can always go back to using MSRs.. */
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "can't fetch device resource info\n");
+ return err;
+ }
+
+ if (!devm_request_region(&pdev->dev, res->start, resource_size(res),
+ pdev->name)) {
+ dev_err(&pdev->dev, "can't request region\n");
+ return err;
+ }
+
+ /* set up the driver-specific struct */
+ cs5535_gpio_chip.base = res->start;
+ cs5535_gpio_chip.pdev = pdev;
+ spin_lock_init(&cs5535_gpio_chip.lock);
+
+ dev_info(&pdev->dev, "reserved resource region %pR\n", res);
+
+ /* mask out reserved pins */
+ mask &= 0x1F7FFFFF;
+
+ /* do not allow pin 28, Power Button, as there's special handling
+ * in the PMC needed. (note 12, p. 48) */
+ mask &= ~(1 << 28);
+
+ if (mask_orig != mask)
+ dev_info(&pdev->dev, "mask changed from 0x%08lX to 0x%08lX\n",
+ mask_orig, mask);
+
+ /* finally, register with the generic GPIO API */
+ return devm_gpiochip_add_data(&pdev->dev, &cs5535_gpio_chip.chip,
+ &cs5535_gpio_chip);
+}
+
+static struct platform_driver cs5535_gpio_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+ .probe = cs5535_gpio_probe,
+};
+
+module_platform_driver(cs5535_gpio_driver);
+
+MODULE_AUTHOR("Andres Salomon <dilinger@queued.net>");
+MODULE_DESCRIPTION("AMD CS5535/CS5536 GPIO driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/gpio/gpio-da9052.c b/drivers/gpio/gpio-da9052.c
new file mode 100644
index 0000000000..6f3905f1b8
--- /dev/null
+++ b/drivers/gpio/gpio-da9052.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * GPIO Driver for Dialog DA9052 PMICs.
+ *
+ * Copyright(c) 2011 Dialog Semiconductor Ltd.
+ *
+ * Author: David Dajun Chen <dchen@diasemi.com>
+ */
+#include <linux/fs.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/syscalls.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/da9052/da9052.h>
+#include <linux/mfd/da9052/pdata.h>
+#include <linux/mfd/da9052/reg.h>
+
+#define DA9052_INPUT 1
+#define DA9052_OUTPUT_OPENDRAIN 2
+#define DA9052_OUTPUT_PUSHPULL 3
+
+#define DA9052_SUPPLY_VDD_IO1 0
+
+#define DA9052_DEBOUNCING_OFF 0
+#define DA9052_DEBOUNCING_ON 1
+
+#define DA9052_OUTPUT_LOWLEVEL 0
+
+#define DA9052_ACTIVE_LOW 0
+#define DA9052_ACTIVE_HIGH 1
+
+#define DA9052_GPIO_MAX_PORTS_PER_REGISTER 8
+#define DA9052_GPIO_SHIFT_COUNT(no) (no%8)
+#define DA9052_GPIO_MASK_UPPER_NIBBLE 0xF0
+#define DA9052_GPIO_MASK_LOWER_NIBBLE 0x0F
+#define DA9052_GPIO_NIBBLE_SHIFT 4
+#define DA9052_IRQ_GPI0 16
+#define DA9052_GPIO_ODD_SHIFT 7
+#define DA9052_GPIO_EVEN_SHIFT 3
+
+struct da9052_gpio {
+ struct da9052 *da9052;
+ struct gpio_chip gp;
+};
+
+static unsigned char da9052_gpio_port_odd(unsigned offset)
+{
+ return offset % 2;
+}
+
+static int da9052_gpio_get(struct gpio_chip *gc, unsigned offset)
+{
+ struct da9052_gpio *gpio = gpiochip_get_data(gc);
+ int da9052_port_direction = 0;
+ int ret;
+
+ ret = da9052_reg_read(gpio->da9052,
+ DA9052_GPIO_0_1_REG + (offset >> 1));
+ if (ret < 0)
+ return ret;
+
+ if (da9052_gpio_port_odd(offset)) {
+ da9052_port_direction = ret & DA9052_GPIO_ODD_PORT_PIN;
+ da9052_port_direction >>= 4;
+ } else {
+ da9052_port_direction = ret & DA9052_GPIO_EVEN_PORT_PIN;
+ }
+
+ switch (da9052_port_direction) {
+ case DA9052_INPUT:
+ if (offset < DA9052_GPIO_MAX_PORTS_PER_REGISTER)
+ ret = da9052_reg_read(gpio->da9052,
+ DA9052_STATUS_C_REG);
+ else
+ ret = da9052_reg_read(gpio->da9052,
+ DA9052_STATUS_D_REG);
+ if (ret < 0)
+ return ret;
+ return !!(ret & (1 << DA9052_GPIO_SHIFT_COUNT(offset)));
+ case DA9052_OUTPUT_PUSHPULL:
+ if (da9052_gpio_port_odd(offset))
+ return !!(ret & DA9052_GPIO_ODD_PORT_MODE);
+ else
+ return !!(ret & DA9052_GPIO_EVEN_PORT_MODE);
+ default:
+ return -EINVAL;
+ }
+}
+
+static void da9052_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
+{
+ struct da9052_gpio *gpio = gpiochip_get_data(gc);
+ int ret;
+
+ if (da9052_gpio_port_odd(offset)) {
+ ret = da9052_reg_update(gpio->da9052, (offset >> 1) +
+ DA9052_GPIO_0_1_REG,
+ DA9052_GPIO_ODD_PORT_MODE,
+ value << DA9052_GPIO_ODD_SHIFT);
+ if (ret != 0)
+ dev_err(gpio->da9052->dev,
+ "Failed to updated gpio odd reg,%d",
+ ret);
+ } else {
+ ret = da9052_reg_update(gpio->da9052, (offset >> 1) +
+ DA9052_GPIO_0_1_REG,
+ DA9052_GPIO_EVEN_PORT_MODE,
+ value << DA9052_GPIO_EVEN_SHIFT);
+ if (ret != 0)
+ dev_err(gpio->da9052->dev,
+ "Failed to updated gpio even reg,%d",
+ ret);
+ }
+}
+
+static int da9052_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
+{
+ struct da9052_gpio *gpio = gpiochip_get_data(gc);
+ unsigned char register_value;
+ int ret;
+
+ /* Format: function - 2 bits type - 1 bit mode - 1 bit */
+ register_value = DA9052_INPUT | DA9052_ACTIVE_LOW << 2 |
+ DA9052_DEBOUNCING_ON << 3;
+
+ if (da9052_gpio_port_odd(offset))
+ ret = da9052_reg_update(gpio->da9052, (offset >> 1) +
+ DA9052_GPIO_0_1_REG,
+ DA9052_GPIO_MASK_UPPER_NIBBLE,
+ (register_value <<
+ DA9052_GPIO_NIBBLE_SHIFT));
+ else
+ ret = da9052_reg_update(gpio->da9052, (offset >> 1) +
+ DA9052_GPIO_0_1_REG,
+ DA9052_GPIO_MASK_LOWER_NIBBLE,
+ register_value);
+
+ return ret;
+}
+
+static int da9052_gpio_direction_output(struct gpio_chip *gc,
+ unsigned offset, int value)
+{
+ struct da9052_gpio *gpio = gpiochip_get_data(gc);
+ unsigned char register_value;
+ int ret;
+
+ /* Format: Function - 2 bits Type - 1 bit Mode - 1 bit */
+ register_value = DA9052_OUTPUT_PUSHPULL | DA9052_SUPPLY_VDD_IO1 << 2 |
+ value << 3;
+
+ if (da9052_gpio_port_odd(offset))
+ ret = da9052_reg_update(gpio->da9052, (offset >> 1) +
+ DA9052_GPIO_0_1_REG,
+ DA9052_GPIO_MASK_UPPER_NIBBLE,
+ (register_value <<
+ DA9052_GPIO_NIBBLE_SHIFT));
+ else
+ ret = da9052_reg_update(gpio->da9052, (offset >> 1) +
+ DA9052_GPIO_0_1_REG,
+ DA9052_GPIO_MASK_LOWER_NIBBLE,
+ register_value);
+
+ return ret;
+}
+
+static int da9052_gpio_to_irq(struct gpio_chip *gc, u32 offset)
+{
+ struct da9052_gpio *gpio = gpiochip_get_data(gc);
+ struct da9052 *da9052 = gpio->da9052;
+
+ int irq;
+
+ irq = regmap_irq_get_virq(da9052->irq_data, DA9052_IRQ_GPI0 + offset);
+
+ return irq;
+}
+
+static const struct gpio_chip reference_gp = {
+ .label = "da9052-gpio",
+ .owner = THIS_MODULE,
+ .get = da9052_gpio_get,
+ .set = da9052_gpio_set,
+ .direction_input = da9052_gpio_direction_input,
+ .direction_output = da9052_gpio_direction_output,
+ .to_irq = da9052_gpio_to_irq,
+ .can_sleep = true,
+ .ngpio = 16,
+ .base = -1,
+};
+
+static int da9052_gpio_probe(struct platform_device *pdev)
+{
+ struct da9052_gpio *gpio;
+ struct da9052_pdata *pdata;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->da9052 = dev_get_drvdata(pdev->dev.parent);
+ pdata = dev_get_platdata(gpio->da9052->dev);
+
+ gpio->gp = reference_gp;
+ if (pdata && pdata->gpio_base)
+ gpio->gp.base = pdata->gpio_base;
+
+ return devm_gpiochip_add_data(&pdev->dev, &gpio->gp, gpio);
+}
+
+static struct platform_driver da9052_gpio_driver = {
+ .probe = da9052_gpio_probe,
+ .driver = {
+ .name = "da9052-gpio",
+ },
+};
+
+module_platform_driver(da9052_gpio_driver);
+
+MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
+MODULE_DESCRIPTION("DA9052 GPIO Device Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9052-gpio");
diff --git a/drivers/gpio/gpio-da9055.c b/drivers/gpio/gpio-da9055.c
new file mode 100644
index 0000000000..49446a030f
--- /dev/null
+++ b/drivers/gpio/gpio-da9055.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * GPIO Driver for Dialog DA9055 PMICs.
+ *
+ * Copyright(c) 2012 Dialog Semiconductor Ltd.
+ *
+ * Author: David Dajun Chen <dchen@diasemi.com>
+ */
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/driver.h>
+
+#include <linux/mfd/da9055/core.h>
+#include <linux/mfd/da9055/reg.h>
+#include <linux/mfd/da9055/pdata.h>
+
+#define DA9055_VDD_IO 0x0
+#define DA9055_PUSH_PULL 0x3
+#define DA9055_ACT_LOW 0x0
+#define DA9055_GPI 0x1
+#define DA9055_PORT_MASK 0x3
+#define DA9055_PORT_SHIFT(offset) (4 * (offset % 2))
+
+#define DA9055_INPUT DA9055_GPI
+#define DA9055_OUTPUT DA9055_PUSH_PULL
+#define DA9055_IRQ_GPI0 3
+
+struct da9055_gpio {
+ struct da9055 *da9055;
+ struct gpio_chip gp;
+};
+
+static int da9055_gpio_get(struct gpio_chip *gc, unsigned offset)
+{
+ struct da9055_gpio *gpio = gpiochip_get_data(gc);
+ int gpio_direction = 0;
+ int ret;
+
+ /* Get GPIO direction */
+ ret = da9055_reg_read(gpio->da9055, (offset >> 1) + DA9055_REG_GPIO0_1);
+ if (ret < 0)
+ return ret;
+
+ gpio_direction = ret & (DA9055_PORT_MASK) << DA9055_PORT_SHIFT(offset);
+ gpio_direction >>= DA9055_PORT_SHIFT(offset);
+ switch (gpio_direction) {
+ case DA9055_INPUT:
+ ret = da9055_reg_read(gpio->da9055, DA9055_REG_STATUS_B);
+ if (ret < 0)
+ return ret;
+ break;
+ case DA9055_OUTPUT:
+ ret = da9055_reg_read(gpio->da9055, DA9055_REG_GPIO_MODE0_2);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret & (1 << offset);
+
+}
+
+static void da9055_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
+{
+ struct da9055_gpio *gpio = gpiochip_get_data(gc);
+
+ da9055_reg_update(gpio->da9055,
+ DA9055_REG_GPIO_MODE0_2,
+ 1 << offset,
+ value << offset);
+}
+
+static int da9055_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
+{
+ struct da9055_gpio *gpio = gpiochip_get_data(gc);
+ unsigned char reg_byte;
+
+ reg_byte = (DA9055_ACT_LOW | DA9055_GPI)
+ << DA9055_PORT_SHIFT(offset);
+
+ return da9055_reg_update(gpio->da9055, (offset >> 1) +
+ DA9055_REG_GPIO0_1,
+ DA9055_PORT_MASK <<
+ DA9055_PORT_SHIFT(offset),
+ reg_byte);
+}
+
+static int da9055_gpio_direction_output(struct gpio_chip *gc,
+ unsigned offset, int value)
+{
+ struct da9055_gpio *gpio = gpiochip_get_data(gc);
+ unsigned char reg_byte;
+ int ret;
+
+ reg_byte = (DA9055_VDD_IO | DA9055_PUSH_PULL)
+ << DA9055_PORT_SHIFT(offset);
+
+ ret = da9055_reg_update(gpio->da9055, (offset >> 1) +
+ DA9055_REG_GPIO0_1,
+ DA9055_PORT_MASK <<
+ DA9055_PORT_SHIFT(offset),
+ reg_byte);
+ if (ret < 0)
+ return ret;
+
+ da9055_gpio_set(gc, offset, value);
+
+ return 0;
+}
+
+static int da9055_gpio_to_irq(struct gpio_chip *gc, u32 offset)
+{
+ struct da9055_gpio *gpio = gpiochip_get_data(gc);
+ struct da9055 *da9055 = gpio->da9055;
+
+ return regmap_irq_get_virq(da9055->irq_data,
+ DA9055_IRQ_GPI0 + offset);
+}
+
+static const struct gpio_chip reference_gp = {
+ .label = "da9055-gpio",
+ .owner = THIS_MODULE,
+ .get = da9055_gpio_get,
+ .set = da9055_gpio_set,
+ .direction_input = da9055_gpio_direction_input,
+ .direction_output = da9055_gpio_direction_output,
+ .to_irq = da9055_gpio_to_irq,
+ .can_sleep = true,
+ .ngpio = 3,
+ .base = -1,
+};
+
+static int da9055_gpio_probe(struct platform_device *pdev)
+{
+ struct da9055_gpio *gpio;
+ struct da9055_pdata *pdata;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->da9055 = dev_get_drvdata(pdev->dev.parent);
+ pdata = dev_get_platdata(gpio->da9055->dev);
+
+ gpio->gp = reference_gp;
+ if (pdata && pdata->gpio_base)
+ gpio->gp.base = pdata->gpio_base;
+
+ return devm_gpiochip_add_data(&pdev->dev, &gpio->gp, gpio);
+}
+
+static struct platform_driver da9055_gpio_driver = {
+ .probe = da9055_gpio_probe,
+ .driver = {
+ .name = "da9055-gpio",
+ },
+};
+
+static int __init da9055_gpio_init(void)
+{
+ return platform_driver_register(&da9055_gpio_driver);
+}
+subsys_initcall(da9055_gpio_init);
+
+static void __exit da9055_gpio_exit(void)
+{
+ platform_driver_unregister(&da9055_gpio_driver);
+}
+module_exit(da9055_gpio_exit);
+
+MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
+MODULE_DESCRIPTION("DA9055 GPIO Device Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9055-gpio");
diff --git a/drivers/gpio/gpio-davinci.c b/drivers/gpio/gpio-davinci.c
new file mode 100644
index 0000000000..8db5717bda
--- /dev/null
+++ b/drivers/gpio/gpio-davinci.c
@@ -0,0 +1,734 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * TI DaVinci GPIO Support
+ *
+ * Copyright (c) 2006-2007 David Brownell
+ * Copyright (c) 2007, MontaVista Software, Inc. <source@mvista.com>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/gpio-davinci.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/spinlock.h>
+#include <linux/pm_runtime.h>
+
+#define MAX_REGS_BANKS 5
+#define MAX_INT_PER_BANK 32
+
+struct davinci_gpio_regs {
+ u32 dir;
+ u32 out_data;
+ u32 set_data;
+ u32 clr_data;
+ u32 in_data;
+ u32 set_rising;
+ u32 clr_rising;
+ u32 set_falling;
+ u32 clr_falling;
+ u32 intstat;
+};
+
+typedef struct irq_chip *(*gpio_get_irq_chip_cb_t)(unsigned int irq);
+
+#define BINTEN 0x8 /* GPIO Interrupt Per-Bank Enable Register */
+
+static void __iomem *gpio_base;
+static unsigned int offset_array[5] = {0x10, 0x38, 0x60, 0x88, 0xb0};
+
+struct davinci_gpio_irq_data {
+ void __iomem *regs;
+ struct davinci_gpio_controller *chip;
+ int bank_num;
+};
+
+struct davinci_gpio_controller {
+ struct gpio_chip chip;
+ struct irq_domain *irq_domain;
+ /* Serialize access to GPIO registers */
+ spinlock_t lock;
+ void __iomem *regs[MAX_REGS_BANKS];
+ int gpio_unbanked;
+ int irqs[MAX_INT_PER_BANK];
+ struct davinci_gpio_regs context[MAX_REGS_BANKS];
+ u32 binten_context;
+};
+
+static inline u32 __gpio_mask(unsigned gpio)
+{
+ return 1 << (gpio % 32);
+}
+
+static inline struct davinci_gpio_regs __iomem *irq2regs(struct irq_data *d)
+{
+ struct davinci_gpio_regs __iomem *g;
+
+ g = (__force struct davinci_gpio_regs __iomem *)irq_data_get_irq_chip_data(d);
+
+ return g;
+}
+
+static int davinci_gpio_irq_setup(struct platform_device *pdev);
+
+/*--------------------------------------------------------------------------*/
+
+/* board setup code *MUST* setup pinmux and enable the GPIO clock. */
+static inline int __davinci_direction(struct gpio_chip *chip,
+ unsigned offset, bool out, int value)
+{
+ struct davinci_gpio_controller *d = gpiochip_get_data(chip);
+ struct davinci_gpio_regs __iomem *g;
+ unsigned long flags;
+ u32 temp;
+ int bank = offset / 32;
+ u32 mask = __gpio_mask(offset);
+
+ g = d->regs[bank];
+ spin_lock_irqsave(&d->lock, flags);
+ temp = readl_relaxed(&g->dir);
+ if (out) {
+ temp &= ~mask;
+ writel_relaxed(mask, value ? &g->set_data : &g->clr_data);
+ } else {
+ temp |= mask;
+ }
+ writel_relaxed(temp, &g->dir);
+ spin_unlock_irqrestore(&d->lock, flags);
+
+ return 0;
+}
+
+static int davinci_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ return __davinci_direction(chip, offset, false, 0);
+}
+
+static int
+davinci_direction_out(struct gpio_chip *chip, unsigned offset, int value)
+{
+ return __davinci_direction(chip, offset, true, value);
+}
+
+/*
+ * Read the pin's value (works even if it's set up as output);
+ * returns zero/nonzero.
+ *
+ * Note that changes are synched to the GPIO clock, so reading values back
+ * right after you've set them may give old values.
+ */
+static int davinci_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct davinci_gpio_controller *d = gpiochip_get_data(chip);
+ struct davinci_gpio_regs __iomem *g;
+ int bank = offset / 32;
+
+ g = d->regs[bank];
+
+ return !!(__gpio_mask(offset) & readl_relaxed(&g->in_data));
+}
+
+/*
+ * Assuming the pin is muxed as a gpio output, set its output value.
+ */
+static void
+davinci_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct davinci_gpio_controller *d = gpiochip_get_data(chip);
+ struct davinci_gpio_regs __iomem *g;
+ int bank = offset / 32;
+
+ g = d->regs[bank];
+
+ writel_relaxed(__gpio_mask(offset),
+ value ? &g->set_data : &g->clr_data);
+}
+
+static struct davinci_gpio_platform_data *
+davinci_gpio_get_pdata(struct platform_device *pdev)
+{
+ struct device_node *dn = pdev->dev.of_node;
+ struct davinci_gpio_platform_data *pdata;
+ int ret;
+ u32 val;
+
+ if (!IS_ENABLED(CONFIG_OF) || !pdev->dev.of_node)
+ return dev_get_platdata(&pdev->dev);
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return NULL;
+
+ ret = of_property_read_u32(dn, "ti,ngpio", &val);
+ if (ret)
+ goto of_err;
+
+ pdata->ngpio = val;
+
+ ret = of_property_read_u32(dn, "ti,davinci-gpio-unbanked", &val);
+ if (ret)
+ goto of_err;
+
+ pdata->gpio_unbanked = val;
+
+ return pdata;
+
+of_err:
+ dev_err(&pdev->dev, "Populating pdata from DT failed: err %d\n", ret);
+ return NULL;
+}
+
+static int davinci_gpio_probe(struct platform_device *pdev)
+{
+ int bank, i, ret = 0;
+ unsigned int ngpio, nbank, nirq;
+ struct davinci_gpio_controller *chips;
+ struct davinci_gpio_platform_data *pdata;
+ struct device *dev = &pdev->dev;
+
+ pdata = davinci_gpio_get_pdata(pdev);
+ if (!pdata) {
+ dev_err(dev, "No platform data found\n");
+ return -EINVAL;
+ }
+
+ dev->platform_data = pdata;
+
+ /*
+ * The gpio banks conceptually expose a segmented bitmap,
+ * and "ngpio" is one more than the largest zero-based
+ * bit index that's valid.
+ */
+ ngpio = pdata->ngpio;
+ if (ngpio == 0) {
+ dev_err(dev, "How many GPIOs?\n");
+ return -EINVAL;
+ }
+
+ /*
+ * If there are unbanked interrupts then the number of
+ * interrupts is equal to number of gpios else all are banked so
+ * number of interrupts is equal to number of banks(each with 16 gpios)
+ */
+ if (pdata->gpio_unbanked)
+ nirq = pdata->gpio_unbanked;
+ else
+ nirq = DIV_ROUND_UP(ngpio, 16);
+
+ chips = devm_kzalloc(dev, sizeof(*chips), GFP_KERNEL);
+ if (!chips)
+ return -ENOMEM;
+
+ gpio_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gpio_base))
+ return PTR_ERR(gpio_base);
+
+ for (i = 0; i < nirq; i++) {
+ chips->irqs[i] = platform_get_irq(pdev, i);
+ if (chips->irqs[i] < 0)
+ return chips->irqs[i];
+ }
+
+ chips->chip.label = dev_name(dev);
+
+ chips->chip.direction_input = davinci_direction_in;
+ chips->chip.get = davinci_gpio_get;
+ chips->chip.direction_output = davinci_direction_out;
+ chips->chip.set = davinci_gpio_set;
+
+ chips->chip.ngpio = ngpio;
+ chips->chip.base = pdata->no_auto_base ? pdata->base : -1;
+
+#ifdef CONFIG_OF_GPIO
+ chips->chip.parent = dev;
+ chips->chip.request = gpiochip_generic_request;
+ chips->chip.free = gpiochip_generic_free;
+#endif
+ spin_lock_init(&chips->lock);
+
+ nbank = DIV_ROUND_UP(ngpio, 32);
+ for (bank = 0; bank < nbank; bank++)
+ chips->regs[bank] = gpio_base + offset_array[bank];
+
+ ret = devm_gpiochip_add_data(dev, &chips->chip, chips);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, chips);
+ ret = davinci_gpio_irq_setup(pdev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+/*
+ * We expect irqs will normally be set up as input pins, but they can also be
+ * used as output pins ... which is convenient for testing.
+ *
+ * NOTE: The first few GPIOs also have direct INTC hookups in addition
+ * to their GPIOBNK0 irq, with a bit less overhead.
+ *
+ * All those INTC hookups (direct, plus several IRQ banks) can also
+ * serve as EDMA event triggers.
+ */
+
+static void gpio_irq_disable(struct irq_data *d)
+{
+ struct davinci_gpio_regs __iomem *g = irq2regs(d);
+ uintptr_t mask = (uintptr_t)irq_data_get_irq_handler_data(d);
+
+ writel_relaxed(mask, &g->clr_falling);
+ writel_relaxed(mask, &g->clr_rising);
+}
+
+static void gpio_irq_enable(struct irq_data *d)
+{
+ struct davinci_gpio_regs __iomem *g = irq2regs(d);
+ uintptr_t mask = (uintptr_t)irq_data_get_irq_handler_data(d);
+ unsigned status = irqd_get_trigger_type(d);
+
+ status &= IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING;
+ if (!status)
+ status = IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING;
+
+ if (status & IRQ_TYPE_EDGE_FALLING)
+ writel_relaxed(mask, &g->set_falling);
+ if (status & IRQ_TYPE_EDGE_RISING)
+ writel_relaxed(mask, &g->set_rising);
+}
+
+static int gpio_irq_type(struct irq_data *d, unsigned trigger)
+{
+ if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
+ return -EINVAL;
+
+ return 0;
+}
+
+static struct irq_chip gpio_irqchip = {
+ .name = "GPIO",
+ .irq_enable = gpio_irq_enable,
+ .irq_disable = gpio_irq_disable,
+ .irq_set_type = gpio_irq_type,
+ .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE,
+};
+
+static void gpio_irq_handler(struct irq_desc *desc)
+{
+ struct davinci_gpio_regs __iomem *g;
+ u32 mask = 0xffff;
+ int bank_num;
+ struct davinci_gpio_controller *d;
+ struct davinci_gpio_irq_data *irqdata;
+
+ irqdata = (struct davinci_gpio_irq_data *)irq_desc_get_handler_data(desc);
+ bank_num = irqdata->bank_num;
+ g = irqdata->regs;
+ d = irqdata->chip;
+
+ /* we only care about one bank */
+ if ((bank_num % 2) == 1)
+ mask <<= 16;
+
+ /* temporarily mask (level sensitive) parent IRQ */
+ chained_irq_enter(irq_desc_get_chip(desc), desc);
+ while (1) {
+ u32 status;
+ int bit;
+ irq_hw_number_t hw_irq;
+
+ /* ack any irqs */
+ status = readl_relaxed(&g->intstat) & mask;
+ if (!status)
+ break;
+ writel_relaxed(status, &g->intstat);
+
+ /* now demux them to the right lowlevel handler */
+
+ while (status) {
+ bit = __ffs(status);
+ status &= ~BIT(bit);
+ /* Max number of gpios per controller is 144 so
+ * hw_irq will be in [0..143]
+ */
+ hw_irq = (bank_num / 2) * 32 + bit;
+
+ generic_handle_domain_irq(d->irq_domain, hw_irq);
+ }
+ }
+ chained_irq_exit(irq_desc_get_chip(desc), desc);
+ /* now it may re-trigger */
+}
+
+static int gpio_to_irq_banked(struct gpio_chip *chip, unsigned offset)
+{
+ struct davinci_gpio_controller *d = gpiochip_get_data(chip);
+
+ if (d->irq_domain)
+ return irq_create_mapping(d->irq_domain, offset);
+ else
+ return -ENXIO;
+}
+
+static int gpio_to_irq_unbanked(struct gpio_chip *chip, unsigned offset)
+{
+ struct davinci_gpio_controller *d = gpiochip_get_data(chip);
+
+ /*
+ * NOTE: we assume for now that only irqs in the first gpio_chip
+ * can provide direct-mapped IRQs to AINTC (up to 32 GPIOs).
+ */
+ if (offset < d->gpio_unbanked)
+ return d->irqs[offset];
+ else
+ return -ENODEV;
+}
+
+static int gpio_irq_type_unbanked(struct irq_data *data, unsigned trigger)
+{
+ struct davinci_gpio_controller *d;
+ struct davinci_gpio_regs __iomem *g;
+ u32 mask, i;
+
+ d = (struct davinci_gpio_controller *)irq_data_get_irq_handler_data(data);
+ g = (struct davinci_gpio_regs __iomem *)d->regs[0];
+ for (i = 0; i < MAX_INT_PER_BANK; i++)
+ if (data->irq == d->irqs[i])
+ break;
+
+ if (i == MAX_INT_PER_BANK)
+ return -EINVAL;
+
+ mask = __gpio_mask(i);
+
+ if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
+ return -EINVAL;
+
+ writel_relaxed(mask, (trigger & IRQ_TYPE_EDGE_FALLING)
+ ? &g->set_falling : &g->clr_falling);
+ writel_relaxed(mask, (trigger & IRQ_TYPE_EDGE_RISING)
+ ? &g->set_rising : &g->clr_rising);
+
+ return 0;
+}
+
+static int
+davinci_gpio_irq_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hw)
+{
+ struct davinci_gpio_controller *chips =
+ (struct davinci_gpio_controller *)d->host_data;
+ struct davinci_gpio_regs __iomem *g = chips->regs[hw / 32];
+
+ irq_set_chip_and_handler_name(irq, &gpio_irqchip, handle_simple_irq,
+ "davinci_gpio");
+ irq_set_irq_type(irq, IRQ_TYPE_NONE);
+ irq_set_chip_data(irq, (__force void *)g);
+ irq_set_handler_data(irq, (void *)(uintptr_t)__gpio_mask(hw));
+
+ return 0;
+}
+
+static const struct irq_domain_ops davinci_gpio_irq_ops = {
+ .map = davinci_gpio_irq_map,
+ .xlate = irq_domain_xlate_onetwocell,
+};
+
+static struct irq_chip *davinci_gpio_get_irq_chip(unsigned int irq)
+{
+ static struct irq_chip_type gpio_unbanked;
+
+ gpio_unbanked = *irq_data_get_chip_type(irq_get_irq_data(irq));
+
+ return &gpio_unbanked.chip;
+};
+
+static struct irq_chip *keystone_gpio_get_irq_chip(unsigned int irq)
+{
+ static struct irq_chip gpio_unbanked;
+
+ gpio_unbanked = *irq_get_chip(irq);
+ return &gpio_unbanked;
+};
+
+static const struct of_device_id davinci_gpio_ids[];
+
+/*
+ * NOTE: for suspend/resume, probably best to make a platform_device with
+ * suspend_late/resume_resume calls hooking into results of the set_wake()
+ * calls ... so if no gpios are wakeup events the clock can be disabled,
+ * with outputs left at previously set levels, and so that VDD3P3V.IOPWDN0
+ * (dm6446) can be set appropriately for GPIOV33 pins.
+ */
+
+static int davinci_gpio_irq_setup(struct platform_device *pdev)
+{
+ unsigned gpio, bank;
+ int irq;
+ int ret;
+ struct clk *clk;
+ u32 binten = 0;
+ unsigned ngpio;
+ struct device *dev = &pdev->dev;
+ struct davinci_gpio_controller *chips = platform_get_drvdata(pdev);
+ struct davinci_gpio_platform_data *pdata = dev->platform_data;
+ struct davinci_gpio_regs __iomem *g;
+ struct irq_domain *irq_domain = NULL;
+ const struct of_device_id *match;
+ struct irq_chip *irq_chip;
+ struct davinci_gpio_irq_data *irqdata;
+ gpio_get_irq_chip_cb_t gpio_get_irq_chip;
+
+ /*
+ * Use davinci_gpio_get_irq_chip by default to handle non DT cases
+ */
+ gpio_get_irq_chip = davinci_gpio_get_irq_chip;
+ match = of_match_device(of_match_ptr(davinci_gpio_ids),
+ dev);
+ if (match)
+ gpio_get_irq_chip = (gpio_get_irq_chip_cb_t)match->data;
+
+ ngpio = pdata->ngpio;
+
+ clk = devm_clk_get(dev, "gpio");
+ if (IS_ERR(clk)) {
+ dev_err(dev, "Error %ld getting gpio clock\n", PTR_ERR(clk));
+ return PTR_ERR(clk);
+ }
+
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ return ret;
+
+ if (!pdata->gpio_unbanked) {
+ irq = devm_irq_alloc_descs(dev, -1, 0, ngpio, 0);
+ if (irq < 0) {
+ dev_err(dev, "Couldn't allocate IRQ numbers\n");
+ clk_disable_unprepare(clk);
+ return irq;
+ }
+
+ irq_domain = irq_domain_add_legacy(dev->of_node, ngpio, irq, 0,
+ &davinci_gpio_irq_ops,
+ chips);
+ if (!irq_domain) {
+ dev_err(dev, "Couldn't register an IRQ domain\n");
+ clk_disable_unprepare(clk);
+ return -ENODEV;
+ }
+ }
+
+ /*
+ * Arrange gpiod_to_irq() support, handling either direct IRQs or
+ * banked IRQs. Having GPIOs in the first GPIO bank use direct
+ * IRQs, while the others use banked IRQs, would need some setup
+ * tweaks to recognize hardware which can do that.
+ */
+ chips->chip.to_irq = gpio_to_irq_banked;
+ chips->irq_domain = irq_domain;
+
+ /*
+ * AINTC can handle direct/unbanked IRQs for GPIOs, with the GPIO
+ * controller only handling trigger modes. We currently assume no
+ * IRQ mux conflicts; gpio_irq_type_unbanked() is only for GPIOs.
+ */
+ if (pdata->gpio_unbanked) {
+ /* pass "bank 0" GPIO IRQs to AINTC */
+ chips->chip.to_irq = gpio_to_irq_unbanked;
+ chips->gpio_unbanked = pdata->gpio_unbanked;
+ binten = GENMASK(pdata->gpio_unbanked / 16, 0);
+
+ /* AINTC handles mask/unmask; GPIO handles triggering */
+ irq = chips->irqs[0];
+ irq_chip = gpio_get_irq_chip(irq);
+ irq_chip->name = "GPIO-AINTC";
+ irq_chip->irq_set_type = gpio_irq_type_unbanked;
+
+ /* default trigger: both edges */
+ g = chips->regs[0];
+ writel_relaxed(~0, &g->set_falling);
+ writel_relaxed(~0, &g->set_rising);
+
+ /* set the direct IRQs up to use that irqchip */
+ for (gpio = 0; gpio < pdata->gpio_unbanked; gpio++) {
+ irq_set_chip(chips->irqs[gpio], irq_chip);
+ irq_set_handler_data(chips->irqs[gpio], chips);
+ irq_set_status_flags(chips->irqs[gpio],
+ IRQ_TYPE_EDGE_BOTH);
+ }
+
+ goto done;
+ }
+
+ /*
+ * Or, AINTC can handle IRQs for banks of 16 GPIO IRQs, which we
+ * then chain through our own handler.
+ */
+ for (gpio = 0, bank = 0; gpio < ngpio; bank++, gpio += 16) {
+ /* disabled by default, enabled only as needed
+ * There are register sets for 32 GPIOs. 2 banks of 16
+ * GPIOs are covered by each set of registers hence divide by 2
+ */
+ g = chips->regs[bank / 2];
+ writel_relaxed(~0, &g->clr_falling);
+ writel_relaxed(~0, &g->clr_rising);
+
+ /*
+ * Each chip handles 32 gpios, and each irq bank consists of 16
+ * gpio irqs. Pass the irq bank's corresponding controller to
+ * the chained irq handler.
+ */
+ irqdata = devm_kzalloc(&pdev->dev,
+ sizeof(struct
+ davinci_gpio_irq_data),
+ GFP_KERNEL);
+ if (!irqdata) {
+ clk_disable_unprepare(clk);
+ return -ENOMEM;
+ }
+
+ irqdata->regs = g;
+ irqdata->bank_num = bank;
+ irqdata->chip = chips;
+
+ irq_set_chained_handler_and_data(chips->irqs[bank],
+ gpio_irq_handler, irqdata);
+
+ binten |= BIT(bank);
+ }
+
+done:
+ /*
+ * BINTEN -- per-bank interrupt enable. genirq would also let these
+ * bits be set/cleared dynamically.
+ */
+ writel_relaxed(binten, gpio_base + BINTEN);
+
+ return 0;
+}
+
+static void davinci_gpio_save_context(struct davinci_gpio_controller *chips,
+ u32 nbank)
+{
+ struct davinci_gpio_regs __iomem *g;
+ struct davinci_gpio_regs *context;
+ u32 bank;
+ void __iomem *base;
+
+ base = chips->regs[0] - offset_array[0];
+ chips->binten_context = readl_relaxed(base + BINTEN);
+
+ for (bank = 0; bank < nbank; bank++) {
+ g = chips->regs[bank];
+ context = &chips->context[bank];
+ context->dir = readl_relaxed(&g->dir);
+ context->set_data = readl_relaxed(&g->set_data);
+ context->set_rising = readl_relaxed(&g->set_rising);
+ context->set_falling = readl_relaxed(&g->set_falling);
+ }
+
+ /* Clear all interrupt status registers */
+ writel_relaxed(GENMASK(31, 0), &g->intstat);
+}
+
+static void davinci_gpio_restore_context(struct davinci_gpio_controller *chips,
+ u32 nbank)
+{
+ struct davinci_gpio_regs __iomem *g;
+ struct davinci_gpio_regs *context;
+ u32 bank;
+ void __iomem *base;
+
+ base = chips->regs[0] - offset_array[0];
+
+ if (readl_relaxed(base + BINTEN) != chips->binten_context)
+ writel_relaxed(chips->binten_context, base + BINTEN);
+
+ for (bank = 0; bank < nbank; bank++) {
+ g = chips->regs[bank];
+ context = &chips->context[bank];
+ if (readl_relaxed(&g->dir) != context->dir)
+ writel_relaxed(context->dir, &g->dir);
+ if (readl_relaxed(&g->set_data) != context->set_data)
+ writel_relaxed(context->set_data, &g->set_data);
+ if (readl_relaxed(&g->set_rising) != context->set_rising)
+ writel_relaxed(context->set_rising, &g->set_rising);
+ if (readl_relaxed(&g->set_falling) != context->set_falling)
+ writel_relaxed(context->set_falling, &g->set_falling);
+ }
+}
+
+static int davinci_gpio_suspend(struct device *dev)
+{
+ struct davinci_gpio_controller *chips = dev_get_drvdata(dev);
+ struct davinci_gpio_platform_data *pdata = dev_get_platdata(dev);
+ u32 nbank = DIV_ROUND_UP(pdata->ngpio, 32);
+
+ davinci_gpio_save_context(chips, nbank);
+
+ return 0;
+}
+
+static int davinci_gpio_resume(struct device *dev)
+{
+ struct davinci_gpio_controller *chips = dev_get_drvdata(dev);
+ struct davinci_gpio_platform_data *pdata = dev_get_platdata(dev);
+ u32 nbank = DIV_ROUND_UP(pdata->ngpio, 32);
+
+ davinci_gpio_restore_context(chips, nbank);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(davinci_gpio_dev_pm_ops, davinci_gpio_suspend,
+ davinci_gpio_resume);
+
+static const struct of_device_id davinci_gpio_ids[] = {
+ { .compatible = "ti,keystone-gpio", keystone_gpio_get_irq_chip},
+ { .compatible = "ti,am654-gpio", keystone_gpio_get_irq_chip},
+ { .compatible = "ti,dm6441-gpio", davinci_gpio_get_irq_chip},
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, davinci_gpio_ids);
+
+static struct platform_driver davinci_gpio_driver = {
+ .probe = davinci_gpio_probe,
+ .driver = {
+ .name = "davinci_gpio",
+ .pm = pm_sleep_ptr(&davinci_gpio_dev_pm_ops),
+ .of_match_table = of_match_ptr(davinci_gpio_ids),
+ },
+};
+
+/*
+ * GPIO driver registration needs to be done before machine_init functions
+ * access GPIO. Hence davinci_gpio_drv_reg() is a postcore_initcall.
+ */
+static int __init davinci_gpio_drv_reg(void)
+{
+ return platform_driver_register(&davinci_gpio_driver);
+}
+postcore_initcall(davinci_gpio_drv_reg);
+
+static void __exit davinci_gpio_exit(void)
+{
+ platform_driver_unregister(&davinci_gpio_driver);
+}
+module_exit(davinci_gpio_exit);
+
+MODULE_AUTHOR("Jan Kotas <jank@cadence.com>");
+MODULE_DESCRIPTION("DAVINCI GPIO driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio-davinci");
diff --git a/drivers/gpio/gpio-dln2.c b/drivers/gpio/gpio-dln2.c
new file mode 100644
index 0000000000..71fa437b49
--- /dev/null
+++ b/drivers/gpio/gpio-dln2.c
@@ -0,0 +1,525 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the Diolan DLN-2 USB-GPIO adapter
+ *
+ * Copyright (c) 2014 Intel Corporation
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/irqdomain.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/dln2.h>
+
+#define DLN2_GPIO_ID 0x01
+
+#define DLN2_GPIO_GET_PIN_COUNT DLN2_CMD(0x01, DLN2_GPIO_ID)
+#define DLN2_GPIO_SET_DEBOUNCE DLN2_CMD(0x04, DLN2_GPIO_ID)
+#define DLN2_GPIO_GET_DEBOUNCE DLN2_CMD(0x05, DLN2_GPIO_ID)
+#define DLN2_GPIO_PORT_GET_VAL DLN2_CMD(0x06, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_GET_VAL DLN2_CMD(0x0B, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_SET_OUT_VAL DLN2_CMD(0x0C, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_GET_OUT_VAL DLN2_CMD(0x0D, DLN2_GPIO_ID)
+#define DLN2_GPIO_CONDITION_MET_EV DLN2_CMD(0x0F, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_ENABLE DLN2_CMD(0x10, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_DISABLE DLN2_CMD(0x11, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_SET_DIRECTION DLN2_CMD(0x13, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_GET_DIRECTION DLN2_CMD(0x14, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_SET_EVENT_CFG DLN2_CMD(0x1E, DLN2_GPIO_ID)
+#define DLN2_GPIO_PIN_GET_EVENT_CFG DLN2_CMD(0x1F, DLN2_GPIO_ID)
+
+#define DLN2_GPIO_EVENT_NONE 0
+#define DLN2_GPIO_EVENT_CHANGE 1
+#define DLN2_GPIO_EVENT_LVL_HIGH 2
+#define DLN2_GPIO_EVENT_LVL_LOW 3
+#define DLN2_GPIO_EVENT_CHANGE_RISING 0x11
+#define DLN2_GPIO_EVENT_CHANGE_FALLING 0x21
+#define DLN2_GPIO_EVENT_MASK 0x0F
+
+#define DLN2_GPIO_MAX_PINS 32
+
+struct dln2_gpio {
+ struct platform_device *pdev;
+ struct gpio_chip gpio;
+
+ /*
+ * Cache pin direction to save us one transfer, since the hardware has
+ * separate commands to read the in and out values.
+ */
+ DECLARE_BITMAP(output_enabled, DLN2_GPIO_MAX_PINS);
+
+ /* active IRQs - not synced to hardware */
+ DECLARE_BITMAP(unmasked_irqs, DLN2_GPIO_MAX_PINS);
+ /* active IRQS - synced to hardware */
+ DECLARE_BITMAP(enabled_irqs, DLN2_GPIO_MAX_PINS);
+ int irq_type[DLN2_GPIO_MAX_PINS];
+ struct mutex irq_lock;
+};
+
+struct dln2_gpio_pin {
+ __le16 pin;
+};
+
+struct dln2_gpio_pin_val {
+ __le16 pin __packed;
+ u8 value;
+};
+
+static int dln2_gpio_get_pin_count(struct platform_device *pdev)
+{
+ int ret;
+ __le16 count;
+ int len = sizeof(count);
+
+ ret = dln2_transfer_rx(pdev, DLN2_GPIO_GET_PIN_COUNT, &count, &len);
+ if (ret < 0)
+ return ret;
+ if (len < sizeof(count))
+ return -EPROTO;
+
+ return le16_to_cpu(count);
+}
+
+static int dln2_gpio_pin_cmd(struct dln2_gpio *dln2, int cmd, unsigned pin)
+{
+ struct dln2_gpio_pin req = {
+ .pin = cpu_to_le16(pin),
+ };
+
+ return dln2_transfer_tx(dln2->pdev, cmd, &req, sizeof(req));
+}
+
+static int dln2_gpio_pin_val(struct dln2_gpio *dln2, int cmd, unsigned int pin)
+{
+ int ret;
+ struct dln2_gpio_pin req = {
+ .pin = cpu_to_le16(pin),
+ };
+ struct dln2_gpio_pin_val rsp;
+ int len = sizeof(rsp);
+
+ ret = dln2_transfer(dln2->pdev, cmd, &req, sizeof(req), &rsp, &len);
+ if (ret < 0)
+ return ret;
+ if (len < sizeof(rsp) || req.pin != rsp.pin)
+ return -EPROTO;
+
+ return rsp.value;
+}
+
+static int dln2_gpio_pin_get_in_val(struct dln2_gpio *dln2, unsigned int pin)
+{
+ int ret;
+
+ ret = dln2_gpio_pin_val(dln2, DLN2_GPIO_PIN_GET_VAL, pin);
+ if (ret < 0)
+ return ret;
+ return !!ret;
+}
+
+static int dln2_gpio_pin_get_out_val(struct dln2_gpio *dln2, unsigned int pin)
+{
+ int ret;
+
+ ret = dln2_gpio_pin_val(dln2, DLN2_GPIO_PIN_GET_OUT_VAL, pin);
+ if (ret < 0)
+ return ret;
+ return !!ret;
+}
+
+static int dln2_gpio_pin_set_out_val(struct dln2_gpio *dln2,
+ unsigned int pin, int value)
+{
+ struct dln2_gpio_pin_val req = {
+ .pin = cpu_to_le16(pin),
+ .value = value,
+ };
+
+ return dln2_transfer_tx(dln2->pdev, DLN2_GPIO_PIN_SET_OUT_VAL, &req,
+ sizeof(req));
+}
+
+#define DLN2_GPIO_DIRECTION_IN 0
+#define DLN2_GPIO_DIRECTION_OUT 1
+
+static int dln2_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct dln2_gpio *dln2 = gpiochip_get_data(chip);
+ struct dln2_gpio_pin req = {
+ .pin = cpu_to_le16(offset),
+ };
+ struct dln2_gpio_pin_val rsp;
+ int len = sizeof(rsp);
+ int ret;
+
+ ret = dln2_gpio_pin_cmd(dln2, DLN2_GPIO_PIN_ENABLE, offset);
+ if (ret < 0)
+ return ret;
+
+ /* cache the pin direction */
+ ret = dln2_transfer(dln2->pdev, DLN2_GPIO_PIN_GET_DIRECTION,
+ &req, sizeof(req), &rsp, &len);
+ if (ret < 0)
+ return ret;
+ if (len < sizeof(rsp) || req.pin != rsp.pin) {
+ ret = -EPROTO;
+ goto out_disable;
+ }
+
+ switch (rsp.value) {
+ case DLN2_GPIO_DIRECTION_IN:
+ clear_bit(offset, dln2->output_enabled);
+ return 0;
+ case DLN2_GPIO_DIRECTION_OUT:
+ set_bit(offset, dln2->output_enabled);
+ return 0;
+ default:
+ ret = -EPROTO;
+ goto out_disable;
+ }
+
+out_disable:
+ dln2_gpio_pin_cmd(dln2, DLN2_GPIO_PIN_DISABLE, offset);
+ return ret;
+}
+
+static void dln2_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+ struct dln2_gpio *dln2 = gpiochip_get_data(chip);
+
+ dln2_gpio_pin_cmd(dln2, DLN2_GPIO_PIN_DISABLE, offset);
+}
+
+static int dln2_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+ struct dln2_gpio *dln2 = gpiochip_get_data(chip);
+
+ if (test_bit(offset, dln2->output_enabled))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int dln2_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct dln2_gpio *dln2 = gpiochip_get_data(chip);
+ int dir;
+
+ dir = dln2_gpio_get_direction(chip, offset);
+ if (dir < 0)
+ return dir;
+
+ if (dir == GPIO_LINE_DIRECTION_IN)
+ return dln2_gpio_pin_get_in_val(dln2, offset);
+
+ return dln2_gpio_pin_get_out_val(dln2, offset);
+}
+
+static void dln2_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct dln2_gpio *dln2 = gpiochip_get_data(chip);
+
+ dln2_gpio_pin_set_out_val(dln2, offset, value);
+}
+
+static int dln2_gpio_set_direction(struct gpio_chip *chip, unsigned offset,
+ unsigned dir)
+{
+ struct dln2_gpio *dln2 = gpiochip_get_data(chip);
+ struct dln2_gpio_pin_val req = {
+ .pin = cpu_to_le16(offset),
+ .value = dir,
+ };
+ int ret;
+
+ ret = dln2_transfer_tx(dln2->pdev, DLN2_GPIO_PIN_SET_DIRECTION,
+ &req, sizeof(req));
+ if (ret < 0)
+ return ret;
+
+ if (dir == DLN2_GPIO_DIRECTION_OUT)
+ set_bit(offset, dln2->output_enabled);
+ else
+ clear_bit(offset, dln2->output_enabled);
+
+ return ret;
+}
+
+static int dln2_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ return dln2_gpio_set_direction(chip, offset, DLN2_GPIO_DIRECTION_IN);
+}
+
+static int dln2_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct dln2_gpio *dln2 = gpiochip_get_data(chip);
+ int ret;
+
+ ret = dln2_gpio_pin_set_out_val(dln2, offset, value);
+ if (ret < 0)
+ return ret;
+
+ return dln2_gpio_set_direction(chip, offset, DLN2_GPIO_DIRECTION_OUT);
+}
+
+static int dln2_gpio_set_config(struct gpio_chip *chip, unsigned offset,
+ unsigned long config)
+{
+ struct dln2_gpio *dln2 = gpiochip_get_data(chip);
+ __le32 duration;
+
+ if (pinconf_to_config_param(config) != PIN_CONFIG_INPUT_DEBOUNCE)
+ return -ENOTSUPP;
+
+ duration = cpu_to_le32(pinconf_to_config_argument(config));
+ return dln2_transfer_tx(dln2->pdev, DLN2_GPIO_SET_DEBOUNCE,
+ &duration, sizeof(duration));
+}
+
+static int dln2_gpio_set_event_cfg(struct dln2_gpio *dln2, unsigned pin,
+ unsigned type, unsigned period)
+{
+ struct {
+ __le16 pin;
+ u8 type;
+ __le16 period;
+ } __packed req = {
+ .pin = cpu_to_le16(pin),
+ .type = type,
+ .period = cpu_to_le16(period),
+ };
+
+ return dln2_transfer_tx(dln2->pdev, DLN2_GPIO_PIN_SET_EVENT_CFG,
+ &req, sizeof(req));
+}
+
+static void dln2_irq_unmask(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct dln2_gpio *dln2 = gpiochip_get_data(gc);
+ int pin = irqd_to_hwirq(irqd);
+
+ gpiochip_enable_irq(gc, pin);
+ set_bit(pin, dln2->unmasked_irqs);
+}
+
+static void dln2_irq_mask(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct dln2_gpio *dln2 = gpiochip_get_data(gc);
+ int pin = irqd_to_hwirq(irqd);
+
+ clear_bit(pin, dln2->unmasked_irqs);
+ gpiochip_disable_irq(gc, pin);
+}
+
+static int dln2_irq_set_type(struct irq_data *irqd, unsigned type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct dln2_gpio *dln2 = gpiochip_get_data(gc);
+ int pin = irqd_to_hwirq(irqd);
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ dln2->irq_type[pin] = DLN2_GPIO_EVENT_LVL_HIGH;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ dln2->irq_type[pin] = DLN2_GPIO_EVENT_LVL_LOW;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ dln2->irq_type[pin] = DLN2_GPIO_EVENT_CHANGE;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ dln2->irq_type[pin] = DLN2_GPIO_EVENT_CHANGE_RISING;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ dln2->irq_type[pin] = DLN2_GPIO_EVENT_CHANGE_FALLING;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void dln2_irq_bus_lock(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct dln2_gpio *dln2 = gpiochip_get_data(gc);
+
+ mutex_lock(&dln2->irq_lock);
+}
+
+static void dln2_irq_bus_unlock(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct dln2_gpio *dln2 = gpiochip_get_data(gc);
+ int pin = irqd_to_hwirq(irqd);
+ int enabled, unmasked;
+ unsigned type;
+ int ret;
+
+ enabled = test_bit(pin, dln2->enabled_irqs);
+ unmasked = test_bit(pin, dln2->unmasked_irqs);
+
+ if (enabled != unmasked) {
+ if (unmasked) {
+ type = dln2->irq_type[pin] & DLN2_GPIO_EVENT_MASK;
+ set_bit(pin, dln2->enabled_irqs);
+ } else {
+ type = DLN2_GPIO_EVENT_NONE;
+ clear_bit(pin, dln2->enabled_irqs);
+ }
+
+ ret = dln2_gpio_set_event_cfg(dln2, pin, type, 0);
+ if (ret)
+ dev_err(dln2->gpio.parent, "failed to set event\n");
+ }
+
+ mutex_unlock(&dln2->irq_lock);
+}
+
+static const struct irq_chip dln2_irqchip = {
+ .name = "dln2-irq",
+ .irq_mask = dln2_irq_mask,
+ .irq_unmask = dln2_irq_unmask,
+ .irq_set_type = dln2_irq_set_type,
+ .irq_bus_lock = dln2_irq_bus_lock,
+ .irq_bus_sync_unlock = dln2_irq_bus_unlock,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static void dln2_gpio_event(struct platform_device *pdev, u16 echo,
+ const void *data, int len)
+{
+ int pin, ret;
+
+ const struct {
+ __le16 count;
+ __u8 type;
+ __le16 pin;
+ __u8 value;
+ } __packed *event = data;
+ struct dln2_gpio *dln2 = platform_get_drvdata(pdev);
+
+ if (len < sizeof(*event)) {
+ dev_err(dln2->gpio.parent, "short event message\n");
+ return;
+ }
+
+ pin = le16_to_cpu(event->pin);
+ if (pin >= dln2->gpio.ngpio) {
+ dev_err(dln2->gpio.parent, "out of bounds pin %d\n", pin);
+ return;
+ }
+
+ switch (dln2->irq_type[pin]) {
+ case DLN2_GPIO_EVENT_CHANGE_RISING:
+ if (!event->value)
+ return;
+ break;
+ case DLN2_GPIO_EVENT_CHANGE_FALLING:
+ if (event->value)
+ return;
+ break;
+ }
+
+ ret = generic_handle_domain_irq(dln2->gpio.irq.domain, pin);
+ if (unlikely(ret))
+ dev_err(dln2->gpio.parent, "pin %d not mapped to IRQ\n", pin);
+}
+
+static int dln2_gpio_probe(struct platform_device *pdev)
+{
+ struct dln2_gpio *dln2;
+ struct device *dev = &pdev->dev;
+ struct gpio_irq_chip *girq;
+ int pins;
+ int ret;
+
+ pins = dln2_gpio_get_pin_count(pdev);
+ if (pins < 0) {
+ dev_err(dev, "failed to get pin count: %d\n", pins);
+ return pins;
+ }
+ if (pins > DLN2_GPIO_MAX_PINS) {
+ pins = DLN2_GPIO_MAX_PINS;
+ dev_warn(dev, "clamping pins to %d\n", DLN2_GPIO_MAX_PINS);
+ }
+
+ dln2 = devm_kzalloc(&pdev->dev, sizeof(*dln2), GFP_KERNEL);
+ if (!dln2)
+ return -ENOMEM;
+
+ mutex_init(&dln2->irq_lock);
+
+ dln2->pdev = pdev;
+
+ dln2->gpio.label = "dln2";
+ dln2->gpio.parent = dev;
+ dln2->gpio.owner = THIS_MODULE;
+ dln2->gpio.base = -1;
+ dln2->gpio.ngpio = pins;
+ dln2->gpio.can_sleep = true;
+ dln2->gpio.set = dln2_gpio_set;
+ dln2->gpio.get = dln2_gpio_get;
+ dln2->gpio.request = dln2_gpio_request;
+ dln2->gpio.free = dln2_gpio_free;
+ dln2->gpio.get_direction = dln2_gpio_get_direction;
+ dln2->gpio.direction_input = dln2_gpio_direction_input;
+ dln2->gpio.direction_output = dln2_gpio_direction_output;
+ dln2->gpio.set_config = dln2_gpio_set_config;
+
+ girq = &dln2->gpio.irq;
+ gpio_irq_chip_set_chip(girq, &dln2_irqchip);
+ /* The event comes from the outside so no parent handler */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+
+ platform_set_drvdata(pdev, dln2);
+
+ ret = devm_gpiochip_add_data(dev, &dln2->gpio, dln2);
+ if (ret < 0) {
+ dev_err(dev, "failed to add gpio chip: %d\n", ret);
+ return ret;
+ }
+
+ ret = dln2_register_event_cb(pdev, DLN2_GPIO_CONDITION_MET_EV,
+ dln2_gpio_event);
+ if (ret) {
+ dev_err(dev, "failed to register event cb: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dln2_gpio_remove(struct platform_device *pdev)
+{
+ dln2_unregister_event_cb(pdev, DLN2_GPIO_CONDITION_MET_EV);
+
+ return 0;
+}
+
+static struct platform_driver dln2_gpio_driver = {
+ .driver.name = "dln2-gpio",
+ .probe = dln2_gpio_probe,
+ .remove = dln2_gpio_remove,
+};
+
+module_platform_driver(dln2_gpio_driver);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com");
+MODULE_DESCRIPTION("Driver for the Diolan DLN2 GPIO interface");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:dln2-gpio");
diff --git a/drivers/gpio/gpio-ds4520.c b/drivers/gpio/gpio-ds4520.c
new file mode 100644
index 0000000000..1903deaef3
--- /dev/null
+++ b/drivers/gpio/gpio-ds4520.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Analog Devices, Inc.
+ * Driver for the DS4520 I/O Expander
+ */
+
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/i2c.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#define DS4520_PULLUP0 0xF0
+#define DS4520_IO_CONTROL0 0xF2
+#define DS4520_IO_STATUS0 0xF8
+
+static const struct regmap_config ds4520_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static int ds4520_gpio_probe(struct i2c_client *client)
+{
+ struct gpio_regmap_config config = { };
+ struct device *dev = &client->dev;
+ struct regmap *regmap;
+ u32 ngpio;
+ u32 base;
+ int ret;
+
+ ret = device_property_read_u32(dev, "reg", &base);
+ if (ret)
+ return dev_err_probe(dev, ret, "Missing 'reg' property.\n");
+
+ ret = device_property_read_u32(dev, "ngpios", &ngpio);
+ if (ret)
+ return dev_err_probe(dev, ret, "Missing 'ngpios' property.\n");
+
+ regmap = devm_regmap_init_i2c(client, &ds4520_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "Failed to allocate register map\n");
+
+ config.regmap = regmap;
+ config.parent = dev;
+ config.ngpio = ngpio;
+
+ config.reg_dat_base = base + DS4520_IO_STATUS0;
+ config.reg_set_base = base + DS4520_PULLUP0;
+ config.reg_dir_out_base = base + DS4520_IO_CONTROL0;
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &config));
+}
+
+static const struct of_device_id ds4520_gpio_of_match_table[] = {
+ { .compatible = "adi,ds4520-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ds4520_gpio_of_match_table);
+
+static const struct i2c_device_id ds4520_gpio_id_table[] = {
+ { "ds4520-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ds4520_gpio_id_table);
+
+static struct i2c_driver ds4520_gpio_driver = {
+ .driver = {
+ .name = "ds4520-gpio",
+ .of_match_table = ds4520_gpio_of_match_table,
+ },
+ .probe = ds4520_gpio_probe,
+ .id_table = ds4520_gpio_id_table,
+};
+module_i2c_driver(ds4520_gpio_driver);
+
+MODULE_DESCRIPTION("DS4520 I/O Expander");
+MODULE_AUTHOR("Okan Sahin <okan.sahin@analog.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-dwapb.c b/drivers/gpio/gpio-dwapb.c
new file mode 100644
index 0000000000..6b7d47a52b
--- /dev/null
+++ b/drivers/gpio/gpio-dwapb.c
@@ -0,0 +1,852 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2011 Jamie Iles
+ *
+ * All enquiries to support@picochip.com
+ */
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include "gpiolib.h"
+#include "gpiolib-acpi.h"
+
+#define GPIO_SWPORTA_DR 0x00
+#define GPIO_SWPORTA_DDR 0x04
+#define GPIO_SWPORTB_DR 0x0c
+#define GPIO_SWPORTB_DDR 0x10
+#define GPIO_SWPORTC_DR 0x18
+#define GPIO_SWPORTC_DDR 0x1c
+#define GPIO_SWPORTD_DR 0x24
+#define GPIO_SWPORTD_DDR 0x28
+#define GPIO_INTEN 0x30
+#define GPIO_INTMASK 0x34
+#define GPIO_INTTYPE_LEVEL 0x38
+#define GPIO_INT_POLARITY 0x3c
+#define GPIO_INTSTATUS 0x40
+#define GPIO_PORTA_DEBOUNCE 0x48
+#define GPIO_PORTA_EOI 0x4c
+#define GPIO_EXT_PORTA 0x50
+#define GPIO_EXT_PORTB 0x54
+#define GPIO_EXT_PORTC 0x58
+#define GPIO_EXT_PORTD 0x5c
+
+#define DWAPB_DRIVER_NAME "gpio-dwapb"
+#define DWAPB_MAX_PORTS 4
+#define DWAPB_MAX_GPIOS 32
+
+#define GPIO_EXT_PORT_STRIDE 0x04 /* register stride 32 bits */
+#define GPIO_SWPORT_DR_STRIDE 0x0c /* register stride 3*32 bits */
+#define GPIO_SWPORT_DDR_STRIDE 0x0c /* register stride 3*32 bits */
+
+#define GPIO_REG_OFFSET_V1 0
+#define GPIO_REG_OFFSET_V2 1
+#define GPIO_REG_OFFSET_MASK BIT(0)
+
+#define GPIO_INTMASK_V2 0x44
+#define GPIO_INTTYPE_LEVEL_V2 0x34
+#define GPIO_INT_POLARITY_V2 0x38
+#define GPIO_INTSTATUS_V2 0x3c
+#define GPIO_PORTA_EOI_V2 0x40
+
+#define DWAPB_NR_CLOCKS 2
+
+struct dwapb_gpio;
+
+struct dwapb_port_property {
+ struct fwnode_handle *fwnode;
+ unsigned int idx;
+ unsigned int ngpio;
+ unsigned int gpio_base;
+ int irq[DWAPB_MAX_GPIOS];
+};
+
+struct dwapb_platform_data {
+ struct dwapb_port_property *properties;
+ unsigned int nports;
+};
+
+#ifdef CONFIG_PM_SLEEP
+/* Store GPIO context across system-wide suspend/resume transitions */
+struct dwapb_context {
+ u32 data;
+ u32 dir;
+ u32 ext;
+ u32 int_en;
+ u32 int_mask;
+ u32 int_type;
+ u32 int_pol;
+ u32 int_deb;
+ u32 wake_en;
+};
+#endif
+
+struct dwapb_gpio_port_irqchip {
+ unsigned int nr_irqs;
+ unsigned int irq[DWAPB_MAX_GPIOS];
+};
+
+struct dwapb_gpio_port {
+ struct gpio_chip gc;
+ struct dwapb_gpio_port_irqchip *pirq;
+ struct dwapb_gpio *gpio;
+#ifdef CONFIG_PM_SLEEP
+ struct dwapb_context *ctx;
+#endif
+ unsigned int idx;
+};
+#define to_dwapb_gpio(_gc) \
+ (container_of(_gc, struct dwapb_gpio_port, gc)->gpio)
+
+struct dwapb_gpio {
+ struct device *dev;
+ void __iomem *regs;
+ struct dwapb_gpio_port *ports;
+ unsigned int nr_ports;
+ unsigned int flags;
+ struct reset_control *rst;
+ struct clk_bulk_data clks[DWAPB_NR_CLOCKS];
+};
+
+static inline u32 gpio_reg_v2_convert(unsigned int offset)
+{
+ switch (offset) {
+ case GPIO_INTMASK:
+ return GPIO_INTMASK_V2;
+ case GPIO_INTTYPE_LEVEL:
+ return GPIO_INTTYPE_LEVEL_V2;
+ case GPIO_INT_POLARITY:
+ return GPIO_INT_POLARITY_V2;
+ case GPIO_INTSTATUS:
+ return GPIO_INTSTATUS_V2;
+ case GPIO_PORTA_EOI:
+ return GPIO_PORTA_EOI_V2;
+ }
+
+ return offset;
+}
+
+static inline u32 gpio_reg_convert(struct dwapb_gpio *gpio, unsigned int offset)
+{
+ if ((gpio->flags & GPIO_REG_OFFSET_MASK) == GPIO_REG_OFFSET_V2)
+ return gpio_reg_v2_convert(offset);
+
+ return offset;
+}
+
+static inline u32 dwapb_read(struct dwapb_gpio *gpio, unsigned int offset)
+{
+ struct gpio_chip *gc = &gpio->ports[0].gc;
+ void __iomem *reg_base = gpio->regs;
+
+ return gc->read_reg(reg_base + gpio_reg_convert(gpio, offset));
+}
+
+static inline void dwapb_write(struct dwapb_gpio *gpio, unsigned int offset,
+ u32 val)
+{
+ struct gpio_chip *gc = &gpio->ports[0].gc;
+ void __iomem *reg_base = gpio->regs;
+
+ gc->write_reg(reg_base + gpio_reg_convert(gpio, offset), val);
+}
+
+static struct dwapb_gpio_port *dwapb_offs_to_port(struct dwapb_gpio *gpio, unsigned int offs)
+{
+ struct dwapb_gpio_port *port;
+ int i;
+
+ for (i = 0; i < gpio->nr_ports; i++) {
+ port = &gpio->ports[i];
+ if (port->idx == offs / DWAPB_MAX_GPIOS)
+ return port;
+ }
+
+ return NULL;
+}
+
+static void dwapb_toggle_trigger(struct dwapb_gpio *gpio, unsigned int offs)
+{
+ struct dwapb_gpio_port *port = dwapb_offs_to_port(gpio, offs);
+ struct gpio_chip *gc;
+ u32 pol;
+ int val;
+
+ if (!port)
+ return;
+ gc = &port->gc;
+
+ pol = dwapb_read(gpio, GPIO_INT_POLARITY);
+ /* Just read the current value right out of the data register */
+ val = gc->get(gc, offs % DWAPB_MAX_GPIOS);
+ if (val)
+ pol &= ~BIT(offs);
+ else
+ pol |= BIT(offs);
+
+ dwapb_write(gpio, GPIO_INT_POLARITY, pol);
+}
+
+static u32 dwapb_do_irq(struct dwapb_gpio *gpio)
+{
+ struct gpio_chip *gc = &gpio->ports[0].gc;
+ unsigned long irq_status;
+ irq_hw_number_t hwirq;
+
+ irq_status = dwapb_read(gpio, GPIO_INTSTATUS);
+ for_each_set_bit(hwirq, &irq_status, DWAPB_MAX_GPIOS) {
+ int gpio_irq = irq_find_mapping(gc->irq.domain, hwirq);
+ u32 irq_type = irq_get_trigger_type(gpio_irq);
+
+ generic_handle_irq(gpio_irq);
+
+ if ((irq_type & IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH)
+ dwapb_toggle_trigger(gpio, hwirq);
+ }
+
+ return irq_status;
+}
+
+static void dwapb_irq_handler(struct irq_desc *desc)
+{
+ struct dwapb_gpio *gpio = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+
+ chained_irq_enter(chip, desc);
+ dwapb_do_irq(gpio);
+ chained_irq_exit(chip, desc);
+}
+
+static irqreturn_t dwapb_irq_handler_mfd(int irq, void *dev_id)
+{
+ return IRQ_RETVAL(dwapb_do_irq(dev_id));
+}
+
+static void dwapb_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct dwapb_gpio *gpio = to_dwapb_gpio(gc);
+ u32 val = BIT(irqd_to_hwirq(d));
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ dwapb_write(gpio, GPIO_PORTA_EOI, val);
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
+static void dwapb_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct dwapb_gpio *gpio = to_dwapb_gpio(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ unsigned long flags;
+ u32 val;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ val = dwapb_read(gpio, GPIO_INTMASK) | BIT(hwirq);
+ dwapb_write(gpio, GPIO_INTMASK, val);
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+ gpiochip_disable_irq(gc, hwirq);
+}
+
+static void dwapb_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct dwapb_gpio *gpio = to_dwapb_gpio(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ unsigned long flags;
+ u32 val;
+
+ gpiochip_enable_irq(gc, hwirq);
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ val = dwapb_read(gpio, GPIO_INTMASK) & ~BIT(hwirq);
+ dwapb_write(gpio, GPIO_INTMASK, val);
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
+static void dwapb_irq_enable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct dwapb_gpio *gpio = to_dwapb_gpio(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ unsigned long flags;
+ u32 val;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ val = dwapb_read(gpio, GPIO_INTEN) | BIT(hwirq);
+ dwapb_write(gpio, GPIO_INTEN, val);
+ val = dwapb_read(gpio, GPIO_INTMASK) & ~BIT(hwirq);
+ dwapb_write(gpio, GPIO_INTMASK, val);
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
+static void dwapb_irq_disable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct dwapb_gpio *gpio = to_dwapb_gpio(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ unsigned long flags;
+ u32 val;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ val = dwapb_read(gpio, GPIO_INTMASK) | BIT(hwirq);
+ dwapb_write(gpio, GPIO_INTMASK, val);
+ val = dwapb_read(gpio, GPIO_INTEN) & ~BIT(hwirq);
+ dwapb_write(gpio, GPIO_INTEN, val);
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
+static int dwapb_irq_set_type(struct irq_data *d, u32 type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct dwapb_gpio *gpio = to_dwapb_gpio(gc);
+ irq_hw_number_t bit = irqd_to_hwirq(d);
+ unsigned long level, polarity, flags;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ level = dwapb_read(gpio, GPIO_INTTYPE_LEVEL);
+ polarity = dwapb_read(gpio, GPIO_INT_POLARITY);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_BOTH:
+ level |= BIT(bit);
+ dwapb_toggle_trigger(gpio, bit);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ level |= BIT(bit);
+ polarity |= BIT(bit);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ level |= BIT(bit);
+ polarity &= ~BIT(bit);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ level &= ~BIT(bit);
+ polarity |= BIT(bit);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ level &= ~BIT(bit);
+ polarity &= ~BIT(bit);
+ break;
+ }
+
+ if (type & IRQ_TYPE_LEVEL_MASK)
+ irq_set_handler_locked(d, handle_level_irq);
+ else if (type & IRQ_TYPE_EDGE_BOTH)
+ irq_set_handler_locked(d, handle_edge_irq);
+
+ dwapb_write(gpio, GPIO_INTTYPE_LEVEL, level);
+ if (type != IRQ_TYPE_EDGE_BOTH)
+ dwapb_write(gpio, GPIO_INT_POLARITY, polarity);
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int dwapb_irq_set_wake(struct irq_data *d, unsigned int enable)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct dwapb_gpio *gpio = to_dwapb_gpio(gc);
+ struct dwapb_context *ctx = gpio->ports[0].ctx;
+ irq_hw_number_t bit = irqd_to_hwirq(d);
+
+ if (enable)
+ ctx->wake_en |= BIT(bit);
+ else
+ ctx->wake_en &= ~BIT(bit);
+
+ return 0;
+}
+#else
+#define dwapb_irq_set_wake NULL
+#endif
+
+static const struct irq_chip dwapb_irq_chip = {
+ .name = DWAPB_DRIVER_NAME,
+ .irq_ack = dwapb_irq_ack,
+ .irq_mask = dwapb_irq_mask,
+ .irq_unmask = dwapb_irq_unmask,
+ .irq_set_type = dwapb_irq_set_type,
+ .irq_enable = dwapb_irq_enable,
+ .irq_disable = dwapb_irq_disable,
+ .irq_set_wake = dwapb_irq_set_wake,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int dwapb_gpio_set_debounce(struct gpio_chip *gc,
+ unsigned offset, unsigned debounce)
+{
+ struct dwapb_gpio_port *port = gpiochip_get_data(gc);
+ struct dwapb_gpio *gpio = port->gpio;
+ unsigned long flags, val_deb;
+ unsigned long mask = BIT(offset);
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+ val_deb = dwapb_read(gpio, GPIO_PORTA_DEBOUNCE);
+ if (debounce)
+ val_deb |= mask;
+ else
+ val_deb &= ~mask;
+ dwapb_write(gpio, GPIO_PORTA_DEBOUNCE, val_deb);
+
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+ return 0;
+}
+
+static int dwapb_gpio_set_config(struct gpio_chip *gc, unsigned offset,
+ unsigned long config)
+{
+ u32 debounce;
+
+ if (pinconf_to_config_param(config) != PIN_CONFIG_INPUT_DEBOUNCE)
+ return -ENOTSUPP;
+
+ debounce = pinconf_to_config_argument(config);
+ return dwapb_gpio_set_debounce(gc, offset, debounce);
+}
+
+static int dwapb_convert_irqs(struct dwapb_gpio_port_irqchip *pirq,
+ struct dwapb_port_property *pp)
+{
+ int i;
+
+ /* Group all available IRQs into an array of parental IRQs. */
+ for (i = 0; i < pp->ngpio; ++i) {
+ if (!pp->irq[i])
+ continue;
+
+ pirq->irq[pirq->nr_irqs++] = pp->irq[i];
+ }
+
+ return pirq->nr_irqs ? 0 : -ENOENT;
+}
+
+static void dwapb_configure_irqs(struct dwapb_gpio *gpio,
+ struct dwapb_gpio_port *port,
+ struct dwapb_port_property *pp)
+{
+ struct dwapb_gpio_port_irqchip *pirq;
+ struct gpio_chip *gc = &port->gc;
+ struct gpio_irq_chip *girq;
+ int err;
+
+ pirq = devm_kzalloc(gpio->dev, sizeof(*pirq), GFP_KERNEL);
+ if (!pirq)
+ return;
+
+ if (dwapb_convert_irqs(pirq, pp)) {
+ dev_warn(gpio->dev, "no IRQ for port%d\n", pp->idx);
+ goto err_kfree_pirq;
+ }
+
+ girq = &gc->irq;
+ girq->handler = handle_bad_irq;
+ girq->default_type = IRQ_TYPE_NONE;
+
+ port->pirq = pirq;
+
+ /*
+ * Intel ACPI-based platforms mostly have the DesignWare APB GPIO
+ * IRQ lane shared between several devices. In that case the parental
+ * IRQ has to be handled in the shared way so to be properly delivered
+ * to all the connected devices.
+ */
+ if (has_acpi_companion(gpio->dev)) {
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->parent_handler = NULL;
+
+ err = devm_request_irq(gpio->dev, pp->irq[0],
+ dwapb_irq_handler_mfd,
+ IRQF_SHARED, DWAPB_DRIVER_NAME, gpio);
+ if (err) {
+ dev_err(gpio->dev, "error requesting IRQ\n");
+ goto err_kfree_pirq;
+ }
+ } else {
+ girq->num_parents = pirq->nr_irqs;
+ girq->parents = pirq->irq;
+ girq->parent_handler_data = gpio;
+ girq->parent_handler = dwapb_irq_handler;
+ }
+
+ gpio_irq_chip_set_chip(girq, &dwapb_irq_chip);
+
+ return;
+
+err_kfree_pirq:
+ devm_kfree(gpio->dev, pirq);
+}
+
+static int dwapb_gpio_add_port(struct dwapb_gpio *gpio,
+ struct dwapb_port_property *pp,
+ unsigned int offs)
+{
+ struct dwapb_gpio_port *port;
+ void __iomem *dat, *set, *dirout;
+ int err;
+
+ port = &gpio->ports[offs];
+ port->gpio = gpio;
+ port->idx = pp->idx;
+
+#ifdef CONFIG_PM_SLEEP
+ port->ctx = devm_kzalloc(gpio->dev, sizeof(*port->ctx), GFP_KERNEL);
+ if (!port->ctx)
+ return -ENOMEM;
+#endif
+
+ dat = gpio->regs + GPIO_EXT_PORTA + pp->idx * GPIO_EXT_PORT_STRIDE;
+ set = gpio->regs + GPIO_SWPORTA_DR + pp->idx * GPIO_SWPORT_DR_STRIDE;
+ dirout = gpio->regs + GPIO_SWPORTA_DDR + pp->idx * GPIO_SWPORT_DDR_STRIDE;
+
+ /* This registers 32 GPIO lines per port */
+ err = bgpio_init(&port->gc, gpio->dev, 4, dat, set, NULL, dirout,
+ NULL, 0);
+ if (err) {
+ dev_err(gpio->dev, "failed to init gpio chip for port%d\n",
+ port->idx);
+ return err;
+ }
+
+ port->gc.fwnode = pp->fwnode;
+ port->gc.ngpio = pp->ngpio;
+ port->gc.base = pp->gpio_base;
+
+ /* Only port A support debounce */
+ if (pp->idx == 0)
+ port->gc.set_config = dwapb_gpio_set_config;
+
+ /* Only port A can provide interrupts in all configurations of the IP */
+ if (pp->idx == 0)
+ dwapb_configure_irqs(gpio, port, pp);
+
+ err = devm_gpiochip_add_data(gpio->dev, &port->gc, port);
+ if (err) {
+ dev_err(gpio->dev, "failed to register gpiochip for port%d\n",
+ port->idx);
+ return err;
+ }
+
+ return 0;
+}
+
+static void dwapb_get_irq(struct device *dev, struct fwnode_handle *fwnode,
+ struct dwapb_port_property *pp)
+{
+ int irq, j;
+
+ for (j = 0; j < pp->ngpio; j++) {
+ if (has_acpi_companion(dev))
+ irq = platform_get_irq_optional(to_platform_device(dev), j);
+ else
+ irq = fwnode_irq_get(fwnode, j);
+ if (irq > 0)
+ pp->irq[j] = irq;
+ }
+}
+
+static struct dwapb_platform_data *dwapb_gpio_get_pdata(struct device *dev)
+{
+ struct fwnode_handle *fwnode;
+ struct dwapb_platform_data *pdata;
+ struct dwapb_port_property *pp;
+ int nports;
+ int i;
+
+ nports = device_get_child_node_count(dev);
+ if (nports == 0)
+ return ERR_PTR(-ENODEV);
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ pdata->properties = devm_kcalloc(dev, nports, sizeof(*pp), GFP_KERNEL);
+ if (!pdata->properties)
+ return ERR_PTR(-ENOMEM);
+
+ pdata->nports = nports;
+
+ i = 0;
+ device_for_each_child_node(dev, fwnode) {
+ pp = &pdata->properties[i++];
+ pp->fwnode = fwnode;
+
+ if (fwnode_property_read_u32(fwnode, "reg", &pp->idx) ||
+ pp->idx >= DWAPB_MAX_PORTS) {
+ dev_err(dev,
+ "missing/invalid port index for port%d\n", i);
+ fwnode_handle_put(fwnode);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (fwnode_property_read_u32(fwnode, "ngpios", &pp->ngpio) &&
+ fwnode_property_read_u32(fwnode, "snps,nr-gpios", &pp->ngpio)) {
+ dev_info(dev,
+ "failed to get number of gpios for port%d\n",
+ i);
+ pp->ngpio = DWAPB_MAX_GPIOS;
+ }
+
+ pp->gpio_base = -1;
+
+ /* For internal use only, new platforms mustn't exercise this */
+ if (is_software_node(fwnode))
+ fwnode_property_read_u32(fwnode, "gpio-base", &pp->gpio_base);
+
+ /*
+ * Only port A can provide interrupts in all configurations of
+ * the IP.
+ */
+ if (pp->idx == 0)
+ dwapb_get_irq(dev, fwnode, pp);
+ }
+
+ return pdata;
+}
+
+static void dwapb_assert_reset(void *data)
+{
+ struct dwapb_gpio *gpio = data;
+
+ reset_control_assert(gpio->rst);
+}
+
+static int dwapb_get_reset(struct dwapb_gpio *gpio)
+{
+ int err;
+
+ gpio->rst = devm_reset_control_get_optional_shared(gpio->dev, NULL);
+ if (IS_ERR(gpio->rst))
+ return dev_err_probe(gpio->dev, PTR_ERR(gpio->rst),
+ "Cannot get reset descriptor\n");
+
+ err = reset_control_deassert(gpio->rst);
+ if (err) {
+ dev_err(gpio->dev, "Cannot deassert reset lane\n");
+ return err;
+ }
+
+ return devm_add_action_or_reset(gpio->dev, dwapb_assert_reset, gpio);
+}
+
+static void dwapb_disable_clks(void *data)
+{
+ struct dwapb_gpio *gpio = data;
+
+ clk_bulk_disable_unprepare(DWAPB_NR_CLOCKS, gpio->clks);
+}
+
+static int dwapb_get_clks(struct dwapb_gpio *gpio)
+{
+ int err;
+
+ /* Optional bus and debounce clocks */
+ gpio->clks[0].id = "bus";
+ gpio->clks[1].id = "db";
+ err = devm_clk_bulk_get_optional(gpio->dev, DWAPB_NR_CLOCKS,
+ gpio->clks);
+ if (err)
+ return dev_err_probe(gpio->dev, err,
+ "Cannot get APB/Debounce clocks\n");
+
+ err = clk_bulk_prepare_enable(DWAPB_NR_CLOCKS, gpio->clks);
+ if (err) {
+ dev_err(gpio->dev, "Cannot enable APB/Debounce clocks\n");
+ return err;
+ }
+
+ return devm_add_action_or_reset(gpio->dev, dwapb_disable_clks, gpio);
+}
+
+static const struct of_device_id dwapb_of_match[] = {
+ { .compatible = "snps,dw-apb-gpio", .data = (void *)GPIO_REG_OFFSET_V1},
+ { .compatible = "apm,xgene-gpio-v2", .data = (void *)GPIO_REG_OFFSET_V2},
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dwapb_of_match);
+
+static const struct acpi_device_id dwapb_acpi_match[] = {
+ {"HISI0181", GPIO_REG_OFFSET_V1},
+ {"APMC0D07", GPIO_REG_OFFSET_V1},
+ {"APMC0D81", GPIO_REG_OFFSET_V2},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, dwapb_acpi_match);
+
+static int dwapb_gpio_probe(struct platform_device *pdev)
+{
+ unsigned int i;
+ struct dwapb_gpio *gpio;
+ int err;
+ struct dwapb_platform_data *pdata;
+ struct device *dev = &pdev->dev;
+
+ pdata = dwapb_gpio_get_pdata(dev);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->dev = &pdev->dev;
+ gpio->nr_ports = pdata->nports;
+
+ err = dwapb_get_reset(gpio);
+ if (err)
+ return err;
+
+ gpio->ports = devm_kcalloc(&pdev->dev, gpio->nr_ports,
+ sizeof(*gpio->ports), GFP_KERNEL);
+ if (!gpio->ports)
+ return -ENOMEM;
+
+ gpio->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gpio->regs))
+ return PTR_ERR(gpio->regs);
+
+ err = dwapb_get_clks(gpio);
+ if (err)
+ return err;
+
+ gpio->flags = (uintptr_t)device_get_match_data(dev);
+
+ for (i = 0; i < gpio->nr_ports; i++) {
+ err = dwapb_gpio_add_port(gpio, &pdata->properties[i], i);
+ if (err)
+ return err;
+ }
+
+ platform_set_drvdata(pdev, gpio);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int dwapb_gpio_suspend(struct device *dev)
+{
+ struct dwapb_gpio *gpio = dev_get_drvdata(dev);
+ struct gpio_chip *gc = &gpio->ports[0].gc;
+ unsigned long flags;
+ int i;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ for (i = 0; i < gpio->nr_ports; i++) {
+ unsigned int offset;
+ unsigned int idx = gpio->ports[i].idx;
+ struct dwapb_context *ctx = gpio->ports[i].ctx;
+
+ offset = GPIO_SWPORTA_DDR + idx * GPIO_SWPORT_DDR_STRIDE;
+ ctx->dir = dwapb_read(gpio, offset);
+
+ offset = GPIO_SWPORTA_DR + idx * GPIO_SWPORT_DR_STRIDE;
+ ctx->data = dwapb_read(gpio, offset);
+
+ offset = GPIO_EXT_PORTA + idx * GPIO_EXT_PORT_STRIDE;
+ ctx->ext = dwapb_read(gpio, offset);
+
+ /* Only port A can provide interrupts */
+ if (idx == 0) {
+ ctx->int_mask = dwapb_read(gpio, GPIO_INTMASK);
+ ctx->int_en = dwapb_read(gpio, GPIO_INTEN);
+ ctx->int_pol = dwapb_read(gpio, GPIO_INT_POLARITY);
+ ctx->int_type = dwapb_read(gpio, GPIO_INTTYPE_LEVEL);
+ ctx->int_deb = dwapb_read(gpio, GPIO_PORTA_DEBOUNCE);
+
+ /* Mask out interrupts */
+ dwapb_write(gpio, GPIO_INTMASK, ~ctx->wake_en);
+ }
+ }
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+ clk_bulk_disable_unprepare(DWAPB_NR_CLOCKS, gpio->clks);
+
+ return 0;
+}
+
+static int dwapb_gpio_resume(struct device *dev)
+{
+ struct dwapb_gpio *gpio = dev_get_drvdata(dev);
+ struct gpio_chip *gc = &gpio->ports[0].gc;
+ unsigned long flags;
+ int i, err;
+
+ err = clk_bulk_prepare_enable(DWAPB_NR_CLOCKS, gpio->clks);
+ if (err) {
+ dev_err(gpio->dev, "Cannot reenable APB/Debounce clocks\n");
+ return err;
+ }
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ for (i = 0; i < gpio->nr_ports; i++) {
+ unsigned int offset;
+ unsigned int idx = gpio->ports[i].idx;
+ struct dwapb_context *ctx = gpio->ports[i].ctx;
+
+ offset = GPIO_SWPORTA_DR + idx * GPIO_SWPORT_DR_STRIDE;
+ dwapb_write(gpio, offset, ctx->data);
+
+ offset = GPIO_SWPORTA_DDR + idx * GPIO_SWPORT_DDR_STRIDE;
+ dwapb_write(gpio, offset, ctx->dir);
+
+ offset = GPIO_EXT_PORTA + idx * GPIO_EXT_PORT_STRIDE;
+ dwapb_write(gpio, offset, ctx->ext);
+
+ /* Only port A can provide interrupts */
+ if (idx == 0) {
+ dwapb_write(gpio, GPIO_INTTYPE_LEVEL, ctx->int_type);
+ dwapb_write(gpio, GPIO_INT_POLARITY, ctx->int_pol);
+ dwapb_write(gpio, GPIO_PORTA_DEBOUNCE, ctx->int_deb);
+ dwapb_write(gpio, GPIO_INTEN, ctx->int_en);
+ dwapb_write(gpio, GPIO_INTMASK, ctx->int_mask);
+
+ /* Clear out spurious interrupts */
+ dwapb_write(gpio, GPIO_PORTA_EOI, 0xffffffff);
+ }
+ }
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(dwapb_gpio_pm_ops, dwapb_gpio_suspend,
+ dwapb_gpio_resume);
+
+static struct platform_driver dwapb_gpio_driver = {
+ .driver = {
+ .name = DWAPB_DRIVER_NAME,
+ .pm = &dwapb_gpio_pm_ops,
+ .of_match_table = dwapb_of_match,
+ .acpi_match_table = dwapb_acpi_match,
+ },
+ .probe = dwapb_gpio_probe,
+};
+
+module_platform_driver(dwapb_gpio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jamie Iles");
+MODULE_DESCRIPTION("Synopsys DesignWare APB GPIO driver");
+MODULE_ALIAS("platform:" DWAPB_DRIVER_NAME);
diff --git a/drivers/gpio/gpio-eic-sprd.c b/drivers/gpio/gpio-eic-sprd.c
new file mode 100644
index 0000000000..b24e349dee
--- /dev/null
+++ b/drivers/gpio/gpio-eic-sprd.c
@@ -0,0 +1,717 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Spreadtrum Communications Inc.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+/* EIC registers definition */
+#define SPRD_EIC_DBNC_DATA 0x0
+#define SPRD_EIC_DBNC_DMSK 0x4
+#define SPRD_EIC_DBNC_IEV 0x14
+#define SPRD_EIC_DBNC_IE 0x18
+#define SPRD_EIC_DBNC_RIS 0x1c
+#define SPRD_EIC_DBNC_MIS 0x20
+#define SPRD_EIC_DBNC_IC 0x24
+#define SPRD_EIC_DBNC_TRIG 0x28
+#define SPRD_EIC_DBNC_CTRL0 0x40
+
+#define SPRD_EIC_LATCH_INTEN 0x0
+#define SPRD_EIC_LATCH_INTRAW 0x4
+#define SPRD_EIC_LATCH_INTMSK 0x8
+#define SPRD_EIC_LATCH_INTCLR 0xc
+#define SPRD_EIC_LATCH_INTPOL 0x10
+#define SPRD_EIC_LATCH_INTMODE 0x14
+
+#define SPRD_EIC_ASYNC_INTIE 0x0
+#define SPRD_EIC_ASYNC_INTRAW 0x4
+#define SPRD_EIC_ASYNC_INTMSK 0x8
+#define SPRD_EIC_ASYNC_INTCLR 0xc
+#define SPRD_EIC_ASYNC_INTMODE 0x10
+#define SPRD_EIC_ASYNC_INTBOTH 0x14
+#define SPRD_EIC_ASYNC_INTPOL 0x18
+#define SPRD_EIC_ASYNC_DATA 0x1c
+
+#define SPRD_EIC_SYNC_INTIE 0x0
+#define SPRD_EIC_SYNC_INTRAW 0x4
+#define SPRD_EIC_SYNC_INTMSK 0x8
+#define SPRD_EIC_SYNC_INTCLR 0xc
+#define SPRD_EIC_SYNC_INTMODE 0x10
+#define SPRD_EIC_SYNC_INTBOTH 0x14
+#define SPRD_EIC_SYNC_INTPOL 0x18
+#define SPRD_EIC_SYNC_DATA 0x1c
+
+/*
+ * The digital-chip EIC controller can support maximum 3 banks, and each bank
+ * contains 8 EICs.
+ */
+#define SPRD_EIC_MAX_BANK 3
+#define SPRD_EIC_PER_BANK_NR 8
+#define SPRD_EIC_DATA_MASK GENMASK(7, 0)
+#define SPRD_EIC_BIT(x) ((x) & (SPRD_EIC_PER_BANK_NR - 1))
+#define SPRD_EIC_DBNC_MASK GENMASK(11, 0)
+
+/*
+ * The Spreadtrum EIC (external interrupt controller) can be used only in
+ * input mode to generate interrupts if detecting input signals.
+ *
+ * The Spreadtrum digital-chip EIC controller contains 4 sub-modules:
+ * debounce EIC, latch EIC, async EIC and sync EIC,
+ *
+ * The debounce EIC is used to capture the input signals' stable status
+ * (millisecond resolution) and a single-trigger mechanism is introduced
+ * into this sub-module to enhance the input event detection reliability.
+ * The debounce range is from 1ms to 4s with a step size of 1ms.
+ *
+ * The latch EIC is used to latch some special power down signals and
+ * generate interrupts, since the latch EIC does not depend on the APB clock
+ * to capture signals.
+ *
+ * The async EIC uses a 32k clock to capture the short signals (microsecond
+ * resolution) to generate interrupts by level or edge trigger.
+ *
+ * The EIC-sync is similar with GPIO's input function, which is a synchronized
+ * signal input register.
+ */
+enum sprd_eic_type {
+ SPRD_EIC_DEBOUNCE,
+ SPRD_EIC_LATCH,
+ SPRD_EIC_ASYNC,
+ SPRD_EIC_SYNC,
+ SPRD_EIC_MAX,
+};
+
+struct sprd_eic {
+ struct gpio_chip chip;
+ void __iomem *base[SPRD_EIC_MAX_BANK];
+ enum sprd_eic_type type;
+ spinlock_t lock;
+ int irq;
+};
+
+struct sprd_eic_variant_data {
+ enum sprd_eic_type type;
+ u32 num_eics;
+};
+
+static const char *sprd_eic_label_name[SPRD_EIC_MAX] = {
+ "eic-debounce", "eic-latch", "eic-async",
+ "eic-sync",
+};
+
+static const struct sprd_eic_variant_data sc9860_eic_dbnc_data = {
+ .type = SPRD_EIC_DEBOUNCE,
+ .num_eics = 8,
+};
+
+static const struct sprd_eic_variant_data sc9860_eic_latch_data = {
+ .type = SPRD_EIC_LATCH,
+ .num_eics = 8,
+};
+
+static const struct sprd_eic_variant_data sc9860_eic_async_data = {
+ .type = SPRD_EIC_ASYNC,
+ .num_eics = 8,
+};
+
+static const struct sprd_eic_variant_data sc9860_eic_sync_data = {
+ .type = SPRD_EIC_SYNC,
+ .num_eics = 8,
+};
+
+static inline void __iomem *sprd_eic_offset_base(struct sprd_eic *sprd_eic,
+ unsigned int bank)
+{
+ if (bank >= SPRD_EIC_MAX_BANK)
+ return NULL;
+
+ return sprd_eic->base[bank];
+}
+
+static void sprd_eic_update(struct gpio_chip *chip, unsigned int offset,
+ u16 reg, unsigned int val)
+{
+ struct sprd_eic *sprd_eic = gpiochip_get_data(chip);
+ void __iomem *base =
+ sprd_eic_offset_base(sprd_eic, offset / SPRD_EIC_PER_BANK_NR);
+ unsigned long flags;
+ u32 tmp;
+
+ spin_lock_irqsave(&sprd_eic->lock, flags);
+ tmp = readl_relaxed(base + reg);
+
+ if (val)
+ tmp |= BIT(SPRD_EIC_BIT(offset));
+ else
+ tmp &= ~BIT(SPRD_EIC_BIT(offset));
+
+ writel_relaxed(tmp, base + reg);
+ spin_unlock_irqrestore(&sprd_eic->lock, flags);
+}
+
+static int sprd_eic_read(struct gpio_chip *chip, unsigned int offset, u16 reg)
+{
+ struct sprd_eic *sprd_eic = gpiochip_get_data(chip);
+ void __iomem *base =
+ sprd_eic_offset_base(sprd_eic, offset / SPRD_EIC_PER_BANK_NR);
+
+ return !!(readl_relaxed(base + reg) & BIT(SPRD_EIC_BIT(offset)));
+}
+
+static int sprd_eic_request(struct gpio_chip *chip, unsigned int offset)
+{
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_DMSK, 1);
+ return 0;
+}
+
+static void sprd_eic_free(struct gpio_chip *chip, unsigned int offset)
+{
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_DMSK, 0);
+}
+
+static int sprd_eic_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct sprd_eic *sprd_eic = gpiochip_get_data(chip);
+
+ switch (sprd_eic->type) {
+ case SPRD_EIC_DEBOUNCE:
+ return sprd_eic_read(chip, offset, SPRD_EIC_DBNC_DATA);
+ case SPRD_EIC_ASYNC:
+ return sprd_eic_read(chip, offset, SPRD_EIC_ASYNC_DATA);
+ case SPRD_EIC_SYNC:
+ return sprd_eic_read(chip, offset, SPRD_EIC_SYNC_DATA);
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static int sprd_eic_direction_input(struct gpio_chip *chip, unsigned int offset)
+{
+ /* EICs are always input, nothing need to do here. */
+ return 0;
+}
+
+static void sprd_eic_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ /* EICs are always input, nothing need to do here. */
+}
+
+static int sprd_eic_set_debounce(struct gpio_chip *chip, unsigned int offset,
+ unsigned int debounce)
+{
+ struct sprd_eic *sprd_eic = gpiochip_get_data(chip);
+ void __iomem *base =
+ sprd_eic_offset_base(sprd_eic, offset / SPRD_EIC_PER_BANK_NR);
+ u32 reg = SPRD_EIC_DBNC_CTRL0 + SPRD_EIC_BIT(offset) * 0x4;
+ u32 value = readl_relaxed(base + reg) & ~SPRD_EIC_DBNC_MASK;
+
+ value |= (debounce / 1000) & SPRD_EIC_DBNC_MASK;
+ writel_relaxed(value, base + reg);
+
+ return 0;
+}
+
+static int sprd_eic_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ unsigned long param = pinconf_to_config_param(config);
+ u32 arg = pinconf_to_config_argument(config);
+
+ if (param == PIN_CONFIG_INPUT_DEBOUNCE)
+ return sprd_eic_set_debounce(chip, offset, arg);
+
+ return -ENOTSUPP;
+}
+
+static void sprd_eic_irq_mask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct sprd_eic *sprd_eic = gpiochip_get_data(chip);
+ u32 offset = irqd_to_hwirq(data);
+
+ switch (sprd_eic->type) {
+ case SPRD_EIC_DEBOUNCE:
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_IE, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_TRIG, 0);
+ break;
+ case SPRD_EIC_LATCH:
+ sprd_eic_update(chip, offset, SPRD_EIC_LATCH_INTEN, 0);
+ break;
+ case SPRD_EIC_ASYNC:
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTIE, 0);
+ break;
+ case SPRD_EIC_SYNC:
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTIE, 0);
+ break;
+ default:
+ dev_err(chip->parent, "Unsupported EIC type.\n");
+ }
+
+ gpiochip_disable_irq(chip, offset);
+}
+
+static void sprd_eic_irq_unmask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct sprd_eic *sprd_eic = gpiochip_get_data(chip);
+ u32 offset = irqd_to_hwirq(data);
+
+ gpiochip_enable_irq(chip, offset);
+
+ switch (sprd_eic->type) {
+ case SPRD_EIC_DEBOUNCE:
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_IE, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_TRIG, 1);
+ break;
+ case SPRD_EIC_LATCH:
+ sprd_eic_update(chip, offset, SPRD_EIC_LATCH_INTEN, 1);
+ break;
+ case SPRD_EIC_ASYNC:
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTIE, 1);
+ break;
+ case SPRD_EIC_SYNC:
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTIE, 1);
+ break;
+ default:
+ dev_err(chip->parent, "Unsupported EIC type.\n");
+ }
+}
+
+static void sprd_eic_irq_ack(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct sprd_eic *sprd_eic = gpiochip_get_data(chip);
+ u32 offset = irqd_to_hwirq(data);
+
+ switch (sprd_eic->type) {
+ case SPRD_EIC_DEBOUNCE:
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_IC, 1);
+ break;
+ case SPRD_EIC_LATCH:
+ sprd_eic_update(chip, offset, SPRD_EIC_LATCH_INTCLR, 1);
+ break;
+ case SPRD_EIC_ASYNC:
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTCLR, 1);
+ break;
+ case SPRD_EIC_SYNC:
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTCLR, 1);
+ break;
+ default:
+ dev_err(chip->parent, "Unsupported EIC type.\n");
+ }
+}
+
+static int sprd_eic_irq_set_type(struct irq_data *data, unsigned int flow_type)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct sprd_eic *sprd_eic = gpiochip_get_data(chip);
+ u32 offset = irqd_to_hwirq(data);
+ int state;
+
+ switch (sprd_eic->type) {
+ case SPRD_EIC_DEBOUNCE:
+ switch (flow_type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_IEV, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_IC, 1);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_IEV, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_IC, 1);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_EDGE_FALLING:
+ case IRQ_TYPE_EDGE_BOTH:
+ state = sprd_eic_get(chip, offset);
+ if (state) {
+ sprd_eic_update(chip, offset,
+ SPRD_EIC_DBNC_IEV, 0);
+ sprd_eic_update(chip, offset,
+ SPRD_EIC_DBNC_IC, 1);
+ } else {
+ sprd_eic_update(chip, offset,
+ SPRD_EIC_DBNC_IEV, 1);
+ sprd_eic_update(chip, offset,
+ SPRD_EIC_DBNC_IC, 1);
+ }
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ irq_set_handler_locked(data, handle_level_irq);
+ break;
+ case SPRD_EIC_LATCH:
+ switch (flow_type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ sprd_eic_update(chip, offset, SPRD_EIC_LATCH_INTPOL, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_LATCH_INTCLR, 1);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ sprd_eic_update(chip, offset, SPRD_EIC_LATCH_INTPOL, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_LATCH_INTCLR, 1);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_EDGE_FALLING:
+ case IRQ_TYPE_EDGE_BOTH:
+ state = sprd_eic_get(chip, offset);
+ if (state) {
+ sprd_eic_update(chip, offset,
+ SPRD_EIC_LATCH_INTPOL, 0);
+ sprd_eic_update(chip, offset,
+ SPRD_EIC_LATCH_INTCLR, 1);
+ } else {
+ sprd_eic_update(chip, offset,
+ SPRD_EIC_LATCH_INTPOL, 1);
+ sprd_eic_update(chip, offset,
+ SPRD_EIC_LATCH_INTCLR, 1);
+ }
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ irq_set_handler_locked(data, handle_level_irq);
+ break;
+ case SPRD_EIC_ASYNC:
+ switch (flow_type) {
+ case IRQ_TYPE_EDGE_RISING:
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTBOTH, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTMODE, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTPOL, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTCLR, 1);
+ irq_set_handler_locked(data, handle_edge_irq);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTBOTH, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTMODE, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTPOL, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTCLR, 1);
+ irq_set_handler_locked(data, handle_edge_irq);
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTMODE, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTBOTH, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTCLR, 1);
+ irq_set_handler_locked(data, handle_edge_irq);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTBOTH, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTMODE, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTPOL, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTCLR, 1);
+ irq_set_handler_locked(data, handle_level_irq);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTBOTH, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTMODE, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTPOL, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_ASYNC_INTCLR, 1);
+ irq_set_handler_locked(data, handle_level_irq);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+ break;
+ case SPRD_EIC_SYNC:
+ switch (flow_type) {
+ case IRQ_TYPE_EDGE_RISING:
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTBOTH, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTMODE, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTPOL, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTCLR, 1);
+ irq_set_handler_locked(data, handle_edge_irq);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTBOTH, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTMODE, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTPOL, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTCLR, 1);
+ irq_set_handler_locked(data, handle_edge_irq);
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTMODE, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTBOTH, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTCLR, 1);
+ irq_set_handler_locked(data, handle_edge_irq);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTBOTH, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTMODE, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTPOL, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTCLR, 1);
+ irq_set_handler_locked(data, handle_level_irq);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTBOTH, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTMODE, 1);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTPOL, 0);
+ sprd_eic_update(chip, offset, SPRD_EIC_SYNC_INTCLR, 1);
+ irq_set_handler_locked(data, handle_level_irq);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+ break;
+ default:
+ dev_err(chip->parent, "Unsupported EIC type.\n");
+ return -ENOTSUPP;
+ }
+
+ return 0;
+}
+
+static void sprd_eic_toggle_trigger(struct gpio_chip *chip, unsigned int irq,
+ unsigned int offset)
+{
+ struct sprd_eic *sprd_eic = gpiochip_get_data(chip);
+ struct irq_data *data = irq_get_irq_data(irq);
+ u32 trigger = irqd_get_trigger_type(data);
+ int state, post_state;
+
+ /*
+ * The debounce EIC and latch EIC can only support level trigger, so we
+ * can toggle the level trigger to emulate the edge trigger.
+ */
+ if ((sprd_eic->type != SPRD_EIC_DEBOUNCE &&
+ sprd_eic->type != SPRD_EIC_LATCH) ||
+ !(trigger & IRQ_TYPE_EDGE_BOTH))
+ return;
+
+ sprd_eic_irq_mask(data);
+ state = sprd_eic_get(chip, offset);
+
+retry:
+ switch (sprd_eic->type) {
+ case SPRD_EIC_DEBOUNCE:
+ if (state)
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_IEV, 0);
+ else
+ sprd_eic_update(chip, offset, SPRD_EIC_DBNC_IEV, 1);
+ break;
+ case SPRD_EIC_LATCH:
+ if (state)
+ sprd_eic_update(chip, offset, SPRD_EIC_LATCH_INTPOL, 0);
+ else
+ sprd_eic_update(chip, offset, SPRD_EIC_LATCH_INTPOL, 1);
+ break;
+ default:
+ sprd_eic_irq_unmask(data);
+ return;
+ }
+
+ post_state = sprd_eic_get(chip, offset);
+ if (state != post_state) {
+ dev_warn(chip->parent, "EIC level was changed.\n");
+ state = post_state;
+ goto retry;
+ }
+
+ sprd_eic_irq_unmask(data);
+}
+
+static int sprd_eic_match_chip_by_type(struct gpio_chip *chip, void *data)
+{
+ enum sprd_eic_type type = *(enum sprd_eic_type *)data;
+
+ return !strcmp(chip->label, sprd_eic_label_name[type]);
+}
+
+static void sprd_eic_handle_one_type(struct gpio_chip *chip)
+{
+ struct sprd_eic *sprd_eic = gpiochip_get_data(chip);
+ u32 bank, n, girq;
+
+ for (bank = 0; bank * SPRD_EIC_PER_BANK_NR < chip->ngpio; bank++) {
+ void __iomem *base = sprd_eic_offset_base(sprd_eic, bank);
+ unsigned long reg;
+
+ switch (sprd_eic->type) {
+ case SPRD_EIC_DEBOUNCE:
+ reg = readl_relaxed(base + SPRD_EIC_DBNC_MIS) &
+ SPRD_EIC_DATA_MASK;
+ break;
+ case SPRD_EIC_LATCH:
+ reg = readl_relaxed(base + SPRD_EIC_LATCH_INTMSK) &
+ SPRD_EIC_DATA_MASK;
+ break;
+ case SPRD_EIC_ASYNC:
+ reg = readl_relaxed(base + SPRD_EIC_ASYNC_INTMSK) &
+ SPRD_EIC_DATA_MASK;
+ break;
+ case SPRD_EIC_SYNC:
+ reg = readl_relaxed(base + SPRD_EIC_SYNC_INTMSK) &
+ SPRD_EIC_DATA_MASK;
+ break;
+ default:
+ dev_err(chip->parent, "Unsupported EIC type.\n");
+ return;
+ }
+
+ for_each_set_bit(n, &reg, SPRD_EIC_PER_BANK_NR) {
+ u32 offset = bank * SPRD_EIC_PER_BANK_NR + n;
+
+ girq = irq_find_mapping(chip->irq.domain, offset);
+
+ generic_handle_irq(girq);
+ sprd_eic_toggle_trigger(chip, girq, offset);
+ }
+ }
+}
+
+static void sprd_eic_irq_handler(struct irq_desc *desc)
+{
+ struct irq_chip *ic = irq_desc_get_chip(desc);
+ struct gpio_chip *chip;
+ enum sprd_eic_type type;
+
+ chained_irq_enter(ic, desc);
+
+ /*
+ * Since the digital-chip EIC 4 sub-modules (debounce, latch, async
+ * and sync) share one same interrupt line, we should iterate each
+ * EIC module to check if there are EIC interrupts were triggered.
+ */
+ for (type = SPRD_EIC_DEBOUNCE; type < SPRD_EIC_MAX; type++) {
+ chip = gpiochip_find(&type, sprd_eic_match_chip_by_type);
+ if (!chip)
+ continue;
+
+ sprd_eic_handle_one_type(chip);
+ }
+
+ chained_irq_exit(ic, desc);
+}
+
+static const struct irq_chip sprd_eic_irq = {
+ .name = "sprd-eic",
+ .irq_ack = sprd_eic_irq_ack,
+ .irq_mask = sprd_eic_irq_mask,
+ .irq_unmask = sprd_eic_irq_unmask,
+ .irq_set_type = sprd_eic_irq_set_type,
+ .flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+static int sprd_eic_probe(struct platform_device *pdev)
+{
+ const struct sprd_eic_variant_data *pdata;
+ struct gpio_irq_chip *irq;
+ struct sprd_eic *sprd_eic;
+ struct resource *res;
+ int ret, i;
+
+ pdata = of_device_get_match_data(&pdev->dev);
+ if (!pdata) {
+ dev_err(&pdev->dev, "No matching driver data found.\n");
+ return -EINVAL;
+ }
+
+ sprd_eic = devm_kzalloc(&pdev->dev, sizeof(*sprd_eic), GFP_KERNEL);
+ if (!sprd_eic)
+ return -ENOMEM;
+
+ spin_lock_init(&sprd_eic->lock);
+ sprd_eic->type = pdata->type;
+
+ sprd_eic->irq = platform_get_irq(pdev, 0);
+ if (sprd_eic->irq < 0)
+ return sprd_eic->irq;
+
+ for (i = 0; i < SPRD_EIC_MAX_BANK; i++) {
+ /*
+ * We can have maximum 3 banks EICs, and each EIC has
+ * its own base address. But some platform maybe only
+ * have one bank EIC, thus base[1] and base[2] can be
+ * optional.
+ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+ if (!res)
+ break;
+
+ sprd_eic->base[i] = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(sprd_eic->base[i]))
+ return PTR_ERR(sprd_eic->base[i]);
+ }
+
+ sprd_eic->chip.label = sprd_eic_label_name[sprd_eic->type];
+ sprd_eic->chip.ngpio = pdata->num_eics;
+ sprd_eic->chip.base = -1;
+ sprd_eic->chip.parent = &pdev->dev;
+ sprd_eic->chip.direction_input = sprd_eic_direction_input;
+ switch (sprd_eic->type) {
+ case SPRD_EIC_DEBOUNCE:
+ sprd_eic->chip.request = sprd_eic_request;
+ sprd_eic->chip.free = sprd_eic_free;
+ sprd_eic->chip.set_config = sprd_eic_set_config;
+ sprd_eic->chip.set = sprd_eic_set;
+ fallthrough;
+ case SPRD_EIC_ASYNC:
+ case SPRD_EIC_SYNC:
+ sprd_eic->chip.get = sprd_eic_get;
+ break;
+ case SPRD_EIC_LATCH:
+ default:
+ break;
+ }
+
+ irq = &sprd_eic->chip.irq;
+ gpio_irq_chip_set_chip(irq, &sprd_eic_irq);
+ irq->handler = handle_bad_irq;
+ irq->default_type = IRQ_TYPE_NONE;
+ irq->parent_handler = sprd_eic_irq_handler;
+ irq->parent_handler_data = sprd_eic;
+ irq->num_parents = 1;
+ irq->parents = &sprd_eic->irq;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &sprd_eic->chip, sprd_eic);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Could not register gpiochip %d.\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id sprd_eic_of_match[] = {
+ {
+ .compatible = "sprd,sc9860-eic-debounce",
+ .data = &sc9860_eic_dbnc_data,
+ },
+ {
+ .compatible = "sprd,sc9860-eic-latch",
+ .data = &sc9860_eic_latch_data,
+ },
+ {
+ .compatible = "sprd,sc9860-eic-async",
+ .data = &sc9860_eic_async_data,
+ },
+ {
+ .compatible = "sprd,sc9860-eic-sync",
+ .data = &sc9860_eic_sync_data,
+ },
+ {
+ /* end of list */
+ }
+};
+MODULE_DEVICE_TABLE(of, sprd_eic_of_match);
+
+static struct platform_driver sprd_eic_driver = {
+ .probe = sprd_eic_probe,
+ .driver = {
+ .name = "sprd-eic",
+ .of_match_table = sprd_eic_of_match,
+ },
+};
+
+module_platform_driver(sprd_eic_driver);
+
+MODULE_DESCRIPTION("Spreadtrum EIC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-elkhartlake.c b/drivers/gpio/gpio-elkhartlake.c
new file mode 100644
index 0000000000..a9c8b16215
--- /dev/null
+++ b/drivers/gpio/gpio-elkhartlake.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Elkhart Lake PSE GPIO driver
+ *
+ * Copyright (c) 2023 Intel Corporation.
+ *
+ * Authors: Pandith N <pandith.n@intel.com>
+ * Raag Jadav <raag.jadav@intel.com>
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+
+#include "gpio-tangier.h"
+
+/* Each Intel EHL PSE GPIO Controller has 30 GPIO pins */
+#define EHL_PSE_NGPIO 30
+
+static int ehl_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tng_gpio *priv;
+ int irq, ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->reg_base))
+ return PTR_ERR(priv->reg_base);
+
+ priv->dev = dev;
+ priv->irq = irq;
+
+ priv->info.base = -1;
+ priv->info.ngpio = EHL_PSE_NGPIO;
+
+ priv->wake_regs.gwmr = GWMR_EHL;
+ priv->wake_regs.gwsr = GWSR_EHL;
+ priv->wake_regs.gsir = GSIR_EHL;
+
+ ret = devm_tng_gpio_probe(dev, priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "tng_gpio_probe error\n");
+
+ platform_set_drvdata(pdev, priv);
+ return 0;
+}
+
+static int ehl_gpio_suspend(struct device *dev)
+{
+ return tng_gpio_suspend(dev);
+}
+
+static int ehl_gpio_resume(struct device *dev)
+{
+ return tng_gpio_resume(dev);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(ehl_gpio_pm_ops, ehl_gpio_suspend, ehl_gpio_resume);
+
+static const struct platform_device_id ehl_gpio_ids[] = {
+ { "gpio-elkhartlake" },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, ehl_gpio_ids);
+
+static struct platform_driver ehl_gpio_driver = {
+ .driver = {
+ .name = "gpio-elkhartlake",
+ .pm = pm_sleep_ptr(&ehl_gpio_pm_ops),
+ },
+ .probe = ehl_gpio_probe,
+ .id_table = ehl_gpio_ids,
+};
+module_platform_driver(ehl_gpio_driver);
+
+MODULE_AUTHOR("Pandith N <pandith.n@intel.com>");
+MODULE_AUTHOR("Raag Jadav <raag.jadav@intel.com>");
+MODULE_DESCRIPTION("Intel Elkhart Lake PSE GPIO driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(GPIO_TANGIER);
diff --git a/drivers/gpio/gpio-em.c b/drivers/gpio/gpio-em.c
new file mode 100644
index 0000000000..858e6ebbb5
--- /dev/null
+++ b/drivers/gpio/gpio-em.c
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Emma Mobile GPIO Support - GIO
+ *
+ * Copyright (C) 2012 Magnus Damm
+ */
+
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/pinctrl/consumer.h>
+
+struct em_gio_priv {
+ void __iomem *base0;
+ void __iomem *base1;
+ spinlock_t sense_lock;
+ struct platform_device *pdev;
+ struct gpio_chip gpio_chip;
+ struct irq_chip irq_chip;
+ struct irq_domain *irq_domain;
+};
+
+#define GIO_E1 0x00
+#define GIO_E0 0x04
+#define GIO_EM 0x04
+#define GIO_OL 0x08
+#define GIO_OH 0x0c
+#define GIO_I 0x10
+#define GIO_IIA 0x14
+#define GIO_IEN 0x18
+#define GIO_IDS 0x1c
+#define GIO_IIM 0x1c
+#define GIO_RAW 0x20
+#define GIO_MST 0x24
+#define GIO_IIR 0x28
+
+#define GIO_IDT0 0x40
+#define GIO_IDT1 0x44
+#define GIO_IDT2 0x48
+#define GIO_IDT3 0x4c
+#define GIO_RAWBL 0x50
+#define GIO_RAWBH 0x54
+#define GIO_IRBL 0x58
+#define GIO_IRBH 0x5c
+
+#define GIO_IDT(n) (GIO_IDT0 + ((n) * 4))
+
+static inline unsigned long em_gio_read(struct em_gio_priv *p, int offs)
+{
+ if (offs < GIO_IDT0)
+ return ioread32(p->base0 + offs);
+ else
+ return ioread32(p->base1 + (offs - GIO_IDT0));
+}
+
+static inline void em_gio_write(struct em_gio_priv *p, int offs,
+ unsigned long value)
+{
+ if (offs < GIO_IDT0)
+ iowrite32(value, p->base0 + offs);
+ else
+ iowrite32(value, p->base1 + (offs - GIO_IDT0));
+}
+
+static void em_gio_irq_disable(struct irq_data *d)
+{
+ struct em_gio_priv *p = irq_data_get_irq_chip_data(d);
+
+ em_gio_write(p, GIO_IDS, BIT(irqd_to_hwirq(d)));
+}
+
+static void em_gio_irq_enable(struct irq_data *d)
+{
+ struct em_gio_priv *p = irq_data_get_irq_chip_data(d);
+
+ em_gio_write(p, GIO_IEN, BIT(irqd_to_hwirq(d)));
+}
+
+static int em_gio_irq_reqres(struct irq_data *d)
+{
+ struct em_gio_priv *p = irq_data_get_irq_chip_data(d);
+ int ret;
+
+ ret = gpiochip_lock_as_irq(&p->gpio_chip, irqd_to_hwirq(d));
+ if (ret) {
+ dev_err(p->gpio_chip.parent,
+ "unable to lock HW IRQ %lu for IRQ\n",
+ irqd_to_hwirq(d));
+ return ret;
+ }
+ return 0;
+}
+
+static void em_gio_irq_relres(struct irq_data *d)
+{
+ struct em_gio_priv *p = irq_data_get_irq_chip_data(d);
+
+ gpiochip_unlock_as_irq(&p->gpio_chip, irqd_to_hwirq(d));
+}
+
+
+#define GIO_ASYNC(x) (x + 8)
+
+static unsigned char em_gio_sense_table[IRQ_TYPE_SENSE_MASK + 1] = {
+ [IRQ_TYPE_EDGE_RISING] = GIO_ASYNC(0x00),
+ [IRQ_TYPE_EDGE_FALLING] = GIO_ASYNC(0x01),
+ [IRQ_TYPE_LEVEL_HIGH] = GIO_ASYNC(0x02),
+ [IRQ_TYPE_LEVEL_LOW] = GIO_ASYNC(0x03),
+ [IRQ_TYPE_EDGE_BOTH] = GIO_ASYNC(0x04),
+};
+
+static int em_gio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ unsigned char value = em_gio_sense_table[type & IRQ_TYPE_SENSE_MASK];
+ struct em_gio_priv *p = irq_data_get_irq_chip_data(d);
+ unsigned int reg, offset, shift;
+ unsigned long flags;
+ unsigned long tmp;
+
+ if (!value)
+ return -EINVAL;
+
+ offset = irqd_to_hwirq(d);
+
+ pr_debug("gio: sense irq = %d, mode = %d\n", offset, value);
+
+ /* 8 x 4 bit fields in 4 IDT registers */
+ reg = GIO_IDT(offset >> 3);
+ shift = (offset & 0x07) << 4;
+
+ spin_lock_irqsave(&p->sense_lock, flags);
+
+ /* disable the interrupt in IIA */
+ tmp = em_gio_read(p, GIO_IIA);
+ tmp &= ~BIT(offset);
+ em_gio_write(p, GIO_IIA, tmp);
+
+ /* change the sense setting in IDT */
+ tmp = em_gio_read(p, reg);
+ tmp &= ~(0xf << shift);
+ tmp |= value << shift;
+ em_gio_write(p, reg, tmp);
+
+ /* clear pending interrupts */
+ em_gio_write(p, GIO_IIR, BIT(offset));
+
+ /* enable the interrupt in IIA */
+ tmp = em_gio_read(p, GIO_IIA);
+ tmp |= BIT(offset);
+ em_gio_write(p, GIO_IIA, tmp);
+
+ spin_unlock_irqrestore(&p->sense_lock, flags);
+
+ return 0;
+}
+
+static irqreturn_t em_gio_irq_handler(int irq, void *dev_id)
+{
+ struct em_gio_priv *p = dev_id;
+ unsigned long pending;
+ unsigned int offset, irqs_handled = 0;
+
+ while ((pending = em_gio_read(p, GIO_MST))) {
+ offset = __ffs(pending);
+ em_gio_write(p, GIO_IIR, BIT(offset));
+ generic_handle_domain_irq(p->irq_domain, offset);
+ irqs_handled++;
+ }
+
+ return irqs_handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static inline struct em_gio_priv *gpio_to_priv(struct gpio_chip *chip)
+{
+ return gpiochip_get_data(chip);
+}
+
+static int em_gio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ em_gio_write(gpio_to_priv(chip), GIO_E0, BIT(offset));
+ return 0;
+}
+
+static int em_gio_get(struct gpio_chip *chip, unsigned offset)
+{
+ return !!(em_gio_read(gpio_to_priv(chip), GIO_I) & BIT(offset));
+}
+
+static void __em_gio_set(struct gpio_chip *chip, unsigned int reg,
+ unsigned shift, int value)
+{
+ /* upper 16 bits contains mask and lower 16 actual value */
+ em_gio_write(gpio_to_priv(chip), reg,
+ (BIT(shift + 16)) | (value << shift));
+}
+
+static void em_gio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ /* output is split into two registers */
+ if (offset < 16)
+ __em_gio_set(chip, GIO_OL, offset, value);
+ else
+ __em_gio_set(chip, GIO_OH, offset - 16, value);
+}
+
+static int em_gio_direction_output(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ /* write GPIO value to output before selecting output mode of pin */
+ em_gio_set(chip, offset, value);
+ em_gio_write(gpio_to_priv(chip), GIO_E1, BIT(offset));
+ return 0;
+}
+
+static int em_gio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ return irq_create_mapping(gpio_to_priv(chip)->irq_domain, offset);
+}
+
+static int em_gio_request(struct gpio_chip *chip, unsigned offset)
+{
+ return pinctrl_gpio_request(chip->base + offset);
+}
+
+static void em_gio_free(struct gpio_chip *chip, unsigned offset)
+{
+ pinctrl_gpio_free(chip->base + offset);
+
+ /* Set the GPIO as an input to ensure that the next GPIO request won't
+ * drive the GPIO pin as an output.
+ */
+ em_gio_direction_input(chip, offset);
+}
+
+static int em_gio_irq_domain_map(struct irq_domain *h, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ struct em_gio_priv *p = h->host_data;
+
+ pr_debug("gio: map hw irq = %d, irq = %d\n", (int)hwirq, irq);
+
+ irq_set_chip_data(irq, h->host_data);
+ irq_set_chip_and_handler(irq, &p->irq_chip, handle_level_irq);
+ return 0;
+}
+
+static const struct irq_domain_ops em_gio_irq_domain_ops = {
+ .map = em_gio_irq_domain_map,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+static void em_gio_irq_domain_remove(void *data)
+{
+ struct irq_domain *domain = data;
+
+ irq_domain_remove(domain);
+}
+
+static int em_gio_probe(struct platform_device *pdev)
+{
+ struct em_gio_priv *p;
+ struct gpio_chip *gpio_chip;
+ struct irq_chip *irq_chip;
+ struct device *dev = &pdev->dev;
+ const char *name = dev_name(dev);
+ unsigned int ngpios;
+ int irq[2], ret;
+
+ p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ p->pdev = pdev;
+ platform_set_drvdata(pdev, p);
+ spin_lock_init(&p->sense_lock);
+
+ irq[0] = platform_get_irq(pdev, 0);
+ if (irq[0] < 0)
+ return irq[0];
+
+ irq[1] = platform_get_irq(pdev, 1);
+ if (irq[1] < 0)
+ return irq[1];
+
+ p->base0 = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(p->base0))
+ return PTR_ERR(p->base0);
+
+ p->base1 = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(p->base1))
+ return PTR_ERR(p->base1);
+
+ if (of_property_read_u32(dev->of_node, "ngpios", &ngpios)) {
+ dev_err(dev, "Missing ngpios OF property\n");
+ return -EINVAL;
+ }
+
+ gpio_chip = &p->gpio_chip;
+ gpio_chip->direction_input = em_gio_direction_input;
+ gpio_chip->get = em_gio_get;
+ gpio_chip->direction_output = em_gio_direction_output;
+ gpio_chip->set = em_gio_set;
+ gpio_chip->to_irq = em_gio_to_irq;
+ gpio_chip->request = em_gio_request;
+ gpio_chip->free = em_gio_free;
+ gpio_chip->label = name;
+ gpio_chip->parent = dev;
+ gpio_chip->owner = THIS_MODULE;
+ gpio_chip->base = -1;
+ gpio_chip->ngpio = ngpios;
+
+ irq_chip = &p->irq_chip;
+ irq_chip->name = "gpio-em";
+ irq_chip->irq_mask = em_gio_irq_disable;
+ irq_chip->irq_unmask = em_gio_irq_enable;
+ irq_chip->irq_set_type = em_gio_irq_set_type;
+ irq_chip->irq_request_resources = em_gio_irq_reqres;
+ irq_chip->irq_release_resources = em_gio_irq_relres;
+ irq_chip->flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_MASK_ON_SUSPEND;
+
+ p->irq_domain = irq_domain_add_simple(dev->of_node, ngpios, 0,
+ &em_gio_irq_domain_ops, p);
+ if (!p->irq_domain) {
+ dev_err(dev, "cannot initialize irq domain\n");
+ return -ENXIO;
+ }
+
+ ret = devm_add_action_or_reset(dev, em_gio_irq_domain_remove,
+ p->irq_domain);
+ if (ret)
+ return ret;
+
+ if (devm_request_irq(dev, irq[0], em_gio_irq_handler, 0, name, p)) {
+ dev_err(dev, "failed to request low IRQ\n");
+ return -ENOENT;
+ }
+
+ if (devm_request_irq(dev, irq[1], em_gio_irq_handler, 0, name, p)) {
+ dev_err(dev, "failed to request high IRQ\n");
+ return -ENOENT;
+ }
+
+ ret = devm_gpiochip_add_data(dev, gpio_chip, p);
+ if (ret) {
+ dev_err(dev, "failed to add GPIO controller\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id em_gio_dt_ids[] = {
+ { .compatible = "renesas,em-gio", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, em_gio_dt_ids);
+
+static struct platform_driver em_gio_device_driver = {
+ .probe = em_gio_probe,
+ .driver = {
+ .name = "em_gio",
+ .of_match_table = em_gio_dt_ids,
+ }
+};
+
+static int __init em_gio_init(void)
+{
+ return platform_driver_register(&em_gio_device_driver);
+}
+postcore_initcall(em_gio_init);
+
+static void __exit em_gio_exit(void)
+{
+ platform_driver_unregister(&em_gio_device_driver);
+}
+module_exit(em_gio_exit);
+
+MODULE_AUTHOR("Magnus Damm");
+MODULE_DESCRIPTION("Renesas Emma Mobile GIO Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-en7523.c b/drivers/gpio/gpio-en7523.c
new file mode 100644
index 0000000000..f836a8db4c
--- /dev/null
+++ b/drivers/gpio/gpio-en7523.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/types.h>
+#include <linux/io.h>
+#include <linux/bits.h>
+#include <linux/gpio/driver.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+#define AIROHA_GPIO_MAX 32
+
+/**
+ * airoha_gpio_ctrl - Airoha GPIO driver data
+ * @gc: Associated gpio_chip instance.
+ * @data: The data register.
+ * @dir0: The direction register for the lower 16 pins.
+ * @dir1: The direction register for the higher 16 pins.
+ * @output: The output enable register.
+ */
+struct airoha_gpio_ctrl {
+ struct gpio_chip gc;
+ void __iomem *data;
+ void __iomem *dir[2];
+ void __iomem *output;
+};
+
+static struct airoha_gpio_ctrl *gc_to_ctrl(struct gpio_chip *gc)
+{
+ return container_of(gc, struct airoha_gpio_ctrl, gc);
+}
+
+static int airoha_dir_set(struct gpio_chip *gc, unsigned int gpio,
+ int val, int out)
+{
+ struct airoha_gpio_ctrl *ctrl = gc_to_ctrl(gc);
+ u32 dir = ioread32(ctrl->dir[gpio / 16]);
+ u32 output = ioread32(ctrl->output);
+ u32 mask = BIT((gpio % 16) * 2);
+
+ if (out) {
+ dir |= mask;
+ output |= BIT(gpio);
+ } else {
+ dir &= ~mask;
+ output &= ~BIT(gpio);
+ }
+
+ iowrite32(dir, ctrl->dir[gpio / 16]);
+
+ if (out)
+ gc->set(gc, gpio, val);
+
+ iowrite32(output, ctrl->output);
+
+ return 0;
+}
+
+static int airoha_dir_out(struct gpio_chip *gc, unsigned int gpio,
+ int val)
+{
+ return airoha_dir_set(gc, gpio, val, 1);
+}
+
+static int airoha_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+ return airoha_dir_set(gc, gpio, 0, 0);
+}
+
+static int airoha_get_dir(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct airoha_gpio_ctrl *ctrl = gc_to_ctrl(gc);
+ u32 dir = ioread32(ctrl->dir[gpio / 16]);
+ u32 mask = BIT((gpio % 16) * 2);
+
+ return (dir & mask) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+}
+
+static int airoha_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct airoha_gpio_ctrl *ctrl;
+ int err;
+
+ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ ctrl->data = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctrl->data))
+ return PTR_ERR(ctrl->data);
+
+ ctrl->dir[0] = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(ctrl->dir[0]))
+ return PTR_ERR(ctrl->dir[0]);
+
+ ctrl->dir[1] = devm_platform_ioremap_resource(pdev, 2);
+ if (IS_ERR(ctrl->dir[1]))
+ return PTR_ERR(ctrl->dir[1]);
+
+ ctrl->output = devm_platform_ioremap_resource(pdev, 3);
+ if (IS_ERR(ctrl->output))
+ return PTR_ERR(ctrl->output);
+
+ err = bgpio_init(&ctrl->gc, dev, 4, ctrl->data, NULL,
+ NULL, NULL, NULL, 0);
+ if (err)
+ return dev_err_probe(dev, err, "unable to init generic GPIO");
+
+ ctrl->gc.ngpio = AIROHA_GPIO_MAX;
+ ctrl->gc.owner = THIS_MODULE;
+ ctrl->gc.direction_output = airoha_dir_out;
+ ctrl->gc.direction_input = airoha_dir_in;
+ ctrl->gc.get_direction = airoha_get_dir;
+
+ return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl);
+}
+
+static const struct of_device_id airoha_gpio_of_match[] = {
+ { .compatible = "airoha,en7523-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, airoha_gpio_of_match);
+
+static struct platform_driver airoha_gpio_driver = {
+ .driver = {
+ .name = "airoha-gpio",
+ .of_match_table = airoha_gpio_of_match,
+ },
+ .probe = airoha_gpio_probe,
+};
+module_platform_driver(airoha_gpio_driver);
+
+MODULE_DESCRIPTION("Airoha GPIO support");
+MODULE_AUTHOR("John Crispin <john@phrozen.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-ep93xx.c b/drivers/gpio/gpio-ep93xx.c
new file mode 100644
index 0000000000..6cedf46efe
--- /dev/null
+++ b/drivers/gpio/gpio-ep93xx.c
@@ -0,0 +1,467 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generic EP93xx GPIO handling
+ *
+ * Copyright (c) 2008 Ryan Mallon
+ * Copyright (c) 2011 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Based on code originally from:
+ * linux/arch/arm/mach-ep93xx/core.c
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/gpio/driver.h>
+#include <linux/bitops.h>
+#include <linux/seq_file.h>
+
+#define EP93XX_GPIO_F_INT_STATUS 0x5c
+#define EP93XX_GPIO_A_INT_STATUS 0xa0
+#define EP93XX_GPIO_B_INT_STATUS 0xbc
+
+/* Maximum value for gpio line identifiers */
+#define EP93XX_GPIO_LINE_MAX 63
+
+/* Number of GPIO chips in EP93XX */
+#define EP93XX_GPIO_CHIP_NUM 8
+
+/* Maximum value for irq capable line identifiers */
+#define EP93XX_GPIO_LINE_MAX_IRQ 23
+
+#define EP93XX_GPIO_A_IRQ_BASE 64
+#define EP93XX_GPIO_B_IRQ_BASE 72
+/*
+ * Static mapping of GPIO bank F IRQS:
+ * F0..F7 (16..24) to irq 80..87.
+ */
+#define EP93XX_GPIO_F_IRQ_BASE 80
+
+struct ep93xx_gpio_irq_chip {
+ u8 irq_offset;
+ u8 int_unmasked;
+ u8 int_enabled;
+ u8 int_type1;
+ u8 int_type2;
+ u8 int_debounce;
+};
+
+struct ep93xx_gpio_chip {
+ struct gpio_chip gc;
+ struct ep93xx_gpio_irq_chip *eic;
+};
+
+struct ep93xx_gpio {
+ void __iomem *base;
+ struct ep93xx_gpio_chip gc[EP93XX_GPIO_CHIP_NUM];
+};
+
+#define to_ep93xx_gpio_chip(x) container_of(x, struct ep93xx_gpio_chip, gc)
+
+static struct ep93xx_gpio_irq_chip *to_ep93xx_gpio_irq_chip(struct gpio_chip *gc)
+{
+ struct ep93xx_gpio_chip *egc = to_ep93xx_gpio_chip(gc);
+
+ return egc->eic;
+}
+
+/*************************************************************************
+ * Interrupt handling for EP93xx on-chip GPIOs
+ *************************************************************************/
+#define EP93XX_INT_TYPE1_OFFSET 0x00
+#define EP93XX_INT_TYPE2_OFFSET 0x04
+#define EP93XX_INT_EOI_OFFSET 0x08
+#define EP93XX_INT_EN_OFFSET 0x0c
+#define EP93XX_INT_STATUS_OFFSET 0x10
+#define EP93XX_INT_RAW_STATUS_OFFSET 0x14
+#define EP93XX_INT_DEBOUNCE_OFFSET 0x18
+
+static void ep93xx_gpio_update_int_params(struct ep93xx_gpio *epg,
+ struct ep93xx_gpio_irq_chip *eic)
+{
+ writeb_relaxed(0, epg->base + eic->irq_offset + EP93XX_INT_EN_OFFSET);
+
+ writeb_relaxed(eic->int_type2,
+ epg->base + eic->irq_offset + EP93XX_INT_TYPE2_OFFSET);
+
+ writeb_relaxed(eic->int_type1,
+ epg->base + eic->irq_offset + EP93XX_INT_TYPE1_OFFSET);
+
+ writeb_relaxed(eic->int_unmasked & eic->int_enabled,
+ epg->base + eic->irq_offset + EP93XX_INT_EN_OFFSET);
+}
+
+static void ep93xx_gpio_int_debounce(struct gpio_chip *gc,
+ unsigned int offset, bool enable)
+{
+ struct ep93xx_gpio *epg = gpiochip_get_data(gc);
+ struct ep93xx_gpio_irq_chip *eic = to_ep93xx_gpio_irq_chip(gc);
+ int port_mask = BIT(offset);
+
+ if (enable)
+ eic->int_debounce |= port_mask;
+ else
+ eic->int_debounce &= ~port_mask;
+
+ writeb(eic->int_debounce,
+ epg->base + eic->irq_offset + EP93XX_INT_DEBOUNCE_OFFSET);
+}
+
+static void ep93xx_gpio_ab_irq_handler(struct irq_desc *desc)
+{
+ struct gpio_chip *gc = irq_desc_get_handler_data(desc);
+ struct ep93xx_gpio *epg = gpiochip_get_data(gc);
+ struct irq_chip *irqchip = irq_desc_get_chip(desc);
+ unsigned long stat;
+ int offset;
+
+ chained_irq_enter(irqchip, desc);
+
+ /*
+ * Dispatch the IRQs to the irqdomain of each A and B
+ * gpiochip irqdomains depending on what has fired.
+ * The tricky part is that the IRQ line is shared
+ * between bank A and B and each has their own gpiochip.
+ */
+ stat = readb(epg->base + EP93XX_GPIO_A_INT_STATUS);
+ for_each_set_bit(offset, &stat, 8)
+ generic_handle_domain_irq(epg->gc[0].gc.irq.domain,
+ offset);
+
+ stat = readb(epg->base + EP93XX_GPIO_B_INT_STATUS);
+ for_each_set_bit(offset, &stat, 8)
+ generic_handle_domain_irq(epg->gc[1].gc.irq.domain,
+ offset);
+
+ chained_irq_exit(irqchip, desc);
+}
+
+static void ep93xx_gpio_f_irq_handler(struct irq_desc *desc)
+{
+ /*
+ * map discontiguous hw irq range to continuous sw irq range:
+ *
+ * IRQ_EP93XX_GPIO{0..7}MUX -> EP93XX_GPIO_LINE_F{0..7}
+ */
+ struct irq_chip *irqchip = irq_desc_get_chip(desc);
+ unsigned int irq = irq_desc_get_irq(desc);
+ int port_f_idx = (irq & 7) ^ 4; /* {20..23,48..51} -> {0..7} */
+ int gpio_irq = EP93XX_GPIO_F_IRQ_BASE + port_f_idx;
+
+ chained_irq_enter(irqchip, desc);
+ generic_handle_irq(gpio_irq);
+ chained_irq_exit(irqchip, desc);
+}
+
+static void ep93xx_gpio_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct ep93xx_gpio_irq_chip *eic = to_ep93xx_gpio_irq_chip(gc);
+ struct ep93xx_gpio *epg = gpiochip_get_data(gc);
+ int port_mask = BIT(d->irq & 7);
+
+ if (irqd_get_trigger_type(d) == IRQ_TYPE_EDGE_BOTH) {
+ eic->int_type2 ^= port_mask; /* switch edge direction */
+ ep93xx_gpio_update_int_params(epg, eic);
+ }
+
+ writeb(port_mask, epg->base + eic->irq_offset + EP93XX_INT_EOI_OFFSET);
+}
+
+static void ep93xx_gpio_irq_mask_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct ep93xx_gpio_irq_chip *eic = to_ep93xx_gpio_irq_chip(gc);
+ struct ep93xx_gpio *epg = gpiochip_get_data(gc);
+ int port_mask = BIT(d->irq & 7);
+
+ if (irqd_get_trigger_type(d) == IRQ_TYPE_EDGE_BOTH)
+ eic->int_type2 ^= port_mask; /* switch edge direction */
+
+ eic->int_unmasked &= ~port_mask;
+ ep93xx_gpio_update_int_params(epg, eic);
+
+ writeb(port_mask, epg->base + eic->irq_offset + EP93XX_INT_EOI_OFFSET);
+ gpiochip_disable_irq(gc, irqd_to_hwirq(d));
+}
+
+static void ep93xx_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct ep93xx_gpio_irq_chip *eic = to_ep93xx_gpio_irq_chip(gc);
+ struct ep93xx_gpio *epg = gpiochip_get_data(gc);
+
+ eic->int_unmasked &= ~BIT(d->irq & 7);
+ ep93xx_gpio_update_int_params(epg, eic);
+ gpiochip_disable_irq(gc, irqd_to_hwirq(d));
+}
+
+static void ep93xx_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct ep93xx_gpio_irq_chip *eic = to_ep93xx_gpio_irq_chip(gc);
+ struct ep93xx_gpio *epg = gpiochip_get_data(gc);
+
+ gpiochip_enable_irq(gc, irqd_to_hwirq(d));
+ eic->int_unmasked |= BIT(d->irq & 7);
+ ep93xx_gpio_update_int_params(epg, eic);
+}
+
+/*
+ * gpio_int_type1 controls whether the interrupt is level (0) or
+ * edge (1) triggered, while gpio_int_type2 controls whether it
+ * triggers on low/falling (0) or high/rising (1).
+ */
+static int ep93xx_gpio_irq_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct ep93xx_gpio_irq_chip *eic = to_ep93xx_gpio_irq_chip(gc);
+ struct ep93xx_gpio *epg = gpiochip_get_data(gc);
+ int offset = d->irq & 7;
+ int port_mask = BIT(offset);
+ irq_flow_handler_t handler;
+
+ gc->direction_input(gc, offset);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ eic->int_type1 |= port_mask;
+ eic->int_type2 |= port_mask;
+ handler = handle_edge_irq;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ eic->int_type1 |= port_mask;
+ eic->int_type2 &= ~port_mask;
+ handler = handle_edge_irq;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ eic->int_type1 &= ~port_mask;
+ eic->int_type2 |= port_mask;
+ handler = handle_level_irq;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ eic->int_type1 &= ~port_mask;
+ eic->int_type2 &= ~port_mask;
+ handler = handle_level_irq;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ eic->int_type1 |= port_mask;
+ /* set initial polarity based on current input level */
+ if (gc->get(gc, offset))
+ eic->int_type2 &= ~port_mask; /* falling */
+ else
+ eic->int_type2 |= port_mask; /* rising */
+ handler = handle_edge_irq;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ irq_set_handler_locked(d, handler);
+
+ eic->int_enabled |= port_mask;
+
+ ep93xx_gpio_update_int_params(epg, eic);
+
+ return 0;
+}
+
+/*************************************************************************
+ * gpiolib interface for EP93xx on-chip GPIOs
+ *************************************************************************/
+struct ep93xx_gpio_bank {
+ const char *label;
+ int data;
+ int dir;
+ int irq;
+ int base;
+ bool has_irq;
+ bool has_hierarchical_irq;
+ unsigned int irq_base;
+};
+
+#define EP93XX_GPIO_BANK(_label, _data, _dir, _irq, _base, _has_irq, _has_hier, _irq_base) \
+ { \
+ .label = _label, \
+ .data = _data, \
+ .dir = _dir, \
+ .irq = _irq, \
+ .base = _base, \
+ .has_irq = _has_irq, \
+ .has_hierarchical_irq = _has_hier, \
+ .irq_base = _irq_base, \
+ }
+
+static struct ep93xx_gpio_bank ep93xx_gpio_banks[] = {
+ /* Bank A has 8 IRQs */
+ EP93XX_GPIO_BANK("A", 0x00, 0x10, 0x90, 0, true, false, EP93XX_GPIO_A_IRQ_BASE),
+ /* Bank B has 8 IRQs */
+ EP93XX_GPIO_BANK("B", 0x04, 0x14, 0xac, 8, true, false, EP93XX_GPIO_B_IRQ_BASE),
+ EP93XX_GPIO_BANK("C", 0x08, 0x18, 0x00, 40, false, false, 0),
+ EP93XX_GPIO_BANK("D", 0x0c, 0x1c, 0x00, 24, false, false, 0),
+ EP93XX_GPIO_BANK("E", 0x20, 0x24, 0x00, 32, false, false, 0),
+ /* Bank F has 8 IRQs */
+ EP93XX_GPIO_BANK("F", 0x30, 0x34, 0x4c, 16, false, true, EP93XX_GPIO_F_IRQ_BASE),
+ EP93XX_GPIO_BANK("G", 0x38, 0x3c, 0x00, 48, false, false, 0),
+ EP93XX_GPIO_BANK("H", 0x40, 0x44, 0x00, 56, false, false, 0),
+};
+
+static int ep93xx_gpio_set_config(struct gpio_chip *gc, unsigned offset,
+ unsigned long config)
+{
+ u32 debounce;
+
+ if (pinconf_to_config_param(config) != PIN_CONFIG_INPUT_DEBOUNCE)
+ return -ENOTSUPP;
+
+ debounce = pinconf_to_config_argument(config);
+ ep93xx_gpio_int_debounce(gc, offset, debounce ? true : false);
+
+ return 0;
+}
+
+static void ep93xx_irq_print_chip(struct irq_data *data, struct seq_file *p)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+
+ seq_printf(p, dev_name(gc->parent));
+}
+
+static const struct irq_chip gpio_eic_irq_chip = {
+ .name = "ep93xx-gpio-eic",
+ .irq_ack = ep93xx_gpio_irq_ack,
+ .irq_mask = ep93xx_gpio_irq_mask,
+ .irq_unmask = ep93xx_gpio_irq_unmask,
+ .irq_mask_ack = ep93xx_gpio_irq_mask_ack,
+ .irq_set_type = ep93xx_gpio_irq_type,
+ .irq_print_chip = ep93xx_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int ep93xx_gpio_add_bank(struct ep93xx_gpio_chip *egc,
+ struct platform_device *pdev,
+ struct ep93xx_gpio *epg,
+ struct ep93xx_gpio_bank *bank)
+{
+ void __iomem *data = epg->base + bank->data;
+ void __iomem *dir = epg->base + bank->dir;
+ struct gpio_chip *gc = &egc->gc;
+ struct device *dev = &pdev->dev;
+ struct gpio_irq_chip *girq;
+ int err;
+
+ err = bgpio_init(gc, dev, 1, data, NULL, NULL, dir, NULL, 0);
+ if (err)
+ return err;
+
+ gc->label = bank->label;
+ gc->base = bank->base;
+
+ girq = &gc->irq;
+ if (bank->has_irq || bank->has_hierarchical_irq) {
+ gc->set_config = ep93xx_gpio_set_config;
+ egc->eic = devm_kcalloc(dev, 1,
+ sizeof(*egc->eic),
+ GFP_KERNEL);
+ if (!egc->eic)
+ return -ENOMEM;
+ egc->eic->irq_offset = bank->irq;
+ gpio_irq_chip_set_chip(girq, &gpio_eic_irq_chip);
+ }
+
+ if (bank->has_irq) {
+ int ab_parent_irq = platform_get_irq(pdev, 0);
+
+ girq->parent_handler = ep93xx_gpio_ab_irq_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(dev, girq->num_parents,
+ sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_level_irq;
+ girq->parents[0] = ab_parent_irq;
+ girq->first = bank->irq_base;
+ }
+
+ /* Only bank F has especially funky IRQ handling */
+ if (bank->has_hierarchical_irq) {
+ int gpio_irq;
+ int i;
+
+ /*
+ * FIXME: convert this to use hierarchical IRQ support!
+ * this requires fixing the root irqchip to be hierarchical.
+ */
+ girq->parent_handler = ep93xx_gpio_f_irq_handler;
+ girq->num_parents = 8;
+ girq->parents = devm_kcalloc(dev, girq->num_parents,
+ sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ /* Pick resources 1..8 for these IRQs */
+ for (i = 0; i < girq->num_parents; i++) {
+ girq->parents[i] = platform_get_irq(pdev, i + 1);
+ gpio_irq = bank->irq_base + i;
+ irq_set_chip_data(gpio_irq, &epg->gc[5]);
+ irq_set_chip_and_handler(gpio_irq,
+ girq->chip,
+ handle_level_irq);
+ irq_clear_status_flags(gpio_irq, IRQ_NOREQUEST);
+ }
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_level_irq;
+ girq->first = bank->irq_base;
+ }
+
+ return devm_gpiochip_add_data(dev, gc, epg);
+}
+
+static int ep93xx_gpio_probe(struct platform_device *pdev)
+{
+ struct ep93xx_gpio *epg;
+ int i;
+
+ epg = devm_kzalloc(&pdev->dev, sizeof(*epg), GFP_KERNEL);
+ if (!epg)
+ return -ENOMEM;
+
+ epg->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(epg->base))
+ return PTR_ERR(epg->base);
+
+ for (i = 0; i < ARRAY_SIZE(ep93xx_gpio_banks); i++) {
+ struct ep93xx_gpio_chip *gc = &epg->gc[i];
+ struct ep93xx_gpio_bank *bank = &ep93xx_gpio_banks[i];
+
+ if (ep93xx_gpio_add_bank(gc, pdev, epg, bank))
+ dev_warn(&pdev->dev, "Unable to add gpio bank %s\n",
+ bank->label);
+ }
+
+ return 0;
+}
+
+static struct platform_driver ep93xx_gpio_driver = {
+ .driver = {
+ .name = "gpio-ep93xx",
+ },
+ .probe = ep93xx_gpio_probe,
+};
+
+static int __init ep93xx_gpio_init(void)
+{
+ return platform_driver_register(&ep93xx_gpio_driver);
+}
+postcore_initcall(ep93xx_gpio_init);
+
+MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com> "
+ "H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_DESCRIPTION("EP93XX GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-exar.c b/drivers/gpio/gpio-exar.c
new file mode 100644
index 0000000000..5170fe7599
--- /dev/null
+++ b/drivers/gpio/gpio-exar.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for Exar XR17V35X chip
+ *
+ * Copyright (C) 2015 Sudip Mukherjee <sudip.mukherjee@codethink.co.uk>
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/idr.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define EXAR_OFFSET_MPIOLVL_LO 0x90
+#define EXAR_OFFSET_MPIOSEL_LO 0x93
+#define EXAR_OFFSET_MPIOLVL_HI 0x96
+#define EXAR_OFFSET_MPIOSEL_HI 0x99
+
+/*
+ * The Device Configuration and UART Configuration Registers
+ * for each UART channel take 1KB of memory address space.
+ */
+#define EXAR_UART_CHANNEL_SIZE 0x400
+
+#define DRIVER_NAME "gpio_exar"
+
+static DEFINE_IDA(ida_index);
+
+struct exar_gpio_chip {
+ struct gpio_chip gpio_chip;
+ struct regmap *regmap;
+ int index;
+ char name[20];
+ unsigned int first_pin;
+ /*
+ * The offset to the cascaded device's (if existing)
+ * Device Configuration Registers.
+ */
+ unsigned int cascaded_offset;
+};
+
+static unsigned int
+exar_offset_to_sel_addr(struct exar_gpio_chip *exar_gpio, unsigned int offset)
+{
+ unsigned int pin = exar_gpio->first_pin + (offset % 16);
+ unsigned int cascaded = offset / 16;
+ unsigned int addr = pin / 8 ? EXAR_OFFSET_MPIOSEL_HI : EXAR_OFFSET_MPIOSEL_LO;
+
+ return addr + (cascaded ? exar_gpio->cascaded_offset : 0);
+}
+
+static unsigned int
+exar_offset_to_lvl_addr(struct exar_gpio_chip *exar_gpio, unsigned int offset)
+{
+ unsigned int pin = exar_gpio->first_pin + (offset % 16);
+ unsigned int cascaded = offset / 16;
+ unsigned int addr = pin / 8 ? EXAR_OFFSET_MPIOLVL_HI : EXAR_OFFSET_MPIOLVL_LO;
+
+ return addr + (cascaded ? exar_gpio->cascaded_offset : 0);
+}
+
+static unsigned int
+exar_offset_to_bit(struct exar_gpio_chip *exar_gpio, unsigned int offset)
+{
+ unsigned int pin = exar_gpio->first_pin + (offset % 16);
+
+ return pin % 8;
+}
+
+static int exar_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ struct exar_gpio_chip *exar_gpio = gpiochip_get_data(chip);
+ unsigned int addr = exar_offset_to_sel_addr(exar_gpio, offset);
+ unsigned int bit = exar_offset_to_bit(exar_gpio, offset);
+
+ if (regmap_test_bits(exar_gpio->regmap, addr, BIT(bit)))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int exar_get_value(struct gpio_chip *chip, unsigned int offset)
+{
+ struct exar_gpio_chip *exar_gpio = gpiochip_get_data(chip);
+ unsigned int addr = exar_offset_to_lvl_addr(exar_gpio, offset);
+ unsigned int bit = exar_offset_to_bit(exar_gpio, offset);
+
+ return !!(regmap_test_bits(exar_gpio->regmap, addr, BIT(bit)));
+}
+
+static void exar_set_value(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct exar_gpio_chip *exar_gpio = gpiochip_get_data(chip);
+ unsigned int addr = exar_offset_to_lvl_addr(exar_gpio, offset);
+ unsigned int bit = exar_offset_to_bit(exar_gpio, offset);
+
+ if (value)
+ regmap_set_bits(exar_gpio->regmap, addr, BIT(bit));
+ else
+ regmap_clear_bits(exar_gpio->regmap, addr, BIT(bit));
+}
+
+static int exar_direction_output(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct exar_gpio_chip *exar_gpio = gpiochip_get_data(chip);
+ unsigned int addr = exar_offset_to_sel_addr(exar_gpio, offset);
+ unsigned int bit = exar_offset_to_bit(exar_gpio, offset);
+
+ exar_set_value(chip, offset, value);
+ regmap_clear_bits(exar_gpio->regmap, addr, BIT(bit));
+
+ return 0;
+}
+
+static int exar_direction_input(struct gpio_chip *chip, unsigned int offset)
+{
+ struct exar_gpio_chip *exar_gpio = gpiochip_get_data(chip);
+ unsigned int addr = exar_offset_to_sel_addr(exar_gpio, offset);
+ unsigned int bit = exar_offset_to_bit(exar_gpio, offset);
+
+ regmap_set_bits(exar_gpio->regmap, addr, BIT(bit));
+
+ return 0;
+}
+
+static void exar_devm_ida_free(void *data)
+{
+ struct exar_gpio_chip *exar_gpio = data;
+
+ ida_free(&ida_index, exar_gpio->index);
+}
+
+static const struct regmap_config exar_regmap_config = {
+ .name = "exar-gpio",
+ .reg_bits = 16,
+ .val_bits = 8,
+ .io_port = true,
+};
+
+static int gpio_exar_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pci_dev *pcidev = to_pci_dev(dev->parent);
+ struct exar_gpio_chip *exar_gpio;
+ u32 first_pin, ngpios;
+ void __iomem *p;
+ int index, ret;
+
+ /*
+ * The UART driver must have mapped region 0 prior to registering this
+ * device - use it.
+ */
+ p = pcim_iomap_table(pcidev)[0];
+ if (!p)
+ return -ENOMEM;
+
+ ret = device_property_read_u32(dev, "exar,first-pin", &first_pin);
+ if (ret)
+ return ret;
+
+ ret = device_property_read_u32(dev, "ngpios", &ngpios);
+ if (ret)
+ return ret;
+
+ exar_gpio = devm_kzalloc(dev, sizeof(*exar_gpio), GFP_KERNEL);
+ if (!exar_gpio)
+ return -ENOMEM;
+
+ /*
+ * If cascaded, secondary xr17v354 or xr17v358 have the same amount
+ * of MPIOs as their primaries and the last 4 bits of the primary's
+ * PCI Device ID is the number of its UART channels.
+ */
+ if (pcidev->device & GENMASK(15, 12)) {
+ ngpios += ngpios;
+ exar_gpio->cascaded_offset = (pcidev->device & GENMASK(3, 0)) *
+ EXAR_UART_CHANNEL_SIZE;
+ }
+
+ /*
+ * We don't need to check the return values of mmio regmap operations (unless
+ * the regmap has a clock attached which is not the case here).
+ */
+ exar_gpio->regmap = devm_regmap_init_mmio(dev, p, &exar_regmap_config);
+ if (IS_ERR(exar_gpio->regmap))
+ return PTR_ERR(exar_gpio->regmap);
+
+ index = ida_alloc(&ida_index, GFP_KERNEL);
+ if (index < 0)
+ return index;
+
+ ret = devm_add_action_or_reset(dev, exar_devm_ida_free, exar_gpio);
+ if (ret)
+ return ret;
+
+ sprintf(exar_gpio->name, "exar_gpio%d", index);
+ exar_gpio->gpio_chip.label = exar_gpio->name;
+ exar_gpio->gpio_chip.parent = dev;
+ exar_gpio->gpio_chip.direction_output = exar_direction_output;
+ exar_gpio->gpio_chip.direction_input = exar_direction_input;
+ exar_gpio->gpio_chip.get_direction = exar_get_direction;
+ exar_gpio->gpio_chip.get = exar_get_value;
+ exar_gpio->gpio_chip.set = exar_set_value;
+ exar_gpio->gpio_chip.base = -1;
+ exar_gpio->gpio_chip.ngpio = ngpios;
+ exar_gpio->index = index;
+ exar_gpio->first_pin = first_pin;
+
+ ret = devm_gpiochip_add_data(dev, &exar_gpio->gpio_chip, exar_gpio);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct platform_driver gpio_exar_driver = {
+ .probe = gpio_exar_probe,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+module_platform_driver(gpio_exar_driver);
+
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_DESCRIPTION("Exar GPIO driver");
+MODULE_AUTHOR("Sudip Mukherjee <sudip.mukherjee@codethink.co.uk>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-f7188x.c b/drivers/gpio/gpio-f7188x.c
new file mode 100644
index 0000000000..f54ca5a177
--- /dev/null
+++ b/drivers/gpio/gpio-f7188x.c
@@ -0,0 +1,666 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * GPIO driver for Fintek and Nuvoton Super-I/O chips
+ *
+ * Copyright (C) 2010-2013 LaCie
+ *
+ * Author: Simon Guinot <simon.guinot@sequanux.org>
+ */
+
+#define DRVNAME "gpio-f7188x"
+#define pr_fmt(fmt) DRVNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/gpio/driver.h>
+#include <linux/bitops.h>
+
+/*
+ * Super-I/O registers
+ */
+#define SIO_LDSEL 0x07 /* Logical device select */
+#define SIO_DEVID 0x20 /* Device ID (2 bytes) */
+
+#define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */
+#define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */
+
+/*
+ * Fintek devices.
+ */
+#define SIO_FINTEK_DEVREV 0x22 /* Fintek Device revision */
+#define SIO_FINTEK_MANID 0x23 /* Fintek ID (2 bytes) */
+
+#define SIO_FINTEK_ID 0x1934 /* Manufacturer ID */
+
+#define SIO_F71869_ID 0x0814 /* F71869 chipset ID */
+#define SIO_F71869A_ID 0x1007 /* F71869A chipset ID */
+#define SIO_F71882_ID 0x0541 /* F71882 chipset ID */
+#define SIO_F71889_ID 0x0909 /* F71889 chipset ID */
+#define SIO_F71889A_ID 0x1005 /* F71889A chipset ID */
+#define SIO_F81866_ID 0x1010 /* F81866 chipset ID */
+#define SIO_F81804_ID 0x1502 /* F81804 chipset ID, same for F81966 */
+#define SIO_F81865_ID 0x0704 /* F81865 chipset ID */
+
+#define SIO_LD_GPIO_FINTEK 0x06 /* GPIO logical device */
+
+/*
+ * Nuvoton devices.
+ */
+#define SIO_NCT6126D_ID 0xD283 /* NCT6126D chipset ID */
+
+#define SIO_LD_GPIO_NUVOTON 0x07 /* GPIO logical device */
+
+
+enum chips {
+ f71869,
+ f71869a,
+ f71882fg,
+ f71889a,
+ f71889f,
+ f81866,
+ f81804,
+ f81865,
+ nct6126d,
+};
+
+static const char * const f7188x_names[] = {
+ "f71869",
+ "f71869a",
+ "f71882fg",
+ "f71889a",
+ "f71889f",
+ "f81866",
+ "f81804",
+ "f81865",
+ "nct6126d",
+};
+
+struct f7188x_sio {
+ int addr;
+ int device;
+ enum chips type;
+};
+
+struct f7188x_gpio_bank {
+ struct gpio_chip chip;
+ unsigned int regbase;
+ struct f7188x_gpio_data *data;
+};
+
+struct f7188x_gpio_data {
+ struct f7188x_sio *sio;
+ int nr_bank;
+ struct f7188x_gpio_bank *bank;
+};
+
+/*
+ * Super-I/O functions.
+ */
+
+static inline int superio_inb(int base, int reg)
+{
+ outb(reg, base);
+ return inb(base + 1);
+}
+
+static int superio_inw(int base, int reg)
+{
+ int val;
+
+ outb(reg++, base);
+ val = inb(base + 1) << 8;
+ outb(reg, base);
+ val |= inb(base + 1);
+
+ return val;
+}
+
+static inline void superio_outb(int base, int reg, int val)
+{
+ outb(reg, base);
+ outb(val, base + 1);
+}
+
+static inline int superio_enter(int base)
+{
+ /* Don't step on other drivers' I/O space by accident. */
+ if (!request_muxed_region(base, 2, DRVNAME)) {
+ pr_err("I/O address 0x%04x already in use\n", base);
+ return -EBUSY;
+ }
+
+ /* According to the datasheet the key must be send twice. */
+ outb(SIO_UNLOCK_KEY, base);
+ outb(SIO_UNLOCK_KEY, base);
+
+ return 0;
+}
+
+static inline void superio_select(int base, int ld)
+{
+ outb(SIO_LDSEL, base);
+ outb(ld, base + 1);
+}
+
+static inline void superio_exit(int base)
+{
+ outb(SIO_LOCK_KEY, base);
+ release_region(base, 2);
+}
+
+/*
+ * GPIO chip.
+ */
+
+static int f7188x_gpio_get_direction(struct gpio_chip *chip, unsigned offset);
+static int f7188x_gpio_direction_in(struct gpio_chip *chip, unsigned offset);
+static int f7188x_gpio_get(struct gpio_chip *chip, unsigned offset);
+static int f7188x_gpio_direction_out(struct gpio_chip *chip,
+ unsigned offset, int value);
+static void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value);
+static int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset,
+ unsigned long config);
+
+#define F7188X_GPIO_BANK(_base, _ngpio, _regbase, _label) \
+ { \
+ .chip = { \
+ .label = _label, \
+ .owner = THIS_MODULE, \
+ .get_direction = f7188x_gpio_get_direction, \
+ .direction_input = f7188x_gpio_direction_in, \
+ .get = f7188x_gpio_get, \
+ .direction_output = f7188x_gpio_direction_out, \
+ .set = f7188x_gpio_set, \
+ .set_config = f7188x_gpio_set_config, \
+ .base = _base, \
+ .ngpio = _ngpio, \
+ .can_sleep = true, \
+ }, \
+ .regbase = _regbase, \
+ }
+
+#define f7188x_gpio_dir(base) ((base) + 0)
+#define f7188x_gpio_data_out(base) ((base) + 1)
+#define f7188x_gpio_data_in(base) ((base) + 2)
+/* Output mode register (0:open drain 1:push-pull). */
+#define f7188x_gpio_out_mode(base) ((base) + 3)
+
+#define f7188x_gpio_dir_invert(type) ((type) == nct6126d)
+#define f7188x_gpio_data_single(type) ((type) == nct6126d)
+
+static struct f7188x_gpio_bank f71869_gpio_bank[] = {
+ F7188X_GPIO_BANK(0, 6, 0xF0, DRVNAME "-0"),
+ F7188X_GPIO_BANK(10, 8, 0xE0, DRVNAME "-1"),
+ F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
+ F7188X_GPIO_BANK(30, 8, 0xC0, DRVNAME "-3"),
+ F7188X_GPIO_BANK(40, 8, 0xB0, DRVNAME "-4"),
+ F7188X_GPIO_BANK(50, 5, 0xA0, DRVNAME "-5"),
+ F7188X_GPIO_BANK(60, 6, 0x90, DRVNAME "-6"),
+};
+
+static struct f7188x_gpio_bank f71869a_gpio_bank[] = {
+ F7188X_GPIO_BANK(0, 6, 0xF0, DRVNAME "-0"),
+ F7188X_GPIO_BANK(10, 8, 0xE0, DRVNAME "-1"),
+ F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
+ F7188X_GPIO_BANK(30, 8, 0xC0, DRVNAME "-3"),
+ F7188X_GPIO_BANK(40, 8, 0xB0, DRVNAME "-4"),
+ F7188X_GPIO_BANK(50, 5, 0xA0, DRVNAME "-5"),
+ F7188X_GPIO_BANK(60, 8, 0x90, DRVNAME "-6"),
+ F7188X_GPIO_BANK(70, 8, 0x80, DRVNAME "-7"),
+};
+
+static struct f7188x_gpio_bank f71882_gpio_bank[] = {
+ F7188X_GPIO_BANK(0, 8, 0xF0, DRVNAME "-0"),
+ F7188X_GPIO_BANK(10, 8, 0xE0, DRVNAME "-1"),
+ F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
+ F7188X_GPIO_BANK(30, 4, 0xC0, DRVNAME "-3"),
+ F7188X_GPIO_BANK(40, 4, 0xB0, DRVNAME "-4"),
+};
+
+static struct f7188x_gpio_bank f71889a_gpio_bank[] = {
+ F7188X_GPIO_BANK(0, 7, 0xF0, DRVNAME "-0"),
+ F7188X_GPIO_BANK(10, 7, 0xE0, DRVNAME "-1"),
+ F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
+ F7188X_GPIO_BANK(30, 8, 0xC0, DRVNAME "-3"),
+ F7188X_GPIO_BANK(40, 8, 0xB0, DRVNAME "-4"),
+ F7188X_GPIO_BANK(50, 5, 0xA0, DRVNAME "-5"),
+ F7188X_GPIO_BANK(60, 8, 0x90, DRVNAME "-6"),
+ F7188X_GPIO_BANK(70, 8, 0x80, DRVNAME "-7"),
+};
+
+static struct f7188x_gpio_bank f71889_gpio_bank[] = {
+ F7188X_GPIO_BANK(0, 7, 0xF0, DRVNAME "-0"),
+ F7188X_GPIO_BANK(10, 7, 0xE0, DRVNAME "-1"),
+ F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
+ F7188X_GPIO_BANK(30, 8, 0xC0, DRVNAME "-3"),
+ F7188X_GPIO_BANK(40, 8, 0xB0, DRVNAME "-4"),
+ F7188X_GPIO_BANK(50, 5, 0xA0, DRVNAME "-5"),
+ F7188X_GPIO_BANK(60, 8, 0x90, DRVNAME "-6"),
+ F7188X_GPIO_BANK(70, 8, 0x80, DRVNAME "-7"),
+};
+
+static struct f7188x_gpio_bank f81866_gpio_bank[] = {
+ F7188X_GPIO_BANK(0, 8, 0xF0, DRVNAME "-0"),
+ F7188X_GPIO_BANK(10, 8, 0xE0, DRVNAME "-1"),
+ F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
+ F7188X_GPIO_BANK(30, 8, 0xC0, DRVNAME "-3"),
+ F7188X_GPIO_BANK(40, 8, 0xB0, DRVNAME "-4"),
+ F7188X_GPIO_BANK(50, 8, 0xA0, DRVNAME "-5"),
+ F7188X_GPIO_BANK(60, 8, 0x90, DRVNAME "-6"),
+ F7188X_GPIO_BANK(70, 8, 0x80, DRVNAME "-7"),
+ F7188X_GPIO_BANK(80, 8, 0x88, DRVNAME "-8"),
+};
+
+
+static struct f7188x_gpio_bank f81804_gpio_bank[] = {
+ F7188X_GPIO_BANK(0, 8, 0xF0, DRVNAME "-0"),
+ F7188X_GPIO_BANK(10, 8, 0xE0, DRVNAME "-1"),
+ F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
+ F7188X_GPIO_BANK(50, 8, 0xA0, DRVNAME "-3"),
+ F7188X_GPIO_BANK(60, 8, 0x90, DRVNAME "-4"),
+ F7188X_GPIO_BANK(70, 8, 0x80, DRVNAME "-5"),
+ F7188X_GPIO_BANK(90, 8, 0x98, DRVNAME "-6"),
+};
+
+static struct f7188x_gpio_bank f81865_gpio_bank[] = {
+ F7188X_GPIO_BANK(0, 8, 0xF0, DRVNAME "-0"),
+ F7188X_GPIO_BANK(10, 8, 0xE0, DRVNAME "-1"),
+ F7188X_GPIO_BANK(20, 8, 0xD0, DRVNAME "-2"),
+ F7188X_GPIO_BANK(30, 8, 0xC0, DRVNAME "-3"),
+ F7188X_GPIO_BANK(40, 8, 0xB0, DRVNAME "-4"),
+ F7188X_GPIO_BANK(50, 8, 0xA0, DRVNAME "-5"),
+ F7188X_GPIO_BANK(60, 5, 0x90, DRVNAME "-6"),
+};
+
+static struct f7188x_gpio_bank nct6126d_gpio_bank[] = {
+ F7188X_GPIO_BANK(0, 8, 0xE0, DRVNAME "-0"),
+ F7188X_GPIO_BANK(10, 8, 0xE4, DRVNAME "-1"),
+ F7188X_GPIO_BANK(20, 8, 0xE8, DRVNAME "-2"),
+ F7188X_GPIO_BANK(30, 8, 0xEC, DRVNAME "-3"),
+ F7188X_GPIO_BANK(40, 8, 0xF0, DRVNAME "-4"),
+ F7188X_GPIO_BANK(50, 8, 0xF4, DRVNAME "-5"),
+ F7188X_GPIO_BANK(60, 8, 0xF8, DRVNAME "-6"),
+ F7188X_GPIO_BANK(70, 8, 0xFC, DRVNAME "-7"),
+};
+
+static int f7188x_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+ int err;
+ struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
+ struct f7188x_sio *sio = bank->data->sio;
+ u8 dir;
+
+ err = superio_enter(sio->addr);
+ if (err)
+ return err;
+ superio_select(sio->addr, sio->device);
+
+ dir = superio_inb(sio->addr, f7188x_gpio_dir(bank->regbase));
+
+ superio_exit(sio->addr);
+
+ if (f7188x_gpio_dir_invert(sio->type))
+ dir = ~dir;
+
+ if (dir & BIT(offset))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int f7188x_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ int err;
+ struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
+ struct f7188x_sio *sio = bank->data->sio;
+ u8 dir;
+
+ err = superio_enter(sio->addr);
+ if (err)
+ return err;
+ superio_select(sio->addr, sio->device);
+
+ dir = superio_inb(sio->addr, f7188x_gpio_dir(bank->regbase));
+
+ if (f7188x_gpio_dir_invert(sio->type))
+ dir |= BIT(offset);
+ else
+ dir &= ~BIT(offset);
+ superio_outb(sio->addr, f7188x_gpio_dir(bank->regbase), dir);
+
+ superio_exit(sio->addr);
+
+ return 0;
+}
+
+static int f7188x_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ int err;
+ struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
+ struct f7188x_sio *sio = bank->data->sio;
+ u8 dir, data;
+
+ err = superio_enter(sio->addr);
+ if (err)
+ return err;
+ superio_select(sio->addr, sio->device);
+
+ dir = superio_inb(sio->addr, f7188x_gpio_dir(bank->regbase));
+ dir = !!(dir & BIT(offset));
+ if (f7188x_gpio_data_single(sio->type) || dir)
+ data = superio_inb(sio->addr, f7188x_gpio_data_out(bank->regbase));
+ else
+ data = superio_inb(sio->addr, f7188x_gpio_data_in(bank->regbase));
+
+ superio_exit(sio->addr);
+
+ return !!(data & BIT(offset));
+}
+
+static int f7188x_gpio_direction_out(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ int err;
+ struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
+ struct f7188x_sio *sio = bank->data->sio;
+ u8 dir, data_out;
+
+ err = superio_enter(sio->addr);
+ if (err)
+ return err;
+ superio_select(sio->addr, sio->device);
+
+ data_out = superio_inb(sio->addr, f7188x_gpio_data_out(bank->regbase));
+ if (value)
+ data_out |= BIT(offset);
+ else
+ data_out &= ~BIT(offset);
+ superio_outb(sio->addr, f7188x_gpio_data_out(bank->regbase), data_out);
+
+ dir = superio_inb(sio->addr, f7188x_gpio_dir(bank->regbase));
+ if (f7188x_gpio_dir_invert(sio->type))
+ dir &= ~BIT(offset);
+ else
+ dir |= BIT(offset);
+ superio_outb(sio->addr, f7188x_gpio_dir(bank->regbase), dir);
+
+ superio_exit(sio->addr);
+
+ return 0;
+}
+
+static void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ int err;
+ struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
+ struct f7188x_sio *sio = bank->data->sio;
+ u8 data_out;
+
+ err = superio_enter(sio->addr);
+ if (err)
+ return;
+ superio_select(sio->addr, sio->device);
+
+ data_out = superio_inb(sio->addr, f7188x_gpio_data_out(bank->regbase));
+ if (value)
+ data_out |= BIT(offset);
+ else
+ data_out &= ~BIT(offset);
+ superio_outb(sio->addr, f7188x_gpio_data_out(bank->regbase), data_out);
+
+ superio_exit(sio->addr);
+}
+
+static int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset,
+ unsigned long config)
+{
+ int err;
+ enum pin_config_param param = pinconf_to_config_param(config);
+ struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
+ struct f7188x_sio *sio = bank->data->sio;
+ u8 data;
+
+ if (param != PIN_CONFIG_DRIVE_OPEN_DRAIN &&
+ param != PIN_CONFIG_DRIVE_PUSH_PULL)
+ return -ENOTSUPP;
+
+ err = superio_enter(sio->addr);
+ if (err)
+ return err;
+ superio_select(sio->addr, sio->device);
+
+ data = superio_inb(sio->addr, f7188x_gpio_out_mode(bank->regbase));
+ if (param == PIN_CONFIG_DRIVE_OPEN_DRAIN)
+ data &= ~BIT(offset);
+ else
+ data |= BIT(offset);
+ superio_outb(sio->addr, f7188x_gpio_out_mode(bank->regbase), data);
+
+ superio_exit(sio->addr);
+ return 0;
+}
+
+/*
+ * Platform device and driver.
+ */
+
+static int f7188x_gpio_probe(struct platform_device *pdev)
+{
+ int err;
+ int i;
+ struct f7188x_sio *sio = dev_get_platdata(&pdev->dev);
+ struct f7188x_gpio_data *data;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ switch (sio->type) {
+ case f71869:
+ data->nr_bank = ARRAY_SIZE(f71869_gpio_bank);
+ data->bank = f71869_gpio_bank;
+ break;
+ case f71869a:
+ data->nr_bank = ARRAY_SIZE(f71869a_gpio_bank);
+ data->bank = f71869a_gpio_bank;
+ break;
+ case f71882fg:
+ data->nr_bank = ARRAY_SIZE(f71882_gpio_bank);
+ data->bank = f71882_gpio_bank;
+ break;
+ case f71889a:
+ data->nr_bank = ARRAY_SIZE(f71889a_gpio_bank);
+ data->bank = f71889a_gpio_bank;
+ break;
+ case f71889f:
+ data->nr_bank = ARRAY_SIZE(f71889_gpio_bank);
+ data->bank = f71889_gpio_bank;
+ break;
+ case f81866:
+ data->nr_bank = ARRAY_SIZE(f81866_gpio_bank);
+ data->bank = f81866_gpio_bank;
+ break;
+ case f81804:
+ data->nr_bank = ARRAY_SIZE(f81804_gpio_bank);
+ data->bank = f81804_gpio_bank;
+ break;
+ case f81865:
+ data->nr_bank = ARRAY_SIZE(f81865_gpio_bank);
+ data->bank = f81865_gpio_bank;
+ break;
+ case nct6126d:
+ data->nr_bank = ARRAY_SIZE(nct6126d_gpio_bank);
+ data->bank = nct6126d_gpio_bank;
+ break;
+ default:
+ return -ENODEV;
+ }
+ data->sio = sio;
+
+ platform_set_drvdata(pdev, data);
+
+ /* For each GPIO bank, register a GPIO chip. */
+ for (i = 0; i < data->nr_bank; i++) {
+ struct f7188x_gpio_bank *bank = &data->bank[i];
+
+ bank->chip.parent = &pdev->dev;
+ bank->data = data;
+
+ err = devm_gpiochip_add_data(&pdev->dev, &bank->chip, bank);
+ if (err) {
+ dev_err(&pdev->dev,
+ "Failed to register gpiochip %d: %d\n",
+ i, err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int __init f7188x_find(int addr, struct f7188x_sio *sio)
+{
+ int err;
+ u16 devid;
+ u16 manid;
+
+ err = superio_enter(addr);
+ if (err)
+ return err;
+
+ err = -ENODEV;
+
+ sio->device = SIO_LD_GPIO_FINTEK;
+ devid = superio_inw(addr, SIO_DEVID);
+ switch (devid) {
+ case SIO_F71869_ID:
+ sio->type = f71869;
+ break;
+ case SIO_F71869A_ID:
+ sio->type = f71869a;
+ break;
+ case SIO_F71882_ID:
+ sio->type = f71882fg;
+ break;
+ case SIO_F71889A_ID:
+ sio->type = f71889a;
+ break;
+ case SIO_F71889_ID:
+ sio->type = f71889f;
+ break;
+ case SIO_F81866_ID:
+ sio->type = f81866;
+ break;
+ case SIO_F81804_ID:
+ sio->type = f81804;
+ break;
+ case SIO_F81865_ID:
+ sio->type = f81865;
+ break;
+ case SIO_NCT6126D_ID:
+ sio->device = SIO_LD_GPIO_NUVOTON;
+ sio->type = nct6126d;
+ break;
+ default:
+ pr_info("Unsupported Fintek device 0x%04x\n", devid);
+ goto err;
+ }
+
+ /* double check manufacturer where possible */
+ if (sio->type != nct6126d) {
+ manid = superio_inw(addr, SIO_FINTEK_MANID);
+ if (manid != SIO_FINTEK_ID) {
+ pr_debug("Not a Fintek device at 0x%08x\n", addr);
+ goto err;
+ }
+ }
+
+ sio->addr = addr;
+ err = 0;
+
+ pr_info("Found %s at %#x\n", f7188x_names[sio->type], (unsigned int)addr);
+ if (sio->type != nct6126d)
+ pr_info(" revision %d\n", superio_inb(addr, SIO_FINTEK_DEVREV));
+
+err:
+ superio_exit(addr);
+ return err;
+}
+
+static struct platform_device *f7188x_gpio_pdev;
+
+static int __init
+f7188x_gpio_device_add(const struct f7188x_sio *sio)
+{
+ int err;
+
+ f7188x_gpio_pdev = platform_device_alloc(DRVNAME, -1);
+ if (!f7188x_gpio_pdev)
+ return -ENOMEM;
+
+ err = platform_device_add_data(f7188x_gpio_pdev,
+ sio, sizeof(*sio));
+ if (err) {
+ pr_err("Platform data allocation failed\n");
+ goto err;
+ }
+
+ err = platform_device_add(f7188x_gpio_pdev);
+ if (err) {
+ pr_err("Device addition failed\n");
+ goto err;
+ }
+
+ return 0;
+
+err:
+ platform_device_put(f7188x_gpio_pdev);
+
+ return err;
+}
+
+/*
+ * Try to match a supported Fintek device by reading the (hard-wired)
+ * configuration I/O ports. If available, then register both the platform
+ * device and driver to support the GPIOs.
+ */
+
+static struct platform_driver f7188x_gpio_driver = {
+ .driver = {
+ .name = DRVNAME,
+ },
+ .probe = f7188x_gpio_probe,
+};
+
+static int __init f7188x_gpio_init(void)
+{
+ int err;
+ struct f7188x_sio sio;
+
+ if (f7188x_find(0x2e, &sio) &&
+ f7188x_find(0x4e, &sio))
+ return -ENODEV;
+
+ err = platform_driver_register(&f7188x_gpio_driver);
+ if (!err) {
+ err = f7188x_gpio_device_add(&sio);
+ if (err)
+ platform_driver_unregister(&f7188x_gpio_driver);
+ }
+
+ return err;
+}
+subsys_initcall(f7188x_gpio_init);
+
+static void __exit f7188x_gpio_exit(void)
+{
+ platform_device_unregister(f7188x_gpio_pdev);
+ platform_driver_unregister(&f7188x_gpio_driver);
+}
+module_exit(f7188x_gpio_exit);
+
+MODULE_DESCRIPTION("GPIO driver for Super-I/O chips F71869, F71869A, F71882FG, F71889A, F71889F and F81866");
+MODULE_AUTHOR("Simon Guinot <simon.guinot@sequanux.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-ftgpio010.c b/drivers/gpio/gpio-ftgpio010.c
new file mode 100644
index 0000000000..5ce59dcf02
--- /dev/null
+++ b/drivers/gpio/gpio-ftgpio010.c
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Faraday Technolog FTGPIO010 gpiochip and interrupt routines
+ * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
+ *
+ * Based on arch/arm/mach-gemini/gpio.c:
+ * Copyright (C) 2008-2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
+ *
+ * Based on plat-mxc/gpio.c:
+ * MXC GPIO support. (c) 2008 Daniel Mack <daniel@caiaq.de>
+ * Copyright 2008 Juergen Beisert, kernel@pengutronix.de
+ */
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+
+/* GPIO registers definition */
+#define GPIO_DATA_OUT 0x00
+#define GPIO_DATA_IN 0x04
+#define GPIO_DIR 0x08
+#define GPIO_BYPASS_IN 0x0C
+#define GPIO_DATA_SET 0x10
+#define GPIO_DATA_CLR 0x14
+#define GPIO_PULL_EN 0x18
+#define GPIO_PULL_TYPE 0x1C
+#define GPIO_INT_EN 0x20
+#define GPIO_INT_STAT_RAW 0x24
+#define GPIO_INT_STAT_MASKED 0x28
+#define GPIO_INT_MASK 0x2C
+#define GPIO_INT_CLR 0x30
+#define GPIO_INT_TYPE 0x34
+#define GPIO_INT_BOTH_EDGE 0x38
+#define GPIO_INT_LEVEL 0x3C
+#define GPIO_DEBOUNCE_EN 0x40
+#define GPIO_DEBOUNCE_PRESCALE 0x44
+
+/**
+ * struct ftgpio_gpio - Gemini GPIO state container
+ * @dev: containing device for this instance
+ * @gc: gpiochip for this instance
+ * @base: remapped I/O-memory base
+ * @clk: silicon clock
+ */
+struct ftgpio_gpio {
+ struct device *dev;
+ struct gpio_chip gc;
+ void __iomem *base;
+ struct clk *clk;
+};
+
+static void ftgpio_gpio_ack_irq(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct ftgpio_gpio *g = gpiochip_get_data(gc);
+
+ writel(BIT(irqd_to_hwirq(d)), g->base + GPIO_INT_CLR);
+}
+
+static void ftgpio_gpio_mask_irq(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct ftgpio_gpio *g = gpiochip_get_data(gc);
+ u32 val;
+
+ val = readl(g->base + GPIO_INT_EN);
+ val &= ~BIT(irqd_to_hwirq(d));
+ writel(val, g->base + GPIO_INT_EN);
+ gpiochip_disable_irq(gc, irqd_to_hwirq(d));
+}
+
+static void ftgpio_gpio_unmask_irq(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct ftgpio_gpio *g = gpiochip_get_data(gc);
+ u32 val;
+
+ gpiochip_enable_irq(gc, irqd_to_hwirq(d));
+ val = readl(g->base + GPIO_INT_EN);
+ val |= BIT(irqd_to_hwirq(d));
+ writel(val, g->base + GPIO_INT_EN);
+}
+
+static int ftgpio_gpio_set_irq_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct ftgpio_gpio *g = gpiochip_get_data(gc);
+ u32 mask = BIT(irqd_to_hwirq(d));
+ u32 reg_both, reg_level, reg_type;
+
+ reg_type = readl(g->base + GPIO_INT_TYPE);
+ reg_level = readl(g->base + GPIO_INT_LEVEL);
+ reg_both = readl(g->base + GPIO_INT_BOTH_EDGE);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_BOTH:
+ irq_set_handler_locked(d, handle_edge_irq);
+ reg_type &= ~mask;
+ reg_both |= mask;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ irq_set_handler_locked(d, handle_edge_irq);
+ reg_type &= ~mask;
+ reg_both &= ~mask;
+ reg_level &= ~mask;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ irq_set_handler_locked(d, handle_edge_irq);
+ reg_type &= ~mask;
+ reg_both &= ~mask;
+ reg_level |= mask;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ irq_set_handler_locked(d, handle_level_irq);
+ reg_type |= mask;
+ reg_level &= ~mask;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ irq_set_handler_locked(d, handle_level_irq);
+ reg_type |= mask;
+ reg_level |= mask;
+ break;
+ default:
+ irq_set_handler_locked(d, handle_bad_irq);
+ return -EINVAL;
+ }
+
+ writel(reg_type, g->base + GPIO_INT_TYPE);
+ writel(reg_level, g->base + GPIO_INT_LEVEL);
+ writel(reg_both, g->base + GPIO_INT_BOTH_EDGE);
+
+ ftgpio_gpio_ack_irq(d);
+
+ return 0;
+}
+
+static void ftgpio_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct gpio_chip *gc = irq_desc_get_handler_data(desc);
+ struct ftgpio_gpio *g = gpiochip_get_data(gc);
+ struct irq_chip *irqchip = irq_desc_get_chip(desc);
+ int offset;
+ unsigned long stat;
+
+ chained_irq_enter(irqchip, desc);
+
+ stat = readl(g->base + GPIO_INT_STAT_RAW);
+ if (stat)
+ for_each_set_bit(offset, &stat, gc->ngpio)
+ generic_handle_domain_irq(gc->irq.domain, offset);
+
+ chained_irq_exit(irqchip, desc);
+}
+
+static int ftgpio_gpio_set_config(struct gpio_chip *gc, unsigned int offset,
+ unsigned long config)
+{
+ enum pin_config_param param = pinconf_to_config_param(config);
+ u32 arg = pinconf_to_config_argument(config);
+ struct ftgpio_gpio *g = gpiochip_get_data(gc);
+ unsigned long pclk_freq;
+ u32 deb_div;
+ u32 val;
+
+ if (param != PIN_CONFIG_INPUT_DEBOUNCE)
+ return -ENOTSUPP;
+
+ /*
+ * Debounce only works if interrupts are enabled. The manual
+ * states that if PCLK is 66 MHz, and this is set to 0x7D0, then
+ * PCLK is divided down to 33 kHz for the debounce timer. 0x7D0 is
+ * 2000 decimal, so what they mean is simply that the PCLK is
+ * divided by this value.
+ *
+ * As we get a debounce setting in microseconds, we calculate the
+ * desired period time and see if we can get a suitable debounce
+ * time.
+ */
+ pclk_freq = clk_get_rate(g->clk);
+ deb_div = DIV_ROUND_CLOSEST(pclk_freq, arg);
+
+ /* This register is only 24 bits wide */
+ if (deb_div > (1 << 24))
+ return -ENOTSUPP;
+
+ dev_dbg(g->dev, "prescale divisor: %08x, resulting frequency %lu Hz\n",
+ deb_div, (pclk_freq/deb_div));
+
+ val = readl(g->base + GPIO_DEBOUNCE_PRESCALE);
+ if (val == deb_div) {
+ /*
+ * The debounce timer happens to already be set to the
+ * desirable value, what a coincidence! We can just enable
+ * debounce on this GPIO line and return. This happens more
+ * often than you think, for example when all GPIO keys
+ * on a system are requesting the same debounce interval.
+ */
+ val = readl(g->base + GPIO_DEBOUNCE_EN);
+ val |= BIT(offset);
+ writel(val, g->base + GPIO_DEBOUNCE_EN);
+ return 0;
+ }
+
+ val = readl(g->base + GPIO_DEBOUNCE_EN);
+ if (val) {
+ /*
+ * Oh no! Someone is already using the debounce with
+ * another setting than what we need. Bummer.
+ */
+ return -ENOTSUPP;
+ }
+
+ /* First come, first serve */
+ writel(deb_div, g->base + GPIO_DEBOUNCE_PRESCALE);
+ /* Enable debounce */
+ val |= BIT(offset);
+ writel(val, g->base + GPIO_DEBOUNCE_EN);
+
+ return 0;
+}
+
+static const struct irq_chip ftgpio_irq_chip = {
+ .name = "FTGPIO010",
+ .irq_ack = ftgpio_gpio_ack_irq,
+ .irq_mask = ftgpio_gpio_mask_irq,
+ .irq_unmask = ftgpio_gpio_unmask_irq,
+ .irq_set_type = ftgpio_gpio_set_irq_type,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int ftgpio_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ftgpio_gpio *g;
+ struct gpio_irq_chip *girq;
+ int irq;
+ int ret;
+
+ g = devm_kzalloc(dev, sizeof(*g), GFP_KERNEL);
+ if (!g)
+ return -ENOMEM;
+
+ g->dev = dev;
+
+ g->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(g->base))
+ return PTR_ERR(g->base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ g->clk = devm_clk_get(dev, NULL);
+ if (!IS_ERR(g->clk)) {
+ ret = clk_prepare_enable(g->clk);
+ if (ret)
+ return ret;
+ } else if (PTR_ERR(g->clk) == -EPROBE_DEFER) {
+ /*
+ * Percolate deferrals, for anything else,
+ * just live without the clocking.
+ */
+ return PTR_ERR(g->clk);
+ }
+
+ ret = bgpio_init(&g->gc, dev, 4,
+ g->base + GPIO_DATA_IN,
+ g->base + GPIO_DATA_SET,
+ g->base + GPIO_DATA_CLR,
+ g->base + GPIO_DIR,
+ NULL,
+ 0);
+ if (ret) {
+ dev_err(dev, "unable to init generic GPIO\n");
+ goto dis_clk;
+ }
+ g->gc.label = dev_name(dev);
+ g->gc.base = -1;
+ g->gc.parent = dev;
+ g->gc.owner = THIS_MODULE;
+ /* ngpio is set by bgpio_init() */
+
+ /* We need a silicon clock to do debounce */
+ if (!IS_ERR(g->clk))
+ g->gc.set_config = ftgpio_gpio_set_config;
+
+ girq = &g->gc.irq;
+ gpio_irq_chip_set_chip(girq, &ftgpio_irq_chip);
+ girq->parent_handler = ftgpio_gpio_irq_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(dev, 1, sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents) {
+ ret = -ENOMEM;
+ goto dis_clk;
+ }
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+ girq->parents[0] = irq;
+
+ /* Disable, unmask and clear all interrupts */
+ writel(0x0, g->base + GPIO_INT_EN);
+ writel(0x0, g->base + GPIO_INT_MASK);
+ writel(~0x0, g->base + GPIO_INT_CLR);
+
+ /* Clear any use of debounce */
+ writel(0x0, g->base + GPIO_DEBOUNCE_EN);
+
+ ret = devm_gpiochip_add_data(dev, &g->gc, g);
+ if (ret)
+ goto dis_clk;
+
+ platform_set_drvdata(pdev, g);
+ dev_info(dev, "FTGPIO010 @%p registered\n", g->base);
+
+ return 0;
+
+dis_clk:
+ clk_disable_unprepare(g->clk);
+
+ return ret;
+}
+
+static int ftgpio_gpio_remove(struct platform_device *pdev)
+{
+ struct ftgpio_gpio *g = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(g->clk);
+
+ return 0;
+}
+
+static const struct of_device_id ftgpio_gpio_of_match[] = {
+ {
+ .compatible = "cortina,gemini-gpio",
+ },
+ {
+ .compatible = "moxa,moxart-gpio",
+ },
+ {
+ .compatible = "faraday,ftgpio010",
+ },
+ {},
+};
+
+static struct platform_driver ftgpio_gpio_driver = {
+ .driver = {
+ .name = "ftgpio010-gpio",
+ .of_match_table = ftgpio_gpio_of_match,
+ },
+ .probe = ftgpio_gpio_probe,
+ .remove = ftgpio_gpio_remove,
+};
+builtin_platform_driver(ftgpio_gpio_driver);
diff --git a/drivers/gpio/gpio-fxl6408.c b/drivers/gpio/gpio-fxl6408.c
new file mode 100644
index 0000000000..c14b5cc5e5
--- /dev/null
+++ b/drivers/gpio/gpio-fxl6408.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * FXL6408 GPIO driver
+ *
+ * Copyright 2023 Toradex
+ *
+ * Author: Emanuele Ghidoli <emanuele.ghidoli@toradex.com>
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/regmap.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#define FXL6408_REG_DEVICE_ID 0x01
+#define FXL6408_MF_FAIRCHILD 0b101
+#define FXL6408_MF_SHIFT 5
+
+/* Bits set here indicate that the GPIO is an output. */
+#define FXL6408_REG_IO_DIR 0x03
+
+/*
+ * Bits set here, when the corresponding bit of IO_DIR is set, drive
+ * the output high instead of low.
+ */
+#define FXL6408_REG_OUTPUT 0x05
+
+/* Bits here make the output High-Z, instead of the OUTPUT value. */
+#define FXL6408_REG_OUTPUT_HIGH_Z 0x07
+
+/* Returns the current status (1 = HIGH) of the input pins. */
+#define FXL6408_REG_INPUT_STATUS 0x0f
+
+/*
+ * Return the current interrupt status
+ * This bit is HIGH if input GPIO != default state (register 09h).
+ * The flag is cleared after being read (bit returns to 0).
+ * The input must go back to default state and change again before this flag is raised again.
+ */
+#define FXL6408_REG_INT_STS 0x13
+
+#define FXL6408_NGPIO 8
+
+static const struct regmap_range rd_range[] = {
+ { FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID },
+ { FXL6408_REG_IO_DIR, FXL6408_REG_OUTPUT },
+ { FXL6408_REG_INPUT_STATUS, FXL6408_REG_INPUT_STATUS },
+};
+
+static const struct regmap_range wr_range[] = {
+ { FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID },
+ { FXL6408_REG_IO_DIR, FXL6408_REG_OUTPUT },
+ { FXL6408_REG_OUTPUT_HIGH_Z, FXL6408_REG_OUTPUT_HIGH_Z },
+};
+
+static const struct regmap_range volatile_range[] = {
+ { FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID },
+ { FXL6408_REG_INPUT_STATUS, FXL6408_REG_INPUT_STATUS },
+};
+
+static const struct regmap_access_table rd_table = {
+ .yes_ranges = rd_range,
+ .n_yes_ranges = ARRAY_SIZE(rd_range),
+};
+
+static const struct regmap_access_table wr_table = {
+ .yes_ranges = wr_range,
+ .n_yes_ranges = ARRAY_SIZE(wr_range),
+};
+
+static const struct regmap_access_table volatile_table = {
+ .yes_ranges = volatile_range,
+ .n_yes_ranges = ARRAY_SIZE(volatile_range),
+};
+
+static const struct regmap_config regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = FXL6408_REG_INT_STS,
+ .wr_table = &wr_table,
+ .rd_table = &rd_table,
+ .volatile_table = &volatile_table,
+
+ .cache_type = REGCACHE_RBTREE,
+ .num_reg_defaults_raw = FXL6408_REG_INT_STS + 1,
+};
+
+static int fxl6408_identify(struct device *dev, struct regmap *regmap)
+{
+ int val, ret;
+
+ ret = regmap_read(regmap, FXL6408_REG_DEVICE_ID, &val);
+ if (ret)
+ return dev_err_probe(dev, ret, "error reading DEVICE_ID\n");
+ if (val >> FXL6408_MF_SHIFT != FXL6408_MF_FAIRCHILD)
+ return dev_err_probe(dev, -ENODEV, "invalid device id 0x%02x\n", val);
+
+ return 0;
+}
+
+static int fxl6408_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ int ret;
+ struct gpio_regmap_config gpio_config = {
+ .parent = dev,
+ .ngpio = FXL6408_NGPIO,
+ .reg_dat_base = GPIO_REGMAP_ADDR(FXL6408_REG_INPUT_STATUS),
+ .reg_set_base = GPIO_REGMAP_ADDR(FXL6408_REG_OUTPUT),
+ .reg_dir_out_base = GPIO_REGMAP_ADDR(FXL6408_REG_IO_DIR),
+ .ngpio_per_reg = FXL6408_NGPIO,
+ };
+
+ gpio_config.regmap = devm_regmap_init_i2c(client, &regmap);
+ if (IS_ERR(gpio_config.regmap))
+ return dev_err_probe(dev, PTR_ERR(gpio_config.regmap),
+ "failed to allocate register map\n");
+
+ ret = fxl6408_identify(dev, gpio_config.regmap);
+ if (ret)
+ return ret;
+
+ /* Disable High-Z of outputs, so that our OUTPUT updates actually take effect. */
+ ret = regmap_write(gpio_config.regmap, FXL6408_REG_OUTPUT_HIGH_Z, 0);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to write 'output high Z' register\n");
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
+}
+
+static const __maybe_unused struct of_device_id fxl6408_dt_ids[] = {
+ { .compatible = "fcs,fxl6408" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, fxl6408_dt_ids);
+
+static const struct i2c_device_id fxl6408_id[] = {
+ { "fxl6408", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, fxl6408_id);
+
+static struct i2c_driver fxl6408_driver = {
+ .driver = {
+ .name = "fxl6408",
+ .of_match_table = fxl6408_dt_ids,
+ },
+ .probe = fxl6408_probe,
+ .id_table = fxl6408_id,
+};
+module_i2c_driver(fxl6408_driver);
+
+MODULE_AUTHOR("Emanuele Ghidoli <emanuele.ghidoli@toradex.com>");
+MODULE_DESCRIPTION("FXL6408 GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-ge.c b/drivers/gpio/gpio-ge.c
new file mode 100644
index 0000000000..5dc49648d8
--- /dev/null
+++ b/drivers/gpio/gpio-ge.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for GE FPGA based GPIO
+ *
+ * Author: Martyn Welch <martyn.welch@ge.com>
+ *
+ * 2008 (c) GE Intelligent Platforms Embedded Systems, Inc.
+ */
+
+/*
+ * TODO:
+ *
+ * Configuration of output modes (totem-pole/open-drain).
+ * Interrupt configuration - interrupts are always generated, the FPGA relies
+ * on the I/O interrupt controllers mask to stop them from being propagated.
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#define GEF_GPIO_DIRECT 0x00
+#define GEF_GPIO_IN 0x04
+#define GEF_GPIO_OUT 0x08
+#define GEF_GPIO_TRIG 0x0C
+#define GEF_GPIO_POLAR_A 0x10
+#define GEF_GPIO_POLAR_B 0x14
+#define GEF_GPIO_INT_STAT 0x18
+#define GEF_GPIO_OVERRUN 0x1C
+#define GEF_GPIO_MODE 0x20
+
+static const struct of_device_id gef_gpio_ids[] = {
+ {
+ .compatible = "gef,sbc610-gpio",
+ .data = (void *)19,
+ }, {
+ .compatible = "gef,sbc310-gpio",
+ .data = (void *)6,
+ }, {
+ .compatible = "ge,imp3a-gpio",
+ .data = (void *)16,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, gef_gpio_ids);
+
+static int __init gef_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct gpio_chip *gc;
+ void __iomem *regs;
+ int ret;
+
+ gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL);
+ if (!gc)
+ return -ENOMEM;
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ ret = bgpio_init(gc, dev, 4, regs + GEF_GPIO_IN, regs + GEF_GPIO_OUT,
+ NULL, NULL, regs + GEF_GPIO_DIRECT,
+ BGPIOF_BIG_ENDIAN_BYTE_ORDER);
+ if (ret)
+ return dev_err_probe(dev, ret, "bgpio_init failed\n");
+
+ /* Setup pointers to chip functions */
+ gc->label = devm_kasprintf(dev, GFP_KERNEL, "%pfw", dev_fwnode(dev));
+ if (!gc->label)
+ return -ENOMEM;
+
+ gc->base = -1;
+ gc->ngpio = (uintptr_t)device_get_match_data(dev);
+
+ /* This function adds a memory mapped GPIO chip */
+ ret = devm_gpiochip_add_data(dev, gc, NULL);
+ if (ret)
+ return dev_err_probe(dev, ret, "GPIO chip registration failed\n");
+
+ return 0;
+};
+
+static struct platform_driver gef_gpio_driver = {
+ .driver = {
+ .name = "gef-gpio",
+ .of_match_table = gef_gpio_ids,
+ },
+};
+module_platform_driver_probe(gef_gpio_driver, gef_gpio_probe);
+
+MODULE_DESCRIPTION("GE I/O FPGA GPIO driver");
+MODULE_AUTHOR("Martyn Welch <martyn.welch@ge.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-gpio-mm.c b/drivers/gpio/gpio-gpio-mm.c
new file mode 100644
index 0000000000..43d823a56e
--- /dev/null
+++ b/drivers/gpio/gpio-gpio-mm.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for the Diamond Systems GPIO-MM
+ * Copyright (C) 2016 William Breathitt Gray
+ *
+ * This driver supports the following Diamond Systems devices: GPIO-MM and
+ * GPIO-MM-12.
+ */
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/isa.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include "gpio-i8255.h"
+
+MODULE_IMPORT_NS(I8255);
+
+#define GPIOMM_EXTENT 8
+#define MAX_NUM_GPIOMM max_num_isa_dev(GPIOMM_EXTENT)
+
+static unsigned int base[MAX_NUM_GPIOMM];
+static unsigned int num_gpiomm;
+module_param_hw_array(base, uint, ioport, &num_gpiomm, 0);
+MODULE_PARM_DESC(base, "Diamond Systems GPIO-MM base addresses");
+
+#define GPIOMM_NUM_PPI 2
+
+static const struct regmap_range gpiomm_volatile_ranges[] = {
+ i8255_volatile_regmap_range(0x0), i8255_volatile_regmap_range(0x4),
+};
+static const struct regmap_access_table gpiomm_volatile_table = {
+ .yes_ranges = gpiomm_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(gpiomm_volatile_ranges),
+};
+static const struct regmap_config gpiomm_regmap_config = {
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .val_bits = 8,
+ .io_port = true,
+ .max_register = 0x7,
+ .volatile_table = &gpiomm_volatile_table,
+ .cache_type = REGCACHE_FLAT,
+};
+
+#define GPIOMM_NGPIO 48
+static const char *gpiomm_names[GPIOMM_NGPIO] = {
+ "Port 1A0", "Port 1A1", "Port 1A2", "Port 1A3", "Port 1A4", "Port 1A5",
+ "Port 1A6", "Port 1A7", "Port 1B0", "Port 1B1", "Port 1B2", "Port 1B3",
+ "Port 1B4", "Port 1B5", "Port 1B6", "Port 1B7", "Port 1C0", "Port 1C1",
+ "Port 1C2", "Port 1C3", "Port 1C4", "Port 1C5", "Port 1C6", "Port 1C7",
+ "Port 2A0", "Port 2A1", "Port 2A2", "Port 2A3", "Port 2A4", "Port 2A5",
+ "Port 2A6", "Port 2A7", "Port 2B0", "Port 2B1", "Port 2B2", "Port 2B3",
+ "Port 2B4", "Port 2B5", "Port 2B6", "Port 2B7", "Port 2C0", "Port 2C1",
+ "Port 2C2", "Port 2C3", "Port 2C4", "Port 2C5", "Port 2C6", "Port 2C7",
+};
+
+static int gpiomm_probe(struct device *dev, unsigned int id)
+{
+ const char *const name = dev_name(dev);
+ struct i8255_regmap_config config = {};
+ void __iomem *regs;
+
+ if (!devm_request_region(dev, base[id], GPIOMM_EXTENT, name)) {
+ dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n",
+ base[id], base[id] + GPIOMM_EXTENT);
+ return -EBUSY;
+ }
+
+ regs = devm_ioport_map(dev, base[id], GPIOMM_EXTENT);
+ if (!regs)
+ return -ENOMEM;
+
+ config.map = devm_regmap_init_mmio(dev, regs, &gpiomm_regmap_config);
+ if (IS_ERR(config.map))
+ return dev_err_probe(dev, PTR_ERR(config.map),
+ "Unable to initialize register map\n");
+
+ config.parent = dev;
+ config.num_ppi = GPIOMM_NUM_PPI;
+ config.names = gpiomm_names;
+
+ return devm_i8255_regmap_register(dev, &config);
+}
+
+static struct isa_driver gpiomm_driver = {
+ .probe = gpiomm_probe,
+ .driver = {
+ .name = "gpio-mm"
+ },
+};
+
+module_isa_driver(gpiomm_driver, num_gpiomm);
+
+MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
+MODULE_DESCRIPTION("Diamond Systems GPIO-MM GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-grgpio.c b/drivers/gpio/gpio-grgpio.c
new file mode 100644
index 0000000000..0163c95f6d
--- /dev/null
+++ b/drivers/gpio/gpio-grgpio.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Aeroflex Gaisler GRGPIO General Purpose I/O cores.
+ *
+ * 2013 (c) Aeroflex Gaisler AB
+ *
+ * This driver supports the GRGPIO GPIO core available in the GRLIB VHDL
+ * IP core library.
+ *
+ * Full documentation of the GRGPIO core can be found here:
+ * http://www.gaisler.com/products/grlib/grip.pdf
+ *
+ * See "Documentation/devicetree/bindings/gpio/gpio-grgpio.txt" for
+ * information on open firmware properties.
+ *
+ * Contributors: Andreas Larsson <andreas@gaisler.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/gpio/driver.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/bitops.h>
+
+#define GRGPIO_MAX_NGPIO 32
+
+#define GRGPIO_DATA 0x00
+#define GRGPIO_OUTPUT 0x04
+#define GRGPIO_DIR 0x08
+#define GRGPIO_IMASK 0x0c
+#define GRGPIO_IPOL 0x10
+#define GRGPIO_IEDGE 0x14
+#define GRGPIO_BYPASS 0x18
+#define GRGPIO_IMAP_BASE 0x20
+
+/* Structure for an irq of the core - called an underlying irq */
+struct grgpio_uirq {
+ u8 refcnt; /* Reference counter to manage requesting/freeing of uirq */
+ u8 uirq; /* Underlying irq of the gpio driver */
+};
+
+/*
+ * Structure for an irq of a gpio line handed out by this driver. The index is
+ * used to map to the corresponding underlying irq.
+ */
+struct grgpio_lirq {
+ s8 index; /* Index into struct grgpio_priv's uirqs, or -1 */
+ u8 irq; /* irq for the gpio line */
+};
+
+struct grgpio_priv {
+ struct gpio_chip gc;
+ void __iomem *regs;
+ struct device *dev;
+
+ u32 imask; /* irq mask shadow register */
+
+ /*
+ * The grgpio core can have multiple "underlying" irqs. The gpio lines
+ * can be mapped to any one or none of these underlying irqs
+ * independently of each other. This driver sets up an irq domain and
+ * hands out separate irqs to each gpio line
+ */
+ struct irq_domain *domain;
+
+ /*
+ * This array contains information on each underlying irq, each
+ * irq of the grgpio core itself.
+ */
+ struct grgpio_uirq uirqs[GRGPIO_MAX_NGPIO];
+
+ /*
+ * This array contains information for each gpio line on the irqs
+ * obtains from this driver. An index value of -1 for a certain gpio
+ * line indicates that the line has no irq. Otherwise the index connects
+ * the irq to the underlying irq by pointing into the uirqs array.
+ */
+ struct grgpio_lirq lirqs[GRGPIO_MAX_NGPIO];
+};
+
+static void grgpio_set_imask(struct grgpio_priv *priv, unsigned int offset,
+ int val)
+{
+ struct gpio_chip *gc = &priv->gc;
+
+ if (val)
+ priv->imask |= BIT(offset);
+ else
+ priv->imask &= ~BIT(offset);
+ gc->write_reg(priv->regs + GRGPIO_IMASK, priv->imask);
+}
+
+static int grgpio_to_irq(struct gpio_chip *gc, unsigned offset)
+{
+ struct grgpio_priv *priv = gpiochip_get_data(gc);
+
+ if (offset >= gc->ngpio)
+ return -ENXIO;
+
+ if (priv->lirqs[offset].index < 0)
+ return -ENXIO;
+
+ return irq_create_mapping(priv->domain, offset);
+}
+
+/* -------------------- IRQ chip functions -------------------- */
+
+static int grgpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct grgpio_priv *priv = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+ u32 mask = BIT(d->hwirq);
+ u32 ipol;
+ u32 iedge;
+ u32 pol;
+ u32 edge;
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_LOW:
+ pol = 0;
+ edge = 0;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ pol = mask;
+ edge = 0;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ pol = 0;
+ edge = mask;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ pol = mask;
+ edge = mask;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ raw_spin_lock_irqsave(&priv->gc.bgpio_lock, flags);
+
+ ipol = priv->gc.read_reg(priv->regs + GRGPIO_IPOL) & ~mask;
+ iedge = priv->gc.read_reg(priv->regs + GRGPIO_IEDGE) & ~mask;
+
+ priv->gc.write_reg(priv->regs + GRGPIO_IPOL, ipol | pol);
+ priv->gc.write_reg(priv->regs + GRGPIO_IEDGE, iedge | edge);
+
+ raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags);
+
+ return 0;
+}
+
+static void grgpio_irq_mask(struct irq_data *d)
+{
+ struct grgpio_priv *priv = irq_data_get_irq_chip_data(d);
+ int offset = d->hwirq;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&priv->gc.bgpio_lock, flags);
+
+ grgpio_set_imask(priv, offset, 0);
+
+ raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags);
+}
+
+static void grgpio_irq_unmask(struct irq_data *d)
+{
+ struct grgpio_priv *priv = irq_data_get_irq_chip_data(d);
+ int offset = d->hwirq;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&priv->gc.bgpio_lock, flags);
+
+ grgpio_set_imask(priv, offset, 1);
+
+ raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags);
+}
+
+static struct irq_chip grgpio_irq_chip = {
+ .name = "grgpio",
+ .irq_mask = grgpio_irq_mask,
+ .irq_unmask = grgpio_irq_unmask,
+ .irq_set_type = grgpio_irq_set_type,
+};
+
+static irqreturn_t grgpio_irq_handler(int irq, void *dev)
+{
+ struct grgpio_priv *priv = dev;
+ int ngpio = priv->gc.ngpio;
+ unsigned long flags;
+ int i;
+ int match = 0;
+
+ raw_spin_lock_irqsave(&priv->gc.bgpio_lock, flags);
+
+ /*
+ * For each gpio line, call its interrupt handler if it its underlying
+ * irq matches the current irq that is handled.
+ */
+ for (i = 0; i < ngpio; i++) {
+ struct grgpio_lirq *lirq = &priv->lirqs[i];
+
+ if (priv->imask & BIT(i) && lirq->index >= 0 &&
+ priv->uirqs[lirq->index].uirq == irq) {
+ generic_handle_irq(lirq->irq);
+ match = 1;
+ }
+ }
+
+ raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags);
+
+ if (!match)
+ dev_warn(priv->dev, "No gpio line matched irq %d\n", irq);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * This function will be called as a consequence of the call to
+ * irq_create_mapping in grgpio_to_irq
+ */
+static int grgpio_irq_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ struct grgpio_priv *priv = d->host_data;
+ struct grgpio_lirq *lirq;
+ struct grgpio_uirq *uirq;
+ unsigned long flags;
+ int offset = hwirq;
+ int ret = 0;
+
+ if (!priv)
+ return -EINVAL;
+
+ lirq = &priv->lirqs[offset];
+ if (lirq->index < 0)
+ return -EINVAL;
+
+ dev_dbg(priv->dev, "Mapping irq %d for gpio line %d\n",
+ irq, offset);
+
+ raw_spin_lock_irqsave(&priv->gc.bgpio_lock, flags);
+
+ /* Request underlying irq if not already requested */
+ lirq->irq = irq;
+ uirq = &priv->uirqs[lirq->index];
+ if (uirq->refcnt == 0) {
+ raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags);
+ ret = request_irq(uirq->uirq, grgpio_irq_handler, 0,
+ dev_name(priv->dev), priv);
+ if (ret) {
+ dev_err(priv->dev,
+ "Could not request underlying irq %d\n",
+ uirq->uirq);
+ return ret;
+ }
+ raw_spin_lock_irqsave(&priv->gc.bgpio_lock, flags);
+ }
+ uirq->refcnt++;
+
+ raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags);
+
+ /* Setup irq */
+ irq_set_chip_data(irq, priv);
+ irq_set_chip_and_handler(irq, &grgpio_irq_chip,
+ handle_simple_irq);
+ irq_set_noprobe(irq);
+
+ return ret;
+}
+
+static void grgpio_irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+ struct grgpio_priv *priv = d->host_data;
+ int index;
+ struct grgpio_lirq *lirq;
+ struct grgpio_uirq *uirq;
+ unsigned long flags;
+ int ngpio = priv->gc.ngpio;
+ int i;
+
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+
+ raw_spin_lock_irqsave(&priv->gc.bgpio_lock, flags);
+
+ /* Free underlying irq if last user unmapped */
+ index = -1;
+ for (i = 0; i < ngpio; i++) {
+ lirq = &priv->lirqs[i];
+ if (lirq->irq == irq) {
+ grgpio_set_imask(priv, i, 0);
+ lirq->irq = 0;
+ index = lirq->index;
+ break;
+ }
+ }
+ WARN_ON(index < 0);
+
+ if (index >= 0) {
+ uirq = &priv->uirqs[lirq->index];
+ uirq->refcnt--;
+ if (uirq->refcnt == 0) {
+ raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags);
+ free_irq(uirq->uirq, priv);
+ return;
+ }
+ }
+
+ raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags);
+}
+
+static const struct irq_domain_ops grgpio_irq_domain_ops = {
+ .map = grgpio_irq_map,
+ .unmap = grgpio_irq_unmap,
+};
+
+/* ------------------------------------------------------------ */
+
+static int grgpio_probe(struct platform_device *ofdev)
+{
+ struct device_node *np = ofdev->dev.of_node;
+ void __iomem *regs;
+ struct gpio_chip *gc;
+ struct grgpio_priv *priv;
+ int err;
+ u32 prop;
+ s32 *irqmap;
+ int size;
+ int i;
+
+ priv = devm_kzalloc(&ofdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ regs = devm_platform_ioremap_resource(ofdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ gc = &priv->gc;
+ err = bgpio_init(gc, &ofdev->dev, 4, regs + GRGPIO_DATA,
+ regs + GRGPIO_OUTPUT, NULL, regs + GRGPIO_DIR, NULL,
+ BGPIOF_BIG_ENDIAN_BYTE_ORDER);
+ if (err) {
+ dev_err(&ofdev->dev, "bgpio_init() failed\n");
+ return err;
+ }
+
+ priv->regs = regs;
+ priv->imask = gc->read_reg(regs + GRGPIO_IMASK);
+ priv->dev = &ofdev->dev;
+
+ gc->owner = THIS_MODULE;
+ gc->to_irq = grgpio_to_irq;
+ gc->label = devm_kasprintf(&ofdev->dev, GFP_KERNEL, "%pOF", np);
+ gc->base = -1;
+
+ err = of_property_read_u32(np, "nbits", &prop);
+ if (err || prop <= 0 || prop > GRGPIO_MAX_NGPIO) {
+ gc->ngpio = GRGPIO_MAX_NGPIO;
+ dev_dbg(&ofdev->dev,
+ "No or invalid nbits property: assume %d\n", gc->ngpio);
+ } else {
+ gc->ngpio = prop;
+ }
+
+ /*
+ * The irqmap contains the index values indicating which underlying irq,
+ * if anyone, is connected to that line
+ */
+ irqmap = (s32 *)of_get_property(np, "irqmap", &size);
+ if (irqmap) {
+ if (size < gc->ngpio) {
+ dev_err(&ofdev->dev,
+ "irqmap shorter than ngpio (%d < %d)\n",
+ size, gc->ngpio);
+ return -EINVAL;
+ }
+
+ priv->domain = irq_domain_add_linear(np, gc->ngpio,
+ &grgpio_irq_domain_ops,
+ priv);
+ if (!priv->domain) {
+ dev_err(&ofdev->dev, "Could not add irq domain\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < gc->ngpio; i++) {
+ struct grgpio_lirq *lirq;
+ int ret;
+
+ lirq = &priv->lirqs[i];
+ lirq->index = irqmap[i];
+
+ if (lirq->index < 0)
+ continue;
+
+ ret = platform_get_irq(ofdev, lirq->index);
+ if (ret <= 0) {
+ /*
+ * Continue without irq functionality for that
+ * gpio line
+ */
+ continue;
+ }
+ priv->uirqs[lirq->index].uirq = ret;
+ }
+ }
+
+ platform_set_drvdata(ofdev, priv);
+
+ err = gpiochip_add_data(gc, priv);
+ if (err) {
+ dev_err(&ofdev->dev, "Could not add gpiochip\n");
+ if (priv->domain)
+ irq_domain_remove(priv->domain);
+ return err;
+ }
+
+ dev_info(&ofdev->dev, "regs=0x%p, base=%d, ngpio=%d, irqs=%s\n",
+ priv->regs, gc->base, gc->ngpio, priv->domain ? "on" : "off");
+
+ return 0;
+}
+
+static int grgpio_remove(struct platform_device *ofdev)
+{
+ struct grgpio_priv *priv = platform_get_drvdata(ofdev);
+
+ gpiochip_remove(&priv->gc);
+
+ if (priv->domain)
+ irq_domain_remove(priv->domain);
+
+ return 0;
+}
+
+static const struct of_device_id grgpio_match[] = {
+ {.name = "GAISLER_GPIO"},
+ {.name = "01_01a"},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, grgpio_match);
+
+static struct platform_driver grgpio_driver = {
+ .driver = {
+ .name = "grgpio",
+ .of_match_table = grgpio_match,
+ },
+ .probe = grgpio_probe,
+ .remove = grgpio_remove,
+};
+module_platform_driver(grgpio_driver);
+
+MODULE_AUTHOR("Aeroflex Gaisler AB.");
+MODULE_DESCRIPTION("Driver for Aeroflex Gaisler GRGPIO");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-gw-pld.c b/drivers/gpio/gpio-gw-pld.c
new file mode 100644
index 0000000000..899335da93
--- /dev/null
+++ b/drivers/gpio/gpio-gw-pld.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Gateworks I2C PLD GPIO expander
+//
+// Copyright (C) 2019 Linus Walleij <linus.walleij@linaro.org>
+//
+// Based on code and know-how from the OpenWrt driver:
+// Copyright (C) 2009 Gateworks Corporation
+// Authors: Chris Lang, Imre Kaloz
+
+#include <linux/bits.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+
+/**
+ * struct gw_pld - State container for Gateworks PLD
+ * @chip: GPIO chip instance
+ * @client: I2C client
+ * @out: shadow register for the output bute
+ */
+struct gw_pld {
+ struct gpio_chip chip;
+ struct i2c_client *client;
+ u8 out;
+};
+
+/*
+ * The Gateworks I2C PLD chip only expose one read and one write register.
+ * Writing a "one" bit (to match the reset state) lets that pin be used as an
+ * input. It is an open-drain model.
+ */
+static int gw_pld_input8(struct gpio_chip *gc, unsigned offset)
+{
+ struct gw_pld *gw = gpiochip_get_data(gc);
+
+ gw->out |= BIT(offset);
+ return i2c_smbus_write_byte(gw->client, gw->out);
+}
+
+static int gw_pld_get8(struct gpio_chip *gc, unsigned offset)
+{
+ struct gw_pld *gw = gpiochip_get_data(gc);
+ s32 val;
+
+ val = i2c_smbus_read_byte(gw->client);
+
+ return (val < 0) ? 0 : !!(val & BIT(offset));
+}
+
+static int gw_pld_output8(struct gpio_chip *gc, unsigned offset, int value)
+{
+ struct gw_pld *gw = gpiochip_get_data(gc);
+
+ if (value)
+ gw->out |= BIT(offset);
+ else
+ gw->out &= ~BIT(offset);
+
+ return i2c_smbus_write_byte(gw->client, gw->out);
+}
+
+static void gw_pld_set8(struct gpio_chip *gc, unsigned offset, int value)
+{
+ gw_pld_output8(gc, offset, value);
+}
+
+static int gw_pld_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct gw_pld *gw;
+ int ret;
+
+ gw = devm_kzalloc(dev, sizeof(*gw), GFP_KERNEL);
+ if (!gw)
+ return -ENOMEM;
+
+ gw->chip.base = -1;
+ gw->chip.can_sleep = true;
+ gw->chip.parent = dev;
+ gw->chip.owner = THIS_MODULE;
+ gw->chip.label = dev_name(dev);
+ gw->chip.ngpio = 8;
+ gw->chip.direction_input = gw_pld_input8;
+ gw->chip.get = gw_pld_get8;
+ gw->chip.direction_output = gw_pld_output8;
+ gw->chip.set = gw_pld_set8;
+ gw->client = client;
+
+ /*
+ * The Gateworks I2C PLD chip does not properly send the acknowledge
+ * bit at all times, but we can still use the standard i2c_smbus
+ * functions by simply ignoring this bit.
+ */
+ client->flags |= I2C_M_IGNORE_NAK;
+ gw->out = 0xFF;
+
+ i2c_set_clientdata(client, gw);
+
+ ret = devm_gpiochip_add_data(dev, &gw->chip, gw);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "registered Gateworks PLD GPIO device\n");
+
+ return 0;
+}
+
+static const struct i2c_device_id gw_pld_id[] = {
+ { "gw-pld", },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, gw_pld_id);
+
+static const struct of_device_id gw_pld_dt_ids[] = {
+ { .compatible = "gateworks,pld-gpio", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, gw_pld_dt_ids);
+
+static struct i2c_driver gw_pld_driver = {
+ .driver = {
+ .name = "gw_pld",
+ .of_match_table = gw_pld_dt_ids,
+ },
+ .probe = gw_pld_probe,
+ .id_table = gw_pld_id,
+};
+module_i2c_driver(gw_pld_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
diff --git a/drivers/gpio/gpio-hisi.c b/drivers/gpio/gpio-hisi.c
new file mode 100644
index 0000000000..29a03de37f
--- /dev/null
+++ b/drivers/gpio/gpio-hisi.c
@@ -0,0 +1,335 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2020 HiSilicon Limited. */
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+#define HISI_GPIO_SWPORT_DR_SET_WX 0x000
+#define HISI_GPIO_SWPORT_DR_CLR_WX 0x004
+#define HISI_GPIO_SWPORT_DDR_SET_WX 0x010
+#define HISI_GPIO_SWPORT_DDR_CLR_WX 0x014
+#define HISI_GPIO_SWPORT_DDR_ST_WX 0x018
+#define HISI_GPIO_INTEN_SET_WX 0x020
+#define HISI_GPIO_INTEN_CLR_WX 0x024
+#define HISI_GPIO_INTMASK_SET_WX 0x030
+#define HISI_GPIO_INTMASK_CLR_WX 0x034
+#define HISI_GPIO_INTTYPE_EDGE_SET_WX 0x040
+#define HISI_GPIO_INTTYPE_EDGE_CLR_WX 0x044
+#define HISI_GPIO_INT_POLARITY_SET_WX 0x050
+#define HISI_GPIO_INT_POLARITY_CLR_WX 0x054
+#define HISI_GPIO_DEBOUNCE_SET_WX 0x060
+#define HISI_GPIO_DEBOUNCE_CLR_WX 0x064
+#define HISI_GPIO_INTSTATUS_WX 0x070
+#define HISI_GPIO_PORTA_EOI_WX 0x078
+#define HISI_GPIO_EXT_PORT_WX 0x080
+#define HISI_GPIO_INTCOMB_MASK_WX 0x0a0
+#define HISI_GPIO_INT_DEDGE_SET 0x0b0
+#define HISI_GPIO_INT_DEDGE_CLR 0x0b4
+#define HISI_GPIO_INT_DEDGE_ST 0x0b8
+
+#define HISI_GPIO_LINE_NUM_MAX 32
+#define HISI_GPIO_DRIVER_NAME "gpio-hisi"
+
+struct hisi_gpio {
+ struct gpio_chip chip;
+ struct device *dev;
+ void __iomem *reg_base;
+ unsigned int line_num;
+ int irq;
+};
+
+static inline u32 hisi_gpio_read_reg(struct gpio_chip *chip,
+ unsigned int off)
+{
+ struct hisi_gpio *hisi_gpio =
+ container_of(chip, struct hisi_gpio, chip);
+ void __iomem *reg = hisi_gpio->reg_base + off;
+
+ return readl(reg);
+}
+
+static inline void hisi_gpio_write_reg(struct gpio_chip *chip,
+ unsigned int off, u32 val)
+{
+ struct hisi_gpio *hisi_gpio =
+ container_of(chip, struct hisi_gpio, chip);
+ void __iomem *reg = hisi_gpio->reg_base + off;
+
+ writel(val, reg);
+}
+
+static void hisi_gpio_set_debounce(struct gpio_chip *chip, unsigned int off,
+ u32 debounce)
+{
+ if (debounce)
+ hisi_gpio_write_reg(chip, HISI_GPIO_DEBOUNCE_SET_WX, BIT(off));
+ else
+ hisi_gpio_write_reg(chip, HISI_GPIO_DEBOUNCE_CLR_WX, BIT(off));
+}
+
+static int hisi_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ u32 config_para = pinconf_to_config_param(config);
+ u32 config_arg;
+
+ switch (config_para) {
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ config_arg = pinconf_to_config_argument(config);
+ hisi_gpio_set_debounce(chip, offset, config_arg);
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ return 0;
+}
+
+static void hisi_gpio_set_ack(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+
+ hisi_gpio_write_reg(chip, HISI_GPIO_PORTA_EOI_WX, BIT(irqd_to_hwirq(d)));
+}
+
+static void hisi_gpio_irq_set_mask(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+
+ hisi_gpio_write_reg(chip, HISI_GPIO_INTMASK_SET_WX, BIT(irqd_to_hwirq(d)));
+ gpiochip_disable_irq(chip, irqd_to_hwirq(d));
+}
+
+static void hisi_gpio_irq_clr_mask(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+
+ gpiochip_enable_irq(chip, irqd_to_hwirq(d));
+ hisi_gpio_write_reg(chip, HISI_GPIO_INTMASK_CLR_WX, BIT(irqd_to_hwirq(d)));
+}
+
+static int hisi_gpio_irq_set_type(struct irq_data *d, u32 type)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ unsigned int mask = BIT(irqd_to_hwirq(d));
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_BOTH:
+ hisi_gpio_write_reg(chip, HISI_GPIO_INT_DEDGE_SET, mask);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ hisi_gpio_write_reg(chip, HISI_GPIO_INTTYPE_EDGE_SET_WX, mask);
+ hisi_gpio_write_reg(chip, HISI_GPIO_INT_POLARITY_SET_WX, mask);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ hisi_gpio_write_reg(chip, HISI_GPIO_INTTYPE_EDGE_SET_WX, mask);
+ hisi_gpio_write_reg(chip, HISI_GPIO_INT_POLARITY_CLR_WX, mask);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ hisi_gpio_write_reg(chip, HISI_GPIO_INTTYPE_EDGE_CLR_WX, mask);
+ hisi_gpio_write_reg(chip, HISI_GPIO_INT_POLARITY_SET_WX, mask);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ hisi_gpio_write_reg(chip, HISI_GPIO_INTTYPE_EDGE_CLR_WX, mask);
+ hisi_gpio_write_reg(chip, HISI_GPIO_INT_POLARITY_CLR_WX, mask);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * The dual-edge interrupt and other interrupt's registers do not
+ * take effect at the same time. The registers of the two-edge
+ * interrupts have higher priorities, the configuration of
+ * the dual-edge interrupts must be disabled before the configuration
+ * of other kind of interrupts.
+ */
+ if (type != IRQ_TYPE_EDGE_BOTH) {
+ unsigned int both = hisi_gpio_read_reg(chip, HISI_GPIO_INT_DEDGE_ST);
+
+ if (both & mask)
+ hisi_gpio_write_reg(chip, HISI_GPIO_INT_DEDGE_CLR, mask);
+ }
+
+ if (type & IRQ_TYPE_LEVEL_MASK)
+ irq_set_handler_locked(d, handle_level_irq);
+ else if (type & IRQ_TYPE_EDGE_BOTH)
+ irq_set_handler_locked(d, handle_edge_irq);
+
+ return 0;
+}
+
+static void hisi_gpio_irq_enable(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+
+ hisi_gpio_irq_clr_mask(d);
+ hisi_gpio_write_reg(chip, HISI_GPIO_INTEN_SET_WX, BIT(irqd_to_hwirq(d)));
+}
+
+static void hisi_gpio_irq_disable(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+
+ hisi_gpio_irq_set_mask(d);
+ hisi_gpio_write_reg(chip, HISI_GPIO_INTEN_CLR_WX, BIT(irqd_to_hwirq(d)));
+}
+
+static void hisi_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct hisi_gpio *hisi_gpio = irq_desc_get_handler_data(desc);
+ unsigned long irq_msk = hisi_gpio_read_reg(&hisi_gpio->chip,
+ HISI_GPIO_INTSTATUS_WX);
+ struct irq_chip *irq_c = irq_desc_get_chip(desc);
+ int hwirq;
+
+ chained_irq_enter(irq_c, desc);
+ for_each_set_bit(hwirq, &irq_msk, HISI_GPIO_LINE_NUM_MAX)
+ generic_handle_domain_irq(hisi_gpio->chip.irq.domain,
+ hwirq);
+ chained_irq_exit(irq_c, desc);
+}
+
+static const struct irq_chip hisi_gpio_irq_chip = {
+ .name = "HISI-GPIO",
+ .irq_ack = hisi_gpio_set_ack,
+ .irq_mask = hisi_gpio_irq_set_mask,
+ .irq_unmask = hisi_gpio_irq_clr_mask,
+ .irq_set_type = hisi_gpio_irq_set_type,
+ .irq_enable = hisi_gpio_irq_enable,
+ .irq_disable = hisi_gpio_irq_disable,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static void hisi_gpio_init_irq(struct hisi_gpio *hisi_gpio)
+{
+ struct gpio_chip *chip = &hisi_gpio->chip;
+ struct gpio_irq_chip *girq_chip = &chip->irq;
+
+ gpio_irq_chip_set_chip(girq_chip, &hisi_gpio_irq_chip);
+ girq_chip->default_type = IRQ_TYPE_NONE;
+ girq_chip->num_parents = 1;
+ girq_chip->parents = &hisi_gpio->irq;
+ girq_chip->parent_handler = hisi_gpio_irq_handler;
+ girq_chip->parent_handler_data = hisi_gpio;
+
+ /* Clear Mask of GPIO controller combine IRQ */
+ hisi_gpio_write_reg(chip, HISI_GPIO_INTCOMB_MASK_WX, 1);
+}
+
+static const struct acpi_device_id hisi_gpio_acpi_match[] = {
+ {"HISI0184", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, hisi_gpio_acpi_match);
+
+static const struct of_device_id hisi_gpio_dts_match[] = {
+ { .compatible = "hisilicon,ascend910-gpio", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, hisi_gpio_dts_match);
+
+static void hisi_gpio_get_pdata(struct device *dev,
+ struct hisi_gpio *hisi_gpio)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct fwnode_handle *fwnode;
+ int idx = 0;
+
+ device_for_each_child_node(dev, fwnode) {
+ /* Cycle for once, no need for an array to save line_num */
+ if (fwnode_property_read_u32(fwnode, "ngpios",
+ &hisi_gpio->line_num)) {
+ dev_err(dev,
+ "failed to get number of lines for port%d and use default value instead\n",
+ idx);
+ hisi_gpio->line_num = HISI_GPIO_LINE_NUM_MAX;
+ }
+
+ if (WARN_ON(hisi_gpio->line_num > HISI_GPIO_LINE_NUM_MAX))
+ hisi_gpio->line_num = HISI_GPIO_LINE_NUM_MAX;
+
+ hisi_gpio->irq = platform_get_irq(pdev, idx);
+
+ dev_info(dev,
+ "get hisi_gpio[%d] with %d lines\n", idx,
+ hisi_gpio->line_num);
+
+ idx++;
+ }
+}
+
+static int hisi_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct hisi_gpio *hisi_gpio;
+ int port_num;
+ int ret;
+
+ /*
+ * One GPIO controller own one port currently,
+ * if we get more from ACPI table, return error.
+ */
+ port_num = device_get_child_node_count(dev);
+ if (WARN_ON(port_num != 1))
+ return -ENODEV;
+
+ hisi_gpio = devm_kzalloc(dev, sizeof(*hisi_gpio), GFP_KERNEL);
+ if (!hisi_gpio)
+ return -ENOMEM;
+
+ hisi_gpio->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(hisi_gpio->reg_base))
+ return PTR_ERR(hisi_gpio->reg_base);
+
+ hisi_gpio_get_pdata(dev, hisi_gpio);
+
+ hisi_gpio->dev = dev;
+
+ ret = bgpio_init(&hisi_gpio->chip, hisi_gpio->dev, 0x4,
+ hisi_gpio->reg_base + HISI_GPIO_EXT_PORT_WX,
+ hisi_gpio->reg_base + HISI_GPIO_SWPORT_DR_SET_WX,
+ hisi_gpio->reg_base + HISI_GPIO_SWPORT_DR_CLR_WX,
+ hisi_gpio->reg_base + HISI_GPIO_SWPORT_DDR_SET_WX,
+ hisi_gpio->reg_base + HISI_GPIO_SWPORT_DDR_CLR_WX,
+ BGPIOF_NO_SET_ON_INPUT);
+ if (ret) {
+ dev_err(dev, "failed to init, ret = %d\n", ret);
+ return ret;
+ }
+
+ hisi_gpio->chip.set_config = hisi_gpio_set_config;
+ hisi_gpio->chip.ngpio = hisi_gpio->line_num;
+ hisi_gpio->chip.bgpio_dir_unreadable = 1;
+ hisi_gpio->chip.base = -1;
+
+ if (hisi_gpio->irq > 0)
+ hisi_gpio_init_irq(hisi_gpio);
+
+ ret = devm_gpiochip_add_data(dev, &hisi_gpio->chip, hisi_gpio);
+ if (ret) {
+ dev_err(dev, "failed to register gpiochip, ret = %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver hisi_gpio_driver = {
+ .driver = {
+ .name = HISI_GPIO_DRIVER_NAME,
+ .acpi_match_table = hisi_gpio_acpi_match,
+ .of_match_table = hisi_gpio_dts_match,
+ },
+ .probe = hisi_gpio_probe,
+};
+
+module_platform_driver(hisi_gpio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Luo Jiaxing <luojiaxing@huawei.com>");
+MODULE_DESCRIPTION("HiSilicon GPIO controller driver");
+MODULE_ALIAS("platform:" HISI_GPIO_DRIVER_NAME);
diff --git a/drivers/gpio/gpio-hlwd.c b/drivers/gpio/gpio-hlwd.c
new file mode 100644
index 0000000000..1bcfc1835d
--- /dev/null
+++ b/drivers/gpio/gpio-hlwd.c
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright (C) 2008-2009 The GameCube Linux Team
+// Copyright (C) 2008,2009 Albert Herranz
+// Copyright (C) 2017-2018 Jonathan Neuschäfer
+//
+// Nintendo Wii (Hollywood) GPIO driver
+
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+
+/*
+ * Register names and offsets courtesy of WiiBrew:
+ * https://wiibrew.org/wiki/Hardware/Hollywood_GPIOs
+ *
+ * Note that for most registers, there are two versions:
+ * - HW_GPIOB_* Is always accessible by the Broadway PowerPC core, but does
+ * always give access to all GPIO lines
+ * - HW_GPIO_* Is only accessible by the Broadway PowerPC code if the memory
+ * firewall (AHBPROT) in the Hollywood chipset has been configured to allow
+ * such access.
+ *
+ * The ownership of each GPIO line can be configured in the HW_GPIO_OWNER
+ * register: A one bit configures the line for access via the HW_GPIOB_*
+ * registers, a zero bit indicates access via HW_GPIO_*. This driver uses
+ * HW_GPIOB_*.
+ */
+#define HW_GPIOB_OUT 0x00
+#define HW_GPIOB_DIR 0x04
+#define HW_GPIOB_IN 0x08
+#define HW_GPIOB_INTLVL 0x0c
+#define HW_GPIOB_INTFLAG 0x10
+#define HW_GPIOB_INTMASK 0x14
+#define HW_GPIOB_INMIR 0x18
+#define HW_GPIO_ENABLE 0x1c
+#define HW_GPIO_OUT 0x20
+#define HW_GPIO_DIR 0x24
+#define HW_GPIO_IN 0x28
+#define HW_GPIO_INTLVL 0x2c
+#define HW_GPIO_INTFLAG 0x30
+#define HW_GPIO_INTMASK 0x34
+#define HW_GPIO_INMIR 0x38
+#define HW_GPIO_OWNER 0x3c
+
+struct hlwd_gpio {
+ struct gpio_chip gpioc;
+ struct device *dev;
+ void __iomem *regs;
+ int irq;
+ u32 edge_emulation;
+ u32 rising_edge, falling_edge;
+};
+
+static void hlwd_gpio_irqhandler(struct irq_desc *desc)
+{
+ struct hlwd_gpio *hlwd =
+ gpiochip_get_data(irq_desc_get_handler_data(desc));
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned long flags;
+ unsigned long pending;
+ int hwirq;
+ u32 emulated_pending;
+
+ raw_spin_lock_irqsave(&hlwd->gpioc.bgpio_lock, flags);
+ pending = ioread32be(hlwd->regs + HW_GPIOB_INTFLAG);
+ pending &= ioread32be(hlwd->regs + HW_GPIOB_INTMASK);
+
+ /* Treat interrupts due to edge trigger emulation separately */
+ emulated_pending = hlwd->edge_emulation & pending;
+ pending &= ~emulated_pending;
+ if (emulated_pending) {
+ u32 level, rising, falling;
+
+ level = ioread32be(hlwd->regs + HW_GPIOB_INTLVL);
+ rising = level & emulated_pending;
+ falling = ~level & emulated_pending;
+
+ /* Invert the levels */
+ iowrite32be(level ^ emulated_pending,
+ hlwd->regs + HW_GPIOB_INTLVL);
+
+ /* Ack all emulated-edge interrupts */
+ iowrite32be(emulated_pending, hlwd->regs + HW_GPIOB_INTFLAG);
+
+ /* Signal interrupts only on the correct edge */
+ rising &= hlwd->rising_edge;
+ falling &= hlwd->falling_edge;
+
+ /* Mark emulated interrupts as pending */
+ pending |= rising | falling;
+ }
+ raw_spin_unlock_irqrestore(&hlwd->gpioc.bgpio_lock, flags);
+
+ chained_irq_enter(chip, desc);
+
+ for_each_set_bit(hwirq, &pending, 32)
+ generic_handle_domain_irq(hlwd->gpioc.irq.domain, hwirq);
+
+ chained_irq_exit(chip, desc);
+}
+
+static void hlwd_gpio_irq_ack(struct irq_data *data)
+{
+ struct hlwd_gpio *hlwd =
+ gpiochip_get_data(irq_data_get_irq_chip_data(data));
+
+ iowrite32be(BIT(data->hwirq), hlwd->regs + HW_GPIOB_INTFLAG);
+}
+
+static void hlwd_gpio_irq_mask(struct irq_data *data)
+{
+ struct hlwd_gpio *hlwd =
+ gpiochip_get_data(irq_data_get_irq_chip_data(data));
+ unsigned long flags;
+ u32 mask;
+
+ raw_spin_lock_irqsave(&hlwd->gpioc.bgpio_lock, flags);
+ mask = ioread32be(hlwd->regs + HW_GPIOB_INTMASK);
+ mask &= ~BIT(data->hwirq);
+ iowrite32be(mask, hlwd->regs + HW_GPIOB_INTMASK);
+ raw_spin_unlock_irqrestore(&hlwd->gpioc.bgpio_lock, flags);
+ gpiochip_disable_irq(&hlwd->gpioc, irqd_to_hwirq(data));
+}
+
+static void hlwd_gpio_irq_unmask(struct irq_data *data)
+{
+ struct hlwd_gpio *hlwd =
+ gpiochip_get_data(irq_data_get_irq_chip_data(data));
+ unsigned long flags;
+ u32 mask;
+
+ gpiochip_enable_irq(&hlwd->gpioc, irqd_to_hwirq(data));
+ raw_spin_lock_irqsave(&hlwd->gpioc.bgpio_lock, flags);
+ mask = ioread32be(hlwd->regs + HW_GPIOB_INTMASK);
+ mask |= BIT(data->hwirq);
+ iowrite32be(mask, hlwd->regs + HW_GPIOB_INTMASK);
+ raw_spin_unlock_irqrestore(&hlwd->gpioc.bgpio_lock, flags);
+}
+
+static void hlwd_gpio_irq_enable(struct irq_data *data)
+{
+ hlwd_gpio_irq_ack(data);
+ hlwd_gpio_irq_unmask(data);
+}
+
+static void hlwd_gpio_irq_setup_emulation(struct hlwd_gpio *hlwd, int hwirq,
+ unsigned int flow_type)
+{
+ u32 level, state;
+
+ /* Set the trigger level to the inactive level */
+ level = ioread32be(hlwd->regs + HW_GPIOB_INTLVL);
+ state = ioread32be(hlwd->regs + HW_GPIOB_IN) & BIT(hwirq);
+ level &= ~BIT(hwirq);
+ level |= state ^ BIT(hwirq);
+ iowrite32be(level, hlwd->regs + HW_GPIOB_INTLVL);
+
+ hlwd->edge_emulation |= BIT(hwirq);
+ hlwd->rising_edge &= ~BIT(hwirq);
+ hlwd->falling_edge &= ~BIT(hwirq);
+ if (flow_type & IRQ_TYPE_EDGE_RISING)
+ hlwd->rising_edge |= BIT(hwirq);
+ if (flow_type & IRQ_TYPE_EDGE_FALLING)
+ hlwd->falling_edge |= BIT(hwirq);
+}
+
+static int hlwd_gpio_irq_set_type(struct irq_data *data, unsigned int flow_type)
+{
+ struct hlwd_gpio *hlwd =
+ gpiochip_get_data(irq_data_get_irq_chip_data(data));
+ unsigned long flags;
+ u32 level;
+
+ raw_spin_lock_irqsave(&hlwd->gpioc.bgpio_lock, flags);
+
+ hlwd->edge_emulation &= ~BIT(data->hwirq);
+
+ switch (flow_type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ level = ioread32be(hlwd->regs + HW_GPIOB_INTLVL);
+ level |= BIT(data->hwirq);
+ iowrite32be(level, hlwd->regs + HW_GPIOB_INTLVL);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ level = ioread32be(hlwd->regs + HW_GPIOB_INTLVL);
+ level &= ~BIT(data->hwirq);
+ iowrite32be(level, hlwd->regs + HW_GPIOB_INTLVL);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_EDGE_FALLING:
+ case IRQ_TYPE_EDGE_BOTH:
+ hlwd_gpio_irq_setup_emulation(hlwd, data->hwirq, flow_type);
+ break;
+ default:
+ raw_spin_unlock_irqrestore(&hlwd->gpioc.bgpio_lock, flags);
+ return -EINVAL;
+ }
+
+ raw_spin_unlock_irqrestore(&hlwd->gpioc.bgpio_lock, flags);
+ return 0;
+}
+
+static void hlwd_gpio_irq_print_chip(struct irq_data *data, struct seq_file *p)
+{
+ struct hlwd_gpio *hlwd =
+ gpiochip_get_data(irq_data_get_irq_chip_data(data));
+
+ seq_printf(p, dev_name(hlwd->dev));
+}
+
+static const struct irq_chip hlwd_gpio_irq_chip = {
+ .irq_mask = hlwd_gpio_irq_mask,
+ .irq_unmask = hlwd_gpio_irq_unmask,
+ .irq_enable = hlwd_gpio_irq_enable,
+ .irq_set_type = hlwd_gpio_irq_set_type,
+ .irq_print_chip = hlwd_gpio_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int hlwd_gpio_probe(struct platform_device *pdev)
+{
+ struct hlwd_gpio *hlwd;
+ u32 ngpios;
+ int res;
+
+ hlwd = devm_kzalloc(&pdev->dev, sizeof(*hlwd), GFP_KERNEL);
+ if (!hlwd)
+ return -ENOMEM;
+
+ hlwd->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(hlwd->regs))
+ return PTR_ERR(hlwd->regs);
+
+ hlwd->dev = &pdev->dev;
+
+ /*
+ * Claim all GPIOs using the OWNER register. This will not work on
+ * systems where the AHBPROT memory firewall hasn't been configured to
+ * permit PPC access to HW_GPIO_*.
+ *
+ * Note that this has to happen before bgpio_init reads the
+ * HW_GPIOB_OUT and HW_GPIOB_DIR, because otherwise it reads the wrong
+ * values.
+ */
+ iowrite32be(0xffffffff, hlwd->regs + HW_GPIO_OWNER);
+
+ res = bgpio_init(&hlwd->gpioc, &pdev->dev, 4,
+ hlwd->regs + HW_GPIOB_IN, hlwd->regs + HW_GPIOB_OUT,
+ NULL, hlwd->regs + HW_GPIOB_DIR, NULL,
+ BGPIOF_BIG_ENDIAN_BYTE_ORDER);
+ if (res < 0) {
+ dev_warn(&pdev->dev, "bgpio_init failed: %d\n", res);
+ return res;
+ }
+
+ res = of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios);
+ if (res)
+ ngpios = 32;
+ hlwd->gpioc.ngpio = ngpios;
+
+ /* Mask and ack all interrupts */
+ iowrite32be(0, hlwd->regs + HW_GPIOB_INTMASK);
+ iowrite32be(0xffffffff, hlwd->regs + HW_GPIOB_INTFLAG);
+
+ /*
+ * If this GPIO controller is not marked as an interrupt controller in
+ * the DT, skip interrupt support.
+ */
+ if (of_property_read_bool(pdev->dev.of_node, "interrupt-controller")) {
+ struct gpio_irq_chip *girq;
+
+ hlwd->irq = platform_get_irq(pdev, 0);
+ if (hlwd->irq < 0) {
+ dev_info(&pdev->dev, "platform_get_irq returned %d\n",
+ hlwd->irq);
+ return hlwd->irq;
+ }
+
+ girq = &hlwd->gpioc.irq;
+ gpio_irq_chip_set_chip(girq, &hlwd_gpio_irq_chip);
+ girq->parent_handler = hlwd_gpio_irqhandler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(&pdev->dev, 1,
+ sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ girq->parents[0] = hlwd->irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_level_irq;
+ }
+
+ return devm_gpiochip_add_data(&pdev->dev, &hlwd->gpioc, hlwd);
+}
+
+static const struct of_device_id hlwd_gpio_match[] = {
+ { .compatible = "nintendo,hollywood-gpio", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, hlwd_gpio_match);
+
+static struct platform_driver hlwd_gpio_driver = {
+ .driver = {
+ .name = "gpio-hlwd",
+ .of_match_table = hlwd_gpio_match,
+ },
+ .probe = hlwd_gpio_probe,
+};
+module_platform_driver(hlwd_gpio_driver);
+
+MODULE_AUTHOR("Jonathan Neuschäfer <j.neuschaefer@gmx.net>");
+MODULE_DESCRIPTION("Nintendo Wii GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-htc-egpio.c b/drivers/gpio/gpio-htc-egpio.c
new file mode 100644
index 0000000000..a40bd56673
--- /dev/null
+++ b/drivers/gpio/gpio-htc-egpio.c
@@ -0,0 +1,409 @@
+/*
+ * Support for the GPIO/IRQ expander chips present on several HTC phones.
+ * These are implemented in CPLD chips present on the board.
+ *
+ * Copyright (c) 2007 Kevin O'Connor <kevin@koconnor.net>
+ * Copyright (c) 2007 Philipp Zabel <philipp.zabel@gmail.com>
+ *
+ * This file may be distributed under the terms of the GNU GPL license.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/platform_data/gpio-htc-egpio.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/gpio/driver.h>
+
+struct egpio_chip {
+ int reg_start;
+ int cached_values;
+ unsigned long is_out;
+ struct device *dev;
+ struct gpio_chip chip;
+};
+
+struct egpio_info {
+ spinlock_t lock;
+
+ /* iomem info */
+ void __iomem *base_addr;
+ int bus_shift; /* byte shift */
+ int reg_shift; /* bit shift */
+ int reg_mask;
+
+ /* irq info */
+ int ack_register;
+ int ack_write;
+ u16 irqs_enabled;
+ uint irq_start;
+ int nirqs;
+ uint chained_irq;
+
+ /* egpio info */
+ struct egpio_chip *chip;
+ int nchips;
+};
+
+static inline void egpio_writew(u16 value, struct egpio_info *ei, int reg)
+{
+ writew(value, ei->base_addr + (reg << ei->bus_shift));
+}
+
+static inline u16 egpio_readw(struct egpio_info *ei, int reg)
+{
+ return readw(ei->base_addr + (reg << ei->bus_shift));
+}
+
+/*
+ * IRQs
+ */
+
+static inline void ack_irqs(struct egpio_info *ei)
+{
+ egpio_writew(ei->ack_write, ei, ei->ack_register);
+ pr_debug("EGPIO ack - write %x to base+%x\n",
+ ei->ack_write, ei->ack_register << ei->bus_shift);
+}
+
+static void egpio_ack(struct irq_data *data)
+{
+}
+
+/* There does not appear to be a way to proactively mask interrupts
+ * on the egpio chip itself. So, we simply ignore interrupts that
+ * aren't desired. */
+static void egpio_mask(struct irq_data *data)
+{
+ struct egpio_info *ei = irq_data_get_irq_chip_data(data);
+ ei->irqs_enabled &= ~(1 << (data->irq - ei->irq_start));
+ pr_debug("EGPIO mask %d %04x\n", data->irq, ei->irqs_enabled);
+}
+
+static void egpio_unmask(struct irq_data *data)
+{
+ struct egpio_info *ei = irq_data_get_irq_chip_data(data);
+ ei->irqs_enabled |= 1 << (data->irq - ei->irq_start);
+ pr_debug("EGPIO unmask %d %04x\n", data->irq, ei->irqs_enabled);
+}
+
+static struct irq_chip egpio_muxed_chip = {
+ .name = "htc-egpio",
+ .irq_ack = egpio_ack,
+ .irq_mask = egpio_mask,
+ .irq_unmask = egpio_unmask,
+};
+
+static void egpio_handler(struct irq_desc *desc)
+{
+ struct egpio_info *ei = irq_desc_get_handler_data(desc);
+ int irqpin;
+
+ /* Read current pins. */
+ unsigned long readval = egpio_readw(ei, ei->ack_register);
+ pr_debug("IRQ reg: %x\n", (unsigned int)readval);
+ /* Ack/unmask interrupts. */
+ ack_irqs(ei);
+ /* Process all set pins. */
+ readval &= ei->irqs_enabled;
+ for_each_set_bit(irqpin, &readval, ei->nirqs) {
+ /* Run irq handler */
+ pr_debug("got IRQ %d\n", irqpin);
+ generic_handle_irq(ei->irq_start + irqpin);
+ }
+}
+
+static inline int egpio_pos(struct egpio_info *ei, int bit)
+{
+ return bit >> ei->reg_shift;
+}
+
+static inline int egpio_bit(struct egpio_info *ei, int bit)
+{
+ return 1 << (bit & ((1 << ei->reg_shift)-1));
+}
+
+/*
+ * Input pins
+ */
+
+static int egpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct egpio_chip *egpio;
+ struct egpio_info *ei;
+ unsigned bit;
+ int reg;
+ int value;
+
+ pr_debug("egpio_get_value(%d)\n", chip->base + offset);
+
+ egpio = gpiochip_get_data(chip);
+ ei = dev_get_drvdata(egpio->dev);
+ bit = egpio_bit(ei, offset);
+ reg = egpio->reg_start + egpio_pos(ei, offset);
+
+ if (test_bit(offset, &egpio->is_out)) {
+ return !!(egpio->cached_values & (1 << offset));
+ } else {
+ value = egpio_readw(ei, reg);
+ pr_debug("readw(%p + %x) = %x\n",
+ ei->base_addr, reg << ei->bus_shift, value);
+ return !!(value & bit);
+ }
+}
+
+static int egpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ struct egpio_chip *egpio;
+
+ egpio = gpiochip_get_data(chip);
+ return test_bit(offset, &egpio->is_out) ? -EINVAL : 0;
+}
+
+
+/*
+ * Output pins
+ */
+
+static void egpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ unsigned long flag;
+ struct egpio_chip *egpio;
+ struct egpio_info *ei;
+ int pos;
+ int reg;
+ int shift;
+
+ pr_debug("egpio_set(%s, %d(%d), %d)\n",
+ chip->label, offset, offset+chip->base, value);
+
+ egpio = gpiochip_get_data(chip);
+ ei = dev_get_drvdata(egpio->dev);
+ pos = egpio_pos(ei, offset);
+ reg = egpio->reg_start + pos;
+ shift = pos << ei->reg_shift;
+
+ pr_debug("egpio %s: reg %d = 0x%04x\n", value ? "set" : "clear",
+ reg, (egpio->cached_values >> shift) & ei->reg_mask);
+
+ spin_lock_irqsave(&ei->lock, flag);
+ if (value)
+ egpio->cached_values |= (1 << offset);
+ else
+ egpio->cached_values &= ~(1 << offset);
+ egpio_writew((egpio->cached_values >> shift) & ei->reg_mask, ei, reg);
+ spin_unlock_irqrestore(&ei->lock, flag);
+}
+
+static int egpio_direction_output(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct egpio_chip *egpio;
+
+ egpio = gpiochip_get_data(chip);
+ if (test_bit(offset, &egpio->is_out)) {
+ egpio_set(chip, offset, value);
+ return 0;
+ } else {
+ return -EINVAL;
+ }
+}
+
+static int egpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+ struct egpio_chip *egpio;
+
+ egpio = gpiochip_get_data(chip);
+
+ if (test_bit(offset, &egpio->is_out))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static void egpio_write_cache(struct egpio_info *ei)
+{
+ int i;
+ struct egpio_chip *egpio;
+ int shift;
+
+ for (i = 0; i < ei->nchips; i++) {
+ egpio = &(ei->chip[i]);
+ if (!egpio->is_out)
+ continue;
+
+ for (shift = 0; shift < egpio->chip.ngpio;
+ shift += (1<<ei->reg_shift)) {
+
+ int reg = egpio->reg_start + egpio_pos(ei, shift);
+
+ if (!((egpio->is_out >> shift) & ei->reg_mask))
+ continue;
+
+ pr_debug("EGPIO: setting %x to %x, was %x\n", reg,
+ (egpio->cached_values >> shift) & ei->reg_mask,
+ egpio_readw(ei, reg));
+
+ egpio_writew((egpio->cached_values >> shift)
+ & ei->reg_mask, ei, reg);
+ }
+ }
+}
+
+
+/*
+ * Setup
+ */
+
+static int __init egpio_probe(struct platform_device *pdev)
+{
+ struct htc_egpio_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct resource *res;
+ struct egpio_info *ei;
+ struct gpio_chip *chip;
+ unsigned int irq, irq_end;
+ int i;
+
+ /* Initialize ei data structure. */
+ ei = devm_kzalloc(&pdev->dev, sizeof(*ei), GFP_KERNEL);
+ if (!ei)
+ return -ENOMEM;
+
+ spin_lock_init(&ei->lock);
+
+ /* Find chained irq */
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (res)
+ ei->chained_irq = res->start;
+
+ /* Map egpio chip into virtual address space. */
+ ei->base_addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ei->base_addr))
+ return PTR_ERR(ei->base_addr);
+
+ if ((pdata->bus_width != 16) && (pdata->bus_width != 32))
+ return -EINVAL;
+
+ ei->bus_shift = fls(pdata->bus_width - 1) - 3;
+ pr_debug("bus_shift = %d\n", ei->bus_shift);
+
+ if ((pdata->reg_width != 8) && (pdata->reg_width != 16))
+ return -EINVAL;
+
+ ei->reg_shift = fls(pdata->reg_width - 1);
+ pr_debug("reg_shift = %d\n", ei->reg_shift);
+
+ ei->reg_mask = (1 << pdata->reg_width) - 1;
+
+ platform_set_drvdata(pdev, ei);
+
+ ei->nchips = pdata->num_chips;
+ ei->chip = devm_kcalloc(&pdev->dev,
+ ei->nchips, sizeof(struct egpio_chip),
+ GFP_KERNEL);
+ if (!ei->chip)
+ return -ENOMEM;
+
+ for (i = 0; i < ei->nchips; i++) {
+ ei->chip[i].reg_start = pdata->chip[i].reg_start;
+ ei->chip[i].cached_values = pdata->chip[i].initial_values;
+ ei->chip[i].is_out = pdata->chip[i].direction;
+ ei->chip[i].dev = &(pdev->dev);
+ chip = &(ei->chip[i].chip);
+ chip->label = devm_kasprintf(&pdev->dev, GFP_KERNEL,
+ "htc-egpio-%d",
+ i);
+ if (!chip->label)
+ return -ENOMEM;
+
+ chip->parent = &pdev->dev;
+ chip->owner = THIS_MODULE;
+ chip->get = egpio_get;
+ chip->set = egpio_set;
+ chip->direction_input = egpio_direction_input;
+ chip->direction_output = egpio_direction_output;
+ chip->get_direction = egpio_get_direction;
+ chip->base = pdata->chip[i].gpio_base;
+ chip->ngpio = pdata->chip[i].num_gpios;
+
+ gpiochip_add_data(chip, &ei->chip[i]);
+ }
+
+ /* Set initial pin values */
+ egpio_write_cache(ei);
+
+ ei->irq_start = pdata->irq_base;
+ ei->nirqs = pdata->num_irqs;
+ ei->ack_register = pdata->ack_register;
+
+ if (ei->chained_irq) {
+ /* Setup irq handlers */
+ ei->ack_write = 0xFFFF;
+ if (pdata->invert_acks)
+ ei->ack_write = 0;
+ irq_end = ei->irq_start + ei->nirqs;
+ for (irq = ei->irq_start; irq < irq_end; irq++) {
+ irq_set_chip_and_handler(irq, &egpio_muxed_chip,
+ handle_simple_irq);
+ irq_set_chip_data(irq, ei);
+ irq_clear_status_flags(irq, IRQ_NOREQUEST | IRQ_NOPROBE);
+ }
+ irq_set_irq_type(ei->chained_irq, IRQ_TYPE_EDGE_RISING);
+ irq_set_chained_handler_and_data(ei->chained_irq,
+ egpio_handler, ei);
+ ack_irqs(ei);
+
+ device_init_wakeup(&pdev->dev, 1);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int egpio_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct egpio_info *ei = platform_get_drvdata(pdev);
+
+ if (ei->chained_irq && device_may_wakeup(&pdev->dev))
+ enable_irq_wake(ei->chained_irq);
+ return 0;
+}
+
+static int egpio_resume(struct platform_device *pdev)
+{
+ struct egpio_info *ei = platform_get_drvdata(pdev);
+
+ if (ei->chained_irq && device_may_wakeup(&pdev->dev))
+ disable_irq_wake(ei->chained_irq);
+
+ /* Update registers from the cache, in case
+ the CPLD was powered off during suspend */
+ egpio_write_cache(ei);
+ return 0;
+}
+#else
+#define egpio_suspend NULL
+#define egpio_resume NULL
+#endif
+
+
+static struct platform_driver egpio_driver = {
+ .driver = {
+ .name = "htc-egpio",
+ .suppress_bind_attrs = true,
+ },
+ .suspend = egpio_suspend,
+ .resume = egpio_resume,
+};
+
+static int __init egpio_init(void)
+{
+ return platform_driver_probe(&egpio_driver, egpio_probe);
+}
+/* start early for dependencies */
+subsys_initcall(egpio_init);
diff --git a/drivers/gpio/gpio-i8255.c b/drivers/gpio/gpio-i8255.c
new file mode 100644
index 0000000000..64ab80fc4a
--- /dev/null
+++ b/drivers/gpio/gpio-i8255.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel 8255 Programmable Peripheral Interface
+ * Copyright (C) 2022 William Breathitt Gray
+ */
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/gpio/regmap.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "gpio-i8255.h"
+
+#define I8255_NGPIO 24
+#define I8255_NGPIO_PER_REG 8
+#define I8255_CONTROL_PORTC_LOWER_DIRECTION BIT(0)
+#define I8255_CONTROL_PORTB_DIRECTION BIT(1)
+#define I8255_CONTROL_PORTC_UPPER_DIRECTION BIT(3)
+#define I8255_CONTROL_PORTA_DIRECTION BIT(4)
+#define I8255_CONTROL_MODE_SET BIT(7)
+#define I8255_PORTA 0x0
+#define I8255_PORTB 0x1
+#define I8255_PORTC 0x2
+#define I8255_CONTROL 0x3
+#define I8255_REG_DAT_BASE I8255_PORTA
+#define I8255_REG_DIR_IN_BASE I8255_CONTROL
+
+static int i8255_direction_mask(const unsigned int offset)
+{
+ const unsigned int stride = offset / I8255_NGPIO_PER_REG;
+ const unsigned int line = offset % I8255_NGPIO_PER_REG;
+
+ switch (stride) {
+ case I8255_PORTA:
+ return I8255_CONTROL_PORTA_DIRECTION;
+ case I8255_PORTB:
+ return I8255_CONTROL_PORTB_DIRECTION;
+ case I8255_PORTC:
+ /* Port C can be configured by nibble */
+ if (line >= 4)
+ return I8255_CONTROL_PORTC_UPPER_DIRECTION;
+ return I8255_CONTROL_PORTC_LOWER_DIRECTION;
+ default:
+ /* Should never reach this path */
+ return 0;
+ }
+}
+
+static int i8255_ppi_init(struct regmap *const map, const unsigned int base)
+{
+ int err;
+
+ /* Configure all ports to MODE 0 output mode */
+ err = regmap_write(map, base + I8255_CONTROL, I8255_CONTROL_MODE_SET);
+ if (err)
+ return err;
+
+ /* Initialize all GPIO to output 0 */
+ err = regmap_write(map, base + I8255_PORTA, 0x00);
+ if (err)
+ return err;
+ err = regmap_write(map, base + I8255_PORTB, 0x00);
+ if (err)
+ return err;
+ 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,
+ unsigned int *mask)
+{
+ const unsigned int ppi = offset / I8255_NGPIO;
+ const unsigned int ppi_offset = offset % I8255_NGPIO;
+ const unsigned int stride = ppi_offset / I8255_NGPIO_PER_REG;
+ const unsigned int line = ppi_offset % I8255_NGPIO_PER_REG;
+
+ switch (base) {
+ case I8255_REG_DAT_BASE:
+ *reg = base + stride + ppi * 4;
+ *mask = BIT(line);
+ return 0;
+ case I8255_REG_DIR_IN_BASE:
+ *reg = base + ppi * 4;
+ *mask = i8255_direction_mask(ppi_offset);
+ return 0;
+ default:
+ /* Should never reach this path */
+ return -EINVAL;
+ }
+}
+
+/**
+ * devm_i8255_regmap_register - Register an i8255 GPIO controller
+ * @dev: device that is registering this i8255 GPIO device
+ * @config: configuration for i8255_regmap_config
+ *
+ * Registers an Intel 8255 Programmable Peripheral Interface GPIO controller.
+ * Returns 0 on success and negative error number on failure.
+ */
+int devm_i8255_regmap_register(struct device *const dev,
+ const struct i8255_regmap_config *const config)
+{
+ struct gpio_regmap_config gpio_config = {0};
+ unsigned long i;
+ int err;
+
+ if (!config->parent)
+ return -EINVAL;
+
+ if (!config->map)
+ return -EINVAL;
+
+ if (!config->num_ppi)
+ return -EINVAL;
+
+ for (i = 0; i < config->num_ppi; i++) {
+ err = i8255_ppi_init(config->map, i * 4);
+ if (err)
+ return err;
+ }
+
+ gpio_config.parent = config->parent;
+ gpio_config.regmap = config->map;
+ gpio_config.ngpio = I8255_NGPIO * config->num_ppi;
+ gpio_config.names = config->names;
+ gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(I8255_REG_DAT_BASE);
+ gpio_config.reg_set_base = GPIO_REGMAP_ADDR(I8255_REG_DAT_BASE);
+ gpio_config.reg_dir_in_base = GPIO_REGMAP_ADDR(I8255_REG_DIR_IN_BASE);
+ gpio_config.ngpio_per_reg = I8255_NGPIO_PER_REG;
+ gpio_config.irq_domain = config->domain;
+ gpio_config.reg_mask_xlate = i8255_reg_mask_xlate;
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
+}
+EXPORT_SYMBOL_NS_GPL(devm_i8255_regmap_register, I8255);
+
+MODULE_AUTHOR("William Breathitt Gray");
+MODULE_DESCRIPTION("Intel 8255 Programmable Peripheral Interface");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-i8255.h b/drivers/gpio/gpio-i8255.h
new file mode 100644
index 0000000000..9dcf639b94
--- /dev/null
+++ b/drivers/gpio/gpio-i8255.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright 2022 William Breathitt Gray */
+#ifndef _I8255_H_
+#define _I8255_H_
+
+struct device;
+struct irq_domain;
+struct regmap;
+
+#define i8255_volatile_regmap_range(_base) regmap_reg_range(_base, _base + 0x2)
+
+/**
+ * struct i8255_regmap_config - Configuration for the register map of an i8255
+ * @parent: parent device
+ * @map: regmap for the i8255
+ * @num_ppi: number of i8255 Programmable Peripheral Interface
+ * @names: (optional) array of names for gpios
+ * @domain: (optional) IRQ domain if the controller is interrupt-capable
+ *
+ * Note: The regmap is expected to have cache enabled and i8255 control
+ * registers not marked as volatile.
+ */
+struct i8255_regmap_config {
+ struct device *parent;
+ struct regmap *map;
+ int num_ppi;
+ const char *const *names;
+ struct irq_domain *domain;
+};
+
+int devm_i8255_regmap_register(struct device *dev,
+ const struct i8255_regmap_config *config);
+
+#endif /* _I8255_H_ */
diff --git a/drivers/gpio/gpio-ich.c b/drivers/gpio/gpio-ich.c
new file mode 100644
index 0000000000..0be9285efe
--- /dev/null
+++ b/drivers/gpio/gpio-ich.c
@@ -0,0 +1,484 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Intel ICH6-10, Series 5 and 6, Atom C2000 (Avoton/Rangeley) GPIO driver
+ *
+ * Copyright (C) 2010 Extreme Engineering Solutions.
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/ioport.h>
+#include <linux/mfd/lpc_ich.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#define DRV_NAME "gpio_ich"
+
+/*
+ * GPIO register offsets in GPIO I/O space.
+ * Each chunk of 32 GPIOs is manipulated via its own USE_SELx, IO_SELx, and
+ * LVLx registers. Logic in the read/write functions takes a register and
+ * an absolute bit number and determines the proper register offset and bit
+ * number in that register. For example, to read the value of GPIO bit 50
+ * the code would access offset ichx_regs[2(=GPIO_LVL)][1(=50/32)],
+ * bit 18 (50%32).
+ */
+enum GPIO_REG {
+ GPIO_USE_SEL = 0,
+ GPIO_IO_SEL,
+ GPIO_LVL,
+ GPO_BLINK
+};
+
+static const u8 ichx_regs[4][3] = {
+ {0x00, 0x30, 0x40}, /* USE_SEL[1-3] offsets */
+ {0x04, 0x34, 0x44}, /* IO_SEL[1-3] offsets */
+ {0x0c, 0x38, 0x48}, /* LVL[1-3] offsets */
+ {0x18, 0x18, 0x18}, /* BLINK offset */
+};
+
+static const u8 ichx_reglen[3] = {
+ 0x30, 0x10, 0x10,
+};
+
+static const u8 avoton_regs[4][3] = {
+ {0x00, 0x80, 0x00},
+ {0x04, 0x84, 0x00},
+ {0x08, 0x88, 0x00},
+};
+
+static const u8 avoton_reglen[3] = {
+ 0x10, 0x10, 0x00,
+};
+
+#define ICHX_WRITE(val, reg, base_res) outl(val, (reg) + (base_res)->start)
+#define ICHX_READ(reg, base_res) inl((reg) + (base_res)->start)
+
+struct ichx_desc {
+ /* Max GPIO pins the chipset can have */
+ uint ngpio;
+
+ /* chipset registers */
+ const u8 (*regs)[3];
+ const u8 *reglen;
+
+ /* GPO_BLINK is available on this chipset */
+ bool have_blink;
+
+ /* Whether the chipset has GPIO in GPE0_STS in the PM IO region */
+ bool uses_gpe0;
+
+ /* USE_SEL is bogus on some chipsets, eg 3100 */
+ u32 use_sel_ignore[3];
+
+ /* Some chipsets have quirks, let these use their own request/get */
+ int (*request)(struct gpio_chip *chip, unsigned int offset);
+ int (*get)(struct gpio_chip *chip, unsigned int offset);
+
+ /*
+ * Some chipsets don't let reading output values on GPIO_LVL register
+ * this option allows driver caching written output values
+ */
+ bool use_outlvl_cache;
+};
+
+static struct {
+ spinlock_t lock;
+ struct device *dev;
+ struct gpio_chip chip;
+ struct resource *gpio_base; /* GPIO IO base */
+ struct resource *pm_base; /* Power Management IO base */
+ struct ichx_desc *desc; /* Pointer to chipset-specific description */
+ u32 orig_gpio_ctrl; /* Orig CTRL value, used to restore on exit */
+ u8 use_gpio; /* Which GPIO groups are usable */
+ int outlvl_cache[3]; /* cached output values */
+} ichx_priv;
+
+static int modparam_gpiobase = -1; /* dynamic */
+module_param_named(gpiobase, modparam_gpiobase, int, 0444);
+MODULE_PARM_DESC(gpiobase, "The GPIO number base. -1 means dynamic, which is the default.");
+
+static int ichx_write_bit(int reg, unsigned int nr, int val, int verify)
+{
+ unsigned long flags;
+ u32 data, tmp;
+ int reg_nr = nr / 32;
+ int bit = nr & 0x1f;
+
+ spin_lock_irqsave(&ichx_priv.lock, flags);
+
+ if (reg == GPIO_LVL && ichx_priv.desc->use_outlvl_cache)
+ data = ichx_priv.outlvl_cache[reg_nr];
+ else
+ data = ICHX_READ(ichx_priv.desc->regs[reg][reg_nr],
+ ichx_priv.gpio_base);
+
+ if (val)
+ data |= BIT(bit);
+ else
+ data &= ~BIT(bit);
+ ICHX_WRITE(data, ichx_priv.desc->regs[reg][reg_nr],
+ ichx_priv.gpio_base);
+ if (reg == GPIO_LVL && ichx_priv.desc->use_outlvl_cache)
+ ichx_priv.outlvl_cache[reg_nr] = data;
+
+ tmp = ICHX_READ(ichx_priv.desc->regs[reg][reg_nr],
+ ichx_priv.gpio_base);
+
+ spin_unlock_irqrestore(&ichx_priv.lock, flags);
+
+ return (verify && data != tmp) ? -EPERM : 0;
+}
+
+static int ichx_read_bit(int reg, unsigned int nr)
+{
+ unsigned long flags;
+ u32 data;
+ int reg_nr = nr / 32;
+ int bit = nr & 0x1f;
+
+ spin_lock_irqsave(&ichx_priv.lock, flags);
+
+ data = ICHX_READ(ichx_priv.desc->regs[reg][reg_nr],
+ ichx_priv.gpio_base);
+
+ if (reg == GPIO_LVL && ichx_priv.desc->use_outlvl_cache)
+ data = ichx_priv.outlvl_cache[reg_nr] | data;
+
+ spin_unlock_irqrestore(&ichx_priv.lock, flags);
+
+ return !!(data & BIT(bit));
+}
+
+static bool ichx_gpio_check_available(struct gpio_chip *gpio, unsigned int nr)
+{
+ return !!(ichx_priv.use_gpio & BIT(nr / 32));
+}
+
+static int ichx_gpio_get_direction(struct gpio_chip *gpio, unsigned int nr)
+{
+ if (ichx_read_bit(GPIO_IO_SEL, nr))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int ichx_gpio_direction_input(struct gpio_chip *gpio, unsigned int nr)
+{
+ /*
+ * Try setting pin as an input and verify it worked since many pins
+ * are output-only.
+ */
+ return ichx_write_bit(GPIO_IO_SEL, nr, 1, 1);
+}
+
+static int ichx_gpio_direction_output(struct gpio_chip *gpio, unsigned int nr,
+ int val)
+{
+ /* Disable blink hardware which is available for GPIOs from 0 to 31. */
+ if (nr < 32 && ichx_priv.desc->have_blink)
+ ichx_write_bit(GPO_BLINK, nr, 0, 0);
+
+ /* Set GPIO output value. */
+ ichx_write_bit(GPIO_LVL, nr, val, 0);
+
+ /*
+ * Try setting pin as an output and verify it worked since many pins
+ * are input-only.
+ */
+ return ichx_write_bit(GPIO_IO_SEL, nr, 0, 1);
+}
+
+static int ichx_gpio_get(struct gpio_chip *chip, unsigned int nr)
+{
+ return ichx_read_bit(GPIO_LVL, nr);
+}
+
+static int ich6_gpio_get(struct gpio_chip *chip, unsigned int nr)
+{
+ unsigned long flags;
+ u32 data;
+
+ /*
+ * GPI 0 - 15 need to be read from the power management registers on
+ * a ICH6/3100 bridge.
+ */
+ if (nr < 16) {
+ if (!ichx_priv.pm_base)
+ return -ENXIO;
+
+ spin_lock_irqsave(&ichx_priv.lock, flags);
+
+ /* GPI 0 - 15 are latched, write 1 to clear*/
+ ICHX_WRITE(BIT(16 + nr), 0, ichx_priv.pm_base);
+ data = ICHX_READ(0, ichx_priv.pm_base);
+
+ spin_unlock_irqrestore(&ichx_priv.lock, flags);
+
+ return !!((data >> 16) & BIT(nr));
+ } else {
+ return ichx_gpio_get(chip, nr);
+ }
+}
+
+static int ichx_gpio_request(struct gpio_chip *chip, unsigned int nr)
+{
+ if (!ichx_gpio_check_available(chip, nr))
+ return -ENXIO;
+
+ /*
+ * Note we assume the BIOS properly set a bridge's USE value. Some
+ * chips (eg Intel 3100) have bogus USE values though, so first see if
+ * the chipset's USE value can be trusted for this specific bit.
+ * If it can't be trusted, assume that the pin can be used as a GPIO.
+ */
+ if (ichx_priv.desc->use_sel_ignore[nr / 32] & BIT(nr & 0x1f))
+ return 0;
+
+ return ichx_read_bit(GPIO_USE_SEL, nr) ? 0 : -ENODEV;
+}
+
+static int ich6_gpio_request(struct gpio_chip *chip, unsigned int nr)
+{
+ /*
+ * Fixups for bits 16 and 17 are necessary on the Intel ICH6/3100
+ * bridge as they are controlled by USE register bits 0 and 1. See
+ * "Table 704 GPIO_USE_SEL1 register" in the i3100 datasheet for
+ * additional info.
+ */
+ if (nr == 16 || nr == 17)
+ nr -= 16;
+
+ return ichx_gpio_request(chip, nr);
+}
+
+static void ichx_gpio_set(struct gpio_chip *chip, unsigned int nr, int val)
+{
+ ichx_write_bit(GPIO_LVL, nr, val, 0);
+}
+
+static void ichx_gpiolib_setup(struct gpio_chip *chip)
+{
+ chip->owner = THIS_MODULE;
+ chip->label = DRV_NAME;
+ chip->parent = ichx_priv.dev;
+
+ /* Allow chip-specific overrides of request()/get() */
+ chip->request = ichx_priv.desc->request ?
+ ichx_priv.desc->request : ichx_gpio_request;
+ chip->get = ichx_priv.desc->get ?
+ ichx_priv.desc->get : ichx_gpio_get;
+
+ chip->set = ichx_gpio_set;
+ chip->get_direction = ichx_gpio_get_direction;
+ chip->direction_input = ichx_gpio_direction_input;
+ chip->direction_output = ichx_gpio_direction_output;
+ chip->base = modparam_gpiobase;
+ chip->ngpio = ichx_priv.desc->ngpio;
+ chip->can_sleep = false;
+ chip->dbg_show = NULL;
+}
+
+/* ICH6-based, 631xesb-based */
+static struct ichx_desc ich6_desc = {
+ /* Bridges using the ICH6 controller need fixups for GPIO 0 - 17 */
+ .request = ich6_gpio_request,
+ .get = ich6_gpio_get,
+
+ /* GPIO 0-15 are read in the GPE0_STS PM register */
+ .uses_gpe0 = true,
+
+ .ngpio = 50,
+ .have_blink = true,
+ .regs = ichx_regs,
+ .reglen = ichx_reglen,
+};
+
+/* Intel 3100 */
+static struct ichx_desc i3100_desc = {
+ /*
+ * Bits 16,17, 20 of USE_SEL and bit 16 of USE_SEL2 always read 0 on
+ * the Intel 3100. See "Table 712. GPIO Summary Table" of 3100
+ * Datasheet for more info.
+ */
+ .use_sel_ignore = {0x00130000, 0x00010000, 0x0},
+
+ /* The 3100 needs fixups for GPIO 0 - 17 */
+ .request = ich6_gpio_request,
+ .get = ich6_gpio_get,
+
+ /* GPIO 0-15 are read in the GPE0_STS PM register */
+ .uses_gpe0 = true,
+
+ .ngpio = 50,
+ .regs = ichx_regs,
+ .reglen = ichx_reglen,
+};
+
+/* ICH7 and ICH8-based */
+static struct ichx_desc ich7_desc = {
+ .ngpio = 50,
+ .have_blink = true,
+ .regs = ichx_regs,
+ .reglen = ichx_reglen,
+};
+
+/* ICH9-based */
+static struct ichx_desc ich9_desc = {
+ .ngpio = 61,
+ .have_blink = true,
+ .regs = ichx_regs,
+ .reglen = ichx_reglen,
+};
+
+/* ICH10-based - Consumer/corporate versions have different amount of GPIO */
+static struct ichx_desc ich10_cons_desc = {
+ .ngpio = 61,
+ .have_blink = true,
+ .regs = ichx_regs,
+ .reglen = ichx_reglen,
+};
+static struct ichx_desc ich10_corp_desc = {
+ .ngpio = 72,
+ .have_blink = true,
+ .regs = ichx_regs,
+ .reglen = ichx_reglen,
+};
+
+/* Intel 5 series, 6 series, 3400 series, and C200 series */
+static struct ichx_desc intel5_desc = {
+ .ngpio = 76,
+ .regs = ichx_regs,
+ .reglen = ichx_reglen,
+};
+
+/* Avoton */
+static struct ichx_desc avoton_desc = {
+ /* Avoton has only 59 GPIOs, but we assume the first set of register
+ * (Core) has 32 instead of 31 to keep gpio-ich compliance
+ */
+ .ngpio = 60,
+ .regs = avoton_regs,
+ .reglen = avoton_reglen,
+ .use_outlvl_cache = true,
+};
+
+static int ichx_gpio_request_regions(struct device *dev,
+ struct resource *res_base, const char *name, u8 use_gpio)
+{
+ int i;
+
+ if (!res_base || !res_base->start || !res_base->end)
+ return -ENODEV;
+
+ for (i = 0; i < ARRAY_SIZE(ichx_priv.desc->regs[0]); i++) {
+ if (!(use_gpio & BIT(i)))
+ continue;
+ if (!devm_request_region(dev,
+ res_base->start + ichx_priv.desc->regs[0][i],
+ ichx_priv.desc->reglen[i], name))
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static int ichx_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct lpc_ich_info *ich_info = dev_get_platdata(dev);
+ struct resource *res_base, *res_pm;
+ int err;
+
+ if (!ich_info)
+ return -ENODEV;
+
+ switch (ich_info->gpio_version) {
+ case ICH_I3100_GPIO:
+ ichx_priv.desc = &i3100_desc;
+ break;
+ case ICH_V5_GPIO:
+ ichx_priv.desc = &intel5_desc;
+ break;
+ case ICH_V6_GPIO:
+ ichx_priv.desc = &ich6_desc;
+ break;
+ case ICH_V7_GPIO:
+ ichx_priv.desc = &ich7_desc;
+ break;
+ case ICH_V9_GPIO:
+ ichx_priv.desc = &ich9_desc;
+ break;
+ case ICH_V10CORP_GPIO:
+ ichx_priv.desc = &ich10_corp_desc;
+ break;
+ case ICH_V10CONS_GPIO:
+ ichx_priv.desc = &ich10_cons_desc;
+ break;
+ case AVOTON_GPIO:
+ ichx_priv.desc = &avoton_desc;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ ichx_priv.dev = dev;
+ spin_lock_init(&ichx_priv.lock);
+
+ res_base = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPIO);
+ err = ichx_gpio_request_regions(dev, res_base, pdev->name,
+ ich_info->use_gpio);
+ if (err)
+ return err;
+
+ ichx_priv.gpio_base = res_base;
+ ichx_priv.use_gpio = ich_info->use_gpio;
+
+ /*
+ * If necessary, determine the I/O address of ACPI/power management
+ * registers which are needed to read the GPE0 register for GPI pins
+ * 0 - 15 on some chipsets.
+ */
+ if (!ichx_priv.desc->uses_gpe0)
+ goto init;
+
+ res_pm = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPE0);
+ if (!res_pm) {
+ dev_warn(dev, "ACPI BAR is unavailable, GPI 0 - 15 unavailable\n");
+ goto init;
+ }
+
+ if (!devm_request_region(dev, res_pm->start, resource_size(res_pm),
+ pdev->name)) {
+ dev_warn(dev, "ACPI BAR is busy, GPI 0 - 15 unavailable\n");
+ goto init;
+ }
+
+ ichx_priv.pm_base = res_pm;
+
+init:
+ ichx_gpiolib_setup(&ichx_priv.chip);
+ err = devm_gpiochip_add_data(dev, &ichx_priv.chip, NULL);
+ if (err) {
+ dev_err(dev, "Failed to register GPIOs\n");
+ return err;
+ }
+
+ dev_info(dev, "GPIO from %d to %d\n", ichx_priv.chip.base,
+ ichx_priv.chip.base + ichx_priv.chip.ngpio - 1);
+
+ return 0;
+}
+
+static struct platform_driver ichx_gpio_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+ .probe = ichx_gpio_probe,
+};
+
+module_platform_driver(ichx_gpio_driver);
+
+MODULE_AUTHOR("Peter Tyser <ptyser@xes-inc.com>");
+MODULE_DESCRIPTION("GPIO interface for Intel ICH series");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:"DRV_NAME);
diff --git a/drivers/gpio/gpio-idio-16.c b/drivers/gpio/gpio-idio-16.c
new file mode 100644
index 0000000000..53b1eb876a
--- /dev/null
+++ b/drivers/gpio/gpio-idio-16.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO library for the ACCES IDIO-16 family
+ * Copyright (C) 2022 William Breathitt Gray
+ */
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/gpio/regmap.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include "gpio-idio-16.h"
+
+#define DEFAULT_SYMBOL_NAMESPACE GPIO_IDIO_16
+
+#define IDIO_16_DAT_BASE 0x0
+#define IDIO_16_OUT_BASE IDIO_16_DAT_BASE
+#define IDIO_16_IN_BASE (IDIO_16_DAT_BASE + 1)
+#define IDIO_16_CLEAR_INTERRUPT 0x1
+#define IDIO_16_ENABLE_IRQ 0x2
+#define IDIO_16_DEACTIVATE_INPUT_FILTERS 0x3
+#define IDIO_16_DISABLE_IRQ IDIO_16_ENABLE_IRQ
+#define IDIO_16_INTERRUPT_STATUS 0x6
+
+#define IDIO_16_NGPIO 32
+#define IDIO_16_NGPIO_PER_REG 8
+#define IDIO_16_REG_STRIDE 4
+
+struct idio_16_data {
+ struct regmap *map;
+ unsigned int irq_mask;
+};
+
+static int idio_16_handle_mask_sync(const int index, const unsigned int mask_buf_def,
+ const unsigned int mask_buf, void *const irq_drv_data)
+{
+ struct idio_16_data *const data = irq_drv_data;
+ const unsigned int prev_mask = data->irq_mask;
+ int err;
+ unsigned int val;
+
+ /* exit early if no change since the previous mask */
+ if (mask_buf == prev_mask)
+ return 0;
+
+ /* remember the current mask for the next mask sync */
+ data->irq_mask = mask_buf;
+
+ /* if all previously masked, enable interrupts when unmasking */
+ if (prev_mask == mask_buf_def) {
+ err = regmap_write(data->map, IDIO_16_CLEAR_INTERRUPT, 0x00);
+ if (err)
+ return err;
+ return regmap_read(data->map, IDIO_16_ENABLE_IRQ, &val);
+ }
+
+ /* if all are currently masked, disable interrupts */
+ if (mask_buf == mask_buf_def)
+ return regmap_write(data->map, IDIO_16_DISABLE_IRQ, 0x00);
+
+ 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)
+{
+ unsigned int stride;
+
+ /* Input lines start at GPIO 16 */
+ if (offset < 16) {
+ stride = offset / IDIO_16_NGPIO_PER_REG;
+ *reg = IDIO_16_OUT_BASE + stride * IDIO_16_REG_STRIDE;
+ } else {
+ stride = (offset - 16) / IDIO_16_NGPIO_PER_REG;
+ *reg = IDIO_16_IN_BASE + stride * IDIO_16_REG_STRIDE;
+ }
+
+ *mask = BIT(offset % IDIO_16_NGPIO_PER_REG);
+
+ return 0;
+}
+
+static const char *idio_16_names[IDIO_16_NGPIO] = {
+ "OUT0", "OUT1", "OUT2", "OUT3", "OUT4", "OUT5", "OUT6", "OUT7",
+ "OUT8", "OUT9", "OUT10", "OUT11", "OUT12", "OUT13", "OUT14", "OUT15",
+ "IIN0", "IIN1", "IIN2", "IIN3", "IIN4", "IIN5", "IIN6", "IIN7",
+ "IIN8", "IIN9", "IIN10", "IIN11", "IIN12", "IIN13", "IIN14", "IIN15",
+};
+
+/**
+ * devm_idio_16_regmap_register - Register an IDIO-16 GPIO device
+ * @dev: device that is registering this IDIO-16 GPIO device
+ * @config: configuration for idio_16_regmap_config
+ *
+ * Registers an IDIO-16 GPIO device. Returns 0 on success and negative error number on failure.
+ */
+int devm_idio_16_regmap_register(struct device *const dev,
+ const struct idio_16_regmap_config *const config)
+{
+ struct gpio_regmap_config gpio_config = {};
+ int err;
+ struct idio_16_data *data;
+ struct regmap_irq_chip *chip;
+ struct regmap_irq_chip_data *chip_data;
+
+ if (!config->parent)
+ return -EINVAL;
+
+ if (!config->map)
+ return -EINVAL;
+
+ if (!config->regmap_irqs)
+ return -EINVAL;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ data->map = config->map;
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->name = dev_name(dev);
+ chip->status_base = IDIO_16_INTERRUPT_STATUS;
+ chip->mask_base = IDIO_16_ENABLE_IRQ;
+ chip->ack_base = IDIO_16_CLEAR_INTERRUPT;
+ chip->no_status = config->no_status;
+ chip->num_regs = 1;
+ chip->irqs = config->regmap_irqs;
+ chip->num_irqs = config->num_regmap_irqs;
+ chip->handle_mask_sync = idio_16_handle_mask_sync;
+ chip->irq_drv_data = data;
+
+ /* Disable IRQ to prevent spurious interrupts before we're ready */
+ err = regmap_write(data->map, IDIO_16_DISABLE_IRQ, 0x00);
+ if (err)
+ return err;
+
+ err = devm_regmap_add_irq_chip(dev, data->map, config->irq, 0, 0, chip, &chip_data);
+ if (err)
+ return dev_err_probe(dev, err, "IRQ registration failed\n");
+
+ if (config->filters) {
+ /* Deactivate input filters */
+ err = regmap_write(data->map, IDIO_16_DEACTIVATE_INPUT_FILTERS, 0x00);
+ if (err)
+ return err;
+ }
+
+ gpio_config.parent = config->parent;
+ gpio_config.regmap = data->map;
+ gpio_config.ngpio = IDIO_16_NGPIO;
+ gpio_config.names = idio_16_names;
+ gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(IDIO_16_DAT_BASE);
+ gpio_config.reg_set_base = GPIO_REGMAP_ADDR(IDIO_16_DAT_BASE);
+ gpio_config.ngpio_per_reg = IDIO_16_NGPIO_PER_REG;
+ gpio_config.reg_stride = IDIO_16_REG_STRIDE;
+ gpio_config.irq_domain = regmap_irq_get_domain(chip_data);
+ gpio_config.reg_mask_xlate = idio_16_reg_mask_xlate;
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
+}
+EXPORT_SYMBOL_GPL(devm_idio_16_regmap_register);
+
+MODULE_AUTHOR("William Breathitt Gray");
+MODULE_DESCRIPTION("ACCES IDIO-16 GPIO Library");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-idio-16.h b/drivers/gpio/gpio-idio-16.h
new file mode 100644
index 0000000000..93b08ad730
--- /dev/null
+++ b/drivers/gpio/gpio-idio-16.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright 2022 William Breathitt Gray */
+#ifndef _IDIO_16_H_
+#define _IDIO_16_H_
+
+struct device;
+struct regmap;
+struct regmap_irq;
+
+/**
+ * struct idio_16_regmap_config - Configuration for the IDIO-16 register map
+ * @parent: parent device
+ * @map: regmap for the IDIO-16 device
+ * @regmap_irqs: descriptors for individual IRQs
+ * @num_regmap_irqs: number of IRQ descriptors
+ * @irq: IRQ number for the IDIO-16 device
+ * @no_status: device has no status register
+ * @filters: device has input filters
+ */
+struct idio_16_regmap_config {
+ struct device *parent;
+ struct regmap *map;
+ const struct regmap_irq *regmap_irqs;
+ int num_regmap_irqs;
+ unsigned int irq;
+ bool no_status;
+ bool filters;
+};
+
+int devm_idio_16_regmap_register(struct device *dev, const struct idio_16_regmap_config *config);
+
+#endif /* _IDIO_16_H_ */
diff --git a/drivers/gpio/gpio-idt3243x.c b/drivers/gpio/gpio-idt3243x.c
new file mode 100644
index 0000000000..00f547d262
--- /dev/null
+++ b/drivers/gpio/gpio-idt3243x.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Driver for IDT/Renesas 79RC3243x Interrupt Controller */
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define IDT_PIC_IRQ_PEND 0x00
+#define IDT_PIC_IRQ_MASK 0x08
+
+#define IDT_GPIO_DIR 0x00
+#define IDT_GPIO_DATA 0x04
+#define IDT_GPIO_ILEVEL 0x08
+#define IDT_GPIO_ISTAT 0x0C
+
+struct idt_gpio_ctrl {
+ struct gpio_chip gc;
+ void __iomem *pic;
+ void __iomem *gpio;
+ u32 mask_cache;
+};
+
+static void idt_gpio_dispatch(struct irq_desc *desc)
+{
+ struct gpio_chip *gc = irq_desc_get_handler_data(desc);
+ struct idt_gpio_ctrl *ctrl = gpiochip_get_data(gc);
+ struct irq_chip *host_chip = irq_desc_get_chip(desc);
+ unsigned int bit, virq;
+ unsigned long pending;
+
+ chained_irq_enter(host_chip, desc);
+
+ pending = readl(ctrl->pic + IDT_PIC_IRQ_PEND);
+ pending &= ~ctrl->mask_cache;
+ for_each_set_bit(bit, &pending, gc->ngpio) {
+ virq = irq_linear_revmap(gc->irq.domain, bit);
+ if (virq)
+ generic_handle_irq(virq);
+ }
+
+ chained_irq_exit(host_chip, desc);
+}
+
+static int idt_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct idt_gpio_ctrl *ctrl = gpiochip_get_data(gc);
+ unsigned int sense = flow_type & IRQ_TYPE_SENSE_MASK;
+ unsigned long flags;
+ u32 ilevel;
+
+ /* hardware only supports level triggered */
+ if (sense == IRQ_TYPE_NONE || (sense & IRQ_TYPE_EDGE_BOTH))
+ return -EINVAL;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+ ilevel = readl(ctrl->gpio + IDT_GPIO_ILEVEL);
+ if (sense & IRQ_TYPE_LEVEL_HIGH)
+ ilevel |= BIT(d->hwirq);
+ else if (sense & IRQ_TYPE_LEVEL_LOW)
+ ilevel &= ~BIT(d->hwirq);
+
+ writel(ilevel, ctrl->gpio + IDT_GPIO_ILEVEL);
+ irq_set_handler_locked(d, handle_level_irq);
+
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+ return 0;
+}
+
+static void idt_gpio_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct idt_gpio_ctrl *ctrl = gpiochip_get_data(gc);
+
+ writel(~BIT(d->hwirq), ctrl->gpio + IDT_GPIO_ISTAT);
+}
+
+static void idt_gpio_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct idt_gpio_ctrl *ctrl = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+ ctrl->mask_cache |= BIT(d->hwirq);
+ writel(ctrl->mask_cache, ctrl->pic + IDT_PIC_IRQ_MASK);
+
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+ gpiochip_disable_irq(gc, irqd_to_hwirq(d));
+}
+
+static void idt_gpio_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct idt_gpio_ctrl *ctrl = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ gpiochip_enable_irq(gc, irqd_to_hwirq(d));
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+ ctrl->mask_cache &= ~BIT(d->hwirq);
+ writel(ctrl->mask_cache, ctrl->pic + IDT_PIC_IRQ_MASK);
+
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
+static int idt_gpio_irq_init_hw(struct gpio_chip *gc)
+{
+ struct idt_gpio_ctrl *ctrl = gpiochip_get_data(gc);
+
+ /* Mask interrupts. */
+ ctrl->mask_cache = 0xffffffff;
+ writel(ctrl->mask_cache, ctrl->pic + IDT_PIC_IRQ_MASK);
+
+ return 0;
+}
+
+static const struct irq_chip idt_gpio_irqchip = {
+ .name = "IDTGPIO",
+ .irq_mask = idt_gpio_mask,
+ .irq_ack = idt_gpio_ack,
+ .irq_unmask = idt_gpio_unmask,
+ .irq_set_type = idt_gpio_irq_set_type,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int idt_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct gpio_irq_chip *girq;
+ struct idt_gpio_ctrl *ctrl;
+ int parent_irq;
+ int ngpios;
+ int ret;
+
+
+ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ ctrl->gpio = devm_platform_ioremap_resource_byname(pdev, "gpio");
+ if (IS_ERR(ctrl->gpio))
+ return PTR_ERR(ctrl->gpio);
+
+ ctrl->gc.parent = dev;
+
+ ret = bgpio_init(&ctrl->gc, &pdev->dev, 4, ctrl->gpio + IDT_GPIO_DATA,
+ NULL, NULL, ctrl->gpio + IDT_GPIO_DIR, NULL, 0);
+ if (ret) {
+ dev_err(dev, "bgpio_init failed\n");
+ return ret;
+ }
+
+ ret = device_property_read_u32(dev, "ngpios", &ngpios);
+ if (!ret)
+ ctrl->gc.ngpio = ngpios;
+
+ if (device_property_read_bool(dev, "interrupt-controller")) {
+ ctrl->pic = devm_platform_ioremap_resource_byname(pdev, "pic");
+ if (IS_ERR(ctrl->pic))
+ return PTR_ERR(ctrl->pic);
+
+ parent_irq = platform_get_irq(pdev, 0);
+ if (parent_irq < 0)
+ return parent_irq;
+
+ girq = &ctrl->gc.irq;
+ gpio_irq_chip_set_chip(girq, &idt_gpio_irqchip);
+ girq->init_hw = idt_gpio_irq_init_hw;
+ girq->parent_handler = idt_gpio_dispatch;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(dev, girq->num_parents,
+ sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+
+ girq->parents[0] = parent_irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+ }
+
+ return devm_gpiochip_add_data(&pdev->dev, &ctrl->gc, ctrl);
+}
+
+static const struct of_device_id idt_gpio_of_match[] = {
+ { .compatible = "idt,32434-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, idt_gpio_of_match);
+
+static struct platform_driver idt_gpio_driver = {
+ .probe = idt_gpio_probe,
+ .driver = {
+ .name = "idt3243x-gpio",
+ .of_match_table = idt_gpio_of_match,
+ },
+};
+module_platform_driver(idt_gpio_driver);
+
+MODULE_DESCRIPTION("IDT 79RC3243x GPIO/PIC Driver");
+MODULE_AUTHOR("Thomas Bogendoerfer <tsbogend@alpha.franken.de>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-imx-scu.c b/drivers/gpio/gpio-imx-scu.c
new file mode 100644
index 0000000000..13baf465ae
--- /dev/null
+++ b/drivers/gpio/gpio-imx-scu.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2021~2022 NXP
+ *
+ * The driver exports a standard gpiochip interface
+ * to control the PIN resources on SCU domain.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/firmware/imx/svc/rm.h>
+#include <dt-bindings/firmware/imx/rsrc.h>
+
+struct scu_gpio_priv {
+ struct gpio_chip chip;
+ struct mutex lock;
+ struct device *dev;
+ struct imx_sc_ipc *handle;
+};
+
+static unsigned int scu_rsrc_arr[] = {
+ IMX_SC_R_BOARD_R0,
+ IMX_SC_R_BOARD_R1,
+ IMX_SC_R_BOARD_R2,
+ IMX_SC_R_BOARD_R3,
+ IMX_SC_R_BOARD_R4,
+ IMX_SC_R_BOARD_R5,
+ IMX_SC_R_BOARD_R6,
+ IMX_SC_R_BOARD_R7,
+};
+
+static int imx_scu_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct scu_gpio_priv *priv = gpiochip_get_data(chip);
+ int level;
+ int err;
+
+ if (offset >= chip->ngpio)
+ return -EINVAL;
+
+ mutex_lock(&priv->lock);
+
+ /* to read PIN state via scu api */
+ err = imx_sc_misc_get_control(priv->handle,
+ scu_rsrc_arr[offset], 0, &level);
+ mutex_unlock(&priv->lock);
+
+ if (err) {
+ dev_err(priv->dev, "SCU get failed: %d\n", err);
+ return err;
+ }
+
+ return level;
+}
+
+static void imx_scu_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct scu_gpio_priv *priv = gpiochip_get_data(chip);
+ int err;
+
+ if (offset >= chip->ngpio)
+ return;
+
+ mutex_lock(&priv->lock);
+
+ /* to set PIN output level via scu api */
+ err = imx_sc_misc_set_control(priv->handle,
+ scu_rsrc_arr[offset], 0, value);
+ mutex_unlock(&priv->lock);
+
+ if (err)
+ dev_err(priv->dev, "SCU set (%d) failed: %d\n",
+ scu_rsrc_arr[offset], err);
+}
+
+static int imx_scu_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ if (offset >= chip->ngpio)
+ return -EINVAL;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int imx_scu_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct scu_gpio_priv *priv;
+ struct gpio_chip *gc;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ ret = imx_scu_get_handle(&priv->handle);
+ if (ret)
+ return ret;
+
+ priv->dev = dev;
+ mutex_init(&priv->lock);
+
+ gc = &priv->chip;
+ gc->base = -1;
+ gc->parent = dev;
+ gc->ngpio = ARRAY_SIZE(scu_rsrc_arr);
+ gc->label = dev_name(dev);
+ gc->get = imx_scu_gpio_get;
+ gc->set = imx_scu_gpio_set;
+ gc->get_direction = imx_scu_gpio_get_direction;
+
+ platform_set_drvdata(pdev, priv);
+
+ return devm_gpiochip_add_data(dev, gc, priv);
+}
+
+static const struct of_device_id imx_scu_gpio_dt_ids[] = {
+ { .compatible = "fsl,imx8qxp-sc-gpio" },
+ { /* sentinel */ }
+};
+
+static struct platform_driver imx_scu_gpio_driver = {
+ .driver = {
+ .name = "gpio-imx-scu",
+ .of_match_table = imx_scu_gpio_dt_ids,
+ },
+ .probe = imx_scu_gpio_probe,
+};
+
+static int __init _imx_scu_gpio_init(void)
+{
+ return platform_driver_register(&imx_scu_gpio_driver);
+}
+
+subsys_initcall_sync(_imx_scu_gpio_init);
+
+MODULE_AUTHOR("Shenwei Wang <shenwei.wang@nxp.com>");
+MODULE_DESCRIPTION("NXP GPIO over IMX SCU API");
diff --git a/drivers/gpio/gpio-it87.c b/drivers/gpio/gpio-it87.c
new file mode 100644
index 0000000000..f332341fd4
--- /dev/null
+++ b/drivers/gpio/gpio-it87.c
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO interface for IT87xx Super I/O chips
+ *
+ * Author: Diego Elio Pettenò <flameeyes@flameeyes.eu>
+ * Copyright (c) 2017 Google, Inc.
+ *
+ * Based on it87_wdt.c by Oliver Schuster
+ * gpio-it8761e.c by Denis Turischev
+ * gpio-stmpe.c by Rabin Vincent
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/gpio/driver.h>
+
+/* Chip Id numbers */
+#define NO_DEV_ID 0xffff
+#define IT8613_ID 0x8613
+#define IT8620_ID 0x8620
+#define IT8628_ID 0x8628
+#define IT8718_ID 0x8718
+#define IT8728_ID 0x8728
+#define IT8732_ID 0x8732
+#define IT8761_ID 0x8761
+#define IT8772_ID 0x8772
+#define IT8786_ID 0x8786
+
+/* IO Ports */
+#define REG 0x2e
+#define VAL 0x2f
+
+/* Logical device Numbers LDN */
+#define GPIO 0x07
+
+/* Configuration Registers and Functions */
+#define LDNREG 0x07
+#define CHIPID 0x20
+#define CHIPREV 0x22
+
+/**
+ * struct it87_gpio - it87-specific GPIO chip
+ * @chip: the underlying gpio_chip structure
+ * @lock: a lock to avoid races between operations
+ * @io_base: base address for gpio ports
+ * @io_size: size of the port rage starting from io_base.
+ * @output_base: Super I/O register address for Output Enable register
+ * @simple_base: Super I/O 'Simple I/O' Enable register
+ * @simple_size: Super IO 'Simple I/O' Enable register size; this is
+ * required because IT87xx chips might only provide Simple I/O
+ * switches on a subset of lines, whereas the others keep the
+ * same status all time.
+ */
+struct it87_gpio {
+ struct gpio_chip chip;
+ spinlock_t lock;
+ u16 io_base;
+ u16 io_size;
+ u8 output_base;
+ u8 simple_base;
+ u8 simple_size;
+};
+
+static struct it87_gpio it87_gpio_chip = {
+ .lock = __SPIN_LOCK_UNLOCKED(it87_gpio_chip.lock),
+};
+
+/* Superio chip access functions; copied from wdt_it87 */
+
+static inline int superio_enter(void)
+{
+ /*
+ * Try to reserve REG and REG + 1 for exclusive access.
+ */
+ if (!request_muxed_region(REG, 2, KBUILD_MODNAME))
+ return -EBUSY;
+
+ outb(0x87, REG);
+ outb(0x01, REG);
+ outb(0x55, REG);
+ outb(0x55, REG);
+ return 0;
+}
+
+static inline void superio_exit(void)
+{
+ outb(0x02, REG);
+ outb(0x02, VAL);
+ release_region(REG, 2);
+}
+
+static inline void superio_select(int ldn)
+{
+ outb(LDNREG, REG);
+ outb(ldn, VAL);
+}
+
+static inline int superio_inb(int reg)
+{
+ outb(reg, REG);
+ return inb(VAL);
+}
+
+static inline void superio_outb(int val, int reg)
+{
+ outb(reg, REG);
+ outb(val, VAL);
+}
+
+static inline int superio_inw(int reg)
+{
+ int val;
+
+ outb(reg++, REG);
+ val = inb(VAL) << 8;
+ outb(reg, REG);
+ val |= inb(VAL);
+ return val;
+}
+
+static inline void superio_set_mask(int mask, int reg)
+{
+ u8 curr_val = superio_inb(reg);
+ u8 new_val = curr_val | mask;
+
+ if (curr_val != new_val)
+ superio_outb(new_val, reg);
+}
+
+static inline void superio_clear_mask(int mask, int reg)
+{
+ u8 curr_val = superio_inb(reg);
+ u8 new_val = curr_val & ~mask;
+
+ if (curr_val != new_val)
+ superio_outb(new_val, reg);
+}
+
+static int it87_gpio_request(struct gpio_chip *chip, unsigned gpio_num)
+{
+ u8 mask, group;
+ int rc = 0;
+ struct it87_gpio *it87_gpio = gpiochip_get_data(chip);
+
+ mask = 1 << (gpio_num % 8);
+ group = (gpio_num / 8);
+
+ spin_lock(&it87_gpio->lock);
+
+ rc = superio_enter();
+ if (rc)
+ goto exit;
+
+ /* not all the IT87xx chips support Simple I/O and not all of
+ * them allow all the lines to be set/unset to Simple I/O.
+ */
+ if (group < it87_gpio->simple_size)
+ superio_set_mask(mask, group + it87_gpio->simple_base);
+
+ /* clear output enable, setting the pin to input, as all the
+ * newly-exported GPIO interfaces are set to input.
+ */
+ superio_clear_mask(mask, group + it87_gpio->output_base);
+
+ superio_exit();
+
+exit:
+ spin_unlock(&it87_gpio->lock);
+ return rc;
+}
+
+static int it87_gpio_get(struct gpio_chip *chip, unsigned gpio_num)
+{
+ u16 reg;
+ u8 mask;
+ struct it87_gpio *it87_gpio = gpiochip_get_data(chip);
+
+ mask = 1 << (gpio_num % 8);
+ reg = (gpio_num / 8) + it87_gpio->io_base;
+
+ return !!(inb(reg) & mask);
+}
+
+static int it87_gpio_direction_in(struct gpio_chip *chip, unsigned gpio_num)
+{
+ u8 mask, group;
+ int rc = 0;
+ struct it87_gpio *it87_gpio = gpiochip_get_data(chip);
+
+ mask = 1 << (gpio_num % 8);
+ group = (gpio_num / 8);
+
+ spin_lock(&it87_gpio->lock);
+
+ rc = superio_enter();
+ if (rc)
+ goto exit;
+
+ /* clear the output enable bit */
+ superio_clear_mask(mask, group + it87_gpio->output_base);
+
+ superio_exit();
+
+exit:
+ spin_unlock(&it87_gpio->lock);
+ return rc;
+}
+
+static void it87_gpio_set(struct gpio_chip *chip,
+ unsigned gpio_num, int val)
+{
+ u8 mask, curr_vals;
+ u16 reg;
+ struct it87_gpio *it87_gpio = gpiochip_get_data(chip);
+
+ mask = 1 << (gpio_num % 8);
+ reg = (gpio_num / 8) + it87_gpio->io_base;
+
+ curr_vals = inb(reg);
+ if (val)
+ outb(curr_vals | mask, reg);
+ else
+ outb(curr_vals & ~mask, reg);
+}
+
+static int it87_gpio_direction_out(struct gpio_chip *chip,
+ unsigned gpio_num, int val)
+{
+ u8 mask, group;
+ int rc = 0;
+ struct it87_gpio *it87_gpio = gpiochip_get_data(chip);
+
+ mask = 1 << (gpio_num % 8);
+ group = (gpio_num / 8);
+
+ spin_lock(&it87_gpio->lock);
+
+ rc = superio_enter();
+ if (rc)
+ goto exit;
+
+ /* set the output enable bit */
+ superio_set_mask(mask, group + it87_gpio->output_base);
+
+ it87_gpio_set(chip, gpio_num, val);
+
+ superio_exit();
+
+exit:
+ spin_unlock(&it87_gpio->lock);
+ return rc;
+}
+
+static const struct gpio_chip it87_template_chip = {
+ .label = KBUILD_MODNAME,
+ .owner = THIS_MODULE,
+ .request = it87_gpio_request,
+ .get = it87_gpio_get,
+ .direction_input = it87_gpio_direction_in,
+ .set = it87_gpio_set,
+ .direction_output = it87_gpio_direction_out,
+ .base = -1
+};
+
+static int __init it87_gpio_init(void)
+{
+ int rc = 0, i;
+ u16 chip_type;
+ u8 chip_rev, gpio_ba_reg;
+ char *labels, **labels_table;
+
+ struct it87_gpio *it87_gpio = &it87_gpio_chip;
+
+ rc = superio_enter();
+ if (rc)
+ return rc;
+
+ chip_type = superio_inw(CHIPID);
+ chip_rev = superio_inb(CHIPREV) & 0x0f;
+ superio_exit();
+
+ it87_gpio->chip = it87_template_chip;
+
+ switch (chip_type) {
+ case IT8613_ID:
+ gpio_ba_reg = 0x62;
+ it87_gpio->io_size = 8; /* it8613 only needs 6, use 8 for alignment */
+ it87_gpio->output_base = 0xc8;
+ it87_gpio->simple_base = 0xc0;
+ it87_gpio->simple_size = 6;
+ it87_gpio->chip.ngpio = 64; /* has 48, use 64 for convenient calc */
+ break;
+ case IT8620_ID:
+ case IT8628_ID:
+ gpio_ba_reg = 0x62;
+ it87_gpio->io_size = 11;
+ it87_gpio->output_base = 0xc8;
+ it87_gpio->simple_size = 0;
+ it87_gpio->chip.ngpio = 64;
+ break;
+ case IT8718_ID:
+ case IT8728_ID:
+ case IT8732_ID:
+ case IT8772_ID:
+ case IT8786_ID:
+ gpio_ba_reg = 0x62;
+ it87_gpio->io_size = 8;
+ it87_gpio->output_base = 0xc8;
+ it87_gpio->simple_base = 0xc0;
+ it87_gpio->simple_size = 5;
+ it87_gpio->chip.ngpio = 64;
+ break;
+ case IT8761_ID:
+ gpio_ba_reg = 0x60;
+ it87_gpio->io_size = 4;
+ it87_gpio->output_base = 0xf0;
+ it87_gpio->simple_size = 0;
+ it87_gpio->chip.ngpio = 16;
+ break;
+ case NO_DEV_ID:
+ pr_err("no device\n");
+ return -ENODEV;
+ default:
+ pr_err("Unknown Chip found, Chip %04x Revision %x\n",
+ chip_type, chip_rev);
+ return -ENODEV;
+ }
+
+ rc = superio_enter();
+ if (rc)
+ return rc;
+
+ superio_select(GPIO);
+
+ /* fetch GPIO base address */
+ it87_gpio->io_base = superio_inw(gpio_ba_reg);
+
+ superio_exit();
+
+ pr_info("Found Chip IT%04x rev %x. %u GPIO lines starting at %04xh\n",
+ chip_type, chip_rev, it87_gpio->chip.ngpio,
+ it87_gpio->io_base);
+
+ if (!request_region(it87_gpio->io_base, it87_gpio->io_size,
+ KBUILD_MODNAME))
+ return -EBUSY;
+
+ /* Set up aliases for the GPIO connection.
+ *
+ * ITE documentation for recent chips such as the IT8728F
+ * refers to the GPIO lines as GPxy, with a coordinates system
+ * where x is the GPIO group (starting from 1) and y is the
+ * bit within the group.
+ *
+ * By creating these aliases, we make it easier to understand
+ * to which GPIO pin we're referring to.
+ */
+ labels = kcalloc(it87_gpio->chip.ngpio, sizeof("it87_gpXY"),
+ GFP_KERNEL);
+ labels_table = kcalloc(it87_gpio->chip.ngpio, sizeof(const char *),
+ GFP_KERNEL);
+
+ if (!labels || !labels_table) {
+ rc = -ENOMEM;
+ goto labels_free;
+ }
+
+ for (i = 0; i < it87_gpio->chip.ngpio; i++) {
+ char *label = &labels[i * sizeof("it87_gpXY")];
+
+ sprintf(label, "it87_gp%u%u", 1+(i/8), i%8);
+ labels_table[i] = label;
+ }
+
+ it87_gpio->chip.names = (const char *const*)labels_table;
+
+ rc = gpiochip_add_data(&it87_gpio->chip, it87_gpio);
+ if (rc)
+ goto labels_free;
+
+ return 0;
+
+labels_free:
+ kfree(labels_table);
+ kfree(labels);
+ release_region(it87_gpio->io_base, it87_gpio->io_size);
+ return rc;
+}
+
+static void __exit it87_gpio_exit(void)
+{
+ struct it87_gpio *it87_gpio = &it87_gpio_chip;
+
+ gpiochip_remove(&it87_gpio->chip);
+ release_region(it87_gpio->io_base, it87_gpio->io_size);
+ kfree(it87_gpio->chip.names[0]);
+ kfree(it87_gpio->chip.names);
+}
+
+module_init(it87_gpio_init);
+module_exit(it87_gpio_exit);
+
+MODULE_AUTHOR("Diego Elio Pettenò <flameeyes@flameeyes.eu>");
+MODULE_DESCRIPTION("GPIO interface for IT87xx Super I/O chips");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-ixp4xx.c b/drivers/gpio/gpio-ixp4xx.c
new file mode 100644
index 0000000000..dde6cf3a57
--- /dev/null
+++ b/drivers/gpio/gpio-ixp4xx.c
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// IXP4 GPIO driver
+// Copyright (C) 2019 Linus Walleij <linus.walleij@linaro.org>
+//
+// based on previous work and know-how from:
+// Deepak Saxena <dsaxena@plexity.net>
+
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/bitops.h>
+
+#define IXP4XX_REG_GPOUT 0x00
+#define IXP4XX_REG_GPOE 0x04
+#define IXP4XX_REG_GPIN 0x08
+#define IXP4XX_REG_GPIS 0x0C
+#define IXP4XX_REG_GPIT1 0x10
+#define IXP4XX_REG_GPIT2 0x14
+#define IXP4XX_REG_GPCLK 0x18
+#define IXP4XX_REG_GPDBSEL 0x1C
+
+/*
+ * The hardware uses 3 bits to indicate interrupt "style".
+ * we clear and set these three bits accordingly. The lower 24
+ * bits in two registers (GPIT1 and GPIT2) are used to set up
+ * the style for 8 lines each for a total of 16 GPIO lines.
+ */
+#define IXP4XX_GPIO_STYLE_ACTIVE_HIGH 0x0
+#define IXP4XX_GPIO_STYLE_ACTIVE_LOW 0x1
+#define IXP4XX_GPIO_STYLE_RISING_EDGE 0x2
+#define IXP4XX_GPIO_STYLE_FALLING_EDGE 0x3
+#define IXP4XX_GPIO_STYLE_TRANSITIONAL 0x4
+#define IXP4XX_GPIO_STYLE_MASK GENMASK(2, 0)
+#define IXP4XX_GPIO_STYLE_SIZE 3
+
+/**
+ * struct ixp4xx_gpio - IXP4 GPIO state container
+ * @dev: containing device for this instance
+ * @fwnode: the fwnode for this GPIO chip
+ * @gc: gpiochip for this instance
+ * @base: remapped I/O-memory base
+ * @irq_edge: Each bit represents an IRQ: 1: edge-triggered,
+ * 0: level triggered
+ */
+struct ixp4xx_gpio {
+ struct device *dev;
+ struct fwnode_handle *fwnode;
+ struct gpio_chip gc;
+ void __iomem *base;
+ unsigned long long irq_edge;
+};
+
+static void ixp4xx_gpio_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct ixp4xx_gpio *g = gpiochip_get_data(gc);
+
+ __raw_writel(BIT(d->hwirq), g->base + IXP4XX_REG_GPIS);
+}
+
+static void ixp4xx_gpio_mask_irq(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+
+ irq_chip_mask_parent(d);
+ gpiochip_disable_irq(gc, d->hwirq);
+}
+
+static void ixp4xx_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct ixp4xx_gpio *g = gpiochip_get_data(gc);
+
+ /* ACK when unmasking if not edge-triggered */
+ if (!(g->irq_edge & BIT(d->hwirq)))
+ ixp4xx_gpio_irq_ack(d);
+
+ gpiochip_enable_irq(gc, d->hwirq);
+ irq_chip_unmask_parent(d);
+}
+
+static int ixp4xx_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct ixp4xx_gpio *g = gpiochip_get_data(gc);
+ int line = d->hwirq;
+ unsigned long flags;
+ u32 int_style;
+ u32 int_reg;
+ u32 val;
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_BOTH:
+ irq_set_handler_locked(d, handle_edge_irq);
+ int_style = IXP4XX_GPIO_STYLE_TRANSITIONAL;
+ g->irq_edge |= BIT(d->hwirq);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ irq_set_handler_locked(d, handle_edge_irq);
+ int_style = IXP4XX_GPIO_STYLE_RISING_EDGE;
+ g->irq_edge |= BIT(d->hwirq);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ irq_set_handler_locked(d, handle_edge_irq);
+ int_style = IXP4XX_GPIO_STYLE_FALLING_EDGE;
+ g->irq_edge |= BIT(d->hwirq);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ irq_set_handler_locked(d, handle_level_irq);
+ int_style = IXP4XX_GPIO_STYLE_ACTIVE_HIGH;
+ g->irq_edge &= ~BIT(d->hwirq);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ irq_set_handler_locked(d, handle_level_irq);
+ int_style = IXP4XX_GPIO_STYLE_ACTIVE_LOW;
+ g->irq_edge &= ~BIT(d->hwirq);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (line >= 8) {
+ /* pins 8-15 */
+ line -= 8;
+ int_reg = IXP4XX_REG_GPIT2;
+ } else {
+ /* pins 0-7 */
+ int_reg = IXP4XX_REG_GPIT1;
+ }
+
+ raw_spin_lock_irqsave(&g->gc.bgpio_lock, flags);
+
+ /* Clear the style for the appropriate pin */
+ val = __raw_readl(g->base + int_reg);
+ val &= ~(IXP4XX_GPIO_STYLE_MASK << (line * IXP4XX_GPIO_STYLE_SIZE));
+ __raw_writel(val, g->base + int_reg);
+
+ __raw_writel(BIT(line), g->base + IXP4XX_REG_GPIS);
+
+ /* Set the new style */
+ val = __raw_readl(g->base + int_reg);
+ val |= (int_style << (line * IXP4XX_GPIO_STYLE_SIZE));
+ __raw_writel(val, g->base + int_reg);
+
+ /* Force-configure this line as an input */
+ val = __raw_readl(g->base + IXP4XX_REG_GPOE);
+ val |= BIT(d->hwirq);
+ __raw_writel(val, g->base + IXP4XX_REG_GPOE);
+
+ raw_spin_unlock_irqrestore(&g->gc.bgpio_lock, flags);
+
+ /* This parent only accept level high (asserted) */
+ return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
+}
+
+static const struct irq_chip ixp4xx_gpio_irqchip = {
+ .name = "IXP4GPIO",
+ .irq_ack = ixp4xx_gpio_irq_ack,
+ .irq_mask = ixp4xx_gpio_mask_irq,
+ .irq_unmask = ixp4xx_gpio_irq_unmask,
+ .irq_set_type = ixp4xx_gpio_irq_set_type,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int ixp4xx_gpio_child_to_parent_hwirq(struct gpio_chip *gc,
+ unsigned int child,
+ unsigned int child_type,
+ unsigned int *parent,
+ unsigned int *parent_type)
+{
+ /* All these interrupts are level high in the CPU */
+ *parent_type = IRQ_TYPE_LEVEL_HIGH;
+
+ /* GPIO lines 0..12 have dedicated IRQs */
+ if (child == 0) {
+ *parent = 6;
+ return 0;
+ }
+ if (child == 1) {
+ *parent = 7;
+ return 0;
+ }
+ if (child >= 2 && child <= 12) {
+ *parent = child + 17;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int ixp4xx_gpio_probe(struct platform_device *pdev)
+{
+ unsigned long flags;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct irq_domain *parent;
+ struct ixp4xx_gpio *g;
+ struct gpio_irq_chip *girq;
+ struct device_node *irq_parent;
+ int ret;
+
+ g = devm_kzalloc(dev, sizeof(*g), GFP_KERNEL);
+ if (!g)
+ return -ENOMEM;
+ g->dev = dev;
+
+ g->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(g->base))
+ return PTR_ERR(g->base);
+
+ irq_parent = of_irq_find_parent(np);
+ if (!irq_parent) {
+ dev_err(dev, "no IRQ parent node\n");
+ return -ENODEV;
+ }
+ parent = irq_find_host(irq_parent);
+ if (!parent) {
+ dev_err(dev, "no IRQ parent domain\n");
+ return -ENODEV;
+ }
+ g->fwnode = of_node_to_fwnode(np);
+
+ /*
+ * Make sure GPIO 14 and 15 are NOT used as clocks but GPIO on
+ * specific machines.
+ */
+ if (of_machine_is_compatible("dlink,dsm-g600-a") ||
+ of_machine_is_compatible("iom,nas-100d"))
+ __raw_writel(0x0, g->base + IXP4XX_REG_GPCLK);
+
+ /*
+ * This is a very special big-endian ARM issue: when the IXP4xx is
+ * run in big endian mode, all registers in the machine are switched
+ * around to the CPU-native endianness. As you see mostly in the
+ * driver we use __raw_readl()/__raw_writel() to access the registers
+ * in the appropriate order. With the GPIO library we need to specify
+ * byte order explicitly, so this flag needs to be set when compiling
+ * for big endian.
+ */
+#if defined(CONFIG_CPU_BIG_ENDIAN)
+ flags = BGPIOF_BIG_ENDIAN_BYTE_ORDER;
+#else
+ flags = 0;
+#endif
+
+ /* Populate and register gpio chip */
+ ret = bgpio_init(&g->gc, dev, 4,
+ g->base + IXP4XX_REG_GPIN,
+ g->base + IXP4XX_REG_GPOUT,
+ NULL,
+ NULL,
+ g->base + IXP4XX_REG_GPOE,
+ flags);
+ if (ret) {
+ dev_err(dev, "unable to init generic GPIO\n");
+ return ret;
+ }
+ g->gc.ngpio = 16;
+ g->gc.label = "IXP4XX_GPIO_CHIP";
+ /*
+ * TODO: when we have migrated to device tree and all GPIOs
+ * are fetched using phandles, set this to -1 to get rid of
+ * the fixed gpiochip base.
+ */
+ g->gc.base = 0;
+ g->gc.parent = &pdev->dev;
+ g->gc.owner = THIS_MODULE;
+
+ girq = &g->gc.irq;
+ gpio_irq_chip_set_chip(girq, &ixp4xx_gpio_irqchip);
+ girq->fwnode = g->fwnode;
+ girq->parent_domain = parent;
+ girq->child_to_parent_hwirq = ixp4xx_gpio_child_to_parent_hwirq;
+ girq->handler = handle_bad_irq;
+ girq->default_type = IRQ_TYPE_NONE;
+
+ ret = devm_gpiochip_add_data(dev, &g->gc, g);
+ if (ret) {
+ dev_err(dev, "failed to add SoC gpiochip\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, g);
+ dev_info(dev, "IXP4 GPIO registered\n");
+
+ return 0;
+}
+
+static const struct of_device_id ixp4xx_gpio_of_match[] = {
+ {
+ .compatible = "intel,ixp4xx-gpio",
+ },
+ {},
+};
+
+
+static struct platform_driver ixp4xx_gpio_driver = {
+ .driver = {
+ .name = "ixp4xx-gpio",
+ .of_match_table = ixp4xx_gpio_of_match,
+ },
+ .probe = ixp4xx_gpio_probe,
+};
+builtin_platform_driver(ixp4xx_gpio_driver);
diff --git a/drivers/gpio/gpio-janz-ttl.c b/drivers/gpio/gpio-janz-ttl.c
new file mode 100644
index 0000000000..cdf50e4ea1
--- /dev/null
+++ b/drivers/gpio/gpio-janz-ttl.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Janz MODULbus VMOD-TTL GPIO Driver
+ *
+ * Copyright (c) 2010 Ira W. Snyder <iws@ovro.caltech.edu>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/gpio/driver.h>
+#include <linux/slab.h>
+#include <linux/bitops.h>
+
+#include <linux/mfd/janz.h>
+
+#define DRV_NAME "janz-ttl"
+
+#define PORTA_DIRECTION 0x23
+#define PORTB_DIRECTION 0x2B
+#define PORTC_DIRECTION 0x06
+#define PORTA_IOCTL 0x24
+#define PORTB_IOCTL 0x2C
+#define PORTC_IOCTL 0x07
+
+#define MASTER_INT_CTL 0x00
+#define MASTER_CONF_CTL 0x01
+
+#define CONF_PAE BIT(2)
+#define CONF_PBE BIT(7)
+#define CONF_PCE BIT(4)
+
+struct ttl_control_regs {
+ __be16 portc;
+ __be16 portb;
+ __be16 porta;
+ __be16 control;
+};
+
+struct ttl_module {
+ struct gpio_chip gpio;
+
+ /* base address of registers */
+ struct ttl_control_regs __iomem *regs;
+
+ u8 portc_shadow;
+ u8 portb_shadow;
+ u8 porta_shadow;
+
+ spinlock_t lock;
+};
+
+static int ttl_get_value(struct gpio_chip *gpio, unsigned offset)
+{
+ struct ttl_module *mod = dev_get_drvdata(gpio->parent);
+ u8 *shadow;
+ int ret;
+
+ if (offset < 8) {
+ shadow = &mod->porta_shadow;
+ } else if (offset < 16) {
+ shadow = &mod->portb_shadow;
+ offset -= 8;
+ } else {
+ shadow = &mod->portc_shadow;
+ offset -= 16;
+ }
+
+ spin_lock(&mod->lock);
+ ret = *shadow & BIT(offset);
+ spin_unlock(&mod->lock);
+ return !!ret;
+}
+
+static void ttl_set_value(struct gpio_chip *gpio, unsigned offset, int value)
+{
+ struct ttl_module *mod = dev_get_drvdata(gpio->parent);
+ void __iomem *port;
+ u8 *shadow;
+
+ if (offset < 8) {
+ port = &mod->regs->porta;
+ shadow = &mod->porta_shadow;
+ } else if (offset < 16) {
+ port = &mod->regs->portb;
+ shadow = &mod->portb_shadow;
+ offset -= 8;
+ } else {
+ port = &mod->regs->portc;
+ shadow = &mod->portc_shadow;
+ offset -= 16;
+ }
+
+ spin_lock(&mod->lock);
+ if (value)
+ *shadow |= BIT(offset);
+ else
+ *shadow &= ~BIT(offset);
+
+ iowrite16be(*shadow, port);
+ spin_unlock(&mod->lock);
+}
+
+static void ttl_write_reg(struct ttl_module *mod, u8 reg, u16 val)
+{
+ iowrite16be(reg, &mod->regs->control);
+ iowrite16be(val, &mod->regs->control);
+}
+
+static void ttl_setup_device(struct ttl_module *mod)
+{
+ /* reset the device to a known state */
+ iowrite16be(0x0000, &mod->regs->control);
+ iowrite16be(0x0001, &mod->regs->control);
+ iowrite16be(0x0000, &mod->regs->control);
+
+ /* put all ports in open-drain mode */
+ ttl_write_reg(mod, PORTA_IOCTL, 0x00ff);
+ ttl_write_reg(mod, PORTB_IOCTL, 0x00ff);
+ ttl_write_reg(mod, PORTC_IOCTL, 0x000f);
+
+ /* set all ports as outputs */
+ ttl_write_reg(mod, PORTA_DIRECTION, 0x0000);
+ ttl_write_reg(mod, PORTB_DIRECTION, 0x0000);
+ ttl_write_reg(mod, PORTC_DIRECTION, 0x0000);
+
+ /* set all ports to drive zeroes */
+ iowrite16be(0x0000, &mod->regs->porta);
+ iowrite16be(0x0000, &mod->regs->portb);
+ iowrite16be(0x0000, &mod->regs->portc);
+
+ /* enable all ports */
+ ttl_write_reg(mod, MASTER_CONF_CTL, CONF_PAE | CONF_PBE | CONF_PCE);
+}
+
+static int ttl_probe(struct platform_device *pdev)
+{
+ struct janz_platform_data *pdata;
+ struct ttl_module *mod;
+ struct gpio_chip *gpio;
+ int ret;
+
+ pdata = dev_get_platdata(&pdev->dev);
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data\n");
+ return -ENXIO;
+ }
+
+ mod = devm_kzalloc(&pdev->dev, sizeof(*mod), GFP_KERNEL);
+ if (!mod)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, mod);
+ spin_lock_init(&mod->lock);
+
+ /* get access to the MODULbus registers for this module */
+ mod->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mod->regs))
+ return PTR_ERR(mod->regs);
+
+ ttl_setup_device(mod);
+
+ /* Initialize the GPIO data structures */
+ gpio = &mod->gpio;
+ gpio->parent = &pdev->dev;
+ gpio->label = pdev->name;
+ gpio->get = ttl_get_value;
+ gpio->set = ttl_set_value;
+ gpio->owner = THIS_MODULE;
+
+ /* request dynamic allocation */
+ gpio->base = -1;
+ gpio->ngpio = 20;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, gpio, NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to add GPIO chip\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver ttl_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+ .probe = ttl_probe,
+};
+
+module_platform_driver(ttl_driver);
+
+MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>");
+MODULE_DESCRIPTION("Janz MODULbus VMOD-TTL Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:janz-ttl");
diff --git a/drivers/gpio/gpio-kempld.c b/drivers/gpio/gpio-kempld.c
new file mode 100644
index 0000000000..4ea15f08e0
--- /dev/null
+++ b/drivers/gpio/gpio-kempld.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Kontron PLD GPIO driver
+ *
+ * Copyright (c) 2010-2013 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/driver.h>
+#include <linux/mfd/kempld.h>
+
+#define KEMPLD_GPIO_MAX_NUM 16
+#define KEMPLD_GPIO_MASK(x) (BIT((x) % 8))
+#define KEMPLD_GPIO_DIR_NUM(x) (0x40 + (x) / 8)
+#define KEMPLD_GPIO_LVL_NUM(x) (0x42 + (x) / 8)
+#define KEMPLD_GPIO_EVT_LVL_EDGE 0x46
+#define KEMPLD_GPIO_IEN 0x4A
+
+struct kempld_gpio_data {
+ struct gpio_chip chip;
+ struct kempld_device_data *pld;
+};
+
+/*
+ * Set or clear GPIO bit
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+static void kempld_gpio_bitop(struct kempld_device_data *pld,
+ u8 reg, u8 bit, u8 val)
+{
+ u8 status;
+
+ status = kempld_read8(pld, reg);
+ if (val)
+ status |= KEMPLD_GPIO_MASK(bit);
+ else
+ status &= ~KEMPLD_GPIO_MASK(bit);
+ kempld_write8(pld, reg, status);
+}
+
+static int kempld_gpio_get_bit(struct kempld_device_data *pld, u8 reg, u8 bit)
+{
+ u8 status;
+
+ kempld_get_mutex(pld);
+ status = kempld_read8(pld, reg);
+ kempld_release_mutex(pld);
+
+ return !!(status & KEMPLD_GPIO_MASK(bit));
+}
+
+static int kempld_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
+ struct kempld_device_data *pld = gpio->pld;
+
+ return !!kempld_gpio_get_bit(pld, KEMPLD_GPIO_LVL_NUM(offset), offset);
+}
+
+static void kempld_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
+ struct kempld_device_data *pld = gpio->pld;
+
+ kempld_get_mutex(pld);
+ kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL_NUM(offset), offset, value);
+ kempld_release_mutex(pld);
+}
+
+static int kempld_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
+ struct kempld_device_data *pld = gpio->pld;
+
+ kempld_get_mutex(pld);
+ kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR_NUM(offset), offset, 0);
+ kempld_release_mutex(pld);
+
+ return 0;
+}
+
+static int kempld_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
+ struct kempld_device_data *pld = gpio->pld;
+
+ kempld_get_mutex(pld);
+ kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL_NUM(offset), offset, value);
+ kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR_NUM(offset), offset, 1);
+ kempld_release_mutex(pld);
+
+ return 0;
+}
+
+static int kempld_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+ struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
+ struct kempld_device_data *pld = gpio->pld;
+
+ if (kempld_gpio_get_bit(pld, KEMPLD_GPIO_DIR_NUM(offset), offset))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int kempld_gpio_pincount(struct kempld_device_data *pld)
+{
+ u16 evt, evt_back;
+
+ kempld_get_mutex(pld);
+
+ /* Backup event register as it might be already initialized */
+ evt_back = kempld_read16(pld, KEMPLD_GPIO_EVT_LVL_EDGE);
+ /* Clear event register */
+ kempld_write16(pld, KEMPLD_GPIO_EVT_LVL_EDGE, 0x0000);
+ /* Read back event register */
+ evt = kempld_read16(pld, KEMPLD_GPIO_EVT_LVL_EDGE);
+ /* Restore event register */
+ kempld_write16(pld, KEMPLD_GPIO_EVT_LVL_EDGE, evt_back);
+
+ kempld_release_mutex(pld);
+
+ return evt ? __ffs(evt) : 16;
+}
+
+static int kempld_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct kempld_device_data *pld = dev_get_drvdata(dev->parent);
+ struct kempld_platform_data *pdata = dev_get_platdata(pld->dev);
+ struct kempld_gpio_data *gpio;
+ struct gpio_chip *chip;
+ int ret;
+
+ if (pld->info.spec_major < 2) {
+ dev_err(dev,
+ "Driver only supports GPIO devices compatible to PLD spec. rev. 2.0 or higher\n");
+ return -ENODEV;
+ }
+
+ gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->pld = pld;
+
+ platform_set_drvdata(pdev, gpio);
+
+ chip = &gpio->chip;
+ chip->label = "gpio-kempld";
+ chip->owner = THIS_MODULE;
+ chip->parent = dev;
+ chip->can_sleep = true;
+ if (pdata && pdata->gpio_base)
+ chip->base = pdata->gpio_base;
+ else
+ chip->base = -1;
+ chip->direction_input = kempld_gpio_direction_input;
+ chip->direction_output = kempld_gpio_direction_output;
+ chip->get_direction = kempld_gpio_get_direction;
+ chip->get = kempld_gpio_get;
+ chip->set = kempld_gpio_set;
+ chip->ngpio = kempld_gpio_pincount(pld);
+ if (chip->ngpio == 0) {
+ dev_err(dev, "No GPIO pins detected\n");
+ return -ENODEV;
+ }
+
+ ret = devm_gpiochip_add_data(dev, chip, gpio);
+ if (ret) {
+ dev_err(dev, "Could not register GPIO chip\n");
+ return ret;
+ }
+
+ dev_info(dev, "GPIO functionality initialized with %d pins\n",
+ chip->ngpio);
+
+ return 0;
+}
+
+static struct platform_driver kempld_gpio_driver = {
+ .driver = {
+ .name = "kempld-gpio",
+ },
+ .probe = kempld_gpio_probe,
+};
+
+module_platform_driver(kempld_gpio_driver);
+
+MODULE_DESCRIPTION("KEM PLD GPIO Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kempld-gpio");
diff --git a/drivers/gpio/gpio-latch.c b/drivers/gpio/gpio-latch.c
new file mode 100644
index 0000000000..d7c3b20c84
--- /dev/null
+++ b/drivers/gpio/gpio-latch.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO latch driver
+ *
+ * Copyright (C) 2022 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This driver implements a GPIO (or better GPO as there is no input)
+ * multiplexer based on latches like this:
+ *
+ * CLK0 ----------------------. ,--------.
+ * CLK1 -------------------. `--------|> #0 |
+ * | | |
+ * OUT0 ----------------+--|-----------|D0 Q0|-----|<
+ * OUT1 --------------+-|--|-----------|D1 Q1|-----|<
+ * OUT2 ------------+-|-|--|-----------|D2 Q2|-----|<
+ * OUT3 ----------+-|-|-|--|-----------|D3 Q3|-----|<
+ * OUT4 --------+-|-|-|-|--|-----------|D4 Q4|-----|<
+ * OUT5 ------+-|-|-|-|-|--|-----------|D5 Q5|-----|<
+ * OUT6 ----+-|-|-|-|-|-|--|-----------|D6 Q6|-----|<
+ * OUT7 --+-|-|-|-|-|-|-|--|-----------|D7 Q7|-----|<
+ * | | | | | | | | | `--------'
+ * | | | | | | | | |
+ * | | | | | | | | | ,--------.
+ * | | | | | | | | `-----------|> #1 |
+ * | | | | | | | | | |
+ * | | | | | | | `--------------|D0 Q0|-----|<
+ * | | | | | | `----------------|D1 Q1|-----|<
+ * | | | | | `------------------|D2 Q2|-----|<
+ * | | | | `--------------------|D3 Q3|-----|<
+ * | | | `----------------------|D4 Q4|-----|<
+ * | | `------------------------|D5 Q5|-----|<
+ * | `--------------------------|D6 Q6|-----|<
+ * `----------------------------|D7 Q7|-----|<
+ * `--------'
+ *
+ * The above is just an example. The actual number of number of latches and
+ * the number of inputs per latch is derived from the number of GPIOs given
+ * in the corresponding device tree properties.
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+
+#include "gpiolib.h"
+
+struct gpio_latch_priv {
+ struct gpio_chip gc;
+ struct gpio_descs *clk_gpios;
+ struct gpio_descs *latched_gpios;
+ int n_latched_gpios;
+ unsigned int setup_duration_ns;
+ unsigned int clock_duration_ns;
+ unsigned long *shadow;
+ /*
+ * Depending on whether any of the underlying GPIOs may sleep we either
+ * use a mutex or a spinlock to protect our shadow map.
+ */
+ union {
+ struct mutex mutex; /* protects @shadow */
+ spinlock_t spinlock; /* protects @shadow */
+ };
+};
+
+static int gpio_latch_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static void gpio_latch_set_unlocked(struct gpio_latch_priv *priv,
+ void (*set)(struct gpio_desc *desc, int value),
+ unsigned int offset, bool val)
+{
+ int latch = offset / priv->n_latched_gpios;
+ int i;
+
+ assign_bit(offset, priv->shadow, val);
+
+ for (i = 0; i < priv->n_latched_gpios; i++)
+ set(priv->latched_gpios->desc[i],
+ test_bit(latch * priv->n_latched_gpios + i, priv->shadow));
+
+ ndelay(priv->setup_duration_ns);
+ set(priv->clk_gpios->desc[latch], 1);
+ ndelay(priv->clock_duration_ns);
+ set(priv->clk_gpios->desc[latch], 0);
+}
+
+static void gpio_latch_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ struct gpio_latch_priv *priv = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->spinlock, flags);
+
+ gpio_latch_set_unlocked(priv, gpiod_set_value, offset, val);
+
+ spin_unlock_irqrestore(&priv->spinlock, flags);
+}
+
+static void gpio_latch_set_can_sleep(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ struct gpio_latch_priv *priv = gpiochip_get_data(gc);
+
+ mutex_lock(&priv->mutex);
+
+ gpio_latch_set_unlocked(priv, gpiod_set_value_cansleep, offset, val);
+
+ mutex_unlock(&priv->mutex);
+}
+
+static bool gpio_latch_can_sleep(struct gpio_latch_priv *priv, unsigned int n_latches)
+{
+ int i;
+
+ for (i = 0; i < n_latches; i++)
+ if (gpiod_cansleep(priv->clk_gpios->desc[i]))
+ return true;
+
+ for (i = 0; i < priv->n_latched_gpios; i++)
+ if (gpiod_cansleep(priv->latched_gpios->desc[i]))
+ return true;
+
+ return false;
+}
+
+/*
+ * Some value which is still acceptable to delay in atomic context.
+ * If we need to go higher we might have to switch to usleep_range(),
+ * but that cannot ne used in atomic context and the driver would have
+ * to be adjusted to support that.
+ */
+#define DURATION_NS_MAX 5000
+
+static int gpio_latch_probe(struct platform_device *pdev)
+{
+ struct gpio_latch_priv *priv;
+ unsigned int n_latches;
+ struct device_node *np = pdev->dev.of_node;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->clk_gpios = devm_gpiod_get_array(&pdev->dev, "clk", GPIOD_OUT_LOW);
+ if (IS_ERR(priv->clk_gpios))
+ return PTR_ERR(priv->clk_gpios);
+
+ priv->latched_gpios = devm_gpiod_get_array(&pdev->dev, "latched", GPIOD_OUT_LOW);
+ if (IS_ERR(priv->latched_gpios))
+ return PTR_ERR(priv->latched_gpios);
+
+ n_latches = priv->clk_gpios->ndescs;
+ priv->n_latched_gpios = priv->latched_gpios->ndescs;
+
+ priv->shadow = devm_bitmap_zalloc(&pdev->dev, n_latches * priv->n_latched_gpios,
+ GFP_KERNEL);
+ if (!priv->shadow)
+ return -ENOMEM;
+
+ if (gpio_latch_can_sleep(priv, n_latches)) {
+ priv->gc.can_sleep = true;
+ priv->gc.set = gpio_latch_set_can_sleep;
+ mutex_init(&priv->mutex);
+ } else {
+ priv->gc.can_sleep = false;
+ priv->gc.set = gpio_latch_set;
+ spin_lock_init(&priv->spinlock);
+ }
+
+ of_property_read_u32(np, "setup-duration-ns", &priv->setup_duration_ns);
+ if (priv->setup_duration_ns > DURATION_NS_MAX) {
+ dev_warn(&pdev->dev, "setup-duration-ns too high, limit to %d\n",
+ DURATION_NS_MAX);
+ priv->setup_duration_ns = DURATION_NS_MAX;
+ }
+
+ of_property_read_u32(np, "clock-duration-ns", &priv->clock_duration_ns);
+ if (priv->clock_duration_ns > DURATION_NS_MAX) {
+ dev_warn(&pdev->dev, "clock-duration-ns too high, limit to %d\n",
+ DURATION_NS_MAX);
+ priv->clock_duration_ns = DURATION_NS_MAX;
+ }
+
+ priv->gc.get_direction = gpio_latch_get_direction;
+ priv->gc.ngpio = n_latches * priv->n_latched_gpios;
+ priv->gc.owner = THIS_MODULE;
+ priv->gc.base = -1;
+ priv->gc.parent = &pdev->dev;
+
+ platform_set_drvdata(pdev, priv);
+
+ return devm_gpiochip_add_data(&pdev->dev, &priv->gc, priv);
+}
+
+static const struct of_device_id gpio_latch_ids[] = {
+ {
+ .compatible = "gpio-latch",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, gpio_latch_ids);
+
+static struct platform_driver gpio_latch_driver = {
+ .driver = {
+ .name = "gpio-latch",
+ .of_match_table = gpio_latch_ids,
+ },
+ .probe = gpio_latch_probe,
+};
+module_platform_driver(gpio_latch_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION("GPIO latch driver");
diff --git a/drivers/gpio/gpio-ljca.c b/drivers/gpio/gpio-ljca.c
new file mode 100644
index 0000000000..87863f0230
--- /dev/null
+++ b/drivers/gpio/gpio-ljca.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel La Jolla Cove Adapter USB-GPIO driver
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/dev_printk.h>
+#include <linux/gpio/driver.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/mfd/ljca.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+/* GPIO commands */
+#define LJCA_GPIO_CONFIG 1
+#define LJCA_GPIO_READ 2
+#define LJCA_GPIO_WRITE 3
+#define LJCA_GPIO_INT_EVENT 4
+#define LJCA_GPIO_INT_MASK 5
+#define LJCA_GPIO_INT_UNMASK 6
+
+#define LJCA_GPIO_CONF_DISABLE BIT(0)
+#define LJCA_GPIO_CONF_INPUT BIT(1)
+#define LJCA_GPIO_CONF_OUTPUT BIT(2)
+#define LJCA_GPIO_CONF_PULLUP BIT(3)
+#define LJCA_GPIO_CONF_PULLDOWN BIT(4)
+#define LJCA_GPIO_CONF_DEFAULT BIT(5)
+#define LJCA_GPIO_CONF_INTERRUPT BIT(6)
+#define LJCA_GPIO_INT_TYPE BIT(7)
+
+#define LJCA_GPIO_CONF_EDGE FIELD_PREP(LJCA_GPIO_INT_TYPE, 1)
+#define LJCA_GPIO_CONF_LEVEL FIELD_PREP(LJCA_GPIO_INT_TYPE, 0)
+
+/* Intentional overlap with PULLUP / PULLDOWN */
+#define LJCA_GPIO_CONF_SET BIT(3)
+#define LJCA_GPIO_CONF_CLR BIT(4)
+
+struct gpio_op {
+ u8 index;
+ u8 value;
+} __packed;
+
+struct gpio_packet {
+ u8 num;
+ struct gpio_op item[];
+} __packed;
+
+#define LJCA_GPIO_BUF_SIZE 60
+struct ljca_gpio_dev {
+ struct platform_device *pdev;
+ struct gpio_chip gc;
+ struct ljca_gpio_info *gpio_info;
+ DECLARE_BITMAP(unmasked_irqs, LJCA_MAX_GPIO_NUM);
+ DECLARE_BITMAP(enabled_irqs, LJCA_MAX_GPIO_NUM);
+ DECLARE_BITMAP(reenable_irqs, LJCA_MAX_GPIO_NUM);
+ u8 *connect_mode;
+ /* mutex to protect irq bus */
+ struct mutex irq_lock;
+ struct work_struct work;
+ /* lock to protect package transfer to Hardware */
+ struct mutex trans_lock;
+
+ u8 obuf[LJCA_GPIO_BUF_SIZE];
+ u8 ibuf[LJCA_GPIO_BUF_SIZE];
+};
+
+static int gpio_config(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id, u8 config)
+{
+ struct gpio_packet *packet = (struct gpio_packet *)ljca_gpio->obuf;
+ int ret;
+
+ mutex_lock(&ljca_gpio->trans_lock);
+ packet->item[0].index = gpio_id;
+ packet->item[0].value = config | ljca_gpio->connect_mode[gpio_id];
+ packet->num = 1;
+
+ ret = ljca_transfer(ljca_gpio->gpio_info->ljca, LJCA_GPIO_CONFIG, packet,
+ struct_size(packet, item, packet->num), NULL, NULL);
+ mutex_unlock(&ljca_gpio->trans_lock);
+ return ret;
+}
+
+static int ljca_gpio_read(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id)
+{
+ struct gpio_packet *packet = (struct gpio_packet *)ljca_gpio->obuf;
+ struct gpio_packet *ack_packet = (struct gpio_packet *)ljca_gpio->ibuf;
+ unsigned int ibuf_len = LJCA_GPIO_BUF_SIZE;
+ int ret;
+
+ mutex_lock(&ljca_gpio->trans_lock);
+ packet->num = 1;
+ packet->item[0].index = gpio_id;
+ ret = ljca_transfer(ljca_gpio->gpio_info->ljca, LJCA_GPIO_READ, packet,
+ struct_size(packet, item, packet->num), ljca_gpio->ibuf, &ibuf_len);
+ if (ret)
+ goto out_unlock;
+
+ if (!ibuf_len || ack_packet->num != packet->num) {
+ dev_err(&ljca_gpio->pdev->dev, "failed gpio_id:%u %u", gpio_id, ack_packet->num);
+ ret = -EIO;
+ }
+
+out_unlock:
+ mutex_unlock(&ljca_gpio->trans_lock);
+ if (ret)
+ return ret;
+ return ack_packet->item[0].value > 0;
+}
+
+static int ljca_gpio_write(struct ljca_gpio_dev *ljca_gpio, u8 gpio_id,
+ int value)
+{
+ struct gpio_packet *packet = (struct gpio_packet *)ljca_gpio->obuf;
+ int ret;
+
+ mutex_lock(&ljca_gpio->trans_lock);
+ packet->num = 1;
+ packet->item[0].index = gpio_id;
+ packet->item[0].value = value & 1;
+
+ ret = ljca_transfer(ljca_gpio->gpio_info->ljca, LJCA_GPIO_WRITE, packet,
+ struct_size(packet, item, packet->num), NULL, NULL);
+ mutex_unlock(&ljca_gpio->trans_lock);
+ return ret;
+}
+
+static int ljca_gpio_get_value(struct gpio_chip *chip, unsigned int offset)
+{
+ struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip);
+
+ return ljca_gpio_read(ljca_gpio, offset);
+}
+
+static void ljca_gpio_set_value(struct gpio_chip *chip, unsigned int offset,
+ int val)
+{
+ struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip);
+ int ret;
+
+ ret = ljca_gpio_write(ljca_gpio, offset, val);
+ if (ret)
+ dev_err(chip->parent, "offset:%u val:%d set value failed %d\n", offset, val, ret);
+}
+
+static int ljca_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip);
+ u8 config = LJCA_GPIO_CONF_INPUT | LJCA_GPIO_CONF_CLR;
+
+ return gpio_config(ljca_gpio, offset, config);
+}
+
+static int ljca_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int val)
+{
+ struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip);
+ u8 config = LJCA_GPIO_CONF_OUTPUT | LJCA_GPIO_CONF_CLR;
+ int ret;
+
+ ret = gpio_config(ljca_gpio, offset, config);
+ if (ret)
+ return ret;
+
+ ljca_gpio_set_value(chip, offset, val);
+ return 0;
+}
+
+static int ljca_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip);
+
+ ljca_gpio->connect_mode[offset] = 0;
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_BIAS_PULL_UP:
+ ljca_gpio->connect_mode[offset] |= LJCA_GPIO_CONF_PULLUP;
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ ljca_gpio->connect_mode[offset] |= LJCA_GPIO_CONF_PULLDOWN;
+ break;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ case PIN_CONFIG_PERSIST_STATE:
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ return 0;
+}
+
+static int ljca_gpio_init_valid_mask(struct gpio_chip *chip, unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip);
+
+ WARN_ON_ONCE(ngpios != ljca_gpio->gpio_info->num);
+ bitmap_copy(valid_mask, ljca_gpio->gpio_info->valid_pin_map, ngpios);
+
+ return 0;
+}
+
+static void ljca_gpio_irq_init_valid_mask(struct gpio_chip *chip, unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ ljca_gpio_init_valid_mask(chip, valid_mask, ngpios);
+}
+
+static int ljca_enable_irq(struct ljca_gpio_dev *ljca_gpio, int gpio_id, bool enable)
+{
+ struct gpio_packet *packet = (struct gpio_packet *)ljca_gpio->obuf;
+ int ret;
+
+ mutex_lock(&ljca_gpio->trans_lock);
+ packet->num = 1;
+ packet->item[0].index = gpio_id;
+ packet->item[0].value = 0;
+
+ ret = ljca_transfer(ljca_gpio->gpio_info->ljca,
+ enable ? LJCA_GPIO_INT_UNMASK : LJCA_GPIO_INT_MASK, packet,
+ struct_size(packet, item, packet->num), NULL, NULL);
+ mutex_unlock(&ljca_gpio->trans_lock);
+ return ret;
+}
+
+static void ljca_gpio_async(struct work_struct *work)
+{
+ struct ljca_gpio_dev *ljca_gpio = container_of(work, struct ljca_gpio_dev, work);
+ int gpio_id;
+ int unmasked;
+
+ for_each_set_bit(gpio_id, ljca_gpio->reenable_irqs, ljca_gpio->gc.ngpio) {
+ clear_bit(gpio_id, ljca_gpio->reenable_irqs);
+ unmasked = test_bit(gpio_id, ljca_gpio->unmasked_irqs);
+ if (unmasked)
+ ljca_enable_irq(ljca_gpio, gpio_id, true);
+ }
+}
+
+static void ljca_gpio_event_cb(void *context, u8 cmd, const void *evt_data, int len)
+{
+ const struct gpio_packet *packet = evt_data;
+ struct ljca_gpio_dev *ljca_gpio = context;
+ int i;
+ int irq;
+
+ if (cmd != LJCA_GPIO_INT_EVENT)
+ return;
+
+ for (i = 0; i < packet->num; i++) {
+ irq = irq_find_mapping(ljca_gpio->gc.irq.domain, packet->item[i].index);
+ if (!irq) {
+ dev_err(ljca_gpio->gc.parent, "gpio_id %u does not mapped to IRQ yet\n",
+ packet->item[i].index);
+ return;
+ }
+
+ generic_handle_domain_irq(ljca_gpio->gc.irq.domain, irq);
+ set_bit(packet->item[i].index, ljca_gpio->reenable_irqs);
+ }
+
+ schedule_work(&ljca_gpio->work);
+}
+
+static void ljca_irq_unmask(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(gc);
+ int gpio_id = irqd_to_hwirq(irqd);
+
+ gpiochip_enable_irq(gc, gpio_id);
+ set_bit(gpio_id, ljca_gpio->unmasked_irqs);
+}
+
+static void ljca_irq_mask(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(gc);
+ int gpio_id = irqd_to_hwirq(irqd);
+
+ clear_bit(gpio_id, ljca_gpio->unmasked_irqs);
+ gpiochip_disable_irq(gc, gpio_id);
+}
+
+static int ljca_irq_set_type(struct irq_data *irqd, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(gc);
+ int gpio_id = irqd_to_hwirq(irqd);
+
+ ljca_gpio->connect_mode[gpio_id] = LJCA_GPIO_CONF_INTERRUPT;
+ switch (type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ ljca_gpio->connect_mode[gpio_id] |= (LJCA_GPIO_CONF_LEVEL | LJCA_GPIO_CONF_PULLUP);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ ljca_gpio->connect_mode[gpio_id] |= (LJCA_GPIO_CONF_LEVEL | LJCA_GPIO_CONF_PULLDOWN);
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ ljca_gpio->connect_mode[gpio_id] |= (LJCA_GPIO_CONF_EDGE | LJCA_GPIO_CONF_PULLUP);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ ljca_gpio->connect_mode[gpio_id] |= (LJCA_GPIO_CONF_EDGE | LJCA_GPIO_CONF_PULLDOWN);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void ljca_irq_bus_lock(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(gc);
+
+ mutex_lock(&ljca_gpio->irq_lock);
+}
+
+static void ljca_irq_bus_unlock(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(gc);
+ int gpio_id = irqd_to_hwirq(irqd);
+ int enabled;
+ int unmasked;
+
+ enabled = test_bit(gpio_id, ljca_gpio->enabled_irqs);
+ unmasked = test_bit(gpio_id, ljca_gpio->unmasked_irqs);
+
+ if (enabled != unmasked) {
+ if (unmasked) {
+ gpio_config(ljca_gpio, gpio_id, 0);
+ ljca_enable_irq(ljca_gpio, gpio_id, true);
+ set_bit(gpio_id, ljca_gpio->enabled_irqs);
+ } else {
+ ljca_enable_irq(ljca_gpio, gpio_id, false);
+ clear_bit(gpio_id, ljca_gpio->enabled_irqs);
+ }
+ }
+
+ mutex_unlock(&ljca_gpio->irq_lock);
+}
+
+static const struct irq_chip ljca_gpio_irqchip = {
+ .name = "ljca-irq",
+ .irq_mask = ljca_irq_mask,
+ .irq_unmask = ljca_irq_unmask,
+ .irq_set_type = ljca_irq_set_type,
+ .irq_bus_lock = ljca_irq_bus_lock,
+ .irq_bus_sync_unlock = ljca_irq_bus_unlock,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int ljca_gpio_probe(struct platform_device *pdev)
+{
+ struct ljca_gpio_dev *ljca_gpio;
+ struct gpio_irq_chip *girq;
+ int ret;
+
+ ljca_gpio = devm_kzalloc(&pdev->dev, sizeof(*ljca_gpio), GFP_KERNEL);
+ if (!ljca_gpio)
+ return -ENOMEM;
+
+ ljca_gpio->gpio_info = dev_get_platdata(&pdev->dev);
+ ljca_gpio->connect_mode = devm_kcalloc(&pdev->dev, ljca_gpio->gpio_info->num,
+ sizeof(*ljca_gpio->connect_mode), GFP_KERNEL);
+ if (!ljca_gpio->connect_mode)
+ return -ENOMEM;
+
+ mutex_init(&ljca_gpio->irq_lock);
+ mutex_init(&ljca_gpio->trans_lock);
+ ljca_gpio->pdev = pdev;
+ ljca_gpio->gc.direction_input = ljca_gpio_direction_input;
+ ljca_gpio->gc.direction_output = ljca_gpio_direction_output;
+ ljca_gpio->gc.get = ljca_gpio_get_value;
+ ljca_gpio->gc.set = ljca_gpio_set_value;
+ ljca_gpio->gc.set_config = ljca_gpio_set_config;
+ ljca_gpio->gc.init_valid_mask = ljca_gpio_init_valid_mask;
+ ljca_gpio->gc.can_sleep = true;
+ ljca_gpio->gc.parent = &pdev->dev;
+
+ ljca_gpio->gc.base = -1;
+ ljca_gpio->gc.ngpio = ljca_gpio->gpio_info->num;
+ ljca_gpio->gc.label = ACPI_COMPANION(&pdev->dev) ?
+ acpi_dev_name(ACPI_COMPANION(&pdev->dev)) :
+ dev_name(&pdev->dev);
+ ljca_gpio->gc.owner = THIS_MODULE;
+
+ platform_set_drvdata(pdev, ljca_gpio);
+ ljca_register_event_cb(ljca_gpio->gpio_info->ljca, ljca_gpio_event_cb, ljca_gpio);
+
+ girq = &ljca_gpio->gc.irq;
+ gpio_irq_chip_set_chip(girq, &ljca_gpio_irqchip);
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ girq->init_valid_mask = ljca_gpio_irq_init_valid_mask;
+
+ INIT_WORK(&ljca_gpio->work, ljca_gpio_async);
+ ret = gpiochip_add_data(&ljca_gpio->gc, ljca_gpio);
+ if (ret) {
+ ljca_unregister_event_cb(ljca_gpio->gpio_info->ljca);
+ mutex_destroy(&ljca_gpio->irq_lock);
+ mutex_destroy(&ljca_gpio->trans_lock);
+ }
+
+ return ret;
+}
+
+static int ljca_gpio_remove(struct platform_device *pdev)
+{
+ struct ljca_gpio_dev *ljca_gpio = platform_get_drvdata(pdev);
+
+ gpiochip_remove(&ljca_gpio->gc);
+ ljca_unregister_event_cb(ljca_gpio->gpio_info->ljca);
+ mutex_destroy(&ljca_gpio->irq_lock);
+ mutex_destroy(&ljca_gpio->trans_lock);
+ return 0;
+}
+
+#define LJCA_GPIO_DRV_NAME "ljca-gpio"
+static const struct platform_device_id ljca_gpio_id[] = {
+ { LJCA_GPIO_DRV_NAME, 0 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, ljca_gpio_id);
+
+static struct platform_driver ljca_gpio_driver = {
+ .driver.name = LJCA_GPIO_DRV_NAME,
+ .probe = ljca_gpio_probe,
+ .remove = ljca_gpio_remove,
+};
+module_platform_driver(ljca_gpio_driver);
+
+MODULE_AUTHOR("Ye Xiang <xiang.ye@intel.com>");
+MODULE_AUTHOR("Wang Zhifeng <zhifeng.wang@intel.com>");
+MODULE_AUTHOR("Zhang Lixu <lixu.zhang@intel.com>");
+MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB-GPIO driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(LJCA);
diff --git a/drivers/gpio/gpio-logicvc.c b/drivers/gpio/gpio-logicvc.c
new file mode 100644
index 0000000000..05d62011f3
--- /dev/null
+++ b/drivers/gpio/gpio-logicvc.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+
+#define LOGICVC_CTRL_REG 0x40
+#define LOGICVC_CTRL_GPIO_SHIFT 11
+#define LOGICVC_CTRL_GPIO_BITS 5
+
+#define LOGICVC_POWER_CTRL_REG 0x78
+#define LOGICVC_POWER_CTRL_GPIO_SHIFT 0
+#define LOGICVC_POWER_CTRL_GPIO_BITS 4
+
+struct logicvc_gpio {
+ struct gpio_chip chip;
+ struct regmap *regmap;
+};
+
+static void logicvc_gpio_offset(struct logicvc_gpio *logicvc, unsigned offset,
+ unsigned int *reg, unsigned int *bit)
+{
+ if (offset >= LOGICVC_CTRL_GPIO_BITS) {
+ *reg = LOGICVC_POWER_CTRL_REG;
+
+ /* To the (virtual) power ctrl offset. */
+ offset -= LOGICVC_CTRL_GPIO_BITS;
+ /* To the actual bit offset in reg. */
+ offset += LOGICVC_POWER_CTRL_GPIO_SHIFT;
+ } else {
+ *reg = LOGICVC_CTRL_REG;
+
+ /* To the actual bit offset in reg. */
+ offset += LOGICVC_CTRL_GPIO_SHIFT;
+ }
+
+ *bit = BIT(offset);
+}
+
+static int logicvc_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct logicvc_gpio *logicvc = gpiochip_get_data(chip);
+ unsigned int reg, bit, value;
+ int ret;
+
+ logicvc_gpio_offset(logicvc, offset, &reg, &bit);
+
+ ret = regmap_read(logicvc->regmap, reg, &value);
+ if (ret)
+ return ret;
+
+ return !!(value & bit);
+}
+
+static void logicvc_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct logicvc_gpio *logicvc = gpiochip_get_data(chip);
+ unsigned int reg, bit;
+
+ logicvc_gpio_offset(logicvc, offset, &reg, &bit);
+
+ regmap_update_bits(logicvc->regmap, reg, bit, value ? bit : 0);
+}
+
+static int logicvc_gpio_direction_output(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ /* Pins are always configured as output, so just set the value. */
+ logicvc_gpio_set(chip, offset, value);
+
+ return 0;
+}
+
+static struct regmap_config logicvc_gpio_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .name = "logicvc-gpio",
+};
+
+static int logicvc_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *of_node = dev->of_node;
+ struct logicvc_gpio *logicvc;
+ int ret;
+
+ logicvc = devm_kzalloc(dev, sizeof(*logicvc), GFP_KERNEL);
+ if (!logicvc)
+ return -ENOMEM;
+
+ /* Try to get regmap from parent first. */
+ logicvc->regmap = syscon_node_to_regmap(of_node->parent);
+
+ /* Grab our own regmap if that fails. */
+ if (IS_ERR(logicvc->regmap)) {
+ struct resource res;
+ void __iomem *base;
+
+ ret = of_address_to_resource(of_node, 0, &res);
+ if (ret) {
+ dev_err(dev, "Failed to get resource from address\n");
+ return ret;
+ }
+
+ base = devm_ioremap_resource(dev, &res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ logicvc_gpio_regmap_config.max_register = resource_size(&res) -
+ logicvc_gpio_regmap_config.reg_stride;
+
+ logicvc->regmap =
+ devm_regmap_init_mmio(dev, base,
+ &logicvc_gpio_regmap_config);
+ if (IS_ERR(logicvc->regmap)) {
+ dev_err(dev, "Failed to create regmap for I/O\n");
+ return PTR_ERR(logicvc->regmap);
+ }
+ }
+
+ logicvc->chip.parent = dev;
+ logicvc->chip.owner = THIS_MODULE;
+ logicvc->chip.label = dev_name(dev);
+ logicvc->chip.base = -1;
+ logicvc->chip.ngpio = LOGICVC_CTRL_GPIO_BITS +
+ LOGICVC_POWER_CTRL_GPIO_BITS;
+ logicvc->chip.get = logicvc_gpio_get;
+ logicvc->chip.set = logicvc_gpio_set;
+ logicvc->chip.direction_output = logicvc_gpio_direction_output;
+
+ return devm_gpiochip_add_data(dev, &logicvc->chip, logicvc);
+}
+
+static const struct of_device_id logicivc_gpio_of_table[] = {
+ {
+ .compatible = "xylon,logicvc-3.02.a-gpio",
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, logicivc_gpio_of_table);
+
+static struct platform_driver logicvc_gpio_driver = {
+ .driver = {
+ .name = "gpio-logicvc",
+ .of_match_table = logicivc_gpio_of_table,
+ },
+ .probe = logicvc_gpio_probe,
+};
+
+module_platform_driver(logicvc_gpio_driver);
+
+MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>");
+MODULE_DESCRIPTION("Xylon LogiCVC GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-loongson-64bit.c b/drivers/gpio/gpio-loongson-64bit.c
new file mode 100644
index 0000000000..06213bbfab
--- /dev/null
+++ b/drivers/gpio/gpio-loongson-64bit.c
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Loongson GPIO Support
+ *
+ * Copyright (C) 2022-2023 Loongson Technology Corporation Limited
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/bitops.h>
+#include <asm/types.h>
+
+enum loongson_gpio_mode {
+ BIT_CTRL_MODE,
+ BYTE_CTRL_MODE,
+};
+
+struct loongson_gpio_chip_data {
+ const char *label;
+ enum loongson_gpio_mode mode;
+ unsigned int conf_offset;
+ unsigned int out_offset;
+ unsigned int in_offset;
+};
+
+struct loongson_gpio_chip {
+ struct gpio_chip chip;
+ struct fwnode_handle *fwnode;
+ spinlock_t lock;
+ void __iomem *reg_base;
+ const struct loongson_gpio_chip_data *chip_data;
+};
+
+static inline struct loongson_gpio_chip *to_loongson_gpio_chip(struct gpio_chip *chip)
+{
+ return container_of(chip, struct loongson_gpio_chip, chip);
+}
+
+static inline void loongson_commit_direction(struct loongson_gpio_chip *lgpio, unsigned int pin,
+ int input)
+{
+ u8 bval = input ? 1 : 0;
+
+ writeb(bval, lgpio->reg_base + lgpio->chip_data->conf_offset + pin);
+}
+
+static void loongson_commit_level(struct loongson_gpio_chip *lgpio, unsigned int pin, int high)
+{
+ u8 bval = high ? 1 : 0;
+
+ writeb(bval, lgpio->reg_base + lgpio->chip_data->out_offset + pin);
+}
+
+static int loongson_gpio_direction_input(struct gpio_chip *chip, unsigned int pin)
+{
+ unsigned long flags;
+ struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
+
+ spin_lock_irqsave(&lgpio->lock, flags);
+ loongson_commit_direction(lgpio, pin, 1);
+ spin_unlock_irqrestore(&lgpio->lock, flags);
+
+ return 0;
+}
+
+static int loongson_gpio_direction_output(struct gpio_chip *chip, unsigned int pin, int value)
+{
+ unsigned long flags;
+ struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
+
+ spin_lock_irqsave(&lgpio->lock, flags);
+ loongson_commit_level(lgpio, pin, value);
+ loongson_commit_direction(lgpio, pin, 0);
+ spin_unlock_irqrestore(&lgpio->lock, flags);
+
+ return 0;
+}
+
+static int loongson_gpio_get(struct gpio_chip *chip, unsigned int pin)
+{
+ u8 bval;
+ int val;
+ struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
+
+ bval = readb(lgpio->reg_base + lgpio->chip_data->in_offset + pin);
+ val = bval & 1;
+
+ return val;
+}
+
+static int loongson_gpio_get_direction(struct gpio_chip *chip, unsigned int pin)
+{
+ u8 bval;
+ struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
+
+ bval = readb(lgpio->reg_base + lgpio->chip_data->conf_offset + pin);
+ if (bval & 1)
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static void loongson_gpio_set(struct gpio_chip *chip, unsigned int pin, int value)
+{
+ unsigned long flags;
+ struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
+
+ spin_lock_irqsave(&lgpio->lock, flags);
+ loongson_commit_level(lgpio, pin, value);
+ spin_unlock_irqrestore(&lgpio->lock, flags);
+}
+
+static int loongson_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
+{
+ struct platform_device *pdev = to_platform_device(chip->parent);
+
+ return platform_get_irq(pdev, offset);
+}
+
+static int loongson_gpio_init(struct device *dev, struct loongson_gpio_chip *lgpio,
+ struct device_node *np, void __iomem *reg_base)
+{
+ int ret;
+ u32 ngpios;
+
+ lgpio->reg_base = reg_base;
+
+ if (lgpio->chip_data->mode == BIT_CTRL_MODE) {
+ ret = bgpio_init(&lgpio->chip, dev, 8,
+ lgpio->reg_base + lgpio->chip_data->in_offset,
+ lgpio->reg_base + lgpio->chip_data->out_offset,
+ NULL, NULL,
+ lgpio->reg_base + lgpio->chip_data->conf_offset,
+ 0);
+ if (ret) {
+ dev_err(dev, "unable to init generic GPIO\n");
+ return ret;
+ }
+ } else {
+ lgpio->chip.direction_input = loongson_gpio_direction_input;
+ lgpio->chip.get = loongson_gpio_get;
+ lgpio->chip.get_direction = loongson_gpio_get_direction;
+ lgpio->chip.direction_output = loongson_gpio_direction_output;
+ lgpio->chip.set = loongson_gpio_set;
+ lgpio->chip.parent = dev;
+ spin_lock_init(&lgpio->lock);
+ }
+
+ device_property_read_u32(dev, "ngpios", &ngpios);
+
+ lgpio->chip.can_sleep = 0;
+ lgpio->chip.ngpio = ngpios;
+ lgpio->chip.label = lgpio->chip_data->label;
+ lgpio->chip.to_irq = loongson_gpio_to_irq;
+
+ return devm_gpiochip_add_data(dev, &lgpio->chip, lgpio);
+}
+
+static int loongson_gpio_probe(struct platform_device *pdev)
+{
+ void __iomem *reg_base;
+ struct loongson_gpio_chip *lgpio;
+ struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+
+ lgpio = devm_kzalloc(dev, sizeof(*lgpio), GFP_KERNEL);
+ if (!lgpio)
+ return -ENOMEM;
+
+ lgpio->chip_data = device_get_match_data(dev);
+
+ reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(reg_base))
+ return PTR_ERR(reg_base);
+
+ return loongson_gpio_init(dev, lgpio, np, reg_base);
+}
+
+static const struct loongson_gpio_chip_data loongson_gpio_ls2k_data = {
+ .label = "ls2k_gpio",
+ .mode = BIT_CTRL_MODE,
+ .conf_offset = 0x0,
+ .in_offset = 0x20,
+ .out_offset = 0x10,
+};
+
+static const struct loongson_gpio_chip_data loongson_gpio_ls7a_data = {
+ .label = "ls7a_gpio",
+ .mode = BYTE_CTRL_MODE,
+ .conf_offset = 0x800,
+ .in_offset = 0xa00,
+ .out_offset = 0x900,
+};
+
+static const struct of_device_id loongson_gpio_of_match[] = {
+ {
+ .compatible = "loongson,ls2k-gpio",
+ .data = &loongson_gpio_ls2k_data,
+ },
+ {
+ .compatible = "loongson,ls7a-gpio",
+ .data = &loongson_gpio_ls7a_data,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, loongson_gpio_of_match);
+
+static const struct acpi_device_id loongson_gpio_acpi_match[] = {
+ {
+ .id = "LOON0002",
+ .driver_data = (kernel_ulong_t)&loongson_gpio_ls7a_data,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, loongson_gpio_acpi_match);
+
+static struct platform_driver loongson_gpio_driver = {
+ .driver = {
+ .name = "loongson-gpio",
+ .of_match_table = loongson_gpio_of_match,
+ .acpi_match_table = loongson_gpio_acpi_match,
+ },
+ .probe = loongson_gpio_probe,
+};
+
+static int __init loongson_gpio_setup(void)
+{
+ return platform_driver_register(&loongson_gpio_driver);
+}
+postcore_initcall(loongson_gpio_setup);
+
+MODULE_DESCRIPTION("Loongson gpio driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-loongson.c b/drivers/gpio/gpio-loongson.c
new file mode 100644
index 0000000000..a42145873c
--- /dev/null
+++ b/drivers/gpio/gpio-loongson.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Loongson-2F/3A/3B GPIO Support
+ *
+ * Copyright (c) 2008 Richard Liu, STMicroelectronics <richard.liu@st.com>
+ * Copyright (c) 2008-2010 Arnaud Patard <apatard@mandriva.com>
+ * Copyright (c) 2013 Hongbing Hu <huhb@lemote.com>
+ * Copyright (c) 2014 Huacai Chen <chenhc@lemote.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/bitops.h>
+#include <asm/types.h>
+#include <loongson.h>
+
+#define STLS2F_N_GPIO 4
+#define STLS3A_N_GPIO 16
+
+#ifdef CONFIG_CPU_LOONGSON64
+#define LOONGSON_N_GPIO STLS3A_N_GPIO
+#else
+#define LOONGSON_N_GPIO STLS2F_N_GPIO
+#endif
+
+/*
+ * Offset into the register where we read lines, we write them from offset 0.
+ * This offset is the only thing that stand between us and using
+ * GPIO_GENERIC.
+ */
+#define LOONGSON_GPIO_IN_OFFSET 16
+
+static DEFINE_SPINLOCK(gpio_lock);
+
+static int loongson_gpio_get_value(struct gpio_chip *chip, unsigned gpio)
+{
+ u32 val;
+
+ spin_lock(&gpio_lock);
+ val = LOONGSON_GPIODATA;
+ spin_unlock(&gpio_lock);
+
+ return !!(val & BIT(gpio + LOONGSON_GPIO_IN_OFFSET));
+}
+
+static void loongson_gpio_set_value(struct gpio_chip *chip,
+ unsigned gpio, int value)
+{
+ u32 val;
+
+ spin_lock(&gpio_lock);
+ val = LOONGSON_GPIODATA;
+ if (value)
+ val |= BIT(gpio);
+ else
+ val &= ~BIT(gpio);
+ LOONGSON_GPIODATA = val;
+ spin_unlock(&gpio_lock);
+}
+
+static int loongson_gpio_direction_input(struct gpio_chip *chip, unsigned gpio)
+{
+ u32 temp;
+
+ spin_lock(&gpio_lock);
+ temp = LOONGSON_GPIOIE;
+ temp |= BIT(gpio);
+ LOONGSON_GPIOIE = temp;
+ spin_unlock(&gpio_lock);
+
+ return 0;
+}
+
+static int loongson_gpio_direction_output(struct gpio_chip *chip,
+ unsigned gpio, int level)
+{
+ u32 temp;
+
+ loongson_gpio_set_value(chip, gpio, level);
+ spin_lock(&gpio_lock);
+ temp = LOONGSON_GPIOIE;
+ temp &= ~BIT(gpio);
+ LOONGSON_GPIOIE = temp;
+ spin_unlock(&gpio_lock);
+
+ return 0;
+}
+
+static int loongson_gpio_probe(struct platform_device *pdev)
+{
+ struct gpio_chip *gc;
+ struct device *dev = &pdev->dev;
+
+ gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL);
+ if (!gc)
+ return -ENOMEM;
+
+ gc->label = "loongson-gpio-chip";
+ gc->base = 0;
+ gc->ngpio = LOONGSON_N_GPIO;
+ gc->get = loongson_gpio_get_value;
+ gc->set = loongson_gpio_set_value;
+ gc->direction_input = loongson_gpio_direction_input;
+ gc->direction_output = loongson_gpio_direction_output;
+
+ return gpiochip_add_data(gc, NULL);
+}
+
+static struct platform_driver loongson_gpio_driver = {
+ .driver = {
+ .name = "loongson-gpio",
+ },
+ .probe = loongson_gpio_probe,
+};
+
+static int __init loongson_gpio_setup(void)
+{
+ struct platform_device *pdev;
+ int ret;
+
+ ret = platform_driver_register(&loongson_gpio_driver);
+ if (ret) {
+ pr_err("error registering loongson GPIO driver\n");
+ return ret;
+ }
+
+ pdev = platform_device_register_simple("loongson-gpio", -1, NULL, 0);
+ return PTR_ERR_OR_ZERO(pdev);
+}
+postcore_initcall(loongson_gpio_setup);
diff --git a/drivers/gpio/gpio-loongson1.c b/drivers/gpio/gpio-loongson1.c
new file mode 100644
index 0000000000..6ca3b969db
--- /dev/null
+++ b/drivers/gpio/gpio-loongson1.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO Driver for Loongson 1 SoC
+ *
+ * Copyright (C) 2015-2023 Keguang Zhang <keguang.zhang@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/bitops.h>
+
+/* Loongson 1 GPIO Register Definitions */
+#define GPIO_CFG 0x0
+#define GPIO_DIR 0x10
+#define GPIO_DATA 0x20
+#define GPIO_OUTPUT 0x30
+
+struct ls1x_gpio_chip {
+ struct gpio_chip gc;
+ void __iomem *reg_base;
+};
+
+static int ls1x_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+ struct ls1x_gpio_chip *ls1x_gc = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ __raw_writel(__raw_readl(ls1x_gc->reg_base + GPIO_CFG) | BIT(offset),
+ ls1x_gc->reg_base + GPIO_CFG);
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+ return 0;
+}
+
+static void ls1x_gpio_free(struct gpio_chip *gc, unsigned int offset)
+{
+ struct ls1x_gpio_chip *ls1x_gc = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ __raw_writel(__raw_readl(ls1x_gc->reg_base + GPIO_CFG) & ~BIT(offset),
+ ls1x_gc->reg_base + GPIO_CFG);
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
+static int ls1x_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ls1x_gpio_chip *ls1x_gc;
+ int ret;
+
+ ls1x_gc = devm_kzalloc(dev, sizeof(*ls1x_gc), GFP_KERNEL);
+ if (!ls1x_gc)
+ return -ENOMEM;
+
+ ls1x_gc->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ls1x_gc->reg_base))
+ return PTR_ERR(ls1x_gc->reg_base);
+
+ ret = bgpio_init(&ls1x_gc->gc, dev, 4, ls1x_gc->reg_base + GPIO_DATA,
+ ls1x_gc->reg_base + GPIO_OUTPUT, NULL,
+ NULL, ls1x_gc->reg_base + GPIO_DIR, 0);
+ if (ret)
+ goto err;
+
+ ls1x_gc->gc.owner = THIS_MODULE;
+ ls1x_gc->gc.request = ls1x_gpio_request;
+ ls1x_gc->gc.free = ls1x_gpio_free;
+ /*
+ * Clear ngpio to let gpiolib get the correct number
+ * by reading ngpios property
+ */
+ ls1x_gc->gc.ngpio = 0;
+
+ ret = devm_gpiochip_add_data(dev, &ls1x_gc->gc, ls1x_gc);
+ if (ret)
+ goto err;
+
+ platform_set_drvdata(pdev, ls1x_gc);
+
+ dev_info(dev, "GPIO controller registered with %d pins\n",
+ ls1x_gc->gc.ngpio);
+
+ return 0;
+err:
+ dev_err(dev, "failed to register GPIO controller\n");
+ return ret;
+}
+
+static const struct of_device_id ls1x_gpio_dt_ids[] = {
+ { .compatible = "loongson,ls1x-gpio" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ls1x_gpio_dt_ids);
+
+static struct platform_driver ls1x_gpio_driver = {
+ .probe = ls1x_gpio_probe,
+ .driver = {
+ .name = "ls1x-gpio",
+ .of_match_table = ls1x_gpio_dt_ids,
+ },
+};
+
+module_platform_driver(ls1x_gpio_driver);
+
+MODULE_AUTHOR("Keguang Zhang <keguang.zhang@gmail.com>");
+MODULE_DESCRIPTION("Loongson1 GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-lp3943.c b/drivers/gpio/gpio-lp3943.c
new file mode 100644
index 0000000000..8e58242f51
--- /dev/null
+++ b/drivers/gpio/gpio-lp3943.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TI/National Semiconductor LP3943 GPIO driver
+ *
+ * Copyright 2013 Texas Instruments
+ *
+ * Author: Milo Kim <milo.kim@ti.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/mfd/lp3943.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+enum lp3943_gpios {
+ LP3943_GPIO1,
+ LP3943_GPIO2,
+ LP3943_GPIO3,
+ LP3943_GPIO4,
+ LP3943_GPIO5,
+ LP3943_GPIO6,
+ LP3943_GPIO7,
+ LP3943_GPIO8,
+ LP3943_GPIO9,
+ LP3943_GPIO10,
+ LP3943_GPIO11,
+ LP3943_GPIO12,
+ LP3943_GPIO13,
+ LP3943_GPIO14,
+ LP3943_GPIO15,
+ LP3943_GPIO16,
+ LP3943_MAX_GPIO,
+};
+
+struct lp3943_gpio {
+ struct gpio_chip chip;
+ struct lp3943 *lp3943;
+ u16 input_mask; /* 1 = GPIO is input direction, 0 = output */
+};
+
+static int lp3943_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ struct lp3943_gpio *lp3943_gpio = gpiochip_get_data(chip);
+ struct lp3943 *lp3943 = lp3943_gpio->lp3943;
+
+ /* Return an error if the pin is already assigned */
+ if (test_and_set_bit(offset, &lp3943->pin_used))
+ return -EBUSY;
+
+ return 0;
+}
+
+static void lp3943_gpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+ struct lp3943_gpio *lp3943_gpio = gpiochip_get_data(chip);
+ struct lp3943 *lp3943 = lp3943_gpio->lp3943;
+
+ clear_bit(offset, &lp3943->pin_used);
+}
+
+static int lp3943_gpio_set_mode(struct lp3943_gpio *lp3943_gpio, u8 offset,
+ u8 val)
+{
+ struct lp3943 *lp3943 = lp3943_gpio->lp3943;
+ const struct lp3943_reg_cfg *mux = lp3943->mux_cfg;
+
+ return lp3943_update_bits(lp3943, mux[offset].reg, mux[offset].mask,
+ val << mux[offset].shift);
+}
+
+static int lp3943_gpio_direction_input(struct gpio_chip *chip, unsigned int offset)
+{
+ struct lp3943_gpio *lp3943_gpio = gpiochip_get_data(chip);
+
+ lp3943_gpio->input_mask |= BIT(offset);
+
+ return lp3943_gpio_set_mode(lp3943_gpio, offset, LP3943_GPIO_IN);
+}
+
+static int lp3943_get_gpio_in_status(struct lp3943_gpio *lp3943_gpio,
+ struct gpio_chip *chip, unsigned int offset)
+{
+ u8 addr, read;
+ int err;
+
+ switch (offset) {
+ case LP3943_GPIO1 ... LP3943_GPIO8:
+ addr = LP3943_REG_GPIO_A;
+ break;
+ case LP3943_GPIO9 ... LP3943_GPIO16:
+ addr = LP3943_REG_GPIO_B;
+ offset = offset - 8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ err = lp3943_read_byte(lp3943_gpio->lp3943, addr, &read);
+ if (err)
+ return err;
+
+ return !!(read & BIT(offset));
+}
+
+static int lp3943_get_gpio_out_status(struct lp3943_gpio *lp3943_gpio,
+ struct gpio_chip *chip, unsigned int offset)
+{
+ struct lp3943 *lp3943 = lp3943_gpio->lp3943;
+ const struct lp3943_reg_cfg *mux = lp3943->mux_cfg;
+ u8 read;
+ int err;
+
+ err = lp3943_read_byte(lp3943, mux[offset].reg, &read);
+ if (err)
+ return err;
+
+ read = (read & mux[offset].mask) >> mux[offset].shift;
+
+ if (read == LP3943_GPIO_OUT_HIGH)
+ return 1;
+ else if (read == LP3943_GPIO_OUT_LOW)
+ return 0;
+ else
+ return -EINVAL;
+}
+
+static int lp3943_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct lp3943_gpio *lp3943_gpio = gpiochip_get_data(chip);
+
+ /*
+ * Limitation:
+ * LP3943 doesn't have the GPIO direction register. It provides
+ * only input and output status registers.
+ * So, direction info is required to handle the 'get' operation.
+ * This variable is updated whenever the direction is changed and
+ * it is used here.
+ */
+
+ if (lp3943_gpio->input_mask & BIT(offset))
+ return lp3943_get_gpio_in_status(lp3943_gpio, chip, offset);
+ else
+ return lp3943_get_gpio_out_status(lp3943_gpio, chip, offset);
+}
+
+static void lp3943_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct lp3943_gpio *lp3943_gpio = gpiochip_get_data(chip);
+ u8 data;
+
+ if (value)
+ data = LP3943_GPIO_OUT_HIGH;
+ else
+ data = LP3943_GPIO_OUT_LOW;
+
+ lp3943_gpio_set_mode(lp3943_gpio, offset, data);
+}
+
+static int lp3943_gpio_direction_output(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct lp3943_gpio *lp3943_gpio = gpiochip_get_data(chip);
+
+ lp3943_gpio_set(chip, offset, value);
+ lp3943_gpio->input_mask &= ~BIT(offset);
+
+ return 0;
+}
+
+static const struct gpio_chip lp3943_gpio_chip = {
+ .label = "lp3943",
+ .owner = THIS_MODULE,
+ .request = lp3943_gpio_request,
+ .free = lp3943_gpio_free,
+ .direction_input = lp3943_gpio_direction_input,
+ .get = lp3943_gpio_get,
+ .direction_output = lp3943_gpio_direction_output,
+ .set = lp3943_gpio_set,
+ .base = -1,
+ .ngpio = LP3943_MAX_GPIO,
+ .can_sleep = 1,
+};
+
+static int lp3943_gpio_probe(struct platform_device *pdev)
+{
+ struct lp3943 *lp3943 = dev_get_drvdata(pdev->dev.parent);
+ struct lp3943_gpio *lp3943_gpio;
+
+ lp3943_gpio = devm_kzalloc(&pdev->dev, sizeof(*lp3943_gpio),
+ GFP_KERNEL);
+ if (!lp3943_gpio)
+ return -ENOMEM;
+
+ lp3943_gpio->lp3943 = lp3943;
+ lp3943_gpio->chip = lp3943_gpio_chip;
+ lp3943_gpio->chip.parent = &pdev->dev;
+
+ return devm_gpiochip_add_data(&pdev->dev, &lp3943_gpio->chip,
+ lp3943_gpio);
+}
+
+static const struct of_device_id lp3943_gpio_of_match[] = {
+ { .compatible = "ti,lp3943-gpio", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lp3943_gpio_of_match);
+
+static struct platform_driver lp3943_gpio_driver = {
+ .probe = lp3943_gpio_probe,
+ .driver = {
+ .name = "lp3943-gpio",
+ .of_match_table = lp3943_gpio_of_match,
+ },
+};
+module_platform_driver(lp3943_gpio_driver);
+
+MODULE_DESCRIPTION("LP3943 GPIO driver");
+MODULE_ALIAS("platform:lp3943-gpio");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-lp873x.c b/drivers/gpio/gpio-lp873x.c
new file mode 100644
index 0000000000..5c79ba1f22
--- /dev/null
+++ b/drivers/gpio/gpio-lp873x.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.com/
+ * Keerthy <j-keerthy@ti.com>
+ *
+ * Based on the TPS65218 driver
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/lp873x.h>
+
+#define BITS_PER_GPO 0x4
+#define LP873X_GPO_CTRL_OD 0x2
+
+struct lp873x_gpio {
+ struct gpio_chip chip;
+ struct lp873x *lp873;
+};
+
+static int lp873x_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ /* This device is output only */
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int lp873x_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ /* This device is output only */
+ return -EINVAL;
+}
+
+static int lp873x_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct lp873x_gpio *gpio = gpiochip_get_data(chip);
+
+ /* Set the initial value */
+ return regmap_update_bits(gpio->lp873->regmap, LP873X_REG_GPO_CTRL,
+ BIT(offset * BITS_PER_GPO),
+ value ? BIT(offset * BITS_PER_GPO) : 0);
+}
+
+static int lp873x_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct lp873x_gpio *gpio = gpiochip_get_data(chip);
+ int ret, val;
+
+ ret = regmap_read(gpio->lp873->regmap, LP873X_REG_GPO_CTRL, &val);
+ if (ret < 0)
+ return ret;
+
+ return val & BIT(offset * BITS_PER_GPO);
+}
+
+static void lp873x_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct lp873x_gpio *gpio = gpiochip_get_data(chip);
+
+ regmap_update_bits(gpio->lp873->regmap, LP873X_REG_GPO_CTRL,
+ BIT(offset * BITS_PER_GPO),
+ value ? BIT(offset * BITS_PER_GPO) : 0);
+}
+
+static int lp873x_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+ struct lp873x_gpio *gpio = gpiochip_get_data(gc);
+ int ret;
+
+ switch (offset) {
+ case 0:
+ /* No MUX Set up Needed for GPO */
+ break;
+ case 1:
+ /* Setup the CLKIN_PIN_SEL MUX to GPO2 */
+ ret = regmap_update_bits(gpio->lp873->regmap, LP873X_REG_CONFIG,
+ LP873X_CONFIG_CLKIN_PIN_SEL, 0);
+ if (ret)
+ return ret;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int lp873x_gpio_set_config(struct gpio_chip *gc, unsigned offset,
+ unsigned long config)
+{
+ struct lp873x_gpio *gpio = gpiochip_get_data(gc);
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ return regmap_update_bits(gpio->lp873->regmap,
+ LP873X_REG_GPO_CTRL,
+ BIT(offset * BITS_PER_GPO +
+ LP873X_GPO_CTRL_OD),
+ BIT(offset * BITS_PER_GPO +
+ LP873X_GPO_CTRL_OD));
+
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ return regmap_update_bits(gpio->lp873->regmap,
+ LP873X_REG_GPO_CTRL,
+ BIT(offset * BITS_PER_GPO +
+ LP873X_GPO_CTRL_OD), 0);
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "lp873x-gpio",
+ .owner = THIS_MODULE,
+ .request = lp873x_gpio_request,
+ .get_direction = lp873x_gpio_get_direction,
+ .direction_input = lp873x_gpio_direction_input,
+ .direction_output = lp873x_gpio_direction_output,
+ .get = lp873x_gpio_get,
+ .set = lp873x_gpio_set,
+ .set_config = lp873x_gpio_set_config,
+ .base = -1,
+ .ngpio = 2,
+ .can_sleep = true,
+};
+
+static int lp873x_gpio_probe(struct platform_device *pdev)
+{
+ struct lp873x_gpio *gpio;
+ int ret;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, gpio);
+
+ gpio->lp873 = dev_get_drvdata(pdev->dev.parent);
+ gpio->chip = template_chip;
+ gpio->chip.parent = gpio->lp873->dev;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct platform_device_id lp873x_gpio_id_table[] = {
+ { "lp873x-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, lp873x_gpio_id_table);
+
+static struct platform_driver lp873x_gpio_driver = {
+ .driver = {
+ .name = "lp873x-gpio",
+ },
+ .probe = lp873x_gpio_probe,
+ .id_table = lp873x_gpio_id_table,
+};
+module_platform_driver(lp873x_gpio_driver);
+
+MODULE_AUTHOR("Keerthy <j-keerthy@ti.com>");
+MODULE_DESCRIPTION("LP873X GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-lp87565.c b/drivers/gpio/gpio-lp87565.c
new file mode 100644
index 0000000000..d3ce027de0
--- /dev/null
+++ b/drivers/gpio/gpio-lp87565.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com/
+ * Keerthy <j-keerthy@ti.com>
+ *
+ * Based on the LP873X driver
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/lp87565.h>
+
+struct lp87565_gpio {
+ struct gpio_chip chip;
+ struct regmap *map;
+};
+
+static int lp87565_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct lp87565_gpio *gpio = gpiochip_get_data(chip);
+ int ret, val;
+
+ ret = regmap_read(gpio->map, LP87565_REG_GPIO_IN, &val);
+ if (ret < 0)
+ return ret;
+
+ return !!(val & BIT(offset));
+}
+
+static void lp87565_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct lp87565_gpio *gpio = gpiochip_get_data(chip);
+
+ regmap_update_bits(gpio->map, LP87565_REG_GPIO_OUT,
+ BIT(offset), value ? BIT(offset) : 0);
+}
+
+static int lp87565_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct lp87565_gpio *gpio = gpiochip_get_data(chip);
+ int ret, val;
+
+ ret = regmap_read(gpio->map, LP87565_REG_GPIO_CONFIG, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val & BIT(offset))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int lp87565_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct lp87565_gpio *gpio = gpiochip_get_data(chip);
+
+ return regmap_update_bits(gpio->map,
+ LP87565_REG_GPIO_CONFIG,
+ BIT(offset), 0);
+}
+
+static int lp87565_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct lp87565_gpio *gpio = gpiochip_get_data(chip);
+
+ lp87565_gpio_set(chip, offset, value);
+
+ return regmap_update_bits(gpio->map,
+ LP87565_REG_GPIO_CONFIG,
+ BIT(offset), BIT(offset));
+}
+
+static int lp87565_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+ struct lp87565_gpio *gpio = gpiochip_get_data(gc);
+ int ret;
+
+ switch (offset) {
+ case 0:
+ case 1:
+ case 2:
+ /*
+ * MUX can program the pin to be in EN1/2/3 pin mode
+ * Or GPIO1/2/3 mode.
+ * Setup the GPIO*_SEL MUX to GPIO mode
+ */
+ ret = regmap_update_bits(gpio->map,
+ LP87565_REG_PIN_FUNCTION,
+ BIT(offset), BIT(offset));
+ if (ret)
+ return ret;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int lp87565_gpio_set_config(struct gpio_chip *gc, unsigned int offset,
+ unsigned long config)
+{
+ struct lp87565_gpio *gpio = gpiochip_get_data(gc);
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ return regmap_update_bits(gpio->map,
+ LP87565_REG_GPIO_CONFIG,
+ BIT(offset +
+ __ffs(LP87565_GPIO1_OD)),
+ BIT(offset +
+ __ffs(LP87565_GPIO1_OD)));
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ return regmap_update_bits(gpio->map,
+ LP87565_REG_GPIO_CONFIG,
+ BIT(offset +
+ __ffs(LP87565_GPIO1_OD)), 0);
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "lp87565-gpio",
+ .owner = THIS_MODULE,
+ .request = lp87565_gpio_request,
+ .get_direction = lp87565_gpio_get_direction,
+ .direction_input = lp87565_gpio_direction_input,
+ .direction_output = lp87565_gpio_direction_output,
+ .get = lp87565_gpio_get,
+ .set = lp87565_gpio_set,
+ .set_config = lp87565_gpio_set_config,
+ .base = -1,
+ .ngpio = 3,
+ .can_sleep = true,
+};
+
+static int lp87565_gpio_probe(struct platform_device *pdev)
+{
+ struct lp87565_gpio *gpio;
+ struct lp87565 *lp87565;
+ int ret;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ lp87565 = dev_get_drvdata(pdev->dev.parent);
+ gpio->chip = template_chip;
+ gpio->chip.parent = lp87565->dev;
+ gpio->map = lp87565->regmap;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct platform_device_id lp87565_gpio_id_table[] = {
+ { "lp87565-q1-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, lp87565_gpio_id_table);
+
+static struct platform_driver lp87565_gpio_driver = {
+ .driver = {
+ .name = "lp87565-gpio",
+ },
+ .probe = lp87565_gpio_probe,
+ .id_table = lp87565_gpio_id_table,
+};
+module_platform_driver(lp87565_gpio_driver);
+
+MODULE_AUTHOR("Keerthy <j-keerthy@ti.com>");
+MODULE_DESCRIPTION("LP87565 GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-lpc18xx.c b/drivers/gpio/gpio-lpc18xx.c
new file mode 100644
index 0000000000..ed3f653a1d
--- /dev/null
+++ b/drivers/gpio/gpio-lpc18xx.c
@@ -0,0 +1,415 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO driver for NXP LPC18xx/43xx.
+ *
+ * Copyright (C) 2018 Vladimir Zapolskiy <vz@mleia.com>
+ * Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com>
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+
+/* LPC18xx GPIO register offsets */
+#define LPC18XX_REG_DIR(n) (0x2000 + n * sizeof(u32))
+
+#define LPC18XX_MAX_PORTS 8
+#define LPC18XX_PINS_PER_PORT 32
+
+/* LPC18xx GPIO pin interrupt controller register offsets */
+#define LPC18XX_GPIO_PIN_IC_ISEL 0x00
+#define LPC18XX_GPIO_PIN_IC_IENR 0x04
+#define LPC18XX_GPIO_PIN_IC_SIENR 0x08
+#define LPC18XX_GPIO_PIN_IC_CIENR 0x0c
+#define LPC18XX_GPIO_PIN_IC_IENF 0x10
+#define LPC18XX_GPIO_PIN_IC_SIENF 0x14
+#define LPC18XX_GPIO_PIN_IC_CIENF 0x18
+#define LPC18XX_GPIO_PIN_IC_RISE 0x1c
+#define LPC18XX_GPIO_PIN_IC_FALL 0x20
+#define LPC18XX_GPIO_PIN_IC_IST 0x24
+
+#define NR_LPC18XX_GPIO_PIN_IC_IRQS 8
+
+struct lpc18xx_gpio_pin_ic {
+ void __iomem *base;
+ struct irq_domain *domain;
+ struct raw_spinlock lock;
+};
+
+struct lpc18xx_gpio_chip {
+ struct gpio_chip gpio;
+ void __iomem *base;
+ struct clk *clk;
+ struct lpc18xx_gpio_pin_ic *pin_ic;
+ spinlock_t lock;
+};
+
+static inline void lpc18xx_gpio_pin_ic_isel(struct lpc18xx_gpio_pin_ic *ic,
+ u32 pin, bool set)
+{
+ u32 val = readl_relaxed(ic->base + LPC18XX_GPIO_PIN_IC_ISEL);
+
+ if (set)
+ val &= ~BIT(pin);
+ else
+ val |= BIT(pin);
+
+ writel_relaxed(val, ic->base + LPC18XX_GPIO_PIN_IC_ISEL);
+}
+
+static inline void lpc18xx_gpio_pin_ic_set(struct lpc18xx_gpio_pin_ic *ic,
+ u32 pin, u32 reg)
+{
+ writel_relaxed(BIT(pin), ic->base + reg);
+}
+
+static void lpc18xx_gpio_pin_ic_mask(struct irq_data *d)
+{
+ struct lpc18xx_gpio_pin_ic *ic = d->chip_data;
+ u32 type = irqd_get_trigger_type(d);
+
+ raw_spin_lock(&ic->lock);
+
+ if (type & IRQ_TYPE_LEVEL_MASK || type & IRQ_TYPE_EDGE_RISING)
+ lpc18xx_gpio_pin_ic_set(ic, d->hwirq,
+ LPC18XX_GPIO_PIN_IC_CIENR);
+
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ lpc18xx_gpio_pin_ic_set(ic, d->hwirq,
+ LPC18XX_GPIO_PIN_IC_CIENF);
+
+ raw_spin_unlock(&ic->lock);
+
+ irq_chip_mask_parent(d);
+}
+
+static void lpc18xx_gpio_pin_ic_unmask(struct irq_data *d)
+{
+ struct lpc18xx_gpio_pin_ic *ic = d->chip_data;
+ u32 type = irqd_get_trigger_type(d);
+
+ raw_spin_lock(&ic->lock);
+
+ if (type & IRQ_TYPE_LEVEL_MASK || type & IRQ_TYPE_EDGE_RISING)
+ lpc18xx_gpio_pin_ic_set(ic, d->hwirq,
+ LPC18XX_GPIO_PIN_IC_SIENR);
+
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ lpc18xx_gpio_pin_ic_set(ic, d->hwirq,
+ LPC18XX_GPIO_PIN_IC_SIENF);
+
+ raw_spin_unlock(&ic->lock);
+
+ irq_chip_unmask_parent(d);
+}
+
+static void lpc18xx_gpio_pin_ic_eoi(struct irq_data *d)
+{
+ struct lpc18xx_gpio_pin_ic *ic = d->chip_data;
+ u32 type = irqd_get_trigger_type(d);
+
+ raw_spin_lock(&ic->lock);
+
+ if (type & IRQ_TYPE_EDGE_BOTH)
+ lpc18xx_gpio_pin_ic_set(ic, d->hwirq,
+ LPC18XX_GPIO_PIN_IC_IST);
+
+ raw_spin_unlock(&ic->lock);
+
+ irq_chip_eoi_parent(d);
+}
+
+static int lpc18xx_gpio_pin_ic_set_type(struct irq_data *d, unsigned int type)
+{
+ struct lpc18xx_gpio_pin_ic *ic = d->chip_data;
+
+ raw_spin_lock(&ic->lock);
+
+ if (type & IRQ_TYPE_LEVEL_HIGH) {
+ lpc18xx_gpio_pin_ic_isel(ic, d->hwirq, true);
+ lpc18xx_gpio_pin_ic_set(ic, d->hwirq,
+ LPC18XX_GPIO_PIN_IC_SIENF);
+ } else if (type & IRQ_TYPE_LEVEL_LOW) {
+ lpc18xx_gpio_pin_ic_isel(ic, d->hwirq, true);
+ lpc18xx_gpio_pin_ic_set(ic, d->hwirq,
+ LPC18XX_GPIO_PIN_IC_CIENF);
+ } else {
+ lpc18xx_gpio_pin_ic_isel(ic, d->hwirq, false);
+ }
+
+ raw_spin_unlock(&ic->lock);
+
+ return 0;
+}
+
+static struct irq_chip lpc18xx_gpio_pin_ic = {
+ .name = "LPC18xx GPIO pin",
+ .irq_mask = lpc18xx_gpio_pin_ic_mask,
+ .irq_unmask = lpc18xx_gpio_pin_ic_unmask,
+ .irq_eoi = lpc18xx_gpio_pin_ic_eoi,
+ .irq_set_type = lpc18xx_gpio_pin_ic_set_type,
+ .flags = IRQCHIP_SET_TYPE_MASKED,
+};
+
+static int lpc18xx_gpio_pin_ic_domain_alloc(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs, void *data)
+{
+ struct irq_fwspec parent_fwspec, *fwspec = data;
+ struct lpc18xx_gpio_pin_ic *ic = domain->host_data;
+ irq_hw_number_t hwirq;
+ int ret;
+
+ if (nr_irqs != 1)
+ return -EINVAL;
+
+ hwirq = fwspec->param[0];
+ if (hwirq >= NR_LPC18XX_GPIO_PIN_IC_IRQS)
+ return -EINVAL;
+
+ /*
+ * All LPC18xx/LPC43xx GPIO pin hardware interrupts are translated
+ * into edge interrupts 32...39 on parent Cortex-M3/M4 NVIC
+ */
+ parent_fwspec.fwnode = domain->parent->fwnode;
+ parent_fwspec.param_count = 1;
+ parent_fwspec.param[0] = hwirq + 32;
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec);
+ if (ret < 0) {
+ pr_err("failed to allocate parent irq %u: %d\n",
+ parent_fwspec.param[0], ret);
+ return ret;
+ }
+
+ return irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
+ &lpc18xx_gpio_pin_ic, ic);
+}
+
+static const struct irq_domain_ops lpc18xx_gpio_pin_ic_domain_ops = {
+ .alloc = lpc18xx_gpio_pin_ic_domain_alloc,
+ .xlate = irq_domain_xlate_twocell,
+ .free = irq_domain_free_irqs_common,
+};
+
+static int lpc18xx_gpio_pin_ic_probe(struct lpc18xx_gpio_chip *gc)
+{
+ struct device *dev = gc->gpio.parent;
+ struct irq_domain *parent_domain;
+ struct device_node *parent_node;
+ struct lpc18xx_gpio_pin_ic *ic;
+ struct resource res;
+ int ret, index;
+
+ parent_node = of_irq_find_parent(dev->of_node);
+ if (!parent_node)
+ return -ENXIO;
+
+ parent_domain = irq_find_host(parent_node);
+ of_node_put(parent_node);
+ if (!parent_domain)
+ return -ENXIO;
+
+ ic = devm_kzalloc(dev, sizeof(*ic), GFP_KERNEL);
+ if (!ic)
+ return -ENOMEM;
+
+ index = of_property_match_string(dev->of_node, "reg-names",
+ "gpio-pin-ic");
+ if (index < 0) {
+ ret = -ENODEV;
+ goto free_ic;
+ }
+
+ ret = of_address_to_resource(dev->of_node, index, &res);
+ if (ret < 0)
+ goto free_ic;
+
+ ic->base = devm_ioremap_resource(dev, &res);
+ if (IS_ERR(ic->base)) {
+ ret = PTR_ERR(ic->base);
+ goto free_ic;
+ }
+
+ raw_spin_lock_init(&ic->lock);
+
+ ic->domain = irq_domain_add_hierarchy(parent_domain, 0,
+ NR_LPC18XX_GPIO_PIN_IC_IRQS,
+ dev->of_node,
+ &lpc18xx_gpio_pin_ic_domain_ops,
+ ic);
+ if (!ic->domain) {
+ pr_err("unable to add irq domain\n");
+ ret = -ENODEV;
+ goto free_iomap;
+ }
+
+ gc->pin_ic = ic;
+
+ return 0;
+
+free_iomap:
+ devm_iounmap(dev, ic->base);
+free_ic:
+ devm_kfree(dev, ic);
+
+ return ret;
+}
+
+static void lpc18xx_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct lpc18xx_gpio_chip *gc = gpiochip_get_data(chip);
+ writeb(value ? 1 : 0, gc->base + offset);
+}
+
+static int lpc18xx_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct lpc18xx_gpio_chip *gc = gpiochip_get_data(chip);
+ return !!readb(gc->base + offset);
+}
+
+static int lpc18xx_gpio_direction(struct gpio_chip *chip, unsigned offset,
+ bool out)
+{
+ struct lpc18xx_gpio_chip *gc = gpiochip_get_data(chip);
+ unsigned long flags;
+ u32 port, pin, dir;
+
+ port = offset / LPC18XX_PINS_PER_PORT;
+ pin = offset % LPC18XX_PINS_PER_PORT;
+
+ spin_lock_irqsave(&gc->lock, flags);
+ dir = readl(gc->base + LPC18XX_REG_DIR(port));
+ if (out)
+ dir |= BIT(pin);
+ else
+ dir &= ~BIT(pin);
+ writel(dir, gc->base + LPC18XX_REG_DIR(port));
+ spin_unlock_irqrestore(&gc->lock, flags);
+
+ return 0;
+}
+
+static int lpc18xx_gpio_direction_input(struct gpio_chip *chip,
+ unsigned offset)
+{
+ return lpc18xx_gpio_direction(chip, offset, false);
+}
+
+static int lpc18xx_gpio_direction_output(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ lpc18xx_gpio_set(chip, offset, value);
+ return lpc18xx_gpio_direction(chip, offset, true);
+}
+
+static const struct gpio_chip lpc18xx_chip = {
+ .label = "lpc18xx/43xx-gpio",
+ .request = gpiochip_generic_request,
+ .free = gpiochip_generic_free,
+ .direction_input = lpc18xx_gpio_direction_input,
+ .direction_output = lpc18xx_gpio_direction_output,
+ .set = lpc18xx_gpio_set,
+ .get = lpc18xx_gpio_get,
+ .ngpio = LPC18XX_MAX_PORTS * LPC18XX_PINS_PER_PORT,
+ .owner = THIS_MODULE,
+};
+
+static int lpc18xx_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct lpc18xx_gpio_chip *gc;
+ int index, ret;
+
+ gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL);
+ if (!gc)
+ return -ENOMEM;
+
+ gc->gpio = lpc18xx_chip;
+ platform_set_drvdata(pdev, gc);
+
+ index = of_property_match_string(dev->of_node, "reg-names", "gpio");
+ if (index < 0) {
+ /* To support backward compatibility take the first resource */
+ gc->base = devm_platform_ioremap_resource(pdev, 0);
+ } else {
+ struct resource res;
+
+ ret = of_address_to_resource(dev->of_node, index, &res);
+ if (ret < 0)
+ return ret;
+
+ gc->base = devm_ioremap_resource(dev, &res);
+ }
+ if (IS_ERR(gc->base))
+ return PTR_ERR(gc->base);
+
+ gc->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(gc->clk)) {
+ dev_err(dev, "input clock not found\n");
+ return PTR_ERR(gc->clk);
+ }
+
+ ret = clk_prepare_enable(gc->clk);
+ if (ret) {
+ dev_err(dev, "unable to enable clock\n");
+ return ret;
+ }
+
+ spin_lock_init(&gc->lock);
+
+ gc->gpio.parent = dev;
+
+ ret = devm_gpiochip_add_data(dev, &gc->gpio, gc);
+ if (ret) {
+ dev_err(dev, "failed to add gpio chip\n");
+ clk_disable_unprepare(gc->clk);
+ return ret;
+ }
+
+ /* On error GPIO pin interrupt controller just won't be registered */
+ lpc18xx_gpio_pin_ic_probe(gc);
+
+ return 0;
+}
+
+static int lpc18xx_gpio_remove(struct platform_device *pdev)
+{
+ struct lpc18xx_gpio_chip *gc = platform_get_drvdata(pdev);
+
+ if (gc->pin_ic)
+ irq_domain_remove(gc->pin_ic->domain);
+
+ clk_disable_unprepare(gc->clk);
+
+ return 0;
+}
+
+static const struct of_device_id lpc18xx_gpio_match[] = {
+ { .compatible = "nxp,lpc1850-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lpc18xx_gpio_match);
+
+static struct platform_driver lpc18xx_gpio_driver = {
+ .probe = lpc18xx_gpio_probe,
+ .remove = lpc18xx_gpio_remove,
+ .driver = {
+ .name = "lpc18xx-gpio",
+ .of_match_table = lpc18xx_gpio_match,
+ },
+};
+module_platform_driver(lpc18xx_gpio_driver);
+
+MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>");
+MODULE_AUTHOR("Vladimir Zapolskiy <vz@mleia.com>");
+MODULE_DESCRIPTION("GPIO driver for LPC18xx/43xx");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-lpc32xx.c b/drivers/gpio/gpio-lpc32xx.c
new file mode 100644
index 0000000000..5ef8af8249
--- /dev/null
+++ b/drivers/gpio/gpio-lpc32xx.c
@@ -0,0 +1,545 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * GPIO driver for LPC32xx SoC
+ *
+ * Author: Kevin Wells <kevin.wells@nxp.com>
+ *
+ * Copyright (C) 2010 NXP Semiconductors
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+
+#define LPC32XX_GPIO_P3_INP_STATE (0x000)
+#define LPC32XX_GPIO_P3_OUTP_SET (0x004)
+#define LPC32XX_GPIO_P3_OUTP_CLR (0x008)
+#define LPC32XX_GPIO_P3_OUTP_STATE (0x00C)
+#define LPC32XX_GPIO_P2_DIR_SET (0x010)
+#define LPC32XX_GPIO_P2_DIR_CLR (0x014)
+#define LPC32XX_GPIO_P2_DIR_STATE (0x018)
+#define LPC32XX_GPIO_P2_INP_STATE (0x01C)
+#define LPC32XX_GPIO_P2_OUTP_SET (0x020)
+#define LPC32XX_GPIO_P2_OUTP_CLR (0x024)
+#define LPC32XX_GPIO_P2_MUX_SET (0x028)
+#define LPC32XX_GPIO_P2_MUX_CLR (0x02C)
+#define LPC32XX_GPIO_P2_MUX_STATE (0x030)
+#define LPC32XX_GPIO_P0_INP_STATE (0x040)
+#define LPC32XX_GPIO_P0_OUTP_SET (0x044)
+#define LPC32XX_GPIO_P0_OUTP_CLR (0x048)
+#define LPC32XX_GPIO_P0_OUTP_STATE (0x04C)
+#define LPC32XX_GPIO_P0_DIR_SET (0x050)
+#define LPC32XX_GPIO_P0_DIR_CLR (0x054)
+#define LPC32XX_GPIO_P0_DIR_STATE (0x058)
+#define LPC32XX_GPIO_P1_INP_STATE (0x060)
+#define LPC32XX_GPIO_P1_OUTP_SET (0x064)
+#define LPC32XX_GPIO_P1_OUTP_CLR (0x068)
+#define LPC32XX_GPIO_P1_OUTP_STATE (0x06C)
+#define LPC32XX_GPIO_P1_DIR_SET (0x070)
+#define LPC32XX_GPIO_P1_DIR_CLR (0x074)
+#define LPC32XX_GPIO_P1_DIR_STATE (0x078)
+
+#define GPIO012_PIN_TO_BIT(x) (1 << (x))
+#define GPIO3_PIN_TO_BIT(x) (1 << ((x) + 25))
+#define GPO3_PIN_TO_BIT(x) (1 << (x))
+#define GPIO012_PIN_IN_SEL(x, y) (((x) >> (y)) & 1)
+#define GPIO3_PIN_IN_SHIFT(x) ((x) == 5 ? 24 : 10 + (x))
+#define GPIO3_PIN_IN_SEL(x, y) (((x) >> GPIO3_PIN_IN_SHIFT(y)) & 1)
+#define GPIO3_PIN5_IN_SEL(x) (((x) >> 24) & 1)
+#define GPI3_PIN_IN_SEL(x, y) (((x) >> (y)) & 1)
+#define GPO3_PIN_IN_SEL(x, y) (((x) >> (y)) & 1)
+
+#define LPC32XX_GPIO_P0_MAX 8
+#define LPC32XX_GPIO_P1_MAX 24
+#define LPC32XX_GPIO_P2_MAX 13
+#define LPC32XX_GPIO_P3_MAX 6
+#define LPC32XX_GPI_P3_MAX 29
+#define LPC32XX_GPO_P3_MAX 24
+
+#define LPC32XX_GPIO_P0_GRP 0
+#define LPC32XX_GPIO_P1_GRP (LPC32XX_GPIO_P0_GRP + LPC32XX_GPIO_P0_MAX)
+#define LPC32XX_GPIO_P2_GRP (LPC32XX_GPIO_P1_GRP + LPC32XX_GPIO_P1_MAX)
+#define LPC32XX_GPIO_P3_GRP (LPC32XX_GPIO_P2_GRP + LPC32XX_GPIO_P2_MAX)
+#define LPC32XX_GPI_P3_GRP (LPC32XX_GPIO_P3_GRP + LPC32XX_GPIO_P3_MAX)
+#define LPC32XX_GPO_P3_GRP (LPC32XX_GPI_P3_GRP + LPC32XX_GPI_P3_MAX)
+
+struct gpio_regs {
+ unsigned long inp_state;
+ unsigned long outp_state;
+ unsigned long outp_set;
+ unsigned long outp_clr;
+ unsigned long dir_set;
+ unsigned long dir_clr;
+};
+
+/*
+ * GPIO names
+ */
+static const char *gpio_p0_names[LPC32XX_GPIO_P0_MAX] = {
+ "p0.0", "p0.1", "p0.2", "p0.3",
+ "p0.4", "p0.5", "p0.6", "p0.7"
+};
+
+static const char *gpio_p1_names[LPC32XX_GPIO_P1_MAX] = {
+ "p1.0", "p1.1", "p1.2", "p1.3",
+ "p1.4", "p1.5", "p1.6", "p1.7",
+ "p1.8", "p1.9", "p1.10", "p1.11",
+ "p1.12", "p1.13", "p1.14", "p1.15",
+ "p1.16", "p1.17", "p1.18", "p1.19",
+ "p1.20", "p1.21", "p1.22", "p1.23",
+};
+
+static const char *gpio_p2_names[LPC32XX_GPIO_P2_MAX] = {
+ "p2.0", "p2.1", "p2.2", "p2.3",
+ "p2.4", "p2.5", "p2.6", "p2.7",
+ "p2.8", "p2.9", "p2.10", "p2.11",
+ "p2.12"
+};
+
+static const char *gpio_p3_names[LPC32XX_GPIO_P3_MAX] = {
+ "gpio00", "gpio01", "gpio02", "gpio03",
+ "gpio04", "gpio05"
+};
+
+static const char *gpi_p3_names[LPC32XX_GPI_P3_MAX] = {
+ "gpi00", "gpi01", "gpi02", "gpi03",
+ "gpi04", "gpi05", "gpi06", "gpi07",
+ "gpi08", "gpi09", NULL, NULL,
+ NULL, NULL, NULL, "gpi15",
+ "gpi16", "gpi17", "gpi18", "gpi19",
+ "gpi20", "gpi21", "gpi22", "gpi23",
+ "gpi24", "gpi25", "gpi26", "gpi27",
+ "gpi28"
+};
+
+static const char *gpo_p3_names[LPC32XX_GPO_P3_MAX] = {
+ "gpo00", "gpo01", "gpo02", "gpo03",
+ "gpo04", "gpo05", "gpo06", "gpo07",
+ "gpo08", "gpo09", "gpo10", "gpo11",
+ "gpo12", "gpo13", "gpo14", "gpo15",
+ "gpo16", "gpo17", "gpo18", "gpo19",
+ "gpo20", "gpo21", "gpo22", "gpo23"
+};
+
+static struct gpio_regs gpio_grp_regs_p0 = {
+ .inp_state = LPC32XX_GPIO_P0_INP_STATE,
+ .outp_set = LPC32XX_GPIO_P0_OUTP_SET,
+ .outp_clr = LPC32XX_GPIO_P0_OUTP_CLR,
+ .dir_set = LPC32XX_GPIO_P0_DIR_SET,
+ .dir_clr = LPC32XX_GPIO_P0_DIR_CLR,
+};
+
+static struct gpio_regs gpio_grp_regs_p1 = {
+ .inp_state = LPC32XX_GPIO_P1_INP_STATE,
+ .outp_set = LPC32XX_GPIO_P1_OUTP_SET,
+ .outp_clr = LPC32XX_GPIO_P1_OUTP_CLR,
+ .dir_set = LPC32XX_GPIO_P1_DIR_SET,
+ .dir_clr = LPC32XX_GPIO_P1_DIR_CLR,
+};
+
+static struct gpio_regs gpio_grp_regs_p2 = {
+ .inp_state = LPC32XX_GPIO_P2_INP_STATE,
+ .outp_set = LPC32XX_GPIO_P2_OUTP_SET,
+ .outp_clr = LPC32XX_GPIO_P2_OUTP_CLR,
+ .dir_set = LPC32XX_GPIO_P2_DIR_SET,
+ .dir_clr = LPC32XX_GPIO_P2_DIR_CLR,
+};
+
+static struct gpio_regs gpio_grp_regs_p3 = {
+ .inp_state = LPC32XX_GPIO_P3_INP_STATE,
+ .outp_state = LPC32XX_GPIO_P3_OUTP_STATE,
+ .outp_set = LPC32XX_GPIO_P3_OUTP_SET,
+ .outp_clr = LPC32XX_GPIO_P3_OUTP_CLR,
+ .dir_set = LPC32XX_GPIO_P2_DIR_SET,
+ .dir_clr = LPC32XX_GPIO_P2_DIR_CLR,
+};
+
+struct lpc32xx_gpio_chip {
+ struct gpio_chip chip;
+ struct gpio_regs *gpio_grp;
+ void __iomem *reg_base;
+};
+
+static inline u32 gpreg_read(struct lpc32xx_gpio_chip *group, unsigned long offset)
+{
+ return __raw_readl(group->reg_base + offset);
+}
+
+static inline void gpreg_write(struct lpc32xx_gpio_chip *group, u32 val, unsigned long offset)
+{
+ __raw_writel(val, group->reg_base + offset);
+}
+
+static void __set_gpio_dir_p012(struct lpc32xx_gpio_chip *group,
+ unsigned pin, int input)
+{
+ if (input)
+ gpreg_write(group, GPIO012_PIN_TO_BIT(pin),
+ group->gpio_grp->dir_clr);
+ else
+ gpreg_write(group, GPIO012_PIN_TO_BIT(pin),
+ group->gpio_grp->dir_set);
+}
+
+static void __set_gpio_dir_p3(struct lpc32xx_gpio_chip *group,
+ unsigned pin, int input)
+{
+ u32 u = GPIO3_PIN_TO_BIT(pin);
+
+ if (input)
+ gpreg_write(group, u, group->gpio_grp->dir_clr);
+ else
+ gpreg_write(group, u, group->gpio_grp->dir_set);
+}
+
+static void __set_gpio_level_p012(struct lpc32xx_gpio_chip *group,
+ unsigned pin, int high)
+{
+ if (high)
+ gpreg_write(group, GPIO012_PIN_TO_BIT(pin),
+ group->gpio_grp->outp_set);
+ else
+ gpreg_write(group, GPIO012_PIN_TO_BIT(pin),
+ group->gpio_grp->outp_clr);
+}
+
+static void __set_gpio_level_p3(struct lpc32xx_gpio_chip *group,
+ unsigned pin, int high)
+{
+ u32 u = GPIO3_PIN_TO_BIT(pin);
+
+ if (high)
+ gpreg_write(group, u, group->gpio_grp->outp_set);
+ else
+ gpreg_write(group, u, group->gpio_grp->outp_clr);
+}
+
+static void __set_gpo_level_p3(struct lpc32xx_gpio_chip *group,
+ unsigned pin, int high)
+{
+ if (high)
+ gpreg_write(group, GPO3_PIN_TO_BIT(pin), group->gpio_grp->outp_set);
+ else
+ gpreg_write(group, GPO3_PIN_TO_BIT(pin), group->gpio_grp->outp_clr);
+}
+
+static int __get_gpio_state_p012(struct lpc32xx_gpio_chip *group,
+ unsigned pin)
+{
+ return GPIO012_PIN_IN_SEL(gpreg_read(group, group->gpio_grp->inp_state),
+ pin);
+}
+
+static int __get_gpio_state_p3(struct lpc32xx_gpio_chip *group,
+ unsigned pin)
+{
+ int state = gpreg_read(group, group->gpio_grp->inp_state);
+
+ /*
+ * P3 GPIO pin input mapping is not contiguous, GPIOP3-0..4 is mapped
+ * to bits 10..14, while GPIOP3-5 is mapped to bit 24.
+ */
+ return GPIO3_PIN_IN_SEL(state, pin);
+}
+
+static int __get_gpi_state_p3(struct lpc32xx_gpio_chip *group,
+ unsigned pin)
+{
+ return GPI3_PIN_IN_SEL(gpreg_read(group, group->gpio_grp->inp_state), pin);
+}
+
+static int __get_gpo_state_p3(struct lpc32xx_gpio_chip *group,
+ unsigned pin)
+{
+ return GPO3_PIN_IN_SEL(gpreg_read(group, group->gpio_grp->outp_state), pin);
+}
+
+/*
+ * GPIO primitives.
+ */
+static int lpc32xx_gpio_dir_input_p012(struct gpio_chip *chip,
+ unsigned pin)
+{
+ struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
+
+ __set_gpio_dir_p012(group, pin, 1);
+
+ return 0;
+}
+
+static int lpc32xx_gpio_dir_input_p3(struct gpio_chip *chip,
+ unsigned pin)
+{
+ struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
+
+ __set_gpio_dir_p3(group, pin, 1);
+
+ return 0;
+}
+
+static int lpc32xx_gpio_dir_in_always(struct gpio_chip *chip,
+ unsigned pin)
+{
+ return 0;
+}
+
+static int lpc32xx_gpio_get_value_p012(struct gpio_chip *chip, unsigned pin)
+{
+ struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
+
+ return !!__get_gpio_state_p012(group, pin);
+}
+
+static int lpc32xx_gpio_get_value_p3(struct gpio_chip *chip, unsigned pin)
+{
+ struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
+
+ return !!__get_gpio_state_p3(group, pin);
+}
+
+static int lpc32xx_gpi_get_value(struct gpio_chip *chip, unsigned pin)
+{
+ struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
+
+ return !!__get_gpi_state_p3(group, pin);
+}
+
+static int lpc32xx_gpio_dir_output_p012(struct gpio_chip *chip, unsigned pin,
+ int value)
+{
+ struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
+
+ __set_gpio_level_p012(group, pin, value);
+ __set_gpio_dir_p012(group, pin, 0);
+
+ return 0;
+}
+
+static int lpc32xx_gpio_dir_output_p3(struct gpio_chip *chip, unsigned pin,
+ int value)
+{
+ struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
+
+ __set_gpio_level_p3(group, pin, value);
+ __set_gpio_dir_p3(group, pin, 0);
+
+ return 0;
+}
+
+static int lpc32xx_gpio_dir_out_always(struct gpio_chip *chip, unsigned pin,
+ int value)
+{
+ struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
+
+ __set_gpo_level_p3(group, pin, value);
+ return 0;
+}
+
+static void lpc32xx_gpio_set_value_p012(struct gpio_chip *chip, unsigned pin,
+ int value)
+{
+ struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
+
+ __set_gpio_level_p012(group, pin, value);
+}
+
+static void lpc32xx_gpio_set_value_p3(struct gpio_chip *chip, unsigned pin,
+ int value)
+{
+ struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
+
+ __set_gpio_level_p3(group, pin, value);
+}
+
+static void lpc32xx_gpo_set_value(struct gpio_chip *chip, unsigned pin,
+ int value)
+{
+ struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
+
+ __set_gpo_level_p3(group, pin, value);
+}
+
+static int lpc32xx_gpo_get_value(struct gpio_chip *chip, unsigned pin)
+{
+ struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
+
+ return !!__get_gpo_state_p3(group, pin);
+}
+
+static int lpc32xx_gpio_request(struct gpio_chip *chip, unsigned pin)
+{
+ if (pin < chip->ngpio)
+ return 0;
+
+ return -EINVAL;
+}
+
+static int lpc32xx_gpio_to_irq_p01(struct gpio_chip *chip, unsigned offset)
+{
+ return -ENXIO;
+}
+
+static int lpc32xx_gpio_to_irq_gpio_p3(struct gpio_chip *chip, unsigned offset)
+{
+ return -ENXIO;
+}
+
+static int lpc32xx_gpio_to_irq_gpi_p3(struct gpio_chip *chip, unsigned offset)
+{
+ return -ENXIO;
+}
+
+static struct lpc32xx_gpio_chip lpc32xx_gpiochip[] = {
+ {
+ .chip = {
+ .label = "gpio_p0",
+ .direction_input = lpc32xx_gpio_dir_input_p012,
+ .get = lpc32xx_gpio_get_value_p012,
+ .direction_output = lpc32xx_gpio_dir_output_p012,
+ .set = lpc32xx_gpio_set_value_p012,
+ .request = lpc32xx_gpio_request,
+ .to_irq = lpc32xx_gpio_to_irq_p01,
+ .base = LPC32XX_GPIO_P0_GRP,
+ .ngpio = LPC32XX_GPIO_P0_MAX,
+ .names = gpio_p0_names,
+ .can_sleep = false,
+ },
+ .gpio_grp = &gpio_grp_regs_p0,
+ },
+ {
+ .chip = {
+ .label = "gpio_p1",
+ .direction_input = lpc32xx_gpio_dir_input_p012,
+ .get = lpc32xx_gpio_get_value_p012,
+ .direction_output = lpc32xx_gpio_dir_output_p012,
+ .set = lpc32xx_gpio_set_value_p012,
+ .request = lpc32xx_gpio_request,
+ .to_irq = lpc32xx_gpio_to_irq_p01,
+ .base = LPC32XX_GPIO_P1_GRP,
+ .ngpio = LPC32XX_GPIO_P1_MAX,
+ .names = gpio_p1_names,
+ .can_sleep = false,
+ },
+ .gpio_grp = &gpio_grp_regs_p1,
+ },
+ {
+ .chip = {
+ .label = "gpio_p2",
+ .direction_input = lpc32xx_gpio_dir_input_p012,
+ .get = lpc32xx_gpio_get_value_p012,
+ .direction_output = lpc32xx_gpio_dir_output_p012,
+ .set = lpc32xx_gpio_set_value_p012,
+ .request = lpc32xx_gpio_request,
+ .base = LPC32XX_GPIO_P2_GRP,
+ .ngpio = LPC32XX_GPIO_P2_MAX,
+ .names = gpio_p2_names,
+ .can_sleep = false,
+ },
+ .gpio_grp = &gpio_grp_regs_p2,
+ },
+ {
+ .chip = {
+ .label = "gpio_p3",
+ .direction_input = lpc32xx_gpio_dir_input_p3,
+ .get = lpc32xx_gpio_get_value_p3,
+ .direction_output = lpc32xx_gpio_dir_output_p3,
+ .set = lpc32xx_gpio_set_value_p3,
+ .request = lpc32xx_gpio_request,
+ .to_irq = lpc32xx_gpio_to_irq_gpio_p3,
+ .base = LPC32XX_GPIO_P3_GRP,
+ .ngpio = LPC32XX_GPIO_P3_MAX,
+ .names = gpio_p3_names,
+ .can_sleep = false,
+ },
+ .gpio_grp = &gpio_grp_regs_p3,
+ },
+ {
+ .chip = {
+ .label = "gpi_p3",
+ .direction_input = lpc32xx_gpio_dir_in_always,
+ .get = lpc32xx_gpi_get_value,
+ .request = lpc32xx_gpio_request,
+ .to_irq = lpc32xx_gpio_to_irq_gpi_p3,
+ .base = LPC32XX_GPI_P3_GRP,
+ .ngpio = LPC32XX_GPI_P3_MAX,
+ .names = gpi_p3_names,
+ .can_sleep = false,
+ },
+ .gpio_grp = &gpio_grp_regs_p3,
+ },
+ {
+ .chip = {
+ .label = "gpo_p3",
+ .direction_output = lpc32xx_gpio_dir_out_always,
+ .set = lpc32xx_gpo_set_value,
+ .get = lpc32xx_gpo_get_value,
+ .request = lpc32xx_gpio_request,
+ .base = LPC32XX_GPO_P3_GRP,
+ .ngpio = LPC32XX_GPO_P3_MAX,
+ .names = gpo_p3_names,
+ .can_sleep = false,
+ },
+ .gpio_grp = &gpio_grp_regs_p3,
+ },
+};
+
+static int lpc32xx_of_xlate(struct gpio_chip *gc,
+ const struct of_phandle_args *gpiospec, u32 *flags)
+{
+ /* Is this the correct bank? */
+ u32 bank = gpiospec->args[0];
+ if ((bank >= ARRAY_SIZE(lpc32xx_gpiochip) ||
+ (gc != &lpc32xx_gpiochip[bank].chip)))
+ return -EINVAL;
+
+ if (flags)
+ *flags = gpiospec->args[2];
+ return gpiospec->args[1];
+}
+
+static int lpc32xx_gpio_probe(struct platform_device *pdev)
+{
+ int i;
+ void __iomem *reg_base;
+
+ reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(reg_base))
+ return PTR_ERR(reg_base);
+
+ for (i = 0; i < ARRAY_SIZE(lpc32xx_gpiochip); i++) {
+ lpc32xx_gpiochip[i].chip.parent = &pdev->dev;
+ if (pdev->dev.of_node) {
+ lpc32xx_gpiochip[i].chip.of_xlate = lpc32xx_of_xlate;
+ lpc32xx_gpiochip[i].chip.of_gpio_n_cells = 3;
+ lpc32xx_gpiochip[i].reg_base = reg_base;
+ }
+ devm_gpiochip_add_data(&pdev->dev, &lpc32xx_gpiochip[i].chip,
+ &lpc32xx_gpiochip[i]);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id lpc32xx_gpio_of_match[] = {
+ { .compatible = "nxp,lpc3220-gpio", },
+ { },
+};
+
+static struct platform_driver lpc32xx_gpio_driver = {
+ .driver = {
+ .name = "lpc32xx-gpio",
+ .of_match_table = lpc32xx_gpio_of_match,
+ },
+ .probe = lpc32xx_gpio_probe,
+};
+
+module_platform_driver(lpc32xx_gpio_driver);
+
+MODULE_AUTHOR("Kevin Wells <kevin.wells@nxp.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("GPIO driver for LPC32xx SoC");
diff --git a/drivers/gpio/gpio-madera.c b/drivers/gpio/gpio-madera.c
new file mode 100644
index 0000000000..8f38303fcb
--- /dev/null
+++ b/drivers/gpio/gpio-madera.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO support for Cirrus Logic Madera codecs
+ *
+ * Copyright (C) 2015-2018 Cirrus Logic
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <linux/mfd/madera/core.h>
+#include <linux/mfd/madera/pdata.h>
+#include <linux/mfd/madera/registers.h>
+
+struct madera_gpio {
+ struct madera *madera;
+ /* storage space for the gpio_chip we're using */
+ struct gpio_chip gpio_chip;
+};
+
+static int madera_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct madera_gpio *madera_gpio = gpiochip_get_data(chip);
+ struct madera *madera = madera_gpio->madera;
+ unsigned int reg_offset = 2 * offset;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(madera->regmap, MADERA_GPIO1_CTRL_2 + reg_offset,
+ &val);
+ if (ret < 0)
+ return ret;
+
+ if (val & MADERA_GP1_DIR_MASK)
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int madera_gpio_direction_in(struct gpio_chip *chip, unsigned int offset)
+{
+ struct madera_gpio *madera_gpio = gpiochip_get_data(chip);
+ struct madera *madera = madera_gpio->madera;
+ unsigned int reg_offset = 2 * offset;
+
+ return regmap_update_bits(madera->regmap,
+ MADERA_GPIO1_CTRL_2 + reg_offset,
+ MADERA_GP1_DIR_MASK, MADERA_GP1_DIR);
+}
+
+static int madera_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct madera_gpio *madera_gpio = gpiochip_get_data(chip);
+ struct madera *madera = madera_gpio->madera;
+ unsigned int reg_offset = 2 * offset;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(madera->regmap, MADERA_GPIO1_CTRL_1 + reg_offset,
+ &val);
+ if (ret < 0)
+ return ret;
+
+ return !!(val & MADERA_GP1_LVL_MASK);
+}
+
+static int madera_gpio_direction_out(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct madera_gpio *madera_gpio = gpiochip_get_data(chip);
+ struct madera *madera = madera_gpio->madera;
+ unsigned int reg_offset = 2 * offset;
+ unsigned int reg_val = value ? MADERA_GP1_LVL : 0;
+ int ret;
+
+ ret = regmap_update_bits(madera->regmap,
+ MADERA_GPIO1_CTRL_2 + reg_offset,
+ MADERA_GP1_DIR_MASK, 0);
+ if (ret < 0)
+ return ret;
+
+ return regmap_update_bits(madera->regmap,
+ MADERA_GPIO1_CTRL_1 + reg_offset,
+ MADERA_GP1_LVL_MASK, reg_val);
+}
+
+static void madera_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct madera_gpio *madera_gpio = gpiochip_get_data(chip);
+ struct madera *madera = madera_gpio->madera;
+ unsigned int reg_offset = 2 * offset;
+ unsigned int reg_val = value ? MADERA_GP1_LVL : 0;
+ int ret;
+
+ ret = regmap_update_bits(madera->regmap,
+ MADERA_GPIO1_CTRL_1 + reg_offset,
+ MADERA_GP1_LVL_MASK, reg_val);
+
+ /* set() doesn't return an error so log a warning */
+ if (ret)
+ dev_warn(madera->dev, "Failed to write to 0x%x (%d)\n",
+ MADERA_GPIO1_CTRL_1 + reg_offset, ret);
+}
+
+static const struct gpio_chip madera_gpio_chip = {
+ .label = "madera",
+ .owner = THIS_MODULE,
+ .request = gpiochip_generic_request,
+ .free = gpiochip_generic_free,
+ .get_direction = madera_gpio_get_direction,
+ .direction_input = madera_gpio_direction_in,
+ .get = madera_gpio_get,
+ .direction_output = madera_gpio_direction_out,
+ .set = madera_gpio_set,
+ .set_config = gpiochip_generic_config,
+ .can_sleep = true,
+};
+
+static int madera_gpio_probe(struct platform_device *pdev)
+{
+ struct madera *madera = dev_get_drvdata(pdev->dev.parent);
+ struct madera_pdata *pdata = &madera->pdata;
+ struct madera_gpio *madera_gpio;
+ int ret;
+
+ madera_gpio = devm_kzalloc(&pdev->dev, sizeof(*madera_gpio),
+ GFP_KERNEL);
+ if (!madera_gpio)
+ return -ENOMEM;
+
+ madera_gpio->madera = madera;
+
+ /* Construct suitable gpio_chip from the template in madera_gpio_chip */
+ madera_gpio->gpio_chip = madera_gpio_chip;
+ madera_gpio->gpio_chip.parent = pdev->dev.parent;
+
+ switch (madera->type) {
+ case CS47L15:
+ madera_gpio->gpio_chip.ngpio = CS47L15_NUM_GPIOS;
+ break;
+ case CS47L35:
+ madera_gpio->gpio_chip.ngpio = CS47L35_NUM_GPIOS;
+ break;
+ case CS47L85:
+ case WM1840:
+ madera_gpio->gpio_chip.ngpio = CS47L85_NUM_GPIOS;
+ break;
+ case CS47L90:
+ case CS47L91:
+ madera_gpio->gpio_chip.ngpio = CS47L90_NUM_GPIOS;
+ break;
+ case CS42L92:
+ case CS47L92:
+ case CS47L93:
+ madera_gpio->gpio_chip.ngpio = CS47L92_NUM_GPIOS;
+ break;
+ default:
+ dev_err(&pdev->dev, "Unknown chip variant %d\n", madera->type);
+ return -EINVAL;
+ }
+
+ /* We want to be usable on systems that don't use devicetree or acpi */
+ if (pdata->gpio_base)
+ madera_gpio->gpio_chip.base = pdata->gpio_base;
+ else
+ madera_gpio->gpio_chip.base = -1;
+
+ ret = devm_gpiochip_add_data(&pdev->dev,
+ &madera_gpio->gpio_chip,
+ madera_gpio);
+ if (ret < 0) {
+ dev_dbg(&pdev->dev, "Could not register gpiochip, %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * This is part of a composite MFD device which can only be used with
+ * the corresponding pinctrl driver. On all supported silicon the GPIO
+ * to pinctrl mapping is fixed in the silicon, so we register it
+ * explicitly instead of requiring a redundant gpio-ranges in the
+ * devicetree.
+ * In any case we also want to work on systems that don't use devicetree
+ * or acpi.
+ */
+ ret = gpiochip_add_pin_range(&madera_gpio->gpio_chip, "madera-pinctrl",
+ 0, 0, madera_gpio->gpio_chip.ngpio);
+ if (ret) {
+ dev_dbg(&pdev->dev, "Failed to add pin range (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver madera_gpio_driver = {
+ .driver = {
+ .name = "madera-gpio",
+ },
+ .probe = madera_gpio_probe,
+};
+
+module_platform_driver(madera_gpio_driver);
+
+MODULE_SOFTDEP("pre: pinctrl-madera");
+MODULE_DESCRIPTION("GPIO interface for Madera codecs");
+MODULE_AUTHOR("Nariman Poushin <nariman@opensource.cirrus.com>");
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:madera-gpio");
diff --git a/drivers/gpio/gpio-max3191x.c b/drivers/gpio/gpio-max3191x.c
new file mode 100644
index 0000000000..bbacc71463
--- /dev/null
+++ b/drivers/gpio/gpio-max3191x.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * gpio-max3191x.c - GPIO driver for Maxim MAX3191x industrial serializer
+ *
+ * Copyright (C) 2017 KUNBUS GmbH
+ *
+ * The MAX3191x makes 8 digital 24V inputs available via SPI.
+ * Multiple chips can be daisy-chained, the spec does not impose
+ * a limit on the number of chips and neither does this driver.
+ *
+ * Either of two modes is selectable: In 8-bit mode, only the state
+ * of the inputs is clocked out to achieve high readout speeds;
+ * In 16-bit mode, an additional status byte is clocked out with
+ * a CRC and indicator bits for undervoltage and overtemperature.
+ * The driver returns an error instead of potentially bogus data
+ * if any of these fault conditions occur. However it does allow
+ * readout of non-faulting chips in the same daisy-chain.
+ *
+ * MAX3191x supports four debounce settings and the driver is
+ * capable of configuring these differently for each chip in the
+ * daisy-chain.
+ *
+ * If the chips are hardwired to 8-bit mode ("modesel" pulled high),
+ * gpio-pisosr.c can be used alternatively to this driver.
+ *
+ * https://datasheets.maximintegrated.com/en/ds/MAX31910.pdf
+ * https://datasheets.maximintegrated.com/en/ds/MAX31911.pdf
+ * https://datasheets.maximintegrated.com/en/ds/MAX31912.pdf
+ * https://datasheets.maximintegrated.com/en/ds/MAX31913.pdf
+ * https://datasheets.maximintegrated.com/en/ds/MAX31953-MAX31963.pdf
+ */
+
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/crc8.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+enum max3191x_mode {
+ STATUS_BYTE_ENABLED,
+ STATUS_BYTE_DISABLED,
+};
+
+/**
+ * struct max3191x_chip - max3191x daisy-chain
+ * @gpio: GPIO controller struct
+ * @lock: protects read sequences
+ * @nchips: number of chips in the daisy-chain
+ * @mode: current mode, 0 for 16-bit, 1 for 8-bit;
+ * for simplicity, all chips in the daisy-chain are assumed
+ * to use the same mode
+ * @modesel_pins: GPIO pins to configure modesel of each chip
+ * @fault_pins: GPIO pins to detect fault of each chip
+ * @db0_pins: GPIO pins to configure debounce of each chip
+ * @db1_pins: GPIO pins to configure debounce of each chip
+ * @mesg: SPI message to perform a readout
+ * @xfer: SPI transfer used by @mesg
+ * @crc_error: bitmap signaling CRC error for each chip
+ * @overtemp: bitmap signaling overtemperature alarm for each chip
+ * @undervolt1: bitmap signaling undervoltage alarm for each chip
+ * @undervolt2: bitmap signaling undervoltage warning for each chip
+ * @fault: bitmap signaling assertion of @fault_pins for each chip
+ * @ignore_uv: whether to ignore undervoltage alarms;
+ * set by a device property if the chips are powered through
+ * 5VOUT instead of VCC24V, in which case they will constantly
+ * signal undervoltage;
+ * for simplicity, all chips in the daisy-chain are assumed
+ * to be powered the same way
+ */
+struct max3191x_chip {
+ struct gpio_chip gpio;
+ struct mutex lock;
+ u32 nchips;
+ enum max3191x_mode mode;
+ struct gpio_descs *modesel_pins;
+ struct gpio_descs *fault_pins;
+ struct gpio_descs *db0_pins;
+ struct gpio_descs *db1_pins;
+ struct spi_message mesg;
+ struct spi_transfer xfer;
+ unsigned long *crc_error;
+ unsigned long *overtemp;
+ unsigned long *undervolt1;
+ unsigned long *undervolt2;
+ unsigned long *fault;
+ bool ignore_uv;
+};
+
+#define MAX3191X_NGPIO 8
+#define MAX3191X_CRC8_POLYNOMIAL 0xa8 /* (x^5) + x^4 + x^2 + x^0 */
+
+DECLARE_CRC8_TABLE(max3191x_crc8);
+
+static int max3191x_get_direction(struct gpio_chip *gpio, unsigned int offset)
+{
+ return GPIO_LINE_DIRECTION_IN; /* always in */
+}
+
+static int max3191x_direction_input(struct gpio_chip *gpio, unsigned int offset)
+{
+ return 0;
+}
+
+static int max3191x_direction_output(struct gpio_chip *gpio,
+ unsigned int offset, int value)
+{
+ return -EINVAL;
+}
+
+static void max3191x_set(struct gpio_chip *gpio, unsigned int offset, int value)
+{ }
+
+static void max3191x_set_multiple(struct gpio_chip *gpio, unsigned long *mask,
+ unsigned long *bits)
+{ }
+
+static unsigned int max3191x_wordlen(struct max3191x_chip *max3191x)
+{
+ return max3191x->mode == STATUS_BYTE_ENABLED ? 2 : 1;
+}
+
+static int max3191x_readout_locked(struct max3191x_chip *max3191x)
+{
+ struct device *dev = max3191x->gpio.parent;
+ struct spi_device *spi = to_spi_device(dev);
+ int val, i, ot = 0, uv1 = 0;
+
+ val = spi_sync(spi, &max3191x->mesg);
+ if (val) {
+ dev_err_ratelimited(dev, "SPI receive error %d\n", val);
+ return val;
+ }
+
+ for (i = 0; i < max3191x->nchips; i++) {
+ if (max3191x->mode == STATUS_BYTE_ENABLED) {
+ u8 in = ((u8 *)max3191x->xfer.rx_buf)[i * 2];
+ u8 status = ((u8 *)max3191x->xfer.rx_buf)[i * 2 + 1];
+
+ val = (status & 0xf8) != crc8(max3191x_crc8, &in, 1, 0);
+ __assign_bit(i, max3191x->crc_error, val);
+ if (val)
+ dev_err_ratelimited(dev,
+ "chip %d: CRC error\n", i);
+
+ ot = (status >> 1) & 1;
+ __assign_bit(i, max3191x->overtemp, ot);
+ if (ot)
+ dev_err_ratelimited(dev,
+ "chip %d: overtemperature\n", i);
+
+ if (!max3191x->ignore_uv) {
+ uv1 = !((status >> 2) & 1);
+ __assign_bit(i, max3191x->undervolt1, uv1);
+ if (uv1)
+ dev_err_ratelimited(dev,
+ "chip %d: undervoltage\n", i);
+
+ val = !(status & 1);
+ __assign_bit(i, max3191x->undervolt2, val);
+ if (val && !uv1)
+ dev_warn_ratelimited(dev,
+ "chip %d: voltage warn\n", i);
+ }
+ }
+
+ if (max3191x->fault_pins && !max3191x->ignore_uv) {
+ /* fault pin shared by all chips or per chip */
+ struct gpio_desc *fault_pin =
+ (max3191x->fault_pins->ndescs == 1)
+ ? max3191x->fault_pins->desc[0]
+ : max3191x->fault_pins->desc[i];
+
+ val = gpiod_get_value_cansleep(fault_pin);
+ if (val < 0) {
+ dev_err_ratelimited(dev,
+ "GPIO read error %d\n", val);
+ return val;
+ }
+ __assign_bit(i, max3191x->fault, val);
+ if (val && !uv1 && !ot)
+ dev_err_ratelimited(dev,
+ "chip %d: fault\n", i);
+ }
+ }
+
+ return 0;
+}
+
+static bool max3191x_chip_is_faulting(struct max3191x_chip *max3191x,
+ unsigned int chipnum)
+{
+ /* without status byte the only diagnostic is the fault pin */
+ if (!max3191x->ignore_uv && test_bit(chipnum, max3191x->fault))
+ return true;
+
+ if (max3191x->mode == STATUS_BYTE_DISABLED)
+ return false;
+
+ return test_bit(chipnum, max3191x->crc_error) ||
+ test_bit(chipnum, max3191x->overtemp) ||
+ (!max3191x->ignore_uv &&
+ test_bit(chipnum, max3191x->undervolt1));
+}
+
+static int max3191x_get(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct max3191x_chip *max3191x = gpiochip_get_data(gpio);
+ int ret, chipnum, wordlen = max3191x_wordlen(max3191x);
+ u8 in;
+
+ mutex_lock(&max3191x->lock);
+ ret = max3191x_readout_locked(max3191x);
+ if (ret)
+ goto out_unlock;
+
+ chipnum = offset / MAX3191X_NGPIO;
+ if (max3191x_chip_is_faulting(max3191x, chipnum)) {
+ ret = -EIO;
+ goto out_unlock;
+ }
+
+ in = ((u8 *)max3191x->xfer.rx_buf)[chipnum * wordlen];
+ ret = (in >> (offset % MAX3191X_NGPIO)) & 1;
+
+out_unlock:
+ mutex_unlock(&max3191x->lock);
+ return ret;
+}
+
+static int max3191x_get_multiple(struct gpio_chip *gpio, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct max3191x_chip *max3191x = gpiochip_get_data(gpio);
+ const unsigned int wordlen = max3191x_wordlen(max3191x);
+ int ret;
+ unsigned long bit;
+ unsigned long gpio_mask;
+ unsigned long in;
+
+ mutex_lock(&max3191x->lock);
+ ret = max3191x_readout_locked(max3191x);
+ if (ret)
+ goto out_unlock;
+
+ bitmap_zero(bits, gpio->ngpio);
+ for_each_set_clump8(bit, gpio_mask, mask, gpio->ngpio) {
+ unsigned int chipnum = bit / MAX3191X_NGPIO;
+
+ if (max3191x_chip_is_faulting(max3191x, chipnum)) {
+ ret = -EIO;
+ goto out_unlock;
+ }
+
+ in = ((u8 *)max3191x->xfer.rx_buf)[chipnum * wordlen];
+ in &= gpio_mask;
+ bitmap_set_value8(bits, in, bit);
+ }
+
+out_unlock:
+ mutex_unlock(&max3191x->lock);
+ return ret;
+}
+
+static int max3191x_set_config(struct gpio_chip *gpio, unsigned int offset,
+ unsigned long config)
+{
+ struct max3191x_chip *max3191x = gpiochip_get_data(gpio);
+ u32 debounce, chipnum, db0_val, db1_val;
+
+ if (pinconf_to_config_param(config) != PIN_CONFIG_INPUT_DEBOUNCE)
+ return -ENOTSUPP;
+
+ if (!max3191x->db0_pins || !max3191x->db1_pins)
+ return -EINVAL;
+
+ debounce = pinconf_to_config_argument(config);
+ switch (debounce) {
+ case 0:
+ db0_val = 0;
+ db1_val = 0;
+ break;
+ case 1 ... 25:
+ db0_val = 0;
+ db1_val = 1;
+ break;
+ case 26 ... 750:
+ db0_val = 1;
+ db1_val = 0;
+ break;
+ case 751 ... 3000:
+ db0_val = 1;
+ db1_val = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (max3191x->db0_pins->ndescs == 1)
+ chipnum = 0; /* all chips use the same pair of debounce pins */
+ else
+ chipnum = offset / MAX3191X_NGPIO; /* per chip debounce pins */
+
+ mutex_lock(&max3191x->lock);
+ gpiod_set_value_cansleep(max3191x->db0_pins->desc[chipnum], db0_val);
+ gpiod_set_value_cansleep(max3191x->db1_pins->desc[chipnum], db1_val);
+ mutex_unlock(&max3191x->lock);
+ return 0;
+}
+
+static void gpiod_set_array_single_value_cansleep(unsigned int ndescs,
+ struct gpio_desc **desc,
+ struct gpio_array *info,
+ int value)
+{
+ unsigned long *values;
+
+ values = bitmap_alloc(ndescs, GFP_KERNEL);
+ if (!values)
+ return;
+
+ if (value)
+ bitmap_fill(values, ndescs);
+ else
+ bitmap_zero(values, ndescs);
+
+ gpiod_set_array_value_cansleep(ndescs, desc, info, values);
+ bitmap_free(values);
+}
+
+static struct gpio_descs *devm_gpiod_get_array_optional_count(
+ struct device *dev, const char *con_id,
+ enum gpiod_flags flags, unsigned int expected)
+{
+ struct gpio_descs *descs;
+ int found = gpiod_count(dev, con_id);
+
+ if (found == -ENOENT)
+ return NULL;
+
+ if (found != expected && found != 1) {
+ dev_err(dev, "ignoring %s-gpios: found %d, expected %u or 1\n",
+ con_id, found, expected);
+ return NULL;
+ }
+
+ descs = devm_gpiod_get_array_optional(dev, con_id, flags);
+
+ if (IS_ERR(descs)) {
+ dev_err(dev, "failed to get %s-gpios: %ld\n",
+ con_id, PTR_ERR(descs));
+ return NULL;
+ }
+
+ return descs;
+}
+
+static int max3191x_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct max3191x_chip *max3191x;
+ int n, ret;
+
+ max3191x = devm_kzalloc(dev, sizeof(*max3191x), GFP_KERNEL);
+ if (!max3191x)
+ return -ENOMEM;
+ spi_set_drvdata(spi, max3191x);
+
+ max3191x->nchips = 1;
+ device_property_read_u32(dev, "#daisy-chained-devices",
+ &max3191x->nchips);
+
+ n = BITS_TO_LONGS(max3191x->nchips);
+ max3191x->crc_error = devm_kcalloc(dev, n, sizeof(long), GFP_KERNEL);
+ max3191x->undervolt1 = devm_kcalloc(dev, n, sizeof(long), GFP_KERNEL);
+ max3191x->undervolt2 = devm_kcalloc(dev, n, sizeof(long), GFP_KERNEL);
+ max3191x->overtemp = devm_kcalloc(dev, n, sizeof(long), GFP_KERNEL);
+ max3191x->fault = devm_kcalloc(dev, n, sizeof(long), GFP_KERNEL);
+ max3191x->xfer.rx_buf = devm_kcalloc(dev, max3191x->nchips,
+ 2, GFP_KERNEL);
+ if (!max3191x->crc_error || !max3191x->undervolt1 ||
+ !max3191x->overtemp || !max3191x->undervolt2 ||
+ !max3191x->fault || !max3191x->xfer.rx_buf)
+ return -ENOMEM;
+
+ max3191x->modesel_pins = devm_gpiod_get_array_optional_count(dev,
+ "maxim,modesel", GPIOD_ASIS, max3191x->nchips);
+ max3191x->fault_pins = devm_gpiod_get_array_optional_count(dev,
+ "maxim,fault", GPIOD_IN, max3191x->nchips);
+ max3191x->db0_pins = devm_gpiod_get_array_optional_count(dev,
+ "maxim,db0", GPIOD_OUT_LOW, max3191x->nchips);
+ max3191x->db1_pins = devm_gpiod_get_array_optional_count(dev,
+ "maxim,db1", GPIOD_OUT_LOW, max3191x->nchips);
+
+ max3191x->mode = device_property_read_bool(dev, "maxim,modesel-8bit")
+ ? STATUS_BYTE_DISABLED : STATUS_BYTE_ENABLED;
+ if (max3191x->modesel_pins)
+ gpiod_set_array_single_value_cansleep(
+ max3191x->modesel_pins->ndescs,
+ max3191x->modesel_pins->desc,
+ max3191x->modesel_pins->info, max3191x->mode);
+
+ max3191x->ignore_uv = device_property_read_bool(dev,
+ "maxim,ignore-undervoltage");
+
+ if (max3191x->db0_pins && max3191x->db1_pins &&
+ max3191x->db0_pins->ndescs != max3191x->db1_pins->ndescs) {
+ dev_err(dev, "ignoring maxim,db*-gpios: array len mismatch\n");
+ devm_gpiod_put_array(dev, max3191x->db0_pins);
+ devm_gpiod_put_array(dev, max3191x->db1_pins);
+ max3191x->db0_pins = NULL;
+ max3191x->db1_pins = NULL;
+ }
+
+ max3191x->xfer.len = max3191x->nchips * max3191x_wordlen(max3191x);
+ spi_message_init_with_transfers(&max3191x->mesg, &max3191x->xfer, 1);
+
+ max3191x->gpio.label = spi->modalias;
+ max3191x->gpio.owner = THIS_MODULE;
+ max3191x->gpio.parent = dev;
+ max3191x->gpio.base = -1;
+ max3191x->gpio.ngpio = max3191x->nchips * MAX3191X_NGPIO;
+ max3191x->gpio.can_sleep = true;
+
+ max3191x->gpio.get_direction = max3191x_get_direction;
+ max3191x->gpio.direction_input = max3191x_direction_input;
+ max3191x->gpio.direction_output = max3191x_direction_output;
+ max3191x->gpio.set = max3191x_set;
+ max3191x->gpio.set_multiple = max3191x_set_multiple;
+ max3191x->gpio.get = max3191x_get;
+ max3191x->gpio.get_multiple = max3191x_get_multiple;
+ max3191x->gpio.set_config = max3191x_set_config;
+
+ mutex_init(&max3191x->lock);
+
+ ret = gpiochip_add_data(&max3191x->gpio, max3191x);
+ if (ret) {
+ mutex_destroy(&max3191x->lock);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void max3191x_remove(struct spi_device *spi)
+{
+ struct max3191x_chip *max3191x = spi_get_drvdata(spi);
+
+ gpiochip_remove(&max3191x->gpio);
+ mutex_destroy(&max3191x->lock);
+}
+
+static int __init max3191x_register_driver(struct spi_driver *sdrv)
+{
+ crc8_populate_msb(max3191x_crc8, MAX3191X_CRC8_POLYNOMIAL);
+ return spi_register_driver(sdrv);
+}
+
+static const struct of_device_id max3191x_of_id[] = {
+ { .compatible = "maxim,max31910" },
+ { .compatible = "maxim,max31911" },
+ { .compatible = "maxim,max31912" },
+ { .compatible = "maxim,max31913" },
+ { .compatible = "maxim,max31953" },
+ { .compatible = "maxim,max31963" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max3191x_of_id);
+
+static const struct spi_device_id max3191x_spi_id[] = {
+ { "max31910" },
+ { "max31911" },
+ { "max31912" },
+ { "max31913" },
+ { "max31953" },
+ { "max31963" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, max3191x_spi_id);
+
+static struct spi_driver max3191x_driver = {
+ .driver = {
+ .name = "max3191x",
+ .of_match_table = max3191x_of_id,
+ },
+ .probe = max3191x_probe,
+ .remove = max3191x_remove,
+ .id_table = max3191x_spi_id,
+};
+module_driver(max3191x_driver, max3191x_register_driver, spi_unregister_driver);
+
+MODULE_AUTHOR("Lukas Wunner <lukas@wunner.de>");
+MODULE_DESCRIPTION("GPIO driver for Maxim MAX3191x industrial serializer");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-max7300.c b/drivers/gpio/gpio-max7300.c
new file mode 100644
index 0000000000..31c2b95321
--- /dev/null
+++ b/drivers/gpio/gpio-max7300.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2009 Wolfram Sang, Pengutronix
+ *
+ * Check max730x.c for further details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/i2c.h>
+#include <linux/spi/max7301.h>
+#include <linux/slab.h>
+
+static int max7300_i2c_write(struct device *dev, unsigned int reg,
+ unsigned int val)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return i2c_smbus_write_byte_data(client, reg, val);
+}
+
+static int max7300_i2c_read(struct device *dev, unsigned int reg)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return i2c_smbus_read_byte_data(client, reg);
+}
+
+static int max7300_probe(struct i2c_client *client)
+{
+ struct max7301 *ts;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA))
+ return -EIO;
+
+ ts = devm_kzalloc(&client->dev, sizeof(struct max7301), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->read = max7300_i2c_read;
+ ts->write = max7300_i2c_write;
+ ts->dev = &client->dev;
+
+ return __max730x_probe(ts);
+}
+
+static void max7300_remove(struct i2c_client *client)
+{
+ __max730x_remove(&client->dev);
+}
+
+static const struct i2c_device_id max7300_id[] = {
+ { "max7300", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max7300_id);
+
+static struct i2c_driver max7300_driver = {
+ .driver = {
+ .name = "max7300",
+ },
+ .probe = max7300_probe,
+ .remove = max7300_remove,
+ .id_table = max7300_id,
+};
+
+static int __init max7300_init(void)
+{
+ return i2c_add_driver(&max7300_driver);
+}
+subsys_initcall(max7300_init);
+
+static void __exit max7300_exit(void)
+{
+ i2c_del_driver(&max7300_driver);
+}
+module_exit(max7300_exit);
+
+MODULE_AUTHOR("Wolfram Sang");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MAX7300 GPIO-Expander");
diff --git a/drivers/gpio/gpio-max7301.c b/drivers/gpio/gpio-max7301.c
new file mode 100644
index 0000000000..11813f41d4
--- /dev/null
+++ b/drivers/gpio/gpio-max7301.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2006 Juergen Beisert, Pengutronix
+ * Copyright (C) 2008 Guennadi Liakhovetski, Pengutronix
+ * Copyright (C) 2009 Wolfram Sang, Pengutronix
+ *
+ * Check max730x.c for further details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/max7301.h>
+
+/* A write to the MAX7301 means one message with one transfer */
+static int max7301_spi_write(struct device *dev, unsigned int reg,
+ unsigned int val)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ u16 word = ((reg & 0x7F) << 8) | (val & 0xFF);
+
+ return spi_write_then_read(spi, &word, sizeof(word), NULL, 0);
+}
+
+/* A read from the MAX7301 means two transfers; here, one message each */
+
+static int max7301_spi_read(struct device *dev, unsigned int reg)
+{
+ int ret;
+ u16 word;
+ struct spi_device *spi = to_spi_device(dev);
+
+ word = 0x8000 | (reg << 8);
+ ret = spi_write_then_read(spi, &word, sizeof(word), &word,
+ sizeof(word));
+ if (ret)
+ return ret;
+ return word & 0xff;
+}
+
+static int max7301_probe(struct spi_device *spi)
+{
+ struct max7301 *ts;
+ int ret;
+
+ /* bits_per_word cannot be configured in platform data */
+ spi->bits_per_word = 16;
+ ret = spi_setup(spi);
+ if (ret < 0)
+ return ret;
+
+ ts = devm_kzalloc(&spi->dev, sizeof(struct max7301), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->read = max7301_spi_read;
+ ts->write = max7301_spi_write;
+ ts->dev = &spi->dev;
+
+ ret = __max730x_probe(ts);
+ return ret;
+}
+
+static void max7301_remove(struct spi_device *spi)
+{
+ __max730x_remove(&spi->dev);
+}
+
+static const struct spi_device_id max7301_id[] = {
+ { "max7301", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, max7301_id);
+
+static struct spi_driver max7301_driver = {
+ .driver = {
+ .name = "max7301",
+ },
+ .probe = max7301_probe,
+ .remove = max7301_remove,
+ .id_table = max7301_id,
+};
+
+static int __init max7301_init(void)
+{
+ return spi_register_driver(&max7301_driver);
+}
+/* register after spi postcore initcall and before
+ * subsys initcalls that may rely on these GPIOs
+ */
+subsys_initcall(max7301_init);
+
+static void __exit max7301_exit(void)
+{
+ spi_unregister_driver(&max7301_driver);
+}
+module_exit(max7301_exit);
+
+MODULE_AUTHOR("Juergen Beisert, Wolfram Sang");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MAX7301 GPIO-Expander");
diff --git a/drivers/gpio/gpio-max730x.c b/drivers/gpio/gpio-max730x.c
new file mode 100644
index 0000000000..bb5cf14ae4
--- /dev/null
+++ b/drivers/gpio/gpio-max730x.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/**
+ * Copyright (C) 2006 Juergen Beisert, Pengutronix
+ * Copyright (C) 2008 Guennadi Liakhovetski, Pengutronix
+ * Copyright (C) 2009 Wolfram Sang, Pengutronix
+ *
+ * The Maxim MAX7300/1 device is an I2C/SPI driven GPIO expander. There are
+ * 28 GPIOs. 8 of them can trigger an interrupt. See datasheet for more
+ * details
+ * Note:
+ * - DIN must be stable at the rising edge of clock.
+ * - when writing:
+ * - always clock in 16 clocks at once
+ * - at DIN: D15 first, D0 last
+ * - D0..D7 = databyte, D8..D14 = commandbyte
+ * - D15 = low -> write command
+ * - when reading
+ * - always clock in 16 clocks at once
+ * - at DIN: D15 first, D0 last
+ * - D0..D7 = dummy, D8..D14 = register address
+ * - D15 = high -> read command
+ * - raise CS and assert it again
+ * - always clock in 16 clocks at once
+ * - at DOUT: D15 first, D0 last
+ * - D0..D7 contains the data from the first cycle
+ *
+ * The driver exports a standard gpiochip interface
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/spi/max7301.h>
+#include <linux/gpio/driver.h>
+#include <linux/slab.h>
+
+/*
+ * Pin configurations, see MAX7301 datasheet page 6
+ */
+#define PIN_CONFIG_MASK 0x03
+#define PIN_CONFIG_IN_PULLUP 0x03
+#define PIN_CONFIG_IN_WO_PULLUP 0x02
+#define PIN_CONFIG_OUT 0x01
+
+#define PIN_NUMBER 28
+
+static int max7301_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ struct max7301 *ts = container_of(chip, struct max7301, chip);
+ u8 *config;
+ u8 offset_bits, pin_config;
+ int ret;
+
+ /* First 4 pins are unused in the controller */
+ offset += 4;
+ offset_bits = (offset & 3) << 1;
+
+ config = &ts->port_config[offset >> 2];
+
+ if (ts->input_pullup_active & BIT(offset))
+ pin_config = PIN_CONFIG_IN_PULLUP;
+ else
+ pin_config = PIN_CONFIG_IN_WO_PULLUP;
+
+ mutex_lock(&ts->lock);
+
+ *config = (*config & ~(PIN_CONFIG_MASK << offset_bits))
+ | (pin_config << offset_bits);
+
+ ret = ts->write(ts->dev, 0x08 + (offset >> 2), *config);
+
+ mutex_unlock(&ts->lock);
+
+ return ret;
+}
+
+static int __max7301_set(struct max7301 *ts, unsigned offset, int value)
+{
+ if (value) {
+ ts->out_level |= 1 << offset;
+ return ts->write(ts->dev, 0x20 + offset, 0x01);
+ } else {
+ ts->out_level &= ~(1 << offset);
+ return ts->write(ts->dev, 0x20 + offset, 0x00);
+ }
+}
+
+static int max7301_direction_output(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct max7301 *ts = container_of(chip, struct max7301, chip);
+ u8 *config;
+ u8 offset_bits;
+ int ret;
+
+ /* First 4 pins are unused in the controller */
+ offset += 4;
+ offset_bits = (offset & 3) << 1;
+
+ config = &ts->port_config[offset >> 2];
+
+ mutex_lock(&ts->lock);
+
+ *config = (*config & ~(PIN_CONFIG_MASK << offset_bits))
+ | (PIN_CONFIG_OUT << offset_bits);
+
+ ret = __max7301_set(ts, offset, value);
+
+ if (!ret)
+ ret = ts->write(ts->dev, 0x08 + (offset >> 2), *config);
+
+ mutex_unlock(&ts->lock);
+
+ return ret;
+}
+
+static int max7301_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct max7301 *ts = gpiochip_get_data(chip);
+ int config, level = -EINVAL;
+
+ /* First 4 pins are unused in the controller */
+ offset += 4;
+
+ mutex_lock(&ts->lock);
+
+ config = (ts->port_config[offset >> 2] >> ((offset & 3) << 1))
+ & PIN_CONFIG_MASK;
+
+ switch (config) {
+ case PIN_CONFIG_OUT:
+ /* Output: return cached level */
+ level = !!(ts->out_level & (1 << offset));
+ break;
+ case PIN_CONFIG_IN_WO_PULLUP:
+ case PIN_CONFIG_IN_PULLUP:
+ /* Input: read out */
+ level = ts->read(ts->dev, 0x20 + offset) & 0x01;
+ }
+ mutex_unlock(&ts->lock);
+
+ return level;
+}
+
+static void max7301_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct max7301 *ts = gpiochip_get_data(chip);
+
+ /* First 4 pins are unused in the controller */
+ offset += 4;
+
+ mutex_lock(&ts->lock);
+
+ __max7301_set(ts, offset, value);
+
+ mutex_unlock(&ts->lock);
+}
+
+int __max730x_probe(struct max7301 *ts)
+{
+ struct device *dev = ts->dev;
+ struct max7301_platform_data *pdata;
+ int i, ret;
+
+ pdata = dev_get_platdata(dev);
+
+ mutex_init(&ts->lock);
+ dev_set_drvdata(dev, ts);
+
+ /* Power up the chip and disable IRQ output */
+ ts->write(dev, 0x04, 0x01);
+
+ if (pdata) {
+ ts->input_pullup_active = pdata->input_pullup_active;
+ ts->chip.base = pdata->base;
+ } else {
+ ts->chip.base = -1;
+ }
+ ts->chip.label = dev->driver->name;
+
+ ts->chip.direction_input = max7301_direction_input;
+ ts->chip.get = max7301_get;
+ ts->chip.direction_output = max7301_direction_output;
+ ts->chip.set = max7301_set;
+
+ ts->chip.ngpio = PIN_NUMBER;
+ ts->chip.can_sleep = true;
+ ts->chip.parent = dev;
+ ts->chip.owner = THIS_MODULE;
+
+ /*
+ * initialize pullups according to platform data and cache the
+ * register values for later use.
+ */
+ for (i = 1; i < 8; i++) {
+ int j;
+ /*
+ * initialize port_config with "0xAA", which means
+ * input with internal pullup disabled. This is needed
+ * to avoid writing zeros (in the inner for loop),
+ * which is not allowed according to the datasheet.
+ */
+ ts->port_config[i] = 0xAA;
+ for (j = 0; j < 4; j++) {
+ int offset = (i - 1) * 4 + j;
+ ret = max7301_direction_input(&ts->chip, offset);
+ if (ret)
+ goto exit_destroy;
+ }
+ }
+
+ ret = gpiochip_add_data(&ts->chip, ts);
+ if (!ret)
+ return ret;
+
+exit_destroy:
+ mutex_destroy(&ts->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(__max730x_probe);
+
+void __max730x_remove(struct device *dev)
+{
+ struct max7301 *ts = dev_get_drvdata(dev);
+
+ /* Power down the chip and disable IRQ output */
+ ts->write(dev, 0x04, 0x00);
+ gpiochip_remove(&ts->chip);
+ mutex_destroy(&ts->lock);
+}
+EXPORT_SYMBOL_GPL(__max730x_remove);
+
+MODULE_AUTHOR("Juergen Beisert, Wolfram Sang");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MAX730x GPIO-Expanders, generic parts");
diff --git a/drivers/gpio/gpio-max732x.c b/drivers/gpio/gpio-max732x.c
new file mode 100644
index 0000000000..49d362907b
--- /dev/null
+++ b/drivers/gpio/gpio-max732x.c
@@ -0,0 +1,731 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MAX732x I2C Port Expander with 8/16 I/O
+ *
+ * Copyright (C) 2007 Marvell International Ltd.
+ * Copyright (C) 2008 Jack Ren <jack.ren@marvell.com>
+ * Copyright (C) 2008 Eric Miao <eric.miao@marvell.com>
+ * Copyright (C) 2015 Linus Walleij <linus.walleij@linaro.org>
+ *
+ * Derived from drivers/gpio/pca953x.c
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/platform_data/max732x.h>
+
+/*
+ * Each port of MAX732x (including MAX7319) falls into one of the
+ * following three types:
+ *
+ * - Push Pull Output
+ * - Input
+ * - Open Drain I/O
+ *
+ * designated by 'O', 'I' and 'P' individually according to MAXIM's
+ * datasheets. 'I' and 'P' ports are interrupt capables, some with
+ * a dedicated interrupt mask.
+ *
+ * There are two groups of I/O ports, each group usually includes
+ * up to 8 I/O ports, and is accessed by a specific I2C address:
+ *
+ * - Group A : by I2C address 0b'110xxxx
+ * - Group B : by I2C address 0b'101xxxx
+ *
+ * where 'xxxx' is decided by the connections of pin AD2/AD0. The
+ * address used also affects the initial state of output signals.
+ *
+ * Within each group of ports, there are five known combinations of
+ * I/O ports: 4I4O, 4P4O, 8I, 8P, 8O, see the definitions below for
+ * the detailed organization of these ports. Only Goup A is interrupt
+ * capable.
+ *
+ * GPIO numbers start from 'gpio_base + 0' to 'gpio_base + 8/16',
+ * and GPIOs from GROUP_A are numbered before those from GROUP_B
+ * (if there are two groups).
+ *
+ * NOTE: MAX7328/MAX7329 are drop-in replacements for PCF8574/a, so
+ * they are not supported by this driver.
+ */
+
+#define PORT_NONE 0x0 /* '/' No Port */
+#define PORT_OUTPUT 0x1 /* 'O' Push-Pull, Output Only */
+#define PORT_INPUT 0x2 /* 'I' Input Only */
+#define PORT_OPENDRAIN 0x3 /* 'P' Open-Drain, I/O */
+
+#define IO_4I4O 0x5AA5 /* O7 O6 I5 I4 I3 I2 O1 O0 */
+#define IO_4P4O 0x5FF5 /* O7 O6 P5 P4 P3 P2 O1 O0 */
+#define IO_8I 0xAAAA /* I7 I6 I5 I4 I3 I2 I1 I0 */
+#define IO_8P 0xFFFF /* P7 P6 P5 P4 P3 P2 P1 P0 */
+#define IO_8O 0x5555 /* O7 O6 O5 O4 O3 O2 O1 O0 */
+
+#define GROUP_A(x) ((x) & 0xffff) /* I2C Addr: 0b'110xxxx */
+#define GROUP_B(x) ((x) << 16) /* I2C Addr: 0b'101xxxx */
+
+#define INT_NONE 0x0 /* No interrupt capability */
+#define INT_NO_MASK 0x1 /* Has interrupts, no mask */
+#define INT_INDEP_MASK 0x2 /* Has interrupts, independent mask */
+#define INT_MERGED_MASK 0x3 /* Has interrupts, merged mask */
+
+#define INT_CAPS(x) (((uint64_t)(x)) << 32)
+
+enum {
+ MAX7319,
+ MAX7320,
+ MAX7321,
+ MAX7322,
+ MAX7323,
+ MAX7324,
+ MAX7325,
+ MAX7326,
+ MAX7327,
+};
+
+static uint64_t max732x_features[] = {
+ [MAX7319] = GROUP_A(IO_8I) | INT_CAPS(INT_MERGED_MASK),
+ [MAX7320] = GROUP_B(IO_8O),
+ [MAX7321] = GROUP_A(IO_8P) | INT_CAPS(INT_NO_MASK),
+ [MAX7322] = GROUP_A(IO_4I4O) | INT_CAPS(INT_MERGED_MASK),
+ [MAX7323] = GROUP_A(IO_4P4O) | INT_CAPS(INT_INDEP_MASK),
+ [MAX7324] = GROUP_A(IO_8I) | GROUP_B(IO_8O) | INT_CAPS(INT_MERGED_MASK),
+ [MAX7325] = GROUP_A(IO_8P) | GROUP_B(IO_8O) | INT_CAPS(INT_NO_MASK),
+ [MAX7326] = GROUP_A(IO_4I4O) | GROUP_B(IO_8O) | INT_CAPS(INT_MERGED_MASK),
+ [MAX7327] = GROUP_A(IO_4P4O) | GROUP_B(IO_8O) | INT_CAPS(INT_NO_MASK),
+};
+
+static const struct i2c_device_id max732x_id[] = {
+ { "max7319", MAX7319 },
+ { "max7320", MAX7320 },
+ { "max7321", MAX7321 },
+ { "max7322", MAX7322 },
+ { "max7323", MAX7323 },
+ { "max7324", MAX7324 },
+ { "max7325", MAX7325 },
+ { "max7326", MAX7326 },
+ { "max7327", MAX7327 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, max732x_id);
+
+static const struct of_device_id max732x_of_table[] = {
+ { .compatible = "maxim,max7319" },
+ { .compatible = "maxim,max7320" },
+ { .compatible = "maxim,max7321" },
+ { .compatible = "maxim,max7322" },
+ { .compatible = "maxim,max7323" },
+ { .compatible = "maxim,max7324" },
+ { .compatible = "maxim,max7325" },
+ { .compatible = "maxim,max7326" },
+ { .compatible = "maxim,max7327" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max732x_of_table);
+
+struct max732x_chip {
+ struct gpio_chip gpio_chip;
+
+ struct i2c_client *client; /* "main" client */
+ struct i2c_client *client_dummy;
+ struct i2c_client *client_group_a;
+ struct i2c_client *client_group_b;
+
+ unsigned int mask_group_a;
+ unsigned int dir_input;
+ unsigned int dir_output;
+
+ struct mutex lock;
+ uint8_t reg_out[2];
+
+#ifdef CONFIG_GPIO_MAX732X_IRQ
+ struct mutex irq_lock;
+ uint8_t irq_mask;
+ uint8_t irq_mask_cur;
+ uint8_t irq_trig_raise;
+ uint8_t irq_trig_fall;
+ uint8_t irq_features;
+#endif
+};
+
+static int max732x_writeb(struct max732x_chip *chip, int group_a, uint8_t val)
+{
+ struct i2c_client *client;
+ int ret;
+
+ client = group_a ? chip->client_group_a : chip->client_group_b;
+ ret = i2c_smbus_write_byte(client, val);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed writing\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int max732x_readb(struct max732x_chip *chip, int group_a, uint8_t *val)
+{
+ struct i2c_client *client;
+ int ret;
+
+ client = group_a ? chip->client_group_a : chip->client_group_b;
+ ret = i2c_smbus_read_byte(client);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed reading\n");
+ return ret;
+ }
+
+ *val = (uint8_t)ret;
+ return 0;
+}
+
+static inline int is_group_a(struct max732x_chip *chip, unsigned off)
+{
+ return (1u << off) & chip->mask_group_a;
+}
+
+static int max732x_gpio_get_value(struct gpio_chip *gc, unsigned off)
+{
+ struct max732x_chip *chip = gpiochip_get_data(gc);
+ uint8_t reg_val;
+ int ret;
+
+ ret = max732x_readb(chip, is_group_a(chip, off), &reg_val);
+ if (ret < 0)
+ return ret;
+
+ return !!(reg_val & (1u << (off & 0x7)));
+}
+
+static void max732x_gpio_set_mask(struct gpio_chip *gc, unsigned off, int mask,
+ int val)
+{
+ struct max732x_chip *chip = gpiochip_get_data(gc);
+ uint8_t reg_out;
+ int ret;
+
+ mutex_lock(&chip->lock);
+
+ reg_out = (off > 7) ? chip->reg_out[1] : chip->reg_out[0];
+ reg_out = (reg_out & ~mask) | (val & mask);
+
+ ret = max732x_writeb(chip, is_group_a(chip, off), reg_out);
+ if (ret < 0)
+ goto out;
+
+ /* update the shadow register then */
+ if (off > 7)
+ chip->reg_out[1] = reg_out;
+ else
+ chip->reg_out[0] = reg_out;
+out:
+ mutex_unlock(&chip->lock);
+}
+
+static void max732x_gpio_set_value(struct gpio_chip *gc, unsigned off, int val)
+{
+ unsigned base = off & ~0x7;
+ uint8_t mask = 1u << (off & 0x7);
+
+ max732x_gpio_set_mask(gc, base, mask, val << (off & 0x7));
+}
+
+static void max732x_gpio_set_multiple(struct gpio_chip *gc,
+ unsigned long *mask, unsigned long *bits)
+{
+ unsigned mask_lo = mask[0] & 0xff;
+ unsigned mask_hi = (mask[0] >> 8) & 0xff;
+
+ if (mask_lo)
+ max732x_gpio_set_mask(gc, 0, mask_lo, bits[0] & 0xff);
+ if (mask_hi)
+ max732x_gpio_set_mask(gc, 8, mask_hi, (bits[0] >> 8) & 0xff);
+}
+
+static int max732x_gpio_direction_input(struct gpio_chip *gc, unsigned off)
+{
+ struct max732x_chip *chip = gpiochip_get_data(gc);
+ unsigned int mask = 1u << off;
+
+ if ((mask & chip->dir_input) == 0) {
+ dev_dbg(&chip->client->dev, "%s port %d is output only\n",
+ chip->client->name, off);
+ return -EACCES;
+ }
+
+ /*
+ * Open-drain pins must be set to high impedance (which is
+ * equivalent to output-high) to be turned into an input.
+ */
+ if ((mask & chip->dir_output))
+ max732x_gpio_set_value(gc, off, 1);
+
+ return 0;
+}
+
+static int max732x_gpio_direction_output(struct gpio_chip *gc,
+ unsigned off, int val)
+{
+ struct max732x_chip *chip = gpiochip_get_data(gc);
+ unsigned int mask = 1u << off;
+
+ if ((mask & chip->dir_output) == 0) {
+ dev_dbg(&chip->client->dev, "%s port %d is input only\n",
+ chip->client->name, off);
+ return -EACCES;
+ }
+
+ max732x_gpio_set_value(gc, off, val);
+ return 0;
+}
+
+#ifdef CONFIG_GPIO_MAX732X_IRQ
+static int max732x_writew(struct max732x_chip *chip, uint16_t val)
+{
+ int ret;
+
+ val = cpu_to_le16(val);
+
+ ret = i2c_master_send(chip->client_group_a, (char *)&val, 2);
+ if (ret < 0) {
+ dev_err(&chip->client_group_a->dev, "failed writing\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int max732x_readw(struct max732x_chip *chip, uint16_t *val)
+{
+ int ret;
+
+ ret = i2c_master_recv(chip->client_group_a, (char *)val, 2);
+ if (ret < 0) {
+ dev_err(&chip->client_group_a->dev, "failed reading\n");
+ return ret;
+ }
+
+ *val = le16_to_cpu(*val);
+ return 0;
+}
+
+static void max732x_irq_update_mask(struct max732x_chip *chip)
+{
+ uint16_t msg;
+
+ if (chip->irq_mask == chip->irq_mask_cur)
+ return;
+
+ chip->irq_mask = chip->irq_mask_cur;
+
+ if (chip->irq_features == INT_NO_MASK)
+ return;
+
+ mutex_lock(&chip->lock);
+
+ switch (chip->irq_features) {
+ case INT_INDEP_MASK:
+ msg = (chip->irq_mask << 8) | chip->reg_out[0];
+ max732x_writew(chip, msg);
+ break;
+
+ case INT_MERGED_MASK:
+ msg = chip->irq_mask | chip->reg_out[0];
+ max732x_writeb(chip, 1, (uint8_t)msg);
+ break;
+ }
+
+ mutex_unlock(&chip->lock);
+}
+
+static void max732x_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct max732x_chip *chip = gpiochip_get_data(gc);
+
+ chip->irq_mask_cur &= ~(1 << d->hwirq);
+ gpiochip_disable_irq(gc, irqd_to_hwirq(d));
+}
+
+static void max732x_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct max732x_chip *chip = gpiochip_get_data(gc);
+
+ gpiochip_enable_irq(gc, irqd_to_hwirq(d));
+ chip->irq_mask_cur |= 1 << d->hwirq;
+}
+
+static void max732x_irq_bus_lock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct max732x_chip *chip = gpiochip_get_data(gc);
+
+ mutex_lock(&chip->irq_lock);
+ chip->irq_mask_cur = chip->irq_mask;
+}
+
+static void max732x_irq_bus_sync_unlock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct max732x_chip *chip = gpiochip_get_data(gc);
+ uint16_t new_irqs;
+ uint16_t level;
+
+ max732x_irq_update_mask(chip);
+
+ new_irqs = chip->irq_trig_fall | chip->irq_trig_raise;
+ while (new_irqs) {
+ level = __ffs(new_irqs);
+ max732x_gpio_direction_input(&chip->gpio_chip, level);
+ new_irqs &= ~(1 << level);
+ }
+
+ mutex_unlock(&chip->irq_lock);
+}
+
+static int max732x_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct max732x_chip *chip = gpiochip_get_data(gc);
+ uint16_t off = d->hwirq;
+ uint16_t mask = 1 << off;
+
+ if (!(mask & chip->dir_input)) {
+ dev_dbg(&chip->client->dev, "%s port %d is output only\n",
+ chip->client->name, off);
+ return -EACCES;
+ }
+
+ if (!(type & IRQ_TYPE_EDGE_BOTH)) {
+ dev_err(&chip->client->dev, "irq %d: unsupported type %d\n",
+ d->irq, type);
+ return -EINVAL;
+ }
+
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ chip->irq_trig_fall |= mask;
+ else
+ chip->irq_trig_fall &= ~mask;
+
+ if (type & IRQ_TYPE_EDGE_RISING)
+ chip->irq_trig_raise |= mask;
+ else
+ chip->irq_trig_raise &= ~mask;
+
+ return 0;
+}
+
+static int max732x_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+ struct max732x_chip *chip = irq_data_get_irq_chip_data(data);
+
+ irq_set_irq_wake(chip->client->irq, on);
+ return 0;
+}
+
+static const struct irq_chip max732x_irq_chip = {
+ .name = "max732x",
+ .irq_mask = max732x_irq_mask,
+ .irq_unmask = max732x_irq_unmask,
+ .irq_bus_lock = max732x_irq_bus_lock,
+ .irq_bus_sync_unlock = max732x_irq_bus_sync_unlock,
+ .irq_set_type = max732x_irq_set_type,
+ .irq_set_wake = max732x_irq_set_wake,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static uint8_t max732x_irq_pending(struct max732x_chip *chip)
+{
+ uint8_t cur_stat;
+ uint8_t old_stat;
+ uint8_t trigger;
+ uint8_t pending;
+ uint16_t status;
+ int ret;
+
+ ret = max732x_readw(chip, &status);
+ if (ret)
+ return 0;
+
+ trigger = status >> 8;
+ trigger &= chip->irq_mask;
+
+ if (!trigger)
+ return 0;
+
+ cur_stat = status & 0xFF;
+ cur_stat &= chip->irq_mask;
+
+ old_stat = cur_stat ^ trigger;
+
+ pending = (old_stat & chip->irq_trig_fall) |
+ (cur_stat & chip->irq_trig_raise);
+ pending &= trigger;
+
+ return pending;
+}
+
+static irqreturn_t max732x_irq_handler(int irq, void *devid)
+{
+ struct max732x_chip *chip = devid;
+ uint8_t pending;
+ uint8_t level;
+
+ pending = max732x_irq_pending(chip);
+
+ if (!pending)
+ return IRQ_HANDLED;
+
+ do {
+ level = __ffs(pending);
+ handle_nested_irq(irq_find_mapping(chip->gpio_chip.irq.domain,
+ level));
+
+ pending &= ~(1 << level);
+ } while (pending);
+
+ return IRQ_HANDLED;
+}
+
+static int max732x_irq_setup(struct max732x_chip *chip,
+ const struct i2c_device_id *id)
+{
+ struct i2c_client *client = chip->client;
+ int has_irq = max732x_features[id->driver_data] >> 32;
+ int irq_base = 0;
+ int ret;
+
+ if (client->irq && has_irq != INT_NONE) {
+ struct gpio_irq_chip *girq;
+
+ chip->irq_features = has_irq;
+ mutex_init(&chip->irq_lock);
+
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, max732x_irq_handler, IRQF_ONESHOT |
+ IRQF_TRIGGER_FALLING | IRQF_SHARED,
+ dev_name(&client->dev), chip);
+ if (ret) {
+ dev_err(&client->dev, "failed to request irq %d\n",
+ client->irq);
+ return ret;
+ }
+
+ girq = &chip->gpio_chip.irq;
+ gpio_irq_chip_set_chip(girq, &max732x_irq_chip);
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ girq->threaded = true;
+ girq->first = irq_base; /* FIXME: get rid of this */
+ }
+
+ return 0;
+}
+
+#else /* CONFIG_GPIO_MAX732X_IRQ */
+static int max732x_irq_setup(struct max732x_chip *chip,
+ const struct i2c_device_id *id)
+{
+ struct i2c_client *client = chip->client;
+ int has_irq = max732x_features[id->driver_data] >> 32;
+
+ if (client->irq && has_irq != INT_NONE)
+ dev_warn(&client->dev, "interrupt support not compiled in\n");
+
+ return 0;
+}
+#endif
+
+static int max732x_setup_gpio(struct max732x_chip *chip,
+ const struct i2c_device_id *id,
+ unsigned gpio_start)
+{
+ struct gpio_chip *gc = &chip->gpio_chip;
+ uint32_t id_data = (uint32_t)max732x_features[id->driver_data];
+ int i, port = 0;
+
+ for (i = 0; i < 16; i++, id_data >>= 2) {
+ unsigned int mask = 1 << port;
+
+ switch (id_data & 0x3) {
+ case PORT_OUTPUT:
+ chip->dir_output |= mask;
+ break;
+ case PORT_INPUT:
+ chip->dir_input |= mask;
+ break;
+ case PORT_OPENDRAIN:
+ chip->dir_output |= mask;
+ chip->dir_input |= mask;
+ break;
+ default:
+ continue;
+ }
+
+ if (i < 8)
+ chip->mask_group_a |= mask;
+ port++;
+ }
+
+ if (chip->dir_input)
+ gc->direction_input = max732x_gpio_direction_input;
+ if (chip->dir_output) {
+ gc->direction_output = max732x_gpio_direction_output;
+ gc->set = max732x_gpio_set_value;
+ gc->set_multiple = max732x_gpio_set_multiple;
+ }
+ gc->get = max732x_gpio_get_value;
+ gc->can_sleep = true;
+
+ gc->base = gpio_start;
+ gc->ngpio = port;
+ gc->label = chip->client->name;
+ gc->parent = &chip->client->dev;
+ gc->owner = THIS_MODULE;
+
+ return port;
+}
+
+static struct max732x_platform_data *of_gpio_max732x(struct device *dev)
+{
+ struct max732x_platform_data *pdata;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return NULL;
+
+ pdata->gpio_base = -1;
+
+ return pdata;
+}
+
+static int max732x_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct max732x_platform_data *pdata;
+ struct device_node *node;
+ struct max732x_chip *chip;
+ struct i2c_client *c;
+ uint16_t addr_a, addr_b;
+ int ret, nr_port;
+
+ pdata = dev_get_platdata(&client->dev);
+ node = client->dev.of_node;
+
+ if (!pdata && node)
+ pdata = of_gpio_max732x(&client->dev);
+
+ if (!pdata) {
+ dev_dbg(&client->dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+ chip->client = client;
+
+ nr_port = max732x_setup_gpio(chip, id, pdata->gpio_base);
+ chip->gpio_chip.parent = &client->dev;
+
+ addr_a = (client->addr & 0x0f) | 0x60;
+ addr_b = (client->addr & 0x0f) | 0x50;
+
+ switch (client->addr & 0x70) {
+ case 0x60:
+ chip->client_group_a = client;
+ if (nr_port > 8) {
+ c = devm_i2c_new_dummy_device(&client->dev,
+ client->adapter, addr_b);
+ if (IS_ERR(c)) {
+ dev_err(&client->dev,
+ "Failed to allocate I2C device\n");
+ return PTR_ERR(c);
+ }
+ chip->client_group_b = chip->client_dummy = c;
+ }
+ break;
+ case 0x50:
+ chip->client_group_b = client;
+ if (nr_port > 8) {
+ c = devm_i2c_new_dummy_device(&client->dev,
+ client->adapter, addr_a);
+ if (IS_ERR(c)) {
+ dev_err(&client->dev,
+ "Failed to allocate I2C device\n");
+ return PTR_ERR(c);
+ }
+ chip->client_group_a = chip->client_dummy = c;
+ }
+ break;
+ default:
+ dev_err(&client->dev, "invalid I2C address specified %02x\n",
+ client->addr);
+ return -EINVAL;
+ }
+
+ if (nr_port > 8 && !chip->client_dummy) {
+ dev_err(&client->dev,
+ "Failed to allocate second group I2C device\n");
+ return -ENODEV;
+ }
+
+ mutex_init(&chip->lock);
+
+ ret = max732x_readb(chip, is_group_a(chip, 0), &chip->reg_out[0]);
+ if (ret)
+ return ret;
+ if (nr_port > 8) {
+ ret = max732x_readb(chip, is_group_a(chip, 8), &chip->reg_out[1]);
+ if (ret)
+ return ret;
+ }
+
+ ret = max732x_irq_setup(chip, id);
+ if (ret)
+ return ret;
+
+ ret = devm_gpiochip_add_data(&client->dev, &chip->gpio_chip, chip);
+ if (ret)
+ return ret;
+
+ i2c_set_clientdata(client, chip);
+ return 0;
+}
+
+static struct i2c_driver max732x_driver = {
+ .driver = {
+ .name = "max732x",
+ .of_match_table = max732x_of_table,
+ },
+ .probe = max732x_probe,
+ .id_table = max732x_id,
+};
+
+static int __init max732x_init(void)
+{
+ return i2c_add_driver(&max732x_driver);
+}
+/* register after i2c postcore initcall and before
+ * subsys initcalls that may rely on these GPIOs
+ */
+subsys_initcall(max732x_init);
+
+static void __exit max732x_exit(void)
+{
+ i2c_del_driver(&max732x_driver);
+}
+module_exit(max732x_exit);
+
+MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>");
+MODULE_DESCRIPTION("GPIO expander driver for MAX732X");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-max77620.c b/drivers/gpio/gpio-max77620.c
new file mode 100644
index 0000000000..8c2a560916
--- /dev/null
+++ b/drivers/gpio/gpio-max77620.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MAXIM MAX77620 GPIO driver
+ *
+ * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max77620.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define GPIO_REG_ADDR(offset) (MAX77620_REG_GPIO0 + offset)
+
+struct max77620_gpio {
+ struct gpio_chip gpio_chip;
+ struct regmap *rmap;
+ struct device *dev;
+ struct mutex buslock; /* irq_bus_lock */
+ unsigned int irq_type[MAX77620_GPIO_NR];
+ bool irq_enabled[MAX77620_GPIO_NR];
+};
+
+static irqreturn_t max77620_gpio_irqhandler(int irq, void *data)
+{
+ struct max77620_gpio *gpio = data;
+ unsigned int value, offset;
+ unsigned long pending;
+ int err;
+
+ err = regmap_read(gpio->rmap, MAX77620_REG_IRQ_LVL2_GPIO, &value);
+ if (err < 0) {
+ dev_err(gpio->dev, "REG_IRQ_LVL2_GPIO read failed: %d\n", err);
+ return IRQ_NONE;
+ }
+
+ pending = value;
+
+ for_each_set_bit(offset, &pending, MAX77620_GPIO_NR) {
+ unsigned int virq;
+
+ virq = irq_find_mapping(gpio->gpio_chip.irq.domain, offset);
+ handle_nested_irq(virq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void max77620_gpio_irq_mask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct max77620_gpio *gpio = gpiochip_get_data(chip);
+
+ gpio->irq_enabled[data->hwirq] = false;
+ gpiochip_disable_irq(chip, data->hwirq);
+}
+
+static void max77620_gpio_irq_unmask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct max77620_gpio *gpio = gpiochip_get_data(chip);
+
+ gpiochip_enable_irq(chip, data->hwirq);
+ gpio->irq_enabled[data->hwirq] = true;
+}
+
+static int max77620_gpio_set_irq_type(struct irq_data *data, unsigned int type)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct max77620_gpio *gpio = gpiochip_get_data(chip);
+ unsigned int irq_type;
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ irq_type = MAX77620_CNFG_GPIO_INT_RISING;
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ irq_type = MAX77620_CNFG_GPIO_INT_FALLING;
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ irq_type = MAX77620_CNFG_GPIO_INT_RISING |
+ MAX77620_CNFG_GPIO_INT_FALLING;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ gpio->irq_type[data->hwirq] = irq_type;
+
+ return 0;
+}
+
+static void max77620_gpio_bus_lock(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct max77620_gpio *gpio = gpiochip_get_data(chip);
+
+ mutex_lock(&gpio->buslock);
+}
+
+static void max77620_gpio_bus_sync_unlock(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct max77620_gpio *gpio = gpiochip_get_data(chip);
+ unsigned int value, offset = data->hwirq;
+ int err;
+
+ value = gpio->irq_enabled[offset] ? gpio->irq_type[offset] : 0;
+
+ err = regmap_update_bits(gpio->rmap, GPIO_REG_ADDR(offset),
+ MAX77620_CNFG_GPIO_INT_MASK, value);
+ if (err < 0)
+ dev_err(chip->parent, "failed to update interrupt mask: %d\n",
+ err);
+
+ mutex_unlock(&gpio->buslock);
+}
+
+static const struct irq_chip max77620_gpio_irqchip = {
+ .name = "max77620-gpio",
+ .irq_mask = max77620_gpio_irq_mask,
+ .irq_unmask = max77620_gpio_irq_unmask,
+ .irq_set_type = max77620_gpio_set_irq_type,
+ .irq_bus_lock = max77620_gpio_bus_lock,
+ .irq_bus_sync_unlock = max77620_gpio_bus_sync_unlock,
+ .flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int max77620_gpio_dir_input(struct gpio_chip *gc, unsigned int offset)
+{
+ struct max77620_gpio *mgpio = gpiochip_get_data(gc);
+ int ret;
+
+ ret = regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset),
+ MAX77620_CNFG_GPIO_DIR_MASK,
+ MAX77620_CNFG_GPIO_DIR_INPUT);
+ if (ret < 0)
+ dev_err(mgpio->dev, "CNFG_GPIOx dir update failed: %d\n", ret);
+
+ return ret;
+}
+
+static int max77620_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct max77620_gpio *mgpio = gpiochip_get_data(gc);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(mgpio->rmap, GPIO_REG_ADDR(offset), &val);
+ if (ret < 0) {
+ dev_err(mgpio->dev, "CNFG_GPIOx read failed: %d\n", ret);
+ return ret;
+ }
+
+ if (val & MAX77620_CNFG_GPIO_DIR_MASK)
+ return !!(val & MAX77620_CNFG_GPIO_INPUT_VAL_MASK);
+ else
+ return !!(val & MAX77620_CNFG_GPIO_OUTPUT_VAL_MASK);
+}
+
+static int max77620_gpio_dir_output(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct max77620_gpio *mgpio = gpiochip_get_data(gc);
+ u8 val;
+ int ret;
+
+ val = (value) ? MAX77620_CNFG_GPIO_OUTPUT_VAL_HIGH :
+ MAX77620_CNFG_GPIO_OUTPUT_VAL_LOW;
+
+ ret = regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset),
+ MAX77620_CNFG_GPIO_OUTPUT_VAL_MASK, val);
+ if (ret < 0) {
+ dev_err(mgpio->dev, "CNFG_GPIOx val update failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset),
+ MAX77620_CNFG_GPIO_DIR_MASK,
+ MAX77620_CNFG_GPIO_DIR_OUTPUT);
+ if (ret < 0)
+ dev_err(mgpio->dev, "CNFG_GPIOx dir update failed: %d\n", ret);
+
+ return ret;
+}
+
+static int max77620_gpio_set_debounce(struct max77620_gpio *mgpio,
+ unsigned int offset,
+ unsigned int debounce)
+{
+ u8 val;
+ int ret;
+
+ switch (debounce) {
+ case 0:
+ val = MAX77620_CNFG_GPIO_DBNC_None;
+ break;
+ case 1 ... 8000:
+ val = MAX77620_CNFG_GPIO_DBNC_8ms;
+ break;
+ case 8001 ... 16000:
+ val = MAX77620_CNFG_GPIO_DBNC_16ms;
+ break;
+ case 16001 ... 32000:
+ val = MAX77620_CNFG_GPIO_DBNC_32ms;
+ break;
+ default:
+ dev_err(mgpio->dev, "Illegal value %u\n", debounce);
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset),
+ MAX77620_CNFG_GPIO_DBNC_MASK, val);
+ if (ret < 0)
+ dev_err(mgpio->dev, "CNFG_GPIOx_DBNC update failed: %d\n", ret);
+
+ return ret;
+}
+
+static void max77620_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct max77620_gpio *mgpio = gpiochip_get_data(gc);
+ u8 val;
+ int ret;
+
+ val = (value) ? MAX77620_CNFG_GPIO_OUTPUT_VAL_HIGH :
+ MAX77620_CNFG_GPIO_OUTPUT_VAL_LOW;
+
+ ret = regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset),
+ MAX77620_CNFG_GPIO_OUTPUT_VAL_MASK, val);
+ if (ret < 0)
+ dev_err(mgpio->dev, "CNFG_GPIO_OUT update failed: %d\n", ret);
+}
+
+static int max77620_gpio_set_config(struct gpio_chip *gc, unsigned int offset,
+ unsigned long config)
+{
+ struct max77620_gpio *mgpio = gpiochip_get_data(gc);
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ return regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset),
+ MAX77620_CNFG_GPIO_DRV_MASK,
+ MAX77620_CNFG_GPIO_DRV_OPENDRAIN);
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ return regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset),
+ MAX77620_CNFG_GPIO_DRV_MASK,
+ MAX77620_CNFG_GPIO_DRV_PUSHPULL);
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ return max77620_gpio_set_debounce(mgpio, offset,
+ pinconf_to_config_argument(config));
+ default:
+ break;
+ }
+
+ return -ENOTSUPP;
+}
+
+static int max77620_gpio_irq_init_hw(struct gpio_chip *gc)
+{
+ struct max77620_gpio *gpio = gpiochip_get_data(gc);
+ unsigned int i;
+ int err;
+
+ /*
+ * GPIO interrupts may be left ON after bootloader, hence let's
+ * pre-initialize hardware to the expected state by disabling all
+ * the interrupts.
+ */
+ for (i = 0; i < MAX77620_GPIO_NR; i++) {
+ err = regmap_update_bits(gpio->rmap, GPIO_REG_ADDR(i),
+ MAX77620_CNFG_GPIO_INT_MASK, 0);
+ if (err < 0) {
+ dev_err(gpio->dev,
+ "failed to disable interrupt: %d\n", err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int max77620_gpio_probe(struct platform_device *pdev)
+{
+ struct max77620_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct max77620_gpio *mgpio;
+ struct gpio_irq_chip *girq;
+ unsigned int gpio_irq;
+ int ret;
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+
+ gpio_irq = ret;
+
+ mgpio = devm_kzalloc(&pdev->dev, sizeof(*mgpio), GFP_KERNEL);
+ if (!mgpio)
+ return -ENOMEM;
+
+ mutex_init(&mgpio->buslock);
+ mgpio->rmap = chip->rmap;
+ mgpio->dev = &pdev->dev;
+
+ mgpio->gpio_chip.label = pdev->name;
+ mgpio->gpio_chip.parent = pdev->dev.parent;
+ mgpio->gpio_chip.direction_input = max77620_gpio_dir_input;
+ mgpio->gpio_chip.get = max77620_gpio_get;
+ mgpio->gpio_chip.direction_output = max77620_gpio_dir_output;
+ mgpio->gpio_chip.set = max77620_gpio_set;
+ mgpio->gpio_chip.set_config = max77620_gpio_set_config;
+ mgpio->gpio_chip.ngpio = MAX77620_GPIO_NR;
+ mgpio->gpio_chip.can_sleep = 1;
+ mgpio->gpio_chip.base = -1;
+
+ girq = &mgpio->gpio_chip.irq;
+ gpio_irq_chip_set_chip(girq, &max77620_gpio_irqchip);
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_edge_irq;
+ girq->init_hw = max77620_gpio_irq_init_hw;
+ girq->threaded = true;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &mgpio->gpio_chip, mgpio);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "gpio_init: Failed to add max77620_gpio\n");
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, gpio_irq, NULL,
+ max77620_gpio_irqhandler, IRQF_ONESHOT,
+ "max77620-gpio", mgpio);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to request IRQ: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct platform_device_id max77620_gpio_devtype[] = {
+ { .name = "max77620-gpio", },
+ { .name = "max20024-gpio", },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, max77620_gpio_devtype);
+
+static struct platform_driver max77620_gpio_driver = {
+ .driver.name = "max77620-gpio",
+ .probe = max77620_gpio_probe,
+ .id_table = max77620_gpio_devtype,
+};
+
+module_platform_driver(max77620_gpio_driver);
+
+MODULE_DESCRIPTION("GPIO interface for MAX77620 and MAX20024 PMIC");
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
+MODULE_AUTHOR("Chaitanya Bandi <bandik@nvidia.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-max77650.c b/drivers/gpio/gpio-max77650.c
new file mode 100644
index 0000000000..3075f2513c
--- /dev/null
+++ b/drivers/gpio/gpio-max77650.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2018 BayLibre SAS
+// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
+//
+// GPIO driver for MAXIM 77650/77651 charger/power-supply.
+
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/mfd/max77650.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MAX77650_GPIO_DIR_MASK BIT(0)
+#define MAX77650_GPIO_INVAL_MASK BIT(1)
+#define MAX77650_GPIO_DRV_MASK BIT(2)
+#define MAX77650_GPIO_OUTVAL_MASK BIT(3)
+#define MAX77650_GPIO_DEBOUNCE_MASK BIT(4)
+
+#define MAX77650_GPIO_DIR_OUT 0x00
+#define MAX77650_GPIO_DIR_IN BIT(0)
+#define MAX77650_GPIO_OUT_LOW 0x00
+#define MAX77650_GPIO_OUT_HIGH BIT(3)
+#define MAX77650_GPIO_DRV_OPEN_DRAIN 0x00
+#define MAX77650_GPIO_DRV_PUSH_PULL BIT(2)
+#define MAX77650_GPIO_DEBOUNCE BIT(4)
+
+#define MAX77650_GPIO_DIR_BITS(_reg) \
+ ((_reg) & MAX77650_GPIO_DIR_MASK)
+#define MAX77650_GPIO_INVAL_BITS(_reg) \
+ (((_reg) & MAX77650_GPIO_INVAL_MASK) >> 1)
+
+struct max77650_gpio_chip {
+ struct regmap *map;
+ struct gpio_chip gc;
+ int irq;
+};
+
+static int max77650_gpio_direction_input(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+
+ return regmap_update_bits(chip->map,
+ MAX77650_REG_CNFG_GPIO,
+ MAX77650_GPIO_DIR_MASK,
+ MAX77650_GPIO_DIR_IN);
+}
+
+static int max77650_gpio_direction_output(struct gpio_chip *gc,
+ unsigned int offset, int value)
+{
+ struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+ int mask, regval;
+
+ mask = MAX77650_GPIO_DIR_MASK | MAX77650_GPIO_OUTVAL_MASK;
+ regval = value ? MAX77650_GPIO_OUT_HIGH : MAX77650_GPIO_OUT_LOW;
+ regval |= MAX77650_GPIO_DIR_OUT;
+
+ return regmap_update_bits(chip->map,
+ MAX77650_REG_CNFG_GPIO, mask, regval);
+}
+
+static void max77650_gpio_set_value(struct gpio_chip *gc,
+ unsigned int offset, int value)
+{
+ struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+ int rv, regval;
+
+ regval = value ? MAX77650_GPIO_OUT_HIGH : MAX77650_GPIO_OUT_LOW;
+
+ rv = regmap_update_bits(chip->map, MAX77650_REG_CNFG_GPIO,
+ MAX77650_GPIO_OUTVAL_MASK, regval);
+ if (rv)
+ dev_err(gc->parent, "cannot set GPIO value: %d\n", rv);
+}
+
+static int max77650_gpio_get_value(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+ unsigned int val;
+ int rv;
+
+ rv = regmap_read(chip->map, MAX77650_REG_CNFG_GPIO, &val);
+ if (rv)
+ return rv;
+
+ return MAX77650_GPIO_INVAL_BITS(val);
+}
+
+static int max77650_gpio_get_direction(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+ unsigned int val;
+ int rv;
+
+ rv = regmap_read(chip->map, MAX77650_REG_CNFG_GPIO, &val);
+ if (rv)
+ return rv;
+
+ return MAX77650_GPIO_DIR_BITS(val);
+}
+
+static int max77650_gpio_set_config(struct gpio_chip *gc,
+ unsigned int offset, unsigned long cfg)
+{
+ struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+
+ switch (pinconf_to_config_param(cfg)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ return regmap_update_bits(chip->map,
+ MAX77650_REG_CNFG_GPIO,
+ MAX77650_GPIO_DRV_MASK,
+ MAX77650_GPIO_DRV_OPEN_DRAIN);
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ return regmap_update_bits(chip->map,
+ MAX77650_REG_CNFG_GPIO,
+ MAX77650_GPIO_DRV_MASK,
+ MAX77650_GPIO_DRV_PUSH_PULL);
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ return regmap_update_bits(chip->map,
+ MAX77650_REG_CNFG_GPIO,
+ MAX77650_GPIO_DEBOUNCE_MASK,
+ MAX77650_GPIO_DEBOUNCE);
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static int max77650_gpio_to_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+
+ return chip->irq;
+}
+
+static int max77650_gpio_probe(struct platform_device *pdev)
+{
+ struct max77650_gpio_chip *chip;
+ struct device *dev, *parent;
+ struct i2c_client *i2c;
+
+ dev = &pdev->dev;
+ parent = dev->parent;
+ i2c = to_i2c_client(parent);
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->map = dev_get_regmap(parent, NULL);
+ if (!chip->map)
+ return -ENODEV;
+
+ chip->irq = platform_get_irq_byname(pdev, "GPI");
+ if (chip->irq < 0)
+ return chip->irq;
+
+ chip->gc.base = -1;
+ chip->gc.ngpio = 1;
+ chip->gc.label = i2c->name;
+ chip->gc.parent = dev;
+ chip->gc.owner = THIS_MODULE;
+ chip->gc.can_sleep = true;
+
+ chip->gc.direction_input = max77650_gpio_direction_input;
+ chip->gc.direction_output = max77650_gpio_direction_output;
+ chip->gc.set = max77650_gpio_set_value;
+ chip->gc.get = max77650_gpio_get_value;
+ chip->gc.get_direction = max77650_gpio_get_direction;
+ chip->gc.set_config = max77650_gpio_set_config;
+ chip->gc.to_irq = max77650_gpio_to_irq;
+
+ return devm_gpiochip_add_data(dev, &chip->gc, chip);
+}
+
+static struct platform_driver max77650_gpio_driver = {
+ .driver = {
+ .name = "max77650-gpio",
+ },
+ .probe = max77650_gpio_probe,
+};
+module_platform_driver(max77650_gpio_driver);
+
+MODULE_DESCRIPTION("MAXIM 77650/77651 GPIO driver");
+MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:max77650-gpio");
diff --git a/drivers/gpio/gpio-mb86s7x.c b/drivers/gpio/gpio-mb86s7x.c
new file mode 100644
index 0000000000..ca7eb5e8bf
--- /dev/null
+++ b/drivers/gpio/gpio-mb86s7x.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/gpio/gpio-mb86s7x.c
+ *
+ * Copyright (C) 2015 Fujitsu Semiconductor Limited
+ * Copyright (C) 2015 Linaro Ltd.
+ */
+
+#include <linux/acpi.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/clk.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+
+#include "gpiolib.h"
+#include "gpiolib-acpi.h"
+
+/*
+ * Only first 8bits of a register correspond to each pin,
+ * so there are 4 registers for 32 pins.
+ */
+#define PDR(x) (0x0 + x / 8 * 4)
+#define DDR(x) (0x10 + x / 8 * 4)
+#define PFR(x) (0x20 + x / 8 * 4)
+
+#define OFFSET(x) BIT((x) % 8)
+
+struct mb86s70_gpio_chip {
+ struct gpio_chip gc;
+ void __iomem *base;
+ struct clk *clk;
+ spinlock_t lock;
+};
+
+static int mb86s70_gpio_request(struct gpio_chip *gc, unsigned gpio)
+{
+ struct mb86s70_gpio_chip *gchip = gpiochip_get_data(gc);
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&gchip->lock, flags);
+
+ val = readl(gchip->base + PFR(gpio));
+ val &= ~OFFSET(gpio);
+ writel(val, gchip->base + PFR(gpio));
+
+ spin_unlock_irqrestore(&gchip->lock, flags);
+
+ return 0;
+}
+
+static void mb86s70_gpio_free(struct gpio_chip *gc, unsigned gpio)
+{
+ struct mb86s70_gpio_chip *gchip = gpiochip_get_data(gc);
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&gchip->lock, flags);
+
+ val = readl(gchip->base + PFR(gpio));
+ val |= OFFSET(gpio);
+ writel(val, gchip->base + PFR(gpio));
+
+ spin_unlock_irqrestore(&gchip->lock, flags);
+}
+
+static int mb86s70_gpio_direction_input(struct gpio_chip *gc, unsigned gpio)
+{
+ struct mb86s70_gpio_chip *gchip = gpiochip_get_data(gc);
+ unsigned long flags;
+ unsigned char val;
+
+ spin_lock_irqsave(&gchip->lock, flags);
+
+ val = readl(gchip->base + DDR(gpio));
+ val &= ~OFFSET(gpio);
+ writel(val, gchip->base + DDR(gpio));
+
+ spin_unlock_irqrestore(&gchip->lock, flags);
+
+ return 0;
+}
+
+static int mb86s70_gpio_direction_output(struct gpio_chip *gc,
+ unsigned gpio, int value)
+{
+ struct mb86s70_gpio_chip *gchip = gpiochip_get_data(gc);
+ unsigned long flags;
+ unsigned char val;
+
+ spin_lock_irqsave(&gchip->lock, flags);
+
+ val = readl(gchip->base + PDR(gpio));
+ if (value)
+ val |= OFFSET(gpio);
+ else
+ val &= ~OFFSET(gpio);
+ writel(val, gchip->base + PDR(gpio));
+
+ val = readl(gchip->base + DDR(gpio));
+ val |= OFFSET(gpio);
+ writel(val, gchip->base + DDR(gpio));
+
+ spin_unlock_irqrestore(&gchip->lock, flags);
+
+ return 0;
+}
+
+static int mb86s70_gpio_get(struct gpio_chip *gc, unsigned gpio)
+{
+ struct mb86s70_gpio_chip *gchip = gpiochip_get_data(gc);
+
+ return !!(readl(gchip->base + PDR(gpio)) & OFFSET(gpio));
+}
+
+static void mb86s70_gpio_set(struct gpio_chip *gc, unsigned gpio, int value)
+{
+ struct mb86s70_gpio_chip *gchip = gpiochip_get_data(gc);
+ unsigned long flags;
+ unsigned char val;
+
+ spin_lock_irqsave(&gchip->lock, flags);
+
+ val = readl(gchip->base + PDR(gpio));
+ if (value)
+ val |= OFFSET(gpio);
+ else
+ val &= ~OFFSET(gpio);
+ writel(val, gchip->base + PDR(gpio));
+
+ spin_unlock_irqrestore(&gchip->lock, flags);
+}
+
+static int mb86s70_gpio_to_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ int irq, index;
+
+ for (index = 0;; index++) {
+ irq = platform_get_irq(to_platform_device(gc->parent), index);
+ if (irq < 0)
+ return irq;
+ if (irq == 0)
+ break;
+ if (irq_get_irq_data(irq)->hwirq == offset)
+ return irq;
+ }
+ return -EINVAL;
+}
+
+static int mb86s70_gpio_probe(struct platform_device *pdev)
+{
+ struct mb86s70_gpio_chip *gchip;
+ int ret;
+
+ gchip = devm_kzalloc(&pdev->dev, sizeof(*gchip), GFP_KERNEL);
+ if (gchip == NULL)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, gchip);
+
+ gchip->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gchip->base))
+ return PTR_ERR(gchip->base);
+
+ gchip->clk = devm_clk_get_optional(&pdev->dev, NULL);
+ if (IS_ERR(gchip->clk))
+ return PTR_ERR(gchip->clk);
+
+ ret = clk_prepare_enable(gchip->clk);
+ if (ret)
+ return ret;
+
+ spin_lock_init(&gchip->lock);
+
+ gchip->gc.direction_output = mb86s70_gpio_direction_output;
+ gchip->gc.direction_input = mb86s70_gpio_direction_input;
+ gchip->gc.request = mb86s70_gpio_request;
+ gchip->gc.free = mb86s70_gpio_free;
+ gchip->gc.get = mb86s70_gpio_get;
+ gchip->gc.set = mb86s70_gpio_set;
+ gchip->gc.to_irq = mb86s70_gpio_to_irq;
+ gchip->gc.label = dev_name(&pdev->dev);
+ gchip->gc.ngpio = 32;
+ gchip->gc.owner = THIS_MODULE;
+ gchip->gc.parent = &pdev->dev;
+ gchip->gc.base = -1;
+
+ ret = gpiochip_add_data(&gchip->gc, gchip);
+ if (ret) {
+ dev_err(&pdev->dev, "couldn't register gpio driver\n");
+ clk_disable_unprepare(gchip->clk);
+ return ret;
+ }
+
+ acpi_gpiochip_request_interrupts(&gchip->gc);
+
+ return 0;
+}
+
+static int mb86s70_gpio_remove(struct platform_device *pdev)
+{
+ struct mb86s70_gpio_chip *gchip = platform_get_drvdata(pdev);
+
+ acpi_gpiochip_free_interrupts(&gchip->gc);
+ gpiochip_remove(&gchip->gc);
+ clk_disable_unprepare(gchip->clk);
+
+ return 0;
+}
+
+static const struct of_device_id mb86s70_gpio_dt_ids[] = {
+ { .compatible = "fujitsu,mb86s70-gpio" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mb86s70_gpio_dt_ids);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id mb86s70_gpio_acpi_ids[] = {
+ { "SCX0007" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(acpi, mb86s70_gpio_acpi_ids);
+#endif
+
+static struct platform_driver mb86s70_gpio_driver = {
+ .driver = {
+ .name = "mb86s70-gpio",
+ .of_match_table = mb86s70_gpio_dt_ids,
+ .acpi_match_table = ACPI_PTR(mb86s70_gpio_acpi_ids),
+ },
+ .probe = mb86s70_gpio_probe,
+ .remove = mb86s70_gpio_remove,
+};
+module_platform_driver(mb86s70_gpio_driver);
+
+MODULE_DESCRIPTION("MB86S7x GPIO Driver");
+MODULE_ALIAS("platform:mb86s70-gpio");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-mc33880.c b/drivers/gpio/gpio-mc33880.c
new file mode 100644
index 0000000000..cd9b16dbe1
--- /dev/null
+++ b/drivers/gpio/gpio-mc33880.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MC33880 high-side/low-side switch GPIO driver
+ * Copyright (c) 2009 Intel Corporation
+ */
+
+/* Supports:
+ * Freescale MC33880 high-side/low-side switch
+ */
+
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/mc33880.h>
+#include <linux/gpio/driver.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#define DRIVER_NAME "mc33880"
+
+/*
+ * Pin configurations, see MAX7301 datasheet page 6
+ */
+#define PIN_CONFIG_MASK 0x03
+#define PIN_CONFIG_IN_PULLUP 0x03
+#define PIN_CONFIG_IN_WO_PULLUP 0x02
+#define PIN_CONFIG_OUT 0x01
+
+#define PIN_NUMBER 8
+
+
+/*
+ * Some registers must be read back to modify.
+ * To save time we cache them here in memory
+ */
+struct mc33880 {
+ struct mutex lock; /* protect from simultaneous accesses */
+ u8 port_config;
+ struct gpio_chip chip;
+ struct spi_device *spi;
+};
+
+static int mc33880_write_config(struct mc33880 *mc)
+{
+ return spi_write(mc->spi, &mc->port_config, sizeof(mc->port_config));
+}
+
+
+static int __mc33880_set(struct mc33880 *mc, unsigned offset, int value)
+{
+ if (value)
+ mc->port_config |= 1 << offset;
+ else
+ mc->port_config &= ~(1 << offset);
+
+ return mc33880_write_config(mc);
+}
+
+
+static void mc33880_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct mc33880 *mc = gpiochip_get_data(chip);
+
+ mutex_lock(&mc->lock);
+
+ __mc33880_set(mc, offset, value);
+
+ mutex_unlock(&mc->lock);
+}
+
+static int mc33880_probe(struct spi_device *spi)
+{
+ struct mc33880 *mc;
+ struct mc33880_platform_data *pdata;
+ int ret;
+
+ pdata = dev_get_platdata(&spi->dev);
+ if (!pdata || !pdata->base) {
+ dev_dbg(&spi->dev, "incorrect or missing platform data\n");
+ return -EINVAL;
+ }
+
+ /*
+ * bits_per_word cannot be configured in platform data
+ */
+ spi->bits_per_word = 8;
+
+ ret = spi_setup(spi);
+ if (ret < 0)
+ return ret;
+
+ mc = devm_kzalloc(&spi->dev, sizeof(struct mc33880), GFP_KERNEL);
+ if (!mc)
+ return -ENOMEM;
+
+ mutex_init(&mc->lock);
+
+ spi_set_drvdata(spi, mc);
+
+ mc->spi = spi;
+
+ mc->chip.label = DRIVER_NAME,
+ mc->chip.set = mc33880_set;
+ mc->chip.base = pdata->base;
+ mc->chip.ngpio = PIN_NUMBER;
+ mc->chip.can_sleep = true;
+ mc->chip.parent = &spi->dev;
+ mc->chip.owner = THIS_MODULE;
+
+ mc->port_config = 0x00;
+ /* write twice, because during initialisation the first setting
+ * is just for testing SPI communication, and the second is the
+ * "real" configuration
+ */
+ ret = mc33880_write_config(mc);
+ mc->port_config = 0x00;
+ if (!ret)
+ ret = mc33880_write_config(mc);
+
+ if (ret) {
+ dev_err(&spi->dev, "Failed writing to " DRIVER_NAME ": %d\n",
+ ret);
+ goto exit_destroy;
+ }
+
+ ret = gpiochip_add_data(&mc->chip, mc);
+ if (ret)
+ goto exit_destroy;
+
+ return ret;
+
+exit_destroy:
+ mutex_destroy(&mc->lock);
+ return ret;
+}
+
+static void mc33880_remove(struct spi_device *spi)
+{
+ struct mc33880 *mc;
+
+ mc = spi_get_drvdata(spi);
+
+ gpiochip_remove(&mc->chip);
+ mutex_destroy(&mc->lock);
+}
+
+static struct spi_driver mc33880_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+ .probe = mc33880_probe,
+ .remove = mc33880_remove,
+};
+
+static int __init mc33880_init(void)
+{
+ return spi_register_driver(&mc33880_driver);
+}
+/* register after spi postcore initcall and before
+ * subsys initcalls that may rely on these GPIOs
+ */
+subsys_initcall(mc33880_init);
+
+static void __exit mc33880_exit(void)
+{
+ spi_unregister_driver(&mc33880_driver);
+}
+module_exit(mc33880_exit);
+
+MODULE_AUTHOR("Mocean Laboratories <info@mocean-labs.com>");
+MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/gpio/gpio-menz127.c b/drivers/gpio/gpio-menz127.c
new file mode 100644
index 0000000000..a035a9bcb5
--- /dev/null
+++ b/drivers/gpio/gpio-menz127.c
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MEN 16Z127 GPIO driver
+ *
+ * Copyright (C) 2016 MEN Mikroelektronik GmbH (www.men.de)
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/mcb.h>
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+
+#define MEN_Z127_CTRL 0x00
+#define MEN_Z127_PSR 0x04
+#define MEN_Z127_IRQR 0x08
+#define MEN_Z127_GPIODR 0x0c
+#define MEN_Z127_IER1 0x10
+#define MEN_Z127_IER2 0x14
+#define MEN_Z127_DBER 0x18
+#define MEN_Z127_ODER 0x1C
+#define GPIO_TO_DBCNT_REG(gpio) ((gpio * 4) + 0x80)
+
+#define MEN_Z127_DB_MIN_US 50
+/* 16 bit compare register. Each bit represents 50us */
+#define MEN_Z127_DB_MAX_US (0xffff * MEN_Z127_DB_MIN_US)
+#define MEN_Z127_DB_IN_RANGE(db) ((db >= MEN_Z127_DB_MIN_US) && \
+ (db <= MEN_Z127_DB_MAX_US))
+
+struct men_z127_gpio {
+ struct gpio_chip gc;
+ void __iomem *reg_base;
+ struct resource *mem;
+};
+
+static int men_z127_debounce(struct gpio_chip *gc, unsigned gpio,
+ unsigned debounce)
+{
+ struct men_z127_gpio *priv = gpiochip_get_data(gc);
+ struct device *dev = gc->parent;
+ unsigned int rnd;
+ u32 db_en, db_cnt;
+
+ if (!MEN_Z127_DB_IN_RANGE(debounce)) {
+ dev_err(dev, "debounce value %u out of range", debounce);
+ return -EINVAL;
+ }
+
+ if (debounce > 0) {
+ /* round up or down depending on MSB-1 */
+ rnd = fls(debounce) - 1;
+
+ if (rnd && (debounce & BIT(rnd - 1)))
+ debounce = roundup(debounce, MEN_Z127_DB_MIN_US);
+ else
+ debounce = rounddown(debounce, MEN_Z127_DB_MIN_US);
+
+ if (debounce > MEN_Z127_DB_MAX_US)
+ debounce = MEN_Z127_DB_MAX_US;
+
+ /* 50us per register unit */
+ debounce /= 50;
+ }
+
+ raw_spin_lock(&gc->bgpio_lock);
+
+ db_en = readl(priv->reg_base + MEN_Z127_DBER);
+
+ if (debounce == 0) {
+ db_en &= ~BIT(gpio);
+ db_cnt = 0;
+ } else {
+ db_en |= BIT(gpio);
+ db_cnt = debounce;
+ }
+
+ writel(db_en, priv->reg_base + MEN_Z127_DBER);
+ writel(db_cnt, priv->reg_base + GPIO_TO_DBCNT_REG(gpio));
+
+ raw_spin_unlock(&gc->bgpio_lock);
+
+ return 0;
+}
+
+static int men_z127_set_single_ended(struct gpio_chip *gc,
+ unsigned offset,
+ enum pin_config_param param)
+{
+ struct men_z127_gpio *priv = gpiochip_get_data(gc);
+ u32 od_en;
+
+ raw_spin_lock(&gc->bgpio_lock);
+ od_en = readl(priv->reg_base + MEN_Z127_ODER);
+
+ if (param == PIN_CONFIG_DRIVE_OPEN_DRAIN)
+ od_en |= BIT(offset);
+ else
+ /* Implicitly PIN_CONFIG_DRIVE_PUSH_PULL */
+ od_en &= ~BIT(offset);
+
+ writel(od_en, priv->reg_base + MEN_Z127_ODER);
+ raw_spin_unlock(&gc->bgpio_lock);
+
+ return 0;
+}
+
+static int men_z127_set_config(struct gpio_chip *gc, unsigned offset,
+ unsigned long config)
+{
+ enum pin_config_param param = pinconf_to_config_param(config);
+
+ switch (param) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ return men_z127_set_single_ended(gc, offset, param);
+
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ return men_z127_debounce(gc, offset,
+ pinconf_to_config_argument(config));
+
+ default:
+ break;
+ }
+
+ return -ENOTSUPP;
+}
+
+static int men_z127_probe(struct mcb_device *mdev,
+ const struct mcb_device_id *id)
+{
+ struct men_z127_gpio *men_z127_gpio;
+ struct device *dev = &mdev->dev;
+ int ret;
+
+ men_z127_gpio = devm_kzalloc(dev, sizeof(struct men_z127_gpio),
+ GFP_KERNEL);
+ if (!men_z127_gpio)
+ return -ENOMEM;
+
+ men_z127_gpio->mem = mcb_request_mem(mdev, dev_name(dev));
+ if (IS_ERR(men_z127_gpio->mem)) {
+ dev_err(dev, "failed to request device memory");
+ return PTR_ERR(men_z127_gpio->mem);
+ }
+
+ men_z127_gpio->reg_base = ioremap(men_z127_gpio->mem->start,
+ resource_size(men_z127_gpio->mem));
+ if (men_z127_gpio->reg_base == NULL) {
+ ret = -ENXIO;
+ goto err_release;
+ }
+
+ mcb_set_drvdata(mdev, men_z127_gpio);
+
+ ret = bgpio_init(&men_z127_gpio->gc, &mdev->dev, 4,
+ men_z127_gpio->reg_base + MEN_Z127_PSR,
+ men_z127_gpio->reg_base + MEN_Z127_CTRL,
+ NULL,
+ men_z127_gpio->reg_base + MEN_Z127_GPIODR,
+ NULL, 0);
+ if (ret)
+ goto err_unmap;
+
+ men_z127_gpio->gc.set_config = men_z127_set_config;
+
+ ret = gpiochip_add_data(&men_z127_gpio->gc, men_z127_gpio);
+ if (ret) {
+ dev_err(dev, "failed to register MEN 16Z127 GPIO controller");
+ goto err_unmap;
+ }
+
+ dev_info(dev, "MEN 16Z127 GPIO driver registered");
+
+ return 0;
+
+err_unmap:
+ iounmap(men_z127_gpio->reg_base);
+err_release:
+ mcb_release_mem(men_z127_gpio->mem);
+ return ret;
+}
+
+static void men_z127_remove(struct mcb_device *mdev)
+{
+ struct men_z127_gpio *men_z127_gpio = mcb_get_drvdata(mdev);
+
+ gpiochip_remove(&men_z127_gpio->gc);
+ iounmap(men_z127_gpio->reg_base);
+ mcb_release_mem(men_z127_gpio->mem);
+}
+
+static const struct mcb_device_id men_z127_ids[] = {
+ { .device = 0x7f },
+ { }
+};
+MODULE_DEVICE_TABLE(mcb, men_z127_ids);
+
+static struct mcb_driver men_z127_driver = {
+ .driver = {
+ .name = "z127-gpio",
+ },
+ .probe = men_z127_probe,
+ .remove = men_z127_remove,
+ .id_table = men_z127_ids,
+};
+module_mcb_driver(men_z127_driver);
+
+MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>");
+MODULE_DESCRIPTION("MEN 16z127 GPIO Controller");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("mcb:16z127");
+MODULE_IMPORT_NS(MCB);
diff --git a/drivers/gpio/gpio-merrifield.c b/drivers/gpio/gpio-merrifield.c
new file mode 100644
index 0000000000..421d7e3a6c
--- /dev/null
+++ b/drivers/gpio/gpio-merrifield.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Merrifield SoC GPIO driver
+ *
+ * Copyright (c) 2016, 2023 Intel Corporation.
+ * Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/types.h>
+
+#include "gpio-tangier.h"
+
+/* Intel Merrifield has 192 GPIO pins */
+#define MRFLD_NGPIO 192
+
+static const struct tng_gpio_pinrange mrfld_gpio_ranges[] = {
+ GPIO_PINRANGE(0, 11, 146),
+ GPIO_PINRANGE(12, 13, 144),
+ GPIO_PINRANGE(14, 15, 35),
+ GPIO_PINRANGE(16, 16, 164),
+ GPIO_PINRANGE(17, 18, 105),
+ GPIO_PINRANGE(19, 22, 101),
+ GPIO_PINRANGE(23, 30, 107),
+ GPIO_PINRANGE(32, 43, 67),
+ GPIO_PINRANGE(44, 63, 195),
+ GPIO_PINRANGE(64, 67, 140),
+ GPIO_PINRANGE(68, 69, 165),
+ GPIO_PINRANGE(70, 71, 65),
+ GPIO_PINRANGE(72, 76, 228),
+ GPIO_PINRANGE(77, 86, 37),
+ GPIO_PINRANGE(87, 87, 48),
+ GPIO_PINRANGE(88, 88, 47),
+ GPIO_PINRANGE(89, 96, 49),
+ GPIO_PINRANGE(97, 97, 34),
+ GPIO_PINRANGE(102, 119, 83),
+ GPIO_PINRANGE(120, 123, 79),
+ GPIO_PINRANGE(124, 135, 115),
+ GPIO_PINRANGE(137, 142, 158),
+ GPIO_PINRANGE(154, 163, 24),
+ GPIO_PINRANGE(164, 176, 215),
+ GPIO_PINRANGE(177, 189, 127),
+ GPIO_PINRANGE(190, 191, 178),
+};
+
+static const char *mrfld_gpio_get_pinctrl_dev_name(struct tng_gpio *priv)
+{
+ struct device *dev = priv->dev;
+ struct acpi_device *adev;
+ const char *name;
+
+ adev = acpi_dev_get_first_match_dev("INTC1002", NULL, -1);
+ if (adev) {
+ name = devm_kstrdup(dev, acpi_dev_name(adev), GFP_KERNEL);
+ acpi_dev_put(adev);
+ } else {
+ name = "pinctrl-merrifield";
+ }
+
+ return name;
+}
+
+static int mrfld_gpio_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct device *dev = &pdev->dev;
+ struct tng_gpio *priv;
+ u32 gpio_base, irq_base;
+ void __iomem *base;
+ int retval;
+
+ retval = pcim_enable_device(pdev);
+ if (retval)
+ return retval;
+
+ retval = pcim_iomap_regions(pdev, BIT(1) | BIT(0), pci_name(pdev));
+ if (retval)
+ return dev_err_probe(dev, retval, "I/O memory mapping error\n");
+
+ base = pcim_iomap_table(pdev)[1];
+
+ irq_base = readl(base + 0 * sizeof(u32));
+ gpio_base = readl(base + 1 * sizeof(u32));
+
+ /* Release the IO mapping, since we already get the info from BAR1 */
+ pcim_iounmap_regions(pdev, BIT(1));
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->reg_base = pcim_iomap_table(pdev)[0];
+
+ priv->pin_info.pin_ranges = mrfld_gpio_ranges;
+ priv->pin_info.nranges = ARRAY_SIZE(mrfld_gpio_ranges);
+ priv->pin_info.name = mrfld_gpio_get_pinctrl_dev_name(priv);
+ if (!priv->pin_info.name)
+ return -ENOMEM;
+
+ priv->info.base = gpio_base;
+ priv->info.ngpio = MRFLD_NGPIO;
+ priv->info.first = irq_base;
+
+ retval = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
+ if (retval < 0)
+ return retval;
+
+ priv->irq = pci_irq_vector(pdev, 0);
+
+ priv->wake_regs.gwmr = GWMR_MRFLD;
+ priv->wake_regs.gwsr = GWSR_MRFLD;
+ priv->wake_regs.gsir = GSIR_MRFLD;
+
+ retval = devm_tng_gpio_probe(dev, priv);
+ if (retval)
+ return dev_err_probe(dev, retval, "tng_gpio_probe error\n");
+
+ pci_set_drvdata(pdev, priv);
+ return 0;
+}
+
+static const struct pci_device_id mrfld_gpio_ids[] = {
+ { PCI_VDEVICE(INTEL, 0x1199) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, mrfld_gpio_ids);
+
+static struct pci_driver mrfld_gpio_driver = {
+ .name = "gpio-merrifield",
+ .id_table = mrfld_gpio_ids,
+ .probe = mrfld_gpio_probe,
+};
+module_pci_driver(mrfld_gpio_driver);
+
+MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>");
+MODULE_DESCRIPTION("Intel Merrifield SoC GPIO driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(GPIO_TANGIER);
diff --git a/drivers/gpio/gpio-ml-ioh.c b/drivers/gpio/gpio-ml-ioh.c
new file mode 100644
index 0000000000..48e3768a83
--- /dev/null
+++ b/drivers/gpio/gpio-ml-ioh.c
@@ -0,0 +1,526 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2010 OKI SEMICONDUCTOR Co., LTD.
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#define IOH_EDGE_FALLING 0
+#define IOH_EDGE_RISING BIT(0)
+#define IOH_LEVEL_L BIT(1)
+#define IOH_LEVEL_H (BIT(0) | BIT(1))
+#define IOH_EDGE_BOTH BIT(2)
+#define IOH_IM_MASK (BIT(0) | BIT(1) | BIT(2))
+
+#define IOH_IRQ_BASE 0
+
+struct ioh_reg_comn {
+ u32 ien;
+ u32 istatus;
+ u32 idisp;
+ u32 iclr;
+ u32 imask;
+ u32 imaskclr;
+ u32 po;
+ u32 pi;
+ u32 pm;
+ u32 im_0;
+ u32 im_1;
+ u32 reserved;
+};
+
+struct ioh_regs {
+ struct ioh_reg_comn regs[8];
+ u32 reserve1[16];
+ u32 ioh_sel_reg[4];
+ u32 reserve2[11];
+ u32 srst;
+};
+
+/**
+ * struct ioh_gpio_reg_data - The register store data.
+ * @ien_reg: To store contents of interrupt enable register.
+ * @imask_reg: To store contents of interrupt mask regist
+ * @po_reg: To store contents of PO register.
+ * @pm_reg: To store contents of PM register.
+ * @im0_reg: To store contents of interrupt mode regist0
+ * @im1_reg: To store contents of interrupt mode regist1
+ * @use_sel_reg: To store contents of GPIO_USE_SEL0~3
+ */
+struct ioh_gpio_reg_data {
+ u32 ien_reg;
+ u32 imask_reg;
+ u32 po_reg;
+ u32 pm_reg;
+ u32 im0_reg;
+ u32 im1_reg;
+ u32 use_sel_reg;
+};
+
+/**
+ * struct ioh_gpio - GPIO private data structure.
+ * @base: PCI base address of Memory mapped I/O register.
+ * @reg: Memory mapped IOH GPIO register list.
+ * @dev: Pointer to device structure.
+ * @gpio: Data for GPIO infrastructure.
+ * @ioh_gpio_reg: Memory mapped Register data is saved here
+ * when suspend.
+ * @gpio_use_sel: Save GPIO_USE_SEL1~4 register for PM
+ * @ch: Indicate GPIO channel
+ * @irq_base: Save base of IRQ number for interrupt
+ * @spinlock: Used for register access protection
+ */
+struct ioh_gpio {
+ void __iomem *base;
+ struct ioh_regs __iomem *reg;
+ struct device *dev;
+ struct gpio_chip gpio;
+ struct ioh_gpio_reg_data ioh_gpio_reg;
+ u32 gpio_use_sel;
+ int ch;
+ int irq_base;
+ spinlock_t spinlock;
+};
+
+static const int num_ports[] = {6, 12, 16, 16, 15, 16, 16, 12};
+
+static void ioh_gpio_set(struct gpio_chip *gpio, unsigned nr, int val)
+{
+ u32 reg_val;
+ struct ioh_gpio *chip = gpiochip_get_data(gpio);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ reg_val = ioread32(&chip->reg->regs[chip->ch].po);
+ if (val)
+ reg_val |= BIT(nr);
+ else
+ reg_val &= ~BIT(nr);
+
+ iowrite32(reg_val, &chip->reg->regs[chip->ch].po);
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+}
+
+static int ioh_gpio_get(struct gpio_chip *gpio, unsigned nr)
+{
+ struct ioh_gpio *chip = gpiochip_get_data(gpio);
+
+ return !!(ioread32(&chip->reg->regs[chip->ch].pi) & BIT(nr));
+}
+
+static int ioh_gpio_direction_output(struct gpio_chip *gpio, unsigned nr,
+ int val)
+{
+ struct ioh_gpio *chip = gpiochip_get_data(gpio);
+ u32 pm;
+ u32 reg_val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ pm = ioread32(&chip->reg->regs[chip->ch].pm);
+ pm &= BIT(num_ports[chip->ch]) - 1;
+ pm |= BIT(nr);
+ iowrite32(pm, &chip->reg->regs[chip->ch].pm);
+
+ reg_val = ioread32(&chip->reg->regs[chip->ch].po);
+ if (val)
+ reg_val |= BIT(nr);
+ else
+ reg_val &= ~BIT(nr);
+ iowrite32(reg_val, &chip->reg->regs[chip->ch].po);
+
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 0;
+}
+
+static int ioh_gpio_direction_input(struct gpio_chip *gpio, unsigned nr)
+{
+ struct ioh_gpio *chip = gpiochip_get_data(gpio);
+ u32 pm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ pm = ioread32(&chip->reg->regs[chip->ch].pm);
+ pm &= BIT(num_ports[chip->ch]) - 1;
+ pm &= ~BIT(nr);
+ iowrite32(pm, &chip->reg->regs[chip->ch].pm);
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 0;
+}
+
+/*
+ * Save register configuration and disable interrupts.
+ */
+static void __maybe_unused ioh_gpio_save_reg_conf(struct ioh_gpio *chip)
+{
+ int i;
+
+ for (i = 0; i < 8; i ++, chip++) {
+ chip->ioh_gpio_reg.po_reg =
+ ioread32(&chip->reg->regs[chip->ch].po);
+ chip->ioh_gpio_reg.pm_reg =
+ ioread32(&chip->reg->regs[chip->ch].pm);
+ chip->ioh_gpio_reg.ien_reg =
+ ioread32(&chip->reg->regs[chip->ch].ien);
+ chip->ioh_gpio_reg.imask_reg =
+ ioread32(&chip->reg->regs[chip->ch].imask);
+ chip->ioh_gpio_reg.im0_reg =
+ ioread32(&chip->reg->regs[chip->ch].im_0);
+ chip->ioh_gpio_reg.im1_reg =
+ ioread32(&chip->reg->regs[chip->ch].im_1);
+ if (i < 4)
+ chip->ioh_gpio_reg.use_sel_reg =
+ ioread32(&chip->reg->ioh_sel_reg[i]);
+ }
+}
+
+/*
+ * This function restores the register configuration of the GPIO device.
+ */
+static void __maybe_unused ioh_gpio_restore_reg_conf(struct ioh_gpio *chip)
+{
+ int i;
+
+ for (i = 0; i < 8; i ++, chip++) {
+ iowrite32(chip->ioh_gpio_reg.po_reg,
+ &chip->reg->regs[chip->ch].po);
+ iowrite32(chip->ioh_gpio_reg.pm_reg,
+ &chip->reg->regs[chip->ch].pm);
+ iowrite32(chip->ioh_gpio_reg.ien_reg,
+ &chip->reg->regs[chip->ch].ien);
+ iowrite32(chip->ioh_gpio_reg.imask_reg,
+ &chip->reg->regs[chip->ch].imask);
+ iowrite32(chip->ioh_gpio_reg.im0_reg,
+ &chip->reg->regs[chip->ch].im_0);
+ iowrite32(chip->ioh_gpio_reg.im1_reg,
+ &chip->reg->regs[chip->ch].im_1);
+ if (i < 4)
+ iowrite32(chip->ioh_gpio_reg.use_sel_reg,
+ &chip->reg->ioh_sel_reg[i]);
+ }
+}
+
+static int ioh_gpio_to_irq(struct gpio_chip *gpio, unsigned offset)
+{
+ struct ioh_gpio *chip = gpiochip_get_data(gpio);
+ return chip->irq_base + offset;
+}
+
+static void ioh_gpio_setup(struct ioh_gpio *chip, int num_port)
+{
+ struct gpio_chip *gpio = &chip->gpio;
+
+ gpio->label = dev_name(chip->dev);
+ gpio->owner = THIS_MODULE;
+ gpio->direction_input = ioh_gpio_direction_input;
+ gpio->get = ioh_gpio_get;
+ gpio->direction_output = ioh_gpio_direction_output;
+ gpio->set = ioh_gpio_set;
+ gpio->dbg_show = NULL;
+ gpio->base = -1;
+ gpio->ngpio = num_port;
+ gpio->can_sleep = false;
+ gpio->to_irq = ioh_gpio_to_irq;
+}
+
+static int ioh_irq_type(struct irq_data *d, unsigned int type)
+{
+ u32 im;
+ void __iomem *im_reg;
+ u32 ien;
+ u32 im_pos;
+ int ch;
+ unsigned long flags;
+ u32 val;
+ int irq = d->irq;
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct ioh_gpio *chip = gc->private;
+
+ ch = irq - chip->irq_base;
+ if (irq <= chip->irq_base + 7) {
+ im_reg = &chip->reg->regs[chip->ch].im_0;
+ im_pos = ch;
+ } else {
+ im_reg = &chip->reg->regs[chip->ch].im_1;
+ im_pos = ch - 8;
+ }
+ dev_dbg(chip->dev, "%s:irq=%d type=%d ch=%d pos=%d type=%d\n",
+ __func__, irq, type, ch, im_pos, type);
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ val = IOH_EDGE_RISING;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ val = IOH_EDGE_FALLING;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ val = IOH_EDGE_BOTH;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ val = IOH_LEVEL_H;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ val = IOH_LEVEL_L;
+ break;
+ case IRQ_TYPE_PROBE:
+ goto end;
+ default:
+ dev_warn(chip->dev, "%s: unknown type(%dd)",
+ __func__, type);
+ goto end;
+ }
+
+ /* Set interrupt mode */
+ im = ioread32(im_reg) & ~(IOH_IM_MASK << (im_pos * 4));
+ iowrite32(im | (val << (im_pos * 4)), im_reg);
+
+ /* iclr */
+ iowrite32(BIT(ch), &chip->reg->regs[chip->ch].iclr);
+
+ /* IMASKCLR */
+ iowrite32(BIT(ch), &chip->reg->regs[chip->ch].imaskclr);
+
+ /* Enable interrupt */
+ ien = ioread32(&chip->reg->regs[chip->ch].ien);
+ iowrite32(ien | BIT(ch), &chip->reg->regs[chip->ch].ien);
+end:
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 0;
+}
+
+static void ioh_irq_unmask(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct ioh_gpio *chip = gc->private;
+
+ iowrite32(BIT(d->irq - chip->irq_base),
+ &chip->reg->regs[chip->ch].imaskclr);
+}
+
+static void ioh_irq_mask(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct ioh_gpio *chip = gc->private;
+
+ iowrite32(BIT(d->irq - chip->irq_base),
+ &chip->reg->regs[chip->ch].imask);
+}
+
+static void ioh_irq_disable(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct ioh_gpio *chip = gc->private;
+ unsigned long flags;
+ u32 ien;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ ien = ioread32(&chip->reg->regs[chip->ch].ien);
+ ien &= ~BIT(d->irq - chip->irq_base);
+ iowrite32(ien, &chip->reg->regs[chip->ch].ien);
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+}
+
+static void ioh_irq_enable(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct ioh_gpio *chip = gc->private;
+ unsigned long flags;
+ u32 ien;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ ien = ioread32(&chip->reg->regs[chip->ch].ien);
+ ien |= BIT(d->irq - chip->irq_base);
+ iowrite32(ien, &chip->reg->regs[chip->ch].ien);
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+}
+
+static irqreturn_t ioh_gpio_handler(int irq, void *dev_id)
+{
+ struct ioh_gpio *chip = dev_id;
+ u32 reg_val;
+ int i, j;
+ int ret = IRQ_NONE;
+
+ for (i = 0; i < 8; i++, chip++) {
+ reg_val = ioread32(&chip->reg->regs[i].istatus);
+ for (j = 0; j < num_ports[i]; j++) {
+ if (reg_val & BIT(j)) {
+ dev_dbg(chip->dev,
+ "%s:[%d]:irq=%d status=0x%x\n",
+ __func__, j, irq, reg_val);
+ iowrite32(BIT(j),
+ &chip->reg->regs[chip->ch].iclr);
+ generic_handle_irq(chip->irq_base + j);
+ ret = IRQ_HANDLED;
+ }
+ }
+ }
+ return ret;
+}
+
+static int ioh_gpio_alloc_generic_chip(struct ioh_gpio *chip,
+ unsigned int irq_start,
+ unsigned int num)
+{
+ struct irq_chip_generic *gc;
+ struct irq_chip_type *ct;
+ int rv;
+
+ gc = devm_irq_alloc_generic_chip(chip->dev, "ioh_gpio", 1, irq_start,
+ chip->base, handle_simple_irq);
+ if (!gc)
+ return -ENOMEM;
+
+ gc->private = chip;
+ ct = gc->chip_types;
+
+ ct->chip.irq_mask = ioh_irq_mask;
+ ct->chip.irq_unmask = ioh_irq_unmask;
+ ct->chip.irq_set_type = ioh_irq_type;
+ ct->chip.irq_disable = ioh_irq_disable;
+ ct->chip.irq_enable = ioh_irq_enable;
+
+ rv = devm_irq_setup_generic_chip(chip->dev, gc, IRQ_MSK(num),
+ IRQ_GC_INIT_MASK_CACHE,
+ IRQ_NOREQUEST | IRQ_NOPROBE, 0);
+
+ return rv;
+}
+
+static int ioh_gpio_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+ int i, j;
+ struct ioh_gpio *chip;
+ void __iomem *base;
+ void *chip_save;
+ int irq_base;
+
+ ret = pcim_enable_device(pdev);
+ if (ret) {
+ dev_err(dev, "%s : pcim_enable_device failed", __func__);
+ return ret;
+ }
+
+ ret = pcim_iomap_regions(pdev, BIT(1), KBUILD_MODNAME);
+ if (ret) {
+ dev_err(dev, "pcim_iomap_regions failed-%d", ret);
+ return ret;
+ }
+
+ base = pcim_iomap_table(pdev)[1];
+ if (!base) {
+ dev_err(dev, "%s : pcim_iomap_table failed", __func__);
+ return -ENOMEM;
+ }
+
+ chip_save = devm_kcalloc(dev, 8, sizeof(*chip), GFP_KERNEL);
+ if (chip_save == NULL) {
+ return -ENOMEM;
+ }
+
+ chip = chip_save;
+ for (i = 0; i < 8; i++, chip++) {
+ chip->dev = dev;
+ chip->base = base;
+ chip->reg = chip->base;
+ chip->ch = i;
+ spin_lock_init(&chip->spinlock);
+ ioh_gpio_setup(chip, num_ports[i]);
+ ret = devm_gpiochip_add_data(dev, &chip->gpio, chip);
+ if (ret) {
+ dev_err(dev, "IOH gpio: Failed to register GPIO\n");
+ return ret;
+ }
+ }
+
+ chip = chip_save;
+ for (j = 0; j < 8; j++, chip++) {
+ irq_base = devm_irq_alloc_descs(dev, -1, IOH_IRQ_BASE,
+ num_ports[j], NUMA_NO_NODE);
+ if (irq_base < 0) {
+ dev_warn(dev,
+ "ml_ioh_gpio: Failed to get IRQ base num\n");
+ return irq_base;
+ }
+ chip->irq_base = irq_base;
+
+ ret = ioh_gpio_alloc_generic_chip(chip,
+ irq_base, num_ports[j]);
+ if (ret)
+ return ret;
+ }
+
+ chip = chip_save;
+ ret = devm_request_irq(dev, pdev->irq, ioh_gpio_handler,
+ IRQF_SHARED, KBUILD_MODNAME, chip);
+ if (ret != 0) {
+ dev_err(dev, "%s request_irq failed\n", __func__);
+ return ret;
+ }
+
+ pci_set_drvdata(pdev, chip);
+
+ return 0;
+}
+
+static int __maybe_unused ioh_gpio_suspend(struct device *dev)
+{
+ struct ioh_gpio *chip = dev_get_drvdata(dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ ioh_gpio_save_reg_conf(chip);
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 0;
+}
+
+static int __maybe_unused ioh_gpio_resume(struct device *dev)
+{
+ struct ioh_gpio *chip = dev_get_drvdata(dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ iowrite32(0x01, &chip->reg->srst);
+ iowrite32(0x00, &chip->reg->srst);
+ ioh_gpio_restore_reg_conf(chip);
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ioh_gpio_pm_ops, ioh_gpio_suspend, ioh_gpio_resume);
+
+static const struct pci_device_id ioh_gpio_pcidev_id[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ROHM, 0x802E) },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, ioh_gpio_pcidev_id);
+
+static struct pci_driver ioh_gpio_driver = {
+ .name = "ml_ioh_gpio",
+ .id_table = ioh_gpio_pcidev_id,
+ .probe = ioh_gpio_probe,
+ .driver = {
+ .pm = &ioh_gpio_pm_ops,
+ },
+};
+
+module_pci_driver(ioh_gpio_driver);
+
+MODULE_DESCRIPTION("OKI SEMICONDUCTOR ML-IOH series GPIO Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-mlxbf.c b/drivers/gpio/gpio-mlxbf.c
new file mode 100644
index 0000000000..1fa9973f55
--- /dev/null
+++ b/drivers/gpio/gpio-mlxbf.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/acpi.h>
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/resource.h>
+#include <linux/types.h>
+
+/* Number of pins on BlueField */
+#define MLXBF_GPIO_NR 54
+
+/* Pad Electrical Controls. */
+#define MLXBF_GPIO_PAD_CONTROL_FIRST_WORD 0x0700
+#define MLXBF_GPIO_PAD_CONTROL_1_FIRST_WORD 0x0708
+#define MLXBF_GPIO_PAD_CONTROL_2_FIRST_WORD 0x0710
+#define MLXBF_GPIO_PAD_CONTROL_3_FIRST_WORD 0x0718
+
+#define MLXBF_GPIO_PIN_DIR_I 0x1040
+#define MLXBF_GPIO_PIN_DIR_O 0x1048
+#define MLXBF_GPIO_PIN_STATE 0x1000
+#define MLXBF_GPIO_SCRATCHPAD 0x20
+
+#ifdef CONFIG_PM
+struct mlxbf_gpio_context_save_regs {
+ u64 scratchpad;
+ u64 pad_control[MLXBF_GPIO_NR];
+ u64 pin_dir_i;
+ u64 pin_dir_o;
+};
+#endif
+
+/* Device state structure. */
+struct mlxbf_gpio_state {
+ struct gpio_chip gc;
+
+ /* Memory Address */
+ void __iomem *base;
+
+#ifdef CONFIG_PM
+ struct mlxbf_gpio_context_save_regs csave_regs;
+#endif
+};
+
+static int mlxbf_gpio_probe(struct platform_device *pdev)
+{
+ struct mlxbf_gpio_state *gs;
+ struct device *dev = &pdev->dev;
+ struct gpio_chip *gc;
+ int ret;
+
+ gs = devm_kzalloc(&pdev->dev, sizeof(*gs), GFP_KERNEL);
+ if (!gs)
+ return -ENOMEM;
+
+ gs->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gs->base))
+ return PTR_ERR(gs->base);
+
+ gc = &gs->gc;
+ ret = bgpio_init(gc, dev, 8,
+ gs->base + MLXBF_GPIO_PIN_STATE,
+ NULL,
+ NULL,
+ gs->base + MLXBF_GPIO_PIN_DIR_O,
+ gs->base + MLXBF_GPIO_PIN_DIR_I,
+ 0);
+ if (ret)
+ return -ENODEV;
+
+ gc->owner = THIS_MODULE;
+ gc->ngpio = MLXBF_GPIO_NR;
+
+ ret = devm_gpiochip_add_data(dev, &gs->gc, gs);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, gs);
+ dev_info(&pdev->dev, "registered Mellanox BlueField GPIO");
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mlxbf_gpio_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct mlxbf_gpio_state *gs = platform_get_drvdata(pdev);
+
+ gs->csave_regs.scratchpad = readq(gs->base + MLXBF_GPIO_SCRATCHPAD);
+ gs->csave_regs.pad_control[0] =
+ readq(gs->base + MLXBF_GPIO_PAD_CONTROL_FIRST_WORD);
+ gs->csave_regs.pad_control[1] =
+ readq(gs->base + MLXBF_GPIO_PAD_CONTROL_1_FIRST_WORD);
+ gs->csave_regs.pad_control[2] =
+ readq(gs->base + MLXBF_GPIO_PAD_CONTROL_2_FIRST_WORD);
+ gs->csave_regs.pad_control[3] =
+ readq(gs->base + MLXBF_GPIO_PAD_CONTROL_3_FIRST_WORD);
+ gs->csave_regs.pin_dir_i = readq(gs->base + MLXBF_GPIO_PIN_DIR_I);
+ gs->csave_regs.pin_dir_o = readq(gs->base + MLXBF_GPIO_PIN_DIR_O);
+
+ return 0;
+}
+
+static int mlxbf_gpio_resume(struct platform_device *pdev)
+{
+ struct mlxbf_gpio_state *gs = platform_get_drvdata(pdev);
+
+ writeq(gs->csave_regs.scratchpad, gs->base + MLXBF_GPIO_SCRATCHPAD);
+ writeq(gs->csave_regs.pad_control[0],
+ gs->base + MLXBF_GPIO_PAD_CONTROL_FIRST_WORD);
+ writeq(gs->csave_regs.pad_control[1],
+ gs->base + MLXBF_GPIO_PAD_CONTROL_1_FIRST_WORD);
+ writeq(gs->csave_regs.pad_control[2],
+ gs->base + MLXBF_GPIO_PAD_CONTROL_2_FIRST_WORD);
+ writeq(gs->csave_regs.pad_control[3],
+ gs->base + MLXBF_GPIO_PAD_CONTROL_3_FIRST_WORD);
+ writeq(gs->csave_regs.pin_dir_i, gs->base + MLXBF_GPIO_PIN_DIR_I);
+ writeq(gs->csave_regs.pin_dir_o, gs->base + MLXBF_GPIO_PIN_DIR_O);
+
+ return 0;
+}
+#endif
+
+static const struct acpi_device_id __maybe_unused mlxbf_gpio_acpi_match[] = {
+ { "MLNXBF02", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, mlxbf_gpio_acpi_match);
+
+static struct platform_driver mlxbf_gpio_driver = {
+ .driver = {
+ .name = "mlxbf_gpio",
+ .acpi_match_table = ACPI_PTR(mlxbf_gpio_acpi_match),
+ },
+ .probe = mlxbf_gpio_probe,
+#ifdef CONFIG_PM
+ .suspend = mlxbf_gpio_suspend,
+ .resume = mlxbf_gpio_resume,
+#endif
+};
+
+module_platform_driver(mlxbf_gpio_driver);
+
+MODULE_DESCRIPTION("Mellanox BlueField GPIO Driver");
+MODULE_AUTHOR("Mellanox Technologies");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-mlxbf2.c b/drivers/gpio/gpio-mlxbf2.c
new file mode 100644
index 0000000000..6abe01bc39
--- /dev/null
+++ b/drivers/gpio/gpio-mlxbf2.c
@@ -0,0 +1,478 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2020-2021 NVIDIA CORPORATION & AFFILIATES
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/resource.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+/*
+ * There are 3 YU GPIO blocks:
+ * gpio[0]: HOST_GPIO0->HOST_GPIO31
+ * gpio[1]: HOST_GPIO32->HOST_GPIO63
+ * gpio[2]: HOST_GPIO64->HOST_GPIO69
+ */
+#define MLXBF2_GPIO_MAX_PINS_PER_BLOCK 32
+
+/*
+ * arm_gpio_lock register:
+ * bit[31] lock status: active if set
+ * bit[15:0] set lock
+ * The lock is enabled only if 0xd42f is written to this field
+ */
+#define YU_ARM_GPIO_LOCK_ADDR 0x2801088
+#define YU_ARM_GPIO_LOCK_SIZE 0x8
+#define YU_LOCK_ACTIVE_BIT(val) (val >> 31)
+#define YU_ARM_GPIO_LOCK_ACQUIRE 0xd42f
+#define YU_ARM_GPIO_LOCK_RELEASE 0x0
+
+/*
+ * gpio[x] block registers and their offset
+ */
+#define YU_GPIO_DATAIN 0x04
+#define YU_GPIO_MODE1 0x08
+#define YU_GPIO_MODE0 0x0c
+#define YU_GPIO_DATASET 0x14
+#define YU_GPIO_DATACLEAR 0x18
+#define YU_GPIO_CAUSE_RISE_EN 0x44
+#define YU_GPIO_CAUSE_FALL_EN 0x48
+#define YU_GPIO_MODE1_CLEAR 0x50
+#define YU_GPIO_MODE0_SET 0x54
+#define YU_GPIO_MODE0_CLEAR 0x58
+#define YU_GPIO_CAUSE_OR_CAUSE_EVTEN0 0x80
+#define YU_GPIO_CAUSE_OR_EVTEN0 0x94
+#define YU_GPIO_CAUSE_OR_CLRCAUSE 0x98
+
+struct mlxbf2_gpio_context_save_regs {
+ u32 gpio_mode0;
+ u32 gpio_mode1;
+};
+
+/* BlueField-2 gpio block context structure. */
+struct mlxbf2_gpio_context {
+ struct gpio_chip gc;
+
+ /* YU GPIO blocks address */
+ void __iomem *gpio_io;
+ struct device *dev;
+
+ struct mlxbf2_gpio_context_save_regs *csave_regs;
+};
+
+/* BlueField-2 gpio shared structure. */
+struct mlxbf2_gpio_param {
+ void __iomem *io;
+ struct resource *res;
+ struct mutex *lock;
+};
+
+static struct resource yu_arm_gpio_lock_res =
+ DEFINE_RES_MEM_NAMED(YU_ARM_GPIO_LOCK_ADDR, YU_ARM_GPIO_LOCK_SIZE, "YU_ARM_GPIO_LOCK");
+
+static DEFINE_MUTEX(yu_arm_gpio_lock_mutex);
+
+static struct mlxbf2_gpio_param yu_arm_gpio_lock_param = {
+ .res = &yu_arm_gpio_lock_res,
+ .lock = &yu_arm_gpio_lock_mutex,
+};
+
+/* Request memory region and map yu_arm_gpio_lock resource */
+static int mlxbf2_gpio_get_lock_res(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ resource_size_t size;
+ int ret = 0;
+
+ mutex_lock(yu_arm_gpio_lock_param.lock);
+
+ /* Check if the memory map already exists */
+ if (yu_arm_gpio_lock_param.io)
+ goto exit;
+
+ res = yu_arm_gpio_lock_param.res;
+ size = resource_size(res);
+
+ if (!devm_request_mem_region(dev, res->start, size, res->name)) {
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ yu_arm_gpio_lock_param.io = devm_ioremap(dev, res->start, size);
+ if (!yu_arm_gpio_lock_param.io)
+ ret = -ENOMEM;
+
+exit:
+ mutex_unlock(yu_arm_gpio_lock_param.lock);
+
+ return ret;
+}
+
+/*
+ * Acquire the YU arm_gpio_lock to be able to change the direction
+ * mode. If the lock_active bit is already set, return an error.
+ */
+static int mlxbf2_gpio_lock_acquire(struct mlxbf2_gpio_context *gs)
+{
+ u32 arm_gpio_lock_val;
+
+ mutex_lock(yu_arm_gpio_lock_param.lock);
+ raw_spin_lock(&gs->gc.bgpio_lock);
+
+ arm_gpio_lock_val = readl(yu_arm_gpio_lock_param.io);
+
+ /*
+ * When lock active bit[31] is set, ModeX is write enabled
+ */
+ if (YU_LOCK_ACTIVE_BIT(arm_gpio_lock_val)) {
+ raw_spin_unlock(&gs->gc.bgpio_lock);
+ mutex_unlock(yu_arm_gpio_lock_param.lock);
+ return -EINVAL;
+ }
+
+ writel(YU_ARM_GPIO_LOCK_ACQUIRE, yu_arm_gpio_lock_param.io);
+
+ return 0;
+}
+
+/*
+ * Release the YU arm_gpio_lock after changing the direction mode.
+ */
+static void mlxbf2_gpio_lock_release(struct mlxbf2_gpio_context *gs)
+ __releases(&gs->gc.bgpio_lock)
+ __releases(yu_arm_gpio_lock_param.lock)
+{
+ writel(YU_ARM_GPIO_LOCK_RELEASE, yu_arm_gpio_lock_param.io);
+ raw_spin_unlock(&gs->gc.bgpio_lock);
+ mutex_unlock(yu_arm_gpio_lock_param.lock);
+}
+
+/*
+ * mode0 and mode1 are both locked by the gpio_lock field.
+ *
+ * Together, mode0 and mode1 define the gpio Mode dependeing also
+ * on Reg_DataOut.
+ *
+ * {mode1,mode0}:{Reg_DataOut=0,Reg_DataOut=1}->{DataOut=0,DataOut=1}
+ *
+ * {0,0}:Reg_DataOut{0,1}->{Z,Z} Input PAD
+ * {0,1}:Reg_DataOut{0,1}->{0,1} Full drive Output PAD
+ * {1,0}:Reg_DataOut{0,1}->{0,Z} 0-set PAD to low, 1-float
+ * {1,1}:Reg_DataOut{0,1}->{Z,1} 0-float, 1-set PAD to high
+ */
+
+/*
+ * Set input direction:
+ * {mode1,mode0} = {0,0}
+ */
+static int mlxbf2_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct mlxbf2_gpio_context *gs = gpiochip_get_data(chip);
+ int ret;
+
+ /*
+ * Although the arm_gpio_lock was set in the probe function, check again
+ * if it is still enabled to be able to write to the ModeX registers.
+ */
+ ret = mlxbf2_gpio_lock_acquire(gs);
+ if (ret < 0)
+ return ret;
+
+ writel(BIT(offset), gs->gpio_io + YU_GPIO_MODE0_CLEAR);
+ writel(BIT(offset), gs->gpio_io + YU_GPIO_MODE1_CLEAR);
+
+ mlxbf2_gpio_lock_release(gs);
+
+ return ret;
+}
+
+/*
+ * Set output direction:
+ * {mode1,mode0} = {0,1}
+ */
+static int mlxbf2_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset,
+ int value)
+{
+ struct mlxbf2_gpio_context *gs = gpiochip_get_data(chip);
+ int ret = 0;
+
+ /*
+ * Although the arm_gpio_lock was set in the probe function,
+ * check again it is still enabled to be able to write to the
+ * ModeX registers.
+ */
+ ret = mlxbf2_gpio_lock_acquire(gs);
+ if (ret < 0)
+ return ret;
+
+ writel(BIT(offset), gs->gpio_io + YU_GPIO_MODE1_CLEAR);
+ writel(BIT(offset), gs->gpio_io + YU_GPIO_MODE0_SET);
+
+ mlxbf2_gpio_lock_release(gs);
+
+ return ret;
+}
+
+static void mlxbf2_gpio_irq_enable(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct mlxbf2_gpio_context *gs = gpiochip_get_data(gc);
+ int offset = irqd_to_hwirq(irqd);
+ unsigned long flags;
+ u32 val;
+
+ gpiochip_enable_irq(gc, irqd_to_hwirq(irqd));
+ raw_spin_lock_irqsave(&gs->gc.bgpio_lock, flags);
+ val = readl(gs->gpio_io + YU_GPIO_CAUSE_OR_CLRCAUSE);
+ val |= BIT(offset);
+ writel(val, gs->gpio_io + YU_GPIO_CAUSE_OR_CLRCAUSE);
+
+ val = readl(gs->gpio_io + YU_GPIO_CAUSE_OR_EVTEN0);
+ val |= BIT(offset);
+ writel(val, gs->gpio_io + YU_GPIO_CAUSE_OR_EVTEN0);
+ raw_spin_unlock_irqrestore(&gs->gc.bgpio_lock, flags);
+}
+
+static void mlxbf2_gpio_irq_disable(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct mlxbf2_gpio_context *gs = gpiochip_get_data(gc);
+ int offset = irqd_to_hwirq(irqd);
+ unsigned long flags;
+ u32 val;
+
+ raw_spin_lock_irqsave(&gs->gc.bgpio_lock, flags);
+ val = readl(gs->gpio_io + YU_GPIO_CAUSE_OR_EVTEN0);
+ val &= ~BIT(offset);
+ writel(val, gs->gpio_io + YU_GPIO_CAUSE_OR_EVTEN0);
+ raw_spin_unlock_irqrestore(&gs->gc.bgpio_lock, flags);
+ gpiochip_disable_irq(gc, irqd_to_hwirq(irqd));
+}
+
+static irqreturn_t mlxbf2_gpio_irq_handler(int irq, void *ptr)
+{
+ struct mlxbf2_gpio_context *gs = ptr;
+ struct gpio_chip *gc = &gs->gc;
+ unsigned long pending;
+ u32 level;
+
+ pending = readl(gs->gpio_io + YU_GPIO_CAUSE_OR_CAUSE_EVTEN0);
+ writel(pending, gs->gpio_io + YU_GPIO_CAUSE_OR_CLRCAUSE);
+
+ for_each_set_bit(level, &pending, gc->ngpio)
+ generic_handle_domain_irq_safe(gc->irq.domain, level);
+
+ return IRQ_RETVAL(pending);
+}
+
+static int
+mlxbf2_gpio_irq_set_type(struct irq_data *irqd, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct mlxbf2_gpio_context *gs = gpiochip_get_data(gc);
+ int offset = irqd_to_hwirq(irqd);
+ unsigned long flags;
+ bool fall = false;
+ bool rise = false;
+ u32 val;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_BOTH:
+ fall = true;
+ rise = true;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ rise = true;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ fall = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ raw_spin_lock_irqsave(&gs->gc.bgpio_lock, flags);
+ if (fall) {
+ val = readl(gs->gpio_io + YU_GPIO_CAUSE_FALL_EN);
+ val |= BIT(offset);
+ writel(val, gs->gpio_io + YU_GPIO_CAUSE_FALL_EN);
+ }
+
+ if (rise) {
+ val = readl(gs->gpio_io + YU_GPIO_CAUSE_RISE_EN);
+ val |= BIT(offset);
+ writel(val, gs->gpio_io + YU_GPIO_CAUSE_RISE_EN);
+ }
+ raw_spin_unlock_irqrestore(&gs->gc.bgpio_lock, flags);
+
+ return 0;
+}
+
+static void mlxbf2_gpio_irq_print_chip(struct irq_data *irqd,
+ struct seq_file *p)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct mlxbf2_gpio_context *gs = gpiochip_get_data(gc);
+
+ seq_printf(p, dev_name(gs->dev));
+}
+
+static const struct irq_chip mlxbf2_gpio_irq_chip = {
+ .irq_set_type = mlxbf2_gpio_irq_set_type,
+ .irq_enable = mlxbf2_gpio_irq_enable,
+ .irq_disable = mlxbf2_gpio_irq_disable,
+ .irq_print_chip = mlxbf2_gpio_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+/* BlueField-2 GPIO driver initialization routine. */
+static int
+mlxbf2_gpio_probe(struct platform_device *pdev)
+{
+ struct mlxbf2_gpio_context *gs;
+ struct device *dev = &pdev->dev;
+ struct gpio_irq_chip *girq;
+ struct gpio_chip *gc;
+ unsigned int npins;
+ const char *name;
+ int ret, irq;
+
+ name = dev_name(dev);
+
+ gs = devm_kzalloc(dev, sizeof(*gs), GFP_KERNEL);
+ if (!gs)
+ return -ENOMEM;
+
+ gs->dev = dev;
+
+ /* YU GPIO block address */
+ gs->gpio_io = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gs->gpio_io))
+ return PTR_ERR(gs->gpio_io);
+
+ ret = mlxbf2_gpio_get_lock_res(pdev);
+ if (ret) {
+ dev_err(dev, "Failed to get yu_arm_gpio_lock resource\n");
+ return ret;
+ }
+
+ if (device_property_read_u32(dev, "npins", &npins))
+ npins = MLXBF2_GPIO_MAX_PINS_PER_BLOCK;
+
+ gc = &gs->gc;
+
+ ret = bgpio_init(gc, dev, 4,
+ gs->gpio_io + YU_GPIO_DATAIN,
+ gs->gpio_io + YU_GPIO_DATASET,
+ gs->gpio_io + YU_GPIO_DATACLEAR,
+ NULL,
+ NULL,
+ 0);
+
+ if (ret) {
+ dev_err(dev, "bgpio_init failed\n");
+ return ret;
+ }
+
+ gc->direction_input = mlxbf2_gpio_direction_input;
+ gc->direction_output = mlxbf2_gpio_direction_output;
+ gc->ngpio = npins;
+ gc->owner = THIS_MODULE;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq >= 0) {
+ girq = &gs->gc.irq;
+ gpio_irq_chip_set_chip(girq, &mlxbf2_gpio_irq_chip);
+ girq->handler = handle_simple_irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ /* This will let us handle the parent IRQ in the driver */
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->parent_handler = NULL;
+
+ /*
+ * Directly request the irq here instead of passing
+ * a flow-handler because the irq is shared.
+ */
+ ret = devm_request_irq(dev, irq, mlxbf2_gpio_irq_handler,
+ IRQF_SHARED, name, gs);
+ if (ret) {
+ dev_err(dev, "failed to request IRQ");
+ return ret;
+ }
+ }
+
+ platform_set_drvdata(pdev, gs);
+
+ ret = devm_gpiochip_add_data(dev, &gs->gc, gs);
+ if (ret) {
+ dev_err(dev, "Failed adding memory mapped gpiochip\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused mlxbf2_gpio_suspend(struct device *dev)
+{
+ struct mlxbf2_gpio_context *gs = dev_get_drvdata(dev);
+
+ gs->csave_regs->gpio_mode0 = readl(gs->gpio_io +
+ YU_GPIO_MODE0);
+ gs->csave_regs->gpio_mode1 = readl(gs->gpio_io +
+ YU_GPIO_MODE1);
+
+ return 0;
+}
+
+static int __maybe_unused mlxbf2_gpio_resume(struct device *dev)
+{
+ struct mlxbf2_gpio_context *gs = dev_get_drvdata(dev);
+
+ writel(gs->csave_regs->gpio_mode0, gs->gpio_io +
+ YU_GPIO_MODE0);
+ writel(gs->csave_regs->gpio_mode1, gs->gpio_io +
+ YU_GPIO_MODE1);
+
+ return 0;
+}
+static SIMPLE_DEV_PM_OPS(mlxbf2_pm_ops, mlxbf2_gpio_suspend, mlxbf2_gpio_resume);
+
+static const struct acpi_device_id __maybe_unused mlxbf2_gpio_acpi_match[] = {
+ { "MLNXBF22", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, mlxbf2_gpio_acpi_match);
+
+static struct platform_driver mlxbf2_gpio_driver = {
+ .driver = {
+ .name = "mlxbf2_gpio",
+ .acpi_match_table = mlxbf2_gpio_acpi_match,
+ .pm = &mlxbf2_pm_ops,
+ },
+ .probe = mlxbf2_gpio_probe,
+};
+
+module_platform_driver(mlxbf2_gpio_driver);
+
+MODULE_DESCRIPTION("Mellanox BlueField-2 GPIO Driver");
+MODULE_AUTHOR("Asmaa Mnebhi <asmaa@nvidia.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-mlxbf3.c b/drivers/gpio/gpio-mlxbf3.c
new file mode 100644
index 0000000000..d5906d419b
--- /dev/null
+++ b/drivers/gpio/gpio-mlxbf3.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
+/* Copyright (C) 2022 NVIDIA CORPORATION & AFFILIATES */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+/*
+ * There are 2 YU GPIO blocks:
+ * gpio[0]: HOST_GPIO0->HOST_GPIO31
+ * gpio[1]: HOST_GPIO32->HOST_GPIO55
+ */
+#define MLXBF3_GPIO_MAX_PINS_PER_BLOCK 32
+#define MLXBF3_GPIO_MAX_PINS_BLOCK0 32
+#define MLXBF3_GPIO_MAX_PINS_BLOCK1 24
+
+/*
+ * fw_gpio[x] block registers and their offset
+ */
+#define MLXBF_GPIO_FW_OUTPUT_ENABLE_SET 0x00
+#define MLXBF_GPIO_FW_DATA_OUT_SET 0x04
+
+#define MLXBF_GPIO_FW_OUTPUT_ENABLE_CLEAR 0x00
+#define MLXBF_GPIO_FW_DATA_OUT_CLEAR 0x04
+
+#define MLXBF_GPIO_CAUSE_RISE_EN 0x00
+#define MLXBF_GPIO_CAUSE_FALL_EN 0x04
+#define MLXBF_GPIO_READ_DATA_IN 0x08
+
+#define MLXBF_GPIO_CAUSE_OR_CAUSE_EVTEN0 0x00
+#define MLXBF_GPIO_CAUSE_OR_EVTEN0 0x14
+#define MLXBF_GPIO_CAUSE_OR_CLRCAUSE 0x18
+
+struct mlxbf3_gpio_context {
+ struct gpio_chip gc;
+
+ /* YU GPIO block address */
+ void __iomem *gpio_set_io;
+ void __iomem *gpio_clr_io;
+ void __iomem *gpio_io;
+
+ /* YU GPIO cause block address */
+ void __iomem *gpio_cause_io;
+};
+
+static void mlxbf3_gpio_irq_enable(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct mlxbf3_gpio_context *gs = gpiochip_get_data(gc);
+ irq_hw_number_t offset = irqd_to_hwirq(irqd);
+ unsigned long flags;
+ u32 val;
+
+ gpiochip_enable_irq(gc, offset);
+
+ raw_spin_lock_irqsave(&gs->gc.bgpio_lock, flags);
+ writel(BIT(offset), gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_CLRCAUSE);
+
+ val = readl(gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_EVTEN0);
+ val |= BIT(offset);
+ writel(val, gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_EVTEN0);
+ raw_spin_unlock_irqrestore(&gs->gc.bgpio_lock, flags);
+}
+
+static void mlxbf3_gpio_irq_disable(struct irq_data *irqd)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct mlxbf3_gpio_context *gs = gpiochip_get_data(gc);
+ irq_hw_number_t offset = irqd_to_hwirq(irqd);
+ unsigned long flags;
+ u32 val;
+
+ raw_spin_lock_irqsave(&gs->gc.bgpio_lock, flags);
+ val = readl(gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_EVTEN0);
+ val &= ~BIT(offset);
+ writel(val, gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_EVTEN0);
+ raw_spin_unlock_irqrestore(&gs->gc.bgpio_lock, flags);
+
+ gpiochip_disable_irq(gc, offset);
+}
+
+static irqreturn_t mlxbf3_gpio_irq_handler(int irq, void *ptr)
+{
+ struct mlxbf3_gpio_context *gs = ptr;
+ struct gpio_chip *gc = &gs->gc;
+ unsigned long pending;
+ u32 level;
+
+ pending = readl(gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_CAUSE_EVTEN0);
+ writel(pending, gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_CLRCAUSE);
+
+ for_each_set_bit(level, &pending, gc->ngpio)
+ generic_handle_domain_irq(gc->irq.domain, level);
+
+ return IRQ_RETVAL(pending);
+}
+
+static int
+mlxbf3_gpio_irq_set_type(struct irq_data *irqd, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
+ struct mlxbf3_gpio_context *gs = gpiochip_get_data(gc);
+ irq_hw_number_t offset = irqd_to_hwirq(irqd);
+ unsigned long flags;
+ u32 val;
+
+ raw_spin_lock_irqsave(&gs->gc.bgpio_lock, flags);
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_BOTH:
+ val = readl(gs->gpio_io + MLXBF_GPIO_CAUSE_FALL_EN);
+ val |= BIT(offset);
+ writel(val, gs->gpio_io + MLXBF_GPIO_CAUSE_FALL_EN);
+ val = readl(gs->gpio_io + MLXBF_GPIO_CAUSE_RISE_EN);
+ val |= BIT(offset);
+ writel(val, gs->gpio_io + MLXBF_GPIO_CAUSE_RISE_EN);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ val = readl(gs->gpio_io + MLXBF_GPIO_CAUSE_RISE_EN);
+ val |= BIT(offset);
+ writel(val, gs->gpio_io + MLXBF_GPIO_CAUSE_RISE_EN);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ val = readl(gs->gpio_io + MLXBF_GPIO_CAUSE_FALL_EN);
+ val |= BIT(offset);
+ writel(val, gs->gpio_io + MLXBF_GPIO_CAUSE_FALL_EN);
+ break;
+ default:
+ raw_spin_unlock_irqrestore(&gs->gc.bgpio_lock, flags);
+ return -EINVAL;
+ }
+
+ raw_spin_unlock_irqrestore(&gs->gc.bgpio_lock, flags);
+
+ irq_set_handler_locked(irqd, handle_edge_irq);
+
+ return 0;
+}
+
+/* This function needs to be defined for handle_edge_irq() */
+static void mlxbf3_gpio_irq_ack(struct irq_data *data)
+{
+}
+
+static const struct irq_chip gpio_mlxbf3_irqchip = {
+ .name = "MLNXBF33",
+ .irq_ack = mlxbf3_gpio_irq_ack,
+ .irq_set_type = mlxbf3_gpio_irq_set_type,
+ .irq_enable = mlxbf3_gpio_irq_enable,
+ .irq_disable = mlxbf3_gpio_irq_disable,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int mlxbf3_gpio_add_pin_ranges(struct gpio_chip *chip)
+{
+ unsigned int id;
+
+ switch(chip->ngpio) {
+ case MLXBF3_GPIO_MAX_PINS_BLOCK0:
+ id = 0;
+ break;
+ case MLXBF3_GPIO_MAX_PINS_BLOCK1:
+ id = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return gpiochip_add_pin_range(chip, "MLNXBF34:00",
+ chip->base, id * MLXBF3_GPIO_MAX_PINS_PER_BLOCK,
+ chip->ngpio);
+}
+
+static int mlxbf3_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mlxbf3_gpio_context *gs;
+ struct gpio_irq_chip *girq;
+ struct gpio_chip *gc;
+ int ret, irq;
+
+ gs = devm_kzalloc(dev, sizeof(*gs), GFP_KERNEL);
+ if (!gs)
+ return -ENOMEM;
+
+ gs->gpio_io = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gs->gpio_io))
+ return PTR_ERR(gs->gpio_io);
+
+ gs->gpio_cause_io = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(gs->gpio_cause_io))
+ return PTR_ERR(gs->gpio_cause_io);
+
+ gs->gpio_set_io = devm_platform_ioremap_resource(pdev, 2);
+ if (IS_ERR(gs->gpio_set_io))
+ return PTR_ERR(gs->gpio_set_io);
+
+ gs->gpio_clr_io = devm_platform_ioremap_resource(pdev, 3);
+ if (IS_ERR(gs->gpio_clr_io))
+ return PTR_ERR(gs->gpio_clr_io);
+ gc = &gs->gc;
+
+ ret = bgpio_init(gc, dev, 4,
+ gs->gpio_io + MLXBF_GPIO_READ_DATA_IN,
+ gs->gpio_set_io + MLXBF_GPIO_FW_DATA_OUT_SET,
+ gs->gpio_clr_io + MLXBF_GPIO_FW_DATA_OUT_CLEAR,
+ gs->gpio_set_io + MLXBF_GPIO_FW_OUTPUT_ENABLE_SET,
+ gs->gpio_clr_io + MLXBF_GPIO_FW_OUTPUT_ENABLE_CLEAR, 0);
+ if (ret)
+ return dev_err_probe(dev, ret, "%s: bgpio_init() failed", __func__);
+
+ gc->request = gpiochip_generic_request;
+ gc->free = gpiochip_generic_free;
+ gc->owner = THIS_MODULE;
+ gc->add_pin_ranges = mlxbf3_gpio_add_pin_ranges;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq >= 0) {
+ girq = &gs->gc.irq;
+ gpio_irq_chip_set_chip(girq, &gpio_mlxbf3_irqchip);
+ girq->default_type = IRQ_TYPE_NONE;
+ /* This will let us handle the parent IRQ in the driver */
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->parent_handler = NULL;
+ girq->handler = handle_bad_irq;
+
+ /*
+ * Directly request the irq here instead of passing
+ * a flow-handler because the irq is shared.
+ */
+ ret = devm_request_irq(dev, irq, mlxbf3_gpio_irq_handler,
+ IRQF_SHARED, dev_name(dev), gs);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to request IRQ");
+ }
+
+ platform_set_drvdata(pdev, gs);
+
+ ret = devm_gpiochip_add_data(dev, &gs->gc, gs);
+ if (ret)
+ dev_err_probe(dev, ret, "Failed adding memory mapped gpiochip\n");
+
+ return 0;
+}
+
+static const struct acpi_device_id mlxbf3_gpio_acpi_match[] = {
+ { "MLNXBF33", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, mlxbf3_gpio_acpi_match);
+
+static struct platform_driver mlxbf3_gpio_driver = {
+ .driver = {
+ .name = "mlxbf3_gpio",
+ .acpi_match_table = mlxbf3_gpio_acpi_match,
+ },
+ .probe = mlxbf3_gpio_probe,
+};
+module_platform_driver(mlxbf3_gpio_driver);
+
+MODULE_SOFTDEP("pre: pinctrl-mlxbf3");
+MODULE_DESCRIPTION("NVIDIA BlueField-3 GPIO Driver");
+MODULE_AUTHOR("Asmaa Mnebhi <asmaa@nvidia.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/gpio/gpio-mm-lantiq.c b/drivers/gpio/gpio-mm-lantiq.c
new file mode 100644
index 0000000000..f3c1582596
--- /dev/null
+++ b/drivers/gpio/gpio-mm-lantiq.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *
+ * Copyright (C) 2012 John Crispin <john@phrozen.org>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/legacy-of-mm-gpiochip.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <lantiq_soc.h>
+
+/*
+ * By attaching hardware latches to the EBU it is possible to create output
+ * only gpios. This driver configures a special memory address, which when
+ * written to outputs 16 bit to the latches.
+ */
+
+#define LTQ_EBU_BUSCON 0x1e7ff /* 16 bit access, slowest timing */
+#define LTQ_EBU_WP 0x80000000 /* write protect bit */
+
+struct ltq_mm {
+ struct of_mm_gpio_chip mmchip;
+ u16 shadow; /* shadow the latches state */
+};
+
+/**
+ * ltq_mm_apply() - write the shadow value to the ebu address.
+ * @chip: Pointer to our private data structure.
+ *
+ * Write the shadow value to the EBU to set the gpios. We need to set the
+ * global EBU lock to make sure that PCI/MTD don't break.
+ */
+static void ltq_mm_apply(struct ltq_mm *chip)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ebu_lock, flags);
+ ltq_ebu_w32(LTQ_EBU_BUSCON, LTQ_EBU_BUSCON1);
+ __raw_writew(chip->shadow, chip->mmchip.regs);
+ ltq_ebu_w32(LTQ_EBU_BUSCON | LTQ_EBU_WP, LTQ_EBU_BUSCON1);
+ spin_unlock_irqrestore(&ebu_lock, flags);
+}
+
+/**
+ * ltq_mm_set() - gpio_chip->set - set gpios.
+ * @gc: Pointer to gpio_chip device structure.
+ * @gpio: GPIO signal number.
+ * @val: Value to be written to specified signal.
+ *
+ * Set the shadow value and call ltq_mm_apply.
+ */
+static void ltq_mm_set(struct gpio_chip *gc, unsigned offset, int value)
+{
+ struct ltq_mm *chip = gpiochip_get_data(gc);
+
+ if (value)
+ chip->shadow |= (1 << offset);
+ else
+ chip->shadow &= ~(1 << offset);
+ ltq_mm_apply(chip);
+}
+
+/**
+ * ltq_mm_dir_out() - gpio_chip->dir_out - set gpio direction.
+ * @gc: Pointer to gpio_chip device structure.
+ * @gpio: GPIO signal number.
+ * @val: Value to be written to specified signal.
+ *
+ * Same as ltq_mm_set, always returns 0.
+ */
+static int ltq_mm_dir_out(struct gpio_chip *gc, unsigned offset, int value)
+{
+ ltq_mm_set(gc, offset, value);
+
+ return 0;
+}
+
+/**
+ * ltq_mm_save_regs() - Set initial values of GPIO pins
+ * @mm_gc: pointer to memory mapped GPIO chip structure
+ */
+static void ltq_mm_save_regs(struct of_mm_gpio_chip *mm_gc)
+{
+ struct ltq_mm *chip =
+ container_of(mm_gc, struct ltq_mm, mmchip);
+
+ /* tell the ebu controller which memory address we will be using */
+ ltq_ebu_w32(CPHYSADDR(chip->mmchip.regs) | 0x1, LTQ_EBU_ADDRSEL1);
+
+ ltq_mm_apply(chip);
+}
+
+static int ltq_mm_probe(struct platform_device *pdev)
+{
+ struct ltq_mm *chip;
+ u32 shadow;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, chip);
+
+ chip->mmchip.gc.ngpio = 16;
+ chip->mmchip.gc.direction_output = ltq_mm_dir_out;
+ chip->mmchip.gc.set = ltq_mm_set;
+ chip->mmchip.save_regs = ltq_mm_save_regs;
+
+ /* store the shadow value if one was passed by the devicetree */
+ if (!of_property_read_u32(pdev->dev.of_node, "lantiq,shadow", &shadow))
+ chip->shadow = shadow;
+
+ return of_mm_gpiochip_add_data(pdev->dev.of_node, &chip->mmchip, chip);
+}
+
+static int ltq_mm_remove(struct platform_device *pdev)
+{
+ struct ltq_mm *chip = platform_get_drvdata(pdev);
+
+ of_mm_gpiochip_remove(&chip->mmchip);
+
+ return 0;
+}
+
+static const struct of_device_id ltq_mm_match[] = {
+ { .compatible = "lantiq,gpio-mm" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ltq_mm_match);
+
+static struct platform_driver ltq_mm_driver = {
+ .probe = ltq_mm_probe,
+ .remove = ltq_mm_remove,
+ .driver = {
+ .name = "gpio-mm-ltq",
+ .of_match_table = ltq_mm_match,
+ },
+};
+
+static int __init ltq_mm_init(void)
+{
+ return platform_driver_register(&ltq_mm_driver);
+}
+
+subsys_initcall(ltq_mm_init);
+
+static void __exit ltq_mm_exit(void)
+{
+ platform_driver_unregister(&ltq_mm_driver);
+}
+module_exit(ltq_mm_exit);
diff --git a/drivers/gpio/gpio-mmio.c b/drivers/gpio/gpio-mmio.c
new file mode 100644
index 0000000000..74fdf0d87b
--- /dev/null
+++ b/drivers/gpio/gpio-mmio.c
@@ -0,0 +1,829 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Generic driver for memory-mapped GPIO controllers.
+ *
+ * Copyright 2008 MontaVista Software, Inc.
+ * Copyright 2008,2010 Anton Vorontsov <cbouatmailru@gmail.com>
+ *
+ * ....``.```~~~~````.`.`.`.`.```````'',,,.........`````......`.......
+ * ...`` ```````..
+ * ..The simplest form of a GPIO controller that the driver supports is``
+ * `.just a single "data" register, where GPIO state can be read and/or `
+ * `,..written. ,,..``~~~~ .....``.`.`.~~.```.`.........``````.```````
+ * `````````
+ ___
+_/~~|___/~| . ```~~~~~~ ___/___\___ ,~.`.`.`.`````.~~...,,,,...
+__________|~$@~~~ %~ /o*o*o*o*o*o\ .. Implementing such a GPIO .
+o ` ~~~~\___/~~~~ ` controller in FPGA is ,.`
+ `....trivial..'~`.```.```
+ * ```````
+ * .```````~~~~`..`.``.``.
+ * . The driver supports `... ,..```.`~~~```````````````....````.``,,
+ * . big-endian notation, just`. .. A bit more sophisticated controllers ,
+ * . register the device with -be`. .with a pair of set/clear-bit registers ,
+ * `.. suffix. ```~~`````....`.` . affecting the data register and the .`
+ * ``.`.``...``` ```.. output pins are also supported.`
+ * ^^ `````.`````````.,``~``~``~~``````
+ * . ^^
+ * ,..`.`.`...````````````......`.`.`.`.`.`..`.`.`..
+ * .. The expectation is that in at least some cases . ,-~~~-,
+ * .this will be used with roll-your-own ASIC/FPGA .` \ /
+ * .logic in Verilog or VHDL. ~~~`````````..`````~~` \ /
+ * ..````````......``````````` \o_
+ * |
+ * ^^ / \
+ *
+ * ...`````~~`.....``.`..........``````.`.``.```........``.
+ * ` 8, 16, 32 and 64 bits registers are supported, and``.
+ * . the number of GPIOs is determined by the width of ~
+ * .. the registers. ,............```.`.`..`.`.~~~.`.`.`~
+ * `.......````.```
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/bug.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/log2.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <linux/gpio/driver.h>
+#include <linux/slab.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include "gpiolib.h"
+
+static void bgpio_write8(void __iomem *reg, unsigned long data)
+{
+ writeb(data, reg);
+}
+
+static unsigned long bgpio_read8(void __iomem *reg)
+{
+ return readb(reg);
+}
+
+static void bgpio_write16(void __iomem *reg, unsigned long data)
+{
+ writew(data, reg);
+}
+
+static unsigned long bgpio_read16(void __iomem *reg)
+{
+ return readw(reg);
+}
+
+static void bgpio_write32(void __iomem *reg, unsigned long data)
+{
+ writel(data, reg);
+}
+
+static unsigned long bgpio_read32(void __iomem *reg)
+{
+ return readl(reg);
+}
+
+#if BITS_PER_LONG >= 64
+static void bgpio_write64(void __iomem *reg, unsigned long data)
+{
+ writeq(data, reg);
+}
+
+static unsigned long bgpio_read64(void __iomem *reg)
+{
+ return readq(reg);
+}
+#endif /* BITS_PER_LONG >= 64 */
+
+static void bgpio_write16be(void __iomem *reg, unsigned long data)
+{
+ iowrite16be(data, reg);
+}
+
+static unsigned long bgpio_read16be(void __iomem *reg)
+{
+ return ioread16be(reg);
+}
+
+static void bgpio_write32be(void __iomem *reg, unsigned long data)
+{
+ iowrite32be(data, reg);
+}
+
+static unsigned long bgpio_read32be(void __iomem *reg)
+{
+ return ioread32be(reg);
+}
+
+static unsigned long bgpio_line2mask(struct gpio_chip *gc, unsigned int line)
+{
+ if (gc->be_bits)
+ return BIT(gc->bgpio_bits - 1 - line);
+ return BIT(line);
+}
+
+static int bgpio_get_set(struct gpio_chip *gc, unsigned int gpio)
+{
+ unsigned long pinmask = bgpio_line2mask(gc, gpio);
+ bool dir = !!(gc->bgpio_dir & pinmask);
+
+ if (dir)
+ return !!(gc->read_reg(gc->reg_set) & pinmask);
+ else
+ return !!(gc->read_reg(gc->reg_dat) & pinmask);
+}
+
+/*
+ * This assumes that the bits in the GPIO register are in native endianness.
+ * We only assign the function pointer if we have that.
+ */
+static int bgpio_get_set_multiple(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ unsigned long get_mask = 0;
+ unsigned long set_mask = 0;
+
+ /* Make sure we first clear any bits that are zero when we read the register */
+ *bits &= ~*mask;
+
+ set_mask = *mask & gc->bgpio_dir;
+ get_mask = *mask & ~gc->bgpio_dir;
+
+ if (set_mask)
+ *bits |= gc->read_reg(gc->reg_set) & set_mask;
+ if (get_mask)
+ *bits |= gc->read_reg(gc->reg_dat) & get_mask;
+
+ return 0;
+}
+
+static int bgpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ return !!(gc->read_reg(gc->reg_dat) & bgpio_line2mask(gc, gpio));
+}
+
+/*
+ * This only works if the bits in the GPIO register are in native endianness.
+ */
+static int bgpio_get_multiple(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ /* Make sure we first clear any bits that are zero when we read the register */
+ *bits &= ~*mask;
+ *bits |= gc->read_reg(gc->reg_dat) & *mask;
+ return 0;
+}
+
+/*
+ * With big endian mirrored bit order it becomes more tedious.
+ */
+static int bgpio_get_multiple_be(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ unsigned long readmask = 0;
+ unsigned long val;
+ int bit;
+
+ /* Make sure we first clear any bits that are zero when we read the register */
+ *bits &= ~*mask;
+
+ /* Create a mirrored mask */
+ for_each_set_bit(bit, mask, gc->ngpio)
+ readmask |= bgpio_line2mask(gc, bit);
+
+ /* Read the register */
+ val = gc->read_reg(gc->reg_dat) & readmask;
+
+ /*
+ * Mirror the result into the "bits" result, this will give line 0
+ * in bit 0 ... line 31 in bit 31 for a 32bit register.
+ */
+ for_each_set_bit(bit, &val, gc->ngpio)
+ *bits |= bgpio_line2mask(gc, bit);
+
+ return 0;
+}
+
+static void bgpio_set_none(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+}
+
+static void bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ unsigned long mask = bgpio_line2mask(gc, gpio);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+ if (val)
+ gc->bgpio_data |= mask;
+ else
+ gc->bgpio_data &= ~mask;
+
+ gc->write_reg(gc->reg_dat, gc->bgpio_data);
+
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
+static void bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio,
+ int val)
+{
+ unsigned long mask = bgpio_line2mask(gc, gpio);
+
+ if (val)
+ gc->write_reg(gc->reg_set, mask);
+ else
+ gc->write_reg(gc->reg_clr, mask);
+}
+
+static void bgpio_set_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ unsigned long mask = bgpio_line2mask(gc, gpio);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+ if (val)
+ gc->bgpio_data |= mask;
+ else
+ gc->bgpio_data &= ~mask;
+
+ gc->write_reg(gc->reg_set, gc->bgpio_data);
+
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
+static void bgpio_multiple_get_masks(struct gpio_chip *gc,
+ unsigned long *mask, unsigned long *bits,
+ unsigned long *set_mask,
+ unsigned long *clear_mask)
+{
+ int i;
+
+ *set_mask = 0;
+ *clear_mask = 0;
+
+ for_each_set_bit(i, mask, gc->bgpio_bits) {
+ if (test_bit(i, bits))
+ *set_mask |= bgpio_line2mask(gc, i);
+ else
+ *clear_mask |= bgpio_line2mask(gc, i);
+ }
+}
+
+static void bgpio_set_multiple_single_reg(struct gpio_chip *gc,
+ unsigned long *mask,
+ unsigned long *bits,
+ void __iomem *reg)
+{
+ unsigned long flags;
+ unsigned long set_mask, clear_mask;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+ bgpio_multiple_get_masks(gc, mask, bits, &set_mask, &clear_mask);
+
+ gc->bgpio_data |= set_mask;
+ gc->bgpio_data &= ~clear_mask;
+
+ gc->write_reg(reg, gc->bgpio_data);
+
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
+static void bgpio_set_multiple(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ bgpio_set_multiple_single_reg(gc, mask, bits, gc->reg_dat);
+}
+
+static void bgpio_set_multiple_set(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ bgpio_set_multiple_single_reg(gc, mask, bits, gc->reg_set);
+}
+
+static void bgpio_set_multiple_with_clear(struct gpio_chip *gc,
+ unsigned long *mask,
+ unsigned long *bits)
+{
+ unsigned long set_mask, clear_mask;
+
+ bgpio_multiple_get_masks(gc, mask, bits, &set_mask, &clear_mask);
+
+ if (set_mask)
+ gc->write_reg(gc->reg_set, set_mask);
+ if (clear_mask)
+ gc->write_reg(gc->reg_clr, clear_mask);
+}
+
+static int bgpio_simple_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+ return 0;
+}
+
+static int bgpio_dir_out_err(struct gpio_chip *gc, unsigned int gpio,
+ int val)
+{
+ return -EINVAL;
+}
+
+static int bgpio_simple_dir_out(struct gpio_chip *gc, unsigned int gpio,
+ int val)
+{
+ gc->set(gc, gpio, val);
+
+ return 0;
+}
+
+static int bgpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+ gc->bgpio_dir &= ~bgpio_line2mask(gc, gpio);
+
+ if (gc->reg_dir_in)
+ gc->write_reg(gc->reg_dir_in, ~gc->bgpio_dir);
+ if (gc->reg_dir_out)
+ gc->write_reg(gc->reg_dir_out, gc->bgpio_dir);
+
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+ return 0;
+}
+
+static int bgpio_get_dir(struct gpio_chip *gc, unsigned int gpio)
+{
+ /* Return 0 if output, 1 if input */
+ if (gc->bgpio_dir_unreadable) {
+ if (gc->bgpio_dir & bgpio_line2mask(gc, gpio))
+ return GPIO_LINE_DIRECTION_OUT;
+ return GPIO_LINE_DIRECTION_IN;
+ }
+
+ if (gc->reg_dir_out) {
+ if (gc->read_reg(gc->reg_dir_out) & bgpio_line2mask(gc, gpio))
+ return GPIO_LINE_DIRECTION_OUT;
+ return GPIO_LINE_DIRECTION_IN;
+ }
+
+ if (gc->reg_dir_in)
+ if (!(gc->read_reg(gc->reg_dir_in) & bgpio_line2mask(gc, gpio)))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static void bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+ gc->bgpio_dir |= bgpio_line2mask(gc, gpio);
+
+ if (gc->reg_dir_in)
+ gc->write_reg(gc->reg_dir_in, ~gc->bgpio_dir);
+ if (gc->reg_dir_out)
+ gc->write_reg(gc->reg_dir_out, gc->bgpio_dir);
+
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
+static int bgpio_dir_out_dir_first(struct gpio_chip *gc, unsigned int gpio,
+ int val)
+{
+ bgpio_dir_out(gc, gpio, val);
+ gc->set(gc, gpio, val);
+ return 0;
+}
+
+static int bgpio_dir_out_val_first(struct gpio_chip *gc, unsigned int gpio,
+ int val)
+{
+ gc->set(gc, gpio, val);
+ bgpio_dir_out(gc, gpio, val);
+ return 0;
+}
+
+static int bgpio_setup_accessors(struct device *dev,
+ struct gpio_chip *gc,
+ bool byte_be)
+{
+
+ switch (gc->bgpio_bits) {
+ case 8:
+ gc->read_reg = bgpio_read8;
+ gc->write_reg = bgpio_write8;
+ break;
+ case 16:
+ if (byte_be) {
+ gc->read_reg = bgpio_read16be;
+ gc->write_reg = bgpio_write16be;
+ } else {
+ gc->read_reg = bgpio_read16;
+ gc->write_reg = bgpio_write16;
+ }
+ break;
+ case 32:
+ if (byte_be) {
+ gc->read_reg = bgpio_read32be;
+ gc->write_reg = bgpio_write32be;
+ } else {
+ gc->read_reg = bgpio_read32;
+ gc->write_reg = bgpio_write32;
+ }
+ break;
+#if BITS_PER_LONG >= 64
+ case 64:
+ if (byte_be) {
+ dev_err(dev,
+ "64 bit big endian byte order unsupported\n");
+ return -EINVAL;
+ } else {
+ gc->read_reg = bgpio_read64;
+ gc->write_reg = bgpio_write64;
+ }
+ break;
+#endif /* BITS_PER_LONG >= 64 */
+ default:
+ dev_err(dev, "unsupported data width %u bits\n", gc->bgpio_bits);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Create the device and allocate the resources. For setting GPIO's there are
+ * three supported configurations:
+ *
+ * - single input/output register resource (named "dat").
+ * - set/clear pair (named "set" and "clr").
+ * - single output register resource and single input resource ("set" and
+ * dat").
+ *
+ * For the single output register, this drives a 1 by setting a bit and a zero
+ * by clearing a bit. For the set clr pair, this drives a 1 by setting a bit
+ * in the set register and clears it by setting a bit in the clear register.
+ * The configuration is detected by which resources are present.
+ *
+ * For setting the GPIO direction, there are three supported configurations:
+ *
+ * - simple bidirection GPIO that requires no configuration.
+ * - an output direction register (named "dirout") where a 1 bit
+ * indicates the GPIO is an output.
+ * - an input direction register (named "dirin") where a 1 bit indicates
+ * the GPIO is an input.
+ */
+static int bgpio_setup_io(struct gpio_chip *gc,
+ void __iomem *dat,
+ void __iomem *set,
+ void __iomem *clr,
+ unsigned long flags)
+{
+
+ gc->reg_dat = dat;
+ if (!gc->reg_dat)
+ return -EINVAL;
+
+ if (set && clr) {
+ gc->reg_set = set;
+ gc->reg_clr = clr;
+ gc->set = bgpio_set_with_clear;
+ gc->set_multiple = bgpio_set_multiple_with_clear;
+ } else if (set && !clr) {
+ gc->reg_set = set;
+ gc->set = bgpio_set_set;
+ gc->set_multiple = bgpio_set_multiple_set;
+ } else if (flags & BGPIOF_NO_OUTPUT) {
+ gc->set = bgpio_set_none;
+ gc->set_multiple = NULL;
+ } else {
+ gc->set = bgpio_set;
+ gc->set_multiple = bgpio_set_multiple;
+ }
+
+ if (!(flags & BGPIOF_UNREADABLE_REG_SET) &&
+ (flags & BGPIOF_READ_OUTPUT_REG_SET)) {
+ gc->get = bgpio_get_set;
+ if (!gc->be_bits)
+ gc->get_multiple = bgpio_get_set_multiple;
+ /*
+ * We deliberately avoid assigning the ->get_multiple() call
+ * for big endian mirrored registers which are ALSO reflecting
+ * their value in the set register when used as output. It is
+ * simply too much complexity, let the GPIO core fall back to
+ * reading each line individually in that fringe case.
+ */
+ } else {
+ gc->get = bgpio_get;
+ if (gc->be_bits)
+ gc->get_multiple = bgpio_get_multiple_be;
+ else
+ gc->get_multiple = bgpio_get_multiple;
+ }
+
+ return 0;
+}
+
+static int bgpio_setup_direction(struct gpio_chip *gc,
+ void __iomem *dirout,
+ void __iomem *dirin,
+ unsigned long flags)
+{
+ if (dirout || dirin) {
+ gc->reg_dir_out = dirout;
+ gc->reg_dir_in = dirin;
+ if (flags & BGPIOF_NO_SET_ON_INPUT)
+ gc->direction_output = bgpio_dir_out_dir_first;
+ else
+ gc->direction_output = bgpio_dir_out_val_first;
+ gc->direction_input = bgpio_dir_in;
+ gc->get_direction = bgpio_get_dir;
+ } else {
+ if (flags & BGPIOF_NO_OUTPUT)
+ gc->direction_output = bgpio_dir_out_err;
+ else
+ gc->direction_output = bgpio_simple_dir_out;
+ gc->direction_input = bgpio_simple_dir_in;
+ }
+
+ return 0;
+}
+
+static int bgpio_request(struct gpio_chip *chip, unsigned gpio_pin)
+{
+ if (gpio_pin < chip->ngpio)
+ return 0;
+
+ return -EINVAL;
+}
+
+/**
+ * bgpio_init() - Initialize generic GPIO accessor functions
+ * @gc: the GPIO chip to set up
+ * @dev: the parent device of the new GPIO chip (compulsory)
+ * @sz: the size (width) of the MMIO registers in bytes, typically 1, 2 or 4
+ * @dat: MMIO address for the register to READ the value of the GPIO lines, it
+ * is expected that a 1 in the corresponding bit in this register means the
+ * line is asserted
+ * @set: MMIO address for the register to SET the value of the GPIO lines, it is
+ * expected that we write the line with 1 in this register to drive the GPIO line
+ * high.
+ * @clr: MMIO address for the register to CLEAR the value of the GPIO lines, it is
+ * expected that we write the line with 1 in this register to drive the GPIO line
+ * low. It is allowed to leave this address as NULL, in that case the SET register
+ * will be assumed to also clear the GPIO lines, by actively writing the line
+ * with 0.
+ * @dirout: MMIO address for the register to set the line as OUTPUT. It is assumed
+ * that setting a line to 1 in this register will turn that line into an
+ * output line. Conversely, setting the line to 0 will turn that line into
+ * an input.
+ * @dirin: MMIO address for the register to set this line as INPUT. It is assumed
+ * that setting a line to 1 in this register will turn that line into an
+ * input line. Conversely, setting the line to 0 will turn that line into
+ * an output.
+ * @flags: Different flags that will affect the behaviour of the device, such as
+ * endianness etc.
+ */
+int bgpio_init(struct gpio_chip *gc, struct device *dev,
+ unsigned long sz, void __iomem *dat, void __iomem *set,
+ void __iomem *clr, void __iomem *dirout, void __iomem *dirin,
+ unsigned long flags)
+{
+ int ret;
+
+ if (!is_power_of_2(sz))
+ return -EINVAL;
+
+ gc->bgpio_bits = sz * 8;
+ if (gc->bgpio_bits > BITS_PER_LONG)
+ return -EINVAL;
+
+ raw_spin_lock_init(&gc->bgpio_lock);
+ gc->parent = dev;
+ gc->label = dev_name(dev);
+ gc->base = -1;
+ gc->request = bgpio_request;
+ gc->be_bits = !!(flags & BGPIOF_BIG_ENDIAN);
+
+ ret = gpiochip_get_ngpios(gc, dev);
+ if (ret)
+ gc->ngpio = gc->bgpio_bits;
+ else
+ gc->bgpio_bits = roundup_pow_of_two(round_up(gc->ngpio, 8));
+
+ ret = bgpio_setup_io(gc, dat, set, clr, flags);
+ if (ret)
+ return ret;
+
+ ret = bgpio_setup_accessors(dev, gc, flags & BGPIOF_BIG_ENDIAN_BYTE_ORDER);
+ if (ret)
+ return ret;
+
+ ret = bgpio_setup_direction(gc, dirout, dirin, flags);
+ if (ret)
+ return ret;
+
+ gc->bgpio_data = gc->read_reg(gc->reg_dat);
+ if (gc->set == bgpio_set_set &&
+ !(flags & BGPIOF_UNREADABLE_REG_SET))
+ gc->bgpio_data = gc->read_reg(gc->reg_set);
+
+ if (flags & BGPIOF_UNREADABLE_REG_DIR)
+ gc->bgpio_dir_unreadable = true;
+
+ /*
+ * Inspect hardware to find initial direction setting.
+ */
+ if ((gc->reg_dir_out || gc->reg_dir_in) &&
+ !(flags & BGPIOF_UNREADABLE_REG_DIR)) {
+ if (gc->reg_dir_out)
+ gc->bgpio_dir = gc->read_reg(gc->reg_dir_out);
+ else if (gc->reg_dir_in)
+ gc->bgpio_dir = ~gc->read_reg(gc->reg_dir_in);
+ /*
+ * If we have two direction registers, synchronise
+ * input setting to output setting, the library
+ * can not handle a line being input and output at
+ * the same time.
+ */
+ if (gc->reg_dir_out && gc->reg_dir_in)
+ gc->write_reg(gc->reg_dir_in, ~gc->bgpio_dir);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(bgpio_init);
+
+#if IS_ENABLED(CONFIG_GPIO_GENERIC_PLATFORM)
+
+static void __iomem *bgpio_map(struct platform_device *pdev,
+ const char *name,
+ resource_size_t sane_sz)
+{
+ struct resource *r;
+ resource_size_t sz;
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
+ if (!r)
+ return NULL;
+
+ sz = resource_size(r);
+ if (sz != sane_sz)
+ return IOMEM_ERR_PTR(-EINVAL);
+
+ return devm_ioremap_resource(&pdev->dev, r);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id bgpio_of_match[] = {
+ { .compatible = "brcm,bcm6345-gpio" },
+ { .compatible = "wd,mbl-gpio" },
+ { .compatible = "ni,169445-nand-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bgpio_of_match);
+
+static struct bgpio_pdata *bgpio_parse_dt(struct platform_device *pdev,
+ unsigned long *flags)
+{
+ struct bgpio_pdata *pdata;
+
+ if (!of_match_device(bgpio_of_match, &pdev->dev))
+ return NULL;
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(struct bgpio_pdata),
+ GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ pdata->base = -1;
+
+ if (of_device_is_big_endian(pdev->dev.of_node))
+ *flags |= BGPIOF_BIG_ENDIAN_BYTE_ORDER;
+
+ if (of_property_read_bool(pdev->dev.of_node, "no-output"))
+ *flags |= BGPIOF_NO_OUTPUT;
+
+ return pdata;
+}
+#else
+static struct bgpio_pdata *bgpio_parse_dt(struct platform_device *pdev,
+ unsigned long *flags)
+{
+ return NULL;
+}
+#endif /* CONFIG_OF */
+
+static int bgpio_pdev_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *r;
+ void __iomem *dat;
+ void __iomem *set;
+ void __iomem *clr;
+ void __iomem *dirout;
+ void __iomem *dirin;
+ unsigned long sz;
+ unsigned long flags = 0;
+ int err;
+ struct gpio_chip *gc;
+ struct bgpio_pdata *pdata;
+
+ pdata = bgpio_parse_dt(pdev, &flags);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+
+ if (!pdata) {
+ pdata = dev_get_platdata(dev);
+ flags = pdev->id_entry->driver_data;
+ }
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat");
+ if (!r)
+ return -EINVAL;
+
+ sz = resource_size(r);
+
+ dat = bgpio_map(pdev, "dat", sz);
+ if (IS_ERR(dat))
+ return PTR_ERR(dat);
+
+ set = bgpio_map(pdev, "set", sz);
+ if (IS_ERR(set))
+ return PTR_ERR(set);
+
+ clr = bgpio_map(pdev, "clr", sz);
+ if (IS_ERR(clr))
+ return PTR_ERR(clr);
+
+ dirout = bgpio_map(pdev, "dirout", sz);
+ if (IS_ERR(dirout))
+ return PTR_ERR(dirout);
+
+ dirin = bgpio_map(pdev, "dirin", sz);
+ if (IS_ERR(dirin))
+ return PTR_ERR(dirin);
+
+ gc = devm_kzalloc(&pdev->dev, sizeof(*gc), GFP_KERNEL);
+ if (!gc)
+ return -ENOMEM;
+
+ err = bgpio_init(gc, dev, sz, dat, set, clr, dirout, dirin, flags);
+ if (err)
+ return err;
+
+ if (pdata) {
+ if (pdata->label)
+ gc->label = pdata->label;
+ gc->base = pdata->base;
+ if (pdata->ngpio > 0)
+ gc->ngpio = pdata->ngpio;
+ }
+
+ platform_set_drvdata(pdev, gc);
+
+ return devm_gpiochip_add_data(&pdev->dev, gc, NULL);
+}
+
+static const struct platform_device_id bgpio_id_table[] = {
+ {
+ .name = "basic-mmio-gpio",
+ .driver_data = 0,
+ }, {
+ .name = "basic-mmio-gpio-be",
+ .driver_data = BGPIOF_BIG_ENDIAN,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, bgpio_id_table);
+
+static struct platform_driver bgpio_driver = {
+ .driver = {
+ .name = "basic-mmio-gpio",
+ .of_match_table = of_match_ptr(bgpio_of_match),
+ },
+ .id_table = bgpio_id_table,
+ .probe = bgpio_pdev_probe,
+};
+
+module_platform_driver(bgpio_driver);
+
+#endif /* CONFIG_GPIO_GENERIC_PLATFORM */
+
+MODULE_DESCRIPTION("Driver for basic memory-mapped GPIO controllers");
+MODULE_AUTHOR("Anton Vorontsov <cbouatmailru@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-mockup.c b/drivers/gpio/gpio-mockup.c
new file mode 100644
index 0000000000..b32063ac84
--- /dev/null
+++ b/drivers/gpio/gpio-mockup.c
@@ -0,0 +1,615 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * GPIO Testing Device Driver
+ *
+ * Copyright (C) 2014 Kamlakant Patel <kamlakant.patel@broadcom.com>
+ * Copyright (C) 2015-2016 Bamvor Jian Zhang <bamv2005@gmail.com>
+ * Copyright (C) 2017 Bartosz Golaszewski <brgl@bgdev.pl>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/debugfs.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irq_sim.h>
+#include <linux/irqdomain.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/string_helpers.h>
+#include <linux/uaccess.h>
+
+#include "gpiolib.h"
+
+#define GPIO_MOCKUP_MAX_GC 10
+/*
+ * We're storing two values per chip: the GPIO base and the number
+ * of GPIO lines.
+ */
+#define GPIO_MOCKUP_MAX_RANGES (GPIO_MOCKUP_MAX_GC * 2)
+/* Maximum of four properties + the sentinel. */
+#define GPIO_MOCKUP_MAX_PROP 5
+
+/*
+ * struct gpio_pin_status - structure describing a GPIO status
+ * @dir: Configures direction of gpio as "in" or "out"
+ * @value: Configures status of the gpio as 0(low) or 1(high)
+ */
+struct gpio_mockup_line_status {
+ int dir;
+ int value;
+ int pull;
+};
+
+struct gpio_mockup_chip {
+ struct gpio_chip gc;
+ struct gpio_mockup_line_status *lines;
+ struct irq_domain *irq_sim_domain;
+ struct dentry *dbg_dir;
+ struct mutex lock;
+};
+
+struct gpio_mockup_dbgfs_private {
+ struct gpio_mockup_chip *chip;
+ struct gpio_desc *desc;
+ unsigned int offset;
+};
+
+static int gpio_mockup_ranges[GPIO_MOCKUP_MAX_RANGES];
+static int gpio_mockup_num_ranges;
+module_param_array(gpio_mockup_ranges, int, &gpio_mockup_num_ranges, 0400);
+
+static bool gpio_mockup_named_lines;
+module_param_named(gpio_mockup_named_lines,
+ gpio_mockup_named_lines, bool, 0400);
+
+static struct dentry *gpio_mockup_dbg_dir;
+
+static int gpio_mockup_range_base(unsigned int index)
+{
+ return gpio_mockup_ranges[index * 2];
+}
+
+static int gpio_mockup_range_ngpio(unsigned int index)
+{
+ return gpio_mockup_ranges[index * 2 + 1];
+}
+
+static int __gpio_mockup_get(struct gpio_mockup_chip *chip,
+ unsigned int offset)
+{
+ return chip->lines[offset].value;
+}
+
+static int gpio_mockup_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_mockup_chip *chip = gpiochip_get_data(gc);
+ int val;
+
+ mutex_lock(&chip->lock);
+ val = __gpio_mockup_get(chip, offset);
+ mutex_unlock(&chip->lock);
+
+ return val;
+}
+
+static int gpio_mockup_get_multiple(struct gpio_chip *gc,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct gpio_mockup_chip *chip = gpiochip_get_data(gc);
+ unsigned int bit, val;
+
+ mutex_lock(&chip->lock);
+ for_each_set_bit(bit, mask, gc->ngpio) {
+ val = __gpio_mockup_get(chip, bit);
+ __assign_bit(bit, bits, val);
+ }
+ mutex_unlock(&chip->lock);
+
+ return 0;
+}
+
+static void __gpio_mockup_set(struct gpio_mockup_chip *chip,
+ unsigned int offset, int value)
+{
+ chip->lines[offset].value = !!value;
+}
+
+static void gpio_mockup_set(struct gpio_chip *gc,
+ unsigned int offset, int value)
+{
+ struct gpio_mockup_chip *chip = gpiochip_get_data(gc);
+
+ mutex_lock(&chip->lock);
+ __gpio_mockup_set(chip, offset, value);
+ mutex_unlock(&chip->lock);
+}
+
+static void gpio_mockup_set_multiple(struct gpio_chip *gc,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct gpio_mockup_chip *chip = gpiochip_get_data(gc);
+ unsigned int bit;
+
+ mutex_lock(&chip->lock);
+ for_each_set_bit(bit, mask, gc->ngpio)
+ __gpio_mockup_set(chip, bit, test_bit(bit, bits));
+ mutex_unlock(&chip->lock);
+}
+
+static int gpio_mockup_apply_pull(struct gpio_mockup_chip *chip,
+ unsigned int offset, int value)
+{
+ struct gpio_chip *gc = &chip->gc;
+ struct gpio_desc *desc = gpiochip_get_desc(gc, offset);
+ int curr, irq, irq_type, ret = 0;
+
+ mutex_lock(&chip->lock);
+
+ if (test_bit(FLAG_REQUESTED, &desc->flags) &&
+ !test_bit(FLAG_IS_OUT, &desc->flags)) {
+ curr = __gpio_mockup_get(chip, offset);
+ if (curr == value)
+ goto out;
+
+ irq = irq_find_mapping(chip->irq_sim_domain, offset);
+ if (!irq)
+ /*
+ * This is fine - it just means, nobody is listening
+ * for interrupts on this line, otherwise
+ * irq_create_mapping() would have been called from
+ * the to_irq() callback.
+ */
+ goto set_value;
+
+ irq_type = irq_get_trigger_type(irq);
+
+ if ((value == 1 && (irq_type & IRQ_TYPE_EDGE_RISING)) ||
+ (value == 0 && (irq_type & IRQ_TYPE_EDGE_FALLING))) {
+ ret = irq_set_irqchip_state(irq, IRQCHIP_STATE_PENDING,
+ true);
+ if (ret)
+ goto out;
+ }
+ }
+
+set_value:
+ /* Change the value unless we're actively driving the line. */
+ if (!test_bit(FLAG_REQUESTED, &desc->flags) ||
+ !test_bit(FLAG_IS_OUT, &desc->flags))
+ __gpio_mockup_set(chip, offset, value);
+
+out:
+ chip->lines[offset].pull = value;
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static int gpio_mockup_set_config(struct gpio_chip *gc,
+ unsigned int offset, unsigned long config)
+{
+ struct gpio_mockup_chip *chip = gpiochip_get_data(gc);
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_BIAS_PULL_UP:
+ return gpio_mockup_apply_pull(chip, offset, 1);
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ return gpio_mockup_apply_pull(chip, offset, 0);
+ default:
+ break;
+ }
+ return -ENOTSUPP;
+}
+
+static int gpio_mockup_dirout(struct gpio_chip *gc,
+ unsigned int offset, int value)
+{
+ struct gpio_mockup_chip *chip = gpiochip_get_data(gc);
+
+ mutex_lock(&chip->lock);
+ chip->lines[offset].dir = GPIO_LINE_DIRECTION_OUT;
+ __gpio_mockup_set(chip, offset, value);
+ mutex_unlock(&chip->lock);
+
+ return 0;
+}
+
+static int gpio_mockup_dirin(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_mockup_chip *chip = gpiochip_get_data(gc);
+
+ mutex_lock(&chip->lock);
+ chip->lines[offset].dir = GPIO_LINE_DIRECTION_IN;
+ mutex_unlock(&chip->lock);
+
+ return 0;
+}
+
+static int gpio_mockup_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_mockup_chip *chip = gpiochip_get_data(gc);
+ int direction;
+
+ mutex_lock(&chip->lock);
+ direction = chip->lines[offset].dir;
+ mutex_unlock(&chip->lock);
+
+ return direction;
+}
+
+static int gpio_mockup_to_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_mockup_chip *chip = gpiochip_get_data(gc);
+
+ return irq_create_mapping(chip->irq_sim_domain, offset);
+}
+
+static void gpio_mockup_free(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_mockup_chip *chip = gpiochip_get_data(gc);
+
+ __gpio_mockup_set(chip, offset, chip->lines[offset].pull);
+}
+
+static ssize_t gpio_mockup_debugfs_read(struct file *file,
+ char __user *usr_buf,
+ size_t size, loff_t *ppos)
+{
+ struct gpio_mockup_dbgfs_private *priv;
+ struct gpio_mockup_chip *chip;
+ struct seq_file *sfile;
+ struct gpio_chip *gc;
+ int val, cnt;
+ char buf[3];
+
+ if (*ppos != 0)
+ return 0;
+
+ sfile = file->private_data;
+ priv = sfile->private;
+ chip = priv->chip;
+ gc = &chip->gc;
+
+ val = gpio_mockup_get(gc, priv->offset);
+ cnt = snprintf(buf, sizeof(buf), "%d\n", val);
+
+ return simple_read_from_buffer(usr_buf, size, ppos, buf, cnt);
+}
+
+static ssize_t gpio_mockup_debugfs_write(struct file *file,
+ const char __user *usr_buf,
+ size_t size, loff_t *ppos)
+{
+ struct gpio_mockup_dbgfs_private *priv;
+ int rv, val;
+ struct seq_file *sfile;
+
+ if (*ppos != 0)
+ return -EINVAL;
+
+ rv = kstrtoint_from_user(usr_buf, size, 0, &val);
+ if (rv)
+ return rv;
+ if (val != 0 && val != 1)
+ return -EINVAL;
+
+ sfile = file->private_data;
+ priv = sfile->private;
+ rv = gpio_mockup_apply_pull(priv->chip, priv->offset, val);
+ if (rv)
+ return rv;
+
+ return size;
+}
+
+static int gpio_mockup_debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, NULL, inode->i_private);
+}
+
+/*
+ * Each mockup chip is represented by a directory named after the chip's device
+ * name under /sys/kernel/debug/gpio-mockup/. Each line is represented by
+ * a file using the line's offset as the name under the chip's directory.
+ *
+ * Reading from the line's file yields the current *value*, writing to the
+ * line's file changes the current *pull*. Default pull for mockup lines is
+ * down.
+ *
+ * Examples:
+ * - when a line pulled down is requested in output mode and driven high, its
+ * value will return to 0 once it's released
+ * - when the line is requested in output mode and driven high, writing 0 to
+ * the corresponding debugfs file will change the pull to down but the
+ * reported value will still be 1 until the line is released
+ * - line requested in input mode always reports the same value as its pull
+ * configuration
+ * - when the line is requested in input mode and monitored for events, writing
+ * the same value to the debugfs file will be a noop, while writing the
+ * opposite value will generate a dummy interrupt with an appropriate edge
+ */
+static const struct file_operations gpio_mockup_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = gpio_mockup_debugfs_open,
+ .read = gpio_mockup_debugfs_read,
+ .write = gpio_mockup_debugfs_write,
+ .llseek = no_llseek,
+ .release = single_release,
+};
+
+static void gpio_mockup_debugfs_setup(struct device *dev,
+ struct gpio_mockup_chip *chip)
+{
+ struct gpio_mockup_dbgfs_private *priv;
+ struct gpio_chip *gc;
+ const char *devname;
+ char *name;
+ int i;
+
+ gc = &chip->gc;
+ devname = dev_name(&gc->gpiodev->dev);
+
+ chip->dbg_dir = debugfs_create_dir(devname, gpio_mockup_dbg_dir);
+
+ for (i = 0; i < gc->ngpio; i++) {
+ name = devm_kasprintf(dev, GFP_KERNEL, "%d", i);
+ if (!name)
+ return;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return;
+
+ priv->chip = chip;
+ priv->offset = i;
+ priv->desc = gpiochip_get_desc(gc, i);
+
+ debugfs_create_file(name, 0600, chip->dbg_dir, priv,
+ &gpio_mockup_debugfs_ops);
+ }
+}
+
+static void gpio_mockup_debugfs_cleanup(void *data)
+{
+ struct gpio_mockup_chip *chip = data;
+
+ debugfs_remove_recursive(chip->dbg_dir);
+}
+
+static void gpio_mockup_dispose_mappings(void *data)
+{
+ struct gpio_mockup_chip *chip = data;
+ struct gpio_chip *gc = &chip->gc;
+ int i, irq;
+
+ for (i = 0; i < gc->ngpio; i++) {
+ irq = irq_find_mapping(chip->irq_sim_domain, i);
+ if (irq)
+ irq_dispose_mapping(irq);
+ }
+}
+
+static int gpio_mockup_probe(struct platform_device *pdev)
+{
+ struct gpio_mockup_chip *chip;
+ struct gpio_chip *gc;
+ struct device *dev;
+ const char *name;
+ int rv, base, i;
+ u16 ngpio;
+
+ dev = &pdev->dev;
+
+ rv = device_property_read_u32(dev, "gpio-base", &base);
+ if (rv)
+ base = -1;
+
+ rv = device_property_read_u16(dev, "nr-gpios", &ngpio);
+ if (rv)
+ return rv;
+
+ rv = device_property_read_string(dev, "chip-label", &name);
+ if (rv)
+ name = dev_name(dev);
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ mutex_init(&chip->lock);
+
+ gc = &chip->gc;
+ gc->base = base;
+ gc->ngpio = ngpio;
+ gc->label = name;
+ gc->owner = THIS_MODULE;
+ gc->parent = dev;
+ gc->get = gpio_mockup_get;
+ gc->set = gpio_mockup_set;
+ gc->get_multiple = gpio_mockup_get_multiple;
+ gc->set_multiple = gpio_mockup_set_multiple;
+ gc->direction_output = gpio_mockup_dirout;
+ gc->direction_input = gpio_mockup_dirin;
+ gc->get_direction = gpio_mockup_get_direction;
+ gc->set_config = gpio_mockup_set_config;
+ gc->to_irq = gpio_mockup_to_irq;
+ gc->free = gpio_mockup_free;
+
+ chip->lines = devm_kcalloc(dev, gc->ngpio,
+ sizeof(*chip->lines), GFP_KERNEL);
+ if (!chip->lines)
+ return -ENOMEM;
+
+ for (i = 0; i < gc->ngpio; i++)
+ chip->lines[i].dir = GPIO_LINE_DIRECTION_IN;
+
+ chip->irq_sim_domain = devm_irq_domain_create_sim(dev, NULL,
+ gc->ngpio);
+ if (IS_ERR(chip->irq_sim_domain))
+ return PTR_ERR(chip->irq_sim_domain);
+
+ rv = devm_add_action_or_reset(dev, gpio_mockup_dispose_mappings, chip);
+ if (rv)
+ return rv;
+
+ rv = devm_gpiochip_add_data(dev, &chip->gc, chip);
+ if (rv)
+ return rv;
+
+ gpio_mockup_debugfs_setup(dev, chip);
+
+ return devm_add_action_or_reset(dev, gpio_mockup_debugfs_cleanup, chip);
+}
+
+static const struct of_device_id gpio_mockup_of_match[] = {
+ { .compatible = "gpio-mockup", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, gpio_mockup_of_match);
+
+static struct platform_driver gpio_mockup_driver = {
+ .driver = {
+ .name = "gpio-mockup",
+ .of_match_table = gpio_mockup_of_match,
+ },
+ .probe = gpio_mockup_probe,
+};
+
+static struct platform_device *gpio_mockup_pdevs[GPIO_MOCKUP_MAX_GC];
+
+static void gpio_mockup_unregister_pdevs(void)
+{
+ struct platform_device *pdev;
+ struct fwnode_handle *fwnode;
+ int i;
+
+ for (i = 0; i < GPIO_MOCKUP_MAX_GC; i++) {
+ pdev = gpio_mockup_pdevs[i];
+ if (!pdev)
+ continue;
+
+ fwnode = dev_fwnode(&pdev->dev);
+ platform_device_unregister(pdev);
+ fwnode_remove_software_node(fwnode);
+ }
+}
+
+static int __init gpio_mockup_register_chip(int idx)
+{
+ struct property_entry properties[GPIO_MOCKUP_MAX_PROP];
+ struct platform_device_info pdevinfo;
+ struct platform_device *pdev;
+ struct fwnode_handle *fwnode;
+ char **line_names = NULL;
+ char chip_label[32];
+ int prop = 0, base;
+ u16 ngpio;
+
+ memset(properties, 0, sizeof(properties));
+ memset(&pdevinfo, 0, sizeof(pdevinfo));
+
+ snprintf(chip_label, sizeof(chip_label), "gpio-mockup-%c", idx + 'A');
+ properties[prop++] = PROPERTY_ENTRY_STRING("chip-label", chip_label);
+
+ base = gpio_mockup_range_base(idx);
+ if (base >= 0)
+ properties[prop++] = PROPERTY_ENTRY_U32("gpio-base", base);
+
+ ngpio = base < 0 ? gpio_mockup_range_ngpio(idx)
+ : gpio_mockup_range_ngpio(idx) - base;
+ properties[prop++] = PROPERTY_ENTRY_U16("nr-gpios", ngpio);
+
+ if (gpio_mockup_named_lines) {
+ line_names = kasprintf_strarray(GFP_KERNEL, chip_label, ngpio);
+ if (!line_names)
+ return -ENOMEM;
+
+ properties[prop++] = PROPERTY_ENTRY_STRING_ARRAY_LEN(
+ "gpio-line-names", line_names, ngpio);
+ }
+
+ fwnode = fwnode_create_software_node(properties, NULL);
+ if (IS_ERR(fwnode)) {
+ kfree_strarray(line_names, ngpio);
+ return PTR_ERR(fwnode);
+ }
+
+ pdevinfo.name = "gpio-mockup";
+ pdevinfo.id = idx;
+ pdevinfo.fwnode = fwnode;
+
+ pdev = platform_device_register_full(&pdevinfo);
+ kfree_strarray(line_names, ngpio);
+ if (IS_ERR(pdev)) {
+ fwnode_remove_software_node(fwnode);
+ pr_err("error registering device");
+ return PTR_ERR(pdev);
+ }
+
+ gpio_mockup_pdevs[idx] = pdev;
+
+ return 0;
+}
+
+static int __init gpio_mockup_init(void)
+{
+ int i, num_chips, err;
+
+ if ((gpio_mockup_num_ranges % 2) ||
+ (gpio_mockup_num_ranges > GPIO_MOCKUP_MAX_RANGES))
+ return -EINVAL;
+
+ /* Each chip is described by two values. */
+ num_chips = gpio_mockup_num_ranges / 2;
+
+ /*
+ * The second value in the <base GPIO - number of GPIOS> pair must
+ * always be greater than 0.
+ */
+ for (i = 0; i < num_chips; i++) {
+ if (gpio_mockup_range_ngpio(i) < 0)
+ return -EINVAL;
+ }
+
+ gpio_mockup_dbg_dir = debugfs_create_dir("gpio-mockup", NULL);
+
+ err = platform_driver_register(&gpio_mockup_driver);
+ if (err) {
+ pr_err("error registering platform driver\n");
+ debugfs_remove_recursive(gpio_mockup_dbg_dir);
+ return err;
+ }
+
+ for (i = 0; i < num_chips; i++) {
+ err = gpio_mockup_register_chip(i);
+ if (err) {
+ platform_driver_unregister(&gpio_mockup_driver);
+ gpio_mockup_unregister_pdevs();
+ debugfs_remove_recursive(gpio_mockup_dbg_dir);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static void __exit gpio_mockup_exit(void)
+{
+ gpio_mockup_unregister_pdevs();
+ debugfs_remove_recursive(gpio_mockup_dbg_dir);
+ platform_driver_unregister(&gpio_mockup_driver);
+}
+
+module_init(gpio_mockup_init);
+module_exit(gpio_mockup_exit);
+
+MODULE_AUTHOR("Kamlakant Patel <kamlakant.patel@broadcom.com>");
+MODULE_AUTHOR("Bamvor Jian Zhang <bamv2005@gmail.com>");
+MODULE_AUTHOR("Bartosz Golaszewski <brgl@bgdev.pl>");
+MODULE_DESCRIPTION("GPIO Testing driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-moxtet.c b/drivers/gpio/gpio-moxtet.c
new file mode 100644
index 0000000000..61f9efd6c6
--- /dev/null
+++ b/drivers/gpio/gpio-moxtet.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Turris Mox Moxtet GPIO expander
+ *
+ * Copyright (C) 2018 Marek Behún <kabel@kernel.org>
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/moxtet.h>
+#include <linux/module.h>
+
+#define MOXTET_GPIO_NGPIOS 12
+#define MOXTET_GPIO_INPUTS 4
+
+struct moxtet_gpio_desc {
+ u16 in_mask;
+ u16 out_mask;
+};
+
+static const struct moxtet_gpio_desc descs[] = {
+ [TURRIS_MOX_MODULE_SFP] = {
+ .in_mask = GENMASK(2, 0),
+ .out_mask = GENMASK(5, 4),
+ },
+};
+
+struct moxtet_gpio_chip {
+ struct device *dev;
+ struct gpio_chip gpio_chip;
+ const struct moxtet_gpio_desc *desc;
+};
+
+static int moxtet_gpio_get_value(struct gpio_chip *gc, unsigned int offset)
+{
+ struct moxtet_gpio_chip *chip = gpiochip_get_data(gc);
+ int ret;
+
+ if (chip->desc->in_mask & BIT(offset)) {
+ ret = moxtet_device_read(chip->dev);
+ } else if (chip->desc->out_mask & BIT(offset)) {
+ ret = moxtet_device_written(chip->dev);
+ if (ret >= 0)
+ ret <<= MOXTET_GPIO_INPUTS;
+ } else {
+ return -EINVAL;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ return !!(ret & BIT(offset));
+}
+
+static void moxtet_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
+ int val)
+{
+ struct moxtet_gpio_chip *chip = gpiochip_get_data(gc);
+ int state;
+
+ state = moxtet_device_written(chip->dev);
+ if (state < 0)
+ return;
+
+ offset -= MOXTET_GPIO_INPUTS;
+
+ if (val)
+ state |= BIT(offset);
+ else
+ state &= ~BIT(offset);
+
+ moxtet_device_write(chip->dev, state);
+}
+
+static int moxtet_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct moxtet_gpio_chip *chip = gpiochip_get_data(gc);
+
+ /* All lines are hard wired to be either input or output, not both. */
+ if (chip->desc->in_mask & BIT(offset))
+ return GPIO_LINE_DIRECTION_IN;
+ else if (chip->desc->out_mask & BIT(offset))
+ return GPIO_LINE_DIRECTION_OUT;
+ else
+ return -EINVAL;
+}
+
+static int moxtet_gpio_direction_input(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ struct moxtet_gpio_chip *chip = gpiochip_get_data(gc);
+
+ if (chip->desc->in_mask & BIT(offset))
+ return 0;
+ else if (chip->desc->out_mask & BIT(offset))
+ return -ENOTSUPP;
+ else
+ return -EINVAL;
+}
+
+static int moxtet_gpio_direction_output(struct gpio_chip *gc,
+ unsigned int offset, int val)
+{
+ struct moxtet_gpio_chip *chip = gpiochip_get_data(gc);
+
+ if (chip->desc->out_mask & BIT(offset))
+ moxtet_gpio_set_value(gc, offset, val);
+ else if (chip->desc->in_mask & BIT(offset))
+ return -ENOTSUPP;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int moxtet_gpio_probe(struct device *dev)
+{
+ struct moxtet_gpio_chip *chip;
+ struct device_node *nc = dev->of_node;
+ int id;
+
+ id = to_moxtet_device(dev)->id;
+
+ if (id >= ARRAY_SIZE(descs)) {
+ dev_err(dev, "%pOF Moxtet device id 0x%x is not supported by gpio-moxtet driver\n",
+ nc, id);
+ return -ENOTSUPP;
+ }
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = dev;
+ chip->gpio_chip.parent = dev;
+ chip->desc = &descs[id];
+
+ dev_set_drvdata(dev, chip);
+
+ chip->gpio_chip.label = dev_name(dev);
+ chip->gpio_chip.get_direction = moxtet_gpio_get_direction;
+ chip->gpio_chip.direction_input = moxtet_gpio_direction_input;
+ chip->gpio_chip.direction_output = moxtet_gpio_direction_output;
+ chip->gpio_chip.get = moxtet_gpio_get_value;
+ chip->gpio_chip.set = moxtet_gpio_set_value;
+ chip->gpio_chip.base = -1;
+
+ chip->gpio_chip.ngpio = MOXTET_GPIO_NGPIOS;
+
+ chip->gpio_chip.can_sleep = true;
+ chip->gpio_chip.owner = THIS_MODULE;
+
+ return devm_gpiochip_add_data(dev, &chip->gpio_chip, chip);
+}
+
+static const struct of_device_id moxtet_gpio_dt_ids[] = {
+ { .compatible = "cznic,moxtet-gpio", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, moxtet_gpio_dt_ids);
+
+static const enum turris_mox_module_id moxtet_gpio_module_table[] = {
+ TURRIS_MOX_MODULE_SFP,
+ 0,
+};
+
+static struct moxtet_driver moxtet_gpio_driver = {
+ .driver = {
+ .name = "moxtet-gpio",
+ .of_match_table = moxtet_gpio_dt_ids,
+ .probe = moxtet_gpio_probe,
+ },
+ .id_table = moxtet_gpio_module_table,
+};
+module_moxtet_driver(moxtet_gpio_driver);
+
+MODULE_AUTHOR("Marek Behun <kabel@kernel.org>");
+MODULE_DESCRIPTION("Turris Mox Moxtet GPIO expander");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-mpc5200.c b/drivers/gpio/gpio-mpc5200.c
new file mode 100644
index 0000000000..b49e3ca640
--- /dev/null
+++ b/drivers/gpio/gpio-mpc5200.c
@@ -0,0 +1,366 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MPC52xx gpio driver
+ *
+ * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
+ */
+
+#include <linux/of.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/gpio/legacy-of-mm-gpiochip.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+
+#include <asm/mpc52xx.h>
+#include <sysdev/fsl_soc.h>
+
+static DEFINE_SPINLOCK(gpio_lock);
+
+struct mpc52xx_gpiochip {
+ struct of_mm_gpio_chip mmchip;
+ unsigned int shadow_dvo;
+ unsigned int shadow_gpioe;
+ unsigned int shadow_ddr;
+};
+
+/*
+ * GPIO LIB API implementation for wakeup GPIOs.
+ *
+ * There's a maximum of 8 wakeup GPIOs. Which of these are available
+ * for use depends on your board setup.
+ *
+ * 0 -> GPIO_WKUP_7
+ * 1 -> GPIO_WKUP_6
+ * 2 -> PSC6_1
+ * 3 -> PSC6_0
+ * 4 -> ETH_17
+ * 5 -> PSC3_9
+ * 6 -> PSC2_4
+ * 7 -> PSC1_4
+ *
+ */
+static int mpc52xx_wkup_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+ struct mpc52xx_gpio_wkup __iomem *regs = mm_gc->regs;
+ unsigned int ret;
+
+ ret = (in_8(&regs->wkup_ival) >> (7 - gpio)) & 1;
+
+ pr_debug("%s: gpio: %d ret: %d\n", __func__, gpio, ret);
+
+ return ret;
+}
+
+static inline void
+__mpc52xx_wkup_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+ struct mpc52xx_gpiochip *chip = gpiochip_get_data(gc);
+ struct mpc52xx_gpio_wkup __iomem *regs = mm_gc->regs;
+
+ if (val)
+ chip->shadow_dvo |= 1 << (7 - gpio);
+ else
+ chip->shadow_dvo &= ~(1 << (7 - gpio));
+
+ out_8(&regs->wkup_dvo, chip->shadow_dvo);
+}
+
+static void
+mpc52xx_wkup_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ __mpc52xx_wkup_gpio_set(gc, gpio, val);
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+}
+
+static int mpc52xx_wkup_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+ struct mpc52xx_gpiochip *chip = gpiochip_get_data(gc);
+ struct mpc52xx_gpio_wkup __iomem *regs = mm_gc->regs;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ /* set the direction */
+ chip->shadow_ddr &= ~(1 << (7 - gpio));
+ out_8(&regs->wkup_ddr, chip->shadow_ddr);
+
+ /* and enable the pin */
+ chip->shadow_gpioe |= 1 << (7 - gpio);
+ out_8(&regs->wkup_gpioe, chip->shadow_gpioe);
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ return 0;
+}
+
+static int
+mpc52xx_wkup_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+ struct mpc52xx_gpio_wkup __iomem *regs = mm_gc->regs;
+ struct mpc52xx_gpiochip *chip = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ __mpc52xx_wkup_gpio_set(gc, gpio, val);
+
+ /* Then set direction */
+ chip->shadow_ddr |= 1 << (7 - gpio);
+ out_8(&regs->wkup_ddr, chip->shadow_ddr);
+
+ /* Finally enable the pin */
+ chip->shadow_gpioe |= 1 << (7 - gpio);
+ out_8(&regs->wkup_gpioe, chip->shadow_gpioe);
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+
+ return 0;
+}
+
+static int mpc52xx_wkup_gpiochip_probe(struct platform_device *ofdev)
+{
+ struct mpc52xx_gpiochip *chip;
+ struct mpc52xx_gpio_wkup __iomem *regs;
+ struct gpio_chip *gc;
+ int ret;
+
+ chip = devm_kzalloc(&ofdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ platform_set_drvdata(ofdev, chip);
+
+ gc = &chip->mmchip.gc;
+
+ gc->ngpio = 8;
+ gc->direction_input = mpc52xx_wkup_gpio_dir_in;
+ gc->direction_output = mpc52xx_wkup_gpio_dir_out;
+ gc->get = mpc52xx_wkup_gpio_get;
+ gc->set = mpc52xx_wkup_gpio_set;
+
+ ret = of_mm_gpiochip_add_data(ofdev->dev.of_node, &chip->mmchip, chip);
+ if (ret)
+ return ret;
+
+ regs = chip->mmchip.regs;
+ chip->shadow_gpioe = in_8(&regs->wkup_gpioe);
+ chip->shadow_ddr = in_8(&regs->wkup_ddr);
+ chip->shadow_dvo = in_8(&regs->wkup_dvo);
+
+ return 0;
+}
+
+static int mpc52xx_gpiochip_remove(struct platform_device *ofdev)
+{
+ struct mpc52xx_gpiochip *chip = platform_get_drvdata(ofdev);
+
+ of_mm_gpiochip_remove(&chip->mmchip);
+
+ return 0;
+}
+
+static const struct of_device_id mpc52xx_wkup_gpiochip_match[] = {
+ { .compatible = "fsl,mpc5200-gpio-wkup", },
+ {}
+};
+
+static struct platform_driver mpc52xx_wkup_gpiochip_driver = {
+ .driver = {
+ .name = "mpc5200-gpio-wkup",
+ .of_match_table = mpc52xx_wkup_gpiochip_match,
+ },
+ .probe = mpc52xx_wkup_gpiochip_probe,
+ .remove = mpc52xx_gpiochip_remove,
+};
+
+/*
+ * GPIO LIB API implementation for simple GPIOs
+ *
+ * There's a maximum of 32 simple GPIOs. Which of these are available
+ * for use depends on your board setup.
+ * The numbering reflects the bit numbering in the port registers:
+ *
+ * 0..1 > reserved
+ * 2..3 > IRDA
+ * 4..7 > ETHR
+ * 8..11 > reserved
+ * 12..15 > USB
+ * 16..17 > reserved
+ * 18..23 > PSC3
+ * 24..27 > PSC2
+ * 28..31 > PSC1
+ */
+static int mpc52xx_simple_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+ struct mpc52xx_gpio __iomem *regs = mm_gc->regs;
+ unsigned int ret;
+
+ ret = (in_be32(&regs->simple_ival) >> (31 - gpio)) & 1;
+
+ return ret;
+}
+
+static inline void
+__mpc52xx_simple_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+ struct mpc52xx_gpiochip *chip = gpiochip_get_data(gc);
+ struct mpc52xx_gpio __iomem *regs = mm_gc->regs;
+
+ if (val)
+ chip->shadow_dvo |= 1 << (31 - gpio);
+ else
+ chip->shadow_dvo &= ~(1 << (31 - gpio));
+ out_be32(&regs->simple_dvo, chip->shadow_dvo);
+}
+
+static void
+mpc52xx_simple_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ __mpc52xx_simple_gpio_set(gc, gpio, val);
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+}
+
+static int mpc52xx_simple_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+ struct mpc52xx_gpiochip *chip = gpiochip_get_data(gc);
+ struct mpc52xx_gpio __iomem *regs = mm_gc->regs;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ /* set the direction */
+ chip->shadow_ddr &= ~(1 << (31 - gpio));
+ out_be32(&regs->simple_ddr, chip->shadow_ddr);
+
+ /* and enable the pin */
+ chip->shadow_gpioe |= 1 << (31 - gpio);
+ out_be32(&regs->simple_gpioe, chip->shadow_gpioe);
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ return 0;
+}
+
+static int
+mpc52xx_simple_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+ struct mpc52xx_gpiochip *chip = gpiochip_get_data(gc);
+ struct mpc52xx_gpio __iomem *regs = mm_gc->regs;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ /* First set initial value */
+ __mpc52xx_simple_gpio_set(gc, gpio, val);
+
+ /* Then set direction */
+ chip->shadow_ddr |= 1 << (31 - gpio);
+ out_be32(&regs->simple_ddr, chip->shadow_ddr);
+
+ /* Finally enable the pin */
+ chip->shadow_gpioe |= 1 << (31 - gpio);
+ out_be32(&regs->simple_gpioe, chip->shadow_gpioe);
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+
+ return 0;
+}
+
+static int mpc52xx_simple_gpiochip_probe(struct platform_device *ofdev)
+{
+ struct mpc52xx_gpiochip *chip;
+ struct gpio_chip *gc;
+ struct mpc52xx_gpio __iomem *regs;
+ int ret;
+
+ chip = devm_kzalloc(&ofdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ platform_set_drvdata(ofdev, chip);
+
+ gc = &chip->mmchip.gc;
+
+ gc->ngpio = 32;
+ gc->direction_input = mpc52xx_simple_gpio_dir_in;
+ gc->direction_output = mpc52xx_simple_gpio_dir_out;
+ gc->get = mpc52xx_simple_gpio_get;
+ gc->set = mpc52xx_simple_gpio_set;
+
+ ret = of_mm_gpiochip_add_data(ofdev->dev.of_node, &chip->mmchip, chip);
+ if (ret)
+ return ret;
+
+ regs = chip->mmchip.regs;
+ chip->shadow_gpioe = in_be32(&regs->simple_gpioe);
+ chip->shadow_ddr = in_be32(&regs->simple_ddr);
+ chip->shadow_dvo = in_be32(&regs->simple_dvo);
+
+ return 0;
+}
+
+static const struct of_device_id mpc52xx_simple_gpiochip_match[] = {
+ { .compatible = "fsl,mpc5200-gpio", },
+ {}
+};
+
+static struct platform_driver mpc52xx_simple_gpiochip_driver = {
+ .driver = {
+ .name = "mpc5200-gpio",
+ .of_match_table = mpc52xx_simple_gpiochip_match,
+ },
+ .probe = mpc52xx_simple_gpiochip_probe,
+ .remove = mpc52xx_gpiochip_remove,
+};
+
+static struct platform_driver * const drivers[] = {
+ &mpc52xx_wkup_gpiochip_driver,
+ &mpc52xx_simple_gpiochip_driver,
+};
+
+static int __init mpc52xx_gpio_init(void)
+{
+ return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
+}
+
+/* Make sure we get initialised before anyone else tries to use us */
+subsys_initcall(mpc52xx_gpio_init);
+
+static void __exit mpc52xx_gpio_exit(void)
+{
+ platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
+}
+module_exit(mpc52xx_gpio_exit);
+
+MODULE_DESCRIPTION("Freescale MPC52xx gpio driver");
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de");
+MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/gpio/gpio-mpc8xxx.c b/drivers/gpio/gpio-mpc8xxx.c
new file mode 100644
index 0000000000..ebf2f511df
--- /dev/null
+++ b/drivers/gpio/gpio-mpc8xxx.c
@@ -0,0 +1,457 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIOs on MPC512x/8349/8572/8610/QorIQ and compatible
+ *
+ * Copyright (C) 2008 Peter Korsgaard <jacmet@sunsite.dk>
+ * Copyright (C) 2016 Freescale Semiconductor Inc.
+ */
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/gpio/driver.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+
+#define MPC8XXX_GPIO_PINS 32
+
+#define GPIO_DIR 0x00
+#define GPIO_ODR 0x04
+#define GPIO_DAT 0x08
+#define GPIO_IER 0x0c
+#define GPIO_IMR 0x10
+#define GPIO_ICR 0x14
+#define GPIO_ICR2 0x18
+#define GPIO_IBE 0x18
+
+struct mpc8xxx_gpio_chip {
+ struct gpio_chip gc;
+ void __iomem *regs;
+ raw_spinlock_t lock;
+
+ int (*direction_output)(struct gpio_chip *chip,
+ unsigned offset, int value);
+
+ struct irq_domain *irq;
+ int irqn;
+};
+
+/*
+ * This hardware has a big endian bit assignment such that GPIO line 0 is
+ * connected to bit 31, line 1 to bit 30 ... line 31 to bit 0.
+ * This inline helper give the right bitmask for a certain line.
+ */
+static inline u32 mpc_pin2mask(unsigned int offset)
+{
+ return BIT(31 - offset);
+}
+
+/* Workaround GPIO 1 errata on MPC8572/MPC8536. The status of GPIOs
+ * defined as output cannot be determined by reading GPDAT register,
+ * so we use shadow data register instead. The status of input pins
+ * is determined by reading GPDAT register.
+ */
+static int mpc8572_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ u32 val;
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc = gpiochip_get_data(gc);
+ u32 out_mask, out_shadow;
+
+ out_mask = gc->read_reg(mpc8xxx_gc->regs + GPIO_DIR);
+ val = gc->read_reg(mpc8xxx_gc->regs + GPIO_DAT) & ~out_mask;
+ out_shadow = gc->bgpio_data & out_mask;
+
+ return !!((val | out_shadow) & mpc_pin2mask(gpio));
+}
+
+static int mpc5121_gpio_dir_out(struct gpio_chip *gc,
+ unsigned int gpio, int val)
+{
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc = gpiochip_get_data(gc);
+ /* GPIO 28..31 are input only on MPC5121 */
+ if (gpio >= 28)
+ return -EINVAL;
+
+ return mpc8xxx_gc->direction_output(gc, gpio, val);
+}
+
+static int mpc5125_gpio_dir_out(struct gpio_chip *gc,
+ unsigned int gpio, int val)
+{
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc = gpiochip_get_data(gc);
+ /* GPIO 0..3 are input only on MPC5125 */
+ if (gpio <= 3)
+ return -EINVAL;
+
+ return mpc8xxx_gc->direction_output(gc, gpio, val);
+}
+
+static int mpc8xxx_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
+{
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc = gpiochip_get_data(gc);
+
+ if (mpc8xxx_gc->irq && offset < MPC8XXX_GPIO_PINS)
+ return irq_create_mapping(mpc8xxx_gc->irq, offset);
+ else
+ return -ENXIO;
+}
+
+static irqreturn_t mpc8xxx_gpio_irq_cascade(int irq, void *data)
+{
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc = data;
+ struct gpio_chip *gc = &mpc8xxx_gc->gc;
+ unsigned long mask;
+ int i;
+
+ mask = gc->read_reg(mpc8xxx_gc->regs + GPIO_IER)
+ & gc->read_reg(mpc8xxx_gc->regs + GPIO_IMR);
+ for_each_set_bit(i, &mask, 32)
+ generic_handle_domain_irq(mpc8xxx_gc->irq, 31 - i);
+
+ return IRQ_HANDLED;
+}
+
+static void mpc8xxx_irq_unmask(struct irq_data *d)
+{
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d);
+ struct gpio_chip *gc = &mpc8xxx_gc->gc;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&mpc8xxx_gc->lock, flags);
+
+ gc->write_reg(mpc8xxx_gc->regs + GPIO_IMR,
+ gc->read_reg(mpc8xxx_gc->regs + GPIO_IMR)
+ | mpc_pin2mask(irqd_to_hwirq(d)));
+
+ raw_spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags);
+}
+
+static void mpc8xxx_irq_mask(struct irq_data *d)
+{
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d);
+ struct gpio_chip *gc = &mpc8xxx_gc->gc;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&mpc8xxx_gc->lock, flags);
+
+ gc->write_reg(mpc8xxx_gc->regs + GPIO_IMR,
+ gc->read_reg(mpc8xxx_gc->regs + GPIO_IMR)
+ & ~mpc_pin2mask(irqd_to_hwirq(d)));
+
+ raw_spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags);
+}
+
+static void mpc8xxx_irq_ack(struct irq_data *d)
+{
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d);
+ struct gpio_chip *gc = &mpc8xxx_gc->gc;
+
+ gc->write_reg(mpc8xxx_gc->regs + GPIO_IER,
+ mpc_pin2mask(irqd_to_hwirq(d)));
+}
+
+static int mpc8xxx_irq_set_type(struct irq_data *d, unsigned int flow_type)
+{
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d);
+ struct gpio_chip *gc = &mpc8xxx_gc->gc;
+ unsigned long flags;
+
+ switch (flow_type) {
+ case IRQ_TYPE_EDGE_FALLING:
+ case IRQ_TYPE_LEVEL_LOW:
+ raw_spin_lock_irqsave(&mpc8xxx_gc->lock, flags);
+ gc->write_reg(mpc8xxx_gc->regs + GPIO_ICR,
+ gc->read_reg(mpc8xxx_gc->regs + GPIO_ICR)
+ | mpc_pin2mask(irqd_to_hwirq(d)));
+ raw_spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags);
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ raw_spin_lock_irqsave(&mpc8xxx_gc->lock, flags);
+ gc->write_reg(mpc8xxx_gc->regs + GPIO_ICR,
+ gc->read_reg(mpc8xxx_gc->regs + GPIO_ICR)
+ & ~mpc_pin2mask(irqd_to_hwirq(d)));
+ raw_spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mpc512x_irq_set_type(struct irq_data *d, unsigned int flow_type)
+{
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d);
+ struct gpio_chip *gc = &mpc8xxx_gc->gc;
+ unsigned long gpio = irqd_to_hwirq(d);
+ void __iomem *reg;
+ unsigned int shift;
+ unsigned long flags;
+
+ if (gpio < 16) {
+ reg = mpc8xxx_gc->regs + GPIO_ICR;
+ shift = (15 - gpio) * 2;
+ } else {
+ reg = mpc8xxx_gc->regs + GPIO_ICR2;
+ shift = (15 - (gpio % 16)) * 2;
+ }
+
+ switch (flow_type) {
+ case IRQ_TYPE_EDGE_FALLING:
+ case IRQ_TYPE_LEVEL_LOW:
+ raw_spin_lock_irqsave(&mpc8xxx_gc->lock, flags);
+ gc->write_reg(reg, (gc->read_reg(reg) & ~(3 << shift))
+ | (2 << shift));
+ raw_spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags);
+ break;
+
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_LEVEL_HIGH:
+ raw_spin_lock_irqsave(&mpc8xxx_gc->lock, flags);
+ gc->write_reg(reg, (gc->read_reg(reg) & ~(3 << shift))
+ | (1 << shift));
+ raw_spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags);
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ raw_spin_lock_irqsave(&mpc8xxx_gc->lock, flags);
+ gc->write_reg(reg, (gc->read_reg(reg) & ~(3 << shift)));
+ raw_spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct irq_chip mpc8xxx_irq_chip = {
+ .name = "mpc8xxx-gpio",
+ .irq_unmask = mpc8xxx_irq_unmask,
+ .irq_mask = mpc8xxx_irq_mask,
+ .irq_ack = mpc8xxx_irq_ack,
+ /* this might get overwritten in mpc8xxx_probe() */
+ .irq_set_type = mpc8xxx_irq_set_type,
+};
+
+static int mpc8xxx_gpio_irq_map(struct irq_domain *h, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ irq_set_chip_data(irq, h->host_data);
+ irq_set_chip_and_handler(irq, &mpc8xxx_irq_chip, handle_edge_irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops mpc8xxx_gpio_irq_ops = {
+ .map = mpc8xxx_gpio_irq_map,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+struct mpc8xxx_gpio_devtype {
+ int (*gpio_dir_out)(struct gpio_chip *, unsigned int, int);
+ int (*gpio_get)(struct gpio_chip *, unsigned int);
+ int (*irq_set_type)(struct irq_data *, unsigned int);
+};
+
+static const struct mpc8xxx_gpio_devtype mpc512x_gpio_devtype = {
+ .gpio_dir_out = mpc5121_gpio_dir_out,
+ .irq_set_type = mpc512x_irq_set_type,
+};
+
+static const struct mpc8xxx_gpio_devtype mpc5125_gpio_devtype = {
+ .gpio_dir_out = mpc5125_gpio_dir_out,
+ .irq_set_type = mpc512x_irq_set_type,
+};
+
+static const struct mpc8xxx_gpio_devtype mpc8572_gpio_devtype = {
+ .gpio_get = mpc8572_gpio_get,
+};
+
+static const struct mpc8xxx_gpio_devtype mpc8xxx_gpio_devtype_default = {
+ .irq_set_type = mpc8xxx_irq_set_type,
+};
+
+static const struct of_device_id mpc8xxx_gpio_ids[] = {
+ { .compatible = "fsl,mpc8349-gpio", },
+ { .compatible = "fsl,mpc8572-gpio", .data = &mpc8572_gpio_devtype, },
+ { .compatible = "fsl,mpc8610-gpio", },
+ { .compatible = "fsl,mpc5121-gpio", .data = &mpc512x_gpio_devtype, },
+ { .compatible = "fsl,mpc5125-gpio", .data = &mpc5125_gpio_devtype, },
+ { .compatible = "fsl,pq3-gpio", },
+ { .compatible = "fsl,ls1028a-gpio", },
+ { .compatible = "fsl,ls1088a-gpio", },
+ { .compatible = "fsl,qoriq-gpio", },
+ {}
+};
+
+static int mpc8xxx_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc;
+ struct gpio_chip *gc;
+ const struct mpc8xxx_gpio_devtype *devtype = NULL;
+ struct fwnode_handle *fwnode;
+ int ret;
+
+ mpc8xxx_gc = devm_kzalloc(&pdev->dev, sizeof(*mpc8xxx_gc), GFP_KERNEL);
+ if (!mpc8xxx_gc)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, mpc8xxx_gc);
+
+ raw_spin_lock_init(&mpc8xxx_gc->lock);
+
+ mpc8xxx_gc->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mpc8xxx_gc->regs))
+ return PTR_ERR(mpc8xxx_gc->regs);
+
+ gc = &mpc8xxx_gc->gc;
+ gc->parent = &pdev->dev;
+
+ if (device_property_read_bool(&pdev->dev, "little-endian")) {
+ ret = bgpio_init(gc, &pdev->dev, 4,
+ mpc8xxx_gc->regs + GPIO_DAT,
+ NULL, NULL,
+ mpc8xxx_gc->regs + GPIO_DIR, NULL,
+ BGPIOF_BIG_ENDIAN);
+ if (ret)
+ return ret;
+ dev_dbg(&pdev->dev, "GPIO registers are LITTLE endian\n");
+ } else {
+ ret = bgpio_init(gc, &pdev->dev, 4,
+ mpc8xxx_gc->regs + GPIO_DAT,
+ NULL, NULL,
+ mpc8xxx_gc->regs + GPIO_DIR, NULL,
+ BGPIOF_BIG_ENDIAN
+ | BGPIOF_BIG_ENDIAN_BYTE_ORDER);
+ if (ret)
+ return ret;
+ dev_dbg(&pdev->dev, "GPIO registers are BIG endian\n");
+ }
+
+ mpc8xxx_gc->direction_output = gc->direction_output;
+
+ devtype = device_get_match_data(&pdev->dev);
+ if (!devtype)
+ devtype = &mpc8xxx_gpio_devtype_default;
+
+ /*
+ * It's assumed that only a single type of gpio controller is available
+ * on the current machine, so overwriting global data is fine.
+ */
+ if (devtype->irq_set_type)
+ mpc8xxx_irq_chip.irq_set_type = devtype->irq_set_type;
+
+ if (devtype->gpio_dir_out)
+ gc->direction_output = devtype->gpio_dir_out;
+ if (devtype->gpio_get)
+ gc->get = devtype->gpio_get;
+
+ gc->to_irq = mpc8xxx_gpio_to_irq;
+
+ /*
+ * The GPIO Input Buffer Enable register(GPIO_IBE) is used to control
+ * the input enable of each individual GPIO port. When an individual
+ * GPIO port’s direction is set to input (GPIO_GPDIR[DRn=0]), the
+ * associated input enable must be set (GPIOxGPIE[IEn]=1) to propagate
+ * the port value to the GPIO Data Register.
+ */
+ fwnode = dev_fwnode(&pdev->dev);
+ if (of_device_is_compatible(np, "fsl,qoriq-gpio") ||
+ of_device_is_compatible(np, "fsl,ls1028a-gpio") ||
+ of_device_is_compatible(np, "fsl,ls1088a-gpio") ||
+ is_acpi_node(fwnode)) {
+ gc->write_reg(mpc8xxx_gc->regs + GPIO_IBE, 0xffffffff);
+ /* Also, latch state of GPIOs configured as output by bootloader. */
+ gc->bgpio_data = gc->read_reg(mpc8xxx_gc->regs + GPIO_DAT) &
+ gc->read_reg(mpc8xxx_gc->regs + GPIO_DIR);
+ }
+
+ ret = devm_gpiochip_add_data(&pdev->dev, gc, mpc8xxx_gc);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "GPIO chip registration failed with status %d\n", ret);
+ return ret;
+ }
+
+ mpc8xxx_gc->irqn = platform_get_irq(pdev, 0);
+ if (mpc8xxx_gc->irqn < 0)
+ return mpc8xxx_gc->irqn;
+
+ mpc8xxx_gc->irq = irq_domain_create_linear(fwnode,
+ MPC8XXX_GPIO_PINS,
+ &mpc8xxx_gpio_irq_ops,
+ mpc8xxx_gc);
+
+ if (!mpc8xxx_gc->irq)
+ return 0;
+
+ /* ack and mask all irqs */
+ gc->write_reg(mpc8xxx_gc->regs + GPIO_IER, 0xffffffff);
+ gc->write_reg(mpc8xxx_gc->regs + GPIO_IMR, 0);
+
+ ret = devm_request_irq(&pdev->dev, mpc8xxx_gc->irqn,
+ mpc8xxx_gpio_irq_cascade,
+ IRQF_NO_THREAD | IRQF_SHARED, "gpio-cascade",
+ mpc8xxx_gc);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to devm_request_irq(%d), ret = %d\n",
+ mpc8xxx_gc->irqn, ret);
+ goto err;
+ }
+
+ return 0;
+err:
+ irq_domain_remove(mpc8xxx_gc->irq);
+ return ret;
+}
+
+static int mpc8xxx_remove(struct platform_device *pdev)
+{
+ struct mpc8xxx_gpio_chip *mpc8xxx_gc = platform_get_drvdata(pdev);
+
+ if (mpc8xxx_gc->irq) {
+ irq_set_chained_handler_and_data(mpc8xxx_gc->irqn, NULL, NULL);
+ irq_domain_remove(mpc8xxx_gc->irq);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id gpio_acpi_ids[] = {
+ {"NXP0031",},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, gpio_acpi_ids);
+#endif
+
+static struct platform_driver mpc8xxx_plat_driver = {
+ .probe = mpc8xxx_probe,
+ .remove = mpc8xxx_remove,
+ .driver = {
+ .name = "gpio-mpc8xxx",
+ .of_match_table = mpc8xxx_gpio_ids,
+ .acpi_match_table = ACPI_PTR(gpio_acpi_ids),
+ },
+};
+
+static int __init mpc8xxx_init(void)
+{
+ return platform_driver_register(&mpc8xxx_plat_driver);
+}
+
+arch_initcall(mpc8xxx_init);
diff --git a/drivers/gpio/gpio-msc313.c b/drivers/gpio/gpio-msc313.c
new file mode 100644
index 0000000000..2f448eb23a
--- /dev/null
+++ b/drivers/gpio/gpio-msc313.c
@@ -0,0 +1,726 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2020 Daniel Palmer<daniel@thingy.jp> */
+
+#include <linux/bitops.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <dt-bindings/gpio/msc313-gpio.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+#define DRIVER_NAME "gpio-msc313"
+
+#define MSC313_GPIO_IN BIT(0)
+#define MSC313_GPIO_OUT BIT(4)
+#define MSC313_GPIO_OEN BIT(5)
+
+/*
+ * These bits need to be saved to correctly restore the
+ * gpio state when resuming from suspend to memory.
+ */
+#define MSC313_GPIO_BITSTOSAVE (MSC313_GPIO_OUT | MSC313_GPIO_OEN)
+
+/* pad names for fuart, same for all SoCs so far */
+#define MSC313_PINNAME_FUART_RX "fuart_rx"
+#define MSC313_PINNAME_FUART_TX "fuart_tx"
+#define MSC313_PINNAME_FUART_CTS "fuart_cts"
+#define MSC313_PINNAME_FUART_RTS "fuart_rts"
+
+/* pad names for sr, mercury5 is different */
+#define MSC313_PINNAME_SR_IO2 "sr_io2"
+#define MSC313_PINNAME_SR_IO3 "sr_io3"
+#define MSC313_PINNAME_SR_IO4 "sr_io4"
+#define MSC313_PINNAME_SR_IO5 "sr_io5"
+#define MSC313_PINNAME_SR_IO6 "sr_io6"
+#define MSC313_PINNAME_SR_IO7 "sr_io7"
+#define MSC313_PINNAME_SR_IO8 "sr_io8"
+#define MSC313_PINNAME_SR_IO9 "sr_io9"
+#define MSC313_PINNAME_SR_IO10 "sr_io10"
+#define MSC313_PINNAME_SR_IO11 "sr_io11"
+#define MSC313_PINNAME_SR_IO12 "sr_io12"
+#define MSC313_PINNAME_SR_IO13 "sr_io13"
+#define MSC313_PINNAME_SR_IO14 "sr_io14"
+#define MSC313_PINNAME_SR_IO15 "sr_io15"
+#define MSC313_PINNAME_SR_IO16 "sr_io16"
+#define MSC313_PINNAME_SR_IO17 "sr_io17"
+
+/* pad names for sd, same for all SoCs so far */
+#define MSC313_PINNAME_SD_CLK "sd_clk"
+#define MSC313_PINNAME_SD_CMD "sd_cmd"
+#define MSC313_PINNAME_SD_D0 "sd_d0"
+#define MSC313_PINNAME_SD_D1 "sd_d1"
+#define MSC313_PINNAME_SD_D2 "sd_d2"
+#define MSC313_PINNAME_SD_D3 "sd_d3"
+
+/* pad names for i2c1, same for all SoCs so for */
+#define MSC313_PINNAME_I2C1_SCL "i2c1_scl"
+#define MSC313_PINNAME_I2C1_SCA "i2c1_sda"
+
+/* pad names for spi0, same for all SoCs so far */
+#define MSC313_PINNAME_SPI0_CZ "spi0_cz"
+#define MSC313_PINNAME_SPI0_CK "spi0_ck"
+#define MSC313_PINNAME_SPI0_DI "spi0_di"
+#define MSC313_PINNAME_SPI0_DO "spi0_do"
+
+#define FUART_NAMES \
+ MSC313_PINNAME_FUART_RX, \
+ MSC313_PINNAME_FUART_TX, \
+ MSC313_PINNAME_FUART_CTS, \
+ MSC313_PINNAME_FUART_RTS
+
+#define OFF_FUART_RX 0x50
+#define OFF_FUART_TX 0x54
+#define OFF_FUART_CTS 0x58
+#define OFF_FUART_RTS 0x5c
+
+#define FUART_OFFSETS \
+ OFF_FUART_RX, \
+ OFF_FUART_TX, \
+ OFF_FUART_CTS, \
+ OFF_FUART_RTS
+
+#define SR_NAMES \
+ MSC313_PINNAME_SR_IO2, \
+ MSC313_PINNAME_SR_IO3, \
+ MSC313_PINNAME_SR_IO4, \
+ MSC313_PINNAME_SR_IO5, \
+ MSC313_PINNAME_SR_IO6, \
+ MSC313_PINNAME_SR_IO7, \
+ MSC313_PINNAME_SR_IO8, \
+ MSC313_PINNAME_SR_IO9, \
+ MSC313_PINNAME_SR_IO10, \
+ MSC313_PINNAME_SR_IO11, \
+ MSC313_PINNAME_SR_IO12, \
+ MSC313_PINNAME_SR_IO13, \
+ MSC313_PINNAME_SR_IO14, \
+ MSC313_PINNAME_SR_IO15, \
+ MSC313_PINNAME_SR_IO16, \
+ MSC313_PINNAME_SR_IO17
+
+#define OFF_SR_IO2 0x88
+#define OFF_SR_IO3 0x8c
+#define OFF_SR_IO4 0x90
+#define OFF_SR_IO5 0x94
+#define OFF_SR_IO6 0x98
+#define OFF_SR_IO7 0x9c
+#define OFF_SR_IO8 0xa0
+#define OFF_SR_IO9 0xa4
+#define OFF_SR_IO10 0xa8
+#define OFF_SR_IO11 0xac
+#define OFF_SR_IO12 0xb0
+#define OFF_SR_IO13 0xb4
+#define OFF_SR_IO14 0xb8
+#define OFF_SR_IO15 0xbc
+#define OFF_SR_IO16 0xc0
+#define OFF_SR_IO17 0xc4
+
+#define SR_OFFSETS \
+ OFF_SR_IO2, \
+ OFF_SR_IO3, \
+ OFF_SR_IO4, \
+ OFF_SR_IO5, \
+ OFF_SR_IO6, \
+ OFF_SR_IO7, \
+ OFF_SR_IO8, \
+ OFF_SR_IO9, \
+ OFF_SR_IO10, \
+ OFF_SR_IO11, \
+ OFF_SR_IO12, \
+ OFF_SR_IO13, \
+ OFF_SR_IO14, \
+ OFF_SR_IO15, \
+ OFF_SR_IO16, \
+ OFF_SR_IO17
+
+#define SD_NAMES \
+ MSC313_PINNAME_SD_CLK, \
+ MSC313_PINNAME_SD_CMD, \
+ MSC313_PINNAME_SD_D0, \
+ MSC313_PINNAME_SD_D1, \
+ MSC313_PINNAME_SD_D2, \
+ MSC313_PINNAME_SD_D3
+
+#define OFF_SD_CLK 0x140
+#define OFF_SD_CMD 0x144
+#define OFF_SD_D0 0x148
+#define OFF_SD_D1 0x14c
+#define OFF_SD_D2 0x150
+#define OFF_SD_D3 0x154
+
+#define SD_OFFSETS \
+ OFF_SD_CLK, \
+ OFF_SD_CMD, \
+ OFF_SD_D0, \
+ OFF_SD_D1, \
+ OFF_SD_D2, \
+ OFF_SD_D3
+
+#define I2C1_NAMES \
+ MSC313_PINNAME_I2C1_SCL, \
+ MSC313_PINNAME_I2C1_SCA
+
+#define OFF_I2C1_SCL 0x188
+#define OFF_I2C1_SCA 0x18c
+
+#define I2C1_OFFSETS \
+ OFF_I2C1_SCL, \
+ OFF_I2C1_SCA
+
+#define SPI0_NAMES \
+ MSC313_PINNAME_SPI0_CZ, \
+ MSC313_PINNAME_SPI0_CK, \
+ MSC313_PINNAME_SPI0_DI, \
+ MSC313_PINNAME_SPI0_DO
+
+#define OFF_SPI0_CZ 0x1c0
+#define OFF_SPI0_CK 0x1c4
+#define OFF_SPI0_DI 0x1c8
+#define OFF_SPI0_DO 0x1cc
+
+#define SPI0_OFFSETS \
+ OFF_SPI0_CZ, \
+ OFF_SPI0_CK, \
+ OFF_SPI0_DI, \
+ OFF_SPI0_DO
+
+struct msc313_gpio_data {
+ const char * const *names;
+ const unsigned int *offsets;
+ const unsigned int num;
+};
+
+#define MSC313_GPIO_CHIPDATA(_chip) \
+static const struct msc313_gpio_data _chip##_data = { \
+ .names = _chip##_names, \
+ .offsets = _chip##_offsets, \
+ .num = ARRAY_SIZE(_chip##_offsets), \
+}
+
+#ifdef CONFIG_MACH_INFINITY
+static const char * const msc313_names[] = {
+ FUART_NAMES,
+ SR_NAMES,
+ SD_NAMES,
+ I2C1_NAMES,
+ SPI0_NAMES,
+};
+
+static const unsigned int msc313_offsets[] = {
+ FUART_OFFSETS,
+ SR_OFFSETS,
+ SD_OFFSETS,
+ I2C1_OFFSETS,
+ SPI0_OFFSETS,
+};
+
+MSC313_GPIO_CHIPDATA(msc313);
+
+/*
+ * Unlike the msc313(e) the ssd20xd have a bunch of pins
+ * that are actually called gpio probably because they
+ * have no dedicated function.
+ */
+#define SSD20XD_PINNAME_GPIO0 "gpio0"
+#define SSD20XD_PINNAME_GPIO1 "gpio1"
+#define SSD20XD_PINNAME_GPIO2 "gpio2"
+#define SSD20XD_PINNAME_GPIO3 "gpio3"
+#define SSD20XD_PINNAME_GPIO4 "gpio4"
+#define SSD20XD_PINNAME_GPIO5 "gpio5"
+#define SSD20XD_PINNAME_GPIO6 "gpio6"
+#define SSD20XD_PINNAME_GPIO7 "gpio7"
+#define SSD20XD_PINNAME_GPIO10 "gpio10"
+#define SSD20XD_PINNAME_GPIO11 "gpio11"
+#define SSD20XD_PINNAME_GPIO12 "gpio12"
+#define SSD20XD_PINNAME_GPIO13 "gpio13"
+#define SSD20XD_PINNAME_GPIO14 "gpio14"
+#define SSD20XD_PINNAME_GPIO85 "gpio85"
+#define SSD20XD_PINNAME_GPIO86 "gpio86"
+#define SSD20XD_PINNAME_GPIO90 "gpio90"
+
+#define SSD20XD_GPIO_NAMES SSD20XD_PINNAME_GPIO0, \
+ SSD20XD_PINNAME_GPIO1, \
+ SSD20XD_PINNAME_GPIO2, \
+ SSD20XD_PINNAME_GPIO3, \
+ SSD20XD_PINNAME_GPIO4, \
+ SSD20XD_PINNAME_GPIO5, \
+ SSD20XD_PINNAME_GPIO6, \
+ SSD20XD_PINNAME_GPIO7, \
+ SSD20XD_PINNAME_GPIO10, \
+ SSD20XD_PINNAME_GPIO11, \
+ SSD20XD_PINNAME_GPIO12, \
+ SSD20XD_PINNAME_GPIO13, \
+ SSD20XD_PINNAME_GPIO14, \
+ SSD20XD_PINNAME_GPIO85, \
+ SSD20XD_PINNAME_GPIO86, \
+ SSD20XD_PINNAME_GPIO90
+
+#define SSD20XD_GPIO_OFF_GPIO0 0x0
+#define SSD20XD_GPIO_OFF_GPIO1 0x4
+#define SSD20XD_GPIO_OFF_GPIO2 0x8
+#define SSD20XD_GPIO_OFF_GPIO3 0xc
+#define SSD20XD_GPIO_OFF_GPIO4 0x10
+#define SSD20XD_GPIO_OFF_GPIO5 0x14
+#define SSD20XD_GPIO_OFF_GPIO6 0x18
+#define SSD20XD_GPIO_OFF_GPIO7 0x1c
+#define SSD20XD_GPIO_OFF_GPIO10 0x28
+#define SSD20XD_GPIO_OFF_GPIO11 0x2c
+#define SSD20XD_GPIO_OFF_GPIO12 0x30
+#define SSD20XD_GPIO_OFF_GPIO13 0x34
+#define SSD20XD_GPIO_OFF_GPIO14 0x38
+#define SSD20XD_GPIO_OFF_GPIO85 0x100
+#define SSD20XD_GPIO_OFF_GPIO86 0x104
+#define SSD20XD_GPIO_OFF_GPIO90 0x114
+
+#define SSD20XD_GPIO_OFFSETS SSD20XD_GPIO_OFF_GPIO0, \
+ SSD20XD_GPIO_OFF_GPIO1, \
+ SSD20XD_GPIO_OFF_GPIO2, \
+ SSD20XD_GPIO_OFF_GPIO3, \
+ SSD20XD_GPIO_OFF_GPIO4, \
+ SSD20XD_GPIO_OFF_GPIO5, \
+ SSD20XD_GPIO_OFF_GPIO6, \
+ SSD20XD_GPIO_OFF_GPIO7, \
+ SSD20XD_GPIO_OFF_GPIO10, \
+ SSD20XD_GPIO_OFF_GPIO11, \
+ SSD20XD_GPIO_OFF_GPIO12, \
+ SSD20XD_GPIO_OFF_GPIO13, \
+ SSD20XD_GPIO_OFF_GPIO14, \
+ SSD20XD_GPIO_OFF_GPIO85, \
+ SSD20XD_GPIO_OFF_GPIO86, \
+ SSD20XD_GPIO_OFF_GPIO90
+
+/* "ttl" pins lcd interface pins */
+#define SSD20XD_PINNAME_TTL0 "ttl0"
+#define SSD20XD_PINNAME_TTL1 "ttl1"
+#define SSD20XD_PINNAME_TTL2 "ttl2"
+#define SSD20XD_PINNAME_TTL3 "ttl3"
+#define SSD20XD_PINNAME_TTL4 "ttl4"
+#define SSD20XD_PINNAME_TTL5 "ttl5"
+#define SSD20XD_PINNAME_TTL6 "ttl6"
+#define SSD20XD_PINNAME_TTL7 "ttl7"
+#define SSD20XD_PINNAME_TTL8 "ttl8"
+#define SSD20XD_PINNAME_TTL9 "ttl9"
+#define SSD20XD_PINNAME_TTL10 "ttl10"
+#define SSD20XD_PINNAME_TTL11 "ttl11"
+#define SSD20XD_PINNAME_TTL12 "ttl12"
+#define SSD20XD_PINNAME_TTL13 "ttl13"
+#define SSD20XD_PINNAME_TTL14 "ttl14"
+#define SSD20XD_PINNAME_TTL15 "ttl15"
+#define SSD20XD_PINNAME_TTL16 "ttl16"
+#define SSD20XD_PINNAME_TTL17 "ttl17"
+#define SSD20XD_PINNAME_TTL18 "ttl18"
+#define SSD20XD_PINNAME_TTL19 "ttl19"
+#define SSD20XD_PINNAME_TTL20 "ttl20"
+#define SSD20XD_PINNAME_TTL21 "ttl21"
+#define SSD20XD_PINNAME_TTL22 "ttl22"
+#define SSD20XD_PINNAME_TTL23 "ttl23"
+#define SSD20XD_PINNAME_TTL24 "ttl24"
+#define SSD20XD_PINNAME_TTL25 "ttl25"
+#define SSD20XD_PINNAME_TTL26 "ttl26"
+#define SSD20XD_PINNAME_TTL27 "ttl27"
+
+#define SSD20XD_TTL_PINNAMES SSD20XD_PINNAME_TTL0, \
+ SSD20XD_PINNAME_TTL1, \
+ SSD20XD_PINNAME_TTL2, \
+ SSD20XD_PINNAME_TTL3, \
+ SSD20XD_PINNAME_TTL4, \
+ SSD20XD_PINNAME_TTL5, \
+ SSD20XD_PINNAME_TTL6, \
+ SSD20XD_PINNAME_TTL7, \
+ SSD20XD_PINNAME_TTL8, \
+ SSD20XD_PINNAME_TTL9, \
+ SSD20XD_PINNAME_TTL10, \
+ SSD20XD_PINNAME_TTL11, \
+ SSD20XD_PINNAME_TTL12, \
+ SSD20XD_PINNAME_TTL13, \
+ SSD20XD_PINNAME_TTL14, \
+ SSD20XD_PINNAME_TTL15, \
+ SSD20XD_PINNAME_TTL16, \
+ SSD20XD_PINNAME_TTL17, \
+ SSD20XD_PINNAME_TTL18, \
+ SSD20XD_PINNAME_TTL19, \
+ SSD20XD_PINNAME_TTL20, \
+ SSD20XD_PINNAME_TTL21, \
+ SSD20XD_PINNAME_TTL22, \
+ SSD20XD_PINNAME_TTL23, \
+ SSD20XD_PINNAME_TTL24, \
+ SSD20XD_PINNAME_TTL25, \
+ SSD20XD_PINNAME_TTL26, \
+ SSD20XD_PINNAME_TTL27
+
+#define SSD20XD_TTL_OFFSET_TTL0 0x80
+#define SSD20XD_TTL_OFFSET_TTL1 0x84
+#define SSD20XD_TTL_OFFSET_TTL2 0x88
+#define SSD20XD_TTL_OFFSET_TTL3 0x8c
+#define SSD20XD_TTL_OFFSET_TTL4 0x90
+#define SSD20XD_TTL_OFFSET_TTL5 0x94
+#define SSD20XD_TTL_OFFSET_TTL6 0x98
+#define SSD20XD_TTL_OFFSET_TTL7 0x9c
+#define SSD20XD_TTL_OFFSET_TTL8 0xa0
+#define SSD20XD_TTL_OFFSET_TTL9 0xa4
+#define SSD20XD_TTL_OFFSET_TTL10 0xa8
+#define SSD20XD_TTL_OFFSET_TTL11 0xac
+#define SSD20XD_TTL_OFFSET_TTL12 0xb0
+#define SSD20XD_TTL_OFFSET_TTL13 0xb4
+#define SSD20XD_TTL_OFFSET_TTL14 0xb8
+#define SSD20XD_TTL_OFFSET_TTL15 0xbc
+#define SSD20XD_TTL_OFFSET_TTL16 0xc0
+#define SSD20XD_TTL_OFFSET_TTL17 0xc4
+#define SSD20XD_TTL_OFFSET_TTL18 0xc8
+#define SSD20XD_TTL_OFFSET_TTL19 0xcc
+#define SSD20XD_TTL_OFFSET_TTL20 0xd0
+#define SSD20XD_TTL_OFFSET_TTL21 0xd4
+#define SSD20XD_TTL_OFFSET_TTL22 0xd8
+#define SSD20XD_TTL_OFFSET_TTL23 0xdc
+#define SSD20XD_TTL_OFFSET_TTL24 0xe0
+#define SSD20XD_TTL_OFFSET_TTL25 0xe4
+#define SSD20XD_TTL_OFFSET_TTL26 0xe8
+#define SSD20XD_TTL_OFFSET_TTL27 0xec
+
+#define SSD20XD_TTL_OFFSETS SSD20XD_TTL_OFFSET_TTL0, \
+ SSD20XD_TTL_OFFSET_TTL1, \
+ SSD20XD_TTL_OFFSET_TTL2, \
+ SSD20XD_TTL_OFFSET_TTL3, \
+ SSD20XD_TTL_OFFSET_TTL4, \
+ SSD20XD_TTL_OFFSET_TTL5, \
+ SSD20XD_TTL_OFFSET_TTL6, \
+ SSD20XD_TTL_OFFSET_TTL7, \
+ SSD20XD_TTL_OFFSET_TTL8, \
+ SSD20XD_TTL_OFFSET_TTL9, \
+ SSD20XD_TTL_OFFSET_TTL10, \
+ SSD20XD_TTL_OFFSET_TTL11, \
+ SSD20XD_TTL_OFFSET_TTL12, \
+ SSD20XD_TTL_OFFSET_TTL13, \
+ SSD20XD_TTL_OFFSET_TTL14, \
+ SSD20XD_TTL_OFFSET_TTL15, \
+ SSD20XD_TTL_OFFSET_TTL16, \
+ SSD20XD_TTL_OFFSET_TTL17, \
+ SSD20XD_TTL_OFFSET_TTL18, \
+ SSD20XD_TTL_OFFSET_TTL19, \
+ SSD20XD_TTL_OFFSET_TTL20, \
+ SSD20XD_TTL_OFFSET_TTL21, \
+ SSD20XD_TTL_OFFSET_TTL22, \
+ SSD20XD_TTL_OFFSET_TTL23, \
+ SSD20XD_TTL_OFFSET_TTL24, \
+ SSD20XD_TTL_OFFSET_TTL25, \
+ SSD20XD_TTL_OFFSET_TTL26, \
+ SSD20XD_TTL_OFFSET_TTL27
+
+/* On the ssd20xd the two normal uarts have dedicated pins */
+#define SSD20XD_PINNAME_UART0_RX "uart0_rx"
+#define SSD20XD_PINNAME_UART0_TX "uart0_tx"
+
+#define SSD20XD_UART0_NAMES \
+ SSD20XD_PINNAME_UART0_RX, \
+ SSD20XD_PINNAME_UART0_TX
+
+#define SSD20XD_PINNAME_UART1_RX "uart1_rx"
+#define SSD20XD_PINNAME_UART1_TX "uart1_tx"
+
+#define SSD20XD_UART1_NAMES \
+ SSD20XD_PINNAME_UART1_RX, \
+ SSD20XD_PINNAME_UART1_TX
+
+#define SSD20XD_OFF_UART0_RX 0x60
+#define SSD20XD_OFF_UART0_TX 0x64
+
+#define SSD20XD_UART0_OFFSETS \
+ SSD20XD_OFF_UART0_RX, \
+ SSD20XD_OFF_UART0_TX
+
+#define SSD20XD_OFF_UART1_RX 0x68
+#define SSD20XD_OFF_UART1_TX 0x6c
+
+#define SSD20XD_UART1_OFFSETS \
+ SSD20XD_OFF_UART1_RX, \
+ SSD20XD_OFF_UART1_TX
+
+/*
+ * ssd20x has the same pin names but different ordering
+ * of the registers that control the gpio.
+ */
+#define SSD20XD_OFF_SD_D0 0x140
+#define SSD20XD_OFF_SD_D1 0x144
+#define SSD20XD_OFF_SD_D2 0x148
+#define SSD20XD_OFF_SD_D3 0x14c
+#define SSD20XD_OFF_SD_CMD 0x150
+#define SSD20XD_OFF_SD_CLK 0x154
+
+#define SSD20XD_SD_OFFSETS SSD20XD_OFF_SD_CLK, \
+ SSD20XD_OFF_SD_CMD, \
+ SSD20XD_OFF_SD_D0, \
+ SSD20XD_OFF_SD_D1, \
+ SSD20XD_OFF_SD_D2, \
+ SSD20XD_OFF_SD_D3
+
+static const char * const ssd20xd_names[] = {
+ FUART_NAMES,
+ SD_NAMES,
+ SSD20XD_UART0_NAMES,
+ SSD20XD_UART1_NAMES,
+ SSD20XD_TTL_PINNAMES,
+ SSD20XD_GPIO_NAMES,
+};
+
+static const unsigned int ssd20xd_offsets[] = {
+ FUART_OFFSETS,
+ SSD20XD_SD_OFFSETS,
+ SSD20XD_UART0_OFFSETS,
+ SSD20XD_UART1_OFFSETS,
+ SSD20XD_TTL_OFFSETS,
+ SSD20XD_GPIO_OFFSETS,
+};
+
+MSC313_GPIO_CHIPDATA(ssd20xd);
+#endif
+
+struct msc313_gpio {
+ void __iomem *base;
+ const struct msc313_gpio_data *gpio_data;
+ u8 *saved;
+};
+
+static void msc313_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct msc313_gpio *gpio = gpiochip_get_data(chip);
+ u8 gpioreg = readb_relaxed(gpio->base + gpio->gpio_data->offsets[offset]);
+
+ if (value)
+ gpioreg |= MSC313_GPIO_OUT;
+ else
+ gpioreg &= ~MSC313_GPIO_OUT;
+
+ writeb_relaxed(gpioreg, gpio->base + gpio->gpio_data->offsets[offset]);
+}
+
+static int msc313_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct msc313_gpio *gpio = gpiochip_get_data(chip);
+
+ return readb_relaxed(gpio->base + gpio->gpio_data->offsets[offset]) & MSC313_GPIO_IN;
+}
+
+static int msc313_gpio_direction_input(struct gpio_chip *chip, unsigned int offset)
+{
+ struct msc313_gpio *gpio = gpiochip_get_data(chip);
+ u8 gpioreg = readb_relaxed(gpio->base + gpio->gpio_data->offsets[offset]);
+
+ gpioreg |= MSC313_GPIO_OEN;
+ writeb_relaxed(gpioreg, gpio->base + gpio->gpio_data->offsets[offset]);
+
+ return 0;
+}
+
+static int msc313_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct msc313_gpio *gpio = gpiochip_get_data(chip);
+ u8 gpioreg = readb_relaxed(gpio->base + gpio->gpio_data->offsets[offset]);
+
+ gpioreg &= ~MSC313_GPIO_OEN;
+ if (value)
+ gpioreg |= MSC313_GPIO_OUT;
+ else
+ gpioreg &= ~MSC313_GPIO_OUT;
+ writeb_relaxed(gpioreg, gpio->base + gpio->gpio_data->offsets[offset]);
+
+ return 0;
+}
+
+static void msc313_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+
+ irq_chip_mask_parent(d);
+ gpiochip_disable_irq(gc, d->hwirq);
+}
+
+static void msc313_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+
+ gpiochip_enable_irq(gc, d->hwirq);
+ irq_chip_unmask_parent(d);
+}
+
+/*
+ * The interrupt handling happens in the parent interrupt controller,
+ * we don't do anything here.
+ */
+static const struct irq_chip msc313_gpio_irqchip = {
+ .name = "GPIO",
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_mask = msc313_gpio_irq_mask,
+ .irq_unmask = msc313_gpio_irq_unmask,
+ .irq_set_type = irq_chip_set_type_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+/*
+ * The parent interrupt controller needs the GIC interrupt type set to GIC_SPI
+ * so we need to provide the fwspec. Essentially gpiochip_populate_parent_fwspec_twocell
+ * that puts GIC_SPI into the first cell.
+ */
+static int msc313_gpio_populate_parent_fwspec(struct gpio_chip *gc,
+ union gpio_irq_fwspec *gfwspec,
+ unsigned int parent_hwirq,
+ unsigned int parent_type)
+{
+ struct irq_fwspec *fwspec = &gfwspec->fwspec;
+
+ fwspec->fwnode = gc->irq.parent_domain->fwnode;
+ fwspec->param_count = 3;
+ fwspec->param[0] = GIC_SPI;
+ fwspec->param[1] = parent_hwirq;
+ fwspec->param[2] = parent_type;
+
+ return 0;
+}
+
+static int msc313e_gpio_child_to_parent_hwirq(struct gpio_chip *chip,
+ unsigned int child,
+ unsigned int child_type,
+ unsigned int *parent,
+ unsigned int *parent_type)
+{
+ struct msc313_gpio *priv = gpiochip_get_data(chip);
+ unsigned int offset = priv->gpio_data->offsets[child];
+
+ /*
+ * only the spi0 pins have interrupts on the parent
+ * on all of the known chips and so far they are all
+ * mapped to the same place
+ */
+ if (offset >= OFF_SPI0_CZ && offset <= OFF_SPI0_DO) {
+ *parent_type = child_type;
+ *parent = ((offset - OFF_SPI0_CZ) >> 2) + 28;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int msc313_gpio_probe(struct platform_device *pdev)
+{
+ const struct msc313_gpio_data *match_data;
+ struct msc313_gpio *gpio;
+ struct gpio_chip *gpiochip;
+ struct gpio_irq_chip *gpioirqchip;
+ struct irq_domain *parent_domain;
+ struct device_node *parent_node;
+ struct device *dev = &pdev->dev;
+
+ match_data = of_device_get_match_data(dev);
+ if (!match_data)
+ return -EINVAL;
+
+ parent_node = of_irq_find_parent(dev->of_node);
+ if (!parent_node)
+ return -ENODEV;
+
+ parent_domain = irq_find_host(parent_node);
+ if (!parent_domain)
+ return -ENODEV;
+
+ gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->gpio_data = match_data;
+
+ gpio->saved = devm_kcalloc(dev, gpio->gpio_data->num, sizeof(*gpio->saved), GFP_KERNEL);
+ if (!gpio->saved)
+ return -ENOMEM;
+
+ gpio->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gpio->base))
+ return PTR_ERR(gpio->base);
+
+ platform_set_drvdata(pdev, gpio);
+
+ gpiochip = devm_kzalloc(dev, sizeof(*gpiochip), GFP_KERNEL);
+ if (!gpiochip)
+ return -ENOMEM;
+
+ gpiochip->label = DRIVER_NAME;
+ gpiochip->parent = dev;
+ gpiochip->request = gpiochip_generic_request;
+ gpiochip->free = gpiochip_generic_free;
+ gpiochip->direction_input = msc313_gpio_direction_input;
+ gpiochip->direction_output = msc313_gpio_direction_output;
+ gpiochip->get = msc313_gpio_get;
+ gpiochip->set = msc313_gpio_set;
+ gpiochip->base = -1;
+ gpiochip->ngpio = gpio->gpio_data->num;
+ gpiochip->names = gpio->gpio_data->names;
+
+ gpioirqchip = &gpiochip->irq;
+ gpio_irq_chip_set_chip(gpioirqchip, &msc313_gpio_irqchip);
+ gpioirqchip->fwnode = of_node_to_fwnode(dev->of_node);
+ gpioirqchip->parent_domain = parent_domain;
+ gpioirqchip->child_to_parent_hwirq = msc313e_gpio_child_to_parent_hwirq;
+ gpioirqchip->populate_parent_alloc_arg = msc313_gpio_populate_parent_fwspec;
+ gpioirqchip->handler = handle_bad_irq;
+ gpioirqchip->default_type = IRQ_TYPE_NONE;
+
+ return devm_gpiochip_add_data(dev, gpiochip, gpio);
+}
+
+static const struct of_device_id msc313_gpio_of_match[] = {
+#ifdef CONFIG_MACH_INFINITY
+ {
+ .compatible = "mstar,msc313-gpio",
+ .data = &msc313_data,
+ },
+ {
+ .compatible = "sstar,ssd20xd-gpio",
+ .data = &ssd20xd_data,
+ },
+#endif
+ { }
+};
+
+/*
+ * The GPIO controller loses the state of the registers when the
+ * SoC goes into suspend to memory mode so we need to save some
+ * of the register bits before suspending and put it back when resuming
+ */
+static int __maybe_unused msc313_gpio_suspend(struct device *dev)
+{
+ struct msc313_gpio *gpio = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < gpio->gpio_data->num; i++)
+ gpio->saved[i] = readb_relaxed(gpio->base + gpio->gpio_data->offsets[i]) & MSC313_GPIO_BITSTOSAVE;
+
+ return 0;
+}
+
+static int __maybe_unused msc313_gpio_resume(struct device *dev)
+{
+ struct msc313_gpio *gpio = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < gpio->gpio_data->num; i++)
+ writeb_relaxed(gpio->saved[i], gpio->base + gpio->gpio_data->offsets[i]);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(msc313_gpio_ops, msc313_gpio_suspend, msc313_gpio_resume);
+
+static struct platform_driver msc313_gpio_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = msc313_gpio_of_match,
+ .pm = &msc313_gpio_ops,
+ },
+ .probe = msc313_gpio_probe,
+};
+builtin_platform_driver(msc313_gpio_driver);
diff --git a/drivers/gpio/gpio-mt7621.c b/drivers/gpio/gpio-mt7621.c
new file mode 100644
index 0000000000..93facbebb8
--- /dev/null
+++ b/drivers/gpio/gpio-mt7621.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2009-2011 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2013 John Crispin <blogic@openwrt.org>
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define MTK_BANK_CNT 3
+#define MTK_BANK_WIDTH 32
+
+#define GPIO_BANK_STRIDE 0x04
+#define GPIO_REG_CTRL 0x00
+#define GPIO_REG_POL 0x10
+#define GPIO_REG_DATA 0x20
+#define GPIO_REG_DSET 0x30
+#define GPIO_REG_DCLR 0x40
+#define GPIO_REG_REDGE 0x50
+#define GPIO_REG_FEDGE 0x60
+#define GPIO_REG_HLVL 0x70
+#define GPIO_REG_LLVL 0x80
+#define GPIO_REG_STAT 0x90
+#define GPIO_REG_EDGE 0xA0
+
+struct mtk_gc {
+ struct irq_chip irq_chip;
+ struct gpio_chip chip;
+ spinlock_t lock;
+ int bank;
+ u32 rising;
+ u32 falling;
+ u32 hlevel;
+ u32 llevel;
+};
+
+/**
+ * struct mtk - state container for
+ * data of the platform driver. It is 3
+ * separate gpio-chip each one with its
+ * own irq_chip.
+ * @dev: device instance
+ * @base: memory base address
+ * @gpio_irq: irq number from the device tree
+ * @gc_map: array of the gpio chips
+ */
+struct mtk {
+ struct device *dev;
+ void __iomem *base;
+ int gpio_irq;
+ struct mtk_gc gc_map[MTK_BANK_CNT];
+};
+
+static inline struct mtk_gc *
+to_mediatek_gpio(struct gpio_chip *chip)
+{
+ return container_of(chip, struct mtk_gc, chip);
+}
+
+static inline void
+mtk_gpio_w32(struct mtk_gc *rg, u32 offset, u32 val)
+{
+ struct gpio_chip *gc = &rg->chip;
+ struct mtk *mtk = gpiochip_get_data(gc);
+
+ offset = (rg->bank * GPIO_BANK_STRIDE) + offset;
+ gc->write_reg(mtk->base + offset, val);
+}
+
+static inline u32
+mtk_gpio_r32(struct mtk_gc *rg, u32 offset)
+{
+ struct gpio_chip *gc = &rg->chip;
+ struct mtk *mtk = gpiochip_get_data(gc);
+
+ offset = (rg->bank * GPIO_BANK_STRIDE) + offset;
+ return gc->read_reg(mtk->base + offset);
+}
+
+static irqreturn_t
+mediatek_gpio_irq_handler(int irq, void *data)
+{
+ struct gpio_chip *gc = data;
+ struct mtk_gc *rg = to_mediatek_gpio(gc);
+ irqreturn_t ret = IRQ_NONE;
+ unsigned long pending;
+ int bit;
+
+ pending = mtk_gpio_r32(rg, GPIO_REG_STAT);
+
+ for_each_set_bit(bit, &pending, MTK_BANK_WIDTH) {
+ generic_handle_domain_irq(gc->irq.domain, bit);
+ mtk_gpio_w32(rg, GPIO_REG_STAT, BIT(bit));
+ ret |= IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+static void
+mediatek_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mtk_gc *rg = to_mediatek_gpio(gc);
+ int pin = d->hwirq;
+ unsigned long flags;
+ u32 rise, fall, high, low;
+
+ gpiochip_enable_irq(gc, d->hwirq);
+
+ spin_lock_irqsave(&rg->lock, flags);
+ rise = mtk_gpio_r32(rg, GPIO_REG_REDGE);
+ fall = mtk_gpio_r32(rg, GPIO_REG_FEDGE);
+ high = mtk_gpio_r32(rg, GPIO_REG_HLVL);
+ low = mtk_gpio_r32(rg, GPIO_REG_LLVL);
+ mtk_gpio_w32(rg, GPIO_REG_REDGE, rise | (BIT(pin) & rg->rising));
+ mtk_gpio_w32(rg, GPIO_REG_FEDGE, fall | (BIT(pin) & rg->falling));
+ mtk_gpio_w32(rg, GPIO_REG_HLVL, high | (BIT(pin) & rg->hlevel));
+ mtk_gpio_w32(rg, GPIO_REG_LLVL, low | (BIT(pin) & rg->llevel));
+ spin_unlock_irqrestore(&rg->lock, flags);
+}
+
+static void
+mediatek_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mtk_gc *rg = to_mediatek_gpio(gc);
+ int pin = d->hwirq;
+ unsigned long flags;
+ u32 rise, fall, high, low;
+
+ spin_lock_irqsave(&rg->lock, flags);
+ rise = mtk_gpio_r32(rg, GPIO_REG_REDGE);
+ fall = mtk_gpio_r32(rg, GPIO_REG_FEDGE);
+ high = mtk_gpio_r32(rg, GPIO_REG_HLVL);
+ low = mtk_gpio_r32(rg, GPIO_REG_LLVL);
+ mtk_gpio_w32(rg, GPIO_REG_FEDGE, fall & ~BIT(pin));
+ mtk_gpio_w32(rg, GPIO_REG_REDGE, rise & ~BIT(pin));
+ mtk_gpio_w32(rg, GPIO_REG_HLVL, high & ~BIT(pin));
+ mtk_gpio_w32(rg, GPIO_REG_LLVL, low & ~BIT(pin));
+ spin_unlock_irqrestore(&rg->lock, flags);
+
+ gpiochip_disable_irq(gc, d->hwirq);
+}
+
+static int
+mediatek_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 = to_mediatek_gpio(gc);
+ int pin = d->hwirq;
+ u32 mask = BIT(pin);
+
+ if (type == IRQ_TYPE_PROBE) {
+ if ((rg->rising | rg->falling |
+ rg->hlevel | rg->llevel) & mask)
+ return 0;
+
+ type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
+ }
+
+ rg->rising &= ~mask;
+ rg->falling &= ~mask;
+ rg->hlevel &= ~mask;
+ rg->llevel &= ~mask;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_BOTH:
+ rg->rising |= mask;
+ rg->falling |= mask;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ rg->rising |= mask;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ rg->falling |= mask;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ rg->hlevel |= mask;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ rg->llevel |= mask;
+ break;
+ }
+
+ return 0;
+}
+
+static int
+mediatek_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);
+
+ if (rg->bank != gpio / MTK_BANK_WIDTH)
+ return -EINVAL;
+
+ if (flags)
+ *flags = spec->args[1];
+
+ return gpio % MTK_BANK_WIDTH;
+}
+
+static const struct irq_chip mt7621_irq_chip = {
+ .name = "mt7621-gpio",
+ .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,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int
+mediatek_gpio_bank_probe(struct device *dev, int bank)
+{
+ struct mtk *mtk = dev_get_drvdata(dev);
+ struct mtk_gc *rg;
+ void __iomem *dat, *set, *ctrl, *diro;
+ int ret;
+
+ rg = &mtk->gc_map[bank];
+ memset(rg, 0, sizeof(*rg));
+
+ spin_lock_init(&rg->lock);
+ rg->bank = bank;
+
+ dat = mtk->base + GPIO_REG_DATA + (rg->bank * GPIO_BANK_STRIDE);
+ set = mtk->base + GPIO_REG_DSET + (rg->bank * GPIO_BANK_STRIDE);
+ ctrl = mtk->base + GPIO_REG_DCLR + (rg->bank * GPIO_BANK_STRIDE);
+ diro = mtk->base + GPIO_REG_CTRL + (rg->bank * GPIO_BANK_STRIDE);
+
+ ret = bgpio_init(&rg->chip, dev, 4, dat, set, ctrl, diro, NULL,
+ BGPIOF_NO_SET_ON_INPUT);
+ if (ret) {
+ dev_err(dev, "bgpio_init() failed\n");
+ return ret;
+ }
+
+ rg->chip.of_gpio_n_cells = 2;
+ rg->chip.of_xlate = mediatek_gpio_xlate;
+ rg->chip.label = devm_kasprintf(dev, GFP_KERNEL, "%s-bank%d",
+ dev_name(dev), bank);
+ if (!rg->chip.label)
+ return -ENOMEM;
+
+ rg->chip.offset = bank * MTK_BANK_WIDTH;
+
+ if (mtk->gpio_irq) {
+ struct gpio_irq_chip *girq;
+
+ /*
+ * Directly request the irq here instead of passing
+ * a flow-handler because the irq is shared.
+ */
+ ret = devm_request_irq(dev, mtk->gpio_irq,
+ mediatek_gpio_irq_handler, IRQF_SHARED,
+ rg->chip.label, &rg->chip);
+
+ if (ret) {
+ dev_err(dev, "Error requesting IRQ %d: %d\n",
+ mtk->gpio_irq, ret);
+ return ret;
+ }
+
+ girq = &rg->chip.irq;
+ gpio_irq_chip_set_chip(girq, &mt7621_irq_chip);
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ }
+
+ ret = devm_gpiochip_add_data(dev, &rg->chip, mtk);
+ if (ret < 0) {
+ dev_err(dev, "Could not register gpio %d, ret=%d\n",
+ rg->chip.ngpio, ret);
+ return ret;
+ }
+
+ /* set polarity to low for all gpios */
+ mtk_gpio_w32(rg, GPIO_REG_POL, 0);
+
+ dev_info(dev, "registering %d gpios\n", rg->chip.ngpio);
+
+ return 0;
+}
+
+static int
+mediatek_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mtk *mtk;
+ int i;
+ int ret;
+
+ mtk = devm_kzalloc(dev, sizeof(*mtk), GFP_KERNEL);
+ if (!mtk)
+ return -ENOMEM;
+
+ mtk->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mtk->base))
+ return PTR_ERR(mtk->base);
+
+ mtk->gpio_irq = platform_get_irq(pdev, 0);
+ if (mtk->gpio_irq < 0)
+ return mtk->gpio_irq;
+
+ mtk->dev = dev;
+ platform_set_drvdata(pdev, mtk);
+
+ for (i = 0; i < MTK_BANK_CNT; i++) {
+ ret = mediatek_gpio_bank_probe(dev, i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id mediatek_gpio_match[] = {
+ { .compatible = "mediatek,mt7621-gpio" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mediatek_gpio_match);
+
+static struct platform_driver mediatek_gpio_driver = {
+ .probe = mediatek_gpio_probe,
+ .driver = {
+ .name = "mt7621_gpio",
+ .of_match_table = mediatek_gpio_match,
+ },
+};
+
+builtin_platform_driver(mediatek_gpio_driver);
diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c
new file mode 100644
index 0000000000..67497116ce
--- /dev/null
+++ b/drivers/gpio/gpio-mvebu.c
@@ -0,0 +1,1318 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for Marvell SoCs
+ *
+ * Copyright (C) 2012 Marvell
+ *
+ * Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+ * Andrew Lunn <andrew@lunn.ch>
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ *
+ * This driver is a fairly straightforward GPIO driver for the
+ * complete family of Marvell EBU SoC platforms (Orion, Dove,
+ * Kirkwood, Discovery, Armada 370/XP). The only complexity of this
+ * driver is the different register layout that exists between the
+ * non-SMP platforms (Orion, Dove, Kirkwood, Armada 370) and the SMP
+ * platforms (MV78200 from the Discovery family and the Armada
+ * XP). Therefore, this driver handles three variants of the GPIO
+ * block:
+ * - the basic variant, called "orion-gpio", with the simplest
+ * register set. Used on Orion, Dove, Kirkwoord, Armada 370 and
+ * non-SMP Discovery systems
+ * - the mv78200 variant for MV78200 Discovery systems. This variant
+ * turns the edge mask and level mask registers into CPU0 edge
+ * mask/level mask registers, and adds CPU1 edge mask/level mask
+ * registers.
+ * - the armadaxp variant for Armada XP systems. This variant keeps
+ * the normal cause/edge mask/level mask registers when the global
+ * interrupts are used, but adds per-CPU cause/edge mask/level mask
+ * registers n a separate memory area for the per-CPU GPIO
+ * interrupts.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/machine.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_device.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+/*
+ * GPIO unit register offsets.
+ */
+#define GPIO_OUT_OFF 0x0000
+#define GPIO_IO_CONF_OFF 0x0004
+#define GPIO_BLINK_EN_OFF 0x0008
+#define GPIO_IN_POL_OFF 0x000c
+#define GPIO_DATA_IN_OFF 0x0010
+#define GPIO_EDGE_CAUSE_OFF 0x0014
+#define GPIO_EDGE_MASK_OFF 0x0018
+#define GPIO_LEVEL_MASK_OFF 0x001c
+#define GPIO_BLINK_CNT_SELECT_OFF 0x0020
+
+/*
+ * PWM register offsets.
+ */
+#define PWM_BLINK_ON_DURATION_OFF 0x0
+#define PWM_BLINK_OFF_DURATION_OFF 0x4
+#define PWM_BLINK_COUNTER_B_OFF 0x8
+
+/* Armada 8k variant gpios register offsets */
+#define AP80X_GPIO0_OFF_A8K 0x1040
+#define CP11X_GPIO0_OFF_A8K 0x100
+#define CP11X_GPIO1_OFF_A8K 0x140
+
+/* The MV78200 has per-CPU registers for edge mask and level mask */
+#define GPIO_EDGE_MASK_MV78200_OFF(cpu) ((cpu) ? 0x30 : 0x18)
+#define GPIO_LEVEL_MASK_MV78200_OFF(cpu) ((cpu) ? 0x34 : 0x1C)
+
+/*
+ * The Armada XP has per-CPU registers for interrupt cause, interrupt
+ * mask and interrupt level mask. Those are in percpu_regs range.
+ */
+#define GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu) ((cpu) * 0x4)
+#define GPIO_EDGE_MASK_ARMADAXP_OFF(cpu) (0x10 + (cpu) * 0x4)
+#define GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu) (0x20 + (cpu) * 0x4)
+
+#define MVEBU_GPIO_SOC_VARIANT_ORION 0x1
+#define MVEBU_GPIO_SOC_VARIANT_MV78200 0x2
+#define MVEBU_GPIO_SOC_VARIANT_ARMADAXP 0x3
+#define MVEBU_GPIO_SOC_VARIANT_A8K 0x4
+
+#define MVEBU_MAX_GPIO_PER_BANK 32
+
+struct mvebu_pwm {
+ struct regmap *regs;
+ u32 offset;
+ unsigned long clk_rate;
+ struct gpio_desc *gpiod;
+ struct pwm_chip chip;
+ spinlock_t lock;
+ struct mvebu_gpio_chip *mvchip;
+
+ /* Used to preserve GPIO/PWM registers across suspend/resume */
+ u32 blink_select;
+ u32 blink_on_duration;
+ u32 blink_off_duration;
+};
+
+struct mvebu_gpio_chip {
+ struct gpio_chip chip;
+ struct regmap *regs;
+ u32 offset;
+ struct regmap *percpu_regs;
+ int irqbase;
+ struct irq_domain *domain;
+ int soc_variant;
+
+ /* Used for PWM support */
+ struct clk *clk;
+ struct mvebu_pwm *mvpwm;
+
+ /* Used to preserve GPIO registers across suspend/resume */
+ u32 out_reg;
+ u32 io_conf_reg;
+ u32 blink_en_reg;
+ u32 in_pol_reg;
+ u32 edge_mask_regs[4];
+ u32 level_mask_regs[4];
+};
+
+/*
+ * Functions returning addresses of individual registers for a given
+ * GPIO controller.
+ */
+
+static void mvebu_gpioreg_edge_cause(struct mvebu_gpio_chip *mvchip,
+ struct regmap **map, unsigned int *offset)
+{
+ int cpu;
+
+ switch (mvchip->soc_variant) {
+ case MVEBU_GPIO_SOC_VARIANT_ORION:
+ case MVEBU_GPIO_SOC_VARIANT_MV78200:
+ case MVEBU_GPIO_SOC_VARIANT_A8K:
+ *map = mvchip->regs;
+ *offset = GPIO_EDGE_CAUSE_OFF + mvchip->offset;
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
+ cpu = smp_processor_id();
+ *map = mvchip->percpu_regs;
+ *offset = GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu);
+ break;
+ default:
+ BUG();
+ }
+}
+
+static u32
+mvebu_gpio_read_edge_cause(struct mvebu_gpio_chip *mvchip)
+{
+ struct regmap *map;
+ unsigned int offset;
+ u32 val;
+
+ mvebu_gpioreg_edge_cause(mvchip, &map, &offset);
+ regmap_read(map, offset, &val);
+
+ return val;
+}
+
+static void
+mvebu_gpio_write_edge_cause(struct mvebu_gpio_chip *mvchip, u32 val)
+{
+ struct regmap *map;
+ unsigned int offset;
+
+ mvebu_gpioreg_edge_cause(mvchip, &map, &offset);
+ regmap_write(map, offset, val);
+}
+
+static inline void
+mvebu_gpioreg_edge_mask(struct mvebu_gpio_chip *mvchip,
+ struct regmap **map, unsigned int *offset)
+{
+ int cpu;
+
+ switch (mvchip->soc_variant) {
+ case MVEBU_GPIO_SOC_VARIANT_ORION:
+ case MVEBU_GPIO_SOC_VARIANT_A8K:
+ *map = mvchip->regs;
+ *offset = GPIO_EDGE_MASK_OFF + mvchip->offset;
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_MV78200:
+ cpu = smp_processor_id();
+ *map = mvchip->regs;
+ *offset = GPIO_EDGE_MASK_MV78200_OFF(cpu);
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
+ cpu = smp_processor_id();
+ *map = mvchip->percpu_regs;
+ *offset = GPIO_EDGE_MASK_ARMADAXP_OFF(cpu);
+ break;
+ default:
+ BUG();
+ }
+}
+
+static u32
+mvebu_gpio_read_edge_mask(struct mvebu_gpio_chip *mvchip)
+{
+ struct regmap *map;
+ unsigned int offset;
+ u32 val;
+
+ mvebu_gpioreg_edge_mask(mvchip, &map, &offset);
+ regmap_read(map, offset, &val);
+
+ return val;
+}
+
+static void
+mvebu_gpio_write_edge_mask(struct mvebu_gpio_chip *mvchip, u32 val)
+{
+ struct regmap *map;
+ unsigned int offset;
+
+ mvebu_gpioreg_edge_mask(mvchip, &map, &offset);
+ regmap_write(map, offset, val);
+}
+
+static void
+mvebu_gpioreg_level_mask(struct mvebu_gpio_chip *mvchip,
+ struct regmap **map, unsigned int *offset)
+{
+ int cpu;
+
+ switch (mvchip->soc_variant) {
+ case MVEBU_GPIO_SOC_VARIANT_ORION:
+ case MVEBU_GPIO_SOC_VARIANT_A8K:
+ *map = mvchip->regs;
+ *offset = GPIO_LEVEL_MASK_OFF + mvchip->offset;
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_MV78200:
+ cpu = smp_processor_id();
+ *map = mvchip->regs;
+ *offset = GPIO_LEVEL_MASK_MV78200_OFF(cpu);
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
+ cpu = smp_processor_id();
+ *map = mvchip->percpu_regs;
+ *offset = GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu);
+ break;
+ default:
+ BUG();
+ }
+}
+
+static u32
+mvebu_gpio_read_level_mask(struct mvebu_gpio_chip *mvchip)
+{
+ struct regmap *map;
+ unsigned int offset;
+ u32 val;
+
+ mvebu_gpioreg_level_mask(mvchip, &map, &offset);
+ regmap_read(map, offset, &val);
+
+ return val;
+}
+
+static void
+mvebu_gpio_write_level_mask(struct mvebu_gpio_chip *mvchip, u32 val)
+{
+ struct regmap *map;
+ unsigned int offset;
+
+ mvebu_gpioreg_level_mask(mvchip, &map, &offset);
+ regmap_write(map, offset, val);
+}
+
+/*
+ * Functions returning offsets of individual registers for a given
+ * PWM controller.
+ */
+static unsigned int mvebu_pwmreg_blink_on_duration(struct mvebu_pwm *mvpwm)
+{
+ return mvpwm->offset + PWM_BLINK_ON_DURATION_OFF;
+}
+
+static unsigned int mvebu_pwmreg_blink_off_duration(struct mvebu_pwm *mvpwm)
+{
+ return mvpwm->offset + PWM_BLINK_OFF_DURATION_OFF;
+}
+
+/*
+ * Functions implementing the gpio_chip methods
+ */
+static void mvebu_gpio_set(struct gpio_chip *chip, unsigned int pin, int value)
+{
+ struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
+
+ regmap_update_bits(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
+ BIT(pin), value ? BIT(pin) : 0);
+}
+
+static int mvebu_gpio_get(struct gpio_chip *chip, unsigned int pin)
+{
+ struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
+ u32 u;
+
+ regmap_read(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset, &u);
+
+ if (u & BIT(pin)) {
+ u32 data_in, in_pol;
+
+ regmap_read(mvchip->regs, GPIO_DATA_IN_OFF + mvchip->offset,
+ &data_in);
+ regmap_read(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset,
+ &in_pol);
+ u = data_in ^ in_pol;
+ } else {
+ regmap_read(mvchip->regs, GPIO_OUT_OFF + mvchip->offset, &u);
+ }
+
+ return (u >> pin) & 1;
+}
+
+static void mvebu_gpio_blink(struct gpio_chip *chip, unsigned int pin,
+ int value)
+{
+ struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
+
+ regmap_update_bits(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset,
+ BIT(pin), value ? BIT(pin) : 0);
+}
+
+static int mvebu_gpio_direction_input(struct gpio_chip *chip, unsigned int pin)
+{
+ struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
+ int ret;
+
+ /*
+ * Check with the pinctrl driver whether this pin is usable as
+ * an input GPIO
+ */
+ ret = pinctrl_gpio_direction_input(chip->base + pin);
+ if (ret)
+ return ret;
+
+ regmap_update_bits(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset,
+ BIT(pin), BIT(pin));
+
+ return 0;
+}
+
+static int mvebu_gpio_direction_output(struct gpio_chip *chip, unsigned int pin,
+ int value)
+{
+ struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
+ int ret;
+
+ /*
+ * Check with the pinctrl driver whether this pin is usable as
+ * an output GPIO
+ */
+ ret = pinctrl_gpio_direction_output(chip->base + pin);
+ if (ret)
+ return ret;
+
+ mvebu_gpio_blink(chip, pin, 0);
+ mvebu_gpio_set(chip, pin, value);
+
+ regmap_update_bits(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset,
+ BIT(pin), 0);
+
+ return 0;
+}
+
+static int mvebu_gpio_get_direction(struct gpio_chip *chip, unsigned int pin)
+{
+ struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
+ u32 u;
+
+ regmap_read(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset, &u);
+
+ if (u & BIT(pin))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int mvebu_gpio_to_irq(struct gpio_chip *chip, unsigned int pin)
+{
+ struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
+
+ return irq_create_mapping(mvchip->domain, pin);
+}
+
+/*
+ * Functions implementing the irq_chip methods
+ */
+static void mvebu_gpio_irq_ack(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct mvebu_gpio_chip *mvchip = gc->private;
+ u32 mask = d->mask;
+
+ irq_gc_lock(gc);
+ mvebu_gpio_write_edge_cause(mvchip, ~mask);
+ irq_gc_unlock(gc);
+}
+
+static void mvebu_gpio_edge_irq_mask(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct mvebu_gpio_chip *mvchip = gc->private;
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
+ u32 mask = d->mask;
+
+ irq_gc_lock(gc);
+ ct->mask_cache_priv &= ~mask;
+ mvebu_gpio_write_edge_mask(mvchip, ct->mask_cache_priv);
+ irq_gc_unlock(gc);
+}
+
+static void mvebu_gpio_edge_irq_unmask(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct mvebu_gpio_chip *mvchip = gc->private;
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
+ u32 mask = d->mask;
+
+ irq_gc_lock(gc);
+ mvebu_gpio_write_edge_cause(mvchip, ~mask);
+ ct->mask_cache_priv |= mask;
+ mvebu_gpio_write_edge_mask(mvchip, ct->mask_cache_priv);
+ irq_gc_unlock(gc);
+}
+
+static void mvebu_gpio_level_irq_mask(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct mvebu_gpio_chip *mvchip = gc->private;
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
+ u32 mask = d->mask;
+
+ irq_gc_lock(gc);
+ ct->mask_cache_priv &= ~mask;
+ mvebu_gpio_write_level_mask(mvchip, ct->mask_cache_priv);
+ irq_gc_unlock(gc);
+}
+
+static void mvebu_gpio_level_irq_unmask(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct mvebu_gpio_chip *mvchip = gc->private;
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
+ u32 mask = d->mask;
+
+ irq_gc_lock(gc);
+ ct->mask_cache_priv |= mask;
+ mvebu_gpio_write_level_mask(mvchip, ct->mask_cache_priv);
+ irq_gc_unlock(gc);
+}
+
+/*****************************************************************************
+ * MVEBU GPIO IRQ
+ *
+ * GPIO_IN_POL register controls whether GPIO_DATA_IN will hold the same
+ * value of the line or the opposite value.
+ *
+ * Level IRQ handlers: DATA_IN is used directly as cause register.
+ * Interrupt are masked by LEVEL_MASK registers.
+ * Edge IRQ handlers: Change in DATA_IN are latched in EDGE_CAUSE.
+ * Interrupt are masked by EDGE_MASK registers.
+ * Both-edge handlers: Similar to regular Edge handlers, but also swaps
+ * the polarity to catch the next line transaction.
+ * This is a race condition that might not perfectly
+ * work on some use cases.
+ *
+ * Every eight GPIO lines are grouped (OR'ed) before going up to main
+ * cause register.
+ *
+ * EDGE cause mask
+ * data-in /--------| |-----| |----\
+ * -----| |----- ---- to main cause reg
+ * X \----------------| |----/
+ * polarity LEVEL mask
+ *
+ ****************************************************************************/
+
+static int mvebu_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
+ struct mvebu_gpio_chip *mvchip = gc->private;
+ int pin;
+ u32 u;
+
+ pin = d->hwirq;
+
+ regmap_read(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset, &u);
+ if ((u & BIT(pin)) == 0)
+ return -EINVAL;
+
+ type &= IRQ_TYPE_SENSE_MASK;
+ if (type == IRQ_TYPE_NONE)
+ return -EINVAL;
+
+ /* Check if we need to change chip and handler */
+ if (!(ct->type & type))
+ if (irq_setup_alt_chip(d, type))
+ return -EINVAL;
+
+ /*
+ * Configure interrupt polarity.
+ */
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_LEVEL_HIGH:
+ regmap_update_bits(mvchip->regs,
+ GPIO_IN_POL_OFF + mvchip->offset,
+ BIT(pin), 0);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ case IRQ_TYPE_LEVEL_LOW:
+ regmap_update_bits(mvchip->regs,
+ GPIO_IN_POL_OFF + mvchip->offset,
+ BIT(pin), BIT(pin));
+ break;
+ case IRQ_TYPE_EDGE_BOTH: {
+ u32 data_in, in_pol, val;
+
+ regmap_read(mvchip->regs,
+ GPIO_IN_POL_OFF + mvchip->offset, &in_pol);
+ regmap_read(mvchip->regs,
+ GPIO_DATA_IN_OFF + mvchip->offset, &data_in);
+
+ /*
+ * set initial polarity based on current input level
+ */
+ if ((data_in ^ in_pol) & BIT(pin))
+ val = BIT(pin); /* falling */
+ else
+ val = 0; /* raising */
+
+ regmap_update_bits(mvchip->regs,
+ GPIO_IN_POL_OFF + mvchip->offset,
+ BIT(pin), val);
+ break;
+ }
+ }
+ return 0;
+}
+
+static void mvebu_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct mvebu_gpio_chip *mvchip = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ u32 cause, type, data_in, level_mask, edge_cause, edge_mask;
+ int i;
+
+ if (mvchip == NULL)
+ return;
+
+ chained_irq_enter(chip, desc);
+
+ regmap_read(mvchip->regs, GPIO_DATA_IN_OFF + mvchip->offset, &data_in);
+ level_mask = mvebu_gpio_read_level_mask(mvchip);
+ edge_cause = mvebu_gpio_read_edge_cause(mvchip);
+ edge_mask = mvebu_gpio_read_edge_mask(mvchip);
+
+ cause = (data_in & level_mask) | (edge_cause & edge_mask);
+
+ for (i = 0; i < mvchip->chip.ngpio; i++) {
+ int irq;
+
+ irq = irq_find_mapping(mvchip->domain, i);
+
+ if (!(cause & BIT(i)))
+ continue;
+
+ type = irq_get_trigger_type(irq);
+ if ((type & IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH) {
+ /* Swap polarity (race with GPIO line) */
+ u32 polarity;
+
+ regmap_read(mvchip->regs,
+ GPIO_IN_POL_OFF + mvchip->offset,
+ &polarity);
+ polarity ^= BIT(i);
+ regmap_write(mvchip->regs,
+ GPIO_IN_POL_OFF + mvchip->offset,
+ polarity);
+ }
+
+ generic_handle_irq(irq);
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static const struct regmap_config mvebu_gpio_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .fast_io = true,
+};
+
+/*
+ * Functions implementing the pwm_chip methods
+ */
+static struct mvebu_pwm *to_mvebu_pwm(struct pwm_chip *chip)
+{
+ return container_of(chip, struct mvebu_pwm, chip);
+}
+
+static int mvebu_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip);
+ struct mvebu_gpio_chip *mvchip = mvpwm->mvchip;
+ struct gpio_desc *desc;
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&mvpwm->lock, flags);
+
+ if (mvpwm->gpiod) {
+ ret = -EBUSY;
+ } else {
+ desc = gpiochip_request_own_desc(&mvchip->chip,
+ pwm->hwpwm, "mvebu-pwm",
+ GPIO_ACTIVE_HIGH,
+ GPIOD_OUT_LOW);
+ if (IS_ERR(desc)) {
+ ret = PTR_ERR(desc);
+ goto out;
+ }
+
+ mvpwm->gpiod = desc;
+ }
+out:
+ spin_unlock_irqrestore(&mvpwm->lock, flags);
+ return ret;
+}
+
+static void mvebu_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip);
+ unsigned long flags;
+
+ spin_lock_irqsave(&mvpwm->lock, flags);
+ gpiochip_free_own_desc(mvpwm->gpiod);
+ mvpwm->gpiod = NULL;
+ spin_unlock_irqrestore(&mvpwm->lock, flags);
+}
+
+static int mvebu_pwm_get_state(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+
+ struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip);
+ struct mvebu_gpio_chip *mvchip = mvpwm->mvchip;
+ unsigned long long val;
+ unsigned long flags;
+ u32 u;
+
+ spin_lock_irqsave(&mvpwm->lock, flags);
+
+ regmap_read(mvpwm->regs, mvebu_pwmreg_blink_on_duration(mvpwm), &u);
+ /* Hardware treats zero as 2^32. See mvebu_pwm_apply(). */
+ if (u > 0)
+ val = u;
+ else
+ val = UINT_MAX + 1ULL;
+ state->duty_cycle = DIV_ROUND_UP_ULL(val * NSEC_PER_SEC,
+ mvpwm->clk_rate);
+
+ regmap_read(mvpwm->regs, mvebu_pwmreg_blink_off_duration(mvpwm), &u);
+ /* period = on + off duration */
+ if (u > 0)
+ val += u;
+ else
+ val += UINT_MAX + 1ULL;
+ state->period = DIV_ROUND_UP_ULL(val * NSEC_PER_SEC, mvpwm->clk_rate);
+
+ regmap_read(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset, &u);
+ if (u)
+ state->enabled = true;
+ else
+ state->enabled = false;
+
+ spin_unlock_irqrestore(&mvpwm->lock, flags);
+
+ return 0;
+}
+
+static int mvebu_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip);
+ struct mvebu_gpio_chip *mvchip = mvpwm->mvchip;
+ unsigned long long val;
+ unsigned long flags;
+ unsigned int on, off;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ val = (unsigned long long) mvpwm->clk_rate * state->duty_cycle;
+ do_div(val, NSEC_PER_SEC);
+ if (val > UINT_MAX + 1ULL)
+ return -EINVAL;
+ /*
+ * Zero on/off values don't work as expected. Experimentation shows
+ * that zero value is treated as 2^32. This behavior is not documented.
+ */
+ if (val == UINT_MAX + 1ULL)
+ on = 0;
+ else if (val)
+ on = val;
+ else
+ on = 1;
+
+ val = (unsigned long long) mvpwm->clk_rate * state->period;
+ do_div(val, NSEC_PER_SEC);
+ val -= on;
+ if (val > UINT_MAX + 1ULL)
+ return -EINVAL;
+ if (val == UINT_MAX + 1ULL)
+ off = 0;
+ else if (val)
+ off = val;
+ else
+ off = 1;
+
+ spin_lock_irqsave(&mvpwm->lock, flags);
+
+ regmap_write(mvpwm->regs, mvebu_pwmreg_blink_on_duration(mvpwm), on);
+ regmap_write(mvpwm->regs, mvebu_pwmreg_blink_off_duration(mvpwm), off);
+ if (state->enabled)
+ mvebu_gpio_blink(&mvchip->chip, pwm->hwpwm, 1);
+ else
+ mvebu_gpio_blink(&mvchip->chip, pwm->hwpwm, 0);
+
+ spin_unlock_irqrestore(&mvpwm->lock, flags);
+
+ return 0;
+}
+
+static const struct pwm_ops mvebu_pwm_ops = {
+ .request = mvebu_pwm_request,
+ .free = mvebu_pwm_free,
+ .get_state = mvebu_pwm_get_state,
+ .apply = mvebu_pwm_apply,
+ .owner = THIS_MODULE,
+};
+
+static void __maybe_unused mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip)
+{
+ struct mvebu_pwm *mvpwm = mvchip->mvpwm;
+
+ regmap_read(mvchip->regs, GPIO_BLINK_CNT_SELECT_OFF + mvchip->offset,
+ &mvpwm->blink_select);
+ regmap_read(mvpwm->regs, mvebu_pwmreg_blink_on_duration(mvpwm),
+ &mvpwm->blink_on_duration);
+ regmap_read(mvpwm->regs, mvebu_pwmreg_blink_off_duration(mvpwm),
+ &mvpwm->blink_off_duration);
+}
+
+static void __maybe_unused mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip)
+{
+ struct mvebu_pwm *mvpwm = mvchip->mvpwm;
+
+ regmap_write(mvchip->regs, GPIO_BLINK_CNT_SELECT_OFF + mvchip->offset,
+ mvpwm->blink_select);
+ regmap_write(mvpwm->regs, mvebu_pwmreg_blink_on_duration(mvpwm),
+ mvpwm->blink_on_duration);
+ regmap_write(mvpwm->regs, mvebu_pwmreg_blink_off_duration(mvpwm),
+ mvpwm->blink_off_duration);
+}
+
+static int mvebu_pwm_probe(struct platform_device *pdev,
+ struct mvebu_gpio_chip *mvchip,
+ int id)
+{
+ struct device *dev = &pdev->dev;
+ struct mvebu_pwm *mvpwm;
+ void __iomem *base;
+ u32 offset;
+ u32 set;
+
+ if (mvchip->soc_variant == MVEBU_GPIO_SOC_VARIANT_A8K) {
+ int ret = of_property_read_u32(dev->of_node,
+ "marvell,pwm-offset", &offset);
+ if (ret < 0)
+ return 0;
+ } else {
+ /*
+ * There are only two sets of PWM configuration registers for
+ * all the GPIO lines on those SoCs which this driver reserves
+ * for the first two GPIO chips. So if the resource is missing
+ * we can't treat it as an error.
+ */
+ if (!platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm"))
+ return 0;
+ offset = 0;
+ }
+
+ if (IS_ERR(mvchip->clk))
+ return PTR_ERR(mvchip->clk);
+
+ mvpwm = devm_kzalloc(dev, sizeof(struct mvebu_pwm), GFP_KERNEL);
+ if (!mvpwm)
+ return -ENOMEM;
+ mvchip->mvpwm = mvpwm;
+ mvpwm->mvchip = mvchip;
+ mvpwm->offset = offset;
+
+ if (mvchip->soc_variant == MVEBU_GPIO_SOC_VARIANT_A8K) {
+ mvpwm->regs = mvchip->regs;
+
+ switch (mvchip->offset) {
+ case AP80X_GPIO0_OFF_A8K:
+ case CP11X_GPIO0_OFF_A8K:
+ /* Blink counter A */
+ set = 0;
+ break;
+ case CP11X_GPIO1_OFF_A8K:
+ /* Blink counter B */
+ set = U32_MAX;
+ mvpwm->offset += PWM_BLINK_COUNTER_B_OFF;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else {
+ base = devm_platform_ioremap_resource_byname(pdev, "pwm");
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ mvpwm->regs = devm_regmap_init_mmio(&pdev->dev, base,
+ &mvebu_gpio_regmap_config);
+ if (IS_ERR(mvpwm->regs))
+ return PTR_ERR(mvpwm->regs);
+
+ /*
+ * Use set A for lines of GPIO chip with id 0, B for GPIO chip
+ * with id 1. Don't allow further GPIO chips to be used for PWM.
+ */
+ if (id == 0)
+ set = 0;
+ else if (id == 1)
+ set = U32_MAX;
+ else
+ return -EINVAL;
+ }
+
+ regmap_write(mvchip->regs,
+ GPIO_BLINK_CNT_SELECT_OFF + mvchip->offset, set);
+
+ mvpwm->clk_rate = clk_get_rate(mvchip->clk);
+ if (!mvpwm->clk_rate) {
+ dev_err(dev, "failed to get clock rate\n");
+ return -EINVAL;
+ }
+
+ mvpwm->chip.dev = dev;
+ mvpwm->chip.ops = &mvebu_pwm_ops;
+ mvpwm->chip.npwm = mvchip->chip.ngpio;
+
+ spin_lock_init(&mvpwm->lock);
+
+ return devm_pwmchip_add(dev, &mvpwm->chip);
+}
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/seq_file.h>
+
+static void mvebu_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
+ u32 out, io_conf, blink, in_pol, data_in, cause, edg_msk, lvl_msk;
+ const char *label;
+ int i;
+
+ regmap_read(mvchip->regs, GPIO_OUT_OFF + mvchip->offset, &out);
+ regmap_read(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset, &io_conf);
+ regmap_read(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset, &blink);
+ regmap_read(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset, &in_pol);
+ regmap_read(mvchip->regs, GPIO_DATA_IN_OFF + mvchip->offset, &data_in);
+ cause = mvebu_gpio_read_edge_cause(mvchip);
+ edg_msk = mvebu_gpio_read_edge_mask(mvchip);
+ lvl_msk = mvebu_gpio_read_level_mask(mvchip);
+
+ for_each_requested_gpio(chip, i, label) {
+ u32 msk;
+ bool is_out;
+
+ msk = BIT(i);
+ is_out = !(io_conf & msk);
+
+ seq_printf(s, " gpio-%-3d (%-20.20s)", chip->base + i, label);
+
+ if (is_out) {
+ seq_printf(s, " out %s %s\n",
+ out & msk ? "hi" : "lo",
+ blink & msk ? "(blink )" : "");
+ continue;
+ }
+
+ seq_printf(s, " in %s (act %s) - IRQ",
+ (data_in ^ in_pol) & msk ? "hi" : "lo",
+ in_pol & msk ? "lo" : "hi");
+ if (!((edg_msk | lvl_msk) & msk)) {
+ seq_puts(s, " disabled\n");
+ continue;
+ }
+ if (edg_msk & msk)
+ seq_puts(s, " edge ");
+ if (lvl_msk & msk)
+ seq_puts(s, " level");
+ seq_printf(s, " (%s)\n", cause & msk ? "pending" : "clear ");
+ }
+}
+#else
+#define mvebu_gpio_dbg_show NULL
+#endif
+
+static const struct of_device_id mvebu_gpio_of_match[] = {
+ {
+ .compatible = "marvell,orion-gpio",
+ .data = (void *) MVEBU_GPIO_SOC_VARIANT_ORION,
+ },
+ {
+ .compatible = "marvell,mv78200-gpio",
+ .data = (void *) MVEBU_GPIO_SOC_VARIANT_MV78200,
+ },
+ {
+ .compatible = "marvell,armadaxp-gpio",
+ .data = (void *) MVEBU_GPIO_SOC_VARIANT_ARMADAXP,
+ },
+ {
+ .compatible = "marvell,armada-370-gpio",
+ .data = (void *) MVEBU_GPIO_SOC_VARIANT_ORION,
+ },
+ {
+ .compatible = "marvell,armada-8k-gpio",
+ .data = (void *) MVEBU_GPIO_SOC_VARIANT_A8K,
+ },
+ {
+ /* sentinel */
+ },
+};
+
+static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct mvebu_gpio_chip *mvchip = platform_get_drvdata(pdev);
+ int i;
+
+ regmap_read(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
+ &mvchip->out_reg);
+ regmap_read(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset,
+ &mvchip->io_conf_reg);
+ regmap_read(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset,
+ &mvchip->blink_en_reg);
+ regmap_read(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset,
+ &mvchip->in_pol_reg);
+
+ switch (mvchip->soc_variant) {
+ case MVEBU_GPIO_SOC_VARIANT_ORION:
+ case MVEBU_GPIO_SOC_VARIANT_A8K:
+ regmap_read(mvchip->regs, GPIO_EDGE_MASK_OFF + mvchip->offset,
+ &mvchip->edge_mask_regs[0]);
+ regmap_read(mvchip->regs, GPIO_LEVEL_MASK_OFF + mvchip->offset,
+ &mvchip->level_mask_regs[0]);
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_MV78200:
+ for (i = 0; i < 2; i++) {
+ regmap_read(mvchip->regs,
+ GPIO_EDGE_MASK_MV78200_OFF(i),
+ &mvchip->edge_mask_regs[i]);
+ regmap_read(mvchip->regs,
+ GPIO_LEVEL_MASK_MV78200_OFF(i),
+ &mvchip->level_mask_regs[i]);
+ }
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
+ for (i = 0; i < 4; i++) {
+ regmap_read(mvchip->regs,
+ GPIO_EDGE_MASK_ARMADAXP_OFF(i),
+ &mvchip->edge_mask_regs[i]);
+ regmap_read(mvchip->regs,
+ GPIO_LEVEL_MASK_ARMADAXP_OFF(i),
+ &mvchip->level_mask_regs[i]);
+ }
+ break;
+ default:
+ BUG();
+ }
+
+ if (IS_REACHABLE(CONFIG_PWM))
+ mvebu_pwm_suspend(mvchip);
+
+ return 0;
+}
+
+static int mvebu_gpio_resume(struct platform_device *pdev)
+{
+ struct mvebu_gpio_chip *mvchip = platform_get_drvdata(pdev);
+ int i;
+
+ regmap_write(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
+ mvchip->out_reg);
+ regmap_write(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset,
+ mvchip->io_conf_reg);
+ regmap_write(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset,
+ mvchip->blink_en_reg);
+ regmap_write(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset,
+ mvchip->in_pol_reg);
+
+ switch (mvchip->soc_variant) {
+ case MVEBU_GPIO_SOC_VARIANT_ORION:
+ case MVEBU_GPIO_SOC_VARIANT_A8K:
+ regmap_write(mvchip->regs, GPIO_EDGE_MASK_OFF + mvchip->offset,
+ mvchip->edge_mask_regs[0]);
+ regmap_write(mvchip->regs, GPIO_LEVEL_MASK_OFF + mvchip->offset,
+ mvchip->level_mask_regs[0]);
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_MV78200:
+ for (i = 0; i < 2; i++) {
+ regmap_write(mvchip->regs,
+ GPIO_EDGE_MASK_MV78200_OFF(i),
+ mvchip->edge_mask_regs[i]);
+ regmap_write(mvchip->regs,
+ GPIO_LEVEL_MASK_MV78200_OFF(i),
+ mvchip->level_mask_regs[i]);
+ }
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
+ for (i = 0; i < 4; i++) {
+ regmap_write(mvchip->regs,
+ GPIO_EDGE_MASK_ARMADAXP_OFF(i),
+ mvchip->edge_mask_regs[i]);
+ regmap_write(mvchip->regs,
+ GPIO_LEVEL_MASK_ARMADAXP_OFF(i),
+ mvchip->level_mask_regs[i]);
+ }
+ break;
+ default:
+ BUG();
+ }
+
+ if (IS_REACHABLE(CONFIG_PWM))
+ mvebu_pwm_resume(mvchip);
+
+ return 0;
+}
+
+static int mvebu_gpio_probe_raw(struct platform_device *pdev,
+ struct mvebu_gpio_chip *mvchip)
+{
+ void __iomem *base;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ mvchip->regs = devm_regmap_init_mmio(&pdev->dev, base,
+ &mvebu_gpio_regmap_config);
+ if (IS_ERR(mvchip->regs))
+ return PTR_ERR(mvchip->regs);
+
+ /*
+ * For the legacy SoCs, the regmap directly maps to the GPIO
+ * registers, so no offset is needed.
+ */
+ mvchip->offset = 0;
+
+ /*
+ * The Armada XP has a second range of registers for the
+ * per-CPU registers
+ */
+ if (mvchip->soc_variant == MVEBU_GPIO_SOC_VARIANT_ARMADAXP) {
+ base = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ mvchip->percpu_regs =
+ devm_regmap_init_mmio(&pdev->dev, base,
+ &mvebu_gpio_regmap_config);
+ if (IS_ERR(mvchip->percpu_regs))
+ return PTR_ERR(mvchip->percpu_regs);
+ }
+
+ return 0;
+}
+
+static int mvebu_gpio_probe_syscon(struct platform_device *pdev,
+ struct mvebu_gpio_chip *mvchip)
+{
+ mvchip->regs = syscon_node_to_regmap(pdev->dev.parent->of_node);
+ if (IS_ERR(mvchip->regs))
+ return PTR_ERR(mvchip->regs);
+
+ if (of_property_read_u32(pdev->dev.of_node, "offset", &mvchip->offset))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void mvebu_gpio_remove_irq_domain(void *data)
+{
+ struct irq_domain *domain = data;
+
+ irq_domain_remove(domain);
+}
+
+static int mvebu_gpio_probe(struct platform_device *pdev)
+{
+ struct mvebu_gpio_chip *mvchip;
+ const struct of_device_id *match;
+ struct device_node *np = pdev->dev.of_node;
+ struct irq_chip_generic *gc;
+ struct irq_chip_type *ct;
+ unsigned int ngpios;
+ bool have_irqs;
+ int soc_variant;
+ int i, cpu, id;
+ int err;
+
+ match = of_match_device(mvebu_gpio_of_match, &pdev->dev);
+ if (match)
+ soc_variant = (unsigned long) match->data;
+ else
+ soc_variant = MVEBU_GPIO_SOC_VARIANT_ORION;
+
+ /* Some gpio controllers do not provide irq support */
+ err = platform_irq_count(pdev);
+ if (err < 0)
+ return err;
+
+ have_irqs = err != 0;
+
+ mvchip = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_gpio_chip),
+ GFP_KERNEL);
+ if (!mvchip)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, mvchip);
+
+ if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
+ dev_err(&pdev->dev, "Missing ngpios OF property\n");
+ return -ENODEV;
+ }
+
+ id = of_alias_get_id(pdev->dev.of_node, "gpio");
+ if (id < 0) {
+ dev_err(&pdev->dev, "Couldn't get OF id\n");
+ return id;
+ }
+
+ mvchip->clk = devm_clk_get(&pdev->dev, NULL);
+ /* Not all SoCs require a clock.*/
+ if (!IS_ERR(mvchip->clk))
+ clk_prepare_enable(mvchip->clk);
+
+ mvchip->soc_variant = soc_variant;
+ mvchip->chip.label = dev_name(&pdev->dev);
+ mvchip->chip.parent = &pdev->dev;
+ mvchip->chip.request = gpiochip_generic_request;
+ mvchip->chip.free = gpiochip_generic_free;
+ mvchip->chip.get_direction = mvebu_gpio_get_direction;
+ mvchip->chip.direction_input = mvebu_gpio_direction_input;
+ mvchip->chip.get = mvebu_gpio_get;
+ mvchip->chip.direction_output = mvebu_gpio_direction_output;
+ mvchip->chip.set = mvebu_gpio_set;
+ if (have_irqs)
+ mvchip->chip.to_irq = mvebu_gpio_to_irq;
+ mvchip->chip.base = id * MVEBU_MAX_GPIO_PER_BANK;
+ mvchip->chip.ngpio = ngpios;
+ mvchip->chip.can_sleep = false;
+ mvchip->chip.dbg_show = mvebu_gpio_dbg_show;
+
+ if (soc_variant == MVEBU_GPIO_SOC_VARIANT_A8K)
+ err = mvebu_gpio_probe_syscon(pdev, mvchip);
+ else
+ err = mvebu_gpio_probe_raw(pdev, mvchip);
+
+ if (err)
+ return err;
+
+ /*
+ * Mask and clear GPIO interrupts.
+ */
+ switch (soc_variant) {
+ case MVEBU_GPIO_SOC_VARIANT_ORION:
+ case MVEBU_GPIO_SOC_VARIANT_A8K:
+ regmap_write(mvchip->regs,
+ GPIO_EDGE_CAUSE_OFF + mvchip->offset, 0);
+ regmap_write(mvchip->regs,
+ GPIO_EDGE_MASK_OFF + mvchip->offset, 0);
+ regmap_write(mvchip->regs,
+ GPIO_LEVEL_MASK_OFF + mvchip->offset, 0);
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_MV78200:
+ regmap_write(mvchip->regs, GPIO_EDGE_CAUSE_OFF, 0);
+ for (cpu = 0; cpu < 2; cpu++) {
+ regmap_write(mvchip->regs,
+ GPIO_EDGE_MASK_MV78200_OFF(cpu), 0);
+ regmap_write(mvchip->regs,
+ GPIO_LEVEL_MASK_MV78200_OFF(cpu), 0);
+ }
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
+ regmap_write(mvchip->regs, GPIO_EDGE_CAUSE_OFF, 0);
+ regmap_write(mvchip->regs, GPIO_EDGE_MASK_OFF, 0);
+ regmap_write(mvchip->regs, GPIO_LEVEL_MASK_OFF, 0);
+ for (cpu = 0; cpu < 4; cpu++) {
+ regmap_write(mvchip->percpu_regs,
+ GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu), 0);
+ regmap_write(mvchip->percpu_regs,
+ GPIO_EDGE_MASK_ARMADAXP_OFF(cpu), 0);
+ regmap_write(mvchip->percpu_regs,
+ GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu), 0);
+ }
+ break;
+ default:
+ BUG();
+ }
+
+ devm_gpiochip_add_data(&pdev->dev, &mvchip->chip, mvchip);
+
+ /* Some MVEBU SoCs have simple PWM support for GPIO lines */
+ if (IS_REACHABLE(CONFIG_PWM)) {
+ err = mvebu_pwm_probe(pdev, mvchip, id);
+ if (err)
+ return err;
+ }
+
+ /* Some gpio controllers do not provide irq support */
+ if (!have_irqs)
+ return 0;
+
+ mvchip->domain =
+ irq_domain_add_linear(np, ngpios, &irq_generic_chip_ops, NULL);
+ if (!mvchip->domain) {
+ dev_err(&pdev->dev, "couldn't allocate irq domain %s (DT).\n",
+ mvchip->chip.label);
+ return -ENODEV;
+ }
+
+ err = devm_add_action_or_reset(&pdev->dev, mvebu_gpio_remove_irq_domain,
+ mvchip->domain);
+ if (err)
+ return err;
+
+ err = irq_alloc_domain_generic_chips(
+ mvchip->domain, ngpios, 2, np->name, handle_level_irq,
+ IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_LEVEL, 0, 0);
+ if (err) {
+ dev_err(&pdev->dev, "couldn't allocate irq chips %s (DT).\n",
+ mvchip->chip.label);
+ return err;
+ }
+
+ /*
+ * NOTE: The common accessors cannot be used because of the percpu
+ * access to the mask registers
+ */
+ 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->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.name = mvchip->chip.label;
+
+ ct = &gc->chip_types[1];
+ ct->type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
+ 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->handler = handle_edge_irq;
+ ct->chip.name = mvchip->chip.label;
+
+ /*
+ * Setup the interrupt handlers. Each chip can have up to 4
+ * interrupt handlers, with each handler dealing with 8 GPIO
+ * pins.
+ */
+ for (i = 0; i < 4; 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);
+ }
+
+ return 0;
+}
+
+static struct platform_driver mvebu_gpio_driver = {
+ .driver = {
+ .name = "mvebu-gpio",
+ .of_match_table = mvebu_gpio_of_match,
+ },
+ .probe = mvebu_gpio_probe,
+ .suspend = mvebu_gpio_suspend,
+ .resume = mvebu_gpio_resume,
+};
+builtin_platform_driver(mvebu_gpio_driver);
diff --git a/drivers/gpio/gpio-mxc.c b/drivers/gpio/gpio-mxc.c
new file mode 100644
index 0000000000..4cb455b2bd
--- /dev/null
+++ b/drivers/gpio/gpio-mxc.c
@@ -0,0 +1,722 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// MXC GPIO support. (c) 2008 Daniel Mack <daniel@caiaq.de>
+// Copyright 2008 Juergen Beisert, kernel@pengutronix.de
+//
+// Based on code from Freescale Semiconductor,
+// Authors: Daniel Mack, Juergen Beisert.
+// Copyright (C) 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/syscore_ops.h>
+#include <linux/gpio/driver.h>
+#include <linux/of.h>
+#include <linux/bug.h>
+
+#define IMX_SCU_WAKEUP_OFF 0
+#define IMX_SCU_WAKEUP_LOW_LVL 4
+#define IMX_SCU_WAKEUP_FALL_EDGE 5
+#define IMX_SCU_WAKEUP_RISE_EDGE 6
+#define IMX_SCU_WAKEUP_HIGH_LVL 7
+
+/* device type dependent stuff */
+struct mxc_gpio_hwdata {
+ unsigned dr_reg;
+ unsigned gdir_reg;
+ unsigned psr_reg;
+ unsigned icr1_reg;
+ unsigned icr2_reg;
+ unsigned imr_reg;
+ unsigned isr_reg;
+ int edge_sel_reg;
+ unsigned low_level;
+ unsigned high_level;
+ unsigned rise_edge;
+ unsigned fall_edge;
+};
+
+struct mxc_gpio_reg_saved {
+ u32 icr1;
+ u32 icr2;
+ u32 imr;
+ u32 gdir;
+ u32 edge_sel;
+ u32 dr;
+};
+
+struct mxc_gpio_port {
+ struct list_head node;
+ void __iomem *base;
+ struct clk *clk;
+ int irq;
+ int irq_high;
+ void (*mx_irq_handler)(struct irq_desc *desc);
+ struct irq_domain *domain;
+ struct gpio_chip gc;
+ struct device *dev;
+ u32 both_edges;
+ struct mxc_gpio_reg_saved gpio_saved_reg;
+ bool power_off;
+ u32 wakeup_pads;
+ bool is_pad_wakeup;
+ u32 pad_type[32];
+ const struct mxc_gpio_hwdata *hwdata;
+};
+
+static struct mxc_gpio_hwdata imx1_imx21_gpio_hwdata = {
+ .dr_reg = 0x1c,
+ .gdir_reg = 0x00,
+ .psr_reg = 0x24,
+ .icr1_reg = 0x28,
+ .icr2_reg = 0x2c,
+ .imr_reg = 0x30,
+ .isr_reg = 0x34,
+ .edge_sel_reg = -EINVAL,
+ .low_level = 0x03,
+ .high_level = 0x02,
+ .rise_edge = 0x00,
+ .fall_edge = 0x01,
+};
+
+static struct mxc_gpio_hwdata imx31_gpio_hwdata = {
+ .dr_reg = 0x00,
+ .gdir_reg = 0x04,
+ .psr_reg = 0x08,
+ .icr1_reg = 0x0c,
+ .icr2_reg = 0x10,
+ .imr_reg = 0x14,
+ .isr_reg = 0x18,
+ .edge_sel_reg = -EINVAL,
+ .low_level = 0x00,
+ .high_level = 0x01,
+ .rise_edge = 0x02,
+ .fall_edge = 0x03,
+};
+
+static struct mxc_gpio_hwdata imx35_gpio_hwdata = {
+ .dr_reg = 0x00,
+ .gdir_reg = 0x04,
+ .psr_reg = 0x08,
+ .icr1_reg = 0x0c,
+ .icr2_reg = 0x10,
+ .imr_reg = 0x14,
+ .isr_reg = 0x18,
+ .edge_sel_reg = 0x1c,
+ .low_level = 0x00,
+ .high_level = 0x01,
+ .rise_edge = 0x02,
+ .fall_edge = 0x03,
+};
+
+#define GPIO_DR (port->hwdata->dr_reg)
+#define GPIO_GDIR (port->hwdata->gdir_reg)
+#define GPIO_PSR (port->hwdata->psr_reg)
+#define GPIO_ICR1 (port->hwdata->icr1_reg)
+#define GPIO_ICR2 (port->hwdata->icr2_reg)
+#define GPIO_IMR (port->hwdata->imr_reg)
+#define GPIO_ISR (port->hwdata->isr_reg)
+#define GPIO_EDGE_SEL (port->hwdata->edge_sel_reg)
+
+#define GPIO_INT_LOW_LEV (port->hwdata->low_level)
+#define GPIO_INT_HIGH_LEV (port->hwdata->high_level)
+#define GPIO_INT_RISE_EDGE (port->hwdata->rise_edge)
+#define GPIO_INT_FALL_EDGE (port->hwdata->fall_edge)
+#define GPIO_INT_BOTH_EDGES 0x4
+
+static const struct of_device_id mxc_gpio_dt_ids[] = {
+ { .compatible = "fsl,imx1-gpio", .data = &imx1_imx21_gpio_hwdata },
+ { .compatible = "fsl,imx21-gpio", .data = &imx1_imx21_gpio_hwdata },
+ { .compatible = "fsl,imx31-gpio", .data = &imx31_gpio_hwdata },
+ { .compatible = "fsl,imx35-gpio", .data = &imx35_gpio_hwdata },
+ { .compatible = "fsl,imx7d-gpio", .data = &imx35_gpio_hwdata },
+ { .compatible = "fsl,imx8dxl-gpio", .data = &imx35_gpio_hwdata },
+ { .compatible = "fsl,imx8qm-gpio", .data = &imx35_gpio_hwdata },
+ { .compatible = "fsl,imx8qxp-gpio", .data = &imx35_gpio_hwdata },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mxc_gpio_dt_ids);
+
+/*
+ * MX2 has one interrupt *for all* gpio ports. The list is used
+ * to save the references to all ports, so that mx2_gpio_irq_handler
+ * can walk through all interrupt status registers.
+ */
+static LIST_HEAD(mxc_gpio_ports);
+
+/* Note: This driver assumes 32 GPIOs are handled in one register */
+
+static int gpio_set_irq_type(struct irq_data *d, u32 type)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct mxc_gpio_port *port = gc->private;
+ unsigned long flags;
+ u32 bit, val;
+ u32 gpio_idx = d->hwirq;
+ int edge;
+ void __iomem *reg = port->base;
+
+ port->both_edges &= ~(1 << gpio_idx);
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ edge = GPIO_INT_RISE_EDGE;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ edge = GPIO_INT_FALL_EDGE;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ if (GPIO_EDGE_SEL >= 0) {
+ edge = GPIO_INT_BOTH_EDGES;
+ } else {
+ val = port->gc.get(&port->gc, gpio_idx);
+ if (val) {
+ edge = GPIO_INT_LOW_LEV;
+ pr_debug("mxc: set GPIO %d to low trigger\n", gpio_idx);
+ } else {
+ edge = GPIO_INT_HIGH_LEV;
+ pr_debug("mxc: set GPIO %d to high trigger\n", gpio_idx);
+ }
+ port->both_edges |= 1 << gpio_idx;
+ }
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ edge = GPIO_INT_LOW_LEV;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ edge = GPIO_INT_HIGH_LEV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ raw_spin_lock_irqsave(&port->gc.bgpio_lock, flags);
+
+ if (GPIO_EDGE_SEL >= 0) {
+ val = readl(port->base + GPIO_EDGE_SEL);
+ if (edge == GPIO_INT_BOTH_EDGES)
+ writel(val | (1 << gpio_idx),
+ port->base + GPIO_EDGE_SEL);
+ else
+ writel(val & ~(1 << gpio_idx),
+ port->base + GPIO_EDGE_SEL);
+ }
+
+ if (edge != GPIO_INT_BOTH_EDGES) {
+ reg += GPIO_ICR1 + ((gpio_idx & 0x10) >> 2); /* lower or upper register */
+ bit = gpio_idx & 0xf;
+ val = readl(reg) & ~(0x3 << (bit << 1));
+ writel(val | (edge << (bit << 1)), reg);
+ }
+
+ writel(1 << gpio_idx, port->base + GPIO_ISR);
+ port->pad_type[gpio_idx] = type;
+
+ raw_spin_unlock_irqrestore(&port->gc.bgpio_lock, flags);
+
+ return port->gc.direction_input(&port->gc, gpio_idx);
+}
+
+static void mxc_flip_edge(struct mxc_gpio_port *port, u32 gpio)
+{
+ void __iomem *reg = port->base;
+ unsigned long flags;
+ u32 bit, val;
+ int edge;
+
+ raw_spin_lock_irqsave(&port->gc.bgpio_lock, flags);
+
+ reg += GPIO_ICR1 + ((gpio & 0x10) >> 2); /* lower or upper register */
+ bit = gpio & 0xf;
+ val = readl(reg);
+ edge = (val >> (bit << 1)) & 3;
+ val &= ~(0x3 << (bit << 1));
+ if (edge == GPIO_INT_HIGH_LEV) {
+ edge = GPIO_INT_LOW_LEV;
+ pr_debug("mxc: switch GPIO %d to low trigger\n", gpio);
+ } else if (edge == GPIO_INT_LOW_LEV) {
+ edge = GPIO_INT_HIGH_LEV;
+ pr_debug("mxc: switch GPIO %d to high trigger\n", gpio);
+ } else {
+ pr_err("mxc: invalid configuration for GPIO %d: %x\n",
+ gpio, edge);
+ goto unlock;
+ }
+ writel(val | (edge << (bit << 1)), reg);
+
+unlock:
+ raw_spin_unlock_irqrestore(&port->gc.bgpio_lock, flags);
+}
+
+/* handle 32 interrupts in one status register */
+static void mxc_gpio_irq_handler(struct mxc_gpio_port *port, u32 irq_stat)
+{
+ while (irq_stat != 0) {
+ int irqoffset = fls(irq_stat) - 1;
+
+ if (port->both_edges & (1 << irqoffset))
+ mxc_flip_edge(port, irqoffset);
+
+ generic_handle_domain_irq(port->domain, irqoffset);
+
+ irq_stat &= ~(1 << irqoffset);
+ }
+}
+
+/* MX1 and MX3 has one interrupt *per* gpio port */
+static void mx3_gpio_irq_handler(struct irq_desc *desc)
+{
+ u32 irq_stat;
+ struct mxc_gpio_port *port = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+
+ if (port->is_pad_wakeup)
+ return;
+
+ chained_irq_enter(chip, desc);
+
+ irq_stat = readl(port->base + GPIO_ISR) & readl(port->base + GPIO_IMR);
+
+ mxc_gpio_irq_handler(port, irq_stat);
+
+ chained_irq_exit(chip, desc);
+}
+
+/* MX2 has one interrupt *for all* gpio ports */
+static void mx2_gpio_irq_handler(struct irq_desc *desc)
+{
+ u32 irq_msk, irq_stat;
+ struct mxc_gpio_port *port;
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+
+ chained_irq_enter(chip, desc);
+
+ /* walk through all interrupt status registers */
+ list_for_each_entry(port, &mxc_gpio_ports, node) {
+ irq_msk = readl(port->base + GPIO_IMR);
+ if (!irq_msk)
+ continue;
+
+ irq_stat = readl(port->base + GPIO_ISR) & irq_msk;
+ if (irq_stat)
+ mxc_gpio_irq_handler(port, irq_stat);
+ }
+ chained_irq_exit(chip, desc);
+}
+
+/*
+ * Set interrupt number "irq" in the GPIO as a wake-up source.
+ * While system is running, all registered GPIO interrupts need to have
+ * wake-up enabled. When system is suspended, only selected GPIO interrupts
+ * need to have wake-up enabled.
+ * @param irq interrupt source number
+ * @param enable enable as wake-up if equal to non-zero
+ * @return This function returns 0 on success.
+ */
+static int gpio_set_wake_irq(struct irq_data *d, u32 enable)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct mxc_gpio_port *port = gc->private;
+ u32 gpio_idx = d->hwirq;
+ int ret;
+
+ if (enable) {
+ if (port->irq_high && (gpio_idx >= 16))
+ ret = enable_irq_wake(port->irq_high);
+ else
+ ret = enable_irq_wake(port->irq);
+ port->wakeup_pads |= (1 << gpio_idx);
+ } else {
+ if (port->irq_high && (gpio_idx >= 16))
+ ret = disable_irq_wake(port->irq_high);
+ else
+ ret = disable_irq_wake(port->irq);
+ port->wakeup_pads &= ~(1 << gpio_idx);
+ }
+
+ return ret;
+}
+
+static int mxc_gpio_init_gc(struct mxc_gpio_port *port, int irq_base)
+{
+ struct irq_chip_generic *gc;
+ struct irq_chip_type *ct;
+ int rv;
+
+ gc = devm_irq_alloc_generic_chip(port->dev, "gpio-mxc", 1, irq_base,
+ port->base, handle_level_irq);
+ if (!gc)
+ return -ENOMEM;
+ gc->private = port;
+
+ ct = gc->chip_types;
+ ct->chip.irq_ack = irq_gc_ack_set_bit;
+ ct->chip.irq_mask = irq_gc_mask_clr_bit;
+ ct->chip.irq_unmask = irq_gc_mask_set_bit;
+ ct->chip.irq_set_type = gpio_set_irq_type;
+ ct->chip.irq_set_wake = gpio_set_wake_irq;
+ ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND;
+ ct->regs.ack = GPIO_ISR;
+ ct->regs.mask = GPIO_IMR;
+
+ rv = devm_irq_setup_generic_chip(port->dev, gc, IRQ_MSK(32),
+ IRQ_GC_INIT_NESTED_LOCK,
+ IRQ_NOREQUEST, 0);
+
+ return rv;
+}
+
+static int mxc_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
+{
+ struct mxc_gpio_port *port = gpiochip_get_data(gc);
+
+ return irq_find_mapping(port->domain, offset);
+}
+
+static int mxc_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ int ret;
+
+ ret = gpiochip_generic_request(chip, offset);
+ if (ret)
+ return ret;
+
+ return pm_runtime_resume_and_get(chip->parent);
+}
+
+static void mxc_gpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+ gpiochip_generic_free(chip, offset);
+ pm_runtime_put(chip->parent);
+}
+
+static void mxc_update_irq_chained_handler(struct mxc_gpio_port *port, bool enable)
+{
+ if (enable)
+ irq_set_chained_handler_and_data(port->irq, port->mx_irq_handler, port);
+ else
+ irq_set_chained_handler_and_data(port->irq, NULL, NULL);
+
+ /* setup handler for GPIO 16 to 31 */
+ if (port->irq_high > 0) {
+ if (enable)
+ irq_set_chained_handler_and_data(port->irq_high,
+ port->mx_irq_handler,
+ port);
+ else
+ irq_set_chained_handler_and_data(port->irq_high, NULL, NULL);
+ }
+}
+
+static int mxc_gpio_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct mxc_gpio_port *port;
+ int irq_count;
+ int irq_base;
+ int err;
+
+ port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->dev = &pdev->dev;
+ port->hwdata = device_get_match_data(&pdev->dev);
+
+ port->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(port->base))
+ return PTR_ERR(port->base);
+
+ irq_count = platform_irq_count(pdev);
+ if (irq_count < 0)
+ return irq_count;
+
+ if (irq_count > 1) {
+ port->irq_high = platform_get_irq(pdev, 1);
+ if (port->irq_high < 0)
+ port->irq_high = 0;
+ }
+
+ port->irq = platform_get_irq(pdev, 0);
+ if (port->irq < 0)
+ return port->irq;
+
+ /* the controller clock is optional */
+ port->clk = devm_clk_get_optional_enabled(&pdev->dev, NULL);
+ if (IS_ERR(port->clk))
+ return PTR_ERR(port->clk);
+
+ if (of_device_is_compatible(np, "fsl,imx7d-gpio"))
+ port->power_off = true;
+
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ /* disable the interrupt and clear the status */
+ writel(0, port->base + GPIO_IMR);
+ writel(~0, port->base + GPIO_ISR);
+
+ if (of_device_is_compatible(np, "fsl,imx21-gpio")) {
+ /*
+ * Setup one handler for all GPIO interrupts. Actually setting
+ * the handler is needed only once, but doing it for every port
+ * is more robust and easier.
+ */
+ port->irq_high = -1;
+ port->mx_irq_handler = mx2_gpio_irq_handler;
+ } else
+ port->mx_irq_handler = mx3_gpio_irq_handler;
+
+ mxc_update_irq_chained_handler(port, true);
+ err = bgpio_init(&port->gc, &pdev->dev, 4,
+ port->base + GPIO_PSR,
+ port->base + GPIO_DR, NULL,
+ port->base + GPIO_GDIR, NULL,
+ BGPIOF_READ_OUTPUT_REG_SET);
+ if (err)
+ goto out_bgio;
+
+ port->gc.request = mxc_gpio_request;
+ port->gc.free = mxc_gpio_free;
+ port->gc.to_irq = mxc_gpio_to_irq;
+ port->gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
+ pdev->id * 32;
+
+ err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port);
+ if (err)
+ goto out_bgio;
+
+ irq_base = devm_irq_alloc_descs(&pdev->dev, -1, 0, 32, numa_node_id());
+ if (irq_base < 0) {
+ err = irq_base;
+ goto out_bgio;
+ }
+
+ port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
+ &irq_domain_simple_ops, NULL);
+ if (!port->domain) {
+ err = -ENODEV;
+ goto out_bgio;
+ }
+
+ irq_domain_set_pm_device(port->domain, &pdev->dev);
+
+ /* gpio-mxc can be a generic irq chip */
+ err = mxc_gpio_init_gc(port, irq_base);
+ if (err < 0)
+ goto out_irqdomain_remove;
+
+ list_add_tail(&port->node, &mxc_gpio_ports);
+
+ platform_set_drvdata(pdev, port);
+ pm_runtime_put_autosuspend(&pdev->dev);
+
+ return 0;
+
+out_irqdomain_remove:
+ irq_domain_remove(port->domain);
+out_bgio:
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ dev_info(&pdev->dev, "%s failed with errno %d\n", __func__, err);
+ return err;
+}
+
+static void mxc_gpio_save_regs(struct mxc_gpio_port *port)
+{
+ if (!port->power_off)
+ return;
+
+ port->gpio_saved_reg.icr1 = readl(port->base + GPIO_ICR1);
+ port->gpio_saved_reg.icr2 = readl(port->base + GPIO_ICR2);
+ port->gpio_saved_reg.imr = readl(port->base + GPIO_IMR);
+ port->gpio_saved_reg.gdir = readl(port->base + GPIO_GDIR);
+ port->gpio_saved_reg.edge_sel = readl(port->base + GPIO_EDGE_SEL);
+ port->gpio_saved_reg.dr = readl(port->base + GPIO_DR);
+}
+
+static void mxc_gpio_restore_regs(struct mxc_gpio_port *port)
+{
+ if (!port->power_off)
+ return;
+
+ writel(port->gpio_saved_reg.icr1, port->base + GPIO_ICR1);
+ writel(port->gpio_saved_reg.icr2, port->base + GPIO_ICR2);
+ writel(port->gpio_saved_reg.imr, port->base + GPIO_IMR);
+ writel(port->gpio_saved_reg.gdir, port->base + GPIO_GDIR);
+ writel(port->gpio_saved_reg.edge_sel, port->base + GPIO_EDGE_SEL);
+ writel(port->gpio_saved_reg.dr, port->base + GPIO_DR);
+}
+
+static bool mxc_gpio_generic_config(struct mxc_gpio_port *port,
+ unsigned int offset, unsigned long conf)
+{
+ struct device_node *np = port->dev->of_node;
+
+ if (of_device_is_compatible(np, "fsl,imx8dxl-gpio") ||
+ of_device_is_compatible(np, "fsl,imx8qxp-gpio") ||
+ of_device_is_compatible(np, "fsl,imx8qm-gpio"))
+ return (gpiochip_generic_config(&port->gc, offset, conf) == 0);
+
+ return false;
+}
+
+static bool mxc_gpio_set_pad_wakeup(struct mxc_gpio_port *port, bool enable)
+{
+ unsigned long config;
+ bool ret = false;
+ int i, type;
+
+ static const u32 pad_type_map[] = {
+ IMX_SCU_WAKEUP_OFF, /* 0 */
+ IMX_SCU_WAKEUP_RISE_EDGE, /* IRQ_TYPE_EDGE_RISING */
+ IMX_SCU_WAKEUP_FALL_EDGE, /* IRQ_TYPE_EDGE_FALLING */
+ IMX_SCU_WAKEUP_FALL_EDGE, /* IRQ_TYPE_EDGE_BOTH */
+ IMX_SCU_WAKEUP_HIGH_LVL, /* IRQ_TYPE_LEVEL_HIGH */
+ IMX_SCU_WAKEUP_OFF, /* 5 */
+ IMX_SCU_WAKEUP_OFF, /* 6 */
+ IMX_SCU_WAKEUP_OFF, /* 7 */
+ IMX_SCU_WAKEUP_LOW_LVL, /* IRQ_TYPE_LEVEL_LOW */
+ };
+
+ for (i = 0; i < 32; i++) {
+ if ((port->wakeup_pads & (1 << i))) {
+ type = port->pad_type[i];
+ if (enable)
+ config = pad_type_map[type];
+ else
+ config = IMX_SCU_WAKEUP_OFF;
+ ret |= mxc_gpio_generic_config(port, i, config);
+ }
+ }
+
+ return ret;
+}
+
+static int mxc_gpio_runtime_suspend(struct device *dev)
+{
+ struct mxc_gpio_port *port = dev_get_drvdata(dev);
+
+ mxc_gpio_save_regs(port);
+ clk_disable_unprepare(port->clk);
+ mxc_update_irq_chained_handler(port, false);
+
+ return 0;
+}
+
+static int mxc_gpio_runtime_resume(struct device *dev)
+{
+ struct mxc_gpio_port *port = dev_get_drvdata(dev);
+ int ret;
+
+ mxc_update_irq_chained_handler(port, true);
+ ret = clk_prepare_enable(port->clk);
+ if (ret) {
+ mxc_update_irq_chained_handler(port, false);
+ return ret;
+ }
+
+ mxc_gpio_restore_regs(port);
+
+ return 0;
+}
+
+static int mxc_gpio_noirq_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mxc_gpio_port *port = platform_get_drvdata(pdev);
+
+ if (port->wakeup_pads > 0)
+ port->is_pad_wakeup = mxc_gpio_set_pad_wakeup(port, true);
+
+ return 0;
+}
+
+static int mxc_gpio_noirq_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mxc_gpio_port *port = platform_get_drvdata(pdev);
+
+ if (port->wakeup_pads > 0)
+ mxc_gpio_set_pad_wakeup(port, false);
+ port->is_pad_wakeup = false;
+
+ return 0;
+}
+
+static const struct dev_pm_ops mxc_gpio_dev_pm_ops = {
+ NOIRQ_SYSTEM_SLEEP_PM_OPS(mxc_gpio_noirq_suspend, mxc_gpio_noirq_resume)
+ RUNTIME_PM_OPS(mxc_gpio_runtime_suspend, mxc_gpio_runtime_resume, NULL)
+};
+
+static int mxc_gpio_syscore_suspend(void)
+{
+ struct mxc_gpio_port *port;
+ int ret;
+
+ /* walk through all ports */
+ list_for_each_entry(port, &mxc_gpio_ports, node) {
+ ret = clk_prepare_enable(port->clk);
+ if (ret)
+ return ret;
+ mxc_gpio_save_regs(port);
+ clk_disable_unprepare(port->clk);
+ }
+
+ return 0;
+}
+
+static void mxc_gpio_syscore_resume(void)
+{
+ struct mxc_gpio_port *port;
+ int ret;
+
+ /* walk through all ports */
+ list_for_each_entry(port, &mxc_gpio_ports, node) {
+ ret = clk_prepare_enable(port->clk);
+ if (ret) {
+ pr_err("mxc: failed to enable gpio clock %d\n", ret);
+ return;
+ }
+ mxc_gpio_restore_regs(port);
+ clk_disable_unprepare(port->clk);
+ }
+}
+
+static struct syscore_ops mxc_gpio_syscore_ops = {
+ .suspend = mxc_gpio_syscore_suspend,
+ .resume = mxc_gpio_syscore_resume,
+};
+
+static struct platform_driver mxc_gpio_driver = {
+ .driver = {
+ .name = "gpio-mxc",
+ .of_match_table = mxc_gpio_dt_ids,
+ .suppress_bind_attrs = true,
+ .pm = pm_ptr(&mxc_gpio_dev_pm_ops),
+ },
+ .probe = mxc_gpio_probe,
+};
+
+static int __init gpio_mxc_init(void)
+{
+ register_syscore_ops(&mxc_gpio_syscore_ops);
+
+ return platform_driver_register(&mxc_gpio_driver);
+}
+subsys_initcall(gpio_mxc_init);
+
+MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
+MODULE_DESCRIPTION("i.MX GPIO Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-mxs.c b/drivers/gpio/gpio-mxs.c
new file mode 100644
index 0000000000..024ad077e9
--- /dev/null
+++ b/drivers/gpio/gpio-mxs.c
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// MXS GPIO support. (c) 2008 Daniel Mack <daniel@caiaq.de>
+// Copyright 2008 Juergen Beisert, kernel@pengutronix.de
+//
+// Based on code from Freescale,
+// Copyright (C) 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+
+#define MXS_SET 0x4
+#define MXS_CLR 0x8
+
+#define PINCTRL_DOUT(p) ((is_imx23_gpio(p) ? 0x0500 : 0x0700) + (p->id) * 0x10)
+#define PINCTRL_DIN(p) ((is_imx23_gpio(p) ? 0x0600 : 0x0900) + (p->id) * 0x10)
+#define PINCTRL_DOE(p) ((is_imx23_gpio(p) ? 0x0700 : 0x0b00) + (p->id) * 0x10)
+#define PINCTRL_PIN2IRQ(p) ((is_imx23_gpio(p) ? 0x0800 : 0x1000) + (p->id) * 0x10)
+#define PINCTRL_IRQEN(p) ((is_imx23_gpio(p) ? 0x0900 : 0x1100) + (p->id) * 0x10)
+#define PINCTRL_IRQLEV(p) ((is_imx23_gpio(p) ? 0x0a00 : 0x1200) + (p->id) * 0x10)
+#define PINCTRL_IRQPOL(p) ((is_imx23_gpio(p) ? 0x0b00 : 0x1300) + (p->id) * 0x10)
+#define PINCTRL_IRQSTAT(p) ((is_imx23_gpio(p) ? 0x0c00 : 0x1400) + (p->id) * 0x10)
+
+#define GPIO_INT_FALL_EDGE 0x0
+#define GPIO_INT_LOW_LEV 0x1
+#define GPIO_INT_RISE_EDGE 0x2
+#define GPIO_INT_HIGH_LEV 0x3
+#define GPIO_INT_LEV_MASK (1 << 0)
+#define GPIO_INT_POL_MASK (1 << 1)
+
+enum mxs_gpio_id {
+ IMX23_GPIO,
+ IMX28_GPIO,
+};
+
+struct mxs_gpio_port {
+ void __iomem *base;
+ int id;
+ int irq;
+ struct irq_domain *domain;
+ struct gpio_chip gc;
+ struct device *dev;
+ enum mxs_gpio_id devid;
+ u32 both_edges;
+};
+
+static inline int is_imx23_gpio(struct mxs_gpio_port *port)
+{
+ return port->devid == IMX23_GPIO;
+}
+
+/* Note: This driver assumes 32 GPIOs are handled in one register */
+
+static int mxs_gpio_set_irq_type(struct irq_data *d, unsigned int type)
+{
+ u32 val;
+ u32 pin_mask = 1 << d->hwirq;
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
+ struct mxs_gpio_port *port = gc->private;
+ void __iomem *pin_addr;
+ int edge;
+
+ if (!(ct->type & type))
+ if (irq_setup_alt_chip(d, type))
+ return -EINVAL;
+
+ port->both_edges &= ~pin_mask;
+ switch (type) {
+ case IRQ_TYPE_EDGE_BOTH:
+ val = readl(port->base + PINCTRL_DIN(port)) & pin_mask;
+ if (val)
+ edge = GPIO_INT_FALL_EDGE;
+ else
+ edge = GPIO_INT_RISE_EDGE;
+ port->both_edges |= pin_mask;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ edge = GPIO_INT_RISE_EDGE;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ edge = GPIO_INT_FALL_EDGE;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ edge = GPIO_INT_LOW_LEV;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ edge = GPIO_INT_HIGH_LEV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set level or edge */
+ pin_addr = port->base + PINCTRL_IRQLEV(port);
+ if (edge & GPIO_INT_LEV_MASK) {
+ writel(pin_mask, pin_addr + MXS_SET);
+ writel(pin_mask, port->base + PINCTRL_IRQEN(port) + MXS_SET);
+ } else {
+ writel(pin_mask, pin_addr + MXS_CLR);
+ writel(pin_mask, port->base + PINCTRL_PIN2IRQ(port) + MXS_SET);
+ }
+
+ /* set polarity */
+ pin_addr = port->base + PINCTRL_IRQPOL(port);
+ if (edge & GPIO_INT_POL_MASK)
+ writel(pin_mask, pin_addr + MXS_SET);
+ else
+ writel(pin_mask, pin_addr + MXS_CLR);
+
+ writel(pin_mask, port->base + PINCTRL_IRQSTAT(port) + MXS_CLR);
+
+ return 0;
+}
+
+static void mxs_flip_edge(struct mxs_gpio_port *port, u32 gpio)
+{
+ u32 bit, val, edge;
+ void __iomem *pin_addr;
+
+ bit = 1 << gpio;
+
+ pin_addr = port->base + PINCTRL_IRQPOL(port);
+ val = readl(pin_addr);
+ edge = val & bit;
+
+ if (edge)
+ writel(bit, pin_addr + MXS_CLR);
+ else
+ writel(bit, pin_addr + MXS_SET);
+}
+
+/* MXS has one interrupt *per* gpio port */
+static void mxs_gpio_irq_handler(struct irq_desc *desc)
+{
+ u32 irq_stat;
+ struct mxs_gpio_port *port = irq_desc_get_handler_data(desc);
+
+ desc->irq_data.chip->irq_ack(&desc->irq_data);
+
+ irq_stat = readl(port->base + PINCTRL_IRQSTAT(port)) &
+ readl(port->base + PINCTRL_IRQEN(port));
+
+ while (irq_stat != 0) {
+ int irqoffset = fls(irq_stat) - 1;
+ if (port->both_edges & (1 << irqoffset))
+ mxs_flip_edge(port, irqoffset);
+
+ generic_handle_domain_irq(port->domain, irqoffset);
+ irq_stat &= ~(1 << irqoffset);
+ }
+}
+
+/*
+ * Set interrupt number "irq" in the GPIO as a wake-up source.
+ * While system is running, all registered GPIO interrupts need to have
+ * wake-up enabled. When system is suspended, only selected GPIO interrupts
+ * need to have wake-up enabled.
+ * @param irq interrupt source number
+ * @param enable enable as wake-up if equal to non-zero
+ * @return This function returns 0 on success.
+ */
+static int mxs_gpio_set_wake_irq(struct irq_data *d, unsigned int enable)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct mxs_gpio_port *port = gc->private;
+
+ if (enable)
+ enable_irq_wake(port->irq);
+ else
+ disable_irq_wake(port->irq);
+
+ return 0;
+}
+
+static int mxs_gpio_init_gc(struct mxs_gpio_port *port, int irq_base)
+{
+ struct irq_chip_generic *gc;
+ struct irq_chip_type *ct;
+ int rv;
+
+ gc = devm_irq_alloc_generic_chip(port->dev, "gpio-mxs", 2, irq_base,
+ port->base, handle_level_irq);
+ if (!gc)
+ return -ENOMEM;
+
+ gc->private = port;
+
+ ct = &gc->chip_types[0];
+ ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW;
+ 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;
+ ct->chip.irq_set_type = mxs_gpio_set_irq_type;
+ ct->chip.irq_set_wake = mxs_gpio_set_wake_irq;
+ ct->chip.flags = IRQCHIP_SET_TYPE_MASKED;
+ ct->regs.ack = PINCTRL_IRQSTAT(port) + MXS_CLR;
+ ct->regs.enable = PINCTRL_PIN2IRQ(port) + MXS_SET;
+ ct->regs.disable = PINCTRL_PIN2IRQ(port) + MXS_CLR;
+
+ ct = &gc->chip_types[1];
+ ct->type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
+ 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;
+ ct->chip.irq_set_type = mxs_gpio_set_irq_type;
+ ct->chip.irq_set_wake = mxs_gpio_set_wake_irq;
+ ct->chip.flags = IRQCHIP_SET_TYPE_MASKED;
+ ct->regs.ack = PINCTRL_IRQSTAT(port) + MXS_CLR;
+ ct->regs.enable = PINCTRL_IRQEN(port) + MXS_SET;
+ ct->regs.disable = PINCTRL_IRQEN(port) + MXS_CLR;
+ ct->handler = handle_level_irq;
+
+ rv = devm_irq_setup_generic_chip(port->dev, gc, IRQ_MSK(32),
+ IRQ_GC_INIT_NESTED_LOCK,
+ IRQ_NOREQUEST, 0);
+
+ return rv;
+}
+
+static int mxs_gpio_to_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ struct mxs_gpio_port *port = gpiochip_get_data(gc);
+
+ return irq_find_mapping(port->domain, offset);
+}
+
+static int mxs_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct mxs_gpio_port *port = gpiochip_get_data(gc);
+ u32 mask = 1 << offset;
+ u32 dir;
+
+ dir = readl(port->base + PINCTRL_DOE(port));
+ if (dir & mask)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static const struct of_device_id mxs_gpio_dt_ids[] = {
+ { .compatible = "fsl,imx23-gpio", .data = (void *) IMX23_GPIO, },
+ { .compatible = "fsl,imx28-gpio", .data = (void *) IMX28_GPIO, },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mxs_gpio_dt_ids);
+
+static int mxs_gpio_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *parent;
+ static void __iomem *base;
+ struct mxs_gpio_port *port;
+ int irq_base;
+ int err;
+
+ port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->id = of_alias_get_id(np, "gpio");
+ if (port->id < 0)
+ return port->id;
+ port->devid = (uintptr_t)of_device_get_match_data(&pdev->dev);
+ port->dev = &pdev->dev;
+ port->irq = platform_get_irq(pdev, 0);
+ if (port->irq < 0)
+ return port->irq;
+
+ /*
+ * map memory region only once, as all the gpio ports
+ * share the same one
+ */
+ if (!base) {
+ parent = of_get_parent(np);
+ base = of_iomap(parent, 0);
+ of_node_put(parent);
+ if (!base)
+ return -EADDRNOTAVAIL;
+ }
+ port->base = base;
+
+ /* initially disable the interrupts */
+ writel(0, port->base + PINCTRL_PIN2IRQ(port));
+ writel(0, port->base + PINCTRL_IRQEN(port));
+
+ /* clear address has to be used to clear IRQSTAT bits */
+ writel(~0U, port->base + PINCTRL_IRQSTAT(port) + MXS_CLR);
+
+ irq_base = devm_irq_alloc_descs(&pdev->dev, -1, 0, 32, numa_node_id());
+ if (irq_base < 0) {
+ err = irq_base;
+ goto out_iounmap;
+ }
+
+ port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
+ &irq_domain_simple_ops, NULL);
+ if (!port->domain) {
+ err = -ENODEV;
+ goto out_iounmap;
+ }
+
+ /* gpio-mxs can be a generic irq chip */
+ err = mxs_gpio_init_gc(port, irq_base);
+ if (err < 0)
+ goto out_irqdomain_remove;
+
+ /* setup one handler for each entry */
+ irq_set_chained_handler_and_data(port->irq, mxs_gpio_irq_handler,
+ port);
+
+ err = bgpio_init(&port->gc, &pdev->dev, 4,
+ port->base + PINCTRL_DIN(port),
+ port->base + PINCTRL_DOUT(port) + MXS_SET,
+ port->base + PINCTRL_DOUT(port) + MXS_CLR,
+ port->base + PINCTRL_DOE(port), NULL, 0);
+ if (err)
+ goto out_irqdomain_remove;
+
+ port->gc.to_irq = mxs_gpio_to_irq;
+ port->gc.get_direction = mxs_gpio_get_direction;
+ port->gc.base = port->id * 32;
+
+ err = gpiochip_add_data(&port->gc, port);
+ if (err)
+ goto out_irqdomain_remove;
+
+ return 0;
+
+out_irqdomain_remove:
+ irq_domain_remove(port->domain);
+out_iounmap:
+ iounmap(port->base);
+ return err;
+}
+
+static struct platform_driver mxs_gpio_driver = {
+ .driver = {
+ .name = "gpio-mxs",
+ .of_match_table = mxs_gpio_dt_ids,
+ .suppress_bind_attrs = true,
+ },
+ .probe = mxs_gpio_probe,
+};
+
+static int __init mxs_gpio_init(void)
+{
+ return platform_driver_register(&mxs_gpio_driver);
+}
+postcore_initcall(mxs_gpio_init);
+
+MODULE_AUTHOR("Freescale Semiconductor, "
+ "Daniel Mack <danielncaiaq.de>, "
+ "Juergen Beisert <kernel@pengutronix.de>");
+MODULE_DESCRIPTION("Freescale MXS GPIO");
diff --git a/drivers/gpio/gpio-octeon.c b/drivers/gpio/gpio-octeon.c
new file mode 100644
index 0000000000..afb0e8a791
--- /dev/null
+++ b/drivers/gpio/gpio-octeon.c
@@ -0,0 +1,137 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2011, 2012 Cavium Inc.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+
+#include <asm/octeon/octeon.h>
+#include <asm/octeon/cvmx-gpio-defs.h>
+
+#define RX_DAT 0x80
+#define TX_SET 0x88
+#define TX_CLEAR 0x90
+/*
+ * The address offset of the GPIO configuration register for a given
+ * line.
+ */
+static unsigned int bit_cfg_reg(unsigned int offset)
+{
+ /*
+ * The register stride is 8, with a discontinuity after the
+ * first 16.
+ */
+ if (offset < 16)
+ return 8 * offset;
+ else
+ return 8 * (offset - 16) + 0x100;
+}
+
+struct octeon_gpio {
+ struct gpio_chip chip;
+ u64 register_base;
+};
+
+static int octeon_gpio_dir_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct octeon_gpio *gpio = gpiochip_get_data(chip);
+
+ cvmx_write_csr(gpio->register_base + bit_cfg_reg(offset), 0);
+ return 0;
+}
+
+static void octeon_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct octeon_gpio *gpio = gpiochip_get_data(chip);
+ u64 mask = 1ull << offset;
+ u64 reg = gpio->register_base + (value ? TX_SET : TX_CLEAR);
+ cvmx_write_csr(reg, mask);
+}
+
+static int octeon_gpio_dir_out(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct octeon_gpio *gpio = gpiochip_get_data(chip);
+ union cvmx_gpio_bit_cfgx cfgx;
+
+ octeon_gpio_set(chip, offset, value);
+
+ cfgx.u64 = 0;
+ cfgx.s.tx_oe = 1;
+
+ cvmx_write_csr(gpio->register_base + bit_cfg_reg(offset), cfgx.u64);
+ return 0;
+}
+
+static int octeon_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct octeon_gpio *gpio = gpiochip_get_data(chip);
+ u64 read_bits = cvmx_read_csr(gpio->register_base + RX_DAT);
+
+ return ((1ull << offset) & read_bits) != 0;
+}
+
+static int octeon_gpio_probe(struct platform_device *pdev)
+{
+ struct octeon_gpio *gpio;
+ struct gpio_chip *chip;
+ void __iomem *reg_base;
+ int err = 0;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+ chip = &gpio->chip;
+
+ reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(reg_base))
+ return PTR_ERR(reg_base);
+
+ gpio->register_base = (u64)reg_base;
+ pdev->dev.platform_data = chip;
+ chip->label = "octeon-gpio";
+ chip->parent = &pdev->dev;
+ chip->owner = THIS_MODULE;
+ chip->base = 0;
+ chip->can_sleep = false;
+ chip->ngpio = 20;
+ chip->direction_input = octeon_gpio_dir_in;
+ chip->get = octeon_gpio_get;
+ chip->direction_output = octeon_gpio_dir_out;
+ chip->set = octeon_gpio_set;
+ err = devm_gpiochip_add_data(&pdev->dev, chip, gpio);
+ if (err)
+ return err;
+
+ dev_info(&pdev->dev, "OCTEON GPIO driver probed.\n");
+ return 0;
+}
+
+static const struct of_device_id octeon_gpio_match[] = {
+ {
+ .compatible = "cavium,octeon-3860-gpio",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, octeon_gpio_match);
+
+static struct platform_driver octeon_gpio_driver = {
+ .driver = {
+ .name = "octeon_gpio",
+ .of_match_table = octeon_gpio_match,
+ },
+ .probe = octeon_gpio_probe,
+};
+
+module_platform_driver(octeon_gpio_driver);
+
+MODULE_DESCRIPTION("Cavium Inc. OCTEON GPIO Driver");
+MODULE_AUTHOR("David Daney");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-omap.c b/drivers/gpio/gpio-omap.c
new file mode 100644
index 0000000000..a927680c66
--- /dev/null
+++ b/drivers/gpio/gpio-omap.c
@@ -0,0 +1,1590 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Support functions for OMAP GPIO
+ *
+ * Copyright (C) 2003-2005 Nokia Corporation
+ * Written by Juha Yrjölä <juha.yrjola@nokia.com>
+ *
+ * Copyright (C) 2009 Texas Instruments
+ * Added OMAP4 support - Santosh Shilimkar <santosh.shilimkar@ti.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/seq_file.h>
+#include <linux/syscore_ops.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/cpu_pm.h>
+#include <linux/device.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm.h>
+#include <linux/of.h>
+#include <linux/gpio/driver.h>
+#include <linux/bitops.h>
+#include <linux/platform_data/gpio-omap.h>
+
+#define OMAP4_GPIO_DEBOUNCINGTIME_MASK 0xFF
+
+struct gpio_regs {
+ u32 sysconfig;
+ u32 irqenable1;
+ u32 irqenable2;
+ u32 wake_en;
+ u32 ctrl;
+ u32 oe;
+ u32 leveldetect0;
+ u32 leveldetect1;
+ u32 risingdetect;
+ u32 fallingdetect;
+ u32 dataout;
+ u32 debounce;
+ u32 debounce_en;
+};
+
+struct gpio_bank {
+ void __iomem *base;
+ const struct omap_gpio_reg_offs *regs;
+ struct device *dev;
+
+ int irq;
+ u32 non_wakeup_gpios;
+ u32 enabled_non_wakeup_gpios;
+ struct gpio_regs context;
+ u32 saved_datain;
+ u32 level_mask;
+ u32 toggle_mask;
+ raw_spinlock_t lock;
+ raw_spinlock_t wa_lock;
+ struct gpio_chip chip;
+ struct clk *dbck;
+ struct notifier_block nb;
+ unsigned int is_suspended:1;
+ unsigned int needs_resume:1;
+ u32 mod_usage;
+ u32 irq_usage;
+ u32 dbck_enable_mask;
+ bool dbck_enabled;
+ bool is_mpuio;
+ bool dbck_flag;
+ bool loses_context;
+ bool context_valid;
+ int stride;
+ u32 width;
+ int context_loss_count;
+
+ void (*set_dataout)(struct gpio_bank *bank, unsigned gpio, int enable);
+ int (*get_context_loss_count)(struct device *dev);
+};
+
+#define GPIO_MOD_CTRL_BIT BIT(0)
+
+#define BANK_USED(bank) (bank->mod_usage || bank->irq_usage)
+#define LINE_USED(line, offset) (line & (BIT(offset)))
+
+static void omap_gpio_unmask_irq(struct irq_data *d);
+
+static inline struct gpio_bank *omap_irq_data_get_bank(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ return gpiochip_get_data(chip);
+}
+
+static inline u32 omap_gpio_rmw(void __iomem *reg, u32 mask, bool set)
+{
+ u32 val = readl_relaxed(reg);
+
+ if (set)
+ val |= mask;
+ else
+ val &= ~mask;
+
+ writel_relaxed(val, reg);
+
+ return val;
+}
+
+static void omap_set_gpio_direction(struct gpio_bank *bank, int gpio,
+ int is_input)
+{
+ bank->context.oe = omap_gpio_rmw(bank->base + bank->regs->direction,
+ BIT(gpio), is_input);
+}
+
+
+/* set data out value using dedicate set/clear register */
+static void omap_set_gpio_dataout_reg(struct gpio_bank *bank, unsigned offset,
+ int enable)
+{
+ void __iomem *reg = bank->base;
+ u32 l = BIT(offset);
+
+ if (enable) {
+ reg += bank->regs->set_dataout;
+ bank->context.dataout |= l;
+ } else {
+ reg += bank->regs->clr_dataout;
+ bank->context.dataout &= ~l;
+ }
+
+ writel_relaxed(l, reg);
+}
+
+/* set data out value using mask register */
+static void omap_set_gpio_dataout_mask(struct gpio_bank *bank, unsigned offset,
+ int enable)
+{
+ bank->context.dataout = omap_gpio_rmw(bank->base + bank->regs->dataout,
+ BIT(offset), enable);
+}
+
+static inline void omap_gpio_dbck_enable(struct gpio_bank *bank)
+{
+ if (bank->dbck_enable_mask && !bank->dbck_enabled) {
+ clk_enable(bank->dbck);
+ bank->dbck_enabled = true;
+
+ writel_relaxed(bank->dbck_enable_mask,
+ bank->base + bank->regs->debounce_en);
+ }
+}
+
+static inline void omap_gpio_dbck_disable(struct gpio_bank *bank)
+{
+ if (bank->dbck_enable_mask && bank->dbck_enabled) {
+ /*
+ * Disable debounce before cutting it's clock. If debounce is
+ * enabled but the clock is not, GPIO module seems to be unable
+ * to detect events and generate interrupts at least on OMAP3.
+ */
+ writel_relaxed(0, bank->base + bank->regs->debounce_en);
+
+ clk_disable(bank->dbck);
+ bank->dbck_enabled = false;
+ }
+}
+
+/**
+ * omap2_set_gpio_debounce - low level gpio debounce time
+ * @bank: the gpio bank we're acting upon
+ * @offset: the gpio number on this @bank
+ * @debounce: debounce time to use
+ *
+ * OMAP's debounce time is in 31us steps
+ * <debounce time> = (GPIO_DEBOUNCINGTIME[7:0].DEBOUNCETIME + 1) x 31
+ * so we need to convert and round up to the closest unit.
+ *
+ * Return: 0 on success, negative error otherwise.
+ */
+static int omap2_set_gpio_debounce(struct gpio_bank *bank, unsigned offset,
+ unsigned debounce)
+{
+ u32 val;
+ u32 l;
+ bool enable = !!debounce;
+
+ if (!bank->dbck_flag)
+ return -ENOTSUPP;
+
+ if (enable) {
+ debounce = DIV_ROUND_UP(debounce, 31) - 1;
+ if ((debounce & OMAP4_GPIO_DEBOUNCINGTIME_MASK) != debounce)
+ return -EINVAL;
+ }
+
+ l = BIT(offset);
+
+ clk_enable(bank->dbck);
+ writel_relaxed(debounce, bank->base + bank->regs->debounce);
+
+ val = omap_gpio_rmw(bank->base + bank->regs->debounce_en, l, enable);
+ bank->dbck_enable_mask = val;
+
+ clk_disable(bank->dbck);
+ /*
+ * Enable debounce clock per module.
+ * This call is mandatory because in omap_gpio_request() when
+ * *_runtime_get_sync() is called, _gpio_dbck_enable() within
+ * runtime callbck fails to turn on dbck because dbck_enable_mask
+ * used within _gpio_dbck_enable() is still not initialized at
+ * that point. Therefore we have to enable dbck here.
+ */
+ omap_gpio_dbck_enable(bank);
+ if (bank->dbck_enable_mask) {
+ bank->context.debounce = debounce;
+ bank->context.debounce_en = val;
+ }
+
+ return 0;
+}
+
+/**
+ * omap_clear_gpio_debounce - clear debounce settings for a gpio
+ * @bank: the gpio bank we're acting upon
+ * @offset: the gpio number on this @bank
+ *
+ * If a gpio is using debounce, then clear the debounce enable bit and if
+ * this is the only gpio in this bank using debounce, then clear the debounce
+ * time too. The debounce clock will also be disabled when calling this function
+ * if this is the only gpio in the bank using debounce.
+ */
+static void omap_clear_gpio_debounce(struct gpio_bank *bank, unsigned offset)
+{
+ u32 gpio_bit = BIT(offset);
+
+ if (!bank->dbck_flag)
+ return;
+
+ if (!(bank->dbck_enable_mask & gpio_bit))
+ return;
+
+ bank->dbck_enable_mask &= ~gpio_bit;
+ bank->context.debounce_en &= ~gpio_bit;
+ writel_relaxed(bank->context.debounce_en,
+ bank->base + bank->regs->debounce_en);
+
+ if (!bank->dbck_enable_mask) {
+ bank->context.debounce = 0;
+ writel_relaxed(bank->context.debounce, bank->base +
+ bank->regs->debounce);
+ clk_disable(bank->dbck);
+ bank->dbck_enabled = false;
+ }
+}
+
+/*
+ * Off mode wake-up capable GPIOs in bank(s) that are in the wakeup domain.
+ * See TRM section for GPIO for "Wake-Up Generation" for the list of GPIOs
+ * in wakeup domain. If bank->non_wakeup_gpios is not configured, assume none
+ * are capable waking up the system from off mode.
+ */
+static bool omap_gpio_is_off_wakeup_capable(struct gpio_bank *bank, u32 gpio_mask)
+{
+ u32 no_wake = bank->non_wakeup_gpios;
+
+ if (no_wake)
+ return !!(~no_wake & gpio_mask);
+
+ return false;
+}
+
+static inline void omap_set_gpio_trigger(struct gpio_bank *bank, int gpio,
+ unsigned trigger)
+{
+ void __iomem *base = bank->base;
+ u32 gpio_bit = BIT(gpio);
+
+ omap_gpio_rmw(base + bank->regs->leveldetect0, gpio_bit,
+ trigger & IRQ_TYPE_LEVEL_LOW);
+ omap_gpio_rmw(base + bank->regs->leveldetect1, gpio_bit,
+ trigger & IRQ_TYPE_LEVEL_HIGH);
+
+ /*
+ * We need the edge detection enabled for to allow the GPIO block
+ * to be woken from idle state. Set the appropriate edge detection
+ * in addition to the level detection.
+ */
+ omap_gpio_rmw(base + bank->regs->risingdetect, gpio_bit,
+ trigger & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_LEVEL_HIGH));
+ omap_gpio_rmw(base + bank->regs->fallingdetect, gpio_bit,
+ trigger & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_LEVEL_LOW));
+
+ bank->context.leveldetect0 =
+ readl_relaxed(bank->base + bank->regs->leveldetect0);
+ bank->context.leveldetect1 =
+ readl_relaxed(bank->base + bank->regs->leveldetect1);
+ bank->context.risingdetect =
+ readl_relaxed(bank->base + bank->regs->risingdetect);
+ bank->context.fallingdetect =
+ readl_relaxed(bank->base + bank->regs->fallingdetect);
+
+ bank->level_mask = bank->context.leveldetect0 |
+ bank->context.leveldetect1;
+
+ /* This part needs to be executed always for OMAP{34xx, 44xx} */
+ if (!bank->regs->irqctrl && !omap_gpio_is_off_wakeup_capable(bank, gpio)) {
+ /*
+ * Log the edge gpio and manually trigger the IRQ
+ * after resume if the input level changes
+ * to avoid irq lost during PER RET/OFF mode
+ * Applies for omap2 non-wakeup gpio and all omap3 gpios
+ */
+ if (trigger & IRQ_TYPE_EDGE_BOTH)
+ bank->enabled_non_wakeup_gpios |= gpio_bit;
+ else
+ bank->enabled_non_wakeup_gpios &= ~gpio_bit;
+ }
+}
+
+/*
+ * This only applies to chips that can't do both rising and falling edge
+ * detection at once. For all other chips, this function is a noop.
+ */
+static void omap_toggle_gpio_edge_triggering(struct gpio_bank *bank, int gpio)
+{
+ if (IS_ENABLED(CONFIG_ARCH_OMAP1) && bank->regs->irqctrl) {
+ void __iomem *reg = bank->base + bank->regs->irqctrl;
+
+ writel_relaxed(readl_relaxed(reg) ^ BIT(gpio), reg);
+ }
+}
+
+static int omap_set_gpio_triggering(struct gpio_bank *bank, int gpio,
+ unsigned trigger)
+{
+ void __iomem *reg = bank->base;
+ u32 l = 0;
+
+ if (bank->regs->leveldetect0 && bank->regs->wkup_en) {
+ omap_set_gpio_trigger(bank, gpio, trigger);
+ } else if (bank->regs->irqctrl) {
+ reg += bank->regs->irqctrl;
+
+ l = readl_relaxed(reg);
+ if ((trigger & IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH)
+ bank->toggle_mask |= BIT(gpio);
+ if (trigger & IRQ_TYPE_EDGE_RISING)
+ l |= BIT(gpio);
+ else if (trigger & IRQ_TYPE_EDGE_FALLING)
+ l &= ~(BIT(gpio));
+ else
+ return -EINVAL;
+
+ writel_relaxed(l, reg);
+ } else if (bank->regs->edgectrl1) {
+ if (gpio & 0x08)
+ reg += bank->regs->edgectrl2;
+ else
+ reg += bank->regs->edgectrl1;
+
+ gpio &= 0x07;
+ l = readl_relaxed(reg);
+ l &= ~(3 << (gpio << 1));
+ if (trigger & IRQ_TYPE_EDGE_RISING)
+ l |= 2 << (gpio << 1);
+ if (trigger & IRQ_TYPE_EDGE_FALLING)
+ l |= BIT(gpio << 1);
+ writel_relaxed(l, reg);
+ }
+ return 0;
+}
+
+static void omap_enable_gpio_module(struct gpio_bank *bank, unsigned offset)
+{
+ if (bank->regs->pinctrl) {
+ void __iomem *reg = bank->base + bank->regs->pinctrl;
+
+ /* Claim the pin for MPU */
+ writel_relaxed(readl_relaxed(reg) | (BIT(offset)), reg);
+ }
+
+ if (bank->regs->ctrl && !BANK_USED(bank)) {
+ void __iomem *reg = bank->base + bank->regs->ctrl;
+ u32 ctrl;
+
+ ctrl = readl_relaxed(reg);
+ /* Module is enabled, clocks are not gated */
+ ctrl &= ~GPIO_MOD_CTRL_BIT;
+ writel_relaxed(ctrl, reg);
+ bank->context.ctrl = ctrl;
+ }
+}
+
+static void omap_disable_gpio_module(struct gpio_bank *bank, unsigned offset)
+{
+ if (bank->regs->ctrl && !BANK_USED(bank)) {
+ void __iomem *reg = bank->base + bank->regs->ctrl;
+ u32 ctrl;
+
+ ctrl = readl_relaxed(reg);
+ /* Module is disabled, clocks are gated */
+ ctrl |= GPIO_MOD_CTRL_BIT;
+ writel_relaxed(ctrl, reg);
+ bank->context.ctrl = ctrl;
+ }
+}
+
+static int omap_gpio_is_input(struct gpio_bank *bank, unsigned offset)
+{
+ void __iomem *reg = bank->base + bank->regs->direction;
+
+ return readl_relaxed(reg) & BIT(offset);
+}
+
+static void omap_gpio_init_irq(struct gpio_bank *bank, unsigned offset)
+{
+ if (!LINE_USED(bank->mod_usage, offset)) {
+ omap_enable_gpio_module(bank, offset);
+ omap_set_gpio_direction(bank, offset, 1);
+ }
+ bank->irq_usage |= BIT(offset);
+}
+
+static int omap_gpio_irq_type(struct irq_data *d, unsigned type)
+{
+ struct gpio_bank *bank = omap_irq_data_get_bank(d);
+ int retval;
+ unsigned long flags;
+ unsigned offset = d->hwirq;
+
+ if (type & ~IRQ_TYPE_SENSE_MASK)
+ return -EINVAL;
+
+ if (!bank->regs->leveldetect0 &&
+ (type & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH)))
+ return -EINVAL;
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ retval = omap_set_gpio_triggering(bank, offset, type);
+ if (retval) {
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+ goto error;
+ }
+ omap_gpio_init_irq(bank, offset);
+ if (!omap_gpio_is_input(bank, offset)) {
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+ retval = -EINVAL;
+ goto error;
+ }
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+
+ if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
+ irq_set_handler_locked(d, handle_level_irq);
+ else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
+ /*
+ * Edge IRQs are already cleared/acked in irq_handler and
+ * not need to be masked, as result handle_edge_irq()
+ * logic is excessed here and may cause lose of interrupts.
+ * So just use handle_simple_irq.
+ */
+ irq_set_handler_locked(d, handle_simple_irq);
+
+ return 0;
+
+error:
+ return retval;
+}
+
+static void omap_clear_gpio_irqbank(struct gpio_bank *bank, int gpio_mask)
+{
+ void __iomem *reg = bank->base;
+
+ reg += bank->regs->irqstatus;
+ writel_relaxed(gpio_mask, reg);
+
+ /* Workaround for clearing DSP GPIO interrupts to allow retention */
+ if (bank->regs->irqstatus2) {
+ reg = bank->base + bank->regs->irqstatus2;
+ writel_relaxed(gpio_mask, reg);
+ }
+
+ /* Flush posted write for the irq status to avoid spurious interrupts */
+ readl_relaxed(reg);
+}
+
+static inline void omap_clear_gpio_irqstatus(struct gpio_bank *bank,
+ unsigned offset)
+{
+ omap_clear_gpio_irqbank(bank, BIT(offset));
+}
+
+static u32 omap_get_gpio_irqbank_mask(struct gpio_bank *bank)
+{
+ void __iomem *reg = bank->base;
+ u32 l;
+ u32 mask = (BIT(bank->width)) - 1;
+
+ reg += bank->regs->irqenable;
+ l = readl_relaxed(reg);
+ if (bank->regs->irqenable_inv)
+ l = ~l;
+ l &= mask;
+ return l;
+}
+
+static inline void omap_set_gpio_irqenable(struct gpio_bank *bank,
+ unsigned offset, int enable)
+{
+ void __iomem *reg = bank->base;
+ u32 gpio_mask = BIT(offset);
+
+ if (bank->regs->set_irqenable && bank->regs->clr_irqenable) {
+ if (enable) {
+ reg += bank->regs->set_irqenable;
+ bank->context.irqenable1 |= gpio_mask;
+ } else {
+ reg += bank->regs->clr_irqenable;
+ bank->context.irqenable1 &= ~gpio_mask;
+ }
+ writel_relaxed(gpio_mask, reg);
+ } else {
+ bank->context.irqenable1 =
+ omap_gpio_rmw(reg + bank->regs->irqenable, gpio_mask,
+ enable ^ bank->regs->irqenable_inv);
+ }
+
+ /*
+ * Program GPIO wakeup along with IRQ enable to satisfy OMAP4430 TRM
+ * note requiring correlation between the IRQ enable registers and
+ * the wakeup registers. In any case, we want wakeup from idle
+ * enabled for the GPIOs which support this feature.
+ */
+ if (bank->regs->wkup_en &&
+ (bank->regs->edgectrl1 || !(bank->non_wakeup_gpios & gpio_mask))) {
+ bank->context.wake_en =
+ omap_gpio_rmw(bank->base + bank->regs->wkup_en,
+ gpio_mask, enable);
+ }
+}
+
+/* Use disable_irq_wake() and enable_irq_wake() functions from drivers */
+static int omap_gpio_wake_enable(struct irq_data *d, unsigned int enable)
+{
+ struct gpio_bank *bank = omap_irq_data_get_bank(d);
+
+ return irq_set_irq_wake(bank->irq, enable);
+}
+
+/*
+ * We need to unmask the GPIO bank interrupt as soon as possible to
+ * avoid missing GPIO interrupts for other lines in the bank.
+ * Then we need to mask-read-clear-unmask the triggered GPIO lines
+ * in the bank to avoid missing nested interrupts for a GPIO line.
+ * If we wait to unmask individual GPIO lines in the bank after the
+ * line's interrupt handler has been run, we may miss some nested
+ * interrupts.
+ */
+static irqreturn_t omap_gpio_irq_handler(int irq, void *gpiobank)
+{
+ void __iomem *isr_reg = NULL;
+ u32 enabled, isr, edge;
+ unsigned int bit;
+ struct gpio_bank *bank = gpiobank;
+ unsigned long wa_lock_flags;
+ unsigned long lock_flags;
+
+ isr_reg = bank->base + bank->regs->irqstatus;
+ if (WARN_ON(!isr_reg))
+ goto exit;
+
+ if (WARN_ONCE(!pm_runtime_active(bank->chip.parent),
+ "gpio irq%i while runtime suspended?\n", irq))
+ return IRQ_NONE;
+
+ while (1) {
+ raw_spin_lock_irqsave(&bank->lock, lock_flags);
+
+ enabled = omap_get_gpio_irqbank_mask(bank);
+ isr = readl_relaxed(isr_reg) & enabled;
+
+ /*
+ * Clear edge sensitive interrupts before calling handler(s)
+ * so subsequent edge transitions are not missed while the
+ * handlers are running.
+ */
+ edge = isr & ~bank->level_mask;
+ if (edge)
+ omap_clear_gpio_irqbank(bank, edge);
+
+ raw_spin_unlock_irqrestore(&bank->lock, lock_flags);
+
+ if (!isr)
+ break;
+
+ while (isr) {
+ bit = __ffs(isr);
+ isr &= ~(BIT(bit));
+
+ raw_spin_lock_irqsave(&bank->lock, lock_flags);
+ /*
+ * Some chips can't respond to both rising and falling
+ * at the same time. If this irq was requested with
+ * both flags, we need to flip the ICR data for the IRQ
+ * to respond to the IRQ for the opposite direction.
+ * This will be indicated in the bank toggle_mask.
+ */
+ if (bank->toggle_mask & (BIT(bit)))
+ omap_toggle_gpio_edge_triggering(bank, bit);
+
+ raw_spin_unlock_irqrestore(&bank->lock, lock_flags);
+
+ raw_spin_lock_irqsave(&bank->wa_lock, wa_lock_flags);
+
+ generic_handle_domain_irq(bank->chip.irq.domain, bit);
+
+ raw_spin_unlock_irqrestore(&bank->wa_lock,
+ wa_lock_flags);
+ }
+ }
+exit:
+ return IRQ_HANDLED;
+}
+
+static unsigned int omap_gpio_irq_startup(struct irq_data *d)
+{
+ struct gpio_bank *bank = omap_irq_data_get_bank(d);
+ unsigned long flags;
+ unsigned offset = d->hwirq;
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+
+ if (!LINE_USED(bank->mod_usage, offset))
+ omap_set_gpio_direction(bank, offset, 1);
+ omap_enable_gpio_module(bank, offset);
+ bank->irq_usage |= BIT(offset);
+
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+ omap_gpio_unmask_irq(d);
+
+ return 0;
+}
+
+static void omap_gpio_irq_shutdown(struct irq_data *d)
+{
+ struct gpio_bank *bank = omap_irq_data_get_bank(d);
+ unsigned long flags;
+ unsigned offset = d->hwirq;
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ bank->irq_usage &= ~(BIT(offset));
+ omap_set_gpio_triggering(bank, offset, IRQ_TYPE_NONE);
+ omap_clear_gpio_irqstatus(bank, offset);
+ omap_set_gpio_irqenable(bank, offset, 0);
+ if (!LINE_USED(bank->mod_usage, offset))
+ omap_clear_gpio_debounce(bank, offset);
+ omap_disable_gpio_module(bank, offset);
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+}
+
+static void omap_gpio_irq_bus_lock(struct irq_data *data)
+{
+ struct gpio_bank *bank = omap_irq_data_get_bank(data);
+
+ pm_runtime_get_sync(bank->chip.parent);
+}
+
+static void gpio_irq_bus_sync_unlock(struct irq_data *data)
+{
+ struct gpio_bank *bank = omap_irq_data_get_bank(data);
+
+ pm_runtime_put(bank->chip.parent);
+}
+
+static void omap_gpio_mask_irq(struct irq_data *d)
+{
+ struct gpio_bank *bank = omap_irq_data_get_bank(d);
+ unsigned offset = d->hwirq;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ omap_set_gpio_triggering(bank, offset, IRQ_TYPE_NONE);
+ omap_set_gpio_irqenable(bank, offset, 0);
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+ gpiochip_disable_irq(&bank->chip, offset);
+}
+
+static void omap_gpio_unmask_irq(struct irq_data *d)
+{
+ struct gpio_bank *bank = omap_irq_data_get_bank(d);
+ unsigned offset = d->hwirq;
+ u32 trigger = irqd_get_trigger_type(d);
+ unsigned long flags;
+
+ gpiochip_enable_irq(&bank->chip, offset);
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ omap_set_gpio_irqenable(bank, offset, 1);
+
+ /*
+ * For level-triggered GPIOs, clearing must be done after the source
+ * is cleared, thus after the handler has run. OMAP4 needs this done
+ * 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))
+ omap_clear_gpio_irqstatus(bank, offset);
+
+ if (trigger)
+ omap_set_gpio_triggering(bank, offset, trigger);
+
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+}
+
+static void omap_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p)
+{
+ struct gpio_bank *bank = omap_irq_data_get_bank(d);
+
+ seq_printf(p, dev_name(bank->dev));
+}
+
+static const struct irq_chip omap_gpio_irq_chip = {
+ .irq_startup = omap_gpio_irq_startup,
+ .irq_shutdown = omap_gpio_irq_shutdown,
+ .irq_mask = omap_gpio_mask_irq,
+ .irq_unmask = omap_gpio_unmask_irq,
+ .irq_set_type = omap_gpio_irq_type,
+ .irq_set_wake = omap_gpio_wake_enable,
+ .irq_bus_lock = omap_gpio_irq_bus_lock,
+ .irq_bus_sync_unlock = gpio_irq_bus_sync_unlock,
+ .irq_print_chip = omap_gpio_irq_print_chip,
+ .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static const struct irq_chip omap_gpio_irq_chip_nowake = {
+ .irq_startup = omap_gpio_irq_startup,
+ .irq_shutdown = omap_gpio_irq_shutdown,
+ .irq_mask = omap_gpio_mask_irq,
+ .irq_unmask = omap_gpio_unmask_irq,
+ .irq_set_type = omap_gpio_irq_type,
+ .irq_bus_lock = omap_gpio_irq_bus_lock,
+ .irq_bus_sync_unlock = gpio_irq_bus_sync_unlock,
+ .irq_print_chip = omap_gpio_irq_print_chip,
+ .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+/*---------------------------------------------------------------------*/
+
+static int omap_mpuio_suspend_noirq(struct device *dev)
+{
+ struct gpio_bank *bank = dev_get_drvdata(dev);
+ void __iomem *mask_reg = bank->base +
+ OMAP_MPUIO_GPIO_MASKIT / bank->stride;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ writel_relaxed(0xffff & ~bank->context.wake_en, mask_reg);
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+
+ return 0;
+}
+
+static int omap_mpuio_resume_noirq(struct device *dev)
+{
+ struct gpio_bank *bank = dev_get_drvdata(dev);
+ void __iomem *mask_reg = bank->base +
+ OMAP_MPUIO_GPIO_MASKIT / bank->stride;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ writel_relaxed(bank->context.wake_en, mask_reg);
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+
+ return 0;
+}
+
+static const struct dev_pm_ops omap_mpuio_dev_pm_ops = {
+ .suspend_noirq = omap_mpuio_suspend_noirq,
+ .resume_noirq = omap_mpuio_resume_noirq,
+};
+
+/* use platform_driver for this. */
+static struct platform_driver omap_mpuio_driver = {
+ .driver = {
+ .name = "mpuio",
+ .pm = &omap_mpuio_dev_pm_ops,
+ },
+};
+
+static struct platform_device omap_mpuio_device = {
+ .name = "mpuio",
+ .id = -1,
+ .dev = {
+ .driver = &omap_mpuio_driver.driver,
+ }
+ /* could list the /proc/iomem resources */
+};
+
+static inline void omap_mpuio_init(struct gpio_bank *bank)
+{
+ platform_set_drvdata(&omap_mpuio_device, bank);
+
+ if (platform_driver_register(&omap_mpuio_driver) == 0)
+ (void) platform_device_register(&omap_mpuio_device);
+}
+
+/*---------------------------------------------------------------------*/
+
+static int omap_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_bank *bank = gpiochip_get_data(chip);
+ unsigned long flags;
+
+ pm_runtime_get_sync(chip->parent);
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ omap_enable_gpio_module(bank, offset);
+ bank->mod_usage |= BIT(offset);
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+
+ return 0;
+}
+
+static void omap_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_bank *bank = gpiochip_get_data(chip);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ bank->mod_usage &= ~(BIT(offset));
+ if (!LINE_USED(bank->irq_usage, offset)) {
+ omap_set_gpio_direction(bank, offset, 1);
+ omap_clear_gpio_debounce(bank, offset);
+ }
+ omap_disable_gpio_module(bank, offset);
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+
+ pm_runtime_put(chip->parent);
+}
+
+static int omap_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_bank *bank = gpiochip_get_data(chip);
+
+ if (readl_relaxed(bank->base + bank->regs->direction) & BIT(offset))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int omap_gpio_input(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_bank *bank;
+ unsigned long flags;
+
+ bank = gpiochip_get_data(chip);
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ omap_set_gpio_direction(bank, offset, 1);
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+ return 0;
+}
+
+static int omap_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_bank *bank = gpiochip_get_data(chip);
+ void __iomem *reg;
+
+ if (omap_gpio_is_input(bank, offset))
+ reg = bank->base + bank->regs->datain;
+ else
+ reg = bank->base + bank->regs->dataout;
+
+ return (readl_relaxed(reg) & BIT(offset)) != 0;
+}
+
+static int omap_gpio_output(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct gpio_bank *bank;
+ unsigned long flags;
+
+ bank = gpiochip_get_data(chip);
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ bank->set_dataout(bank, offset, value);
+ omap_set_gpio_direction(bank, offset, 0);
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+ return 0;
+}
+
+static int omap_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct gpio_bank *bank = gpiochip_get_data(chip);
+ void __iomem *base = bank->base;
+ u32 direction, m, val = 0;
+
+ direction = readl_relaxed(base + bank->regs->direction);
+
+ m = direction & *mask;
+ if (m)
+ val |= readl_relaxed(base + bank->regs->datain) & m;
+
+ m = ~direction & *mask;
+ if (m)
+ val |= readl_relaxed(base + bank->regs->dataout) & m;
+
+ *bits = val;
+
+ return 0;
+}
+
+static int omap_gpio_debounce(struct gpio_chip *chip, unsigned offset,
+ unsigned debounce)
+{
+ struct gpio_bank *bank;
+ unsigned long flags;
+ int ret;
+
+ bank = gpiochip_get_data(chip);
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ ret = omap2_set_gpio_debounce(bank, offset, debounce);
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+
+ if (ret)
+ dev_info(chip->parent,
+ "Could not set line %u debounce to %u microseconds (%d)",
+ offset, debounce, ret);
+
+ return ret;
+}
+
+static int omap_gpio_set_config(struct gpio_chip *chip, unsigned offset,
+ unsigned long config)
+{
+ u32 debounce;
+ int ret = -ENOTSUPP;
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ case PIN_CONFIG_BIAS_PULL_UP:
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ ret = gpiochip_generic_config(chip, offset, config);
+ break;
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ debounce = pinconf_to_config_argument(config);
+ ret = omap_gpio_debounce(chip, offset, debounce);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void omap_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct gpio_bank *bank;
+ unsigned long flags;
+
+ bank = gpiochip_get_data(chip);
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ bank->set_dataout(bank, offset, value);
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+}
+
+static void omap_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct gpio_bank *bank = gpiochip_get_data(chip);
+ void __iomem *reg = bank->base + bank->regs->dataout;
+ unsigned long flags;
+ u32 l;
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ l = (readl_relaxed(reg) & ~*mask) | (*bits & *mask);
+ writel_relaxed(l, reg);
+ bank->context.dataout = l;
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+}
+
+/*---------------------------------------------------------------------*/
+
+static void omap_gpio_show_rev(struct gpio_bank *bank)
+{
+ static bool called;
+ u32 rev;
+
+ if (called || bank->regs->revision == USHRT_MAX)
+ return;
+
+ rev = readw_relaxed(bank->base + bank->regs->revision);
+ pr_info("OMAP GPIO hardware version %d.%d\n",
+ (rev >> 4) & 0x0f, rev & 0x0f);
+
+ called = true;
+}
+
+static void omap_gpio_mod_init(struct gpio_bank *bank)
+{
+ void __iomem *base = bank->base;
+ u32 l = 0xffffffff;
+
+ if (bank->width == 16)
+ l = 0xffff;
+
+ if (bank->is_mpuio) {
+ writel_relaxed(l, bank->base + bank->regs->irqenable);
+ return;
+ }
+
+ omap_gpio_rmw(base + bank->regs->irqenable, l,
+ bank->regs->irqenable_inv);
+ omap_gpio_rmw(base + bank->regs->irqstatus, l,
+ !bank->regs->irqenable_inv);
+ if (bank->regs->debounce_en)
+ writel_relaxed(0, base + bank->regs->debounce_en);
+
+ /* Save OE default value (0xffffffff) in the context */
+ bank->context.oe = readl_relaxed(bank->base + bank->regs->direction);
+ /* Initialize interface clk ungated, module enabled */
+ if (bank->regs->ctrl)
+ writel_relaxed(0, base + bank->regs->ctrl);
+}
+
+static int omap_gpio_chip_init(struct gpio_bank *bank, struct device *pm_dev)
+{
+ struct gpio_irq_chip *irq;
+ static int gpio;
+ const char *label;
+ int ret;
+
+ /*
+ * REVISIT eventually switch from OMAP-specific gpio structs
+ * over to the generic ones
+ */
+ bank->chip.request = omap_gpio_request;
+ bank->chip.free = omap_gpio_free;
+ bank->chip.get_direction = omap_gpio_get_direction;
+ bank->chip.direction_input = omap_gpio_input;
+ bank->chip.get = omap_gpio_get;
+ bank->chip.get_multiple = omap_gpio_get_multiple;
+ bank->chip.direction_output = omap_gpio_output;
+ bank->chip.set_config = omap_gpio_set_config;
+ bank->chip.set = omap_gpio_set;
+ bank->chip.set_multiple = omap_gpio_set_multiple;
+ if (bank->is_mpuio) {
+ bank->chip.label = "mpuio";
+ if (bank->regs->wkup_en)
+ bank->chip.parent = &omap_mpuio_device.dev;
+ bank->chip.base = OMAP_MPUIO(0);
+ } else {
+ label = devm_kasprintf(bank->chip.parent, GFP_KERNEL, "gpio-%d-%d",
+ gpio, gpio + bank->width - 1);
+ if (!label)
+ return -ENOMEM;
+ bank->chip.label = label;
+ bank->chip.base = -1;
+ }
+ bank->chip.ngpio = bank->width;
+
+ irq = &bank->chip.irq;
+ /* MPUIO is a bit different, reading IRQ status clears it */
+ if (bank->is_mpuio && !bank->regs->wkup_en)
+ gpio_irq_chip_set_chip(irq, &omap_gpio_irq_chip_nowake);
+ else
+ gpio_irq_chip_set_chip(irq, &omap_gpio_irq_chip);
+ irq->handler = handle_bad_irq;
+ irq->default_type = IRQ_TYPE_NONE;
+ irq->num_parents = 1;
+ irq->parents = &bank->irq;
+
+ ret = gpiochip_add_data(&bank->chip, bank);
+ if (ret)
+ return dev_err_probe(bank->chip.parent, ret, "Could not register gpio chip\n");
+
+ irq_domain_set_pm_device(bank->chip.irq.domain, pm_dev);
+ ret = devm_request_irq(bank->chip.parent, bank->irq,
+ omap_gpio_irq_handler,
+ 0, dev_name(bank->chip.parent), bank);
+ if (ret)
+ gpiochip_remove(&bank->chip);
+
+ if (!bank->is_mpuio)
+ gpio += bank->width;
+
+ return ret;
+}
+
+static void omap_gpio_init_context(struct gpio_bank *p)
+{
+ const struct omap_gpio_reg_offs *regs = p->regs;
+ void __iomem *base = p->base;
+
+ p->context.sysconfig = readl_relaxed(base + regs->sysconfig);
+ p->context.ctrl = readl_relaxed(base + regs->ctrl);
+ p->context.oe = readl_relaxed(base + regs->direction);
+ p->context.wake_en = readl_relaxed(base + regs->wkup_en);
+ p->context.leveldetect0 = readl_relaxed(base + regs->leveldetect0);
+ p->context.leveldetect1 = readl_relaxed(base + regs->leveldetect1);
+ p->context.risingdetect = readl_relaxed(base + regs->risingdetect);
+ p->context.fallingdetect = readl_relaxed(base + regs->fallingdetect);
+ p->context.irqenable1 = readl_relaxed(base + regs->irqenable);
+ p->context.irqenable2 = readl_relaxed(base + regs->irqenable2);
+ p->context.dataout = readl_relaxed(base + regs->dataout);
+
+ p->context_valid = true;
+}
+
+static void omap_gpio_restore_context(struct gpio_bank *bank)
+{
+ const struct omap_gpio_reg_offs *regs = bank->regs;
+ void __iomem *base = bank->base;
+
+ writel_relaxed(bank->context.sysconfig, base + regs->sysconfig);
+ writel_relaxed(bank->context.wake_en, base + regs->wkup_en);
+ writel_relaxed(bank->context.ctrl, base + regs->ctrl);
+ writel_relaxed(bank->context.leveldetect0, base + regs->leveldetect0);
+ writel_relaxed(bank->context.leveldetect1, base + regs->leveldetect1);
+ writel_relaxed(bank->context.risingdetect, base + regs->risingdetect);
+ writel_relaxed(bank->context.fallingdetect, base + regs->fallingdetect);
+ writel_relaxed(bank->context.dataout, base + regs->dataout);
+ writel_relaxed(bank->context.oe, base + regs->direction);
+
+ if (bank->dbck_enable_mask) {
+ writel_relaxed(bank->context.debounce, base + regs->debounce);
+ writel_relaxed(bank->context.debounce_en,
+ base + regs->debounce_en);
+ }
+
+ writel_relaxed(bank->context.irqenable1, base + regs->irqenable);
+ writel_relaxed(bank->context.irqenable2, base + regs->irqenable2);
+}
+
+static void omap_gpio_idle(struct gpio_bank *bank, bool may_lose_context)
+{
+ struct device *dev = bank->chip.parent;
+ void __iomem *base = bank->base;
+ u32 mask, nowake;
+
+ bank->saved_datain = readl_relaxed(base + bank->regs->datain);
+
+ /* Save syconfig, it's runtime value can be different from init value */
+ if (bank->loses_context)
+ bank->context.sysconfig = readl_relaxed(base + bank->regs->sysconfig);
+
+ if (!bank->enabled_non_wakeup_gpios)
+ goto update_gpio_context_count;
+
+ /* Check for pending EDGE_FALLING, ignore EDGE_BOTH */
+ mask = bank->enabled_non_wakeup_gpios & bank->context.fallingdetect;
+ mask &= ~bank->context.risingdetect;
+ bank->saved_datain |= mask;
+
+ /* Check for pending EDGE_RISING, ignore EDGE_BOTH */
+ mask = bank->enabled_non_wakeup_gpios & bank->context.risingdetect;
+ mask &= ~bank->context.fallingdetect;
+ bank->saved_datain &= ~mask;
+
+ if (!may_lose_context)
+ goto update_gpio_context_count;
+
+ /*
+ * If going to OFF, remove triggering for all wkup domain
+ * non-wakeup GPIOs. Otherwise spurious IRQs will be
+ * generated. See OMAP2420 Errata item 1.101.
+ */
+ if (!bank->loses_context && bank->enabled_non_wakeup_gpios) {
+ nowake = bank->enabled_non_wakeup_gpios;
+ omap_gpio_rmw(base + bank->regs->fallingdetect, nowake, ~nowake);
+ omap_gpio_rmw(base + bank->regs->risingdetect, nowake, ~nowake);
+ }
+
+update_gpio_context_count:
+ if (bank->get_context_loss_count)
+ bank->context_loss_count =
+ bank->get_context_loss_count(dev);
+
+ omap_gpio_dbck_disable(bank);
+}
+
+static void omap_gpio_unidle(struct gpio_bank *bank)
+{
+ struct device *dev = bank->chip.parent;
+ u32 l = 0, gen, gen0, gen1;
+ int c;
+
+ /*
+ * On the first resume during the probe, the context has not
+ * been initialised and so initialise it now. Also initialise
+ * the context loss count.
+ */
+ if (bank->loses_context && !bank->context_valid) {
+ omap_gpio_init_context(bank);
+
+ if (bank->get_context_loss_count)
+ bank->context_loss_count =
+ bank->get_context_loss_count(dev);
+ }
+
+ omap_gpio_dbck_enable(bank);
+
+ if (bank->loses_context) {
+ if (!bank->get_context_loss_count) {
+ omap_gpio_restore_context(bank);
+ } else {
+ c = bank->get_context_loss_count(dev);
+ if (c != bank->context_loss_count) {
+ omap_gpio_restore_context(bank);
+ } else {
+ return;
+ }
+ }
+ } else {
+ /* Restore changes done for OMAP2420 errata 1.101 */
+ writel_relaxed(bank->context.fallingdetect,
+ bank->base + bank->regs->fallingdetect);
+ writel_relaxed(bank->context.risingdetect,
+ bank->base + bank->regs->risingdetect);
+ }
+
+ l = readl_relaxed(bank->base + bank->regs->datain);
+
+ /*
+ * Check if any of the non-wakeup interrupt GPIOs have changed
+ * state. If so, generate an IRQ by software. This is
+ * horribly racy, but it's the best we can do to work around
+ * this silicon bug.
+ */
+ l ^= bank->saved_datain;
+ l &= bank->enabled_non_wakeup_gpios;
+
+ /*
+ * No need to generate IRQs for the rising edge for gpio IRQs
+ * configured with falling edge only; and vice versa.
+ */
+ gen0 = l & bank->context.fallingdetect;
+ gen0 &= bank->saved_datain;
+
+ gen1 = l & bank->context.risingdetect;
+ gen1 &= ~(bank->saved_datain);
+
+ /* FIXME: Consider GPIO IRQs with level detections properly! */
+ gen = l & (~(bank->context.fallingdetect) &
+ ~(bank->context.risingdetect));
+ /* Consider all GPIO IRQs needed to be updated */
+ gen |= gen0 | gen1;
+
+ if (gen) {
+ u32 old0, old1;
+
+ old0 = readl_relaxed(bank->base + bank->regs->leveldetect0);
+ old1 = readl_relaxed(bank->base + bank->regs->leveldetect1);
+
+ if (!bank->regs->irqstatus_raw0) {
+ writel_relaxed(old0 | gen, bank->base +
+ bank->regs->leveldetect0);
+ writel_relaxed(old1 | gen, bank->base +
+ bank->regs->leveldetect1);
+ }
+
+ if (bank->regs->irqstatus_raw0) {
+ writel_relaxed(old0 | l, bank->base +
+ bank->regs->leveldetect0);
+ writel_relaxed(old1 | l, bank->base +
+ bank->regs->leveldetect1);
+ }
+ writel_relaxed(old0, bank->base + bank->regs->leveldetect0);
+ writel_relaxed(old1, bank->base + bank->regs->leveldetect1);
+ }
+}
+
+static int gpio_omap_cpu_notifier(struct notifier_block *nb,
+ unsigned long cmd, void *v)
+{
+ struct gpio_bank *bank;
+ unsigned long flags;
+ int ret = NOTIFY_OK;
+ u32 isr, mask;
+
+ bank = container_of(nb, struct gpio_bank, nb);
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ if (bank->is_suspended)
+ goto out_unlock;
+
+ switch (cmd) {
+ case CPU_CLUSTER_PM_ENTER:
+ mask = omap_get_gpio_irqbank_mask(bank);
+ isr = readl_relaxed(bank->base + bank->regs->irqstatus) & mask;
+ if (isr) {
+ ret = NOTIFY_BAD;
+ break;
+ }
+ omap_gpio_idle(bank, true);
+ break;
+ case CPU_CLUSTER_PM_ENTER_FAILED:
+ case CPU_CLUSTER_PM_EXIT:
+ omap_gpio_unidle(bank);
+ break;
+ }
+
+out_unlock:
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+
+ return ret;
+}
+
+static const struct omap_gpio_reg_offs omap2_gpio_regs = {
+ .revision = OMAP24XX_GPIO_REVISION,
+ .sysconfig = OMAP24XX_GPIO_SYSCONFIG,
+ .direction = OMAP24XX_GPIO_OE,
+ .datain = OMAP24XX_GPIO_DATAIN,
+ .dataout = OMAP24XX_GPIO_DATAOUT,
+ .set_dataout = OMAP24XX_GPIO_SETDATAOUT,
+ .clr_dataout = OMAP24XX_GPIO_CLEARDATAOUT,
+ .irqstatus = OMAP24XX_GPIO_IRQSTATUS1,
+ .irqstatus2 = OMAP24XX_GPIO_IRQSTATUS2,
+ .irqenable = OMAP24XX_GPIO_IRQENABLE1,
+ .irqenable2 = OMAP24XX_GPIO_IRQENABLE2,
+ .set_irqenable = OMAP24XX_GPIO_SETIRQENABLE1,
+ .clr_irqenable = OMAP24XX_GPIO_CLEARIRQENABLE1,
+ .debounce = OMAP24XX_GPIO_DEBOUNCE_VAL,
+ .debounce_en = OMAP24XX_GPIO_DEBOUNCE_EN,
+ .ctrl = OMAP24XX_GPIO_CTRL,
+ .wkup_en = OMAP24XX_GPIO_WAKE_EN,
+ .leveldetect0 = OMAP24XX_GPIO_LEVELDETECT0,
+ .leveldetect1 = OMAP24XX_GPIO_LEVELDETECT1,
+ .risingdetect = OMAP24XX_GPIO_RISINGDETECT,
+ .fallingdetect = OMAP24XX_GPIO_FALLINGDETECT,
+};
+
+static const struct omap_gpio_reg_offs omap4_gpio_regs = {
+ .revision = OMAP4_GPIO_REVISION,
+ .sysconfig = OMAP4_GPIO_SYSCONFIG,
+ .direction = OMAP4_GPIO_OE,
+ .datain = OMAP4_GPIO_DATAIN,
+ .dataout = OMAP4_GPIO_DATAOUT,
+ .set_dataout = OMAP4_GPIO_SETDATAOUT,
+ .clr_dataout = OMAP4_GPIO_CLEARDATAOUT,
+ .irqstatus = OMAP4_GPIO_IRQSTATUS0,
+ .irqstatus2 = OMAP4_GPIO_IRQSTATUS1,
+ .irqstatus_raw0 = OMAP4_GPIO_IRQSTATUSRAW0,
+ .irqstatus_raw1 = OMAP4_GPIO_IRQSTATUSRAW1,
+ .irqenable = OMAP4_GPIO_IRQSTATUSSET0,
+ .irqenable2 = OMAP4_GPIO_IRQSTATUSSET1,
+ .set_irqenable = OMAP4_GPIO_IRQSTATUSSET0,
+ .clr_irqenable = OMAP4_GPIO_IRQSTATUSCLR0,
+ .debounce = OMAP4_GPIO_DEBOUNCINGTIME,
+ .debounce_en = OMAP4_GPIO_DEBOUNCENABLE,
+ .ctrl = OMAP4_GPIO_CTRL,
+ .wkup_en = OMAP4_GPIO_IRQWAKEN0,
+ .leveldetect0 = OMAP4_GPIO_LEVELDETECT0,
+ .leveldetect1 = OMAP4_GPIO_LEVELDETECT1,
+ .risingdetect = OMAP4_GPIO_RISINGDETECT,
+ .fallingdetect = OMAP4_GPIO_FALLINGDETECT,
+};
+
+static const struct omap_gpio_platform_data omap2_pdata = {
+ .regs = &omap2_gpio_regs,
+ .bank_width = 32,
+ .dbck_flag = false,
+};
+
+static const struct omap_gpio_platform_data omap3_pdata = {
+ .regs = &omap2_gpio_regs,
+ .bank_width = 32,
+ .dbck_flag = true,
+};
+
+static const struct omap_gpio_platform_data omap4_pdata = {
+ .regs = &omap4_gpio_regs,
+ .bank_width = 32,
+ .dbck_flag = true,
+};
+
+static const struct of_device_id omap_gpio_match[] = {
+ {
+ .compatible = "ti,omap4-gpio",
+ .data = &omap4_pdata,
+ },
+ {
+ .compatible = "ti,omap3-gpio",
+ .data = &omap3_pdata,
+ },
+ {
+ .compatible = "ti,omap2-gpio",
+ .data = &omap2_pdata,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, omap_gpio_match);
+
+static int omap_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+ const struct omap_gpio_platform_data *pdata;
+ struct gpio_bank *bank;
+ int ret;
+
+ pdata = device_get_match_data(dev);
+
+ pdata = pdata ?: dev_get_platdata(dev);
+ if (!pdata)
+ return -EINVAL;
+
+ bank = devm_kzalloc(dev, sizeof(*bank), GFP_KERNEL);
+ if (!bank)
+ return -ENOMEM;
+
+ bank->dev = dev;
+
+ bank->irq = platform_get_irq(pdev, 0);
+ if (bank->irq < 0)
+ return bank->irq;
+
+ bank->chip.parent = dev;
+ bank->chip.owner = THIS_MODULE;
+ bank->dbck_flag = pdata->dbck_flag;
+ bank->stride = pdata->bank_stride;
+ bank->width = pdata->bank_width;
+ bank->is_mpuio = pdata->is_mpuio;
+ bank->non_wakeup_gpios = pdata->non_wakeup_gpios;
+ bank->regs = pdata->regs;
+
+ if (node) {
+ if (!of_property_read_bool(node, "ti,gpio-always-on"))
+ bank->loses_context = true;
+ } else {
+ bank->loses_context = pdata->loses_context;
+
+ if (bank->loses_context)
+ bank->get_context_loss_count =
+ pdata->get_context_loss_count;
+ }
+
+ if (bank->regs->set_dataout && bank->regs->clr_dataout)
+ bank->set_dataout = omap_set_gpio_dataout_reg;
+ else
+ bank->set_dataout = omap_set_gpio_dataout_mask;
+
+ raw_spin_lock_init(&bank->lock);
+ raw_spin_lock_init(&bank->wa_lock);
+
+ /* Static mapping, never released */
+ bank->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(bank->base)) {
+ return PTR_ERR(bank->base);
+ }
+
+ if (bank->dbck_flag) {
+ bank->dbck = devm_clk_get(dev, "dbclk");
+ if (IS_ERR(bank->dbck)) {
+ dev_err(dev,
+ "Could not get gpio dbck. Disable debounce\n");
+ bank->dbck_flag = false;
+ } else {
+ clk_prepare(bank->dbck);
+ }
+ }
+
+ platform_set_drvdata(pdev, bank);
+
+ pm_runtime_enable(dev);
+ pm_runtime_get_sync(dev);
+
+ if (bank->is_mpuio)
+ omap_mpuio_init(bank);
+
+ omap_gpio_mod_init(bank);
+
+ ret = omap_gpio_chip_init(bank, dev);
+ if (ret) {
+ pm_runtime_put_sync(dev);
+ pm_runtime_disable(dev);
+ if (bank->dbck_flag)
+ clk_unprepare(bank->dbck);
+ return ret;
+ }
+
+ omap_gpio_show_rev(bank);
+
+ bank->nb.notifier_call = gpio_omap_cpu_notifier;
+ cpu_pm_register_notifier(&bank->nb);
+
+ pm_runtime_put(dev);
+
+ return 0;
+}
+
+static int omap_gpio_remove(struct platform_device *pdev)
+{
+ struct gpio_bank *bank = platform_get_drvdata(pdev);
+
+ cpu_pm_unregister_notifier(&bank->nb);
+ gpiochip_remove(&bank->chip);
+ pm_runtime_disable(&pdev->dev);
+ if (bank->dbck_flag)
+ clk_unprepare(bank->dbck);
+
+ return 0;
+}
+
+static int __maybe_unused omap_gpio_runtime_suspend(struct device *dev)
+{
+ struct gpio_bank *bank = dev_get_drvdata(dev);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ omap_gpio_idle(bank, true);
+ bank->is_suspended = true;
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+
+ return 0;
+}
+
+static int __maybe_unused omap_gpio_runtime_resume(struct device *dev)
+{
+ struct gpio_bank *bank = dev_get_drvdata(dev);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&bank->lock, flags);
+ omap_gpio_unidle(bank);
+ bank->is_suspended = false;
+ raw_spin_unlock_irqrestore(&bank->lock, flags);
+
+ return 0;
+}
+
+static int __maybe_unused omap_gpio_suspend(struct device *dev)
+{
+ struct gpio_bank *bank = dev_get_drvdata(dev);
+
+ if (bank->is_suspended)
+ return 0;
+
+ bank->needs_resume = 1;
+
+ return omap_gpio_runtime_suspend(dev);
+}
+
+static int __maybe_unused omap_gpio_resume(struct device *dev)
+{
+ struct gpio_bank *bank = dev_get_drvdata(dev);
+
+ if (!bank->needs_resume)
+ return 0;
+
+ bank->needs_resume = 0;
+
+ return omap_gpio_runtime_resume(dev);
+}
+
+static const struct dev_pm_ops gpio_pm_ops = {
+ SET_RUNTIME_PM_OPS(omap_gpio_runtime_suspend, omap_gpio_runtime_resume,
+ NULL)
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(omap_gpio_suspend, omap_gpio_resume)
+};
+
+static struct platform_driver omap_gpio_driver = {
+ .probe = omap_gpio_probe,
+ .remove = omap_gpio_remove,
+ .driver = {
+ .name = "omap_gpio",
+ .pm = &gpio_pm_ops,
+ .of_match_table = omap_gpio_match,
+ },
+};
+
+/*
+ * gpio driver register needs to be done before
+ * machine_init functions access gpio APIs.
+ * Hence omap_gpio_drv_reg() is a postcore_initcall.
+ */
+static int __init omap_gpio_drv_reg(void)
+{
+ return platform_driver_register(&omap_gpio_driver);
+}
+postcore_initcall(omap_gpio_drv_reg);
+
+static void __exit omap_gpio_exit(void)
+{
+ platform_driver_unregister(&omap_gpio_driver);
+}
+module_exit(omap_gpio_exit);
+
+MODULE_DESCRIPTION("omap gpio driver");
+MODULE_ALIAS("platform:gpio-omap");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-palmas.c b/drivers/gpio/gpio-palmas.c
new file mode 100644
index 0000000000..28dba70485
--- /dev/null
+++ b/drivers/gpio/gpio-palmas.c
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TI Palma series PMIC's GPIO driver.
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Author: Laxman Dewangan <ldewangan@nvidia.com>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/mfd/palmas.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+struct palmas_gpio {
+ struct gpio_chip gpio_chip;
+ struct palmas *palmas;
+};
+
+struct palmas_device_data {
+ int ngpio;
+};
+
+static int palmas_gpio_get(struct gpio_chip *gc, unsigned offset)
+{
+ struct palmas_gpio *pg = gpiochip_get_data(gc);
+ struct palmas *palmas = pg->palmas;
+ unsigned int val;
+ int ret;
+ unsigned int reg;
+ int gpio16 = (offset/8);
+
+ offset %= 8;
+ reg = (gpio16) ? PALMAS_GPIO_DATA_DIR2 : PALMAS_GPIO_DATA_DIR;
+
+ ret = palmas_read(palmas, PALMAS_GPIO_BASE, reg, &val);
+ if (ret < 0) {
+ dev_err(gc->parent, "Reg 0x%02x read failed, %d\n", reg, ret);
+ return ret;
+ }
+
+ if (val & BIT(offset))
+ reg = (gpio16) ? PALMAS_GPIO_DATA_OUT2 : PALMAS_GPIO_DATA_OUT;
+ else
+ reg = (gpio16) ? PALMAS_GPIO_DATA_IN2 : PALMAS_GPIO_DATA_IN;
+
+ ret = palmas_read(palmas, PALMAS_GPIO_BASE, reg, &val);
+ if (ret < 0) {
+ dev_err(gc->parent, "Reg 0x%02x read failed, %d\n", reg, ret);
+ return ret;
+ }
+ return !!(val & BIT(offset));
+}
+
+static void palmas_gpio_set(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ struct palmas_gpio *pg = gpiochip_get_data(gc);
+ struct palmas *palmas = pg->palmas;
+ int ret;
+ unsigned int reg;
+ int gpio16 = (offset/8);
+
+ offset %= 8;
+ if (gpio16)
+ reg = (value) ?
+ PALMAS_GPIO_SET_DATA_OUT2 : PALMAS_GPIO_CLEAR_DATA_OUT2;
+ else
+ reg = (value) ?
+ PALMAS_GPIO_SET_DATA_OUT : PALMAS_GPIO_CLEAR_DATA_OUT;
+
+ ret = palmas_write(palmas, PALMAS_GPIO_BASE, reg, BIT(offset));
+ if (ret < 0)
+ dev_err(gc->parent, "Reg 0x%02x write failed, %d\n", reg, ret);
+}
+
+static int palmas_gpio_output(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ struct palmas_gpio *pg = gpiochip_get_data(gc);
+ struct palmas *palmas = pg->palmas;
+ int ret;
+ unsigned int reg;
+ int gpio16 = (offset/8);
+
+ offset %= 8;
+ reg = (gpio16) ? PALMAS_GPIO_DATA_DIR2 : PALMAS_GPIO_DATA_DIR;
+
+ /* Set the initial value */
+ palmas_gpio_set(gc, offset, value);
+
+ ret = palmas_update_bits(palmas, PALMAS_GPIO_BASE, reg,
+ BIT(offset), BIT(offset));
+ if (ret < 0)
+ dev_err(gc->parent, "Reg 0x%02x update failed, %d\n", reg,
+ ret);
+ return ret;
+}
+
+static int palmas_gpio_input(struct gpio_chip *gc, unsigned offset)
+{
+ struct palmas_gpio *pg = gpiochip_get_data(gc);
+ struct palmas *palmas = pg->palmas;
+ int ret;
+ unsigned int reg;
+ int gpio16 = (offset/8);
+
+ offset %= 8;
+ reg = (gpio16) ? PALMAS_GPIO_DATA_DIR2 : PALMAS_GPIO_DATA_DIR;
+
+ ret = palmas_update_bits(palmas, PALMAS_GPIO_BASE, reg, BIT(offset), 0);
+ if (ret < 0)
+ dev_err(gc->parent, "Reg 0x%02x update failed, %d\n", reg,
+ ret);
+ return ret;
+}
+
+static int palmas_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
+{
+ struct palmas_gpio *pg = gpiochip_get_data(gc);
+ struct palmas *palmas = pg->palmas;
+
+ return palmas_irq_get_virq(palmas, PALMAS_GPIO_0_IRQ + offset);
+}
+
+static const struct palmas_device_data palmas_dev_data = {
+ .ngpio = 8,
+};
+
+static const struct palmas_device_data tps80036_dev_data = {
+ .ngpio = 16,
+};
+
+static const struct of_device_id of_palmas_gpio_match[] = {
+ { .compatible = "ti,palmas-gpio", .data = &palmas_dev_data,},
+ { .compatible = "ti,tps65913-gpio", .data = &palmas_dev_data,},
+ { .compatible = "ti,tps65914-gpio", .data = &palmas_dev_data,},
+ { .compatible = "ti,tps80036-gpio", .data = &tps80036_dev_data,},
+ { },
+};
+
+static int palmas_gpio_probe(struct platform_device *pdev)
+{
+ struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);
+ struct palmas_platform_data *palmas_pdata;
+ struct palmas_gpio *palmas_gpio;
+ int ret;
+ const struct palmas_device_data *dev_data;
+
+ dev_data = of_device_get_match_data(&pdev->dev);
+ if (!dev_data)
+ dev_data = &palmas_dev_data;
+
+ palmas_gpio = devm_kzalloc(&pdev->dev,
+ sizeof(*palmas_gpio), GFP_KERNEL);
+ if (!palmas_gpio)
+ return -ENOMEM;
+
+ palmas_gpio->palmas = palmas;
+ palmas_gpio->gpio_chip.owner = THIS_MODULE;
+ palmas_gpio->gpio_chip.label = dev_name(&pdev->dev);
+ palmas_gpio->gpio_chip.ngpio = dev_data->ngpio;
+ palmas_gpio->gpio_chip.can_sleep = true;
+ palmas_gpio->gpio_chip.direction_input = palmas_gpio_input;
+ palmas_gpio->gpio_chip.direction_output = palmas_gpio_output;
+ palmas_gpio->gpio_chip.to_irq = palmas_gpio_to_irq;
+ palmas_gpio->gpio_chip.set = palmas_gpio_set;
+ palmas_gpio->gpio_chip.get = palmas_gpio_get;
+ palmas_gpio->gpio_chip.parent = &pdev->dev;
+
+ palmas_pdata = dev_get_platdata(palmas->dev);
+ if (palmas_pdata && palmas_pdata->gpio_base)
+ palmas_gpio->gpio_chip.base = palmas_pdata->gpio_base;
+ else
+ palmas_gpio->gpio_chip.base = -1;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &palmas_gpio->gpio_chip,
+ palmas_gpio);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct platform_driver palmas_gpio_driver = {
+ .driver.name = "palmas-gpio",
+ .driver.of_match_table = of_palmas_gpio_match,
+ .probe = palmas_gpio_probe,
+};
+
+static int __init palmas_gpio_init(void)
+{
+ return platform_driver_register(&palmas_gpio_driver);
+}
+subsys_initcall(palmas_gpio_init);
diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c
new file mode 100644
index 0000000000..bdd50a78e4
--- /dev/null
+++ b/drivers/gpio/gpio-pca953x.c
@@ -0,0 +1,1390 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PCA953x 4/8/16/24/40 bit I/O ports
+ *
+ * Copyright (C) 2005 Ben Gardner <bgardner@wabtec.com>
+ * Copyright (C) 2007 Marvell International Ltd.
+ *
+ * Derived from drivers/i2c/chips/pca9539.c
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitmap.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/pca953x.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+
+#include <asm/unaligned.h>
+
+#define PCA953X_INPUT 0x00
+#define PCA953X_OUTPUT 0x01
+#define PCA953X_INVERT 0x02
+#define PCA953X_DIRECTION 0x03
+
+#define REG_ADDR_MASK GENMASK(5, 0)
+#define REG_ADDR_EXT BIT(6)
+#define REG_ADDR_AI BIT(7)
+
+#define PCA957X_IN 0x00
+#define PCA957X_INVRT 0x01
+#define PCA957X_BKEN 0x02
+#define PCA957X_PUPD 0x03
+#define PCA957X_CFG 0x04
+#define PCA957X_OUT 0x05
+#define PCA957X_MSK 0x06
+#define PCA957X_INTS 0x07
+
+#define PCAL953X_OUT_STRENGTH 0x20
+#define PCAL953X_IN_LATCH 0x22
+#define PCAL953X_PULL_EN 0x23
+#define PCAL953X_PULL_SEL 0x24
+#define PCAL953X_INT_MASK 0x25
+#define PCAL953X_INT_STAT 0x26
+#define PCAL953X_OUT_CONF 0x27
+
+#define PCAL6524_INT_EDGE 0x28
+#define PCAL6524_INT_CLR 0x2a
+#define PCAL6524_IN_STATUS 0x2b
+#define PCAL6524_OUT_INDCONF 0x2c
+#define PCAL6524_DEBOUNCE 0x2d
+
+#define PCA_GPIO_MASK GENMASK(7, 0)
+
+#define PCAL_GPIO_MASK GENMASK(4, 0)
+#define PCAL_PINCTRL_MASK GENMASK(6, 5)
+
+#define PCA_INT BIT(8)
+#define PCA_PCAL BIT(9)
+#define PCA_LATCH_INT (PCA_PCAL | PCA_INT)
+#define PCA953X_TYPE BIT(12)
+#define PCA957X_TYPE BIT(13)
+#define PCAL653X_TYPE BIT(14)
+#define PCA_TYPE_MASK GENMASK(15, 12)
+
+#define PCA_CHIP_TYPE(x) ((x) & PCA_TYPE_MASK)
+
+static const struct i2c_device_id pca953x_id[] = {
+ { "pca6408", 8 | PCA953X_TYPE | PCA_INT, },
+ { "pca6416", 16 | PCA953X_TYPE | PCA_INT, },
+ { "pca9505", 40 | PCA953X_TYPE | PCA_INT, },
+ { "pca9506", 40 | PCA953X_TYPE | PCA_INT, },
+ { "pca9534", 8 | PCA953X_TYPE | PCA_INT, },
+ { "pca9535", 16 | PCA953X_TYPE | PCA_INT, },
+ { "pca9536", 4 | PCA953X_TYPE, },
+ { "pca9537", 4 | PCA953X_TYPE | PCA_INT, },
+ { "pca9538", 8 | PCA953X_TYPE | PCA_INT, },
+ { "pca9539", 16 | PCA953X_TYPE | PCA_INT, },
+ { "pca9554", 8 | PCA953X_TYPE | PCA_INT, },
+ { "pca9555", 16 | PCA953X_TYPE | PCA_INT, },
+ { "pca9556", 8 | PCA953X_TYPE, },
+ { "pca9557", 8 | PCA953X_TYPE, },
+ { "pca9574", 8 | PCA957X_TYPE | PCA_INT, },
+ { "pca9575", 16 | PCA957X_TYPE | PCA_INT, },
+ { "pca9698", 40 | PCA953X_TYPE, },
+
+ { "pcal6408", 8 | PCA953X_TYPE | PCA_LATCH_INT, },
+ { "pcal6416", 16 | PCA953X_TYPE | PCA_LATCH_INT, },
+ { "pcal6524", 24 | PCA953X_TYPE | PCA_LATCH_INT, },
+ { "pcal6534", 34 | PCAL653X_TYPE | PCA_LATCH_INT, },
+ { "pcal9535", 16 | PCA953X_TYPE | PCA_LATCH_INT, },
+ { "pcal9554b", 8 | PCA953X_TYPE | PCA_LATCH_INT, },
+ { "pcal9555a", 16 | PCA953X_TYPE | PCA_LATCH_INT, },
+
+ { "max7310", 8 | PCA953X_TYPE, },
+ { "max7312", 16 | PCA953X_TYPE | PCA_INT, },
+ { "max7313", 16 | PCA953X_TYPE | PCA_INT, },
+ { "max7315", 8 | PCA953X_TYPE | PCA_INT, },
+ { "max7318", 16 | PCA953X_TYPE | PCA_INT, },
+ { "pca6107", 8 | PCA953X_TYPE | PCA_INT, },
+ { "tca6408", 8 | PCA953X_TYPE | PCA_INT, },
+ { "tca6416", 16 | PCA953X_TYPE | PCA_INT, },
+ { "tca6424", 24 | PCA953X_TYPE | PCA_INT, },
+ { "tca9538", 8 | PCA953X_TYPE | PCA_INT, },
+ { "tca9539", 16 | PCA953X_TYPE | PCA_INT, },
+ { "tca9554", 8 | PCA953X_TYPE | PCA_INT, },
+ { "xra1202", 8 | PCA953X_TYPE },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, pca953x_id);
+
+#ifdef CONFIG_GPIO_PCA953X_IRQ
+
+#include <linux/dmi.h>
+
+static const struct acpi_gpio_params pca953x_irq_gpios = { 0, 0, true };
+
+static const struct acpi_gpio_mapping pca953x_acpi_irq_gpios[] = {
+ { "irq-gpios", &pca953x_irq_gpios, 1, ACPI_GPIO_QUIRK_ABSOLUTE_NUMBER },
+ { }
+};
+
+static int pca953x_acpi_get_irq(struct device *dev)
+{
+ int ret;
+
+ ret = devm_acpi_dev_add_driver_gpios(dev, pca953x_acpi_irq_gpios);
+ if (ret)
+ dev_warn(dev, "can't add GPIO ACPI mapping\n");
+
+ ret = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev), "irq-gpios", 0);
+ if (ret < 0)
+ return ret;
+
+ dev_info(dev, "ACPI interrupt quirk (IRQ %d)\n", ret);
+ return ret;
+}
+
+static const struct dmi_system_id pca953x_dmi_acpi_irq_info[] = {
+ {
+ /*
+ * On Intel Galileo Gen 2 board the IRQ pin of one of
+ * the I²C GPIO expanders, which has GpioInt() resource,
+ * is provided as an absolute number instead of being
+ * relative. Since first controller (gpio-sch.c) and
+ * second (gpio-dwapb.c) are at the fixed bases, we may
+ * safely refer to the number in the global space to get
+ * an IRQ out of it.
+ */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GalileoGen2"),
+ },
+ },
+ {}
+};
+#endif
+
+static const struct acpi_device_id pca953x_acpi_ids[] = {
+ { "INT3491", 16 | PCA953X_TYPE | PCA_LATCH_INT, },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, pca953x_acpi_ids);
+
+#define MAX_BANK 5
+#define BANK_SZ 8
+#define MAX_LINE (MAX_BANK * BANK_SZ)
+
+#define NBANK(chip) DIV_ROUND_UP(chip->gpio_chip.ngpio, BANK_SZ)
+
+struct pca953x_reg_config {
+ int direction;
+ int output;
+ int input;
+ int invert;
+};
+
+static const struct pca953x_reg_config pca953x_regs = {
+ .direction = PCA953X_DIRECTION,
+ .output = PCA953X_OUTPUT,
+ .input = PCA953X_INPUT,
+ .invert = PCA953X_INVERT,
+};
+
+static const struct pca953x_reg_config pca957x_regs = {
+ .direction = PCA957X_CFG,
+ .output = PCA957X_OUT,
+ .input = PCA957X_IN,
+ .invert = PCA957X_INVRT,
+};
+
+struct pca953x_chip {
+ unsigned gpio_start;
+ struct mutex i2c_lock;
+ struct regmap *regmap;
+
+#ifdef CONFIG_GPIO_PCA953X_IRQ
+ struct mutex irq_lock;
+ DECLARE_BITMAP(irq_mask, MAX_LINE);
+ DECLARE_BITMAP(irq_stat, MAX_LINE);
+ DECLARE_BITMAP(irq_trig_raise, MAX_LINE);
+ DECLARE_BITMAP(irq_trig_fall, MAX_LINE);
+#endif
+ atomic_t wakeup_path;
+
+ struct i2c_client *client;
+ struct gpio_chip gpio_chip;
+ const char *const *names;
+ unsigned long driver_data;
+ struct regulator *regulator;
+
+ const struct pca953x_reg_config *regs;
+
+ u8 (*recalc_addr)(struct pca953x_chip *chip, int reg, int off);
+ bool (*check_reg)(struct pca953x_chip *chip, unsigned int reg,
+ u32 checkbank);
+};
+
+static int pca953x_bank_shift(struct pca953x_chip *chip)
+{
+ return fls((chip->gpio_chip.ngpio - 1) / BANK_SZ);
+}
+
+#define PCA953x_BANK_INPUT BIT(0)
+#define PCA953x_BANK_OUTPUT BIT(1)
+#define PCA953x_BANK_POLARITY BIT(2)
+#define PCA953x_BANK_CONFIG BIT(3)
+
+#define PCA957x_BANK_INPUT BIT(0)
+#define PCA957x_BANK_POLARITY BIT(1)
+#define PCA957x_BANK_BUSHOLD BIT(2)
+#define PCA957x_BANK_CONFIG BIT(4)
+#define PCA957x_BANK_OUTPUT BIT(5)
+
+#define PCAL9xxx_BANK_IN_LATCH BIT(8 + 2)
+#define PCAL9xxx_BANK_PULL_EN BIT(8 + 3)
+#define PCAL9xxx_BANK_PULL_SEL BIT(8 + 4)
+#define PCAL9xxx_BANK_IRQ_MASK BIT(8 + 5)
+#define PCAL9xxx_BANK_IRQ_STAT BIT(8 + 6)
+
+/*
+ * We care about the following registers:
+ * - Standard set, below 0x40, each port can be replicated up to 8 times
+ * - PCA953x standard
+ * Input port 0x00 + 0 * bank_size R
+ * Output port 0x00 + 1 * bank_size RW
+ * Polarity Inversion port 0x00 + 2 * bank_size RW
+ * Configuration port 0x00 + 3 * bank_size RW
+ * - PCA957x with mixed up registers
+ * Input port 0x00 + 0 * bank_size R
+ * Polarity Inversion port 0x00 + 1 * bank_size RW
+ * Bus hold port 0x00 + 2 * bank_size RW
+ * Configuration port 0x00 + 4 * bank_size RW
+ * Output port 0x00 + 5 * bank_size RW
+ *
+ * - Extended set, above 0x40, often chip specific.
+ * - PCAL6524/PCAL9555A with custom PCAL IRQ handling:
+ * Input latch register 0x40 + 2 * bank_size RW
+ * Pull-up/pull-down enable reg 0x40 + 3 * bank_size RW
+ * Pull-up/pull-down select reg 0x40 + 4 * bank_size RW
+ * Interrupt mask register 0x40 + 5 * bank_size RW
+ * Interrupt status register 0x40 + 6 * bank_size R
+ *
+ * - Registers with bit 0x80 set, the AI bit
+ * The bit is cleared and the registers fall into one of the
+ * categories above.
+ */
+
+static bool pca953x_check_register(struct pca953x_chip *chip, unsigned int reg,
+ u32 checkbank)
+{
+ int bank_shift = pca953x_bank_shift(chip);
+ int bank = (reg & REG_ADDR_MASK) >> bank_shift;
+ int offset = reg & (BIT(bank_shift) - 1);
+
+ /* Special PCAL extended register check. */
+ if (reg & REG_ADDR_EXT) {
+ if (!(chip->driver_data & PCA_PCAL))
+ return false;
+ bank += 8;
+ }
+
+ /* Register is not in the matching bank. */
+ if (!(BIT(bank) & checkbank))
+ return false;
+
+ /* Register is not within allowed range of bank. */
+ if (offset >= NBANK(chip))
+ return false;
+
+ return true;
+}
+
+/*
+ * Unfortunately, whilst the PCAL6534 chip (and compatibles) broadly follow the
+ * same register layout as the PCAL6524, the spacing of the registers has been
+ * fundamentally altered by compacting them and thus does not obey the same
+ * rules, including being able to use bit shifting to determine bank. These
+ * chips hence need special handling here.
+ */
+static bool pcal6534_check_register(struct pca953x_chip *chip, unsigned int reg,
+ u32 checkbank)
+{
+ int bank_shift;
+ int bank;
+ int offset;
+
+ if (reg >= 0x54) {
+ /*
+ * Handle lack of reserved registers after output port
+ * configuration register to form a bank.
+ */
+ reg -= 0x54;
+ bank_shift = 16;
+ } else if (reg >= 0x30) {
+ /*
+ * Reserved block between 14h and 2Fh does not align on
+ * expected bank boundaries like other devices.
+ */
+ reg -= 0x30;
+ bank_shift = 8;
+ } else {
+ bank_shift = 0;
+ }
+
+ bank = bank_shift + reg / NBANK(chip);
+ offset = reg % NBANK(chip);
+
+ /* Register is not in the matching bank. */
+ if (!(BIT(bank) & checkbank))
+ return false;
+
+ /* Register is not within allowed range of bank. */
+ if (offset >= NBANK(chip))
+ return false;
+
+ return true;
+}
+
+static bool pca953x_readable_register(struct device *dev, unsigned int reg)
+{
+ struct pca953x_chip *chip = dev_get_drvdata(dev);
+ u32 bank;
+
+ if (PCA_CHIP_TYPE(chip->driver_data) == PCA957X_TYPE) {
+ bank = PCA957x_BANK_INPUT | PCA957x_BANK_OUTPUT |
+ PCA957x_BANK_POLARITY | PCA957x_BANK_CONFIG |
+ PCA957x_BANK_BUSHOLD;
+ } else {
+ bank = PCA953x_BANK_INPUT | PCA953x_BANK_OUTPUT |
+ PCA953x_BANK_POLARITY | PCA953x_BANK_CONFIG;
+ }
+
+ if (chip->driver_data & PCA_PCAL) {
+ bank |= PCAL9xxx_BANK_IN_LATCH | PCAL9xxx_BANK_PULL_EN |
+ PCAL9xxx_BANK_PULL_SEL | PCAL9xxx_BANK_IRQ_MASK |
+ PCAL9xxx_BANK_IRQ_STAT;
+ }
+
+ return chip->check_reg(chip, reg, bank);
+}
+
+static bool pca953x_writeable_register(struct device *dev, unsigned int reg)
+{
+ struct pca953x_chip *chip = dev_get_drvdata(dev);
+ u32 bank;
+
+ if (PCA_CHIP_TYPE(chip->driver_data) == PCA957X_TYPE) {
+ bank = PCA957x_BANK_OUTPUT | PCA957x_BANK_POLARITY |
+ PCA957x_BANK_CONFIG | PCA957x_BANK_BUSHOLD;
+ } else {
+ bank = PCA953x_BANK_OUTPUT | PCA953x_BANK_POLARITY |
+ PCA953x_BANK_CONFIG;
+ }
+
+ if (chip->driver_data & PCA_PCAL)
+ bank |= PCAL9xxx_BANK_IN_LATCH | PCAL9xxx_BANK_PULL_EN |
+ PCAL9xxx_BANK_PULL_SEL | PCAL9xxx_BANK_IRQ_MASK;
+
+ return chip->check_reg(chip, reg, bank);
+}
+
+static bool pca953x_volatile_register(struct device *dev, unsigned int reg)
+{
+ struct pca953x_chip *chip = dev_get_drvdata(dev);
+ u32 bank;
+
+ if (PCA_CHIP_TYPE(chip->driver_data) == PCA957X_TYPE)
+ bank = PCA957x_BANK_INPUT;
+ else
+ bank = PCA953x_BANK_INPUT;
+
+ if (chip->driver_data & PCA_PCAL)
+ bank |= PCAL9xxx_BANK_IRQ_STAT;
+
+ return chip->check_reg(chip, reg, bank);
+}
+
+static const struct regmap_config pca953x_i2c_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .use_single_read = true,
+ .use_single_write = true,
+
+ .readable_reg = pca953x_readable_register,
+ .writeable_reg = pca953x_writeable_register,
+ .volatile_reg = pca953x_volatile_register,
+
+ .disable_locking = true,
+ .cache_type = REGCACHE_RBTREE,
+ .max_register = 0x7f,
+};
+
+static const struct regmap_config pca953x_ai_i2c_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .read_flag_mask = REG_ADDR_AI,
+ .write_flag_mask = REG_ADDR_AI,
+
+ .readable_reg = pca953x_readable_register,
+ .writeable_reg = pca953x_writeable_register,
+ .volatile_reg = pca953x_volatile_register,
+
+ .disable_locking = true,
+ .cache_type = REGCACHE_RBTREE,
+ .max_register = 0x7f,
+};
+
+static u8 pca953x_recalc_addr(struct pca953x_chip *chip, int reg, int off)
+{
+ int bank_shift = pca953x_bank_shift(chip);
+ int addr = (reg & PCAL_GPIO_MASK) << bank_shift;
+ int pinctrl = (reg & PCAL_PINCTRL_MASK) << 1;
+ u8 regaddr = pinctrl | addr | (off / BANK_SZ);
+
+ return regaddr;
+}
+
+/*
+ * The PCAL6534 and compatible chips have altered bank alignment that doesn't
+ * fit within the bit shifting scheme used for other devices.
+ */
+static u8 pcal6534_recalc_addr(struct pca953x_chip *chip, int reg, int off)
+{
+ int addr;
+ int pinctrl;
+
+ addr = (reg & PCAL_GPIO_MASK) * NBANK(chip);
+
+ switch (reg) {
+ case PCAL953X_OUT_STRENGTH:
+ case PCAL953X_IN_LATCH:
+ case PCAL953X_PULL_EN:
+ case PCAL953X_PULL_SEL:
+ case PCAL953X_INT_MASK:
+ case PCAL953X_INT_STAT:
+ pinctrl = ((reg & PCAL_PINCTRL_MASK) >> 1) + 0x20;
+ break;
+ case PCAL6524_INT_EDGE:
+ case PCAL6524_INT_CLR:
+ case PCAL6524_IN_STATUS:
+ case PCAL6524_OUT_INDCONF:
+ case PCAL6524_DEBOUNCE:
+ pinctrl = ((reg & PCAL_PINCTRL_MASK) >> 1) + 0x1c;
+ break;
+ default:
+ pinctrl = 0;
+ break;
+ }
+
+ return pinctrl + addr + (off / BANK_SZ);
+}
+
+static int pca953x_write_regs(struct pca953x_chip *chip, int reg, unsigned long *val)
+{
+ u8 regaddr = chip->recalc_addr(chip, reg, 0);
+ u8 value[MAX_BANK];
+ int i, ret;
+
+ for (i = 0; i < NBANK(chip); i++)
+ value[i] = bitmap_get_value8(val, i * BANK_SZ);
+
+ ret = regmap_bulk_write(chip->regmap, regaddr, value, NBANK(chip));
+ if (ret < 0) {
+ dev_err(&chip->client->dev, "failed writing register\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int pca953x_read_regs(struct pca953x_chip *chip, int reg, unsigned long *val)
+{
+ u8 regaddr = chip->recalc_addr(chip, reg, 0);
+ u8 value[MAX_BANK];
+ int i, ret;
+
+ ret = regmap_bulk_read(chip->regmap, regaddr, value, NBANK(chip));
+ if (ret < 0) {
+ dev_err(&chip->client->dev, "failed reading register\n");
+ return ret;
+ }
+
+ for (i = 0; i < NBANK(chip); i++)
+ bitmap_set_value8(val, value[i], i * BANK_SZ);
+
+ return 0;
+}
+
+static int pca953x_gpio_direction_input(struct gpio_chip *gc, unsigned off)
+{
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+ u8 dirreg = chip->recalc_addr(chip, chip->regs->direction, off);
+ u8 bit = BIT(off % BANK_SZ);
+ int ret;
+
+ mutex_lock(&chip->i2c_lock);
+ ret = regmap_write_bits(chip->regmap, dirreg, bit, bit);
+ mutex_unlock(&chip->i2c_lock);
+ return ret;
+}
+
+static int pca953x_gpio_direction_output(struct gpio_chip *gc,
+ unsigned off, int val)
+{
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+ u8 dirreg = chip->recalc_addr(chip, chip->regs->direction, off);
+ u8 outreg = chip->recalc_addr(chip, chip->regs->output, off);
+ u8 bit = BIT(off % BANK_SZ);
+ int ret;
+
+ mutex_lock(&chip->i2c_lock);
+ /* set output level */
+ ret = regmap_write_bits(chip->regmap, outreg, bit, val ? bit : 0);
+ if (ret)
+ goto exit;
+
+ /* then direction */
+ ret = regmap_write_bits(chip->regmap, dirreg, bit, 0);
+exit:
+ mutex_unlock(&chip->i2c_lock);
+ return ret;
+}
+
+static int pca953x_gpio_get_value(struct gpio_chip *gc, unsigned off)
+{
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+ u8 inreg = chip->recalc_addr(chip, chip->regs->input, off);
+ u8 bit = BIT(off % BANK_SZ);
+ u32 reg_val;
+ int ret;
+
+ mutex_lock(&chip->i2c_lock);
+ ret = regmap_read(chip->regmap, inreg, &reg_val);
+ mutex_unlock(&chip->i2c_lock);
+ if (ret < 0)
+ return ret;
+
+ return !!(reg_val & bit);
+}
+
+static void pca953x_gpio_set_value(struct gpio_chip *gc, unsigned off, int val)
+{
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+ u8 outreg = chip->recalc_addr(chip, chip->regs->output, off);
+ u8 bit = BIT(off % BANK_SZ);
+
+ mutex_lock(&chip->i2c_lock);
+ regmap_write_bits(chip->regmap, outreg, bit, val ? bit : 0);
+ mutex_unlock(&chip->i2c_lock);
+}
+
+static int pca953x_gpio_get_direction(struct gpio_chip *gc, unsigned off)
+{
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+ u8 dirreg = chip->recalc_addr(chip, chip->regs->direction, off);
+ u8 bit = BIT(off % BANK_SZ);
+ u32 reg_val;
+ int ret;
+
+ mutex_lock(&chip->i2c_lock);
+ ret = regmap_read(chip->regmap, dirreg, &reg_val);
+ mutex_unlock(&chip->i2c_lock);
+ if (ret < 0)
+ return ret;
+
+ if (reg_val & bit)
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int pca953x_gpio_get_multiple(struct gpio_chip *gc,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+ DECLARE_BITMAP(reg_val, MAX_LINE);
+ int ret;
+
+ mutex_lock(&chip->i2c_lock);
+ ret = pca953x_read_regs(chip, chip->regs->input, reg_val);
+ mutex_unlock(&chip->i2c_lock);
+ if (ret)
+ return ret;
+
+ bitmap_replace(bits, bits, reg_val, mask, gc->ngpio);
+ return 0;
+}
+
+static void pca953x_gpio_set_multiple(struct gpio_chip *gc,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+ DECLARE_BITMAP(reg_val, MAX_LINE);
+ int ret;
+
+ mutex_lock(&chip->i2c_lock);
+ ret = pca953x_read_regs(chip, chip->regs->output, reg_val);
+ if (ret)
+ goto exit;
+
+ bitmap_replace(reg_val, reg_val, bits, mask, gc->ngpio);
+
+ pca953x_write_regs(chip, chip->regs->output, reg_val);
+exit:
+ mutex_unlock(&chip->i2c_lock);
+}
+
+static int pca953x_gpio_set_pull_up_down(struct pca953x_chip *chip,
+ unsigned int offset,
+ unsigned long config)
+{
+ enum pin_config_param param = pinconf_to_config_param(config);
+
+ u8 pull_en_reg = chip->recalc_addr(chip, PCAL953X_PULL_EN, offset);
+ u8 pull_sel_reg = chip->recalc_addr(chip, PCAL953X_PULL_SEL, offset);
+ u8 bit = BIT(offset % BANK_SZ);
+ int ret;
+
+ /*
+ * pull-up/pull-down configuration requires PCAL extended
+ * registers
+ */
+ if (!(chip->driver_data & PCA_PCAL))
+ return -ENOTSUPP;
+
+ mutex_lock(&chip->i2c_lock);
+
+ /* Configure pull-up/pull-down */
+ if (param == PIN_CONFIG_BIAS_PULL_UP)
+ ret = regmap_write_bits(chip->regmap, pull_sel_reg, bit, bit);
+ else if (param == PIN_CONFIG_BIAS_PULL_DOWN)
+ ret = regmap_write_bits(chip->regmap, pull_sel_reg, bit, 0);
+ else
+ ret = 0;
+ if (ret)
+ goto exit;
+
+ /* Disable/Enable pull-up/pull-down */
+ if (param == PIN_CONFIG_BIAS_DISABLE)
+ ret = regmap_write_bits(chip->regmap, pull_en_reg, bit, 0);
+ else
+ ret = regmap_write_bits(chip->regmap, pull_en_reg, bit, bit);
+
+exit:
+ mutex_unlock(&chip->i2c_lock);
+ return ret;
+}
+
+static int pca953x_gpio_set_config(struct gpio_chip *gc, unsigned int offset,
+ unsigned long config)
+{
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_BIAS_PULL_UP:
+ case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT:
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ case PIN_CONFIG_BIAS_DISABLE:
+ return pca953x_gpio_set_pull_up_down(chip, offset, config);
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static void pca953x_setup_gpio(struct pca953x_chip *chip, int gpios)
+{
+ struct gpio_chip *gc;
+
+ gc = &chip->gpio_chip;
+
+ gc->direction_input = pca953x_gpio_direction_input;
+ gc->direction_output = pca953x_gpio_direction_output;
+ gc->get = pca953x_gpio_get_value;
+ gc->set = pca953x_gpio_set_value;
+ gc->get_direction = pca953x_gpio_get_direction;
+ gc->get_multiple = pca953x_gpio_get_multiple;
+ gc->set_multiple = pca953x_gpio_set_multiple;
+ gc->set_config = pca953x_gpio_set_config;
+ gc->can_sleep = true;
+
+ gc->base = chip->gpio_start;
+ gc->ngpio = gpios;
+ gc->label = dev_name(&chip->client->dev);
+ gc->parent = &chip->client->dev;
+ gc->owner = THIS_MODULE;
+ gc->names = chip->names;
+}
+
+#ifdef CONFIG_GPIO_PCA953X_IRQ
+static void pca953x_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ clear_bit(hwirq, chip->irq_mask);
+ gpiochip_disable_irq(gc, hwirq);
+}
+
+static void pca953x_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ gpiochip_enable_irq(gc, hwirq);
+ set_bit(hwirq, chip->irq_mask);
+}
+
+static int pca953x_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+
+ if (on)
+ atomic_inc(&chip->wakeup_path);
+ else
+ atomic_dec(&chip->wakeup_path);
+
+ return irq_set_irq_wake(chip->client->irq, on);
+}
+
+static void pca953x_irq_bus_lock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+
+ mutex_lock(&chip->irq_lock);
+}
+
+static void pca953x_irq_bus_sync_unlock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+ DECLARE_BITMAP(irq_mask, MAX_LINE);
+ DECLARE_BITMAP(reg_direction, MAX_LINE);
+ int level;
+
+ if (chip->driver_data & PCA_PCAL) {
+ /* Enable latch on interrupt-enabled inputs */
+ pca953x_write_regs(chip, PCAL953X_IN_LATCH, chip->irq_mask);
+
+ bitmap_complement(irq_mask, chip->irq_mask, gc->ngpio);
+
+ /* Unmask enabled interrupts */
+ pca953x_write_regs(chip, PCAL953X_INT_MASK, irq_mask);
+ }
+
+ /* Switch direction to input if needed */
+ pca953x_read_regs(chip, chip->regs->direction, reg_direction);
+
+ bitmap_or(irq_mask, chip->irq_trig_fall, chip->irq_trig_raise, gc->ngpio);
+ bitmap_complement(reg_direction, reg_direction, gc->ngpio);
+ bitmap_and(irq_mask, irq_mask, reg_direction, gc->ngpio);
+
+ /* Look for any newly setup interrupt */
+ for_each_set_bit(level, irq_mask, gc->ngpio)
+ pca953x_gpio_direction_input(&chip->gpio_chip, level);
+
+ mutex_unlock(&chip->irq_lock);
+}
+
+static int pca953x_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ if (!(type & IRQ_TYPE_EDGE_BOTH)) {
+ dev_err(&chip->client->dev, "irq %d: unsupported type %d\n",
+ d->irq, type);
+ return -EINVAL;
+ }
+
+ assign_bit(hwirq, chip->irq_trig_fall, type & IRQ_TYPE_EDGE_FALLING);
+ assign_bit(hwirq, chip->irq_trig_raise, type & IRQ_TYPE_EDGE_RISING);
+
+ return 0;
+}
+
+static void pca953x_irq_shutdown(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pca953x_chip *chip = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ clear_bit(hwirq, chip->irq_trig_raise);
+ clear_bit(hwirq, chip->irq_trig_fall);
+}
+
+static void pca953x_irq_print_chip(struct irq_data *data, struct seq_file *p)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+
+ seq_printf(p, dev_name(gc->parent));
+}
+
+static const struct irq_chip pca953x_irq_chip = {
+ .irq_mask = pca953x_irq_mask,
+ .irq_unmask = pca953x_irq_unmask,
+ .irq_set_wake = pca953x_irq_set_wake,
+ .irq_bus_lock = pca953x_irq_bus_lock,
+ .irq_bus_sync_unlock = pca953x_irq_bus_sync_unlock,
+ .irq_set_type = pca953x_irq_set_type,
+ .irq_shutdown = pca953x_irq_shutdown,
+ .irq_print_chip = pca953x_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pending)
+{
+ struct gpio_chip *gc = &chip->gpio_chip;
+ DECLARE_BITMAP(reg_direction, MAX_LINE);
+ DECLARE_BITMAP(old_stat, MAX_LINE);
+ DECLARE_BITMAP(cur_stat, MAX_LINE);
+ DECLARE_BITMAP(new_stat, MAX_LINE);
+ DECLARE_BITMAP(trigger, MAX_LINE);
+ int ret;
+
+ if (chip->driver_data & PCA_PCAL) {
+ /* Read the current interrupt status from the device */
+ ret = pca953x_read_regs(chip, PCAL953X_INT_STAT, trigger);
+ if (ret)
+ return false;
+
+ /* Check latched inputs and clear interrupt status */
+ ret = pca953x_read_regs(chip, chip->regs->input, cur_stat);
+ if (ret)
+ return false;
+
+ /* Apply filter for rising/falling edge selection */
+ bitmap_replace(new_stat, chip->irq_trig_fall, chip->irq_trig_raise, cur_stat, gc->ngpio);
+
+ bitmap_and(pending, new_stat, trigger, gc->ngpio);
+
+ return !bitmap_empty(pending, gc->ngpio);
+ }
+
+ ret = pca953x_read_regs(chip, chip->regs->input, cur_stat);
+ if (ret)
+ return false;
+
+ /* Remove output pins from the equation */
+ pca953x_read_regs(chip, chip->regs->direction, reg_direction);
+
+ bitmap_copy(old_stat, chip->irq_stat, gc->ngpio);
+
+ bitmap_and(new_stat, cur_stat, reg_direction, gc->ngpio);
+ bitmap_xor(cur_stat, new_stat, old_stat, gc->ngpio);
+ bitmap_and(trigger, cur_stat, chip->irq_mask, gc->ngpio);
+
+ bitmap_copy(chip->irq_stat, new_stat, gc->ngpio);
+
+ if (bitmap_empty(trigger, gc->ngpio))
+ return false;
+
+ bitmap_and(cur_stat, chip->irq_trig_fall, old_stat, gc->ngpio);
+ bitmap_and(old_stat, chip->irq_trig_raise, new_stat, gc->ngpio);
+ bitmap_or(new_stat, old_stat, cur_stat, gc->ngpio);
+ bitmap_and(pending, new_stat, trigger, gc->ngpio);
+
+ return !bitmap_empty(pending, gc->ngpio);
+}
+
+static irqreturn_t pca953x_irq_handler(int irq, void *devid)
+{
+ struct pca953x_chip *chip = devid;
+ struct gpio_chip *gc = &chip->gpio_chip;
+ DECLARE_BITMAP(pending, MAX_LINE);
+ int level;
+ bool ret;
+
+ bitmap_zero(pending, MAX_LINE);
+
+ mutex_lock(&chip->i2c_lock);
+ ret = pca953x_irq_pending(chip, pending);
+ mutex_unlock(&chip->i2c_lock);
+
+ if (ret) {
+ ret = 0;
+
+ for_each_set_bit(level, pending, gc->ngpio) {
+ int nested_irq = irq_find_mapping(gc->irq.domain, level);
+
+ if (unlikely(nested_irq <= 0)) {
+ dev_warn_ratelimited(gc->parent, "unmapped interrupt %d\n", level);
+ continue;
+ }
+
+ handle_nested_irq(nested_irq);
+ ret = 1;
+ }
+ }
+
+ return IRQ_RETVAL(ret);
+}
+
+static int pca953x_irq_setup(struct pca953x_chip *chip, int irq_base)
+{
+ struct i2c_client *client = chip->client;
+ DECLARE_BITMAP(reg_direction, MAX_LINE);
+ DECLARE_BITMAP(irq_stat, MAX_LINE);
+ struct gpio_irq_chip *girq;
+ int ret;
+
+ if (dmi_first_match(pca953x_dmi_acpi_irq_info)) {
+ ret = pca953x_acpi_get_irq(&client->dev);
+ if (ret > 0)
+ client->irq = ret;
+ }
+
+ if (!client->irq)
+ return 0;
+
+ if (irq_base == -1)
+ return 0;
+
+ if (!(chip->driver_data & PCA_INT))
+ return 0;
+
+ ret = pca953x_read_regs(chip, chip->regs->input, irq_stat);
+ if (ret)
+ return ret;
+
+ /*
+ * There is no way to know which GPIO line generated the
+ * interrupt. We have to rely on the previous read for
+ * this purpose.
+ */
+ pca953x_read_regs(chip, chip->regs->direction, reg_direction);
+ bitmap_and(chip->irq_stat, irq_stat, reg_direction, chip->gpio_chip.ngpio);
+ mutex_init(&chip->irq_lock);
+
+ girq = &chip->gpio_chip.irq;
+ gpio_irq_chip_set_chip(girq, &pca953x_irq_chip);
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ girq->threaded = true;
+ girq->first = irq_base; /* FIXME: get rid of this */
+
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, pca953x_irq_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(&client->dev), chip);
+ if (ret) {
+ dev_err(&client->dev, "failed to request irq %d\n",
+ client->irq);
+ return ret;
+ }
+
+ return 0;
+}
+
+#else /* CONFIG_GPIO_PCA953X_IRQ */
+static int pca953x_irq_setup(struct pca953x_chip *chip,
+ int irq_base)
+{
+ struct i2c_client *client = chip->client;
+
+ if (client->irq && irq_base != -1 && (chip->driver_data & PCA_INT))
+ dev_warn(&client->dev, "interrupt support not compiled in\n");
+
+ return 0;
+}
+#endif
+
+static int device_pca95xx_init(struct pca953x_chip *chip, u32 invert)
+{
+ DECLARE_BITMAP(val, MAX_LINE);
+ u8 regaddr;
+ int ret;
+
+ regaddr = chip->recalc_addr(chip, chip->regs->output, 0);
+ ret = regcache_sync_region(chip->regmap, regaddr,
+ regaddr + NBANK(chip) - 1);
+ if (ret)
+ goto out;
+
+ regaddr = chip->recalc_addr(chip, chip->regs->direction, 0);
+ ret = regcache_sync_region(chip->regmap, regaddr,
+ regaddr + NBANK(chip) - 1);
+ if (ret)
+ goto out;
+
+ /* set platform specific polarity inversion */
+ if (invert)
+ bitmap_fill(val, MAX_LINE);
+ else
+ bitmap_zero(val, MAX_LINE);
+
+ ret = pca953x_write_regs(chip, chip->regs->invert, val);
+out:
+ return ret;
+}
+
+static int device_pca957x_init(struct pca953x_chip *chip, u32 invert)
+{
+ DECLARE_BITMAP(val, MAX_LINE);
+ unsigned int i;
+ int ret;
+
+ ret = device_pca95xx_init(chip, invert);
+ if (ret)
+ goto out;
+
+ /* To enable register 6, 7 to control pull up and pull down */
+ for (i = 0; i < NBANK(chip); i++)
+ bitmap_set_value8(val, 0x02, i * BANK_SZ);
+
+ ret = pca953x_write_regs(chip, PCA957X_BKEN, val);
+ if (ret)
+ goto out;
+
+ return 0;
+out:
+ return ret;
+}
+
+static int pca953x_probe(struct i2c_client *client)
+{
+ struct pca953x_platform_data *pdata;
+ struct pca953x_chip *chip;
+ int irq_base = 0;
+ int ret;
+ u32 invert = 0;
+ struct regulator *reg;
+ const struct regmap_config *regmap_config;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+
+ pdata = dev_get_platdata(&client->dev);
+ if (pdata) {
+ irq_base = pdata->irq_base;
+ chip->gpio_start = pdata->gpio_base;
+ invert = pdata->invert;
+ chip->names = pdata->names;
+ } else {
+ struct gpio_desc *reset_gpio;
+
+ chip->gpio_start = -1;
+ irq_base = 0;
+
+ /*
+ * See if we need to de-assert a reset pin.
+ *
+ * There is no known ACPI-enabled platforms that are
+ * using "reset" GPIO. Otherwise any of those platform
+ * must use _DSD method with corresponding property.
+ */
+ reset_gpio = devm_gpiod_get_optional(&client->dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(reset_gpio))
+ return PTR_ERR(reset_gpio);
+ }
+
+ chip->client = client;
+ chip->driver_data = (uintptr_t)i2c_get_match_data(client);
+ if (!chip->driver_data)
+ return -ENODEV;
+
+ reg = devm_regulator_get(&client->dev, "vcc");
+ if (IS_ERR(reg))
+ return dev_err_probe(&client->dev, PTR_ERR(reg), "reg get err\n");
+
+ ret = regulator_enable(reg);
+ if (ret) {
+ dev_err(&client->dev, "reg en err: %d\n", ret);
+ return ret;
+ }
+ chip->regulator = reg;
+
+ i2c_set_clientdata(client, chip);
+
+ pca953x_setup_gpio(chip, chip->driver_data & PCA_GPIO_MASK);
+
+ if (NBANK(chip) > 2 || PCA_CHIP_TYPE(chip->driver_data) == PCA957X_TYPE) {
+ dev_info(&client->dev, "using AI\n");
+ regmap_config = &pca953x_ai_i2c_regmap;
+ } else {
+ dev_info(&client->dev, "using no AI\n");
+ regmap_config = &pca953x_i2c_regmap;
+ }
+
+ if (PCA_CHIP_TYPE(chip->driver_data) == PCAL653X_TYPE) {
+ chip->recalc_addr = pcal6534_recalc_addr;
+ chip->check_reg = pcal6534_check_register;
+ } else {
+ chip->recalc_addr = pca953x_recalc_addr;
+ chip->check_reg = pca953x_check_register;
+ }
+
+ chip->regmap = devm_regmap_init_i2c(client, regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ ret = PTR_ERR(chip->regmap);
+ goto err_exit;
+ }
+
+ regcache_mark_dirty(chip->regmap);
+
+ mutex_init(&chip->i2c_lock);
+ /*
+ * In case we have an i2c-mux controlled by a GPIO provided by an
+ * expander using the same driver higher on the device tree, read the
+ * i2c adapter nesting depth and use the retrieved value as lockdep
+ * subclass for chip->i2c_lock.
+ *
+ * REVISIT: This solution is not complete. It protects us from lockdep
+ * false positives when the expander controlling the i2c-mux is on
+ * a different level on the device tree, but not when it's on the same
+ * level on a different branch (in which case the subclass number
+ * would be the same).
+ *
+ * TODO: Once a correct solution is developed, a similar fix should be
+ * applied to all other i2c-controlled GPIO expanders (and potentially
+ * regmap-i2c).
+ */
+ lockdep_set_subclass(&chip->i2c_lock,
+ i2c_adapter_depth(client->adapter));
+
+ /* initialize cached registers from their original values.
+ * we can't share this chip with another i2c master.
+ */
+ if (PCA_CHIP_TYPE(chip->driver_data) == PCA957X_TYPE) {
+ chip->regs = &pca957x_regs;
+ ret = device_pca957x_init(chip, invert);
+ } else {
+ chip->regs = &pca953x_regs;
+ ret = device_pca95xx_init(chip, invert);
+ }
+ if (ret)
+ goto err_exit;
+
+ ret = pca953x_irq_setup(chip, irq_base);
+ if (ret)
+ goto err_exit;
+
+ ret = devm_gpiochip_add_data(&client->dev, &chip->gpio_chip, chip);
+ if (ret)
+ goto err_exit;
+
+ if (pdata && pdata->setup) {
+ ret = pdata->setup(client, chip->gpio_chip.base,
+ chip->gpio_chip.ngpio, pdata->context);
+ if (ret < 0)
+ dev_warn(&client->dev, "setup failed, %d\n", ret);
+ }
+
+ return 0;
+
+err_exit:
+ regulator_disable(chip->regulator);
+ return ret;
+}
+
+static void pca953x_remove(struct i2c_client *client)
+{
+ struct pca953x_platform_data *pdata = dev_get_platdata(&client->dev);
+ struct pca953x_chip *chip = i2c_get_clientdata(client);
+
+ if (pdata && pdata->teardown) {
+ pdata->teardown(client, chip->gpio_chip.base,
+ chip->gpio_chip.ngpio, pdata->context);
+ }
+
+ regulator_disable(chip->regulator);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int pca953x_regcache_sync(struct device *dev)
+{
+ struct pca953x_chip *chip = dev_get_drvdata(dev);
+ int ret;
+ u8 regaddr;
+
+ /*
+ * The ordering between direction and output is important,
+ * sync these registers first and only then sync the rest.
+ */
+ regaddr = chip->recalc_addr(chip, chip->regs->direction, 0);
+ ret = regcache_sync_region(chip->regmap, regaddr, regaddr + NBANK(chip) - 1);
+ if (ret) {
+ dev_err(dev, "Failed to sync GPIO dir registers: %d\n", ret);
+ return ret;
+ }
+
+ regaddr = chip->recalc_addr(chip, chip->regs->output, 0);
+ ret = regcache_sync_region(chip->regmap, regaddr, regaddr + NBANK(chip) - 1);
+ if (ret) {
+ dev_err(dev, "Failed to sync GPIO out registers: %d\n", ret);
+ return ret;
+ }
+
+#ifdef CONFIG_GPIO_PCA953X_IRQ
+ if (chip->driver_data & PCA_PCAL) {
+ regaddr = chip->recalc_addr(chip, PCAL953X_IN_LATCH, 0);
+ ret = regcache_sync_region(chip->regmap, regaddr,
+ regaddr + NBANK(chip) - 1);
+ if (ret) {
+ dev_err(dev, "Failed to sync INT latch registers: %d\n",
+ ret);
+ return ret;
+ }
+
+ regaddr = chip->recalc_addr(chip, PCAL953X_INT_MASK, 0);
+ ret = regcache_sync_region(chip->regmap, regaddr,
+ regaddr + NBANK(chip) - 1);
+ if (ret) {
+ dev_err(dev, "Failed to sync INT mask registers: %d\n",
+ ret);
+ return ret;
+ }
+ }
+#endif
+
+ return 0;
+}
+
+static int pca953x_suspend(struct device *dev)
+{
+ struct pca953x_chip *chip = dev_get_drvdata(dev);
+
+ mutex_lock(&chip->i2c_lock);
+ regcache_cache_only(chip->regmap, true);
+ mutex_unlock(&chip->i2c_lock);
+
+ if (atomic_read(&chip->wakeup_path))
+ device_set_wakeup_path(dev);
+ else
+ regulator_disable(chip->regulator);
+
+ return 0;
+}
+
+static int pca953x_resume(struct device *dev)
+{
+ struct pca953x_chip *chip = dev_get_drvdata(dev);
+ int ret;
+
+ if (!atomic_read(&chip->wakeup_path)) {
+ ret = regulator_enable(chip->regulator);
+ if (ret) {
+ dev_err(dev, "Failed to enable regulator: %d\n", ret);
+ return 0;
+ }
+ }
+
+ mutex_lock(&chip->i2c_lock);
+ regcache_cache_only(chip->regmap, false);
+ regcache_mark_dirty(chip->regmap);
+ ret = pca953x_regcache_sync(dev);
+ if (ret) {
+ mutex_unlock(&chip->i2c_lock);
+ return ret;
+ }
+
+ ret = regcache_sync(chip->regmap);
+ mutex_unlock(&chip->i2c_lock);
+ if (ret) {
+ dev_err(dev, "Failed to restore register map: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+#endif
+
+/* convenience to stop overlong match-table lines */
+#define OF_653X(__nrgpio, __int) ((void *)(__nrgpio | PCAL653X_TYPE | __int))
+#define OF_953X(__nrgpio, __int) (void *)(__nrgpio | PCA953X_TYPE | __int)
+#define OF_957X(__nrgpio, __int) (void *)(__nrgpio | PCA957X_TYPE | __int)
+
+static const struct of_device_id pca953x_dt_ids[] = {
+ { .compatible = "nxp,pca6408", .data = OF_953X(8, PCA_INT), },
+ { .compatible = "nxp,pca6416", .data = OF_953X(16, PCA_INT), },
+ { .compatible = "nxp,pca9505", .data = OF_953X(40, PCA_INT), },
+ { .compatible = "nxp,pca9506", .data = OF_953X(40, PCA_INT), },
+ { .compatible = "nxp,pca9534", .data = OF_953X( 8, PCA_INT), },
+ { .compatible = "nxp,pca9535", .data = OF_953X(16, PCA_INT), },
+ { .compatible = "nxp,pca9536", .data = OF_953X( 4, 0), },
+ { .compatible = "nxp,pca9537", .data = OF_953X( 4, PCA_INT), },
+ { .compatible = "nxp,pca9538", .data = OF_953X( 8, PCA_INT), },
+ { .compatible = "nxp,pca9539", .data = OF_953X(16, PCA_INT), },
+ { .compatible = "nxp,pca9554", .data = OF_953X( 8, PCA_INT), },
+ { .compatible = "nxp,pca9555", .data = OF_953X(16, PCA_INT), },
+ { .compatible = "nxp,pca9556", .data = OF_953X( 8, 0), },
+ { .compatible = "nxp,pca9557", .data = OF_953X( 8, 0), },
+ { .compatible = "nxp,pca9574", .data = OF_957X( 8, PCA_INT), },
+ { .compatible = "nxp,pca9575", .data = OF_957X(16, PCA_INT), },
+ { .compatible = "nxp,pca9698", .data = OF_953X(40, 0), },
+
+ { .compatible = "nxp,pcal6408", .data = OF_953X(8, PCA_LATCH_INT), },
+ { .compatible = "nxp,pcal6416", .data = OF_953X(16, PCA_LATCH_INT), },
+ { .compatible = "nxp,pcal6524", .data = OF_953X(24, PCA_LATCH_INT), },
+ { .compatible = "nxp,pcal6534", .data = OF_653X(34, PCA_LATCH_INT), },
+ { .compatible = "nxp,pcal9535", .data = OF_953X(16, PCA_LATCH_INT), },
+ { .compatible = "nxp,pcal9554b", .data = OF_953X( 8, PCA_LATCH_INT), },
+ { .compatible = "nxp,pcal9555a", .data = OF_953X(16, PCA_LATCH_INT), },
+
+ { .compatible = "maxim,max7310", .data = OF_953X( 8, 0), },
+ { .compatible = "maxim,max7312", .data = OF_953X(16, PCA_INT), },
+ { .compatible = "maxim,max7313", .data = OF_953X(16, PCA_INT), },
+ { .compatible = "maxim,max7315", .data = OF_953X( 8, PCA_INT), },
+ { .compatible = "maxim,max7318", .data = OF_953X(16, PCA_INT), },
+
+ { .compatible = "ti,pca6107", .data = OF_953X( 8, PCA_INT), },
+ { .compatible = "ti,pca9536", .data = OF_953X( 4, 0), },
+ { .compatible = "ti,tca6408", .data = OF_953X( 8, PCA_INT), },
+ { .compatible = "ti,tca6416", .data = OF_953X(16, PCA_INT), },
+ { .compatible = "ti,tca6424", .data = OF_953X(24, PCA_INT), },
+ { .compatible = "ti,tca9538", .data = OF_953X( 8, PCA_INT), },
+ { .compatible = "ti,tca9539", .data = OF_953X(16, PCA_INT), },
+
+ { .compatible = "onnn,cat9554", .data = OF_953X( 8, PCA_INT), },
+ { .compatible = "onnn,pca9654", .data = OF_953X( 8, PCA_INT), },
+ { .compatible = "onnn,pca9655", .data = OF_953X(16, PCA_INT), },
+
+ { .compatible = "exar,xra1202", .data = OF_953X( 8, 0), },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, pca953x_dt_ids);
+
+static SIMPLE_DEV_PM_OPS(pca953x_pm_ops, pca953x_suspend, pca953x_resume);
+
+static struct i2c_driver pca953x_driver = {
+ .driver = {
+ .name = "pca953x",
+ .pm = &pca953x_pm_ops,
+ .of_match_table = pca953x_dt_ids,
+ .acpi_match_table = pca953x_acpi_ids,
+ },
+ .probe = pca953x_probe,
+ .remove = pca953x_remove,
+ .id_table = pca953x_id,
+};
+
+static int __init pca953x_init(void)
+{
+ return i2c_add_driver(&pca953x_driver);
+}
+/* register after i2c postcore initcall and before
+ * subsys initcalls that may rely on these GPIOs
+ */
+subsys_initcall(pca953x_init);
+
+static void __exit pca953x_exit(void)
+{
+ i2c_del_driver(&pca953x_driver);
+}
+module_exit(pca953x_exit);
+
+MODULE_AUTHOR("eric miao <eric.miao@marvell.com>");
+MODULE_DESCRIPTION("GPIO expander driver for PCA953x");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-pca9570.c b/drivers/gpio/gpio-pca9570.c
new file mode 100644
index 0000000000..d37ba40493
--- /dev/null
+++ b/drivers/gpio/gpio-pca9570.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for PCA9570 I2C GPO expander
+ *
+ * Copyright (C) 2020 Sungbo Eo <mans0n@gorani.run>
+ *
+ * Based on gpio-tpic2810.c
+ * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/
+ * Andrew F. Davis <afd@ti.com>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+
+#define SLG7XL45106_GPO_REG 0xDB
+
+/**
+ * struct pca9570_chip_data - GPIO platformdata
+ * @ngpio: no of gpios
+ * @command: Command to be sent
+ */
+struct pca9570_chip_data {
+ u16 ngpio;
+ u32 command;
+};
+
+/**
+ * struct pca9570 - GPIO driver data
+ * @chip: GPIO controller chip
+ * @chip_data: GPIO controller platform data
+ * @lock: Protects write sequences
+ * @out: Buffer for device register
+ */
+struct pca9570 {
+ struct gpio_chip chip;
+ const struct pca9570_chip_data *chip_data;
+ struct mutex lock;
+ u8 out;
+};
+
+static int pca9570_read(struct pca9570 *gpio, u8 *value)
+{
+ struct i2c_client *client = to_i2c_client(gpio->chip.parent);
+ int ret;
+
+ if (gpio->chip_data->command != 0)
+ ret = i2c_smbus_read_byte_data(client, gpio->chip_data->command);
+ else
+ ret = i2c_smbus_read_byte(client);
+
+ if (ret < 0)
+ return ret;
+
+ *value = ret;
+ return 0;
+}
+
+static int pca9570_write(struct pca9570 *gpio, u8 value)
+{
+ struct i2c_client *client = to_i2c_client(gpio->chip.parent);
+
+ if (gpio->chip_data->command != 0)
+ return i2c_smbus_write_byte_data(client, gpio->chip_data->command, value);
+
+ return i2c_smbus_write_byte(client, value);
+}
+
+static int pca9570_get_direction(struct gpio_chip *chip,
+ unsigned offset)
+{
+ /* This device always output */
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int pca9570_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct pca9570 *gpio = gpiochip_get_data(chip);
+ u8 buffer;
+ int ret;
+
+ ret = pca9570_read(gpio, &buffer);
+ if (ret)
+ return ret;
+
+ return !!(buffer & BIT(offset));
+}
+
+static void pca9570_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct pca9570 *gpio = gpiochip_get_data(chip);
+ u8 buffer;
+ int ret;
+
+ mutex_lock(&gpio->lock);
+
+ buffer = gpio->out;
+ if (value)
+ buffer |= BIT(offset);
+ else
+ buffer &= ~BIT(offset);
+
+ ret = pca9570_write(gpio, buffer);
+ if (ret)
+ goto out;
+
+ gpio->out = buffer;
+
+out:
+ mutex_unlock(&gpio->lock);
+}
+
+static int pca9570_probe(struct i2c_client *client)
+{
+ struct pca9570 *gpio;
+
+ gpio = devm_kzalloc(&client->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->chip.label = client->name;
+ gpio->chip.parent = &client->dev;
+ gpio->chip.owner = THIS_MODULE;
+ gpio->chip.get_direction = pca9570_get_direction;
+ 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.ngpio = gpio->chip_data->ngpio;
+ gpio->chip.can_sleep = true;
+
+ mutex_init(&gpio->lock);
+
+ /* Read the current output level */
+ pca9570_read(gpio, &gpio->out);
+
+ i2c_set_clientdata(client, gpio);
+
+ return devm_gpiochip_add_data(&client->dev, &gpio->chip, gpio);
+}
+
+static const struct pca9570_chip_data pca9570_gpio = {
+ .ngpio = 4,
+};
+
+static const struct pca9570_chip_data pca9571_gpio = {
+ .ngpio = 8,
+};
+
+static const struct pca9570_chip_data slg7xl45106_gpio = {
+ .ngpio = 8,
+ .command = SLG7XL45106_GPO_REG,
+};
+
+static const struct i2c_device_id pca9570_id_table[] = {
+ { "pca9570", (kernel_ulong_t)&pca9570_gpio},
+ { "pca9571", (kernel_ulong_t)&pca9571_gpio },
+ { "slg7xl45106", (kernel_ulong_t)&slg7xl45106_gpio },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, pca9570_id_table);
+
+static const struct of_device_id pca9570_of_match_table[] = {
+ { .compatible = "dlg,slg7xl45106", .data = &slg7xl45106_gpio},
+ { .compatible = "nxp,pca9570", .data = &pca9570_gpio },
+ { .compatible = "nxp,pca9571", .data = &pca9571_gpio },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pca9570_of_match_table);
+
+static struct i2c_driver pca9570_driver = {
+ .driver = {
+ .name = "pca9570",
+ .of_match_table = pca9570_of_match_table,
+ },
+ .probe = pca9570_probe,
+ .id_table = pca9570_id_table,
+};
+module_i2c_driver(pca9570_driver);
+
+MODULE_AUTHOR("Sungbo Eo <mans0n@gorani.run>");
+MODULE_DESCRIPTION("GPIO expander driver for PCA9570");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-pcf857x.c b/drivers/gpio/gpio-pcf857x.c
new file mode 100644
index 0000000000..53b69abe67
--- /dev/null
+++ b/drivers/gpio/gpio-pcf857x.c
@@ -0,0 +1,442 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for pcf857x, pca857x, and pca967x I2C GPIO expanders
+ *
+ * Copyright (C) 2007 David Brownell
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+static const struct i2c_device_id pcf857x_id[] = {
+ { "pcf8574", 8 },
+ { "pcf8574a", 8 },
+ { "pca8574", 8 },
+ { "pca9670", 8 },
+ { "pca9672", 8 },
+ { "pca9674", 8 },
+ { "pcf8575", 16 },
+ { "pca8575", 16 },
+ { "pca9671", 16 },
+ { "pca9673", 16 },
+ { "pca9675", 16 },
+ { "max7328", 8 },
+ { "max7329", 8 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, pcf857x_id);
+
+static const struct of_device_id pcf857x_of_table[] = {
+ { .compatible = "nxp,pcf8574", (void *)8 },
+ { .compatible = "nxp,pcf8574a", (void *)8 },
+ { .compatible = "nxp,pca8574", (void *)8 },
+ { .compatible = "nxp,pca9670", (void *)8 },
+ { .compatible = "nxp,pca9672", (void *)8 },
+ { .compatible = "nxp,pca9674", (void *)8 },
+ { .compatible = "nxp,pcf8575", (void *)16 },
+ { .compatible = "nxp,pca8575", (void *)16 },
+ { .compatible = "nxp,pca9671", (void *)16 },
+ { .compatible = "nxp,pca9673", (void *)16 },
+ { .compatible = "nxp,pca9675", (void *)16 },
+ { .compatible = "maxim,max7328", (void *)8 },
+ { .compatible = "maxim,max7329", (void *)8 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pcf857x_of_table);
+
+/*
+ * The pcf857x, pca857x, and pca967x chips only expose one read and one
+ * write register. Writing a "one" bit (to match the reset state) lets
+ * that pin be used as an input; it's not an open-drain model, but acts
+ * a bit like one. This is described as "quasi-bidirectional"; read the
+ * chip documentation for details.
+ *
+ * Many other I2C GPIO expander chips (like the pca953x models) have
+ * more complex register models and more conventional circuitry using
+ * push/pull drivers. They often use the same 0x20..0x27 addresses as
+ * pcf857x parts, making the "legacy" I2C driver model problematic.
+ */
+struct pcf857x {
+ struct gpio_chip chip;
+ struct i2c_client *client;
+ struct mutex lock; /* protect 'out' */
+ unsigned int out; /* software latch */
+ unsigned int status; /* current status */
+ unsigned int irq_enabled; /* enabled irqs */
+
+ int (*write)(struct i2c_client *client, unsigned int data);
+ int (*read)(struct i2c_client *client);
+};
+
+/*-------------------------------------------------------------------------*/
+
+/* Talk to 8-bit I/O expander */
+
+static int i2c_write_le8(struct i2c_client *client, unsigned int data)
+{
+ return i2c_smbus_write_byte(client, data);
+}
+
+static int i2c_read_le8(struct i2c_client *client)
+{
+ return i2c_smbus_read_byte(client);
+}
+
+/* Talk to 16-bit I/O expander */
+
+static int i2c_write_le16(struct i2c_client *client, unsigned int word)
+{
+ u8 buf[2] = { word & 0xff, word >> 8, };
+ int status;
+
+ status = i2c_master_send(client, buf, 2);
+ return (status < 0) ? status : 0;
+}
+
+static int i2c_read_le16(struct i2c_client *client)
+{
+ u8 buf[2];
+ int status;
+
+ status = i2c_master_recv(client, buf, 2);
+ if (status < 0)
+ return status;
+ return (buf[1] << 8) | buf[0];
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int pcf857x_input(struct gpio_chip *chip, unsigned int offset)
+{
+ struct pcf857x *gpio = gpiochip_get_data(chip);
+ int status;
+
+ mutex_lock(&gpio->lock);
+ gpio->out |= (1 << offset);
+ status = gpio->write(gpio->client, gpio->out);
+ mutex_unlock(&gpio->lock);
+
+ return status;
+}
+
+static int pcf857x_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct pcf857x *gpio = gpiochip_get_data(chip);
+ int value;
+
+ value = gpio->read(gpio->client);
+ return (value < 0) ? value : !!(value & (1 << offset));
+}
+
+static int pcf857x_get_multiple(struct gpio_chip *chip, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct pcf857x *gpio = gpiochip_get_data(chip);
+ int value = gpio->read(gpio->client);
+
+ if (value < 0)
+ return value;
+
+ *bits &= ~*mask;
+ *bits |= value & *mask;
+
+ return 0;
+}
+
+static int pcf857x_output(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct pcf857x *gpio = gpiochip_get_data(chip);
+ unsigned int bit = 1 << offset;
+ int status;
+
+ mutex_lock(&gpio->lock);
+ if (value)
+ gpio->out |= bit;
+ else
+ gpio->out &= ~bit;
+ status = gpio->write(gpio->client, gpio->out);
+ mutex_unlock(&gpio->lock);
+
+ return status;
+}
+
+static void pcf857x_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ pcf857x_output(chip, offset, value);
+}
+
+static void pcf857x_set_multiple(struct gpio_chip *chip, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct pcf857x *gpio = gpiochip_get_data(chip);
+
+ mutex_lock(&gpio->lock);
+ gpio->out &= ~*mask;
+ gpio->out |= *bits & *mask;
+ gpio->write(gpio->client, gpio->out);
+ mutex_unlock(&gpio->lock);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static irqreturn_t pcf857x_irq(int irq, void *data)
+{
+ struct pcf857x *gpio = data;
+ unsigned long change, i, status;
+
+ status = gpio->read(gpio->client);
+
+ /*
+ * call the interrupt handler iff gpio is used as
+ * interrupt source, just to avoid bad irqs
+ */
+ mutex_lock(&gpio->lock);
+ change = (gpio->status ^ status) & gpio->irq_enabled;
+ gpio->status = status;
+ mutex_unlock(&gpio->lock);
+
+ for_each_set_bit(i, &change, gpio->chip.ngpio)
+ handle_nested_irq(irq_find_mapping(gpio->chip.irq.domain, i));
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * NOP functions
+ */
+static void noop(struct irq_data *data) { }
+
+static int pcf857x_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+ struct pcf857x *gpio = irq_data_get_irq_chip_data(data);
+
+ return irq_set_irq_wake(gpio->client->irq, on);
+}
+
+static void pcf857x_irq_enable(struct irq_data *data)
+{
+ struct pcf857x *gpio = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ gpiochip_enable_irq(&gpio->chip, hwirq);
+ gpio->irq_enabled |= (1 << hwirq);
+}
+
+static void pcf857x_irq_disable(struct irq_data *data)
+{
+ struct pcf857x *gpio = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ gpio->irq_enabled &= ~(1 << hwirq);
+ gpiochip_disable_irq(&gpio->chip, hwirq);
+}
+
+static void pcf857x_irq_bus_lock(struct irq_data *data)
+{
+ struct pcf857x *gpio = irq_data_get_irq_chip_data(data);
+
+ mutex_lock(&gpio->lock);
+}
+
+static void pcf857x_irq_bus_sync_unlock(struct irq_data *data)
+{
+ struct pcf857x *gpio = irq_data_get_irq_chip_data(data);
+
+ mutex_unlock(&gpio->lock);
+}
+
+static const struct irq_chip pcf857x_irq_chip = {
+ .name = "pcf857x",
+ .irq_enable = pcf857x_irq_enable,
+ .irq_disable = pcf857x_irq_disable,
+ .irq_ack = noop,
+ .irq_mask = noop,
+ .irq_unmask = noop,
+ .irq_set_wake = pcf857x_irq_set_wake,
+ .irq_bus_lock = pcf857x_irq_bus_lock,
+ .irq_bus_sync_unlock = pcf857x_irq_bus_sync_unlock,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int pcf857x_probe(struct i2c_client *client)
+{
+ struct pcf857x *gpio;
+ unsigned int n_latch = 0;
+ int status;
+
+ device_property_read_u32(&client->dev, "lines-initial-states", &n_latch);
+
+ /* Allocate, initialize, and register this gpio_chip. */
+ gpio = devm_kzalloc(&client->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ mutex_init(&gpio->lock);
+
+ gpio->chip.base = -1;
+ gpio->chip.can_sleep = true;
+ gpio->chip.parent = &client->dev;
+ gpio->chip.owner = THIS_MODULE;
+ gpio->chip.get = pcf857x_get;
+ gpio->chip.get_multiple = pcf857x_get_multiple;
+ gpio->chip.set = pcf857x_set;
+ gpio->chip.set_multiple = pcf857x_set_multiple;
+ gpio->chip.direction_input = pcf857x_input;
+ gpio->chip.direction_output = pcf857x_output;
+ gpio->chip.ngpio = (uintptr_t)i2c_get_match_data(client);
+
+ /* NOTE: the OnSemi jlc1562b is also largely compatible with
+ * these parts, notably for output. It has a low-resolution
+ * DAC instead of pin change IRQs; and its inputs can be the
+ * result of comparators.
+ */
+
+ /* 8574 addresses are 0x20..0x27; 8574a uses 0x38..0x3f;
+ * 9670, 9672, 9764, and 9764a use quite a variety.
+ *
+ * NOTE: we don't distinguish here between *4 and *4a parts.
+ */
+ if (gpio->chip.ngpio == 8) {
+ gpio->write = i2c_write_le8;
+ gpio->read = i2c_read_le8;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE))
+ status = -EIO;
+
+ /* fail if there's no chip present */
+ else
+ status = i2c_smbus_read_byte(client);
+
+ /* '75/'75c addresses are 0x20..0x27, just like the '74;
+ * the '75c doesn't have a current source pulling high.
+ * 9671, 9673, and 9765 use quite a variety of addresses.
+ *
+ * NOTE: we don't distinguish here between '75 and '75c parts.
+ */
+ } else if (gpio->chip.ngpio == 16) {
+ gpio->write = i2c_write_le16;
+ gpio->read = i2c_read_le16;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ status = -EIO;
+
+ /* fail if there's no chip present */
+ else
+ status = i2c_read_le16(client);
+
+ } else {
+ dev_dbg(&client->dev, "unsupported number of gpios\n");
+ status = -EINVAL;
+ }
+
+ if (status < 0)
+ goto fail;
+
+ gpio->chip.label = client->name;
+
+ gpio->client = client;
+ i2c_set_clientdata(client, gpio);
+
+ /* NOTE: these chips have strange "quasi-bidirectional" I/O pins.
+ * We can't actually know whether a pin is configured (a) as output
+ * and driving the signal low, or (b) as input and reporting a low
+ * value ... without knowing the last value written since the chip
+ * came out of reset (if any). We can't read the latched output.
+ *
+ * In short, the only reliable solution for setting up pin direction
+ * is to do it explicitly. The setup() method can do that, but it
+ * may cause transient glitching since it can't know the last value
+ * written (some pins may need to be driven low).
+ *
+ * Using n_latch avoids that trouble. When left initialized to zero,
+ * our software copy of the "latch" then matches the chip's all-ones
+ * reset state. Otherwise it flags pins to be driven low.
+ */
+ gpio->out = ~n_latch;
+ gpio->status = gpio->read(gpio->client);
+
+ /* Enable irqchip if we have an interrupt */
+ if (client->irq) {
+ struct gpio_irq_chip *girq;
+
+ status = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, pcf857x_irq, IRQF_ONESHOT |
+ IRQF_TRIGGER_FALLING | IRQF_SHARED,
+ dev_name(&client->dev), gpio);
+ if (status)
+ goto fail;
+
+ girq = &gpio->chip.irq;
+ gpio_irq_chip_set_chip(girq, &pcf857x_irq_chip);
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_level_irq;
+ girq->threaded = true;
+ }
+
+ status = devm_gpiochip_add_data(&client->dev, &gpio->chip, gpio);
+ if (status < 0)
+ goto fail;
+
+ dev_info(&client->dev, "probed\n");
+
+ return 0;
+
+fail:
+ dev_dbg(&client->dev, "probe error %d for '%s'\n", status,
+ client->name);
+
+ return status;
+}
+
+static void pcf857x_shutdown(struct i2c_client *client)
+{
+ struct pcf857x *gpio = i2c_get_clientdata(client);
+
+ /* Drive all the I/O lines high */
+ gpio->write(gpio->client, BIT(gpio->chip.ngpio) - 1);
+}
+
+static struct i2c_driver pcf857x_driver = {
+ .driver = {
+ .name = "pcf857x",
+ .of_match_table = pcf857x_of_table,
+ },
+ .probe = pcf857x_probe,
+ .shutdown = pcf857x_shutdown,
+ .id_table = pcf857x_id,
+};
+
+static int __init pcf857x_init(void)
+{
+ return i2c_add_driver(&pcf857x_driver);
+}
+/* register after i2c postcore initcall and before
+ * subsys initcalls that may rely on these GPIOs
+ */
+subsys_initcall(pcf857x_init);
+
+static void __exit pcf857x_exit(void)
+{
+ i2c_del_driver(&pcf857x_driver);
+}
+module_exit(pcf857x_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Brownell");
diff --git a/drivers/gpio/gpio-pch.c b/drivers/gpio/gpio-pch.c
new file mode 100644
index 0000000000..ee37ecb615
--- /dev/null
+++ b/drivers/gpio/gpio-pch.c
@@ -0,0 +1,453 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2011 LAPIS Semiconductor Co., Ltd.
+ */
+#include <linux/bits.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#define PCH_EDGE_FALLING 0
+#define PCH_EDGE_RISING 1
+#define PCH_LEVEL_L 2
+#define PCH_LEVEL_H 3
+#define PCH_EDGE_BOTH 4
+#define PCH_IM_MASK GENMASK(2, 0)
+
+#define PCH_IRQ_BASE 24
+
+struct pch_regs {
+ u32 ien;
+ u32 istatus;
+ u32 idisp;
+ u32 iclr;
+ u32 imask;
+ u32 imaskclr;
+ u32 po;
+ u32 pi;
+ u32 pm;
+ u32 im0;
+ u32 im1;
+ u32 reserved[3];
+ u32 gpio_use_sel;
+ u32 reset;
+};
+
+#define PCI_DEVICE_ID_INTEL_EG20T_PCH 0x8803
+#define PCI_DEVICE_ID_ROHM_ML7223m_IOH 0x8014
+#define PCI_DEVICE_ID_ROHM_ML7223n_IOH 0x8043
+#define PCI_DEVICE_ID_ROHM_EG20T_PCH 0x8803
+
+enum pch_type_t {
+ INTEL_EG20T_PCH,
+ OKISEMI_ML7223m_IOH, /* LAPIS Semiconductor ML7223 IOH PCIe Bus-m */
+ OKISEMI_ML7223n_IOH /* LAPIS Semiconductor ML7223 IOH PCIe Bus-n */
+};
+
+/* Specifies number of GPIO PINS */
+static int gpio_pins[] = {
+ [INTEL_EG20T_PCH] = 12,
+ [OKISEMI_ML7223m_IOH] = 8,
+ [OKISEMI_ML7223n_IOH] = 8,
+};
+
+/**
+ * struct pch_gpio_reg_data - The register store data.
+ * @ien_reg: To store contents of IEN register.
+ * @imask_reg: To store contents of IMASK register.
+ * @po_reg: To store contents of PO register.
+ * @pm_reg: To store contents of PM register.
+ * @im0_reg: To store contents of IM0 register.
+ * @im1_reg: To store contents of IM1 register.
+ * @gpio_use_sel_reg : To store contents of GPIO_USE_SEL register.
+ * (Only ML7223 Bus-n)
+ */
+struct pch_gpio_reg_data {
+ u32 ien_reg;
+ u32 imask_reg;
+ u32 po_reg;
+ u32 pm_reg;
+ u32 im0_reg;
+ u32 im1_reg;
+ u32 gpio_use_sel_reg;
+};
+
+/**
+ * struct pch_gpio - GPIO private data structure.
+ * @base: PCI base address of Memory mapped I/O register.
+ * @reg: Memory mapped PCH GPIO register list.
+ * @dev: Pointer to device structure.
+ * @gpio: Data for GPIO infrastructure.
+ * @pch_gpio_reg: Memory mapped Register data is saved here
+ * when suspend.
+ * @lock: Used for register access protection
+ * @irq_base: Save base of IRQ number for interrupt
+ * @ioh: IOH ID
+ * @spinlock: Used for register access protection
+ */
+struct pch_gpio {
+ void __iomem *base;
+ struct pch_regs __iomem *reg;
+ struct device *dev;
+ struct gpio_chip gpio;
+ struct pch_gpio_reg_data pch_gpio_reg;
+ int irq_base;
+ enum pch_type_t ioh;
+ spinlock_t spinlock;
+};
+
+static void pch_gpio_set(struct gpio_chip *gpio, unsigned int nr, int val)
+{
+ u32 reg_val;
+ struct pch_gpio *chip = gpiochip_get_data(gpio);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ reg_val = ioread32(&chip->reg->po);
+ if (val)
+ reg_val |= BIT(nr);
+ else
+ reg_val &= ~BIT(nr);
+
+ iowrite32(reg_val, &chip->reg->po);
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+}
+
+static int pch_gpio_get(struct gpio_chip *gpio, unsigned int nr)
+{
+ struct pch_gpio *chip = gpiochip_get_data(gpio);
+
+ return !!(ioread32(&chip->reg->pi) & BIT(nr));
+}
+
+static int pch_gpio_direction_output(struct gpio_chip *gpio, unsigned int nr,
+ int val)
+{
+ struct pch_gpio *chip = gpiochip_get_data(gpio);
+ u32 pm;
+ u32 reg_val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+
+ reg_val = ioread32(&chip->reg->po);
+ if (val)
+ reg_val |= BIT(nr);
+ else
+ reg_val &= ~BIT(nr);
+ iowrite32(reg_val, &chip->reg->po);
+
+ pm = ioread32(&chip->reg->pm);
+ pm &= BIT(gpio_pins[chip->ioh]) - 1;
+ pm |= BIT(nr);
+ iowrite32(pm, &chip->reg->pm);
+
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 0;
+}
+
+static int pch_gpio_direction_input(struct gpio_chip *gpio, unsigned int nr)
+{
+ struct pch_gpio *chip = gpiochip_get_data(gpio);
+ u32 pm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ pm = ioread32(&chip->reg->pm);
+ pm &= BIT(gpio_pins[chip->ioh]) - 1;
+ pm &= ~BIT(nr);
+ iowrite32(pm, &chip->reg->pm);
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 0;
+}
+
+/*
+ * Save register configuration and disable interrupts.
+ */
+static void __maybe_unused pch_gpio_save_reg_conf(struct pch_gpio *chip)
+{
+ chip->pch_gpio_reg.ien_reg = ioread32(&chip->reg->ien);
+ chip->pch_gpio_reg.imask_reg = ioread32(&chip->reg->imask);
+ chip->pch_gpio_reg.po_reg = ioread32(&chip->reg->po);
+ chip->pch_gpio_reg.pm_reg = ioread32(&chip->reg->pm);
+ chip->pch_gpio_reg.im0_reg = ioread32(&chip->reg->im0);
+ if (chip->ioh == INTEL_EG20T_PCH)
+ chip->pch_gpio_reg.im1_reg = ioread32(&chip->reg->im1);
+ if (chip->ioh == OKISEMI_ML7223n_IOH)
+ chip->pch_gpio_reg.gpio_use_sel_reg = ioread32(&chip->reg->gpio_use_sel);
+}
+
+/*
+ * This function restores the register configuration of the GPIO device.
+ */
+static void __maybe_unused pch_gpio_restore_reg_conf(struct pch_gpio *chip)
+{
+ iowrite32(chip->pch_gpio_reg.ien_reg, &chip->reg->ien);
+ iowrite32(chip->pch_gpio_reg.imask_reg, &chip->reg->imask);
+ /* to store contents of PO register */
+ iowrite32(chip->pch_gpio_reg.po_reg, &chip->reg->po);
+ /* to store contents of PM register */
+ iowrite32(chip->pch_gpio_reg.pm_reg, &chip->reg->pm);
+ iowrite32(chip->pch_gpio_reg.im0_reg, &chip->reg->im0);
+ if (chip->ioh == INTEL_EG20T_PCH)
+ iowrite32(chip->pch_gpio_reg.im1_reg, &chip->reg->im1);
+ if (chip->ioh == OKISEMI_ML7223n_IOH)
+ iowrite32(chip->pch_gpio_reg.gpio_use_sel_reg, &chip->reg->gpio_use_sel);
+}
+
+static int pch_gpio_to_irq(struct gpio_chip *gpio, unsigned int offset)
+{
+ struct pch_gpio *chip = gpiochip_get_data(gpio);
+
+ return chip->irq_base + offset;
+}
+
+static void pch_gpio_setup(struct pch_gpio *chip)
+{
+ struct gpio_chip *gpio = &chip->gpio;
+
+ gpio->label = dev_name(chip->dev);
+ gpio->parent = chip->dev;
+ gpio->owner = THIS_MODULE;
+ gpio->direction_input = pch_gpio_direction_input;
+ gpio->get = pch_gpio_get;
+ gpio->direction_output = pch_gpio_direction_output;
+ gpio->set = pch_gpio_set;
+ gpio->base = -1;
+ gpio->ngpio = gpio_pins[chip->ioh];
+ gpio->can_sleep = false;
+ gpio->to_irq = pch_gpio_to_irq;
+}
+
+static int pch_irq_type(struct irq_data *d, unsigned int type)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct pch_gpio *chip = gc->private;
+ u32 im, im_pos, val;
+ u32 __iomem *im_reg;
+ unsigned long flags;
+ int ch, irq = d->irq;
+
+ ch = irq - chip->irq_base;
+ if (irq < chip->irq_base + 8) {
+ im_reg = &chip->reg->im0;
+ im_pos = ch - 0;
+ } else {
+ im_reg = &chip->reg->im1;
+ im_pos = ch - 8;
+ }
+ dev_dbg(chip->dev, "irq=%d type=%d ch=%d pos=%d\n", irq, type, ch, im_pos);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ val = PCH_EDGE_RISING;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ val = PCH_EDGE_FALLING;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ val = PCH_EDGE_BOTH;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ val = PCH_LEVEL_H;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ val = PCH_LEVEL_L;
+ break;
+ default:
+ return 0;
+ }
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+
+ /* Set interrupt mode */
+ im = ioread32(im_reg) & ~(PCH_IM_MASK << (im_pos * 4));
+ iowrite32(im | (val << (im_pos * 4)), im_reg);
+
+ /* And the handler */
+ if (type & IRQ_TYPE_LEVEL_MASK)
+ irq_set_handler_locked(d, handle_level_irq);
+ else if (type & IRQ_TYPE_EDGE_BOTH)
+ irq_set_handler_locked(d, handle_edge_irq);
+
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+ return 0;
+}
+
+static void pch_irq_unmask(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct pch_gpio *chip = gc->private;
+
+ iowrite32(BIT(d->irq - chip->irq_base), &chip->reg->imaskclr);
+}
+
+static void pch_irq_mask(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct pch_gpio *chip = gc->private;
+
+ iowrite32(BIT(d->irq - chip->irq_base), &chip->reg->imask);
+}
+
+static void pch_irq_ack(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct pch_gpio *chip = gc->private;
+
+ iowrite32(BIT(d->irq - chip->irq_base), &chip->reg->iclr);
+}
+
+static irqreturn_t pch_gpio_handler(int irq, void *dev_id)
+{
+ struct pch_gpio *chip = dev_id;
+ unsigned long reg_val = ioread32(&chip->reg->istatus);
+ int i;
+
+ dev_vdbg(chip->dev, "irq=%d status=0x%lx\n", irq, reg_val);
+
+ reg_val &= BIT(gpio_pins[chip->ioh]) - 1;
+
+ for_each_set_bit(i, &reg_val, gpio_pins[chip->ioh])
+ generic_handle_irq(chip->irq_base + i);
+
+ return IRQ_RETVAL(reg_val);
+}
+
+static int pch_gpio_alloc_generic_chip(struct pch_gpio *chip,
+ unsigned int irq_start,
+ unsigned int num)
+{
+ struct irq_chip_generic *gc;
+ struct irq_chip_type *ct;
+ int rv;
+
+ gc = devm_irq_alloc_generic_chip(chip->dev, "pch_gpio", 1, irq_start,
+ chip->base, handle_simple_irq);
+ if (!gc)
+ return -ENOMEM;
+
+ gc->private = chip;
+ ct = gc->chip_types;
+
+ ct->chip.irq_ack = pch_irq_ack;
+ ct->chip.irq_mask = pch_irq_mask;
+ ct->chip.irq_unmask = pch_irq_unmask;
+ ct->chip.irq_set_type = pch_irq_type;
+
+ rv = devm_irq_setup_generic_chip(chip->dev, gc, IRQ_MSK(num),
+ IRQ_GC_INIT_MASK_CACHE,
+ IRQ_NOREQUEST | IRQ_NOPROBE, 0);
+
+ return rv;
+}
+
+static int pch_gpio_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct device *dev = &pdev->dev;
+ s32 ret;
+ struct pch_gpio *chip;
+ int irq_base;
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+
+ chip->dev = dev;
+ ret = pcim_enable_device(pdev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable PCI device\n");
+
+ ret = pcim_iomap_regions(pdev, BIT(1), KBUILD_MODNAME);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request and map PCI regions\n");
+
+ chip->base = pcim_iomap_table(pdev)[1];
+ chip->ioh = id->driver_data;
+ chip->reg = chip->base;
+ pci_set_drvdata(pdev, chip);
+ spin_lock_init(&chip->spinlock);
+ pch_gpio_setup(chip);
+
+ ret = devm_gpiochip_add_data(dev, &chip->gpio, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register GPIO\n");
+
+ irq_base = devm_irq_alloc_descs(dev, -1, 0,
+ gpio_pins[chip->ioh], NUMA_NO_NODE);
+ if (irq_base < 0) {
+ dev_warn(dev, "PCH gpio: Failed to get IRQ base num\n");
+ chip->irq_base = -1;
+ return 0;
+ }
+ chip->irq_base = irq_base;
+
+ /* Mask all interrupts, but enable them */
+ iowrite32(BIT(gpio_pins[chip->ioh]) - 1, &chip->reg->imask);
+ iowrite32(BIT(gpio_pins[chip->ioh]) - 1, &chip->reg->ien);
+
+ ret = devm_request_irq(dev, pdev->irq, pch_gpio_handler,
+ IRQF_SHARED, KBUILD_MODNAME, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request IRQ\n");
+
+ return pch_gpio_alloc_generic_chip(chip, irq_base, gpio_pins[chip->ioh]);
+}
+
+static int __maybe_unused pch_gpio_suspend(struct device *dev)
+{
+ struct pch_gpio *chip = dev_get_drvdata(dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ pch_gpio_save_reg_conf(chip);
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 0;
+}
+
+static int __maybe_unused pch_gpio_resume(struct device *dev)
+{
+ struct pch_gpio *chip = dev_get_drvdata(dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ iowrite32(0x01, &chip->reg->reset);
+ iowrite32(0x00, &chip->reg->reset);
+ pch_gpio_restore_reg_conf(chip);
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(pch_gpio_pm_ops, pch_gpio_suspend, pch_gpio_resume);
+
+static const struct pci_device_id pch_gpio_pcidev_id[] = {
+ { PCI_DEVICE_DATA(INTEL, EG20T_PCH, INTEL_EG20T_PCH) },
+ { PCI_DEVICE_DATA(ROHM, ML7223m_IOH, OKISEMI_ML7223m_IOH) },
+ { PCI_DEVICE_DATA(ROHM, ML7223n_IOH, OKISEMI_ML7223n_IOH) },
+ { PCI_DEVICE_DATA(ROHM, EG20T_PCH, INTEL_EG20T_PCH) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, pch_gpio_pcidev_id);
+
+static struct pci_driver pch_gpio_driver = {
+ .name = "pch_gpio",
+ .id_table = pch_gpio_pcidev_id,
+ .probe = pch_gpio_probe,
+ .driver = {
+ .pm = &pch_gpio_pm_ops,
+ },
+};
+
+module_pci_driver(pch_gpio_driver);
+
+MODULE_DESCRIPTION("PCH GPIO PCI Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-pci-idio-16.c b/drivers/gpio/gpio-pci-idio-16.c
new file mode 100644
index 0000000000..44c0a21b1d
--- /dev/null
+++ b/drivers/gpio/gpio-pci-idio-16.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for the ACCES PCI-IDIO-16
+ * Copyright (C) 2017 William Breathitt Gray
+ */
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include "gpio-idio-16.h"
+
+static const struct regmap_range idio_16_wr_ranges[] = {
+ regmap_reg_range(0x0, 0x2), regmap_reg_range(0x3, 0x4),
+};
+static const struct regmap_range idio_16_rd_ranges[] = {
+ regmap_reg_range(0x1, 0x2), regmap_reg_range(0x5, 0x6),
+};
+static const struct regmap_range idio_16_precious_ranges[] = {
+ regmap_reg_range(0x2, 0x2),
+};
+static const struct regmap_access_table idio_16_wr_table = {
+ .yes_ranges = idio_16_wr_ranges,
+ .n_yes_ranges = ARRAY_SIZE(idio_16_wr_ranges),
+};
+static const struct regmap_access_table idio_16_rd_table = {
+ .yes_ranges = idio_16_rd_ranges,
+ .n_yes_ranges = ARRAY_SIZE(idio_16_rd_ranges),
+};
+static const struct regmap_access_table idio_16_precious_table = {
+ .yes_ranges = idio_16_precious_ranges,
+ .n_yes_ranges = ARRAY_SIZE(idio_16_precious_ranges),
+};
+static const struct regmap_config idio_16_regmap_config = {
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .val_bits = 8,
+ .io_port = true,
+ .wr_table = &idio_16_wr_table,
+ .rd_table = &idio_16_rd_table,
+ .volatile_table = &idio_16_rd_table,
+ .precious_table = &idio_16_precious_table,
+ .cache_type = REGCACHE_FLAT,
+ .use_raw_spinlock = true,
+};
+
+/* Only input lines (GPIO 16-31) support interrupts */
+#define IDIO_16_REGMAP_IRQ(_id) \
+ [16 + _id] = { \
+ .mask = BIT(2), \
+ .type = { .types_supported = IRQ_TYPE_EDGE_BOTH }, \
+ }
+
+static const struct regmap_irq idio_16_regmap_irqs[] = {
+ IDIO_16_REGMAP_IRQ(0), IDIO_16_REGMAP_IRQ(1), IDIO_16_REGMAP_IRQ(2), /* 0-2 */
+ IDIO_16_REGMAP_IRQ(3), IDIO_16_REGMAP_IRQ(4), IDIO_16_REGMAP_IRQ(5), /* 3-5 */
+ IDIO_16_REGMAP_IRQ(6), IDIO_16_REGMAP_IRQ(7), IDIO_16_REGMAP_IRQ(8), /* 6-8 */
+ IDIO_16_REGMAP_IRQ(9), IDIO_16_REGMAP_IRQ(10), IDIO_16_REGMAP_IRQ(11), /* 9-11 */
+ IDIO_16_REGMAP_IRQ(12), IDIO_16_REGMAP_IRQ(13), IDIO_16_REGMAP_IRQ(14), /* 12-14 */
+ IDIO_16_REGMAP_IRQ(15), /* 15 */
+};
+
+static int idio_16_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct device *const dev = &pdev->dev;
+ int err;
+ const size_t pci_bar_index = 2;
+ const char *const name = pci_name(pdev);
+ struct idio_16_regmap_config config = {};
+ void __iomem *regs;
+ struct regmap *map;
+
+ err = pcim_enable_device(pdev);
+ if (err) {
+ dev_err(dev, "Failed to enable PCI device (%d)\n", err);
+ return err;
+ }
+
+ err = pcim_iomap_regions(pdev, BIT(pci_bar_index), name);
+ if (err) {
+ dev_err(dev, "Unable to map PCI I/O addresses (%d)\n", err);
+ return err;
+ }
+
+ regs = pcim_iomap_table(pdev)[pci_bar_index];
+
+ map = devm_regmap_init_mmio(dev, regs, &idio_16_regmap_config);
+ if (IS_ERR(map))
+ return dev_err_probe(dev, PTR_ERR(map), "Unable to initialize register map\n");
+
+ config.parent = dev;
+ config.map = map;
+ config.regmap_irqs = idio_16_regmap_irqs;
+ config.num_regmap_irqs = ARRAY_SIZE(idio_16_regmap_irqs);
+ config.irq = pdev->irq;
+ config.filters = true;
+
+ return devm_idio_16_regmap_register(dev, &config);
+}
+
+static const struct pci_device_id idio_16_pci_dev_id[] = {
+ { PCI_DEVICE(0x494F, 0x0DC8) }, { 0 }
+};
+MODULE_DEVICE_TABLE(pci, idio_16_pci_dev_id);
+
+static struct pci_driver idio_16_driver = {
+ .name = "pci-idio-16",
+ .id_table = idio_16_pci_dev_id,
+ .probe = idio_16_probe
+};
+
+module_pci_driver(idio_16_driver);
+
+MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
+MODULE_DESCRIPTION("ACCES PCI-IDIO-16 GPIO driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(GPIO_IDIO_16);
diff --git a/drivers/gpio/gpio-pcie-idio-24.c b/drivers/gpio/gpio-pcie-idio-24.c
new file mode 100644
index 0000000000..2efd1b1a08
--- /dev/null
+++ b/drivers/gpio/gpio-pcie-idio-24.c
@@ -0,0 +1,404 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for the ACCES PCIe-IDIO-24 family
+ * Copyright (C) 2018 William Breathitt Gray
+ *
+ * This driver supports the following ACCES devices: PCIe-IDIO-24,
+ * PCIe-IDI-24, PCIe-IDO-24, and PCIe-IDIO-12.
+ */
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/regmap.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+/*
+ * PLX PEX8311 PCI LCS_INTCSR Interrupt Control/Status
+ *
+ * Bit: Description
+ * 0: Enable Interrupt Sources (Bit 0)
+ * 1: Enable Interrupt Sources (Bit 1)
+ * 2: Generate Internal PCI Bus Internal SERR# Interrupt
+ * 3: Mailbox Interrupt Enable
+ * 4: Power Management Interrupt Enable
+ * 5: Power Management Interrupt
+ * 6: Slave Read Local Data Parity Check Error Enable
+ * 7: Slave Read Local Data Parity Check Error Status
+ * 8: Internal PCI Wire Interrupt Enable
+ * 9: PCI Express Doorbell Interrupt Enable
+ * 10: PCI Abort Interrupt Enable
+ * 11: Local Interrupt Input Enable
+ * 12: Retry Abort Enable
+ * 13: PCI Express Doorbell Interrupt Active
+ * 14: PCI Abort Interrupt Active
+ * 15: Local Interrupt Input Active
+ * 16: Local Interrupt Output Enable
+ * 17: Local Doorbell Interrupt Enable
+ * 18: DMA Channel 0 Interrupt Enable
+ * 19: DMA Channel 1 Interrupt Enable
+ * 20: Local Doorbell Interrupt Active
+ * 21: DMA Channel 0 Interrupt Active
+ * 22: DMA Channel 1 Interrupt Active
+ * 23: Built-In Self-Test (BIST) Interrupt Active
+ * 24: Direct Master was the Bus Master during a Master or Target Abort
+ * 25: DMA Channel 0 was the Bus Master during a Master or Target Abort
+ * 26: DMA Channel 1 was the Bus Master during a Master or Target Abort
+ * 27: Target Abort after internal 256 consecutive Master Retrys
+ * 28: PCI Bus wrote data to LCS_MBOX0
+ * 29: PCI Bus wrote data to LCS_MBOX1
+ * 30: PCI Bus wrote data to LCS_MBOX2
+ * 31: PCI Bus wrote data to LCS_MBOX3
+ */
+#define PLX_PEX8311_PCI_LCS_INTCSR 0x68
+#define INTCSR_INTERNAL_PCI_WIRE BIT(8)
+#define INTCSR_LOCAL_INPUT BIT(11)
+#define IDIO_24_ENABLE_IRQ (INTCSR_INTERNAL_PCI_WIRE | INTCSR_LOCAL_INPUT)
+
+#define IDIO_24_OUT_BASE 0x0
+#define IDIO_24_TTLCMOS_OUT_REG 0x3
+#define IDIO_24_IN_BASE 0x4
+#define IDIO_24_TTLCMOS_IN_REG 0x7
+#define IDIO_24_COS_STATUS_BASE 0x8
+#define IDIO_24_CONTROL_REG 0xC
+#define IDIO_24_COS_ENABLE 0xE
+#define IDIO_24_SOFT_RESET 0xF
+
+#define CONTROL_REG_OUT_MODE BIT(1)
+
+#define COS_ENABLE_RISING BIT(1)
+#define COS_ENABLE_FALLING BIT(4)
+#define COS_ENABLE_BOTH (COS_ENABLE_RISING | COS_ENABLE_FALLING)
+
+static const struct regmap_config pex8311_intcsr_regmap_config = {
+ .name = "pex8311_intcsr",
+ .reg_bits = 32,
+ .reg_stride = 1,
+ .reg_base = PLX_PEX8311_PCI_LCS_INTCSR,
+ .val_bits = 32,
+ .io_port = true,
+};
+
+static const struct regmap_range idio_24_wr_ranges[] = {
+ regmap_reg_range(0x0, 0x3), regmap_reg_range(0x8, 0xC),
+ regmap_reg_range(0xE, 0xF),
+};
+static const struct regmap_range idio_24_rd_ranges[] = {
+ regmap_reg_range(0x0, 0xC), regmap_reg_range(0xE, 0xF),
+};
+static const struct regmap_range idio_24_volatile_ranges[] = {
+ regmap_reg_range(0x4, 0xB), regmap_reg_range(0xF, 0xF),
+};
+static const struct regmap_access_table idio_24_wr_table = {
+ .yes_ranges = idio_24_wr_ranges,
+ .n_yes_ranges = ARRAY_SIZE(idio_24_wr_ranges),
+};
+static const struct regmap_access_table idio_24_rd_table = {
+ .yes_ranges = idio_24_rd_ranges,
+ .n_yes_ranges = ARRAY_SIZE(idio_24_rd_ranges),
+};
+static const struct regmap_access_table idio_24_volatile_table = {
+ .yes_ranges = idio_24_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(idio_24_volatile_ranges),
+};
+
+static const struct regmap_config idio_24_regmap_config = {
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .val_bits = 8,
+ .io_port = true,
+ .wr_table = &idio_24_wr_table,
+ .rd_table = &idio_24_rd_table,
+ .volatile_table = &idio_24_volatile_table,
+ .cache_type = REGCACHE_FLAT,
+ .use_raw_spinlock = true,
+};
+
+#define IDIO_24_NGPIO_PER_REG 8
+#define IDIO_24_REGMAP_IRQ(_id) \
+ [24 + _id] = { \
+ .reg_offset = (_id) / IDIO_24_NGPIO_PER_REG, \
+ .mask = BIT((_id) % IDIO_24_NGPIO_PER_REG), \
+ .type = { .types_supported = IRQ_TYPE_EDGE_BOTH }, \
+ }
+#define IDIO_24_IIN_IRQ(_id) IDIO_24_REGMAP_IRQ(_id)
+#define IDIO_24_TTL_IRQ(_id) IDIO_24_REGMAP_IRQ(24 + _id)
+
+static const struct regmap_irq idio_24_regmap_irqs[] = {
+ IDIO_24_IIN_IRQ(0), IDIO_24_IIN_IRQ(1), IDIO_24_IIN_IRQ(2), /* IIN 0-2 */
+ IDIO_24_IIN_IRQ(3), IDIO_24_IIN_IRQ(4), IDIO_24_IIN_IRQ(5), /* IIN 3-5 */
+ IDIO_24_IIN_IRQ(6), IDIO_24_IIN_IRQ(7), IDIO_24_IIN_IRQ(8), /* IIN 6-8 */
+ IDIO_24_IIN_IRQ(9), IDIO_24_IIN_IRQ(10), IDIO_24_IIN_IRQ(11), /* IIN 9-11 */
+ IDIO_24_IIN_IRQ(12), IDIO_24_IIN_IRQ(13), IDIO_24_IIN_IRQ(14), /* IIN 12-14 */
+ IDIO_24_IIN_IRQ(15), IDIO_24_IIN_IRQ(16), IDIO_24_IIN_IRQ(17), /* IIN 15-17 */
+ IDIO_24_IIN_IRQ(18), IDIO_24_IIN_IRQ(19), IDIO_24_IIN_IRQ(20), /* IIN 18-20 */
+ IDIO_24_IIN_IRQ(21), IDIO_24_IIN_IRQ(22), IDIO_24_IIN_IRQ(23), /* IIN 21-23 */
+ IDIO_24_TTL_IRQ(0), IDIO_24_TTL_IRQ(1), IDIO_24_TTL_IRQ(2), /* TTL 0-2 */
+ IDIO_24_TTL_IRQ(3), IDIO_24_TTL_IRQ(4), IDIO_24_TTL_IRQ(5), /* TTL 3-5 */
+ IDIO_24_TTL_IRQ(6), IDIO_24_TTL_IRQ(7), /* TTL 6-7 */
+};
+
+/**
+ * struct idio_24_gpio - GPIO device private data structure
+ * @map: regmap for the device
+ * @lock: synchronization lock to prevent I/O race conditions
+ * @irq_type: type configuration for IRQs
+ */
+struct idio_24_gpio {
+ struct regmap *map;
+ raw_spinlock_t lock;
+ u8 irq_type;
+};
+
+static int idio_24_handle_mask_sync(const int index, const unsigned int mask_buf_def,
+ const unsigned int mask_buf, void *const irq_drv_data)
+{
+ const unsigned int type_mask = COS_ENABLE_BOTH << index;
+ struct idio_24_gpio *const idio24gpio = irq_drv_data;
+ u8 type;
+ int ret;
+
+ raw_spin_lock(&idio24gpio->lock);
+
+ /* if all are masked, then disable interrupts, else set to type */
+ type = (mask_buf == mask_buf_def) ? ~type_mask : idio24gpio->irq_type;
+
+ ret = regmap_update_bits(idio24gpio->map, IDIO_24_COS_ENABLE, type_mask, type);
+
+ raw_spin_unlock(&idio24gpio->lock);
+
+ return ret;
+}
+
+static int idio_24_set_type_config(unsigned int **const buf, const unsigned int type,
+ const struct regmap_irq *const irq_data, const int idx,
+ void *const irq_drv_data)
+{
+ const unsigned int offset = irq_data->reg_offset;
+ const unsigned int rising = COS_ENABLE_RISING << offset;
+ const unsigned int falling = COS_ENABLE_FALLING << offset;
+ const unsigned int mask = COS_ENABLE_BOTH << offset;
+ struct idio_24_gpio *const idio24gpio = irq_drv_data;
+ unsigned int new;
+ unsigned int cos_enable;
+ int ret;
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ new = rising;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ new = falling;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ new = mask;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ raw_spin_lock(&idio24gpio->lock);
+
+ /* replace old bitmap with new bitmap */
+ idio24gpio->irq_type = (idio24gpio->irq_type & ~mask) | (new & mask);
+
+ ret = regmap_read(idio24gpio->map, IDIO_24_COS_ENABLE, &cos_enable);
+ if (ret)
+ goto exit_unlock;
+
+ /* if COS is currently enabled then update the edge type */
+ if (cos_enable & mask) {
+ ret = regmap_update_bits(idio24gpio->map, IDIO_24_COS_ENABLE, mask,
+ idio24gpio->irq_type);
+ if (ret)
+ goto exit_unlock;
+ }
+
+exit_unlock:
+ raw_spin_unlock(&idio24gpio->lock);
+
+ 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)
+{
+ const unsigned int out_stride = offset / IDIO_24_NGPIO_PER_REG;
+ const unsigned int in_stride = (offset - 24) / IDIO_24_NGPIO_PER_REG;
+ struct regmap *const map = gpio_regmap_get_drvdata(gpio);
+ int err;
+ unsigned int ctrl_reg;
+
+ switch (base) {
+ case IDIO_24_OUT_BASE:
+ *mask = BIT(offset % IDIO_24_NGPIO_PER_REG);
+
+ /* FET Outputs */
+ if (offset < 24) {
+ *reg = IDIO_24_OUT_BASE + out_stride;
+ return 0;
+ }
+
+ /* Isolated Inputs */
+ if (offset < 48) {
+ *reg = IDIO_24_IN_BASE + in_stride;
+ return 0;
+ }
+
+ err = regmap_read(map, IDIO_24_CONTROL_REG, &ctrl_reg);
+ if (err)
+ return err;
+
+ /* TTL/CMOS Outputs */
+ if (ctrl_reg & CONTROL_REG_OUT_MODE) {
+ *reg = IDIO_24_TTLCMOS_OUT_REG;
+ return 0;
+ }
+
+ /* TTL/CMOS Inputs */
+ *reg = IDIO_24_TTLCMOS_IN_REG;
+ return 0;
+ case IDIO_24_CONTROL_REG:
+ /* We can only set direction for TTL/CMOS lines */
+ if (offset < 48)
+ return -EOPNOTSUPP;
+
+ *reg = IDIO_24_CONTROL_REG;
+ *mask = CONTROL_REG_OUT_MODE;
+ return 0;
+ default:
+ /* Should never reach this path */
+ return -EINVAL;
+ }
+}
+
+#define IDIO_24_NGPIO 56
+static const char *idio_24_names[IDIO_24_NGPIO] = {
+ "OUT0", "OUT1", "OUT2", "OUT3", "OUT4", "OUT5", "OUT6", "OUT7",
+ "OUT8", "OUT9", "OUT10", "OUT11", "OUT12", "OUT13", "OUT14", "OUT15",
+ "OUT16", "OUT17", "OUT18", "OUT19", "OUT20", "OUT21", "OUT22", "OUT23",
+ "IIN0", "IIN1", "IIN2", "IIN3", "IIN4", "IIN5", "IIN6", "IIN7",
+ "IIN8", "IIN9", "IIN10", "IIN11", "IIN12", "IIN13", "IIN14", "IIN15",
+ "IIN16", "IIN17", "IIN18", "IIN19", "IIN20", "IIN21", "IIN22", "IIN23",
+ "TTL0", "TTL1", "TTL2", "TTL3", "TTL4", "TTL5", "TTL6", "TTL7"
+};
+
+static int idio_24_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct device *const dev = &pdev->dev;
+ struct idio_24_gpio *idio24gpio;
+ int err;
+ const size_t pci_plx_bar_index = 1;
+ const size_t pci_bar_index = 2;
+ const char *const name = pci_name(pdev);
+ struct gpio_regmap_config gpio_config = {};
+ void __iomem *pex8311_regs;
+ void __iomem *idio_24_regs;
+ struct regmap *intcsr_map;
+ struct regmap_irq_chip *chip;
+ struct regmap_irq_chip_data *chip_data;
+
+ err = pcim_enable_device(pdev);
+ if (err) {
+ dev_err(dev, "Failed to enable PCI device (%d)\n", err);
+ return err;
+ }
+
+ err = pcim_iomap_regions(pdev, BIT(pci_plx_bar_index) | BIT(pci_bar_index), name);
+ if (err) {
+ dev_err(dev, "Unable to map PCI I/O addresses (%d)\n", err);
+ return err;
+ }
+
+ pex8311_regs = pcim_iomap_table(pdev)[pci_plx_bar_index];
+ idio_24_regs = pcim_iomap_table(pdev)[pci_bar_index];
+
+ intcsr_map = devm_regmap_init_mmio(dev, pex8311_regs, &pex8311_intcsr_regmap_config);
+ if (IS_ERR(intcsr_map))
+ return dev_err_probe(dev, PTR_ERR(intcsr_map),
+ "Unable to initialize PEX8311 register map\n");
+
+ idio24gpio = devm_kzalloc(dev, sizeof(*idio24gpio), GFP_KERNEL);
+ if (!idio24gpio)
+ return -ENOMEM;
+
+ idio24gpio->map = devm_regmap_init_mmio(dev, idio_24_regs, &idio_24_regmap_config);
+ if (IS_ERR(idio24gpio->map))
+ return dev_err_probe(dev, PTR_ERR(idio24gpio->map),
+ "Unable to initialize register map\n");
+
+ raw_spin_lock_init(&idio24gpio->lock);
+
+ /* Initialize all IRQ type configuration to IRQ_TYPE_EDGE_BOTH */
+ idio24gpio->irq_type = GENMASK(7, 0);
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->name = name;
+ chip->status_base = IDIO_24_COS_STATUS_BASE;
+ chip->mask_base = IDIO_24_COS_ENABLE;
+ chip->ack_base = IDIO_24_COS_STATUS_BASE;
+ chip->num_regs = 4;
+ chip->irqs = idio_24_regmap_irqs;
+ chip->num_irqs = ARRAY_SIZE(idio_24_regmap_irqs);
+ chip->handle_mask_sync = idio_24_handle_mask_sync;
+ chip->set_type_config = idio_24_set_type_config;
+ chip->irq_drv_data = idio24gpio;
+
+ /* Software board reset */
+ err = regmap_write(idio24gpio->map, IDIO_24_SOFT_RESET, 0);
+ if (err)
+ return err;
+ /*
+ * enable PLX PEX8311 internal PCI wire interrupt and local interrupt
+ * input
+ */
+ err = regmap_update_bits(intcsr_map, 0x0, IDIO_24_ENABLE_IRQ, IDIO_24_ENABLE_IRQ);
+ if (err)
+ return err;
+
+ err = devm_regmap_add_irq_chip(dev, idio24gpio->map, pdev->irq, 0, 0, chip, &chip_data);
+ if (err)
+ return dev_err_probe(dev, err, "IRQ registration failed\n");
+
+ gpio_config.parent = dev;
+ gpio_config.regmap = idio24gpio->map;
+ gpio_config.ngpio = IDIO_24_NGPIO;
+ gpio_config.names = idio_24_names;
+ gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(IDIO_24_OUT_BASE);
+ gpio_config.reg_set_base = GPIO_REGMAP_ADDR(IDIO_24_OUT_BASE);
+ gpio_config.reg_dir_out_base = GPIO_REGMAP_ADDR(IDIO_24_CONTROL_REG);
+ gpio_config.ngpio_per_reg = IDIO_24_NGPIO_PER_REG;
+ gpio_config.irq_domain = regmap_irq_get_domain(chip_data);
+ gpio_config.reg_mask_xlate = idio_24_reg_mask_xlate;
+ gpio_config.drvdata = idio24gpio->map;
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
+}
+
+static const struct pci_device_id idio_24_pci_dev_id[] = {
+ { PCI_DEVICE(0x494F, 0x0FD0) }, { PCI_DEVICE(0x494F, 0x0BD0) },
+ { PCI_DEVICE(0x494F, 0x07D0) }, { PCI_DEVICE(0x494F, 0x0FC0) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, idio_24_pci_dev_id);
+
+static struct pci_driver idio_24_driver = {
+ .name = "pcie-idio-24",
+ .id_table = idio_24_pci_dev_id,
+ .probe = idio_24_probe
+};
+
+module_pci_driver(idio_24_driver);
+
+MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
+MODULE_DESCRIPTION("ACCES PCIe-IDIO-24 GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-pisosr.c b/drivers/gpio/gpio-pisosr.c
new file mode 100644
index 0000000000..e3013e778e
--- /dev/null
+++ b/drivers/gpio/gpio-pisosr.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2015-2023 Texas Instruments Incorporated - https://www.ti.com/
+ * Andrew Davis <afd@ti.com>
+ */
+
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spi/spi.h>
+
+#define DEFAULT_NGPIO 8
+
+/**
+ * struct pisosr_gpio - GPIO driver data
+ * @chip: GPIO controller chip
+ * @spi: SPI device pointer
+ * @buffer: Buffer for device reads
+ * @buffer_size: Size of buffer
+ * @load_gpio: GPIO pin used to load input into device
+ * @lock: Protects read sequences
+ */
+struct pisosr_gpio {
+ struct gpio_chip chip;
+ struct spi_device *spi;
+ u8 *buffer;
+ size_t buffer_size;
+ struct gpio_desc *load_gpio;
+ struct mutex lock;
+};
+
+static int pisosr_gpio_refresh(struct pisosr_gpio *gpio)
+{
+ int ret;
+
+ mutex_lock(&gpio->lock);
+
+ if (gpio->load_gpio) {
+ gpiod_set_value_cansleep(gpio->load_gpio, 1);
+ udelay(1); /* registers load time (~10ns) */
+ gpiod_set_value_cansleep(gpio->load_gpio, 0);
+ udelay(1); /* registers recovery time (~5ns) */
+ }
+
+ ret = spi_read(gpio->spi, gpio->buffer, gpio->buffer_size);
+
+ mutex_unlock(&gpio->lock);
+
+ return ret;
+}
+
+static int pisosr_gpio_get_direction(struct gpio_chip *chip,
+ unsigned offset)
+{
+ /* This device always input */
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int pisosr_gpio_direction_input(struct gpio_chip *chip,
+ unsigned offset)
+{
+ /* This device always input */
+ return 0;
+}
+
+static int pisosr_gpio_direction_output(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ /* This device is input only */
+ return -EINVAL;
+}
+
+static int pisosr_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct pisosr_gpio *gpio = gpiochip_get_data(chip);
+
+ /* Refresh may not always be needed */
+ pisosr_gpio_refresh(gpio);
+
+ return (gpio->buffer[offset / 8] >> (offset % 8)) & 0x1;
+}
+
+static int pisosr_gpio_get_multiple(struct gpio_chip *chip,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct pisosr_gpio *gpio = gpiochip_get_data(chip);
+ unsigned long offset;
+ unsigned long gpio_mask;
+ unsigned long buffer_state;
+
+ pisosr_gpio_refresh(gpio);
+
+ bitmap_zero(bits, chip->ngpio);
+ for_each_set_clump8(offset, gpio_mask, mask, chip->ngpio) {
+ buffer_state = gpio->buffer[offset / 8] & gpio_mask;
+ bitmap_set_value8(bits, buffer_state, offset);
+ }
+
+ return 0;
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "pisosr-gpio",
+ .owner = THIS_MODULE,
+ .get_direction = pisosr_gpio_get_direction,
+ .direction_input = pisosr_gpio_direction_input,
+ .direction_output = pisosr_gpio_direction_output,
+ .get = pisosr_gpio_get,
+ .get_multiple = pisosr_gpio_get_multiple,
+ .base = -1,
+ .ngpio = DEFAULT_NGPIO,
+ .can_sleep = true,
+};
+
+static void pisosr_mutex_destroy(void *lock)
+{
+ mutex_destroy(lock);
+}
+
+static int pisosr_gpio_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct pisosr_gpio *gpio;
+ int ret;
+
+ gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->chip = template_chip;
+ gpio->chip.parent = dev;
+ of_property_read_u16(dev->of_node, "ngpios", &gpio->chip.ngpio);
+
+ gpio->spi = spi;
+
+ gpio->buffer_size = DIV_ROUND_UP(gpio->chip.ngpio, 8);
+ gpio->buffer = devm_kzalloc(dev, gpio->buffer_size, GFP_KERNEL);
+ if (!gpio->buffer)
+ return -ENOMEM;
+
+ gpio->load_gpio = devm_gpiod_get_optional(dev, "load", GPIOD_OUT_LOW);
+ if (IS_ERR(gpio->load_gpio))
+ return dev_err_probe(dev, PTR_ERR(gpio->load_gpio),
+ "Unable to allocate load GPIO\n");
+
+ mutex_init(&gpio->lock);
+ ret = devm_add_action_or_reset(dev, pisosr_mutex_destroy, &gpio->lock);
+ if (ret)
+ return ret;
+
+ ret = devm_gpiochip_add_data(dev, &gpio->chip, gpio);
+ if (ret < 0) {
+ dev_err(dev, "Unable to register gpiochip\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct spi_device_id pisosr_gpio_id_table[] = {
+ { "pisosr-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(spi, pisosr_gpio_id_table);
+
+static const struct of_device_id pisosr_gpio_of_match_table[] = {
+ { .compatible = "pisosr-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pisosr_gpio_of_match_table);
+
+static struct spi_driver pisosr_gpio_driver = {
+ .driver = {
+ .name = "pisosr-gpio",
+ .of_match_table = pisosr_gpio_of_match_table,
+ },
+ .probe = pisosr_gpio_probe,
+ .id_table = pisosr_gpio_id_table,
+};
+module_spi_driver(pisosr_gpio_driver);
+
+MODULE_AUTHOR("Andrew Davis <afd@ti.com>");
+MODULE_DESCRIPTION("SPI Compatible PISO Shift Register GPIO Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-pl061.c b/drivers/gpio/gpio-pl061.c
new file mode 100644
index 0000000000..9fc1f3dd41
--- /dev/null
+++ b/drivers/gpio/gpio-pl061.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2008, 2009 Provigent Ltd.
+ *
+ * Author: Baruch Siach <baruch@tkos.co.il>
+ *
+ * Driver for the ARM PrimeCell(tm) General Purpose Input/Output (PL061)
+ *
+ * Data sheet: ARM DDI 0190B, September 2000
+ */
+#include <linux/amba/bus.h>
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/module.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pm.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define GPIODIR 0x400
+#define GPIOIS 0x404
+#define GPIOIBE 0x408
+#define GPIOIEV 0x40C
+#define GPIOIE 0x410
+#define GPIORIS 0x414
+#define GPIOMIS 0x418
+#define GPIOIC 0x41C
+
+#define PL061_GPIO_NR 8
+
+#ifdef CONFIG_PM
+struct pl061_context_save_regs {
+ u8 gpio_data;
+ u8 gpio_dir;
+ u8 gpio_is;
+ u8 gpio_ibe;
+ u8 gpio_iev;
+ u8 gpio_ie;
+};
+#endif
+
+struct pl061 {
+ raw_spinlock_t lock;
+
+ void __iomem *base;
+ struct gpio_chip gc;
+ int parent_irq;
+
+#ifdef CONFIG_PM
+ struct pl061_context_save_regs csave_regs;
+#endif
+};
+
+static int pl061_get_direction(struct gpio_chip *gc, unsigned offset)
+{
+ struct pl061 *pl061 = gpiochip_get_data(gc);
+
+ if (readb(pl061->base + GPIODIR) & BIT(offset))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int pl061_direction_input(struct gpio_chip *gc, unsigned offset)
+{
+ struct pl061 *pl061 = gpiochip_get_data(gc);
+ unsigned long flags;
+ unsigned char gpiodir;
+
+ raw_spin_lock_irqsave(&pl061->lock, flags);
+ gpiodir = readb(pl061->base + GPIODIR);
+ gpiodir &= ~(BIT(offset));
+ writeb(gpiodir, pl061->base + GPIODIR);
+ raw_spin_unlock_irqrestore(&pl061->lock, flags);
+
+ return 0;
+}
+
+static int pl061_direction_output(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ struct pl061 *pl061 = gpiochip_get_data(gc);
+ unsigned long flags;
+ unsigned char gpiodir;
+
+ raw_spin_lock_irqsave(&pl061->lock, flags);
+ writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
+ gpiodir = readb(pl061->base + GPIODIR);
+ gpiodir |= BIT(offset);
+ writeb(gpiodir, pl061->base + GPIODIR);
+
+ /*
+ * gpio value is set again, because pl061 doesn't allow to set value of
+ * a gpio pin before configuring it in OUT mode.
+ */
+ writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
+ raw_spin_unlock_irqrestore(&pl061->lock, flags);
+
+ return 0;
+}
+
+static int pl061_get_value(struct gpio_chip *gc, unsigned offset)
+{
+ struct pl061 *pl061 = gpiochip_get_data(gc);
+
+ return !!readb(pl061->base + (BIT(offset + 2)));
+}
+
+static void pl061_set_value(struct gpio_chip *gc, unsigned offset, int value)
+{
+ struct pl061 *pl061 = gpiochip_get_data(gc);
+
+ writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
+}
+
+static int pl061_irq_type(struct irq_data *d, unsigned trigger)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pl061 *pl061 = gpiochip_get_data(gc);
+ int offset = irqd_to_hwirq(d);
+ unsigned long flags;
+ u8 gpiois, gpioibe, gpioiev;
+ u8 bit = BIT(offset);
+
+ 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)))
+ {
+ dev_err(gc->parent,
+ "trying to configure line %d for both level and edge "
+ "detection, choose one!\n",
+ offset);
+ return -EINVAL;
+ }
+
+
+ raw_spin_lock_irqsave(&pl061->lock, flags);
+
+ gpioiev = readb(pl061->base + GPIOIEV);
+ gpiois = readb(pl061->base + GPIOIS);
+ gpioibe = readb(pl061->base + GPIOIBE);
+
+ if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) {
+ bool polarity = trigger & IRQ_TYPE_LEVEL_HIGH;
+
+ /* Disable edge detection */
+ gpioibe &= ~bit;
+ /* Enable level detection */
+ gpiois |= bit;
+ /* Select polarity */
+ if (polarity)
+ gpioiev |= bit;
+ else
+ gpioiev &= ~bit;
+ irq_set_handler_locked(d, handle_level_irq);
+ dev_dbg(gc->parent, "line %d: IRQ on %s level\n",
+ offset,
+ polarity ? "HIGH" : "LOW");
+ } else if ((trigger & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) {
+ /* Disable level detection */
+ gpiois &= ~bit;
+ /* Select both edges, setting this makes GPIOEV be ignored */
+ gpioibe |= bit;
+ irq_set_handler_locked(d, handle_edge_irq);
+ dev_dbg(gc->parent, "line %d: IRQ on both edges\n", offset);
+ } else if ((trigger & IRQ_TYPE_EDGE_RISING) ||
+ (trigger & IRQ_TYPE_EDGE_FALLING)) {
+ bool rising = trigger & IRQ_TYPE_EDGE_RISING;
+
+ /* Disable level detection */
+ gpiois &= ~bit;
+ /* Clear detection on both edges */
+ gpioibe &= ~bit;
+ /* Select edge */
+ if (rising)
+ gpioiev |= bit;
+ else
+ gpioiev &= ~bit;
+ irq_set_handler_locked(d, handle_edge_irq);
+ dev_dbg(gc->parent, "line %d: IRQ on %s edge\n",
+ offset,
+ rising ? "RISING" : "FALLING");
+ } else {
+ /* No trigger: disable everything */
+ gpiois &= ~bit;
+ gpioibe &= ~bit;
+ gpioiev &= ~bit;
+ irq_set_handler_locked(d, handle_bad_irq);
+ dev_warn(gc->parent, "no trigger selected for line %d\n",
+ offset);
+ }
+
+ writeb(gpiois, pl061->base + GPIOIS);
+ writeb(gpioibe, pl061->base + GPIOIBE);
+ writeb(gpioiev, pl061->base + GPIOIEV);
+
+ raw_spin_unlock_irqrestore(&pl061->lock, flags);
+
+ return 0;
+}
+
+static void pl061_irq_handler(struct irq_desc *desc)
+{
+ unsigned long pending;
+ int offset;
+ struct gpio_chip *gc = irq_desc_get_handler_data(desc);
+ struct pl061 *pl061 = gpiochip_get_data(gc);
+ struct irq_chip *irqchip = irq_desc_get_chip(desc);
+
+ chained_irq_enter(irqchip, desc);
+
+ pending = readb(pl061->base + GPIOMIS);
+ if (pending) {
+ for_each_set_bit(offset, &pending, PL061_GPIO_NR)
+ generic_handle_domain_irq(gc->irq.domain,
+ offset);
+ }
+
+ chained_irq_exit(irqchip, desc);
+}
+
+static void pl061_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pl061 *pl061 = gpiochip_get_data(gc);
+ u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
+ u8 gpioie;
+
+ raw_spin_lock(&pl061->lock);
+ gpioie = readb(pl061->base + GPIOIE) & ~mask;
+ writeb(gpioie, pl061->base + GPIOIE);
+ raw_spin_unlock(&pl061->lock);
+
+ gpiochip_disable_irq(gc, d->hwirq);
+}
+
+static void pl061_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pl061 *pl061 = gpiochip_get_data(gc);
+ u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
+ u8 gpioie;
+
+ gpiochip_enable_irq(gc, d->hwirq);
+
+ raw_spin_lock(&pl061->lock);
+ gpioie = readb(pl061->base + GPIOIE) | mask;
+ writeb(gpioie, pl061->base + GPIOIE);
+ raw_spin_unlock(&pl061->lock);
+}
+
+/**
+ * pl061_irq_ack() - ACK an edge IRQ
+ * @d: IRQ data for this IRQ
+ *
+ * This gets called from the edge IRQ handler to ACK the edge IRQ
+ * in the GPIOIC (interrupt-clear) register. For level IRQs this is
+ * not needed: these go away when the level signal goes away.
+ */
+static void pl061_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pl061 *pl061 = gpiochip_get_data(gc);
+ u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
+
+ raw_spin_lock(&pl061->lock);
+ writeb(mask, pl061->base + GPIOIC);
+ raw_spin_unlock(&pl061->lock);
+}
+
+static int pl061_irq_set_wake(struct irq_data *d, unsigned int state)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct pl061 *pl061 = gpiochip_get_data(gc);
+
+ return irq_set_irq_wake(pl061->parent_irq, state);
+}
+
+static void pl061_irq_print_chip(struct irq_data *data, struct seq_file *p)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+
+ seq_printf(p, dev_name(gc->parent));
+}
+
+static const struct irq_chip pl061_irq_chip = {
+ .irq_ack = pl061_irq_ack,
+ .irq_mask = pl061_irq_mask,
+ .irq_unmask = pl061_irq_unmask,
+ .irq_set_type = pl061_irq_type,
+ .irq_set_wake = pl061_irq_set_wake,
+ .irq_print_chip = pl061_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct pl061 *pl061;
+ struct gpio_irq_chip *girq;
+ int ret, irq;
+
+ pl061 = devm_kzalloc(dev, sizeof(*pl061), GFP_KERNEL);
+ if (pl061 == NULL)
+ return -ENOMEM;
+
+ pl061->base = devm_ioremap_resource(dev, &adev->res);
+ if (IS_ERR(pl061->base))
+ return PTR_ERR(pl061->base);
+
+ raw_spin_lock_init(&pl061->lock);
+ pl061->gc.request = gpiochip_generic_request;
+ pl061->gc.free = gpiochip_generic_free;
+ pl061->gc.base = -1;
+ pl061->gc.get_direction = pl061_get_direction;
+ pl061->gc.direction_input = pl061_direction_input;
+ pl061->gc.direction_output = pl061_direction_output;
+ pl061->gc.get = pl061_get_value;
+ pl061->gc.set = pl061_set_value;
+ pl061->gc.ngpio = PL061_GPIO_NR;
+ pl061->gc.label = dev_name(dev);
+ pl061->gc.parent = dev;
+ pl061->gc.owner = THIS_MODULE;
+
+ /*
+ * irq_chip support
+ */
+ writeb(0, pl061->base + GPIOIE); /* disable irqs */
+ irq = adev->irq[0];
+ if (!irq)
+ dev_warn(&adev->dev, "IRQ support disabled\n");
+ pl061->parent_irq = irq;
+
+ girq = &pl061->gc.irq;
+ gpio_irq_chip_set_chip(girq, &pl061_irq_chip);
+ girq->parent_handler = pl061_irq_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(dev, 1, sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ girq->parents[0] = irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+
+ ret = devm_gpiochip_add_data(dev, &pl061->gc, pl061);
+ if (ret)
+ return ret;
+
+ amba_set_drvdata(adev, pl061);
+ dev_info(dev, "PL061 GPIO chip registered\n");
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int pl061_suspend(struct device *dev)
+{
+ struct pl061 *pl061 = dev_get_drvdata(dev);
+ int offset;
+
+ pl061->csave_regs.gpio_data = 0;
+ pl061->csave_regs.gpio_dir = readb(pl061->base + GPIODIR);
+ pl061->csave_regs.gpio_is = readb(pl061->base + GPIOIS);
+ pl061->csave_regs.gpio_ibe = readb(pl061->base + GPIOIBE);
+ pl061->csave_regs.gpio_iev = readb(pl061->base + GPIOIEV);
+ pl061->csave_regs.gpio_ie = readb(pl061->base + GPIOIE);
+
+ for (offset = 0; offset < PL061_GPIO_NR; offset++) {
+ if (pl061->csave_regs.gpio_dir & (BIT(offset)))
+ pl061->csave_regs.gpio_data |=
+ pl061_get_value(&pl061->gc, offset) << offset;
+ }
+
+ return 0;
+}
+
+static int pl061_resume(struct device *dev)
+{
+ struct pl061 *pl061 = dev_get_drvdata(dev);
+ int offset;
+
+ for (offset = 0; offset < PL061_GPIO_NR; offset++) {
+ if (pl061->csave_regs.gpio_dir & (BIT(offset)))
+ pl061_direction_output(&pl061->gc, offset,
+ pl061->csave_regs.gpio_data &
+ (BIT(offset)));
+ else
+ pl061_direction_input(&pl061->gc, offset);
+ }
+
+ writeb(pl061->csave_regs.gpio_is, pl061->base + GPIOIS);
+ writeb(pl061->csave_regs.gpio_ibe, pl061->base + GPIOIBE);
+ writeb(pl061->csave_regs.gpio_iev, pl061->base + GPIOIEV);
+ writeb(pl061->csave_regs.gpio_ie, pl061->base + GPIOIE);
+
+ return 0;
+}
+
+static const struct dev_pm_ops pl061_dev_pm_ops = {
+ .suspend = pl061_suspend,
+ .resume = pl061_resume,
+ .freeze = pl061_suspend,
+ .restore = pl061_resume,
+};
+#endif
+
+static const struct amba_id pl061_ids[] = {
+ {
+ .id = 0x00041061,
+ .mask = 0x000fffff,
+ },
+ { 0, 0 },
+};
+MODULE_DEVICE_TABLE(amba, pl061_ids);
+
+static struct amba_driver pl061_gpio_driver = {
+ .drv = {
+ .name = "pl061_gpio",
+#ifdef CONFIG_PM
+ .pm = &pl061_dev_pm_ops,
+#endif
+ },
+ .id_table = pl061_ids,
+ .probe = pl061_probe,
+};
+module_amba_driver(pl061_gpio_driver);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-pmic-eic-sprd.c b/drivers/gpio/gpio-pmic-eic-sprd.c
new file mode 100644
index 0000000000..01c0fd0a9d
--- /dev/null
+++ b/drivers/gpio/gpio-pmic-eic-sprd.c
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Spreadtrum Communications Inc.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* EIC registers definition */
+#define SPRD_PMIC_EIC_DATA 0x0
+#define SPRD_PMIC_EIC_DMSK 0x4
+#define SPRD_PMIC_EIC_IEV 0x14
+#define SPRD_PMIC_EIC_IE 0x18
+#define SPRD_PMIC_EIC_RIS 0x1c
+#define SPRD_PMIC_EIC_MIS 0x20
+#define SPRD_PMIC_EIC_IC 0x24
+#define SPRD_PMIC_EIC_TRIG 0x28
+#define SPRD_PMIC_EIC_CTRL0 0x40
+
+/*
+ * The PMIC EIC controller only has one bank, and each bank now can contain
+ * 16 EICs.
+ */
+#define SPRD_PMIC_EIC_PER_BANK_NR 16
+#define SPRD_PMIC_EIC_NR SPRD_PMIC_EIC_PER_BANK_NR
+#define SPRD_PMIC_EIC_DATA_MASK GENMASK(15, 0)
+#define SPRD_PMIC_EIC_BIT(x) ((x) & (SPRD_PMIC_EIC_PER_BANK_NR - 1))
+#define SPRD_PMIC_EIC_DBNC_MASK GENMASK(11, 0)
+
+/*
+ * These registers are modified under the irq bus lock and cached to avoid
+ * unnecessary writes in bus_sync_unlock.
+ */
+enum {
+ REG_IEV,
+ REG_IE,
+ REG_TRIG,
+ CACHE_NR_REGS
+};
+
+/**
+ * struct sprd_pmic_eic - PMIC EIC controller
+ * @chip: the gpio_chip structure.
+ * @map: the regmap from the parent device.
+ * @offset: the EIC controller's offset address of the PMIC.
+ * @reg: the array to cache the EIC registers.
+ * @buslock: for bus lock/sync and unlock.
+ * @irq: the interrupt number of the PMIC EIC conteroller.
+ */
+struct sprd_pmic_eic {
+ struct gpio_chip chip;
+ struct regmap *map;
+ u32 offset;
+ u8 reg[CACHE_NR_REGS];
+ struct mutex buslock;
+ int irq;
+};
+
+static void sprd_pmic_eic_update(struct gpio_chip *chip, unsigned int offset,
+ u16 reg, unsigned int val)
+{
+ struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip);
+ u32 shift = SPRD_PMIC_EIC_BIT(offset);
+
+ regmap_update_bits(pmic_eic->map, pmic_eic->offset + reg,
+ BIT(shift), val << shift);
+}
+
+static int sprd_pmic_eic_read(struct gpio_chip *chip, unsigned int offset,
+ u16 reg)
+{
+ struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip);
+ u32 value;
+ int ret;
+
+ ret = regmap_read(pmic_eic->map, pmic_eic->offset + reg, &value);
+ if (ret)
+ return ret;
+
+ return !!(value & BIT(SPRD_PMIC_EIC_BIT(offset)));
+}
+
+static int sprd_pmic_eic_request(struct gpio_chip *chip, unsigned int offset)
+{
+ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_DMSK, 1);
+ return 0;
+}
+
+static void sprd_pmic_eic_free(struct gpio_chip *chip, unsigned int offset)
+{
+ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_DMSK, 0);
+}
+
+static int sprd_pmic_eic_get(struct gpio_chip *chip, unsigned int offset)
+{
+ return sprd_pmic_eic_read(chip, offset, SPRD_PMIC_EIC_DATA);
+}
+
+static int sprd_pmic_eic_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ /* EICs are always input, nothing need to do here. */
+ return 0;
+}
+
+static void sprd_pmic_eic_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ /* EICs are always input, nothing need to do here. */
+}
+
+static int sprd_pmic_eic_set_debounce(struct gpio_chip *chip,
+ unsigned int offset,
+ unsigned int debounce)
+{
+ struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip);
+ u32 reg, value;
+ int ret;
+
+ reg = SPRD_PMIC_EIC_CTRL0 + SPRD_PMIC_EIC_BIT(offset) * 0x4;
+ ret = regmap_read(pmic_eic->map, pmic_eic->offset + reg, &value);
+ if (ret)
+ return ret;
+
+ value &= ~SPRD_PMIC_EIC_DBNC_MASK;
+ value |= (debounce / 1000) & SPRD_PMIC_EIC_DBNC_MASK;
+ return regmap_write(pmic_eic->map, pmic_eic->offset + reg, value);
+}
+
+static int sprd_pmic_eic_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ unsigned long param = pinconf_to_config_param(config);
+ u32 arg = pinconf_to_config_argument(config);
+
+ if (param == PIN_CONFIG_INPUT_DEBOUNCE)
+ return sprd_pmic_eic_set_debounce(chip, offset, arg);
+
+ return -ENOTSUPP;
+}
+
+static void sprd_pmic_eic_irq_mask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip);
+ u32 offset = irqd_to_hwirq(data);
+
+ pmic_eic->reg[REG_IE] = 0;
+ pmic_eic->reg[REG_TRIG] = 0;
+
+ gpiochip_disable_irq(chip, offset);
+}
+
+static void sprd_pmic_eic_irq_unmask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip);
+ u32 offset = irqd_to_hwirq(data);
+
+ gpiochip_enable_irq(chip, offset);
+
+ pmic_eic->reg[REG_IE] = 1;
+ pmic_eic->reg[REG_TRIG] = 1;
+}
+
+static int sprd_pmic_eic_irq_set_type(struct irq_data *data,
+ unsigned int flow_type)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip);
+
+ switch (flow_type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ pmic_eic->reg[REG_IEV] = 1;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ pmic_eic->reg[REG_IEV] = 0;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_EDGE_FALLING:
+ case IRQ_TYPE_EDGE_BOTH:
+ /*
+ * Will set the trigger level according to current EIC level
+ * in irq_bus_sync_unlock() interface, so here nothing to do.
+ */
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ return 0;
+}
+
+static void sprd_pmic_eic_bus_lock(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip);
+
+ mutex_lock(&pmic_eic->buslock);
+}
+
+static void sprd_pmic_eic_bus_sync_unlock(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip);
+ u32 trigger = irqd_get_trigger_type(data);
+ u32 offset = irqd_to_hwirq(data);
+ int state;
+
+ /* Set irq type */
+ if (trigger & IRQ_TYPE_EDGE_BOTH) {
+ state = sprd_pmic_eic_get(chip, offset);
+ if (state)
+ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_IEV, 0);
+ else
+ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_IEV, 1);
+ } else {
+ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_IEV,
+ pmic_eic->reg[REG_IEV]);
+ }
+
+ /* Set irq unmask */
+ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_IE,
+ pmic_eic->reg[REG_IE]);
+ /* Generate trigger start pulse for debounce EIC */
+ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_TRIG,
+ pmic_eic->reg[REG_TRIG]);
+
+ mutex_unlock(&pmic_eic->buslock);
+}
+
+static void sprd_pmic_eic_toggle_trigger(struct gpio_chip *chip,
+ unsigned int irq, unsigned int offset)
+{
+ u32 trigger = irq_get_trigger_type(irq);
+ int state, post_state;
+
+ if (!(trigger & IRQ_TYPE_EDGE_BOTH))
+ return;
+
+ state = sprd_pmic_eic_get(chip, offset);
+retry:
+ if (state)
+ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_IEV, 0);
+ else
+ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_IEV, 1);
+
+ post_state = sprd_pmic_eic_get(chip, offset);
+ if (state != post_state) {
+ dev_warn(chip->parent, "PMIC EIC level was changed.\n");
+ state = post_state;
+ goto retry;
+ }
+
+ /* Set irq unmask */
+ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_IE, 1);
+ /* Generate trigger start pulse for debounce EIC */
+ sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_TRIG, 1);
+}
+
+static irqreturn_t sprd_pmic_eic_irq_handler(int irq, void *data)
+{
+ struct sprd_pmic_eic *pmic_eic = data;
+ struct gpio_chip *chip = &pmic_eic->chip;
+ unsigned long status;
+ u32 n, girq, val;
+ int ret;
+
+ ret = regmap_read(pmic_eic->map, pmic_eic->offset + SPRD_PMIC_EIC_MIS,
+ &val);
+ if (ret)
+ return IRQ_RETVAL(ret);
+
+ status = val & SPRD_PMIC_EIC_DATA_MASK;
+
+ for_each_set_bit(n, &status, chip->ngpio) {
+ /* Clear the interrupt */
+ sprd_pmic_eic_update(chip, n, SPRD_PMIC_EIC_IC, 1);
+
+ girq = irq_find_mapping(chip->irq.domain, n);
+ handle_nested_irq(girq);
+
+ /*
+ * The PMIC EIC can only support level trigger, so we can
+ * toggle the level trigger to emulate the edge trigger.
+ */
+ sprd_pmic_eic_toggle_trigger(chip, girq, n);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static const struct irq_chip pmic_eic_irq_chip = {
+ .name = "sprd-pmic-eic",
+ .irq_mask = sprd_pmic_eic_irq_mask,
+ .irq_unmask = sprd_pmic_eic_irq_unmask,
+ .irq_set_type = sprd_pmic_eic_irq_set_type,
+ .irq_bus_lock = sprd_pmic_eic_bus_lock,
+ .irq_bus_sync_unlock = sprd_pmic_eic_bus_sync_unlock,
+ .flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int sprd_pmic_eic_probe(struct platform_device *pdev)
+{
+ struct gpio_irq_chip *irq;
+ struct sprd_pmic_eic *pmic_eic;
+ int ret;
+
+ pmic_eic = devm_kzalloc(&pdev->dev, sizeof(*pmic_eic), GFP_KERNEL);
+ if (!pmic_eic)
+ return -ENOMEM;
+
+ mutex_init(&pmic_eic->buslock);
+
+ pmic_eic->irq = platform_get_irq(pdev, 0);
+ if (pmic_eic->irq < 0)
+ return pmic_eic->irq;
+
+ pmic_eic->map = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!pmic_eic->map)
+ return -ENODEV;
+
+ ret = of_property_read_u32(pdev->dev.of_node, "reg", &pmic_eic->offset);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to get PMIC EIC base address.\n");
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, pmic_eic->irq, NULL,
+ sprd_pmic_eic_irq_handler,
+ IRQF_ONESHOT | IRQF_NO_SUSPEND,
+ dev_name(&pdev->dev), pmic_eic);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request PMIC EIC IRQ.\n");
+ return ret;
+ }
+
+ pmic_eic->chip.label = dev_name(&pdev->dev);
+ pmic_eic->chip.ngpio = SPRD_PMIC_EIC_NR;
+ pmic_eic->chip.base = -1;
+ pmic_eic->chip.parent = &pdev->dev;
+ pmic_eic->chip.direction_input = sprd_pmic_eic_direction_input;
+ pmic_eic->chip.request = sprd_pmic_eic_request;
+ pmic_eic->chip.free = sprd_pmic_eic_free;
+ pmic_eic->chip.set_config = sprd_pmic_eic_set_config;
+ pmic_eic->chip.set = sprd_pmic_eic_set;
+ pmic_eic->chip.get = sprd_pmic_eic_get;
+ pmic_eic->chip.can_sleep = true;
+
+ irq = &pmic_eic->chip.irq;
+ gpio_irq_chip_set_chip(irq, &pmic_eic_irq_chip);
+ irq->threaded = true;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &pmic_eic->chip, pmic_eic);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Could not register gpiochip %d.\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id sprd_pmic_eic_of_match[] = {
+ { .compatible = "sprd,sc2731-eic", },
+ { /* end of list */ }
+};
+MODULE_DEVICE_TABLE(of, sprd_pmic_eic_of_match);
+
+static struct platform_driver sprd_pmic_eic_driver = {
+ .probe = sprd_pmic_eic_probe,
+ .driver = {
+ .name = "sprd-pmic-eic",
+ .of_match_table = sprd_pmic_eic_of_match,
+ },
+};
+
+module_platform_driver(sprd_pmic_eic_driver);
+
+MODULE_DESCRIPTION("Spreadtrum PMIC EIC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-pxa.c b/drivers/gpio/gpio-pxa.c
new file mode 100644
index 0000000000..cae9661862
--- /dev/null
+++ b/drivers/gpio/gpio-pxa.c
@@ -0,0 +1,804 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/arch/arm/plat-pxa/gpio.c
+ *
+ * Generic PXA GPIO handling
+ *
+ * Author: Nicolas Pitre
+ * Created: Jun 15, 2001
+ * Copyright: MontaVista Software Inc.
+ */
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio-pxa.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/syscore_ops.h>
+#include <linux/slab.h>
+
+/*
+ * We handle the GPIOs by banks, each bank covers up to 32 GPIOs with
+ * one set of registers. The register offsets are organized below:
+ *
+ * GPLR GPDR GPSR GPCR GRER GFER GEDR
+ * BANK 0 - 0x0000 0x000C 0x0018 0x0024 0x0030 0x003C 0x0048
+ * BANK 1 - 0x0004 0x0010 0x001C 0x0028 0x0034 0x0040 0x004C
+ * BANK 2 - 0x0008 0x0014 0x0020 0x002C 0x0038 0x0044 0x0050
+ *
+ * BANK 3 - 0x0100 0x010C 0x0118 0x0124 0x0130 0x013C 0x0148
+ * BANK 4 - 0x0104 0x0110 0x011C 0x0128 0x0134 0x0140 0x014C
+ * BANK 5 - 0x0108 0x0114 0x0120 0x012C 0x0138 0x0144 0x0150
+ *
+ * BANK 6 - 0x0200 0x020C 0x0218 0x0224 0x0230 0x023C 0x0248
+ *
+ * NOTE:
+ * BANK 3 is only available on PXA27x and later processors.
+ * BANK 4 and 5 are only available on PXA935, PXA1928
+ * BANK 6 is only available on PXA1928
+ */
+
+#define GPLR_OFFSET 0x00
+#define GPDR_OFFSET 0x0C
+#define GPSR_OFFSET 0x18
+#define GPCR_OFFSET 0x24
+#define GRER_OFFSET 0x30
+#define GFER_OFFSET 0x3C
+#define GEDR_OFFSET 0x48
+#define GAFR_OFFSET 0x54
+#define ED_MASK_OFFSET 0x9C /* GPIO edge detection for AP side */
+
+#define BANK_OFF(n) (((n) / 3) << 8) + (((n) % 3) << 2)
+
+int pxa_last_gpio;
+static int irq_base;
+
+struct pxa_gpio_bank {
+ void __iomem *regbase;
+ unsigned long irq_mask;
+ unsigned long irq_edge_rise;
+ unsigned long irq_edge_fall;
+
+#ifdef CONFIG_PM
+ unsigned long saved_gplr;
+ unsigned long saved_gpdr;
+ unsigned long saved_grer;
+ unsigned long saved_gfer;
+#endif
+};
+
+struct pxa_gpio_chip {
+ struct device *dev;
+ struct gpio_chip chip;
+ struct pxa_gpio_bank *banks;
+ struct irq_domain *irqdomain;
+
+ int irq0;
+ int irq1;
+ int (*set_wake)(unsigned int gpio, unsigned int on);
+};
+
+enum pxa_gpio_type {
+ PXA25X_GPIO = 0,
+ PXA26X_GPIO,
+ PXA27X_GPIO,
+ PXA3XX_GPIO,
+ PXA93X_GPIO,
+ MMP_GPIO = 0x10,
+ MMP2_GPIO,
+ PXA1928_GPIO,
+};
+
+struct pxa_gpio_id {
+ enum pxa_gpio_type type;
+ int gpio_nums;
+};
+
+static DEFINE_SPINLOCK(gpio_lock);
+static struct pxa_gpio_chip *pxa_gpio_chip;
+static enum pxa_gpio_type gpio_type;
+
+static struct pxa_gpio_id pxa25x_id = {
+ .type = PXA25X_GPIO,
+ .gpio_nums = 85,
+};
+
+static struct pxa_gpio_id pxa26x_id = {
+ .type = PXA26X_GPIO,
+ .gpio_nums = 90,
+};
+
+static struct pxa_gpio_id pxa27x_id = {
+ .type = PXA27X_GPIO,
+ .gpio_nums = 121,
+};
+
+static struct pxa_gpio_id pxa3xx_id = {
+ .type = PXA3XX_GPIO,
+ .gpio_nums = 128,
+};
+
+static struct pxa_gpio_id pxa93x_id = {
+ .type = PXA93X_GPIO,
+ .gpio_nums = 192,
+};
+
+static struct pxa_gpio_id mmp_id = {
+ .type = MMP_GPIO,
+ .gpio_nums = 128,
+};
+
+static struct pxa_gpio_id mmp2_id = {
+ .type = MMP2_GPIO,
+ .gpio_nums = 192,
+};
+
+static struct pxa_gpio_id pxa1928_id = {
+ .type = PXA1928_GPIO,
+ .gpio_nums = 224,
+};
+
+#define for_each_gpio_bank(i, b, pc) \
+ for (i = 0, b = pc->banks; i <= pxa_last_gpio; i += 32, b++)
+
+static inline struct pxa_gpio_chip *chip_to_pxachip(struct gpio_chip *c)
+{
+ struct pxa_gpio_chip *pxa_chip = gpiochip_get_data(c);
+
+ return pxa_chip;
+}
+
+static inline void __iomem *gpio_bank_base(struct gpio_chip *c, int gpio)
+{
+ struct pxa_gpio_chip *p = gpiochip_get_data(c);
+ struct pxa_gpio_bank *bank = p->banks + (gpio / 32);
+
+ return bank->regbase;
+}
+
+static inline struct pxa_gpio_bank *gpio_to_pxabank(struct gpio_chip *c,
+ unsigned gpio)
+{
+ return chip_to_pxachip(c)->banks + gpio / 32;
+}
+
+static inline int gpio_is_mmp_type(int type)
+{
+ return (type & MMP_GPIO) != 0;
+}
+
+/* GPIO86/87/88/89 on PXA26x have their direction bits in PXA_GPDR(2 inverted,
+ * as well as their Alternate Function value being '1' for GPIO in GAFRx.
+ */
+static inline int __gpio_is_inverted(int gpio)
+{
+ if ((gpio_type == PXA26X_GPIO) && (gpio > 85))
+ return 1;
+ return 0;
+}
+
+/*
+ * On PXA25x and PXA27x, GAFRx and GPDRx together decide the alternate
+ * function of a GPIO, and GPDRx cannot be altered once configured. It
+ * is attributed as "occupied" here (I know this terminology isn't
+ * accurate, you are welcome to propose a better one :-)
+ */
+static inline int __gpio_is_occupied(struct pxa_gpio_chip *pchip, unsigned gpio)
+{
+ void __iomem *base;
+ unsigned long gafr = 0, gpdr = 0;
+ int ret, af = 0, dir = 0;
+
+ base = gpio_bank_base(&pchip->chip, gpio);
+ gpdr = readl_relaxed(base + GPDR_OFFSET);
+
+ switch (gpio_type) {
+ case PXA25X_GPIO:
+ case PXA26X_GPIO:
+ case PXA27X_GPIO:
+ gafr = readl_relaxed(base + GAFR_OFFSET);
+ af = (gafr >> ((gpio & 0xf) * 2)) & 0x3;
+ dir = gpdr & GPIO_bit(gpio);
+
+ if (__gpio_is_inverted(gpio))
+ ret = (af != 1) || (dir == 0);
+ else
+ ret = (af != 0) || (dir != 0);
+ break;
+ default:
+ ret = gpdr & GPIO_bit(gpio);
+ break;
+ }
+ return ret;
+}
+
+int pxa_irq_to_gpio(int irq)
+{
+ struct pxa_gpio_chip *pchip = pxa_gpio_chip;
+ int irq_gpio0;
+
+ irq_gpio0 = irq_find_mapping(pchip->irqdomain, 0);
+ if (irq_gpio0 > 0)
+ return irq - irq_gpio0;
+
+ return irq_gpio0;
+}
+
+static bool pxa_gpio_has_pinctrl(void)
+{
+ switch (gpio_type) {
+ case PXA3XX_GPIO:
+ case MMP2_GPIO:
+ case MMP_GPIO:
+ return false;
+
+ default:
+ return true;
+ }
+}
+
+static int pxa_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ struct pxa_gpio_chip *pchip = chip_to_pxachip(chip);
+
+ return irq_find_mapping(pchip->irqdomain, offset);
+}
+
+static int pxa_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ void __iomem *base = gpio_bank_base(chip, offset);
+ uint32_t value, mask = GPIO_bit(offset);
+ unsigned long flags;
+ int ret;
+
+ if (pxa_gpio_has_pinctrl()) {
+ ret = pinctrl_gpio_direction_input(chip->base + offset);
+ if (ret)
+ return ret;
+ }
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ value = readl_relaxed(base + GPDR_OFFSET);
+ if (__gpio_is_inverted(chip->base + offset))
+ value |= mask;
+ else
+ value &= ~mask;
+ writel_relaxed(value, base + GPDR_OFFSET);
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ return 0;
+}
+
+static int pxa_gpio_direction_output(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ void __iomem *base = gpio_bank_base(chip, offset);
+ uint32_t tmp, mask = GPIO_bit(offset);
+ unsigned long flags;
+ int ret;
+
+ writel_relaxed(mask, base + (value ? GPSR_OFFSET : GPCR_OFFSET));
+
+ if (pxa_gpio_has_pinctrl()) {
+ ret = pinctrl_gpio_direction_output(chip->base + offset);
+ if (ret)
+ return ret;
+ }
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ tmp = readl_relaxed(base + GPDR_OFFSET);
+ if (__gpio_is_inverted(chip->base + offset))
+ tmp &= ~mask;
+ else
+ tmp |= mask;
+ writel_relaxed(tmp, base + GPDR_OFFSET);
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ return 0;
+}
+
+static int pxa_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ void __iomem *base = gpio_bank_base(chip, offset);
+ u32 gplr = readl_relaxed(base + GPLR_OFFSET);
+
+ return !!(gplr & GPIO_bit(offset));
+}
+
+static void pxa_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ void __iomem *base = gpio_bank_base(chip, offset);
+
+ writel_relaxed(GPIO_bit(offset),
+ base + (value ? GPSR_OFFSET : GPCR_OFFSET));
+}
+
+#ifdef CONFIG_OF_GPIO
+static int pxa_gpio_of_xlate(struct gpio_chip *gc,
+ const struct of_phandle_args *gpiospec,
+ u32 *flags)
+{
+ if (gpiospec->args[0] > pxa_last_gpio)
+ return -EINVAL;
+
+ if (flags)
+ *flags = gpiospec->args[1];
+
+ return gpiospec->args[0];
+}
+#endif
+
+static int pxa_init_gpio_chip(struct pxa_gpio_chip *pchip, int ngpio, void __iomem *regbase)
+{
+ int i, gpio, nbanks = DIV_ROUND_UP(ngpio, 32);
+ struct pxa_gpio_bank *bank;
+
+ pchip->banks = devm_kcalloc(pchip->dev, nbanks, sizeof(*pchip->banks),
+ GFP_KERNEL);
+ if (!pchip->banks)
+ return -ENOMEM;
+
+ pchip->chip.parent = pchip->dev;
+ pchip->chip.label = "gpio-pxa";
+ pchip->chip.direction_input = pxa_gpio_direction_input;
+ pchip->chip.direction_output = pxa_gpio_direction_output;
+ pchip->chip.get = pxa_gpio_get;
+ pchip->chip.set = pxa_gpio_set;
+ pchip->chip.to_irq = pxa_gpio_to_irq;
+ pchip->chip.ngpio = ngpio;
+ pchip->chip.request = gpiochip_generic_request;
+ pchip->chip.free = gpiochip_generic_free;
+
+#ifdef CONFIG_OF_GPIO
+ pchip->chip.of_xlate = pxa_gpio_of_xlate;
+ pchip->chip.of_gpio_n_cells = 2;
+#endif
+
+ for (i = 0, gpio = 0; i < nbanks; i++, gpio += 32) {
+ bank = pchip->banks + i;
+ bank->regbase = regbase + BANK_OFF(i);
+ }
+
+ return gpiochip_add_data(&pchip->chip, pchip);
+}
+
+/* Update only those GRERx and GFERx edge detection register bits if those
+ * bits are set in c->irq_mask
+ */
+static inline void update_edge_detect(struct pxa_gpio_bank *c)
+{
+ uint32_t grer, gfer;
+
+ grer = readl_relaxed(c->regbase + GRER_OFFSET) & ~c->irq_mask;
+ gfer = readl_relaxed(c->regbase + GFER_OFFSET) & ~c->irq_mask;
+ grer |= c->irq_edge_rise & c->irq_mask;
+ gfer |= c->irq_edge_fall & c->irq_mask;
+ writel_relaxed(grer, c->regbase + GRER_OFFSET);
+ writel_relaxed(gfer, c->regbase + GFER_OFFSET);
+}
+
+static int pxa_gpio_irq_type(struct irq_data *d, unsigned int type)
+{
+ struct pxa_gpio_chip *pchip = irq_data_get_irq_chip_data(d);
+ unsigned int gpio = irqd_to_hwirq(d);
+ struct pxa_gpio_bank *c = gpio_to_pxabank(&pchip->chip, gpio);
+ unsigned long gpdr, mask = GPIO_bit(gpio);
+
+ if (type == IRQ_TYPE_PROBE) {
+ /* Don't mess with enabled GPIOs using preconfigured edges or
+ * GPIOs set to alternate function or to output during probe
+ */
+ if ((c->irq_edge_rise | c->irq_edge_fall) & GPIO_bit(gpio))
+ return 0;
+
+ if (__gpio_is_occupied(pchip, gpio))
+ return 0;
+
+ type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
+ }
+
+ gpdr = readl_relaxed(c->regbase + GPDR_OFFSET);
+
+ if (__gpio_is_inverted(gpio))
+ writel_relaxed(gpdr | mask, c->regbase + GPDR_OFFSET);
+ else
+ writel_relaxed(gpdr & ~mask, c->regbase + GPDR_OFFSET);
+
+ if (type & IRQ_TYPE_EDGE_RISING)
+ c->irq_edge_rise |= mask;
+ else
+ c->irq_edge_rise &= ~mask;
+
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ c->irq_edge_fall |= mask;
+ else
+ c->irq_edge_fall &= ~mask;
+
+ update_edge_detect(c);
+
+ pr_debug("%s: IRQ%d (GPIO%d) - edge%s%s\n", __func__, d->irq, gpio,
+ ((type & IRQ_TYPE_EDGE_RISING) ? " rising" : ""),
+ ((type & IRQ_TYPE_EDGE_FALLING) ? " falling" : ""));
+ return 0;
+}
+
+static irqreturn_t pxa_gpio_demux_handler(int in_irq, void *d)
+{
+ int loop, gpio, n, handled = 0;
+ unsigned long gedr;
+ struct pxa_gpio_chip *pchip = d;
+ struct pxa_gpio_bank *c;
+
+ do {
+ loop = 0;
+ for_each_gpio_bank(gpio, c, pchip) {
+ gedr = readl_relaxed(c->regbase + GEDR_OFFSET);
+ gedr = gedr & c->irq_mask;
+ writel_relaxed(gedr, c->regbase + GEDR_OFFSET);
+
+ for_each_set_bit(n, &gedr, BITS_PER_LONG) {
+ loop = 1;
+
+ generic_handle_domain_irq(pchip->irqdomain,
+ gpio + n);
+ }
+ }
+ handled += loop;
+ } while (loop);
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static irqreturn_t pxa_gpio_direct_handler(int in_irq, void *d)
+{
+ struct pxa_gpio_chip *pchip = d;
+
+ if (in_irq == pchip->irq0) {
+ generic_handle_domain_irq(pchip->irqdomain, 0);
+ } else if (in_irq == pchip->irq1) {
+ generic_handle_domain_irq(pchip->irqdomain, 1);
+ } else {
+ pr_err("%s() unknown irq %d\n", __func__, in_irq);
+ return IRQ_NONE;
+ }
+ return IRQ_HANDLED;
+}
+
+static void pxa_ack_muxed_gpio(struct irq_data *d)
+{
+ struct pxa_gpio_chip *pchip = irq_data_get_irq_chip_data(d);
+ unsigned int gpio = irqd_to_hwirq(d);
+ void __iomem *base = gpio_bank_base(&pchip->chip, gpio);
+
+ writel_relaxed(GPIO_bit(gpio), base + GEDR_OFFSET);
+}
+
+static void pxa_mask_muxed_gpio(struct irq_data *d)
+{
+ struct pxa_gpio_chip *pchip = irq_data_get_irq_chip_data(d);
+ unsigned int gpio = irqd_to_hwirq(d);
+ struct pxa_gpio_bank *b = gpio_to_pxabank(&pchip->chip, gpio);
+ void __iomem *base = gpio_bank_base(&pchip->chip, gpio);
+ uint32_t grer, gfer;
+
+ b->irq_mask &= ~GPIO_bit(gpio);
+
+ grer = readl_relaxed(base + GRER_OFFSET) & ~GPIO_bit(gpio);
+ gfer = readl_relaxed(base + GFER_OFFSET) & ~GPIO_bit(gpio);
+ writel_relaxed(grer, base + GRER_OFFSET);
+ writel_relaxed(gfer, base + GFER_OFFSET);
+}
+
+static int pxa_gpio_set_wake(struct irq_data *d, unsigned int on)
+{
+ struct pxa_gpio_chip *pchip = irq_data_get_irq_chip_data(d);
+ unsigned int gpio = irqd_to_hwirq(d);
+
+ if (pchip->set_wake)
+ return pchip->set_wake(gpio, on);
+ else
+ return 0;
+}
+
+static void pxa_unmask_muxed_gpio(struct irq_data *d)
+{
+ struct pxa_gpio_chip *pchip = irq_data_get_irq_chip_data(d);
+ unsigned int gpio = irqd_to_hwirq(d);
+ struct pxa_gpio_bank *c = gpio_to_pxabank(&pchip->chip, gpio);
+
+ c->irq_mask |= GPIO_bit(gpio);
+ update_edge_detect(c);
+}
+
+static struct irq_chip pxa_muxed_gpio_chip = {
+ .name = "GPIO",
+ .irq_ack = pxa_ack_muxed_gpio,
+ .irq_mask = pxa_mask_muxed_gpio,
+ .irq_unmask = pxa_unmask_muxed_gpio,
+ .irq_set_type = pxa_gpio_irq_type,
+ .irq_set_wake = pxa_gpio_set_wake,
+};
+
+static int pxa_gpio_nums(struct platform_device *pdev)
+{
+ const struct platform_device_id *id = platform_get_device_id(pdev);
+ struct pxa_gpio_id *pxa_id = (struct pxa_gpio_id *)id->driver_data;
+ int count = 0;
+
+ switch (pxa_id->type) {
+ case PXA25X_GPIO:
+ case PXA26X_GPIO:
+ case PXA27X_GPIO:
+ case PXA3XX_GPIO:
+ case PXA93X_GPIO:
+ case MMP_GPIO:
+ case MMP2_GPIO:
+ case PXA1928_GPIO:
+ gpio_type = pxa_id->type;
+ count = pxa_id->gpio_nums - 1;
+ break;
+ default:
+ count = -EINVAL;
+ break;
+ }
+ return count;
+}
+
+static int pxa_irq_domain_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hw)
+{
+ irq_set_chip_and_handler(irq, &pxa_muxed_gpio_chip,
+ handle_edge_irq);
+ irq_set_chip_data(irq, d->host_data);
+ irq_set_noprobe(irq);
+ return 0;
+}
+
+static const struct irq_domain_ops pxa_irq_domain_ops = {
+ .map = pxa_irq_domain_map,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id pxa_gpio_dt_ids[] = {
+ { .compatible = "intel,pxa25x-gpio", .data = &pxa25x_id, },
+ { .compatible = "intel,pxa26x-gpio", .data = &pxa26x_id, },
+ { .compatible = "intel,pxa27x-gpio", .data = &pxa27x_id, },
+ { .compatible = "intel,pxa3xx-gpio", .data = &pxa3xx_id, },
+ { .compatible = "marvell,pxa93x-gpio", .data = &pxa93x_id, },
+ { .compatible = "marvell,mmp-gpio", .data = &mmp_id, },
+ { .compatible = "marvell,mmp2-gpio", .data = &mmp2_id, },
+ { .compatible = "marvell,pxa1928-gpio", .data = &pxa1928_id, },
+ {}
+};
+
+static int pxa_gpio_probe_dt(struct platform_device *pdev,
+ struct pxa_gpio_chip *pchip)
+{
+ int nr_gpios;
+ const struct pxa_gpio_id *gpio_id;
+
+ gpio_id = of_device_get_match_data(&pdev->dev);
+ gpio_type = gpio_id->type;
+
+ nr_gpios = gpio_id->gpio_nums;
+ pxa_last_gpio = nr_gpios - 1;
+
+ irq_base = devm_irq_alloc_descs(&pdev->dev, -1, 0, nr_gpios, 0);
+ if (irq_base < 0) {
+ dev_err(&pdev->dev, "Failed to allocate IRQ numbers\n");
+ return irq_base;
+ }
+ return irq_base;
+}
+#else
+#define pxa_gpio_probe_dt(pdev, pchip) (-1)
+#endif
+
+static int pxa_gpio_probe(struct platform_device *pdev)
+{
+ struct pxa_gpio_chip *pchip;
+ struct pxa_gpio_bank *c;
+ struct clk *clk;
+ struct pxa_gpio_platform_data *info;
+ void __iomem *gpio_reg_base;
+ int gpio, ret;
+ int irq0 = 0, irq1 = 0, irq_mux;
+
+ pchip = devm_kzalloc(&pdev->dev, sizeof(*pchip), GFP_KERNEL);
+ if (!pchip)
+ return -ENOMEM;
+ pchip->dev = &pdev->dev;
+
+ info = dev_get_platdata(&pdev->dev);
+ if (info) {
+ irq_base = info->irq_base;
+ if (irq_base <= 0)
+ return -EINVAL;
+ pxa_last_gpio = pxa_gpio_nums(pdev);
+ pchip->set_wake = info->gpio_set_wake;
+ } else {
+ irq_base = pxa_gpio_probe_dt(pdev, pchip);
+ if (irq_base < 0)
+ return -EINVAL;
+ }
+
+ if (!pxa_last_gpio)
+ return -EINVAL;
+
+ pchip->irqdomain = irq_domain_add_legacy(pdev->dev.of_node,
+ pxa_last_gpio + 1, irq_base,
+ 0, &pxa_irq_domain_ops, pchip);
+ if (!pchip->irqdomain)
+ return -ENOMEM;
+
+ irq0 = platform_get_irq_byname_optional(pdev, "gpio0");
+ irq1 = platform_get_irq_byname_optional(pdev, "gpio1");
+ irq_mux = platform_get_irq_byname(pdev, "gpio_mux");
+ if ((irq0 > 0 && irq1 <= 0) || (irq0 <= 0 && irq1 > 0)
+ || (irq_mux <= 0))
+ return -EINVAL;
+
+ pchip->irq0 = irq0;
+ pchip->irq1 = irq1;
+
+ gpio_reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gpio_reg_base))
+ return PTR_ERR(gpio_reg_base);
+
+ clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(&pdev->dev, "Error %ld to get gpio clock\n",
+ PTR_ERR(clk));
+ return PTR_ERR(clk);
+ }
+
+ /* Initialize GPIO chips */
+ ret = pxa_init_gpio_chip(pchip, pxa_last_gpio + 1, gpio_reg_base);
+ if (ret)
+ return ret;
+
+ /* clear all GPIO edge detects */
+ for_each_gpio_bank(gpio, c, pchip) {
+ writel_relaxed(0, c->regbase + GFER_OFFSET);
+ writel_relaxed(0, c->regbase + GRER_OFFSET);
+ writel_relaxed(~0, c->regbase + GEDR_OFFSET);
+ /* unmask GPIO edge detect for AP side */
+ if (gpio_is_mmp_type(gpio_type))
+ writel_relaxed(~0, c->regbase + ED_MASK_OFFSET);
+ }
+
+ if (irq0 > 0) {
+ ret = devm_request_irq(&pdev->dev,
+ irq0, pxa_gpio_direct_handler, 0,
+ "gpio-0", pchip);
+ if (ret)
+ dev_err(&pdev->dev, "request of gpio0 irq failed: %d\n",
+ ret);
+ }
+ if (irq1 > 0) {
+ ret = devm_request_irq(&pdev->dev,
+ irq1, pxa_gpio_direct_handler, 0,
+ "gpio-1", pchip);
+ if (ret)
+ dev_err(&pdev->dev, "request of gpio1 irq failed: %d\n",
+ ret);
+ }
+ ret = devm_request_irq(&pdev->dev,
+ irq_mux, pxa_gpio_demux_handler, 0,
+ "gpio-mux", pchip);
+ if (ret)
+ dev_err(&pdev->dev, "request of gpio-mux irq failed: %d\n",
+ ret);
+
+ pxa_gpio_chip = pchip;
+
+ return 0;
+}
+
+static const struct platform_device_id gpio_id_table[] = {
+ { "pxa25x-gpio", (unsigned long)&pxa25x_id },
+ { "pxa26x-gpio", (unsigned long)&pxa26x_id },
+ { "pxa27x-gpio", (unsigned long)&pxa27x_id },
+ { "pxa3xx-gpio", (unsigned long)&pxa3xx_id },
+ { "pxa93x-gpio", (unsigned long)&pxa93x_id },
+ { "mmp-gpio", (unsigned long)&mmp_id },
+ { "mmp2-gpio", (unsigned long)&mmp2_id },
+ { "pxa1928-gpio", (unsigned long)&pxa1928_id },
+ { },
+};
+
+static struct platform_driver pxa_gpio_driver = {
+ .probe = pxa_gpio_probe,
+ .driver = {
+ .name = "pxa-gpio",
+ .of_match_table = of_match_ptr(pxa_gpio_dt_ids),
+ },
+ .id_table = gpio_id_table,
+};
+
+static int __init pxa_gpio_legacy_init(void)
+{
+ if (of_have_populated_dt())
+ return 0;
+
+ return platform_driver_register(&pxa_gpio_driver);
+}
+postcore_initcall(pxa_gpio_legacy_init);
+
+static int __init pxa_gpio_dt_init(void)
+{
+ if (of_have_populated_dt())
+ return platform_driver_register(&pxa_gpio_driver);
+
+ return 0;
+}
+device_initcall(pxa_gpio_dt_init);
+
+#ifdef CONFIG_PM
+static int pxa_gpio_suspend(void)
+{
+ struct pxa_gpio_chip *pchip = pxa_gpio_chip;
+ struct pxa_gpio_bank *c;
+ int gpio;
+
+ if (!pchip)
+ return 0;
+
+ for_each_gpio_bank(gpio, c, pchip) {
+ c->saved_gplr = readl_relaxed(c->regbase + GPLR_OFFSET);
+ c->saved_gpdr = readl_relaxed(c->regbase + GPDR_OFFSET);
+ c->saved_grer = readl_relaxed(c->regbase + GRER_OFFSET);
+ c->saved_gfer = readl_relaxed(c->regbase + GFER_OFFSET);
+
+ /* Clear GPIO transition detect bits */
+ writel_relaxed(0xffffffff, c->regbase + GEDR_OFFSET);
+ }
+ return 0;
+}
+
+static void pxa_gpio_resume(void)
+{
+ struct pxa_gpio_chip *pchip = pxa_gpio_chip;
+ struct pxa_gpio_bank *c;
+ int gpio;
+
+ if (!pchip)
+ return;
+
+ for_each_gpio_bank(gpio, c, pchip) {
+ /* restore level with set/clear */
+ writel_relaxed(c->saved_gplr, c->regbase + GPSR_OFFSET);
+ writel_relaxed(~c->saved_gplr, c->regbase + GPCR_OFFSET);
+
+ writel_relaxed(c->saved_grer, c->regbase + GRER_OFFSET);
+ writel_relaxed(c->saved_gfer, c->regbase + GFER_OFFSET);
+ writel_relaxed(c->saved_gpdr, c->regbase + GPDR_OFFSET);
+ }
+}
+#else
+#define pxa_gpio_suspend NULL
+#define pxa_gpio_resume NULL
+#endif
+
+static struct syscore_ops pxa_gpio_syscore_ops = {
+ .suspend = pxa_gpio_suspend,
+ .resume = pxa_gpio_resume,
+};
+
+static int __init pxa_gpio_sysinit(void)
+{
+ register_syscore_ops(&pxa_gpio_syscore_ops);
+ return 0;
+}
+postcore_initcall(pxa_gpio_sysinit);
diff --git a/drivers/gpio/gpio-raspberrypi-exp.c b/drivers/gpio/gpio-raspberrypi-exp.c
new file mode 100644
index 0000000000..9d1b95e429
--- /dev/null
+++ b/drivers/gpio/gpio-raspberrypi-exp.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Raspberry Pi 3 expander GPIO driver
+ *
+ * Uses the firmware mailbox service to communicate with the
+ * GPIO expander on the VPU.
+ *
+ * Copyright (C) 2017 Raspberry Pi Trading Ltd.
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+#define MODULE_NAME "raspberrypi-exp-gpio"
+#define NUM_GPIO 8
+
+#define RPI_EXP_GPIO_BASE 128
+
+#define RPI_EXP_GPIO_DIR_IN 0
+#define RPI_EXP_GPIO_DIR_OUT 1
+
+struct rpi_exp_gpio {
+ struct gpio_chip gc;
+ struct rpi_firmware *fw;
+};
+
+/* VC4 firmware mailbox interface data structures */
+
+struct gpio_set_config {
+ u32 gpio;
+ u32 direction;
+ u32 polarity;
+ u32 term_en;
+ u32 term_pull_up;
+ u32 state;
+};
+
+struct gpio_get_config {
+ u32 gpio;
+ u32 direction;
+ u32 polarity;
+ u32 term_en;
+ u32 term_pull_up;
+};
+
+struct gpio_get_set_state {
+ u32 gpio;
+ u32 state;
+};
+
+static int rpi_exp_gpio_get_polarity(struct gpio_chip *gc, unsigned int off)
+{
+ struct rpi_exp_gpio *gpio;
+ struct gpio_get_config get;
+ int ret;
+
+ gpio = gpiochip_get_data(gc);
+
+ get.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */
+
+ ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_GET_GPIO_CONFIG,
+ &get, sizeof(get));
+ if (ret || get.gpio != 0) {
+ dev_err(gc->parent, "Failed to get GPIO %u config (%d %x)\n",
+ off, ret, get.gpio);
+ return ret ? ret : -EIO;
+ }
+ return get.polarity;
+}
+
+static int rpi_exp_gpio_dir_in(struct gpio_chip *gc, unsigned int off)
+{
+ struct rpi_exp_gpio *gpio;
+ struct gpio_set_config set_in;
+ int ret;
+
+ gpio = gpiochip_get_data(gc);
+
+ set_in.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */
+ set_in.direction = RPI_EXP_GPIO_DIR_IN;
+ set_in.term_en = 0; /* termination disabled */
+ set_in.term_pull_up = 0; /* n/a as termination disabled */
+ set_in.state = 0; /* n/a as configured as an input */
+
+ ret = rpi_exp_gpio_get_polarity(gc, off);
+ if (ret < 0)
+ return ret;
+ set_in.polarity = ret; /* Retain existing setting */
+
+ ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_CONFIG,
+ &set_in, sizeof(set_in));
+ if (ret || set_in.gpio != 0) {
+ dev_err(gc->parent, "Failed to set GPIO %u to input (%d %x)\n",
+ off, ret, set_in.gpio);
+ return ret ? ret : -EIO;
+ }
+ return 0;
+}
+
+static int rpi_exp_gpio_dir_out(struct gpio_chip *gc, unsigned int off, int val)
+{
+ struct rpi_exp_gpio *gpio;
+ struct gpio_set_config set_out;
+ int ret;
+
+ gpio = gpiochip_get_data(gc);
+
+ set_out.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */
+ set_out.direction = RPI_EXP_GPIO_DIR_OUT;
+ set_out.term_en = 0; /* n/a as an output */
+ set_out.term_pull_up = 0; /* n/a as termination disabled */
+ set_out.state = val; /* Output state */
+
+ ret = rpi_exp_gpio_get_polarity(gc, off);
+ if (ret < 0)
+ return ret;
+ set_out.polarity = ret; /* Retain existing setting */
+
+ ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_CONFIG,
+ &set_out, sizeof(set_out));
+ if (ret || set_out.gpio != 0) {
+ dev_err(gc->parent, "Failed to set GPIO %u to output (%d %x)\n",
+ off, ret, set_out.gpio);
+ return ret ? ret : -EIO;
+ }
+ return 0;
+}
+
+static int rpi_exp_gpio_get_direction(struct gpio_chip *gc, unsigned int off)
+{
+ struct rpi_exp_gpio *gpio;
+ struct gpio_get_config get;
+ int ret;
+
+ gpio = gpiochip_get_data(gc);
+
+ get.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */
+
+ ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_GET_GPIO_CONFIG,
+ &get, sizeof(get));
+ if (ret || get.gpio != 0) {
+ dev_err(gc->parent,
+ "Failed to get GPIO %u config (%d %x)\n", off, ret,
+ get.gpio);
+ return ret ? ret : -EIO;
+ }
+ if (get.direction)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int rpi_exp_gpio_get(struct gpio_chip *gc, unsigned int off)
+{
+ struct rpi_exp_gpio *gpio;
+ struct gpio_get_set_state get;
+ int ret;
+
+ gpio = gpiochip_get_data(gc);
+
+ get.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */
+ get.state = 0; /* storage for returned value */
+
+ ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_GET_GPIO_STATE,
+ &get, sizeof(get));
+ if (ret || get.gpio != 0) {
+ dev_err(gc->parent,
+ "Failed to get GPIO %u state (%d %x)\n", off, ret,
+ get.gpio);
+ return ret ? ret : -EIO;
+ }
+ return !!get.state;
+}
+
+static void rpi_exp_gpio_set(struct gpio_chip *gc, unsigned int off, int val)
+{
+ struct rpi_exp_gpio *gpio;
+ struct gpio_get_set_state set;
+ int ret;
+
+ gpio = gpiochip_get_data(gc);
+
+ set.gpio = off + RPI_EXP_GPIO_BASE; /* GPIO to update */
+ set.state = val; /* Output state */
+
+ ret = rpi_firmware_property(gpio->fw, RPI_FIRMWARE_SET_GPIO_STATE,
+ &set, sizeof(set));
+ if (ret || set.gpio != 0)
+ dev_err(gc->parent,
+ "Failed to set GPIO %u state (%d %x)\n", off, ret,
+ set.gpio);
+}
+
+static int rpi_exp_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *fw_node;
+ struct rpi_firmware *fw;
+ struct rpi_exp_gpio *rpi_gpio;
+
+ fw_node = of_get_parent(np);
+ if (!fw_node) {
+ dev_err(dev, "Missing firmware node\n");
+ return -ENOENT;
+ }
+
+ fw = devm_rpi_firmware_get(&pdev->dev, fw_node);
+ of_node_put(fw_node);
+ if (!fw)
+ return -EPROBE_DEFER;
+
+ rpi_gpio = devm_kzalloc(dev, sizeof(*rpi_gpio), GFP_KERNEL);
+ if (!rpi_gpio)
+ return -ENOMEM;
+
+ rpi_gpio->fw = fw;
+ rpi_gpio->gc.parent = dev;
+ rpi_gpio->gc.label = MODULE_NAME;
+ rpi_gpio->gc.owner = THIS_MODULE;
+ rpi_gpio->gc.base = -1;
+ rpi_gpio->gc.ngpio = NUM_GPIO;
+
+ rpi_gpio->gc.direction_input = rpi_exp_gpio_dir_in;
+ rpi_gpio->gc.direction_output = rpi_exp_gpio_dir_out;
+ rpi_gpio->gc.get_direction = rpi_exp_gpio_get_direction;
+ rpi_gpio->gc.get = rpi_exp_gpio_get;
+ rpi_gpio->gc.set = rpi_exp_gpio_set;
+ rpi_gpio->gc.can_sleep = true;
+
+ return devm_gpiochip_add_data(dev, &rpi_gpio->gc, rpi_gpio);
+}
+
+static const struct of_device_id rpi_exp_gpio_ids[] = {
+ { .compatible = "raspberrypi,firmware-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rpi_exp_gpio_ids);
+
+static struct platform_driver rpi_exp_gpio_driver = {
+ .driver = {
+ .name = MODULE_NAME,
+ .of_match_table = rpi_exp_gpio_ids,
+ },
+ .probe = rpi_exp_gpio_probe,
+};
+module_platform_driver(rpi_exp_gpio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.org>");
+MODULE_DESCRIPTION("Raspberry Pi 3 expander GPIO driver");
+MODULE_ALIAS("platform:rpi-exp-gpio");
diff --git a/drivers/gpio/gpio-rc5t583.c b/drivers/gpio/gpio-rc5t583.c
new file mode 100644
index 0000000000..c34dcadaee
--- /dev/null
+++ b/drivers/gpio/gpio-rc5t583.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for RICOH583 power management chip.
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
+ * Author: Laxman dewangan <ldewangan@nvidia.com>
+ *
+ * Based on code
+ * Copyright (C) 2011 RICOH COMPANY,LTD
+ */
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/mfd/rc5t583.h>
+
+struct rc5t583_gpio {
+ struct gpio_chip gpio_chip;
+ struct rc5t583 *rc5t583;
+};
+
+static int rc5t583_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct rc5t583_gpio *rc5t583_gpio = gpiochip_get_data(gc);
+ struct device *parent = rc5t583_gpio->rc5t583->dev;
+ uint8_t val = 0;
+ int ret;
+
+ ret = rc5t583_read(parent, RC5T583_GPIO_MON_IOIN, &val);
+ if (ret < 0)
+ return ret;
+
+ return !!(val & BIT(offset));
+}
+
+static void rc5t583_gpio_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ struct rc5t583_gpio *rc5t583_gpio = gpiochip_get_data(gc);
+ struct device *parent = rc5t583_gpio->rc5t583->dev;
+ if (val)
+ rc5t583_set_bits(parent, RC5T583_GPIO_IOOUT, BIT(offset));
+ else
+ rc5t583_clear_bits(parent, RC5T583_GPIO_IOOUT, BIT(offset));
+}
+
+static int rc5t583_gpio_dir_input(struct gpio_chip *gc, unsigned int offset)
+{
+ struct rc5t583_gpio *rc5t583_gpio = gpiochip_get_data(gc);
+ struct device *parent = rc5t583_gpio->rc5t583->dev;
+ int ret;
+
+ ret = rc5t583_clear_bits(parent, RC5T583_GPIO_IOSEL, BIT(offset));
+ if (ret < 0)
+ return ret;
+
+ /* Set pin to gpio mode */
+ return rc5t583_clear_bits(parent, RC5T583_GPIO_PGSEL, BIT(offset));
+}
+
+static int rc5t583_gpio_dir_output(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ struct rc5t583_gpio *rc5t583_gpio = gpiochip_get_data(gc);
+ struct device *parent = rc5t583_gpio->rc5t583->dev;
+ int ret;
+
+ rc5t583_gpio_set(gc, offset, value);
+ ret = rc5t583_set_bits(parent, RC5T583_GPIO_IOSEL, BIT(offset));
+ if (ret < 0)
+ return ret;
+
+ /* Set pin to gpio mode */
+ return rc5t583_clear_bits(parent, RC5T583_GPIO_PGSEL, BIT(offset));
+}
+
+static int rc5t583_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
+{
+ struct rc5t583_gpio *rc5t583_gpio = gpiochip_get_data(gc);
+
+ if (offset < RC5T583_MAX_GPIO)
+ return rc5t583_gpio->rc5t583->irq_base +
+ RC5T583_IRQ_GPIO0 + offset;
+ return -EINVAL;
+}
+
+static void rc5t583_gpio_free(struct gpio_chip *gc, unsigned offset)
+{
+ struct rc5t583_gpio *rc5t583_gpio = gpiochip_get_data(gc);
+ struct device *parent = rc5t583_gpio->rc5t583->dev;
+
+ rc5t583_set_bits(parent, RC5T583_GPIO_PGSEL, BIT(offset));
+}
+
+static int rc5t583_gpio_probe(struct platform_device *pdev)
+{
+ struct rc5t583 *rc5t583 = dev_get_drvdata(pdev->dev.parent);
+ struct rc5t583_platform_data *pdata = dev_get_platdata(rc5t583->dev);
+ struct rc5t583_gpio *rc5t583_gpio;
+
+ rc5t583_gpio = devm_kzalloc(&pdev->dev, sizeof(*rc5t583_gpio),
+ GFP_KERNEL);
+ if (!rc5t583_gpio)
+ return -ENOMEM;
+
+ rc5t583_gpio->gpio_chip.label = "gpio-rc5t583",
+ rc5t583_gpio->gpio_chip.owner = THIS_MODULE,
+ rc5t583_gpio->gpio_chip.free = rc5t583_gpio_free,
+ rc5t583_gpio->gpio_chip.direction_input = rc5t583_gpio_dir_input,
+ rc5t583_gpio->gpio_chip.direction_output = rc5t583_gpio_dir_output,
+ rc5t583_gpio->gpio_chip.set = rc5t583_gpio_set,
+ rc5t583_gpio->gpio_chip.get = rc5t583_gpio_get,
+ rc5t583_gpio->gpio_chip.to_irq = rc5t583_gpio_to_irq,
+ rc5t583_gpio->gpio_chip.ngpio = RC5T583_MAX_GPIO,
+ rc5t583_gpio->gpio_chip.can_sleep = true,
+ rc5t583_gpio->gpio_chip.parent = &pdev->dev;
+ rc5t583_gpio->gpio_chip.base = -1;
+ rc5t583_gpio->rc5t583 = rc5t583;
+
+ if (pdata && pdata->gpio_base)
+ rc5t583_gpio->gpio_chip.base = pdata->gpio_base;
+
+ return devm_gpiochip_add_data(&pdev->dev, &rc5t583_gpio->gpio_chip,
+ rc5t583_gpio);
+}
+
+static struct platform_driver rc5t583_gpio_driver = {
+ .driver = {
+ .name = "rc5t583-gpio",
+ },
+ .probe = rc5t583_gpio_probe,
+};
+
+static int __init rc5t583_gpio_init(void)
+{
+ return platform_driver_register(&rc5t583_gpio_driver);
+}
+subsys_initcall(rc5t583_gpio_init);
diff --git a/drivers/gpio/gpio-rcar.c b/drivers/gpio/gpio-rcar.c
new file mode 100644
index 0000000000..86e69cde04
--- /dev/null
+++ b/drivers/gpio/gpio-rcar.c
@@ -0,0 +1,673 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas R-Car GPIO Support
+ *
+ * Copyright (C) 2014 Renesas Electronics Corporation
+ * Copyright (C) 2013 Magnus Damm
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+
+struct gpio_rcar_bank_info {
+ u32 iointsel;
+ u32 inoutsel;
+ u32 outdt;
+ u32 posneg;
+ u32 edglevel;
+ u32 bothedge;
+ u32 intmsk;
+};
+
+struct gpio_rcar_info {
+ bool has_outdtsel;
+ bool has_both_edge_trigger;
+ bool has_always_in;
+ bool has_inen;
+};
+
+struct gpio_rcar_priv {
+ void __iomem *base;
+ spinlock_t lock;
+ struct device *dev;
+ struct gpio_chip gpio_chip;
+ unsigned int irq_parent;
+ atomic_t wakeup_path;
+ struct gpio_rcar_info info;
+ struct gpio_rcar_bank_info bank_info;
+};
+
+#define IOINTSEL 0x00 /* General IO/Interrupt Switching Register */
+#define INOUTSEL 0x04 /* General Input/Output Switching Register */
+#define OUTDT 0x08 /* General Output Register */
+#define INDT 0x0c /* General Input Register */
+#define INTDT 0x10 /* Interrupt Display Register */
+#define INTCLR 0x14 /* Interrupt Clear Register */
+#define INTMSK 0x18 /* Interrupt Mask Register */
+#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 */
+
+#define RCAR_MAX_GPIO_PER_BANK 32
+
+static inline u32 gpio_rcar_read(struct gpio_rcar_priv *p, int offs)
+{
+ return ioread32(p->base + offs);
+}
+
+static inline void gpio_rcar_write(struct gpio_rcar_priv *p, int offs,
+ u32 value)
+{
+ iowrite32(value, p->base + offs);
+}
+
+static void gpio_rcar_modify_bit(struct gpio_rcar_priv *p, int offs,
+ int bit, bool value)
+{
+ u32 tmp = gpio_rcar_read(p, offs);
+
+ if (value)
+ tmp |= BIT(bit);
+ else
+ tmp &= ~BIT(bit);
+
+ gpio_rcar_write(p, offs, tmp);
+}
+
+static void gpio_rcar_irq_disable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct gpio_rcar_priv *p = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ gpio_rcar_write(p, INTMSK, ~BIT(hwirq));
+ gpiochip_disable_irq(gc, hwirq);
+}
+
+static void gpio_rcar_irq_enable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct gpio_rcar_priv *p = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ gpiochip_enable_irq(gc, hwirq);
+ gpio_rcar_write(p, MSKCLR, BIT(hwirq));
+}
+
+static void gpio_rcar_config_interrupt_input_mode(struct gpio_rcar_priv *p,
+ unsigned int hwirq,
+ bool active_high_rising_edge,
+ bool level_trigger,
+ bool both)
+{
+ unsigned long flags;
+
+ /* follow steps in the GPIO documentation for
+ * "Setting Edge-Sensitive Interrupt Input Mode" and
+ * "Setting Level-Sensitive Interrupt Input Mode"
+ */
+
+ spin_lock_irqsave(&p->lock, flags);
+
+ /* Configure positive or negative logic in POSNEG */
+ gpio_rcar_modify_bit(p, POSNEG, hwirq, !active_high_rising_edge);
+
+ /* Configure edge or level trigger in EDGLEVEL */
+ gpio_rcar_modify_bit(p, EDGLEVEL, hwirq, !level_trigger);
+
+ /* Select one edge or both edges in BOTHEDGE */
+ if (p->info.has_both_edge_trigger)
+ gpio_rcar_modify_bit(p, BOTHEDGE, hwirq, both);
+
+ /* Select "Interrupt Input Mode" in IOINTSEL */
+ gpio_rcar_modify_bit(p, IOINTSEL, hwirq, true);
+
+ /* Write INTCLR in case of edge trigger */
+ if (!level_trigger)
+ gpio_rcar_write(p, INTCLR, BIT(hwirq));
+
+ spin_unlock_irqrestore(&p->lock, flags);
+}
+
+static int gpio_rcar_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct gpio_rcar_priv *p = gpiochip_get_data(gc);
+ unsigned int hwirq = irqd_to_hwirq(d);
+
+ dev_dbg(p->dev, "sense irq = %d, type = %d\n", hwirq, type);
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ gpio_rcar_config_interrupt_input_mode(p, hwirq, true, true,
+ false);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ gpio_rcar_config_interrupt_input_mode(p, hwirq, false, true,
+ false);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ gpio_rcar_config_interrupt_input_mode(p, hwirq, true, false,
+ false);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ gpio_rcar_config_interrupt_input_mode(p, hwirq, false, false,
+ false);
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ if (!p->info.has_both_edge_trigger)
+ return -EINVAL;
+ gpio_rcar_config_interrupt_input_mode(p, hwirq, true, false,
+ true);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int gpio_rcar_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct gpio_rcar_priv *p = gpiochip_get_data(gc);
+ int error;
+
+ if (p->irq_parent) {
+ error = irq_set_irq_wake(p->irq_parent, on);
+ if (error) {
+ dev_dbg(p->dev, "irq %u doesn't support irq_set_wake\n",
+ p->irq_parent);
+ p->irq_parent = 0;
+ }
+ }
+
+ if (on)
+ atomic_inc(&p->wakeup_path);
+ else
+ atomic_dec(&p->wakeup_path);
+
+ return 0;
+}
+
+static const struct irq_chip gpio_rcar_irq_chip = {
+ .name = "gpio-rcar",
+ .irq_mask = gpio_rcar_irq_disable,
+ .irq_unmask = gpio_rcar_irq_enable,
+ .irq_set_type = gpio_rcar_irq_set_type,
+ .irq_set_wake = gpio_rcar_irq_set_wake,
+ .flags = IRQCHIP_IMMUTABLE | IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_MASK_ON_SUSPEND,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static irqreturn_t gpio_rcar_irq_handler(int irq, void *dev_id)
+{
+ struct gpio_rcar_priv *p = dev_id;
+ u32 pending;
+ unsigned int offset, irqs_handled = 0;
+
+ while ((pending = gpio_rcar_read(p, INTDT) &
+ gpio_rcar_read(p, INTMSK))) {
+ offset = __ffs(pending);
+ gpio_rcar_write(p, INTCLR, BIT(offset));
+ generic_handle_domain_irq(p->gpio_chip.irq.domain,
+ offset);
+ irqs_handled++;
+ }
+
+ return irqs_handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static void gpio_rcar_config_general_input_output_mode(struct gpio_chip *chip,
+ unsigned int gpio,
+ bool output)
+{
+ struct gpio_rcar_priv *p = gpiochip_get_data(chip);
+ unsigned long flags;
+
+ /* follow steps in the GPIO documentation for
+ * "Setting General Output Mode" and
+ * "Setting General Input Mode"
+ */
+
+ spin_lock_irqsave(&p->lock, flags);
+
+ /* Configure positive logic in POSNEG */
+ gpio_rcar_modify_bit(p, POSNEG, gpio, false);
+
+ /* Select "General Input/Output Mode" in IOINTSEL */
+ gpio_rcar_modify_bit(p, IOINTSEL, gpio, false);
+
+ /* Select Input Mode or Output Mode in INOUTSEL */
+ gpio_rcar_modify_bit(p, INOUTSEL, gpio, output);
+
+ /* Select General Output Register to output data in OUTDTSEL */
+ if (p->info.has_outdtsel && output)
+ gpio_rcar_modify_bit(p, OUTDTSEL, gpio, false);
+
+ spin_unlock_irqrestore(&p->lock, flags);
+}
+
+static int gpio_rcar_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_rcar_priv *p = gpiochip_get_data(chip);
+ int error;
+
+ error = pm_runtime_get_sync(p->dev);
+ if (error < 0) {
+ pm_runtime_put(p->dev);
+ return error;
+ }
+
+ error = pinctrl_gpio_request(chip->base + offset);
+ if (error)
+ pm_runtime_put(p->dev);
+
+ return error;
+}
+
+static void gpio_rcar_free(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_rcar_priv *p = gpiochip_get_data(chip);
+
+ pinctrl_gpio_free(chip->base + offset);
+
+ /*
+ * Set the GPIO as an input to ensure that the next GPIO request won't
+ * drive the GPIO pin as an output.
+ */
+ gpio_rcar_config_general_input_output_mode(chip, offset, false);
+
+ pm_runtime_put(p->dev);
+}
+
+static int gpio_rcar_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gpio_rcar_priv *p = gpiochip_get_data(chip);
+
+ if (gpio_rcar_read(p, INOUTSEL) & BIT(offset))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int gpio_rcar_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ gpio_rcar_config_general_input_output_mode(chip, offset, false);
+ return 0;
+}
+
+static int gpio_rcar_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_rcar_priv *p = gpiochip_get_data(chip);
+ u32 bit = BIT(offset);
+
+ /*
+ * Before R-Car Gen3, INDT does not show correct pin state when
+ * configured as output, so use OUTDT in case of output pins
+ */
+ if (!p->info.has_always_in && (gpio_rcar_read(p, INOUTSEL) & bit))
+ return !!(gpio_rcar_read(p, OUTDT) & bit);
+ else
+ return !!(gpio_rcar_read(p, INDT) & bit);
+}
+
+static int gpio_rcar_get_multiple(struct gpio_chip *chip, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct gpio_rcar_priv *p = gpiochip_get_data(chip);
+ u32 bankmask, outputs, m, val = 0;
+ unsigned long flags;
+
+ bankmask = mask[0] & GENMASK(chip->ngpio - 1, 0);
+ if (chip->valid_mask)
+ bankmask &= chip->valid_mask[0];
+
+ if (!bankmask)
+ return 0;
+
+ if (p->info.has_always_in) {
+ bits[0] = gpio_rcar_read(p, INDT) & bankmask;
+ return 0;
+ }
+
+ spin_lock_irqsave(&p->lock, flags);
+ outputs = gpio_rcar_read(p, INOUTSEL);
+ m = outputs & bankmask;
+ if (m)
+ val |= gpio_rcar_read(p, OUTDT) & m;
+
+ m = ~outputs & bankmask;
+ if (m)
+ val |= gpio_rcar_read(p, INDT) & m;
+ spin_unlock_irqrestore(&p->lock, flags);
+
+ bits[0] = val;
+ return 0;
+}
+
+static void gpio_rcar_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct gpio_rcar_priv *p = gpiochip_get_data(chip);
+ unsigned long flags;
+
+ spin_lock_irqsave(&p->lock, flags);
+ gpio_rcar_modify_bit(p, OUTDT, offset, value);
+ spin_unlock_irqrestore(&p->lock, flags);
+}
+
+static void gpio_rcar_set_multiple(struct gpio_chip *chip, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct gpio_rcar_priv *p = gpiochip_get_data(chip);
+ unsigned long flags;
+ u32 val, bankmask;
+
+ bankmask = mask[0] & GENMASK(chip->ngpio - 1, 0);
+ if (chip->valid_mask)
+ bankmask &= chip->valid_mask[0];
+
+ if (!bankmask)
+ return;
+
+ spin_lock_irqsave(&p->lock, flags);
+ val = gpio_rcar_read(p, OUTDT);
+ val &= ~bankmask;
+ val |= (bankmask & bits[0]);
+ gpio_rcar_write(p, OUTDT, val);
+ spin_unlock_irqrestore(&p->lock, flags);
+}
+
+static int gpio_rcar_direction_output(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ /* write GPIO value to output before selecting output mode of pin */
+ gpio_rcar_set(chip, offset, value);
+ gpio_rcar_config_general_input_output_mode(chip, offset, true);
+ return 0;
+}
+
+static const struct gpio_rcar_info gpio_rcar_info_gen1 = {
+ .has_outdtsel = false,
+ .has_both_edge_trigger = false,
+ .has_always_in = false,
+ .has_inen = false,
+};
+
+static const struct gpio_rcar_info gpio_rcar_info_gen2 = {
+ .has_outdtsel = true,
+ .has_both_edge_trigger = true,
+ .has_always_in = false,
+ .has_inen = false,
+};
+
+static const struct gpio_rcar_info gpio_rcar_info_gen3 = {
+ .has_outdtsel = true,
+ .has_both_edge_trigger = true,
+ .has_always_in = true,
+ .has_inen = false,
+};
+
+static const struct gpio_rcar_info gpio_rcar_info_gen4 = {
+ .has_outdtsel = true,
+ .has_both_edge_trigger = true,
+ .has_always_in = true,
+ .has_inen = true,
+};
+
+static const struct of_device_id gpio_rcar_of_table[] = {
+ {
+ .compatible = "renesas,gpio-r8a779a0",
+ .data = &gpio_rcar_info_gen4,
+ }, {
+ .compatible = "renesas,rcar-gen1-gpio",
+ .data = &gpio_rcar_info_gen1,
+ }, {
+ .compatible = "renesas,rcar-gen2-gpio",
+ .data = &gpio_rcar_info_gen2,
+ }, {
+ .compatible = "renesas,rcar-gen3-gpio",
+ .data = &gpio_rcar_info_gen3,
+ }, {
+ .compatible = "renesas,rcar-gen4-gpio",
+ .data = &gpio_rcar_info_gen4,
+ }, {
+ .compatible = "renesas,gpio-rcar",
+ .data = &gpio_rcar_info_gen1,
+ }, {
+ /* Terminator */
+ },
+};
+
+MODULE_DEVICE_TABLE(of, gpio_rcar_of_table);
+
+static int gpio_rcar_parse_dt(struct gpio_rcar_priv *p, unsigned int *npins)
+{
+ struct device_node *np = p->dev->of_node;
+ const struct gpio_rcar_info *info;
+ struct of_phandle_args args;
+ int ret;
+
+ info = of_device_get_match_data(p->dev);
+ p->info = *info;
+
+ ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3, 0, &args);
+ *npins = ret == 0 ? args.args[2] : RCAR_MAX_GPIO_PER_BANK;
+
+ if (*npins == 0 || *npins > RCAR_MAX_GPIO_PER_BANK) {
+ dev_warn(p->dev, "Invalid number of gpio lines %u, using %u\n",
+ *npins, RCAR_MAX_GPIO_PER_BANK);
+ *npins = RCAR_MAX_GPIO_PER_BANK;
+ }
+
+ return 0;
+}
+
+static void gpio_rcar_enable_inputs(struct gpio_rcar_priv *p)
+{
+ u32 mask = GENMASK(p->gpio_chip.ngpio - 1, 0);
+
+ /* Select "Input Enable" in INEN */
+ if (p->gpio_chip.valid_mask)
+ mask &= p->gpio_chip.valid_mask[0];
+ if (mask)
+ gpio_rcar_write(p, INEN, gpio_rcar_read(p, INEN) | mask);
+}
+
+static int gpio_rcar_probe(struct platform_device *pdev)
+{
+ struct gpio_rcar_priv *p;
+ struct gpio_chip *gpio_chip;
+ struct gpio_irq_chip *girq;
+ struct device *dev = &pdev->dev;
+ const char *name = dev_name(dev);
+ unsigned int npins;
+ int ret;
+
+ p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ p->dev = dev;
+ spin_lock_init(&p->lock);
+
+ /* Get device configuration from DT node */
+ ret = gpio_rcar_parse_dt(p, &npins);
+ if (ret < 0)
+ return ret;
+
+ platform_set_drvdata(pdev, p);
+
+ pm_runtime_enable(dev);
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ goto err0;
+ p->irq_parent = ret;
+
+ p->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(p->base)) {
+ ret = PTR_ERR(p->base);
+ goto err0;
+ }
+
+ gpio_chip = &p->gpio_chip;
+ gpio_chip->request = gpio_rcar_request;
+ gpio_chip->free = gpio_rcar_free;
+ gpio_chip->get_direction = gpio_rcar_get_direction;
+ gpio_chip->direction_input = gpio_rcar_direction_input;
+ gpio_chip->get = gpio_rcar_get;
+ gpio_chip->get_multiple = gpio_rcar_get_multiple;
+ gpio_chip->direction_output = gpio_rcar_direction_output;
+ gpio_chip->set = gpio_rcar_set;
+ gpio_chip->set_multiple = gpio_rcar_set_multiple;
+ gpio_chip->label = name;
+ gpio_chip->parent = dev;
+ gpio_chip->owner = THIS_MODULE;
+ gpio_chip->base = -1;
+ gpio_chip->ngpio = npins;
+
+ girq = &gpio_chip->irq;
+ gpio_irq_chip_set_chip(girq, &gpio_rcar_irq_chip);
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_level_irq;
+
+ ret = gpiochip_add_data(gpio_chip, p);
+ if (ret) {
+ dev_err(dev, "failed to add GPIO controller\n");
+ goto err0;
+ }
+
+ irq_domain_set_pm_device(gpio_chip->irq.domain, dev);
+ ret = devm_request_irq(dev, p->irq_parent, gpio_rcar_irq_handler,
+ IRQF_SHARED, name, p);
+ if (ret) {
+ dev_err(dev, "failed to request IRQ\n");
+ goto err1;
+ }
+
+ if (p->info.has_inen) {
+ pm_runtime_get_sync(dev);
+ gpio_rcar_enable_inputs(p);
+ pm_runtime_put(dev);
+ }
+
+ dev_info(dev, "driving %d GPIOs\n", npins);
+
+ return 0;
+
+err1:
+ gpiochip_remove(gpio_chip);
+err0:
+ pm_runtime_disable(dev);
+ return ret;
+}
+
+static int gpio_rcar_remove(struct platform_device *pdev)
+{
+ struct gpio_rcar_priv *p = platform_get_drvdata(pdev);
+
+ gpiochip_remove(&p->gpio_chip);
+
+ pm_runtime_disable(&pdev->dev);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int gpio_rcar_suspend(struct device *dev)
+{
+ struct gpio_rcar_priv *p = dev_get_drvdata(dev);
+
+ p->bank_info.iointsel = gpio_rcar_read(p, IOINTSEL);
+ p->bank_info.inoutsel = gpio_rcar_read(p, INOUTSEL);
+ p->bank_info.outdt = gpio_rcar_read(p, OUTDT);
+ p->bank_info.intmsk = gpio_rcar_read(p, INTMSK);
+ p->bank_info.posneg = gpio_rcar_read(p, POSNEG);
+ p->bank_info.edglevel = gpio_rcar_read(p, EDGLEVEL);
+ if (p->info.has_both_edge_trigger)
+ p->bank_info.bothedge = gpio_rcar_read(p, BOTHEDGE);
+
+ if (atomic_read(&p->wakeup_path))
+ device_set_wakeup_path(dev);
+
+ return 0;
+}
+
+static int gpio_rcar_resume(struct device *dev)
+{
+ struct gpio_rcar_priv *p = dev_get_drvdata(dev);
+ unsigned int offset;
+ u32 mask;
+
+ for (offset = 0; offset < p->gpio_chip.ngpio; offset++) {
+ if (!gpiochip_line_is_valid(&p->gpio_chip, offset))
+ continue;
+
+ mask = BIT(offset);
+ /* I/O pin */
+ if (!(p->bank_info.iointsel & mask)) {
+ if (p->bank_info.inoutsel & mask)
+ gpio_rcar_direction_output(
+ &p->gpio_chip, offset,
+ !!(p->bank_info.outdt & mask));
+ else
+ gpio_rcar_direction_input(&p->gpio_chip,
+ offset);
+ } else {
+ /* Interrupt pin */
+ gpio_rcar_config_interrupt_input_mode(
+ p,
+ offset,
+ !(p->bank_info.posneg & mask),
+ !(p->bank_info.edglevel & mask),
+ !!(p->bank_info.bothedge & mask));
+
+ if (p->bank_info.intmsk & mask)
+ gpio_rcar_write(p, MSKCLR, mask);
+ }
+ }
+
+ if (p->info.has_inen)
+ gpio_rcar_enable_inputs(p);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP*/
+
+static SIMPLE_DEV_PM_OPS(gpio_rcar_pm_ops, gpio_rcar_suspend, gpio_rcar_resume);
+
+static struct platform_driver gpio_rcar_device_driver = {
+ .probe = gpio_rcar_probe,
+ .remove = gpio_rcar_remove,
+ .driver = {
+ .name = "gpio_rcar",
+ .pm = &gpio_rcar_pm_ops,
+ .of_match_table = gpio_rcar_of_table,
+ }
+};
+
+module_platform_driver(gpio_rcar_device_driver);
+
+MODULE_AUTHOR("Magnus Damm");
+MODULE_DESCRIPTION("Renesas R-Car GPIO Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-rda.c b/drivers/gpio/gpio-rda.c
new file mode 100644
index 0000000000..cb2f63eee2
--- /dev/null
+++ b/drivers/gpio/gpio-rda.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RDA Micro GPIO driver
+ *
+ * Copyright (C) 2012 RDA Micro Inc.
+ * Copyright (C) 2019 Manivannan Sadhasivam
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define RDA_GPIO_OEN_VAL 0x00
+#define RDA_GPIO_OEN_SET_OUT 0x04
+#define RDA_GPIO_OEN_SET_IN 0x08
+#define RDA_GPIO_VAL 0x0c
+#define RDA_GPIO_SET 0x10
+#define RDA_GPIO_CLR 0x14
+#define RDA_GPIO_INT_CTRL_SET 0x18
+#define RDA_GPIO_INT_CTRL_CLR 0x1c
+#define RDA_GPIO_INT_CLR 0x20
+#define RDA_GPIO_INT_STATUS 0x24
+
+#define RDA_GPIO_IRQ_RISE_SHIFT 0
+#define RDA_GPIO_IRQ_FALL_SHIFT 8
+#define RDA_GPIO_DEBOUCE_SHIFT 16
+#define RDA_GPIO_LEVEL_SHIFT 24
+
+#define RDA_GPIO_IRQ_MASK 0xff
+
+/* Each bank consists of 32 GPIOs */
+#define RDA_GPIO_BANK_NR 32
+
+struct rda_gpio {
+ struct gpio_chip chip;
+ void __iomem *base;
+ spinlock_t lock;
+ int irq;
+};
+
+static inline void rda_gpio_update(struct gpio_chip *chip, unsigned int offset,
+ u16 reg, int val)
+{
+ struct rda_gpio *rda_gpio = gpiochip_get_data(chip);
+ void __iomem *base = rda_gpio->base;
+ unsigned long flags;
+ u32 tmp;
+
+ spin_lock_irqsave(&rda_gpio->lock, flags);
+ tmp = readl_relaxed(base + reg);
+
+ if (val)
+ tmp |= BIT(offset);
+ else
+ tmp &= ~BIT(offset);
+
+ writel_relaxed(tmp, base + reg);
+ spin_unlock_irqrestore(&rda_gpio->lock, flags);
+}
+
+static void rda_gpio_irq_mask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct rda_gpio *rda_gpio = gpiochip_get_data(chip);
+ void __iomem *base = rda_gpio->base;
+ u32 offset = irqd_to_hwirq(data);
+ u32 value;
+
+ value = BIT(offset) << RDA_GPIO_IRQ_RISE_SHIFT;
+ value |= BIT(offset) << RDA_GPIO_IRQ_FALL_SHIFT;
+
+ writel_relaxed(value, base + RDA_GPIO_INT_CTRL_CLR);
+ gpiochip_disable_irq(chip, offset);
+}
+
+static void rda_gpio_irq_ack(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ u32 offset = irqd_to_hwirq(data);
+
+ rda_gpio_update(chip, offset, RDA_GPIO_INT_CLR, 1);
+}
+
+static int rda_gpio_set_irq(struct gpio_chip *chip, u32 offset,
+ unsigned int flow_type)
+{
+ struct rda_gpio *rda_gpio = gpiochip_get_data(chip);
+ void __iomem *base = rda_gpio->base;
+ u32 value;
+
+ switch (flow_type) {
+ case IRQ_TYPE_EDGE_RISING:
+ /* Set rising edge trigger */
+ value = BIT(offset) << RDA_GPIO_IRQ_RISE_SHIFT;
+ writel_relaxed(value, base + RDA_GPIO_INT_CTRL_SET);
+
+ /* Switch to edge trigger interrupt */
+ value = BIT(offset) << RDA_GPIO_LEVEL_SHIFT;
+ writel_relaxed(value, base + RDA_GPIO_INT_CTRL_CLR);
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ /* Set falling edge trigger */
+ value = BIT(offset) << RDA_GPIO_IRQ_FALL_SHIFT;
+ writel_relaxed(value, base + RDA_GPIO_INT_CTRL_SET);
+
+ /* Switch to edge trigger interrupt */
+ value = BIT(offset) << RDA_GPIO_LEVEL_SHIFT;
+ writel_relaxed(value, base + RDA_GPIO_INT_CTRL_CLR);
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ /* Set both edge trigger */
+ value = BIT(offset) << RDA_GPIO_IRQ_RISE_SHIFT;
+ value |= BIT(offset) << RDA_GPIO_IRQ_FALL_SHIFT;
+ writel_relaxed(value, base + RDA_GPIO_INT_CTRL_SET);
+
+ /* Switch to edge trigger interrupt */
+ value = BIT(offset) << RDA_GPIO_LEVEL_SHIFT;
+ writel_relaxed(value, base + RDA_GPIO_INT_CTRL_CLR);
+ break;
+
+ case IRQ_TYPE_LEVEL_HIGH:
+ /* Set high level trigger */
+ value = BIT(offset) << RDA_GPIO_IRQ_RISE_SHIFT;
+
+ /* Switch to level trigger interrupt */
+ value |= BIT(offset) << RDA_GPIO_LEVEL_SHIFT;
+ writel_relaxed(value, base + RDA_GPIO_INT_CTRL_SET);
+ break;
+
+ case IRQ_TYPE_LEVEL_LOW:
+ /* Set low level trigger */
+ value = BIT(offset) << RDA_GPIO_IRQ_FALL_SHIFT;
+
+ /* Switch to level trigger interrupt */
+ value |= BIT(offset) << RDA_GPIO_LEVEL_SHIFT;
+ writel_relaxed(value, base + RDA_GPIO_INT_CTRL_SET);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void rda_gpio_irq_unmask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ u32 offset = irqd_to_hwirq(data);
+ u32 trigger = irqd_get_trigger_type(data);
+
+ gpiochip_enable_irq(chip, offset);
+ rda_gpio_set_irq(chip, offset, trigger);
+}
+
+static int rda_gpio_irq_set_type(struct irq_data *data, unsigned int flow_type)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ u32 offset = irqd_to_hwirq(data);
+ int ret;
+
+ ret = rda_gpio_set_irq(chip, offset, flow_type);
+ if (ret)
+ return ret;
+
+ if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
+ irq_set_handler_locked(data, handle_level_irq);
+ else if (flow_type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
+ irq_set_handler_locked(data, handle_edge_irq);
+
+ return 0;
+}
+
+static void rda_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct gpio_chip *chip = irq_desc_get_handler_data(desc);
+ struct irq_chip *ic = irq_desc_get_chip(desc);
+ struct rda_gpio *rda_gpio = gpiochip_get_data(chip);
+ unsigned long status;
+ u32 n;
+
+ chained_irq_enter(ic, desc);
+
+ status = readl_relaxed(rda_gpio->base + RDA_GPIO_INT_STATUS);
+ /* Only lower 8 bits are capable of generating interrupts */
+ status &= RDA_GPIO_IRQ_MASK;
+
+ for_each_set_bit(n, &status, RDA_GPIO_BANK_NR)
+ generic_handle_domain_irq(chip->irq.domain, n);
+
+ chained_irq_exit(ic, desc);
+}
+
+static const struct irq_chip rda_gpio_irq_chip = {
+ .name = "rda-gpio",
+ .irq_ack = rda_gpio_irq_ack,
+ .irq_mask = rda_gpio_irq_mask,
+ .irq_unmask = rda_gpio_irq_unmask,
+ .irq_set_type = rda_gpio_irq_set_type,
+ .flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int rda_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct gpio_irq_chip *girq;
+ struct rda_gpio *rda_gpio;
+ u32 ngpios;
+ int ret;
+
+ rda_gpio = devm_kzalloc(dev, sizeof(*rda_gpio), GFP_KERNEL);
+ if (!rda_gpio)
+ return -ENOMEM;
+
+ ret = device_property_read_u32(dev, "ngpios", &ngpios);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Not all ports have interrupt capability. For instance, on
+ * RDA8810PL, GPIOC doesn't support interrupt. So we must handle
+ * those also.
+ */
+ rda_gpio->irq = platform_get_irq(pdev, 0);
+
+ rda_gpio->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(rda_gpio->base))
+ return PTR_ERR(rda_gpio->base);
+
+ spin_lock_init(&rda_gpio->lock);
+
+ ret = bgpio_init(&rda_gpio->chip, dev, 4,
+ rda_gpio->base + RDA_GPIO_VAL,
+ rda_gpio->base + RDA_GPIO_SET,
+ rda_gpio->base + RDA_GPIO_CLR,
+ rda_gpio->base + RDA_GPIO_OEN_SET_OUT,
+ rda_gpio->base + RDA_GPIO_OEN_SET_IN,
+ BGPIOF_READ_OUTPUT_REG_SET);
+ if (ret) {
+ dev_err(dev, "bgpio_init failed\n");
+ return ret;
+ }
+
+ rda_gpio->chip.label = dev_name(dev);
+ rda_gpio->chip.ngpio = ngpios;
+ rda_gpio->chip.base = -1;
+
+ if (rda_gpio->irq >= 0) {
+ girq = &rda_gpio->chip.irq;
+ gpio_irq_chip_set_chip(girq, &rda_gpio_irq_chip);
+ girq->handler = handle_bad_irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->parent_handler = rda_gpio_irq_handler;
+ girq->parent_handler_data = rda_gpio;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(dev, 1,
+ sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ girq->parents[0] = rda_gpio->irq;
+ }
+
+ platform_set_drvdata(pdev, rda_gpio);
+
+ return devm_gpiochip_add_data(dev, &rda_gpio->chip, rda_gpio);
+}
+
+static const struct of_device_id rda_gpio_of_match[] = {
+ { .compatible = "rda,8810pl-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rda_gpio_of_match);
+
+static struct platform_driver rda_gpio_driver = {
+ .probe = rda_gpio_probe,
+ .driver = {
+ .name = "rda-gpio",
+ .of_match_table = rda_gpio_of_match,
+ },
+};
+
+module_platform_driver_probe(rda_gpio_driver, rda_gpio_probe);
+
+MODULE_DESCRIPTION("RDA Micro GPIO driver");
+MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
diff --git a/drivers/gpio/gpio-rdc321x.c b/drivers/gpio/gpio-rdc321x.c
new file mode 100644
index 0000000000..01ed2517e9
--- /dev/null
+++ b/drivers/gpio/gpio-rdc321x.c
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * RDC321x GPIO driver
+ *
+ * Copyright (C) 2008, Volker Weiss <dev@tintuc.de>
+ * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org>
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/pci.h>
+#include <linux/gpio/driver.h>
+#include <linux/mfd/rdc321x.h>
+#include <linux/slab.h>
+
+struct rdc321x_gpio {
+ spinlock_t lock;
+ struct pci_dev *sb_pdev;
+ u32 data_reg[2];
+ int reg1_ctrl_base;
+ int reg1_data_base;
+ int reg2_ctrl_base;
+ int reg2_data_base;
+ struct gpio_chip chip;
+};
+
+/* read GPIO pin */
+static int rdc_gpio_get_value(struct gpio_chip *chip, unsigned gpio)
+{
+ struct rdc321x_gpio *gpch;
+ u32 value = 0;
+ int reg;
+
+ gpch = gpiochip_get_data(chip);
+ reg = gpio < 32 ? gpch->reg1_data_base : gpch->reg2_data_base;
+
+ spin_lock(&gpch->lock);
+ pci_write_config_dword(gpch->sb_pdev, reg,
+ gpch->data_reg[gpio < 32 ? 0 : 1]);
+ pci_read_config_dword(gpch->sb_pdev, reg, &value);
+ spin_unlock(&gpch->lock);
+
+ return (1 << (gpio & 0x1f)) & value ? 1 : 0;
+}
+
+static void rdc_gpio_set_value_impl(struct gpio_chip *chip,
+ unsigned gpio, int value)
+{
+ struct rdc321x_gpio *gpch;
+ int reg = (gpio < 32) ? 0 : 1;
+
+ gpch = gpiochip_get_data(chip);
+
+ if (value)
+ gpch->data_reg[reg] |= 1 << (gpio & 0x1f);
+ else
+ gpch->data_reg[reg] &= ~(1 << (gpio & 0x1f));
+
+ pci_write_config_dword(gpch->sb_pdev,
+ reg ? gpch->reg2_data_base : gpch->reg1_data_base,
+ gpch->data_reg[reg]);
+}
+
+/* set GPIO pin to value */
+static void rdc_gpio_set_value(struct gpio_chip *chip,
+ unsigned gpio, int value)
+{
+ struct rdc321x_gpio *gpch;
+
+ gpch = gpiochip_get_data(chip);
+ spin_lock(&gpch->lock);
+ rdc_gpio_set_value_impl(chip, gpio, value);
+ spin_unlock(&gpch->lock);
+}
+
+static int rdc_gpio_config(struct gpio_chip *chip,
+ unsigned gpio, int value)
+{
+ struct rdc321x_gpio *gpch;
+ int err;
+ u32 reg;
+
+ gpch = gpiochip_get_data(chip);
+
+ spin_lock(&gpch->lock);
+ err = pci_read_config_dword(gpch->sb_pdev, gpio < 32 ?
+ gpch->reg1_ctrl_base : gpch->reg2_ctrl_base, &reg);
+ if (err)
+ goto unlock;
+
+ reg |= 1 << (gpio & 0x1f);
+
+ err = pci_write_config_dword(gpch->sb_pdev, gpio < 32 ?
+ gpch->reg1_ctrl_base : gpch->reg2_ctrl_base, reg);
+ if (err)
+ goto unlock;
+
+ rdc_gpio_set_value_impl(chip, gpio, value);
+
+unlock:
+ spin_unlock(&gpch->lock);
+
+ return err;
+}
+
+/* configure GPIO pin as input */
+static int rdc_gpio_direction_input(struct gpio_chip *chip, unsigned gpio)
+{
+ return rdc_gpio_config(chip, gpio, 1);
+}
+
+/*
+ * Cache the initial value of both GPIO data registers
+ */
+static int rdc321x_gpio_probe(struct platform_device *pdev)
+{
+ int err;
+ struct resource *r;
+ struct rdc321x_gpio *rdc321x_gpio_dev;
+ struct rdc321x_gpio_pdata *pdata;
+
+ pdata = dev_get_platdata(&pdev->dev);
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data supplied\n");
+ return -ENODEV;
+ }
+
+ rdc321x_gpio_dev = devm_kzalloc(&pdev->dev, sizeof(struct rdc321x_gpio),
+ GFP_KERNEL);
+ if (!rdc321x_gpio_dev)
+ return -ENOMEM;
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_IO, "gpio-reg1");
+ if (!r) {
+ dev_err(&pdev->dev, "failed to get gpio-reg1 resource\n");
+ return -ENODEV;
+ }
+
+ spin_lock_init(&rdc321x_gpio_dev->lock);
+ rdc321x_gpio_dev->sb_pdev = pdata->sb_pdev;
+ rdc321x_gpio_dev->reg1_ctrl_base = r->start;
+ rdc321x_gpio_dev->reg1_data_base = r->start + 0x4;
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_IO, "gpio-reg2");
+ if (!r) {
+ dev_err(&pdev->dev, "failed to get gpio-reg2 resource\n");
+ return -ENODEV;
+ }
+
+ rdc321x_gpio_dev->reg2_ctrl_base = r->start;
+ rdc321x_gpio_dev->reg2_data_base = r->start + 0x4;
+
+ rdc321x_gpio_dev->chip.label = "rdc321x-gpio";
+ rdc321x_gpio_dev->chip.owner = THIS_MODULE;
+ rdc321x_gpio_dev->chip.direction_input = rdc_gpio_direction_input;
+ rdc321x_gpio_dev->chip.direction_output = rdc_gpio_config;
+ rdc321x_gpio_dev->chip.get = rdc_gpio_get_value;
+ rdc321x_gpio_dev->chip.set = rdc_gpio_set_value;
+ rdc321x_gpio_dev->chip.base = 0;
+ rdc321x_gpio_dev->chip.ngpio = pdata->max_gpios;
+
+ platform_set_drvdata(pdev, rdc321x_gpio_dev);
+
+ /* This might not be, what others (BIOS, bootloader, etc.)
+ wrote to these registers before, but it's a good guess. Still
+ better than just using 0xffffffff. */
+ err = pci_read_config_dword(rdc321x_gpio_dev->sb_pdev,
+ rdc321x_gpio_dev->reg1_data_base,
+ &rdc321x_gpio_dev->data_reg[0]);
+ if (err)
+ return err;
+
+ err = pci_read_config_dword(rdc321x_gpio_dev->sb_pdev,
+ rdc321x_gpio_dev->reg2_data_base,
+ &rdc321x_gpio_dev->data_reg[1]);
+ if (err)
+ return err;
+
+ dev_info(&pdev->dev, "registering %d GPIOs\n",
+ rdc321x_gpio_dev->chip.ngpio);
+ return devm_gpiochip_add_data(&pdev->dev, &rdc321x_gpio_dev->chip,
+ rdc321x_gpio_dev);
+}
+
+static struct platform_driver rdc321x_gpio_driver = {
+ .driver.name = "rdc321x-gpio",
+ .probe = rdc321x_gpio_probe,
+};
+
+module_platform_driver(rdc321x_gpio_driver);
+
+MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
+MODULE_DESCRIPTION("RDC321x GPIO driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rdc321x-gpio");
diff --git a/drivers/gpio/gpio-realtek-otto.c b/drivers/gpio/gpio-realtek-otto.c
new file mode 100644
index 0000000000..d6418f89d3
--- /dev/null
+++ b/drivers/gpio/gpio-realtek-otto.c
@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/gpio/driver.h>
+#include <linux/cpumask.h>
+#include <linux/irq.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+/*
+ * Total register block size is 0x1C for one bank of four ports (A, B, C, D).
+ * An optional second bank, with ports E, F, G, and H, may be present, starting
+ * at register offset 0x1C.
+ */
+
+/*
+ * Pin select: (0) "normal", (1) "dedicate peripheral"
+ * Not used on RTL8380/RTL8390, peripheral selection is managed by control bits
+ * in the peripheral registers.
+ */
+#define REALTEK_GPIO_REG_CNR 0x00
+/* Clear bit (0) for input, set bit (1) for output */
+#define REALTEK_GPIO_REG_DIR 0x08
+#define REALTEK_GPIO_REG_DATA 0x0C
+/* Read bit for IRQ status, write 1 to clear IRQ */
+#define REALTEK_GPIO_REG_ISR 0x10
+/* Two bits per GPIO in IMR registers */
+#define REALTEK_GPIO_REG_IMR 0x14
+#define REALTEK_GPIO_REG_IMR_AB 0x14
+#define REALTEK_GPIO_REG_IMR_CD 0x18
+#define REALTEK_GPIO_IMR_LINE_MASK GENMASK(1, 0)
+#define REALTEK_GPIO_IRQ_EDGE_FALLING 1
+#define REALTEK_GPIO_IRQ_EDGE_RISING 2
+#define REALTEK_GPIO_IRQ_EDGE_BOTH 3
+
+#define REALTEK_GPIO_MAX 32
+#define REALTEK_GPIO_PORTS_PER_BANK 4
+
+/**
+ * realtek_gpio_ctrl - Realtek Otto GPIO driver data
+ *
+ * @gc: Associated gpio_chip instance
+ * @base: Base address of the register block for a GPIO bank
+ * @lock: Lock for accessing the IRQ registers and values
+ * @intr_mask: Mask for interrupts lines
+ * @intr_type: Interrupt type selection
+ * @bank_read: Read a bank setting as a single 32-bit value
+ * @bank_write: Write a bank setting as a single 32-bit value
+ * @imr_line_pos: Bit shift of an IRQ line's IMR value.
+ *
+ * The DIR, DATA, and ISR registers consist of four 8-bit port values, packed
+ * into a single 32-bit register. Use @bank_read (@bank_write) to get (assign)
+ * a value from (to) these registers. The IMR register consists of four 16-bit
+ * port values, packed into two 32-bit registers. Use @imr_line_pos to get the
+ * bit shift of the 2-bit field for a line's IMR settings. Shifts larger than
+ * 32 overflow into the second register.
+ *
+ * Because the interrupt mask register (IMR) combines the function of IRQ type
+ * selection and masking, two extra values are stored. @intr_mask is used to
+ * mask/unmask the interrupts for a GPIO line, and @intr_type is used to store
+ * the selected interrupt types. The logical AND of these values is written to
+ * IMR on changes.
+ */
+struct realtek_gpio_ctrl {
+ struct gpio_chip gc;
+ void __iomem *base;
+ void __iomem *cpumask_base;
+ struct cpumask cpu_irq_maskable;
+ raw_spinlock_t lock;
+ u8 intr_mask[REALTEK_GPIO_MAX];
+ u8 intr_type[REALTEK_GPIO_MAX];
+ u32 (*bank_read)(void __iomem *reg);
+ void (*bank_write)(void __iomem *reg, u32 value);
+ unsigned int (*line_imr_pos)(unsigned int line);
+};
+
+/* Expand with more flags as devices with other quirks are added */
+enum realtek_gpio_flags {
+ /*
+ * Allow disabling interrupts, for cases where the port order is
+ * unknown. This may result in a port mismatch between ISR and IMR.
+ * An interrupt would appear to come from a different line than the
+ * line the IRQ handler was assigned to, causing uncaught interrupts.
+ */
+ GPIO_INTERRUPTS_DISABLED = BIT(0),
+ /*
+ * Port order is reversed, meaning DCBA register layout for 1-bit
+ * fields, and [BA, DC] for 2-bit fields.
+ */
+ GPIO_PORTS_REVERSED = BIT(1),
+ /*
+ * Interrupts can be enabled per cpu. This requires a secondary IO
+ * range, where the per-cpu enable masks are located.
+ */
+ GPIO_INTERRUPTS_PER_CPU = BIT(2),
+};
+
+static struct realtek_gpio_ctrl *irq_data_to_ctrl(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+
+ return container_of(gc, struct realtek_gpio_ctrl, gc);
+}
+
+/*
+ * Normal port order register access
+ *
+ * Port information is stored with the first port at offset 0, followed by the
+ * second, etc. Most registers store one bit per GPIO and use a u8 value per
+ * port. The two interrupt mask registers store two bits per GPIO, so use u16
+ * values.
+ */
+static u32 realtek_gpio_bank_read_swapped(void __iomem *reg)
+{
+ return ioread32be(reg);
+}
+
+static void realtek_gpio_bank_write_swapped(void __iomem *reg, u32 value)
+{
+ iowrite32be(value, reg);
+}
+
+static unsigned int realtek_gpio_line_imr_pos_swapped(unsigned int line)
+{
+ unsigned int port_pin = line % 8;
+ unsigned int port = line / 8;
+
+ return 2 * (8 * (port ^ 1) + port_pin);
+}
+
+/*
+ * Reversed port order register access
+ *
+ * For registers with one bit per GPIO, all ports are stored as u8-s in one
+ * register in reversed order. The two interrupt mask registers store two bits
+ * per GPIO, so use u16 values. The first register contains ports 1 and 0, the
+ * second ports 3 and 2.
+ */
+static u32 realtek_gpio_bank_read(void __iomem *reg)
+{
+ return ioread32(reg);
+}
+
+static void realtek_gpio_bank_write(void __iomem *reg, u32 value)
+{
+ iowrite32(value, reg);
+}
+
+static unsigned int realtek_gpio_line_imr_pos(unsigned int line)
+{
+ return 2 * line;
+}
+
+static void realtek_gpio_clear_isr(struct realtek_gpio_ctrl *ctrl, u32 mask)
+{
+ ctrl->bank_write(ctrl->base + REALTEK_GPIO_REG_ISR, mask);
+}
+
+static u32 realtek_gpio_read_isr(struct realtek_gpio_ctrl *ctrl)
+{
+ return ctrl->bank_read(ctrl->base + REALTEK_GPIO_REG_ISR);
+}
+
+/* Set the rising and falling edge mask bits for a GPIO pin */
+static void realtek_gpio_update_line_imr(struct realtek_gpio_ctrl *ctrl, unsigned int line)
+{
+ void __iomem *reg = ctrl->base + REALTEK_GPIO_REG_IMR;
+ unsigned int line_shift = ctrl->line_imr_pos(line);
+ unsigned int shift = line_shift % 32;
+ u32 irq_type = ctrl->intr_type[line];
+ u32 irq_mask = ctrl->intr_mask[line];
+ u32 reg_val;
+
+ reg += 4 * (line_shift / 32);
+ reg_val = ioread32(reg);
+ reg_val &= ~(REALTEK_GPIO_IMR_LINE_MASK << shift);
+ reg_val |= (irq_type & irq_mask & REALTEK_GPIO_IMR_LINE_MASK) << shift;
+ iowrite32(reg_val, reg);
+}
+
+static void realtek_gpio_irq_ack(struct irq_data *data)
+{
+ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
+ irq_hw_number_t line = irqd_to_hwirq(data);
+
+ realtek_gpio_clear_isr(ctrl, BIT(line));
+}
+
+static void realtek_gpio_irq_unmask(struct irq_data *data)
+{
+ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
+ unsigned int line = irqd_to_hwirq(data);
+ unsigned long flags;
+
+ gpiochip_enable_irq(&ctrl->gc, line);
+
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+ ctrl->intr_mask[line] = REALTEK_GPIO_IMR_LINE_MASK;
+ realtek_gpio_update_line_imr(ctrl, line);
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+}
+
+static void realtek_gpio_irq_mask(struct irq_data *data)
+{
+ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
+ unsigned int line = irqd_to_hwirq(data);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+ ctrl->intr_mask[line] = 0;
+ realtek_gpio_update_line_imr(ctrl, line);
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+
+ gpiochip_disable_irq(&ctrl->gc, line);
+}
+
+static int realtek_gpio_irq_set_type(struct irq_data *data, unsigned int flow_type)
+{
+ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
+ unsigned int line = irqd_to_hwirq(data);
+ unsigned long flags;
+ u8 type;
+
+ switch (flow_type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_FALLING:
+ type = REALTEK_GPIO_IRQ_EDGE_FALLING;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ type = REALTEK_GPIO_IRQ_EDGE_RISING;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ type = REALTEK_GPIO_IRQ_EDGE_BOTH;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ irq_set_handler_locked(data, handle_edge_irq);
+
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+ ctrl->intr_type[line] = type;
+ realtek_gpio_update_line_imr(ctrl, line);
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+
+ return 0;
+}
+
+static void realtek_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct gpio_chip *gc = irq_desc_get_handler_data(desc);
+ struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
+ struct irq_chip *irq_chip = irq_desc_get_chip(desc);
+ unsigned long status;
+ int offset;
+
+ chained_irq_enter(irq_chip, desc);
+
+ status = realtek_gpio_read_isr(ctrl);
+ for_each_set_bit(offset, &status, gc->ngpio)
+ generic_handle_domain_irq(gc->irq.domain, offset);
+
+ chained_irq_exit(irq_chip, desc);
+}
+
+static inline void __iomem *realtek_gpio_irq_cpu_mask(struct realtek_gpio_ctrl *ctrl, int cpu)
+{
+ return ctrl->cpumask_base + REALTEK_GPIO_PORTS_PER_BANK * cpu;
+}
+
+static int realtek_gpio_irq_set_affinity(struct irq_data *data,
+ const struct cpumask *dest, bool force)
+{
+ struct realtek_gpio_ctrl *ctrl = irq_data_to_ctrl(data);
+ unsigned int line = irqd_to_hwirq(data);
+ void __iomem *irq_cpu_mask;
+ unsigned long flags;
+ int cpu;
+ u32 v;
+
+ if (!ctrl->cpumask_base)
+ return -ENXIO;
+
+ raw_spin_lock_irqsave(&ctrl->lock, flags);
+
+ for_each_cpu(cpu, &ctrl->cpu_irq_maskable) {
+ irq_cpu_mask = realtek_gpio_irq_cpu_mask(ctrl, cpu);
+ v = ctrl->bank_read(irq_cpu_mask);
+
+ if (cpumask_test_cpu(cpu, dest))
+ v |= BIT(line);
+ else
+ v &= ~BIT(line);
+
+ ctrl->bank_write(irq_cpu_mask, v);
+ }
+
+ raw_spin_unlock_irqrestore(&ctrl->lock, flags);
+
+ irq_data_update_effective_affinity(data, dest);
+
+ return 0;
+}
+
+static int realtek_gpio_irq_init(struct gpio_chip *gc)
+{
+ struct realtek_gpio_ctrl *ctrl = gpiochip_get_data(gc);
+ u32 mask_all = GENMASK(gc->ngpio - 1, 0);
+ unsigned int line;
+ int cpu;
+
+ for (line = 0; line < gc->ngpio; line++)
+ realtek_gpio_update_line_imr(ctrl, line);
+
+ realtek_gpio_clear_isr(ctrl, mask_all);
+
+ for_each_cpu(cpu, &ctrl->cpu_irq_maskable)
+ ctrl->bank_write(realtek_gpio_irq_cpu_mask(ctrl, cpu), mask_all);
+
+ return 0;
+}
+
+static const struct irq_chip realtek_gpio_irq_chip = {
+ .name = "realtek-otto-gpio",
+ .irq_ack = realtek_gpio_irq_ack,
+ .irq_mask = realtek_gpio_irq_mask,
+ .irq_unmask = realtek_gpio_irq_unmask,
+ .irq_set_type = realtek_gpio_irq_set_type,
+ .irq_set_affinity = realtek_gpio_irq_set_affinity,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static const struct of_device_id realtek_gpio_of_match[] = {
+ {
+ .compatible = "realtek,otto-gpio",
+ .data = (void *)GPIO_INTERRUPTS_DISABLED,
+ },
+ {
+ .compatible = "realtek,rtl8380-gpio",
+ },
+ {
+ .compatible = "realtek,rtl8390-gpio",
+ },
+ {
+ .compatible = "realtek,rtl9300-gpio",
+ .data = (void *)(GPIO_PORTS_REVERSED | GPIO_INTERRUPTS_PER_CPU)
+ },
+ {
+ .compatible = "realtek,rtl9310-gpio",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, realtek_gpio_of_match);
+
+static int realtek_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ unsigned long bgpio_flags;
+ unsigned int dev_flags;
+ struct gpio_irq_chip *girq;
+ struct realtek_gpio_ctrl *ctrl;
+ struct resource *res;
+ u32 ngpios;
+ unsigned int nr_cpus;
+ int cpu, err, irq;
+
+ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ dev_flags = (unsigned int) device_get_match_data(dev);
+
+ ngpios = REALTEK_GPIO_MAX;
+ device_property_read_u32(dev, "ngpios", &ngpios);
+
+ if (ngpios > REALTEK_GPIO_MAX) {
+ dev_err(&pdev->dev, "invalid ngpios (max. %d)\n",
+ REALTEK_GPIO_MAX);
+ return -EINVAL;
+ }
+
+ ctrl->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctrl->base))
+ return PTR_ERR(ctrl->base);
+
+ raw_spin_lock_init(&ctrl->lock);
+
+ if (dev_flags & GPIO_PORTS_REVERSED) {
+ bgpio_flags = 0;
+ ctrl->bank_read = realtek_gpio_bank_read;
+ ctrl->bank_write = realtek_gpio_bank_write;
+ ctrl->line_imr_pos = realtek_gpio_line_imr_pos;
+ } else {
+ bgpio_flags = BGPIOF_BIG_ENDIAN_BYTE_ORDER;
+ ctrl->bank_read = realtek_gpio_bank_read_swapped;
+ ctrl->bank_write = realtek_gpio_bank_write_swapped;
+ ctrl->line_imr_pos = realtek_gpio_line_imr_pos_swapped;
+ }
+
+ err = bgpio_init(&ctrl->gc, dev, 4,
+ ctrl->base + REALTEK_GPIO_REG_DATA, NULL, NULL,
+ ctrl->base + REALTEK_GPIO_REG_DIR, NULL,
+ bgpio_flags);
+ if (err) {
+ dev_err(dev, "unable to init generic GPIO");
+ return err;
+ }
+
+ ctrl->gc.ngpio = ngpios;
+ ctrl->gc.owner = THIS_MODULE;
+
+ irq = platform_get_irq_optional(pdev, 0);
+ if (!(dev_flags & GPIO_INTERRUPTS_DISABLED) && irq > 0) {
+ girq = &ctrl->gc.irq;
+ gpio_irq_chip_set_chip(girq, &realtek_gpio_irq_chip);
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+ girq->parent_handler = realtek_gpio_irq_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(dev, girq->num_parents,
+ sizeof(*girq->parents), GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ girq->parents[0] = irq;
+ girq->init_hw = realtek_gpio_irq_init;
+ }
+
+ cpumask_clear(&ctrl->cpu_irq_maskable);
+
+ if ((dev_flags & GPIO_INTERRUPTS_PER_CPU) && irq > 0) {
+ ctrl->cpumask_base = devm_platform_get_and_ioremap_resource(pdev, 1, &res);
+ if (IS_ERR(ctrl->cpumask_base))
+ return dev_err_probe(dev, PTR_ERR(ctrl->cpumask_base),
+ "missing CPU IRQ mask registers");
+
+ nr_cpus = resource_size(res) / REALTEK_GPIO_PORTS_PER_BANK;
+ nr_cpus = min(nr_cpus, num_present_cpus());
+
+ for (cpu = 0; cpu < nr_cpus; cpu++)
+ cpumask_set_cpu(cpu, &ctrl->cpu_irq_maskable);
+ }
+
+ return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl);
+}
+
+static struct platform_driver realtek_gpio_driver = {
+ .driver = {
+ .name = "realtek-otto-gpio",
+ .of_match_table = realtek_gpio_of_match,
+ },
+ .probe = realtek_gpio_probe,
+};
+module_platform_driver(realtek_gpio_driver);
+
+MODULE_DESCRIPTION("Realtek Otto GPIO support");
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-reg.c b/drivers/gpio/gpio-reg.c
new file mode 100644
index 0000000000..73c7260d89
--- /dev/null
+++ b/drivers/gpio/gpio-reg.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * gpio-reg: single register individually fixed-direction GPIOs
+ *
+ * Copyright (C) 2016 Russell King
+ */
+#include <linux/bits.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/irqdomain.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include <linux/gpio/driver.h>
+#include <linux/gpio/gpio-reg.h>
+
+struct gpio_reg {
+ struct gpio_chip gc;
+ spinlock_t lock;
+ u32 direction;
+ u32 out;
+ void __iomem *reg;
+ struct irq_domain *irqdomain;
+ const int *irqs;
+};
+
+#define to_gpio_reg(x) container_of(x, struct gpio_reg, gc)
+
+static int gpio_reg_get_direction(struct gpio_chip *gc, unsigned offset)
+{
+ struct gpio_reg *r = to_gpio_reg(gc);
+
+ return r->direction & BIT(offset) ? GPIO_LINE_DIRECTION_IN :
+ GPIO_LINE_DIRECTION_OUT;
+}
+
+static int gpio_reg_direction_output(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ struct gpio_reg *r = to_gpio_reg(gc);
+
+ if (r->direction & BIT(offset))
+ return -ENOTSUPP;
+
+ gc->set(gc, offset, value);
+ return 0;
+}
+
+static int gpio_reg_direction_input(struct gpio_chip *gc, unsigned offset)
+{
+ struct gpio_reg *r = to_gpio_reg(gc);
+
+ return r->direction & BIT(offset) ? 0 : -ENOTSUPP;
+}
+
+static void gpio_reg_set(struct gpio_chip *gc, unsigned offset, int value)
+{
+ struct gpio_reg *r = to_gpio_reg(gc);
+ unsigned long flags;
+ u32 val, mask = BIT(offset);
+
+ spin_lock_irqsave(&r->lock, flags);
+ val = r->out;
+ if (value)
+ val |= mask;
+ else
+ val &= ~mask;
+ r->out = val;
+ writel_relaxed(val, r->reg);
+ spin_unlock_irqrestore(&r->lock, flags);
+}
+
+static int gpio_reg_get(struct gpio_chip *gc, unsigned offset)
+{
+ struct gpio_reg *r = to_gpio_reg(gc);
+ u32 val, mask = BIT(offset);
+
+ if (r->direction & mask) {
+ /*
+ * double-read the value, some registers latch after the
+ * first read.
+ */
+ readl_relaxed(r->reg);
+ val = readl_relaxed(r->reg);
+ } else {
+ val = r->out;
+ }
+ return !!(val & mask);
+}
+
+static void gpio_reg_set_multiple(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct gpio_reg *r = to_gpio_reg(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&r->lock, flags);
+ r->out = (r->out & ~*mask) | (*bits & *mask);
+ writel_relaxed(r->out, r->reg);
+ spin_unlock_irqrestore(&r->lock, flags);
+}
+
+static int gpio_reg_to_irq(struct gpio_chip *gc, unsigned offset)
+{
+ struct gpio_reg *r = to_gpio_reg(gc);
+ int irq = r->irqs[offset];
+
+ if (irq >= 0 && r->irqdomain)
+ irq = irq_find_mapping(r->irqdomain, irq);
+
+ return irq;
+}
+
+/**
+ * gpio_reg_init - add a fixed in/out register as gpio
+ * @dev: optional struct device associated with this register
+ * @base: start gpio number, or -1 to allocate
+ * @num: number of GPIOs, maximum 32
+ * @label: GPIO chip label
+ * @direction: bitmask of fixed direction, one per GPIO signal, 1 = in
+ * @def_out: initial GPIO output value
+ * @names: array of %num strings describing each GPIO signal or %NULL
+ * @irqdom: irq domain or %NULL
+ * @irqs: array of %num ints describing the interrupt mapping for each
+ * GPIO signal, or %NULL. If @irqdom is %NULL, then this
+ * describes the Linux interrupt number, otherwise it describes
+ * the hardware interrupt number in the specified irq domain.
+ *
+ * Add a single-register GPIO device containing up to 32 GPIO signals,
+ * where each GPIO has a fixed input or output configuration. Only
+ * input GPIOs are assumed to be readable from the register, and only
+ * then after a double-read. Output values are assumed not to be
+ * readable.
+ */
+struct gpio_chip *gpio_reg_init(struct device *dev, void __iomem *reg,
+ int base, int num, const char *label, u32 direction, u32 def_out,
+ const char *const *names, struct irq_domain *irqdom, const int *irqs)
+{
+ struct gpio_reg *r;
+ int ret;
+
+ if (dev)
+ r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL);
+ else
+ r = kzalloc(sizeof(*r), GFP_KERNEL);
+
+ if (!r)
+ return ERR_PTR(-ENOMEM);
+
+ spin_lock_init(&r->lock);
+
+ r->gc.label = label;
+ r->gc.get_direction = gpio_reg_get_direction;
+ r->gc.direction_input = gpio_reg_direction_input;
+ r->gc.direction_output = gpio_reg_direction_output;
+ r->gc.set = gpio_reg_set;
+ r->gc.get = gpio_reg_get;
+ r->gc.set_multiple = gpio_reg_set_multiple;
+ if (irqs)
+ r->gc.to_irq = gpio_reg_to_irq;
+ r->gc.base = base;
+ r->gc.ngpio = num;
+ r->gc.names = names;
+ r->direction = direction;
+ r->out = def_out;
+ r->reg = reg;
+ r->irqs = irqs;
+
+ if (dev)
+ ret = devm_gpiochip_add_data(dev, &r->gc, r);
+ else
+ ret = gpiochip_add_data(&r->gc, r);
+
+ return ret ? ERR_PTR(ret) : &r->gc;
+}
+
+int gpio_reg_resume(struct gpio_chip *gc)
+{
+ struct gpio_reg *r = to_gpio_reg(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&r->lock, flags);
+ writel_relaxed(r->out, r->reg);
+ spin_unlock_irqrestore(&r->lock, flags);
+
+ return 0;
+}
diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
new file mode 100644
index 0000000000..c08c8e5288
--- /dev/null
+++ b/drivers/gpio/gpio-regmap.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * regmap based generic GPIO driver
+ *
+ * Copyright 2020 Michael Walle <michael@walle.cc>
+ */
+
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+
+struct gpio_regmap {
+ struct device *parent;
+ struct regmap *regmap;
+ struct gpio_chip gpio_chip;
+
+ int reg_stride;
+ int ngpio_per_reg;
+ unsigned int reg_dat_base;
+ unsigned int reg_set_base;
+ unsigned int reg_clr_base;
+ unsigned int reg_dir_in_base;
+ unsigned int reg_dir_out_base;
+
+ int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base,
+ unsigned int offset, unsigned int *reg,
+ unsigned int *mask);
+
+ void *driver_data;
+};
+
+static unsigned int gpio_regmap_addr(unsigned int addr)
+{
+ if (addr == GPIO_REGMAP_ADDR_ZERO)
+ return 0;
+
+ return addr;
+}
+
+static int gpio_regmap_simple_xlate(struct gpio_regmap *gpio,
+ unsigned int base, unsigned int offset,
+ unsigned int *reg, unsigned int *mask)
+{
+ unsigned int line = offset % gpio->ngpio_per_reg;
+ unsigned int stride = offset / gpio->ngpio_per_reg;
+
+ *reg = base + stride * gpio->reg_stride;
+ *mask = BIT(line);
+
+ return 0;
+}
+
+static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gpio_regmap *gpio = gpiochip_get_data(chip);
+ unsigned int base, val, reg, mask;
+ int ret;
+
+ /* we might not have an output register if we are input only */
+ if (gpio->reg_dat_base)
+ base = gpio_regmap_addr(gpio->reg_dat_base);
+ else
+ base = gpio_regmap_addr(gpio->reg_set_base);
+
+ ret = gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(gpio->regmap, reg, &val);
+ if (ret)
+ return ret;
+
+ return !!(val & mask);
+}
+
+static void gpio_regmap_set(struct gpio_chip *chip, unsigned int offset,
+ int val)
+{
+ struct gpio_regmap *gpio = gpiochip_get_data(chip);
+ unsigned int base = gpio_regmap_addr(gpio->reg_set_base);
+ unsigned int reg, mask;
+
+ gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
+ if (val)
+ regmap_update_bits(gpio->regmap, reg, mask, mask);
+ else
+ regmap_update_bits(gpio->regmap, reg, mask, 0);
+}
+
+static void 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;
+
+ if (val)
+ base = gpio_regmap_addr(gpio->reg_set_base);
+ else
+ base = gpio_regmap_addr(gpio->reg_clr_base);
+
+ gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
+ regmap_write(gpio->regmap, reg, mask);
+}
+
+static int gpio_regmap_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct gpio_regmap *gpio = gpiochip_get_data(chip);
+ unsigned int base, val, reg, mask;
+ int invert, ret;
+
+ if (gpio->reg_dat_base && !gpio->reg_set_base)
+ return GPIO_LINE_DIRECTION_IN;
+ if (gpio->reg_set_base && !gpio->reg_dat_base)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ if (gpio->reg_dir_out_base) {
+ base = gpio_regmap_addr(gpio->reg_dir_out_base);
+ invert = 0;
+ } else if (gpio->reg_dir_in_base) {
+ base = gpio_regmap_addr(gpio->reg_dir_in_base);
+ invert = 1;
+ } else {
+ return -EOPNOTSUPP;
+ }
+
+ ret = gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(gpio->regmap, reg, &val);
+ if (ret)
+ return ret;
+
+ if (!!(val & mask) ^ invert)
+ return GPIO_LINE_DIRECTION_OUT;
+ else
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int gpio_regmap_set_direction(struct gpio_chip *chip,
+ unsigned int offset, bool output)
+{
+ struct gpio_regmap *gpio = gpiochip_get_data(chip);
+ unsigned int base, val, reg, mask;
+ int invert, ret;
+
+ if (gpio->reg_dir_out_base) {
+ base = gpio_regmap_addr(gpio->reg_dir_out_base);
+ invert = 0;
+ } else if (gpio->reg_dir_in_base) {
+ base = gpio_regmap_addr(gpio->reg_dir_in_base);
+ invert = 1;
+ } else {
+ return -EOPNOTSUPP;
+ }
+
+ ret = gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
+ if (ret)
+ return ret;
+
+ if (invert)
+ val = output ? 0 : mask;
+ else
+ val = output ? mask : 0;
+
+ return regmap_update_bits(gpio->regmap, reg, mask, val);
+}
+
+static int gpio_regmap_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ return gpio_regmap_set_direction(chip, offset, false);
+}
+
+static int gpio_regmap_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ gpio_regmap_set(chip, offset, value);
+
+ return gpio_regmap_set_direction(chip, offset, true);
+}
+
+void *gpio_regmap_get_drvdata(struct gpio_regmap *gpio)
+{
+ return gpio->driver_data;
+}
+EXPORT_SYMBOL_GPL(gpio_regmap_get_drvdata);
+
+/**
+ * gpio_regmap_register() - Register a generic regmap GPIO controller
+ * @config: configuration for gpio_regmap
+ *
+ * Return: A pointer to the registered gpio_regmap or ERR_PTR error value.
+ */
+struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config)
+{
+ struct gpio_regmap *gpio;
+ struct gpio_chip *chip;
+ int ret;
+
+ if (!config->parent)
+ return ERR_PTR(-EINVAL);
+
+ if (!config->ngpio)
+ return ERR_PTR(-EINVAL);
+
+ /* we need at least one */
+ if (!config->reg_dat_base && !config->reg_set_base)
+ return ERR_PTR(-EINVAL);
+
+ /* if we have a direction register we need both input and output */
+ if ((config->reg_dir_out_base || config->reg_dir_in_base) &&
+ (!config->reg_dat_base || !config->reg_set_base))
+ return ERR_PTR(-EINVAL);
+
+ /* we don't support having both registers simultaneously for now */
+ if (config->reg_dir_out_base && config->reg_dir_in_base)
+ return ERR_PTR(-EINVAL);
+
+ gpio = kzalloc(sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return ERR_PTR(-ENOMEM);
+
+ gpio->parent = config->parent;
+ gpio->driver_data = config->drvdata;
+ gpio->regmap = config->regmap;
+ gpio->ngpio_per_reg = config->ngpio_per_reg;
+ gpio->reg_stride = config->reg_stride;
+ gpio->reg_mask_xlate = config->reg_mask_xlate;
+ gpio->reg_dat_base = config->reg_dat_base;
+ gpio->reg_set_base = config->reg_set_base;
+ gpio->reg_clr_base = config->reg_clr_base;
+ gpio->reg_dir_in_base = config->reg_dir_in_base;
+ gpio->reg_dir_out_base = config->reg_dir_out_base;
+
+ /* if not set, assume there is only one register */
+ if (!gpio->ngpio_per_reg)
+ gpio->ngpio_per_reg = config->ngpio;
+
+ /* if not set, assume they are consecutive */
+ if (!gpio->reg_stride)
+ gpio->reg_stride = 1;
+
+ if (!gpio->reg_mask_xlate)
+ gpio->reg_mask_xlate = gpio_regmap_simple_xlate;
+
+ chip = &gpio->gpio_chip;
+ chip->parent = config->parent;
+ chip->fwnode = config->fwnode;
+ chip->base = -1;
+ chip->ngpio = config->ngpio;
+ chip->names = config->names;
+ chip->label = config->label ?: dev_name(config->parent);
+ chip->can_sleep = regmap_might_sleep(config->regmap);
+
+ chip->get = gpio_regmap_get;
+ if (gpio->reg_set_base && gpio->reg_clr_base)
+ chip->set = gpio_regmap_set_with_clear;
+ else if (gpio->reg_set_base)
+ chip->set = gpio_regmap_set;
+
+ chip->get_direction = gpio_regmap_get_direction;
+ if (gpio->reg_dir_in_base || gpio->reg_dir_out_base) {
+ chip->direction_input = gpio_regmap_direction_input;
+ chip->direction_output = gpio_regmap_direction_output;
+ }
+
+ ret = gpiochip_add_data(chip, gpio);
+ if (ret < 0)
+ goto err_free_gpio;
+
+ if (config->irq_domain) {
+ ret = gpiochip_irqchip_add_domain(chip, config->irq_domain);
+ if (ret)
+ goto err_remove_gpiochip;
+ }
+
+ return gpio;
+
+err_remove_gpiochip:
+ gpiochip_remove(chip);
+err_free_gpio:
+ kfree(gpio);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(gpio_regmap_register);
+
+/**
+ * gpio_regmap_unregister() - Unregister a generic regmap GPIO controller
+ * @gpio: gpio_regmap device to unregister
+ */
+void gpio_regmap_unregister(struct gpio_regmap *gpio)
+{
+ gpiochip_remove(&gpio->gpio_chip);
+ kfree(gpio);
+}
+EXPORT_SYMBOL_GPL(gpio_regmap_unregister);
+
+static void devm_gpio_regmap_unregister(void *res)
+{
+ gpio_regmap_unregister(res);
+}
+
+/**
+ * devm_gpio_regmap_register() - resource managed gpio_regmap_register()
+ * @dev: device that is registering this GPIO device
+ * @config: configuration for gpio_regmap
+ *
+ * Managed gpio_regmap_register(). For generic regmap GPIO device registered by
+ * this function, gpio_regmap_unregister() is automatically called on driver
+ * detach. See gpio_regmap_register() for more information.
+ *
+ * Return: A pointer to the registered gpio_regmap or ERR_PTR error value.
+ */
+struct gpio_regmap *devm_gpio_regmap_register(struct device *dev,
+ const struct gpio_regmap_config *config)
+{
+ struct gpio_regmap *gpio;
+ int ret;
+
+ gpio = gpio_regmap_register(config);
+
+ if (IS_ERR(gpio))
+ return gpio;
+
+ ret = devm_add_action_or_reset(dev, devm_gpio_regmap_unregister, gpio);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return gpio;
+}
+EXPORT_SYMBOL_GPL(devm_gpio_regmap_register);
+
+MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
+MODULE_DESCRIPTION("GPIO generic regmap driver core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-rockchip.c b/drivers/gpio/gpio-rockchip.c
new file mode 100644
index 0000000000..b35b960441
--- /dev/null
+++ b/drivers/gpio/gpio-rockchip.c
@@ -0,0 +1,821 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2013 MundoReader S.L.
+ * Author: Heiko Stuebner <heiko@sntech.de>
+ *
+ * Copyright (c) 2021 Rockchip Electronics Co. Ltd.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "../pinctrl/core.h"
+#include "../pinctrl/pinctrl-rockchip.h"
+
+#define GPIO_TYPE_V1 (0) /* GPIO Version ID reserved */
+#define GPIO_TYPE_V2 (0x01000C2B) /* GPIO Version ID 0x01000C2B */
+#define GPIO_TYPE_V2_1 (0x0101157C) /* GPIO Version ID 0x0101157C */
+
+static const struct rockchip_gpio_regs gpio_regs_v1 = {
+ .port_dr = 0x00,
+ .port_ddr = 0x04,
+ .int_en = 0x30,
+ .int_mask = 0x34,
+ .int_type = 0x38,
+ .int_polarity = 0x3c,
+ .int_status = 0x40,
+ .int_rawstatus = 0x44,
+ .debounce = 0x48,
+ .port_eoi = 0x4c,
+ .ext_port = 0x50,
+};
+
+static const struct rockchip_gpio_regs gpio_regs_v2 = {
+ .port_dr = 0x00,
+ .port_ddr = 0x08,
+ .int_en = 0x10,
+ .int_mask = 0x18,
+ .int_type = 0x20,
+ .int_polarity = 0x28,
+ .int_bothedge = 0x30,
+ .int_status = 0x50,
+ .int_rawstatus = 0x58,
+ .debounce = 0x38,
+ .dbclk_div_en = 0x40,
+ .dbclk_div_con = 0x48,
+ .port_eoi = 0x60,
+ .ext_port = 0x70,
+ .version_id = 0x78,
+};
+
+static inline void gpio_writel_v2(u32 val, void __iomem *reg)
+{
+ writel((val & 0xffff) | 0xffff0000, reg);
+ writel((val >> 16) | 0xffff0000, reg + 0x4);
+}
+
+static inline u32 gpio_readl_v2(void __iomem *reg)
+{
+ return readl(reg + 0x4) << 16 | readl(reg);
+}
+
+static inline void rockchip_gpio_writel(struct rockchip_pin_bank *bank,
+ u32 value, unsigned int offset)
+{
+ void __iomem *reg = bank->reg_base + offset;
+
+ if (bank->gpio_type == GPIO_TYPE_V2)
+ gpio_writel_v2(value, reg);
+ else
+ writel(value, reg);
+}
+
+static inline u32 rockchip_gpio_readl(struct rockchip_pin_bank *bank,
+ unsigned int offset)
+{
+ void __iomem *reg = bank->reg_base + offset;
+ u32 value;
+
+ if (bank->gpio_type == GPIO_TYPE_V2)
+ value = gpio_readl_v2(reg);
+ else
+ value = readl(reg);
+
+ return value;
+}
+
+static inline void rockchip_gpio_writel_bit(struct rockchip_pin_bank *bank,
+ u32 bit, u32 value,
+ unsigned int offset)
+{
+ void __iomem *reg = bank->reg_base + offset;
+ u32 data;
+
+ if (bank->gpio_type == GPIO_TYPE_V2) {
+ if (value)
+ data = BIT(bit % 16) | BIT(bit % 16 + 16);
+ else
+ data = BIT(bit % 16 + 16);
+ writel(data, bit >= 16 ? reg + 0x4 : reg);
+ } else {
+ data = readl(reg);
+ data &= ~BIT(bit);
+ if (value)
+ data |= BIT(bit);
+ writel(data, reg);
+ }
+}
+
+static inline u32 rockchip_gpio_readl_bit(struct rockchip_pin_bank *bank,
+ u32 bit, unsigned int offset)
+{
+ void __iomem *reg = bank->reg_base + offset;
+ u32 data;
+
+ if (bank->gpio_type == GPIO_TYPE_V2) {
+ data = readl(bit >= 16 ? reg + 0x4 : reg);
+ data >>= bit % 16;
+ } else {
+ data = readl(reg);
+ data >>= bit;
+ }
+
+ return data & (0x1);
+}
+
+static int rockchip_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct rockchip_pin_bank *bank = gpiochip_get_data(chip);
+ u32 data;
+
+ data = rockchip_gpio_readl_bit(bank, offset, bank->gpio_regs->port_ddr);
+ if (data)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int rockchip_gpio_set_direction(struct gpio_chip *chip,
+ unsigned int offset, bool input)
+{
+ struct rockchip_pin_bank *bank = gpiochip_get_data(chip);
+ unsigned long flags;
+ u32 data = input ? 0 : 1;
+
+
+ if (input)
+ pinctrl_gpio_direction_input(bank->pin_base + offset);
+ else
+ pinctrl_gpio_direction_output(bank->pin_base + offset);
+
+ raw_spin_lock_irqsave(&bank->slock, flags);
+ rockchip_gpio_writel_bit(bank, offset, data, bank->gpio_regs->port_ddr);
+ raw_spin_unlock_irqrestore(&bank->slock, flags);
+
+ return 0;
+}
+
+static void rockchip_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct rockchip_pin_bank *bank = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&bank->slock, flags);
+ rockchip_gpio_writel_bit(bank, offset, value, bank->gpio_regs->port_dr);
+ raw_spin_unlock_irqrestore(&bank->slock, flags);
+}
+
+static int rockchip_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct rockchip_pin_bank *bank = gpiochip_get_data(gc);
+ u32 data;
+
+ data = readl(bank->reg_base + bank->gpio_regs->ext_port);
+ data >>= offset;
+ data &= 1;
+
+ return data;
+}
+
+static int rockchip_gpio_set_debounce(struct gpio_chip *gc,
+ unsigned int offset,
+ unsigned int debounce)
+{
+ struct rockchip_pin_bank *bank = gpiochip_get_data(gc);
+ const struct rockchip_gpio_regs *reg = bank->gpio_regs;
+ unsigned long flags, div_reg, freq, max_debounce;
+ bool div_debounce_support;
+ unsigned int cur_div_reg;
+ u64 div;
+
+ if (bank->gpio_type == GPIO_TYPE_V2 && !IS_ERR(bank->db_clk)) {
+ div_debounce_support = true;
+ freq = clk_get_rate(bank->db_clk);
+ max_debounce = (GENMASK(23, 0) + 1) * 2 * 1000000 / freq;
+ if (debounce > max_debounce)
+ return -EINVAL;
+
+ div = debounce * freq;
+ div_reg = DIV_ROUND_CLOSEST_ULL(div, 2 * USEC_PER_SEC) - 1;
+ } else {
+ div_debounce_support = false;
+ }
+
+ raw_spin_lock_irqsave(&bank->slock, flags);
+
+ /* Only the v1 needs to configure div_en and div_con for dbclk */
+ if (debounce) {
+ if (div_debounce_support) {
+ /* Configure the max debounce from consumers */
+ cur_div_reg = readl(bank->reg_base +
+ reg->dbclk_div_con);
+ if (cur_div_reg < div_reg)
+ writel(div_reg, bank->reg_base +
+ reg->dbclk_div_con);
+ rockchip_gpio_writel_bit(bank, offset, 1,
+ reg->dbclk_div_en);
+ }
+
+ rockchip_gpio_writel_bit(bank, offset, 1, reg->debounce);
+ } else {
+ if (div_debounce_support)
+ rockchip_gpio_writel_bit(bank, offset, 0,
+ reg->dbclk_div_en);
+
+ rockchip_gpio_writel_bit(bank, offset, 0, reg->debounce);
+ }
+
+ raw_spin_unlock_irqrestore(&bank->slock, flags);
+
+ /* Enable or disable dbclk at last */
+ if (div_debounce_support) {
+ if (debounce)
+ clk_prepare_enable(bank->db_clk);
+ else
+ clk_disable_unprepare(bank->db_clk);
+ }
+
+ return 0;
+}
+
+static int rockchip_gpio_direction_input(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ return rockchip_gpio_set_direction(gc, offset, true);
+}
+
+static int rockchip_gpio_direction_output(struct gpio_chip *gc,
+ unsigned int offset, int value)
+{
+ rockchip_gpio_set(gc, offset, value);
+
+ return rockchip_gpio_set_direction(gc, offset, false);
+}
+
+/*
+ * gpiolib set_config callback function. The setting of the pin
+ * mux function as 'gpio output' will be handled by the pinctrl subsystem
+ * interface.
+ */
+static int rockchip_gpio_set_config(struct gpio_chip *gc, unsigned int offset,
+ unsigned long config)
+{
+ enum pin_config_param param = pinconf_to_config_param(config);
+
+ switch (param) {
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ rockchip_gpio_set_debounce(gc, offset, true);
+ /*
+ * Rockchip's gpio could only support up to one period
+ * of the debounce clock(pclk), which is far away from
+ * satisftying the requirement, as pclk is usually near
+ * 100MHz shared by all peripherals. So the fact is it
+ * has crippled debounce capability could only be useful
+ * to prevent any spurious glitches from waking up the system
+ * if the gpio is conguired as wakeup interrupt source. Let's
+ * still return -ENOTSUPP as before, to make sure the caller
+ * of gpiod_set_debounce won't change its behaviour.
+ */
+ return -ENOTSUPP;
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+/*
+ * gpiod_to_irq() callback function. Creates a mapping between a GPIO pin
+ * and a virtual IRQ, if not already present.
+ */
+static int rockchip_gpio_to_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ struct rockchip_pin_bank *bank = gpiochip_get_data(gc);
+ unsigned int virq;
+
+ if (!bank->domain)
+ return -ENXIO;
+
+ virq = irq_create_mapping(bank->domain, offset);
+
+ return (virq) ? : -ENXIO;
+}
+
+static const struct gpio_chip rockchip_gpiolib_chip = {
+ .request = gpiochip_generic_request,
+ .free = gpiochip_generic_free,
+ .set = rockchip_gpio_set,
+ .get = rockchip_gpio_get,
+ .get_direction = rockchip_gpio_get_direction,
+ .direction_input = rockchip_gpio_direction_input,
+ .direction_output = rockchip_gpio_direction_output,
+ .set_config = rockchip_gpio_set_config,
+ .to_irq = rockchip_gpio_to_irq,
+ .owner = THIS_MODULE,
+};
+
+static void rockchip_irq_demux(struct irq_desc *desc)
+{
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct rockchip_pin_bank *bank = irq_desc_get_handler_data(desc);
+ unsigned long pending;
+ unsigned int irq;
+
+ dev_dbg(bank->dev, "got irq for bank %s\n", bank->name);
+
+ chained_irq_enter(chip, desc);
+
+ pending = readl_relaxed(bank->reg_base + bank->gpio_regs->int_status);
+ for_each_set_bit(irq, &pending, 32) {
+ dev_dbg(bank->dev, "handling irq %d\n", irq);
+
+ /*
+ * Triggering IRQ on both rising and falling edge
+ * needs manual intervention.
+ */
+ if (bank->toggle_edge_mode & BIT(irq)) {
+ u32 data, data_old, polarity;
+ unsigned long flags;
+
+ data = readl_relaxed(bank->reg_base +
+ bank->gpio_regs->ext_port);
+ do {
+ raw_spin_lock_irqsave(&bank->slock, flags);
+
+ polarity = readl_relaxed(bank->reg_base +
+ bank->gpio_regs->int_polarity);
+ if (data & BIT(irq))
+ polarity &= ~BIT(irq);
+ else
+ polarity |= BIT(irq);
+ writel(polarity,
+ bank->reg_base +
+ bank->gpio_regs->int_polarity);
+
+ raw_spin_unlock_irqrestore(&bank->slock, flags);
+
+ data_old = data;
+ data = readl_relaxed(bank->reg_base +
+ bank->gpio_regs->ext_port);
+ } while ((data & BIT(irq)) != (data_old & BIT(irq)));
+ }
+
+ generic_handle_domain_irq(bank->domain, irq);
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static int rockchip_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct rockchip_pin_bank *bank = gc->private;
+ u32 mask = BIT(d->hwirq);
+ u32 polarity;
+ u32 level;
+ u32 data;
+ unsigned long flags;
+ int ret = 0;
+
+ raw_spin_lock_irqsave(&bank->slock, flags);
+
+ rockchip_gpio_writel_bit(bank, d->hwirq, 0,
+ bank->gpio_regs->port_ddr);
+
+ raw_spin_unlock_irqrestore(&bank->slock, flags);
+
+ if (type & IRQ_TYPE_EDGE_BOTH)
+ irq_set_handler_locked(d, handle_edge_irq);
+ else
+ irq_set_handler_locked(d, handle_level_irq);
+
+ raw_spin_lock_irqsave(&bank->slock, flags);
+
+ level = rockchip_gpio_readl(bank, bank->gpio_regs->int_type);
+ polarity = rockchip_gpio_readl(bank, bank->gpio_regs->int_polarity);
+
+ if (type == IRQ_TYPE_EDGE_BOTH) {
+ if (bank->gpio_type == GPIO_TYPE_V2) {
+ rockchip_gpio_writel_bit(bank, d->hwirq, 1,
+ bank->gpio_regs->int_bothedge);
+ goto out;
+ } else {
+ bank->toggle_edge_mode |= mask;
+ level &= ~mask;
+
+ /*
+ * Determine gpio state. If 1 next interrupt should be
+ * low otherwise high.
+ */
+ data = readl(bank->reg_base + bank->gpio_regs->ext_port);
+ if (data & mask)
+ polarity &= ~mask;
+ else
+ polarity |= mask;
+ }
+ } else {
+ if (bank->gpio_type == GPIO_TYPE_V2) {
+ rockchip_gpio_writel_bit(bank, d->hwirq, 0,
+ bank->gpio_regs->int_bothedge);
+ } else {
+ bank->toggle_edge_mode &= ~mask;
+ }
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ level |= mask;
+ polarity |= mask;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ level |= mask;
+ polarity &= ~mask;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ level &= ~mask;
+ polarity |= mask;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ level &= ~mask;
+ polarity &= ~mask;
+ break;
+ default:
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+
+ rockchip_gpio_writel(bank, level, bank->gpio_regs->int_type);
+ rockchip_gpio_writel(bank, polarity, bank->gpio_regs->int_polarity);
+out:
+ raw_spin_unlock_irqrestore(&bank->slock, flags);
+
+ return ret;
+}
+
+static int rockchip_irq_reqres(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct rockchip_pin_bank *bank = gc->private;
+
+ return gpiochip_reqres_irq(&bank->gpio_chip, d->hwirq);
+}
+
+static void rockchip_irq_relres(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct rockchip_pin_bank *bank = gc->private;
+
+ gpiochip_relres_irq(&bank->gpio_chip, d->hwirq);
+}
+
+static void rockchip_irq_suspend(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct rockchip_pin_bank *bank = gc->private;
+
+ bank->saved_masks = irq_reg_readl(gc, bank->gpio_regs->int_mask);
+ irq_reg_writel(gc, ~gc->wake_active, bank->gpio_regs->int_mask);
+}
+
+static void rockchip_irq_resume(struct irq_data *d)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct rockchip_pin_bank *bank = gc->private;
+
+ irq_reg_writel(gc, bank->saved_masks, bank->gpio_regs->int_mask);
+}
+
+static void rockchip_irq_enable(struct irq_data *d)
+{
+ irq_gc_mask_clr_bit(d);
+}
+
+static void rockchip_irq_disable(struct irq_data *d)
+{
+ irq_gc_mask_set_bit(d);
+}
+
+static int rockchip_interrupts_register(struct rockchip_pin_bank *bank)
+{
+ unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
+ struct irq_chip_generic *gc;
+ int ret;
+
+ bank->domain = irq_domain_add_linear(bank->of_node, 32,
+ &irq_generic_chip_ops, NULL);
+ if (!bank->domain) {
+ dev_warn(bank->dev, "could not init irq domain for bank %s\n",
+ bank->name);
+ return -EINVAL;
+ }
+
+ ret = irq_alloc_domain_generic_chips(bank->domain, 32, 1,
+ "rockchip_gpio_irq",
+ handle_level_irq,
+ clr, 0, 0);
+ if (ret) {
+ dev_err(bank->dev, "could not alloc generic chips for bank %s\n",
+ bank->name);
+ irq_domain_remove(bank->domain);
+ return -EINVAL;
+ }
+
+ gc = irq_get_domain_generic_chip(bank->domain, 0);
+ if (bank->gpio_type == GPIO_TYPE_V2) {
+ gc->reg_writel = gpio_writel_v2;
+ gc->reg_readl = gpio_readl_v2;
+ }
+
+ gc->reg_base = bank->reg_base;
+ gc->private = bank;
+ gc->chip_types[0].regs.mask = bank->gpio_regs->int_mask;
+ gc->chip_types[0].regs.ack = bank->gpio_regs->port_eoi;
+ gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
+ gc->chip_types[0].chip.irq_mask = irq_gc_mask_set_bit;
+ gc->chip_types[0].chip.irq_unmask = irq_gc_mask_clr_bit;
+ gc->chip_types[0].chip.irq_enable = rockchip_irq_enable;
+ gc->chip_types[0].chip.irq_disable = rockchip_irq_disable;
+ gc->chip_types[0].chip.irq_set_wake = irq_gc_set_wake;
+ gc->chip_types[0].chip.irq_suspend = rockchip_irq_suspend;
+ gc->chip_types[0].chip.irq_resume = rockchip_irq_resume;
+ gc->chip_types[0].chip.irq_set_type = rockchip_irq_set_type;
+ gc->chip_types[0].chip.irq_request_resources = rockchip_irq_reqres;
+ gc->chip_types[0].chip.irq_release_resources = rockchip_irq_relres;
+ gc->wake_enabled = IRQ_MSK(bank->nr_pins);
+
+ /*
+ * Linux assumes that all interrupts start out disabled/masked.
+ * Our driver only uses the concept of masked and always keeps
+ * things enabled, so for us that's all masked and all enabled.
+ */
+ rockchip_gpio_writel(bank, 0xffffffff, bank->gpio_regs->int_mask);
+ rockchip_gpio_writel(bank, 0xffffffff, bank->gpio_regs->port_eoi);
+ rockchip_gpio_writel(bank, 0xffffffff, bank->gpio_regs->int_en);
+ gc->mask_cache = 0xffffffff;
+
+ irq_set_chained_handler_and_data(bank->irq,
+ rockchip_irq_demux, bank);
+
+ return 0;
+}
+
+static int rockchip_gpiolib_register(struct rockchip_pin_bank *bank)
+{
+ struct gpio_chip *gc;
+ int ret;
+
+ bank->gpio_chip = rockchip_gpiolib_chip;
+
+ gc = &bank->gpio_chip;
+ gc->base = bank->pin_base;
+ gc->ngpio = bank->nr_pins;
+ gc->label = bank->name;
+ gc->parent = bank->dev;
+
+ ret = gpiochip_add_data(gc, bank);
+ if (ret) {
+ dev_err(bank->dev, "failed to add gpiochip %s, %d\n",
+ gc->label, ret);
+ return ret;
+ }
+
+ /*
+ * For DeviceTree-supported systems, the gpio core checks the
+ * pinctrl's device node for the "gpio-ranges" property.
+ * If it is present, it takes care of adding the pin ranges
+ * for the driver. In this case the driver can skip ahead.
+ *
+ * In order to remain compatible with older, existing DeviceTree
+ * files which don't set the "gpio-ranges" property or systems that
+ * utilize ACPI the driver has to call gpiochip_add_pin_range().
+ */
+ if (!of_property_read_bool(bank->of_node, "gpio-ranges")) {
+ struct device_node *pctlnp = of_get_parent(bank->of_node);
+ struct pinctrl_dev *pctldev = NULL;
+
+ if (!pctlnp)
+ return -ENODATA;
+
+ pctldev = of_pinctrl_get(pctlnp);
+ of_node_put(pctlnp);
+ if (!pctldev)
+ return -ENODEV;
+
+ ret = gpiochip_add_pin_range(gc, dev_name(pctldev->dev), 0,
+ gc->base, gc->ngpio);
+ if (ret) {
+ dev_err(bank->dev, "Failed to add pin range\n");
+ goto fail;
+ }
+ }
+
+ ret = rockchip_interrupts_register(bank);
+ if (ret) {
+ dev_err(bank->dev, "failed to register interrupt, %d\n", ret);
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ gpiochip_remove(&bank->gpio_chip);
+
+ return ret;
+}
+
+static int rockchip_get_bank_data(struct rockchip_pin_bank *bank)
+{
+ struct resource res;
+ int id = 0;
+
+ 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);
+ 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;
+
+ bank->clk = of_clk_get(bank->of_node, 0);
+ if (IS_ERR(bank->clk))
+ return PTR_ERR(bank->clk);
+
+ clk_prepare_enable(bank->clk);
+ id = readl(bank->reg_base + gpio_regs_v2.version_id);
+
+ /* If not gpio v2, that is default to v1. */
+ if (id == GPIO_TYPE_V2 || id == GPIO_TYPE_V2_1) {
+ bank->gpio_regs = &gpio_regs_v2;
+ bank->gpio_type = GPIO_TYPE_V2;
+ bank->db_clk = of_clk_get(bank->of_node, 1);
+ if (IS_ERR(bank->db_clk)) {
+ dev_err(bank->dev, "cannot find debounce clk\n");
+ clk_disable_unprepare(bank->clk);
+ return -EINVAL;
+ }
+ } else {
+ bank->gpio_regs = &gpio_regs_v1;
+ bank->gpio_type = GPIO_TYPE_V1;
+ }
+
+ return 0;
+}
+
+static struct rockchip_pin_bank *
+rockchip_gpio_find_bank(struct pinctrl_dev *pctldev, int id)
+{
+ struct rockchip_pinctrl *info;
+ struct rockchip_pin_bank *bank;
+ int i, found = 0;
+
+ info = pinctrl_dev_get_drvdata(pctldev);
+ bank = info->ctrl->pin_banks;
+ for (i = 0; i < info->ctrl->nr_banks; i++, bank++) {
+ if (bank->bank_num == id) {
+ found = 1;
+ break;
+ }
+ }
+
+ return found ? bank : NULL;
+}
+
+static int rockchip_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *pctlnp = of_get_parent(np);
+ struct pinctrl_dev *pctldev = NULL;
+ struct rockchip_pin_bank *bank = NULL;
+ struct rockchip_pin_deferred *cfg;
+ static int gpio;
+ int id, ret;
+
+ if (!np || !pctlnp)
+ return -ENODEV;
+
+ pctldev = of_pinctrl_get(pctlnp);
+ if (!pctldev)
+ return -EPROBE_DEFER;
+
+ id = of_alias_get_id(np, "gpio");
+ if (id < 0)
+ id = gpio++;
+
+ bank = rockchip_gpio_find_bank(pctldev, id);
+ if (!bank)
+ return -EINVAL;
+
+ bank->dev = dev;
+ bank->of_node = np;
+
+ raw_spin_lock_init(&bank->slock);
+
+ ret = rockchip_get_bank_data(bank);
+ if (ret)
+ return ret;
+
+ /*
+ * Prevent clashes with a deferred output setting
+ * being added right at this moment.
+ */
+ mutex_lock(&bank->deferred_lock);
+
+ ret = rockchip_gpiolib_register(bank);
+ if (ret) {
+ clk_disable_unprepare(bank->clk);
+ mutex_unlock(&bank->deferred_lock);
+ return ret;
+ }
+
+ while (!list_empty(&bank->deferred_pins)) {
+ cfg = list_first_entry(&bank->deferred_pins,
+ struct rockchip_pin_deferred, head);
+ list_del(&cfg->head);
+
+ switch (cfg->param) {
+ case PIN_CONFIG_OUTPUT:
+ ret = rockchip_gpio_direction_output(&bank->gpio_chip, cfg->pin, cfg->arg);
+ if (ret)
+ dev_warn(dev, "setting output pin %u to %u failed\n", cfg->pin,
+ cfg->arg);
+ break;
+ case PIN_CONFIG_INPUT_ENABLE:
+ ret = rockchip_gpio_direction_input(&bank->gpio_chip, cfg->pin);
+ if (ret)
+ dev_warn(dev, "setting input pin %u failed\n", cfg->pin);
+ break;
+ default:
+ dev_warn(dev, "unknown deferred config param %d\n", cfg->param);
+ break;
+ }
+ kfree(cfg);
+ }
+
+ mutex_unlock(&bank->deferred_lock);
+
+ platform_set_drvdata(pdev, bank);
+ dev_info(dev, "probed %pOF\n", np);
+
+ return 0;
+}
+
+static int rockchip_gpio_remove(struct platform_device *pdev)
+{
+ struct rockchip_pin_bank *bank = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(bank->clk);
+ gpiochip_remove(&bank->gpio_chip);
+
+ return 0;
+}
+
+static const struct of_device_id rockchip_gpio_match[] = {
+ { .compatible = "rockchip,gpio-bank", },
+ { .compatible = "rockchip,rk3188-gpio-bank0" },
+ { },
+};
+
+static struct platform_driver rockchip_gpio_driver = {
+ .probe = rockchip_gpio_probe,
+ .remove = rockchip_gpio_remove,
+ .driver = {
+ .name = "rockchip-gpio",
+ .of_match_table = rockchip_gpio_match,
+ },
+};
+
+static int __init rockchip_gpio_init(void)
+{
+ return platform_driver_register(&rockchip_gpio_driver);
+}
+postcore_initcall(rockchip_gpio_init);
+
+static void __exit rockchip_gpio_exit(void)
+{
+ platform_driver_unregister(&rockchip_gpio_driver);
+}
+module_exit(rockchip_gpio_exit);
+
+MODULE_DESCRIPTION("Rockchip gpio driver");
+MODULE_ALIAS("platform:rockchip-gpio");
+MODULE_LICENSE("GPL v2");
+MODULE_DEVICE_TABLE(of, rockchip_gpio_match);
diff --git a/drivers/gpio/gpio-sa1100.c b/drivers/gpio/gpio-sa1100.c
new file mode 100644
index 0000000000..242dad763a
--- /dev/null
+++ b/drivers/gpio/gpio-sa1100.c
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/arch/arm/mach-sa1100/gpio.c
+ *
+ * Generic SA-1100 GPIO handling
+ */
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/syscore_ops.h>
+#include <soc/sa1100/pwer.h>
+#include <mach/hardware.h>
+#include <mach/irqs.h>
+#include <mach/generic.h>
+
+struct sa1100_gpio_chip {
+ struct gpio_chip chip;
+ void __iomem *membase;
+ int irqbase;
+ u32 irqmask;
+ u32 irqrising;
+ u32 irqfalling;
+ u32 irqwake;
+};
+
+#define sa1100_gpio_chip(x) container_of(x, struct sa1100_gpio_chip, chip)
+
+enum {
+ R_GPLR = 0x00,
+ R_GPDR = 0x04,
+ R_GPSR = 0x08,
+ R_GPCR = 0x0c,
+ R_GRER = 0x10,
+ R_GFER = 0x14,
+ R_GEDR = 0x18,
+ R_GAFR = 0x1c,
+};
+
+static int sa1100_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ return readl_relaxed(sa1100_gpio_chip(chip)->membase + R_GPLR) &
+ BIT(offset);
+}
+
+static void sa1100_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ int reg = value ? R_GPSR : R_GPCR;
+
+ writel_relaxed(BIT(offset), sa1100_gpio_chip(chip)->membase + reg);
+}
+
+static int sa1100_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+ void __iomem *gpdr = sa1100_gpio_chip(chip)->membase + R_GPDR;
+
+ if (readl_relaxed(gpdr) & BIT(offset))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int sa1100_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ void __iomem *gpdr = sa1100_gpio_chip(chip)->membase + R_GPDR;
+ unsigned long flags;
+
+ local_irq_save(flags);
+ writel_relaxed(readl_relaxed(gpdr) & ~BIT(offset), gpdr);
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+static int sa1100_direction_output(struct gpio_chip *chip, unsigned offset, int value)
+{
+ void __iomem *gpdr = sa1100_gpio_chip(chip)->membase + R_GPDR;
+ unsigned long flags;
+
+ local_irq_save(flags);
+ sa1100_gpio_set(chip, offset, value);
+ writel_relaxed(readl_relaxed(gpdr) | BIT(offset), gpdr);
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+static int sa1100_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ return sa1100_gpio_chip(chip)->irqbase + offset;
+}
+
+static struct sa1100_gpio_chip sa1100_gpio_chip = {
+ .chip = {
+ .label = "gpio",
+ .get_direction = sa1100_get_direction,
+ .direction_input = sa1100_direction_input,
+ .direction_output = sa1100_direction_output,
+ .set = sa1100_gpio_set,
+ .get = sa1100_gpio_get,
+ .to_irq = sa1100_to_irq,
+ .base = 0,
+ .ngpio = GPIO_MAX + 1,
+ },
+ .membase = (void *)&GPLR,
+ .irqbase = IRQ_GPIO0,
+};
+
+/*
+ * SA1100 GPIO edge detection for IRQs:
+ * IRQs are generated on Falling-Edge, Rising-Edge, or both.
+ * Use this instead of directly setting GRER/GFER.
+ */
+static void sa1100_update_edge_regs(struct sa1100_gpio_chip *sgc)
+{
+ void *base = sgc->membase;
+ u32 grer, gfer;
+
+ grer = sgc->irqrising & sgc->irqmask;
+ gfer = sgc->irqfalling & sgc->irqmask;
+
+ writel_relaxed(grer, base + R_GRER);
+ writel_relaxed(gfer, base + R_GFER);
+}
+
+static int sa1100_gpio_type(struct irq_data *d, unsigned int type)
+{
+ struct sa1100_gpio_chip *sgc = irq_data_get_irq_chip_data(d);
+ unsigned int mask = BIT(d->hwirq);
+
+ if (type == IRQ_TYPE_PROBE) {
+ if ((sgc->irqrising | sgc->irqfalling) & mask)
+ return 0;
+ type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
+ }
+
+ if (type & IRQ_TYPE_EDGE_RISING)
+ sgc->irqrising |= mask;
+ else
+ sgc->irqrising &= ~mask;
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ sgc->irqfalling |= mask;
+ else
+ sgc->irqfalling &= ~mask;
+
+ sa1100_update_edge_regs(sgc);
+
+ return 0;
+}
+
+/*
+ * GPIO IRQs must be acknowledged.
+ */
+static void sa1100_gpio_ack(struct irq_data *d)
+{
+ struct sa1100_gpio_chip *sgc = irq_data_get_irq_chip_data(d);
+
+ writel_relaxed(BIT(d->hwirq), sgc->membase + R_GEDR);
+}
+
+static void sa1100_gpio_mask(struct irq_data *d)
+{
+ struct sa1100_gpio_chip *sgc = irq_data_get_irq_chip_data(d);
+ unsigned int mask = BIT(d->hwirq);
+
+ sgc->irqmask &= ~mask;
+
+ sa1100_update_edge_regs(sgc);
+}
+
+static void sa1100_gpio_unmask(struct irq_data *d)
+{
+ struct sa1100_gpio_chip *sgc = irq_data_get_irq_chip_data(d);
+ unsigned int mask = BIT(d->hwirq);
+
+ sgc->irqmask |= mask;
+
+ sa1100_update_edge_regs(sgc);
+}
+
+static int sa1100_gpio_wake(struct irq_data *d, unsigned int on)
+{
+ struct sa1100_gpio_chip *sgc = irq_data_get_irq_chip_data(d);
+ int ret = sa11x0_gpio_set_wake(d->hwirq, on);
+ if (!ret) {
+ if (on)
+ sgc->irqwake |= BIT(d->hwirq);
+ else
+ sgc->irqwake &= ~BIT(d->hwirq);
+ }
+ return ret;
+}
+
+/*
+ * This is for GPIO IRQs
+ */
+static struct irq_chip sa1100_gpio_irq_chip = {
+ .name = "GPIO",
+ .irq_ack = sa1100_gpio_ack,
+ .irq_mask = sa1100_gpio_mask,
+ .irq_unmask = sa1100_gpio_unmask,
+ .irq_set_type = sa1100_gpio_type,
+ .irq_set_wake = sa1100_gpio_wake,
+};
+
+static int sa1100_gpio_irqdomain_map(struct irq_domain *d,
+ unsigned int irq, irq_hw_number_t hwirq)
+{
+ struct sa1100_gpio_chip *sgc = d->host_data;
+
+ irq_set_chip_data(irq, sgc);
+ irq_set_chip_and_handler(irq, &sa1100_gpio_irq_chip, handle_edge_irq);
+ irq_set_probe(irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops sa1100_gpio_irqdomain_ops = {
+ .map = sa1100_gpio_irqdomain_map,
+ .xlate = irq_domain_xlate_onetwocell,
+};
+
+static struct irq_domain *sa1100_gpio_irqdomain;
+
+/*
+ * IRQ 0-11 (GPIO) handler. We enter here with the
+ * irq_controller_lock held, and IRQs disabled. Decode the IRQ
+ * and call the handler.
+ */
+static void sa1100_gpio_handler(struct irq_desc *desc)
+{
+ struct sa1100_gpio_chip *sgc = irq_desc_get_handler_data(desc);
+ unsigned int irq, mask;
+ void __iomem *gedr = sgc->membase + R_GEDR;
+
+ mask = readl_relaxed(gedr);
+ do {
+ /*
+ * clear down all currently active IRQ sources.
+ * We will be processing them all.
+ */
+ writel_relaxed(mask, gedr);
+
+ irq = sgc->irqbase;
+ do {
+ if (mask & 1)
+ generic_handle_irq(irq);
+ mask >>= 1;
+ irq++;
+ } while (mask);
+
+ mask = readl_relaxed(gedr);
+ } while (mask);
+}
+
+static int sa1100_gpio_suspend(void)
+{
+ struct sa1100_gpio_chip *sgc = &sa1100_gpio_chip;
+
+ /*
+ * Set the appropriate edges for wakeup.
+ */
+ writel_relaxed(sgc->irqwake & sgc->irqrising, sgc->membase + R_GRER);
+ writel_relaxed(sgc->irqwake & sgc->irqfalling, sgc->membase + R_GFER);
+
+ /*
+ * Clear any pending GPIO interrupts.
+ */
+ writel_relaxed(readl_relaxed(sgc->membase + R_GEDR),
+ sgc->membase + R_GEDR);
+
+ return 0;
+}
+
+static void sa1100_gpio_resume(void)
+{
+ sa1100_update_edge_regs(&sa1100_gpio_chip);
+}
+
+static struct syscore_ops sa1100_gpio_syscore_ops = {
+ .suspend = sa1100_gpio_suspend,
+ .resume = sa1100_gpio_resume,
+};
+
+static int __init sa1100_gpio_init_devicefs(void)
+{
+ register_syscore_ops(&sa1100_gpio_syscore_ops);
+ return 0;
+}
+
+device_initcall(sa1100_gpio_init_devicefs);
+
+static const int sa1100_gpio_irqs[] __initconst = {
+ /* Install handlers for GPIO 0-10 edge detect interrupts */
+ IRQ_GPIO0_SC,
+ IRQ_GPIO1_SC,
+ IRQ_GPIO2_SC,
+ IRQ_GPIO3_SC,
+ IRQ_GPIO4_SC,
+ IRQ_GPIO5_SC,
+ IRQ_GPIO6_SC,
+ IRQ_GPIO7_SC,
+ IRQ_GPIO8_SC,
+ IRQ_GPIO9_SC,
+ IRQ_GPIO10_SC,
+ /* Install handler for GPIO 11-27 edge detect interrupts */
+ IRQ_GPIO11_27,
+};
+
+void __init sa1100_init_gpio(void)
+{
+ struct sa1100_gpio_chip *sgc = &sa1100_gpio_chip;
+ int i;
+
+ /* clear all GPIO edge detects */
+ writel_relaxed(0, sgc->membase + R_GFER);
+ writel_relaxed(0, sgc->membase + R_GRER);
+ writel_relaxed(-1, sgc->membase + R_GEDR);
+
+ gpiochip_add_data(&sa1100_gpio_chip.chip, NULL);
+
+ sa1100_gpio_irqdomain = irq_domain_add_simple(NULL,
+ 28, IRQ_GPIO0,
+ &sa1100_gpio_irqdomain_ops, sgc);
+
+ for (i = 0; i < ARRAY_SIZE(sa1100_gpio_irqs); i++)
+ irq_set_chained_handler_and_data(sa1100_gpio_irqs[i],
+ sa1100_gpio_handler, sgc);
+}
diff --git a/drivers/gpio/gpio-sama5d2-piobu.c b/drivers/gpio/gpio-sama5d2-piobu.c
new file mode 100644
index 0000000000..d89da7300d
--- /dev/null
+++ b/drivers/gpio/gpio-sama5d2-piobu.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SAMA5D2 PIOBU GPIO controller
+ *
+ * Copyright (C) 2018 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Andrei Stefanescu <andrei.stefanescu@microchip.com>
+ *
+ */
+#include <linux/bits.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define PIOBU_NUM 8
+#define PIOBU_REG_SIZE 4
+
+/*
+ * backup mode protection register for tamper detection
+ * normal mode protection register for tamper detection
+ * wakeup signal generation
+ */
+#define PIOBU_BMPR 0x7C
+#define PIOBU_NMPR 0x80
+#define PIOBU_WKPR 0x90
+
+#define PIOBU_BASE 0x18 /* PIOBU offset from SECUMOD base register address. */
+
+#define PIOBU_DET_OFFSET 16
+
+/* In the datasheet this bit is called OUTPUT */
+#define PIOBU_DIRECTION BIT(8)
+#define PIOBU_OUT BIT(8)
+#define PIOBU_IN 0
+
+#define PIOBU_SOD BIT(9)
+#define PIOBU_PDS BIT(10)
+
+#define PIOBU_HIGH BIT(9)
+#define PIOBU_LOW 0
+
+struct sama5d2_piobu {
+ struct gpio_chip chip;
+ struct regmap *regmap;
+};
+
+/*
+ * sama5d2_piobu_setup_pin() - prepares a pin for set_direction call
+ *
+ * Do not consider pin for tamper detection (normal and backup modes)
+ * Do not consider pin as tamper wakeup interrupt source
+ */
+static int sama5d2_piobu_setup_pin(struct gpio_chip *chip, unsigned int pin)
+{
+ int ret;
+ struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu,
+ chip);
+ unsigned int mask = BIT(PIOBU_DET_OFFSET + pin);
+
+ ret = regmap_update_bits(piobu->regmap, PIOBU_BMPR, mask, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(piobu->regmap, PIOBU_NMPR, mask, 0);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(piobu->regmap, PIOBU_WKPR, mask, 0);
+}
+
+/*
+ * sama5d2_piobu_write_value() - writes value & mask at the pin's PIOBU register
+ */
+static int sama5d2_piobu_write_value(struct gpio_chip *chip, unsigned int pin,
+ unsigned int mask, unsigned int value)
+{
+ int reg;
+ struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu,
+ chip);
+
+ reg = PIOBU_BASE + pin * PIOBU_REG_SIZE;
+
+ return regmap_update_bits(piobu->regmap, reg, mask, value);
+}
+
+/*
+ * sama5d2_piobu_read_value() - read the value with masking from the pin's PIOBU
+ * register
+ */
+static int sama5d2_piobu_read_value(struct gpio_chip *chip, unsigned int pin,
+ unsigned int mask)
+{
+ struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu,
+ chip);
+ unsigned int val, reg;
+ int ret;
+
+ reg = PIOBU_BASE + pin * PIOBU_REG_SIZE;
+ ret = regmap_read(piobu->regmap, reg, &val);
+ if (ret < 0)
+ return ret;
+
+ return val & mask;
+}
+
+/*
+ * sama5d2_piobu_get_direction() - gpiochip get_direction
+ */
+static int sama5d2_piobu_get_direction(struct gpio_chip *chip,
+ unsigned int pin)
+{
+ int ret = sama5d2_piobu_read_value(chip, pin, PIOBU_DIRECTION);
+
+ if (ret < 0)
+ return ret;
+
+ return (ret == PIOBU_IN) ? GPIO_LINE_DIRECTION_IN :
+ GPIO_LINE_DIRECTION_OUT;
+}
+
+/*
+ * sama5d2_piobu_direction_input() - gpiochip direction_input
+ */
+static int sama5d2_piobu_direction_input(struct gpio_chip *chip,
+ unsigned int pin)
+{
+ return sama5d2_piobu_write_value(chip, pin, PIOBU_DIRECTION, PIOBU_IN);
+}
+
+/*
+ * sama5d2_piobu_direction_output() - gpiochip direction_output
+ */
+static int sama5d2_piobu_direction_output(struct gpio_chip *chip,
+ unsigned int pin, int value)
+{
+ unsigned int val = PIOBU_OUT;
+
+ if (value)
+ val |= PIOBU_HIGH;
+
+ return sama5d2_piobu_write_value(chip, pin, PIOBU_DIRECTION | PIOBU_SOD,
+ val);
+}
+
+/*
+ * sama5d2_piobu_get() - gpiochip get
+ */
+static int sama5d2_piobu_get(struct gpio_chip *chip, unsigned int pin)
+{
+ /* if pin is input, read value from PDS else read from SOD */
+ int ret = sama5d2_piobu_get_direction(chip, pin);
+
+ if (ret == GPIO_LINE_DIRECTION_IN)
+ ret = sama5d2_piobu_read_value(chip, pin, PIOBU_PDS);
+ else if (ret == GPIO_LINE_DIRECTION_OUT)
+ ret = sama5d2_piobu_read_value(chip, pin, PIOBU_SOD);
+
+ if (ret < 0)
+ return ret;
+
+ return !!ret;
+}
+
+/*
+ * sama5d2_piobu_set() - gpiochip set
+ */
+static void sama5d2_piobu_set(struct gpio_chip *chip, unsigned int pin,
+ int value)
+{
+ if (!value)
+ value = PIOBU_LOW;
+ else
+ value = PIOBU_HIGH;
+
+ sama5d2_piobu_write_value(chip, pin, PIOBU_SOD, value);
+}
+
+static int sama5d2_piobu_probe(struct platform_device *pdev)
+{
+ struct sama5d2_piobu *piobu;
+ int ret, i;
+
+ piobu = devm_kzalloc(&pdev->dev, sizeof(*piobu), GFP_KERNEL);
+ if (!piobu)
+ return -ENOMEM;
+
+ piobu->chip.label = pdev->name;
+ piobu->chip.parent = &pdev->dev;
+ piobu->chip.owner = THIS_MODULE,
+ piobu->chip.get_direction = sama5d2_piobu_get_direction,
+ piobu->chip.direction_input = sama5d2_piobu_direction_input,
+ piobu->chip.direction_output = sama5d2_piobu_direction_output,
+ piobu->chip.get = sama5d2_piobu_get,
+ piobu->chip.set = sama5d2_piobu_set,
+ piobu->chip.base = -1,
+ piobu->chip.ngpio = PIOBU_NUM,
+ piobu->chip.can_sleep = 0,
+
+ piobu->regmap = syscon_node_to_regmap(pdev->dev.of_node);
+ if (IS_ERR(piobu->regmap)) {
+ dev_err(&pdev->dev, "Failed to get syscon regmap %ld\n",
+ PTR_ERR(piobu->regmap));
+ return PTR_ERR(piobu->regmap);
+ }
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &piobu->chip, piobu);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to add gpiochip %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < PIOBU_NUM; ++i) {
+ ret = sama5d2_piobu_setup_pin(&piobu->chip, i);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to setup pin: %d %d\n",
+ i, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct of_device_id sama5d2_piobu_ids[] = {
+ { .compatible = "atmel,sama5d2-secumod" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sama5d2_piobu_ids);
+
+static struct platform_driver sama5d2_piobu_driver = {
+ .driver = {
+ .name = "sama5d2-piobu",
+ .of_match_table = sama5d2_piobu_ids,
+ },
+ .probe = sama5d2_piobu_probe,
+};
+
+module_platform_driver(sama5d2_piobu_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("SAMA5D2 PIOBU controller driver");
+MODULE_AUTHOR("Andrei Stefanescu <andrei.stefanescu@microchip.com>");
diff --git a/drivers/gpio/gpio-sch.c b/drivers/gpio/gpio-sch.c
new file mode 100644
index 0000000000..e48392074e
--- /dev/null
+++ b/drivers/gpio/gpio-sch.c
@@ -0,0 +1,414 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO interface for Intel Poulsbo SCH
+ *
+ * Copyright (c) 2010 CompuLab Ltd
+ * Author: Denis Turischev <denis@compulab.co.il>
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci_ids.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#define GEN 0x00
+#define GIO 0x04
+#define GLV 0x08
+#define GTPE 0x0c
+#define GTNE 0x10
+#define GGPE 0x14
+#define GSMI 0x18
+#define GTS 0x1c
+
+#define CORE_BANK_OFFSET 0x00
+#define RESUME_BANK_OFFSET 0x20
+
+/*
+ * iLB datasheet describes GPE0BLK registers, in particular GPE0E.GPIO bit.
+ * Document Number: 328195-001
+ */
+#define GPE0E_GPIO 14
+
+struct sch_gpio {
+ struct gpio_chip chip;
+ spinlock_t lock;
+ unsigned short iobase;
+ unsigned short resume_base;
+
+ /* GPE handling */
+ u32 gpe;
+ acpi_gpe_handler gpe_handler;
+};
+
+static unsigned int sch_gpio_offset(struct sch_gpio *sch, unsigned int gpio,
+ unsigned int reg)
+{
+ unsigned int base = CORE_BANK_OFFSET;
+
+ if (gpio >= sch->resume_base) {
+ gpio -= sch->resume_base;
+ base = RESUME_BANK_OFFSET;
+ }
+
+ return base + reg + gpio / 8;
+}
+
+static unsigned int sch_gpio_bit(struct sch_gpio *sch, unsigned int gpio)
+{
+ if (gpio >= sch->resume_base)
+ gpio -= sch->resume_base;
+ return gpio % 8;
+}
+
+static int sch_gpio_reg_get(struct sch_gpio *sch, unsigned int gpio, unsigned int reg)
+{
+ unsigned short offset, bit;
+ u8 reg_val;
+
+ offset = sch_gpio_offset(sch, gpio, reg);
+ bit = sch_gpio_bit(sch, gpio);
+
+ reg_val = !!(inb(sch->iobase + offset) & BIT(bit));
+
+ return reg_val;
+}
+
+static void sch_gpio_reg_set(struct sch_gpio *sch, unsigned int gpio, unsigned int reg,
+ int val)
+{
+ unsigned short offset, bit;
+ u8 reg_val;
+
+ offset = sch_gpio_offset(sch, gpio, reg);
+ bit = sch_gpio_bit(sch, gpio);
+
+ reg_val = inb(sch->iobase + offset);
+
+ if (val)
+ outb(reg_val | BIT(bit), sch->iobase + offset);
+ else
+ outb((reg_val & ~BIT(bit)), sch->iobase + offset);
+}
+
+static int sch_gpio_direction_in(struct gpio_chip *gc, unsigned int gpio_num)
+{
+ struct sch_gpio *sch = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sch->lock, flags);
+ sch_gpio_reg_set(sch, gpio_num, GIO, 1);
+ spin_unlock_irqrestore(&sch->lock, flags);
+ return 0;
+}
+
+static int sch_gpio_get(struct gpio_chip *gc, unsigned int gpio_num)
+{
+ struct sch_gpio *sch = gpiochip_get_data(gc);
+
+ return sch_gpio_reg_get(sch, gpio_num, GLV);
+}
+
+static void sch_gpio_set(struct gpio_chip *gc, unsigned int gpio_num, int val)
+{
+ struct sch_gpio *sch = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sch->lock, flags);
+ sch_gpio_reg_set(sch, gpio_num, GLV, val);
+ spin_unlock_irqrestore(&sch->lock, flags);
+}
+
+static int sch_gpio_direction_out(struct gpio_chip *gc, unsigned int gpio_num,
+ int val)
+{
+ struct sch_gpio *sch = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sch->lock, flags);
+ sch_gpio_reg_set(sch, gpio_num, GIO, 0);
+ spin_unlock_irqrestore(&sch->lock, flags);
+
+ /*
+ * according to the datasheet, writing to the level register has no
+ * effect when GPIO is programmed as input.
+ * Actually the level register is read-only when configured as input.
+ * Thus presetting the output level before switching to output is _NOT_ possible.
+ * Hence we set the level after configuring the GPIO as output.
+ * But we cannot prevent a short low pulse if direction is set to high
+ * and an external pull-up is connected.
+ */
+ sch_gpio_set(gc, gpio_num, val);
+ return 0;
+}
+
+static int sch_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio_num)
+{
+ struct sch_gpio *sch = gpiochip_get_data(gc);
+
+ if (sch_gpio_reg_get(sch, gpio_num, GIO))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static const struct gpio_chip sch_gpio_chip = {
+ .label = "sch_gpio",
+ .owner = THIS_MODULE,
+ .direction_input = sch_gpio_direction_in,
+ .get = sch_gpio_get,
+ .direction_output = sch_gpio_direction_out,
+ .set = sch_gpio_set,
+ .get_direction = sch_gpio_get_direction,
+};
+
+static int sch_irq_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct sch_gpio *sch = gpiochip_get_data(gc);
+ irq_hw_number_t gpio_num = irqd_to_hwirq(d);
+ unsigned long flags;
+ int rising, falling;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_RISING:
+ rising = 1;
+ falling = 0;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ rising = 0;
+ falling = 1;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ rising = 1;
+ falling = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&sch->lock, flags);
+
+ sch_gpio_reg_set(sch, gpio_num, GTPE, rising);
+ sch_gpio_reg_set(sch, gpio_num, GTNE, falling);
+
+ irq_set_handler_locked(d, handle_edge_irq);
+
+ spin_unlock_irqrestore(&sch->lock, flags);
+
+ return 0;
+}
+
+static void sch_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct sch_gpio *sch = gpiochip_get_data(gc);
+ irq_hw_number_t gpio_num = irqd_to_hwirq(d);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sch->lock, flags);
+ sch_gpio_reg_set(sch, gpio_num, GTS, 1);
+ spin_unlock_irqrestore(&sch->lock, flags);
+}
+
+static void sch_irq_mask_unmask(struct gpio_chip *gc, irq_hw_number_t gpio_num, int val)
+{
+ struct sch_gpio *sch = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sch->lock, flags);
+ sch_gpio_reg_set(sch, gpio_num, GGPE, val);
+ spin_unlock_irqrestore(&sch->lock, flags);
+}
+
+static void sch_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ irq_hw_number_t gpio_num = irqd_to_hwirq(d);
+
+ sch_irq_mask_unmask(gc, gpio_num, 0);
+ gpiochip_disable_irq(gc, gpio_num);
+}
+
+static void sch_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ irq_hw_number_t gpio_num = irqd_to_hwirq(d);
+
+ gpiochip_enable_irq(gc, gpio_num);
+ sch_irq_mask_unmask(gc, gpio_num, 1);
+}
+
+static const struct irq_chip sch_irqchip = {
+ .name = "sch_gpio",
+ .irq_ack = sch_irq_ack,
+ .irq_mask = sch_irq_mask,
+ .irq_unmask = sch_irq_unmask,
+ .irq_set_type = sch_irq_type,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static u32 sch_gpio_gpe_handler(acpi_handle gpe_device, u32 gpe, void *context)
+{
+ struct sch_gpio *sch = context;
+ struct gpio_chip *gc = &sch->chip;
+ unsigned long core_status, resume_status;
+ unsigned long pending;
+ unsigned long flags;
+ int offset;
+ u32 ret;
+
+ spin_lock_irqsave(&sch->lock, flags);
+
+ core_status = inl(sch->iobase + CORE_BANK_OFFSET + GTS);
+ resume_status = inl(sch->iobase + RESUME_BANK_OFFSET + GTS);
+
+ spin_unlock_irqrestore(&sch->lock, flags);
+
+ pending = (resume_status << sch->resume_base) | core_status;
+ for_each_set_bit(offset, &pending, sch->chip.ngpio)
+ generic_handle_domain_irq(gc->irq.domain, offset);
+
+ /* Set returning value depending on whether we handled an interrupt */
+ ret = pending ? ACPI_INTERRUPT_HANDLED : ACPI_INTERRUPT_NOT_HANDLED;
+
+ /* Acknowledge GPE to ACPICA */
+ ret |= ACPI_REENABLE_GPE;
+
+ return ret;
+}
+
+static void sch_gpio_remove_gpe_handler(void *data)
+{
+ struct sch_gpio *sch = data;
+
+ acpi_disable_gpe(NULL, sch->gpe);
+ acpi_remove_gpe_handler(NULL, sch->gpe, sch->gpe_handler);
+}
+
+static int sch_gpio_install_gpe_handler(struct sch_gpio *sch)
+{
+ struct device *dev = sch->chip.parent;
+ acpi_status status;
+
+ status = acpi_install_gpe_handler(NULL, sch->gpe, ACPI_GPE_LEVEL_TRIGGERED,
+ sch->gpe_handler, sch);
+ if (ACPI_FAILURE(status)) {
+ dev_err(dev, "Failed to install GPE handler for %u: %s\n",
+ sch->gpe, acpi_format_exception(status));
+ return -ENODEV;
+ }
+
+ status = acpi_enable_gpe(NULL, sch->gpe);
+ if (ACPI_FAILURE(status)) {
+ dev_err(dev, "Failed to enable GPE handler for %u: %s\n",
+ sch->gpe, acpi_format_exception(status));
+ acpi_remove_gpe_handler(NULL, sch->gpe, sch->gpe_handler);
+ return -ENODEV;
+ }
+
+ return devm_add_action_or_reset(dev, sch_gpio_remove_gpe_handler, sch);
+}
+
+static int sch_gpio_probe(struct platform_device *pdev)
+{
+ struct gpio_irq_chip *girq;
+ struct sch_gpio *sch;
+ struct resource *res;
+ int ret;
+
+ sch = devm_kzalloc(&pdev->dev, sizeof(*sch), GFP_KERNEL);
+ if (!sch)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!res)
+ return -EBUSY;
+
+ if (!devm_request_region(&pdev->dev, res->start, resource_size(res),
+ pdev->name))
+ return -EBUSY;
+
+ spin_lock_init(&sch->lock);
+ sch->iobase = res->start;
+ sch->chip = sch_gpio_chip;
+ sch->chip.label = dev_name(&pdev->dev);
+ sch->chip.parent = &pdev->dev;
+
+ switch (pdev->id) {
+ case PCI_DEVICE_ID_INTEL_SCH_LPC:
+ sch->resume_base = 10;
+ sch->chip.ngpio = 14;
+
+ /*
+ * GPIO[6:0] enabled by default
+ * GPIO7 is configured by the CMC as SLPIOVR
+ * Enable GPIO[9:8] core powered gpios explicitly
+ */
+ sch_gpio_reg_set(sch, 8, GEN, 1);
+ sch_gpio_reg_set(sch, 9, GEN, 1);
+ /*
+ * SUS_GPIO[2:0] enabled by default
+ * Enable SUS_GPIO3 resume powered gpio explicitly
+ */
+ sch_gpio_reg_set(sch, 13, GEN, 1);
+ break;
+
+ case PCI_DEVICE_ID_INTEL_ITC_LPC:
+ sch->resume_base = 5;
+ sch->chip.ngpio = 14;
+ break;
+
+ case PCI_DEVICE_ID_INTEL_CENTERTON_ILB:
+ sch->resume_base = 21;
+ sch->chip.ngpio = 30;
+ break;
+
+ case PCI_DEVICE_ID_INTEL_QUARK_X1000_ILB:
+ sch->resume_base = 2;
+ sch->chip.ngpio = 8;
+ break;
+
+ default:
+ return -ENODEV;
+ }
+
+ girq = &sch->chip.irq;
+ gpio_irq_chip_set_chip(girq, &sch_irqchip);
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->parent_handler = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+
+ /* GPE setup is optional */
+ sch->gpe = GPE0E_GPIO;
+ sch->gpe_handler = sch_gpio_gpe_handler;
+
+ ret = sch_gpio_install_gpe_handler(sch);
+ if (ret)
+ dev_warn(&pdev->dev, "Can't setup GPE, no IRQ support\n");
+
+ return devm_gpiochip_add_data(&pdev->dev, &sch->chip, sch);
+}
+
+static struct platform_driver sch_gpio_driver = {
+ .driver = {
+ .name = "sch_gpio",
+ },
+ .probe = sch_gpio_probe,
+};
+
+module_platform_driver(sch_gpio_driver);
+
+MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>");
+MODULE_DESCRIPTION("GPIO interface for Intel Poulsbo SCH");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sch_gpio");
diff --git a/drivers/gpio/gpio-sch311x.c b/drivers/gpio/gpio-sch311x.c
new file mode 100644
index 0000000000..ba4fccf3cc
--- /dev/null
+++ b/drivers/gpio/gpio-sch311x.c
@@ -0,0 +1,449 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * GPIO driver for the SMSC SCH311x Super-I/O chips
+ *
+ * Copyright (C) 2013 Bruno Randolf <br1@einfach.org>
+ *
+ * SuperIO functions and chip detection:
+ * (c) Copyright 2008 Wim Van Sebroeck <wim@iguana.be>.
+ */
+
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/driver.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+
+#define DRV_NAME "gpio-sch311x"
+
+#define SCH311X_GPIO_CONF_DIR BIT(0)
+#define SCH311X_GPIO_CONF_INVERT BIT(1)
+#define SCH311X_GPIO_CONF_OPEN_DRAIN BIT(7)
+
+#define SIO_CONFIG_KEY_ENTER 0x55
+#define SIO_CONFIG_KEY_EXIT 0xaa
+
+#define GP1 0x4b
+
+static int sch311x_ioports[] = { 0x2e, 0x4e, 0x162e, 0x164e };
+
+static struct platform_device *sch311x_gpio_pdev;
+
+struct sch311x_pdev_data { /* platform device data */
+ unsigned short runtime_reg; /* runtime register base address */
+};
+
+struct sch311x_gpio_block { /* one GPIO block runtime data */
+ struct gpio_chip chip;
+ unsigned short data_reg; /* from definition below */
+ unsigned short *config_regs; /* pointer to definition below */
+ unsigned short runtime_reg; /* runtime register */
+ spinlock_t lock; /* lock for this GPIO block */
+};
+
+struct sch311x_gpio_priv { /* driver private data */
+ struct sch311x_gpio_block blocks[6];
+};
+
+struct sch311x_gpio_block_def { /* register address definitions */
+ unsigned short data_reg;
+ unsigned short config_regs[8];
+ unsigned short base;
+};
+
+/* Note: some GPIOs are not available, these are marked with 0x00 */
+
+static struct sch311x_gpio_block_def sch311x_gpio_blocks[] = {
+ {
+ .data_reg = 0x4b, /* GP1 */
+ .config_regs = {0x23, 0x24, 0x25, 0x26, 0x27, 0x29, 0x2a, 0x2b},
+ .base = 10,
+ },
+ {
+ .data_reg = 0x4c, /* GP2 */
+ .config_regs = {0x00, 0x2c, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x32},
+ .base = 20,
+ },
+ {
+ .data_reg = 0x4d, /* GP3 */
+ .config_regs = {0x33, 0x34, 0x35, 0x36, 0x37, 0x00, 0x39, 0x3a},
+ .base = 30,
+ },
+ {
+ .data_reg = 0x4e, /* GP4 */
+ .config_regs = {0x3b, 0x00, 0x3d, 0x00, 0x6e, 0x6f, 0x72, 0x73},
+ .base = 40,
+ },
+ {
+ .data_reg = 0x4f, /* GP5 */
+ .config_regs = {0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46},
+ .base = 50,
+ },
+ {
+ .data_reg = 0x50, /* GP6 */
+ .config_regs = {0x47, 0x48, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59},
+ .base = 60,
+ },
+};
+
+/*
+ * Super-IO functions
+ */
+
+static inline int sch311x_sio_enter(int sio_config_port)
+{
+ /* Don't step on other drivers' I/O space by accident. */
+ if (!request_muxed_region(sio_config_port, 2, DRV_NAME)) {
+ pr_err(DRV_NAME "I/O address 0x%04x already in use\n",
+ sio_config_port);
+ return -EBUSY;
+ }
+
+ outb(SIO_CONFIG_KEY_ENTER, sio_config_port);
+ return 0;
+}
+
+static inline void sch311x_sio_exit(int sio_config_port)
+{
+ outb(SIO_CONFIG_KEY_EXIT, sio_config_port);
+ release_region(sio_config_port, 2);
+}
+
+static inline int sch311x_sio_inb(int sio_config_port, int reg)
+{
+ outb(reg, sio_config_port);
+ return inb(sio_config_port + 1);
+}
+
+static inline void sch311x_sio_outb(int sio_config_port, int reg, int val)
+{
+ outb(reg, sio_config_port);
+ outb(val, sio_config_port + 1);
+}
+
+
+/*
+ * GPIO functions
+ */
+
+static int sch311x_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct sch311x_gpio_block *block = gpiochip_get_data(chip);
+
+ if (block->config_regs[offset] == 0) /* GPIO is not available */
+ return -ENODEV;
+
+ if (!request_region(block->runtime_reg + block->config_regs[offset],
+ 1, DRV_NAME)) {
+ dev_err(chip->parent, "Failed to request region 0x%04x.\n",
+ block->runtime_reg + block->config_regs[offset]);
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static void sch311x_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+ struct sch311x_gpio_block *block = gpiochip_get_data(chip);
+
+ if (block->config_regs[offset] == 0) /* GPIO is not available */
+ return;
+
+ release_region(block->runtime_reg + block->config_regs[offset], 1);
+}
+
+static int sch311x_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct sch311x_gpio_block *block = gpiochip_get_data(chip);
+ u8 data;
+
+ spin_lock(&block->lock);
+ data = inb(block->runtime_reg + block->data_reg);
+ spin_unlock(&block->lock);
+
+ return !!(data & BIT(offset));
+}
+
+static void __sch311x_gpio_set(struct sch311x_gpio_block *block,
+ unsigned offset, int value)
+{
+ u8 data = inb(block->runtime_reg + block->data_reg);
+ if (value)
+ data |= BIT(offset);
+ else
+ data &= ~BIT(offset);
+ outb(data, block->runtime_reg + block->data_reg);
+}
+
+static void sch311x_gpio_set(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct sch311x_gpio_block *block = gpiochip_get_data(chip);
+
+ spin_lock(&block->lock);
+ __sch311x_gpio_set(block, offset, value);
+ spin_unlock(&block->lock);
+}
+
+static int sch311x_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct sch311x_gpio_block *block = gpiochip_get_data(chip);
+ u8 data;
+
+ spin_lock(&block->lock);
+ data = inb(block->runtime_reg + block->config_regs[offset]);
+ data |= SCH311X_GPIO_CONF_DIR;
+ outb(data, block->runtime_reg + block->config_regs[offset]);
+ spin_unlock(&block->lock);
+
+ return 0;
+}
+
+static int sch311x_gpio_direction_out(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct sch311x_gpio_block *block = gpiochip_get_data(chip);
+ u8 data;
+
+ spin_lock(&block->lock);
+
+ data = inb(block->runtime_reg + block->config_regs[offset]);
+ data &= ~SCH311X_GPIO_CONF_DIR;
+ outb(data, block->runtime_reg + block->config_regs[offset]);
+ __sch311x_gpio_set(block, offset, value);
+
+ spin_unlock(&block->lock);
+ return 0;
+}
+
+static int sch311x_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+ struct sch311x_gpio_block *block = gpiochip_get_data(chip);
+ u8 data;
+
+ spin_lock(&block->lock);
+ data = inb(block->runtime_reg + block->config_regs[offset]);
+ spin_unlock(&block->lock);
+
+ if (data & SCH311X_GPIO_CONF_DIR)
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int sch311x_gpio_set_config(struct gpio_chip *chip, unsigned offset,
+ unsigned long config)
+{
+ struct sch311x_gpio_block *block = gpiochip_get_data(chip);
+ enum pin_config_param param = pinconf_to_config_param(config);
+ u8 data;
+
+ switch (param) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ spin_lock(&block->lock);
+ data = inb(block->runtime_reg + block->config_regs[offset]);
+ data |= SCH311X_GPIO_CONF_OPEN_DRAIN;
+ outb(data, block->runtime_reg + block->config_regs[offset]);
+ spin_unlock(&block->lock);
+ return 0;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ spin_lock(&block->lock);
+ data = inb(block->runtime_reg + block->config_regs[offset]);
+ data &= ~SCH311X_GPIO_CONF_OPEN_DRAIN;
+ outb(data, block->runtime_reg + block->config_regs[offset]);
+ spin_unlock(&block->lock);
+ return 0;
+ default:
+ break;
+ }
+ return -ENOTSUPP;
+}
+
+static int sch311x_gpio_probe(struct platform_device *pdev)
+{
+ struct sch311x_pdev_data *pdata = dev_get_platdata(&pdev->dev);
+ struct sch311x_gpio_priv *priv;
+ struct sch311x_gpio_block *block;
+ int err, i;
+
+ /* we can register all GPIO data registers at once */
+ if (!devm_request_region(&pdev->dev, pdata->runtime_reg + GP1, 6,
+ DRV_NAME)) {
+ dev_err(&pdev->dev, "Failed to request region 0x%04x-0x%04x.\n",
+ pdata->runtime_reg + GP1, pdata->runtime_reg + GP1 + 5);
+ return -EBUSY;
+ }
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(priv->blocks); i++) {
+ block = &priv->blocks[i];
+
+ spin_lock_init(&block->lock);
+
+ block->chip.label = DRV_NAME;
+ block->chip.owner = THIS_MODULE;
+ block->chip.request = sch311x_gpio_request;
+ block->chip.free = sch311x_gpio_free;
+ block->chip.direction_input = sch311x_gpio_direction_in;
+ block->chip.direction_output = sch311x_gpio_direction_out;
+ block->chip.get_direction = sch311x_gpio_get_direction;
+ block->chip.set_config = sch311x_gpio_set_config;
+ block->chip.get = sch311x_gpio_get;
+ block->chip.set = sch311x_gpio_set;
+ block->chip.ngpio = 8;
+ block->chip.parent = &pdev->dev;
+ block->chip.base = sch311x_gpio_blocks[i].base;
+ block->config_regs = sch311x_gpio_blocks[i].config_regs;
+ block->data_reg = sch311x_gpio_blocks[i].data_reg;
+ block->runtime_reg = pdata->runtime_reg;
+
+ err = devm_gpiochip_add_data(&pdev->dev, &block->chip, block);
+ if (err < 0) {
+ dev_err(&pdev->dev,
+ "Could not register gpiochip, %d\n", err);
+ return err;
+ }
+ dev_info(&pdev->dev,
+ "SMSC SCH311x GPIO block %d registered.\n", i);
+ }
+
+ return 0;
+}
+
+static struct platform_driver sch311x_gpio_driver = {
+ .driver.name = DRV_NAME,
+ .probe = sch311x_gpio_probe,
+};
+
+
+/*
+ * Init & exit routines
+ */
+
+static int __init sch311x_detect(int sio_config_port, unsigned short *addr)
+{
+ int err = 0, reg;
+ unsigned short base_addr;
+ u8 dev_id;
+
+ err = sch311x_sio_enter(sio_config_port);
+ if (err)
+ return err;
+
+ /* Check device ID. */
+ reg = sch311x_sio_inb(sio_config_port, 0x20);
+ switch (reg) {
+ case 0x7c: /* SCH3112 */
+ dev_id = 2;
+ break;
+ case 0x7d: /* SCH3114 */
+ dev_id = 4;
+ break;
+ case 0x7f: /* SCH3116 */
+ dev_id = 6;
+ break;
+ default:
+ err = -ENODEV;
+ goto exit;
+ }
+
+ /* Select logical device A (runtime registers) */
+ sch311x_sio_outb(sio_config_port, 0x07, 0x0a);
+
+ /* Check if Logical Device Register is currently active */
+ if ((sch311x_sio_inb(sio_config_port, 0x30) & 0x01) == 0)
+ pr_info("Seems that LDN 0x0a is not active...\n");
+
+ /* Get the base address of the runtime registers */
+ base_addr = (sch311x_sio_inb(sio_config_port, 0x60) << 8) |
+ sch311x_sio_inb(sio_config_port, 0x61);
+ if (!base_addr) {
+ pr_err("Base address not set\n");
+ err = -ENODEV;
+ goto exit;
+ }
+ *addr = base_addr;
+
+ pr_info("Found an SMSC SCH311%d chip at 0x%04x\n", dev_id, base_addr);
+
+exit:
+ sch311x_sio_exit(sio_config_port);
+ return err;
+}
+
+static int __init sch311x_gpio_pdev_add(const unsigned short addr)
+{
+ struct sch311x_pdev_data pdata;
+ int err;
+
+ pdata.runtime_reg = addr;
+
+ sch311x_gpio_pdev = platform_device_alloc(DRV_NAME, -1);
+ if (!sch311x_gpio_pdev)
+ return -ENOMEM;
+
+ err = platform_device_add_data(sch311x_gpio_pdev,
+ &pdata, sizeof(pdata));
+ if (err) {
+ pr_err(DRV_NAME "Platform data allocation failed\n");
+ goto err;
+ }
+
+ err = platform_device_add(sch311x_gpio_pdev);
+ if (err) {
+ pr_err(DRV_NAME "Device addition failed\n");
+ goto err;
+ }
+ return 0;
+
+err:
+ platform_device_put(sch311x_gpio_pdev);
+ return err;
+}
+
+static int __init sch311x_gpio_init(void)
+{
+ int err, i;
+ unsigned short addr = 0;
+
+ for (i = 0; i < ARRAY_SIZE(sch311x_ioports); i++)
+ if (sch311x_detect(sch311x_ioports[i], &addr) == 0)
+ break;
+
+ if (!addr)
+ return -ENODEV;
+
+ err = platform_driver_register(&sch311x_gpio_driver);
+ if (err)
+ return err;
+
+ err = sch311x_gpio_pdev_add(addr);
+ if (err)
+ goto unreg_platform_driver;
+
+ return 0;
+
+unreg_platform_driver:
+ platform_driver_unregister(&sch311x_gpio_driver);
+ return err;
+}
+
+static void __exit sch311x_gpio_exit(void)
+{
+ platform_device_unregister(sch311x_gpio_pdev);
+ platform_driver_unregister(&sch311x_gpio_driver);
+}
+
+module_init(sch311x_gpio_init);
+module_exit(sch311x_gpio_exit);
+
+MODULE_AUTHOR("Bruno Randolf <br1@einfach.org>");
+MODULE_DESCRIPTION("SMSC SCH311x GPIO Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio-sch311x");
diff --git a/drivers/gpio/gpio-sifive.c b/drivers/gpio/gpio-sifive.c
new file mode 100644
index 0000000000..8decd9b5d2
--- /dev/null
+++ b/drivers/gpio/gpio-sifive.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 SiFive
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/regmap.h>
+
+#define SIFIVE_GPIO_INPUT_VAL 0x00
+#define SIFIVE_GPIO_INPUT_EN 0x04
+#define SIFIVE_GPIO_OUTPUT_EN 0x08
+#define SIFIVE_GPIO_OUTPUT_VAL 0x0C
+#define SIFIVE_GPIO_RISE_IE 0x18
+#define SIFIVE_GPIO_RISE_IP 0x1C
+#define SIFIVE_GPIO_FALL_IE 0x20
+#define SIFIVE_GPIO_FALL_IP 0x24
+#define SIFIVE_GPIO_HIGH_IE 0x28
+#define SIFIVE_GPIO_HIGH_IP 0x2C
+#define SIFIVE_GPIO_LOW_IE 0x30
+#define SIFIVE_GPIO_LOW_IP 0x34
+#define SIFIVE_GPIO_OUTPUT_XOR 0x40
+
+#define SIFIVE_GPIO_MAX 32
+
+struct sifive_gpio {
+ void __iomem *base;
+ struct gpio_chip gc;
+ struct regmap *regs;
+ unsigned long irq_state;
+ unsigned int trigger[SIFIVE_GPIO_MAX];
+ unsigned int irq_number[SIFIVE_GPIO_MAX];
+};
+
+static void sifive_gpio_set_ie(struct sifive_gpio *chip, unsigned int offset)
+{
+ unsigned long flags;
+ unsigned int trigger;
+
+ raw_spin_lock_irqsave(&chip->gc.bgpio_lock, flags);
+ trigger = (chip->irq_state & BIT(offset)) ? chip->trigger[offset] : 0;
+ regmap_update_bits(chip->regs, SIFIVE_GPIO_RISE_IE, BIT(offset),
+ (trigger & IRQ_TYPE_EDGE_RISING) ? BIT(offset) : 0);
+ regmap_update_bits(chip->regs, SIFIVE_GPIO_FALL_IE, BIT(offset),
+ (trigger & IRQ_TYPE_EDGE_FALLING) ? BIT(offset) : 0);
+ regmap_update_bits(chip->regs, SIFIVE_GPIO_HIGH_IE, BIT(offset),
+ (trigger & IRQ_TYPE_LEVEL_HIGH) ? BIT(offset) : 0);
+ regmap_update_bits(chip->regs, SIFIVE_GPIO_LOW_IE, BIT(offset),
+ (trigger & IRQ_TYPE_LEVEL_LOW) ? BIT(offset) : 0);
+ raw_spin_unlock_irqrestore(&chip->gc.bgpio_lock, flags);
+}
+
+static int sifive_gpio_irq_set_type(struct irq_data *d, unsigned int trigger)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
+ int offset = irqd_to_hwirq(d);
+
+ if (offset < 0 || offset >= gc->ngpio)
+ return -EINVAL;
+
+ chip->trigger[offset] = trigger;
+ sifive_gpio_set_ie(chip, offset);
+ return 0;
+}
+
+static void sifive_gpio_irq_enable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ int offset = hwirq % SIFIVE_GPIO_MAX;
+ u32 bit = BIT(offset);
+ unsigned long flags;
+
+ gpiochip_enable_irq(gc, hwirq);
+ irq_chip_enable_parent(d);
+
+ /* Switch to input */
+ gc->direction_input(gc, offset);
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ /* Clear any sticky pending interrupts */
+ regmap_write(chip->regs, SIFIVE_GPIO_RISE_IP, bit);
+ regmap_write(chip->regs, SIFIVE_GPIO_FALL_IP, bit);
+ regmap_write(chip->regs, SIFIVE_GPIO_HIGH_IP, bit);
+ regmap_write(chip->regs, SIFIVE_GPIO_LOW_IP, bit);
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+ /* Enable interrupts */
+ assign_bit(offset, &chip->irq_state, 1);
+ sifive_gpio_set_ie(chip, offset);
+}
+
+static void sifive_gpio_irq_disable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ int offset = hwirq % SIFIVE_GPIO_MAX;
+
+ assign_bit(offset, &chip->irq_state, 0);
+ sifive_gpio_set_ie(chip, offset);
+ irq_chip_disable_parent(d);
+ gpiochip_disable_irq(gc, hwirq);
+}
+
+static void sifive_gpio_irq_eoi(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
+ int offset = irqd_to_hwirq(d) % SIFIVE_GPIO_MAX;
+ u32 bit = BIT(offset);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+ /* Clear all pending interrupts */
+ regmap_write(chip->regs, SIFIVE_GPIO_RISE_IP, bit);
+ regmap_write(chip->regs, SIFIVE_GPIO_FALL_IP, bit);
+ regmap_write(chip->regs, SIFIVE_GPIO_HIGH_IP, bit);
+ regmap_write(chip->regs, SIFIVE_GPIO_LOW_IP, bit);
+ raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+ irq_chip_eoi_parent(d);
+}
+
+static int sifive_gpio_irq_set_affinity(struct irq_data *data,
+ const struct cpumask *dest,
+ bool force)
+{
+ if (data->parent_data)
+ return irq_chip_set_affinity_parent(data, dest, force);
+
+ return -EINVAL;
+}
+
+static const struct irq_chip sifive_gpio_irqchip = {
+ .name = "sifive-gpio",
+ .irq_set_type = sifive_gpio_irq_set_type,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_enable = sifive_gpio_irq_enable,
+ .irq_disable = sifive_gpio_irq_disable,
+ .irq_eoi = sifive_gpio_irq_eoi,
+ .irq_set_affinity = sifive_gpio_irq_set_affinity,
+ .irq_set_wake = irq_chip_set_wake_parent,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int sifive_gpio_child_to_parent_hwirq(struct gpio_chip *gc,
+ unsigned int child,
+ unsigned int child_type,
+ unsigned int *parent,
+ unsigned int *parent_type)
+{
+ struct sifive_gpio *chip = gpiochip_get_data(gc);
+ struct irq_data *d = irq_get_irq_data(chip->irq_number[child]);
+
+ *parent_type = IRQ_TYPE_NONE;
+ *parent = irqd_to_hwirq(d);
+
+ return 0;
+}
+
+static const struct regmap_config sifive_gpio_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .fast_io = true,
+ .disable_locking = true,
+};
+
+static int sifive_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct irq_domain *parent;
+ struct gpio_irq_chip *girq;
+ struct sifive_gpio *chip;
+ int ret, ngpio;
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(chip->base)) {
+ dev_err(dev, "failed to allocate device memory\n");
+ return PTR_ERR(chip->base);
+ }
+
+ chip->regs = devm_regmap_init_mmio(dev, chip->base,
+ &sifive_gpio_regmap_config);
+ if (IS_ERR(chip->regs))
+ return PTR_ERR(chip->regs);
+
+ for (ngpio = 0; ngpio < SIFIVE_GPIO_MAX; ngpio++) {
+ ret = platform_get_irq_optional(pdev, ngpio);
+ if (ret < 0)
+ break;
+ chip->irq_number[ngpio] = ret;
+ }
+ if (!ngpio) {
+ dev_err(dev, "no IRQ found\n");
+ return -ENODEV;
+ }
+
+ /*
+ * The check above ensures at least one parent IRQ is valid.
+ * Assume all parent IRQs belong to the same domain.
+ */
+ parent = irq_get_irq_data(chip->irq_number[0])->domain;
+
+ ret = bgpio_init(&chip->gc, dev, 4,
+ chip->base + SIFIVE_GPIO_INPUT_VAL,
+ chip->base + SIFIVE_GPIO_OUTPUT_VAL,
+ NULL,
+ chip->base + SIFIVE_GPIO_OUTPUT_EN,
+ chip->base + SIFIVE_GPIO_INPUT_EN,
+ BGPIOF_READ_OUTPUT_REG_SET);
+ if (ret) {
+ dev_err(dev, "unable to init generic GPIO\n");
+ return ret;
+ }
+
+ /* Disable all GPIO interrupts before enabling parent interrupts */
+ regmap_write(chip->regs, SIFIVE_GPIO_RISE_IE, 0);
+ regmap_write(chip->regs, SIFIVE_GPIO_FALL_IE, 0);
+ regmap_write(chip->regs, SIFIVE_GPIO_HIGH_IE, 0);
+ regmap_write(chip->regs, SIFIVE_GPIO_LOW_IE, 0);
+ chip->irq_state = 0;
+
+ chip->gc.base = -1;
+ chip->gc.ngpio = ngpio;
+ chip->gc.label = dev_name(dev);
+ chip->gc.parent = dev;
+ chip->gc.owner = THIS_MODULE;
+ girq = &chip->gc.irq;
+ gpio_irq_chip_set_chip(girq, &sifive_gpio_irqchip);
+ girq->fwnode = dev_fwnode(dev);
+ girq->parent_domain = parent;
+ girq->child_to_parent_hwirq = sifive_gpio_child_to_parent_hwirq;
+ girq->handler = handle_bad_irq;
+ girq->default_type = IRQ_TYPE_NONE;
+
+ platform_set_drvdata(pdev, chip);
+ return gpiochip_add_data(&chip->gc, chip);
+}
+
+static const struct of_device_id sifive_gpio_match[] = {
+ { .compatible = "sifive,gpio0" },
+ { .compatible = "sifive,fu540-c000-gpio" },
+ { },
+};
+
+static struct platform_driver sifive_gpio_driver = {
+ .probe = sifive_gpio_probe,
+ .driver = {
+ .name = "sifive_gpio",
+ .of_match_table = sifive_gpio_match,
+ },
+};
+module_platform_driver(sifive_gpio_driver)
+
+MODULE_AUTHOR("Yash Shah <yash.shah@sifive.com>");
+MODULE_DESCRIPTION("SiFive GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c
new file mode 100644
index 0000000000..a8e5ac95cf
--- /dev/null
+++ b/drivers/gpio/gpio-sim.c
@@ -0,0 +1,1517 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * GPIO testing driver based on configfs.
+ *
+ * Copyright (C) 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitmap.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/configfs.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/machine.h>
+#include <linux/idr.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irq_sim.h>
+#include <linux/list.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/string_helpers.h>
+#include <linux/sysfs.h>
+
+#include "gpiolib.h"
+
+#define GPIO_SIM_NGPIO_MAX 1024
+#define GPIO_SIM_PROP_MAX 4 /* Max 3 properties + sentinel. */
+#define GPIO_SIM_NUM_ATTRS 3 /* value, pull and sentinel */
+
+static DEFINE_IDA(gpio_sim_ida);
+
+struct gpio_sim_chip {
+ struct gpio_chip gc;
+ unsigned long *direction_map;
+ unsigned long *value_map;
+ unsigned long *pull_map;
+ struct irq_domain *irq_sim;
+ struct mutex lock;
+ const struct attribute_group **attr_groups;
+};
+
+struct gpio_sim_attribute {
+ struct device_attribute dev_attr;
+ unsigned int offset;
+};
+
+static struct gpio_sim_attribute *
+to_gpio_sim_attr(struct device_attribute *dev_attr)
+{
+ return container_of(dev_attr, struct gpio_sim_attribute, dev_attr);
+}
+
+static int gpio_sim_apply_pull(struct gpio_sim_chip *chip,
+ unsigned int offset, int value)
+{
+ int irq, irq_type, ret;
+ struct gpio_desc *desc;
+ struct gpio_chip *gc;
+
+ gc = &chip->gc;
+ desc = &gc->gpiodev->descs[offset];
+
+ guard(mutex)(&chip->lock);
+
+ if (test_bit(FLAG_REQUESTED, &desc->flags) &&
+ !test_bit(FLAG_IS_OUT, &desc->flags)) {
+ if (value == !!test_bit(offset, chip->value_map))
+ goto set_pull;
+
+ /*
+ * This is fine - it just means, nobody is listening
+ * for interrupts on this line, otherwise
+ * irq_create_mapping() would have been called from
+ * the to_irq() callback.
+ */
+ irq = irq_find_mapping(chip->irq_sim, offset);
+ if (!irq)
+ goto set_value;
+
+ irq_type = irq_get_trigger_type(irq);
+
+ if ((value && (irq_type & IRQ_TYPE_EDGE_RISING)) ||
+ (!value && (irq_type & IRQ_TYPE_EDGE_FALLING))) {
+ ret = irq_set_irqchip_state(irq, IRQCHIP_STATE_PENDING,
+ true);
+ if (ret)
+ goto set_pull;
+ }
+ }
+
+set_value:
+ /* Change the value unless we're actively driving the line. */
+ if (!test_bit(FLAG_REQUESTED, &desc->flags) ||
+ !test_bit(FLAG_IS_OUT, &desc->flags))
+ __assign_bit(offset, chip->value_map, value);
+
+set_pull:
+ __assign_bit(offset, chip->pull_map, value);
+ return 0;
+}
+
+static int gpio_sim_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_sim_chip *chip = gpiochip_get_data(gc);
+
+ guard(mutex)(&chip->lock);
+
+ return !!test_bit(offset, chip->value_map);
+}
+
+static void gpio_sim_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ struct gpio_sim_chip *chip = gpiochip_get_data(gc);
+
+ scoped_guard(mutex, &chip->lock)
+ __assign_bit(offset, chip->value_map, value);
+}
+
+static int gpio_sim_get_multiple(struct gpio_chip *gc,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct gpio_sim_chip *chip = gpiochip_get_data(gc);
+
+ scoped_guard(mutex, &chip->lock)
+ bitmap_replace(bits, bits, chip->value_map, mask, gc->ngpio);
+
+ return 0;
+}
+
+static void gpio_sim_set_multiple(struct gpio_chip *gc,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct gpio_sim_chip *chip = gpiochip_get_data(gc);
+
+ scoped_guard(mutex, &chip->lock)
+ bitmap_replace(chip->value_map, chip->value_map, bits, mask,
+ gc->ngpio);
+}
+
+static int gpio_sim_direction_output(struct gpio_chip *gc,
+ unsigned int offset, int value)
+{
+ struct gpio_sim_chip *chip = gpiochip_get_data(gc);
+
+ scoped_guard(mutex, &chip->lock) {
+ __clear_bit(offset, chip->direction_map);
+ __assign_bit(offset, chip->value_map, value);
+ }
+
+ return 0;
+}
+
+static int gpio_sim_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_sim_chip *chip = gpiochip_get_data(gc);
+
+ scoped_guard(mutex, &chip->lock)
+ __set_bit(offset, chip->direction_map);
+
+ return 0;
+}
+
+static int gpio_sim_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_sim_chip *chip = gpiochip_get_data(gc);
+ int direction;
+
+ scoped_guard(mutex, &chip->lock)
+ direction = !!test_bit(offset, chip->direction_map);
+
+ return direction ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
+}
+
+static int gpio_sim_set_config(struct gpio_chip *gc,
+ unsigned int offset, unsigned long config)
+{
+ struct gpio_sim_chip *chip = gpiochip_get_data(gc);
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_BIAS_PULL_UP:
+ return gpio_sim_apply_pull(chip, offset, 1);
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ return gpio_sim_apply_pull(chip, offset, 0);
+ default:
+ break;
+ }
+
+ return -ENOTSUPP;
+}
+
+static int gpio_sim_to_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_sim_chip *chip = gpiochip_get_data(gc);
+
+ return irq_create_mapping(chip->irq_sim, offset);
+}
+
+static void gpio_sim_free(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_sim_chip *chip = gpiochip_get_data(gc);
+
+ scoped_guard(mutex, &chip->lock)
+ __assign_bit(offset, chip->value_map,
+ !!test_bit(offset, chip->pull_map));
+}
+
+static ssize_t gpio_sim_sysfs_val_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gpio_sim_attribute *line_attr = to_gpio_sim_attr(attr);
+ struct gpio_sim_chip *chip = dev_get_drvdata(dev);
+ int val;
+
+ scoped_guard(mutex, &chip->lock)
+ val = !!test_bit(line_attr->offset, chip->value_map);
+
+ return sysfs_emit(buf, "%d\n", val);
+}
+
+static ssize_t gpio_sim_sysfs_val_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ /*
+ * Not assigning this function will result in write() returning -EIO
+ * which is confusing. Return -EPERM explicitly.
+ */
+ return -EPERM;
+}
+
+static const char *const gpio_sim_sysfs_pull_strings[] = {
+ [0] = "pull-down",
+ [1] = "pull-up",
+};
+
+static ssize_t gpio_sim_sysfs_pull_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct gpio_sim_attribute *line_attr = to_gpio_sim_attr(attr);
+ struct gpio_sim_chip *chip = dev_get_drvdata(dev);
+ int pull;
+
+ scoped_guard(mutex, &chip->lock)
+ pull = !!test_bit(line_attr->offset, chip->pull_map);
+
+ return sysfs_emit(buf, "%s\n", gpio_sim_sysfs_pull_strings[pull]);
+}
+
+static ssize_t gpio_sim_sysfs_pull_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct gpio_sim_attribute *line_attr = to_gpio_sim_attr(attr);
+ struct gpio_sim_chip *chip = dev_get_drvdata(dev);
+ int ret, pull;
+
+ pull = sysfs_match_string(gpio_sim_sysfs_pull_strings, buf);
+ if (pull < 0)
+ return pull;
+
+ ret = gpio_sim_apply_pull(chip, line_attr->offset, pull);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static void gpio_sim_mutex_destroy(void *data)
+{
+ struct mutex *lock = data;
+
+ mutex_destroy(lock);
+}
+
+static void gpio_sim_dispose_mappings(void *data)
+{
+ struct gpio_sim_chip *chip = data;
+ unsigned int i;
+
+ for (i = 0; i < chip->gc.ngpio; i++)
+ irq_dispose_mapping(irq_find_mapping(chip->irq_sim, i));
+}
+
+static void gpio_sim_sysfs_remove(void *data)
+{
+ struct gpio_sim_chip *chip = data;
+
+ sysfs_remove_groups(&chip->gc.gpiodev->dev.kobj, chip->attr_groups);
+}
+
+static int gpio_sim_setup_sysfs(struct gpio_sim_chip *chip)
+{
+ struct device_attribute *val_dev_attr, *pull_dev_attr;
+ struct gpio_sim_attribute *val_attr, *pull_attr;
+ unsigned int num_lines = chip->gc.ngpio;
+ struct device *dev = chip->gc.parent;
+ struct attribute_group *attr_group;
+ struct attribute **attrs;
+ int i, ret;
+
+ chip->attr_groups = devm_kcalloc(dev, sizeof(*chip->attr_groups),
+ num_lines + 1, GFP_KERNEL);
+ if (!chip->attr_groups)
+ return -ENOMEM;
+
+ for (i = 0; i < num_lines; i++) {
+ attr_group = devm_kzalloc(dev, sizeof(*attr_group), GFP_KERNEL);
+ attrs = devm_kcalloc(dev, GPIO_SIM_NUM_ATTRS, sizeof(*attrs),
+ GFP_KERNEL);
+ val_attr = devm_kzalloc(dev, sizeof(*val_attr), GFP_KERNEL);
+ pull_attr = devm_kzalloc(dev, sizeof(*pull_attr), GFP_KERNEL);
+ if (!attr_group || !attrs || !val_attr || !pull_attr)
+ return -ENOMEM;
+
+ attr_group->name = devm_kasprintf(dev, GFP_KERNEL,
+ "sim_gpio%u", i);
+ if (!attr_group->name)
+ return -ENOMEM;
+
+ val_attr->offset = pull_attr->offset = i;
+
+ val_dev_attr = &val_attr->dev_attr;
+ pull_dev_attr = &pull_attr->dev_attr;
+
+ sysfs_attr_init(&val_dev_attr->attr);
+ sysfs_attr_init(&pull_dev_attr->attr);
+
+ val_dev_attr->attr.name = "value";
+ pull_dev_attr->attr.name = "pull";
+
+ val_dev_attr->attr.mode = pull_dev_attr->attr.mode = 0644;
+
+ val_dev_attr->show = gpio_sim_sysfs_val_show;
+ val_dev_attr->store = gpio_sim_sysfs_val_store;
+ pull_dev_attr->show = gpio_sim_sysfs_pull_show;
+ pull_dev_attr->store = gpio_sim_sysfs_pull_store;
+
+ attrs[0] = &val_dev_attr->attr;
+ attrs[1] = &pull_dev_attr->attr;
+
+ attr_group->attrs = attrs;
+ chip->attr_groups[i] = attr_group;
+ }
+
+ ret = sysfs_create_groups(&chip->gc.gpiodev->dev.kobj,
+ chip->attr_groups);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, gpio_sim_sysfs_remove, chip);
+}
+
+static int gpio_sim_add_bank(struct fwnode_handle *swnode, struct device *dev)
+{
+ struct gpio_sim_chip *chip;
+ struct gpio_chip *gc;
+ const char *label;
+ u32 num_lines;
+ int ret;
+
+ ret = fwnode_property_read_u32(swnode, "ngpios", &num_lines);
+ if (ret)
+ return ret;
+
+ if (num_lines > GPIO_SIM_NGPIO_MAX)
+ return -ERANGE;
+
+ ret = fwnode_property_read_string(swnode, "gpio-sim,label", &label);
+ if (ret) {
+ label = devm_kasprintf(dev, GFP_KERNEL, "%s-%pfwP",
+ dev_name(dev), swnode);
+ if (!label)
+ return -ENOMEM;
+ }
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->direction_map = devm_bitmap_alloc(dev, num_lines, GFP_KERNEL);
+ if (!chip->direction_map)
+ return -ENOMEM;
+
+ /* Default to input mode. */
+ bitmap_fill(chip->direction_map, num_lines);
+
+ chip->value_map = devm_bitmap_zalloc(dev, num_lines, GFP_KERNEL);
+ if (!chip->value_map)
+ return -ENOMEM;
+
+ chip->pull_map = devm_bitmap_zalloc(dev, num_lines, GFP_KERNEL);
+ if (!chip->pull_map)
+ return -ENOMEM;
+
+ chip->irq_sim = devm_irq_domain_create_sim(dev, swnode, num_lines);
+ if (IS_ERR(chip->irq_sim))
+ return PTR_ERR(chip->irq_sim);
+
+ ret = devm_add_action_or_reset(dev, gpio_sim_dispose_mappings, chip);
+ if (ret)
+ return ret;
+
+ mutex_init(&chip->lock);
+ ret = devm_add_action_or_reset(dev, gpio_sim_mutex_destroy,
+ &chip->lock);
+ if (ret)
+ return ret;
+
+ gc = &chip->gc;
+ gc->base = -1;
+ gc->ngpio = num_lines;
+ gc->label = label;
+ gc->owner = THIS_MODULE;
+ gc->parent = dev;
+ gc->fwnode = swnode;
+ gc->get = gpio_sim_get;
+ gc->set = gpio_sim_set;
+ gc->get_multiple = gpio_sim_get_multiple;
+ gc->set_multiple = gpio_sim_set_multiple;
+ gc->direction_output = gpio_sim_direction_output;
+ gc->direction_input = gpio_sim_direction_input;
+ gc->get_direction = gpio_sim_get_direction;
+ gc->set_config = gpio_sim_set_config;
+ gc->to_irq = gpio_sim_to_irq;
+ gc->free = gpio_sim_free;
+ gc->can_sleep = true;
+
+ ret = devm_gpiochip_add_data(dev, gc, chip);
+ if (ret)
+ return ret;
+
+ /* Used by sysfs and configfs callbacks. */
+ dev_set_drvdata(&gc->gpiodev->dev, chip);
+
+ return gpio_sim_setup_sysfs(chip);
+}
+
+static int gpio_sim_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fwnode_handle *swnode;
+ int ret;
+
+ device_for_each_child_node(dev, swnode) {
+ ret = gpio_sim_add_bank(swnode, dev);
+ if (ret) {
+ fwnode_handle_put(swnode);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct of_device_id gpio_sim_of_match[] = {
+ { .compatible = "gpio-simulator" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, gpio_sim_of_match);
+
+static struct platform_driver gpio_sim_driver = {
+ .driver = {
+ .name = "gpio-sim",
+ .of_match_table = gpio_sim_of_match,
+ },
+ .probe = gpio_sim_probe,
+};
+
+struct gpio_sim_device {
+ struct config_group group;
+
+ /*
+ * If pdev is NULL, the device is 'pending' (waiting for configuration).
+ * Once the pointer is assigned, the device has been created and the
+ * item is 'live'.
+ */
+ struct platform_device *pdev;
+ int id;
+
+ /*
+ * Each configfs filesystem operation is protected with the subsystem
+ * mutex. Each separate attribute is protected with the buffer mutex.
+ * This structure however can be modified by callbacks of different
+ * attributes so we need another lock.
+ *
+ * We use this lock for protecting all data structures owned by this
+ * object too.
+ */
+ struct mutex lock;
+
+ /*
+ * This is used to synchronously wait for the driver's probe to complete
+ * and notify the user-space about any errors.
+ */
+ struct notifier_block bus_notifier;
+ struct completion probe_completion;
+ bool driver_bound;
+
+ struct gpiod_hog *hogs;
+
+ struct list_head bank_list;
+};
+
+/* This is called with dev->lock already taken. */
+static int gpio_sim_bus_notifier_call(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct gpio_sim_device *simdev = container_of(nb,
+ struct gpio_sim_device,
+ bus_notifier);
+ struct device *dev = data;
+ char devname[32];
+
+ snprintf(devname, sizeof(devname), "gpio-sim.%u", simdev->id);
+
+ if (strcmp(dev_name(dev), devname) == 0) {
+ if (action == BUS_NOTIFY_BOUND_DRIVER)
+ simdev->driver_bound = true;
+ else if (action == BUS_NOTIFY_DRIVER_NOT_BOUND)
+ simdev->driver_bound = false;
+ else
+ return NOTIFY_DONE;
+
+ complete(&simdev->probe_completion);
+ return NOTIFY_OK;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct gpio_sim_device *to_gpio_sim_device(struct config_item *item)
+{
+ struct config_group *group = to_config_group(item);
+
+ return container_of(group, struct gpio_sim_device, group);
+}
+
+struct gpio_sim_bank {
+ struct config_group group;
+
+ /*
+ * We could have used the ci_parent field of the config_item but
+ * configfs is stupid and calls the item's release callback after
+ * already having cleared the parent pointer even though the parent
+ * is guaranteed to survive the child...
+ *
+ * So we need to store the pointer to the parent struct here. We can
+ * dereference it anywhere we need with no checks and no locking as
+ * it's guaranteed to survive the children and protected by configfs
+ * locks.
+ *
+ * Same for other structures.
+ */
+ struct gpio_sim_device *parent;
+ struct list_head siblings;
+
+ char *label;
+ unsigned int num_lines;
+
+ struct list_head line_list;
+
+ struct fwnode_handle *swnode;
+};
+
+static struct gpio_sim_bank *to_gpio_sim_bank(struct config_item *item)
+{
+ struct config_group *group = to_config_group(item);
+
+ return container_of(group, struct gpio_sim_bank, group);
+}
+
+static bool gpio_sim_bank_has_label(struct gpio_sim_bank *bank)
+{
+ return bank->label && *bank->label;
+}
+
+static struct gpio_sim_device *
+gpio_sim_bank_get_device(struct gpio_sim_bank *bank)
+{
+ return bank->parent;
+}
+
+struct gpio_sim_hog;
+
+struct gpio_sim_line {
+ struct config_group group;
+
+ struct gpio_sim_bank *parent;
+ struct list_head siblings;
+
+ unsigned int offset;
+ char *name;
+
+ /* There can only be one hog per line. */
+ struct gpio_sim_hog *hog;
+};
+
+static struct gpio_sim_line *to_gpio_sim_line(struct config_item *item)
+{
+ struct config_group *group = to_config_group(item);
+
+ return container_of(group, struct gpio_sim_line, group);
+}
+
+static struct gpio_sim_device *
+gpio_sim_line_get_device(struct gpio_sim_line *line)
+{
+ struct gpio_sim_bank *bank = line->parent;
+
+ return gpio_sim_bank_get_device(bank);
+}
+
+struct gpio_sim_hog {
+ struct config_item item;
+ struct gpio_sim_line *parent;
+
+ char *name;
+ int dir;
+};
+
+static struct gpio_sim_hog *to_gpio_sim_hog(struct config_item *item)
+{
+ return container_of(item, struct gpio_sim_hog, item);
+}
+
+static struct gpio_sim_device *gpio_sim_hog_get_device(struct gpio_sim_hog *hog)
+{
+ struct gpio_sim_line *line = hog->parent;
+
+ return gpio_sim_line_get_device(line);
+}
+
+static bool gpio_sim_device_is_live_unlocked(struct gpio_sim_device *dev)
+{
+ return !!dev->pdev;
+}
+
+static char *gpio_sim_strdup_trimmed(const char *str, size_t count)
+{
+ char *trimmed;
+
+ trimmed = kstrndup(skip_spaces(str), count, GFP_KERNEL);
+ if (!trimmed)
+ return NULL;
+
+ return strim(trimmed);
+}
+
+static ssize_t gpio_sim_device_config_dev_name_show(struct config_item *item,
+ char *page)
+{
+ struct gpio_sim_device *dev = to_gpio_sim_device(item);
+ struct platform_device *pdev;
+
+ guard(mutex)(&dev->lock);
+
+ pdev = dev->pdev;
+ if (pdev)
+ return sprintf(page, "%s\n", dev_name(&pdev->dev));
+
+ return sprintf(page, "gpio-sim.%d\n", dev->id);
+}
+
+CONFIGFS_ATTR_RO(gpio_sim_device_config_, dev_name);
+
+static ssize_t
+gpio_sim_device_config_live_show(struct config_item *item, char *page)
+{
+ struct gpio_sim_device *dev = to_gpio_sim_device(item);
+ bool live;
+
+ scoped_guard(mutex, &dev->lock)
+ live = gpio_sim_device_is_live_unlocked(dev);
+
+ return sprintf(page, "%c\n", live ? '1' : '0');
+}
+
+static unsigned int gpio_sim_get_line_names_size(struct gpio_sim_bank *bank)
+{
+ struct gpio_sim_line *line;
+ unsigned int size = 0;
+
+ list_for_each_entry(line, &bank->line_list, siblings) {
+ if (!line->name || (line->offset >= bank->num_lines))
+ continue;
+
+ size = max(size, line->offset + 1);
+ }
+
+ return size;
+}
+
+static void
+gpio_sim_set_line_names(struct gpio_sim_bank *bank, char **line_names)
+{
+ struct gpio_sim_line *line;
+
+ list_for_each_entry(line, &bank->line_list, siblings) {
+ if (!line->name || (line->offset >= bank->num_lines))
+ continue;
+
+ line_names[line->offset] = line->name;
+ }
+}
+
+static void gpio_sim_remove_hogs(struct gpio_sim_device *dev)
+{
+ struct gpiod_hog *hog;
+
+ if (!dev->hogs)
+ return;
+
+ gpiod_remove_hogs(dev->hogs);
+
+ for (hog = dev->hogs; hog->chip_label; hog++) {
+ kfree(hog->chip_label);
+ kfree(hog->line_name);
+ }
+
+ kfree(dev->hogs);
+ dev->hogs = NULL;
+}
+
+static int gpio_sim_add_hogs(struct gpio_sim_device *dev)
+{
+ unsigned int num_hogs = 0, idx = 0;
+ struct gpio_sim_bank *bank;
+ struct gpio_sim_line *line;
+ struct gpiod_hog *hog;
+
+ list_for_each_entry(bank, &dev->bank_list, siblings) {
+ list_for_each_entry(line, &bank->line_list, siblings) {
+ if (line->offset >= bank->num_lines)
+ continue;
+
+ if (line->hog)
+ num_hogs++;
+ }
+ }
+
+ if (!num_hogs)
+ return 0;
+
+ /* Allocate one more for the sentinel. */
+ dev->hogs = kcalloc(num_hogs + 1, sizeof(*dev->hogs), GFP_KERNEL);
+ if (!dev->hogs)
+ return -ENOMEM;
+
+ list_for_each_entry(bank, &dev->bank_list, siblings) {
+ list_for_each_entry(line, &bank->line_list, siblings) {
+ if (line->offset >= bank->num_lines)
+ continue;
+
+ if (!line->hog)
+ continue;
+
+ hog = &dev->hogs[idx++];
+
+ /*
+ * We need to make this string manually because at this
+ * point the device doesn't exist yet and so dev_name()
+ * is not available.
+ */
+ if (gpio_sim_bank_has_label(bank))
+ hog->chip_label = kstrdup(bank->label,
+ GFP_KERNEL);
+ else
+ hog->chip_label = kasprintf(GFP_KERNEL,
+ "gpio-sim.%u-%pfwP",
+ dev->id,
+ bank->swnode);
+ if (!hog->chip_label) {
+ gpio_sim_remove_hogs(dev);
+ return -ENOMEM;
+ }
+
+ /*
+ * We need to duplicate this because the hog config
+ * item can be removed at any time (and we can't block
+ * it) and gpiolib doesn't make a deep copy of the hog
+ * data.
+ */
+ if (line->hog->name) {
+ hog->line_name = kstrdup(line->hog->name,
+ GFP_KERNEL);
+ if (!hog->line_name) {
+ gpio_sim_remove_hogs(dev);
+ return -ENOMEM;
+ }
+ }
+
+ hog->chip_hwnum = line->offset;
+ hog->dflags = line->hog->dir;
+ }
+ }
+
+ gpiod_add_hogs(dev->hogs);
+
+ return 0;
+}
+
+static struct fwnode_handle *
+gpio_sim_make_bank_swnode(struct gpio_sim_bank *bank,
+ struct fwnode_handle *parent)
+{
+ struct property_entry properties[GPIO_SIM_PROP_MAX];
+ unsigned int prop_idx = 0, line_names_size;
+ char **line_names __free(kfree) = NULL;
+
+ memset(properties, 0, sizeof(properties));
+
+ properties[prop_idx++] = PROPERTY_ENTRY_U32("ngpios", bank->num_lines);
+
+ if (gpio_sim_bank_has_label(bank))
+ properties[prop_idx++] = PROPERTY_ENTRY_STRING("gpio-sim,label",
+ bank->label);
+
+ line_names_size = gpio_sim_get_line_names_size(bank);
+ if (line_names_size) {
+ line_names = kcalloc(line_names_size, sizeof(*line_names),
+ GFP_KERNEL);
+ if (!line_names)
+ return ERR_PTR(-ENOMEM);
+
+ gpio_sim_set_line_names(bank, line_names);
+
+ properties[prop_idx++] = PROPERTY_ENTRY_STRING_ARRAY_LEN(
+ "gpio-line-names",
+ line_names, line_names_size);
+ }
+
+ return fwnode_create_software_node(properties, parent);
+}
+
+static void gpio_sim_remove_swnode_recursive(struct fwnode_handle *swnode)
+{
+ struct fwnode_handle *child;
+
+ fwnode_for_each_child_node(swnode, child)
+ fwnode_remove_software_node(child);
+
+ fwnode_remove_software_node(swnode);
+}
+
+static bool gpio_sim_bank_labels_non_unique(struct gpio_sim_device *dev)
+{
+ struct gpio_sim_bank *this, *pos;
+
+ list_for_each_entry(this, &dev->bank_list, siblings) {
+ list_for_each_entry(pos, &dev->bank_list, siblings) {
+ if (this == pos || (!this->label || !pos->label))
+ continue;
+
+ if (strcmp(this->label, pos->label) == 0)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int gpio_sim_device_activate_unlocked(struct gpio_sim_device *dev)
+{
+ struct platform_device_info pdevinfo;
+ struct fwnode_handle *swnode;
+ struct platform_device *pdev;
+ struct gpio_sim_bank *bank;
+ int ret;
+
+ if (list_empty(&dev->bank_list))
+ return -ENODATA;
+
+ /*
+ * Non-unique GPIO device labels are a corner-case we don't support
+ * as it would interfere with machine hogging mechanism and has little
+ * use in real life.
+ */
+ if (gpio_sim_bank_labels_non_unique(dev))
+ return -EINVAL;
+
+ memset(&pdevinfo, 0, sizeof(pdevinfo));
+
+ swnode = fwnode_create_software_node(NULL, NULL);
+ if (IS_ERR(swnode))
+ return PTR_ERR(swnode);
+
+ list_for_each_entry(bank, &dev->bank_list, siblings) {
+ bank->swnode = gpio_sim_make_bank_swnode(bank, swnode);
+ if (IS_ERR(bank->swnode)) {
+ ret = PTR_ERR(bank->swnode);
+ gpio_sim_remove_swnode_recursive(swnode);
+ return ret;
+ }
+ }
+
+ ret = gpio_sim_add_hogs(dev);
+ if (ret) {
+ gpio_sim_remove_swnode_recursive(swnode);
+ return ret;
+ }
+
+ pdevinfo.name = "gpio-sim";
+ pdevinfo.fwnode = swnode;
+ pdevinfo.id = dev->id;
+
+ reinit_completion(&dev->probe_completion);
+ dev->driver_bound = false;
+ bus_register_notifier(&platform_bus_type, &dev->bus_notifier);
+
+ pdev = platform_device_register_full(&pdevinfo);
+ if (IS_ERR(pdev)) {
+ bus_unregister_notifier(&platform_bus_type, &dev->bus_notifier);
+ gpio_sim_remove_hogs(dev);
+ gpio_sim_remove_swnode_recursive(swnode);
+ return PTR_ERR(pdev);
+ }
+
+ wait_for_completion(&dev->probe_completion);
+ bus_unregister_notifier(&platform_bus_type, &dev->bus_notifier);
+
+ if (!dev->driver_bound) {
+ /* Probe failed, check kernel log. */
+ platform_device_unregister(pdev);
+ gpio_sim_remove_hogs(dev);
+ gpio_sim_remove_swnode_recursive(swnode);
+ return -ENXIO;
+ }
+
+ dev->pdev = pdev;
+
+ return 0;
+}
+
+static void gpio_sim_device_deactivate_unlocked(struct gpio_sim_device *dev)
+{
+ struct fwnode_handle *swnode;
+
+ swnode = dev_fwnode(&dev->pdev->dev);
+ platform_device_unregister(dev->pdev);
+ gpio_sim_remove_hogs(dev);
+ gpio_sim_remove_swnode_recursive(swnode);
+ dev->pdev = NULL;
+}
+
+static ssize_t
+gpio_sim_device_config_live_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct gpio_sim_device *dev = to_gpio_sim_device(item);
+ bool live;
+ int ret;
+
+ ret = kstrtobool(page, &live);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&dev->lock);
+
+ if (live == gpio_sim_device_is_live_unlocked(dev))
+ ret = -EPERM;
+ else if (live)
+ ret = gpio_sim_device_activate_unlocked(dev);
+ else
+ gpio_sim_device_deactivate_unlocked(dev);
+
+ return ret ?: count;
+}
+
+CONFIGFS_ATTR(gpio_sim_device_config_, live);
+
+static struct configfs_attribute *gpio_sim_device_config_attrs[] = {
+ &gpio_sim_device_config_attr_dev_name,
+ &gpio_sim_device_config_attr_live,
+ NULL
+};
+
+struct gpio_sim_chip_name_ctx {
+ struct fwnode_handle *swnode;
+ char *page;
+};
+
+static int gpio_sim_emit_chip_name(struct device *dev, void *data)
+{
+ struct gpio_sim_chip_name_ctx *ctx = data;
+
+ /* This would be the sysfs device exported in /sys/class/gpio. */
+ if (dev->class)
+ return 0;
+
+ if (device_match_fwnode(dev, ctx->swnode))
+ return sprintf(ctx->page, "%s\n", dev_name(dev));
+
+ return 0;
+}
+
+static ssize_t gpio_sim_bank_config_chip_name_show(struct config_item *item,
+ char *page)
+{
+ struct gpio_sim_bank *bank = to_gpio_sim_bank(item);
+ struct gpio_sim_device *dev = gpio_sim_bank_get_device(bank);
+ struct gpio_sim_chip_name_ctx ctx = { bank->swnode, page };
+
+ guard(mutex)(&dev->lock);
+
+ if (gpio_sim_device_is_live_unlocked(dev))
+ return device_for_each_child(&dev->pdev->dev, &ctx,
+ gpio_sim_emit_chip_name);
+
+ return sprintf(page, "none\n");
+}
+
+CONFIGFS_ATTR_RO(gpio_sim_bank_config_, chip_name);
+
+static ssize_t
+gpio_sim_bank_config_label_show(struct config_item *item, char *page)
+{
+ struct gpio_sim_bank *bank = to_gpio_sim_bank(item);
+ struct gpio_sim_device *dev = gpio_sim_bank_get_device(bank);
+
+ guard(mutex)(&dev->lock);
+
+ return sprintf(page, "%s\n", bank->label ?: "");
+}
+
+static ssize_t gpio_sim_bank_config_label_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct gpio_sim_bank *bank = to_gpio_sim_bank(item);
+ struct gpio_sim_device *dev = gpio_sim_bank_get_device(bank);
+ char *trimmed;
+
+ guard(mutex)(&dev->lock);
+
+ if (gpio_sim_device_is_live_unlocked(dev))
+ return -EBUSY;
+
+ trimmed = gpio_sim_strdup_trimmed(page, count);
+ if (!trimmed)
+ return -ENOMEM;
+
+ kfree(bank->label);
+ bank->label = trimmed;
+
+ return count;
+}
+
+CONFIGFS_ATTR(gpio_sim_bank_config_, label);
+
+static ssize_t
+gpio_sim_bank_config_num_lines_show(struct config_item *item, char *page)
+{
+ struct gpio_sim_bank *bank = to_gpio_sim_bank(item);
+ struct gpio_sim_device *dev = gpio_sim_bank_get_device(bank);
+
+ guard(mutex)(&dev->lock);
+
+ return sprintf(page, "%u\n", bank->num_lines);
+}
+
+static ssize_t
+gpio_sim_bank_config_num_lines_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct gpio_sim_bank *bank = to_gpio_sim_bank(item);
+ struct gpio_sim_device *dev = gpio_sim_bank_get_device(bank);
+ unsigned int num_lines;
+ int ret;
+
+ ret = kstrtouint(page, 0, &num_lines);
+ if (ret)
+ return ret;
+
+ if (num_lines == 0)
+ return -EINVAL;
+
+ guard(mutex)(&dev->lock);
+
+ if (gpio_sim_device_is_live_unlocked(dev))
+ return -EBUSY;
+
+ bank->num_lines = num_lines;
+
+ return count;
+}
+
+CONFIGFS_ATTR(gpio_sim_bank_config_, num_lines);
+
+static struct configfs_attribute *gpio_sim_bank_config_attrs[] = {
+ &gpio_sim_bank_config_attr_chip_name,
+ &gpio_sim_bank_config_attr_label,
+ &gpio_sim_bank_config_attr_num_lines,
+ NULL
+};
+
+static ssize_t
+gpio_sim_line_config_name_show(struct config_item *item, char *page)
+{
+ struct gpio_sim_line *line = to_gpio_sim_line(item);
+ struct gpio_sim_device *dev = gpio_sim_line_get_device(line);
+
+ guard(mutex)(&dev->lock);
+
+ return sprintf(page, "%s\n", line->name ?: "");
+}
+
+static ssize_t gpio_sim_line_config_name_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct gpio_sim_line *line = to_gpio_sim_line(item);
+ struct gpio_sim_device *dev = gpio_sim_line_get_device(line);
+ char *trimmed;
+
+ guard(mutex)(&dev->lock);
+
+ if (gpio_sim_device_is_live_unlocked(dev))
+ return -EBUSY;
+
+ trimmed = gpio_sim_strdup_trimmed(page, count);
+ if (!trimmed)
+ return -ENOMEM;
+
+ kfree(line->name);
+ line->name = trimmed;
+
+ return count;
+}
+
+CONFIGFS_ATTR(gpio_sim_line_config_, name);
+
+static struct configfs_attribute *gpio_sim_line_config_attrs[] = {
+ &gpio_sim_line_config_attr_name,
+ NULL
+};
+
+static ssize_t gpio_sim_hog_config_name_show(struct config_item *item,
+ char *page)
+{
+ struct gpio_sim_hog *hog = to_gpio_sim_hog(item);
+ struct gpio_sim_device *dev = gpio_sim_hog_get_device(hog);
+
+ guard(mutex)(&dev->lock);
+
+ return sprintf(page, "%s\n", hog->name ?: "");
+}
+
+static ssize_t gpio_sim_hog_config_name_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct gpio_sim_hog *hog = to_gpio_sim_hog(item);
+ struct gpio_sim_device *dev = gpio_sim_hog_get_device(hog);
+ char *trimmed;
+
+ guard(mutex)(&dev->lock);
+
+ if (gpio_sim_device_is_live_unlocked(dev))
+ return -EBUSY;
+
+ trimmed = gpio_sim_strdup_trimmed(page, count);
+ if (!trimmed)
+ return -ENOMEM;
+
+ kfree(hog->name);
+ hog->name = trimmed;
+
+ return count;
+}
+
+CONFIGFS_ATTR(gpio_sim_hog_config_, name);
+
+static ssize_t gpio_sim_hog_config_direction_show(struct config_item *item,
+ char *page)
+{
+ struct gpio_sim_hog *hog = to_gpio_sim_hog(item);
+ struct gpio_sim_device *dev = gpio_sim_hog_get_device(hog);
+ char *repr;
+ int dir;
+
+ scoped_guard(mutex, &dev->lock)
+ dir = hog->dir;
+
+ switch (dir) {
+ case GPIOD_IN:
+ repr = "input";
+ break;
+ case GPIOD_OUT_HIGH:
+ repr = "output-high";
+ break;
+ case GPIOD_OUT_LOW:
+ repr = "output-low";
+ break;
+ default:
+ /* This would be a programmer bug. */
+ WARN(1, "Unexpected hog direction value: %d", dir);
+ return -EINVAL;
+ }
+
+ return sprintf(page, "%s\n", repr);
+}
+
+static ssize_t
+gpio_sim_hog_config_direction_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct gpio_sim_hog *hog = to_gpio_sim_hog(item);
+ struct gpio_sim_device *dev = gpio_sim_hog_get_device(hog);
+ int dir;
+
+ guard(mutex)(&dev->lock);
+
+ if (gpio_sim_device_is_live_unlocked(dev))
+ return -EBUSY;
+
+ if (sysfs_streq(page, "input"))
+ dir = GPIOD_IN;
+ else if (sysfs_streq(page, "output-high"))
+ dir = GPIOD_OUT_HIGH;
+ else if (sysfs_streq(page, "output-low"))
+ dir = GPIOD_OUT_LOW;
+ else
+ return -EINVAL;
+
+ hog->dir = dir;
+
+ return count;
+}
+
+CONFIGFS_ATTR(gpio_sim_hog_config_, direction);
+
+static struct configfs_attribute *gpio_sim_hog_config_attrs[] = {
+ &gpio_sim_hog_config_attr_name,
+ &gpio_sim_hog_config_attr_direction,
+ NULL
+};
+
+static void gpio_sim_hog_config_item_release(struct config_item *item)
+{
+ struct gpio_sim_hog *hog = to_gpio_sim_hog(item);
+ struct gpio_sim_line *line = hog->parent;
+ struct gpio_sim_device *dev = gpio_sim_hog_get_device(hog);
+
+ scoped_guard(mutex, &dev->lock)
+ line->hog = NULL;
+
+ kfree(hog->name);
+ kfree(hog);
+}
+
+static struct configfs_item_operations gpio_sim_hog_config_item_ops = {
+ .release = gpio_sim_hog_config_item_release,
+};
+
+static const struct config_item_type gpio_sim_hog_config_type = {
+ .ct_item_ops = &gpio_sim_hog_config_item_ops,
+ .ct_attrs = gpio_sim_hog_config_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_item *
+gpio_sim_line_config_make_hog_item(struct config_group *group, const char *name)
+{
+ struct gpio_sim_line *line = to_gpio_sim_line(&group->cg_item);
+ struct gpio_sim_device *dev = gpio_sim_line_get_device(line);
+ struct gpio_sim_hog *hog;
+
+ if (strcmp(name, "hog") != 0)
+ return ERR_PTR(-EINVAL);
+
+ guard(mutex)(&dev->lock);
+
+ hog = kzalloc(sizeof(*hog), GFP_KERNEL);
+ if (!hog)
+ return ERR_PTR(-ENOMEM);
+
+ config_item_init_type_name(&hog->item, name,
+ &gpio_sim_hog_config_type);
+
+ hog->dir = GPIOD_IN;
+ hog->name = NULL;
+ hog->parent = line;
+ line->hog = hog;
+
+ return &hog->item;
+}
+
+static void gpio_sim_line_config_group_release(struct config_item *item)
+{
+ struct gpio_sim_line *line = to_gpio_sim_line(item);
+ struct gpio_sim_device *dev = gpio_sim_line_get_device(line);
+
+ scoped_guard(mutex, &dev->lock)
+ list_del(&line->siblings);
+
+ kfree(line->name);
+ kfree(line);
+}
+
+static struct configfs_item_operations gpio_sim_line_config_item_ops = {
+ .release = gpio_sim_line_config_group_release,
+};
+
+static struct configfs_group_operations gpio_sim_line_config_group_ops = {
+ .make_item = gpio_sim_line_config_make_hog_item,
+};
+
+static const struct config_item_type gpio_sim_line_config_type = {
+ .ct_item_ops = &gpio_sim_line_config_item_ops,
+ .ct_group_ops = &gpio_sim_line_config_group_ops,
+ .ct_attrs = gpio_sim_line_config_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_group *
+gpio_sim_bank_config_make_line_group(struct config_group *group,
+ const char *name)
+{
+ struct gpio_sim_bank *bank = to_gpio_sim_bank(&group->cg_item);
+ struct gpio_sim_device *dev = gpio_sim_bank_get_device(bank);
+ struct gpio_sim_line *line;
+ unsigned int offset;
+ int ret, nchar;
+
+ ret = sscanf(name, "line%u%n", &offset, &nchar);
+ if (ret != 1 || nchar != strlen(name))
+ return ERR_PTR(-EINVAL);
+
+ guard(mutex)(&dev->lock);
+
+ if (gpio_sim_device_is_live_unlocked(dev))
+ return ERR_PTR(-EBUSY);
+
+ line = kzalloc(sizeof(*line), GFP_KERNEL);
+ if (!line)
+ return ERR_PTR(-ENOMEM);
+
+ config_group_init_type_name(&line->group, name,
+ &gpio_sim_line_config_type);
+
+ line->parent = bank;
+ line->offset = offset;
+ list_add_tail(&line->siblings, &bank->line_list);
+
+ return &line->group;
+}
+
+static void gpio_sim_bank_config_group_release(struct config_item *item)
+{
+ struct gpio_sim_bank *bank = to_gpio_sim_bank(item);
+ struct gpio_sim_device *dev = gpio_sim_bank_get_device(bank);
+
+ scoped_guard(mutex, &dev->lock)
+ list_del(&bank->siblings);
+
+ kfree(bank->label);
+ kfree(bank);
+}
+
+static struct configfs_item_operations gpio_sim_bank_config_item_ops = {
+ .release = gpio_sim_bank_config_group_release,
+};
+
+static struct configfs_group_operations gpio_sim_bank_config_group_ops = {
+ .make_group = gpio_sim_bank_config_make_line_group,
+};
+
+static const struct config_item_type gpio_sim_bank_config_group_type = {
+ .ct_item_ops = &gpio_sim_bank_config_item_ops,
+ .ct_group_ops = &gpio_sim_bank_config_group_ops,
+ .ct_attrs = gpio_sim_bank_config_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_group *
+gpio_sim_device_config_make_bank_group(struct config_group *group,
+ const char *name)
+{
+ struct gpio_sim_device *dev = to_gpio_sim_device(&group->cg_item);
+ struct gpio_sim_bank *bank;
+
+ guard(mutex)(&dev->lock);
+
+ if (gpio_sim_device_is_live_unlocked(dev))
+ return ERR_PTR(-EBUSY);
+
+ bank = kzalloc(sizeof(*bank), GFP_KERNEL);
+ if (!bank)
+ return ERR_PTR(-ENOMEM);
+
+ config_group_init_type_name(&bank->group, name,
+ &gpio_sim_bank_config_group_type);
+ bank->num_lines = 1;
+ bank->parent = dev;
+ INIT_LIST_HEAD(&bank->line_list);
+ list_add_tail(&bank->siblings, &dev->bank_list);
+
+ return &bank->group;
+}
+
+static void gpio_sim_device_config_group_release(struct config_item *item)
+{
+ struct gpio_sim_device *dev = to_gpio_sim_device(item);
+
+ scoped_guard(mutex, &dev->lock) {
+ if (gpio_sim_device_is_live_unlocked(dev))
+ gpio_sim_device_deactivate_unlocked(dev);
+ }
+
+ mutex_destroy(&dev->lock);
+ ida_free(&gpio_sim_ida, dev->id);
+ kfree(dev);
+}
+
+static struct configfs_item_operations gpio_sim_device_config_item_ops = {
+ .release = gpio_sim_device_config_group_release,
+};
+
+static struct configfs_group_operations gpio_sim_device_config_group_ops = {
+ .make_group = gpio_sim_device_config_make_bank_group,
+};
+
+static const struct config_item_type gpio_sim_device_config_group_type = {
+ .ct_item_ops = &gpio_sim_device_config_item_ops,
+ .ct_group_ops = &gpio_sim_device_config_group_ops,
+ .ct_attrs = gpio_sim_device_config_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_group *
+gpio_sim_config_make_device_group(struct config_group *group, const char *name)
+{
+ int id;
+
+ struct gpio_sim_device *dev __free(kfree) = kzalloc(sizeof(*dev),
+ GFP_KERNEL);
+ if (!dev)
+ return ERR_PTR(-ENOMEM);
+
+ id = ida_alloc(&gpio_sim_ida, GFP_KERNEL);
+ if (id < 0)
+ return ERR_PTR(id);
+
+ config_group_init_type_name(&dev->group, name,
+ &gpio_sim_device_config_group_type);
+ dev->id = id;
+ mutex_init(&dev->lock);
+ INIT_LIST_HEAD(&dev->bank_list);
+
+ dev->bus_notifier.notifier_call = gpio_sim_bus_notifier_call;
+ init_completion(&dev->probe_completion);
+
+ return &no_free_ptr(dev)->group;
+}
+
+static struct configfs_group_operations gpio_sim_config_group_ops = {
+ .make_group = gpio_sim_config_make_device_group,
+};
+
+static const struct config_item_type gpio_sim_config_type = {
+ .ct_group_ops = &gpio_sim_config_group_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct configfs_subsystem gpio_sim_config_subsys = {
+ .su_group = {
+ .cg_item = {
+ .ci_namebuf = "gpio-sim",
+ .ci_type = &gpio_sim_config_type,
+ },
+ },
+};
+
+static int __init gpio_sim_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&gpio_sim_driver);
+ if (ret) {
+ pr_err("Error %d while registering the platform driver\n", ret);
+ return ret;
+ }
+
+ config_group_init(&gpio_sim_config_subsys.su_group);
+ mutex_init(&gpio_sim_config_subsys.su_mutex);
+ ret = configfs_register_subsystem(&gpio_sim_config_subsys);
+ if (ret) {
+ pr_err("Error %d while registering the configfs subsystem %s\n",
+ ret, gpio_sim_config_subsys.su_group.cg_item.ci_namebuf);
+ mutex_destroy(&gpio_sim_config_subsys.su_mutex);
+ platform_driver_unregister(&gpio_sim_driver);
+ return ret;
+ }
+
+ return 0;
+}
+module_init(gpio_sim_init);
+
+static void __exit gpio_sim_exit(void)
+{
+ configfs_unregister_subsystem(&gpio_sim_config_subsys);
+ mutex_destroy(&gpio_sim_config_subsys.su_mutex);
+ platform_driver_unregister(&gpio_sim_driver);
+}
+module_exit(gpio_sim_exit);
+
+MODULE_AUTHOR("Bartosz Golaszewski <brgl@bgdev.pl");
+MODULE_DESCRIPTION("GPIO Simulator Module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-siox.c b/drivers/gpio/gpio-siox.c
new file mode 100644
index 0000000000..051bc99bdf
--- /dev/null
+++ b/drivers/gpio/gpio-siox.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015-2018 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de>
+ */
+
+#include <linux/module.h>
+#include <linux/siox.h>
+#include <linux/gpio/driver.h>
+#include <linux/of.h>
+
+struct gpio_siox_ddata {
+ struct gpio_chip gchip;
+ struct mutex lock;
+ u8 setdata[1];
+ u8 getdata[3];
+
+ raw_spinlock_t irqlock;
+ u32 irq_enable;
+ u32 irq_status;
+ u32 irq_type[20];
+};
+
+/*
+ * Note that this callback only sets the value that is clocked out in the next
+ * cycle.
+ */
+static int gpio_siox_set_data(struct siox_device *sdevice, u8 status, u8 buf[])
+{
+ struct gpio_siox_ddata *ddata = dev_get_drvdata(&sdevice->dev);
+
+ mutex_lock(&ddata->lock);
+ buf[0] = ddata->setdata[0];
+ mutex_unlock(&ddata->lock);
+
+ return 0;
+}
+
+static int gpio_siox_get_data(struct siox_device *sdevice, const u8 buf[])
+{
+ struct gpio_siox_ddata *ddata = dev_get_drvdata(&sdevice->dev);
+ size_t offset;
+ u32 trigger;
+
+ mutex_lock(&ddata->lock);
+
+ raw_spin_lock_irq(&ddata->irqlock);
+
+ for (offset = 0; offset < 12; ++offset) {
+ unsigned int bitpos = 11 - offset;
+ unsigned int gpiolevel = buf[bitpos / 8] & (1 << bitpos % 8);
+ unsigned int prev_level =
+ ddata->getdata[bitpos / 8] & (1 << (bitpos % 8));
+ u32 irq_type = ddata->irq_type[offset];
+
+ if (gpiolevel) {
+ if ((irq_type & IRQ_TYPE_LEVEL_HIGH) ||
+ ((irq_type & IRQ_TYPE_EDGE_RISING) && !prev_level))
+ ddata->irq_status |= 1 << offset;
+ } else {
+ if ((irq_type & IRQ_TYPE_LEVEL_LOW) ||
+ ((irq_type & IRQ_TYPE_EDGE_FALLING) && prev_level))
+ ddata->irq_status |= 1 << offset;
+ }
+ }
+
+ trigger = ddata->irq_status & ddata->irq_enable;
+
+ raw_spin_unlock_irq(&ddata->irqlock);
+
+ ddata->getdata[0] = buf[0];
+ ddata->getdata[1] = buf[1];
+ ddata->getdata[2] = buf[2];
+
+ mutex_unlock(&ddata->lock);
+
+ for (offset = 0; offset < 12; ++offset) {
+ if (trigger & (1 << offset)) {
+ struct irq_domain *irqdomain = ddata->gchip.irq.domain;
+ unsigned int irq = irq_find_mapping(irqdomain, offset);
+
+ /*
+ * Conceptually handle_nested_irq should call the flow
+ * handler of the irq chip. But it doesn't, so we have
+ * to clean the irq_status here.
+ */
+ raw_spin_lock_irq(&ddata->irqlock);
+ ddata->irq_status &= ~(1 << offset);
+ raw_spin_unlock_irq(&ddata->irqlock);
+
+ handle_nested_irq(irq);
+ }
+ }
+
+ return 0;
+}
+
+static void gpio_siox_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct gpio_siox_ddata *ddata = gpiochip_get_data(gc);
+
+ raw_spin_lock(&ddata->irqlock);
+ ddata->irq_status &= ~(1 << d->hwirq);
+ raw_spin_unlock(&ddata->irqlock);
+}
+
+static void gpio_siox_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct gpio_siox_ddata *ddata = gpiochip_get_data(gc);
+
+ raw_spin_lock(&ddata->irqlock);
+ ddata->irq_enable &= ~(1 << d->hwirq);
+ raw_spin_unlock(&ddata->irqlock);
+ gpiochip_disable_irq(gc, irqd_to_hwirq(d));
+}
+
+static void gpio_siox_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct gpio_siox_ddata *ddata = gpiochip_get_data(gc);
+
+ gpiochip_enable_irq(gc, irqd_to_hwirq(d));
+ raw_spin_lock(&ddata->irqlock);
+ ddata->irq_enable |= 1 << d->hwirq;
+ raw_spin_unlock(&ddata->irqlock);
+}
+
+static int gpio_siox_irq_set_type(struct irq_data *d, u32 type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct gpio_siox_ddata *ddata = gpiochip_get_data(gc);
+
+ raw_spin_lock(&ddata->irqlock);
+ ddata->irq_type[d->hwirq] = type;
+ raw_spin_unlock(&ddata->irqlock);
+
+ return 0;
+}
+
+static int gpio_siox_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gpio_siox_ddata *ddata = gpiochip_get_data(chip);
+ int ret;
+
+ mutex_lock(&ddata->lock);
+
+ if (offset >= 12) {
+ unsigned int bitpos = 19 - offset;
+
+ ret = ddata->setdata[0] & (1 << bitpos);
+ } else {
+ unsigned int bitpos = 11 - offset;
+
+ ret = ddata->getdata[bitpos / 8] & (1 << (bitpos % 8));
+ }
+
+ mutex_unlock(&ddata->lock);
+
+ return ret;
+}
+
+static void gpio_siox_set(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct gpio_siox_ddata *ddata = gpiochip_get_data(chip);
+ u8 mask = 1 << (19 - offset);
+
+ mutex_lock(&ddata->lock);
+
+ if (value)
+ ddata->setdata[0] |= mask;
+ else
+ ddata->setdata[0] &= ~mask;
+
+ mutex_unlock(&ddata->lock);
+}
+
+static int gpio_siox_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ if (offset >= 12)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int gpio_siox_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ if (offset < 12)
+ return -EINVAL;
+
+ gpio_siox_set(chip, offset, value);
+ return 0;
+}
+
+static int gpio_siox_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ if (offset < 12)
+ return GPIO_LINE_DIRECTION_IN;
+ else
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static const struct irq_chip gpio_siox_irq_chip = {
+ .name = "siox-gpio",
+ .irq_ack = gpio_siox_irq_ack,
+ .irq_mask = gpio_siox_irq_mask,
+ .irq_unmask = gpio_siox_irq_unmask,
+ .irq_set_type = gpio_siox_irq_set_type,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int gpio_siox_probe(struct siox_device *sdevice)
+{
+ struct gpio_siox_ddata *ddata;
+ struct gpio_irq_chip *girq;
+ struct device *dev = &sdevice->dev;
+ struct gpio_chip *gc;
+ int ret;
+
+ ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, ddata);
+
+ mutex_init(&ddata->lock);
+ raw_spin_lock_init(&ddata->irqlock);
+
+ gc = &ddata->gchip;
+ gc->base = -1;
+ gc->can_sleep = 1;
+ gc->parent = dev;
+ gc->owner = THIS_MODULE;
+ gc->get = gpio_siox_get;
+ gc->set = gpio_siox_set;
+ gc->direction_input = gpio_siox_direction_input;
+ gc->direction_output = gpio_siox_direction_output;
+ gc->get_direction = gpio_siox_get_direction;
+ gc->ngpio = 20;
+
+ girq = &gc->irq;
+ gpio_irq_chip_set_chip(girq, &gpio_siox_irq_chip);
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_level_irq;
+ girq->threaded = true;
+
+ ret = devm_gpiochip_add_data(dev, gc, ddata);
+ if (ret)
+ dev_err(dev, "Failed to register gpio chip (%d)\n", ret);
+
+ return ret;
+}
+
+static struct siox_driver gpio_siox_driver = {
+ .probe = gpio_siox_probe,
+ .set_data = gpio_siox_set_data,
+ .get_data = gpio_siox_get_data,
+ .driver = {
+ .name = "gpio-siox",
+ },
+};
+module_siox_driver(gpio_siox_driver);
+
+MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>");
+MODULE_DESCRIPTION("SIOX gpio driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-sl28cpld.c b/drivers/gpio/gpio-sl28cpld.c
new file mode 100644
index 0000000000..2195f88c20
--- /dev/null
+++ b/drivers/gpio/gpio-sl28cpld.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sl28cpld GPIO driver
+ *
+ * Copyright 2020 Michael Walle <michael@walle.cc>
+ */
+
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* GPIO flavor */
+#define GPIO_REG_DIR 0x00
+#define GPIO_REG_OUT 0x01
+#define GPIO_REG_IN 0x02
+#define GPIO_REG_IE 0x03
+#define GPIO_REG_IP 0x04
+
+/* input-only flavor */
+#define GPI_REG_IN 0x00
+
+/* output-only flavor */
+#define GPO_REG_OUT 0x00
+
+enum sl28cpld_gpio_type {
+ SL28CPLD_GPIO = 1,
+ SL28CPLD_GPI,
+ SL28CPLD_GPO,
+};
+
+static const struct regmap_irq sl28cpld_gpio_irqs[] = {
+ REGMAP_IRQ_REG_LINE(0, 8),
+ REGMAP_IRQ_REG_LINE(1, 8),
+ REGMAP_IRQ_REG_LINE(2, 8),
+ REGMAP_IRQ_REG_LINE(3, 8),
+ REGMAP_IRQ_REG_LINE(4, 8),
+ REGMAP_IRQ_REG_LINE(5, 8),
+ REGMAP_IRQ_REG_LINE(6, 8),
+ REGMAP_IRQ_REG_LINE(7, 8),
+};
+
+static int sl28cpld_gpio_irq_init(struct platform_device *pdev,
+ unsigned int base,
+ struct gpio_regmap_config *config)
+{
+ struct regmap_irq_chip_data *irq_data;
+ struct regmap_irq_chip *irq_chip;
+ struct device *dev = &pdev->dev;
+ int irq, ret;
+
+ if (!device_property_read_bool(dev, "interrupt-controller"))
+ return 0;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ irq_chip = devm_kzalloc(dev, sizeof(*irq_chip), GFP_KERNEL);
+ if (!irq_chip)
+ return -ENOMEM;
+
+ irq_chip->name = "sl28cpld-gpio-irq";
+ irq_chip->irqs = sl28cpld_gpio_irqs;
+ irq_chip->num_irqs = ARRAY_SIZE(sl28cpld_gpio_irqs);
+ irq_chip->num_regs = 1;
+ irq_chip->status_base = base + GPIO_REG_IP;
+ irq_chip->unmask_base = base + GPIO_REG_IE;
+ irq_chip->ack_base = base + GPIO_REG_IP;
+
+ ret = devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(dev),
+ config->regmap, irq,
+ IRQF_SHARED | IRQF_ONESHOT,
+ 0, irq_chip, &irq_data);
+ if (ret)
+ return ret;
+
+ config->irq_domain = regmap_irq_get_domain(irq_data);
+
+ return 0;
+}
+
+static int sl28cpld_gpio_probe(struct platform_device *pdev)
+{
+ struct gpio_regmap_config config = {0};
+ enum sl28cpld_gpio_type type;
+ struct regmap *regmap;
+ u32 base;
+ int ret;
+
+ if (!pdev->dev.parent)
+ return -ENODEV;
+
+ type = (uintptr_t)device_get_match_data(&pdev->dev);
+ if (!type)
+ return -ENODEV;
+
+ ret = device_property_read_u32(&pdev->dev, "reg", &base);
+ if (ret)
+ return -EINVAL;
+
+ regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!regmap)
+ return -ENODEV;
+
+ config.regmap = regmap;
+ config.parent = &pdev->dev;
+ config.ngpio = 8;
+
+ switch (type) {
+ case SL28CPLD_GPIO:
+ config.reg_dat_base = base + GPIO_REG_IN;
+ config.reg_set_base = base + GPIO_REG_OUT;
+ /* reg_dir_out_base might be zero */
+ config.reg_dir_out_base = GPIO_REGMAP_ADDR(base + GPIO_REG_DIR);
+
+ /* This type supports interrupts */
+ ret = sl28cpld_gpio_irq_init(pdev, base, &config);
+ if (ret)
+ return ret;
+ break;
+ case SL28CPLD_GPO:
+ config.reg_set_base = base + GPO_REG_OUT;
+ break;
+ case SL28CPLD_GPI:
+ config.reg_dat_base = base + GPI_REG_IN;
+ break;
+ default:
+ dev_err(&pdev->dev, "unknown type %d\n", type);
+ return -ENODEV;
+ }
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(&pdev->dev, &config));
+}
+
+static const struct of_device_id sl28cpld_gpio_of_match[] = {
+ { .compatible = "kontron,sl28cpld-gpio", .data = (void *)SL28CPLD_GPIO },
+ { .compatible = "kontron,sl28cpld-gpi", .data = (void *)SL28CPLD_GPI },
+ { .compatible = "kontron,sl28cpld-gpo", .data = (void *)SL28CPLD_GPO },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sl28cpld_gpio_of_match);
+
+static struct platform_driver sl28cpld_gpio_driver = {
+ .probe = sl28cpld_gpio_probe,
+ .driver = {
+ .name = "sl28cpld-gpio",
+ .of_match_table = sl28cpld_gpio_of_match,
+ },
+};
+module_platform_driver(sl28cpld_gpio_driver);
+
+MODULE_DESCRIPTION("sl28cpld GPIO Driver");
+MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-sodaville.c b/drivers/gpio/gpio-sodaville.c
new file mode 100644
index 0000000000..c2a2c76c16
--- /dev/null
+++ b/drivers/gpio/gpio-sodaville.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO interface for Intel Sodaville SoCs.
+ *
+ * Copyright (c) 2010, 2011 Intel Corporation
+ *
+ * Author: Hans J. Koch <hjk@linutronix.de>
+ */
+
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/of_irq.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+
+#define DRV_NAME "sdv_gpio"
+#define SDV_NUM_PUB_GPIOS 12
+#define PCI_DEVICE_ID_SDV_GPIO 0x2e67
+#define GPIO_BAR 0
+
+#define GPOUTR 0x00
+#define GPOER 0x04
+#define GPINR 0x08
+
+#define GPSTR 0x0c
+#define GPIT1R0 0x10
+#define GPIO_INT 0x14
+#define GPIT1R1 0x18
+
+#define GPMUXCTL 0x1c
+
+struct sdv_gpio_chip_data {
+ int irq_base;
+ void __iomem *gpio_pub_base;
+ struct irq_domain *id;
+ struct irq_chip_generic *gc;
+ struct gpio_chip chip;
+};
+
+static int sdv_gpio_pub_set_type(struct irq_data *d, unsigned int type)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct sdv_gpio_chip_data *sd = gc->private;
+ void __iomem *type_reg;
+ u32 reg;
+
+ if (d->hwirq < 8)
+ type_reg = sd->gpio_pub_base + GPIT1R0;
+ else
+ type_reg = sd->gpio_pub_base + GPIT1R1;
+
+ reg = readl(type_reg);
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ reg &= ~BIT(4 * (d->hwirq % 8));
+ break;
+
+ case IRQ_TYPE_LEVEL_LOW:
+ reg |= BIT(4 * (d->hwirq % 8));
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ writel(reg, type_reg);
+ return 0;
+}
+
+static irqreturn_t sdv_gpio_pub_irq_handler(int irq, void *data)
+{
+ struct sdv_gpio_chip_data *sd = data;
+ unsigned long irq_stat = readl(sd->gpio_pub_base + GPSTR);
+ int irq_bit;
+
+ irq_stat &= readl(sd->gpio_pub_base + GPIO_INT);
+ if (!irq_stat)
+ return IRQ_NONE;
+
+ for_each_set_bit(irq_bit, &irq_stat, 32)
+ generic_handle_domain_irq(sd->id, irq_bit);
+
+ return IRQ_HANDLED;
+}
+
+static int sdv_xlate(struct irq_domain *h, struct device_node *node,
+ const u32 *intspec, u32 intsize, irq_hw_number_t *out_hwirq,
+ u32 *out_type)
+{
+ u32 line, type;
+
+ if (node != irq_domain_get_of_node(h))
+ return -EINVAL;
+
+ if (intsize < 2)
+ return -EINVAL;
+
+ line = *intspec;
+ *out_hwirq = line;
+
+ intspec++;
+ type = *intspec;
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_LOW:
+ case IRQ_TYPE_LEVEL_HIGH:
+ *out_type = type;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct irq_domain_ops irq_domain_sdv_ops = {
+ .xlate = sdv_xlate,
+};
+
+static int sdv_register_irqsupport(struct sdv_gpio_chip_data *sd,
+ struct pci_dev *pdev)
+{
+ struct irq_chip_type *ct;
+ int ret;
+
+ sd->irq_base = devm_irq_alloc_descs(&pdev->dev, -1, 0,
+ SDV_NUM_PUB_GPIOS, -1);
+ if (sd->irq_base < 0)
+ return sd->irq_base;
+
+ /* mask + ACK all interrupt sources */
+ writel(0, sd->gpio_pub_base + GPIO_INT);
+ writel((1 << 11) - 1, sd->gpio_pub_base + GPSTR);
+
+ ret = devm_request_irq(&pdev->dev, pdev->irq,
+ sdv_gpio_pub_irq_handler, IRQF_SHARED,
+ "sdv_gpio", sd);
+ if (ret)
+ return ret;
+
+ /*
+ * This gpio irq controller latches level irqs. Testing shows that if
+ * we unmask & ACK the IRQ before the source of the interrupt is gone
+ * then the interrupt is active again.
+ */
+ sd->gc = devm_irq_alloc_generic_chip(&pdev->dev, "sdv-gpio", 1,
+ sd->irq_base,
+ sd->gpio_pub_base,
+ handle_fasteoi_irq);
+ if (!sd->gc)
+ return -ENOMEM;
+
+ sd->gc->private = sd;
+ ct = sd->gc->chip_types;
+ ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW;
+ ct->regs.eoi = GPSTR;
+ ct->regs.mask = GPIO_INT;
+ ct->chip.irq_mask = irq_gc_mask_clr_bit;
+ ct->chip.irq_unmask = irq_gc_mask_set_bit;
+ ct->chip.irq_eoi = irq_gc_eoi;
+ ct->chip.irq_set_type = sdv_gpio_pub_set_type;
+
+ irq_setup_generic_chip(sd->gc, IRQ_MSK(SDV_NUM_PUB_GPIOS),
+ IRQ_GC_INIT_MASK_CACHE, IRQ_NOREQUEST,
+ IRQ_LEVEL | IRQ_NOPROBE);
+
+ sd->id = irq_domain_add_legacy(pdev->dev.of_node, SDV_NUM_PUB_GPIOS,
+ sd->irq_base, 0, &irq_domain_sdv_ops, sd);
+ if (!sd->id)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int sdv_gpio_probe(struct pci_dev *pdev,
+ const struct pci_device_id *pci_id)
+{
+ struct sdv_gpio_chip_data *sd;
+ int ret;
+ u32 mux_val;
+
+ sd = devm_kzalloc(&pdev->dev, sizeof(*sd), GFP_KERNEL);
+ if (!sd)
+ return -ENOMEM;
+
+ ret = pcim_enable_device(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "can't enable device.\n");
+ return ret;
+ }
+
+ ret = pcim_iomap_regions(pdev, 1 << GPIO_BAR, DRV_NAME);
+ if (ret) {
+ dev_err(&pdev->dev, "can't alloc PCI BAR #%d\n", GPIO_BAR);
+ return ret;
+ }
+
+ sd->gpio_pub_base = pcim_iomap_table(pdev)[GPIO_BAR];
+
+ ret = of_property_read_u32(pdev->dev.of_node, "intel,muxctl", &mux_val);
+ if (!ret)
+ writel(mux_val, sd->gpio_pub_base + GPMUXCTL);
+
+ ret = bgpio_init(&sd->chip, &pdev->dev, 4,
+ sd->gpio_pub_base + GPINR, sd->gpio_pub_base + GPOUTR,
+ NULL, sd->gpio_pub_base + GPOER, NULL, 0);
+ if (ret)
+ return ret;
+
+ sd->chip.ngpio = SDV_NUM_PUB_GPIOS;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &sd->chip, sd);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "gpiochip_add() failed.\n");
+ return ret;
+ }
+
+ ret = sdv_register_irqsupport(sd, pdev);
+ if (ret)
+ return ret;
+
+ pci_set_drvdata(pdev, sd);
+ dev_info(&pdev->dev, "Sodaville GPIO driver registered.\n");
+ return 0;
+}
+
+static const struct pci_device_id sdv_gpio_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_SDV_GPIO) },
+ { 0, },
+};
+
+static struct pci_driver sdv_gpio_driver = {
+ .driver = {
+ .suppress_bind_attrs = true,
+ },
+ .name = DRV_NAME,
+ .id_table = sdv_gpio_pci_ids,
+ .probe = sdv_gpio_probe,
+};
+builtin_pci_driver(sdv_gpio_driver);
diff --git a/drivers/gpio/gpio-spear-spics.c b/drivers/gpio/gpio-spear-spics.c
new file mode 100644
index 0000000000..5153918540
--- /dev/null
+++ b/drivers/gpio/gpio-spear-spics.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * SPEAr platform SPI chipselect abstraction over gpiolib
+ *
+ * Copyright (C) 2012 ST Microelectronics
+ * Shiraz Hashim <shiraz.linux.kernel@gmail.com>
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+/* maximum chipselects */
+#define NUM_OF_GPIO 4
+
+/*
+ * Provision is available on some SPEAr SoCs to control ARM PL022 spi cs
+ * through system registers. This register lies outside spi (pl022)
+ * address space into system registers.
+ *
+ * It provides control for spi chip select lines so that any chipselect
+ * (out of 4 possible chipselects in pl022) can be made low to select
+ * the particular slave.
+ */
+
+/**
+ * struct spear_spics - represents spi chip select control
+ * @base: base address
+ * @perip_cfg: configuration register
+ * @sw_enable_bit: bit to enable s/w control over chipselects
+ * @cs_value_bit: bit to program high or low chipselect
+ * @cs_enable_mask: mask to select bits required to select chipselect
+ * @cs_enable_shift: bit pos of cs_enable_mask
+ * @use_count: use count of a spi controller cs lines
+ * @last_off: stores last offset caller of set_value()
+ * @chip: gpio_chip abstraction
+ */
+struct spear_spics {
+ void __iomem *base;
+ u32 perip_cfg;
+ u32 sw_enable_bit;
+ u32 cs_value_bit;
+ u32 cs_enable_mask;
+ u32 cs_enable_shift;
+ unsigned long use_count;
+ int last_off;
+ struct gpio_chip chip;
+};
+
+/* gpio framework specific routines */
+static int spics_get_value(struct gpio_chip *chip, unsigned offset)
+{
+ return -ENXIO;
+}
+
+static void spics_set_value(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct spear_spics *spics = gpiochip_get_data(chip);
+ u32 tmp;
+
+ /* select chip select from register */
+ tmp = readl_relaxed(spics->base + spics->perip_cfg);
+ if (spics->last_off != offset) {
+ spics->last_off = offset;
+ tmp &= ~(spics->cs_enable_mask << spics->cs_enable_shift);
+ tmp |= offset << spics->cs_enable_shift;
+ }
+
+ /* toggle chip select line */
+ tmp &= ~(0x1 << spics->cs_value_bit);
+ tmp |= value << spics->cs_value_bit;
+ writel_relaxed(tmp, spics->base + spics->perip_cfg);
+}
+
+static int spics_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ return -ENXIO;
+}
+
+static int spics_direction_output(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ spics_set_value(chip, offset, value);
+ return 0;
+}
+
+static int spics_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct spear_spics *spics = gpiochip_get_data(chip);
+ u32 tmp;
+
+ if (!spics->use_count++) {
+ tmp = readl_relaxed(spics->base + spics->perip_cfg);
+ tmp |= 0x1 << spics->sw_enable_bit;
+ tmp |= 0x1 << spics->cs_value_bit;
+ writel_relaxed(tmp, spics->base + spics->perip_cfg);
+ }
+
+ return 0;
+}
+
+static void spics_free(struct gpio_chip *chip, unsigned offset)
+{
+ struct spear_spics *spics = gpiochip_get_data(chip);
+ u32 tmp;
+
+ if (!--spics->use_count) {
+ tmp = readl_relaxed(spics->base + spics->perip_cfg);
+ tmp &= ~(0x1 << spics->sw_enable_bit);
+ writel_relaxed(tmp, spics->base + spics->perip_cfg);
+ }
+}
+
+static int spics_gpio_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct spear_spics *spics;
+
+ spics = devm_kzalloc(&pdev->dev, sizeof(*spics), GFP_KERNEL);
+ if (!spics)
+ return -ENOMEM;
+
+ spics->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(spics->base))
+ return PTR_ERR(spics->base);
+
+ if (of_property_read_u32(np, "st-spics,peripcfg-reg",
+ &spics->perip_cfg))
+ goto err_dt_data;
+ if (of_property_read_u32(np, "st-spics,sw-enable-bit",
+ &spics->sw_enable_bit))
+ goto err_dt_data;
+ if (of_property_read_u32(np, "st-spics,cs-value-bit",
+ &spics->cs_value_bit))
+ goto err_dt_data;
+ if (of_property_read_u32(np, "st-spics,cs-enable-mask",
+ &spics->cs_enable_mask))
+ goto err_dt_data;
+ if (of_property_read_u32(np, "st-spics,cs-enable-shift",
+ &spics->cs_enable_shift))
+ goto err_dt_data;
+
+ spics->chip.ngpio = NUM_OF_GPIO;
+ spics->chip.base = -1;
+ spics->chip.request = spics_request;
+ spics->chip.free = spics_free;
+ spics->chip.direction_input = spics_direction_input;
+ spics->chip.direction_output = spics_direction_output;
+ spics->chip.get = spics_get_value;
+ spics->chip.set = spics_set_value;
+ spics->chip.label = dev_name(&pdev->dev);
+ spics->chip.parent = &pdev->dev;
+ spics->chip.owner = THIS_MODULE;
+ spics->last_off = -1;
+
+ return devm_gpiochip_add_data(&pdev->dev, &spics->chip, spics);
+
+err_dt_data:
+ dev_err(&pdev->dev, "DT probe failed\n");
+ return -EINVAL;
+}
+
+static const struct of_device_id spics_gpio_of_match[] = {
+ { .compatible = "st,spear-spics-gpio" },
+ {}
+};
+
+static struct platform_driver spics_gpio_driver = {
+ .probe = spics_gpio_probe,
+ .driver = {
+ .name = "spear-spics-gpio",
+ .of_match_table = spics_gpio_of_match,
+ },
+};
+
+static int __init spics_gpio_init(void)
+{
+ return platform_driver_register(&spics_gpio_driver);
+}
+subsys_initcall(spics_gpio_init);
diff --git a/drivers/gpio/gpio-sprd.c b/drivers/gpio/gpio-sprd.c
new file mode 100644
index 0000000000..c117c11bfb
--- /dev/null
+++ b/drivers/gpio/gpio-sprd.c
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Spreadtrum Communications Inc.
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+/* GPIO registers definition */
+#define SPRD_GPIO_DATA 0x0
+#define SPRD_GPIO_DMSK 0x4
+#define SPRD_GPIO_DIR 0x8
+#define SPRD_GPIO_IS 0xc
+#define SPRD_GPIO_IBE 0x10
+#define SPRD_GPIO_IEV 0x14
+#define SPRD_GPIO_IE 0x18
+#define SPRD_GPIO_RIS 0x1c
+#define SPRD_GPIO_MIS 0x20
+#define SPRD_GPIO_IC 0x24
+#define SPRD_GPIO_INEN 0x28
+
+/* We have 16 banks GPIOs and each bank contain 16 GPIOs */
+#define SPRD_GPIO_BANK_NR 16
+#define SPRD_GPIO_NR 256
+#define SPRD_GPIO_BANK_SIZE 0x80
+#define SPRD_GPIO_BANK_MASK GENMASK(15, 0)
+#define SPRD_GPIO_BIT(x) ((x) & (SPRD_GPIO_BANK_NR - 1))
+
+struct sprd_gpio {
+ struct gpio_chip chip;
+ void __iomem *base;
+ spinlock_t lock;
+ int irq;
+};
+
+static inline void __iomem *sprd_gpio_bank_base(struct sprd_gpio *sprd_gpio,
+ unsigned int bank)
+{
+ return sprd_gpio->base + SPRD_GPIO_BANK_SIZE * bank;
+}
+
+static void sprd_gpio_update(struct gpio_chip *chip, unsigned int offset,
+ u16 reg, int val)
+{
+ struct sprd_gpio *sprd_gpio = gpiochip_get_data(chip);
+ void __iomem *base = sprd_gpio_bank_base(sprd_gpio,
+ offset / SPRD_GPIO_BANK_NR);
+ unsigned long flags;
+ u32 tmp;
+
+ spin_lock_irqsave(&sprd_gpio->lock, flags);
+ tmp = readl_relaxed(base + reg);
+
+ if (val)
+ tmp |= BIT(SPRD_GPIO_BIT(offset));
+ else
+ tmp &= ~BIT(SPRD_GPIO_BIT(offset));
+
+ writel_relaxed(tmp, base + reg);
+ spin_unlock_irqrestore(&sprd_gpio->lock, flags);
+}
+
+static int sprd_gpio_read(struct gpio_chip *chip, unsigned int offset, u16 reg)
+{
+ struct sprd_gpio *sprd_gpio = gpiochip_get_data(chip);
+ void __iomem *base = sprd_gpio_bank_base(sprd_gpio,
+ offset / SPRD_GPIO_BANK_NR);
+
+ return !!(readl_relaxed(base + reg) & BIT(SPRD_GPIO_BIT(offset)));
+}
+
+static int sprd_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ sprd_gpio_update(chip, offset, SPRD_GPIO_DMSK, 1);
+ return 0;
+}
+
+static void sprd_gpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+ sprd_gpio_update(chip, offset, SPRD_GPIO_DMSK, 0);
+}
+
+static int sprd_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ sprd_gpio_update(chip, offset, SPRD_GPIO_DIR, 0);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_INEN, 1);
+ return 0;
+}
+
+static int sprd_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ sprd_gpio_update(chip, offset, SPRD_GPIO_DIR, 1);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_INEN, 0);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_DATA, value);
+ return 0;
+}
+
+static int sprd_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ return sprd_gpio_read(chip, offset, SPRD_GPIO_DATA);
+}
+
+static void sprd_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ sprd_gpio_update(chip, offset, SPRD_GPIO_DATA, value);
+}
+
+static void sprd_gpio_irq_mask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ u32 offset = irqd_to_hwirq(data);
+
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IE, 0);
+ gpiochip_disable_irq(chip, offset);
+}
+
+static void sprd_gpio_irq_ack(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ u32 offset = irqd_to_hwirq(data);
+
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IC, 1);
+}
+
+static void sprd_gpio_irq_unmask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ u32 offset = irqd_to_hwirq(data);
+
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IE, 1);
+ gpiochip_enable_irq(chip, offset);
+}
+
+static int sprd_gpio_irq_set_type(struct irq_data *data,
+ unsigned int flow_type)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ u32 offset = irqd_to_hwirq(data);
+
+ switch (flow_type) {
+ case IRQ_TYPE_EDGE_RISING:
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IS, 0);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IBE, 0);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IEV, 1);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IC, 1);
+ irq_set_handler_locked(data, handle_edge_irq);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IS, 0);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IBE, 0);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IEV, 0);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IC, 1);
+ irq_set_handler_locked(data, handle_edge_irq);
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IS, 0);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IBE, 1);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IC, 1);
+ irq_set_handler_locked(data, handle_edge_irq);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IS, 1);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IBE, 0);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IEV, 1);
+ irq_set_handler_locked(data, handle_level_irq);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IS, 1);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IBE, 0);
+ sprd_gpio_update(chip, offset, SPRD_GPIO_IEV, 0);
+ irq_set_handler_locked(data, handle_level_irq);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void sprd_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct gpio_chip *chip = irq_desc_get_handler_data(desc);
+ struct irq_chip *ic = irq_desc_get_chip(desc);
+ struct sprd_gpio *sprd_gpio = gpiochip_get_data(chip);
+ u32 bank, n;
+
+ chained_irq_enter(ic, desc);
+
+ for (bank = 0; bank * SPRD_GPIO_BANK_NR < chip->ngpio; bank++) {
+ void __iomem *base = sprd_gpio_bank_base(sprd_gpio, bank);
+ unsigned long reg = readl_relaxed(base + SPRD_GPIO_MIS) &
+ SPRD_GPIO_BANK_MASK;
+
+ for_each_set_bit(n, &reg, SPRD_GPIO_BANK_NR)
+ generic_handle_domain_irq(chip->irq.domain,
+ bank * SPRD_GPIO_BANK_NR + n);
+ }
+ chained_irq_exit(ic, desc);
+}
+
+static const struct irq_chip sprd_gpio_irqchip = {
+ .name = "sprd-gpio",
+ .irq_ack = sprd_gpio_irq_ack,
+ .irq_mask = sprd_gpio_irq_mask,
+ .irq_unmask = sprd_gpio_irq_unmask,
+ .irq_set_type = sprd_gpio_irq_set_type,
+ .flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int sprd_gpio_probe(struct platform_device *pdev)
+{
+ struct gpio_irq_chip *irq;
+ struct sprd_gpio *sprd_gpio;
+
+ sprd_gpio = devm_kzalloc(&pdev->dev, sizeof(*sprd_gpio), GFP_KERNEL);
+ if (!sprd_gpio)
+ return -ENOMEM;
+
+ sprd_gpio->irq = platform_get_irq(pdev, 0);
+ if (sprd_gpio->irq < 0)
+ return sprd_gpio->irq;
+
+ sprd_gpio->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(sprd_gpio->base))
+ return PTR_ERR(sprd_gpio->base);
+
+ spin_lock_init(&sprd_gpio->lock);
+
+ sprd_gpio->chip.label = dev_name(&pdev->dev);
+ sprd_gpio->chip.ngpio = SPRD_GPIO_NR;
+ sprd_gpio->chip.base = -1;
+ sprd_gpio->chip.parent = &pdev->dev;
+ sprd_gpio->chip.request = sprd_gpio_request;
+ sprd_gpio->chip.free = sprd_gpio_free;
+ sprd_gpio->chip.get = sprd_gpio_get;
+ sprd_gpio->chip.set = sprd_gpio_set;
+ sprd_gpio->chip.direction_input = sprd_gpio_direction_input;
+ sprd_gpio->chip.direction_output = sprd_gpio_direction_output;
+
+ irq = &sprd_gpio->chip.irq;
+ gpio_irq_chip_set_chip(irq, &sprd_gpio_irqchip);
+ irq->handler = handle_bad_irq;
+ irq->default_type = IRQ_TYPE_NONE;
+ irq->parent_handler = sprd_gpio_irq_handler;
+ irq->parent_handler_data = sprd_gpio;
+ irq->num_parents = 1;
+ irq->parents = &sprd_gpio->irq;
+
+ return devm_gpiochip_add_data(&pdev->dev, &sprd_gpio->chip, sprd_gpio);
+}
+
+static const struct of_device_id sprd_gpio_of_match[] = {
+ { .compatible = "sprd,sc9860-gpio", },
+ { /* end of list */ }
+};
+MODULE_DEVICE_TABLE(of, sprd_gpio_of_match);
+
+static struct platform_driver sprd_gpio_driver = {
+ .probe = sprd_gpio_probe,
+ .driver = {
+ .name = "sprd-gpio",
+ .of_match_table = sprd_gpio_of_match,
+ },
+};
+
+module_platform_driver_probe(sprd_gpio_driver, sprd_gpio_probe);
+
+MODULE_DESCRIPTION("Spreadtrum GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-stmpe.c b/drivers/gpio/gpio-stmpe.c
new file mode 100644
index 0000000000..27cc4da535
--- /dev/null
+++ b/drivers/gpio/gpio-stmpe.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/mfd/stmpe.h>
+#include <linux/seq_file.h>
+#include <linux/bitops.h>
+
+/*
+ * These registers are modified under the irq bus lock and cached to avoid
+ * unnecessary writes in bus_sync_unlock.
+ */
+enum { REG_RE, REG_FE, REG_IE };
+
+enum { LSB, CSB, MSB };
+
+#define CACHE_NR_REGS 3
+/* No variant has more than 24 GPIOs */
+#define CACHE_NR_BANKS (24 / 8)
+
+struct stmpe_gpio {
+ struct gpio_chip chip;
+ struct stmpe *stmpe;
+ struct device *dev;
+ struct mutex irq_lock;
+ u32 norequest_mask;
+ /* Caches of interrupt control registers for bus_lock */
+ u8 regs[CACHE_NR_REGS][CACHE_NR_BANKS];
+ u8 oldregs[CACHE_NR_REGS][CACHE_NR_BANKS];
+};
+
+static int stmpe_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(chip);
+ struct stmpe *stmpe = stmpe_gpio->stmpe;
+ u8 reg = stmpe->regs[STMPE_IDX_GPMR_LSB + (offset / 8)];
+ u8 mask = BIT(offset % 8);
+ int ret;
+
+ ret = stmpe_reg_read(stmpe, reg);
+ if (ret < 0)
+ return ret;
+
+ return !!(ret & mask);
+}
+
+static void stmpe_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
+{
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(chip);
+ struct stmpe *stmpe = stmpe_gpio->stmpe;
+ int which = val ? STMPE_IDX_GPSR_LSB : STMPE_IDX_GPCR_LSB;
+ u8 reg = stmpe->regs[which + (offset / 8)];
+ u8 mask = BIT(offset % 8);
+
+ /*
+ * Some variants have single register for gpio set/clear functionality.
+ * For them we need to write 0 to clear and 1 to set.
+ */
+ if (stmpe->regs[STMPE_IDX_GPSR_LSB] == stmpe->regs[STMPE_IDX_GPCR_LSB])
+ stmpe_set_bits(stmpe, reg, mask, val ? mask : 0);
+ else
+ stmpe_reg_write(stmpe, reg, mask);
+}
+
+static int stmpe_gpio_get_direction(struct gpio_chip *chip,
+ unsigned offset)
+{
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(chip);
+ struct stmpe *stmpe = stmpe_gpio->stmpe;
+ u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB] - (offset / 8);
+ u8 mask = BIT(offset % 8);
+ int ret;
+
+ ret = stmpe_reg_read(stmpe, reg);
+ if (ret < 0)
+ return ret;
+
+ if (ret & mask)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int stmpe_gpio_direction_output(struct gpio_chip *chip,
+ unsigned offset, int val)
+{
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(chip);
+ struct stmpe *stmpe = stmpe_gpio->stmpe;
+ u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB + (offset / 8)];
+ u8 mask = BIT(offset % 8);
+
+ stmpe_gpio_set(chip, offset, val);
+
+ return stmpe_set_bits(stmpe, reg, mask, mask);
+}
+
+static int stmpe_gpio_direction_input(struct gpio_chip *chip,
+ unsigned offset)
+{
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(chip);
+ struct stmpe *stmpe = stmpe_gpio->stmpe;
+ u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB + (offset / 8)];
+ u8 mask = BIT(offset % 8);
+
+ return stmpe_set_bits(stmpe, reg, mask, 0);
+}
+
+static int stmpe_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(chip);
+ struct stmpe *stmpe = stmpe_gpio->stmpe;
+
+ if (stmpe_gpio->norequest_mask & BIT(offset))
+ return -EINVAL;
+
+ return stmpe_set_altfunc(stmpe, BIT(offset), STMPE_BLOCK_GPIO);
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "stmpe",
+ .owner = THIS_MODULE,
+ .get_direction = stmpe_gpio_get_direction,
+ .direction_input = stmpe_gpio_direction_input,
+ .get = stmpe_gpio_get,
+ .direction_output = stmpe_gpio_direction_output,
+ .set = stmpe_gpio_set,
+ .request = stmpe_gpio_request,
+ .can_sleep = true,
+};
+
+static int stmpe_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(gc);
+ int offset = d->hwirq;
+ int regoffset = offset / 8;
+ int mask = BIT(offset % 8);
+
+ if (type & IRQ_TYPE_LEVEL_LOW || type & IRQ_TYPE_LEVEL_HIGH)
+ return -EINVAL;
+
+ /* STMPE801 and STMPE 1600 don't have RE and FE registers */
+ if (stmpe_gpio->stmpe->partnum == STMPE801 ||
+ stmpe_gpio->stmpe->partnum == STMPE1600)
+ return 0;
+
+ if (type & IRQ_TYPE_EDGE_RISING)
+ stmpe_gpio->regs[REG_RE][regoffset] |= mask;
+ else
+ stmpe_gpio->regs[REG_RE][regoffset] &= ~mask;
+
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ stmpe_gpio->regs[REG_FE][regoffset] |= mask;
+ else
+ stmpe_gpio->regs[REG_FE][regoffset] &= ~mask;
+
+ return 0;
+}
+
+static void stmpe_gpio_irq_lock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(gc);
+
+ mutex_lock(&stmpe_gpio->irq_lock);
+}
+
+static void stmpe_gpio_irq_sync_unlock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(gc);
+ struct stmpe *stmpe = stmpe_gpio->stmpe;
+ int num_banks = DIV_ROUND_UP(stmpe->num_gpios, 8);
+ static const u8 regmap[CACHE_NR_REGS][CACHE_NR_BANKS] = {
+ [REG_RE][LSB] = STMPE_IDX_GPRER_LSB,
+ [REG_RE][CSB] = STMPE_IDX_GPRER_CSB,
+ [REG_RE][MSB] = STMPE_IDX_GPRER_MSB,
+ [REG_FE][LSB] = STMPE_IDX_GPFER_LSB,
+ [REG_FE][CSB] = STMPE_IDX_GPFER_CSB,
+ [REG_FE][MSB] = STMPE_IDX_GPFER_MSB,
+ [REG_IE][LSB] = STMPE_IDX_IEGPIOR_LSB,
+ [REG_IE][CSB] = STMPE_IDX_IEGPIOR_CSB,
+ [REG_IE][MSB] = STMPE_IDX_IEGPIOR_MSB,
+ };
+ int i, j;
+
+ /*
+ * STMPE1600: to be able to get IRQ from pins,
+ * a read must be done on GPMR register, or a write in
+ * GPSR or GPCR registers
+ */
+ if (stmpe->partnum == STMPE1600) {
+ stmpe_reg_read(stmpe, stmpe->regs[STMPE_IDX_GPMR_LSB]);
+ stmpe_reg_read(stmpe, stmpe->regs[STMPE_IDX_GPMR_CSB]);
+ }
+
+ for (i = 0; i < CACHE_NR_REGS; i++) {
+ /* STMPE801 and STMPE1600 don't have RE and FE registers */
+ if ((stmpe->partnum == STMPE801 ||
+ stmpe->partnum == STMPE1600) &&
+ (i != REG_IE))
+ continue;
+
+ for (j = 0; j < num_banks; j++) {
+ u8 old = stmpe_gpio->oldregs[i][j];
+ u8 new = stmpe_gpio->regs[i][j];
+
+ if (new == old)
+ continue;
+
+ stmpe_gpio->oldregs[i][j] = new;
+ stmpe_reg_write(stmpe, stmpe->regs[regmap[i][j]], new);
+ }
+ }
+
+ mutex_unlock(&stmpe_gpio->irq_lock);
+}
+
+static void stmpe_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(gc);
+ int offset = d->hwirq;
+ int regoffset = offset / 8;
+ int mask = BIT(offset % 8);
+
+ stmpe_gpio->regs[REG_IE][regoffset] &= ~mask;
+ gpiochip_disable_irq(gc, offset);
+}
+
+static void stmpe_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(gc);
+ int offset = d->hwirq;
+ int regoffset = offset / 8;
+ int mask = BIT(offset % 8);
+
+ gpiochip_enable_irq(gc, offset);
+ stmpe_gpio->regs[REG_IE][regoffset] |= mask;
+}
+
+static void stmpe_dbg_show_one(struct seq_file *s,
+ struct gpio_chip *gc,
+ unsigned offset, unsigned gpio)
+{
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(gc);
+ struct stmpe *stmpe = stmpe_gpio->stmpe;
+ const char *label = gpiochip_is_requested(gc, offset);
+ bool val = !!stmpe_gpio_get(gc, offset);
+ u8 bank = offset / 8;
+ u8 dir_reg = stmpe->regs[STMPE_IDX_GPDR_LSB + bank];
+ u8 mask = BIT(offset % 8);
+ int ret;
+ u8 dir;
+
+ ret = stmpe_reg_read(stmpe, dir_reg);
+ if (ret < 0)
+ return;
+ dir = !!(ret & mask);
+
+ if (dir) {
+ seq_printf(s, " gpio-%-3d (%-20.20s) out %s",
+ gpio, label ?: "(none)",
+ val ? "hi" : "lo");
+ } else {
+ u8 edge_det_reg;
+ u8 rise_reg;
+ u8 fall_reg;
+ u8 irqen_reg;
+
+ static const char * const edge_det_values[] = {
+ "edge-inactive",
+ "edge-asserted",
+ "not-supported"
+ };
+ static const char * const rise_values[] = {
+ "no-rising-edge-detection",
+ "rising-edge-detection",
+ "not-supported"
+ };
+ static const char * const fall_values[] = {
+ "no-falling-edge-detection",
+ "falling-edge-detection",
+ "not-supported"
+ };
+ #define NOT_SUPPORTED_IDX 2
+ u8 edge_det = NOT_SUPPORTED_IDX;
+ u8 rise = NOT_SUPPORTED_IDX;
+ u8 fall = NOT_SUPPORTED_IDX;
+ bool irqen;
+
+ switch (stmpe->partnum) {
+ case STMPE610:
+ case STMPE811:
+ case STMPE1601:
+ case STMPE2401:
+ case STMPE2403:
+ edge_det_reg = stmpe->regs[STMPE_IDX_GPEDR_LSB + bank];
+ ret = stmpe_reg_read(stmpe, edge_det_reg);
+ if (ret < 0)
+ return;
+ edge_det = !!(ret & mask);
+ fallthrough;
+ case STMPE1801:
+ rise_reg = stmpe->regs[STMPE_IDX_GPRER_LSB + bank];
+ fall_reg = stmpe->regs[STMPE_IDX_GPFER_LSB + bank];
+
+ ret = stmpe_reg_read(stmpe, rise_reg);
+ if (ret < 0)
+ return;
+ rise = !!(ret & mask);
+ ret = stmpe_reg_read(stmpe, fall_reg);
+ if (ret < 0)
+ return;
+ fall = !!(ret & mask);
+ fallthrough;
+ case STMPE801:
+ case STMPE1600:
+ irqen_reg = stmpe->regs[STMPE_IDX_IEGPIOR_LSB + bank];
+ break;
+
+ default:
+ return;
+ }
+
+ ret = stmpe_reg_read(stmpe, irqen_reg);
+ if (ret < 0)
+ return;
+ irqen = !!(ret & mask);
+
+ seq_printf(s, " gpio-%-3d (%-20.20s) in %s %13s %13s %25s %25s",
+ gpio, label ?: "(none)",
+ val ? "hi" : "lo",
+ edge_det_values[edge_det],
+ irqen ? "IRQ-enabled" : "IRQ-disabled",
+ rise_values[rise],
+ fall_values[fall]);
+ }
+}
+
+static void stmpe_dbg_show(struct seq_file *s, struct gpio_chip *gc)
+{
+ unsigned i;
+ unsigned gpio = gc->base;
+
+ for (i = 0; i < gc->ngpio; i++, gpio++) {
+ stmpe_dbg_show_one(s, gc, i, gpio);
+ seq_putc(s, '\n');
+ }
+}
+
+static const struct irq_chip stmpe_gpio_irq_chip = {
+ .name = "stmpe-gpio",
+ .irq_bus_lock = stmpe_gpio_irq_lock,
+ .irq_bus_sync_unlock = stmpe_gpio_irq_sync_unlock,
+ .irq_mask = stmpe_gpio_irq_mask,
+ .irq_unmask = stmpe_gpio_irq_unmask,
+ .irq_set_type = stmpe_gpio_irq_set_type,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+#define MAX_GPIOS 24
+
+static irqreturn_t stmpe_gpio_irq(int irq, void *dev)
+{
+ struct stmpe_gpio *stmpe_gpio = dev;
+ struct stmpe *stmpe = stmpe_gpio->stmpe;
+ u8 statmsbreg;
+ int num_banks = DIV_ROUND_UP(stmpe->num_gpios, 8);
+ u8 status[DIV_ROUND_UP(MAX_GPIOS, 8)];
+ int ret;
+ int i;
+
+ /*
+ * the stmpe_block_read() call below, imposes to set statmsbreg
+ * with the register located at the lowest address. As STMPE1600
+ * variant is the only one which respect registers address's order
+ * (LSB regs located at lowest address than MSB ones) whereas all
+ * the others have a registers layout with MSB located before the
+ * LSB regs.
+ */
+ if (stmpe->partnum == STMPE1600)
+ statmsbreg = stmpe->regs[STMPE_IDX_ISGPIOR_LSB];
+ else
+ statmsbreg = stmpe->regs[STMPE_IDX_ISGPIOR_MSB];
+
+ ret = stmpe_block_read(stmpe, statmsbreg, num_banks, status);
+ if (ret < 0)
+ return IRQ_NONE;
+
+ for (i = 0; i < num_banks; i++) {
+ int bank = (stmpe_gpio->stmpe->partnum == STMPE1600) ? i :
+ num_banks - i - 1;
+ unsigned int enabled = stmpe_gpio->regs[REG_IE][bank];
+ unsigned int stat = status[i];
+
+ stat &= enabled;
+ if (!stat)
+ continue;
+
+ while (stat) {
+ int bit = __ffs(stat);
+ int line = bank * 8 + bit;
+ int child_irq = irq_find_mapping(stmpe_gpio->chip.irq.domain,
+ line);
+
+ handle_nested_irq(child_irq);
+ stat &= ~BIT(bit);
+ }
+
+ /*
+ * interrupt status register write has no effect on
+ * 801/1801/1600, bits are cleared when read.
+ * Edge detect register is not present on 801/1600/1801
+ */
+ if (stmpe->partnum != STMPE801 && stmpe->partnum != STMPE1600 &&
+ stmpe->partnum != STMPE1801) {
+ stmpe_reg_write(stmpe, statmsbreg + i, status[i]);
+ stmpe_reg_write(stmpe,
+ stmpe->regs[STMPE_IDX_GPEDR_MSB] + i,
+ status[i]);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void stmpe_init_irq_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ struct stmpe_gpio *stmpe_gpio = gpiochip_get_data(gc);
+ int i;
+
+ if (!stmpe_gpio->norequest_mask)
+ return;
+
+ /* Forbid unused lines to be mapped as IRQs */
+ for (i = 0; i < sizeof(u32); i++) {
+ if (stmpe_gpio->norequest_mask & BIT(i))
+ clear_bit(i, valid_mask);
+ }
+}
+
+static void stmpe_gpio_disable(void *stmpe)
+{
+ stmpe_disable(stmpe, STMPE_BLOCK_GPIO);
+}
+
+static int stmpe_gpio_probe(struct platform_device *pdev)
+{
+ struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+ struct device_node *np = pdev->dev.of_node;
+ struct stmpe_gpio *stmpe_gpio;
+ int ret, irq;
+
+ if (stmpe->num_gpios > MAX_GPIOS) {
+ dev_err(&pdev->dev, "Need to increase maximum GPIO number\n");
+ return -EINVAL;
+ }
+
+ stmpe_gpio = devm_kzalloc(&pdev->dev, sizeof(*stmpe_gpio), GFP_KERNEL);
+ if (!stmpe_gpio)
+ return -ENOMEM;
+
+ mutex_init(&stmpe_gpio->irq_lock);
+
+ stmpe_gpio->dev = &pdev->dev;
+ stmpe_gpio->stmpe = stmpe;
+ stmpe_gpio->chip = template_chip;
+ stmpe_gpio->chip.ngpio = stmpe->num_gpios;
+ stmpe_gpio->chip.parent = &pdev->dev;
+ stmpe_gpio->chip.base = -1;
+
+ if (IS_ENABLED(CONFIG_DEBUG_FS))
+ stmpe_gpio->chip.dbg_show = stmpe_dbg_show;
+
+ of_property_read_u32(np, "st,norequest-mask",
+ &stmpe_gpio->norequest_mask);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ dev_info(&pdev->dev,
+ "device configured in no-irq mode: "
+ "irqs are not available\n");
+
+ ret = stmpe_enable(stmpe, STMPE_BLOCK_GPIO);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(&pdev->dev, stmpe_gpio_disable, stmpe);
+ if (ret)
+ return ret;
+
+ if (irq > 0) {
+ struct gpio_irq_chip *girq;
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ stmpe_gpio_irq, IRQF_ONESHOT,
+ "stmpe-gpio", stmpe_gpio);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+ return ret;
+ }
+
+ girq = &stmpe_gpio->chip.irq;
+ gpio_irq_chip_set_chip(girq, &stmpe_gpio_irq_chip);
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ girq->threaded = true;
+ girq->init_valid_mask = stmpe_init_irq_valid_mask;
+ }
+
+ return devm_gpiochip_add_data(&pdev->dev, &stmpe_gpio->chip, stmpe_gpio);
+}
+
+static struct platform_driver stmpe_gpio_driver = {
+ .driver = {
+ .suppress_bind_attrs = true,
+ .name = "stmpe-gpio",
+ },
+ .probe = stmpe_gpio_probe,
+};
+
+static int __init stmpe_gpio_init(void)
+{
+ return platform_driver_register(&stmpe_gpio_driver);
+}
+subsys_initcall(stmpe_gpio_init);
diff --git a/drivers/gpio/gpio-stp-xway.c b/drivers/gpio/gpio-stp-xway.c
new file mode 100644
index 0000000000..053d616f2e
--- /dev/null
+++ b/drivers/gpio/gpio-stp-xway.c
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *
+ * Copyright (C) 2012 John Crispin <john@phrozen.org>
+ */
+
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/of.h>
+#include <linux/mutex.h>
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+/*
+ * The Serial To Parallel (STP) is found on MIPS based Lantiq socs. It is a
+ * peripheral controller used to drive external shift register cascades. At most
+ * 3 groups of 8 bits can be driven. The hardware is able to allow the DSL modem
+ * to drive the 2 LSBs of the cascade automatically.
+ */
+
+/* control register 0 */
+#define XWAY_STP_CON0 0x00
+/* control register 1 */
+#define XWAY_STP_CON1 0x04
+/* data register 0 */
+#define XWAY_STP_CPU0 0x08
+/* data register 1 */
+#define XWAY_STP_CPU1 0x0C
+/* access register */
+#define XWAY_STP_AR 0x10
+
+/* software or hardware update select bit */
+#define XWAY_STP_CON_SWU BIT(31)
+
+/* automatic update rates */
+#define XWAY_STP_2HZ 0
+#define XWAY_STP_4HZ BIT(23)
+#define XWAY_STP_8HZ BIT(24)
+#define XWAY_STP_10HZ (BIT(24) | BIT(23))
+#define XWAY_STP_SPEED_MASK (BIT(23) | BIT(24) | BIT(25) | BIT(26) | BIT(27))
+
+#define XWAY_STP_FPIS_VALUE BIT(21)
+#define XWAY_STP_FPIS_MASK (BIT(20) | BIT(21))
+
+/* clock source for automatic update */
+#define XWAY_STP_UPD_FPI BIT(31)
+#define XWAY_STP_UPD_MASK (BIT(31) | BIT(30))
+
+/* let the adsl core drive the 2 LSBs */
+#define XWAY_STP_ADSL_SHIFT 24
+#define XWAY_STP_ADSL_MASK 0x3
+
+/* 2 groups of 3 bits can be driven by the phys */
+#define XWAY_STP_PHY_MASK 0x7
+#define XWAY_STP_PHY1_SHIFT 27
+#define XWAY_STP_PHY2_SHIFT 3
+#define XWAY_STP_PHY3_SHIFT 6
+#define XWAY_STP_PHY4_SHIFT 15
+
+/* STP has 3 groups of 8 bits */
+#define XWAY_STP_GROUP0 BIT(0)
+#define XWAY_STP_GROUP1 BIT(1)
+#define XWAY_STP_GROUP2 BIT(2)
+#define XWAY_STP_GROUP_MASK (0x7)
+
+/* Edge configuration bits */
+#define XWAY_STP_FALLING BIT(26)
+#define XWAY_STP_EDGE_MASK BIT(26)
+
+#define xway_stp_r32(m, reg) __raw_readl(m + reg)
+#define xway_stp_w32(m, val, reg) __raw_writel(val, m + reg)
+#define xway_stp_w32_mask(m, clear, set, reg) \
+ xway_stp_w32(m, (xway_stp_r32(m, reg) & ~(clear)) | (set), reg)
+
+struct xway_stp {
+ struct gpio_chip gc;
+ void __iomem *virt;
+ u32 edge; /* rising or falling edge triggered shift register */
+ u32 shadow; /* shadow the shift registers state */
+ u8 groups; /* we can drive 1-3 groups of 8bit each */
+ u8 dsl; /* the 2 LSBs can be driven by the dsl core */
+ u8 phy1; /* 3 bits can be driven by phy1 */
+ u8 phy2; /* 3 bits can be driven by phy2 */
+ u8 phy3; /* 3 bits can be driven by phy3 */
+ u8 phy4; /* 3 bits can be driven by phy4 */
+ u8 reserved; /* mask out the hw driven bits in gpio_request */
+};
+
+/**
+ * xway_stp_get() - gpio_chip->get - get gpios.
+ * @gc: Pointer to gpio_chip device structure.
+ * @gpio: GPIO signal number.
+ *
+ * Gets the shadow value.
+ */
+static int xway_stp_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct xway_stp *chip = gpiochip_get_data(gc);
+
+ return (xway_stp_r32(chip->virt, XWAY_STP_CPU0) & BIT(gpio));
+}
+
+/**
+ * xway_stp_set() - gpio_chip->set - set gpios.
+ * @gc: Pointer to gpio_chip device structure.
+ * @gpio: GPIO signal number.
+ * @val: Value to be written to specified signal.
+ *
+ * Set the shadow value and call ltq_ebu_apply.
+ */
+static void xway_stp_set(struct gpio_chip *gc, unsigned gpio, int val)
+{
+ struct xway_stp *chip = gpiochip_get_data(gc);
+
+ if (val)
+ chip->shadow |= BIT(gpio);
+ else
+ chip->shadow &= ~BIT(gpio);
+ xway_stp_w32(chip->virt, chip->shadow, XWAY_STP_CPU0);
+ if (!chip->reserved)
+ xway_stp_w32_mask(chip->virt, 0, XWAY_STP_CON_SWU, XWAY_STP_CON0);
+}
+
+/**
+ * xway_stp_dir_out() - gpio_chip->dir_out - set gpio direction.
+ * @gc: Pointer to gpio_chip device structure.
+ * @gpio: GPIO signal number.
+ * @val: Value to be written to specified signal.
+ *
+ * Same as xway_stp_set, always returns 0.
+ */
+static int xway_stp_dir_out(struct gpio_chip *gc, unsigned gpio, int val)
+{
+ xway_stp_set(gc, gpio, val);
+
+ return 0;
+}
+
+/**
+ * xway_stp_request() - gpio_chip->request
+ * @gc: Pointer to gpio_chip device structure.
+ * @gpio: GPIO signal number.
+ *
+ * We mask out the HW driven pins
+ */
+static int xway_stp_request(struct gpio_chip *gc, unsigned gpio)
+{
+ struct xway_stp *chip = gpiochip_get_data(gc);
+
+ if ((gpio < 8) && (chip->reserved & BIT(gpio))) {
+ dev_err(gc->parent, "GPIO %d is driven by hardware\n", gpio);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/**
+ * xway_stp_hw_init() - Configure the STP unit and enable the clock gate
+ * @chip: Pointer to the xway_stp chip structure
+ */
+static void xway_stp_hw_init(struct xway_stp *chip)
+{
+ /* sane defaults */
+ xway_stp_w32(chip->virt, 0, XWAY_STP_AR);
+ xway_stp_w32(chip->virt, 0, XWAY_STP_CPU0);
+ xway_stp_w32(chip->virt, 0, XWAY_STP_CPU1);
+ xway_stp_w32(chip->virt, XWAY_STP_CON_SWU, XWAY_STP_CON0);
+ xway_stp_w32(chip->virt, 0, XWAY_STP_CON1);
+
+ /* apply edge trigger settings for the shift register */
+ xway_stp_w32_mask(chip->virt, XWAY_STP_EDGE_MASK,
+ chip->edge, XWAY_STP_CON0);
+
+ /* apply led group settings */
+ xway_stp_w32_mask(chip->virt, XWAY_STP_GROUP_MASK,
+ chip->groups, XWAY_STP_CON1);
+
+ /* tell the hardware which pins are controlled by the dsl modem */
+ xway_stp_w32_mask(chip->virt,
+ XWAY_STP_ADSL_MASK << XWAY_STP_ADSL_SHIFT,
+ chip->dsl << XWAY_STP_ADSL_SHIFT,
+ XWAY_STP_CON0);
+
+ /* tell the hardware which pins are controlled by the phys */
+ xway_stp_w32_mask(chip->virt,
+ XWAY_STP_PHY_MASK << XWAY_STP_PHY1_SHIFT,
+ chip->phy1 << XWAY_STP_PHY1_SHIFT,
+ XWAY_STP_CON0);
+ xway_stp_w32_mask(chip->virt,
+ XWAY_STP_PHY_MASK << XWAY_STP_PHY2_SHIFT,
+ chip->phy2 << XWAY_STP_PHY2_SHIFT,
+ XWAY_STP_CON1);
+
+ if (of_machine_is_compatible("lantiq,grx390")
+ || of_machine_is_compatible("lantiq,ar10")) {
+ xway_stp_w32_mask(chip->virt,
+ XWAY_STP_PHY_MASK << XWAY_STP_PHY3_SHIFT,
+ chip->phy3 << XWAY_STP_PHY3_SHIFT,
+ XWAY_STP_CON1);
+ }
+
+ if (of_machine_is_compatible("lantiq,grx390")) {
+ xway_stp_w32_mask(chip->virt,
+ XWAY_STP_PHY_MASK << XWAY_STP_PHY4_SHIFT,
+ chip->phy4 << XWAY_STP_PHY4_SHIFT,
+ XWAY_STP_CON1);
+ }
+
+ /* mask out the hw driven bits in gpio_request */
+ chip->reserved = (chip->phy4 << 11) | (chip->phy3 << 8) | (chip->phy2 << 5)
+ | (chip->phy1 << 2) | chip->dsl;
+
+ /*
+ * if we have pins that are driven by hw, we need to tell the stp what
+ * clock to use as a timer.
+ */
+ if (chip->reserved) {
+ xway_stp_w32_mask(chip->virt, XWAY_STP_UPD_MASK,
+ XWAY_STP_UPD_FPI, XWAY_STP_CON1);
+ xway_stp_w32_mask(chip->virt, XWAY_STP_SPEED_MASK,
+ XWAY_STP_10HZ, XWAY_STP_CON1);
+ xway_stp_w32_mask(chip->virt, XWAY_STP_FPIS_MASK,
+ XWAY_STP_FPIS_VALUE, XWAY_STP_CON1);
+ }
+}
+
+static int xway_stp_probe(struct platform_device *pdev)
+{
+ u32 shadow, groups, dsl, phy;
+ struct xway_stp *chip;
+ struct clk *clk;
+ int ret = 0;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->virt = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(chip->virt))
+ return PTR_ERR(chip->virt);
+
+ chip->gc.parent = &pdev->dev;
+ chip->gc.label = "stp-xway";
+ chip->gc.direction_output = xway_stp_dir_out;
+ chip->gc.get = xway_stp_get;
+ chip->gc.set = xway_stp_set;
+ chip->gc.request = xway_stp_request;
+ chip->gc.base = -1;
+ chip->gc.owner = THIS_MODULE;
+
+ /* store the shadow value if one was passed by the devicetree */
+ if (!of_property_read_u32(pdev->dev.of_node, "lantiq,shadow", &shadow))
+ chip->shadow = shadow;
+
+ /* find out which gpio groups should be enabled */
+ if (!of_property_read_u32(pdev->dev.of_node, "lantiq,groups", &groups))
+ chip->groups = groups & XWAY_STP_GROUP_MASK;
+ else
+ chip->groups = XWAY_STP_GROUP0;
+ chip->gc.ngpio = fls(chip->groups) * 8;
+
+ /* find out which gpios are controlled by the dsl core */
+ if (!of_property_read_u32(pdev->dev.of_node, "lantiq,dsl", &dsl))
+ chip->dsl = dsl & XWAY_STP_ADSL_MASK;
+
+ /* find out which gpios are controlled by the phys */
+ if (of_machine_is_compatible("lantiq,ar9") ||
+ of_machine_is_compatible("lantiq,gr9") ||
+ of_machine_is_compatible("lantiq,vr9") ||
+ of_machine_is_compatible("lantiq,ar10") ||
+ of_machine_is_compatible("lantiq,grx390")) {
+ if (!of_property_read_u32(pdev->dev.of_node, "lantiq,phy1", &phy))
+ chip->phy1 = phy & XWAY_STP_PHY_MASK;
+ if (!of_property_read_u32(pdev->dev.of_node, "lantiq,phy2", &phy))
+ chip->phy2 = phy & XWAY_STP_PHY_MASK;
+ }
+
+ if (of_machine_is_compatible("lantiq,ar10") ||
+ of_machine_is_compatible("lantiq,grx390")) {
+ if (!of_property_read_u32(pdev->dev.of_node, "lantiq,phy3", &phy))
+ chip->phy3 = phy & XWAY_STP_PHY_MASK;
+ }
+
+ if (of_machine_is_compatible("lantiq,grx390")) {
+ if (!of_property_read_u32(pdev->dev.of_node, "lantiq,phy4", &phy))
+ chip->phy4 = phy & XWAY_STP_PHY_MASK;
+ }
+
+ /* check which edge trigger we should use, default to a falling edge */
+ if (!of_property_read_bool(pdev->dev.of_node, "lantiq,rising"))
+ chip->edge = XWAY_STP_FALLING;
+
+ clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(&pdev->dev, "Failed to get clock\n");
+ return PTR_ERR(clk);
+ }
+
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ return ret;
+
+ xway_stp_hw_init(chip);
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &chip->gc, chip);
+ if (ret) {
+ clk_disable_unprepare(clk);
+ return ret;
+ }
+
+ dev_info(&pdev->dev, "Init done\n");
+
+ return 0;
+}
+
+static const struct of_device_id xway_stp_match[] = {
+ { .compatible = "lantiq,gpio-stp-xway" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, xway_stp_match);
+
+static struct platform_driver xway_stp_driver = {
+ .probe = xway_stp_probe,
+ .driver = {
+ .name = "gpio-stp-xway",
+ .of_match_table = xway_stp_match,
+ },
+};
+
+static int __init xway_stp_init(void)
+{
+ return platform_driver_register(&xway_stp_driver);
+}
+
+subsys_initcall(xway_stp_init);
diff --git a/drivers/gpio/gpio-syscon.c b/drivers/gpio/gpio-syscon.c
new file mode 100644
index 0000000000..6e1a2581e6
--- /dev/null
+++ b/drivers/gpio/gpio-syscon.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SYSCON GPIO driver
+ *
+ * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+
+#define GPIO_SYSCON_FEAT_IN BIT(0)
+#define GPIO_SYSCON_FEAT_OUT BIT(1)
+#define GPIO_SYSCON_FEAT_DIR BIT(2)
+
+/* SYSCON driver is designed to use 32-bit wide registers */
+#define SYSCON_REG_SIZE (4)
+#define SYSCON_REG_BITS (SYSCON_REG_SIZE * 8)
+
+/**
+ * struct syscon_gpio_data - Configuration for the device.
+ * @compatible: SYSCON driver compatible string.
+ * @flags: Set of GPIO_SYSCON_FEAT_ flags:
+ * GPIO_SYSCON_FEAT_IN: GPIOs supports input,
+ * GPIO_SYSCON_FEAT_OUT: GPIOs supports output,
+ * GPIO_SYSCON_FEAT_DIR: GPIOs supports switch direction.
+ * @bit_count: Number of bits used as GPIOs.
+ * @dat_bit_offset: Offset (in bits) to the first GPIO bit.
+ * @dir_bit_offset: Optional offset (in bits) to the first bit to switch
+ * GPIO direction (Used with GPIO_SYSCON_FEAT_DIR flag).
+ * @set: HW specific callback to assigns output value
+ * for signal "offset"
+ */
+
+struct syscon_gpio_data {
+ unsigned int flags;
+ unsigned int bit_count;
+ unsigned int dat_bit_offset;
+ unsigned int dir_bit_offset;
+ void (*set)(struct gpio_chip *chip,
+ unsigned offset, int value);
+};
+
+struct syscon_gpio_priv {
+ struct gpio_chip chip;
+ struct regmap *syscon;
+ const struct syscon_gpio_data *data;
+ u32 dreg_offset;
+ u32 dir_reg_offset;
+};
+
+static int syscon_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct syscon_gpio_priv *priv = gpiochip_get_data(chip);
+ unsigned int val, offs;
+ int ret;
+
+ offs = priv->dreg_offset + priv->data->dat_bit_offset + offset;
+
+ ret = regmap_read(priv->syscon,
+ (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, &val);
+ if (ret)
+ return ret;
+
+ return !!(val & BIT(offs % SYSCON_REG_BITS));
+}
+
+static void syscon_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
+{
+ struct syscon_gpio_priv *priv = gpiochip_get_data(chip);
+ unsigned int offs;
+
+ offs = priv->dreg_offset + priv->data->dat_bit_offset + offset;
+
+ regmap_update_bits(priv->syscon,
+ (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
+ BIT(offs % SYSCON_REG_BITS),
+ val ? BIT(offs % SYSCON_REG_BITS) : 0);
+}
+
+static int syscon_gpio_dir_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct syscon_gpio_priv *priv = gpiochip_get_data(chip);
+
+ if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) {
+ unsigned int offs;
+
+ offs = priv->dir_reg_offset +
+ priv->data->dir_bit_offset + offset;
+
+ regmap_update_bits(priv->syscon,
+ (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
+ BIT(offs % SYSCON_REG_BITS), 0);
+ }
+
+ return 0;
+}
+
+static int syscon_gpio_dir_out(struct gpio_chip *chip, unsigned offset, int val)
+{
+ struct syscon_gpio_priv *priv = gpiochip_get_data(chip);
+
+ if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) {
+ unsigned int offs;
+
+ offs = priv->dir_reg_offset +
+ priv->data->dir_bit_offset + offset;
+
+ regmap_update_bits(priv->syscon,
+ (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
+ BIT(offs % SYSCON_REG_BITS),
+ BIT(offs % SYSCON_REG_BITS));
+ }
+
+ chip->set(chip, offset, val);
+
+ return 0;
+}
+
+static const struct syscon_gpio_data clps711x_mctrl_gpio = {
+ /* ARM CLPS711X SYSFLG1 Bits 8-10 */
+ .flags = GPIO_SYSCON_FEAT_IN,
+ .bit_count = 3,
+ .dat_bit_offset = 0x40 * 8 + 8,
+};
+
+static void rockchip_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int val)
+{
+ struct syscon_gpio_priv *priv = gpiochip_get_data(chip);
+ unsigned int offs;
+ u8 bit;
+ u32 data;
+ int ret;
+
+ offs = priv->dreg_offset + priv->data->dat_bit_offset + offset;
+ bit = offs % SYSCON_REG_BITS;
+ data = (val ? BIT(bit) : 0) | BIT(bit + 16);
+ ret = regmap_write(priv->syscon,
+ (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
+ data);
+ if (ret < 0)
+ dev_err(chip->parent, "gpio write failed ret(%d)\n", ret);
+}
+
+static const struct syscon_gpio_data rockchip_rk3328_gpio_mute = {
+ /* RK3328 GPIO_MUTE is an output only pin at GRF_SOC_CON10[1] */
+ .flags = GPIO_SYSCON_FEAT_OUT,
+ .bit_count = 1,
+ .dat_bit_offset = 0x0428 * 8 + 1,
+ .set = rockchip_gpio_set,
+};
+
+#define KEYSTONE_LOCK_BIT BIT(0)
+
+static void keystone_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
+{
+ struct syscon_gpio_priv *priv = gpiochip_get_data(chip);
+ unsigned int offs;
+ int ret;
+
+ offs = priv->dreg_offset + priv->data->dat_bit_offset + offset;
+
+ if (!val)
+ return;
+
+ ret = regmap_update_bits(
+ priv->syscon,
+ (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
+ BIT(offs % SYSCON_REG_BITS) | KEYSTONE_LOCK_BIT,
+ BIT(offs % SYSCON_REG_BITS) | KEYSTONE_LOCK_BIT);
+ if (ret < 0)
+ dev_err(chip->parent, "gpio write failed ret(%d)\n", ret);
+}
+
+static const struct syscon_gpio_data keystone_dsp_gpio = {
+ /* ARM Keystone 2 */
+ .flags = GPIO_SYSCON_FEAT_OUT,
+ .bit_count = 28,
+ .dat_bit_offset = 4,
+ .set = keystone_gpio_set,
+};
+
+static const struct of_device_id syscon_gpio_ids[] = {
+ {
+ .compatible = "cirrus,ep7209-mctrl-gpio",
+ .data = &clps711x_mctrl_gpio,
+ },
+ {
+ .compatible = "ti,keystone-dsp-gpio",
+ .data = &keystone_dsp_gpio,
+ },
+ {
+ .compatible = "rockchip,rk3328-grf-gpio",
+ .data = &rockchip_rk3328_gpio_mute,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, syscon_gpio_ids);
+
+static int syscon_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct syscon_gpio_priv *priv;
+ struct device_node *np = dev->of_node;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->data = of_device_get_match_data(dev);
+
+ priv->syscon = syscon_regmap_lookup_by_phandle(np, "gpio,syscon-dev");
+ if (IS_ERR(priv->syscon) && np->parent)
+ priv->syscon = syscon_node_to_regmap(np->parent);
+ if (IS_ERR(priv->syscon))
+ return PTR_ERR(priv->syscon);
+
+ ret = of_property_read_u32_index(np, "gpio,syscon-dev", 1,
+ &priv->dreg_offset);
+ if (ret)
+ dev_err(dev, "can't read the data register offset!\n");
+
+ priv->dreg_offset <<= 3;
+
+ ret = of_property_read_u32_index(np, "gpio,syscon-dev", 2,
+ &priv->dir_reg_offset);
+ if (ret)
+ dev_dbg(dev, "can't read the dir register offset!\n");
+
+ priv->dir_reg_offset <<= 3;
+
+ priv->chip.parent = dev;
+ priv->chip.owner = THIS_MODULE;
+ priv->chip.label = dev_name(dev);
+ priv->chip.base = -1;
+ priv->chip.ngpio = priv->data->bit_count;
+ priv->chip.get = syscon_gpio_get;
+ if (priv->data->flags & GPIO_SYSCON_FEAT_IN)
+ priv->chip.direction_input = syscon_gpio_dir_in;
+ if (priv->data->flags & GPIO_SYSCON_FEAT_OUT) {
+ priv->chip.set = priv->data->set ? : syscon_gpio_set;
+ priv->chip.direction_output = syscon_gpio_dir_out;
+ }
+
+ return devm_gpiochip_add_data(&pdev->dev, &priv->chip, priv);
+}
+
+static struct platform_driver syscon_gpio_driver = {
+ .driver = {
+ .name = "gpio-syscon",
+ .of_match_table = syscon_gpio_ids,
+ },
+ .probe = syscon_gpio_probe,
+};
+module_platform_driver(syscon_gpio_driver);
+
+MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
+MODULE_DESCRIPTION("SYSCON GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-tangier.c b/drivers/gpio/gpio-tangier.c
new file mode 100644
index 0000000000..7ce3eddaed
--- /dev/null
+++ b/drivers/gpio/gpio-tangier.c
@@ -0,0 +1,538 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Tangier GPIO driver
+ *
+ * Copyright (c) 2016, 2021, 2023 Intel Corporation.
+ *
+ * Authors: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+ * Pandith N <pandith.n@intel.com>
+ * Raag Jadav <raag.jadav@intel.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/spinlock.h>
+#include <linux/string_helpers.h>
+#include <linux/types.h>
+
+#include <linux/gpio/driver.h>
+
+#include "gpio-tangier.h"
+
+#define GCCR 0x000 /* Controller configuration */
+#define GPLR 0x004 /* Pin level r/o */
+#define GPDR 0x01c /* Pin direction */
+#define GPSR 0x034 /* Pin set w/o */
+#define GPCR 0x04c /* Pin clear w/o */
+#define GRER 0x064 /* Rising edge detect */
+#define GFER 0x07c /* Falling edge detect */
+#define GFBR 0x094 /* Glitch filter bypass */
+#define GIMR 0x0ac /* Interrupt mask */
+#define GISR 0x0c4 /* Interrupt source */
+#define GITR 0x300 /* Input type */
+#define GLPR 0x318 /* Level input polarity */
+
+/**
+ * struct tng_gpio_context - Context to be saved during suspend-resume
+ * @level: Pin level
+ * @gpdr: Pin direction
+ * @grer: Rising edge detect enable
+ * @gfer: Falling edge detect enable
+ * @gimr: Interrupt mask
+ * @gwmr: Wake mask
+ */
+struct tng_gpio_context {
+ u32 level;
+ u32 gpdr;
+ u32 grer;
+ u32 gfer;
+ u32 gimr;
+ u32 gwmr;
+};
+
+static void __iomem *gpio_reg(struct gpio_chip *chip, unsigned int offset,
+ unsigned int reg)
+{
+ struct tng_gpio *priv = gpiochip_get_data(chip);
+ u8 reg_offset = offset / 32;
+
+ return priv->reg_base + reg + reg_offset * 4;
+}
+
+static void __iomem *gpio_reg_and_bit(struct gpio_chip *chip, unsigned int offset,
+ unsigned int reg, u8 *bit)
+{
+ struct tng_gpio *priv = gpiochip_get_data(chip);
+ u8 reg_offset = offset / 32;
+ u8 shift = offset % 32;
+
+ *bit = shift;
+ return priv->reg_base + reg + reg_offset * 4;
+}
+
+static int tng_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ void __iomem *gplr;
+ u8 shift;
+
+ gplr = gpio_reg_and_bit(chip, offset, GPLR, &shift);
+
+ return !!(readl(gplr) & BIT(shift));
+}
+
+static void tng_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct tng_gpio *priv = gpiochip_get_data(chip);
+ unsigned long flags;
+ void __iomem *reg;
+ u8 shift;
+
+ reg = gpio_reg_and_bit(chip, offset, value ? GPSR : GPCR, &shift);
+
+ raw_spin_lock_irqsave(&priv->lock, flags);
+
+ writel(BIT(shift), reg);
+
+ raw_spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static int tng_gpio_direction_input(struct gpio_chip *chip, unsigned int offset)
+{
+ struct tng_gpio *priv = gpiochip_get_data(chip);
+ unsigned long flags;
+ void __iomem *gpdr;
+ u32 value;
+ u8 shift;
+
+ gpdr = gpio_reg_and_bit(chip, offset, GPDR, &shift);
+
+ raw_spin_lock_irqsave(&priv->lock, flags);
+
+ value = readl(gpdr);
+ value &= ~BIT(shift);
+ writel(value, gpdr);
+
+ raw_spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static int tng_gpio_direction_output(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct tng_gpio *priv = gpiochip_get_data(chip);
+ unsigned long flags;
+ void __iomem *gpdr;
+ u8 shift;
+
+ gpdr = gpio_reg_and_bit(chip, offset, GPDR, &shift);
+ tng_gpio_set(chip, offset, value);
+
+ raw_spin_lock_irqsave(&priv->lock, flags);
+
+ value = readl(gpdr);
+ value |= BIT(shift);
+ writel(value, gpdr);
+
+ raw_spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static int tng_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ void __iomem *gpdr;
+ u8 shift;
+
+ gpdr = gpio_reg_and_bit(chip, offset, GPDR, &shift);
+
+ if (readl(gpdr) & BIT(shift))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int tng_gpio_set_debounce(struct gpio_chip *chip, unsigned int offset,
+ unsigned int debounce)
+{
+ struct tng_gpio *priv = gpiochip_get_data(chip);
+ unsigned long flags;
+ void __iomem *gfbr;
+ u32 value;
+ u8 shift;
+
+ gfbr = gpio_reg_and_bit(chip, offset, GFBR, &shift);
+
+ raw_spin_lock_irqsave(&priv->lock, flags);
+
+ value = readl(gfbr);
+ if (debounce)
+ value &= ~BIT(shift);
+ else
+ value |= BIT(shift);
+ writel(value, gfbr);
+
+ raw_spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static int tng_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ u32 debounce;
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ case PIN_CONFIG_BIAS_PULL_UP:
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ return gpiochip_generic_config(chip, offset, config);
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ debounce = pinconf_to_config_argument(config);
+ return tng_gpio_set_debounce(chip, offset, debounce);
+ default:
+ return -ENOTSUPP;
+ }
+}
+
+static void tng_irq_ack(struct irq_data *d)
+{
+ struct tng_gpio *priv = irq_data_get_irq_chip_data(d);
+ irq_hw_number_t gpio = irqd_to_hwirq(d);
+ unsigned long flags;
+ void __iomem *gisr;
+ u8 shift;
+
+ gisr = gpio_reg_and_bit(&priv->chip, gpio, GISR, &shift);
+
+ raw_spin_lock_irqsave(&priv->lock, flags);
+ writel(BIT(shift), gisr);
+ raw_spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static void tng_irq_unmask_mask(struct tng_gpio *priv, u32 gpio, bool unmask)
+{
+ unsigned long flags;
+ void __iomem *gimr;
+ u32 value;
+ u8 shift;
+
+ gimr = gpio_reg_and_bit(&priv->chip, gpio, GIMR, &shift);
+
+ raw_spin_lock_irqsave(&priv->lock, flags);
+
+ value = readl(gimr);
+ if (unmask)
+ value |= BIT(shift);
+ else
+ value &= ~BIT(shift);
+ writel(value, gimr);
+
+ raw_spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static void tng_irq_mask(struct irq_data *d)
+{
+ struct tng_gpio *priv = irq_data_get_irq_chip_data(d);
+ irq_hw_number_t gpio = irqd_to_hwirq(d);
+
+ tng_irq_unmask_mask(priv, gpio, false);
+ gpiochip_disable_irq(&priv->chip, gpio);
+}
+
+static void tng_irq_unmask(struct irq_data *d)
+{
+ struct tng_gpio *priv = irq_data_get_irq_chip_data(d);
+ irq_hw_number_t gpio = irqd_to_hwirq(d);
+
+ gpiochip_enable_irq(&priv->chip, gpio);
+ tng_irq_unmask_mask(priv, gpio, true);
+}
+
+static int tng_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct tng_gpio *priv = gpiochip_get_data(gc);
+ irq_hw_number_t gpio = irqd_to_hwirq(d);
+ void __iomem *grer = gpio_reg(&priv->chip, gpio, GRER);
+ void __iomem *gfer = gpio_reg(&priv->chip, gpio, GFER);
+ void __iomem *gitr = gpio_reg(&priv->chip, gpio, GITR);
+ void __iomem *glpr = gpio_reg(&priv->chip, gpio, GLPR);
+ u8 shift = gpio % 32;
+ unsigned long flags;
+ u32 value;
+
+ raw_spin_lock_irqsave(&priv->lock, flags);
+
+ value = readl(grer);
+ if (type & IRQ_TYPE_EDGE_RISING)
+ value |= BIT(shift);
+ else
+ value &= ~BIT(shift);
+ writel(value, grer);
+
+ value = readl(gfer);
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ value |= BIT(shift);
+ else
+ value &= ~BIT(shift);
+ writel(value, gfer);
+
+ /*
+ * To prevent glitches from triggering an unintended level interrupt,
+ * configure GLPR register first and then configure GITR.
+ */
+ value = readl(glpr);
+ if (type & IRQ_TYPE_LEVEL_LOW)
+ value |= BIT(shift);
+ else
+ value &= ~BIT(shift);
+ writel(value, glpr);
+
+ if (type & IRQ_TYPE_LEVEL_MASK) {
+ value = readl(gitr);
+ value |= BIT(shift);
+ writel(value, gitr);
+
+ irq_set_handler_locked(d, handle_level_irq);
+ } else if (type & IRQ_TYPE_EDGE_BOTH) {
+ value = readl(gitr);
+ value &= ~BIT(shift);
+ writel(value, gitr);
+
+ irq_set_handler_locked(d, handle_edge_irq);
+ }
+
+ raw_spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static int tng_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct tng_gpio *priv = gpiochip_get_data(gc);
+ irq_hw_number_t gpio = irqd_to_hwirq(d);
+ void __iomem *gwmr = gpio_reg(&priv->chip, gpio, priv->wake_regs.gwmr);
+ void __iomem *gwsr = gpio_reg(&priv->chip, gpio, priv->wake_regs.gwsr);
+ u8 shift = gpio % 32;
+ unsigned long flags;
+ u32 value;
+
+ raw_spin_lock_irqsave(&priv->lock, flags);
+
+ /* Clear the existing wake status */
+ writel(BIT(shift), gwsr);
+
+ value = readl(gwmr);
+ if (on)
+ value |= BIT(shift);
+ else
+ value &= ~BIT(shift);
+ writel(value, gwmr);
+
+ raw_spin_unlock_irqrestore(&priv->lock, flags);
+
+ dev_dbg(priv->dev, "%s wake for gpio %lu\n", str_enable_disable(on), gpio);
+ return 0;
+}
+
+static const struct irq_chip tng_irqchip = {
+ .name = "gpio-tangier",
+ .irq_ack = tng_irq_ack,
+ .irq_mask = tng_irq_mask,
+ .irq_unmask = tng_irq_unmask,
+ .irq_set_type = tng_irq_set_type,
+ .irq_set_wake = tng_irq_set_wake,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static void tng_irq_handler(struct irq_desc *desc)
+{
+ struct gpio_chip *gc = irq_desc_get_handler_data(desc);
+ struct tng_gpio *priv = gpiochip_get_data(gc);
+ struct irq_chip *irqchip = irq_desc_get_chip(desc);
+ unsigned long base, gpio;
+
+ chained_irq_enter(irqchip, desc);
+
+ /* Check GPIO controller to check which pin triggered the interrupt */
+ for (base = 0; base < priv->chip.ngpio; base += 32) {
+ void __iomem *gisr = gpio_reg(&priv->chip, base, GISR);
+ void __iomem *gimr = gpio_reg(&priv->chip, base, GIMR);
+ unsigned long pending, enabled;
+
+ pending = readl(gisr);
+ enabled = readl(gimr);
+
+ /* Only interrupts that are enabled */
+ pending &= enabled;
+
+ for_each_set_bit(gpio, &pending, 32)
+ generic_handle_domain_irq(gc->irq.domain, base + gpio);
+ }
+
+ chained_irq_exit(irqchip, desc);
+}
+
+static int tng_irq_init_hw(struct gpio_chip *chip)
+{
+ struct tng_gpio *priv = gpiochip_get_data(chip);
+ void __iomem *reg;
+ unsigned int base;
+
+ for (base = 0; base < priv->chip.ngpio; base += 32) {
+ /* Clear the rising-edge detect register */
+ reg = gpio_reg(&priv->chip, base, GRER);
+ writel(0, reg);
+
+ /* Clear the falling-edge detect register */
+ reg = gpio_reg(&priv->chip, base, GFER);
+ writel(0, reg);
+ }
+
+ return 0;
+}
+
+static int tng_gpio_add_pin_ranges(struct gpio_chip *chip)
+{
+ struct tng_gpio *priv = gpiochip_get_data(chip);
+ const struct tng_gpio_pinrange *range;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < priv->pin_info.nranges; i++) {
+ range = &priv->pin_info.pin_ranges[i];
+ ret = gpiochip_add_pin_range(&priv->chip,
+ priv->pin_info.name,
+ range->gpio_base,
+ range->pin_base,
+ range->npins);
+ if (ret) {
+ dev_err(priv->dev, "failed to add GPIO pin range\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+int devm_tng_gpio_probe(struct device *dev, struct tng_gpio *gpio)
+{
+ const struct tng_gpio_info *info = &gpio->info;
+ size_t nctx = DIV_ROUND_UP(info->ngpio, 32);
+ struct gpio_irq_chip *girq;
+ int ret;
+
+ gpio->ctx = devm_kcalloc(dev, nctx, sizeof(*gpio->ctx), GFP_KERNEL);
+ if (!gpio->ctx)
+ return -ENOMEM;
+
+ gpio->chip.label = dev_name(dev);
+ gpio->chip.parent = dev;
+ gpio->chip.request = gpiochip_generic_request;
+ gpio->chip.free = gpiochip_generic_free;
+ gpio->chip.direction_input = tng_gpio_direction_input;
+ gpio->chip.direction_output = tng_gpio_direction_output;
+ gpio->chip.get = tng_gpio_get;
+ gpio->chip.set = tng_gpio_set;
+ gpio->chip.get_direction = tng_gpio_get_direction;
+ gpio->chip.set_config = tng_gpio_set_config;
+ gpio->chip.base = info->base;
+ gpio->chip.ngpio = info->ngpio;
+ gpio->chip.can_sleep = false;
+ gpio->chip.add_pin_ranges = tng_gpio_add_pin_ranges;
+
+ raw_spin_lock_init(&gpio->lock);
+
+ girq = &gpio->chip.irq;
+ gpio_irq_chip_set_chip(girq, &tng_irqchip);
+ girq->init_hw = tng_irq_init_hw;
+ girq->parent_handler = tng_irq_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(dev, girq->num_parents,
+ sizeof(*girq->parents), GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+
+ girq->parents[0] = gpio->irq;
+ girq->first = info->first;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+
+ ret = devm_gpiochip_add_data(dev, &gpio->chip, gpio);
+ if (ret)
+ return dev_err_probe(dev, ret, "gpiochip_add error\n");
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(devm_tng_gpio_probe, GPIO_TANGIER);
+
+int tng_gpio_suspend(struct device *dev)
+{
+ struct tng_gpio *priv = dev_get_drvdata(dev);
+ struct tng_gpio_context *ctx = priv->ctx;
+ unsigned long flags;
+ unsigned int base;
+
+ raw_spin_lock_irqsave(&priv->lock, flags);
+
+ for (base = 0; base < priv->chip.ngpio; base += 32, ctx++) {
+ /* GPLR is RO, values read will be restored using GPSR */
+ ctx->level = readl(gpio_reg(&priv->chip, base, GPLR));
+
+ ctx->gpdr = readl(gpio_reg(&priv->chip, base, GPDR));
+ ctx->grer = readl(gpio_reg(&priv->chip, base, GRER));
+ ctx->gfer = readl(gpio_reg(&priv->chip, base, GFER));
+ ctx->gimr = readl(gpio_reg(&priv->chip, base, GIMR));
+
+ ctx->gwmr = readl(gpio_reg(&priv->chip, base, priv->wake_regs.gwmr));
+ }
+
+ raw_spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(tng_gpio_suspend, GPIO_TANGIER);
+
+int tng_gpio_resume(struct device *dev)
+{
+ struct tng_gpio *priv = dev_get_drvdata(dev);
+ struct tng_gpio_context *ctx = priv->ctx;
+ unsigned long flags;
+ unsigned int base;
+
+ raw_spin_lock_irqsave(&priv->lock, flags);
+
+ for (base = 0; base < priv->chip.ngpio; base += 32, ctx++) {
+ /* GPLR is RO, values read will be restored using GPSR */
+ writel(ctx->level, gpio_reg(&priv->chip, base, GPSR));
+
+ writel(ctx->gpdr, gpio_reg(&priv->chip, base, GPDR));
+ writel(ctx->grer, gpio_reg(&priv->chip, base, GRER));
+ writel(ctx->gfer, gpio_reg(&priv->chip, base, GFER));
+ writel(ctx->gimr, gpio_reg(&priv->chip, base, GIMR));
+
+ writel(ctx->gwmr, gpio_reg(&priv->chip, base, priv->wake_regs.gwmr));
+ }
+
+ raw_spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(tng_gpio_resume, GPIO_TANGIER);
+
+MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>");
+MODULE_AUTHOR("Pandith N <pandith.n@intel.com>");
+MODULE_AUTHOR("Raag Jadav <raag.jadav@intel.com>");
+MODULE_DESCRIPTION("Intel Tangier GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-tangier.h b/drivers/gpio/gpio-tangier.h
new file mode 100644
index 0000000000..16c4f22908
--- /dev/null
+++ b/drivers/gpio/gpio-tangier.h
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel Tangier GPIO functions
+ *
+ * Copyright (c) 2016, 2021, 2023 Intel Corporation.
+ *
+ * Authors: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+ * Pandith N <pandith.n@intel.com>
+ * Raag Jadav <raag.jadav@intel.com>
+ */
+
+#ifndef _GPIO_TANGIER_H_
+#define _GPIO_TANGIER_H_
+
+#include <linux/gpio/driver.h>
+#include <linux/spinlock_types.h>
+#include <linux/types.h>
+
+struct device;
+
+struct tng_gpio_context;
+
+/* Elkhart Lake specific wake registers */
+#define GWMR_EHL 0x100 /* Wake mask */
+#define GWSR_EHL 0x118 /* Wake source */
+#define GSIR_EHL 0x130 /* Secure input */
+
+/* Merrifield specific wake registers */
+#define GWMR_MRFLD 0x400 /* Wake mask */
+#define GWSR_MRFLD 0x418 /* Wake source */
+#define GSIR_MRFLD 0xc00 /* Secure input */
+
+/**
+ * struct tng_wake_regs - Platform specific wake registers
+ * @gwmr: Wake mask
+ * @gwsr: Wake source
+ * @gsir: Secure input
+ */
+struct tng_wake_regs {
+ u32 gwmr;
+ u32 gwsr;
+ u32 gsir;
+};
+
+/**
+ * struct tng_gpio_pinrange - Map pin numbers to gpio numbers
+ * @gpio_base: Starting GPIO number of this range
+ * @pin_base: Starting pin number of this range
+ * @npins: Number of pins in this range
+ */
+struct tng_gpio_pinrange {
+ unsigned int gpio_base;
+ unsigned int pin_base;
+ unsigned int npins;
+};
+
+#define GPIO_PINRANGE(gstart, gend, pstart) \
+(struct tng_gpio_pinrange) { \
+ .gpio_base = (gstart), \
+ .pin_base = (pstart), \
+ .npins = (gend) - (gstart) + 1, \
+ }
+
+/**
+ * struct tng_gpio_pin_info - Platform specific pinout information
+ * @pin_ranges: Pin to GPIO mapping
+ * @nranges: Number of pin ranges
+ * @name: Respective pinctrl device name
+ */
+struct tng_gpio_pin_info {
+ const struct tng_gpio_pinrange *pin_ranges;
+ unsigned int nranges;
+ const char *name;
+};
+
+/**
+ * struct tng_gpio_info - Platform specific GPIO and IRQ information
+ * @base: GPIO base to start numbering with
+ * @ngpio: Amount of GPIOs supported by the controller
+ * @first: First IRQ to start numbering with
+ */
+struct tng_gpio_info {
+ int base;
+ u16 ngpio;
+ unsigned int first;
+};
+
+/**
+ * struct tng_gpio - Platform specific private data
+ * @chip: Instance of the struct gpio_chip
+ * @reg_base: Base address of MMIO registers
+ * @irq: Interrupt for the GPIO device
+ * @lock: Synchronization lock to prevent I/O race conditions
+ * @dev: The GPIO device
+ * @ctx: Context to be saved during suspend-resume
+ * @wake_regs: Platform specific wake registers
+ * @pin_info: Platform specific pinout information
+ * @info: Platform specific GPIO and IRQ information
+ */
+struct tng_gpio {
+ struct gpio_chip chip;
+ void __iomem *reg_base;
+ int irq;
+ raw_spinlock_t lock;
+ struct device *dev;
+ struct tng_gpio_context *ctx;
+ struct tng_wake_regs wake_regs;
+ struct tng_gpio_pin_info pin_info;
+ struct tng_gpio_info info;
+};
+
+int devm_tng_gpio_probe(struct device *dev, struct tng_gpio *gpio);
+
+int tng_gpio_suspend(struct device *dev);
+int tng_gpio_resume(struct device *dev);
+
+#endif /* _GPIO_TANGIER_H_ */
diff --git a/drivers/gpio/gpio-tb10x.c b/drivers/gpio/gpio-tb10x.c
new file mode 100644
index 0000000000..f96d260a4a
--- /dev/null
+++ b/drivers/gpio/gpio-tb10x.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Abilis Systems MODULE DESCRIPTION
+ *
+ * Copyright (C) Abilis Systems 2013
+ *
+ * Authors: Sascha Leuenberger <sascha.leuenberger@abilis.com>
+ * Christian Ruppert <christian.ruppert@abilis.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/driver.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/spinlock.h>
+#include <linux/bitops.h>
+#include <linux/pinctrl/consumer.h>
+
+#define TB10X_GPIO_DIR_IN (0x00000000)
+#define TB10X_GPIO_DIR_OUT (0x00000001)
+#define OFFSET_TO_REG_DDR (0x00)
+#define OFFSET_TO_REG_DATA (0x04)
+#define OFFSET_TO_REG_INT_EN (0x08)
+#define OFFSET_TO_REG_CHANGE (0x0C)
+#define OFFSET_TO_REG_WRMASK (0x10)
+#define OFFSET_TO_REG_INT_TYPE (0x14)
+
+
+/**
+ * @base: register base address
+ * @domain: IRQ domain of GPIO generated interrupts managed by this controller
+ * @irq: Interrupt line of parent interrupt controller
+ * @gc: gpio_chip structure associated to this GPIO controller
+ */
+struct tb10x_gpio {
+ void __iomem *base;
+ struct irq_domain *domain;
+ int irq;
+ struct gpio_chip gc;
+};
+
+static inline u32 tb10x_reg_read(struct tb10x_gpio *gpio, unsigned int offs)
+{
+ return ioread32(gpio->base + offs);
+}
+
+static inline void tb10x_reg_write(struct tb10x_gpio *gpio, unsigned int offs,
+ u32 val)
+{
+ iowrite32(val, gpio->base + offs);
+}
+
+static inline void tb10x_set_bits(struct tb10x_gpio *gpio, unsigned int offs,
+ u32 mask, u32 val)
+{
+ u32 r;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gpio->gc.bgpio_lock, flags);
+
+ r = tb10x_reg_read(gpio, offs);
+ r = (r & ~mask) | (val & mask);
+
+ tb10x_reg_write(gpio, offs, r);
+
+ raw_spin_unlock_irqrestore(&gpio->gc.bgpio_lock, flags);
+}
+
+static int tb10x_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ struct tb10x_gpio *tb10x_gpio = gpiochip_get_data(chip);
+
+ return irq_create_mapping(tb10x_gpio->domain, offset);
+}
+
+static int tb10x_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ if ((type & IRQF_TRIGGER_MASK) != IRQ_TYPE_EDGE_BOTH) {
+ pr_err("Only (both) edge triggered interrupts supported.\n");
+ return -EINVAL;
+ }
+
+ irqd_set_trigger_type(data, type);
+
+ return IRQ_SET_MASK_OK;
+}
+
+static irqreturn_t tb10x_gpio_irq_cascade(int irq, void *data)
+{
+ struct tb10x_gpio *tb10x_gpio = data;
+ u32 r = tb10x_reg_read(tb10x_gpio, OFFSET_TO_REG_CHANGE);
+ u32 m = tb10x_reg_read(tb10x_gpio, OFFSET_TO_REG_INT_EN);
+ const unsigned long bits = r & m;
+ int i;
+
+ for_each_set_bit(i, &bits, 32)
+ generic_handle_domain_irq(tb10x_gpio->domain, i);
+
+ return IRQ_HANDLED;
+}
+
+static int tb10x_gpio_probe(struct platform_device *pdev)
+{
+ struct tb10x_gpio *tb10x_gpio;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ int ret = -EBUSY;
+ u32 ngpio;
+
+ if (!np)
+ return -EINVAL;
+
+ if (of_property_read_u32(np, "abilis,ngpio", &ngpio))
+ return -EINVAL;
+
+ tb10x_gpio = devm_kzalloc(dev, sizeof(*tb10x_gpio), GFP_KERNEL);
+ if (tb10x_gpio == NULL)
+ return -ENOMEM;
+
+ tb10x_gpio->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(tb10x_gpio->base))
+ return PTR_ERR(tb10x_gpio->base);
+
+ tb10x_gpio->gc.label =
+ devm_kasprintf(dev, GFP_KERNEL, "%pOF", pdev->dev.of_node);
+ if (!tb10x_gpio->gc.label)
+ return -ENOMEM;
+
+ /*
+ * Initialize generic GPIO with one single register for reading and setting
+ * the lines, no special set or clear registers and a data direction register
+ * wher 1 means "output".
+ */
+ ret = bgpio_init(&tb10x_gpio->gc, dev, 4,
+ tb10x_gpio->base + OFFSET_TO_REG_DATA,
+ NULL,
+ NULL,
+ tb10x_gpio->base + OFFSET_TO_REG_DDR,
+ NULL,
+ 0);
+ if (ret) {
+ dev_err(dev, "unable to init generic GPIO\n");
+ return ret;
+ }
+ tb10x_gpio->gc.base = -1;
+ tb10x_gpio->gc.parent = dev;
+ tb10x_gpio->gc.owner = THIS_MODULE;
+ /*
+ * ngpio is set by bgpio_init() but we override it, this .request()
+ * callback also overrides the one set up by generic GPIO.
+ */
+ tb10x_gpio->gc.ngpio = ngpio;
+ tb10x_gpio->gc.request = gpiochip_generic_request;
+ tb10x_gpio->gc.free = gpiochip_generic_free;
+
+ ret = devm_gpiochip_add_data(dev, &tb10x_gpio->gc, tb10x_gpio);
+ if (ret < 0) {
+ dev_err(dev, "Could not add gpiochip.\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, tb10x_gpio);
+
+ if (of_property_read_bool(np, "interrupt-controller")) {
+ struct irq_chip_generic *gc;
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+
+ tb10x_gpio->gc.to_irq = tb10x_gpio_to_irq;
+ tb10x_gpio->irq = ret;
+
+ ret = devm_request_irq(dev, ret, tb10x_gpio_irq_cascade,
+ IRQF_TRIGGER_NONE | IRQF_SHARED,
+ dev_name(dev), tb10x_gpio);
+ if (ret != 0)
+ return ret;
+
+ tb10x_gpio->domain = irq_domain_add_linear(np,
+ tb10x_gpio->gc.ngpio,
+ &irq_generic_chip_ops, NULL);
+ if (!tb10x_gpio->domain) {
+ return -ENOMEM;
+ }
+
+ ret = irq_alloc_domain_generic_chips(tb10x_gpio->domain,
+ tb10x_gpio->gc.ngpio, 1, tb10x_gpio->gc.label,
+ handle_edge_irq, IRQ_NOREQUEST, IRQ_NOPROBE,
+ IRQ_GC_INIT_MASK_CACHE);
+ if (ret)
+ goto err_remove_domain;
+
+ gc = tb10x_gpio->domain->gc->gc[0];
+ gc->reg_base = tb10x_gpio->base;
+ gc->chip_types[0].type = IRQ_TYPE_EDGE_BOTH;
+ gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
+ gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit;
+ gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit;
+ gc->chip_types[0].chip.irq_set_type = tb10x_gpio_irq_set_type;
+ gc->chip_types[0].regs.ack = OFFSET_TO_REG_CHANGE;
+ gc->chip_types[0].regs.mask = OFFSET_TO_REG_INT_EN;
+ }
+
+ return 0;
+
+err_remove_domain:
+ irq_domain_remove(tb10x_gpio->domain);
+ return ret;
+}
+
+static int tb10x_gpio_remove(struct platform_device *pdev)
+{
+ struct tb10x_gpio *tb10x_gpio = platform_get_drvdata(pdev);
+
+ if (tb10x_gpio->gc.to_irq) {
+ irq_remove_generic_chip(tb10x_gpio->domain->gc->gc[0],
+ BIT(tb10x_gpio->gc.ngpio) - 1, 0, 0);
+ kfree(tb10x_gpio->domain->gc);
+ irq_domain_remove(tb10x_gpio->domain);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id tb10x_gpio_dt_ids[] = {
+ { .compatible = "abilis,tb10x-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tb10x_gpio_dt_ids);
+
+static struct platform_driver tb10x_gpio_driver = {
+ .probe = tb10x_gpio_probe,
+ .remove = tb10x_gpio_remove,
+ .driver = {
+ .name = "tb10x-gpio",
+ .of_match_table = tb10x_gpio_dt_ids,
+ }
+};
+
+module_platform_driver(tb10x_gpio_driver);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("tb10x gpio.");
diff --git a/drivers/gpio/gpio-tc3589x.c b/drivers/gpio/gpio-tc3589x.c
new file mode 100644
index 0000000000..e62ee7e569
--- /dev/null
+++ b/drivers/gpio/gpio-tc3589x.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Hanumath Prasad <hanumath.prasad@stericsson.com> for ST-Ericsson
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio/driver.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/tc3589x.h>
+#include <linux/bitops.h>
+
+/*
+ * These registers are modified under the irq bus lock and cached to avoid
+ * unnecessary writes in bus_sync_unlock.
+ */
+enum { REG_IBE, REG_IEV, REG_IS, REG_IE, REG_DIRECT };
+
+#define CACHE_NR_REGS 5
+#define CACHE_NR_BANKS 3
+
+struct tc3589x_gpio {
+ struct gpio_chip chip;
+ struct tc3589x *tc3589x;
+ struct device *dev;
+ struct mutex irq_lock;
+ /* Caches of interrupt control registers for bus_lock */
+ u8 regs[CACHE_NR_REGS][CACHE_NR_BANKS];
+ u8 oldregs[CACHE_NR_REGS][CACHE_NR_BANKS];
+};
+
+static int tc3589x_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct tc3589x_gpio *tc3589x_gpio = gpiochip_get_data(chip);
+ struct tc3589x *tc3589x = tc3589x_gpio->tc3589x;
+ u8 reg = TC3589x_GPIODATA0 + (offset / 8) * 2;
+ u8 mask = BIT(offset % 8);
+ int ret;
+
+ ret = tc3589x_reg_read(tc3589x, reg);
+ if (ret < 0)
+ return ret;
+
+ return !!(ret & mask);
+}
+
+static void tc3589x_gpio_set(struct gpio_chip *chip, unsigned int offset, int val)
+{
+ struct tc3589x_gpio *tc3589x_gpio = gpiochip_get_data(chip);
+ struct tc3589x *tc3589x = tc3589x_gpio->tc3589x;
+ u8 reg = TC3589x_GPIODATA0 + (offset / 8) * 2;
+ unsigned int pos = offset % 8;
+ u8 data[] = {val ? BIT(pos) : 0, BIT(pos)};
+
+ tc3589x_block_write(tc3589x, reg, ARRAY_SIZE(data), data);
+}
+
+static int tc3589x_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int val)
+{
+ struct tc3589x_gpio *tc3589x_gpio = gpiochip_get_data(chip);
+ struct tc3589x *tc3589x = tc3589x_gpio->tc3589x;
+ u8 reg = TC3589x_GPIODIR0 + offset / 8;
+ unsigned int pos = offset % 8;
+
+ tc3589x_gpio_set(chip, offset, val);
+
+ return tc3589x_set_bits(tc3589x, reg, BIT(pos), BIT(pos));
+}
+
+static int tc3589x_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct tc3589x_gpio *tc3589x_gpio = gpiochip_get_data(chip);
+ struct tc3589x *tc3589x = tc3589x_gpio->tc3589x;
+ u8 reg = TC3589x_GPIODIR0 + offset / 8;
+ unsigned int pos = offset % 8;
+
+ return tc3589x_set_bits(tc3589x, reg, BIT(pos), 0);
+}
+
+static int tc3589x_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct tc3589x_gpio *tc3589x_gpio = gpiochip_get_data(chip);
+ struct tc3589x *tc3589x = tc3589x_gpio->tc3589x;
+ u8 reg = TC3589x_GPIODIR0 + offset / 8;
+ unsigned int pos = offset % 8;
+ int ret;
+
+ ret = tc3589x_reg_read(tc3589x, reg);
+ if (ret < 0)
+ return ret;
+
+ if (ret & BIT(pos))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int tc3589x_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ struct tc3589x_gpio *tc3589x_gpio = gpiochip_get_data(chip);
+ struct tc3589x *tc3589x = tc3589x_gpio->tc3589x;
+ /*
+ * These registers are alterated at each second address
+ * ODM bit 0 = drive to GND or Hi-Z (open drain)
+ * ODM bit 1 = drive to VDD or Hi-Z (open source)
+ */
+ u8 odmreg = TC3589x_GPIOODM0 + (offset / 8) * 2;
+ u8 odereg = TC3589x_GPIOODE0 + (offset / 8) * 2;
+ unsigned int pos = offset % 8;
+ int ret;
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ /* Set open drain mode */
+ ret = tc3589x_set_bits(tc3589x, odmreg, BIT(pos), 0);
+ if (ret)
+ return ret;
+ /* Enable open drain/source mode */
+ return tc3589x_set_bits(tc3589x, odereg, BIT(pos), BIT(pos));
+ case PIN_CONFIG_DRIVE_OPEN_SOURCE:
+ /* Set open source mode */
+ ret = tc3589x_set_bits(tc3589x, odmreg, BIT(pos), BIT(pos));
+ if (ret)
+ return ret;
+ /* Enable open drain/source mode */
+ return tc3589x_set_bits(tc3589x, odereg, BIT(pos), BIT(pos));
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ /* Disable open drain/source mode */
+ return tc3589x_set_bits(tc3589x, odereg, BIT(pos), 0);
+ default:
+ break;
+ }
+ return -ENOTSUPP;
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "tc3589x",
+ .owner = THIS_MODULE,
+ .get = tc3589x_gpio_get,
+ .set = tc3589x_gpio_set,
+ .direction_output = tc3589x_gpio_direction_output,
+ .direction_input = tc3589x_gpio_direction_input,
+ .get_direction = tc3589x_gpio_get_direction,
+ .set_config = tc3589x_gpio_set_config,
+ .can_sleep = true,
+};
+
+static int tc3589x_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct tc3589x_gpio *tc3589x_gpio = gpiochip_get_data(gc);
+ int offset = d->hwirq;
+ int regoffset = offset / 8;
+ int mask = BIT(offset % 8);
+
+ if (type == IRQ_TYPE_EDGE_BOTH) {
+ tc3589x_gpio->regs[REG_IBE][regoffset] |= mask;
+ return 0;
+ }
+
+ tc3589x_gpio->regs[REG_IBE][regoffset] &= ~mask;
+
+ if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH)
+ tc3589x_gpio->regs[REG_IS][regoffset] |= mask;
+ else
+ tc3589x_gpio->regs[REG_IS][regoffset] &= ~mask;
+
+ if (type == IRQ_TYPE_EDGE_RISING || type == IRQ_TYPE_LEVEL_HIGH)
+ tc3589x_gpio->regs[REG_IEV][regoffset] |= mask;
+ else
+ tc3589x_gpio->regs[REG_IEV][regoffset] &= ~mask;
+
+ return 0;
+}
+
+static void tc3589x_gpio_irq_lock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct tc3589x_gpio *tc3589x_gpio = gpiochip_get_data(gc);
+
+ mutex_lock(&tc3589x_gpio->irq_lock);
+}
+
+static void tc3589x_gpio_irq_sync_unlock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct tc3589x_gpio *tc3589x_gpio = gpiochip_get_data(gc);
+ struct tc3589x *tc3589x = tc3589x_gpio->tc3589x;
+ static const u8 regmap[] = {
+ [REG_IBE] = TC3589x_GPIOIBE0,
+ [REG_IEV] = TC3589x_GPIOIEV0,
+ [REG_IS] = TC3589x_GPIOIS0,
+ [REG_IE] = TC3589x_GPIOIE0,
+ [REG_DIRECT] = TC3589x_DIRECT0,
+ };
+ int i, j;
+
+ for (i = 0; i < CACHE_NR_REGS; i++) {
+ for (j = 0; j < CACHE_NR_BANKS; j++) {
+ u8 old = tc3589x_gpio->oldregs[i][j];
+ u8 new = tc3589x_gpio->regs[i][j];
+
+ if (new == old)
+ continue;
+
+ tc3589x_gpio->oldregs[i][j] = new;
+ tc3589x_reg_write(tc3589x, regmap[i] + j, new);
+ }
+ }
+
+ mutex_unlock(&tc3589x_gpio->irq_lock);
+}
+
+static void tc3589x_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct tc3589x_gpio *tc3589x_gpio = gpiochip_get_data(gc);
+ int offset = d->hwirq;
+ int regoffset = offset / 8;
+ int mask = BIT(offset % 8);
+
+ tc3589x_gpio->regs[REG_IE][regoffset] &= ~mask;
+ tc3589x_gpio->regs[REG_DIRECT][regoffset] |= mask;
+ gpiochip_disable_irq(gc, offset);
+}
+
+static void tc3589x_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct tc3589x_gpio *tc3589x_gpio = gpiochip_get_data(gc);
+ int offset = d->hwirq;
+ int regoffset = offset / 8;
+ int mask = BIT(offset % 8);
+
+ gpiochip_enable_irq(gc, offset);
+ tc3589x_gpio->regs[REG_IE][regoffset] |= mask;
+ tc3589x_gpio->regs[REG_DIRECT][regoffset] &= ~mask;
+}
+
+static const struct irq_chip tc3589x_gpio_irq_chip = {
+ .name = "tc3589x-gpio",
+ .irq_bus_lock = tc3589x_gpio_irq_lock,
+ .irq_bus_sync_unlock = tc3589x_gpio_irq_sync_unlock,
+ .irq_mask = tc3589x_gpio_irq_mask,
+ .irq_unmask = tc3589x_gpio_irq_unmask,
+ .irq_set_type = tc3589x_gpio_irq_set_type,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static irqreturn_t tc3589x_gpio_irq(int irq, void *dev)
+{
+ struct tc3589x_gpio *tc3589x_gpio = dev;
+ struct tc3589x *tc3589x = tc3589x_gpio->tc3589x;
+ u8 status[CACHE_NR_BANKS];
+ int ret;
+ int i;
+
+ ret = tc3589x_block_read(tc3589x, TC3589x_GPIOMIS0,
+ ARRAY_SIZE(status), status);
+ if (ret < 0)
+ return IRQ_NONE;
+
+ for (i = 0; i < ARRAY_SIZE(status); i++) {
+ unsigned int stat = status[i];
+ if (!stat)
+ continue;
+
+ while (stat) {
+ int bit = __ffs(stat);
+ int line = i * 8 + bit;
+ int irq = irq_find_mapping(tc3589x_gpio->chip.irq.domain,
+ line);
+
+ handle_nested_irq(irq);
+ stat &= ~(1 << bit);
+ }
+
+ tc3589x_reg_write(tc3589x, TC3589x_GPIOIC0 + i, status[i]);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int tc3589x_gpio_probe(struct platform_device *pdev)
+{
+ struct tc3589x *tc3589x = dev_get_drvdata(pdev->dev.parent);
+ struct device_node *np = pdev->dev.of_node;
+ struct tc3589x_gpio *tc3589x_gpio;
+ struct gpio_irq_chip *girq;
+ int ret;
+ int irq;
+
+ if (!np) {
+ dev_err(&pdev->dev, "No Device Tree node found\n");
+ return -EINVAL;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ tc3589x_gpio = devm_kzalloc(&pdev->dev, sizeof(struct tc3589x_gpio),
+ GFP_KERNEL);
+ if (!tc3589x_gpio)
+ return -ENOMEM;
+
+ mutex_init(&tc3589x_gpio->irq_lock);
+
+ tc3589x_gpio->dev = &pdev->dev;
+ tc3589x_gpio->tc3589x = tc3589x;
+
+ tc3589x_gpio->chip = template_chip;
+ tc3589x_gpio->chip.ngpio = tc3589x->num_gpio;
+ tc3589x_gpio->chip.parent = &pdev->dev;
+ tc3589x_gpio->chip.base = -1;
+
+ girq = &tc3589x_gpio->chip.irq;
+ gpio_irq_chip_set_chip(girq, &tc3589x_gpio_irq_chip);
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ girq->threaded = true;
+
+ /* Bring the GPIO module out of reset */
+ ret = tc3589x_set_bits(tc3589x, TC3589x_RSTCTRL,
+ TC3589x_RSTCTRL_GPIRST, 0);
+ if (ret < 0)
+ return ret;
+
+ /* For tc35894, have to disable Direct KBD interrupts,
+ * else IRQST will always be 0x20, IRQN low level, can't
+ * clear the irq status.
+ * TODO: need more test on other tc3589x chip.
+ *
+ */
+ ret = tc3589x_reg_write(tc3589x, TC3589x_DKBDMSK,
+ TC3589x_DKBDMSK_ELINT | TC3589x_DKBDMSK_EINT);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_request_threaded_irq(&pdev->dev,
+ irq, NULL, tc3589x_gpio_irq,
+ IRQF_ONESHOT, "tc3589x-gpio",
+ tc3589x_gpio);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+ return ret;
+ }
+
+ return devm_gpiochip_add_data(&pdev->dev, &tc3589x_gpio->chip, tc3589x_gpio);
+}
+
+static struct platform_driver tc3589x_gpio_driver = {
+ .driver.name = "tc3589x-gpio",
+ .probe = tc3589x_gpio_probe,
+};
+
+static int __init tc3589x_gpio_init(void)
+{
+ return platform_driver_register(&tc3589x_gpio_driver);
+}
+subsys_initcall(tc3589x_gpio_init);
diff --git a/drivers/gpio/gpio-tegra.c b/drivers/gpio/gpio-tegra.c
new file mode 100644
index 0000000000..ea715582bc
--- /dev/null
+++ b/drivers/gpio/gpio-tegra.c
@@ -0,0 +1,846 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * arch/arm/mach-tegra/gpio.c
+ *
+ * Copyright (c) 2010 Google, Inc
+ * Copyright (c) 2011-2016, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Author:
+ * Erik Gilling <konkers@google.com>
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/gpio/driver.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pm.h>
+
+#define GPIO_BANK(x) ((x) >> 5)
+#define GPIO_PORT(x) (((x) >> 3) & 0x3)
+#define GPIO_BIT(x) ((x) & 0x7)
+
+#define GPIO_REG(tgi, x) (GPIO_BANK(x) * tgi->soc->bank_stride + \
+ GPIO_PORT(x) * 4)
+
+#define GPIO_CNF(t, x) (GPIO_REG(t, x) + 0x00)
+#define GPIO_OE(t, x) (GPIO_REG(t, x) + 0x10)
+#define GPIO_OUT(t, x) (GPIO_REG(t, x) + 0X20)
+#define GPIO_IN(t, x) (GPIO_REG(t, x) + 0x30)
+#define GPIO_INT_STA(t, x) (GPIO_REG(t, x) + 0x40)
+#define GPIO_INT_ENB(t, x) (GPIO_REG(t, x) + 0x50)
+#define GPIO_INT_LVL(t, x) (GPIO_REG(t, x) + 0x60)
+#define GPIO_INT_CLR(t, x) (GPIO_REG(t, x) + 0x70)
+#define GPIO_DBC_CNT(t, x) (GPIO_REG(t, x) + 0xF0)
+
+
+#define GPIO_MSK_CNF(t, x) (GPIO_REG(t, x) + t->soc->upper_offset + 0x00)
+#define GPIO_MSK_OE(t, x) (GPIO_REG(t, x) + t->soc->upper_offset + 0x10)
+#define GPIO_MSK_OUT(t, x) (GPIO_REG(t, x) + t->soc->upper_offset + 0X20)
+#define GPIO_MSK_DBC_EN(t, x) (GPIO_REG(t, x) + t->soc->upper_offset + 0x30)
+#define GPIO_MSK_INT_STA(t, x) (GPIO_REG(t, x) + t->soc->upper_offset + 0x40)
+#define GPIO_MSK_INT_ENB(t, x) (GPIO_REG(t, x) + t->soc->upper_offset + 0x50)
+#define GPIO_MSK_INT_LVL(t, x) (GPIO_REG(t, x) + t->soc->upper_offset + 0x60)
+
+#define GPIO_INT_LVL_MASK 0x010101
+#define GPIO_INT_LVL_EDGE_RISING 0x000101
+#define GPIO_INT_LVL_EDGE_FALLING 0x000100
+#define GPIO_INT_LVL_EDGE_BOTH 0x010100
+#define GPIO_INT_LVL_LEVEL_HIGH 0x000001
+#define GPIO_INT_LVL_LEVEL_LOW 0x000000
+
+struct tegra_gpio_info;
+
+struct tegra_gpio_bank {
+ unsigned int bank;
+
+ /*
+ * IRQ-core code uses raw locking, and thus, nested locking also
+ * should be raw in order not to trip spinlock debug warnings.
+ */
+ raw_spinlock_t lvl_lock[4];
+
+ /* Lock for updating debounce count register */
+ spinlock_t dbc_lock[4];
+
+#ifdef CONFIG_PM_SLEEP
+ u32 cnf[4];
+ u32 out[4];
+ u32 oe[4];
+ u32 int_enb[4];
+ u32 int_lvl[4];
+ u32 wake_enb[4];
+ u32 dbc_enb[4];
+#endif
+ u32 dbc_cnt[4];
+};
+
+struct tegra_gpio_soc_config {
+ bool debounce_supported;
+ u32 bank_stride;
+ u32 upper_offset;
+};
+
+struct tegra_gpio_info {
+ struct device *dev;
+ void __iomem *regs;
+ struct tegra_gpio_bank *bank_info;
+ const struct tegra_gpio_soc_config *soc;
+ struct gpio_chip gc;
+ u32 bank_count;
+ unsigned int *irqs;
+};
+
+static inline void tegra_gpio_writel(struct tegra_gpio_info *tgi,
+ u32 val, u32 reg)
+{
+ writel_relaxed(val, tgi->regs + reg);
+}
+
+static inline u32 tegra_gpio_readl(struct tegra_gpio_info *tgi, u32 reg)
+{
+ return readl_relaxed(tgi->regs + reg);
+}
+
+static unsigned int tegra_gpio_compose(unsigned int bank, unsigned int port,
+ unsigned int bit)
+{
+ return (bank << 5) | ((port & 0x3) << 3) | (bit & 0x7);
+}
+
+static void tegra_gpio_mask_write(struct tegra_gpio_info *tgi, u32 reg,
+ unsigned int gpio, u32 value)
+{
+ u32 val;
+
+ val = 0x100 << GPIO_BIT(gpio);
+ if (value)
+ val |= 1 << GPIO_BIT(gpio);
+ tegra_gpio_writel(tgi, val, reg);
+}
+
+static void tegra_gpio_enable(struct tegra_gpio_info *tgi, unsigned int gpio)
+{
+ tegra_gpio_mask_write(tgi, GPIO_MSK_CNF(tgi, gpio), gpio, 1);
+}
+
+static void tegra_gpio_disable(struct tegra_gpio_info *tgi, unsigned int gpio)
+{
+ tegra_gpio_mask_write(tgi, GPIO_MSK_CNF(tgi, gpio), gpio, 0);
+}
+
+static int tegra_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ return pinctrl_gpio_request(chip->base + offset);
+}
+
+static void tegra_gpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+
+ pinctrl_gpio_free(chip->base + offset);
+ tegra_gpio_disable(tgi, offset);
+}
+
+static void tegra_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+
+ tegra_gpio_mask_write(tgi, GPIO_MSK_OUT(tgi, offset), offset, value);
+}
+
+static int tegra_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+ unsigned int bval = BIT(GPIO_BIT(offset));
+
+ /* If gpio is in output mode then read from the out value */
+ if (tegra_gpio_readl(tgi, GPIO_OE(tgi, offset)) & bval)
+ return !!(tegra_gpio_readl(tgi, GPIO_OUT(tgi, offset)) & bval);
+
+ return !!(tegra_gpio_readl(tgi, GPIO_IN(tgi, offset)) & bval);
+}
+
+static int tegra_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+ int ret;
+
+ tegra_gpio_mask_write(tgi, GPIO_MSK_OE(tgi, offset), offset, 0);
+ tegra_gpio_enable(tgi, offset);
+
+ ret = pinctrl_gpio_direction_input(chip->base + offset);
+ if (ret < 0)
+ dev_err(tgi->dev,
+ "Failed to set pinctrl input direction of GPIO %d: %d",
+ chip->base + offset, ret);
+
+ return ret;
+}
+
+static int tegra_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset,
+ int value)
+{
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+ int ret;
+
+ tegra_gpio_set(chip, offset, value);
+ tegra_gpio_mask_write(tgi, GPIO_MSK_OE(tgi, offset), offset, 1);
+ tegra_gpio_enable(tgi, offset);
+
+ ret = pinctrl_gpio_direction_output(chip->base + offset);
+ if (ret < 0)
+ dev_err(tgi->dev,
+ "Failed to set pinctrl output direction of GPIO %d: %d",
+ chip->base + offset, ret);
+
+ return ret;
+}
+
+static int tegra_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+ u32 pin_mask = BIT(GPIO_BIT(offset));
+ u32 cnf, oe;
+
+ cnf = tegra_gpio_readl(tgi, GPIO_CNF(tgi, offset));
+ if (!(cnf & pin_mask))
+ return -EINVAL;
+
+ oe = tegra_gpio_readl(tgi, GPIO_OE(tgi, offset));
+
+ if (oe & pin_mask)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int tegra_gpio_set_debounce(struct gpio_chip *chip, unsigned int offset,
+ unsigned int debounce)
+{
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+ struct tegra_gpio_bank *bank = &tgi->bank_info[GPIO_BANK(offset)];
+ unsigned int debounce_ms = DIV_ROUND_UP(debounce, 1000);
+ unsigned long flags;
+ unsigned int port;
+
+ if (!debounce_ms) {
+ tegra_gpio_mask_write(tgi, GPIO_MSK_DBC_EN(tgi, offset),
+ offset, 0);
+ return 0;
+ }
+
+ debounce_ms = min(debounce_ms, 255U);
+ port = GPIO_PORT(offset);
+
+ /* There is only one debounce count register per port and hence
+ * set the maximum of current and requested debounce time.
+ */
+ spin_lock_irqsave(&bank->dbc_lock[port], flags);
+ if (bank->dbc_cnt[port] < debounce_ms) {
+ tegra_gpio_writel(tgi, debounce_ms, GPIO_DBC_CNT(tgi, offset));
+ bank->dbc_cnt[port] = debounce_ms;
+ }
+ spin_unlock_irqrestore(&bank->dbc_lock[port], flags);
+
+ tegra_gpio_mask_write(tgi, GPIO_MSK_DBC_EN(tgi, offset), offset, 1);
+
+ return 0;
+}
+
+static int tegra_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ u32 debounce;
+
+ if (pinconf_to_config_param(config) != PIN_CONFIG_INPUT_DEBOUNCE)
+ return -ENOTSUPP;
+
+ debounce = pinconf_to_config_argument(config);
+ return tegra_gpio_set_debounce(chip, offset, debounce);
+}
+
+static void tegra_gpio_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+ unsigned int gpio = d->hwirq;
+
+ tegra_gpio_writel(tgi, 1 << GPIO_BIT(gpio), GPIO_INT_CLR(tgi, gpio));
+}
+
+static void tegra_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+ unsigned int gpio = d->hwirq;
+
+ tegra_gpio_mask_write(tgi, GPIO_MSK_INT_ENB(tgi, gpio), gpio, 0);
+ gpiochip_disable_irq(chip, gpio);
+}
+
+static void tegra_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+ unsigned int gpio = d->hwirq;
+
+ gpiochip_enable_irq(chip, gpio);
+ tegra_gpio_mask_write(tgi, GPIO_MSK_INT_ENB(tgi, gpio), gpio, 1);
+}
+
+static int tegra_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ unsigned int gpio = d->hwirq, port = GPIO_PORT(gpio), lvl_type;
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+ struct tegra_gpio_bank *bank;
+ unsigned long flags;
+ int ret;
+ u32 val;
+
+ bank = &tgi->bank_info[GPIO_BANK(d->hwirq)];
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_RISING:
+ lvl_type = GPIO_INT_LVL_EDGE_RISING;
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ lvl_type = GPIO_INT_LVL_EDGE_FALLING;
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ lvl_type = GPIO_INT_LVL_EDGE_BOTH;
+ break;
+
+ case IRQ_TYPE_LEVEL_HIGH:
+ lvl_type = GPIO_INT_LVL_LEVEL_HIGH;
+ break;
+
+ case IRQ_TYPE_LEVEL_LOW:
+ lvl_type = GPIO_INT_LVL_LEVEL_LOW;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ raw_spin_lock_irqsave(&bank->lvl_lock[port], flags);
+
+ val = tegra_gpio_readl(tgi, GPIO_INT_LVL(tgi, gpio));
+ val &= ~(GPIO_INT_LVL_MASK << GPIO_BIT(gpio));
+ val |= lvl_type << GPIO_BIT(gpio);
+ tegra_gpio_writel(tgi, val, GPIO_INT_LVL(tgi, gpio));
+
+ raw_spin_unlock_irqrestore(&bank->lvl_lock[port], flags);
+
+ tegra_gpio_mask_write(tgi, GPIO_MSK_OE(tgi, gpio), gpio, 0);
+ tegra_gpio_enable(tgi, gpio);
+
+ ret = gpiochip_lock_as_irq(&tgi->gc, gpio);
+ if (ret) {
+ dev_err(tgi->dev,
+ "unable to lock Tegra GPIO %u as IRQ\n", gpio);
+ tegra_gpio_disable(tgi, gpio);
+ return ret;
+ }
+
+ if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
+ irq_set_handler_locked(d, handle_level_irq);
+ else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
+ irq_set_handler_locked(d, handle_edge_irq);
+
+ if (d->parent_data)
+ ret = irq_chip_set_type_parent(d, type);
+
+ return ret;
+}
+
+static void tegra_gpio_irq_shutdown(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+ unsigned int gpio = d->hwirq;
+
+ tegra_gpio_irq_mask(d);
+ gpiochip_unlock_as_irq(&tgi->gc, gpio);
+}
+
+static void tegra_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct tegra_gpio_info *tgi = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct irq_domain *domain = tgi->gc.irq.domain;
+ unsigned int irq = irq_desc_get_irq(desc);
+ struct tegra_gpio_bank *bank = NULL;
+ unsigned int port, pin, gpio, i;
+ bool unmasked = false;
+ unsigned long sta;
+ u32 lvl;
+
+ for (i = 0; i < tgi->bank_count; i++) {
+ if (tgi->irqs[i] == irq) {
+ bank = &tgi->bank_info[i];
+ break;
+ }
+ }
+
+ if (WARN_ON(bank == NULL))
+ return;
+
+ chained_irq_enter(chip, desc);
+
+ for (port = 0; port < 4; port++) {
+ gpio = tegra_gpio_compose(bank->bank, port, 0);
+ sta = tegra_gpio_readl(tgi, GPIO_INT_STA(tgi, gpio)) &
+ tegra_gpio_readl(tgi, GPIO_INT_ENB(tgi, gpio));
+ lvl = tegra_gpio_readl(tgi, GPIO_INT_LVL(tgi, gpio));
+
+ for_each_set_bit(pin, &sta, 8) {
+ int ret;
+
+ tegra_gpio_writel(tgi, 1 << pin,
+ GPIO_INT_CLR(tgi, gpio));
+
+ /* if gpio is edge triggered, clear condition
+ * before executing the handler so that we don't
+ * miss edges
+ */
+ if (!unmasked && lvl & (0x100 << pin)) {
+ unmasked = true;
+ chained_irq_exit(chip, desc);
+ }
+
+ ret = generic_handle_domain_irq(domain, gpio + pin);
+ WARN_RATELIMIT(ret, "hwirq = %d", gpio + pin);
+ }
+ }
+
+ if (!unmasked)
+ chained_irq_exit(chip, desc);
+}
+
+static int tegra_gpio_child_to_parent_hwirq(struct gpio_chip *chip,
+ unsigned int hwirq,
+ unsigned int type,
+ unsigned int *parent_hwirq,
+ unsigned int *parent_type)
+{
+ *parent_hwirq = chip->irq.child_offset_to_irq(chip, hwirq);
+ *parent_type = type;
+
+ return 0;
+}
+
+static int tegra_gpio_populate_parent_fwspec(struct gpio_chip *chip,
+ union gpio_irq_fwspec *gfwspec,
+ unsigned int parent_hwirq,
+ unsigned int parent_type)
+{
+ struct irq_fwspec *fwspec = &gfwspec->fwspec;
+
+ fwspec->fwnode = chip->irq.parent_domain->fwnode;
+ fwspec->param_count = 3;
+ fwspec->param[0] = 0;
+ fwspec->param[1] = parent_hwirq;
+ fwspec->param[2] = parent_type;
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tegra_gpio_resume(struct device *dev)
+{
+ struct tegra_gpio_info *tgi = dev_get_drvdata(dev);
+ unsigned int b, p;
+
+ for (b = 0; b < tgi->bank_count; b++) {
+ struct tegra_gpio_bank *bank = &tgi->bank_info[b];
+
+ for (p = 0; p < ARRAY_SIZE(bank->oe); p++) {
+ unsigned int gpio = (b << 5) | (p << 3);
+
+ tegra_gpio_writel(tgi, bank->cnf[p],
+ GPIO_CNF(tgi, gpio));
+
+ if (tgi->soc->debounce_supported) {
+ tegra_gpio_writel(tgi, bank->dbc_cnt[p],
+ GPIO_DBC_CNT(tgi, gpio));
+ tegra_gpio_writel(tgi, bank->dbc_enb[p],
+ GPIO_MSK_DBC_EN(tgi, gpio));
+ }
+
+ tegra_gpio_writel(tgi, bank->out[p],
+ GPIO_OUT(tgi, gpio));
+ tegra_gpio_writel(tgi, bank->oe[p],
+ GPIO_OE(tgi, gpio));
+ tegra_gpio_writel(tgi, bank->int_lvl[p],
+ GPIO_INT_LVL(tgi, gpio));
+ tegra_gpio_writel(tgi, bank->int_enb[p],
+ GPIO_INT_ENB(tgi, gpio));
+ }
+ }
+
+ return 0;
+}
+
+static int tegra_gpio_suspend(struct device *dev)
+{
+ struct tegra_gpio_info *tgi = dev_get_drvdata(dev);
+ unsigned int b, p;
+
+ for (b = 0; b < tgi->bank_count; b++) {
+ struct tegra_gpio_bank *bank = &tgi->bank_info[b];
+
+ for (p = 0; p < ARRAY_SIZE(bank->oe); p++) {
+ unsigned int gpio = (b << 5) | (p << 3);
+
+ bank->cnf[p] = tegra_gpio_readl(tgi,
+ GPIO_CNF(tgi, gpio));
+ bank->out[p] = tegra_gpio_readl(tgi,
+ GPIO_OUT(tgi, gpio));
+ bank->oe[p] = tegra_gpio_readl(tgi,
+ GPIO_OE(tgi, gpio));
+ if (tgi->soc->debounce_supported) {
+ bank->dbc_enb[p] = tegra_gpio_readl(tgi,
+ GPIO_MSK_DBC_EN(tgi, gpio));
+ bank->dbc_enb[p] = (bank->dbc_enb[p] << 8) |
+ bank->dbc_enb[p];
+ }
+
+ bank->int_enb[p] = tegra_gpio_readl(tgi,
+ GPIO_INT_ENB(tgi, gpio));
+ bank->int_lvl[p] = tegra_gpio_readl(tgi,
+ GPIO_INT_LVL(tgi, gpio));
+
+ /* Enable gpio irq for wake up source */
+ tegra_gpio_writel(tgi, bank->wake_enb[p],
+ GPIO_INT_ENB(tgi, gpio));
+ }
+ }
+
+ return 0;
+}
+
+static int tegra_gpio_irq_set_wake(struct irq_data *d, unsigned int enable)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+ struct tegra_gpio_bank *bank;
+ unsigned int gpio = d->hwirq;
+ u32 port, bit, mask;
+ int err;
+
+ bank = &tgi->bank_info[GPIO_BANK(d->hwirq)];
+
+ port = GPIO_PORT(gpio);
+ bit = GPIO_BIT(gpio);
+ mask = BIT(bit);
+
+ err = irq_set_irq_wake(tgi->irqs[bank->bank], enable);
+ if (err)
+ return err;
+
+ if (d->parent_data) {
+ err = irq_chip_set_wake_parent(d, enable);
+ if (err) {
+ irq_set_irq_wake(tgi->irqs[bank->bank], !enable);
+ return err;
+ }
+ }
+
+ if (enable)
+ bank->wake_enb[port] |= mask;
+ else
+ bank->wake_enb[port] &= ~mask;
+
+ return 0;
+}
+#endif
+
+static int tegra_gpio_irq_set_affinity(struct irq_data *data,
+ const struct cpumask *dest,
+ bool force)
+{
+ if (data->parent_data)
+ return irq_chip_set_affinity_parent(data, dest, force);
+
+ return -EINVAL;
+}
+
+static int tegra_gpio_irq_request_resources(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+
+ tegra_gpio_enable(tgi, d->hwirq);
+
+ return gpiochip_reqres_irq(chip, d->hwirq);
+}
+
+static void tegra_gpio_irq_release_resources(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct tegra_gpio_info *tgi = gpiochip_get_data(chip);
+
+ gpiochip_relres_irq(chip, d->hwirq);
+ tegra_gpio_enable(tgi, d->hwirq);
+}
+
+static void tegra_gpio_irq_print_chip(struct irq_data *d, struct seq_file *s)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+
+ seq_printf(s, dev_name(chip->parent));
+}
+
+static const struct irq_chip tegra_gpio_irq_chip = {
+ .irq_shutdown = tegra_gpio_irq_shutdown,
+ .irq_ack = tegra_gpio_irq_ack,
+ .irq_mask = tegra_gpio_irq_mask,
+ .irq_unmask = tegra_gpio_irq_unmask,
+ .irq_set_type = tegra_gpio_irq_set_type,
+#ifdef CONFIG_PM_SLEEP
+ .irq_set_wake = tegra_gpio_irq_set_wake,
+#endif
+ .irq_print_chip = tegra_gpio_irq_print_chip,
+ .irq_request_resources = tegra_gpio_irq_request_resources,
+ .irq_release_resources = tegra_gpio_irq_release_resources,
+ .flags = IRQCHIP_IMMUTABLE,
+};
+
+static const struct irq_chip tegra210_gpio_irq_chip = {
+ .irq_shutdown = tegra_gpio_irq_shutdown,
+ .irq_ack = tegra_gpio_irq_ack,
+ .irq_mask = tegra_gpio_irq_mask,
+ .irq_unmask = tegra_gpio_irq_unmask,
+ .irq_set_affinity = tegra_gpio_irq_set_affinity,
+ .irq_set_type = tegra_gpio_irq_set_type,
+#ifdef CONFIG_PM_SLEEP
+ .irq_set_wake = tegra_gpio_irq_set_wake,
+#endif
+ .irq_print_chip = tegra_gpio_irq_print_chip,
+ .irq_request_resources = tegra_gpio_irq_request_resources,
+ .irq_release_resources = tegra_gpio_irq_release_resources,
+ .flags = IRQCHIP_IMMUTABLE,
+};
+
+#ifdef CONFIG_DEBUG_FS
+
+#include <linux/debugfs.h>
+
+static int tegra_dbg_gpio_show(struct seq_file *s, void *unused)
+{
+ struct tegra_gpio_info *tgi = dev_get_drvdata(s->private);
+ unsigned int i, j;
+
+ for (i = 0; i < tgi->bank_count; i++) {
+ for (j = 0; j < 4; j++) {
+ unsigned int gpio = tegra_gpio_compose(i, j, 0);
+
+ seq_printf(s,
+ "%u:%u %02x %02x %02x %02x %02x %02x %06x\n",
+ i, j,
+ tegra_gpio_readl(tgi, GPIO_CNF(tgi, gpio)),
+ tegra_gpio_readl(tgi, GPIO_OE(tgi, gpio)),
+ tegra_gpio_readl(tgi, GPIO_OUT(tgi, gpio)),
+ tegra_gpio_readl(tgi, GPIO_IN(tgi, gpio)),
+ tegra_gpio_readl(tgi, GPIO_INT_STA(tgi, gpio)),
+ tegra_gpio_readl(tgi, GPIO_INT_ENB(tgi, gpio)),
+ tegra_gpio_readl(tgi, GPIO_INT_LVL(tgi, gpio)));
+ }
+ }
+ return 0;
+}
+
+static void tegra_gpio_debuginit(struct tegra_gpio_info *tgi)
+{
+ debugfs_create_devm_seqfile(tgi->dev, "tegra_gpio", NULL,
+ tegra_dbg_gpio_show);
+}
+
+#else
+
+static inline void tegra_gpio_debuginit(struct tegra_gpio_info *tgi)
+{
+}
+
+#endif
+
+static const struct dev_pm_ops tegra_gpio_pm_ops = {
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(tegra_gpio_suspend, tegra_gpio_resume)
+};
+
+static const struct of_device_id tegra_pmc_of_match[] = {
+ { .compatible = "nvidia,tegra210-pmc", },
+ { /* sentinel */ },
+};
+
+static int tegra_gpio_probe(struct platform_device *pdev)
+{
+ struct tegra_gpio_bank *bank;
+ struct tegra_gpio_info *tgi;
+ struct gpio_irq_chip *irq;
+ struct device_node *np;
+ unsigned int i, j;
+ int ret;
+
+ tgi = devm_kzalloc(&pdev->dev, sizeof(*tgi), GFP_KERNEL);
+ if (!tgi)
+ return -ENODEV;
+
+ tgi->soc = of_device_get_match_data(&pdev->dev);
+ tgi->dev = &pdev->dev;
+
+ ret = platform_irq_count(pdev);
+ if (ret < 0)
+ return ret;
+
+ tgi->bank_count = ret;
+
+ if (!tgi->bank_count) {
+ dev_err(&pdev->dev, "Missing IRQ resource\n");
+ return -ENODEV;
+ }
+
+ tgi->gc.label = "tegra-gpio";
+ tgi->gc.request = tegra_gpio_request;
+ tgi->gc.free = tegra_gpio_free;
+ tgi->gc.direction_input = tegra_gpio_direction_input;
+ tgi->gc.get = tegra_gpio_get;
+ tgi->gc.direction_output = tegra_gpio_direction_output;
+ tgi->gc.set = tegra_gpio_set;
+ tgi->gc.get_direction = tegra_gpio_get_direction;
+ tgi->gc.base = 0;
+ tgi->gc.ngpio = tgi->bank_count * 32;
+ tgi->gc.parent = &pdev->dev;
+
+ platform_set_drvdata(pdev, tgi);
+
+ if (tgi->soc->debounce_supported)
+ tgi->gc.set_config = tegra_gpio_set_config;
+
+ tgi->bank_info = devm_kcalloc(&pdev->dev, tgi->bank_count,
+ sizeof(*tgi->bank_info), GFP_KERNEL);
+ if (!tgi->bank_info)
+ return -ENOMEM;
+
+ tgi->irqs = devm_kcalloc(&pdev->dev, tgi->bank_count,
+ sizeof(*tgi->irqs), GFP_KERNEL);
+ if (!tgi->irqs)
+ return -ENOMEM;
+
+ for (i = 0; i < tgi->bank_count; i++) {
+ ret = platform_get_irq(pdev, i);
+ if (ret < 0)
+ return ret;
+
+ bank = &tgi->bank_info[i];
+ bank->bank = i;
+
+ tgi->irqs[i] = ret;
+
+ for (j = 0; j < 4; j++) {
+ raw_spin_lock_init(&bank->lvl_lock[j]);
+ spin_lock_init(&bank->dbc_lock[j]);
+ }
+ }
+
+ irq = &tgi->gc.irq;
+ irq->fwnode = of_node_to_fwnode(pdev->dev.of_node);
+ irq->child_to_parent_hwirq = tegra_gpio_child_to_parent_hwirq;
+ irq->populate_parent_alloc_arg = tegra_gpio_populate_parent_fwspec;
+ irq->handler = handle_simple_irq;
+ irq->default_type = IRQ_TYPE_NONE;
+ irq->parent_handler = tegra_gpio_irq_handler;
+ irq->parent_handler_data = tgi;
+ irq->num_parents = tgi->bank_count;
+ irq->parents = tgi->irqs;
+
+ np = of_find_matching_node(NULL, tegra_pmc_of_match);
+ if (np) {
+ irq->parent_domain = irq_find_host(np);
+ of_node_put(np);
+
+ if (!irq->parent_domain)
+ return -EPROBE_DEFER;
+
+ gpio_irq_chip_set_chip(irq, &tegra210_gpio_irq_chip);
+ } else {
+ gpio_irq_chip_set_chip(irq, &tegra_gpio_irq_chip);
+ }
+
+ tgi->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(tgi->regs))
+ return PTR_ERR(tgi->regs);
+
+ for (i = 0; i < tgi->bank_count; i++) {
+ for (j = 0; j < 4; j++) {
+ int gpio = tegra_gpio_compose(i, j, 0);
+
+ tegra_gpio_writel(tgi, 0x00, GPIO_INT_ENB(tgi, gpio));
+ }
+ }
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &tgi->gc, tgi);
+ if (ret < 0)
+ return ret;
+
+ tegra_gpio_debuginit(tgi);
+
+ return 0;
+}
+
+static const struct tegra_gpio_soc_config tegra20_gpio_config = {
+ .bank_stride = 0x80,
+ .upper_offset = 0x800,
+};
+
+static const struct tegra_gpio_soc_config tegra30_gpio_config = {
+ .bank_stride = 0x100,
+ .upper_offset = 0x80,
+};
+
+static const struct tegra_gpio_soc_config tegra210_gpio_config = {
+ .debounce_supported = true,
+ .bank_stride = 0x100,
+ .upper_offset = 0x80,
+};
+
+static const struct of_device_id tegra_gpio_of_match[] = {
+ { .compatible = "nvidia,tegra210-gpio", .data = &tegra210_gpio_config },
+ { .compatible = "nvidia,tegra30-gpio", .data = &tegra30_gpio_config },
+ { .compatible = "nvidia,tegra20-gpio", .data = &tegra20_gpio_config },
+ { },
+};
+MODULE_DEVICE_TABLE(of, tegra_gpio_of_match);
+
+static struct platform_driver tegra_gpio_driver = {
+ .driver = {
+ .name = "tegra-gpio",
+ .pm = &tegra_gpio_pm_ops,
+ .of_match_table = tegra_gpio_of_match,
+ },
+ .probe = tegra_gpio_probe,
+};
+module_platform_driver(tegra_gpio_driver);
+
+MODULE_DESCRIPTION("NVIDIA Tegra GPIO controller driver");
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_AUTHOR("Erik Gilling <konkers@google.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-tegra186.c b/drivers/gpio/gpio-tegra186.c
new file mode 100644
index 0000000000..d87dd06db4
--- /dev/null
+++ b/drivers/gpio/gpio-tegra186.c
@@ -0,0 +1,1321 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2016-2022 NVIDIA Corporation
+ *
+ * Author: Thierry Reding <treding@nvidia.com>
+ * Dipen Patel <dpatel@nvidia.com>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/hte.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+
+#include <dt-bindings/gpio/tegra186-gpio.h>
+#include <dt-bindings/gpio/tegra194-gpio.h>
+#include <dt-bindings/gpio/tegra234-gpio.h>
+#include <dt-bindings/gpio/tegra241-gpio.h>
+
+/* security registers */
+#define TEGRA186_GPIO_CTL_SCR 0x0c
+#define TEGRA186_GPIO_CTL_SCR_SEC_WEN BIT(28)
+#define TEGRA186_GPIO_CTL_SCR_SEC_REN BIT(27)
+
+#define TEGRA186_GPIO_INT_ROUTE_MAPPING(p, x) (0x14 + (p) * 0x20 + (x) * 4)
+
+#define TEGRA186_GPIO_VM 0x00
+#define TEGRA186_GPIO_VM_RW_MASK 0x03
+#define TEGRA186_GPIO_SCR 0x04
+#define TEGRA186_GPIO_SCR_PIN_SIZE 0x08
+#define TEGRA186_GPIO_SCR_PORT_SIZE 0x40
+#define TEGRA186_GPIO_SCR_SEC_WEN BIT(28)
+#define TEGRA186_GPIO_SCR_SEC_REN BIT(27)
+#define TEGRA186_GPIO_SCR_SEC_G1W BIT(9)
+#define TEGRA186_GPIO_SCR_SEC_G1R BIT(1)
+#define TEGRA186_GPIO_FULL_ACCESS (TEGRA186_GPIO_SCR_SEC_WEN | \
+ TEGRA186_GPIO_SCR_SEC_REN | \
+ TEGRA186_GPIO_SCR_SEC_G1R | \
+ TEGRA186_GPIO_SCR_SEC_G1W)
+#define TEGRA186_GPIO_SCR_SEC_ENABLE (TEGRA186_GPIO_SCR_SEC_WEN | \
+ TEGRA186_GPIO_SCR_SEC_REN)
+
+/* control registers */
+#define TEGRA186_GPIO_ENABLE_CONFIG 0x00
+#define TEGRA186_GPIO_ENABLE_CONFIG_ENABLE BIT(0)
+#define TEGRA186_GPIO_ENABLE_CONFIG_OUT BIT(1)
+#define TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_NONE (0x0 << 2)
+#define TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_LEVEL (0x1 << 2)
+#define TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_SINGLE_EDGE (0x2 << 2)
+#define TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_DOUBLE_EDGE (0x3 << 2)
+#define TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_MASK (0x3 << 2)
+#define TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL BIT(4)
+#define TEGRA186_GPIO_ENABLE_CONFIG_DEBOUNCE BIT(5)
+#define TEGRA186_GPIO_ENABLE_CONFIG_INTERRUPT BIT(6)
+#define TEGRA186_GPIO_ENABLE_CONFIG_TIMESTAMP_FUNC BIT(7)
+
+#define TEGRA186_GPIO_DEBOUNCE_CONTROL 0x04
+#define TEGRA186_GPIO_DEBOUNCE_CONTROL_THRESHOLD(x) ((x) & 0xff)
+
+#define TEGRA186_GPIO_INPUT 0x08
+#define TEGRA186_GPIO_INPUT_HIGH BIT(0)
+
+#define TEGRA186_GPIO_OUTPUT_CONTROL 0x0c
+#define TEGRA186_GPIO_OUTPUT_CONTROL_FLOATED BIT(0)
+
+#define TEGRA186_GPIO_OUTPUT_VALUE 0x10
+#define TEGRA186_GPIO_OUTPUT_VALUE_HIGH BIT(0)
+
+#define TEGRA186_GPIO_INTERRUPT_CLEAR 0x14
+
+#define TEGRA186_GPIO_INTERRUPT_STATUS(x) (0x100 + (x) * 4)
+
+struct tegra_gpio_port {
+ const char *name;
+ unsigned int bank;
+ unsigned int port;
+ unsigned int pins;
+};
+
+struct tegra186_pin_range {
+ unsigned int offset;
+ const char *group;
+};
+
+struct tegra_gpio_soc {
+ const struct tegra_gpio_port *ports;
+ unsigned int num_ports;
+ const char *name;
+ unsigned int instance;
+
+ unsigned int num_irqs_per_bank;
+
+ const struct tegra186_pin_range *pin_ranges;
+ unsigned int num_pin_ranges;
+ const char *pinmux;
+ bool has_gte;
+ bool has_vm_support;
+};
+
+struct tegra_gpio {
+ struct gpio_chip gpio;
+ unsigned int num_irq;
+ unsigned int *irq;
+
+ const struct tegra_gpio_soc *soc;
+ unsigned int num_irqs_per_bank;
+ unsigned int num_banks;
+
+ void __iomem *secure;
+ void __iomem *base;
+};
+
+static const struct tegra_gpio_port *
+tegra186_gpio_get_port(struct tegra_gpio *gpio, unsigned int *pin)
+{
+ unsigned int start = 0, i;
+
+ for (i = 0; i < gpio->soc->num_ports; i++) {
+ const struct tegra_gpio_port *port = &gpio->soc->ports[i];
+
+ if (*pin >= start && *pin < start + port->pins) {
+ *pin -= start;
+ return port;
+ }
+
+ start += port->pins;
+ }
+
+ return NULL;
+}
+
+static void __iomem *tegra186_gpio_get_base(struct tegra_gpio *gpio,
+ unsigned int pin)
+{
+ const struct tegra_gpio_port *port;
+ unsigned int offset;
+
+ port = tegra186_gpio_get_port(gpio, &pin);
+ if (!port)
+ return NULL;
+
+ offset = port->bank * 0x1000 + port->port * 0x200;
+
+ return gpio->base + offset + pin * 0x20;
+}
+
+static void __iomem *tegra186_gpio_get_secure_base(struct tegra_gpio *gpio,
+ unsigned int pin)
+{
+ const struct tegra_gpio_port *port;
+ unsigned int offset;
+
+ port = tegra186_gpio_get_port(gpio, &pin);
+ if (!port)
+ return NULL;
+
+ offset = port->bank * 0x1000 + port->port * TEGRA186_GPIO_SCR_PORT_SIZE;
+
+ return gpio->secure + offset + pin * TEGRA186_GPIO_SCR_PIN_SIZE;
+}
+
+static inline bool tegra186_gpio_is_accessible(struct tegra_gpio *gpio, unsigned int pin)
+{
+ void __iomem *secure;
+ u32 value;
+
+ secure = tegra186_gpio_get_secure_base(gpio, pin);
+
+ if (gpio->soc->has_vm_support) {
+ value = readl(secure + TEGRA186_GPIO_VM);
+ if ((value & TEGRA186_GPIO_VM_RW_MASK) != TEGRA186_GPIO_VM_RW_MASK)
+ return false;
+ }
+
+ value = __raw_readl(secure + TEGRA186_GPIO_SCR);
+
+ if ((value & TEGRA186_GPIO_SCR_SEC_ENABLE) == 0)
+ return true;
+
+ if ((value & TEGRA186_GPIO_FULL_ACCESS) == TEGRA186_GPIO_FULL_ACCESS)
+ return true;
+
+ return false;
+}
+
+static int tegra186_init_valid_mask(struct gpio_chip *chip,
+ unsigned long *valid_mask, unsigned int ngpios)
+{
+ struct tegra_gpio *gpio = gpiochip_get_data(chip);
+ unsigned int j;
+
+ for (j = 0; j < ngpios; j++) {
+ if (!tegra186_gpio_is_accessible(gpio, j))
+ clear_bit(j, valid_mask);
+ }
+ return 0;
+}
+
+static int tegra186_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct tegra_gpio *gpio = gpiochip_get_data(chip);
+ void __iomem *base;
+ u32 value;
+
+ base = tegra186_gpio_get_base(gpio, offset);
+ if (WARN_ON(base == NULL))
+ return -ENODEV;
+
+ value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG);
+ if (value & TEGRA186_GPIO_ENABLE_CONFIG_OUT)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int tegra186_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct tegra_gpio *gpio = gpiochip_get_data(chip);
+ void __iomem *base;
+ u32 value;
+
+ base = tegra186_gpio_get_base(gpio, offset);
+ if (WARN_ON(base == NULL))
+ return -ENODEV;
+
+ value = readl(base + TEGRA186_GPIO_OUTPUT_CONTROL);
+ value |= TEGRA186_GPIO_OUTPUT_CONTROL_FLOATED;
+ writel(value, base + TEGRA186_GPIO_OUTPUT_CONTROL);
+
+ value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG);
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_ENABLE;
+ value &= ~TEGRA186_GPIO_ENABLE_CONFIG_OUT;
+ writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG);
+
+ return 0;
+}
+
+static int tegra186_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int level)
+{
+ struct tegra_gpio *gpio = gpiochip_get_data(chip);
+ void __iomem *base;
+ u32 value;
+
+ /* configure output level first */
+ chip->set(chip, offset, level);
+
+ base = tegra186_gpio_get_base(gpio, offset);
+ if (WARN_ON(base == NULL))
+ return -EINVAL;
+
+ /* set the direction */
+ value = readl(base + TEGRA186_GPIO_OUTPUT_CONTROL);
+ value &= ~TEGRA186_GPIO_OUTPUT_CONTROL_FLOATED;
+ writel(value, base + TEGRA186_GPIO_OUTPUT_CONTROL);
+
+ value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG);
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_ENABLE;
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_OUT;
+ writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG);
+
+ return 0;
+}
+
+#define HTE_BOTH_EDGES (HTE_RISING_EDGE_TS | HTE_FALLING_EDGE_TS)
+
+static int tegra186_gpio_en_hw_ts(struct gpio_chip *gc, u32 offset,
+ unsigned long flags)
+{
+ struct tegra_gpio *gpio;
+ void __iomem *base;
+ int value;
+
+ if (!gc)
+ return -EINVAL;
+
+ gpio = gpiochip_get_data(gc);
+ if (!gpio)
+ return -ENODEV;
+
+ base = tegra186_gpio_get_base(gpio, offset);
+ if (WARN_ON(base == NULL))
+ return -EINVAL;
+
+ value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG);
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_TIMESTAMP_FUNC;
+
+ if (flags == HTE_BOTH_EDGES) {
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_DOUBLE_EDGE;
+ } else if (flags == HTE_RISING_EDGE_TS) {
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_SINGLE_EDGE;
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL;
+ } else if (flags == HTE_FALLING_EDGE_TS) {
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_SINGLE_EDGE;
+ }
+
+ writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG);
+
+ return 0;
+}
+
+static int tegra186_gpio_dis_hw_ts(struct gpio_chip *gc, u32 offset,
+ unsigned long flags)
+{
+ struct tegra_gpio *gpio;
+ void __iomem *base;
+ int value;
+
+ if (!gc)
+ return -EINVAL;
+
+ gpio = gpiochip_get_data(gc);
+ if (!gpio)
+ return -ENODEV;
+
+ base = tegra186_gpio_get_base(gpio, offset);
+ if (WARN_ON(base == NULL))
+ return -EINVAL;
+
+ value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG);
+ value &= ~TEGRA186_GPIO_ENABLE_CONFIG_TIMESTAMP_FUNC;
+ if (flags == HTE_BOTH_EDGES) {
+ value &= ~TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_DOUBLE_EDGE;
+ } else if (flags == HTE_RISING_EDGE_TS) {
+ value &= ~TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_SINGLE_EDGE;
+ value &= ~TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL;
+ } else if (flags == HTE_FALLING_EDGE_TS) {
+ value &= ~TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_SINGLE_EDGE;
+ }
+ writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG);
+
+ return 0;
+}
+
+static int tegra186_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct tegra_gpio *gpio = gpiochip_get_data(chip);
+ void __iomem *base;
+ u32 value;
+
+ base = tegra186_gpio_get_base(gpio, offset);
+ if (WARN_ON(base == NULL))
+ return -ENODEV;
+
+ value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG);
+ if (value & TEGRA186_GPIO_ENABLE_CONFIG_OUT)
+ value = readl(base + TEGRA186_GPIO_OUTPUT_VALUE);
+ else
+ value = readl(base + TEGRA186_GPIO_INPUT);
+
+ return value & BIT(0);
+}
+
+static void tegra186_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int level)
+{
+ struct tegra_gpio *gpio = gpiochip_get_data(chip);
+ void __iomem *base;
+ u32 value;
+
+ base = tegra186_gpio_get_base(gpio, offset);
+ if (WARN_ON(base == NULL))
+ return;
+
+ value = readl(base + TEGRA186_GPIO_OUTPUT_VALUE);
+ if (level == 0)
+ value &= ~TEGRA186_GPIO_OUTPUT_VALUE_HIGH;
+ else
+ value |= TEGRA186_GPIO_OUTPUT_VALUE_HIGH;
+
+ writel(value, base + TEGRA186_GPIO_OUTPUT_VALUE);
+}
+
+static int tegra186_gpio_set_config(struct gpio_chip *chip,
+ unsigned int offset,
+ unsigned long config)
+{
+ struct tegra_gpio *gpio = gpiochip_get_data(chip);
+ u32 debounce, value;
+ void __iomem *base;
+
+ base = tegra186_gpio_get_base(gpio, offset);
+ if (base == NULL)
+ return -ENXIO;
+
+ if (pinconf_to_config_param(config) != PIN_CONFIG_INPUT_DEBOUNCE)
+ return -ENOTSUPP;
+
+ debounce = pinconf_to_config_argument(config);
+
+ /*
+ * The Tegra186 GPIO controller supports a maximum of 255 ms debounce
+ * time.
+ */
+ if (debounce > 255000)
+ return -EINVAL;
+
+ debounce = DIV_ROUND_UP(debounce, USEC_PER_MSEC);
+
+ value = TEGRA186_GPIO_DEBOUNCE_CONTROL_THRESHOLD(debounce);
+ writel(value, base + TEGRA186_GPIO_DEBOUNCE_CONTROL);
+
+ value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG);
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_DEBOUNCE;
+ writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG);
+
+ return 0;
+}
+
+static int tegra186_gpio_add_pin_ranges(struct gpio_chip *chip)
+{
+ struct tegra_gpio *gpio = gpiochip_get_data(chip);
+ struct pinctrl_dev *pctldev;
+ struct device_node *np;
+ unsigned int i, j;
+ int err;
+
+ if (!gpio->soc->pinmux || gpio->soc->num_pin_ranges == 0)
+ return 0;
+
+ np = of_find_compatible_node(NULL, NULL, gpio->soc->pinmux);
+ if (!np)
+ return -ENODEV;
+
+ pctldev = of_pinctrl_get(np);
+ of_node_put(np);
+ if (!pctldev)
+ return -EPROBE_DEFER;
+
+ for (i = 0; i < gpio->soc->num_pin_ranges; i++) {
+ unsigned int pin = gpio->soc->pin_ranges[i].offset, port;
+ const char *group = gpio->soc->pin_ranges[i].group;
+
+ port = pin / 8;
+ pin = pin % 8;
+
+ if (port >= gpio->soc->num_ports) {
+ dev_warn(chip->parent, "invalid port %u for %s\n",
+ port, group);
+ continue;
+ }
+
+ for (j = 0; j < port; j++)
+ pin += gpio->soc->ports[j].pins;
+
+ err = gpiochip_add_pingroup_range(chip, pctldev, pin, group);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int tegra186_gpio_of_xlate(struct gpio_chip *chip,
+ const struct of_phandle_args *spec,
+ u32 *flags)
+{
+ struct tegra_gpio *gpio = gpiochip_get_data(chip);
+ unsigned int port, pin, i, offset = 0;
+
+ if (WARN_ON(chip->of_gpio_n_cells < 2))
+ return -EINVAL;
+
+ if (WARN_ON(spec->args_count < chip->of_gpio_n_cells))
+ return -EINVAL;
+
+ port = spec->args[0] / 8;
+ pin = spec->args[0] % 8;
+
+ if (port >= gpio->soc->num_ports) {
+ dev_err(chip->parent, "invalid port number: %u\n", port);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < port; i++)
+ offset += gpio->soc->ports[i].pins;
+
+ if (flags)
+ *flags = spec->args[1];
+
+ return offset + pin;
+}
+
+#define to_tegra_gpio(x) container_of((x), struct tegra_gpio, gpio)
+
+static void tegra186_irq_ack(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct tegra_gpio *gpio = to_tegra_gpio(gc);
+ void __iomem *base;
+
+ base = tegra186_gpio_get_base(gpio, data->hwirq);
+ if (WARN_ON(base == NULL))
+ return;
+
+ writel(1, base + TEGRA186_GPIO_INTERRUPT_CLEAR);
+}
+
+static void tegra186_irq_mask(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct tegra_gpio *gpio = to_tegra_gpio(gc);
+ void __iomem *base;
+ u32 value;
+
+ base = tegra186_gpio_get_base(gpio, data->hwirq);
+ if (WARN_ON(base == NULL))
+ return;
+
+ value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG);
+ value &= ~TEGRA186_GPIO_ENABLE_CONFIG_INTERRUPT;
+ writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG);
+
+ gpiochip_disable_irq(&gpio->gpio, data->hwirq);
+}
+
+static void tegra186_irq_unmask(struct irq_data *data)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct tegra_gpio *gpio = to_tegra_gpio(gc);
+ void __iomem *base;
+ u32 value;
+
+ base = tegra186_gpio_get_base(gpio, data->hwirq);
+ if (WARN_ON(base == NULL))
+ return;
+
+ gpiochip_enable_irq(&gpio->gpio, data->hwirq);
+
+ value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG);
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_INTERRUPT;
+ writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG);
+}
+
+static int tegra186_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct tegra_gpio *gpio = to_tegra_gpio(gc);
+ void __iomem *base;
+ u32 value;
+
+ base = tegra186_gpio_get_base(gpio, data->hwirq);
+ if (WARN_ON(base == NULL))
+ return -ENODEV;
+
+ value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG);
+ value &= ~TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_MASK;
+ value &= ~TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_NONE:
+ break;
+
+ case IRQ_TYPE_EDGE_RISING:
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_SINGLE_EDGE;
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL;
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_SINGLE_EDGE;
+ break;
+
+ case IRQ_TYPE_EDGE_BOTH:
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_DOUBLE_EDGE;
+ break;
+
+ case IRQ_TYPE_LEVEL_HIGH:
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_LEVEL;
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL;
+ break;
+
+ case IRQ_TYPE_LEVEL_LOW:
+ value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_LEVEL;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG);
+
+ if ((type & IRQ_TYPE_EDGE_BOTH) == 0)
+ irq_set_handler_locked(data, handle_level_irq);
+ else
+ irq_set_handler_locked(data, handle_edge_irq);
+
+ if (data->parent_data)
+ return irq_chip_set_type_parent(data, type);
+
+ return 0;
+}
+
+static int tegra186_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+ if (data->parent_data)
+ return irq_chip_set_wake_parent(data, on);
+
+ return 0;
+}
+
+static void tegra186_irq_print_chip(struct irq_data *data, struct seq_file *p)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+
+ seq_printf(p, dev_name(gc->parent));
+}
+
+static const struct irq_chip tegra186_gpio_irq_chip = {
+ .irq_ack = tegra186_irq_ack,
+ .irq_mask = tegra186_irq_mask,
+ .irq_unmask = tegra186_irq_unmask,
+ .irq_set_type = tegra186_irq_set_type,
+ .irq_set_wake = tegra186_irq_set_wake,
+ .irq_print_chip = tegra186_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static void tegra186_gpio_irq(struct irq_desc *desc)
+{
+ struct tegra_gpio *gpio = irq_desc_get_handler_data(desc);
+ struct irq_domain *domain = gpio->gpio.irq.domain;
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int parent = irq_desc_get_irq(desc);
+ unsigned int i, j, offset = 0;
+
+ chained_irq_enter(chip, desc);
+
+ for (i = 0; i < gpio->soc->num_ports; i++) {
+ const struct tegra_gpio_port *port = &gpio->soc->ports[i];
+ unsigned int pin;
+ unsigned long value;
+ void __iomem *base;
+
+ base = gpio->base + port->bank * 0x1000 + port->port * 0x200;
+
+ /* skip ports that are not associated with this bank */
+ for (j = 0; j < gpio->num_irqs_per_bank; j++) {
+ if (parent == gpio->irq[port->bank * gpio->num_irqs_per_bank + j])
+ break;
+ }
+
+ if (j == gpio->num_irqs_per_bank)
+ goto skip;
+
+ value = readl(base + TEGRA186_GPIO_INTERRUPT_STATUS(1));
+
+ for_each_set_bit(pin, &value, port->pins) {
+ int ret = generic_handle_domain_irq(domain, offset + pin);
+ WARN_RATELIMIT(ret, "hwirq = %d", offset + pin);
+ }
+
+skip:
+ offset += port->pins;
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static int tegra186_gpio_irq_domain_translate(struct irq_domain *domain,
+ struct irq_fwspec *fwspec,
+ unsigned long *hwirq,
+ unsigned int *type)
+{
+ struct tegra_gpio *gpio = gpiochip_get_data(domain->host_data);
+ unsigned int port, pin, i, offset = 0;
+
+ if (WARN_ON(gpio->gpio.of_gpio_n_cells < 2))
+ return -EINVAL;
+
+ if (WARN_ON(fwspec->param_count < gpio->gpio.of_gpio_n_cells))
+ return -EINVAL;
+
+ port = fwspec->param[0] / 8;
+ pin = fwspec->param[0] % 8;
+
+ if (port >= gpio->soc->num_ports)
+ return -EINVAL;
+
+ for (i = 0; i < port; i++)
+ offset += gpio->soc->ports[i].pins;
+
+ *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
+ *hwirq = offset + pin;
+
+ return 0;
+}
+
+static int tegra186_gpio_populate_parent_fwspec(struct gpio_chip *chip,
+ union gpio_irq_fwspec *gfwspec,
+ unsigned int parent_hwirq,
+ unsigned int parent_type)
+{
+ struct tegra_gpio *gpio = gpiochip_get_data(chip);
+ struct irq_fwspec *fwspec = &gfwspec->fwspec;
+
+ fwspec->fwnode = chip->irq.parent_domain->fwnode;
+ fwspec->param_count = 3;
+ fwspec->param[0] = gpio->soc->instance;
+ fwspec->param[1] = parent_hwirq;
+ fwspec->param[2] = parent_type;
+
+ return 0;
+}
+
+static int tegra186_gpio_child_to_parent_hwirq(struct gpio_chip *chip,
+ unsigned int hwirq,
+ unsigned int type,
+ unsigned int *parent_hwirq,
+ unsigned int *parent_type)
+{
+ *parent_hwirq = chip->irq.child_offset_to_irq(chip, hwirq);
+ *parent_type = type;
+
+ return 0;
+}
+
+static unsigned int tegra186_gpio_child_offset_to_irq(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct tegra_gpio *gpio = gpiochip_get_data(chip);
+ unsigned int i;
+
+ for (i = 0; i < gpio->soc->num_ports; i++) {
+ if (offset < gpio->soc->ports[i].pins)
+ break;
+
+ offset -= gpio->soc->ports[i].pins;
+ }
+
+ return offset + i * 8;
+}
+
+static const struct of_device_id tegra186_pmc_of_match[] = {
+ { .compatible = "nvidia,tegra186-pmc" },
+ { .compatible = "nvidia,tegra194-pmc" },
+ { .compatible = "nvidia,tegra234-pmc" },
+ { /* sentinel */ }
+};
+
+static void tegra186_gpio_init_route_mapping(struct tegra_gpio *gpio)
+{
+ struct device *dev = gpio->gpio.parent;
+ unsigned int i;
+ u32 value;
+
+ for (i = 0; i < gpio->soc->num_ports; i++) {
+ const struct tegra_gpio_port *port = &gpio->soc->ports[i];
+ unsigned int offset, p = port->port;
+ void __iomem *base;
+
+ base = gpio->secure + port->bank * 0x1000 + 0x800;
+
+ value = readl(base + TEGRA186_GPIO_CTL_SCR);
+
+ /*
+ * For controllers that haven't been locked down yet, make
+ * sure to program the default interrupt route mapping.
+ */
+ if ((value & TEGRA186_GPIO_CTL_SCR_SEC_REN) == 0 &&
+ (value & TEGRA186_GPIO_CTL_SCR_SEC_WEN) == 0) {
+ /*
+ * On Tegra194 and later, each pin can be routed to one or more
+ * interrupts.
+ */
+ dev_dbg(dev, "programming default interrupt routing for port %s\n",
+ port->name);
+
+ offset = TEGRA186_GPIO_INT_ROUTE_MAPPING(p, 0);
+
+ /*
+ * By default we only want to route GPIO pins to IRQ 0. This works
+ * only under the assumption that we're running as the host kernel
+ * and hence all GPIO pins are owned by Linux.
+ *
+ * For cases where Linux is the guest OS, the hypervisor will have
+ * to configure the interrupt routing and pass only the valid
+ * interrupts via device tree.
+ */
+ value = readl(base + offset);
+ value = BIT(port->pins) - 1;
+ writel(value, base + offset);
+ }
+ }
+}
+
+static unsigned int tegra186_gpio_irqs_per_bank(struct tegra_gpio *gpio)
+{
+ struct device *dev = gpio->gpio.parent;
+
+ if (gpio->num_irq > gpio->num_banks) {
+ if (gpio->num_irq % gpio->num_banks != 0)
+ goto error;
+ }
+
+ if (gpio->num_irq < gpio->num_banks)
+ goto error;
+
+ gpio->num_irqs_per_bank = gpio->num_irq / gpio->num_banks;
+
+ if (gpio->num_irqs_per_bank > gpio->soc->num_irqs_per_bank)
+ goto error;
+
+ return 0;
+
+error:
+ dev_err(dev, "invalid number of interrupts (%u) for %u banks\n",
+ gpio->num_irq, gpio->num_banks);
+ return -EINVAL;
+}
+
+static int tegra186_gpio_probe(struct platform_device *pdev)
+{
+ unsigned int i, j, offset;
+ struct gpio_irq_chip *irq;
+ struct tegra_gpio *gpio;
+ struct device_node *np;
+ char **names;
+ int err;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->soc = device_get_match_data(&pdev->dev);
+ gpio->gpio.label = gpio->soc->name;
+ gpio->gpio.parent = &pdev->dev;
+
+ /* count the number of banks in the controller */
+ for (i = 0; i < gpio->soc->num_ports; i++)
+ if (gpio->soc->ports[i].bank > gpio->num_banks)
+ gpio->num_banks = gpio->soc->ports[i].bank;
+
+ gpio->num_banks++;
+
+ /* get register apertures */
+ gpio->secure = devm_platform_ioremap_resource_byname(pdev, "security");
+ if (IS_ERR(gpio->secure)) {
+ gpio->secure = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gpio->secure))
+ return PTR_ERR(gpio->secure);
+ }
+
+ gpio->base = devm_platform_ioremap_resource_byname(pdev, "gpio");
+ if (IS_ERR(gpio->base)) {
+ gpio->base = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(gpio->base))
+ return PTR_ERR(gpio->base);
+ }
+
+ err = platform_irq_count(pdev);
+ if (err < 0)
+ return err;
+
+ gpio->num_irq = err;
+
+ err = tegra186_gpio_irqs_per_bank(gpio);
+ if (err < 0)
+ return err;
+
+ gpio->irq = devm_kcalloc(&pdev->dev, gpio->num_irq, sizeof(*gpio->irq),
+ GFP_KERNEL);
+ if (!gpio->irq)
+ return -ENOMEM;
+
+ for (i = 0; i < gpio->num_irq; i++) {
+ err = platform_get_irq(pdev, i);
+ if (err < 0)
+ return err;
+
+ gpio->irq[i] = err;
+ }
+
+ gpio->gpio.request = gpiochip_generic_request;
+ gpio->gpio.free = gpiochip_generic_free;
+ gpio->gpio.get_direction = tegra186_gpio_get_direction;
+ gpio->gpio.direction_input = tegra186_gpio_direction_input;
+ gpio->gpio.direction_output = tegra186_gpio_direction_output;
+ gpio->gpio.get = tegra186_gpio_get;
+ gpio->gpio.set = tegra186_gpio_set;
+ gpio->gpio.set_config = tegra186_gpio_set_config;
+ gpio->gpio.add_pin_ranges = tegra186_gpio_add_pin_ranges;
+ gpio->gpio.init_valid_mask = tegra186_init_valid_mask;
+ if (gpio->soc->has_gte) {
+ gpio->gpio.en_hw_timestamp = tegra186_gpio_en_hw_ts;
+ gpio->gpio.dis_hw_timestamp = tegra186_gpio_dis_hw_ts;
+ }
+
+ gpio->gpio.base = -1;
+
+ for (i = 0; i < gpio->soc->num_ports; i++)
+ gpio->gpio.ngpio += gpio->soc->ports[i].pins;
+
+ names = devm_kcalloc(gpio->gpio.parent, gpio->gpio.ngpio,
+ sizeof(*names), GFP_KERNEL);
+ if (!names)
+ return -ENOMEM;
+
+ for (i = 0, offset = 0; i < gpio->soc->num_ports; i++) {
+ const struct tegra_gpio_port *port = &gpio->soc->ports[i];
+ char *name;
+
+ for (j = 0; j < port->pins; j++) {
+ name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL,
+ "P%s.%02x", port->name, j);
+ if (!name)
+ return -ENOMEM;
+
+ names[offset + j] = name;
+ }
+
+ offset += port->pins;
+ }
+
+ gpio->gpio.names = (const char * const *)names;
+
+#if defined(CONFIG_OF_GPIO)
+ gpio->gpio.of_gpio_n_cells = 2;
+ gpio->gpio.of_xlate = tegra186_gpio_of_xlate;
+#endif /* CONFIG_OF_GPIO */
+
+ irq = &gpio->gpio.irq;
+ gpio_irq_chip_set_chip(irq, &tegra186_gpio_irq_chip);
+ irq->fwnode = of_node_to_fwnode(pdev->dev.of_node);
+ irq->child_to_parent_hwirq = tegra186_gpio_child_to_parent_hwirq;
+ irq->populate_parent_alloc_arg = tegra186_gpio_populate_parent_fwspec;
+ irq->child_offset_to_irq = tegra186_gpio_child_offset_to_irq;
+ irq->child_irq_domain_ops.translate = tegra186_gpio_irq_domain_translate;
+ irq->handler = handle_simple_irq;
+ irq->default_type = IRQ_TYPE_NONE;
+ irq->parent_handler = tegra186_gpio_irq;
+ irq->parent_handler_data = gpio;
+ irq->num_parents = gpio->num_irq;
+
+ /*
+ * To simplify things, use a single interrupt per bank for now. Some
+ * chips support up to 8 interrupts per bank, which can be useful to
+ * distribute the load and decrease the processing latency for GPIOs
+ * but it also requires a more complicated interrupt routing than we
+ * currently program.
+ */
+ if (gpio->num_irqs_per_bank > 1) {
+ irq->parents = devm_kcalloc(&pdev->dev, gpio->num_banks,
+ sizeof(*irq->parents), GFP_KERNEL);
+ if (!irq->parents)
+ return -ENOMEM;
+
+ for (i = 0; i < gpio->num_banks; i++)
+ irq->parents[i] = gpio->irq[i * gpio->num_irqs_per_bank];
+
+ irq->num_parents = gpio->num_banks;
+ } else {
+ irq->num_parents = gpio->num_irq;
+ irq->parents = gpio->irq;
+ }
+
+ if (gpio->soc->num_irqs_per_bank > 1)
+ tegra186_gpio_init_route_mapping(gpio);
+
+ np = of_find_matching_node(NULL, tegra186_pmc_of_match);
+ if (np) {
+ if (of_device_is_available(np)) {
+ irq->parent_domain = irq_find_host(np);
+ of_node_put(np);
+
+ if (!irq->parent_domain)
+ return -EPROBE_DEFER;
+ } else {
+ of_node_put(np);
+ }
+ }
+
+ irq->map = devm_kcalloc(&pdev->dev, gpio->gpio.ngpio,
+ sizeof(*irq->map), GFP_KERNEL);
+ if (!irq->map)
+ return -ENOMEM;
+
+ for (i = 0, offset = 0; i < gpio->soc->num_ports; i++) {
+ const struct tegra_gpio_port *port = &gpio->soc->ports[i];
+
+ for (j = 0; j < port->pins; j++)
+ irq->map[offset + j] = irq->parents[port->bank];
+
+ offset += port->pins;
+ }
+
+ return devm_gpiochip_add_data(&pdev->dev, &gpio->gpio, gpio);
+}
+
+#define TEGRA186_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \
+ [TEGRA186_MAIN_GPIO_PORT_##_name] = { \
+ .name = #_name, \
+ .bank = _bank, \
+ .port = _port, \
+ .pins = _pins, \
+ }
+
+static const struct tegra_gpio_port tegra186_main_ports[] = {
+ TEGRA186_MAIN_GPIO_PORT( A, 2, 0, 7),
+ TEGRA186_MAIN_GPIO_PORT( B, 3, 0, 7),
+ TEGRA186_MAIN_GPIO_PORT( C, 3, 1, 7),
+ TEGRA186_MAIN_GPIO_PORT( D, 3, 2, 6),
+ TEGRA186_MAIN_GPIO_PORT( E, 2, 1, 8),
+ TEGRA186_MAIN_GPIO_PORT( F, 2, 2, 6),
+ TEGRA186_MAIN_GPIO_PORT( G, 4, 1, 6),
+ TEGRA186_MAIN_GPIO_PORT( H, 1, 0, 7),
+ TEGRA186_MAIN_GPIO_PORT( I, 0, 4, 8),
+ TEGRA186_MAIN_GPIO_PORT( J, 5, 0, 8),
+ TEGRA186_MAIN_GPIO_PORT( K, 5, 1, 1),
+ TEGRA186_MAIN_GPIO_PORT( L, 1, 1, 8),
+ TEGRA186_MAIN_GPIO_PORT( M, 5, 3, 6),
+ TEGRA186_MAIN_GPIO_PORT( N, 0, 0, 7),
+ TEGRA186_MAIN_GPIO_PORT( O, 0, 1, 4),
+ TEGRA186_MAIN_GPIO_PORT( P, 4, 0, 7),
+ TEGRA186_MAIN_GPIO_PORT( Q, 0, 2, 6),
+ TEGRA186_MAIN_GPIO_PORT( R, 0, 5, 6),
+ TEGRA186_MAIN_GPIO_PORT( T, 0, 3, 4),
+ TEGRA186_MAIN_GPIO_PORT( X, 1, 2, 8),
+ TEGRA186_MAIN_GPIO_PORT( Y, 1, 3, 7),
+ TEGRA186_MAIN_GPIO_PORT(BB, 2, 3, 2),
+ TEGRA186_MAIN_GPIO_PORT(CC, 5, 2, 4),
+};
+
+static const struct tegra_gpio_soc tegra186_main_soc = {
+ .num_ports = ARRAY_SIZE(tegra186_main_ports),
+ .ports = tegra186_main_ports,
+ .name = "tegra186-gpio",
+ .instance = 0,
+ .num_irqs_per_bank = 1,
+ .has_vm_support = false,
+};
+
+#define TEGRA186_AON_GPIO_PORT(_name, _bank, _port, _pins) \
+ [TEGRA186_AON_GPIO_PORT_##_name] = { \
+ .name = #_name, \
+ .bank = _bank, \
+ .port = _port, \
+ .pins = _pins, \
+ }
+
+static const struct tegra_gpio_port tegra186_aon_ports[] = {
+ TEGRA186_AON_GPIO_PORT( S, 0, 1, 5),
+ TEGRA186_AON_GPIO_PORT( U, 0, 2, 6),
+ TEGRA186_AON_GPIO_PORT( V, 0, 4, 8),
+ TEGRA186_AON_GPIO_PORT( W, 0, 5, 8),
+ TEGRA186_AON_GPIO_PORT( Z, 0, 7, 4),
+ TEGRA186_AON_GPIO_PORT(AA, 0, 6, 8),
+ TEGRA186_AON_GPIO_PORT(EE, 0, 3, 3),
+ TEGRA186_AON_GPIO_PORT(FF, 0, 0, 5),
+};
+
+static const struct tegra_gpio_soc tegra186_aon_soc = {
+ .num_ports = ARRAY_SIZE(tegra186_aon_ports),
+ .ports = tegra186_aon_ports,
+ .name = "tegra186-gpio-aon",
+ .instance = 1,
+ .num_irqs_per_bank = 1,
+ .has_vm_support = false,
+};
+
+#define TEGRA194_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \
+ [TEGRA194_MAIN_GPIO_PORT_##_name] = { \
+ .name = #_name, \
+ .bank = _bank, \
+ .port = _port, \
+ .pins = _pins, \
+ }
+
+static const struct tegra_gpio_port tegra194_main_ports[] = {
+ TEGRA194_MAIN_GPIO_PORT( A, 1, 2, 8),
+ TEGRA194_MAIN_GPIO_PORT( B, 4, 7, 2),
+ TEGRA194_MAIN_GPIO_PORT( C, 4, 3, 8),
+ TEGRA194_MAIN_GPIO_PORT( D, 4, 4, 4),
+ TEGRA194_MAIN_GPIO_PORT( E, 4, 5, 8),
+ TEGRA194_MAIN_GPIO_PORT( F, 4, 6, 6),
+ TEGRA194_MAIN_GPIO_PORT( G, 4, 0, 8),
+ TEGRA194_MAIN_GPIO_PORT( H, 4, 1, 8),
+ TEGRA194_MAIN_GPIO_PORT( I, 4, 2, 5),
+ TEGRA194_MAIN_GPIO_PORT( J, 5, 1, 6),
+ TEGRA194_MAIN_GPIO_PORT( K, 3, 0, 8),
+ TEGRA194_MAIN_GPIO_PORT( L, 3, 1, 4),
+ TEGRA194_MAIN_GPIO_PORT( M, 2, 3, 8),
+ TEGRA194_MAIN_GPIO_PORT( N, 2, 4, 3),
+ TEGRA194_MAIN_GPIO_PORT( O, 5, 0, 6),
+ TEGRA194_MAIN_GPIO_PORT( P, 2, 5, 8),
+ TEGRA194_MAIN_GPIO_PORT( Q, 2, 6, 8),
+ TEGRA194_MAIN_GPIO_PORT( R, 2, 7, 6),
+ TEGRA194_MAIN_GPIO_PORT( S, 3, 3, 8),
+ TEGRA194_MAIN_GPIO_PORT( T, 3, 4, 8),
+ TEGRA194_MAIN_GPIO_PORT( U, 3, 5, 1),
+ TEGRA194_MAIN_GPIO_PORT( V, 1, 0, 8),
+ TEGRA194_MAIN_GPIO_PORT( W, 1, 1, 2),
+ TEGRA194_MAIN_GPIO_PORT( X, 2, 0, 8),
+ TEGRA194_MAIN_GPIO_PORT( Y, 2, 1, 8),
+ TEGRA194_MAIN_GPIO_PORT( Z, 2, 2, 8),
+ TEGRA194_MAIN_GPIO_PORT(FF, 3, 2, 2),
+ TEGRA194_MAIN_GPIO_PORT(GG, 0, 0, 2)
+};
+
+static const struct tegra186_pin_range tegra194_main_pin_ranges[] = {
+ { TEGRA194_MAIN_GPIO(GG, 0), "pex_l5_clkreq_n_pgg0" },
+ { TEGRA194_MAIN_GPIO(GG, 1), "pex_l5_rst_n_pgg1" },
+};
+
+static const struct tegra_gpio_soc tegra194_main_soc = {
+ .num_ports = ARRAY_SIZE(tegra194_main_ports),
+ .ports = tegra194_main_ports,
+ .name = "tegra194-gpio",
+ .instance = 0,
+ .num_irqs_per_bank = 8,
+ .num_pin_ranges = ARRAY_SIZE(tegra194_main_pin_ranges),
+ .pin_ranges = tegra194_main_pin_ranges,
+ .pinmux = "nvidia,tegra194-pinmux",
+ .has_vm_support = true,
+};
+
+#define TEGRA194_AON_GPIO_PORT(_name, _bank, _port, _pins) \
+ [TEGRA194_AON_GPIO_PORT_##_name] = { \
+ .name = #_name, \
+ .bank = _bank, \
+ .port = _port, \
+ .pins = _pins, \
+ }
+
+static const struct tegra_gpio_port tegra194_aon_ports[] = {
+ TEGRA194_AON_GPIO_PORT(AA, 0, 3, 8),
+ TEGRA194_AON_GPIO_PORT(BB, 0, 4, 4),
+ TEGRA194_AON_GPIO_PORT(CC, 0, 1, 8),
+ TEGRA194_AON_GPIO_PORT(DD, 0, 2, 3),
+ TEGRA194_AON_GPIO_PORT(EE, 0, 0, 7)
+};
+
+static const struct tegra_gpio_soc tegra194_aon_soc = {
+ .num_ports = ARRAY_SIZE(tegra194_aon_ports),
+ .ports = tegra194_aon_ports,
+ .name = "tegra194-gpio-aon",
+ .instance = 1,
+ .num_irqs_per_bank = 8,
+ .has_gte = true,
+ .has_vm_support = false,
+};
+
+#define TEGRA234_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \
+ [TEGRA234_MAIN_GPIO_PORT_##_name] = { \
+ .name = #_name, \
+ .bank = _bank, \
+ .port = _port, \
+ .pins = _pins, \
+ }
+
+static const struct tegra_gpio_port tegra234_main_ports[] = {
+ TEGRA234_MAIN_GPIO_PORT( A, 0, 0, 8),
+ TEGRA234_MAIN_GPIO_PORT( B, 0, 3, 1),
+ TEGRA234_MAIN_GPIO_PORT( C, 5, 1, 8),
+ TEGRA234_MAIN_GPIO_PORT( D, 5, 2, 4),
+ TEGRA234_MAIN_GPIO_PORT( E, 5, 3, 8),
+ TEGRA234_MAIN_GPIO_PORT( F, 5, 4, 6),
+ TEGRA234_MAIN_GPIO_PORT( G, 4, 0, 8),
+ TEGRA234_MAIN_GPIO_PORT( H, 4, 1, 8),
+ TEGRA234_MAIN_GPIO_PORT( I, 4, 2, 7),
+ TEGRA234_MAIN_GPIO_PORT( J, 5, 0, 6),
+ TEGRA234_MAIN_GPIO_PORT( K, 3, 0, 8),
+ TEGRA234_MAIN_GPIO_PORT( L, 3, 1, 4),
+ TEGRA234_MAIN_GPIO_PORT( M, 2, 0, 8),
+ TEGRA234_MAIN_GPIO_PORT( N, 2, 1, 8),
+ TEGRA234_MAIN_GPIO_PORT( P, 2, 2, 8),
+ TEGRA234_MAIN_GPIO_PORT( Q, 2, 3, 8),
+ TEGRA234_MAIN_GPIO_PORT( R, 2, 4, 6),
+ TEGRA234_MAIN_GPIO_PORT( X, 1, 0, 8),
+ TEGRA234_MAIN_GPIO_PORT( Y, 1, 1, 8),
+ TEGRA234_MAIN_GPIO_PORT( Z, 1, 2, 8),
+ TEGRA234_MAIN_GPIO_PORT(AC, 0, 1, 8),
+ TEGRA234_MAIN_GPIO_PORT(AD, 0, 2, 4),
+ TEGRA234_MAIN_GPIO_PORT(AE, 3, 3, 2),
+ TEGRA234_MAIN_GPIO_PORT(AF, 3, 4, 4),
+ TEGRA234_MAIN_GPIO_PORT(AG, 3, 2, 8),
+};
+
+static const struct tegra_gpio_soc tegra234_main_soc = {
+ .num_ports = ARRAY_SIZE(tegra234_main_ports),
+ .ports = tegra234_main_ports,
+ .name = "tegra234-gpio",
+ .instance = 0,
+ .num_irqs_per_bank = 8,
+ .has_vm_support = true,
+};
+
+#define TEGRA234_AON_GPIO_PORT(_name, _bank, _port, _pins) \
+ [TEGRA234_AON_GPIO_PORT_##_name] = { \
+ .name = #_name, \
+ .bank = _bank, \
+ .port = _port, \
+ .pins = _pins, \
+ }
+
+static const struct tegra_gpio_port tegra234_aon_ports[] = {
+ TEGRA234_AON_GPIO_PORT(AA, 0, 4, 8),
+ TEGRA234_AON_GPIO_PORT(BB, 0, 5, 4),
+ TEGRA234_AON_GPIO_PORT(CC, 0, 2, 8),
+ TEGRA234_AON_GPIO_PORT(DD, 0, 3, 3),
+ TEGRA234_AON_GPIO_PORT(EE, 0, 0, 8),
+ TEGRA234_AON_GPIO_PORT(GG, 0, 1, 1),
+};
+
+static const struct tegra_gpio_soc tegra234_aon_soc = {
+ .num_ports = ARRAY_SIZE(tegra234_aon_ports),
+ .ports = tegra234_aon_ports,
+ .name = "tegra234-gpio-aon",
+ .instance = 1,
+ .num_irqs_per_bank = 8,
+ .has_gte = true,
+ .has_vm_support = false,
+};
+
+#define TEGRA241_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \
+ [TEGRA241_MAIN_GPIO_PORT_##_name] = { \
+ .name = #_name, \
+ .bank = _bank, \
+ .port = _port, \
+ .pins = _pins, \
+ }
+
+static const struct tegra_gpio_port tegra241_main_ports[] = {
+ TEGRA241_MAIN_GPIO_PORT(A, 0, 0, 8),
+ TEGRA241_MAIN_GPIO_PORT(B, 0, 1, 8),
+ TEGRA241_MAIN_GPIO_PORT(C, 0, 2, 2),
+ TEGRA241_MAIN_GPIO_PORT(D, 0, 3, 6),
+ TEGRA241_MAIN_GPIO_PORT(E, 0, 4, 8),
+ TEGRA241_MAIN_GPIO_PORT(F, 1, 0, 8),
+ TEGRA241_MAIN_GPIO_PORT(G, 1, 1, 8),
+ TEGRA241_MAIN_GPIO_PORT(H, 1, 2, 8),
+ TEGRA241_MAIN_GPIO_PORT(J, 1, 3, 8),
+ TEGRA241_MAIN_GPIO_PORT(K, 1, 4, 4),
+ TEGRA241_MAIN_GPIO_PORT(L, 1, 5, 6),
+};
+
+static const struct tegra_gpio_soc tegra241_main_soc = {
+ .num_ports = ARRAY_SIZE(tegra241_main_ports),
+ .ports = tegra241_main_ports,
+ .name = "tegra241-gpio",
+ .instance = 0,
+ .num_irqs_per_bank = 8,
+ .has_vm_support = false,
+};
+
+#define TEGRA241_AON_GPIO_PORT(_name, _bank, _port, _pins) \
+ [TEGRA241_AON_GPIO_PORT_##_name] = { \
+ .name = #_name, \
+ .bank = _bank, \
+ .port = _port, \
+ .pins = _pins, \
+ }
+
+static const struct tegra_gpio_port tegra241_aon_ports[] = {
+ TEGRA241_AON_GPIO_PORT(AA, 0, 0, 8),
+ TEGRA241_AON_GPIO_PORT(BB, 0, 0, 4),
+};
+
+static const struct tegra_gpio_soc tegra241_aon_soc = {
+ .num_ports = ARRAY_SIZE(tegra241_aon_ports),
+ .ports = tegra241_aon_ports,
+ .name = "tegra241-gpio-aon",
+ .instance = 1,
+ .num_irqs_per_bank = 8,
+ .has_vm_support = false,
+};
+
+static const struct of_device_id tegra186_gpio_of_match[] = {
+ {
+ .compatible = "nvidia,tegra186-gpio",
+ .data = &tegra186_main_soc
+ }, {
+ .compatible = "nvidia,tegra186-gpio-aon",
+ .data = &tegra186_aon_soc
+ }, {
+ .compatible = "nvidia,tegra194-gpio",
+ .data = &tegra194_main_soc
+ }, {
+ .compatible = "nvidia,tegra194-gpio-aon",
+ .data = &tegra194_aon_soc
+ }, {
+ .compatible = "nvidia,tegra234-gpio",
+ .data = &tegra234_main_soc
+ }, {
+ .compatible = "nvidia,tegra234-gpio-aon",
+ .data = &tegra234_aon_soc
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, tegra186_gpio_of_match);
+
+static const struct acpi_device_id tegra186_gpio_acpi_match[] = {
+ { .id = "NVDA0108", .driver_data = (kernel_ulong_t)&tegra186_main_soc },
+ { .id = "NVDA0208", .driver_data = (kernel_ulong_t)&tegra186_aon_soc },
+ { .id = "NVDA0308", .driver_data = (kernel_ulong_t)&tegra194_main_soc },
+ { .id = "NVDA0408", .driver_data = (kernel_ulong_t)&tegra194_aon_soc },
+ { .id = "NVDA0508", .driver_data = (kernel_ulong_t)&tegra241_main_soc },
+ { .id = "NVDA0608", .driver_data = (kernel_ulong_t)&tegra241_aon_soc },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, tegra186_gpio_acpi_match);
+
+static struct platform_driver tegra186_gpio_driver = {
+ .driver = {
+ .name = "tegra186-gpio",
+ .of_match_table = tegra186_gpio_of_match,
+ .acpi_match_table = tegra186_gpio_acpi_match,
+ },
+ .probe = tegra186_gpio_probe,
+};
+module_platform_driver(tegra186_gpio_driver);
+
+MODULE_DESCRIPTION("NVIDIA Tegra186 GPIO controller driver");
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-thunderx.c b/drivers/gpio/gpio-thunderx.c
new file mode 100644
index 0000000000..8521c6aaca
--- /dev/null
+++ b/drivers/gpio/gpio-thunderx.c
@@ -0,0 +1,602 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2016, 2017 Cavium Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+
+#define GPIO_RX_DAT 0x0
+#define GPIO_TX_SET 0x8
+#define GPIO_TX_CLR 0x10
+#define GPIO_CONST 0x90
+#define GPIO_CONST_GPIOS_MASK 0xff
+#define GPIO_BIT_CFG 0x400
+#define GPIO_BIT_CFG_TX_OE BIT(0)
+#define GPIO_BIT_CFG_PIN_XOR BIT(1)
+#define GPIO_BIT_CFG_INT_EN BIT(2)
+#define GPIO_BIT_CFG_INT_TYPE BIT(3)
+#define GPIO_BIT_CFG_FIL_MASK GENMASK(11, 4)
+#define GPIO_BIT_CFG_FIL_CNT_SHIFT 4
+#define GPIO_BIT_CFG_FIL_SEL_SHIFT 8
+#define GPIO_BIT_CFG_TX_OD BIT(12)
+#define GPIO_BIT_CFG_PIN_SEL_MASK GENMASK(25, 16)
+#define GPIO_INTR 0x800
+#define GPIO_INTR_INTR BIT(0)
+#define GPIO_INTR_INTR_W1S BIT(1)
+#define GPIO_INTR_ENA_W1C BIT(2)
+#define GPIO_INTR_ENA_W1S BIT(3)
+#define GPIO_2ND_BANK 0x1400
+
+#define GLITCH_FILTER_400NS ((4u << GPIO_BIT_CFG_FIL_SEL_SHIFT) | \
+ (9u << GPIO_BIT_CFG_FIL_CNT_SHIFT))
+
+struct thunderx_gpio;
+
+struct thunderx_line {
+ struct thunderx_gpio *txgpio;
+ unsigned int line;
+ unsigned int fil_bits;
+};
+
+struct thunderx_gpio {
+ struct gpio_chip chip;
+ u8 __iomem *register_base;
+ struct msix_entry *msix_entries; /* per line MSI-X */
+ struct thunderx_line *line_entries; /* per line irq info */
+ raw_spinlock_t lock;
+ unsigned long invert_mask[2];
+ unsigned long od_mask[2];
+ int base_msi;
+};
+
+static unsigned int bit_cfg_reg(unsigned int line)
+{
+ return 8 * line + GPIO_BIT_CFG;
+}
+
+static unsigned int intr_reg(unsigned int line)
+{
+ return 8 * line + GPIO_INTR;
+}
+
+static bool thunderx_gpio_is_gpio_nowarn(struct thunderx_gpio *txgpio,
+ unsigned int line)
+{
+ u64 bit_cfg = readq(txgpio->register_base + bit_cfg_reg(line));
+
+ return (bit_cfg & GPIO_BIT_CFG_PIN_SEL_MASK) == 0;
+}
+
+/*
+ * Check (and WARN) that the pin is available for GPIO. We will not
+ * allow modification of the state of non-GPIO pins from this driver.
+ */
+static bool thunderx_gpio_is_gpio(struct thunderx_gpio *txgpio,
+ unsigned int line)
+{
+ bool rv = thunderx_gpio_is_gpio_nowarn(txgpio, line);
+
+ WARN_RATELIMIT(!rv, "Pin %d not available for GPIO\n", line);
+
+ return rv;
+}
+
+static int thunderx_gpio_request(struct gpio_chip *chip, unsigned int line)
+{
+ struct thunderx_gpio *txgpio = gpiochip_get_data(chip);
+
+ return thunderx_gpio_is_gpio(txgpio, line) ? 0 : -EIO;
+}
+
+static int thunderx_gpio_dir_in(struct gpio_chip *chip, unsigned int line)
+{
+ struct thunderx_gpio *txgpio = gpiochip_get_data(chip);
+
+ if (!thunderx_gpio_is_gpio(txgpio, line))
+ return -EIO;
+
+ raw_spin_lock(&txgpio->lock);
+ clear_bit(line, txgpio->invert_mask);
+ clear_bit(line, txgpio->od_mask);
+ writeq(txgpio->line_entries[line].fil_bits,
+ txgpio->register_base + bit_cfg_reg(line));
+ raw_spin_unlock(&txgpio->lock);
+ return 0;
+}
+
+static void thunderx_gpio_set(struct gpio_chip *chip, unsigned int line,
+ int value)
+{
+ struct thunderx_gpio *txgpio = gpiochip_get_data(chip);
+ int bank = line / 64;
+ int bank_bit = line % 64;
+
+ void __iomem *reg = txgpio->register_base +
+ (bank * GPIO_2ND_BANK) + (value ? GPIO_TX_SET : GPIO_TX_CLR);
+
+ writeq(BIT_ULL(bank_bit), reg);
+}
+
+static int thunderx_gpio_dir_out(struct gpio_chip *chip, unsigned int line,
+ int value)
+{
+ struct thunderx_gpio *txgpio = gpiochip_get_data(chip);
+ u64 bit_cfg = txgpio->line_entries[line].fil_bits | GPIO_BIT_CFG_TX_OE;
+
+ if (!thunderx_gpio_is_gpio(txgpio, line))
+ return -EIO;
+
+ raw_spin_lock(&txgpio->lock);
+
+ thunderx_gpio_set(chip, line, value);
+
+ if (test_bit(line, txgpio->invert_mask))
+ bit_cfg |= GPIO_BIT_CFG_PIN_XOR;
+
+ if (test_bit(line, txgpio->od_mask))
+ bit_cfg |= GPIO_BIT_CFG_TX_OD;
+
+ writeq(bit_cfg, txgpio->register_base + bit_cfg_reg(line));
+
+ raw_spin_unlock(&txgpio->lock);
+ return 0;
+}
+
+static int thunderx_gpio_get_direction(struct gpio_chip *chip, unsigned int line)
+{
+ struct thunderx_gpio *txgpio = gpiochip_get_data(chip);
+ u64 bit_cfg;
+
+ if (!thunderx_gpio_is_gpio_nowarn(txgpio, line))
+ /*
+ * Say it is input for now to avoid WARNing on
+ * gpiochip_add_data(). We will WARN if someone
+ * requests it or tries to use it.
+ */
+ return 1;
+
+ bit_cfg = readq(txgpio->register_base + bit_cfg_reg(line));
+
+ if (bit_cfg & GPIO_BIT_CFG_TX_OE)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int thunderx_gpio_set_config(struct gpio_chip *chip,
+ unsigned int line,
+ unsigned long cfg)
+{
+ bool orig_invert, orig_od, orig_dat, new_invert, new_od;
+ u32 arg, sel;
+ u64 bit_cfg;
+ int bank = line / 64;
+ int bank_bit = line % 64;
+ int ret = -ENOTSUPP;
+ struct thunderx_gpio *txgpio = gpiochip_get_data(chip);
+ void __iomem *reg = txgpio->register_base + (bank * GPIO_2ND_BANK) + GPIO_TX_SET;
+
+ if (!thunderx_gpio_is_gpio(txgpio, line))
+ return -EIO;
+
+ raw_spin_lock(&txgpio->lock);
+ orig_invert = test_bit(line, txgpio->invert_mask);
+ new_invert = orig_invert;
+ orig_od = test_bit(line, txgpio->od_mask);
+ new_od = orig_od;
+ orig_dat = ((readq(reg) >> bank_bit) & 1) ^ orig_invert;
+ bit_cfg = readq(txgpio->register_base + bit_cfg_reg(line));
+ switch (pinconf_to_config_param(cfg)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ /*
+ * Weird, setting open-drain mode causes signal
+ * inversion. Note this so we can compensate in the
+ * dir_out function.
+ */
+ set_bit(line, txgpio->invert_mask);
+ new_invert = true;
+ set_bit(line, txgpio->od_mask);
+ new_od = true;
+ ret = 0;
+ break;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ clear_bit(line, txgpio->invert_mask);
+ new_invert = false;
+ clear_bit(line, txgpio->od_mask);
+ new_od = false;
+ ret = 0;
+ break;
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ arg = pinconf_to_config_argument(cfg);
+ if (arg > 1228) { /* 15 * 2^15 * 2.5nS maximum */
+ ret = -EINVAL;
+ break;
+ }
+ arg *= 400; /* scale to 2.5nS clocks. */
+ sel = 0;
+ while (arg > 15) {
+ sel++;
+ arg++; /* always round up */
+ arg >>= 1;
+ }
+ txgpio->line_entries[line].fil_bits =
+ (sel << GPIO_BIT_CFG_FIL_SEL_SHIFT) |
+ (arg << GPIO_BIT_CFG_FIL_CNT_SHIFT);
+ bit_cfg &= ~GPIO_BIT_CFG_FIL_MASK;
+ bit_cfg |= txgpio->line_entries[line].fil_bits;
+ writeq(bit_cfg, txgpio->register_base + bit_cfg_reg(line));
+ ret = 0;
+ break;
+ default:
+ break;
+ }
+ raw_spin_unlock(&txgpio->lock);
+
+ /*
+ * If currently output and OPEN_DRAIN changed, install the new
+ * settings
+ */
+ if ((new_invert != orig_invert || new_od != orig_od) &&
+ (bit_cfg & GPIO_BIT_CFG_TX_OE))
+ ret = thunderx_gpio_dir_out(chip, line, orig_dat ^ new_invert);
+
+ return ret;
+}
+
+static int thunderx_gpio_get(struct gpio_chip *chip, unsigned int line)
+{
+ struct thunderx_gpio *txgpio = gpiochip_get_data(chip);
+ int bank = line / 64;
+ int bank_bit = line % 64;
+ u64 read_bits = readq(txgpio->register_base + (bank * GPIO_2ND_BANK) + GPIO_RX_DAT);
+ u64 masked_bits = read_bits & BIT_ULL(bank_bit);
+
+ if (test_bit(line, txgpio->invert_mask))
+ return masked_bits == 0;
+ else
+ return masked_bits != 0;
+}
+
+static void thunderx_gpio_set_multiple(struct gpio_chip *chip,
+ unsigned long *mask,
+ unsigned long *bits)
+{
+ int bank;
+ u64 set_bits, clear_bits;
+ struct thunderx_gpio *txgpio = gpiochip_get_data(chip);
+
+ for (bank = 0; bank <= chip->ngpio / 64; bank++) {
+ set_bits = bits[bank] & mask[bank];
+ clear_bits = ~bits[bank] & mask[bank];
+ writeq(set_bits, txgpio->register_base + (bank * GPIO_2ND_BANK) + GPIO_TX_SET);
+ writeq(clear_bits, txgpio->register_base + (bank * GPIO_2ND_BANK) + GPIO_TX_CLR);
+ }
+}
+
+static void thunderx_gpio_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct thunderx_gpio *txgpio = gpiochip_get_data(gc);
+
+ writeq(GPIO_INTR_INTR,
+ txgpio->register_base + intr_reg(irqd_to_hwirq(d)));
+}
+
+static void thunderx_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct thunderx_gpio *txgpio = gpiochip_get_data(gc);
+
+ writeq(GPIO_INTR_ENA_W1C,
+ txgpio->register_base + intr_reg(irqd_to_hwirq(d)));
+}
+
+static void thunderx_gpio_irq_mask_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct thunderx_gpio *txgpio = gpiochip_get_data(gc);
+
+ writeq(GPIO_INTR_ENA_W1C | GPIO_INTR_INTR,
+ txgpio->register_base + intr_reg(irqd_to_hwirq(d)));
+}
+
+static void thunderx_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct thunderx_gpio *txgpio = gpiochip_get_data(gc);
+
+ writeq(GPIO_INTR_ENA_W1S,
+ txgpio->register_base + intr_reg(irqd_to_hwirq(d)));
+}
+
+static int thunderx_gpio_irq_set_type(struct irq_data *d,
+ unsigned int flow_type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct thunderx_gpio *txgpio = gpiochip_get_data(gc);
+ struct thunderx_line *txline =
+ &txgpio->line_entries[irqd_to_hwirq(d)];
+ u64 bit_cfg;
+
+ irqd_set_trigger_type(d, flow_type);
+
+ bit_cfg = txline->fil_bits | GPIO_BIT_CFG_INT_EN;
+
+ if (flow_type & IRQ_TYPE_EDGE_BOTH) {
+ irq_set_handler_locked(d, handle_fasteoi_ack_irq);
+ bit_cfg |= GPIO_BIT_CFG_INT_TYPE;
+ } else {
+ irq_set_handler_locked(d, handle_fasteoi_mask_irq);
+ }
+
+ raw_spin_lock(&txgpio->lock);
+ if (flow_type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_LEVEL_LOW)) {
+ bit_cfg |= GPIO_BIT_CFG_PIN_XOR;
+ set_bit(txline->line, txgpio->invert_mask);
+ } else {
+ clear_bit(txline->line, txgpio->invert_mask);
+ }
+ clear_bit(txline->line, txgpio->od_mask);
+ writeq(bit_cfg, txgpio->register_base + bit_cfg_reg(txline->line));
+ raw_spin_unlock(&txgpio->lock);
+
+ return IRQ_SET_MASK_OK;
+}
+
+static void thunderx_gpio_irq_enable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+
+ gpiochip_enable_irq(gc, irqd_to_hwirq(d));
+ irq_chip_enable_parent(d);
+ thunderx_gpio_irq_unmask(d);
+}
+
+static void thunderx_gpio_irq_disable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+
+ thunderx_gpio_irq_mask(d);
+ irq_chip_disable_parent(d);
+ gpiochip_disable_irq(gc, irqd_to_hwirq(d));
+}
+
+/*
+ * Interrupts are chained from underlying MSI-X vectors. We have
+ * these irq_chip functions to be able to handle level triggering
+ * semantics and other acknowledgment tasks associated with the GPIO
+ * mechanism.
+ */
+static const struct irq_chip thunderx_gpio_irq_chip = {
+ .name = "GPIO",
+ .irq_enable = thunderx_gpio_irq_enable,
+ .irq_disable = thunderx_gpio_irq_disable,
+ .irq_ack = thunderx_gpio_irq_ack,
+ .irq_mask = thunderx_gpio_irq_mask,
+ .irq_mask_ack = thunderx_gpio_irq_mask_ack,
+ .irq_unmask = thunderx_gpio_irq_unmask,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_set_type = thunderx_gpio_irq_set_type,
+ .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int thunderx_gpio_child_to_parent_hwirq(struct gpio_chip *gc,
+ unsigned int child,
+ unsigned int child_type,
+ unsigned int *parent,
+ unsigned int *parent_type)
+{
+ struct thunderx_gpio *txgpio = gpiochip_get_data(gc);
+ struct irq_data *irqd;
+ unsigned int irq;
+
+ irq = txgpio->msix_entries[child].vector;
+ irqd = irq_domain_get_irq_data(gc->irq.parent_domain, irq);
+ if (!irqd)
+ return -EINVAL;
+ *parent = irqd_to_hwirq(irqd);
+ *parent_type = IRQ_TYPE_LEVEL_HIGH;
+ return 0;
+}
+
+static int thunderx_gpio_populate_parent_alloc_info(struct gpio_chip *chip,
+ union gpio_irq_fwspec *gfwspec,
+ unsigned int parent_hwirq,
+ unsigned int parent_type)
+{
+ msi_alloc_info_t *info = &gfwspec->msiinfo;
+
+ info->hwirq = parent_hwirq;
+ return 0;
+}
+
+static int thunderx_gpio_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ void __iomem * const *tbl;
+ struct device *dev = &pdev->dev;
+ struct thunderx_gpio *txgpio;
+ struct gpio_chip *chip;
+ struct gpio_irq_chip *girq;
+ int ngpio, i;
+ int err = 0;
+
+ txgpio = devm_kzalloc(dev, sizeof(*txgpio), GFP_KERNEL);
+ if (!txgpio)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&txgpio->lock);
+ chip = &txgpio->chip;
+
+ pci_set_drvdata(pdev, txgpio);
+
+ err = pcim_enable_device(pdev);
+ if (err) {
+ dev_err(dev, "Failed to enable PCI device: err %d\n", err);
+ goto out;
+ }
+
+ err = pcim_iomap_regions(pdev, 1 << 0, KBUILD_MODNAME);
+ if (err) {
+ dev_err(dev, "Failed to iomap PCI device: err %d\n", err);
+ goto out;
+ }
+
+ tbl = pcim_iomap_table(pdev);
+ txgpio->register_base = tbl[0];
+ if (!txgpio->register_base) {
+ dev_err(dev, "Cannot map PCI resource\n");
+ err = -ENOMEM;
+ goto out;
+ }
+
+ if (pdev->subsystem_device == 0xa10a) {
+ /* CN88XX has no GPIO_CONST register*/
+ ngpio = 50;
+ txgpio->base_msi = 48;
+ } else {
+ u64 c = readq(txgpio->register_base + GPIO_CONST);
+
+ ngpio = c & GPIO_CONST_GPIOS_MASK;
+ txgpio->base_msi = (c >> 8) & 0xff;
+ }
+
+ txgpio->msix_entries = devm_kcalloc(dev,
+ ngpio, sizeof(struct msix_entry),
+ GFP_KERNEL);
+ if (!txgpio->msix_entries) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ txgpio->line_entries = devm_kcalloc(dev,
+ ngpio,
+ sizeof(struct thunderx_line),
+ GFP_KERNEL);
+ if (!txgpio->line_entries) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < ngpio; i++) {
+ u64 bit_cfg = readq(txgpio->register_base + bit_cfg_reg(i));
+
+ txgpio->msix_entries[i].entry = txgpio->base_msi + (2 * i);
+ txgpio->line_entries[i].line = i;
+ txgpio->line_entries[i].txgpio = txgpio;
+ /*
+ * If something has already programmed the pin, use
+ * the existing glitch filter settings, otherwise go
+ * to 400nS.
+ */
+ txgpio->line_entries[i].fil_bits = bit_cfg ?
+ (bit_cfg & GPIO_BIT_CFG_FIL_MASK) : GLITCH_FILTER_400NS;
+
+ if ((bit_cfg & GPIO_BIT_CFG_TX_OE) && (bit_cfg & GPIO_BIT_CFG_TX_OD))
+ set_bit(i, txgpio->od_mask);
+ if (bit_cfg & GPIO_BIT_CFG_PIN_XOR)
+ set_bit(i, txgpio->invert_mask);
+ }
+
+
+ /* Enable all MSI-X for interrupts on all possible lines. */
+ err = pci_enable_msix_range(pdev, txgpio->msix_entries, ngpio, ngpio);
+ if (err < 0)
+ goto out;
+
+ chip->label = KBUILD_MODNAME;
+ chip->parent = dev;
+ chip->owner = THIS_MODULE;
+ chip->request = thunderx_gpio_request;
+ chip->base = -1; /* System allocated */
+ chip->can_sleep = false;
+ chip->ngpio = ngpio;
+ chip->get_direction = thunderx_gpio_get_direction;
+ chip->direction_input = thunderx_gpio_dir_in;
+ chip->get = thunderx_gpio_get;
+ chip->direction_output = thunderx_gpio_dir_out;
+ chip->set = thunderx_gpio_set;
+ chip->set_multiple = thunderx_gpio_set_multiple;
+ chip->set_config = thunderx_gpio_set_config;
+ girq = &chip->irq;
+ gpio_irq_chip_set_chip(girq, &thunderx_gpio_irq_chip);
+ girq->fwnode = of_node_to_fwnode(dev->of_node);
+ girq->parent_domain =
+ irq_get_irq_data(txgpio->msix_entries[0].vector)->domain;
+ girq->child_to_parent_hwirq = thunderx_gpio_child_to_parent_hwirq;
+ girq->populate_parent_alloc_arg = thunderx_gpio_populate_parent_alloc_info;
+ girq->handler = handle_bad_irq;
+ girq->default_type = IRQ_TYPE_NONE;
+
+ err = devm_gpiochip_add_data(dev, chip, txgpio);
+ if (err)
+ goto out;
+
+ /* Push on irq_data and the domain for each line. */
+ for (i = 0; i < ngpio; i++) {
+ struct irq_fwspec fwspec;
+
+ fwspec.fwnode = of_node_to_fwnode(dev->of_node);
+ fwspec.param_count = 2;
+ fwspec.param[0] = i;
+ fwspec.param[1] = IRQ_TYPE_NONE;
+ err = irq_domain_push_irq(girq->domain,
+ txgpio->msix_entries[i].vector,
+ &fwspec);
+ if (err < 0)
+ dev_err(dev, "irq_domain_push_irq: %d\n", err);
+ }
+
+ dev_info(dev, "ThunderX GPIO: %d lines with base %d.\n",
+ ngpio, chip->base);
+ return 0;
+out:
+ pci_set_drvdata(pdev, NULL);
+ return err;
+}
+
+static void thunderx_gpio_remove(struct pci_dev *pdev)
+{
+ int i;
+ struct thunderx_gpio *txgpio = pci_get_drvdata(pdev);
+
+ for (i = 0; i < txgpio->chip.ngpio; i++)
+ irq_domain_pop_irq(txgpio->chip.irq.domain,
+ txgpio->msix_entries[i].vector);
+
+ irq_domain_remove(txgpio->chip.irq.domain);
+
+ pci_set_drvdata(pdev, NULL);
+}
+
+static const struct pci_device_id thunderx_gpio_id_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xA00A) },
+ { 0, } /* end of table */
+};
+
+MODULE_DEVICE_TABLE(pci, thunderx_gpio_id_table);
+
+static struct pci_driver thunderx_gpio_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = thunderx_gpio_id_table,
+ .probe = thunderx_gpio_probe,
+ .remove = thunderx_gpio_remove,
+};
+
+module_pci_driver(thunderx_gpio_driver);
+
+MODULE_DESCRIPTION("Cavium Inc. ThunderX/OCTEON-TX GPIO Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-timberdale.c b/drivers/gpio/gpio-timberdale.c
new file mode 100644
index 0000000000..fad9797974
--- /dev/null
+++ b/drivers/gpio/gpio-timberdale.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Timberdale FPGA GPIO driver
+ * Author: Mocean Laboratories
+ * Copyright (c) 2009 Intel Corporation
+ */
+
+/* Supports:
+ * Timberdale FPGA GPIO
+ */
+
+#include <linux/init.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/timb_gpio.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+#define DRIVER_NAME "timb-gpio"
+
+#define TGPIOVAL 0x00
+#define TGPIODIR 0x04
+#define TGPIO_IER 0x08
+#define TGPIO_ISR 0x0c
+#define TGPIO_IPR 0x10
+#define TGPIO_ICR 0x14
+#define TGPIO_FLR 0x18
+#define TGPIO_LVR 0x1c
+#define TGPIO_VER 0x20
+#define TGPIO_BFLR 0x24
+
+struct timbgpio {
+ void __iomem *membase;
+ spinlock_t lock; /* mutual exclusion */
+ struct gpio_chip gpio;
+ int irq_base;
+ unsigned long last_ier;
+};
+
+static int timbgpio_update_bit(struct gpio_chip *gpio, unsigned index,
+ unsigned offset, bool enabled)
+{
+ struct timbgpio *tgpio = gpiochip_get_data(gpio);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&tgpio->lock, flags);
+ reg = ioread32(tgpio->membase + offset);
+
+ if (enabled)
+ reg |= (1 << index);
+ else
+ reg &= ~(1 << index);
+
+ iowrite32(reg, tgpio->membase + offset);
+ spin_unlock_irqrestore(&tgpio->lock, flags);
+
+ return 0;
+}
+
+static int timbgpio_gpio_direction_input(struct gpio_chip *gpio, unsigned nr)
+{
+ return timbgpio_update_bit(gpio, nr, TGPIODIR, true);
+}
+
+static int timbgpio_gpio_get(struct gpio_chip *gpio, unsigned nr)
+{
+ struct timbgpio *tgpio = gpiochip_get_data(gpio);
+ u32 value;
+
+ value = ioread32(tgpio->membase + TGPIOVAL);
+ return (value & (1 << nr)) ? 1 : 0;
+}
+
+static int timbgpio_gpio_direction_output(struct gpio_chip *gpio,
+ unsigned nr, int val)
+{
+ return timbgpio_update_bit(gpio, nr, TGPIODIR, false);
+}
+
+static void timbgpio_gpio_set(struct gpio_chip *gpio,
+ unsigned nr, int val)
+{
+ timbgpio_update_bit(gpio, nr, TGPIOVAL, val != 0);
+}
+
+static int timbgpio_to_irq(struct gpio_chip *gpio, unsigned offset)
+{
+ struct timbgpio *tgpio = gpiochip_get_data(gpio);
+
+ if (tgpio->irq_base <= 0)
+ return -EINVAL;
+
+ return tgpio->irq_base + offset;
+}
+
+/*
+ * GPIO IRQ
+ */
+static void timbgpio_irq_disable(struct irq_data *d)
+{
+ struct timbgpio *tgpio = irq_data_get_irq_chip_data(d);
+ int offset = d->irq - tgpio->irq_base;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tgpio->lock, flags);
+ tgpio->last_ier &= ~(1UL << offset);
+ iowrite32(tgpio->last_ier, tgpio->membase + TGPIO_IER);
+ spin_unlock_irqrestore(&tgpio->lock, flags);
+}
+
+static void timbgpio_irq_enable(struct irq_data *d)
+{
+ struct timbgpio *tgpio = irq_data_get_irq_chip_data(d);
+ int offset = d->irq - tgpio->irq_base;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tgpio->lock, flags);
+ tgpio->last_ier |= 1UL << offset;
+ iowrite32(tgpio->last_ier, tgpio->membase + TGPIO_IER);
+ spin_unlock_irqrestore(&tgpio->lock, flags);
+}
+
+static int timbgpio_irq_type(struct irq_data *d, unsigned trigger)
+{
+ struct timbgpio *tgpio = irq_data_get_irq_chip_data(d);
+ int offset = d->irq - tgpio->irq_base;
+ unsigned long flags;
+ u32 lvr, flr, bflr = 0;
+ u32 ver;
+ int ret = 0;
+
+ if (offset < 0 || offset > tgpio->gpio.ngpio)
+ return -EINVAL;
+
+ ver = ioread32(tgpio->membase + TGPIO_VER);
+
+ spin_lock_irqsave(&tgpio->lock, flags);
+
+ lvr = ioread32(tgpio->membase + TGPIO_LVR);
+ flr = ioread32(tgpio->membase + TGPIO_FLR);
+ if (ver > 2)
+ bflr = ioread32(tgpio->membase + TGPIO_BFLR);
+
+ if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) {
+ bflr &= ~(1 << offset);
+ flr &= ~(1 << offset);
+ if (trigger & IRQ_TYPE_LEVEL_HIGH)
+ lvr |= 1 << offset;
+ else
+ lvr &= ~(1 << offset);
+ }
+
+ if ((trigger & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) {
+ if (ver < 3) {
+ ret = -EINVAL;
+ goto out;
+ } else {
+ flr |= 1 << offset;
+ bflr |= 1 << offset;
+ }
+ } else {
+ bflr &= ~(1 << offset);
+ flr |= 1 << offset;
+ if (trigger & IRQ_TYPE_EDGE_FALLING)
+ lvr &= ~(1 << offset);
+ else
+ lvr |= 1 << offset;
+ }
+
+ iowrite32(lvr, tgpio->membase + TGPIO_LVR);
+ iowrite32(flr, tgpio->membase + TGPIO_FLR);
+ if (ver > 2)
+ iowrite32(bflr, tgpio->membase + TGPIO_BFLR);
+
+ iowrite32(1 << offset, tgpio->membase + TGPIO_ICR);
+
+out:
+ spin_unlock_irqrestore(&tgpio->lock, flags);
+ return ret;
+}
+
+static void timbgpio_irq(struct irq_desc *desc)
+{
+ struct timbgpio *tgpio = irq_desc_get_handler_data(desc);
+ struct irq_data *data = irq_desc_get_irq_data(desc);
+ unsigned long ipr;
+ int offset;
+
+ data->chip->irq_ack(data);
+ ipr = ioread32(tgpio->membase + TGPIO_IPR);
+ iowrite32(ipr, tgpio->membase + TGPIO_ICR);
+
+ /*
+ * Some versions of the hardware trash the IER register if more than
+ * one interrupt is received simultaneously.
+ */
+ iowrite32(0, tgpio->membase + TGPIO_IER);
+
+ for_each_set_bit(offset, &ipr, tgpio->gpio.ngpio)
+ generic_handle_irq(timbgpio_to_irq(&tgpio->gpio, offset));
+
+ iowrite32(tgpio->last_ier, tgpio->membase + TGPIO_IER);
+}
+
+static struct irq_chip timbgpio_irqchip = {
+ .name = "GPIO",
+ .irq_enable = timbgpio_irq_enable,
+ .irq_disable = timbgpio_irq_disable,
+ .irq_set_type = timbgpio_irq_type,
+};
+
+static int timbgpio_probe(struct platform_device *pdev)
+{
+ int err, i;
+ struct device *dev = &pdev->dev;
+ struct gpio_chip *gc;
+ struct timbgpio *tgpio;
+ struct timbgpio_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ int irq = platform_get_irq(pdev, 0);
+
+ if (!pdata || pdata->nr_pins > 32) {
+ dev_err(dev, "Invalid platform data\n");
+ return -EINVAL;
+ }
+
+ tgpio = devm_kzalloc(dev, sizeof(*tgpio), GFP_KERNEL);
+ if (!tgpio)
+ return -EINVAL;
+
+ tgpio->irq_base = pdata->irq_base;
+
+ spin_lock_init(&tgpio->lock);
+
+ tgpio->membase = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(tgpio->membase))
+ return PTR_ERR(tgpio->membase);
+
+ gc = &tgpio->gpio;
+
+ gc->label = dev_name(&pdev->dev);
+ gc->owner = THIS_MODULE;
+ gc->parent = &pdev->dev;
+ gc->direction_input = timbgpio_gpio_direction_input;
+ gc->get = timbgpio_gpio_get;
+ gc->direction_output = timbgpio_gpio_direction_output;
+ gc->set = timbgpio_gpio_set;
+ gc->to_irq = (irq >= 0 && tgpio->irq_base > 0) ? timbgpio_to_irq : NULL;
+ gc->dbg_show = NULL;
+ gc->base = pdata->gpio_base;
+ gc->ngpio = pdata->nr_pins;
+ gc->can_sleep = false;
+
+ err = devm_gpiochip_add_data(&pdev->dev, gc, tgpio);
+ if (err)
+ return err;
+
+ /* make sure to disable interrupts */
+ iowrite32(0x0, tgpio->membase + TGPIO_IER);
+
+ if (irq < 0 || tgpio->irq_base <= 0)
+ return 0;
+
+ for (i = 0; i < pdata->nr_pins; i++) {
+ irq_set_chip_and_handler(tgpio->irq_base + i,
+ &timbgpio_irqchip, handle_simple_irq);
+ irq_set_chip_data(tgpio->irq_base + i, tgpio);
+ irq_clear_status_flags(tgpio->irq_base + i, IRQ_NOREQUEST | IRQ_NOPROBE);
+ }
+
+ irq_set_chained_handler_and_data(irq, timbgpio_irq, tgpio);
+
+ return 0;
+}
+
+static struct platform_driver timbgpio_platform_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .suppress_bind_attrs = true,
+ },
+ .probe = timbgpio_probe,
+};
+
+/*--------------------------------------------------------------------------*/
+
+builtin_platform_driver(timbgpio_platform_driver);
diff --git a/drivers/gpio/gpio-tn48m.c b/drivers/gpio/gpio-tn48m.c
new file mode 100644
index 0000000000..cd4a80b227
--- /dev/null
+++ b/drivers/gpio/gpio-tn48m.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Delta TN48M CPLD GPIO driver
+ *
+ * Copyright (C) 2021 Sartura Ltd.
+ *
+ * Author: Robert Marko <robert.marko@sartura.hr>
+ */
+
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+enum tn48m_gpio_type {
+ TN48M_GP0 = 1,
+ TN48M_GPI,
+};
+
+struct tn48m_gpio_config {
+ int ngpio;
+ int ngpio_per_reg;
+ enum tn48m_gpio_type type;
+};
+
+static const struct tn48m_gpio_config tn48m_gpo_config = {
+ .ngpio = 4,
+ .ngpio_per_reg = 4,
+ .type = TN48M_GP0,
+};
+
+static const struct tn48m_gpio_config tn48m_gpi_config = {
+ .ngpio = 4,
+ .ngpio_per_reg = 4,
+ .type = TN48M_GPI,
+};
+
+static int tn48m_gpio_probe(struct platform_device *pdev)
+{
+ const struct tn48m_gpio_config *gpio_config;
+ struct gpio_regmap_config config = {};
+ struct regmap *regmap;
+ u32 base;
+ int ret;
+
+ if (!pdev->dev.parent)
+ return -ENODEV;
+
+ gpio_config = device_get_match_data(&pdev->dev);
+ if (!gpio_config)
+ return -ENODEV;
+
+ ret = device_property_read_u32(&pdev->dev, "reg", &base);
+ if (ret)
+ return ret;
+
+ regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!regmap)
+ return -ENODEV;
+
+ config.regmap = regmap;
+ config.parent = &pdev->dev;
+ config.ngpio = gpio_config->ngpio;
+ config.ngpio_per_reg = gpio_config->ngpio_per_reg;
+ switch (gpio_config->type) {
+ case TN48M_GP0:
+ config.reg_set_base = base;
+ break;
+ case TN48M_GPI:
+ config.reg_dat_base = base;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(&pdev->dev, &config));
+}
+
+static const struct of_device_id tn48m_gpio_of_match[] = {
+ { .compatible = "delta,tn48m-gpo", .data = &tn48m_gpo_config },
+ { .compatible = "delta,tn48m-gpi", .data = &tn48m_gpi_config },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tn48m_gpio_of_match);
+
+static struct platform_driver tn48m_gpio_driver = {
+ .driver = {
+ .name = "delta-tn48m-gpio",
+ .of_match_table = tn48m_gpio_of_match,
+ },
+ .probe = tn48m_gpio_probe,
+};
+module_platform_driver(tn48m_gpio_driver);
+
+MODULE_AUTHOR("Robert Marko <robert.marko@sartura.hr>");
+MODULE_DESCRIPTION("Delta TN48M CPLD GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-tpic2810.c b/drivers/gpio/gpio-tpic2810.c
new file mode 100644
index 0000000000..effb7b8ff8
--- /dev/null
+++ b/drivers/gpio/gpio-tpic2810.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2015-2023 Texas Instruments Incorporated - https://www.ti.com/
+ * Andrew Davis <afd@ti.com>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#define TPIC2810_WS_COMMAND 0x44
+
+/**
+ * struct tpic2810 - GPIO driver data
+ * @chip: GPIO controller chip
+ * @client: I2C device pointer
+ * @buffer: Buffer for device register
+ * @lock: Protects write sequences
+ */
+struct tpic2810 {
+ struct gpio_chip chip;
+ struct i2c_client *client;
+ u8 buffer;
+ struct mutex lock;
+};
+
+static void tpic2810_set(struct gpio_chip *chip, unsigned offset, int value);
+
+static int tpic2810_get_direction(struct gpio_chip *chip,
+ unsigned offset)
+{
+ /* This device always output */
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int tpic2810_direction_input(struct gpio_chip *chip,
+ unsigned offset)
+{
+ /* This device is output only */
+ return -EINVAL;
+}
+
+static int tpic2810_direction_output(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ /* This device always output */
+ tpic2810_set(chip, offset, value);
+ return 0;
+}
+
+static void tpic2810_set_mask_bits(struct gpio_chip *chip, u8 mask, u8 bits)
+{
+ struct tpic2810 *gpio = gpiochip_get_data(chip);
+ u8 buffer;
+ int err;
+
+ mutex_lock(&gpio->lock);
+
+ buffer = gpio->buffer & ~mask;
+ buffer |= (mask & bits);
+
+ err = i2c_smbus_write_byte_data(gpio->client, TPIC2810_WS_COMMAND,
+ buffer);
+ if (!err)
+ gpio->buffer = buffer;
+
+ mutex_unlock(&gpio->lock);
+}
+
+static void tpic2810_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ tpic2810_set_mask_bits(chip, BIT(offset), value ? BIT(offset) : 0);
+}
+
+static void tpic2810_set_multiple(struct gpio_chip *chip, unsigned long *mask,
+ unsigned long *bits)
+{
+ tpic2810_set_mask_bits(chip, *mask, *bits);
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "tpic2810",
+ .owner = THIS_MODULE,
+ .get_direction = tpic2810_get_direction,
+ .direction_input = tpic2810_direction_input,
+ .direction_output = tpic2810_direction_output,
+ .set = tpic2810_set,
+ .set_multiple = tpic2810_set_multiple,
+ .base = -1,
+ .ngpio = 8,
+ .can_sleep = true,
+};
+
+static const struct of_device_id tpic2810_of_match_table[] = {
+ { .compatible = "ti,tpic2810" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, tpic2810_of_match_table);
+
+static int tpic2810_probe(struct i2c_client *client)
+{
+ struct tpic2810 *gpio;
+
+ gpio = devm_kzalloc(&client->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->chip = template_chip;
+ gpio->chip.parent = &client->dev;
+
+ gpio->client = client;
+
+ mutex_init(&gpio->lock);
+
+ return devm_gpiochip_add_data(&client->dev, &gpio->chip, gpio);
+}
+
+static const struct i2c_device_id tpic2810_id_table[] = {
+ { "tpic2810", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, tpic2810_id_table);
+
+static struct i2c_driver tpic2810_driver = {
+ .driver = {
+ .name = "tpic2810",
+ .of_match_table = tpic2810_of_match_table,
+ },
+ .probe = tpic2810_probe,
+ .id_table = tpic2810_id_table,
+};
+module_i2c_driver(tpic2810_driver);
+
+MODULE_AUTHOR("Andrew Davis <afd@ti.com>");
+MODULE_DESCRIPTION("TPIC2810 8-Bit LED Driver GPIO Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-tps65086.c b/drivers/gpio/gpio-tps65086.c
new file mode 100644
index 0000000000..8f5827554e
--- /dev/null
+++ b/drivers/gpio/gpio-tps65086.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015-2023 Texas Instruments Incorporated - https://www.ti.com/
+ * Andrew Davis <afd@ti.com>
+ *
+ * Based on the TPS65912 driver
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <linux/mfd/tps65086.h>
+
+struct tps65086_gpio {
+ struct gpio_chip chip;
+ struct tps65086 *tps;
+};
+
+static int tps65086_gpio_get_direction(struct gpio_chip *chip,
+ unsigned offset)
+{
+ /* This device is output only */
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int tps65086_gpio_direction_input(struct gpio_chip *chip,
+ unsigned offset)
+{
+ /* This device is output only */
+ return -EINVAL;
+}
+
+static int tps65086_gpio_direction_output(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct tps65086_gpio *gpio = gpiochip_get_data(chip);
+
+ /* Set the initial value */
+ regmap_update_bits(gpio->tps->regmap, TPS65086_GPOCTRL,
+ BIT(4 + offset), value ? BIT(4 + offset) : 0);
+
+ return 0;
+}
+
+static int tps65086_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct tps65086_gpio *gpio = gpiochip_get_data(chip);
+ int ret, val;
+
+ ret = regmap_read(gpio->tps->regmap, TPS65086_GPOCTRL, &val);
+ if (ret < 0)
+ return ret;
+
+ return val & BIT(4 + offset);
+}
+
+static void tps65086_gpio_set(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ struct tps65086_gpio *gpio = gpiochip_get_data(chip);
+
+ regmap_update_bits(gpio->tps->regmap, TPS65086_GPOCTRL,
+ BIT(4 + offset), value ? BIT(4 + offset) : 0);
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "tps65086-gpio",
+ .owner = THIS_MODULE,
+ .get_direction = tps65086_gpio_get_direction,
+ .direction_input = tps65086_gpio_direction_input,
+ .direction_output = tps65086_gpio_direction_output,
+ .get = tps65086_gpio_get,
+ .set = tps65086_gpio_set,
+ .base = -1,
+ .ngpio = 4,
+ .can_sleep = true,
+};
+
+static int tps65086_gpio_probe(struct platform_device *pdev)
+{
+ struct tps65086_gpio *gpio;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->tps = dev_get_drvdata(pdev->dev.parent);
+ gpio->chip = template_chip;
+ gpio->chip.parent = gpio->tps->dev;
+
+ return devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
+}
+
+static const struct platform_device_id tps65086_gpio_id_table[] = {
+ { "tps65086-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, tps65086_gpio_id_table);
+
+static struct platform_driver tps65086_gpio_driver = {
+ .driver = {
+ .name = "tps65086-gpio",
+ },
+ .probe = tps65086_gpio_probe,
+ .id_table = tps65086_gpio_id_table,
+};
+module_platform_driver(tps65086_gpio_driver);
+
+MODULE_AUTHOR("Andrew Davis <afd@ti.com>");
+MODULE_DESCRIPTION("TPS65086 GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-tps65218.c b/drivers/gpio/gpio-tps65218.c
new file mode 100644
index 0000000000..d7d9d50dcd
--- /dev/null
+++ b/drivers/gpio/gpio-tps65218.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Verifone Int.
+ *
+ * Author: Nicolas Saenz Julienne <nicolassaenzj@gmail.com>
+ *
+ * This driver is based on the gpio-tps65912 implementation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/tps65218.h>
+
+struct tps65218_gpio {
+ struct tps65218 *tps65218;
+ struct gpio_chip gpio_chip;
+};
+
+static int tps65218_gpio_get(struct gpio_chip *gc, unsigned offset)
+{
+ struct tps65218_gpio *tps65218_gpio = gpiochip_get_data(gc);
+ struct tps65218 *tps65218 = tps65218_gpio->tps65218;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(tps65218->regmap, TPS65218_REG_ENABLE2, &val);
+ if (ret)
+ return ret;
+
+ return !!(val & (TPS65218_ENABLE2_GPIO1 << offset));
+}
+
+static void tps65218_gpio_set(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ struct tps65218_gpio *tps65218_gpio = gpiochip_get_data(gc);
+ struct tps65218 *tps65218 = tps65218_gpio->tps65218;
+
+ if (value)
+ tps65218_set_bits(tps65218, TPS65218_REG_ENABLE2,
+ TPS65218_ENABLE2_GPIO1 << offset,
+ TPS65218_ENABLE2_GPIO1 << offset,
+ TPS65218_PROTECT_L1);
+ else
+ tps65218_clear_bits(tps65218, TPS65218_REG_ENABLE2,
+ TPS65218_ENABLE2_GPIO1 << offset,
+ TPS65218_PROTECT_L1);
+}
+
+static int tps65218_gpio_output(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ /* Only drives GPOs */
+ tps65218_gpio_set(gc, offset, value);
+ return 0;
+}
+
+static int tps65218_gpio_input(struct gpio_chip *gc, unsigned offset)
+{
+ return -EPERM;
+}
+
+static int tps65218_gpio_request(struct gpio_chip *gc, unsigned offset)
+{
+ struct tps65218_gpio *tps65218_gpio = gpiochip_get_data(gc);
+ struct tps65218 *tps65218 = tps65218_gpio->tps65218;
+ int ret;
+
+ if (gpiochip_line_is_open_source(gc, offset)) {
+ dev_err(gc->parent, "can't work as open source\n");
+ return -EINVAL;
+ }
+
+ switch (offset) {
+ case 0:
+ if (!gpiochip_line_is_open_drain(gc, offset)) {
+ dev_err(gc->parent, "GPO1 works only as open drain\n");
+ return -EINVAL;
+ }
+
+ /* Disable sequencer for GPO1 */
+ ret = tps65218_clear_bits(tps65218, TPS65218_REG_SEQ7,
+ TPS65218_SEQ7_GPO1_SEQ_MASK,
+ TPS65218_PROTECT_L1);
+ if (ret)
+ return ret;
+
+ /* Setup GPO1 */
+ ret = tps65218_clear_bits(tps65218, TPS65218_REG_CONFIG1,
+ TPS65218_CONFIG1_IO1_SEL,
+ TPS65218_PROTECT_L1);
+ if (ret)
+ return ret;
+
+ break;
+ case 1:
+ /* Setup GPO2 */
+ ret = tps65218_clear_bits(tps65218, TPS65218_REG_CONFIG1,
+ TPS65218_CONFIG1_IO1_SEL,
+ TPS65218_PROTECT_L1);
+ if (ret)
+ return ret;
+
+ break;
+
+ case 2:
+ if (!gpiochip_line_is_open_drain(gc, offset)) {
+ dev_err(gc->parent, "GPO3 works only as open drain\n");
+ return -EINVAL;
+ }
+
+ /* Disable sequencer for GPO3 */
+ ret = tps65218_clear_bits(tps65218, TPS65218_REG_SEQ7,
+ TPS65218_SEQ7_GPO3_SEQ_MASK,
+ TPS65218_PROTECT_L1);
+ if (ret)
+ return ret;
+
+ /* Setup GPO3 */
+ ret = tps65218_clear_bits(tps65218, TPS65218_REG_CONFIG2,
+ TPS65218_CONFIG2_DC12_RST,
+ TPS65218_PROTECT_L1);
+ if (ret)
+ return ret;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tps65218_gpio_set_config(struct gpio_chip *gc, unsigned offset,
+ unsigned long config)
+{
+ struct tps65218_gpio *tps65218_gpio = gpiochip_get_data(gc);
+ struct tps65218 *tps65218 = tps65218_gpio->tps65218;
+ enum pin_config_param param = pinconf_to_config_param(config);
+
+ switch (offset) {
+ case 0:
+ case 2:
+ /* GPO1 is hardwired to be open drain */
+ if (param == PIN_CONFIG_DRIVE_OPEN_DRAIN)
+ return 0;
+ return -ENOTSUPP;
+ case 1:
+ /* GPO2 is push-pull by default, can be set as open drain. */
+ if (param == PIN_CONFIG_DRIVE_OPEN_DRAIN)
+ return tps65218_clear_bits(tps65218,
+ TPS65218_REG_CONFIG1,
+ TPS65218_CONFIG1_GPO2_BUF,
+ TPS65218_PROTECT_L1);
+ if (param == PIN_CONFIG_DRIVE_PUSH_PULL)
+ return tps65218_set_bits(tps65218,
+ TPS65218_REG_CONFIG1,
+ TPS65218_CONFIG1_GPO2_BUF,
+ TPS65218_CONFIG1_GPO2_BUF,
+ TPS65218_PROTECT_L1);
+ return -ENOTSUPP;
+ default:
+ break;
+ }
+ return -ENOTSUPP;
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "gpio-tps65218",
+ .owner = THIS_MODULE,
+ .request = tps65218_gpio_request,
+ .direction_output = tps65218_gpio_output,
+ .direction_input = tps65218_gpio_input,
+ .get = tps65218_gpio_get,
+ .set = tps65218_gpio_set,
+ .set_config = tps65218_gpio_set_config,
+ .can_sleep = true,
+ .ngpio = 3,
+ .base = -1,
+};
+
+static int tps65218_gpio_probe(struct platform_device *pdev)
+{
+ struct tps65218 *tps65218 = dev_get_drvdata(pdev->dev.parent);
+ struct tps65218_gpio *tps65218_gpio;
+
+ tps65218_gpio = devm_kzalloc(&pdev->dev, sizeof(*tps65218_gpio),
+ GFP_KERNEL);
+ if (!tps65218_gpio)
+ return -ENOMEM;
+
+ tps65218_gpio->tps65218 = tps65218;
+ tps65218_gpio->gpio_chip = template_chip;
+ tps65218_gpio->gpio_chip.parent = &pdev->dev;
+
+ return devm_gpiochip_add_data(&pdev->dev, &tps65218_gpio->gpio_chip,
+ tps65218_gpio);
+}
+
+static const struct of_device_id tps65218_dt_match[] = {
+ { .compatible = "ti,tps65218-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tps65218_dt_match);
+
+static const struct platform_device_id tps65218_gpio_id_table[] = {
+ { "tps65218-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, tps65218_gpio_id_table);
+
+static struct platform_driver tps65218_gpio_driver = {
+ .driver = {
+ .name = "tps65218-gpio",
+ .of_match_table = tps65218_dt_match,
+ },
+ .probe = tps65218_gpio_probe,
+ .id_table = tps65218_gpio_id_table,
+};
+
+module_platform_driver(tps65218_gpio_driver);
+
+MODULE_AUTHOR("Nicolas Saenz Julienne <nicolassaenzj@gmail.com>");
+MODULE_DESCRIPTION("GPO interface for TPS65218 PMICs");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-tps65219.c b/drivers/gpio/gpio-tps65219.c
new file mode 100644
index 0000000000..7b38aa3601
--- /dev/null
+++ b/drivers/gpio/gpio-tps65219.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO driver for TI TPS65219 PMICs
+ *
+ * Copyright (C) 2022 Texas Instruments Incorporated - http://www.ti.com/
+ */
+
+#include <linux/bits.h>
+#include <linux/gpio/driver.h>
+#include <linux/mfd/tps65219.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define TPS65219_GPIO0_DIR_MASK BIT(3)
+#define TPS65219_GPIO0_OFFSET 2
+#define TPS65219_GPIO0_IDX 0
+#define TPS65219_GPIO_DIR_IN 1
+#define TPS65219_GPIO_DIR_OUT 0
+
+struct tps65219_gpio {
+ struct gpio_chip gpio_chip;
+ struct tps65219 *tps;
+};
+
+static int tps65219_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct tps65219_gpio *gpio = gpiochip_get_data(gc);
+ int ret, val;
+
+ if (offset != TPS65219_GPIO0_IDX)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ ret = regmap_read(gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG, &val);
+ if (ret)
+ return ret;
+
+ return !!(val & TPS65219_GPIO0_DIR_MASK);
+}
+
+static int tps65219_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct tps65219_gpio *gpio = gpiochip_get_data(gc);
+ struct device *dev = gpio->tps->dev;
+ int ret, val;
+
+ if (offset != TPS65219_GPIO0_IDX) {
+ dev_err(dev, "GPIO%d is output only, cannot get\n", offset);
+ return -ENOTSUPP;
+ }
+
+ ret = regmap_read(gpio->tps->regmap, TPS65219_REG_MFP_CTRL, &val);
+ if (ret)
+ return ret;
+
+ ret = !!(val & BIT(TPS65219_MFP_GPIO_STATUS_MASK));
+ dev_warn(dev, "GPIO%d = %d, MULTI_DEVICE_ENABLE, not a standard GPIO\n", offset, ret);
+
+ /*
+ * Depending on NVM config, return an error if direction is output, otherwise the GPIO0
+ * status bit.
+ */
+
+ if (tps65219_gpio_get_direction(gc, offset) == TPS65219_GPIO_DIR_OUT)
+ return -ENOTSUPP;
+
+ return ret;
+}
+
+static void tps65219_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ struct tps65219_gpio *gpio = gpiochip_get_data(gc);
+ struct device *dev = gpio->tps->dev;
+ int v, mask, bit;
+
+ bit = (offset == TPS65219_GPIO0_IDX) ? TPS65219_GPIO0_OFFSET : offset - 1;
+
+ mask = BIT(bit);
+ v = value ? mask : 0;
+
+ if (regmap_update_bits(gpio->tps->regmap, TPS65219_REG_GENERAL_CONFIG, mask, v))
+ dev_err(dev, "GPIO%d, set to value %d failed.\n", offset, value);
+}
+
+static int tps65219_gpio_change_direction(struct gpio_chip *gc, unsigned int offset,
+ unsigned int direction)
+{
+ struct tps65219_gpio *gpio = gpiochip_get_data(gc);
+ struct device *dev = gpio->tps->dev;
+
+ /*
+ * Documentation is stating that GPIO0 direction must not be changed in Linux:
+ * Table 8-34. MFP_1_CONFIG(3): MULTI_DEVICE_ENABLE, should only be changed in INITIALIZE
+ * state (prior to ON Request).
+ * Set statically by NVM, changing direction in application can cause a hang.
+ * Below can be used for test purpose only.
+ */
+
+ if (IS_ENABLED(CONFIG_DEBUG_GPIO)) {
+ int ret = regmap_update_bits(gpio->tps->regmap, TPS65219_REG_MFP_1_CONFIG,
+ TPS65219_GPIO0_DIR_MASK, direction);
+ if (ret) {
+ dev_err(dev,
+ "GPIO DEBUG enabled: Fail to change direction to %u for GPIO%d.\n",
+ direction, offset);
+ return ret;
+ }
+ }
+
+ dev_err(dev,
+ "GPIO%d direction set by NVM, change to %u failed, not allowed by specification\n",
+ offset, direction);
+
+ return -ENOTSUPP;
+}
+
+static int tps65219_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+ struct tps65219_gpio *gpio = gpiochip_get_data(gc);
+ struct device *dev = gpio->tps->dev;
+
+ if (offset != TPS65219_GPIO0_IDX) {
+ dev_err(dev, "GPIO%d is output only, cannot change to input\n", offset);
+ return -ENOTSUPP;
+ }
+
+ if (tps65219_gpio_get_direction(gc, offset) == TPS65219_GPIO_DIR_IN)
+ return 0;
+
+ return tps65219_gpio_change_direction(gc, offset, TPS65219_GPIO_DIR_IN);
+}
+
+static int tps65219_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ tps65219_gpio_set(gc, offset, value);
+ if (offset != TPS65219_GPIO0_IDX)
+ return 0;
+
+ if (tps65219_gpio_get_direction(gc, offset) == TPS65219_GPIO_DIR_OUT)
+ return 0;
+
+ return tps65219_gpio_change_direction(gc, offset, TPS65219_GPIO_DIR_OUT);
+}
+
+static const struct gpio_chip tps65219_template_chip = {
+ .label = "tps65219-gpio",
+ .owner = THIS_MODULE,
+ .get_direction = tps65219_gpio_get_direction,
+ .direction_input = tps65219_gpio_direction_input,
+ .direction_output = tps65219_gpio_direction_output,
+ .get = tps65219_gpio_get,
+ .set = tps65219_gpio_set,
+ .base = -1,
+ .ngpio = 3,
+ .can_sleep = true,
+};
+
+static int tps65219_gpio_probe(struct platform_device *pdev)
+{
+ struct tps65219 *tps = dev_get_drvdata(pdev->dev.parent);
+ struct tps65219_gpio *gpio;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->tps = tps;
+ gpio->gpio_chip = tps65219_template_chip;
+ gpio->gpio_chip.parent = tps->dev;
+
+ return devm_gpiochip_add_data(&pdev->dev, &gpio->gpio_chip, gpio);
+}
+
+static struct platform_driver tps65219_gpio_driver = {
+ .driver = {
+ .name = "tps65219-gpio",
+ },
+ .probe = tps65219_gpio_probe,
+};
+module_platform_driver(tps65219_gpio_driver);
+
+MODULE_ALIAS("platform:tps65219-gpio");
+MODULE_AUTHOR("Jonathan Cormier <jcormier@criticallink.com>");
+MODULE_DESCRIPTION("TPS65219 GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-tps6586x.c b/drivers/gpio/gpio-tps6586x.c
new file mode 100644
index 0000000000..d277aa9511
--- /dev/null
+++ b/drivers/gpio/gpio-tps6586x.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI TPS6586x GPIO driver
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
+ * Author: Laxman dewangan <ldewangan@nvidia.com>
+ *
+ * Based on tps6586x.c
+ * Copyright (c) 2010 CompuLab Ltd.
+ * Mike Rapoport <mike@compulab.co.il>
+ */
+
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/mfd/tps6586x.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+/* GPIO control registers */
+#define TPS6586X_GPIOSET1 0x5d
+#define TPS6586X_GPIOSET2 0x5e
+
+struct tps6586x_gpio {
+ struct gpio_chip gpio_chip;
+ struct device *parent;
+};
+
+static int tps6586x_gpio_get(struct gpio_chip *gc, unsigned offset)
+{
+ struct tps6586x_gpio *tps6586x_gpio = gpiochip_get_data(gc);
+ uint8_t val;
+ int ret;
+
+ ret = tps6586x_read(tps6586x_gpio->parent, TPS6586X_GPIOSET2, &val);
+ if (ret)
+ return ret;
+
+ return !!(val & (1 << offset));
+}
+
+static void tps6586x_gpio_set(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ struct tps6586x_gpio *tps6586x_gpio = gpiochip_get_data(gc);
+
+ tps6586x_update(tps6586x_gpio->parent, TPS6586X_GPIOSET2,
+ value << offset, 1 << offset);
+}
+
+static int tps6586x_gpio_output(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ struct tps6586x_gpio *tps6586x_gpio = gpiochip_get_data(gc);
+ uint8_t val, mask;
+
+ tps6586x_gpio_set(gc, offset, value);
+
+ val = 0x1 << (offset * 2);
+ mask = 0x3 << (offset * 2);
+
+ return tps6586x_update(tps6586x_gpio->parent, TPS6586X_GPIOSET1,
+ val, mask);
+}
+
+static int tps6586x_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
+{
+ struct tps6586x_gpio *tps6586x_gpio = gpiochip_get_data(gc);
+
+ return tps6586x_irq_get_virq(tps6586x_gpio->parent,
+ TPS6586X_INT_PLDO_0 + offset);
+}
+
+static int tps6586x_gpio_probe(struct platform_device *pdev)
+{
+ struct tps6586x_platform_data *pdata;
+ struct tps6586x_gpio *tps6586x_gpio;
+
+ device_set_node(&pdev->dev, dev_fwnode(pdev->dev.parent));
+
+ pdata = dev_get_platdata(pdev->dev.parent);
+ tps6586x_gpio = devm_kzalloc(&pdev->dev,
+ sizeof(*tps6586x_gpio), GFP_KERNEL);
+ if (!tps6586x_gpio)
+ return -ENOMEM;
+
+ tps6586x_gpio->parent = pdev->dev.parent;
+
+ tps6586x_gpio->gpio_chip.owner = THIS_MODULE;
+ tps6586x_gpio->gpio_chip.label = pdev->name;
+ tps6586x_gpio->gpio_chip.parent = &pdev->dev;
+ tps6586x_gpio->gpio_chip.ngpio = 4;
+ tps6586x_gpio->gpio_chip.can_sleep = true;
+
+ /* FIXME: add handling of GPIOs as dedicated inputs */
+ tps6586x_gpio->gpio_chip.direction_output = tps6586x_gpio_output;
+ tps6586x_gpio->gpio_chip.set = tps6586x_gpio_set;
+ tps6586x_gpio->gpio_chip.get = tps6586x_gpio_get;
+ tps6586x_gpio->gpio_chip.to_irq = tps6586x_gpio_to_irq;
+
+ if (pdata && pdata->gpio_base)
+ tps6586x_gpio->gpio_chip.base = pdata->gpio_base;
+ else
+ tps6586x_gpio->gpio_chip.base = -1;
+
+ return devm_gpiochip_add_data(&pdev->dev, &tps6586x_gpio->gpio_chip,
+ tps6586x_gpio);
+}
+
+static struct platform_driver tps6586x_gpio_driver = {
+ .driver.name = "tps6586x-gpio",
+ .probe = tps6586x_gpio_probe,
+};
+
+static int __init tps6586x_gpio_init(void)
+{
+ return platform_driver_register(&tps6586x_gpio_driver);
+}
+subsys_initcall(tps6586x_gpio_init);
diff --git a/drivers/gpio/gpio-tps65910.c b/drivers/gpio/gpio-tps65910.c
new file mode 100644
index 0000000000..187d215805
--- /dev/null
+++ b/drivers/gpio/gpio-tps65910.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * TI TPS6591x GPIO driver
+ *
+ * Copyright 2010 Texas Instruments Inc.
+ *
+ * Author: Graeme Gregory <gg@slimlogic.co.uk>
+ * Author: Jorge Eduardo Candelaria <jedu@slimlogic.co.uk>
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/tps65910.h>
+#include <linux/of.h>
+
+struct tps65910_gpio {
+ struct gpio_chip gpio_chip;
+ struct tps65910 *tps65910;
+};
+
+static int tps65910_gpio_get(struct gpio_chip *gc, unsigned offset)
+{
+ struct tps65910_gpio *tps65910_gpio = gpiochip_get_data(gc);
+ struct tps65910 *tps65910 = tps65910_gpio->tps65910;
+ unsigned int val;
+
+ regmap_read(tps65910->regmap, TPS65910_GPIO0 + offset, &val);
+
+ if (val & GPIO_STS_MASK)
+ return 1;
+
+ return 0;
+}
+
+static void tps65910_gpio_set(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ struct tps65910_gpio *tps65910_gpio = gpiochip_get_data(gc);
+ struct tps65910 *tps65910 = tps65910_gpio->tps65910;
+
+ if (value)
+ regmap_set_bits(tps65910->regmap, TPS65910_GPIO0 + offset,
+ GPIO_SET_MASK);
+ else
+ regmap_clear_bits(tps65910->regmap, TPS65910_GPIO0 + offset,
+ GPIO_SET_MASK);
+}
+
+static int tps65910_gpio_output(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ struct tps65910_gpio *tps65910_gpio = gpiochip_get_data(gc);
+ struct tps65910 *tps65910 = tps65910_gpio->tps65910;
+
+ /* Set the initial value */
+ tps65910_gpio_set(gc, offset, value);
+
+ return regmap_set_bits(tps65910->regmap, TPS65910_GPIO0 + offset,
+ GPIO_CFG_MASK);
+}
+
+static int tps65910_gpio_input(struct gpio_chip *gc, unsigned offset)
+{
+ struct tps65910_gpio *tps65910_gpio = gpiochip_get_data(gc);
+ struct tps65910 *tps65910 = tps65910_gpio->tps65910;
+
+ return regmap_clear_bits(tps65910->regmap, TPS65910_GPIO0 + offset,
+ GPIO_CFG_MASK);
+}
+
+#ifdef CONFIG_OF
+static struct tps65910_board *tps65910_parse_dt_for_gpio(struct device *dev,
+ struct tps65910 *tps65910, int chip_ngpio)
+{
+ struct tps65910_board *tps65910_board = tps65910->of_plat_data;
+ unsigned int prop_array[TPS6591X_MAX_NUM_GPIO];
+ int ngpio = min(chip_ngpio, TPS6591X_MAX_NUM_GPIO);
+ int ret;
+ int idx;
+
+ tps65910_board->gpio_base = -1;
+ ret = of_property_read_u32_array(tps65910->dev->of_node,
+ "ti,en-gpio-sleep", prop_array, ngpio);
+ if (ret < 0) {
+ dev_dbg(dev, "ti,en-gpio-sleep not specified\n");
+ return tps65910_board;
+ }
+
+ for (idx = 0; idx < ngpio; idx++)
+ tps65910_board->en_gpio_sleep[idx] = (prop_array[idx] != 0);
+
+ return tps65910_board;
+}
+#else
+static struct tps65910_board *tps65910_parse_dt_for_gpio(struct device *dev,
+ struct tps65910 *tps65910, int chip_ngpio)
+{
+ return NULL;
+}
+#endif
+
+static int tps65910_gpio_probe(struct platform_device *pdev)
+{
+ struct tps65910 *tps65910 = dev_get_drvdata(pdev->dev.parent);
+ struct tps65910_board *pdata = dev_get_platdata(tps65910->dev);
+ struct tps65910_gpio *tps65910_gpio;
+ int ret;
+ int i;
+
+ device_set_node(&pdev->dev, dev_fwnode(pdev->dev.parent));
+
+ tps65910_gpio = devm_kzalloc(&pdev->dev,
+ sizeof(*tps65910_gpio), GFP_KERNEL);
+ if (!tps65910_gpio)
+ return -ENOMEM;
+
+ tps65910_gpio->tps65910 = tps65910;
+
+ tps65910_gpio->gpio_chip.owner = THIS_MODULE;
+ tps65910_gpio->gpio_chip.label = tps65910->i2c_client->name;
+
+ switch (tps65910_chip_id(tps65910)) {
+ case TPS65910:
+ tps65910_gpio->gpio_chip.ngpio = TPS65910_NUM_GPIO;
+ break;
+ case TPS65911:
+ tps65910_gpio->gpio_chip.ngpio = TPS65911_NUM_GPIO;
+ break;
+ default:
+ return -EINVAL;
+ }
+ tps65910_gpio->gpio_chip.can_sleep = true;
+ tps65910_gpio->gpio_chip.direction_input = tps65910_gpio_input;
+ tps65910_gpio->gpio_chip.direction_output = tps65910_gpio_output;
+ tps65910_gpio->gpio_chip.set = tps65910_gpio_set;
+ tps65910_gpio->gpio_chip.get = tps65910_gpio_get;
+ tps65910_gpio->gpio_chip.parent = &pdev->dev;
+
+ if (pdata && pdata->gpio_base)
+ tps65910_gpio->gpio_chip.base = pdata->gpio_base;
+ else
+ tps65910_gpio->gpio_chip.base = -1;
+
+ if (!pdata && tps65910->dev->of_node)
+ pdata = tps65910_parse_dt_for_gpio(&pdev->dev, tps65910,
+ tps65910_gpio->gpio_chip.ngpio);
+
+ if (!pdata)
+ goto skip_init;
+
+ /* Configure sleep control for gpios if provided */
+ for (i = 0; i < tps65910_gpio->gpio_chip.ngpio; ++i) {
+ if (!pdata->en_gpio_sleep[i])
+ continue;
+
+ ret = regmap_set_bits(tps65910->regmap,
+ TPS65910_GPIO0 + i, GPIO_SLEEP_MASK);
+ if (ret < 0)
+ dev_warn(tps65910->dev,
+ "GPIO Sleep setting failed with err %d\n", ret);
+ }
+
+skip_init:
+ return devm_gpiochip_add_data(&pdev->dev, &tps65910_gpio->gpio_chip,
+ tps65910_gpio);
+}
+
+static struct platform_driver tps65910_gpio_driver = {
+ .driver.name = "tps65910-gpio",
+ .probe = tps65910_gpio_probe,
+};
+
+static int __init tps65910_gpio_init(void)
+{
+ return platform_driver_register(&tps65910_gpio_driver);
+}
+subsys_initcall(tps65910_gpio_init);
diff --git a/drivers/gpio/gpio-tps65912.c b/drivers/gpio/gpio-tps65912.c
new file mode 100644
index 0000000000..fab771cb6a
--- /dev/null
+++ b/drivers/gpio/gpio-tps65912.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO driver for TI TPS65912x PMICs
+ *
+ * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/
+ * Andrew F. Davis <afd@ti.com>
+ *
+ * Based on the Arizona GPIO driver and the previous TPS65912 driver by
+ * Margarita Olaya Cabrera <magi@slimlogic.co.uk>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <linux/mfd/tps65912.h>
+
+struct tps65912_gpio {
+ struct gpio_chip gpio_chip;
+ struct tps65912 *tps;
+};
+
+static int tps65912_gpio_get_direction(struct gpio_chip *gc,
+ unsigned offset)
+{
+ struct tps65912_gpio *gpio = gpiochip_get_data(gc);
+
+ int ret, val;
+
+ ret = regmap_read(gpio->tps->regmap, TPS65912_GPIO1 + offset, &val);
+ if (ret)
+ return ret;
+
+ if (val & GPIO_CFG_MASK)
+ return GPIO_LINE_DIRECTION_OUT;
+ else
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int tps65912_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
+{
+ struct tps65912_gpio *gpio = gpiochip_get_data(gc);
+
+ return regmap_update_bits(gpio->tps->regmap, TPS65912_GPIO1 + offset,
+ GPIO_CFG_MASK, 0);
+}
+
+static int tps65912_gpio_direction_output(struct gpio_chip *gc,
+ unsigned offset, int value)
+{
+ struct tps65912_gpio *gpio = gpiochip_get_data(gc);
+
+ /* Set the initial value */
+ regmap_update_bits(gpio->tps->regmap, TPS65912_GPIO1 + offset,
+ GPIO_SET_MASK, value ? GPIO_SET_MASK : 0);
+
+ return regmap_update_bits(gpio->tps->regmap, TPS65912_GPIO1 + offset,
+ GPIO_CFG_MASK, GPIO_CFG_MASK);
+}
+
+static int tps65912_gpio_get(struct gpio_chip *gc, unsigned offset)
+{
+ struct tps65912_gpio *gpio = gpiochip_get_data(gc);
+ int ret, val;
+
+ ret = regmap_read(gpio->tps->regmap, TPS65912_GPIO1 + offset, &val);
+ if (ret)
+ return ret;
+
+ if (val & GPIO_STS_MASK)
+ return 1;
+
+ return 0;
+}
+
+static void tps65912_gpio_set(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ struct tps65912_gpio *gpio = gpiochip_get_data(gc);
+
+ regmap_update_bits(gpio->tps->regmap, TPS65912_GPIO1 + offset,
+ GPIO_SET_MASK, value ? GPIO_SET_MASK : 0);
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "tps65912-gpio",
+ .owner = THIS_MODULE,
+ .get_direction = tps65912_gpio_get_direction,
+ .direction_input = tps65912_gpio_direction_input,
+ .direction_output = tps65912_gpio_direction_output,
+ .get = tps65912_gpio_get,
+ .set = tps65912_gpio_set,
+ .base = -1,
+ .ngpio = 5,
+ .can_sleep = true,
+};
+
+static int tps65912_gpio_probe(struct platform_device *pdev)
+{
+ struct tps65912 *tps = dev_get_drvdata(pdev->dev.parent);
+ struct tps65912_gpio *gpio;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->tps = dev_get_drvdata(pdev->dev.parent);
+ gpio->gpio_chip = template_chip;
+ gpio->gpio_chip.parent = tps->dev;
+
+ return devm_gpiochip_add_data(&pdev->dev, &gpio->gpio_chip, gpio);
+}
+
+static const struct platform_device_id tps65912_gpio_id_table[] = {
+ { "tps65912-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, tps65912_gpio_id_table);
+
+static struct platform_driver tps65912_gpio_driver = {
+ .driver = {
+ .name = "tps65912-gpio",
+ },
+ .probe = tps65912_gpio_probe,
+ .id_table = tps65912_gpio_id_table,
+};
+module_platform_driver(tps65912_gpio_driver);
+
+MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>");
+MODULE_DESCRIPTION("TPS65912 GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-tps68470.c b/drivers/gpio/gpio-tps68470.c
new file mode 100644
index 0000000000..532deaddfd
--- /dev/null
+++ b/drivers/gpio/gpio-tps68470.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO driver for TPS68470 PMIC
+ *
+ * Copyright (C) 2017 Intel Corporation
+ *
+ * Authors:
+ * Antti Laakso <antti.laakso@intel.com>
+ * Tianshu Qiu <tian.shu.qiu@intel.com>
+ * Jian Xu Zheng <jian.xu.zheng@intel.com>
+ * Yuning Pu <yuning.pu@intel.com>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/mfd/tps68470.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define TPS68470_N_LOGIC_OUTPUT 3
+#define TPS68470_N_REGULAR_GPIO 7
+#define TPS68470_N_GPIO (TPS68470_N_LOGIC_OUTPUT + TPS68470_N_REGULAR_GPIO)
+
+struct tps68470_gpio_data {
+ struct regmap *tps68470_regmap;
+ struct gpio_chip gc;
+};
+
+static int tps68470_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct tps68470_gpio_data *tps68470_gpio = gpiochip_get_data(gc);
+ struct regmap *regmap = tps68470_gpio->tps68470_regmap;
+ unsigned int reg = TPS68470_REG_GPDO;
+ int val, ret;
+
+ if (offset >= TPS68470_N_REGULAR_GPIO) {
+ offset -= TPS68470_N_REGULAR_GPIO;
+ reg = TPS68470_REG_SGPO;
+ }
+
+ ret = regmap_read(regmap, reg, &val);
+ if (ret) {
+ dev_err(tps68470_gpio->gc.parent, "reg 0x%x read failed\n",
+ TPS68470_REG_SGPO);
+ return ret;
+ }
+ return !!(val & BIT(offset));
+}
+
+static int tps68470_gpio_get_direction(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ struct tps68470_gpio_data *tps68470_gpio = gpiochip_get_data(gc);
+ struct regmap *regmap = tps68470_gpio->tps68470_regmap;
+ int val, ret;
+
+ /* rest are always outputs */
+ if (offset >= TPS68470_N_REGULAR_GPIO)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ ret = regmap_read(regmap, TPS68470_GPIO_CTL_REG_A(offset), &val);
+ if (ret) {
+ dev_err(tps68470_gpio->gc.parent, "reg 0x%x read failed\n",
+ TPS68470_GPIO_CTL_REG_A(offset));
+ return ret;
+ }
+
+ val &= TPS68470_GPIO_MODE_MASK;
+ return val >= TPS68470_GPIO_MODE_OUT_CMOS ? GPIO_LINE_DIRECTION_OUT :
+ GPIO_LINE_DIRECTION_IN;
+}
+
+static void tps68470_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct tps68470_gpio_data *tps68470_gpio = gpiochip_get_data(gc);
+ struct regmap *regmap = tps68470_gpio->tps68470_regmap;
+ unsigned int reg = TPS68470_REG_GPDO;
+
+ if (offset >= TPS68470_N_REGULAR_GPIO) {
+ reg = TPS68470_REG_SGPO;
+ offset -= TPS68470_N_REGULAR_GPIO;
+ }
+
+ regmap_update_bits(regmap, reg, BIT(offset), value ? BIT(offset) : 0);
+}
+
+static int tps68470_gpio_output(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct tps68470_gpio_data *tps68470_gpio = gpiochip_get_data(gc);
+ struct regmap *regmap = tps68470_gpio->tps68470_regmap;
+
+ /* Set the initial value */
+ tps68470_gpio_set(gc, offset, value);
+
+ /* rest are always outputs */
+ if (offset >= TPS68470_N_REGULAR_GPIO)
+ return 0;
+
+ return regmap_update_bits(regmap, TPS68470_GPIO_CTL_REG_A(offset),
+ TPS68470_GPIO_MODE_MASK,
+ TPS68470_GPIO_MODE_OUT_CMOS);
+}
+
+static int tps68470_gpio_input(struct gpio_chip *gc, unsigned int offset)
+{
+ struct tps68470_gpio_data *tps68470_gpio = gpiochip_get_data(gc);
+ struct regmap *regmap = tps68470_gpio->tps68470_regmap;
+
+ /* rest are always outputs */
+ if (offset >= TPS68470_N_REGULAR_GPIO)
+ return -EINVAL;
+
+ return regmap_update_bits(regmap, TPS68470_GPIO_CTL_REG_A(offset),
+ TPS68470_GPIO_MODE_MASK, 0x00);
+}
+
+static const char *tps68470_names[TPS68470_N_GPIO] = {
+ "gpio.0", "gpio.1", "gpio.2", "gpio.3",
+ "gpio.4", "gpio.5", "gpio.6",
+ "s_enable", "s_idle", "s_resetn",
+};
+
+static int tps68470_gpio_probe(struct platform_device *pdev)
+{
+ struct tps68470_gpio_data *tps68470_gpio;
+
+ tps68470_gpio = devm_kzalloc(&pdev->dev, sizeof(*tps68470_gpio),
+ GFP_KERNEL);
+ if (!tps68470_gpio)
+ return -ENOMEM;
+
+ tps68470_gpio->tps68470_regmap = dev_get_drvdata(pdev->dev.parent);
+ tps68470_gpio->gc.label = "tps68470-gpio";
+ tps68470_gpio->gc.owner = THIS_MODULE;
+ tps68470_gpio->gc.direction_input = tps68470_gpio_input;
+ tps68470_gpio->gc.direction_output = tps68470_gpio_output;
+ tps68470_gpio->gc.get = tps68470_gpio_get;
+ tps68470_gpio->gc.get_direction = tps68470_gpio_get_direction;
+ tps68470_gpio->gc.set = tps68470_gpio_set;
+ tps68470_gpio->gc.can_sleep = true;
+ tps68470_gpio->gc.names = tps68470_names;
+ tps68470_gpio->gc.ngpio = TPS68470_N_GPIO;
+ tps68470_gpio->gc.base = -1;
+ tps68470_gpio->gc.parent = &pdev->dev;
+
+ return devm_gpiochip_add_data(&pdev->dev, &tps68470_gpio->gc, tps68470_gpio);
+}
+
+static struct platform_driver tps68470_gpio_driver = {
+ .driver = {
+ .name = "tps68470-gpio",
+ },
+ .probe = tps68470_gpio_probe,
+};
+module_platform_driver(tps68470_gpio_driver);
+
+MODULE_ALIAS("platform:tps68470-gpio");
+MODULE_DESCRIPTION("GPIO driver for TPS68470 PMIC");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-tqmx86.c b/drivers/gpio/gpio-tqmx86.c
new file mode 100644
index 0000000000..3a28c1f273
--- /dev/null
+++ b/drivers/gpio/gpio-tqmx86.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TQ-Systems TQMx86 PLD GPIO driver
+ *
+ * Based on vendor driver by:
+ * Vadim V.Vlasov <vvlasov@dev.rtsoft.ru>
+ */
+
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+
+#define TQMX86_NGPIO 8
+#define TQMX86_NGPO 4 /* 0-3 - output */
+#define TQMX86_NGPI 4 /* 4-7 - input */
+#define TQMX86_DIR_INPUT_MASK 0xf0 /* 0-3 - output, 4-7 - input */
+
+#define TQMX86_GPIODD 0 /* GPIO Data Direction Register */
+#define TQMX86_GPIOD 1 /* GPIO Data Register */
+#define TQMX86_GPIIC 3 /* GPI Interrupt Configuration Register */
+#define TQMX86_GPIIS 4 /* GPI Interrupt Status Register */
+
+#define TQMX86_GPII_FALLING BIT(0)
+#define TQMX86_GPII_RISING BIT(1)
+#define TQMX86_GPII_MASK (BIT(0) | BIT(1))
+#define TQMX86_GPII_BITS 2
+
+struct tqmx86_gpio_data {
+ struct gpio_chip chip;
+ void __iomem *io_base;
+ int irq;
+ raw_spinlock_t spinlock;
+ u8 irq_type[TQMX86_NGPI];
+};
+
+static u8 tqmx86_gpio_read(struct tqmx86_gpio_data *gd, unsigned int reg)
+{
+ return ioread8(gd->io_base + reg);
+}
+
+static void tqmx86_gpio_write(struct tqmx86_gpio_data *gd, u8 val,
+ unsigned int reg)
+{
+ iowrite8(val, gd->io_base + reg);
+}
+
+static int tqmx86_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct tqmx86_gpio_data *gpio = gpiochip_get_data(chip);
+
+ return !!(tqmx86_gpio_read(gpio, TQMX86_GPIOD) & BIT(offset));
+}
+
+static void tqmx86_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct tqmx86_gpio_data *gpio = gpiochip_get_data(chip);
+ unsigned long flags;
+ u8 val;
+
+ raw_spin_lock_irqsave(&gpio->spinlock, flags);
+ val = tqmx86_gpio_read(gpio, TQMX86_GPIOD);
+ if (value)
+ val |= BIT(offset);
+ else
+ val &= ~BIT(offset);
+ tqmx86_gpio_write(gpio, val, TQMX86_GPIOD);
+ raw_spin_unlock_irqrestore(&gpio->spinlock, flags);
+}
+
+static int tqmx86_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ /* Direction cannot be changed. Validate is an input. */
+ if (BIT(offset) & TQMX86_DIR_INPUT_MASK)
+ return 0;
+ else
+ return -EINVAL;
+}
+
+static int tqmx86_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset,
+ int value)
+{
+ /* Direction cannot be changed, validate is an output */
+ if (BIT(offset) & TQMX86_DIR_INPUT_MASK)
+ return -EINVAL;
+
+ tqmx86_gpio_set(chip, offset, value);
+ return 0;
+}
+
+static int tqmx86_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ if (TQMX86_DIR_INPUT_MASK & BIT(offset))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static void tqmx86_gpio_irq_mask(struct irq_data *data)
+{
+ unsigned int offset = (data->hwirq - TQMX86_NGPO);
+ struct tqmx86_gpio_data *gpio = gpiochip_get_data(
+ irq_data_get_irq_chip_data(data));
+ unsigned long flags;
+ u8 gpiic, mask;
+
+ mask = TQMX86_GPII_MASK << (offset * TQMX86_GPII_BITS);
+
+ raw_spin_lock_irqsave(&gpio->spinlock, flags);
+ gpiic = tqmx86_gpio_read(gpio, TQMX86_GPIIC);
+ gpiic &= ~mask;
+ tqmx86_gpio_write(gpio, gpiic, TQMX86_GPIIC);
+ raw_spin_unlock_irqrestore(&gpio->spinlock, flags);
+ gpiochip_disable_irq(&gpio->chip, irqd_to_hwirq(data));
+}
+
+static void tqmx86_gpio_irq_unmask(struct irq_data *data)
+{
+ unsigned int offset = (data->hwirq - TQMX86_NGPO);
+ struct tqmx86_gpio_data *gpio = gpiochip_get_data(
+ irq_data_get_irq_chip_data(data));
+ unsigned long flags;
+ u8 gpiic, mask;
+
+ mask = TQMX86_GPII_MASK << (offset * TQMX86_GPII_BITS);
+
+ gpiochip_enable_irq(&gpio->chip, irqd_to_hwirq(data));
+ raw_spin_lock_irqsave(&gpio->spinlock, flags);
+ gpiic = tqmx86_gpio_read(gpio, TQMX86_GPIIC);
+ gpiic &= ~mask;
+ gpiic |= gpio->irq_type[offset] << (offset * TQMX86_GPII_BITS);
+ tqmx86_gpio_write(gpio, gpiic, TQMX86_GPIIC);
+ raw_spin_unlock_irqrestore(&gpio->spinlock, flags);
+}
+
+static int tqmx86_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ struct tqmx86_gpio_data *gpio = gpiochip_get_data(
+ irq_data_get_irq_chip_data(data));
+ unsigned int offset = (data->hwirq - TQMX86_NGPO);
+ unsigned int edge_type = type & IRQF_TRIGGER_MASK;
+ unsigned long flags;
+ u8 new_type, gpiic;
+
+ switch (edge_type) {
+ case IRQ_TYPE_EDGE_RISING:
+ new_type = TQMX86_GPII_RISING;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ new_type = TQMX86_GPII_FALLING;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ new_type = TQMX86_GPII_FALLING | TQMX86_GPII_RISING;
+ break;
+ default:
+ return -EINVAL; /* not supported */
+ }
+
+ gpio->irq_type[offset] = new_type;
+
+ raw_spin_lock_irqsave(&gpio->spinlock, flags);
+ gpiic = tqmx86_gpio_read(gpio, TQMX86_GPIIC);
+ gpiic &= ~((TQMX86_GPII_MASK) << (offset * TQMX86_GPII_BITS));
+ gpiic |= new_type << (offset * TQMX86_GPII_BITS);
+ tqmx86_gpio_write(gpio, gpiic, TQMX86_GPIIC);
+ raw_spin_unlock_irqrestore(&gpio->spinlock, flags);
+
+ return 0;
+}
+
+static void tqmx86_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct gpio_chip *chip = irq_desc_get_handler_data(desc);
+ struct tqmx86_gpio_data *gpio = gpiochip_get_data(chip);
+ struct irq_chip *irq_chip = irq_desc_get_chip(desc);
+ unsigned long irq_bits;
+ int i = 0;
+ u8 irq_status;
+
+ chained_irq_enter(irq_chip, desc);
+
+ irq_status = tqmx86_gpio_read(gpio, TQMX86_GPIIS);
+ tqmx86_gpio_write(gpio, irq_status, TQMX86_GPIIS);
+
+ irq_bits = irq_status;
+ for_each_set_bit(i, &irq_bits, TQMX86_NGPI)
+ generic_handle_domain_irq(gpio->chip.irq.domain,
+ i + TQMX86_NGPO);
+
+ chained_irq_exit(irq_chip, desc);
+}
+
+/* Minimal runtime PM is needed by the IRQ subsystem */
+static int __maybe_unused tqmx86_gpio_runtime_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int __maybe_unused tqmx86_gpio_runtime_resume(struct device *dev)
+{
+ return 0;
+}
+
+static const struct dev_pm_ops tqmx86_gpio_dev_pm_ops = {
+ SET_RUNTIME_PM_OPS(tqmx86_gpio_runtime_suspend,
+ tqmx86_gpio_runtime_resume, NULL)
+};
+
+static void tqmx86_init_irq_valid_mask(struct gpio_chip *chip,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ /* Only GPIOs 4-7 are valid for interrupts. Clear the others */
+ clear_bit(0, valid_mask);
+ clear_bit(1, valid_mask);
+ clear_bit(2, valid_mask);
+ clear_bit(3, valid_mask);
+}
+
+static void tqmx86_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+
+ seq_printf(p, gc->label);
+}
+
+static const struct irq_chip tqmx86_gpio_irq_chip = {
+ .irq_mask = tqmx86_gpio_irq_mask,
+ .irq_unmask = tqmx86_gpio_irq_unmask,
+ .irq_set_type = tqmx86_gpio_irq_set_type,
+ .irq_print_chip = tqmx86_gpio_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int tqmx86_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tqmx86_gpio_data *gpio;
+ struct gpio_chip *chip;
+ struct gpio_irq_chip *girq;
+ void __iomem *io_base;
+ struct resource *res;
+ int ret, irq;
+
+ irq = platform_get_irq_optional(pdev, 0);
+ if (irq < 0 && irq != -ENXIO)
+ return irq;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Cannot get I/O\n");
+ return -ENODEV;
+ }
+
+ io_base = devm_ioport_map(&pdev->dev, res->start, resource_size(res));
+ if (!io_base)
+ return -ENOMEM;
+
+ gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&gpio->spinlock);
+ gpio->io_base = io_base;
+
+ tqmx86_gpio_write(gpio, (u8)~TQMX86_DIR_INPUT_MASK, TQMX86_GPIODD);
+
+ chip = &gpio->chip;
+ chip->label = "gpio-tqmx86";
+ chip->owner = THIS_MODULE;
+ chip->can_sleep = false;
+ chip->base = -1;
+ chip->direction_input = tqmx86_gpio_direction_input;
+ chip->direction_output = tqmx86_gpio_direction_output;
+ chip->get_direction = tqmx86_gpio_get_direction;
+ chip->get = tqmx86_gpio_get;
+ chip->set = tqmx86_gpio_set;
+ chip->ngpio = TQMX86_NGPIO;
+ chip->parent = pdev->dev.parent;
+
+ pm_runtime_enable(&pdev->dev);
+
+ if (irq > 0) {
+ u8 irq_status;
+
+ /* Mask all interrupts */
+ tqmx86_gpio_write(gpio, 0, TQMX86_GPIIC);
+
+ /* Clear all pending interrupts */
+ irq_status = tqmx86_gpio_read(gpio, TQMX86_GPIIS);
+ tqmx86_gpio_write(gpio, irq_status, TQMX86_GPIIS);
+
+ girq = &chip->irq;
+ gpio_irq_chip_set_chip(girq, &tqmx86_gpio_irq_chip);
+ girq->parent_handler = tqmx86_gpio_irq_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(&pdev->dev, 1,
+ sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents) {
+ ret = -ENOMEM;
+ goto out_pm_dis;
+ }
+ girq->parents[0] = irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ girq->init_valid_mask = tqmx86_init_irq_valid_mask;
+
+ irq_domain_set_pm_device(girq->domain, dev);
+ }
+
+ ret = devm_gpiochip_add_data(dev, chip, gpio);
+ if (ret) {
+ dev_err(dev, "Could not register GPIO chip\n");
+ goto out_pm_dis;
+ }
+
+ dev_info(dev, "GPIO functionality initialized with %d pins\n",
+ chip->ngpio);
+
+ return 0;
+
+out_pm_dis:
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static struct platform_driver tqmx86_gpio_driver = {
+ .driver = {
+ .name = "tqmx86-gpio",
+ .pm = &tqmx86_gpio_dev_pm_ops,
+ },
+ .probe = tqmx86_gpio_probe,
+};
+
+module_platform_driver(tqmx86_gpio_driver);
+
+MODULE_DESCRIPTION("TQMx86 PLD GPIO Driver");
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:tqmx86-gpio");
diff --git a/drivers/gpio/gpio-ts4800.c b/drivers/gpio/gpio-ts4800.c
new file mode 100644
index 0000000000..4748e3d471
--- /dev/null
+++ b/drivers/gpio/gpio-ts4800.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for the TS-4800 board
+ *
+ * Copyright (c) 2016 - Savoir-faire Linux
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#define DEFAULT_PIN_NUMBER 16
+#define INPUT_REG_OFFSET 0x00
+#define OUTPUT_REG_OFFSET 0x02
+#define DIRECTION_REG_OFFSET 0x04
+
+static int ts4800_gpio_probe(struct platform_device *pdev)
+{
+ struct device_node *node;
+ struct gpio_chip *chip;
+ void __iomem *base_addr;
+ int retval;
+ u32 ngpios;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(struct gpio_chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ base_addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base_addr))
+ return PTR_ERR(base_addr);
+
+ node = pdev->dev.of_node;
+ if (!node)
+ return -EINVAL;
+
+ retval = of_property_read_u32(node, "ngpios", &ngpios);
+ if (retval == -EINVAL)
+ ngpios = DEFAULT_PIN_NUMBER;
+ else if (retval)
+ return retval;
+
+ retval = bgpio_init(chip, &pdev->dev, 2, base_addr + INPUT_REG_OFFSET,
+ base_addr + OUTPUT_REG_OFFSET, NULL,
+ base_addr + DIRECTION_REG_OFFSET, NULL, 0);
+ if (retval) {
+ dev_err(&pdev->dev, "bgpio_init failed\n");
+ return retval;
+ }
+
+ chip->ngpio = ngpios;
+
+ platform_set_drvdata(pdev, chip);
+
+ return devm_gpiochip_add_data(&pdev->dev, chip, NULL);
+}
+
+static const struct of_device_id ts4800_gpio_of_match[] = {
+ { .compatible = "technologic,ts4800-gpio", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ts4800_gpio_of_match);
+
+static struct platform_driver ts4800_gpio_driver = {
+ .driver = {
+ .name = "ts4800-gpio",
+ .of_match_table = ts4800_gpio_of_match,
+ },
+ .probe = ts4800_gpio_probe,
+};
+
+module_platform_driver_probe(ts4800_gpio_driver, ts4800_gpio_probe);
+
+MODULE_AUTHOR("Julien Grossholtz <julien.grossholtz@savoirfairelinux.com>");
+MODULE_DESCRIPTION("TS4800 FPGA GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-ts4900.c b/drivers/gpio/gpio-ts4900.c
new file mode 100644
index 0000000000..0f6397b77c
--- /dev/null
+++ b/drivers/gpio/gpio-ts4900.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Digital I/O driver for Technologic Systems I2C FPGA Core
+ *
+ * Copyright (C) 2015, 2018 Technologic Systems
+ * Copyright (C) 2016 Savoir-Faire Linux
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#define DEFAULT_PIN_NUMBER 32
+/*
+ * Register bits used by the GPIO device
+ * Some boards, such as TS-7970 do not have a separate input bit
+ */
+#define TS4900_GPIO_OE 0x01
+#define TS4900_GPIO_OUT 0x02
+#define TS4900_GPIO_IN 0x04
+#define TS7970_GPIO_IN 0x02
+
+struct ts4900_gpio_priv {
+ struct regmap *regmap;
+ struct gpio_chip gpio_chip;
+ unsigned int input_bit;
+};
+
+static int ts4900_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct ts4900_gpio_priv *priv = gpiochip_get_data(chip);
+ unsigned int reg;
+
+ regmap_read(priv->regmap, offset, &reg);
+
+ if (reg & TS4900_GPIO_OE)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int ts4900_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ struct ts4900_gpio_priv *priv = gpiochip_get_data(chip);
+
+ /*
+ * Only clear the OE bit here, requires a RMW. Prevents a potential issue
+ * with OE and DAT getting to the physical pin at different times.
+ */
+ return regmap_update_bits(priv->regmap, offset, TS4900_GPIO_OE, 0);
+}
+
+static int ts4900_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct ts4900_gpio_priv *priv = gpiochip_get_data(chip);
+ unsigned int reg;
+ int ret;
+
+ /*
+ * If changing from an input to an output, we need to first set the
+ * GPIO's DAT bit to what is requested and then set the OE bit. This
+ * prevents a glitch that can occur on the IO line.
+ */
+ regmap_read(priv->regmap, offset, &reg);
+ if (!(reg & TS4900_GPIO_OE)) {
+ if (value)
+ reg = TS4900_GPIO_OUT;
+ else
+ reg &= ~TS4900_GPIO_OUT;
+
+ regmap_write(priv->regmap, offset, reg);
+ }
+
+ if (value)
+ ret = regmap_write(priv->regmap, offset, TS4900_GPIO_OE |
+ TS4900_GPIO_OUT);
+ else
+ ret = regmap_write(priv->regmap, offset, TS4900_GPIO_OE);
+
+ return ret;
+}
+
+static int ts4900_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct ts4900_gpio_priv *priv = gpiochip_get_data(chip);
+ unsigned int reg;
+
+ regmap_read(priv->regmap, offset, &reg);
+
+ return !!(reg & priv->input_bit);
+}
+
+static void ts4900_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct ts4900_gpio_priv *priv = gpiochip_get_data(chip);
+
+ if (value)
+ regmap_update_bits(priv->regmap, offset, TS4900_GPIO_OUT,
+ TS4900_GPIO_OUT);
+ else
+ regmap_update_bits(priv->regmap, offset, TS4900_GPIO_OUT, 0);
+}
+
+static const struct regmap_config ts4900_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+};
+
+static const struct gpio_chip template_chip = {
+ .label = "ts4900-gpio",
+ .owner = THIS_MODULE,
+ .get_direction = ts4900_gpio_get_direction,
+ .direction_input = ts4900_gpio_direction_input,
+ .direction_output = ts4900_gpio_direction_output,
+ .get = ts4900_gpio_get,
+ .set = ts4900_gpio_set,
+ .base = -1,
+ .can_sleep = true,
+};
+
+static const struct of_device_id ts4900_gpio_of_match_table[] = {
+ {
+ .compatible = "technologic,ts4900-gpio",
+ .data = (void *)TS4900_GPIO_IN,
+ }, {
+ .compatible = "technologic,ts7970-gpio",
+ .data = (void *)TS7970_GPIO_IN,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ts4900_gpio_of_match_table);
+
+static int ts4900_gpio_probe(struct i2c_client *client)
+{
+ struct ts4900_gpio_priv *priv;
+ u32 ngpio;
+ int ret;
+
+ if (of_property_read_u32(client->dev.of_node, "ngpios", &ngpio))
+ ngpio = DEFAULT_PIN_NUMBER;
+
+ priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->gpio_chip = template_chip;
+ priv->gpio_chip.label = "ts4900-gpio";
+ priv->gpio_chip.ngpio = ngpio;
+ priv->gpio_chip.parent = &client->dev;
+ priv->input_bit = (uintptr_t)of_device_get_match_data(&client->dev);
+
+ priv->regmap = devm_regmap_init_i2c(client, &ts4900_regmap_config);
+ if (IS_ERR(priv->regmap)) {
+ ret = PTR_ERR(priv->regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = devm_gpiochip_add_data(&client->dev, &priv->gpio_chip, priv);
+ if (ret < 0) {
+ dev_err(&client->dev, "Unable to register gpiochip\n");
+ return ret;
+ }
+
+ i2c_set_clientdata(client, priv);
+
+ return 0;
+}
+
+static const struct i2c_device_id ts4900_gpio_id_table[] = {
+ { "ts4900-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, ts4900_gpio_id_table);
+
+static struct i2c_driver ts4900_gpio_driver = {
+ .driver = {
+ .name = "ts4900-gpio",
+ .of_match_table = ts4900_gpio_of_match_table,
+ },
+ .probe = ts4900_gpio_probe,
+ .id_table = ts4900_gpio_id_table,
+};
+module_i2c_driver(ts4900_gpio_driver);
+
+MODULE_AUTHOR("Technologic Systems");
+MODULE_DESCRIPTION("GPIO interface for Technologic Systems I2C-FPGA core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-ts5500.c b/drivers/gpio/gpio-ts5500.c
new file mode 100644
index 0000000000..8e03614c7a
--- /dev/null
+++ b/drivers/gpio/gpio-ts5500.c
@@ -0,0 +1,446 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Digital I/O driver for Technologic Systems TS-5500
+ *
+ * Copyright (c) 2012 Savoir-faire Linux Inc.
+ * Vivien Didelot <vivien.didelot@savoirfairelinux.com>
+ *
+ * Technologic Systems platforms have pin blocks, exposing several Digital
+ * Input/Output lines (DIO). This driver aims to support single pin blocks.
+ * In that sense, the support is not limited to the TS-5500 blocks.
+ * Actually, the following platforms have DIO support:
+ *
+ * TS-5500:
+ * Documentation: https://docs.embeddedts.com/TS-5500
+ * Blocks: DIO1, DIO2 and LCD port.
+ *
+ * TS-5600:
+ * Documentation: https://docs.embeddedts.com/TS-5600
+ * Blocks: LCD port (identical to TS-5500 LCD).
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+/* List of supported Technologic Systems platforms DIO blocks */
+enum ts5500_blocks { TS5500_DIO1, TS5500_DIO2, TS5500_LCD, TS5600_LCD };
+
+struct ts5500_priv {
+ const struct ts5500_dio *pinout;
+ struct gpio_chip gpio_chip;
+ spinlock_t lock;
+ bool strap;
+ u8 hwirq;
+};
+
+/*
+ * Hex 7D is used to control several blocks (e.g. DIO2 and LCD port).
+ * This flag ensures that the region has been requested by this driver.
+ */
+static bool hex7d_reserved;
+
+/*
+ * This structure is used to describe capabilities of DIO lines,
+ * such as available directions and connected interrupt (if any).
+ */
+struct ts5500_dio {
+ const u8 value_addr;
+ const u8 value_mask;
+ const u8 control_addr;
+ const u8 control_mask;
+ const bool no_input;
+ const bool no_output;
+ const u8 irq;
+};
+
+#define TS5500_DIO_IN_OUT(vaddr, vbit, caddr, cbit) \
+ { \
+ .value_addr = vaddr, \
+ .value_mask = BIT(vbit), \
+ .control_addr = caddr, \
+ .control_mask = BIT(cbit), \
+ }
+
+#define TS5500_DIO_IN(addr, bit) \
+ { \
+ .value_addr = addr, \
+ .value_mask = BIT(bit), \
+ .no_output = true, \
+ }
+
+#define TS5500_DIO_IN_IRQ(addr, bit, _irq) \
+ { \
+ .value_addr = addr, \
+ .value_mask = BIT(bit), \
+ .no_output = true, \
+ .irq = _irq, \
+ }
+
+#define TS5500_DIO_OUT(addr, bit) \
+ { \
+ .value_addr = addr, \
+ .value_mask = BIT(bit), \
+ .no_input = true, \
+ }
+
+/*
+ * Input/Output DIO lines are programmed in groups of 4. Their values are
+ * available through 4 consecutive bits in a value port, whereas the direction
+ * of these 4 lines is driven by only 1 bit in a control port.
+ */
+#define TS5500_DIO_GROUP(vaddr, vbitfrom, caddr, cbit) \
+ TS5500_DIO_IN_OUT(vaddr, vbitfrom + 0, caddr, cbit), \
+ TS5500_DIO_IN_OUT(vaddr, vbitfrom + 1, caddr, cbit), \
+ TS5500_DIO_IN_OUT(vaddr, vbitfrom + 2, caddr, cbit), \
+ TS5500_DIO_IN_OUT(vaddr, vbitfrom + 3, caddr, cbit)
+
+/*
+ * TS-5500 DIO1 block
+ *
+ * value control dir hw
+ * addr bit addr bit in out irq name pin offset
+ *
+ * 0x7b 0 0x7a 0 x x DIO1_0 1 0
+ * 0x7b 1 0x7a 0 x x DIO1_1 3 1
+ * 0x7b 2 0x7a 0 x x DIO1_2 5 2
+ * 0x7b 3 0x7a 0 x x DIO1_3 7 3
+ * 0x7b 4 0x7a 1 x x DIO1_4 9 4
+ * 0x7b 5 0x7a 1 x x DIO1_5 11 5
+ * 0x7b 6 0x7a 1 x x DIO1_6 13 6
+ * 0x7b 7 0x7a 1 x x DIO1_7 15 7
+ * 0x7c 0 0x7a 5 x x DIO1_8 4 8
+ * 0x7c 1 0x7a 5 x x DIO1_9 6 9
+ * 0x7c 2 0x7a 5 x x DIO1_10 8 10
+ * 0x7c 3 0x7a 5 x x DIO1_11 10 11
+ * 0x7c 4 x DIO1_12 12 12
+ * 0x7c 5 x 7 DIO1_13 14 13
+ */
+static const struct ts5500_dio ts5500_dio1[] = {
+ TS5500_DIO_GROUP(0x7b, 0, 0x7a, 0),
+ TS5500_DIO_GROUP(0x7b, 4, 0x7a, 1),
+ TS5500_DIO_GROUP(0x7c, 0, 0x7a, 5),
+ TS5500_DIO_IN(0x7c, 4),
+ TS5500_DIO_IN_IRQ(0x7c, 5, 7),
+};
+
+/*
+ * TS-5500 DIO2 block
+ *
+ * value control dir hw
+ * addr bit addr bit in out irq name pin offset
+ *
+ * 0x7e 0 0x7d 0 x x DIO2_0 1 0
+ * 0x7e 1 0x7d 0 x x DIO2_1 3 1
+ * 0x7e 2 0x7d 0 x x DIO2_2 5 2
+ * 0x7e 3 0x7d 0 x x DIO2_3 7 3
+ * 0x7e 4 0x7d 1 x x DIO2_4 9 4
+ * 0x7e 5 0x7d 1 x x DIO2_5 11 5
+ * 0x7e 6 0x7d 1 x x DIO2_6 13 6
+ * 0x7e 7 0x7d 1 x x DIO2_7 15 7
+ * 0x7f 0 0x7d 5 x x DIO2_8 4 8
+ * 0x7f 1 0x7d 5 x x DIO2_9 6 9
+ * 0x7f 2 0x7d 5 x x DIO2_10 8 10
+ * 0x7f 3 0x7d 5 x x DIO2_11 10 11
+ * 0x7f 4 x 6 DIO2_13 14 12
+ */
+static const struct ts5500_dio ts5500_dio2[] = {
+ TS5500_DIO_GROUP(0x7e, 0, 0x7d, 0),
+ TS5500_DIO_GROUP(0x7e, 4, 0x7d, 1),
+ TS5500_DIO_GROUP(0x7f, 0, 0x7d, 5),
+ TS5500_DIO_IN_IRQ(0x7f, 4, 6),
+};
+
+/*
+ * TS-5500 LCD port used as DIO block
+ * TS-5600 LCD port is identical
+ *
+ * value control dir hw
+ * addr bit addr bit in out irq name pin offset
+ *
+ * 0x72 0 0x7d 2 x x LCD_0 8 0
+ * 0x72 1 0x7d 2 x x LCD_1 7 1
+ * 0x72 2 0x7d 2 x x LCD_2 10 2
+ * 0x72 3 0x7d 2 x x LCD_3 9 3
+ * 0x72 4 0x7d 3 x x LCD_4 12 4
+ * 0x72 5 0x7d 3 x x LCD_5 11 5
+ * 0x72 6 0x7d 3 x x LCD_6 14 6
+ * 0x72 7 0x7d 3 x x LCD_7 13 7
+ * 0x73 0 x LCD_EN 5 8
+ * 0x73 6 x LCD_WR 6 9
+ * 0x73 7 x 1 LCD_RS 3 10
+ */
+static const struct ts5500_dio ts5500_lcd[] = {
+ TS5500_DIO_GROUP(0x72, 0, 0x7d, 2),
+ TS5500_DIO_GROUP(0x72, 4, 0x7d, 3),
+ TS5500_DIO_OUT(0x73, 0),
+ TS5500_DIO_IN(0x73, 6),
+ TS5500_DIO_IN_IRQ(0x73, 7, 1),
+};
+
+static inline void ts5500_set_mask(u8 mask, u8 addr)
+{
+ u8 val = inb(addr);
+ val |= mask;
+ outb(val, addr);
+}
+
+static inline void ts5500_clear_mask(u8 mask, u8 addr)
+{
+ u8 val = inb(addr);
+ val &= ~mask;
+ outb(val, addr);
+}
+
+static int ts5500_gpio_input(struct gpio_chip *chip, unsigned offset)
+{
+ struct ts5500_priv *priv = gpiochip_get_data(chip);
+ const struct ts5500_dio line = priv->pinout[offset];
+ unsigned long flags;
+
+ if (line.no_input)
+ return -ENXIO;
+
+ if (line.no_output)
+ return 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ ts5500_clear_mask(line.control_mask, line.control_addr);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static int ts5500_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct ts5500_priv *priv = gpiochip_get_data(chip);
+ const struct ts5500_dio line = priv->pinout[offset];
+
+ return !!(inb(line.value_addr) & line.value_mask);
+}
+
+static int ts5500_gpio_output(struct gpio_chip *chip, unsigned offset, int val)
+{
+ struct ts5500_priv *priv = gpiochip_get_data(chip);
+ const struct ts5500_dio line = priv->pinout[offset];
+ unsigned long flags;
+
+ if (line.no_output)
+ return -ENXIO;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (!line.no_input)
+ ts5500_set_mask(line.control_mask, line.control_addr);
+
+ if (val)
+ ts5500_set_mask(line.value_mask, line.value_addr);
+ else
+ ts5500_clear_mask(line.value_mask, line.value_addr);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static void ts5500_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
+{
+ struct ts5500_priv *priv = gpiochip_get_data(chip);
+ const struct ts5500_dio line = priv->pinout[offset];
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (val)
+ ts5500_set_mask(line.value_mask, line.value_addr);
+ else
+ ts5500_clear_mask(line.value_mask, line.value_addr);
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static int ts5500_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ struct ts5500_priv *priv = gpiochip_get_data(chip);
+ const struct ts5500_dio *block = priv->pinout;
+ const struct ts5500_dio line = block[offset];
+
+ /* Only one pin is connected to an interrupt */
+ if (line.irq)
+ return line.irq;
+
+ /* As this pin is input-only, we may strap it to another in/out pin */
+ if (priv->strap)
+ return priv->hwirq;
+
+ return -ENXIO;
+}
+
+static int ts5500_enable_irq(struct ts5500_priv *priv)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->hwirq == 7)
+ ts5500_set_mask(BIT(7), 0x7a); /* DIO1_13 on IRQ7 */
+ else if (priv->hwirq == 6)
+ ts5500_set_mask(BIT(7), 0x7d); /* DIO2_13 on IRQ6 */
+ else if (priv->hwirq == 1)
+ ts5500_set_mask(BIT(6), 0x7d); /* LCD_RS on IRQ1 */
+ else
+ ret = -EINVAL;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return ret;
+}
+
+static void ts5500_disable_irq(struct ts5500_priv *priv)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->hwirq == 7)
+ ts5500_clear_mask(BIT(7), 0x7a); /* DIO1_13 on IRQ7 */
+ else if (priv->hwirq == 6)
+ ts5500_clear_mask(BIT(7), 0x7d); /* DIO2_13 on IRQ6 */
+ else if (priv->hwirq == 1)
+ ts5500_clear_mask(BIT(6), 0x7d); /* LCD_RS on IRQ1 */
+ else
+ dev_err(priv->gpio_chip.parent, "invalid hwirq %d\n",
+ priv->hwirq);
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static int ts5500_dio_probe(struct platform_device *pdev)
+{
+ enum ts5500_blocks block = platform_get_device_id(pdev)->driver_data;
+ struct device *dev = &pdev->dev;
+ const char *name = dev_name(dev);
+ struct ts5500_priv *priv;
+ unsigned long flags;
+ int ret;
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+
+ priv = devm_kzalloc(dev, sizeof(struct ts5500_priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+ priv->hwirq = ret;
+ spin_lock_init(&priv->lock);
+
+ priv->gpio_chip.owner = THIS_MODULE;
+ priv->gpio_chip.label = name;
+ priv->gpio_chip.parent = dev;
+ priv->gpio_chip.direction_input = ts5500_gpio_input;
+ priv->gpio_chip.direction_output = ts5500_gpio_output;
+ priv->gpio_chip.get = ts5500_gpio_get;
+ priv->gpio_chip.set = ts5500_gpio_set;
+ priv->gpio_chip.to_irq = ts5500_gpio_to_irq;
+ priv->gpio_chip.base = -1;
+
+ switch (block) {
+ case TS5500_DIO1:
+ priv->pinout = ts5500_dio1;
+ priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_dio1);
+
+ if (!devm_request_region(dev, 0x7a, 3, name)) {
+ dev_err(dev, "failed to request %s ports\n", name);
+ return -EBUSY;
+ }
+ break;
+ case TS5500_DIO2:
+ priv->pinout = ts5500_dio2;
+ priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_dio2);
+
+ if (!devm_request_region(dev, 0x7e, 2, name)) {
+ dev_err(dev, "failed to request %s ports\n", name);
+ return -EBUSY;
+ }
+
+ if (hex7d_reserved)
+ break;
+
+ if (!devm_request_region(dev, 0x7d, 1, name)) {
+ dev_err(dev, "failed to request %s 7D\n", name);
+ return -EBUSY;
+ }
+
+ hex7d_reserved = true;
+ break;
+ case TS5500_LCD:
+ case TS5600_LCD:
+ priv->pinout = ts5500_lcd;
+ priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_lcd);
+
+ if (!devm_request_region(dev, 0x72, 2, name)) {
+ dev_err(dev, "failed to request %s ports\n", name);
+ return -EBUSY;
+ }
+
+ if (!hex7d_reserved) {
+ if (!devm_request_region(dev, 0x7d, 1, name)) {
+ dev_err(dev, "failed to request %s 7D\n", name);
+ return -EBUSY;
+ }
+
+ hex7d_reserved = true;
+ }
+
+ /* Ensure usage of LCD port as DIO */
+ spin_lock_irqsave(&priv->lock, flags);
+ ts5500_clear_mask(BIT(4), 0x7d);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ break;
+ }
+
+ ret = devm_gpiochip_add_data(dev, &priv->gpio_chip, priv);
+ if (ret) {
+ dev_err(dev, "failed to register the gpio chip\n");
+ return ret;
+ }
+
+ ret = ts5500_enable_irq(priv);
+ if (ret) {
+ dev_err(dev, "invalid interrupt %d\n", priv->hwirq);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ts5500_dio_remove(struct platform_device *pdev)
+{
+ struct ts5500_priv *priv = platform_get_drvdata(pdev);
+
+ ts5500_disable_irq(priv);
+
+ return 0;
+}
+
+static const struct platform_device_id ts5500_dio_ids[] = {
+ { "ts5500-dio1", TS5500_DIO1 },
+ { "ts5500-dio2", TS5500_DIO2 },
+ { "ts5500-dio-lcd", TS5500_LCD },
+ { "ts5600-dio-lcd", TS5600_LCD },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, ts5500_dio_ids);
+
+static struct platform_driver ts5500_dio_driver = {
+ .driver = {
+ .name = "ts5500-dio",
+ },
+ .probe = ts5500_dio_probe,
+ .remove = ts5500_dio_remove,
+ .id_table = ts5500_dio_ids,
+};
+
+module_platform_driver(ts5500_dio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Savoir-faire Linux Inc. <kernel@savoirfairelinux.com>");
+MODULE_DESCRIPTION("Technologic Systems TS-5500 Digital I/O driver");
diff --git a/drivers/gpio/gpio-twl4030.c b/drivers/gpio/gpio-twl4030.c
new file mode 100644
index 0000000000..bcd692229c
--- /dev/null
+++ b/drivers/gpio/gpio-twl4030.c
@@ -0,0 +1,639 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Access to GPIOs on TWL4030/TPS659x0 chips
+ *
+ * Copyright (C) 2006-2007 Texas Instruments, Inc.
+ * Copyright (C) 2006 MontaVista Software, Inc.
+ *
+ * Code re-arranged and cleaned up by:
+ * Syed Mohammed Khasim <x0khasim@ti.com>
+ *
+ * Initial Code:
+ * Andy Lowe / Nishanth Menon
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/irq.h>
+#include <linux/gpio/machine.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/irqdomain.h>
+
+#include <linux/mfd/twl.h>
+
+/*
+ * The GPIO "subchip" supports 18 GPIOs which can be configured as
+ * inputs or outputs, with pullups or pulldowns on each pin. Each
+ * GPIO can trigger interrupts on either or both edges.
+ *
+ * GPIO interrupts can be fed to either of two IRQ lines; this is
+ * intended to support multiple hosts.
+ *
+ * There are also two LED pins used sometimes as output-only GPIOs.
+ */
+
+/* genirq interfaces are not available to modules */
+#ifdef MODULE
+#define is_module() true
+#else
+#define is_module() false
+#endif
+
+/* GPIO_CTRL Fields */
+#define MASK_GPIO_CTRL_GPIO0CD1 BIT(0)
+#define MASK_GPIO_CTRL_GPIO1CD2 BIT(1)
+#define MASK_GPIO_CTRL_GPIO_ON BIT(2)
+
+/* Mask for GPIO registers when aggregated into a 32-bit integer */
+#define GPIO_32_MASK 0x0003ffff
+
+struct gpio_twl4030_priv {
+ struct gpio_chip gpio_chip;
+ struct mutex mutex;
+ int irq_base;
+
+ /* Bitfields for state caching */
+ unsigned int usage_count;
+ unsigned int direction;
+ unsigned int out_state;
+};
+
+/*----------------------------------------------------------------------*/
+
+/*
+ * To configure TWL4030 GPIO module registers
+ */
+static inline int gpio_twl4030_write(u8 address, u8 data)
+{
+ return twl_i2c_write_u8(TWL4030_MODULE_GPIO, data, address);
+}
+
+/*----------------------------------------------------------------------*/
+
+/*
+ * LED register offsets from TWL_MODULE_LED base
+ * PWMs A and B are dedicated to LEDs A and B, respectively.
+ */
+
+#define TWL4030_LED_LEDEN_REG 0x00
+#define TWL4030_PWMAON_REG 0x01
+#define TWL4030_PWMAOFF_REG 0x02
+#define TWL4030_PWMBON_REG 0x03
+#define TWL4030_PWMBOFF_REG 0x04
+
+/* LEDEN bits */
+#define LEDEN_LEDAON BIT(0)
+#define LEDEN_LEDBON BIT(1)
+#define LEDEN_LEDAEXT BIT(2)
+#define LEDEN_LEDBEXT BIT(3)
+#define LEDEN_LEDAPWM BIT(4)
+#define LEDEN_LEDBPWM BIT(5)
+#define LEDEN_PWM_LENGTHA BIT(6)
+#define LEDEN_PWM_LENGTHB BIT(7)
+
+#define PWMxON_LENGTH BIT(7)
+
+/*----------------------------------------------------------------------*/
+
+/*
+ * To read a TWL4030 GPIO module register
+ */
+static inline int gpio_twl4030_read(u8 address)
+{
+ u8 data;
+ int ret = 0;
+
+ ret = twl_i2c_read_u8(TWL4030_MODULE_GPIO, &data, address);
+ return (ret < 0) ? ret : data;
+}
+
+/*----------------------------------------------------------------------*/
+
+static u8 cached_leden;
+
+/* The LED lines are open drain outputs ... a FET pulls to GND, so an
+ * external pullup is needed. We could also expose the integrated PWM
+ * as a LED brightness control; we initialize it as "always on".
+ */
+static void twl4030_led_set_value(int led, int value)
+{
+ u8 mask = LEDEN_LEDAON | LEDEN_LEDAPWM;
+
+ if (led)
+ mask <<= 1;
+
+ if (value)
+ cached_leden &= ~mask;
+ else
+ cached_leden |= mask;
+
+ WARN_ON_ONCE(twl_i2c_write_u8(TWL4030_MODULE_LED, cached_leden,
+ TWL4030_LED_LEDEN_REG));
+}
+
+static int twl4030_set_gpio_direction(int gpio, int is_input)
+{
+ u8 d_bnk = gpio >> 3;
+ u8 d_msk = BIT(gpio & 0x7);
+ u8 reg = 0;
+ u8 base = REG_GPIODATADIR1 + d_bnk;
+ int ret = 0;
+
+ ret = gpio_twl4030_read(base);
+ if (ret >= 0) {
+ if (is_input)
+ reg = ret & ~d_msk;
+ else
+ reg = ret | d_msk;
+
+ ret = gpio_twl4030_write(base, reg);
+ }
+ return ret;
+}
+
+static int twl4030_get_gpio_direction(int gpio)
+{
+ u8 d_bnk = gpio >> 3;
+ u8 d_msk = BIT(gpio & 0x7);
+ u8 base = REG_GPIODATADIR1 + d_bnk;
+ int ret = 0;
+
+ ret = gpio_twl4030_read(base);
+ if (ret < 0)
+ return ret;
+
+ if (ret & d_msk)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int twl4030_set_gpio_dataout(int gpio, int enable)
+{
+ u8 d_bnk = gpio >> 3;
+ u8 d_msk = BIT(gpio & 0x7);
+ u8 base = 0;
+
+ if (enable)
+ base = REG_SETGPIODATAOUT1 + d_bnk;
+ else
+ base = REG_CLEARGPIODATAOUT1 + d_bnk;
+
+ return gpio_twl4030_write(base, d_msk);
+}
+
+static int twl4030_get_gpio_datain(int gpio)
+{
+ u8 d_bnk = gpio >> 3;
+ u8 d_off = gpio & 0x7;
+ u8 base = 0;
+ int ret = 0;
+
+ base = REG_GPIODATAIN1 + d_bnk;
+ ret = gpio_twl4030_read(base);
+ if (ret > 0)
+ ret = (ret >> d_off) & 0x1;
+
+ return ret;
+}
+
+/*----------------------------------------------------------------------*/
+
+static int twl_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_twl4030_priv *priv = gpiochip_get_data(chip);
+ int status = 0;
+
+ mutex_lock(&priv->mutex);
+
+ /* Support the two LED outputs as output-only GPIOs. */
+ if (offset >= TWL4030_GPIO_MAX) {
+ u8 ledclr_mask = LEDEN_LEDAON | LEDEN_LEDAEXT
+ | LEDEN_LEDAPWM | LEDEN_PWM_LENGTHA;
+ u8 reg = TWL4030_PWMAON_REG;
+
+ offset -= TWL4030_GPIO_MAX;
+ if (offset) {
+ ledclr_mask <<= 1;
+ reg = TWL4030_PWMBON_REG;
+ }
+
+ /* initialize PWM to always-drive */
+ /* Configure PWM OFF register first */
+ status = twl_i2c_write_u8(TWL4030_MODULE_LED, 0x7f, reg + 1);
+ if (status < 0)
+ goto done;
+
+ /* Followed by PWM ON register */
+ status = twl_i2c_write_u8(TWL4030_MODULE_LED, 0x7f, reg);
+ if (status < 0)
+ goto done;
+
+ /* init LED to not-driven (high) */
+ status = twl_i2c_read_u8(TWL4030_MODULE_LED, &cached_leden,
+ TWL4030_LED_LEDEN_REG);
+ if (status < 0)
+ goto done;
+ cached_leden &= ~ledclr_mask;
+ status = twl_i2c_write_u8(TWL4030_MODULE_LED, cached_leden,
+ TWL4030_LED_LEDEN_REG);
+ if (status < 0)
+ goto done;
+
+ status = 0;
+ goto done;
+ }
+
+ /* on first use, turn GPIO module "on" */
+ if (!priv->usage_count) {
+ struct twl4030_gpio_platform_data *pdata;
+ u8 value = MASK_GPIO_CTRL_GPIO_ON;
+
+ /* optionally have the first two GPIOs switch vMMC1
+ * and vMMC2 power supplies based on card presence.
+ */
+ pdata = dev_get_platdata(chip->parent);
+ if (pdata)
+ value |= pdata->mmc_cd & 0x03;
+
+ status = gpio_twl4030_write(REG_GPIO_CTRL, value);
+ }
+
+done:
+ if (!status)
+ priv->usage_count |= BIT(offset);
+
+ mutex_unlock(&priv->mutex);
+ return status;
+}
+
+static void twl_free(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_twl4030_priv *priv = gpiochip_get_data(chip);
+
+ mutex_lock(&priv->mutex);
+ if (offset >= TWL4030_GPIO_MAX) {
+ twl4030_led_set_value(offset - TWL4030_GPIO_MAX, 1);
+ goto out;
+ }
+
+ priv->usage_count &= ~BIT(offset);
+
+ /* on last use, switch off GPIO module */
+ if (!priv->usage_count)
+ gpio_twl4030_write(REG_GPIO_CTRL, 0x0);
+
+out:
+ mutex_unlock(&priv->mutex);
+}
+
+static int twl_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_twl4030_priv *priv = gpiochip_get_data(chip);
+ int ret;
+
+ mutex_lock(&priv->mutex);
+ if (offset < TWL4030_GPIO_MAX)
+ ret = twl4030_set_gpio_direction(offset, 1);
+ else
+ ret = -EINVAL; /* LED outputs can't be set as input */
+
+ if (!ret)
+ priv->direction &= ~BIT(offset);
+
+ mutex_unlock(&priv->mutex);
+
+ return ret;
+}
+
+static int twl_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_twl4030_priv *priv = gpiochip_get_data(chip);
+ int ret;
+ int status = 0;
+
+ mutex_lock(&priv->mutex);
+ if (!(priv->usage_count & BIT(offset))) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ if (priv->direction & BIT(offset))
+ status = priv->out_state & BIT(offset);
+ else
+ status = twl4030_get_gpio_datain(offset);
+
+ ret = (status < 0) ? status : !!status;
+out:
+ mutex_unlock(&priv->mutex);
+ return ret;
+}
+
+static void twl_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct gpio_twl4030_priv *priv = gpiochip_get_data(chip);
+
+ mutex_lock(&priv->mutex);
+ if (offset < TWL4030_GPIO_MAX)
+ twl4030_set_gpio_dataout(offset, value);
+ else
+ twl4030_led_set_value(offset - TWL4030_GPIO_MAX, value);
+
+ if (value)
+ priv->out_state |= BIT(offset);
+ else
+ priv->out_state &= ~BIT(offset);
+
+ mutex_unlock(&priv->mutex);
+}
+
+static int twl_direction_out(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct gpio_twl4030_priv *priv = gpiochip_get_data(chip);
+ int ret = 0;
+
+ mutex_lock(&priv->mutex);
+ if (offset < TWL4030_GPIO_MAX) {
+ ret = twl4030_set_gpio_direction(offset, 0);
+ if (ret) {
+ mutex_unlock(&priv->mutex);
+ return ret;
+ }
+ }
+
+ /*
+ * LED gpios i.e. offset >= TWL4030_GPIO_MAX are always output
+ */
+
+ priv->direction |= BIT(offset);
+ mutex_unlock(&priv->mutex);
+
+ twl_set(chip, offset, value);
+
+ return ret;
+}
+
+static int twl_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_twl4030_priv *priv = gpiochip_get_data(chip);
+ /*
+ * Default GPIO_LINE_DIRECTION_OUT
+ * LED GPIOs >= TWL4030_GPIO_MAX are always output
+ */
+ int ret = GPIO_LINE_DIRECTION_OUT;
+
+ mutex_lock(&priv->mutex);
+ if (offset < TWL4030_GPIO_MAX) {
+ ret = twl4030_get_gpio_direction(offset);
+ if (ret) {
+ mutex_unlock(&priv->mutex);
+ return ret;
+ }
+ }
+ mutex_unlock(&priv->mutex);
+
+ return ret;
+}
+
+static int twl_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_twl4030_priv *priv = gpiochip_get_data(chip);
+
+ return (priv->irq_base && (offset < TWL4030_GPIO_MAX))
+ ? (priv->irq_base + offset)
+ : -EINVAL;
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "twl4030",
+ .owner = THIS_MODULE,
+ .request = twl_request,
+ .free = twl_free,
+ .direction_input = twl_direction_in,
+ .direction_output = twl_direction_out,
+ .get_direction = twl_get_direction,
+ .get = twl_get,
+ .set = twl_set,
+ .to_irq = twl_to_irq,
+ .can_sleep = true,
+};
+
+/*----------------------------------------------------------------------*/
+
+static int gpio_twl4030_pulls(u32 ups, u32 downs)
+{
+ u8 message[5];
+ unsigned i, gpio_bit;
+
+ /* For most pins, a pulldown was enabled by default.
+ * We should have data that's specific to this board.
+ */
+ for (gpio_bit = 1, i = 0; i < 5; i++) {
+ u8 bit_mask;
+ unsigned j;
+
+ for (bit_mask = 0, j = 0; j < 8; j += 2, gpio_bit <<= 1) {
+ if (ups & gpio_bit)
+ bit_mask |= 1 << (j + 1);
+ else if (downs & gpio_bit)
+ bit_mask |= 1 << (j + 0);
+ }
+ message[i] = bit_mask;
+ }
+
+ return twl_i2c_write(TWL4030_MODULE_GPIO, message,
+ REG_GPIOPUPDCTR1, 5);
+}
+
+static int gpio_twl4030_debounce(u32 debounce, u8 mmc_cd)
+{
+ u8 message[3];
+
+ /* 30 msec of debouncing is always used for MMC card detect,
+ * and is optional for everything else.
+ */
+ message[0] = (debounce & 0xff) | (mmc_cd & 0x03);
+ debounce >>= 8;
+ message[1] = (debounce & 0xff);
+ debounce >>= 8;
+ message[2] = (debounce & 0x03);
+
+ return twl_i2c_write(TWL4030_MODULE_GPIO, message,
+ REG_GPIO_DEBEN1, 3);
+}
+
+static struct twl4030_gpio_platform_data *of_gpio_twl4030(struct device *dev)
+{
+ struct twl4030_gpio_platform_data *omap_twl_info;
+
+ omap_twl_info = devm_kzalloc(dev, sizeof(*omap_twl_info), GFP_KERNEL);
+ if (!omap_twl_info)
+ return NULL;
+
+ omap_twl_info->use_leds = of_property_read_bool(dev->of_node,
+ "ti,use-leds");
+
+ of_property_read_u32(dev->of_node, "ti,debounce",
+ &omap_twl_info->debounce);
+ of_property_read_u32(dev->of_node, "ti,mmc-cd",
+ (u32 *)&omap_twl_info->mmc_cd);
+ of_property_read_u32(dev->of_node, "ti,pullups",
+ &omap_twl_info->pullups);
+ of_property_read_u32(dev->of_node, "ti,pulldowns",
+ &omap_twl_info->pulldowns);
+
+ return omap_twl_info;
+}
+
+/* Called from the registered devm action */
+static void gpio_twl4030_power_off_action(void *data)
+{
+ struct gpio_desc *d = data;
+
+ gpiod_unexport(d);
+ gpiochip_free_own_desc(d);
+}
+
+static int gpio_twl4030_probe(struct platform_device *pdev)
+{
+ struct twl4030_gpio_platform_data *pdata;
+ struct device_node *node = pdev->dev.of_node;
+ struct gpio_twl4030_priv *priv;
+ int ret, irq_base;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct gpio_twl4030_priv),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* maybe setup IRQs */
+ if (is_module()) {
+ dev_err(&pdev->dev, "can't dispatch IRQs from modules\n");
+ goto no_irqs;
+ }
+
+ irq_base = devm_irq_alloc_descs(&pdev->dev, -1,
+ 0, TWL4030_GPIO_MAX, 0);
+ if (irq_base < 0) {
+ dev_err(&pdev->dev, "Failed to alloc irq_descs\n");
+ return irq_base;
+ }
+
+ irq_domain_add_legacy(node, TWL4030_GPIO_MAX, irq_base, 0,
+ &irq_domain_simple_ops, NULL);
+
+ ret = twl4030_sih_setup(&pdev->dev, TWL4030_MODULE_GPIO, irq_base);
+ if (ret < 0)
+ return ret;
+
+ priv->irq_base = irq_base;
+
+no_irqs:
+ priv->gpio_chip = template_chip;
+ priv->gpio_chip.base = -1;
+ priv->gpio_chip.ngpio = TWL4030_GPIO_MAX;
+ priv->gpio_chip.parent = &pdev->dev;
+
+ mutex_init(&priv->mutex);
+
+ pdata = of_gpio_twl4030(&pdev->dev);
+ if (pdata == NULL) {
+ dev_err(&pdev->dev, "Platform data is missing\n");
+ return -ENXIO;
+ }
+
+ /*
+ * NOTE: boards may waste power if they don't set pullups
+ * and pulldowns correctly ... default for non-ULPI pins is
+ * pulldown, and some other pins may have external pullups
+ * or pulldowns. Careful!
+ */
+ ret = gpio_twl4030_pulls(pdata->pullups, pdata->pulldowns);
+ if (ret)
+ dev_dbg(&pdev->dev, "pullups %.05x %.05x --> %d\n",
+ pdata->pullups, pdata->pulldowns, ret);
+
+ ret = gpio_twl4030_debounce(pdata->debounce, pdata->mmc_cd);
+ if (ret)
+ dev_dbg(&pdev->dev, "debounce %.03x %.01x --> %d\n",
+ pdata->debounce, pdata->mmc_cd, ret);
+
+ /*
+ * NOTE: we assume VIBRA_CTL.VIBRA_EN, in MODULE_AUDIO_VOICE,
+ * is (still) clear if use_leds is set.
+ */
+ if (pdata->use_leds)
+ priv->gpio_chip.ngpio += 2;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &priv->gpio_chip, priv);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not register gpiochip, %d\n", ret);
+ priv->gpio_chip.ngpio = 0;
+ return ret;
+ }
+
+ /*
+ * Special quirk for the OMAP3 to hog and export a WLAN power
+ * GPIO.
+ */
+ if (IS_ENABLED(CONFIG_ARCH_OMAP3) &&
+ of_machine_is_compatible("compulab,omap3-sbc-t3730")) {
+ struct gpio_desc *d;
+
+ d = gpiochip_request_own_desc(&priv->gpio_chip,
+ 2, "wlan pwr",
+ GPIO_ACTIVE_HIGH,
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(d))
+ return dev_err_probe(&pdev->dev, PTR_ERR(d),
+ "unable to hog wlan pwr GPIO\n");
+
+ gpiod_export(d, 0);
+
+ ret = devm_add_action_or_reset(&pdev->dev, gpio_twl4030_power_off_action, d);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to install power off handler\n");
+
+ }
+
+ return 0;
+}
+
+static const struct of_device_id twl_gpio_match[] = {
+ { .compatible = "ti,twl4030-gpio", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, twl_gpio_match);
+
+/* Note: this hardware lives inside an I2C-based multi-function device. */
+MODULE_ALIAS("platform:twl4030_gpio");
+
+static struct platform_driver gpio_twl4030_driver = {
+ .driver = {
+ .name = "twl4030_gpio",
+ .of_match_table = twl_gpio_match,
+ },
+ .probe = gpio_twl4030_probe,
+};
+
+static int __init gpio_twl4030_init(void)
+{
+ return platform_driver_register(&gpio_twl4030_driver);
+}
+subsys_initcall(gpio_twl4030_init);
+
+static void __exit gpio_twl4030_exit(void)
+{
+ platform_driver_unregister(&gpio_twl4030_driver);
+}
+module_exit(gpio_twl4030_exit);
+
+MODULE_AUTHOR("Texas Instruments, Inc.");
+MODULE_DESCRIPTION("GPIO interface for TWL4030");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-twl6040.c b/drivers/gpio/gpio-twl6040.c
new file mode 100644
index 0000000000..6c3fbf382d
--- /dev/null
+++ b/drivers/gpio/gpio-twl6040.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Access to GPOs on TWL6040 chip
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Authors:
+ * Sergio Aguirre <saaguirre@ti.com>
+ * Peter Ujfalusi <peter.ujfalusi@ti.com>
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kthread.h>
+#include <linux/irq.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/bitops.h>
+#include <linux/of.h>
+
+#include <linux/mfd/twl6040.h>
+
+static int twl6040gpo_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct twl6040 *twl6040 = dev_get_drvdata(chip->parent->parent);
+ int ret = 0;
+
+ ret = twl6040_reg_read(twl6040, TWL6040_REG_GPOCTL);
+ if (ret < 0)
+ return ret;
+
+ return !!(ret & BIT(offset));
+}
+
+static int twl6040gpo_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int twl6040gpo_direction_out(struct gpio_chip *chip, unsigned offset,
+ int value)
+{
+ /* This only drives GPOs, and can't change direction */
+ return 0;
+}
+
+static void twl6040gpo_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct twl6040 *twl6040 = dev_get_drvdata(chip->parent->parent);
+ int ret;
+ u8 gpoctl;
+
+ ret = twl6040_reg_read(twl6040, TWL6040_REG_GPOCTL);
+ if (ret < 0)
+ return;
+
+ if (value)
+ gpoctl = ret | BIT(offset);
+ else
+ gpoctl = ret & ~BIT(offset);
+
+ twl6040_reg_write(twl6040, TWL6040_REG_GPOCTL, gpoctl);
+}
+
+static struct gpio_chip twl6040gpo_chip = {
+ .label = "twl6040",
+ .owner = THIS_MODULE,
+ .get = twl6040gpo_get,
+ .direction_output = twl6040gpo_direction_out,
+ .get_direction = twl6040gpo_get_direction,
+ .set = twl6040gpo_set,
+ .can_sleep = true,
+};
+
+/*----------------------------------------------------------------------*/
+
+static int gpo_twl6040_probe(struct platform_device *pdev)
+{
+ struct device *twl6040_core_dev = pdev->dev.parent;
+ struct twl6040 *twl6040 = dev_get_drvdata(twl6040_core_dev);
+ int ret;
+
+ device_set_node(&pdev->dev, dev_fwnode(pdev->dev.parent));
+
+ twl6040gpo_chip.base = -1;
+
+ if (twl6040_get_revid(twl6040) < TWL6041_REV_ES2_0)
+ twl6040gpo_chip.ngpio = 3; /* twl6040 have 3 GPO */
+ else
+ twl6040gpo_chip.ngpio = 1; /* twl6041 have 1 GPO */
+
+ twl6040gpo_chip.parent = &pdev->dev;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &twl6040gpo_chip, NULL);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not register gpiochip, %d\n", ret);
+ twl6040gpo_chip.ngpio = 0;
+ }
+
+ return ret;
+}
+
+/* Note: this hardware lives inside an I2C-based multi-function device. */
+MODULE_ALIAS("platform:twl6040-gpo");
+
+static struct platform_driver gpo_twl6040_driver = {
+ .driver = {
+ .name = "twl6040-gpo",
+ },
+ .probe = gpo_twl6040_probe,
+};
+
+module_platform_driver(gpo_twl6040_driver);
+
+MODULE_AUTHOR("Texas Instruments, Inc.");
+MODULE_DESCRIPTION("GPO interface for TWL6040");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-uniphier.c b/drivers/gpio/gpio-uniphier.c
new file mode 100644
index 0000000000..9725b7aa18
--- /dev/null
+++ b/drivers/gpio/gpio-uniphier.c
@@ -0,0 +1,496 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2017 Socionext Inc.
+// Author: Masahiro Yamada <yamada.masahiro@socionext.com>
+
+#include <linux/bits.h>
+#include <linux/gpio/driver.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <dt-bindings/gpio/uniphier-gpio.h>
+
+#define UNIPHIER_GPIO_IRQ_MAX_NUM 24
+
+#define UNIPHIER_GPIO_PORT_DATA 0x0 /* data */
+#define UNIPHIER_GPIO_PORT_DIR 0x4 /* direction (1:in, 0:out) */
+#define UNIPHIER_GPIO_IRQ_EN 0x90 /* irq enable */
+#define UNIPHIER_GPIO_IRQ_MODE 0x94 /* irq mode (1: both edge) */
+#define UNIPHIER_GPIO_IRQ_FLT_EN 0x98 /* noise filter enable */
+#define UNIPHIER_GPIO_IRQ_FLT_CYC 0x9c /* noise filter clock cycle */
+
+struct uniphier_gpio_priv {
+ struct gpio_chip chip;
+ struct irq_chip irq_chip;
+ struct irq_domain *domain;
+ void __iomem *regs;
+ spinlock_t lock;
+ u32 saved_vals[];
+};
+
+static unsigned int uniphier_gpio_bank_to_reg(unsigned int bank)
+{
+ unsigned int reg;
+
+ reg = (bank + 1) * 8;
+
+ /*
+ * Unfortunately, the GPIO port registers are not contiguous because
+ * offset 0x90-0x9f is used for IRQ. Add 0x10 when crossing the region.
+ */
+ if (reg >= UNIPHIER_GPIO_IRQ_EN)
+ reg += 0x10;
+
+ return reg;
+}
+
+static void uniphier_gpio_get_bank_and_mask(unsigned int offset,
+ unsigned int *bank, u32 *mask)
+{
+ *bank = offset / UNIPHIER_GPIO_LINES_PER_BANK;
+ *mask = BIT(offset % UNIPHIER_GPIO_LINES_PER_BANK);
+}
+
+static void uniphier_gpio_reg_update(struct uniphier_gpio_priv *priv,
+ unsigned int reg, u32 mask, u32 val)
+{
+ unsigned long flags;
+ u32 tmp;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ tmp = readl(priv->regs + reg);
+ tmp &= ~mask;
+ tmp |= mask & val;
+ writel(tmp, priv->regs + reg);
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static void uniphier_gpio_bank_write(struct gpio_chip *chip, unsigned int bank,
+ unsigned int reg, u32 mask, u32 val)
+{
+ struct uniphier_gpio_priv *priv = gpiochip_get_data(chip);
+
+ if (!mask)
+ return;
+
+ uniphier_gpio_reg_update(priv, uniphier_gpio_bank_to_reg(bank) + reg,
+ mask, val);
+}
+
+static void uniphier_gpio_offset_write(struct gpio_chip *chip,
+ unsigned int offset, unsigned int reg,
+ int val)
+{
+ unsigned int bank;
+ u32 mask;
+
+ uniphier_gpio_get_bank_and_mask(offset, &bank, &mask);
+
+ uniphier_gpio_bank_write(chip, bank, reg, mask, val ? mask : 0);
+}
+
+static int uniphier_gpio_offset_read(struct gpio_chip *chip,
+ unsigned int offset, unsigned int reg)
+{
+ struct uniphier_gpio_priv *priv = gpiochip_get_data(chip);
+ unsigned int bank, reg_offset;
+ u32 mask;
+
+ uniphier_gpio_get_bank_and_mask(offset, &bank, &mask);
+ reg_offset = uniphier_gpio_bank_to_reg(bank) + reg;
+
+ return !!(readl(priv->regs + reg_offset) & mask);
+}
+
+static int uniphier_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ if (uniphier_gpio_offset_read(chip, offset, UNIPHIER_GPIO_PORT_DIR))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int uniphier_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ uniphier_gpio_offset_write(chip, offset, UNIPHIER_GPIO_PORT_DIR, 1);
+
+ return 0;
+}
+
+static int uniphier_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int val)
+{
+ uniphier_gpio_offset_write(chip, offset, UNIPHIER_GPIO_PORT_DATA, val);
+ uniphier_gpio_offset_write(chip, offset, UNIPHIER_GPIO_PORT_DIR, 0);
+
+ return 0;
+}
+
+static int uniphier_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ return uniphier_gpio_offset_read(chip, offset, UNIPHIER_GPIO_PORT_DATA);
+}
+
+static void uniphier_gpio_set(struct gpio_chip *chip,
+ unsigned int offset, int val)
+{
+ uniphier_gpio_offset_write(chip, offset, UNIPHIER_GPIO_PORT_DATA, val);
+}
+
+static void uniphier_gpio_set_multiple(struct gpio_chip *chip,
+ unsigned long *mask, unsigned long *bits)
+{
+ unsigned long i, bank, bank_mask, bank_bits;
+
+ for_each_set_clump8(i, bank_mask, mask, chip->ngpio) {
+ bank = i / UNIPHIER_GPIO_LINES_PER_BANK;
+ bank_bits = bitmap_get_value8(bits, i);
+
+ uniphier_gpio_bank_write(chip, bank, UNIPHIER_GPIO_PORT_DATA,
+ bank_mask, bank_bits);
+ }
+}
+
+static int uniphier_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
+{
+ struct irq_fwspec fwspec;
+
+ if (offset < UNIPHIER_GPIO_IRQ_OFFSET)
+ return -ENXIO;
+
+ fwspec.fwnode = of_node_to_fwnode(chip->parent->of_node);
+ fwspec.param_count = 2;
+ fwspec.param[0] = offset - UNIPHIER_GPIO_IRQ_OFFSET;
+ /*
+ * IRQ_TYPE_NONE is rejected by the parent irq domain. Set LEVEL_HIGH
+ * temporarily. Anyway, ->irq_set_type() will override it later.
+ */
+ fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH;
+
+ return irq_create_fwspec_mapping(&fwspec);
+}
+
+static void uniphier_gpio_irq_mask(struct irq_data *data)
+{
+ struct uniphier_gpio_priv *priv = irq_data_get_irq_chip_data(data);
+ u32 mask = BIT(irqd_to_hwirq(data));
+
+ uniphier_gpio_reg_update(priv, UNIPHIER_GPIO_IRQ_EN, mask, 0);
+
+ irq_chip_mask_parent(data);
+}
+
+static void uniphier_gpio_irq_unmask(struct irq_data *data)
+{
+ struct uniphier_gpio_priv *priv = irq_data_get_irq_chip_data(data);
+ u32 mask = BIT(irqd_to_hwirq(data));
+
+ uniphier_gpio_reg_update(priv, UNIPHIER_GPIO_IRQ_EN, mask, mask);
+
+ irq_chip_unmask_parent(data);
+}
+
+static int uniphier_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ struct uniphier_gpio_priv *priv = irq_data_get_irq_chip_data(data);
+ u32 mask = BIT(irqd_to_hwirq(data));
+ u32 val = 0;
+
+ if (type == IRQ_TYPE_EDGE_BOTH) {
+ val = mask;
+ type = IRQ_TYPE_EDGE_FALLING;
+ }
+
+ uniphier_gpio_reg_update(priv, UNIPHIER_GPIO_IRQ_MODE, mask, val);
+ /* To enable both edge detection, the noise filter must be enabled. */
+ uniphier_gpio_reg_update(priv, UNIPHIER_GPIO_IRQ_FLT_EN, mask, val);
+
+ return irq_chip_set_type_parent(data, type);
+}
+
+static int uniphier_gpio_irq_get_parent_hwirq(struct uniphier_gpio_priv *priv,
+ unsigned int hwirq)
+{
+ struct device_node *np = priv->chip.parent->of_node;
+ const __be32 *range;
+ u32 base, parent_base, size;
+ int len;
+
+ range = of_get_property(np, "socionext,interrupt-ranges", &len);
+ if (!range)
+ return -EINVAL;
+
+ len /= sizeof(*range);
+
+ for (; len >= 3; len -= 3) {
+ base = be32_to_cpu(*range++);
+ parent_base = be32_to_cpu(*range++);
+ size = be32_to_cpu(*range++);
+
+ if (base <= hwirq && hwirq < base + size)
+ return hwirq - base + parent_base;
+ }
+
+ return -ENOENT;
+}
+
+static int uniphier_gpio_irq_domain_translate(struct irq_domain *domain,
+ struct irq_fwspec *fwspec,
+ unsigned long *out_hwirq,
+ unsigned int *out_type)
+{
+ if (WARN_ON(fwspec->param_count < 2))
+ return -EINVAL;
+
+ *out_hwirq = fwspec->param[0];
+ *out_type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
+
+ return 0;
+}
+
+static int uniphier_gpio_irq_domain_alloc(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct uniphier_gpio_priv *priv = domain->host_data;
+ struct irq_fwspec parent_fwspec;
+ irq_hw_number_t hwirq;
+ unsigned int type;
+ int ret;
+
+ if (WARN_ON(nr_irqs != 1))
+ return -EINVAL;
+
+ ret = uniphier_gpio_irq_domain_translate(domain, arg, &hwirq, &type);
+ if (ret)
+ return ret;
+
+ ret = uniphier_gpio_irq_get_parent_hwirq(priv, hwirq);
+ if (ret < 0)
+ return ret;
+
+ /* parent is UniPhier AIDET */
+ parent_fwspec.fwnode = domain->parent->fwnode;
+ parent_fwspec.param_count = 2;
+ parent_fwspec.param[0] = ret;
+ parent_fwspec.param[1] = (type == IRQ_TYPE_EDGE_BOTH) ?
+ IRQ_TYPE_EDGE_FALLING : type;
+
+ ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
+ &priv->irq_chip, priv);
+ if (ret)
+ return ret;
+
+ return irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec);
+}
+
+static int uniphier_gpio_irq_domain_activate(struct irq_domain *domain,
+ struct irq_data *data, bool early)
+{
+ struct uniphier_gpio_priv *priv = domain->host_data;
+ struct gpio_chip *chip = &priv->chip;
+
+ return gpiochip_lock_as_irq(chip,
+ irqd_to_hwirq(data) + UNIPHIER_GPIO_IRQ_OFFSET);
+}
+
+static void uniphier_gpio_irq_domain_deactivate(struct irq_domain *domain,
+ struct irq_data *data)
+{
+ struct uniphier_gpio_priv *priv = domain->host_data;
+ struct gpio_chip *chip = &priv->chip;
+
+ gpiochip_unlock_as_irq(chip,
+ irqd_to_hwirq(data) + UNIPHIER_GPIO_IRQ_OFFSET);
+}
+
+static const struct irq_domain_ops uniphier_gpio_irq_domain_ops = {
+ .alloc = uniphier_gpio_irq_domain_alloc,
+ .free = irq_domain_free_irqs_common,
+ .activate = uniphier_gpio_irq_domain_activate,
+ .deactivate = uniphier_gpio_irq_domain_deactivate,
+ .translate = uniphier_gpio_irq_domain_translate,
+};
+
+static void uniphier_gpio_hw_init(struct uniphier_gpio_priv *priv)
+{
+ /*
+ * Due to the hardware design, the noise filter must be enabled to
+ * detect both edge interrupts. This filter is intended to remove the
+ * noise from the irq lines. It does not work for GPIO input, so GPIO
+ * debounce is not supported. Unfortunately, the filter period is
+ * shared among all irq lines. Just choose a sensible period here.
+ */
+ writel(0xff, priv->regs + UNIPHIER_GPIO_IRQ_FLT_CYC);
+}
+
+static unsigned int uniphier_gpio_get_nbanks(unsigned int ngpio)
+{
+ return DIV_ROUND_UP(ngpio, UNIPHIER_GPIO_LINES_PER_BANK);
+}
+
+static int uniphier_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *parent_np;
+ struct irq_domain *parent_domain;
+ struct uniphier_gpio_priv *priv;
+ struct gpio_chip *chip;
+ struct irq_chip *irq_chip;
+ unsigned int nregs;
+ u32 ngpios;
+ int ret;
+
+ parent_np = of_irq_find_parent(dev->of_node);
+ if (!parent_np)
+ return -ENXIO;
+
+ parent_domain = irq_find_host(parent_np);
+ of_node_put(parent_np);
+ if (!parent_domain)
+ return -EPROBE_DEFER;
+
+ ret = of_property_read_u32(dev->of_node, "ngpios", &ngpios);
+ if (ret)
+ return ret;
+
+ nregs = uniphier_gpio_get_nbanks(ngpios) * 2 + 3;
+ priv = devm_kzalloc(dev, struct_size(priv, saved_vals, nregs),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->regs))
+ return PTR_ERR(priv->regs);
+
+ spin_lock_init(&priv->lock);
+
+ chip = &priv->chip;
+ chip->label = dev_name(dev);
+ chip->parent = dev;
+ chip->request = gpiochip_generic_request;
+ chip->free = gpiochip_generic_free;
+ chip->get_direction = uniphier_gpio_get_direction;
+ chip->direction_input = uniphier_gpio_direction_input;
+ chip->direction_output = uniphier_gpio_direction_output;
+ chip->get = uniphier_gpio_get;
+ chip->set = uniphier_gpio_set;
+ chip->set_multiple = uniphier_gpio_set_multiple;
+ chip->to_irq = uniphier_gpio_to_irq;
+ chip->base = -1;
+ chip->ngpio = ngpios;
+
+ irq_chip = &priv->irq_chip;
+ irq_chip->name = dev_name(dev);
+ irq_chip->irq_mask = uniphier_gpio_irq_mask;
+ irq_chip->irq_unmask = uniphier_gpio_irq_unmask;
+ irq_chip->irq_eoi = irq_chip_eoi_parent;
+ irq_chip->irq_set_affinity = irq_chip_set_affinity_parent;
+ irq_chip->irq_set_type = uniphier_gpio_irq_set_type;
+
+ uniphier_gpio_hw_init(priv);
+
+ ret = devm_gpiochip_add_data(dev, chip, priv);
+ if (ret)
+ return ret;
+
+ priv->domain = irq_domain_create_hierarchy(
+ parent_domain, 0,
+ UNIPHIER_GPIO_IRQ_MAX_NUM,
+ of_node_to_fwnode(dev->of_node),
+ &uniphier_gpio_irq_domain_ops, priv);
+ if (!priv->domain)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ return 0;
+}
+
+static int uniphier_gpio_remove(struct platform_device *pdev)
+{
+ struct uniphier_gpio_priv *priv = platform_get_drvdata(pdev);
+
+ irq_domain_remove(priv->domain);
+
+ return 0;
+}
+
+static int __maybe_unused uniphier_gpio_suspend(struct device *dev)
+{
+ struct uniphier_gpio_priv *priv = dev_get_drvdata(dev);
+ unsigned int nbanks = uniphier_gpio_get_nbanks(priv->chip.ngpio);
+ u32 *val = priv->saved_vals;
+ unsigned int reg;
+ int i;
+
+ for (i = 0; i < nbanks; i++) {
+ reg = uniphier_gpio_bank_to_reg(i);
+
+ *val++ = readl(priv->regs + reg + UNIPHIER_GPIO_PORT_DATA);
+ *val++ = readl(priv->regs + reg + UNIPHIER_GPIO_PORT_DIR);
+ }
+
+ *val++ = readl(priv->regs + UNIPHIER_GPIO_IRQ_EN);
+ *val++ = readl(priv->regs + UNIPHIER_GPIO_IRQ_MODE);
+ *val++ = readl(priv->regs + UNIPHIER_GPIO_IRQ_FLT_EN);
+
+ return 0;
+}
+
+static int __maybe_unused uniphier_gpio_resume(struct device *dev)
+{
+ struct uniphier_gpio_priv *priv = dev_get_drvdata(dev);
+ unsigned int nbanks = uniphier_gpio_get_nbanks(priv->chip.ngpio);
+ const u32 *val = priv->saved_vals;
+ unsigned int reg;
+ int i;
+
+ for (i = 0; i < nbanks; i++) {
+ reg = uniphier_gpio_bank_to_reg(i);
+
+ writel(*val++, priv->regs + reg + UNIPHIER_GPIO_PORT_DATA);
+ writel(*val++, priv->regs + reg + UNIPHIER_GPIO_PORT_DIR);
+ }
+
+ writel(*val++, priv->regs + UNIPHIER_GPIO_IRQ_EN);
+ writel(*val++, priv->regs + UNIPHIER_GPIO_IRQ_MODE);
+ writel(*val++, priv->regs + UNIPHIER_GPIO_IRQ_FLT_EN);
+
+ uniphier_gpio_hw_init(priv);
+
+ return 0;
+}
+
+static const struct dev_pm_ops uniphier_gpio_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(uniphier_gpio_suspend,
+ uniphier_gpio_resume)
+};
+
+static const struct of_device_id uniphier_gpio_match[] = {
+ { .compatible = "socionext,uniphier-gpio" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, uniphier_gpio_match);
+
+static struct platform_driver uniphier_gpio_driver = {
+ .probe = uniphier_gpio_probe,
+ .remove = uniphier_gpio_remove,
+ .driver = {
+ .name = "uniphier-gpio",
+ .of_match_table = uniphier_gpio_match,
+ .pm = &uniphier_gpio_pm_ops,
+ },
+};
+module_platform_driver(uniphier_gpio_driver);
+
+MODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>");
+MODULE_DESCRIPTION("UniPhier GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-vf610.c b/drivers/gpio/gpio-vf610.c
new file mode 100644
index 0000000000..656d6b1ddd
--- /dev/null
+++ b/drivers/gpio/gpio-vf610.c
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Freescale vf610 GPIO support through PORT and GPIO
+ *
+ * Copyright (c) 2014 Toradex AG.
+ *
+ * Author: Stefan Agner <stefan@agner.ch>.
+ */
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/pinctrl/consumer.h>
+
+#define VF610_GPIO_PER_PORT 32
+
+struct fsl_gpio_soc_data {
+ /* SoCs has a Port Data Direction Register (PDDR) */
+ bool have_paddr;
+};
+
+struct vf610_gpio_port {
+ struct gpio_chip gc;
+ void __iomem *base;
+ void __iomem *gpio_base;
+ const struct fsl_gpio_soc_data *sdata;
+ u8 irqc[VF610_GPIO_PER_PORT];
+ struct clk *clk_port;
+ struct clk *clk_gpio;
+ int irq;
+};
+
+#define GPIO_PDOR 0x00
+#define GPIO_PSOR 0x04
+#define GPIO_PCOR 0x08
+#define GPIO_PTOR 0x0c
+#define GPIO_PDIR 0x10
+#define GPIO_PDDR 0x14
+
+#define PORT_PCR(n) ((n) * 0x4)
+#define PORT_PCR_IRQC_OFFSET 16
+
+#define PORT_ISFR 0xa0
+#define PORT_DFER 0xc0
+#define PORT_DFCR 0xc4
+#define PORT_DFWR 0xc8
+
+#define PORT_INT_OFF 0x0
+#define PORT_INT_LOGIC_ZERO 0x8
+#define PORT_INT_RISING_EDGE 0x9
+#define PORT_INT_FALLING_EDGE 0xa
+#define PORT_INT_EITHER_EDGE 0xb
+#define PORT_INT_LOGIC_ONE 0xc
+
+static const struct fsl_gpio_soc_data imx_data = {
+ .have_paddr = true,
+};
+
+static const struct of_device_id vf610_gpio_dt_ids[] = {
+ { .compatible = "fsl,vf610-gpio", .data = NULL, },
+ { .compatible = "fsl,imx7ulp-gpio", .data = &imx_data, },
+ { /* sentinel */ }
+};
+
+static inline void vf610_gpio_writel(u32 val, void __iomem *reg)
+{
+ writel_relaxed(val, reg);
+}
+
+static inline u32 vf610_gpio_readl(void __iomem *reg)
+{
+ return readl_relaxed(reg);
+}
+
+static int vf610_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct vf610_gpio_port *port = gpiochip_get_data(gc);
+ unsigned long mask = BIT(gpio);
+ unsigned long offset = GPIO_PDIR;
+
+ if (port->sdata && port->sdata->have_paddr) {
+ mask &= vf610_gpio_readl(port->gpio_base + GPIO_PDDR);
+ if (mask)
+ offset = GPIO_PDOR;
+ }
+
+ return !!(vf610_gpio_readl(port->gpio_base + offset) & BIT(gpio));
+}
+
+static void vf610_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ struct vf610_gpio_port *port = gpiochip_get_data(gc);
+ unsigned long mask = BIT(gpio);
+ unsigned long offset = val ? GPIO_PSOR : GPIO_PCOR;
+
+ vf610_gpio_writel(mask, port->gpio_base + offset);
+}
+
+static int vf610_gpio_direction_input(struct gpio_chip *chip, unsigned gpio)
+{
+ struct vf610_gpio_port *port = gpiochip_get_data(chip);
+ unsigned long mask = BIT(gpio);
+ u32 val;
+
+ if (port->sdata && port->sdata->have_paddr) {
+ val = vf610_gpio_readl(port->gpio_base + GPIO_PDDR);
+ val &= ~mask;
+ vf610_gpio_writel(val, port->gpio_base + GPIO_PDDR);
+ }
+
+ return pinctrl_gpio_direction_input(chip->base + gpio);
+}
+
+static int vf610_gpio_direction_output(struct gpio_chip *chip, unsigned gpio,
+ int value)
+{
+ struct vf610_gpio_port *port = gpiochip_get_data(chip);
+ unsigned long mask = BIT(gpio);
+ u32 val;
+
+ vf610_gpio_set(chip, gpio, value);
+
+ if (port->sdata && port->sdata->have_paddr) {
+ val = vf610_gpio_readl(port->gpio_base + GPIO_PDDR);
+ val |= mask;
+ vf610_gpio_writel(val, port->gpio_base + GPIO_PDDR);
+ }
+
+ return pinctrl_gpio_direction_output(chip->base + gpio);
+}
+
+static void vf610_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct vf610_gpio_port *port =
+ gpiochip_get_data(irq_desc_get_handler_data(desc));
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ int pin;
+ unsigned long irq_isfr;
+
+ chained_irq_enter(chip, desc);
+
+ irq_isfr = vf610_gpio_readl(port->base + PORT_ISFR);
+
+ for_each_set_bit(pin, &irq_isfr, VF610_GPIO_PER_PORT) {
+ vf610_gpio_writel(BIT(pin), port->base + PORT_ISFR);
+
+ generic_handle_domain_irq(port->gc.irq.domain, pin);
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static void vf610_gpio_irq_ack(struct irq_data *d)
+{
+ struct vf610_gpio_port *port =
+ gpiochip_get_data(irq_data_get_irq_chip_data(d));
+ int gpio = d->hwirq;
+
+ vf610_gpio_writel(BIT(gpio), port->base + PORT_ISFR);
+}
+
+static int vf610_gpio_irq_set_type(struct irq_data *d, u32 type)
+{
+ struct vf610_gpio_port *port =
+ gpiochip_get_data(irq_data_get_irq_chip_data(d));
+ u8 irqc;
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ irqc = PORT_INT_RISING_EDGE;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ irqc = PORT_INT_FALLING_EDGE;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ irqc = PORT_INT_EITHER_EDGE;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ irqc = PORT_INT_LOGIC_ZERO;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ irqc = PORT_INT_LOGIC_ONE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ port->irqc[d->hwirq] = irqc;
+
+ if (type & IRQ_TYPE_LEVEL_MASK)
+ irq_set_handler_locked(d, handle_level_irq);
+ else
+ irq_set_handler_locked(d, handle_edge_irq);
+
+ return 0;
+}
+
+static void vf610_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct vf610_gpio_port *port = gpiochip_get_data(gc);
+ irq_hw_number_t gpio_num = irqd_to_hwirq(d);
+ void __iomem *pcr_base = port->base + PORT_PCR(gpio_num);
+
+ vf610_gpio_writel(0, pcr_base);
+ gpiochip_disable_irq(gc, gpio_num);
+}
+
+static void vf610_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct vf610_gpio_port *port = gpiochip_get_data(gc);
+ irq_hw_number_t gpio_num = irqd_to_hwirq(d);
+ void __iomem *pcr_base = port->base + PORT_PCR(gpio_num);
+
+ gpiochip_enable_irq(gc, gpio_num);
+ vf610_gpio_writel(port->irqc[gpio_num] << PORT_PCR_IRQC_OFFSET,
+ pcr_base);
+}
+
+static int vf610_gpio_irq_set_wake(struct irq_data *d, u32 enable)
+{
+ struct vf610_gpio_port *port =
+ gpiochip_get_data(irq_data_get_irq_chip_data(d));
+
+ if (enable)
+ enable_irq_wake(port->irq);
+ else
+ disable_irq_wake(port->irq);
+
+ return 0;
+}
+
+static const struct irq_chip vf610_irqchip = {
+ .name = "gpio-vf610",
+ .irq_ack = vf610_gpio_irq_ack,
+ .irq_mask = vf610_gpio_irq_mask,
+ .irq_unmask = vf610_gpio_irq_unmask,
+ .irq_set_type = vf610_gpio_irq_set_type,
+ .irq_set_wake = vf610_gpio_irq_set_wake,
+ .flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND
+ | IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static void vf610_gpio_disable_clk(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int vf610_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct vf610_gpio_port *port;
+ struct gpio_chip *gc;
+ struct gpio_irq_chip *girq;
+ int i;
+ int ret;
+
+ port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->sdata = of_device_get_match_data(dev);
+ port->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(port->base))
+ return PTR_ERR(port->base);
+
+ port->gpio_base = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(port->gpio_base))
+ return PTR_ERR(port->gpio_base);
+
+ port->irq = platform_get_irq(pdev, 0);
+ if (port->irq < 0)
+ return port->irq;
+
+ port->clk_port = devm_clk_get(dev, "port");
+ ret = PTR_ERR_OR_ZERO(port->clk_port);
+ if (!ret) {
+ ret = clk_prepare_enable(port->clk_port);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, vf610_gpio_disable_clk,
+ port->clk_port);
+ if (ret)
+ return ret;
+ } else if (ret == -EPROBE_DEFER) {
+ /*
+ * Percolate deferrals, for anything else,
+ * just live without the clocking.
+ */
+ return ret;
+ }
+
+ port->clk_gpio = devm_clk_get(dev, "gpio");
+ ret = PTR_ERR_OR_ZERO(port->clk_gpio);
+ if (!ret) {
+ ret = clk_prepare_enable(port->clk_gpio);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, vf610_gpio_disable_clk,
+ port->clk_gpio);
+ if (ret)
+ return ret;
+ } else if (ret == -EPROBE_DEFER) {
+ return ret;
+ }
+
+ gc = &port->gc;
+ gc->parent = dev;
+ gc->label = dev_name(dev);
+ gc->ngpio = VF610_GPIO_PER_PORT;
+ gc->base = -1;
+
+ gc->request = gpiochip_generic_request;
+ gc->free = gpiochip_generic_free;
+ gc->direction_input = vf610_gpio_direction_input;
+ gc->get = vf610_gpio_get;
+ gc->direction_output = vf610_gpio_direction_output;
+ gc->set = vf610_gpio_set;
+
+ /* Mask all GPIO interrupts */
+ for (i = 0; i < gc->ngpio; i++)
+ vf610_gpio_writel(0, port->base + PORT_PCR(i));
+
+ /* Clear the interrupt status register for all GPIO's */
+ vf610_gpio_writel(~0, port->base + PORT_ISFR);
+
+ girq = &gc->irq;
+ gpio_irq_chip_set_chip(girq, &vf610_irqchip);
+ girq->parent_handler = vf610_gpio_irq_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(&pdev->dev, 1,
+ sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ girq->parents[0] = port->irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_edge_irq;
+
+ return devm_gpiochip_add_data(dev, gc, port);
+}
+
+static struct platform_driver vf610_gpio_driver = {
+ .driver = {
+ .name = "gpio-vf610",
+ .of_match_table = vf610_gpio_dt_ids,
+ },
+ .probe = vf610_gpio_probe,
+};
+
+builtin_platform_driver(vf610_gpio_driver);
diff --git a/drivers/gpio/gpio-viperboard.c b/drivers/gpio/gpio-viperboard.c
new file mode 100644
index 0000000000..e55d28a8a6
--- /dev/null
+++ b/drivers/gpio/gpio-viperboard.c
@@ -0,0 +1,471 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Nano River Technologies viperboard GPIO lib driver
+ *
+ * (C) 2012 by Lemonage GmbH
+ * Author: Lars Poeschel <poeschel@lemonage.de>
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/usb.h>
+#include <linux/gpio/driver.h>
+
+#include <linux/mfd/viperboard.h>
+
+#define VPRBRD_GPIOA_CLK_1MHZ 0
+#define VPRBRD_GPIOA_CLK_100KHZ 1
+#define VPRBRD_GPIOA_CLK_10KHZ 2
+#define VPRBRD_GPIOA_CLK_1KHZ 3
+#define VPRBRD_GPIOA_CLK_100HZ 4
+#define VPRBRD_GPIOA_CLK_10HZ 5
+
+#define VPRBRD_GPIOA_FREQ_DEFAULT 1000
+
+#define VPRBRD_GPIOA_CMD_CONT 0x00
+#define VPRBRD_GPIOA_CMD_PULSE 0x01
+#define VPRBRD_GPIOA_CMD_PWM 0x02
+#define VPRBRD_GPIOA_CMD_SETOUT 0x03
+#define VPRBRD_GPIOA_CMD_SETIN 0x04
+#define VPRBRD_GPIOA_CMD_SETINT 0x05
+#define VPRBRD_GPIOA_CMD_GETIN 0x06
+
+#define VPRBRD_GPIOB_CMD_SETDIR 0x00
+#define VPRBRD_GPIOB_CMD_SETVAL 0x01
+
+struct vprbrd_gpioa_msg {
+ u8 cmd;
+ u8 clk;
+ u8 offset;
+ u8 t1;
+ u8 t2;
+ u8 invert;
+ u8 pwmlevel;
+ u8 outval;
+ u8 risefall;
+ u8 answer;
+ u8 __fill;
+} __packed;
+
+struct vprbrd_gpiob_msg {
+ u8 cmd;
+ u16 val;
+ u16 mask;
+} __packed;
+
+struct vprbrd_gpio {
+ struct gpio_chip gpioa; /* gpio a related things */
+ u32 gpioa_out;
+ u32 gpioa_val;
+ struct gpio_chip gpiob; /* gpio b related things */
+ u32 gpiob_out;
+ u32 gpiob_val;
+ struct vprbrd *vb;
+};
+
+/* gpioa sampling clock module parameter */
+static unsigned char gpioa_clk;
+static unsigned int gpioa_freq = VPRBRD_GPIOA_FREQ_DEFAULT;
+module_param(gpioa_freq, uint, 0);
+MODULE_PARM_DESC(gpioa_freq,
+ "gpio-a sampling freq in Hz (default is 1000Hz) valid values: 10, 100, 1000, 10000, 100000, 1000000");
+
+/* ----- begin of gipo a chip -------------------------------------------- */
+
+static int vprbrd_gpioa_get(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret, answer, error = 0;
+ struct vprbrd_gpio *gpio = gpiochip_get_data(chip);
+ struct vprbrd *vb = gpio->vb;
+ struct vprbrd_gpioa_msg *gamsg = (struct vprbrd_gpioa_msg *)vb->buf;
+
+ /* if io is set to output, just return the saved value */
+ if (gpio->gpioa_out & (1 << offset))
+ return !!(gpio->gpioa_val & (1 << offset));
+
+ mutex_lock(&vb->lock);
+
+ gamsg->cmd = VPRBRD_GPIOA_CMD_GETIN;
+ gamsg->clk = 0x00;
+ gamsg->offset = offset;
+ gamsg->t1 = 0x00;
+ gamsg->t2 = 0x00;
+ gamsg->invert = 0x00;
+ gamsg->pwmlevel = 0x00;
+ gamsg->outval = 0x00;
+ gamsg->risefall = 0x00;
+ gamsg->answer = 0x00;
+ gamsg->__fill = 0x00;
+
+ ret = usb_control_msg(vb->usb_dev, usb_sndctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_OUT, 0x0000,
+ 0x0000, gamsg, sizeof(struct vprbrd_gpioa_msg),
+ VPRBRD_USB_TIMEOUT_MS);
+ if (ret != sizeof(struct vprbrd_gpioa_msg))
+ error = -EREMOTEIO;
+
+ ret = usb_control_msg(vb->usb_dev, usb_rcvctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_IN, 0x0000,
+ 0x0000, gamsg, sizeof(struct vprbrd_gpioa_msg),
+ VPRBRD_USB_TIMEOUT_MS);
+ answer = gamsg->answer & 0x01;
+
+ mutex_unlock(&vb->lock);
+
+ if (ret != sizeof(struct vprbrd_gpioa_msg))
+ error = -EREMOTEIO;
+
+ if (error)
+ return error;
+
+ return answer;
+}
+
+static void vprbrd_gpioa_set(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ int ret;
+ struct vprbrd_gpio *gpio = gpiochip_get_data(chip);
+ struct vprbrd *vb = gpio->vb;
+ struct vprbrd_gpioa_msg *gamsg = (struct vprbrd_gpioa_msg *)vb->buf;
+
+ if (gpio->gpioa_out & (1 << offset)) {
+ if (value)
+ gpio->gpioa_val |= (1 << offset);
+ else
+ gpio->gpioa_val &= ~(1 << offset);
+
+ mutex_lock(&vb->lock);
+
+ gamsg->cmd = VPRBRD_GPIOA_CMD_SETOUT;
+ gamsg->clk = 0x00;
+ gamsg->offset = offset;
+ gamsg->t1 = 0x00;
+ gamsg->t2 = 0x00;
+ gamsg->invert = 0x00;
+ gamsg->pwmlevel = 0x00;
+ gamsg->outval = value;
+ gamsg->risefall = 0x00;
+ gamsg->answer = 0x00;
+ gamsg->__fill = 0x00;
+
+ ret = usb_control_msg(vb->usb_dev,
+ usb_sndctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_OUT,
+ 0x0000, 0x0000, gamsg,
+ sizeof(struct vprbrd_gpioa_msg), VPRBRD_USB_TIMEOUT_MS);
+
+ mutex_unlock(&vb->lock);
+
+ if (ret != sizeof(struct vprbrd_gpioa_msg))
+ dev_err(chip->parent, "usb error setting pin value\n");
+ }
+}
+
+static int vprbrd_gpioa_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret;
+ struct vprbrd_gpio *gpio = gpiochip_get_data(chip);
+ struct vprbrd *vb = gpio->vb;
+ struct vprbrd_gpioa_msg *gamsg = (struct vprbrd_gpioa_msg *)vb->buf;
+
+ gpio->gpioa_out &= ~(1 << offset);
+
+ mutex_lock(&vb->lock);
+
+ gamsg->cmd = VPRBRD_GPIOA_CMD_SETIN;
+ gamsg->clk = gpioa_clk;
+ gamsg->offset = offset;
+ gamsg->t1 = 0x00;
+ gamsg->t2 = 0x00;
+ gamsg->invert = 0x00;
+ gamsg->pwmlevel = 0x00;
+ gamsg->outval = 0x00;
+ gamsg->risefall = 0x00;
+ gamsg->answer = 0x00;
+ gamsg->__fill = 0x00;
+
+ ret = usb_control_msg(vb->usb_dev, usb_sndctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_OUT, 0x0000,
+ 0x0000, gamsg, sizeof(struct vprbrd_gpioa_msg),
+ VPRBRD_USB_TIMEOUT_MS);
+
+ mutex_unlock(&vb->lock);
+
+ if (ret != sizeof(struct vprbrd_gpioa_msg))
+ return -EREMOTEIO;
+
+ return 0;
+}
+
+static int vprbrd_gpioa_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ int ret;
+ struct vprbrd_gpio *gpio = gpiochip_get_data(chip);
+ struct vprbrd *vb = gpio->vb;
+ struct vprbrd_gpioa_msg *gamsg = (struct vprbrd_gpioa_msg *)vb->buf;
+
+ gpio->gpioa_out |= (1 << offset);
+ if (value)
+ gpio->gpioa_val |= (1 << offset);
+ else
+ gpio->gpioa_val &= ~(1 << offset);
+
+ mutex_lock(&vb->lock);
+
+ gamsg->cmd = VPRBRD_GPIOA_CMD_SETOUT;
+ gamsg->clk = 0x00;
+ gamsg->offset = offset;
+ gamsg->t1 = 0x00;
+ gamsg->t2 = 0x00;
+ gamsg->invert = 0x00;
+ gamsg->pwmlevel = 0x00;
+ gamsg->outval = value;
+ gamsg->risefall = 0x00;
+ gamsg->answer = 0x00;
+ gamsg->__fill = 0x00;
+
+ ret = usb_control_msg(vb->usb_dev, usb_sndctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOA, VPRBRD_USB_TYPE_OUT, 0x0000,
+ 0x0000, gamsg, sizeof(struct vprbrd_gpioa_msg),
+ VPRBRD_USB_TIMEOUT_MS);
+
+ mutex_unlock(&vb->lock);
+
+ if (ret != sizeof(struct vprbrd_gpioa_msg))
+ return -EREMOTEIO;
+
+ return 0;
+}
+
+/* ----- end of gpio a chip ---------------------------------------------- */
+
+/* ----- begin of gipo b chip -------------------------------------------- */
+
+static int vprbrd_gpiob_setdir(struct vprbrd *vb, unsigned int offset,
+ unsigned int dir)
+{
+ struct vprbrd_gpiob_msg *gbmsg = (struct vprbrd_gpiob_msg *)vb->buf;
+ int ret;
+
+ gbmsg->cmd = VPRBRD_GPIOB_CMD_SETDIR;
+ gbmsg->val = cpu_to_be16(dir << offset);
+ gbmsg->mask = cpu_to_be16(0x0001 << offset);
+
+ ret = usb_control_msg(vb->usb_dev, usb_sndctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOB, VPRBRD_USB_TYPE_OUT, 0x0000,
+ 0x0000, gbmsg, sizeof(struct vprbrd_gpiob_msg),
+ VPRBRD_USB_TIMEOUT_MS);
+
+ if (ret != sizeof(struct vprbrd_gpiob_msg))
+ return -EREMOTEIO;
+
+ return 0;
+}
+
+static int vprbrd_gpiob_get(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret;
+ u16 val;
+ struct vprbrd_gpio *gpio = gpiochip_get_data(chip);
+ struct vprbrd *vb = gpio->vb;
+ struct vprbrd_gpiob_msg *gbmsg = (struct vprbrd_gpiob_msg *)vb->buf;
+
+ /* if io is set to output, just return the saved value */
+ if (gpio->gpiob_out & (1 << offset))
+ return gpio->gpiob_val & (1 << offset);
+
+ mutex_lock(&vb->lock);
+
+ ret = usb_control_msg(vb->usb_dev, usb_rcvctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOB, VPRBRD_USB_TYPE_IN, 0x0000,
+ 0x0000, gbmsg, sizeof(struct vprbrd_gpiob_msg),
+ VPRBRD_USB_TIMEOUT_MS);
+ val = gbmsg->val;
+
+ mutex_unlock(&vb->lock);
+
+ if (ret != sizeof(struct vprbrd_gpiob_msg))
+ return ret;
+
+ /* cache the read values */
+ gpio->gpiob_val = be16_to_cpu(val);
+
+ return (gpio->gpiob_val >> offset) & 0x1;
+}
+
+static void vprbrd_gpiob_set(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ int ret;
+ struct vprbrd_gpio *gpio = gpiochip_get_data(chip);
+ struct vprbrd *vb = gpio->vb;
+ struct vprbrd_gpiob_msg *gbmsg = (struct vprbrd_gpiob_msg *)vb->buf;
+
+ if (gpio->gpiob_out & (1 << offset)) {
+ if (value)
+ gpio->gpiob_val |= (1 << offset);
+ else
+ gpio->gpiob_val &= ~(1 << offset);
+
+ mutex_lock(&vb->lock);
+
+ gbmsg->cmd = VPRBRD_GPIOB_CMD_SETVAL;
+ gbmsg->val = cpu_to_be16(value << offset);
+ gbmsg->mask = cpu_to_be16(0x0001 << offset);
+
+ ret = usb_control_msg(vb->usb_dev,
+ usb_sndctrlpipe(vb->usb_dev, 0),
+ VPRBRD_USB_REQUEST_GPIOB, VPRBRD_USB_TYPE_OUT,
+ 0x0000, 0x0000, gbmsg,
+ sizeof(struct vprbrd_gpiob_msg), VPRBRD_USB_TIMEOUT_MS);
+
+ mutex_unlock(&vb->lock);
+
+ if (ret != sizeof(struct vprbrd_gpiob_msg))
+ dev_err(chip->parent, "usb error setting pin value\n");
+ }
+}
+
+static int vprbrd_gpiob_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret;
+ struct vprbrd_gpio *gpio = gpiochip_get_data(chip);
+ struct vprbrd *vb = gpio->vb;
+
+ gpio->gpiob_out &= ~(1 << offset);
+
+ mutex_lock(&vb->lock);
+
+ ret = vprbrd_gpiob_setdir(vb, offset, 0);
+
+ mutex_unlock(&vb->lock);
+
+ if (ret)
+ dev_err(chip->parent, "usb error setting pin to input\n");
+
+ return ret;
+}
+
+static int vprbrd_gpiob_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ int ret;
+ struct vprbrd_gpio *gpio = gpiochip_get_data(chip);
+ struct vprbrd *vb = gpio->vb;
+
+ gpio->gpiob_out |= (1 << offset);
+
+ mutex_lock(&vb->lock);
+
+ ret = vprbrd_gpiob_setdir(vb, offset, 1);
+ if (ret)
+ dev_err(chip->parent, "usb error setting pin to output\n");
+
+ mutex_unlock(&vb->lock);
+
+ vprbrd_gpiob_set(chip, offset, value);
+
+ return ret;
+}
+
+/* ----- end of gpio b chip ---------------------------------------------- */
+
+static int vprbrd_gpio_probe(struct platform_device *pdev)
+{
+ struct vprbrd *vb = dev_get_drvdata(pdev->dev.parent);
+ struct vprbrd_gpio *vb_gpio;
+ int ret;
+
+ vb_gpio = devm_kzalloc(&pdev->dev, sizeof(*vb_gpio), GFP_KERNEL);
+ if (vb_gpio == NULL)
+ return -ENOMEM;
+
+ vb_gpio->vb = vb;
+ /* registering gpio a */
+ vb_gpio->gpioa.label = "viperboard gpio a";
+ vb_gpio->gpioa.parent = &pdev->dev;
+ vb_gpio->gpioa.owner = THIS_MODULE;
+ vb_gpio->gpioa.base = -1;
+ vb_gpio->gpioa.ngpio = 16;
+ vb_gpio->gpioa.can_sleep = true;
+ vb_gpio->gpioa.set = vprbrd_gpioa_set;
+ vb_gpio->gpioa.get = vprbrd_gpioa_get;
+ vb_gpio->gpioa.direction_input = vprbrd_gpioa_direction_input;
+ vb_gpio->gpioa.direction_output = vprbrd_gpioa_direction_output;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &vb_gpio->gpioa, vb_gpio);
+ if (ret < 0)
+ return ret;
+
+ /* registering gpio b */
+ vb_gpio->gpiob.label = "viperboard gpio b";
+ vb_gpio->gpiob.parent = &pdev->dev;
+ vb_gpio->gpiob.owner = THIS_MODULE;
+ vb_gpio->gpiob.base = -1;
+ vb_gpio->gpiob.ngpio = 16;
+ vb_gpio->gpiob.can_sleep = true;
+ vb_gpio->gpiob.set = vprbrd_gpiob_set;
+ vb_gpio->gpiob.get = vprbrd_gpiob_get;
+ vb_gpio->gpiob.direction_input = vprbrd_gpiob_direction_input;
+ vb_gpio->gpiob.direction_output = vprbrd_gpiob_direction_output;
+
+ return devm_gpiochip_add_data(&pdev->dev, &vb_gpio->gpiob, vb_gpio);
+}
+
+static struct platform_driver vprbrd_gpio_driver = {
+ .driver.name = "viperboard-gpio",
+ .probe = vprbrd_gpio_probe,
+};
+
+static int __init vprbrd_gpio_init(void)
+{
+ switch (gpioa_freq) {
+ case 1000000:
+ gpioa_clk = VPRBRD_GPIOA_CLK_1MHZ;
+ break;
+ case 100000:
+ gpioa_clk = VPRBRD_GPIOA_CLK_100KHZ;
+ break;
+ case 10000:
+ gpioa_clk = VPRBRD_GPIOA_CLK_10KHZ;
+ break;
+ case 1000:
+ gpioa_clk = VPRBRD_GPIOA_CLK_1KHZ;
+ break;
+ case 100:
+ gpioa_clk = VPRBRD_GPIOA_CLK_100HZ;
+ break;
+ case 10:
+ gpioa_clk = VPRBRD_GPIOA_CLK_10HZ;
+ break;
+ default:
+ pr_warn("invalid gpioa_freq (%d)\n", gpioa_freq);
+ gpioa_clk = VPRBRD_GPIOA_CLK_1KHZ;
+ }
+
+ return platform_driver_register(&vprbrd_gpio_driver);
+}
+subsys_initcall(vprbrd_gpio_init);
+
+static void __exit vprbrd_gpio_exit(void)
+{
+ platform_driver_unregister(&vprbrd_gpio_driver);
+}
+module_exit(vprbrd_gpio_exit);
+
+MODULE_AUTHOR("Lars Poeschel <poeschel@lemonage.de>");
+MODULE_DESCRIPTION("GPIO driver for Nano River Techs Viperboard");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:viperboard-gpio");
diff --git a/drivers/gpio/gpio-virtio.c b/drivers/gpio/gpio-virtio.c
new file mode 100644
index 0000000000..fcc5e8c089
--- /dev/null
+++ b/drivers/gpio/gpio-virtio.c
@@ -0,0 +1,664 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * GPIO driver for virtio-based virtual GPIO controllers
+ *
+ * Copyright (C) 2021 metux IT consult
+ * Enrico Weigelt, metux IT consult <info@metux.net>
+ *
+ * Copyright (C) 2021 Linaro.
+ * Viresh Kumar <viresh.kumar@linaro.org>
+ */
+
+#include <linux/completion.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/virtio_config.h>
+#include <uapi/linux/virtio_gpio.h>
+#include <uapi/linux/virtio_ids.h>
+
+struct virtio_gpio_line {
+ struct mutex lock; /* Protects line operation */
+ struct completion completion;
+ struct virtio_gpio_request req ____cacheline_aligned;
+ struct virtio_gpio_response res ____cacheline_aligned;
+ unsigned int rxlen;
+};
+
+struct vgpio_irq_line {
+ u8 type;
+ bool disabled;
+ bool masked;
+ bool queued;
+ bool update_pending;
+ bool queue_pending;
+
+ struct virtio_gpio_irq_request ireq ____cacheline_aligned;
+ struct virtio_gpio_irq_response ires ____cacheline_aligned;
+};
+
+struct virtio_gpio {
+ struct virtio_device *vdev;
+ struct mutex lock; /* Protects virtqueue operation */
+ struct gpio_chip gc;
+ struct virtio_gpio_line *lines;
+ struct virtqueue *request_vq;
+
+ /* irq support */
+ struct virtqueue *event_vq;
+ struct mutex irq_lock; /* Protects irq operation */
+ raw_spinlock_t eventq_lock; /* Protects queuing of the buffer */
+ struct vgpio_irq_line *irq_lines;
+};
+
+static int _virtio_gpio_req(struct virtio_gpio *vgpio, u16 type, u16 gpio,
+ u8 txvalue, u8 *rxvalue, void *response, u32 rxlen)
+{
+ struct virtio_gpio_line *line = &vgpio->lines[gpio];
+ struct virtio_gpio_request *req = &line->req;
+ struct virtio_gpio_response *res = response;
+ struct scatterlist *sgs[2], req_sg, res_sg;
+ struct device *dev = &vgpio->vdev->dev;
+ int ret;
+
+ /*
+ * Prevent concurrent requests for the same line since we have
+ * pre-allocated request/response buffers for each GPIO line. Moreover
+ * Linux always accesses a GPIO line sequentially, so this locking shall
+ * always go through without any delays.
+ */
+ mutex_lock(&line->lock);
+
+ req->type = cpu_to_le16(type);
+ req->gpio = cpu_to_le16(gpio);
+ req->value = cpu_to_le32(txvalue);
+
+ sg_init_one(&req_sg, req, sizeof(*req));
+ sg_init_one(&res_sg, res, rxlen);
+ sgs[0] = &req_sg;
+ sgs[1] = &res_sg;
+
+ line->rxlen = 0;
+ reinit_completion(&line->completion);
+
+ /*
+ * Virtqueue callers need to ensure they don't call its APIs with other
+ * virtqueue operations at the same time.
+ */
+ mutex_lock(&vgpio->lock);
+ ret = virtqueue_add_sgs(vgpio->request_vq, sgs, 1, 1, line, GFP_KERNEL);
+ if (ret) {
+ dev_err(dev, "failed to add request to vq\n");
+ mutex_unlock(&vgpio->lock);
+ goto out;
+ }
+
+ virtqueue_kick(vgpio->request_vq);
+ mutex_unlock(&vgpio->lock);
+
+ wait_for_completion(&line->completion);
+
+ if (unlikely(res->status != VIRTIO_GPIO_STATUS_OK)) {
+ dev_err(dev, "GPIO request failed: %d\n", gpio);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (unlikely(line->rxlen != rxlen)) {
+ dev_err(dev, "GPIO operation returned incorrect len (%u : %u)\n",
+ rxlen, line->rxlen);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (rxvalue)
+ *rxvalue = res->value;
+
+out:
+ mutex_unlock(&line->lock);
+ return ret;
+}
+
+static int virtio_gpio_req(struct virtio_gpio *vgpio, u16 type, u16 gpio,
+ u8 txvalue, u8 *rxvalue)
+{
+ struct virtio_gpio_line *line = &vgpio->lines[gpio];
+ struct virtio_gpio_response *res = &line->res;
+
+ return _virtio_gpio_req(vgpio, type, gpio, txvalue, rxvalue, res,
+ sizeof(*res));
+}
+
+static void virtio_gpio_free(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+
+ virtio_gpio_req(vgpio, VIRTIO_GPIO_MSG_SET_DIRECTION, gpio,
+ VIRTIO_GPIO_DIRECTION_NONE, NULL);
+}
+
+static int virtio_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+ u8 direction;
+ int ret;
+
+ ret = virtio_gpio_req(vgpio, VIRTIO_GPIO_MSG_GET_DIRECTION, gpio, 0,
+ &direction);
+ if (ret)
+ return ret;
+
+ switch (direction) {
+ case VIRTIO_GPIO_DIRECTION_IN:
+ return GPIO_LINE_DIRECTION_IN;
+ case VIRTIO_GPIO_DIRECTION_OUT:
+ return GPIO_LINE_DIRECTION_OUT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int virtio_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+
+ return virtio_gpio_req(vgpio, VIRTIO_GPIO_MSG_SET_DIRECTION, gpio,
+ VIRTIO_GPIO_DIRECTION_IN, NULL);
+}
+
+static int virtio_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio,
+ int value)
+{
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+ int ret;
+
+ ret = virtio_gpio_req(vgpio, VIRTIO_GPIO_MSG_SET_VALUE, gpio, value, NULL);
+ if (ret)
+ return ret;
+
+ return virtio_gpio_req(vgpio, VIRTIO_GPIO_MSG_SET_DIRECTION, gpio,
+ VIRTIO_GPIO_DIRECTION_OUT, NULL);
+}
+
+static int virtio_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+ u8 value;
+ int ret;
+
+ ret = virtio_gpio_req(vgpio, VIRTIO_GPIO_MSG_GET_VALUE, gpio, 0, &value);
+ return ret ? ret : value;
+}
+
+static void virtio_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value)
+{
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+
+ virtio_gpio_req(vgpio, VIRTIO_GPIO_MSG_SET_VALUE, gpio, value, NULL);
+}
+
+/* Interrupt handling */
+static void virtio_gpio_irq_prepare(struct virtio_gpio *vgpio, u16 gpio)
+{
+ struct vgpio_irq_line *irq_line = &vgpio->irq_lines[gpio];
+ struct virtio_gpio_irq_request *ireq = &irq_line->ireq;
+ struct virtio_gpio_irq_response *ires = &irq_line->ires;
+ struct scatterlist *sgs[2], req_sg, res_sg;
+ int ret;
+
+ if (WARN_ON(irq_line->queued || irq_line->masked || irq_line->disabled))
+ return;
+
+ ireq->gpio = cpu_to_le16(gpio);
+ sg_init_one(&req_sg, ireq, sizeof(*ireq));
+ sg_init_one(&res_sg, ires, sizeof(*ires));
+ sgs[0] = &req_sg;
+ sgs[1] = &res_sg;
+
+ ret = virtqueue_add_sgs(vgpio->event_vq, sgs, 1, 1, irq_line, GFP_ATOMIC);
+ if (ret) {
+ dev_err(&vgpio->vdev->dev, "failed to add request to eventq\n");
+ return;
+ }
+
+ irq_line->queued = true;
+ virtqueue_kick(vgpio->event_vq);
+}
+
+static void virtio_gpio_irq_enable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+ struct vgpio_irq_line *irq_line = &vgpio->irq_lines[d->hwirq];
+
+ raw_spin_lock(&vgpio->eventq_lock);
+ irq_line->disabled = false;
+ irq_line->masked = false;
+ irq_line->queue_pending = true;
+ raw_spin_unlock(&vgpio->eventq_lock);
+
+ irq_line->update_pending = true;
+}
+
+static void virtio_gpio_irq_disable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+ struct vgpio_irq_line *irq_line = &vgpio->irq_lines[d->hwirq];
+
+ raw_spin_lock(&vgpio->eventq_lock);
+ irq_line->disabled = true;
+ irq_line->masked = true;
+ irq_line->queue_pending = false;
+ raw_spin_unlock(&vgpio->eventq_lock);
+
+ irq_line->update_pending = true;
+}
+
+static void virtio_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+ struct vgpio_irq_line *irq_line = &vgpio->irq_lines[d->hwirq];
+
+ raw_spin_lock(&vgpio->eventq_lock);
+ irq_line->masked = true;
+ raw_spin_unlock(&vgpio->eventq_lock);
+}
+
+static void virtio_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+ struct vgpio_irq_line *irq_line = &vgpio->irq_lines[d->hwirq];
+
+ raw_spin_lock(&vgpio->eventq_lock);
+ irq_line->masked = false;
+
+ /* Queue the buffer unconditionally on unmask */
+ virtio_gpio_irq_prepare(vgpio, d->hwirq);
+ raw_spin_unlock(&vgpio->eventq_lock);
+}
+
+static int virtio_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+ struct vgpio_irq_line *irq_line = &vgpio->irq_lines[d->hwirq];
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ type = VIRTIO_GPIO_IRQ_TYPE_EDGE_RISING;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ type = VIRTIO_GPIO_IRQ_TYPE_EDGE_FALLING;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ type = VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ type = VIRTIO_GPIO_IRQ_TYPE_LEVEL_LOW;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ type = VIRTIO_GPIO_IRQ_TYPE_LEVEL_HIGH;
+ break;
+ default:
+ dev_err(&vgpio->vdev->dev, "unsupported irq type: %u\n", type);
+ return -EINVAL;
+ }
+
+ irq_line->type = type;
+ irq_line->update_pending = true;
+
+ return 0;
+}
+
+static void virtio_gpio_irq_bus_lock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+
+ mutex_lock(&vgpio->irq_lock);
+}
+
+static void virtio_gpio_irq_bus_sync_unlock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct virtio_gpio *vgpio = gpiochip_get_data(gc);
+ struct vgpio_irq_line *irq_line = &vgpio->irq_lines[d->hwirq];
+ u8 type = irq_line->disabled ? VIRTIO_GPIO_IRQ_TYPE_NONE : irq_line->type;
+ unsigned long flags;
+
+ if (irq_line->update_pending) {
+ irq_line->update_pending = false;
+ virtio_gpio_req(vgpio, VIRTIO_GPIO_MSG_IRQ_TYPE, d->hwirq, type,
+ NULL);
+
+ /* Queue the buffer only after interrupt is enabled */
+ raw_spin_lock_irqsave(&vgpio->eventq_lock, flags);
+ if (irq_line->queue_pending) {
+ irq_line->queue_pending = false;
+ virtio_gpio_irq_prepare(vgpio, d->hwirq);
+ }
+ raw_spin_unlock_irqrestore(&vgpio->eventq_lock, flags);
+ }
+
+ mutex_unlock(&vgpio->irq_lock);
+}
+
+static struct irq_chip vgpio_irq_chip = {
+ .name = "virtio-gpio",
+ .irq_enable = virtio_gpio_irq_enable,
+ .irq_disable = virtio_gpio_irq_disable,
+ .irq_mask = virtio_gpio_irq_mask,
+ .irq_unmask = virtio_gpio_irq_unmask,
+ .irq_set_type = virtio_gpio_irq_set_type,
+
+ /* These are required to implement irqchip for slow busses */
+ .irq_bus_lock = virtio_gpio_irq_bus_lock,
+ .irq_bus_sync_unlock = virtio_gpio_irq_bus_sync_unlock,
+};
+
+static bool ignore_irq(struct virtio_gpio *vgpio, int gpio,
+ struct vgpio_irq_line *irq_line)
+{
+ bool ignore = false;
+
+ raw_spin_lock(&vgpio->eventq_lock);
+ irq_line->queued = false;
+
+ /* Interrupt is disabled currently */
+ if (irq_line->masked || irq_line->disabled) {
+ ignore = true;
+ goto unlock;
+ }
+
+ /*
+ * Buffer is returned as the interrupt was disabled earlier, but is
+ * enabled again now. Requeue the buffers.
+ */
+ if (irq_line->ires.status == VIRTIO_GPIO_IRQ_STATUS_INVALID) {
+ virtio_gpio_irq_prepare(vgpio, gpio);
+ ignore = true;
+ goto unlock;
+ }
+
+ if (WARN_ON(irq_line->ires.status != VIRTIO_GPIO_IRQ_STATUS_VALID))
+ ignore = true;
+
+unlock:
+ raw_spin_unlock(&vgpio->eventq_lock);
+
+ return ignore;
+}
+
+static void virtio_gpio_event_vq(struct virtqueue *vq)
+{
+ struct virtio_gpio *vgpio = vq->vdev->priv;
+ struct device *dev = &vgpio->vdev->dev;
+ struct vgpio_irq_line *irq_line;
+ int gpio, ret;
+ unsigned int len;
+
+ while (true) {
+ irq_line = virtqueue_get_buf(vgpio->event_vq, &len);
+ if (!irq_line)
+ break;
+
+ if (len != sizeof(irq_line->ires)) {
+ dev_err(dev, "irq with incorrect length (%u : %u)\n",
+ len, (unsigned int)sizeof(irq_line->ires));
+ continue;
+ }
+
+ /*
+ * Find GPIO line number from the offset of irq_line within the
+ * irq_lines block. We can also get GPIO number from
+ * irq-request, but better not to rely on a buffer returned by
+ * remote.
+ */
+ gpio = irq_line - vgpio->irq_lines;
+ WARN_ON(gpio >= vgpio->gc.ngpio);
+
+ if (unlikely(ignore_irq(vgpio, gpio, irq_line)))
+ continue;
+
+ ret = generic_handle_domain_irq(vgpio->gc.irq.domain, gpio);
+ if (ret)
+ dev_err(dev, "failed to handle interrupt: %d\n", ret);
+ }
+}
+
+static void virtio_gpio_request_vq(struct virtqueue *vq)
+{
+ struct virtio_gpio_line *line;
+ unsigned int len;
+
+ do {
+ line = virtqueue_get_buf(vq, &len);
+ if (!line)
+ return;
+
+ line->rxlen = len;
+ complete(&line->completion);
+ } while (1);
+}
+
+static void virtio_gpio_free_vqs(struct virtio_device *vdev)
+{
+ virtio_reset_device(vdev);
+ vdev->config->del_vqs(vdev);
+}
+
+static int virtio_gpio_alloc_vqs(struct virtio_gpio *vgpio,
+ struct virtio_device *vdev)
+{
+ const char * const names[] = { "requestq", "eventq" };
+ vq_callback_t *cbs[] = {
+ virtio_gpio_request_vq,
+ virtio_gpio_event_vq,
+ };
+ struct virtqueue *vqs[2] = { NULL, NULL };
+ int ret;
+
+ ret = virtio_find_vqs(vdev, vgpio->irq_lines ? 2 : 1, vqs, cbs, names, NULL);
+ if (ret) {
+ dev_err(&vdev->dev, "failed to find vqs: %d\n", ret);
+ return ret;
+ }
+
+ if (!vqs[0]) {
+ dev_err(&vdev->dev, "failed to find requestq vq\n");
+ goto out;
+ }
+ vgpio->request_vq = vqs[0];
+
+ if (vgpio->irq_lines && !vqs[1]) {
+ dev_err(&vdev->dev, "failed to find eventq vq\n");
+ goto out;
+ }
+ vgpio->event_vq = vqs[1];
+
+ return 0;
+
+out:
+ if (vqs[0] || vqs[1])
+ virtio_gpio_free_vqs(vdev);
+
+ return -ENODEV;
+}
+
+static const char **virtio_gpio_get_names(struct virtio_gpio *vgpio,
+ u32 gpio_names_size, u16 ngpio)
+{
+ struct virtio_gpio_response_get_names *res;
+ struct device *dev = &vgpio->vdev->dev;
+ u8 *gpio_names, *str;
+ const char **names;
+ int i, ret, len;
+
+ if (!gpio_names_size)
+ return NULL;
+
+ len = sizeof(*res) + gpio_names_size;
+ res = devm_kzalloc(dev, len, GFP_KERNEL);
+ if (!res)
+ return NULL;
+ gpio_names = res->value;
+
+ ret = _virtio_gpio_req(vgpio, VIRTIO_GPIO_MSG_GET_NAMES, 0, 0, NULL,
+ res, len);
+ if (ret) {
+ dev_err(dev, "Failed to get GPIO names: %d\n", ret);
+ return NULL;
+ }
+
+ names = devm_kcalloc(dev, ngpio, sizeof(*names), GFP_KERNEL);
+ if (!names)
+ return NULL;
+
+ /* NULL terminate the string instead of checking it */
+ gpio_names[gpio_names_size - 1] = '\0';
+
+ for (i = 0, str = gpio_names; i < ngpio; i++) {
+ names[i] = str;
+ str += strlen(str) + 1; /* zero-length strings are allowed */
+
+ if (str > gpio_names + gpio_names_size) {
+ dev_err(dev, "gpio_names block is too short (%d)\n", i);
+ return NULL;
+ }
+ }
+
+ return names;
+}
+
+static int virtio_gpio_probe(struct virtio_device *vdev)
+{
+ struct virtio_gpio_config config;
+ struct device *dev = &vdev->dev;
+ struct virtio_gpio *vgpio;
+ u32 gpio_names_size;
+ u16 ngpio;
+ int ret, i;
+
+ vgpio = devm_kzalloc(dev, sizeof(*vgpio), GFP_KERNEL);
+ if (!vgpio)
+ return -ENOMEM;
+
+ /* Read configuration */
+ virtio_cread_bytes(vdev, 0, &config, sizeof(config));
+ gpio_names_size = le32_to_cpu(config.gpio_names_size);
+ ngpio = le16_to_cpu(config.ngpio);
+ if (!ngpio) {
+ dev_err(dev, "Number of GPIOs can't be zero\n");
+ return -EINVAL;
+ }
+
+ vgpio->lines = devm_kcalloc(dev, ngpio, sizeof(*vgpio->lines), GFP_KERNEL);
+ if (!vgpio->lines)
+ return -ENOMEM;
+
+ for (i = 0; i < ngpio; i++) {
+ mutex_init(&vgpio->lines[i].lock);
+ init_completion(&vgpio->lines[i].completion);
+ }
+
+ mutex_init(&vgpio->lock);
+ vdev->priv = vgpio;
+
+ vgpio->vdev = vdev;
+ vgpio->gc.free = virtio_gpio_free;
+ vgpio->gc.get_direction = virtio_gpio_get_direction;
+ vgpio->gc.direction_input = virtio_gpio_direction_input;
+ vgpio->gc.direction_output = virtio_gpio_direction_output;
+ vgpio->gc.get = virtio_gpio_get;
+ vgpio->gc.set = virtio_gpio_set;
+ vgpio->gc.ngpio = ngpio;
+ vgpio->gc.base = -1; /* Allocate base dynamically */
+ vgpio->gc.label = dev_name(dev);
+ vgpio->gc.parent = dev;
+ vgpio->gc.owner = THIS_MODULE;
+ vgpio->gc.can_sleep = true;
+
+ /* Interrupt support */
+ if (virtio_has_feature(vdev, VIRTIO_GPIO_F_IRQ)) {
+ vgpio->irq_lines = devm_kcalloc(dev, ngpio, sizeof(*vgpio->irq_lines), GFP_KERNEL);
+ if (!vgpio->irq_lines)
+ return -ENOMEM;
+
+ /* The event comes from the outside so no parent handler */
+ vgpio->gc.irq.parent_handler = NULL;
+ vgpio->gc.irq.num_parents = 0;
+ vgpio->gc.irq.parents = NULL;
+ vgpio->gc.irq.default_type = IRQ_TYPE_NONE;
+ vgpio->gc.irq.handler = handle_level_irq;
+ vgpio->gc.irq.chip = &vgpio_irq_chip;
+
+ for (i = 0; i < ngpio; i++) {
+ vgpio->irq_lines[i].type = VIRTIO_GPIO_IRQ_TYPE_NONE;
+ vgpio->irq_lines[i].disabled = true;
+ vgpio->irq_lines[i].masked = true;
+ }
+
+ mutex_init(&vgpio->irq_lock);
+ raw_spin_lock_init(&vgpio->eventq_lock);
+ }
+
+ ret = virtio_gpio_alloc_vqs(vgpio, vdev);
+ if (ret)
+ return ret;
+
+ /* Mark the device ready to perform operations from within probe() */
+ virtio_device_ready(vdev);
+
+ vgpio->gc.names = virtio_gpio_get_names(vgpio, gpio_names_size, ngpio);
+
+ ret = gpiochip_add_data(&vgpio->gc, vgpio);
+ if (ret) {
+ virtio_gpio_free_vqs(vdev);
+ dev_err(dev, "Failed to add virtio-gpio controller\n");
+ }
+
+ return ret;
+}
+
+static void virtio_gpio_remove(struct virtio_device *vdev)
+{
+ struct virtio_gpio *vgpio = vdev->priv;
+
+ gpiochip_remove(&vgpio->gc);
+ virtio_gpio_free_vqs(vdev);
+}
+
+static const struct virtio_device_id id_table[] = {
+ { VIRTIO_ID_GPIO, VIRTIO_DEV_ANY_ID },
+ {},
+};
+MODULE_DEVICE_TABLE(virtio, id_table);
+
+static const unsigned int features[] = {
+ VIRTIO_GPIO_F_IRQ,
+};
+
+static struct virtio_driver virtio_gpio_driver = {
+ .feature_table = features,
+ .feature_table_size = ARRAY_SIZE(features),
+ .id_table = id_table,
+ .probe = virtio_gpio_probe,
+ .remove = virtio_gpio_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .owner = THIS_MODULE,
+ },
+};
+module_virtio_driver(virtio_gpio_driver);
+
+MODULE_AUTHOR("Enrico Weigelt, metux IT consult <info@metux.net>");
+MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.org>");
+MODULE_DESCRIPTION("VirtIO GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-visconti.c b/drivers/gpio/gpio-visconti.c
new file mode 100644
index 0000000000..6734e7e1e2
--- /dev/null
+++ b/drivers/gpio/gpio-visconti.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Toshiba Visconti GPIO Support
+ *
+ * (C) Copyright 2020 Toshiba Electronic Devices & Storage Corporation
+ * (C) Copyright 2020 TOSHIBA CORPORATION
+ *
+ * Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/bitops.h>
+
+/* register offset */
+#define GPIO_DIR 0x00
+#define GPIO_IDATA 0x08
+#define GPIO_ODATA 0x10
+#define GPIO_OSET 0x18
+#define GPIO_OCLR 0x20
+#define GPIO_INTMODE 0x30
+
+#define BASE_HW_IRQ 24
+
+struct visconti_gpio {
+ void __iomem *base;
+ spinlock_t lock; /* protect gpio register */
+ struct gpio_chip gpio_chip;
+ struct device *dev;
+};
+
+static int visconti_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct visconti_gpio *priv = gpiochip_get_data(gc);
+ u32 offset = irqd_to_hwirq(d);
+ u32 bit = BIT(offset);
+ u32 intc_type = IRQ_TYPE_EDGE_RISING;
+ u32 intmode, odata;
+ int ret = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ odata = readl(priv->base + GPIO_ODATA);
+ intmode = readl(priv->base + GPIO_INTMODE);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ odata &= ~bit;
+ intmode &= ~bit;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ odata |= bit;
+ intmode &= ~bit;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ intmode |= bit;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ intc_type = IRQ_TYPE_LEVEL_HIGH;
+ odata &= ~bit;
+ intmode &= ~bit;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ intc_type = IRQ_TYPE_LEVEL_HIGH;
+ odata |= bit;
+ intmode &= ~bit;
+ break;
+ default:
+ ret = -EINVAL;
+ goto err;
+ }
+
+ writel(odata, priv->base + GPIO_ODATA);
+ writel(intmode, priv->base + GPIO_INTMODE);
+ irq_set_irq_type(offset, intc_type);
+
+ ret = irq_chip_set_type_parent(d, type);
+err:
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return ret;
+}
+
+static int visconti_gpio_child_to_parent_hwirq(struct gpio_chip *gc,
+ unsigned int child,
+ unsigned int child_type,
+ unsigned int *parent,
+ unsigned int *parent_type)
+{
+ /* Interrupts 0..15 mapped to interrupts 24..39 on the GIC */
+ if (child < 16) {
+ /* All these interrupts are level high in the CPU */
+ *parent_type = IRQ_TYPE_LEVEL_HIGH;
+ *parent = child + BASE_HW_IRQ;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int visconti_gpio_populate_parent_fwspec(struct gpio_chip *chip,
+ union gpio_irq_fwspec *gfwspec,
+ unsigned int parent_hwirq,
+ unsigned int parent_type)
+{
+ struct irq_fwspec *fwspec = &gfwspec->fwspec;
+
+ fwspec->fwnode = chip->irq.parent_domain->fwnode;
+ fwspec->param_count = 3;
+ fwspec->param[0] = 0;
+ fwspec->param[1] = parent_hwirq;
+ fwspec->param[2] = parent_type;
+
+ return 0;
+}
+
+static void visconti_gpio_mask_irq(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+
+ irq_chip_mask_parent(d);
+ gpiochip_disable_irq(gc, irqd_to_hwirq(d));
+}
+
+static void visconti_gpio_unmask_irq(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+
+ gpiochip_enable_irq(gc, irqd_to_hwirq(d));
+ irq_chip_unmask_parent(d);
+}
+
+static void visconti_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct visconti_gpio *priv = gpiochip_get_data(gc);
+
+ seq_printf(p, dev_name(priv->dev));
+}
+
+static const struct irq_chip visconti_gpio_irq_chip = {
+ .irq_mask = visconti_gpio_mask_irq,
+ .irq_unmask = visconti_gpio_unmask_irq,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_type = visconti_gpio_irq_set_type,
+ .irq_print_chip = visconti_gpio_irq_print_chip,
+ .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND |
+ IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int visconti_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct visconti_gpio *priv;
+ struct gpio_irq_chip *girq;
+ struct irq_domain *parent;
+ struct device_node *irq_parent;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->lock);
+ priv->dev = dev;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ irq_parent = of_irq_find_parent(dev->of_node);
+ if (!irq_parent) {
+ dev_err(dev, "No IRQ parent node\n");
+ return -ENODEV;
+ }
+
+ parent = irq_find_host(irq_parent);
+ of_node_put(irq_parent);
+ if (!parent) {
+ dev_err(dev, "No IRQ parent domain\n");
+ return -ENODEV;
+ }
+
+ ret = bgpio_init(&priv->gpio_chip, dev, 4,
+ priv->base + GPIO_IDATA,
+ priv->base + GPIO_OSET,
+ priv->base + GPIO_OCLR,
+ priv->base + GPIO_DIR,
+ NULL,
+ 0);
+ if (ret) {
+ dev_err(dev, "unable to init generic GPIO\n");
+ return ret;
+ }
+
+ girq = &priv->gpio_chip.irq;
+ gpio_irq_chip_set_chip(girq, &visconti_gpio_irq_chip);
+ girq->fwnode = of_node_to_fwnode(dev->of_node);
+ girq->parent_domain = parent;
+ girq->child_to_parent_hwirq = visconti_gpio_child_to_parent_hwirq;
+ girq->populate_parent_alloc_arg = visconti_gpio_populate_parent_fwspec;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_level_irq;
+
+ return devm_gpiochip_add_data(dev, &priv->gpio_chip, priv);
+}
+
+static const struct of_device_id visconti_gpio_of_match[] = {
+ { .compatible = "toshiba,gpio-tmpv7708", },
+ { /* end of table */ }
+};
+MODULE_DEVICE_TABLE(of, visconti_gpio_of_match);
+
+static struct platform_driver visconti_gpio_driver = {
+ .probe = visconti_gpio_probe,
+ .driver = {
+ .name = "visconti_gpio",
+ .of_match_table = visconti_gpio_of_match,
+ }
+};
+module_platform_driver(visconti_gpio_driver);
+
+MODULE_AUTHOR("Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp>");
+MODULE_DESCRIPTION("Toshiba Visconti GPIO Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-vx855.c b/drivers/gpio/gpio-vx855.c
new file mode 100644
index 0000000000..8fd6c3913d
--- /dev/null
+++ b/drivers/gpio/gpio-vx855.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Linux GPIOlib driver for the VIA VX855 integrated southbridge GPIO
+ *
+ * Copyright (C) 2009 VIA Technologies, Inc.
+ * Copyright (C) 2010 One Laptop per Child
+ * Author: Harald Welte <HaraldWelte@viatech.com>
+ * All rights reserved.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio/driver.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+
+#define MODULE_NAME "vx855_gpio"
+
+/* The VX855 south bridge has the following GPIO pins:
+ * GPI 0...13 General Purpose Input
+ * GPO 0...12 General Purpose Output
+ * GPIO 0...14 General Purpose I/O (Open-Drain)
+ */
+
+#define NR_VX855_GPI 14
+#define NR_VX855_GPO 13
+#define NR_VX855_GPIO 15
+
+#define NR_VX855_GPInO (NR_VX855_GPI + NR_VX855_GPO)
+#define NR_VX855_GP (NR_VX855_GPI + NR_VX855_GPO + NR_VX855_GPIO)
+
+struct vx855_gpio {
+ struct gpio_chip gpio;
+ spinlock_t lock;
+ u32 io_gpi;
+ u32 io_gpo;
+};
+
+/* resolve a GPIx into the corresponding bit position */
+static inline u_int32_t gpi_i_bit(int i)
+{
+ if (i < 10)
+ return 1 << i;
+ else
+ return 1 << (i + 14);
+}
+
+static inline u_int32_t gpo_o_bit(int i)
+{
+ if (i < 11)
+ return 1 << i;
+ else
+ return 1 << (i + 14);
+}
+
+static inline u_int32_t gpio_i_bit(int i)
+{
+ if (i < 14)
+ return 1 << (i + 10);
+ else
+ return 1 << (i + 14);
+}
+
+static inline u_int32_t gpio_o_bit(int i)
+{
+ if (i < 14)
+ return 1 << (i + 11);
+ else
+ return 1 << (i + 13);
+}
+
+/* Mapping between numeric GPIO ID and the actual GPIO hardware numbering:
+ * 0..13 GPI 0..13
+ * 14..26 GPO 0..12
+ * 27..41 GPIO 0..14
+ */
+
+static int vx855gpio_direction_input(struct gpio_chip *gpio,
+ unsigned int nr)
+{
+ struct vx855_gpio *vg = gpiochip_get_data(gpio);
+ unsigned long flags;
+ u_int32_t reg_out;
+
+ /* Real GPI bits are always in input direction */
+ if (nr < NR_VX855_GPI)
+ return 0;
+
+ /* Real GPO bits cannot be put in output direction */
+ if (nr < NR_VX855_GPInO)
+ return -EINVAL;
+
+ /* Open Drain GPIO have to be set to one */
+ spin_lock_irqsave(&vg->lock, flags);
+ reg_out = inl(vg->io_gpo);
+ reg_out |= gpio_o_bit(nr - NR_VX855_GPInO);
+ outl(reg_out, vg->io_gpo);
+ spin_unlock_irqrestore(&vg->lock, flags);
+
+ return 0;
+}
+
+static int vx855gpio_get(struct gpio_chip *gpio, unsigned int nr)
+{
+ struct vx855_gpio *vg = gpiochip_get_data(gpio);
+ u_int32_t reg_in;
+ int ret = 0;
+
+ if (nr < NR_VX855_GPI) {
+ reg_in = inl(vg->io_gpi);
+ if (reg_in & gpi_i_bit(nr))
+ ret = 1;
+ } else if (nr < NR_VX855_GPInO) {
+ /* GPO don't have an input bit, we need to read it
+ * back from the output register */
+ reg_in = inl(vg->io_gpo);
+ if (reg_in & gpo_o_bit(nr - NR_VX855_GPI))
+ ret = 1;
+ } else {
+ reg_in = inl(vg->io_gpi);
+ if (reg_in & gpio_i_bit(nr - NR_VX855_GPInO))
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static void vx855gpio_set(struct gpio_chip *gpio, unsigned int nr,
+ int val)
+{
+ struct vx855_gpio *vg = gpiochip_get_data(gpio);
+ unsigned long flags;
+ u_int32_t reg_out;
+
+ /* True GPI cannot be switched to output mode */
+ if (nr < NR_VX855_GPI)
+ return;
+
+ spin_lock_irqsave(&vg->lock, flags);
+ reg_out = inl(vg->io_gpo);
+ if (nr < NR_VX855_GPInO) {
+ if (val)
+ reg_out |= gpo_o_bit(nr - NR_VX855_GPI);
+ else
+ reg_out &= ~gpo_o_bit(nr - NR_VX855_GPI);
+ } else {
+ if (val)
+ reg_out |= gpio_o_bit(nr - NR_VX855_GPInO);
+ else
+ reg_out &= ~gpio_o_bit(nr - NR_VX855_GPInO);
+ }
+ outl(reg_out, vg->io_gpo);
+ spin_unlock_irqrestore(&vg->lock, flags);
+}
+
+static int vx855gpio_direction_output(struct gpio_chip *gpio,
+ unsigned int nr, int val)
+{
+ /* True GPI cannot be switched to output mode */
+ if (nr < NR_VX855_GPI)
+ return -EINVAL;
+
+ /* True GPO don't need to be switched to output mode,
+ * and GPIO are open-drain, i.e. also need no switching,
+ * so all we do is set the level */
+ vx855gpio_set(gpio, nr, val);
+
+ return 0;
+}
+
+static int vx855gpio_set_config(struct gpio_chip *gpio, unsigned int nr,
+ unsigned long config)
+{
+ enum pin_config_param param = pinconf_to_config_param(config);
+
+ /* The GPI cannot be single-ended */
+ if (nr < NR_VX855_GPI)
+ return -EINVAL;
+
+ /* The GPO's are push-pull */
+ if (nr < NR_VX855_GPInO) {
+ if (param != PIN_CONFIG_DRIVE_PUSH_PULL)
+ return -ENOTSUPP;
+ return 0;
+ }
+
+ /* The GPIO's are open drain */
+ if (param != PIN_CONFIG_DRIVE_OPEN_DRAIN)
+ return -ENOTSUPP;
+
+ return 0;
+}
+
+static const char *vx855gpio_names[NR_VX855_GP] = {
+ "VX855_GPI0", "VX855_GPI1", "VX855_GPI2", "VX855_GPI3", "VX855_GPI4",
+ "VX855_GPI5", "VX855_GPI6", "VX855_GPI7", "VX855_GPI8", "VX855_GPI9",
+ "VX855_GPI10", "VX855_GPI11", "VX855_GPI12", "VX855_GPI13",
+ "VX855_GPO0", "VX855_GPO1", "VX855_GPO2", "VX855_GPO3", "VX855_GPO4",
+ "VX855_GPO5", "VX855_GPO6", "VX855_GPO7", "VX855_GPO8", "VX855_GPO9",
+ "VX855_GPO10", "VX855_GPO11", "VX855_GPO12",
+ "VX855_GPIO0", "VX855_GPIO1", "VX855_GPIO2", "VX855_GPIO3",
+ "VX855_GPIO4", "VX855_GPIO5", "VX855_GPIO6", "VX855_GPIO7",
+ "VX855_GPIO8", "VX855_GPIO9", "VX855_GPIO10", "VX855_GPIO11",
+ "VX855_GPIO12", "VX855_GPIO13", "VX855_GPIO14"
+};
+
+static void vx855gpio_gpio_setup(struct vx855_gpio *vg)
+{
+ struct gpio_chip *c = &vg->gpio;
+
+ c->label = "VX855 South Bridge";
+ c->owner = THIS_MODULE;
+ c->direction_input = vx855gpio_direction_input;
+ c->direction_output = vx855gpio_direction_output;
+ c->get = vx855gpio_get;
+ c->set = vx855gpio_set;
+ c->set_config = vx855gpio_set_config;
+ c->dbg_show = NULL;
+ c->base = 0;
+ c->ngpio = NR_VX855_GP;
+ c->can_sleep = false;
+ c->names = vx855gpio_names;
+}
+
+/* This platform device is ordinarily registered by the vx855 mfd driver */
+static int vx855gpio_probe(struct platform_device *pdev)
+{
+ struct resource *res_gpi;
+ struct resource *res_gpo;
+ struct vx855_gpio *vg;
+
+ res_gpi = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ res_gpo = platform_get_resource(pdev, IORESOURCE_IO, 1);
+ if (!res_gpi || !res_gpo)
+ return -EBUSY;
+
+ vg = devm_kzalloc(&pdev->dev, sizeof(*vg), GFP_KERNEL);
+ if (!vg)
+ return -ENOMEM;
+
+ dev_info(&pdev->dev, "found VX855 GPIO controller\n");
+ vg->io_gpi = res_gpi->start;
+ vg->io_gpo = res_gpo->start;
+ spin_lock_init(&vg->lock);
+
+ /*
+ * A single byte is used to control various GPIO ports on the VX855,
+ * and in the case of the OLPC XO-1.5, some of those ports are used
+ * for switches that are interpreted and exposed through ACPI. ACPI
+ * will have reserved the region, so our own reservation will not
+ * succeed. Ignore and continue.
+ */
+
+ if (!devm_request_region(&pdev->dev, res_gpi->start,
+ resource_size(res_gpi), MODULE_NAME "_gpi"))
+ dev_warn(&pdev->dev,
+ "GPI I/O resource busy, probably claimed by ACPI\n");
+
+ if (!devm_request_region(&pdev->dev, res_gpo->start,
+ resource_size(res_gpo), MODULE_NAME "_gpo"))
+ dev_warn(&pdev->dev,
+ "GPO I/O resource busy, probably claimed by ACPI\n");
+
+ vx855gpio_gpio_setup(vg);
+
+ return devm_gpiochip_add_data(&pdev->dev, &vg->gpio, vg);
+}
+
+static struct platform_driver vx855gpio_driver = {
+ .driver = {
+ .name = MODULE_NAME,
+ },
+ .probe = vx855gpio_probe,
+};
+
+module_platform_driver(vx855gpio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <HaraldWelte@viatech.com>");
+MODULE_DESCRIPTION("GPIO driver for the VIA VX855 chipset");
+MODULE_ALIAS("platform:vx855_gpio");
diff --git a/drivers/gpio/gpio-wcd934x.c b/drivers/gpio/gpio-wcd934x.c
new file mode 100644
index 0000000000..2bba27b139
--- /dev/null
+++ b/drivers/gpio/gpio-wcd934x.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2019, Linaro Limited
+
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define WCD_PIN_MASK(p) BIT(p)
+#define WCD_REG_DIR_CTL_OFFSET 0x42
+#define WCD_REG_VAL_CTL_OFFSET 0x43
+#define WCD934X_NPINS 5
+
+struct wcd_gpio_data {
+ struct regmap *map;
+ struct gpio_chip chip;
+};
+
+static int wcd_gpio_get_direction(struct gpio_chip *chip, unsigned int pin)
+{
+ struct wcd_gpio_data *data = gpiochip_get_data(chip);
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->map, WCD_REG_DIR_CTL_OFFSET, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value & WCD_PIN_MASK(pin))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int wcd_gpio_direction_input(struct gpio_chip *chip, unsigned int pin)
+{
+ struct wcd_gpio_data *data = gpiochip_get_data(chip);
+
+ return regmap_update_bits(data->map, WCD_REG_DIR_CTL_OFFSET,
+ WCD_PIN_MASK(pin), 0);
+}
+
+static int wcd_gpio_direction_output(struct gpio_chip *chip, unsigned int pin,
+ int val)
+{
+ struct wcd_gpio_data *data = gpiochip_get_data(chip);
+
+ regmap_update_bits(data->map, WCD_REG_DIR_CTL_OFFSET,
+ WCD_PIN_MASK(pin), WCD_PIN_MASK(pin));
+
+ return regmap_update_bits(data->map, WCD_REG_VAL_CTL_OFFSET,
+ WCD_PIN_MASK(pin),
+ val ? WCD_PIN_MASK(pin) : 0);
+}
+
+static int wcd_gpio_get(struct gpio_chip *chip, unsigned int pin)
+{
+ struct wcd_gpio_data *data = gpiochip_get_data(chip);
+ unsigned int value;
+
+ regmap_read(data->map, WCD_REG_VAL_CTL_OFFSET, &value);
+
+ return !!(value & WCD_PIN_MASK(pin));
+}
+
+static void wcd_gpio_set(struct gpio_chip *chip, unsigned int pin, int val)
+{
+ struct wcd_gpio_data *data = gpiochip_get_data(chip);
+
+ regmap_update_bits(data->map, WCD_REG_VAL_CTL_OFFSET,
+ WCD_PIN_MASK(pin), val ? WCD_PIN_MASK(pin) : 0);
+}
+
+static int wcd_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct wcd_gpio_data *data;
+ struct gpio_chip *chip;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->map = dev_get_regmap(dev->parent, NULL);
+ if (!data->map) {
+ dev_err(dev, "%s: failed to get regmap\n", __func__);
+ return -EINVAL;
+ }
+
+ chip = &data->chip;
+ chip->direction_input = wcd_gpio_direction_input;
+ chip->direction_output = wcd_gpio_direction_output;
+ chip->get_direction = wcd_gpio_get_direction;
+ chip->get = wcd_gpio_get;
+ chip->set = wcd_gpio_set;
+ chip->parent = dev;
+ chip->base = -1;
+ chip->ngpio = WCD934X_NPINS;
+ chip->label = dev_name(dev);
+ chip->can_sleep = false;
+
+ return devm_gpiochip_add_data(dev, chip, data);
+}
+
+static const struct of_device_id wcd_gpio_of_match[] = {
+ { .compatible = "qcom,wcd9340-gpio" },
+ { .compatible = "qcom,wcd9341-gpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, wcd_gpio_of_match);
+
+static struct platform_driver wcd_gpio_driver = {
+ .driver = {
+ .name = "wcd934x-gpio",
+ .of_match_table = wcd_gpio_of_match,
+ },
+ .probe = wcd_gpio_probe,
+};
+
+module_platform_driver(wcd_gpio_driver);
+MODULE_DESCRIPTION("Qualcomm Technologies, Inc WCD GPIO control driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-wcove.c b/drivers/gpio/gpio-wcove.c
new file mode 100644
index 0000000000..c18b6b4738
--- /dev/null
+++ b/drivers/gpio/gpio-wcove.c
@@ -0,0 +1,514 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Whiskey Cove PMIC GPIO Driver
+ *
+ * This driver is written based on gpio-crystalcove.c
+ *
+ * Copyright (C) 2016 Intel Corporation. All rights reserved.
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/seq_file.h>
+
+/*
+ * Whiskey Cove PMIC has 13 physical GPIO pins divided into 3 banks:
+ * Bank 0: Pin 0 - 6
+ * Bank 1: Pin 7 - 10
+ * Bank 2: Pin 11 - 12
+ * Each pin has one output control register and one input control register.
+ */
+#define BANK0_NR_PINS 7
+#define BANK1_NR_PINS 4
+#define BANK2_NR_PINS 2
+#define WCOVE_GPIO_NUM (BANK0_NR_PINS + BANK1_NR_PINS + BANK2_NR_PINS)
+#define WCOVE_VGPIO_NUM 94
+/* GPIO output control registers (one per pin): 0x4e44 - 0x4e50 */
+#define GPIO_OUT_CTRL_BASE 0x4e44
+/* GPIO input control registers (one per pin): 0x4e51 - 0x4e5d */
+#define GPIO_IN_CTRL_BASE 0x4e51
+
+/*
+ * GPIO interrupts are organized in two groups:
+ * Group 0: Bank 0 pins (Pin 0 - 6)
+ * Group 1: Bank 1 and Bank 2 pins (Pin 7 - 12)
+ * Each group has two registers (one bit per pin): status and mask.
+ */
+#define GROUP0_NR_IRQS 7
+#define GROUP1_NR_IRQS 6
+#define IRQ_MASK_BASE 0x4e19
+#define IRQ_STATUS_BASE 0x4e0b
+#define GPIO_IRQ0_MASK GENMASK(6, 0)
+#define GPIO_IRQ1_MASK GENMASK(5, 0)
+#define UPDATE_IRQ_TYPE BIT(0)
+#define UPDATE_IRQ_MASK BIT(1)
+
+#define CTLI_INTCNT_DIS (0 << 1)
+#define CTLI_INTCNT_NE (1 << 1)
+#define CTLI_INTCNT_PE (2 << 1)
+#define CTLI_INTCNT_BE (3 << 1)
+
+#define CTLO_DIR_IN (0 << 5)
+#define CTLO_DIR_OUT (1 << 5)
+
+#define CTLO_DRV_MASK (1 << 4)
+#define CTLO_DRV_OD (0 << 4)
+#define CTLO_DRV_CMOS (1 << 4)
+
+#define CTLO_DRV_REN (1 << 3)
+
+#define CTLO_RVAL_2KDOWN (0 << 1)
+#define CTLO_RVAL_2KUP (1 << 1)
+#define CTLO_RVAL_50KDOWN (2 << 1)
+#define CTLO_RVAL_50KUP (3 << 1)
+
+#define CTLO_INPUT_SET (CTLO_DRV_CMOS | CTLO_DRV_REN | CTLO_RVAL_2KUP)
+#define CTLO_OUTPUT_SET (CTLO_DIR_OUT | CTLO_INPUT_SET)
+
+enum ctrl_register {
+ CTRL_IN,
+ CTRL_OUT,
+ IRQ_STATUS,
+ IRQ_MASK,
+};
+
+/*
+ * struct wcove_gpio - Whiskey Cove GPIO controller
+ * @buslock: for bus lock/sync and unlock.
+ * @chip: the abstract gpio_chip structure.
+ * @dev: the gpio device
+ * @regmap: the regmap from the parent device.
+ * @regmap_irq_chip: the regmap of the gpio irq chip.
+ * @update: pending IRQ setting update, to be written to the chip upon unlock.
+ * @intcnt: the Interrupt Detect value to be written.
+ * @set_irq_mask: true if the IRQ mask needs to be set, false to clear.
+ */
+struct wcove_gpio {
+ struct mutex buslock;
+ struct gpio_chip chip;
+ struct device *dev;
+ struct regmap *regmap;
+ struct regmap_irq_chip_data *regmap_irq_chip;
+ int update;
+ int intcnt;
+ bool set_irq_mask;
+};
+
+static inline int to_reg(int gpio, enum ctrl_register type)
+{
+ unsigned int reg = type == CTRL_IN ? GPIO_IN_CTRL_BASE : GPIO_OUT_CTRL_BASE;
+
+ if (gpio >= WCOVE_GPIO_NUM)
+ return -EOPNOTSUPP;
+
+ return reg + gpio;
+}
+
+static inline int to_ireg(int gpio, enum ctrl_register type, unsigned int *mask)
+{
+ unsigned int reg = type == IRQ_STATUS ? IRQ_STATUS_BASE : IRQ_MASK_BASE;
+
+ if (gpio < GROUP0_NR_IRQS) {
+ reg += 0;
+ *mask = BIT(gpio);
+ } else {
+ reg += 1;
+ *mask = BIT(gpio - GROUP0_NR_IRQS);
+ }
+
+ return reg;
+}
+
+static void wcove_update_irq_mask(struct wcove_gpio *wg, irq_hw_number_t gpio)
+{
+ unsigned int mask, reg = to_ireg(gpio, IRQ_MASK, &mask);
+
+ if (wg->set_irq_mask)
+ regmap_set_bits(wg->regmap, reg, mask);
+ else
+ regmap_clear_bits(wg->regmap, reg, mask);
+}
+
+static void wcove_update_irq_ctrl(struct wcove_gpio *wg, irq_hw_number_t gpio)
+{
+ int reg = to_reg(gpio, CTRL_IN);
+
+ regmap_update_bits(wg->regmap, reg, CTLI_INTCNT_BE, wg->intcnt);
+}
+
+static int wcove_gpio_dir_in(struct gpio_chip *chip, unsigned int gpio)
+{
+ struct wcove_gpio *wg = gpiochip_get_data(chip);
+ int reg = to_reg(gpio, CTRL_OUT);
+
+ if (reg < 0)
+ return 0;
+
+ return regmap_write(wg->regmap, reg, CTLO_INPUT_SET);
+}
+
+static int wcove_gpio_dir_out(struct gpio_chip *chip, unsigned int gpio,
+ int value)
+{
+ struct wcove_gpio *wg = gpiochip_get_data(chip);
+ int reg = to_reg(gpio, CTRL_OUT);
+
+ if (reg < 0)
+ return 0;
+
+ return regmap_write(wg->regmap, reg, CTLO_OUTPUT_SET | value);
+}
+
+static int wcove_gpio_get_direction(struct gpio_chip *chip, unsigned int gpio)
+{
+ struct wcove_gpio *wg = gpiochip_get_data(chip);
+ unsigned int val;
+ int ret, reg = to_reg(gpio, CTRL_OUT);
+
+ if (reg < 0)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ ret = regmap_read(wg->regmap, reg, &val);
+ if (ret)
+ return ret;
+
+ if (val & CTLO_DIR_OUT)
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+static int wcove_gpio_get(struct gpio_chip *chip, unsigned int gpio)
+{
+ struct wcove_gpio *wg = gpiochip_get_data(chip);
+ unsigned int val;
+ int ret, reg = to_reg(gpio, CTRL_IN);
+
+ if (reg < 0)
+ return 0;
+
+ ret = regmap_read(wg->regmap, reg, &val);
+ if (ret)
+ return ret;
+
+ return val & 0x1;
+}
+
+static void wcove_gpio_set(struct gpio_chip *chip, unsigned int gpio, int value)
+{
+ struct wcove_gpio *wg = gpiochip_get_data(chip);
+ int reg = to_reg(gpio, CTRL_OUT);
+
+ if (reg < 0)
+ return;
+
+ if (value)
+ regmap_set_bits(wg->regmap, reg, 1);
+ else
+ regmap_clear_bits(wg->regmap, reg, 1);
+}
+
+static int wcove_gpio_set_config(struct gpio_chip *chip, unsigned int gpio,
+ unsigned long config)
+{
+ struct wcove_gpio *wg = gpiochip_get_data(chip);
+ int reg = to_reg(gpio, CTRL_OUT);
+
+ if (reg < 0)
+ return 0;
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ return regmap_update_bits(wg->regmap, reg, CTLO_DRV_MASK,
+ CTLO_DRV_OD);
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ return regmap_update_bits(wg->regmap, reg, CTLO_DRV_MASK,
+ CTLO_DRV_CMOS);
+ default:
+ break;
+ }
+
+ return -ENOTSUPP;
+}
+
+static int wcove_irq_type(struct irq_data *data, unsigned int type)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct wcove_gpio *wg = gpiochip_get_data(chip);
+ irq_hw_number_t gpio = irqd_to_hwirq(data);
+
+ if (gpio >= WCOVE_GPIO_NUM)
+ return 0;
+
+ switch (type) {
+ case IRQ_TYPE_NONE:
+ wg->intcnt = CTLI_INTCNT_DIS;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ wg->intcnt = CTLI_INTCNT_BE;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ wg->intcnt = CTLI_INTCNT_PE;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ wg->intcnt = CTLI_INTCNT_NE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wg->update |= UPDATE_IRQ_TYPE;
+
+ return 0;
+}
+
+static void wcove_bus_lock(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct wcove_gpio *wg = gpiochip_get_data(chip);
+
+ mutex_lock(&wg->buslock);
+}
+
+static void wcove_bus_sync_unlock(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct wcove_gpio *wg = gpiochip_get_data(chip);
+ irq_hw_number_t gpio = irqd_to_hwirq(data);
+
+ if (wg->update & UPDATE_IRQ_TYPE)
+ wcove_update_irq_ctrl(wg, gpio);
+ if (wg->update & UPDATE_IRQ_MASK)
+ wcove_update_irq_mask(wg, gpio);
+ wg->update = 0;
+
+ mutex_unlock(&wg->buslock);
+}
+
+static void wcove_irq_unmask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct wcove_gpio *wg = gpiochip_get_data(chip);
+ irq_hw_number_t gpio = irqd_to_hwirq(data);
+
+ if (gpio >= WCOVE_GPIO_NUM)
+ return;
+
+ gpiochip_enable_irq(chip, gpio);
+
+ wg->set_irq_mask = false;
+ wg->update |= UPDATE_IRQ_MASK;
+}
+
+static void wcove_irq_mask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct wcove_gpio *wg = gpiochip_get_data(chip);
+ irq_hw_number_t gpio = irqd_to_hwirq(data);
+
+ if (gpio >= WCOVE_GPIO_NUM)
+ return;
+
+ wg->set_irq_mask = true;
+ wg->update |= UPDATE_IRQ_MASK;
+
+ gpiochip_disable_irq(chip, gpio);
+}
+
+static const struct irq_chip wcove_irqchip = {
+ .name = "Whiskey Cove",
+ .irq_mask = wcove_irq_mask,
+ .irq_unmask = wcove_irq_unmask,
+ .irq_set_type = wcove_irq_type,
+ .irq_bus_lock = wcove_bus_lock,
+ .irq_bus_sync_unlock = wcove_bus_sync_unlock,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static irqreturn_t wcove_gpio_irq_handler(int irq, void *data)
+{
+ struct wcove_gpio *wg = (struct wcove_gpio *)data;
+ unsigned int virq, gpio;
+ unsigned long pending;
+ u8 p[2];
+
+ if (regmap_bulk_read(wg->regmap, IRQ_STATUS_BASE, p, 2)) {
+ dev_err(wg->dev, "Failed to read irq status register\n");
+ return IRQ_NONE;
+ }
+
+ pending = (p[0] & GPIO_IRQ0_MASK) | ((p[1] & GPIO_IRQ1_MASK) << 7);
+ if (!pending)
+ return IRQ_NONE;
+
+ /* Iterate until no interrupt is pending */
+ while (pending) {
+ /* One iteration is for all pending bits */
+ for_each_set_bit(gpio, &pending, WCOVE_GPIO_NUM) {
+ unsigned int mask, reg = to_ireg(gpio, IRQ_STATUS, &mask);
+
+ virq = irq_find_mapping(wg->chip.irq.domain, gpio);
+ handle_nested_irq(virq);
+ regmap_set_bits(wg->regmap, reg, mask);
+ }
+
+ /* Next iteration */
+ if (regmap_bulk_read(wg->regmap, IRQ_STATUS_BASE, p, 2)) {
+ dev_err(wg->dev, "Failed to read irq status\n");
+ break;
+ }
+
+ pending = (p[0] & GPIO_IRQ0_MASK) | ((p[1] & GPIO_IRQ1_MASK) << 7);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void wcove_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ unsigned int ctlo, ctli, irq_mask, irq_status;
+ struct wcove_gpio *wg = gpiochip_get_data(chip);
+ int gpio, mask, ret = 0;
+
+ for (gpio = 0; gpio < WCOVE_GPIO_NUM; gpio++) {
+ ret += regmap_read(wg->regmap, to_reg(gpio, CTRL_OUT), &ctlo);
+ ret += regmap_read(wg->regmap, to_reg(gpio, CTRL_IN), &ctli);
+ if (ret) {
+ dev_err(wg->dev, "Failed to read registers: CTRL out/in\n");
+ break;
+ }
+
+ ret += regmap_read(wg->regmap, to_ireg(gpio, IRQ_MASK, &mask), &irq_mask);
+ ret += regmap_read(wg->regmap, to_ireg(gpio, IRQ_STATUS, &mask), &irq_status);
+ if (ret) {
+ dev_err(wg->dev, "Failed to read registers: IRQ status/mask\n");
+ break;
+ }
+
+ seq_printf(s, " gpio-%-2d %s %s %s %s ctlo=%2x,%s %s\n",
+ gpio, ctlo & CTLO_DIR_OUT ? "out" : "in ",
+ ctli & 0x1 ? "hi" : "lo",
+ ctli & CTLI_INTCNT_NE ? "fall" : " ",
+ ctli & CTLI_INTCNT_PE ? "rise" : " ",
+ ctlo,
+ irq_mask & mask ? "mask " : "unmask",
+ irq_status & mask ? "pending" : " ");
+ }
+}
+
+static int wcove_gpio_probe(struct platform_device *pdev)
+{
+ struct intel_soc_pmic *pmic;
+ struct wcove_gpio *wg;
+ int virq, ret, irq;
+ struct device *dev;
+ struct gpio_irq_chip *girq;
+
+ /*
+ * This gpio platform device is created by a mfd device (see
+ * drivers/mfd/intel_soc_pmic_bxtwc.c for details). Information
+ * shared by all sub-devices created by the mfd device, the regmap
+ * pointer for instance, is stored as driver data of the mfd device
+ * driver.
+ */
+ pmic = dev_get_drvdata(pdev->dev.parent);
+ if (!pmic)
+ return -ENODEV;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ dev = &pdev->dev;
+
+ wg = devm_kzalloc(dev, sizeof(*wg), GFP_KERNEL);
+ if (!wg)
+ return -ENOMEM;
+
+ wg->regmap_irq_chip = pmic->irq_chip_data;
+
+ platform_set_drvdata(pdev, wg);
+
+ mutex_init(&wg->buslock);
+ wg->chip.label = KBUILD_MODNAME;
+ wg->chip.direction_input = wcove_gpio_dir_in;
+ wg->chip.direction_output = wcove_gpio_dir_out;
+ wg->chip.get_direction = wcove_gpio_get_direction;
+ wg->chip.get = wcove_gpio_get;
+ wg->chip.set = wcove_gpio_set;
+ wg->chip.set_config = wcove_gpio_set_config;
+ wg->chip.base = -1;
+ wg->chip.ngpio = WCOVE_VGPIO_NUM;
+ wg->chip.can_sleep = true;
+ wg->chip.parent = pdev->dev.parent;
+ wg->chip.dbg_show = wcove_gpio_dbg_show;
+ wg->dev = dev;
+ wg->regmap = pmic->regmap;
+
+ virq = regmap_irq_get_virq(wg->regmap_irq_chip, irq);
+ if (virq < 0) {
+ dev_err(dev, "Failed to get virq by irq %d\n", irq);
+ return virq;
+ }
+
+ girq = &wg->chip.irq;
+ gpio_irq_chip_set_chip(girq, &wcove_irqchip);
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ girq->threaded = true;
+
+ ret = devm_request_threaded_irq(dev, virq, NULL, wcove_gpio_irq_handler,
+ IRQF_ONESHOT, pdev->name, wg);
+ if (ret) {
+ dev_err(dev, "Failed to request irq %d\n", virq);
+ return ret;
+ }
+
+ ret = devm_gpiochip_add_data(dev, &wg->chip, wg);
+ if (ret) {
+ dev_err(dev, "Failed to add gpiochip: %d\n", ret);
+ return ret;
+ }
+
+ /* Enable GPIO0 interrupts */
+ ret = regmap_clear_bits(wg->regmap, IRQ_MASK_BASE + 0, GPIO_IRQ0_MASK);
+ if (ret)
+ return ret;
+
+ /* Enable GPIO1 interrupts */
+ ret = regmap_clear_bits(wg->regmap, IRQ_MASK_BASE + 1, GPIO_IRQ1_MASK);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * Whiskey Cove PMIC itself is a analog device(but with digital control
+ * interface) providing power management support for other devices in
+ * the accompanied SoC, so we have no .pm for Whiskey Cove GPIO driver.
+ */
+static struct platform_driver wcove_gpio_driver = {
+ .driver = {
+ .name = "bxt_wcove_gpio",
+ },
+ .probe = wcove_gpio_probe,
+};
+
+module_platform_driver(wcove_gpio_driver);
+
+MODULE_AUTHOR("Ajay Thomas <ajay.thomas.david.rajamanickam@intel.com>");
+MODULE_AUTHOR("Bin Gao <bin.gao@intel.com>");
+MODULE_DESCRIPTION("Intel Whiskey Cove GPIO Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:bxt_wcove_gpio");
diff --git a/drivers/gpio/gpio-winbond.c b/drivers/gpio/gpio-winbond.c
new file mode 100644
index 0000000000..4b61d975cc
--- /dev/null
+++ b/drivers/gpio/gpio-winbond.c
@@ -0,0 +1,733 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * GPIO interface for Winbond Super I/O chips
+ * Currently, only W83627UHG (Nuvoton NCT6627UD) is supported.
+ *
+ * Author: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/gpio/driver.h>
+#include <linux/ioport.h>
+#include <linux/isa.h>
+#include <linux/module.h>
+
+#define WB_GPIO_DRIVER_NAME KBUILD_MODNAME
+
+#define WB_SIO_BASE 0x2e
+#define WB_SIO_BASE_HIGH 0x4e
+
+#define WB_SIO_EXT_ENTER_KEY 0x87
+#define WB_SIO_EXT_EXIT_KEY 0xaa
+
+/* global chip registers */
+
+#define WB_SIO_REG_LOGICAL 0x07
+
+#define WB_SIO_REG_CHIP_MSB 0x20
+#define WB_SIO_REG_CHIP_LSB 0x21
+
+#define WB_SIO_CHIP_ID_W83627UHG 0xa230
+#define WB_SIO_CHIP_ID_W83627UHG_MASK GENMASK(15, 4)
+
+#define WB_SIO_REG_DPD 0x22
+#define WB_SIO_REG_DPD_UARTA 4
+#define WB_SIO_REG_DPD_UARTB 5
+
+#define WB_SIO_REG_IDPD 0x23
+#define WB_SIO_REG_IDPD_UARTC 4
+#define WB_SIO_REG_IDPD_UARTD 5
+#define WB_SIO_REG_IDPD_UARTE 6
+#define WB_SIO_REG_IDPD_UARTF 7
+
+#define WB_SIO_REG_GLOBAL_OPT 0x24
+#define WB_SIO_REG_GO_ENFDC 1
+
+#define WB_SIO_REG_OVTGPIO3456 0x29
+#define WB_SIO_REG_OG3456_G3PP 3
+#define WB_SIO_REG_OG3456_G4PP 4
+#define WB_SIO_REG_OG3456_G5PP 5
+#define WB_SIO_REG_OG3456_G6PP 7
+
+#define WB_SIO_REG_I2C_PS 0x2a
+#define WB_SIO_REG_I2CPS_I2CFS 1
+
+#define WB_SIO_REG_GPIO1_MF 0x2c
+#define WB_SIO_REG_G1MF_G1PP 6
+#define WB_SIO_REG_G1MF_G2PP 7
+#define WB_SIO_REG_G1MF_FS_MASK GENMASK(1, 0)
+#define WB_SIO_REG_G1MF_FS_IR_OFF 0
+#define WB_SIO_REG_G1MF_FS_IR 1
+#define WB_SIO_REG_G1MF_FS_GPIO1 2
+#define WB_SIO_REG_G1MF_FS_UARTB 3
+
+/* not an actual device number, just a value meaning 'no device' */
+#define WB_SIO_DEV_NONE 0xff
+
+/* registers with offsets >= 0x30 are specific for a particular device */
+
+/* UART B logical device */
+#define WB_SIO_DEV_UARTB 0x03
+#define WB_SIO_UARTB_REG_ENABLE 0x30
+#define WB_SIO_UARTB_ENABLE_ON 0
+
+/* UART C logical device */
+#define WB_SIO_DEV_UARTC 0x06
+#define WB_SIO_UARTC_REG_ENABLE 0x30
+#define WB_SIO_UARTC_ENABLE_ON 0
+
+/* GPIO3, GPIO4 logical device */
+#define WB_SIO_DEV_GPIO34 0x07
+#define WB_SIO_GPIO34_REG_ENABLE 0x30
+#define WB_SIO_GPIO34_ENABLE_3 0
+#define WB_SIO_GPIO34_ENABLE_4 1
+#define WB_SIO_GPIO34_REG_IO3 0xe0
+#define WB_SIO_GPIO34_REG_DATA3 0xe1
+#define WB_SIO_GPIO34_REG_INV3 0xe2
+#define WB_SIO_GPIO34_REG_IO4 0xe4
+#define WB_SIO_GPIO34_REG_DATA4 0xe5
+#define WB_SIO_GPIO34_REG_INV4 0xe6
+
+/* WDTO, PLED, GPIO5, GPIO6 logical device */
+#define WB_SIO_DEV_WDGPIO56 0x08
+#define WB_SIO_WDGPIO56_REG_ENABLE 0x30
+#define WB_SIO_WDGPIO56_ENABLE_5 1
+#define WB_SIO_WDGPIO56_ENABLE_6 2
+#define WB_SIO_WDGPIO56_REG_IO5 0xe0
+#define WB_SIO_WDGPIO56_REG_DATA5 0xe1
+#define WB_SIO_WDGPIO56_REG_INV5 0xe2
+#define WB_SIO_WDGPIO56_REG_IO6 0xe4
+#define WB_SIO_WDGPIO56_REG_DATA6 0xe5
+#define WB_SIO_WDGPIO56_REG_INV6 0xe6
+
+/* GPIO1, GPIO2, SUSLED logical device */
+#define WB_SIO_DEV_GPIO12 0x09
+#define WB_SIO_GPIO12_REG_ENABLE 0x30
+#define WB_SIO_GPIO12_ENABLE_1 0
+#define WB_SIO_GPIO12_ENABLE_2 1
+#define WB_SIO_GPIO12_REG_IO1 0xe0
+#define WB_SIO_GPIO12_REG_DATA1 0xe1
+#define WB_SIO_GPIO12_REG_INV1 0xe2
+#define WB_SIO_GPIO12_REG_IO2 0xe4
+#define WB_SIO_GPIO12_REG_DATA2 0xe5
+#define WB_SIO_GPIO12_REG_INV2 0xe6
+
+/* UART D logical device */
+#define WB_SIO_DEV_UARTD 0x0d
+#define WB_SIO_UARTD_REG_ENABLE 0x30
+#define WB_SIO_UARTD_ENABLE_ON 0
+
+/* UART E logical device */
+#define WB_SIO_DEV_UARTE 0x0e
+#define WB_SIO_UARTE_REG_ENABLE 0x30
+#define WB_SIO_UARTE_ENABLE_ON 0
+
+/*
+ * for a description what a particular field of this struct means please see
+ * a description of the relevant module parameter at the bottom of this file
+ */
+struct winbond_gpio_params {
+ unsigned long base;
+ unsigned long gpios;
+ unsigned long ppgpios;
+ unsigned long odgpios;
+ bool pledgpio;
+ bool beepgpio;
+ bool i2cgpio;
+};
+
+static struct winbond_gpio_params params;
+
+static int winbond_sio_enter(unsigned long base)
+{
+ if (!request_muxed_region(base, 2, WB_GPIO_DRIVER_NAME))
+ return -EBUSY;
+
+ /*
+ * datasheet says two successive writes of the "key" value are needed
+ * in order for chip to enter the "Extended Function Mode"
+ */
+ outb(WB_SIO_EXT_ENTER_KEY, base);
+ outb(WB_SIO_EXT_ENTER_KEY, base);
+
+ return 0;
+}
+
+static void winbond_sio_select_logical(unsigned long base, u8 dev)
+{
+ outb(WB_SIO_REG_LOGICAL, base);
+ outb(dev, base + 1);
+}
+
+static void winbond_sio_leave(unsigned long base)
+{
+ outb(WB_SIO_EXT_EXIT_KEY, base);
+
+ release_region(base, 2);
+}
+
+static void winbond_sio_reg_write(unsigned long base, u8 reg, u8 data)
+{
+ outb(reg, base);
+ outb(data, base + 1);
+}
+
+static u8 winbond_sio_reg_read(unsigned long base, u8 reg)
+{
+ outb(reg, base);
+ return inb(base + 1);
+}
+
+static void winbond_sio_reg_bset(unsigned long base, u8 reg, u8 bit)
+{
+ u8 val;
+
+ val = winbond_sio_reg_read(base, reg);
+ val |= BIT(bit);
+ winbond_sio_reg_write(base, reg, val);
+}
+
+static void winbond_sio_reg_bclear(unsigned long base, u8 reg, u8 bit)
+{
+ u8 val;
+
+ val = winbond_sio_reg_read(base, reg);
+ val &= ~BIT(bit);
+ winbond_sio_reg_write(base, reg, val);
+}
+
+static bool winbond_sio_reg_btest(unsigned long base, u8 reg, u8 bit)
+{
+ return winbond_sio_reg_read(base, reg) & BIT(bit);
+}
+
+/**
+ * struct winbond_gpio_port_conflict - possibly conflicting device information
+ * @name: device name (NULL means no conflicting device defined)
+ * @dev: Super I/O logical device number where the testreg register
+ * is located (or WB_SIO_DEV_NONE - don't select any
+ * logical device)
+ * @testreg: register number where the testbit bit is located
+ * @testbit: index of a bit to check whether an actual conflict exists
+ * @warnonly: if set then a conflict isn't fatal (just warn about it),
+ * otherwise disable the particular GPIO port if a conflict
+ * is detected
+ */
+struct winbond_gpio_port_conflict {
+ const char *name;
+ u8 dev;
+ u8 testreg;
+ u8 testbit;
+ bool warnonly;
+};
+
+/**
+ * struct winbond_gpio_info - information about a particular GPIO port (device)
+ * @dev: Super I/O logical device number of the registers
+ * specified below
+ * @enablereg: port enable bit register number
+ * @enablebit: index of a port enable bit
+ * @outputreg: output driver mode bit register number
+ * @outputppbit: index of a push-pull output driver mode bit
+ * @ioreg: data direction register number
+ * @invreg: pin data inversion register number
+ * @datareg: pin data register number
+ * @conflict: description of a device that possibly conflicts with
+ * this port
+ */
+struct winbond_gpio_info {
+ u8 dev;
+ u8 enablereg;
+ u8 enablebit;
+ u8 outputreg;
+ u8 outputppbit;
+ u8 ioreg;
+ u8 invreg;
+ u8 datareg;
+ struct winbond_gpio_port_conflict conflict;
+};
+
+static const struct winbond_gpio_info winbond_gpio_infos[6] = {
+ { /* 0 */
+ .dev = WB_SIO_DEV_GPIO12,
+ .enablereg = WB_SIO_GPIO12_REG_ENABLE,
+ .enablebit = WB_SIO_GPIO12_ENABLE_1,
+ .outputreg = WB_SIO_REG_GPIO1_MF,
+ .outputppbit = WB_SIO_REG_G1MF_G1PP,
+ .ioreg = WB_SIO_GPIO12_REG_IO1,
+ .invreg = WB_SIO_GPIO12_REG_INV1,
+ .datareg = WB_SIO_GPIO12_REG_DATA1,
+ .conflict = {
+ .name = "UARTB",
+ .dev = WB_SIO_DEV_UARTB,
+ .testreg = WB_SIO_UARTB_REG_ENABLE,
+ .testbit = WB_SIO_UARTB_ENABLE_ON,
+ .warnonly = true
+ }
+ },
+ { /* 1 */
+ .dev = WB_SIO_DEV_GPIO12,
+ .enablereg = WB_SIO_GPIO12_REG_ENABLE,
+ .enablebit = WB_SIO_GPIO12_ENABLE_2,
+ .outputreg = WB_SIO_REG_GPIO1_MF,
+ .outputppbit = WB_SIO_REG_G1MF_G2PP,
+ .ioreg = WB_SIO_GPIO12_REG_IO2,
+ .invreg = WB_SIO_GPIO12_REG_INV2,
+ .datareg = WB_SIO_GPIO12_REG_DATA2
+ /* special conflict handling so doesn't use conflict data */
+ },
+ { /* 2 */
+ .dev = WB_SIO_DEV_GPIO34,
+ .enablereg = WB_SIO_GPIO34_REG_ENABLE,
+ .enablebit = WB_SIO_GPIO34_ENABLE_3,
+ .outputreg = WB_SIO_REG_OVTGPIO3456,
+ .outputppbit = WB_SIO_REG_OG3456_G3PP,
+ .ioreg = WB_SIO_GPIO34_REG_IO3,
+ .invreg = WB_SIO_GPIO34_REG_INV3,
+ .datareg = WB_SIO_GPIO34_REG_DATA3,
+ .conflict = {
+ .name = "UARTC",
+ .dev = WB_SIO_DEV_UARTC,
+ .testreg = WB_SIO_UARTC_REG_ENABLE,
+ .testbit = WB_SIO_UARTC_ENABLE_ON,
+ .warnonly = true
+ }
+ },
+ { /* 3 */
+ .dev = WB_SIO_DEV_GPIO34,
+ .enablereg = WB_SIO_GPIO34_REG_ENABLE,
+ .enablebit = WB_SIO_GPIO34_ENABLE_4,
+ .outputreg = WB_SIO_REG_OVTGPIO3456,
+ .outputppbit = WB_SIO_REG_OG3456_G4PP,
+ .ioreg = WB_SIO_GPIO34_REG_IO4,
+ .invreg = WB_SIO_GPIO34_REG_INV4,
+ .datareg = WB_SIO_GPIO34_REG_DATA4,
+ .conflict = {
+ .name = "UARTD",
+ .dev = WB_SIO_DEV_UARTD,
+ .testreg = WB_SIO_UARTD_REG_ENABLE,
+ .testbit = WB_SIO_UARTD_ENABLE_ON,
+ .warnonly = true
+ }
+ },
+ { /* 4 */
+ .dev = WB_SIO_DEV_WDGPIO56,
+ .enablereg = WB_SIO_WDGPIO56_REG_ENABLE,
+ .enablebit = WB_SIO_WDGPIO56_ENABLE_5,
+ .outputreg = WB_SIO_REG_OVTGPIO3456,
+ .outputppbit = WB_SIO_REG_OG3456_G5PP,
+ .ioreg = WB_SIO_WDGPIO56_REG_IO5,
+ .invreg = WB_SIO_WDGPIO56_REG_INV5,
+ .datareg = WB_SIO_WDGPIO56_REG_DATA5,
+ .conflict = {
+ .name = "UARTE",
+ .dev = WB_SIO_DEV_UARTE,
+ .testreg = WB_SIO_UARTE_REG_ENABLE,
+ .testbit = WB_SIO_UARTE_ENABLE_ON,
+ .warnonly = true
+ }
+ },
+ { /* 5 */
+ .dev = WB_SIO_DEV_WDGPIO56,
+ .enablereg = WB_SIO_WDGPIO56_REG_ENABLE,
+ .enablebit = WB_SIO_WDGPIO56_ENABLE_6,
+ .outputreg = WB_SIO_REG_OVTGPIO3456,
+ .outputppbit = WB_SIO_REG_OG3456_G6PP,
+ .ioreg = WB_SIO_WDGPIO56_REG_IO6,
+ .invreg = WB_SIO_WDGPIO56_REG_INV6,
+ .datareg = WB_SIO_WDGPIO56_REG_DATA6,
+ .conflict = {
+ .name = "FDC",
+ .dev = WB_SIO_DEV_NONE,
+ .testreg = WB_SIO_REG_GLOBAL_OPT,
+ .testbit = WB_SIO_REG_GO_ENFDC,
+ .warnonly = false
+ }
+ }
+};
+
+/* returns whether changing a pin is allowed */
+static bool winbond_gpio_get_info(unsigned int *gpio_num,
+ const struct winbond_gpio_info **info)
+{
+ bool allow_changing = true;
+ unsigned long i;
+
+ for_each_set_bit(i, &params.gpios, BITS_PER_LONG) {
+ if (*gpio_num < 8)
+ break;
+
+ *gpio_num -= 8;
+ }
+
+ *info = &winbond_gpio_infos[i];
+
+ /*
+ * GPIO2 (the second port) shares some pins with a basic PC
+ * functionality, which is very likely controlled by the firmware.
+ * Don't allow changing these pins by default.
+ */
+ if (i == 1) {
+ if (*gpio_num == 0 && !params.pledgpio)
+ allow_changing = false;
+ else if (*gpio_num == 1 && !params.beepgpio)
+ allow_changing = false;
+ else if ((*gpio_num == 5 || *gpio_num == 6) && !params.i2cgpio)
+ allow_changing = false;
+ }
+
+ return allow_changing;
+}
+
+static int winbond_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ unsigned long *base = gpiochip_get_data(gc);
+ const struct winbond_gpio_info *info;
+ bool val;
+ int ret;
+
+ winbond_gpio_get_info(&offset, &info);
+
+ ret = winbond_sio_enter(*base);
+ if (ret)
+ return ret;
+
+ winbond_sio_select_logical(*base, info->dev);
+
+ val = winbond_sio_reg_btest(*base, info->datareg, offset);
+ if (winbond_sio_reg_btest(*base, info->invreg, offset))
+ val = !val;
+
+ winbond_sio_leave(*base);
+
+ return val;
+}
+
+static int winbond_gpio_direction_in(struct gpio_chip *gc, unsigned int offset)
+{
+ unsigned long *base = gpiochip_get_data(gc);
+ const struct winbond_gpio_info *info;
+ int ret;
+
+ if (!winbond_gpio_get_info(&offset, &info))
+ return -EACCES;
+
+ ret = winbond_sio_enter(*base);
+ if (ret)
+ return ret;
+
+ winbond_sio_select_logical(*base, info->dev);
+
+ winbond_sio_reg_bset(*base, info->ioreg, offset);
+
+ winbond_sio_leave(*base);
+
+ return 0;
+}
+
+static int winbond_gpio_direction_out(struct gpio_chip *gc,
+ unsigned int offset,
+ int val)
+{
+ unsigned long *base = gpiochip_get_data(gc);
+ const struct winbond_gpio_info *info;
+ int ret;
+
+ if (!winbond_gpio_get_info(&offset, &info))
+ return -EACCES;
+
+ ret = winbond_sio_enter(*base);
+ if (ret)
+ return ret;
+
+ winbond_sio_select_logical(*base, info->dev);
+
+ winbond_sio_reg_bclear(*base, info->ioreg, offset);
+
+ if (winbond_sio_reg_btest(*base, info->invreg, offset))
+ val = !val;
+
+ if (val)
+ winbond_sio_reg_bset(*base, info->datareg, offset);
+ else
+ winbond_sio_reg_bclear(*base, info->datareg, offset);
+
+ winbond_sio_leave(*base);
+
+ return 0;
+}
+
+static void winbond_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int val)
+{
+ unsigned long *base = gpiochip_get_data(gc);
+ const struct winbond_gpio_info *info;
+
+ if (!winbond_gpio_get_info(&offset, &info))
+ return;
+
+ if (winbond_sio_enter(*base) != 0)
+ return;
+
+ winbond_sio_select_logical(*base, info->dev);
+
+ if (winbond_sio_reg_btest(*base, info->invreg, offset))
+ val = !val;
+
+ if (val)
+ winbond_sio_reg_bset(*base, info->datareg, offset);
+ else
+ winbond_sio_reg_bclear(*base, info->datareg, offset);
+
+ winbond_sio_leave(*base);
+}
+
+static struct gpio_chip winbond_gpio_chip = {
+ .base = -1,
+ .label = WB_GPIO_DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .can_sleep = true,
+ .get = winbond_gpio_get,
+ .direction_input = winbond_gpio_direction_in,
+ .set = winbond_gpio_set,
+ .direction_output = winbond_gpio_direction_out,
+};
+
+static void winbond_gpio_configure_port0_pins(unsigned long base)
+{
+ unsigned int val;
+
+ val = winbond_sio_reg_read(base, WB_SIO_REG_GPIO1_MF);
+ if ((val & WB_SIO_REG_G1MF_FS_MASK) == WB_SIO_REG_G1MF_FS_GPIO1)
+ return;
+
+ pr_warn("GPIO1 pins were connected to something else (%.2x), fixing\n",
+ val);
+
+ val &= ~WB_SIO_REG_G1MF_FS_MASK;
+ val |= WB_SIO_REG_G1MF_FS_GPIO1;
+
+ winbond_sio_reg_write(base, WB_SIO_REG_GPIO1_MF, val);
+}
+
+static void winbond_gpio_configure_port1_check_i2c(unsigned long base)
+{
+ params.i2cgpio = !winbond_sio_reg_btest(base, WB_SIO_REG_I2C_PS,
+ WB_SIO_REG_I2CPS_I2CFS);
+ if (!params.i2cgpio)
+ pr_warn("disabling GPIO2.5 and GPIO2.6 as I2C is enabled\n");
+}
+
+static bool winbond_gpio_configure_port(unsigned long base, unsigned int idx)
+{
+ const struct winbond_gpio_info *info = &winbond_gpio_infos[idx];
+ const struct winbond_gpio_port_conflict *conflict = &info->conflict;
+
+ /* is there a possible conflicting device defined? */
+ if (conflict->name != NULL) {
+ if (conflict->dev != WB_SIO_DEV_NONE)
+ winbond_sio_select_logical(base, conflict->dev);
+
+ if (winbond_sio_reg_btest(base, conflict->testreg,
+ conflict->testbit)) {
+ if (conflict->warnonly)
+ pr_warn("enabled GPIO%u share pins with active %s\n",
+ idx + 1, conflict->name);
+ else {
+ pr_warn("disabling GPIO%u as %s is enabled\n",
+ idx + 1, conflict->name);
+ return false;
+ }
+ }
+ }
+
+ /* GPIO1 and GPIO2 need some (additional) special handling */
+ if (idx == 0)
+ winbond_gpio_configure_port0_pins(base);
+ else if (idx == 1)
+ winbond_gpio_configure_port1_check_i2c(base);
+
+ winbond_sio_select_logical(base, info->dev);
+
+ winbond_sio_reg_bset(base, info->enablereg, info->enablebit);
+
+ if (params.ppgpios & BIT(idx))
+ winbond_sio_reg_bset(base, info->outputreg,
+ info->outputppbit);
+ else if (params.odgpios & BIT(idx))
+ winbond_sio_reg_bclear(base, info->outputreg,
+ info->outputppbit);
+ else
+ pr_notice("GPIO%u pins are %s\n", idx + 1,
+ winbond_sio_reg_btest(base, info->outputreg,
+ info->outputppbit) ?
+ "push-pull" :
+ "open drain");
+
+ return true;
+}
+
+static int winbond_gpio_configure(unsigned long base)
+{
+ unsigned long i;
+
+ for_each_set_bit(i, &params.gpios, BITS_PER_LONG)
+ if (!winbond_gpio_configure_port(base, i))
+ __clear_bit(i, &params.gpios);
+
+ if (!params.gpios) {
+ pr_err("please use 'gpios' module parameter to select some active GPIO ports to enable\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int winbond_gpio_check_chip(unsigned long base)
+{
+ int ret;
+ unsigned int chip;
+
+ ret = winbond_sio_enter(base);
+ if (ret)
+ return ret;
+
+ chip = winbond_sio_reg_read(base, WB_SIO_REG_CHIP_MSB) << 8;
+ chip |= winbond_sio_reg_read(base, WB_SIO_REG_CHIP_LSB);
+
+ pr_notice("chip ID at %lx is %.4x\n", base, chip);
+
+ if ((chip & WB_SIO_CHIP_ID_W83627UHG_MASK) !=
+ WB_SIO_CHIP_ID_W83627UHG) {
+ pr_err("not an our chip\n");
+ ret = -ENODEV;
+ }
+
+ winbond_sio_leave(base);
+
+ return ret;
+}
+
+static int winbond_gpio_imatch(struct device *dev, unsigned int id)
+{
+ unsigned long gpios_rem;
+ int ret;
+
+ gpios_rem = params.gpios & ~GENMASK(ARRAY_SIZE(winbond_gpio_infos) - 1,
+ 0);
+ if (gpios_rem) {
+ pr_warn("unknown ports (%lx) enabled in GPIO ports bitmask\n",
+ gpios_rem);
+ params.gpios &= ~gpios_rem;
+ }
+
+ if (params.ppgpios & params.odgpios) {
+ pr_err("some GPIO ports are set both to push-pull and open drain mode at the same time\n");
+ return 0;
+ }
+
+ if (params.base != 0)
+ return winbond_gpio_check_chip(params.base) == 0;
+
+ /*
+ * if the 'base' module parameter is unset probe two chip default
+ * I/O port bases
+ */
+ params.base = WB_SIO_BASE;
+ ret = winbond_gpio_check_chip(params.base);
+ if (ret == 0)
+ return 1;
+ if (ret != -ENODEV && ret != -EBUSY)
+ return 0;
+
+ params.base = WB_SIO_BASE_HIGH;
+ return winbond_gpio_check_chip(params.base) == 0;
+}
+
+static int winbond_gpio_iprobe(struct device *dev, unsigned int id)
+{
+ int ret;
+
+ if (params.base == 0)
+ return -EINVAL;
+
+ ret = winbond_sio_enter(params.base);
+ if (ret)
+ return ret;
+
+ ret = winbond_gpio_configure(params.base);
+
+ winbond_sio_leave(params.base);
+
+ if (ret)
+ return ret;
+
+ /*
+ * Add 8 gpios for every GPIO port that was enabled in gpios
+ * module parameter (that wasn't disabled earlier in
+ * winbond_gpio_configure() & co. due to, for example, a pin conflict).
+ */
+ winbond_gpio_chip.ngpio = hweight_long(params.gpios) * 8;
+
+ /*
+ * GPIO6 port has only 5 pins, so if it is enabled we have to adjust
+ * the total count appropriately
+ */
+ if (params.gpios & BIT(5))
+ winbond_gpio_chip.ngpio -= (8 - 5);
+
+ winbond_gpio_chip.parent = dev;
+
+ return devm_gpiochip_add_data(dev, &winbond_gpio_chip, &params.base);
+}
+
+static struct isa_driver winbond_gpio_idriver = {
+ .driver = {
+ .name = WB_GPIO_DRIVER_NAME,
+ },
+ .match = winbond_gpio_imatch,
+ .probe = winbond_gpio_iprobe,
+};
+
+module_isa_driver(winbond_gpio_idriver, 1);
+
+module_param_named(base, params.base, ulong, 0444);
+MODULE_PARM_DESC(base,
+ "I/O port base (when unset - probe chip default ones)");
+
+/* This parameter sets which GPIO devices (ports) we enable */
+module_param_named(gpios, params.gpios, ulong, 0444);
+MODULE_PARM_DESC(gpios,
+ "bitmask of GPIO ports to enable (bit 0 - GPIO1, bit 1 - GPIO2, etc.");
+
+/*
+ * These two parameters below set how we configure GPIO ports output drivers.
+ * It can't be a one bitmask since we need three values per port: push-pull,
+ * open-drain and keep as-is (this is the default).
+ */
+module_param_named(ppgpios, params.ppgpios, ulong, 0444);
+MODULE_PARM_DESC(ppgpios,
+ "bitmask of GPIO ports to set to push-pull mode (bit 0 - GPIO1, bit 1 - GPIO2, etc.");
+
+module_param_named(odgpios, params.odgpios, ulong, 0444);
+MODULE_PARM_DESC(odgpios,
+ "bitmask of GPIO ports to set to open drain mode (bit 0 - GPIO1, bit 1 - GPIO2, etc.");
+
+/*
+ * GPIO2.0 and GPIO2.1 control a basic PC functionality that we
+ * don't allow tinkering with by default (it is very likely that the
+ * firmware owns these pins).
+ * These two parameters below allow overriding these prohibitions.
+ */
+module_param_named(pledgpio, params.pledgpio, bool, 0644);
+MODULE_PARM_DESC(pledgpio,
+ "enable changing value of GPIO2.0 bit (Power LED), default no.");
+
+module_param_named(beepgpio, params.beepgpio, bool, 0644);
+MODULE_PARM_DESC(beepgpio,
+ "enable changing value of GPIO2.1 bit (BEEP), default no.");
+
+MODULE_AUTHOR("Maciej S. Szmigiero <mail@maciej.szmigiero.name>");
+MODULE_DESCRIPTION("GPIO interface for Winbond Super I/O chips");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-wm831x.c b/drivers/gpio/gpio-wm831x.c
new file mode 100644
index 0000000000..7eaf8a2863
--- /dev/null
+++ b/drivers/gpio/gpio-wm831x.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * gpiolib support for Wolfson WM831x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/gpio/driver.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/pdata.h>
+#include <linux/mfd/wm831x/gpio.h>
+#include <linux/mfd/wm831x/irq.h>
+
+struct wm831x_gpio {
+ struct wm831x *wm831x;
+ struct gpio_chip gpio_chip;
+};
+
+static int wm831x_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip);
+ struct wm831x *wm831x = wm831x_gpio->wm831x;
+ int val = WM831X_GPN_DIR;
+
+ if (wm831x->has_gpio_ena)
+ val |= WM831X_GPN_TRI;
+
+ return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset,
+ WM831X_GPN_DIR | WM831X_GPN_TRI |
+ WM831X_GPN_FN_MASK, val);
+}
+
+static int wm831x_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip);
+ struct wm831x *wm831x = wm831x_gpio->wm831x;
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, WM831X_GPIO_LEVEL);
+ if (ret < 0)
+ return ret;
+
+ if (ret & 1 << offset)
+ return 1;
+ else
+ return 0;
+}
+
+static void wm831x_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip);
+ struct wm831x *wm831x = wm831x_gpio->wm831x;
+
+ wm831x_set_bits(wm831x, WM831X_GPIO_LEVEL, 1 << offset,
+ value << offset);
+}
+
+static int wm831x_gpio_direction_out(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip);
+ struct wm831x *wm831x = wm831x_gpio->wm831x;
+ int val = 0;
+ int ret;
+
+ if (wm831x->has_gpio_ena)
+ val |= WM831X_GPN_TRI;
+
+ ret = wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset,
+ WM831X_GPN_DIR | WM831X_GPN_TRI |
+ WM831X_GPN_FN_MASK, val);
+ if (ret < 0)
+ return ret;
+
+ /* Can only set GPIO state once it's in output mode */
+ wm831x_gpio_set(chip, offset, value);
+
+ return 0;
+}
+
+static int wm831x_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip);
+ struct wm831x *wm831x = wm831x_gpio->wm831x;
+
+ return irq_create_mapping(wm831x->irq_domain,
+ WM831X_IRQ_GPIO_1 + offset);
+}
+
+static int wm831x_gpio_set_debounce(struct wm831x *wm831x, unsigned offset,
+ unsigned debounce)
+{
+ int reg = WM831X_GPIO1_CONTROL + offset;
+ int ret, fn;
+
+ ret = wm831x_reg_read(wm831x, reg);
+ if (ret < 0)
+ return ret;
+
+ switch (ret & WM831X_GPN_FN_MASK) {
+ case 0:
+ case 1:
+ break;
+ default:
+ /* Not in GPIO mode */
+ return -EBUSY;
+ }
+
+ if (debounce >= 32 && debounce <= 64)
+ fn = 0;
+ else if (debounce >= 4000 && debounce <= 8000)
+ fn = 1;
+ else
+ return -EINVAL;
+
+ return wm831x_set_bits(wm831x, reg, WM831X_GPN_FN_MASK, fn);
+}
+
+static int wm831x_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip);
+ struct wm831x *wm831x = wm831x_gpio->wm831x;
+ int reg = WM831X_GPIO1_CONTROL + offset;
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ return wm831x_set_bits(wm831x, reg,
+ WM831X_GPN_OD_MASK, WM831X_GPN_OD);
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ return wm831x_set_bits(wm831x, reg,
+ WM831X_GPN_OD_MASK, 0);
+ case PIN_CONFIG_INPUT_DEBOUNCE:
+ return wm831x_gpio_set_debounce(wm831x, offset,
+ pinconf_to_config_argument(config));
+ default:
+ break;
+ }
+
+ return -ENOTSUPP;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip);
+ struct wm831x *wm831x = wm831x_gpio->wm831x;
+ int i, tristated;
+
+ for (i = 0; i < chip->ngpio; i++) {
+ int gpio = i + chip->base;
+ int reg;
+ const char *label, *pull, *powerdomain;
+
+ /* We report the GPIO even if it's not requested since
+ * we're also reporting things like alternate
+ * functions which apply even when the GPIO is not in
+ * use as a GPIO.
+ */
+ label = gpiochip_is_requested(chip, i);
+ if (!label)
+ label = "Unrequested";
+
+ seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label);
+
+ reg = wm831x_reg_read(wm831x, WM831X_GPIO1_CONTROL + i);
+ if (reg < 0) {
+ dev_err(wm831x->dev,
+ "GPIO control %d read failed: %d\n",
+ gpio, reg);
+ seq_putc(s, '\n');
+ continue;
+ }
+
+ switch (reg & WM831X_GPN_PULL_MASK) {
+ case WM831X_GPIO_PULL_NONE:
+ pull = "nopull";
+ break;
+ case WM831X_GPIO_PULL_DOWN:
+ pull = "pulldown";
+ break;
+ case WM831X_GPIO_PULL_UP:
+ pull = "pullup";
+ break;
+ default:
+ pull = "INVALID PULL";
+ break;
+ }
+
+ switch (i + 1) {
+ case 1 ... 3:
+ case 7 ... 9:
+ if (reg & WM831X_GPN_PWR_DOM)
+ powerdomain = "VPMIC";
+ else
+ powerdomain = "DBVDD";
+ break;
+
+ case 4 ... 6:
+ case 10 ... 12:
+ if (reg & WM831X_GPN_PWR_DOM)
+ powerdomain = "SYSVDD";
+ else
+ powerdomain = "DBVDD";
+ break;
+
+ case 13 ... 16:
+ powerdomain = "TPVDD";
+ break;
+
+ default:
+ BUG();
+ break;
+ }
+
+ tristated = reg & WM831X_GPN_TRI;
+ if (wm831x->has_gpio_ena)
+ tristated = !tristated;
+
+ seq_printf(s, " %s %s %s %s%s\n"
+ " %s%s (0x%4x)\n",
+ reg & WM831X_GPN_DIR ? "in" : "out",
+ wm831x_gpio_get(chip, i) ? "high" : "low",
+ pull,
+ powerdomain,
+ reg & WM831X_GPN_POL ? "" : " inverted",
+ reg & WM831X_GPN_OD ? "open-drain" : "push-pull",
+ tristated ? " tristated" : "",
+ reg);
+ }
+}
+#else
+#define wm831x_gpio_dbg_show NULL
+#endif
+
+static const struct gpio_chip template_chip = {
+ .label = "wm831x",
+ .owner = THIS_MODULE,
+ .direction_input = wm831x_gpio_direction_in,
+ .get = wm831x_gpio_get,
+ .direction_output = wm831x_gpio_direction_out,
+ .set = wm831x_gpio_set,
+ .to_irq = wm831x_gpio_to_irq,
+ .set_config = wm831x_set_config,
+ .dbg_show = wm831x_gpio_dbg_show,
+ .can_sleep = true,
+};
+
+static int wm831x_gpio_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *pdata = &wm831x->pdata;
+ struct wm831x_gpio *wm831x_gpio;
+
+ device_set_node(&pdev->dev, dev_fwnode(pdev->dev.parent));
+
+ wm831x_gpio = devm_kzalloc(&pdev->dev, sizeof(*wm831x_gpio),
+ GFP_KERNEL);
+ if (wm831x_gpio == NULL)
+ return -ENOMEM;
+
+ wm831x_gpio->wm831x = wm831x;
+ wm831x_gpio->gpio_chip = template_chip;
+ wm831x_gpio->gpio_chip.ngpio = wm831x->num_gpio;
+ wm831x_gpio->gpio_chip.parent = &pdev->dev;
+ if (pdata && pdata->gpio_base)
+ wm831x_gpio->gpio_chip.base = pdata->gpio_base;
+ else
+ wm831x_gpio->gpio_chip.base = -1;
+
+ return devm_gpiochip_add_data(&pdev->dev, &wm831x_gpio->gpio_chip, wm831x_gpio);
+}
+
+static struct platform_driver wm831x_gpio_driver = {
+ .driver.name = "wm831x-gpio",
+ .probe = wm831x_gpio_probe,
+};
+
+static int __init wm831x_gpio_init(void)
+{
+ return platform_driver_register(&wm831x_gpio_driver);
+}
+subsys_initcall(wm831x_gpio_init);
+
+static void __exit wm831x_gpio_exit(void)
+{
+ platform_driver_unregister(&wm831x_gpio_driver);
+}
+module_exit(wm831x_gpio_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("GPIO interface for WM831x PMICs");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-gpio");
diff --git a/drivers/gpio/gpio-wm8350.c b/drivers/gpio/gpio-wm8350.c
new file mode 100644
index 0000000000..2421cf606e
--- /dev/null
+++ b/drivers/gpio/gpio-wm8350.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * gpiolib support for Wolfson WM835x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/wm8350/core.h>
+#include <linux/mfd/wm8350/gpio.h>
+
+struct wm8350_gpio_data {
+ struct wm8350 *wm8350;
+ struct gpio_chip gpio_chip;
+};
+
+static int wm8350_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8350_gpio_data *wm8350_gpio = gpiochip_get_data(chip);
+ struct wm8350 *wm8350 = wm8350_gpio->wm8350;
+
+ return wm8350_set_bits(wm8350, WM8350_GPIO_CONFIGURATION_I_O,
+ 1 << offset);
+}
+
+static int wm8350_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8350_gpio_data *wm8350_gpio = gpiochip_get_data(chip);
+ struct wm8350 *wm8350 = wm8350_gpio->wm8350;
+ int ret;
+
+ ret = wm8350_reg_read(wm8350, WM8350_GPIO_LEVEL);
+ if (ret < 0)
+ return ret;
+
+ if (ret & (1 << offset))
+ return 1;
+ else
+ return 0;
+}
+
+static void wm8350_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct wm8350_gpio_data *wm8350_gpio = gpiochip_get_data(chip);
+ struct wm8350 *wm8350 = wm8350_gpio->wm8350;
+
+ if (value)
+ wm8350_set_bits(wm8350, WM8350_GPIO_LEVEL, 1 << offset);
+ else
+ wm8350_clear_bits(wm8350, WM8350_GPIO_LEVEL, 1 << offset);
+}
+
+static int wm8350_gpio_direction_out(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct wm8350_gpio_data *wm8350_gpio = gpiochip_get_data(chip);
+ struct wm8350 *wm8350 = wm8350_gpio->wm8350;
+ int ret;
+
+ ret = wm8350_clear_bits(wm8350, WM8350_GPIO_CONFIGURATION_I_O,
+ 1 << offset);
+ if (ret < 0)
+ return ret;
+
+ /* Don't have an atomic direction/value setup */
+ wm8350_gpio_set(chip, offset, value);
+
+ return 0;
+}
+
+static int wm8350_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8350_gpio_data *wm8350_gpio = gpiochip_get_data(chip);
+ struct wm8350 *wm8350 = wm8350_gpio->wm8350;
+
+ if (!wm8350->irq_base)
+ return -EINVAL;
+
+ return wm8350->irq_base + WM8350_IRQ_GPIO(offset);
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "wm8350",
+ .owner = THIS_MODULE,
+ .direction_input = wm8350_gpio_direction_in,
+ .get = wm8350_gpio_get,
+ .direction_output = wm8350_gpio_direction_out,
+ .set = wm8350_gpio_set,
+ .to_irq = wm8350_gpio_to_irq,
+ .can_sleep = true,
+};
+
+static int wm8350_gpio_probe(struct platform_device *pdev)
+{
+ struct wm8350 *wm8350 = dev_get_drvdata(pdev->dev.parent);
+ struct wm8350_platform_data *pdata = dev_get_platdata(wm8350->dev);
+ struct wm8350_gpio_data *wm8350_gpio;
+
+ wm8350_gpio = devm_kzalloc(&pdev->dev, sizeof(*wm8350_gpio),
+ GFP_KERNEL);
+ if (wm8350_gpio == NULL)
+ return -ENOMEM;
+
+ wm8350_gpio->wm8350 = wm8350;
+ wm8350_gpio->gpio_chip = template_chip;
+ wm8350_gpio->gpio_chip.ngpio = 13;
+ wm8350_gpio->gpio_chip.parent = &pdev->dev;
+ if (pdata && pdata->gpio_base)
+ wm8350_gpio->gpio_chip.base = pdata->gpio_base;
+ else
+ wm8350_gpio->gpio_chip.base = -1;
+
+ return devm_gpiochip_add_data(&pdev->dev, &wm8350_gpio->gpio_chip, wm8350_gpio);
+}
+
+static struct platform_driver wm8350_gpio_driver = {
+ .driver.name = "wm8350-gpio",
+ .probe = wm8350_gpio_probe,
+};
+
+static int __init wm8350_gpio_init(void)
+{
+ return platform_driver_register(&wm8350_gpio_driver);
+}
+subsys_initcall(wm8350_gpio_init);
+
+static void __exit wm8350_gpio_exit(void)
+{
+ platform_driver_unregister(&wm8350_gpio_driver);
+}
+module_exit(wm8350_gpio_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("GPIO interface for WM8350 PMICs");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm8350-gpio");
diff --git a/drivers/gpio/gpio-wm8994.c b/drivers/gpio/gpio-wm8994.c
new file mode 100644
index 0000000000..f4a474cef3
--- /dev/null
+++ b/drivers/gpio/gpio-wm8994.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * gpiolib support for Wolfson WM8994
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/gpio/driver.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/wm8994/core.h>
+#include <linux/mfd/wm8994/pdata.h>
+#include <linux/mfd/wm8994/gpio.h>
+#include <linux/mfd/wm8994/registers.h>
+
+struct wm8994_gpio {
+ struct wm8994 *wm8994;
+ struct gpio_chip gpio_chip;
+};
+
+static int wm8994_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8994_gpio *wm8994_gpio = gpiochip_get_data(chip);
+ struct wm8994 *wm8994 = wm8994_gpio->wm8994;
+
+ switch (wm8994->type) {
+ case WM8958:
+ switch (offset) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 6:
+ return -EINVAL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int wm8994_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8994_gpio *wm8994_gpio = gpiochip_get_data(chip);
+ struct wm8994 *wm8994 = wm8994_gpio->wm8994;
+
+ return wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset,
+ WM8994_GPN_DIR, WM8994_GPN_DIR);
+}
+
+static int wm8994_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8994_gpio *wm8994_gpio = gpiochip_get_data(chip);
+ struct wm8994 *wm8994 = wm8994_gpio->wm8994;
+ int ret;
+
+ ret = wm8994_reg_read(wm8994, WM8994_GPIO_1 + offset);
+ if (ret < 0)
+ return ret;
+
+ if (ret & WM8994_GPN_LVL)
+ return 1;
+ else
+ return 0;
+}
+
+static int wm8994_gpio_direction_out(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct wm8994_gpio *wm8994_gpio = gpiochip_get_data(chip);
+ struct wm8994 *wm8994 = wm8994_gpio->wm8994;
+
+ if (value)
+ value = WM8994_GPN_LVL;
+
+ return wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset,
+ WM8994_GPN_DIR | WM8994_GPN_LVL, value);
+}
+
+static void wm8994_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct wm8994_gpio *wm8994_gpio = gpiochip_get_data(chip);
+ struct wm8994 *wm8994 = wm8994_gpio->wm8994;
+
+ if (value)
+ value = WM8994_GPN_LVL;
+
+ wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset, WM8994_GPN_LVL, value);
+}
+
+static int wm8994_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ struct wm8994_gpio *wm8994_gpio = gpiochip_get_data(chip);
+ struct wm8994 *wm8994 = wm8994_gpio->wm8994;
+
+ switch (pinconf_to_config_param(config)) {
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ return wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset,
+ WM8994_GPN_OP_CFG_MASK,
+ WM8994_GPN_OP_CFG);
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ return wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset,
+ WM8994_GPN_OP_CFG_MASK, 0);
+ default:
+ break;
+ }
+
+ return -ENOTSUPP;
+}
+
+static int wm8994_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8994_gpio *wm8994_gpio = gpiochip_get_data(chip);
+ struct wm8994 *wm8994 = wm8994_gpio->wm8994;
+
+ return regmap_irq_get_virq(wm8994->irq_data, offset);
+}
+
+
+#ifdef CONFIG_DEBUG_FS
+static const char *wm8994_gpio_fn(u16 fn)
+{
+ switch (fn) {
+ case WM8994_GP_FN_PIN_SPECIFIC:
+ return "pin-specific";
+ case WM8994_GP_FN_GPIO:
+ return "GPIO";
+ case WM8994_GP_FN_SDOUT:
+ return "SDOUT";
+ case WM8994_GP_FN_IRQ:
+ return "IRQ";
+ case WM8994_GP_FN_TEMPERATURE:
+ return "Temperature";
+ case WM8994_GP_FN_MICBIAS1_DET:
+ return "MICBIAS1 detect";
+ case WM8994_GP_FN_MICBIAS1_SHORT:
+ return "MICBIAS1 short";
+ case WM8994_GP_FN_MICBIAS2_DET:
+ return "MICBIAS2 detect";
+ case WM8994_GP_FN_MICBIAS2_SHORT:
+ return "MICBIAS2 short";
+ case WM8994_GP_FN_FLL1_LOCK:
+ return "FLL1 lock";
+ case WM8994_GP_FN_FLL2_LOCK:
+ return "FLL2 lock";
+ case WM8994_GP_FN_SRC1_LOCK:
+ return "SRC1 lock";
+ case WM8994_GP_FN_SRC2_LOCK:
+ return "SRC2 lock";
+ case WM8994_GP_FN_DRC1_ACT:
+ return "DRC1 activity";
+ case WM8994_GP_FN_DRC2_ACT:
+ return "DRC2 activity";
+ case WM8994_GP_FN_DRC3_ACT:
+ return "DRC3 activity";
+ case WM8994_GP_FN_WSEQ_STATUS:
+ return "Write sequencer";
+ case WM8994_GP_FN_FIFO_ERROR:
+ return "FIFO error";
+ case WM8994_GP_FN_OPCLK:
+ return "OPCLK";
+ case WM8994_GP_FN_THW:
+ return "Thermal warning";
+ case WM8994_GP_FN_DCS_DONE:
+ return "DC servo";
+ case WM8994_GP_FN_FLL1_OUT:
+ return "FLL1 output";
+ case WM8994_GP_FN_FLL2_OUT:
+ return "FLL1 output";
+ default:
+ return "Unknown";
+ }
+}
+
+static void wm8994_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ struct wm8994_gpio *wm8994_gpio = gpiochip_get_data(chip);
+ struct wm8994 *wm8994 = wm8994_gpio->wm8994;
+ int i;
+
+ for (i = 0; i < chip->ngpio; i++) {
+ int gpio = i + chip->base;
+ int reg;
+ const char *label;
+
+ /* We report the GPIO even if it's not requested since
+ * we're also reporting things like alternate
+ * functions which apply even when the GPIO is not in
+ * use as a GPIO.
+ */
+ label = gpiochip_is_requested(chip, i);
+ if (!label)
+ label = "Unrequested";
+
+ seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label);
+
+ reg = wm8994_reg_read(wm8994, WM8994_GPIO_1 + i);
+ if (reg < 0) {
+ dev_err(wm8994->dev,
+ "GPIO control %d read failed: %d\n",
+ gpio, reg);
+ seq_printf(s, "\n");
+ continue;
+ }
+
+ if (reg & WM8994_GPN_DIR)
+ seq_printf(s, "in ");
+ else
+ seq_printf(s, "out ");
+
+ if (reg & WM8994_GPN_PU)
+ seq_printf(s, "pull up ");
+
+ if (reg & WM8994_GPN_PD)
+ seq_printf(s, "pull down ");
+
+ if (reg & WM8994_GPN_POL)
+ seq_printf(s, "inverted ");
+ else
+ seq_printf(s, "noninverted ");
+
+ if (reg & WM8994_GPN_OP_CFG)
+ seq_printf(s, "open drain ");
+ else
+ seq_printf(s, "push-pull ");
+
+ seq_printf(s, "%s (%x)\n",
+ wm8994_gpio_fn(reg & WM8994_GPN_FN_MASK), reg);
+ }
+}
+#else
+#define wm8994_gpio_dbg_show NULL
+#endif
+
+static const struct gpio_chip template_chip = {
+ .label = "wm8994",
+ .owner = THIS_MODULE,
+ .request = wm8994_gpio_request,
+ .direction_input = wm8994_gpio_direction_in,
+ .get = wm8994_gpio_get,
+ .direction_output = wm8994_gpio_direction_out,
+ .set = wm8994_gpio_set,
+ .set_config = wm8994_gpio_set_config,
+ .to_irq = wm8994_gpio_to_irq,
+ .dbg_show = wm8994_gpio_dbg_show,
+ .can_sleep = true,
+};
+
+static int wm8994_gpio_probe(struct platform_device *pdev)
+{
+ struct wm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent);
+ struct wm8994_pdata *pdata = dev_get_platdata(wm8994->dev);
+ struct wm8994_gpio *wm8994_gpio;
+
+ wm8994_gpio = devm_kzalloc(&pdev->dev, sizeof(*wm8994_gpio),
+ GFP_KERNEL);
+ if (wm8994_gpio == NULL)
+ return -ENOMEM;
+
+ wm8994_gpio->wm8994 = wm8994;
+ wm8994_gpio->gpio_chip = template_chip;
+ wm8994_gpio->gpio_chip.ngpio = WM8994_GPIO_MAX;
+ wm8994_gpio->gpio_chip.parent = &pdev->dev;
+ if (pdata && pdata->gpio_base)
+ wm8994_gpio->gpio_chip.base = pdata->gpio_base;
+ else
+ wm8994_gpio->gpio_chip.base = -1;
+
+ return devm_gpiochip_add_data(&pdev->dev, &wm8994_gpio->gpio_chip, wm8994_gpio);
+}
+
+static struct platform_driver wm8994_gpio_driver = {
+ .driver.name = "wm8994-gpio",
+ .probe = wm8994_gpio_probe,
+};
+
+static int __init wm8994_gpio_init(void)
+{
+ return platform_driver_register(&wm8994_gpio_driver);
+}
+subsys_initcall(wm8994_gpio_init);
+
+static void __exit wm8994_gpio_exit(void)
+{
+ platform_driver_unregister(&wm8994_gpio_driver);
+}
+module_exit(wm8994_gpio_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("GPIO interface for WM8994");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm8994-gpio");
diff --git a/drivers/gpio/gpio-ws16c48.c b/drivers/gpio/gpio-ws16c48.c
new file mode 100644
index 0000000000..6289b0510c
--- /dev/null
+++ b/drivers/gpio/gpio-ws16c48.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for the WinSystems WS16C48
+ * Copyright (C) 2016 William Breathitt Gray
+ */
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/regmap.h>
+#include <linux/irq.h>
+#include <linux/isa.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/spinlock.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define WS16C48_EXTENT 11
+#define MAX_NUM_WS16C48 max_num_isa_dev(WS16C48_EXTENT)
+
+static unsigned int base[MAX_NUM_WS16C48];
+static unsigned int num_ws16c48;
+module_param_hw_array(base, uint, ioport, &num_ws16c48, 0);
+MODULE_PARM_DESC(base, "WinSystems WS16C48 base addresses");
+
+static unsigned int irq[MAX_NUM_WS16C48];
+static unsigned int num_irq;
+module_param_hw_array(irq, uint, irq, &num_irq, 0);
+MODULE_PARM_DESC(irq, "WinSystems WS16C48 interrupt line numbers");
+
+#define WS16C48_DAT_BASE 0x0
+#define WS16C48_PAGE_LOCK 0x7
+#define WS16C48_PAGE_BASE 0x8
+#define WS16C48_POL WS16C48_PAGE_BASE
+#define WS16C48_ENAB WS16C48_PAGE_BASE
+#define WS16C48_INT_ID WS16C48_PAGE_BASE
+
+#define PAGE_LOCK_PAGE_FIELD GENMASK(7, 6)
+#define POL_PAGE u8_encode_bits(1, PAGE_LOCK_PAGE_FIELD)
+#define ENAB_PAGE u8_encode_bits(2, PAGE_LOCK_PAGE_FIELD)
+#define INT_ID_PAGE u8_encode_bits(3, PAGE_LOCK_PAGE_FIELD)
+
+static const struct regmap_range ws16c48_wr_ranges[] = {
+ regmap_reg_range(0x0, 0x5), regmap_reg_range(0x7, 0xA),
+};
+static const struct regmap_range ws16c48_rd_ranges[] = {
+ regmap_reg_range(0x0, 0xA),
+};
+static const struct regmap_range ws16c48_volatile_ranges[] = {
+ regmap_reg_range(0x0, 0x6), regmap_reg_range(0x8, 0xA),
+};
+static const struct regmap_access_table ws16c48_wr_table = {
+ .yes_ranges = ws16c48_wr_ranges,
+ .n_yes_ranges = ARRAY_SIZE(ws16c48_wr_ranges),
+};
+static const struct regmap_access_table ws16c48_rd_table = {
+ .yes_ranges = ws16c48_rd_ranges,
+ .n_yes_ranges = ARRAY_SIZE(ws16c48_rd_ranges),
+};
+static const struct regmap_access_table ws16c48_volatile_table = {
+ .yes_ranges = ws16c48_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(ws16c48_volatile_ranges),
+};
+static const struct regmap_config ws16c48_regmap_config = {
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .val_bits = 8,
+ .io_port = true,
+ .wr_table = &ws16c48_wr_table,
+ .rd_table = &ws16c48_rd_table,
+ .volatile_table = &ws16c48_volatile_table,
+ .cache_type = REGCACHE_FLAT,
+ .use_raw_spinlock = true,
+};
+
+#define WS16C48_NGPIO_PER_REG 8
+#define WS16C48_REGMAP_IRQ(_id) \
+ [_id] = { \
+ .reg_offset = (_id) / WS16C48_NGPIO_PER_REG, \
+ .mask = BIT((_id) % WS16C48_NGPIO_PER_REG), \
+ .type = { \
+ .type_reg_offset = (_id) / WS16C48_NGPIO_PER_REG, \
+ .types_supported = IRQ_TYPE_EDGE_BOTH, \
+ }, \
+ }
+
+/* Only the first 24 lines (Port 0-2) support interrupts */
+#define WS16C48_NUM_IRQS 24
+static const struct regmap_irq ws16c48_regmap_irqs[WS16C48_NUM_IRQS] = {
+ WS16C48_REGMAP_IRQ(0), WS16C48_REGMAP_IRQ(1), WS16C48_REGMAP_IRQ(2), /* 0-2 */
+ WS16C48_REGMAP_IRQ(3), WS16C48_REGMAP_IRQ(4), WS16C48_REGMAP_IRQ(5), /* 3-5 */
+ WS16C48_REGMAP_IRQ(6), WS16C48_REGMAP_IRQ(7), WS16C48_REGMAP_IRQ(8), /* 6-8 */
+ WS16C48_REGMAP_IRQ(9), WS16C48_REGMAP_IRQ(10), WS16C48_REGMAP_IRQ(11), /* 9-11 */
+ WS16C48_REGMAP_IRQ(12), WS16C48_REGMAP_IRQ(13), WS16C48_REGMAP_IRQ(14), /* 12-14 */
+ WS16C48_REGMAP_IRQ(15), WS16C48_REGMAP_IRQ(16), WS16C48_REGMAP_IRQ(17), /* 15-17 */
+ WS16C48_REGMAP_IRQ(18), WS16C48_REGMAP_IRQ(19), WS16C48_REGMAP_IRQ(20), /* 18-20 */
+ WS16C48_REGMAP_IRQ(21), WS16C48_REGMAP_IRQ(22), WS16C48_REGMAP_IRQ(23), /* 21-23 */
+};
+
+/**
+ * struct ws16c48_gpio - GPIO device private data structure
+ * @map: regmap for the device
+ * @lock: synchronization lock to prevent I/O race conditions
+ * @irq_mask: I/O bits affected by interrupts
+ */
+struct ws16c48_gpio {
+ struct regmap *map;
+ raw_spinlock_t lock;
+ u8 irq_mask[WS16C48_NUM_IRQS / WS16C48_NGPIO_PER_REG];
+};
+
+static int ws16c48_handle_pre_irq(void *const irq_drv_data) __acquires(&ws16c48gpio->lock)
+{
+ struct ws16c48_gpio *const ws16c48gpio = irq_drv_data;
+
+ /* Lock to prevent Page/Lock register change while we handle IRQ */
+ raw_spin_lock(&ws16c48gpio->lock);
+
+ return 0;
+}
+
+static int ws16c48_handle_post_irq(void *const irq_drv_data) __releases(&ws16c48gpio->lock)
+{
+ struct ws16c48_gpio *const ws16c48gpio = irq_drv_data;
+
+ raw_spin_unlock(&ws16c48gpio->lock);
+
+ return 0;
+}
+
+static int ws16c48_handle_mask_sync(const int index, const unsigned int mask_buf_def,
+ const unsigned int mask_buf, void *const irq_drv_data)
+{
+ struct ws16c48_gpio *const ws16c48gpio = irq_drv_data;
+ unsigned long flags;
+ int ret = 0;
+
+ raw_spin_lock_irqsave(&ws16c48gpio->lock, flags);
+
+ /* exit early if no change since the last mask sync */
+ if (mask_buf == ws16c48gpio->irq_mask[index])
+ goto exit_unlock;
+ ws16c48gpio->irq_mask[index] = mask_buf;
+
+ ret = regmap_write(ws16c48gpio->map, WS16C48_PAGE_LOCK, ENAB_PAGE);
+ if (ret)
+ goto exit_unlock;
+
+ /* Update ENAB register (inverted mask) */
+ ret = regmap_write(ws16c48gpio->map, WS16C48_ENAB + index, ~mask_buf);
+ if (ret)
+ goto exit_unlock;
+
+ ret = regmap_write(ws16c48gpio->map, WS16C48_PAGE_LOCK, INT_ID_PAGE);
+ if (ret)
+ goto exit_unlock;
+
+exit_unlock:
+ raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags);
+
+ return ret;
+}
+
+static int ws16c48_set_type_config(unsigned int **const buf, const unsigned int type,
+ const struct regmap_irq *const irq_data, const int idx,
+ void *const irq_drv_data)
+{
+ struct ws16c48_gpio *const ws16c48gpio = irq_drv_data;
+ unsigned int polarity;
+ unsigned long flags;
+ int ret;
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ polarity = irq_data->mask;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ polarity = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ raw_spin_lock_irqsave(&ws16c48gpio->lock, flags);
+
+ ret = regmap_write(ws16c48gpio->map, WS16C48_PAGE_LOCK, POL_PAGE);
+ if (ret)
+ goto exit_unlock;
+
+ /* Set interrupt polarity */
+ ret = regmap_update_bits(ws16c48gpio->map, WS16C48_POL + idx, irq_data->mask, polarity);
+ if (ret)
+ goto exit_unlock;
+
+ ret = regmap_write(ws16c48gpio->map, WS16C48_PAGE_LOCK, INT_ID_PAGE);
+ if (ret)
+ goto exit_unlock;
+
+exit_unlock:
+ raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags);
+
+ return ret;
+}
+
+#define WS16C48_NGPIO 48
+static const char *ws16c48_names[WS16C48_NGPIO] = {
+ "Port 0 Bit 0", "Port 0 Bit 1", "Port 0 Bit 2", "Port 0 Bit 3",
+ "Port 0 Bit 4", "Port 0 Bit 5", "Port 0 Bit 6", "Port 0 Bit 7",
+ "Port 1 Bit 0", "Port 1 Bit 1", "Port 1 Bit 2", "Port 1 Bit 3",
+ "Port 1 Bit 4", "Port 1 Bit 5", "Port 1 Bit 6", "Port 1 Bit 7",
+ "Port 2 Bit 0", "Port 2 Bit 1", "Port 2 Bit 2", "Port 2 Bit 3",
+ "Port 2 Bit 4", "Port 2 Bit 5", "Port 2 Bit 6", "Port 2 Bit 7",
+ "Port 3 Bit 0", "Port 3 Bit 1", "Port 3 Bit 2", "Port 3 Bit 3",
+ "Port 3 Bit 4", "Port 3 Bit 5", "Port 3 Bit 6", "Port 3 Bit 7",
+ "Port 4 Bit 0", "Port 4 Bit 1", "Port 4 Bit 2", "Port 4 Bit 3",
+ "Port 4 Bit 4", "Port 4 Bit 5", "Port 4 Bit 6", "Port 4 Bit 7",
+ "Port 5 Bit 0", "Port 5 Bit 1", "Port 5 Bit 2", "Port 5 Bit 3",
+ "Port 5 Bit 4", "Port 5 Bit 5", "Port 5 Bit 6", "Port 5 Bit 7"
+};
+
+static int ws16c48_irq_init_hw(struct regmap *const map)
+{
+ int err;
+
+ err = regmap_write(map, WS16C48_PAGE_LOCK, ENAB_PAGE);
+ if (err)
+ return err;
+
+ /* Disable interrupts for all lines */
+ err = regmap_write(map, WS16C48_ENAB + 0, 0x00);
+ if (err)
+ return err;
+ err = regmap_write(map, WS16C48_ENAB + 1, 0x00);
+ if (err)
+ return err;
+ err = regmap_write(map, WS16C48_ENAB + 2, 0x00);
+ if (err)
+ return err;
+
+ return regmap_write(map, WS16C48_PAGE_LOCK, INT_ID_PAGE);
+}
+
+static int ws16c48_probe(struct device *dev, unsigned int id)
+{
+ struct ws16c48_gpio *ws16c48gpio;
+ const char *const name = dev_name(dev);
+ int err;
+ struct gpio_regmap_config gpio_config = {};
+ void __iomem *regs;
+ struct regmap_irq_chip *chip;
+ struct regmap_irq_chip_data *chip_data;
+
+ ws16c48gpio = devm_kzalloc(dev, sizeof(*ws16c48gpio), GFP_KERNEL);
+ if (!ws16c48gpio)
+ return -ENOMEM;
+
+ if (!devm_request_region(dev, base[id], WS16C48_EXTENT, name)) {
+ dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n",
+ base[id], base[id] + WS16C48_EXTENT);
+ return -EBUSY;
+ }
+
+ regs = devm_ioport_map(dev, base[id], WS16C48_EXTENT);
+ if (!regs)
+ return -ENOMEM;
+
+ ws16c48gpio->map = devm_regmap_init_mmio(dev, regs, &ws16c48_regmap_config);
+ if (IS_ERR(ws16c48gpio->map))
+ return dev_err_probe(dev, PTR_ERR(ws16c48gpio->map),
+ "Unable to initialize register map\n");
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->name = name;
+ chip->status_base = WS16C48_INT_ID;
+ chip->mask_base = WS16C48_ENAB;
+ chip->ack_base = WS16C48_INT_ID;
+ chip->num_regs = 3;
+ chip->irqs = ws16c48_regmap_irqs;
+ chip->num_irqs = ARRAY_SIZE(ws16c48_regmap_irqs);
+ chip->handle_pre_irq = ws16c48_handle_pre_irq;
+ chip->handle_post_irq = ws16c48_handle_post_irq;
+ chip->handle_mask_sync = ws16c48_handle_mask_sync;
+ chip->set_type_config = ws16c48_set_type_config;
+ chip->irq_drv_data = ws16c48gpio;
+
+ raw_spin_lock_init(&ws16c48gpio->lock);
+
+ /* Initialize to prevent spurious interrupts before we're ready */
+ err = ws16c48_irq_init_hw(ws16c48gpio->map);
+ if (err)
+ return err;
+
+ err = devm_regmap_add_irq_chip(dev, ws16c48gpio->map, irq[id], 0, 0, chip, &chip_data);
+ if (err)
+ return dev_err_probe(dev, err, "IRQ registration failed\n");
+
+ gpio_config.parent = dev;
+ gpio_config.regmap = ws16c48gpio->map;
+ gpio_config.ngpio = WS16C48_NGPIO;
+ gpio_config.names = ws16c48_names;
+ gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(WS16C48_DAT_BASE);
+ gpio_config.reg_set_base = GPIO_REGMAP_ADDR(WS16C48_DAT_BASE);
+ /* Setting a GPIO to 0 allows it to be used as an input */
+ gpio_config.reg_dir_out_base = GPIO_REGMAP_ADDR(WS16C48_DAT_BASE);
+ gpio_config.ngpio_per_reg = WS16C48_NGPIO_PER_REG;
+ gpio_config.irq_domain = regmap_irq_get_domain(chip_data);
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
+}
+
+static struct isa_driver ws16c48_driver = {
+ .probe = ws16c48_probe,
+ .driver = {
+ .name = "ws16c48"
+ },
+};
+
+module_isa_driver_with_irq(ws16c48_driver, num_ws16c48, num_irq);
+
+MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
+MODULE_DESCRIPTION("WinSystems WS16C48 GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-xgene-sb.c b/drivers/gpio/gpio-xgene-sb.c
new file mode 100644
index 0000000000..a809609ee9
--- /dev/null
+++ b/drivers/gpio/gpio-xgene-sb.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AppliedMicro X-Gene SoC GPIO-Standby Driver
+ *
+ * Copyright (c) 2014, Applied Micro Circuits Corporation
+ * Author: Tin Huynh <tnhuynh@apm.com>.
+ * Y Vo <yvo@apm.com>.
+ * Quan Nguyen <qnguyen@apm.com>.
+ */
+
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/driver.h>
+#include <linux/acpi.h>
+
+#include "gpiolib.h"
+#include "gpiolib-acpi.h"
+
+/* Common property names */
+#define XGENE_NIRQ_PROPERTY "apm,nr-irqs"
+#define XGENE_NGPIO_PROPERTY "apm,nr-gpios"
+#define XGENE_IRQ_START_PROPERTY "apm,irq-start"
+
+#define XGENE_DFLT_MAX_NGPIO 22
+#define XGENE_DFLT_MAX_NIRQ 6
+#define XGENE_DFLT_IRQ_START_PIN 8
+#define GPIO_MASK(x) (1U << ((x) % 32))
+
+#define MPA_GPIO_INT_LVL 0x0290
+#define MPA_GPIO_OE_ADDR 0x029c
+#define MPA_GPIO_OUT_ADDR 0x02a0
+#define MPA_GPIO_IN_ADDR 0x02a4
+#define MPA_GPIO_SEL_LO 0x0294
+
+#define GPIO_INT_LEVEL_H 0x000001
+#define GPIO_INT_LEVEL_L 0x000000
+
+/**
+ * struct xgene_gpio_sb - GPIO-Standby private data structure.
+ * @gc: memory-mapped GPIO controllers.
+ * @regs: GPIO register base offset
+ * @irq_domain: GPIO interrupt domain
+ * @irq_start: GPIO pin that start support interrupt
+ * @nirq: Number of GPIO pins that supports interrupt
+ * @parent_irq_base: Start parent HWIRQ
+ */
+struct xgene_gpio_sb {
+ struct gpio_chip gc;
+ void __iomem *regs;
+ struct irq_domain *irq_domain;
+ u16 irq_start;
+ u16 nirq;
+ u16 parent_irq_base;
+};
+
+#define HWIRQ_TO_GPIO(priv, hwirq) ((hwirq) + (priv)->irq_start)
+#define GPIO_TO_HWIRQ(priv, gpio) ((gpio) - (priv)->irq_start)
+
+static void xgene_gpio_set_bit(struct gpio_chip *gc,
+ void __iomem *reg, u32 gpio, int val)
+{
+ u32 data;
+
+ data = gc->read_reg(reg);
+ if (val)
+ data |= GPIO_MASK(gpio);
+ else
+ data &= ~GPIO_MASK(gpio);
+ gc->write_reg(reg, data);
+}
+
+static int xgene_gpio_sb_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct xgene_gpio_sb *priv = irq_data_get_irq_chip_data(d);
+ int gpio = HWIRQ_TO_GPIO(priv, d->hwirq);
+ int lvl_type = GPIO_INT_LEVEL_H;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_LEVEL_HIGH:
+ lvl_type = GPIO_INT_LEVEL_H;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ case IRQ_TYPE_LEVEL_LOW:
+ lvl_type = GPIO_INT_LEVEL_L;
+ break;
+ default:
+ break;
+ }
+
+ xgene_gpio_set_bit(&priv->gc, priv->regs + MPA_GPIO_SEL_LO,
+ gpio * 2, 1);
+ xgene_gpio_set_bit(&priv->gc, priv->regs + MPA_GPIO_INT_LVL,
+ d->hwirq, lvl_type);
+
+ /* Propagate IRQ type setting to parent */
+ if (type & IRQ_TYPE_EDGE_BOTH)
+ return irq_chip_set_type_parent(d, IRQ_TYPE_EDGE_RISING);
+ else
+ return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
+}
+
+static struct irq_chip xgene_gpio_sb_irq_chip = {
+ .name = "sbgpio",
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_set_type = xgene_gpio_sb_irq_set_type,
+};
+
+static int xgene_gpio_sb_to_irq(struct gpio_chip *gc, u32 gpio)
+{
+ struct xgene_gpio_sb *priv = gpiochip_get_data(gc);
+ struct irq_fwspec fwspec;
+
+ if ((gpio < priv->irq_start) ||
+ (gpio > HWIRQ_TO_GPIO(priv, priv->nirq)))
+ return -ENXIO;
+
+ fwspec.fwnode = gc->parent->fwnode;
+ fwspec.param_count = 2;
+ fwspec.param[0] = GPIO_TO_HWIRQ(priv, gpio);
+ fwspec.param[1] = IRQ_TYPE_EDGE_RISING;
+ return irq_create_fwspec_mapping(&fwspec);
+}
+
+static int xgene_gpio_sb_domain_activate(struct irq_domain *d,
+ struct irq_data *irq_data,
+ bool reserve)
+{
+ struct xgene_gpio_sb *priv = d->host_data;
+ u32 gpio = HWIRQ_TO_GPIO(priv, irq_data->hwirq);
+ int ret;
+
+ ret = gpiochip_lock_as_irq(&priv->gc, gpio);
+ if (ret) {
+ dev_err(priv->gc.parent,
+ "Unable to configure XGene GPIO standby pin %d as IRQ\n",
+ gpio);
+ return ret;
+ }
+
+ xgene_gpio_set_bit(&priv->gc, priv->regs + MPA_GPIO_SEL_LO,
+ gpio * 2, 1);
+ return 0;
+}
+
+static void xgene_gpio_sb_domain_deactivate(struct irq_domain *d,
+ struct irq_data *irq_data)
+{
+ struct xgene_gpio_sb *priv = d->host_data;
+ u32 gpio = HWIRQ_TO_GPIO(priv, irq_data->hwirq);
+
+ gpiochip_unlock_as_irq(&priv->gc, gpio);
+ xgene_gpio_set_bit(&priv->gc, priv->regs + MPA_GPIO_SEL_LO,
+ gpio * 2, 0);
+}
+
+static int xgene_gpio_sb_domain_translate(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ unsigned long *hwirq,
+ unsigned int *type)
+{
+ struct xgene_gpio_sb *priv = d->host_data;
+
+ if ((fwspec->param_count != 2) ||
+ (fwspec->param[0] >= priv->nirq))
+ return -EINVAL;
+ *hwirq = fwspec->param[0];
+ *type = fwspec->param[1];
+ return 0;
+}
+
+static int xgene_gpio_sb_domain_alloc(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs, void *data)
+{
+ struct irq_fwspec *fwspec = data;
+ struct irq_fwspec parent_fwspec;
+ struct xgene_gpio_sb *priv = domain->host_data;
+ irq_hw_number_t hwirq;
+ unsigned int i;
+
+ hwirq = fwspec->param[0];
+ for (i = 0; i < nr_irqs; i++)
+ irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+ &xgene_gpio_sb_irq_chip, priv);
+
+ parent_fwspec.fwnode = domain->parent->fwnode;
+ if (is_of_node(parent_fwspec.fwnode)) {
+ parent_fwspec.param_count = 3;
+ parent_fwspec.param[0] = 0;/* SPI */
+ /* Skip SGIs and PPIs*/
+ parent_fwspec.param[1] = hwirq + priv->parent_irq_base - 32;
+ parent_fwspec.param[2] = fwspec->param[1];
+ } else if (is_fwnode_irqchip(parent_fwspec.fwnode)) {
+ parent_fwspec.param_count = 2;
+ parent_fwspec.param[0] = hwirq + priv->parent_irq_base;
+ parent_fwspec.param[1] = fwspec->param[1];
+ } else
+ return -EINVAL;
+
+ return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
+ &parent_fwspec);
+}
+
+static const struct irq_domain_ops xgene_gpio_sb_domain_ops = {
+ .translate = xgene_gpio_sb_domain_translate,
+ .alloc = xgene_gpio_sb_domain_alloc,
+ .free = irq_domain_free_irqs_common,
+ .activate = xgene_gpio_sb_domain_activate,
+ .deactivate = xgene_gpio_sb_domain_deactivate,
+};
+
+static int xgene_gpio_sb_probe(struct platform_device *pdev)
+{
+ struct xgene_gpio_sb *priv;
+ int ret;
+ void __iomem *regs;
+ struct irq_domain *parent_domain = NULL;
+ u32 val32;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ priv->regs = regs;
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret > 0) {
+ priv->parent_irq_base = irq_get_irq_data(ret)->hwirq;
+ parent_domain = irq_get_irq_data(ret)->domain;
+ }
+ if (!parent_domain) {
+ dev_err(&pdev->dev, "unable to obtain parent domain\n");
+ return -ENODEV;
+ }
+
+ ret = bgpio_init(&priv->gc, &pdev->dev, 4,
+ regs + MPA_GPIO_IN_ADDR,
+ regs + MPA_GPIO_OUT_ADDR, NULL,
+ regs + MPA_GPIO_OE_ADDR, NULL, 0);
+ if (ret)
+ return ret;
+
+ priv->gc.to_irq = xgene_gpio_sb_to_irq;
+
+ /* Retrieve start irq pin, use default if property not found */
+ priv->irq_start = XGENE_DFLT_IRQ_START_PIN;
+ if (!device_property_read_u32(&pdev->dev,
+ XGENE_IRQ_START_PROPERTY, &val32))
+ priv->irq_start = val32;
+
+ /* Retrieve number irqs, use default if property not found */
+ priv->nirq = XGENE_DFLT_MAX_NIRQ;
+ if (!device_property_read_u32(&pdev->dev, XGENE_NIRQ_PROPERTY, &val32))
+ priv->nirq = val32;
+
+ /* Retrieve number gpio, use default if property not found */
+ priv->gc.ngpio = XGENE_DFLT_MAX_NGPIO;
+ if (!device_property_read_u32(&pdev->dev, XGENE_NGPIO_PROPERTY, &val32))
+ priv->gc.ngpio = val32;
+
+ dev_info(&pdev->dev, "Support %d gpios, %d irqs start from pin %d\n",
+ priv->gc.ngpio, priv->nirq, priv->irq_start);
+
+ platform_set_drvdata(pdev, priv);
+
+ priv->irq_domain = irq_domain_create_hierarchy(parent_domain,
+ 0, priv->nirq, pdev->dev.fwnode,
+ &xgene_gpio_sb_domain_ops, priv);
+ if (!priv->irq_domain)
+ return -ENODEV;
+
+ priv->gc.irq.domain = priv->irq_domain;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &priv->gc, priv);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to register X-Gene GPIO Standby driver\n");
+ irq_domain_remove(priv->irq_domain);
+ return ret;
+ }
+
+ dev_info(&pdev->dev, "X-Gene GPIO Standby driver registered\n");
+
+ /* Register interrupt handlers for GPIO signaled ACPI Events */
+ acpi_gpiochip_request_interrupts(&priv->gc);
+
+ return ret;
+}
+
+static int xgene_gpio_sb_remove(struct platform_device *pdev)
+{
+ struct xgene_gpio_sb *priv = platform_get_drvdata(pdev);
+
+ acpi_gpiochip_free_interrupts(&priv->gc);
+
+ irq_domain_remove(priv->irq_domain);
+
+ return 0;
+}
+
+static const struct of_device_id xgene_gpio_sb_of_match[] = {
+ {.compatible = "apm,xgene-gpio-sb", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, xgene_gpio_sb_of_match);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id xgene_gpio_sb_acpi_match[] = {
+ {"APMC0D15", 0},
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, xgene_gpio_sb_acpi_match);
+#endif
+
+static struct platform_driver xgene_gpio_sb_driver = {
+ .driver = {
+ .name = "xgene-gpio-sb",
+ .of_match_table = xgene_gpio_sb_of_match,
+ .acpi_match_table = ACPI_PTR(xgene_gpio_sb_acpi_match),
+ },
+ .probe = xgene_gpio_sb_probe,
+ .remove = xgene_gpio_sb_remove,
+};
+module_platform_driver(xgene_gpio_sb_driver);
+
+MODULE_AUTHOR("AppliedMicro");
+MODULE_DESCRIPTION("APM X-Gene GPIO Standby driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-xgene.c b/drivers/gpio/gpio-xgene.c
new file mode 100644
index 0000000000..fb4b0c67ae
--- /dev/null
+++ b/drivers/gpio/gpio-xgene.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AppliedMicro X-Gene SoC GPIO Driver
+ *
+ * Copyright (c) 2014, Applied Micro Circuits Corporation
+ * Author: Feng Kan <fkan@apm.com>.
+ */
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/driver.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+
+#define GPIO_SET_DR_OFFSET 0x0C
+#define GPIO_DATA_OFFSET 0x14
+#define GPIO_BANK_STRIDE 0x0C
+
+#define XGENE_GPIOS_PER_BANK 16
+#define XGENE_MAX_GPIO_BANKS 3
+#define XGENE_MAX_GPIOS (XGENE_GPIOS_PER_BANK * XGENE_MAX_GPIO_BANKS)
+
+#define GPIO_BIT_OFFSET(x) (x % XGENE_GPIOS_PER_BANK)
+#define GPIO_BANK_OFFSET(x) ((x / XGENE_GPIOS_PER_BANK) * GPIO_BANK_STRIDE)
+
+struct xgene_gpio {
+ struct gpio_chip chip;
+ void __iomem *base;
+ spinlock_t lock;
+ u32 set_dr_val[XGENE_MAX_GPIO_BANKS];
+};
+
+static int xgene_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct xgene_gpio *chip = gpiochip_get_data(gc);
+ unsigned long bank_offset;
+ u32 bit_offset;
+
+ bank_offset = GPIO_DATA_OFFSET + GPIO_BANK_OFFSET(offset);
+ bit_offset = GPIO_BIT_OFFSET(offset);
+ return !!(ioread32(chip->base + bank_offset) & BIT(bit_offset));
+}
+
+static void __xgene_gpio_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ struct xgene_gpio *chip = gpiochip_get_data(gc);
+ unsigned long bank_offset;
+ u32 setval, bit_offset;
+
+ bank_offset = GPIO_SET_DR_OFFSET + GPIO_BANK_OFFSET(offset);
+ bit_offset = GPIO_BIT_OFFSET(offset) + XGENE_GPIOS_PER_BANK;
+
+ setval = ioread32(chip->base + bank_offset);
+ if (val)
+ setval |= BIT(bit_offset);
+ else
+ setval &= ~BIT(bit_offset);
+ iowrite32(setval, chip->base + bank_offset);
+}
+
+static void xgene_gpio_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ struct xgene_gpio *chip = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ __xgene_gpio_set(gc, offset, val);
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static int xgene_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct xgene_gpio *chip = gpiochip_get_data(gc);
+ unsigned long bank_offset, bit_offset;
+
+ bank_offset = GPIO_SET_DR_OFFSET + GPIO_BANK_OFFSET(offset);
+ bit_offset = GPIO_BIT_OFFSET(offset);
+
+ if (ioread32(chip->base + bank_offset) & BIT(bit_offset))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int xgene_gpio_dir_in(struct gpio_chip *gc, unsigned int offset)
+{
+ struct xgene_gpio *chip = gpiochip_get_data(gc);
+ unsigned long flags, bank_offset;
+ u32 dirval, bit_offset;
+
+ bank_offset = GPIO_SET_DR_OFFSET + GPIO_BANK_OFFSET(offset);
+ bit_offset = GPIO_BIT_OFFSET(offset);
+
+ spin_lock_irqsave(&chip->lock, flags);
+
+ dirval = ioread32(chip->base + bank_offset);
+ dirval |= BIT(bit_offset);
+ iowrite32(dirval, chip->base + bank_offset);
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+
+ return 0;
+}
+
+static int xgene_gpio_dir_out(struct gpio_chip *gc,
+ unsigned int offset, int val)
+{
+ struct xgene_gpio *chip = gpiochip_get_data(gc);
+ unsigned long flags, bank_offset;
+ u32 dirval, bit_offset;
+
+ bank_offset = GPIO_SET_DR_OFFSET + GPIO_BANK_OFFSET(offset);
+ bit_offset = GPIO_BIT_OFFSET(offset);
+
+ spin_lock_irqsave(&chip->lock, flags);
+
+ dirval = ioread32(chip->base + bank_offset);
+ dirval &= ~BIT(bit_offset);
+ iowrite32(dirval, chip->base + bank_offset);
+ __xgene_gpio_set(gc, offset, val);
+
+ spin_unlock_irqrestore(&chip->lock, flags);
+
+ return 0;
+}
+
+static __maybe_unused int xgene_gpio_suspend(struct device *dev)
+{
+ struct xgene_gpio *gpio = dev_get_drvdata(dev);
+ unsigned long bank_offset;
+ unsigned int bank;
+
+ for (bank = 0; bank < XGENE_MAX_GPIO_BANKS; bank++) {
+ bank_offset = GPIO_SET_DR_OFFSET + bank * GPIO_BANK_STRIDE;
+ gpio->set_dr_val[bank] = ioread32(gpio->base + bank_offset);
+ }
+ return 0;
+}
+
+static __maybe_unused int xgene_gpio_resume(struct device *dev)
+{
+ struct xgene_gpio *gpio = dev_get_drvdata(dev);
+ unsigned long bank_offset;
+ unsigned int bank;
+
+ for (bank = 0; bank < XGENE_MAX_GPIO_BANKS; bank++) {
+ bank_offset = GPIO_SET_DR_OFFSET + bank * GPIO_BANK_STRIDE;
+ iowrite32(gpio->set_dr_val[bank], gpio->base + bank_offset);
+ }
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(xgene_gpio_pm, xgene_gpio_suspend, xgene_gpio_resume);
+
+static int xgene_gpio_probe(struct platform_device *pdev)
+{
+ struct xgene_gpio *gpio;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gpio->base))
+ return PTR_ERR(gpio->base);
+
+ gpio->chip.ngpio = XGENE_MAX_GPIOS;
+
+ spin_lock_init(&gpio->lock);
+ gpio->chip.parent = &pdev->dev;
+ gpio->chip.get_direction = xgene_gpio_get_direction;
+ gpio->chip.direction_input = xgene_gpio_dir_in;
+ gpio->chip.direction_output = xgene_gpio_dir_out;
+ gpio->chip.get = xgene_gpio_get;
+ gpio->chip.set = xgene_gpio_set;
+ gpio->chip.label = dev_name(&pdev->dev);
+ gpio->chip.base = -1;
+
+ platform_set_drvdata(pdev, gpio);
+
+ return devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
+}
+
+static const struct of_device_id xgene_gpio_of_match[] = {
+ { .compatible = "apm,xgene-gpio", },
+ {},
+};
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id xgene_gpio_acpi_match[] = {
+ { "APMC0D14", 0 },
+ { },
+};
+#endif
+
+static struct platform_driver xgene_gpio_driver = {
+ .driver = {
+ .name = "xgene-gpio",
+ .of_match_table = xgene_gpio_of_match,
+ .acpi_match_table = ACPI_PTR(xgene_gpio_acpi_match),
+ .pm = &xgene_gpio_pm,
+ },
+ .probe = xgene_gpio_probe,
+};
+builtin_platform_driver(xgene_gpio_driver);
diff --git a/drivers/gpio/gpio-xgs-iproc.c b/drivers/gpio/gpio-xgs-iproc.c
new file mode 100644
index 0000000000..2d23b27d55
--- /dev/null
+++ b/drivers/gpio/gpio-xgs-iproc.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017 Broadcom
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+
+#define IPROC_CCA_INT_F_GPIOINT BIT(0)
+#define IPROC_CCA_INT_STS 0x20
+#define IPROC_CCA_INT_MASK 0x24
+
+#define IPROC_GPIO_CCA_DIN 0x0
+#define IPROC_GPIO_CCA_DOUT 0x4
+#define IPROC_GPIO_CCA_OUT_EN 0x8
+#define IPROC_GPIO_CCA_INT_LEVEL 0x10
+#define IPROC_GPIO_CCA_INT_LEVEL_MASK 0x14
+#define IPROC_GPIO_CCA_INT_EVENT 0x18
+#define IPROC_GPIO_CCA_INT_EVENT_MASK 0x1C
+#define IPROC_GPIO_CCA_INT_EDGE 0x24
+
+struct iproc_gpio_chip {
+ struct gpio_chip gc;
+ spinlock_t lock;
+ struct device *dev;
+ void __iomem *base;
+ void __iomem *intr;
+};
+
+static inline struct iproc_gpio_chip *
+to_iproc_gpio(struct gpio_chip *gc)
+{
+ return container_of(gc, struct iproc_gpio_chip, gc);
+}
+
+static void iproc_gpio_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct iproc_gpio_chip *chip = to_iproc_gpio(gc);
+ int pin = d->hwirq;
+ unsigned long flags;
+ u32 irq = d->irq;
+ u32 irq_type, event_status = 0;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ irq_type = irq_get_trigger_type(irq);
+ if (irq_type & IRQ_TYPE_EDGE_BOTH) {
+ event_status |= BIT(pin);
+ writel_relaxed(event_status,
+ chip->base + IPROC_GPIO_CCA_INT_EVENT);
+ }
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static void iproc_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct iproc_gpio_chip *chip = to_iproc_gpio(gc);
+ int pin = d->hwirq;
+ unsigned long flags;
+ u32 irq = d->irq;
+ u32 int_mask, irq_type, event_mask;
+
+ gpiochip_enable_irq(gc, pin);
+ spin_lock_irqsave(&chip->lock, flags);
+ irq_type = irq_get_trigger_type(irq);
+ event_mask = readl_relaxed(chip->base + IPROC_GPIO_CCA_INT_EVENT_MASK);
+ int_mask = readl_relaxed(chip->base + IPROC_GPIO_CCA_INT_LEVEL_MASK);
+
+ if (irq_type & IRQ_TYPE_EDGE_BOTH) {
+ event_mask |= 1 << pin;
+ writel_relaxed(event_mask,
+ chip->base + IPROC_GPIO_CCA_INT_EVENT_MASK);
+ } else {
+ int_mask |= 1 << pin;
+ writel_relaxed(int_mask,
+ chip->base + IPROC_GPIO_CCA_INT_LEVEL_MASK);
+ }
+ spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static void iproc_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct iproc_gpio_chip *chip = to_iproc_gpio(gc);
+ int pin = d->hwirq;
+ unsigned long flags;
+ u32 irq = d->irq;
+ u32 irq_type, int_mask, event_mask;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ irq_type = irq_get_trigger_type(irq);
+ event_mask = readl_relaxed(chip->base + IPROC_GPIO_CCA_INT_EVENT_MASK);
+ int_mask = readl_relaxed(chip->base + IPROC_GPIO_CCA_INT_LEVEL_MASK);
+
+ if (irq_type & IRQ_TYPE_EDGE_BOTH) {
+ event_mask &= ~BIT(pin);
+ writel_relaxed(event_mask,
+ chip->base + IPROC_GPIO_CCA_INT_EVENT_MASK);
+ } else {
+ int_mask &= ~BIT(pin);
+ writel_relaxed(int_mask,
+ chip->base + IPROC_GPIO_CCA_INT_LEVEL_MASK);
+ }
+ spin_unlock_irqrestore(&chip->lock, flags);
+ gpiochip_disable_irq(gc, pin);
+}
+
+static int iproc_gpio_irq_set_type(struct irq_data *d, u32 type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct iproc_gpio_chip *chip = to_iproc_gpio(gc);
+ int pin = d->hwirq;
+ unsigned long flags;
+ u32 irq = d->irq;
+ u32 event_pol, int_pol;
+ int ret = 0;
+
+ spin_lock_irqsave(&chip->lock, flags);
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_RISING:
+ event_pol = readl_relaxed(chip->base + IPROC_GPIO_CCA_INT_EDGE);
+ event_pol &= ~BIT(pin);
+ writel_relaxed(event_pol, chip->base + IPROC_GPIO_CCA_INT_EDGE);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ event_pol = readl_relaxed(chip->base + IPROC_GPIO_CCA_INT_EDGE);
+ event_pol |= BIT(pin);
+ writel_relaxed(event_pol, chip->base + IPROC_GPIO_CCA_INT_EDGE);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ int_pol = readl_relaxed(chip->base + IPROC_GPIO_CCA_INT_LEVEL);
+ int_pol &= ~BIT(pin);
+ writel_relaxed(int_pol, chip->base + IPROC_GPIO_CCA_INT_LEVEL);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ int_pol = readl_relaxed(chip->base + IPROC_GPIO_CCA_INT_LEVEL);
+ int_pol |= BIT(pin);
+ writel_relaxed(int_pol, chip->base + IPROC_GPIO_CCA_INT_LEVEL);
+ break;
+ default:
+ /* should not come here */
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (type & IRQ_TYPE_LEVEL_MASK)
+ irq_set_handler_locked(irq_get_irq_data(irq), handle_level_irq);
+ else if (type & IRQ_TYPE_EDGE_BOTH)
+ irq_set_handler_locked(irq_get_irq_data(irq), handle_edge_irq);
+
+out_unlock:
+ spin_unlock_irqrestore(&chip->lock, flags);
+
+ return ret;
+}
+
+static irqreturn_t iproc_gpio_irq_handler(int irq, void *data)
+{
+ struct gpio_chip *gc = (struct gpio_chip *)data;
+ struct iproc_gpio_chip *chip = to_iproc_gpio(gc);
+ int bit;
+ unsigned long int_bits = 0;
+ u32 int_status;
+
+ /* go through the entire GPIOs and handle all interrupts */
+ int_status = readl_relaxed(chip->intr + IPROC_CCA_INT_STS);
+ if (int_status & IPROC_CCA_INT_F_GPIOINT) {
+ u32 event, level;
+
+ /* Get level and edge interrupts */
+ event =
+ readl_relaxed(chip->base + IPROC_GPIO_CCA_INT_EVENT_MASK);
+ event &= readl_relaxed(chip->base + IPROC_GPIO_CCA_INT_EVENT);
+ level = readl_relaxed(chip->base + IPROC_GPIO_CCA_DIN);
+ level ^= readl_relaxed(chip->base + IPROC_GPIO_CCA_INT_LEVEL);
+ level &=
+ readl_relaxed(chip->base + IPROC_GPIO_CCA_INT_LEVEL_MASK);
+ int_bits = level | event;
+
+ for_each_set_bit(bit, &int_bits, gc->ngpio)
+ generic_handle_domain_irq(gc->irq.domain, bit);
+ }
+
+ return int_bits ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static void iproc_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct iproc_gpio_chip *chip = to_iproc_gpio(gc);
+
+ seq_printf(p, dev_name(chip->dev));
+}
+
+static const struct irq_chip iproc_gpio_irq_chip = {
+ .irq_ack = iproc_gpio_irq_ack,
+ .irq_mask = iproc_gpio_irq_mask,
+ .irq_unmask = iproc_gpio_irq_unmask,
+ .irq_set_type = iproc_gpio_irq_set_type,
+ .irq_print_chip = iproc_gpio_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int iproc_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *dn = pdev->dev.of_node;
+ struct iproc_gpio_chip *chip;
+ u32 num_gpios;
+ int irq, ret;
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = dev;
+ platform_set_drvdata(pdev, chip);
+ spin_lock_init(&chip->lock);
+
+ chip->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(chip->base))
+ return PTR_ERR(chip->base);
+
+ ret = bgpio_init(&chip->gc, dev, 4,
+ chip->base + IPROC_GPIO_CCA_DIN,
+ chip->base + IPROC_GPIO_CCA_DOUT,
+ NULL,
+ chip->base + IPROC_GPIO_CCA_OUT_EN,
+ NULL,
+ 0);
+ if (ret) {
+ dev_err(dev, "unable to init GPIO chip\n");
+ return ret;
+ }
+
+ chip->gc.label = dev_name(dev);
+ if (!of_property_read_u32(dn, "ngpios", &num_gpios))
+ chip->gc.ngpio = num_gpios;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq > 0) {
+ struct gpio_irq_chip *girq;
+ u32 val;
+
+ chip->intr = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(chip->intr))
+ return PTR_ERR(chip->intr);
+
+ /* Enable GPIO interrupts for CCA GPIO */
+ val = readl_relaxed(chip->intr + IPROC_CCA_INT_MASK);
+ val |= IPROC_CCA_INT_F_GPIOINT;
+ writel_relaxed(val, chip->intr + IPROC_CCA_INT_MASK);
+
+ /*
+ * Directly request the irq here instead of passing
+ * a flow-handler because the irq is shared.
+ */
+ ret = devm_request_irq(dev, irq, iproc_gpio_irq_handler,
+ IRQF_SHARED, chip->gc.label, &chip->gc);
+ if (ret) {
+ dev_err(dev, "Fail to request IRQ%d: %d\n", irq, ret);
+ return ret;
+ }
+
+ girq = &chip->gc.irq;
+ gpio_irq_chip_set_chip(girq, &iproc_gpio_irq_chip);
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ }
+
+ ret = devm_gpiochip_add_data(dev, &chip->gc, chip);
+ if (ret) {
+ dev_err(dev, "unable to add GPIO chip\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int iproc_gpio_remove(struct platform_device *pdev)
+{
+ struct iproc_gpio_chip *chip = platform_get_drvdata(pdev);
+
+ if (chip->intr) {
+ u32 val;
+
+ val = readl_relaxed(chip->intr + IPROC_CCA_INT_MASK);
+ val &= ~IPROC_CCA_INT_F_GPIOINT;
+ writel_relaxed(val, chip->intr + IPROC_CCA_INT_MASK);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id bcm_iproc_gpio_of_match[] = {
+ { .compatible = "brcm,iproc-gpio-cca" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, bcm_iproc_gpio_of_match);
+
+static struct platform_driver bcm_iproc_gpio_driver = {
+ .driver = {
+ .name = "iproc-xgs-gpio",
+ .of_match_table = bcm_iproc_gpio_of_match,
+ },
+ .probe = iproc_gpio_probe,
+ .remove = iproc_gpio_remove,
+};
+
+module_platform_driver(bcm_iproc_gpio_driver);
+
+MODULE_DESCRIPTION("XGS IPROC GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-xilinx.c b/drivers/gpio/gpio-xilinx.c
new file mode 100644
index 0000000000..a16945e831
--- /dev/null
+++ b/drivers/gpio/gpio-xilinx.c
@@ -0,0 +1,741 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Xilinx gpio driver for xps/axi_gpio IP.
+ *
+ * Copyright 2008 - 2013 Xilinx, Inc.
+ */
+
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+/* Register Offset Definitions */
+#define XGPIO_DATA_OFFSET (0x0) /* Data register */
+#define XGPIO_TRI_OFFSET (0x4) /* I/O direction register */
+
+#define XGPIO_CHANNEL0_OFFSET 0x0
+#define XGPIO_CHANNEL1_OFFSET 0x8
+
+#define XGPIO_GIER_OFFSET 0x11c /* Global Interrupt Enable */
+#define XGPIO_GIER_IE BIT(31)
+#define XGPIO_IPISR_OFFSET 0x120 /* IP Interrupt Status */
+#define XGPIO_IPIER_OFFSET 0x128 /* IP Interrupt Enable */
+
+/* Read/Write access to the GPIO registers */
+#if defined(CONFIG_ARCH_ZYNQ) || defined(CONFIG_X86)
+# define xgpio_readreg(offset) readl(offset)
+# define xgpio_writereg(offset, val) writel(val, offset)
+#else
+# define xgpio_readreg(offset) __raw_readl(offset)
+# define xgpio_writereg(offset, val) __raw_writel(val, offset)
+#endif
+
+/**
+ * struct xgpio_instance - Stores information about GPIO device
+ * @gc: GPIO chip
+ * @regs: register block
+ * @hw_map: GPIO pin mapping on hardware side
+ * @sw_map: GPIO pin mapping on software side
+ * @state: GPIO write state shadow register
+ * @last_irq_read: GPIO read state register from last interrupt
+ * @dir: GPIO direction shadow register
+ * @gpio_lock: Lock used for synchronization
+ * @irq: IRQ used by GPIO device
+ * @irqchip: IRQ chip
+ * @enable: GPIO IRQ enable/disable bitfield
+ * @rising_edge: GPIO IRQ rising edge enable/disable bitfield
+ * @falling_edge: GPIO IRQ falling edge enable/disable bitfield
+ * @clk: clock resource for this driver
+ */
+struct xgpio_instance {
+ struct gpio_chip gc;
+ void __iomem *regs;
+ DECLARE_BITMAP(hw_map, 64);
+ DECLARE_BITMAP(sw_map, 64);
+ DECLARE_BITMAP(state, 64);
+ DECLARE_BITMAP(last_irq_read, 64);
+ DECLARE_BITMAP(dir, 64);
+ spinlock_t gpio_lock; /* For serializing operations */
+ int irq;
+ DECLARE_BITMAP(enable, 64);
+ DECLARE_BITMAP(rising_edge, 64);
+ DECLARE_BITMAP(falling_edge, 64);
+ struct clk *clk;
+};
+
+static inline int xgpio_from_bit(struct xgpio_instance *chip, int bit)
+{
+ return bitmap_bitremap(bit, chip->hw_map, chip->sw_map, 64);
+}
+
+static inline int xgpio_to_bit(struct xgpio_instance *chip, int gpio)
+{
+ return bitmap_bitremap(gpio, chip->sw_map, chip->hw_map, 64);
+}
+
+static inline u32 xgpio_get_value32(const unsigned long *map, int bit)
+{
+ const size_t index = BIT_WORD(bit);
+ const unsigned long offset = (bit % BITS_PER_LONG) & BIT(5);
+
+ return (map[index] >> offset) & 0xFFFFFFFFul;
+}
+
+static inline void xgpio_set_value32(unsigned long *map, int bit, u32 v)
+{
+ const size_t index = BIT_WORD(bit);
+ const unsigned long offset = (bit % BITS_PER_LONG) & BIT(5);
+
+ map[index] &= ~(0xFFFFFFFFul << offset);
+ map[index] |= (unsigned long)v << offset;
+}
+
+static inline int xgpio_regoffset(struct xgpio_instance *chip, int ch)
+{
+ switch (ch) {
+ case 0:
+ return XGPIO_CHANNEL0_OFFSET;
+ case 1:
+ return XGPIO_CHANNEL1_OFFSET;
+ default:
+ return -EINVAL;
+ }
+}
+
+static void xgpio_read_ch(struct xgpio_instance *chip, int reg, int bit, unsigned long *a)
+{
+ void __iomem *addr = chip->regs + reg + xgpio_regoffset(chip, bit / 32);
+
+ xgpio_set_value32(a, bit, xgpio_readreg(addr));
+}
+
+static void xgpio_write_ch(struct xgpio_instance *chip, int reg, int bit, unsigned long *a)
+{
+ void __iomem *addr = chip->regs + reg + xgpio_regoffset(chip, bit / 32);
+
+ xgpio_writereg(addr, xgpio_get_value32(a, bit));
+}
+
+static void xgpio_read_ch_all(struct xgpio_instance *chip, int reg, unsigned long *a)
+{
+ int bit, lastbit = xgpio_to_bit(chip, chip->gc.ngpio - 1);
+
+ for (bit = 0; bit <= lastbit ; bit += 32)
+ xgpio_read_ch(chip, reg, bit, a);
+}
+
+static void xgpio_write_ch_all(struct xgpio_instance *chip, int reg, unsigned long *a)
+{
+ int bit, lastbit = xgpio_to_bit(chip, chip->gc.ngpio - 1);
+
+ for (bit = 0; bit <= lastbit ; bit += 32)
+ xgpio_write_ch(chip, reg, bit, a);
+}
+
+/**
+ * xgpio_get - Read the specified signal of the GPIO device.
+ * @gc: Pointer to gpio_chip device structure.
+ * @gpio: GPIO signal number.
+ *
+ * This function reads the specified signal of the GPIO device.
+ *
+ * Return:
+ * 0 if direction of GPIO signals is set as input otherwise it
+ * returns negative error value.
+ */
+static int xgpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct xgpio_instance *chip = gpiochip_get_data(gc);
+ int bit = xgpio_to_bit(chip, gpio);
+ DECLARE_BITMAP(state, 64);
+
+ xgpio_read_ch(chip, XGPIO_DATA_OFFSET, bit, state);
+
+ return test_bit(bit, state);
+}
+
+/**
+ * xgpio_set - Write the specified signal of the GPIO device.
+ * @gc: Pointer to gpio_chip device structure.
+ * @gpio: GPIO signal number.
+ * @val: Value to be written to specified signal.
+ *
+ * This function writes the specified value in to the specified signal of the
+ * GPIO device.
+ */
+static void xgpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ unsigned long flags;
+ struct xgpio_instance *chip = gpiochip_get_data(gc);
+ int bit = xgpio_to_bit(chip, gpio);
+
+ spin_lock_irqsave(&chip->gpio_lock, flags);
+
+ /* Write to GPIO signal and set its direction to output */
+ __assign_bit(bit, chip->state, val);
+
+ xgpio_write_ch(chip, XGPIO_DATA_OFFSET, bit, chip->state);
+
+ spin_unlock_irqrestore(&chip->gpio_lock, flags);
+}
+
+/**
+ * xgpio_set_multiple - Write the specified signals of the GPIO device.
+ * @gc: Pointer to gpio_chip device structure.
+ * @mask: Mask of the GPIOS to modify.
+ * @bits: Value to be wrote on each GPIO
+ *
+ * This function writes the specified values into the specified signals of the
+ * GPIO devices.
+ */
+static void xgpio_set_multiple(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ DECLARE_BITMAP(hw_mask, 64);
+ DECLARE_BITMAP(hw_bits, 64);
+ DECLARE_BITMAP(state, 64);
+ unsigned long flags;
+ struct xgpio_instance *chip = gpiochip_get_data(gc);
+
+ bitmap_remap(hw_mask, mask, chip->sw_map, chip->hw_map, 64);
+ bitmap_remap(hw_bits, bits, chip->sw_map, chip->hw_map, 64);
+
+ spin_lock_irqsave(&chip->gpio_lock, flags);
+
+ bitmap_replace(state, chip->state, hw_bits, hw_mask, 64);
+
+ xgpio_write_ch_all(chip, XGPIO_DATA_OFFSET, state);
+
+ bitmap_copy(chip->state, state, 64);
+
+ spin_unlock_irqrestore(&chip->gpio_lock, flags);
+}
+
+/**
+ * xgpio_dir_in - Set the direction of the specified GPIO signal as input.
+ * @gc: Pointer to gpio_chip device structure.
+ * @gpio: GPIO signal number.
+ *
+ * Return:
+ * 0 - if direction of GPIO signals is set as input
+ * otherwise it returns negative error value.
+ */
+static int xgpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+ unsigned long flags;
+ struct xgpio_instance *chip = gpiochip_get_data(gc);
+ int bit = xgpio_to_bit(chip, gpio);
+
+ spin_lock_irqsave(&chip->gpio_lock, flags);
+
+ /* Set the GPIO bit in shadow register and set direction as input */
+ __set_bit(bit, chip->dir);
+ xgpio_write_ch(chip, XGPIO_TRI_OFFSET, bit, chip->dir);
+
+ spin_unlock_irqrestore(&chip->gpio_lock, flags);
+
+ return 0;
+}
+
+/**
+ * xgpio_dir_out - Set the direction of the specified GPIO signal as output.
+ * @gc: Pointer to gpio_chip device structure.
+ * @gpio: GPIO signal number.
+ * @val: Value to be written to specified signal.
+ *
+ * This function sets the direction of specified GPIO signal as output.
+ *
+ * Return:
+ * If all GPIO signals of GPIO chip is configured as input then it returns
+ * error otherwise it returns 0.
+ */
+static int xgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ unsigned long flags;
+ struct xgpio_instance *chip = gpiochip_get_data(gc);
+ int bit = xgpio_to_bit(chip, gpio);
+
+ spin_lock_irqsave(&chip->gpio_lock, flags);
+
+ /* Write state of GPIO signal */
+ __assign_bit(bit, chip->state, val);
+ xgpio_write_ch(chip, XGPIO_DATA_OFFSET, bit, chip->state);
+
+ /* Clear the GPIO bit in shadow register and set direction as output */
+ __clear_bit(bit, chip->dir);
+ xgpio_write_ch(chip, XGPIO_TRI_OFFSET, bit, chip->dir);
+
+ spin_unlock_irqrestore(&chip->gpio_lock, flags);
+
+ return 0;
+}
+
+/**
+ * xgpio_save_regs - Set initial values of GPIO pins
+ * @chip: Pointer to GPIO instance
+ */
+static void xgpio_save_regs(struct xgpio_instance *chip)
+{
+ xgpio_write_ch_all(chip, XGPIO_DATA_OFFSET, chip->state);
+ xgpio_write_ch_all(chip, XGPIO_TRI_OFFSET, chip->dir);
+}
+
+static int xgpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ int ret;
+
+ ret = pm_runtime_get_sync(chip->parent);
+ /*
+ * If the device is already active pm_runtime_get() will return 1 on
+ * success, but gpio_request still needs to return 0.
+ */
+ return ret < 0 ? ret : 0;
+}
+
+static void xgpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+ pm_runtime_put(chip->parent);
+}
+
+static int __maybe_unused xgpio_suspend(struct device *dev)
+{
+ struct xgpio_instance *gpio = dev_get_drvdata(dev);
+ struct irq_data *data = irq_get_irq_data(gpio->irq);
+
+ if (!data) {
+ dev_dbg(dev, "IRQ not connected\n");
+ return pm_runtime_force_suspend(dev);
+ }
+
+ if (!irqd_is_wakeup_set(data))
+ return pm_runtime_force_suspend(dev);
+
+ return 0;
+}
+
+/**
+ * xgpio_remove - Remove method for the GPIO device.
+ * @pdev: pointer to the platform device
+ *
+ * This function remove gpiochips and frees all the allocated resources.
+ *
+ * Return: 0 always
+ */
+static int xgpio_remove(struct platform_device *pdev)
+{
+ struct xgpio_instance *gpio = platform_get_drvdata(pdev);
+
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ clk_disable_unprepare(gpio->clk);
+
+ return 0;
+}
+
+/**
+ * xgpio_irq_ack - Acknowledge a child GPIO interrupt.
+ * @irq_data: per IRQ and chip data passed down to chip functions
+ * This currently does nothing, but irq_ack is unconditionally called by
+ * handle_edge_irq and therefore must be defined.
+ */
+static void xgpio_irq_ack(struct irq_data *irq_data)
+{
+}
+
+static int __maybe_unused xgpio_resume(struct device *dev)
+{
+ struct xgpio_instance *gpio = dev_get_drvdata(dev);
+ struct irq_data *data = irq_get_irq_data(gpio->irq);
+
+ if (!data) {
+ dev_dbg(dev, "IRQ not connected\n");
+ return pm_runtime_force_resume(dev);
+ }
+
+ if (!irqd_is_wakeup_set(data))
+ return pm_runtime_force_resume(dev);
+
+ return 0;
+}
+
+static int __maybe_unused xgpio_runtime_suspend(struct device *dev)
+{
+ struct xgpio_instance *gpio = dev_get_drvdata(dev);
+
+ clk_disable(gpio->clk);
+
+ return 0;
+}
+
+static int __maybe_unused xgpio_runtime_resume(struct device *dev)
+{
+ struct xgpio_instance *gpio = dev_get_drvdata(dev);
+
+ return clk_enable(gpio->clk);
+}
+
+static const struct dev_pm_ops xgpio_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(xgpio_suspend, xgpio_resume)
+ SET_RUNTIME_PM_OPS(xgpio_runtime_suspend,
+ xgpio_runtime_resume, NULL)
+};
+
+/**
+ * xgpio_irq_mask - Write the specified signal of the GPIO device.
+ * @irq_data: per IRQ and chip data passed down to chip functions
+ */
+static void xgpio_irq_mask(struct irq_data *irq_data)
+{
+ unsigned long flags;
+ struct xgpio_instance *chip = irq_data_get_irq_chip_data(irq_data);
+ int irq_offset = irqd_to_hwirq(irq_data);
+ int bit = xgpio_to_bit(chip, irq_offset);
+ u32 mask = BIT(bit / 32), temp;
+
+ spin_lock_irqsave(&chip->gpio_lock, flags);
+
+ __clear_bit(bit, chip->enable);
+
+ if (xgpio_get_value32(chip->enable, bit) == 0) {
+ /* Disable per channel interrupt */
+ temp = xgpio_readreg(chip->regs + XGPIO_IPIER_OFFSET);
+ temp &= ~mask;
+ xgpio_writereg(chip->regs + XGPIO_IPIER_OFFSET, temp);
+ }
+ spin_unlock_irqrestore(&chip->gpio_lock, flags);
+
+ gpiochip_disable_irq(&chip->gc, irq_offset);
+}
+
+/**
+ * xgpio_irq_unmask - Write the specified signal of the GPIO device.
+ * @irq_data: per IRQ and chip data passed down to chip functions
+ */
+static void xgpio_irq_unmask(struct irq_data *irq_data)
+{
+ unsigned long flags;
+ struct xgpio_instance *chip = irq_data_get_irq_chip_data(irq_data);
+ int irq_offset = irqd_to_hwirq(irq_data);
+ int bit = xgpio_to_bit(chip, irq_offset);
+ u32 old_enable = xgpio_get_value32(chip->enable, bit);
+ u32 mask = BIT(bit / 32), val;
+
+ gpiochip_enable_irq(&chip->gc, irq_offset);
+
+ spin_lock_irqsave(&chip->gpio_lock, flags);
+
+ __set_bit(bit, chip->enable);
+
+ if (old_enable == 0) {
+ /* Clear any existing per-channel interrupts */
+ val = xgpio_readreg(chip->regs + XGPIO_IPISR_OFFSET);
+ val &= mask;
+ xgpio_writereg(chip->regs + XGPIO_IPISR_OFFSET, val);
+
+ /* Update GPIO IRQ read data before enabling interrupt*/
+ xgpio_read_ch(chip, XGPIO_DATA_OFFSET, bit, chip->last_irq_read);
+
+ /* Enable per channel interrupt */
+ val = xgpio_readreg(chip->regs + XGPIO_IPIER_OFFSET);
+ val |= mask;
+ xgpio_writereg(chip->regs + XGPIO_IPIER_OFFSET, val);
+ }
+
+ spin_unlock_irqrestore(&chip->gpio_lock, flags);
+}
+
+/**
+ * xgpio_set_irq_type - Write the specified signal of the GPIO device.
+ * @irq_data: Per IRQ and chip data passed down to chip functions
+ * @type: Interrupt type that is to be set for the gpio pin
+ *
+ * Return:
+ * 0 if interrupt type is supported otherwise -EINVAL
+ */
+static int xgpio_set_irq_type(struct irq_data *irq_data, unsigned int type)
+{
+ struct xgpio_instance *chip = irq_data_get_irq_chip_data(irq_data);
+ int irq_offset = irqd_to_hwirq(irq_data);
+ int bit = xgpio_to_bit(chip, irq_offset);
+
+ /*
+ * The Xilinx GPIO hardware provides a single interrupt status
+ * indication for any state change in a given GPIO channel (bank).
+ * Therefore, only rising edge or falling edge triggers are
+ * supported.
+ */
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_BOTH:
+ __set_bit(bit, chip->rising_edge);
+ __set_bit(bit, chip->falling_edge);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ __set_bit(bit, chip->rising_edge);
+ __clear_bit(bit, chip->falling_edge);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ __clear_bit(bit, chip->rising_edge);
+ __set_bit(bit, chip->falling_edge);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ irq_set_handler_locked(irq_data, handle_edge_irq);
+ return 0;
+}
+
+/**
+ * xgpio_irqhandler - Gpio interrupt service routine
+ * @desc: Pointer to interrupt description
+ */
+static void xgpio_irqhandler(struct irq_desc *desc)
+{
+ struct xgpio_instance *chip = irq_desc_get_handler_data(desc);
+ struct gpio_chip *gc = &chip->gc;
+ struct irq_chip *irqchip = irq_desc_get_chip(desc);
+ DECLARE_BITMAP(rising, 64);
+ DECLARE_BITMAP(falling, 64);
+ DECLARE_BITMAP(all, 64);
+ int irq_offset;
+ u32 status;
+ u32 bit;
+
+ status = xgpio_readreg(chip->regs + XGPIO_IPISR_OFFSET);
+ xgpio_writereg(chip->regs + XGPIO_IPISR_OFFSET, status);
+
+ chained_irq_enter(irqchip, desc);
+
+ spin_lock(&chip->gpio_lock);
+
+ xgpio_read_ch_all(chip, XGPIO_DATA_OFFSET, all);
+
+ bitmap_complement(rising, chip->last_irq_read, 64);
+ bitmap_and(rising, rising, all, 64);
+ bitmap_and(rising, rising, chip->enable, 64);
+ bitmap_and(rising, rising, chip->rising_edge, 64);
+
+ bitmap_complement(falling, all, 64);
+ bitmap_and(falling, falling, chip->last_irq_read, 64);
+ bitmap_and(falling, falling, chip->enable, 64);
+ bitmap_and(falling, falling, chip->falling_edge, 64);
+
+ bitmap_copy(chip->last_irq_read, all, 64);
+ bitmap_or(all, rising, falling, 64);
+
+ spin_unlock(&chip->gpio_lock);
+
+ dev_dbg(gc->parent, "IRQ rising %*pb falling %*pb\n", 64, rising, 64, falling);
+
+ for_each_set_bit(bit, all, 64) {
+ irq_offset = xgpio_from_bit(chip, bit);
+ generic_handle_domain_irq(gc->irq.domain, irq_offset);
+ }
+
+ chained_irq_exit(irqchip, desc);
+}
+
+static const struct irq_chip xgpio_irq_chip = {
+ .name = "gpio-xilinx",
+ .irq_ack = xgpio_irq_ack,
+ .irq_mask = xgpio_irq_mask,
+ .irq_unmask = xgpio_irq_unmask,
+ .irq_set_type = xgpio_set_irq_type,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+/**
+ * xgpio_probe - Probe method for the GPIO device.
+ * @pdev: pointer to the platform device
+ *
+ * Return:
+ * It returns 0, if the driver is bound to the GPIO device, or
+ * a negative value if there is an error.
+ */
+static int xgpio_probe(struct platform_device *pdev)
+{
+ struct xgpio_instance *chip;
+ int status = 0;
+ struct device_node *np = pdev->dev.of_node;
+ u32 is_dual = 0;
+ u32 width[2];
+ u32 state[2];
+ u32 dir[2];
+ struct gpio_irq_chip *girq;
+ u32 temp;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, chip);
+
+ /* First, check if the device is dual-channel */
+ of_property_read_u32(np, "xlnx,is-dual", &is_dual);
+
+ /* Setup defaults */
+ memset32(width, 0, ARRAY_SIZE(width));
+ memset32(state, 0, ARRAY_SIZE(state));
+ memset32(dir, 0xFFFFFFFF, ARRAY_SIZE(dir));
+
+ /* Update GPIO state shadow register with default value */
+ of_property_read_u32(np, "xlnx,dout-default", &state[0]);
+ of_property_read_u32(np, "xlnx,dout-default-2", &state[1]);
+
+ bitmap_from_arr32(chip->state, state, 64);
+
+ /* Update GPIO direction shadow register with default value */
+ of_property_read_u32(np, "xlnx,tri-default", &dir[0]);
+ of_property_read_u32(np, "xlnx,tri-default-2", &dir[1]);
+
+ bitmap_from_arr32(chip->dir, dir, 64);
+
+ /*
+ * Check device node and parent device node for device width
+ * and assume default width of 32
+ */
+ if (of_property_read_u32(np, "xlnx,gpio-width", &width[0]))
+ width[0] = 32;
+
+ if (width[0] > 32)
+ return -EINVAL;
+
+ if (is_dual && of_property_read_u32(np, "xlnx,gpio2-width", &width[1]))
+ width[1] = 32;
+
+ if (width[1] > 32)
+ return -EINVAL;
+
+ /* Setup software pin mapping */
+ bitmap_set(chip->sw_map, 0, width[0] + width[1]);
+
+ /* Setup hardware pin mapping */
+ bitmap_set(chip->hw_map, 0, width[0]);
+ bitmap_set(chip->hw_map, 32, width[1]);
+
+ spin_lock_init(&chip->gpio_lock);
+
+ chip->gc.base = -1;
+ chip->gc.ngpio = bitmap_weight(chip->hw_map, 64);
+ chip->gc.parent = &pdev->dev;
+ chip->gc.direction_input = xgpio_dir_in;
+ chip->gc.direction_output = xgpio_dir_out;
+ chip->gc.get = xgpio_get;
+ chip->gc.set = xgpio_set;
+ chip->gc.request = xgpio_request;
+ chip->gc.free = xgpio_free;
+ chip->gc.set_multiple = xgpio_set_multiple;
+
+ chip->gc.label = dev_name(&pdev->dev);
+
+ chip->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(chip->regs)) {
+ dev_err(&pdev->dev, "failed to ioremap memory resource\n");
+ return PTR_ERR(chip->regs);
+ }
+
+ chip->clk = devm_clk_get_optional(&pdev->dev, NULL);
+ if (IS_ERR(chip->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(chip->clk), "input clock not found.\n");
+
+ status = clk_prepare_enable(chip->clk);
+ if (status < 0) {
+ dev_err(&pdev->dev, "Failed to prepare clk\n");
+ return status;
+ }
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ xgpio_save_regs(chip);
+
+ chip->irq = platform_get_irq_optional(pdev, 0);
+ if (chip->irq <= 0)
+ goto skip_irq;
+
+ /* Disable per-channel interrupts */
+ xgpio_writereg(chip->regs + XGPIO_IPIER_OFFSET, 0);
+ /* Clear any existing per-channel interrupts */
+ temp = xgpio_readreg(chip->regs + XGPIO_IPISR_OFFSET);
+ xgpio_writereg(chip->regs + XGPIO_IPISR_OFFSET, temp);
+ /* Enable global interrupts */
+ xgpio_writereg(chip->regs + XGPIO_GIER_OFFSET, XGPIO_GIER_IE);
+
+ girq = &chip->gc.irq;
+ gpio_irq_chip_set_chip(girq, &xgpio_irq_chip);
+ girq->parent_handler = xgpio_irqhandler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(&pdev->dev, 1,
+ sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents) {
+ status = -ENOMEM;
+ goto err_pm_put;
+ }
+ girq->parents[0] = chip->irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+
+skip_irq:
+ status = devm_gpiochip_add_data(&pdev->dev, &chip->gc, chip);
+ if (status) {
+ dev_err(&pdev->dev, "failed to add GPIO chip\n");
+ goto err_pm_put;
+ }
+
+ pm_runtime_put(&pdev->dev);
+ return 0;
+
+err_pm_put:
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ clk_disable_unprepare(chip->clk);
+ return status;
+}
+
+static const struct of_device_id xgpio_of_match[] = {
+ { .compatible = "xlnx,xps-gpio-1.00.a", },
+ { /* end of list */ },
+};
+
+MODULE_DEVICE_TABLE(of, xgpio_of_match);
+
+static struct platform_driver xgpio_plat_driver = {
+ .probe = xgpio_probe,
+ .remove = xgpio_remove,
+ .driver = {
+ .name = "gpio-xilinx",
+ .of_match_table = xgpio_of_match,
+ .pm = &xgpio_dev_pm_ops,
+ },
+};
+
+static int __init xgpio_init(void)
+{
+ return platform_driver_register(&xgpio_plat_driver);
+}
+
+subsys_initcall(xgpio_init);
+
+static void __exit xgpio_exit(void)
+{
+ platform_driver_unregister(&xgpio_plat_driver);
+}
+module_exit(xgpio_exit);
+
+MODULE_AUTHOR("Xilinx, Inc.");
+MODULE_DESCRIPTION("Xilinx GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-xlp.c b/drivers/gpio/gpio-xlp.c
new file mode 100644
index 0000000000..b4b52213bc
--- /dev/null
+++ b/drivers/gpio/gpio-xlp.c
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2003-2015 Broadcom Corporation
+ * All Rights Reserved
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/acpi.h>
+
+/*
+ * XLP GPIO has multiple 32 bit registers for each feature where each register
+ * controls 32 pins. So, pins up to 64 require 2 32-bit registers and up to 96
+ * require 3 32-bit registers for each feature.
+ * Here we only define offset of the first register for each feature. Offset of
+ * the registers for pins greater than 32 can be calculated as following(Use
+ * GPIO_INT_STAT as example):
+ *
+ * offset = (gpio / XLP_GPIO_REGSZ) * 4;
+ * reg_addr = addr + offset;
+ *
+ * where addr is base address of the that feature register and gpio is the pin.
+ */
+#define GPIO_9XX_BYTESWAP 0X00
+#define GPIO_9XX_CTRL 0X04
+#define GPIO_9XX_OUTPUT_EN 0x14
+#define GPIO_9XX_PADDRV 0x24
+/*
+ * Only for 4 interrupt enable reg are defined for now,
+ * total reg available are 12.
+ */
+#define GPIO_9XX_INT_EN00 0x44
+#define GPIO_9XX_INT_EN10 0x54
+#define GPIO_9XX_INT_EN20 0x64
+#define GPIO_9XX_INT_EN30 0x74
+#define GPIO_9XX_INT_POL 0x104
+#define GPIO_9XX_INT_TYPE 0x114
+#define GPIO_9XX_INT_STAT 0x124
+
+/* Interrupt type register mask */
+#define XLP_GPIO_IRQ_TYPE_LVL 0x0
+#define XLP_GPIO_IRQ_TYPE_EDGE 0x1
+
+/* Interrupt polarity register mask */
+#define XLP_GPIO_IRQ_POL_HIGH 0x0
+#define XLP_GPIO_IRQ_POL_LOW 0x1
+
+#define XLP_GPIO_REGSZ 32
+#define XLP_GPIO_IRQ_BASE 768
+#define XLP_MAX_NR_GPIO 96
+
+struct xlp_gpio_priv {
+ struct gpio_chip chip;
+ DECLARE_BITMAP(gpio_enabled_mask, XLP_MAX_NR_GPIO);
+ void __iomem *gpio_intr_en; /* pointer to first intr enable reg */
+ void __iomem *gpio_intr_stat; /* pointer to first intr status reg */
+ void __iomem *gpio_intr_type; /* pointer to first intr type reg */
+ void __iomem *gpio_intr_pol; /* pointer to first intr polarity reg */
+ void __iomem *gpio_out_en; /* pointer to first output enable reg */
+ void __iomem *gpio_paddrv; /* pointer to first pad drive reg */
+ spinlock_t lock;
+};
+
+static int xlp_gpio_get_reg(void __iomem *addr, unsigned gpio)
+{
+ u32 pos, regset;
+
+ pos = gpio % XLP_GPIO_REGSZ;
+ regset = (gpio / XLP_GPIO_REGSZ) * 4;
+ return !!(readl(addr + regset) & BIT(pos));
+}
+
+static void xlp_gpio_set_reg(void __iomem *addr, unsigned gpio, int state)
+{
+ u32 value, pos, regset;
+
+ pos = gpio % XLP_GPIO_REGSZ;
+ regset = (gpio / XLP_GPIO_REGSZ) * 4;
+ value = readl(addr + regset);
+
+ if (state)
+ value |= BIT(pos);
+ else
+ value &= ~BIT(pos);
+
+ writel(value, addr + regset);
+}
+
+static void xlp_gpio_irq_enable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+
+ gpiochip_enable_irq(gc, irqd_to_hwirq(d));
+}
+
+static void xlp_gpio_irq_disable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct xlp_gpio_priv *priv = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ xlp_gpio_set_reg(priv->gpio_intr_en, d->hwirq, 0x0);
+ __clear_bit(d->hwirq, priv->gpio_enabled_mask);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ gpiochip_disable_irq(gc, irqd_to_hwirq(d));
+}
+
+static void xlp_gpio_irq_mask_ack(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct xlp_gpio_priv *priv = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ xlp_gpio_set_reg(priv->gpio_intr_en, d->hwirq, 0x0);
+ xlp_gpio_set_reg(priv->gpio_intr_stat, d->hwirq, 0x1);
+ __clear_bit(d->hwirq, priv->gpio_enabled_mask);
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static void xlp_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct xlp_gpio_priv *priv = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ xlp_gpio_set_reg(priv->gpio_intr_en, d->hwirq, 0x1);
+ __set_bit(d->hwirq, priv->gpio_enabled_mask);
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static int xlp_gpio_set_irq_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct xlp_gpio_priv *priv = gpiochip_get_data(gc);
+ int pol, irq_type;
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ irq_type = XLP_GPIO_IRQ_TYPE_EDGE;
+ pol = XLP_GPIO_IRQ_POL_HIGH;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ irq_type = XLP_GPIO_IRQ_TYPE_EDGE;
+ pol = XLP_GPIO_IRQ_POL_LOW;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ irq_type = XLP_GPIO_IRQ_TYPE_LVL;
+ pol = XLP_GPIO_IRQ_POL_HIGH;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ irq_type = XLP_GPIO_IRQ_TYPE_LVL;
+ pol = XLP_GPIO_IRQ_POL_LOW;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ xlp_gpio_set_reg(priv->gpio_intr_type, d->hwirq, irq_type);
+ xlp_gpio_set_reg(priv->gpio_intr_pol, d->hwirq, pol);
+
+ return 0;
+}
+
+static struct irq_chip xlp_gpio_irq_chip = {
+ .name = "XLP-GPIO",
+ .irq_mask_ack = xlp_gpio_irq_mask_ack,
+ .irq_enable = xlp_gpio_irq_enable,
+ .irq_disable = xlp_gpio_irq_disable,
+ .irq_set_type = xlp_gpio_set_irq_type,
+ .irq_unmask = xlp_gpio_irq_unmask,
+ .flags = IRQCHIP_ONESHOT_SAFE | IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static void xlp_gpio_generic_handler(struct irq_desc *desc)
+{
+ struct xlp_gpio_priv *priv = irq_desc_get_handler_data(desc);
+ struct irq_chip *irqchip = irq_desc_get_chip(desc);
+ int gpio, regoff;
+ u32 gpio_stat;
+
+ regoff = -1;
+ gpio_stat = 0;
+
+ chained_irq_enter(irqchip, desc);
+ for_each_set_bit(gpio, priv->gpio_enabled_mask, XLP_MAX_NR_GPIO) {
+ if (regoff != gpio / XLP_GPIO_REGSZ) {
+ regoff = gpio / XLP_GPIO_REGSZ;
+ gpio_stat = readl(priv->gpio_intr_stat + regoff * 4);
+ }
+
+ if (gpio_stat & BIT(gpio % XLP_GPIO_REGSZ))
+ generic_handle_domain_irq(priv->chip.irq.domain, gpio);
+ }
+ chained_irq_exit(irqchip, desc);
+}
+
+static int xlp_gpio_dir_output(struct gpio_chip *gc, unsigned gpio, int state)
+{
+ struct xlp_gpio_priv *priv = gpiochip_get_data(gc);
+
+ BUG_ON(gpio >= gc->ngpio);
+ xlp_gpio_set_reg(priv->gpio_out_en, gpio, 0x1);
+
+ return 0;
+}
+
+static int xlp_gpio_dir_input(struct gpio_chip *gc, unsigned gpio)
+{
+ struct xlp_gpio_priv *priv = gpiochip_get_data(gc);
+
+ BUG_ON(gpio >= gc->ngpio);
+ xlp_gpio_set_reg(priv->gpio_out_en, gpio, 0x0);
+
+ return 0;
+}
+
+static int xlp_gpio_get(struct gpio_chip *gc, unsigned gpio)
+{
+ struct xlp_gpio_priv *priv = gpiochip_get_data(gc);
+
+ BUG_ON(gpio >= gc->ngpio);
+ return xlp_gpio_get_reg(priv->gpio_paddrv, gpio);
+}
+
+static void xlp_gpio_set(struct gpio_chip *gc, unsigned gpio, int state)
+{
+ struct xlp_gpio_priv *priv = gpiochip_get_data(gc);
+
+ BUG_ON(gpio >= gc->ngpio);
+ xlp_gpio_set_reg(priv->gpio_paddrv, gpio, state);
+}
+
+static int xlp_gpio_probe(struct platform_device *pdev)
+{
+ struct gpio_chip *gc;
+ struct gpio_irq_chip *girq;
+ struct xlp_gpio_priv *priv;
+ void __iomem *gpio_base;
+ int irq, err;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ gpio_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gpio_base))
+ return PTR_ERR(gpio_base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ priv->gpio_out_en = gpio_base + GPIO_9XX_OUTPUT_EN;
+ priv->gpio_paddrv = gpio_base + GPIO_9XX_PADDRV;
+ priv->gpio_intr_stat = gpio_base + GPIO_9XX_INT_STAT;
+ priv->gpio_intr_type = gpio_base + GPIO_9XX_INT_TYPE;
+ priv->gpio_intr_pol = gpio_base + GPIO_9XX_INT_POL;
+ priv->gpio_intr_en = gpio_base + GPIO_9XX_INT_EN00;
+
+ bitmap_zero(priv->gpio_enabled_mask, XLP_MAX_NR_GPIO);
+
+ gc = &priv->chip;
+
+ gc->owner = THIS_MODULE;
+ gc->label = dev_name(&pdev->dev);
+ gc->base = 0;
+ gc->parent = &pdev->dev;
+ gc->ngpio = 70;
+ gc->direction_output = xlp_gpio_dir_output;
+ gc->direction_input = xlp_gpio_dir_input;
+ gc->set = xlp_gpio_set;
+ gc->get = xlp_gpio_get;
+
+ spin_lock_init(&priv->lock);
+
+ girq = &gc->irq;
+ gpio_irq_chip_set_chip(girq, &xlp_gpio_irq_chip);
+ girq->parent_handler = xlp_gpio_generic_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(&pdev->dev, 1,
+ sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ girq->parents[0] = irq;
+ girq->first = 0;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_level_irq;
+
+ err = gpiochip_add_data(gc, priv);
+ if (err < 0)
+ return err;
+
+ dev_info(&pdev->dev, "registered %d GPIOs\n", gc->ngpio);
+
+ return 0;
+}
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id xlp_gpio_acpi_match[] = {
+ { "BRCM9006" },
+ { "CAV9006" },
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, xlp_gpio_acpi_match);
+#endif
+
+static struct platform_driver xlp_gpio_driver = {
+ .driver = {
+ .name = "xlp-gpio",
+ .acpi_match_table = ACPI_PTR(xlp_gpio_acpi_match),
+ },
+ .probe = xlp_gpio_probe,
+};
+module_platform_driver(xlp_gpio_driver);
+
+MODULE_AUTHOR("Kamlakant Patel <kamlakant.patel@broadcom.com>");
+MODULE_AUTHOR("Ganesan Ramalingam <ganesanr@broadcom.com>");
+MODULE_DESCRIPTION("Netlogic XLP GPIO Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-xra1403.c b/drivers/gpio/gpio-xra1403.c
new file mode 100644
index 0000000000..dc2710c21c
--- /dev/null
+++ b/drivers/gpio/gpio-xra1403.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for EXAR XRA1403 16-bit GPIO expander
+ *
+ * Copyright (c) 2017, General Electric Company
+ */
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/seq_file.h>
+#include <linux/spi/spi.h>
+#include <linux/regmap.h>
+
+/* XRA1403 registers */
+#define XRA_GSR 0x00 /* GPIO State */
+#define XRA_OCR 0x02 /* Output Control */
+#define XRA_PIR 0x04 /* Input Polarity Inversion */
+#define XRA_GCR 0x06 /* GPIO Configuration */
+#define XRA_PUR 0x08 /* Input Internal Pull-up Resistor Enable/Disable */
+#define XRA_IER 0x0A /* Input Interrupt Enable */
+#define XRA_TSCR 0x0C /* Output Three-State Control */
+#define XRA_ISR 0x0E /* Input Interrupt Status */
+#define XRA_REIR 0x10 /* Input Rising Edge Interrupt Enable */
+#define XRA_FEIR 0x12 /* Input Falling Edge Interrupt Enable */
+#define XRA_IFR 0x14 /* Input Filter Enable/Disable */
+#define XRA_LAST 0x15 /* Bounds */
+
+struct xra1403 {
+ struct gpio_chip chip;
+ struct regmap *regmap;
+};
+
+static const struct regmap_config xra1403_regmap_cfg = {
+ .reg_bits = 7,
+ .pad_bits = 1,
+ .val_bits = 8,
+
+ .max_register = XRA_LAST,
+};
+
+static unsigned int to_reg(unsigned int reg, unsigned int offset)
+{
+ return reg + (offset > 7);
+}
+
+static int xra1403_direction_input(struct gpio_chip *chip, unsigned int offset)
+{
+ struct xra1403 *xra = gpiochip_get_data(chip);
+
+ return regmap_update_bits(xra->regmap, to_reg(XRA_GCR, offset),
+ BIT(offset % 8), BIT(offset % 8));
+}
+
+static int xra1403_direction_output(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ int ret;
+ struct xra1403 *xra = gpiochip_get_data(chip);
+
+ ret = regmap_update_bits(xra->regmap, to_reg(XRA_GCR, offset),
+ BIT(offset % 8), 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(xra->regmap, to_reg(XRA_OCR, offset),
+ BIT(offset % 8), value ? BIT(offset % 8) : 0);
+
+ return ret;
+}
+
+static int xra1403_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ int ret;
+ unsigned int val;
+ struct xra1403 *xra = gpiochip_get_data(chip);
+
+ ret = regmap_read(xra->regmap, to_reg(XRA_GCR, offset), &val);
+ if (ret)
+ return ret;
+
+ if (val & BIT(offset % 8))
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int xra1403_get(struct gpio_chip *chip, unsigned int offset)
+{
+ int ret;
+ unsigned int val;
+ struct xra1403 *xra = gpiochip_get_data(chip);
+
+ ret = regmap_read(xra->regmap, to_reg(XRA_GSR, offset), &val);
+ if (ret)
+ return ret;
+
+ return !!(val & BIT(offset % 8));
+}
+
+static void xra1403_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ int ret;
+ struct xra1403 *xra = gpiochip_get_data(chip);
+
+ ret = regmap_update_bits(xra->regmap, to_reg(XRA_OCR, offset),
+ BIT(offset % 8), value ? BIT(offset % 8) : 0);
+ if (ret)
+ dev_err(chip->parent, "Failed to set pin: %d, ret: %d\n",
+ offset, ret);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static void xra1403_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ int reg;
+ struct xra1403 *xra = gpiochip_get_data(chip);
+ int value[XRA_LAST];
+ int i;
+ const char *label;
+ unsigned int gcr;
+ unsigned int gsr;
+
+ seq_puts(s, "xra reg:");
+ for (reg = 0; reg <= XRA_LAST; reg++)
+ seq_printf(s, " %2.2x", reg);
+ seq_puts(s, "\n value:");
+ for (reg = 0; reg < XRA_LAST; reg++) {
+ regmap_read(xra->regmap, reg, &value[reg]);
+ seq_printf(s, " %2.2x", value[reg]);
+ }
+ seq_puts(s, "\n");
+
+ gcr = value[XRA_GCR + 1] << 8 | value[XRA_GCR];
+ gsr = value[XRA_GSR + 1] << 8 | value[XRA_GSR];
+ for_each_requested_gpio(chip, i, label) {
+ seq_printf(s, " gpio-%-3d (%-12s) %s %s\n",
+ chip->base + i, label,
+ (gcr & BIT(i)) ? "in" : "out",
+ (gsr & BIT(i)) ? "hi" : "lo");
+ }
+}
+#else
+#define xra1403_dbg_show NULL
+#endif
+
+static int xra1403_probe(struct spi_device *spi)
+{
+ struct xra1403 *xra;
+ struct gpio_desc *reset_gpio;
+ int ret;
+
+ xra = devm_kzalloc(&spi->dev, sizeof(*xra), GFP_KERNEL);
+ if (!xra)
+ return -ENOMEM;
+
+ /* bring the chip out of reset if reset pin is provided*/
+ reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(reset_gpio))
+ dev_warn(&spi->dev, "Could not get reset-gpios\n");
+
+ xra->chip.direction_input = xra1403_direction_input;
+ xra->chip.direction_output = xra1403_direction_output;
+ xra->chip.get_direction = xra1403_get_direction;
+ xra->chip.get = xra1403_get;
+ xra->chip.set = xra1403_set;
+
+ xra->chip.dbg_show = xra1403_dbg_show;
+
+ xra->chip.ngpio = 16;
+ xra->chip.label = "xra1403";
+
+ xra->chip.base = -1;
+ xra->chip.can_sleep = true;
+ xra->chip.parent = &spi->dev;
+ xra->chip.owner = THIS_MODULE;
+
+ xra->regmap = devm_regmap_init_spi(spi, &xra1403_regmap_cfg);
+ if (IS_ERR(xra->regmap)) {
+ ret = PTR_ERR(xra->regmap);
+ dev_err(&spi->dev, "Failed to allocate regmap: %d\n", ret);
+ return ret;
+ }
+
+ return devm_gpiochip_add_data(&spi->dev, &xra->chip, xra);
+}
+
+static const struct spi_device_id xra1403_ids[] = {
+ { "xra1403" },
+ {},
+};
+MODULE_DEVICE_TABLE(spi, xra1403_ids);
+
+static const struct of_device_id xra1403_spi_of_match[] = {
+ { .compatible = "exar,xra1403" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, xra1403_spi_of_match);
+
+static struct spi_driver xra1403_driver = {
+ .probe = xra1403_probe,
+ .id_table = xra1403_ids,
+ .driver = {
+ .name = "xra1403",
+ .of_match_table = xra1403_spi_of_match,
+ },
+};
+
+module_spi_driver(xra1403_driver);
+
+MODULE_AUTHOR("Nandor Han <nandor.han@ge.com>");
+MODULE_AUTHOR("Semi Malinen <semi.malinen@ge.com>");
+MODULE_DESCRIPTION("GPIO expander driver for EXAR XRA1403");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-xtensa.c b/drivers/gpio/gpio-xtensa.c
new file mode 100644
index 0000000000..c8af34a636
--- /dev/null
+++ b/drivers/gpio/gpio-xtensa.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2013 TangoTec Ltd.
+ * Author: Baruch Siach <baruch@tkos.co.il>
+ *
+ * Driver for the Xtensa LX4 GPIO32 Option
+ *
+ * Documentation: Xtensa LX4 Microprocessor Data Book, Section 2.22
+ *
+ * GPIO32 is a standard optional extension to the Xtensa architecture core that
+ * provides preconfigured output and input ports for intra SoC signaling. The
+ * GPIO32 option is implemented as 32bit Tensilica Instruction Extension (TIE)
+ * output state called EXPSTATE, and 32bit input wire called IMPWIRE. This
+ * driver treats input and output states as two distinct devices.
+ *
+ * Access to GPIO32 specific instructions is controlled by the CPENABLE
+ * (Coprocessor Enable Bits) register. By default Xtensa Linux startup code
+ * disables access to all coprocessors. This driver sets the CPENABLE bit
+ * corresponding to GPIO32 before any GPIO32 specific instruction, and restores
+ * CPENABLE state after that.
+ *
+ * This driver is currently incompatible with SMP. The GPIO32 extension is not
+ * guaranteed to be available in all cores. Moreover, each core controls a
+ * different set of IO wires. A theoretical SMP aware version of this driver
+ * would need to have a per core workqueue to do the actual GPIO manipulation.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/gpio/driver.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+
+#include <asm/coprocessor.h> /* CPENABLE read/write macros */
+
+#ifndef XCHAL_CP_ID_XTIOP
+#error GPIO32 option is not enabled for your xtensa core variant
+#endif
+
+#if XCHAL_HAVE_CP
+
+static inline unsigned long enable_cp(unsigned long *cpenable)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ *cpenable = xtensa_get_sr(cpenable);
+ xtensa_set_sr(*cpenable | BIT(XCHAL_CP_ID_XTIOP), cpenable);
+ return flags;
+}
+
+static inline void disable_cp(unsigned long flags, unsigned long cpenable)
+{
+ xtensa_set_sr(cpenable, cpenable);
+ local_irq_restore(flags);
+}
+
+#else
+
+static inline unsigned long enable_cp(unsigned long *cpenable)
+{
+ *cpenable = 0; /* avoid uninitialized value warning */
+ return 0;
+}
+
+static inline void disable_cp(unsigned long flags, unsigned long cpenable)
+{
+}
+
+#endif /* XCHAL_HAVE_CP */
+
+static int xtensa_impwire_get_direction(struct gpio_chip *gc, unsigned offset)
+{
+ return GPIO_LINE_DIRECTION_IN; /* input only */
+}
+
+static int xtensa_impwire_get_value(struct gpio_chip *gc, unsigned offset)
+{
+ unsigned long flags, saved_cpenable;
+ u32 impwire;
+
+ flags = enable_cp(&saved_cpenable);
+ __asm__ __volatile__("read_impwire %0" : "=a" (impwire));
+ disable_cp(flags, saved_cpenable);
+
+ return !!(impwire & BIT(offset));
+}
+
+static void xtensa_impwire_set_value(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ BUG(); /* output only; should never be called */
+}
+
+static int xtensa_expstate_get_direction(struct gpio_chip *gc, unsigned offset)
+{
+ return GPIO_LINE_DIRECTION_OUT; /* output only */
+}
+
+static int xtensa_expstate_get_value(struct gpio_chip *gc, unsigned offset)
+{
+ unsigned long flags, saved_cpenable;
+ u32 expstate;
+
+ flags = enable_cp(&saved_cpenable);
+ __asm__ __volatile__("rur.expstate %0" : "=a" (expstate));
+ disable_cp(flags, saved_cpenable);
+
+ return !!(expstate & BIT(offset));
+}
+
+static void xtensa_expstate_set_value(struct gpio_chip *gc, unsigned offset,
+ int value)
+{
+ unsigned long flags, saved_cpenable;
+ u32 mask = BIT(offset);
+ u32 val = value ? BIT(offset) : 0;
+
+ flags = enable_cp(&saved_cpenable);
+ __asm__ __volatile__("wrmsk_expstate %0, %1"
+ :: "a" (val), "a" (mask));
+ disable_cp(flags, saved_cpenable);
+}
+
+static struct gpio_chip impwire_chip = {
+ .label = "impwire",
+ .base = -1,
+ .ngpio = 32,
+ .get_direction = xtensa_impwire_get_direction,
+ .get = xtensa_impwire_get_value,
+ .set = xtensa_impwire_set_value,
+};
+
+static struct gpio_chip expstate_chip = {
+ .label = "expstate",
+ .base = -1,
+ .ngpio = 32,
+ .get_direction = xtensa_expstate_get_direction,
+ .get = xtensa_expstate_get_value,
+ .set = xtensa_expstate_set_value,
+};
+
+static int xtensa_gpio_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ ret = gpiochip_add_data(&impwire_chip, NULL);
+ if (ret)
+ return ret;
+ return gpiochip_add_data(&expstate_chip, NULL);
+}
+
+static struct platform_driver xtensa_gpio_driver = {
+ .driver = {
+ .name = "xtensa-gpio",
+ },
+ .probe = xtensa_gpio_probe,
+};
+
+static int __init xtensa_gpio_init(void)
+{
+ struct platform_device *pdev;
+
+ pdev = platform_device_register_simple("xtensa-gpio", 0, NULL, 0);
+ if (IS_ERR(pdev))
+ return PTR_ERR(pdev);
+
+ return platform_driver_register(&xtensa_gpio_driver);
+}
+device_initcall(xtensa_gpio_init);
+
+MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>");
+MODULE_DESCRIPTION("Xtensa LX4 GPIO32 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-zevio.c b/drivers/gpio/gpio-zevio.c
new file mode 100644
index 0000000000..2de61337ad
--- /dev/null
+++ b/drivers/gpio/gpio-zevio.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO controller in LSI ZEVIO SoCs.
+ *
+ * Author: Fabian Vogt <fabian@ritter-vogt.de>
+ */
+
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <linux/gpio/driver.h>
+
+/*
+ * Memory layout:
+ * This chip has four gpio sections, each controls 8 GPIOs.
+ * Bit 0 in section 0 is GPIO 0, bit 2 in section 1 is GPIO 10.
+ * Disclaimer: Reverse engineered!
+ * For more information refer to:
+ * http://hackspire.unsads.com/wiki/index.php/Memory-mapped_I/O_ports#90000000_-_General_Purpose_I.2FO_.28GPIO.29
+ *
+ * 0x00-0x3F: Section 0
+ * +0x00: Masked interrupt status (read-only)
+ * +0x04: R: Interrupt status W: Reset interrupt status
+ * +0x08: R: Interrupt mask W: Mask interrupt
+ * +0x0C: W: Unmask interrupt (write-only)
+ * +0x10: Direction: I/O=1/0
+ * +0x14: Output
+ * +0x18: Input (read-only)
+ * +0x20: R: Level interrupt W: Set as level interrupt
+ * 0x40-0x7F: Section 1
+ * 0x80-0xBF: Section 2
+ * 0xC0-0xFF: Section 3
+ */
+
+#define ZEVIO_GPIO_SECTION_SIZE 0x40
+
+/* Offsets to various registers */
+#define ZEVIO_GPIO_INT_MASKED_STATUS 0x00
+#define ZEVIO_GPIO_INT_STATUS 0x04
+#define ZEVIO_GPIO_INT_UNMASK 0x08
+#define ZEVIO_GPIO_INT_MASK 0x0C
+#define ZEVIO_GPIO_DIRECTION 0x10
+#define ZEVIO_GPIO_OUTPUT 0x14
+#define ZEVIO_GPIO_INPUT 0x18
+#define ZEVIO_GPIO_INT_STICKY 0x20
+
+/* Bit number of GPIO in its section */
+#define ZEVIO_GPIO_BIT(gpio) (gpio&7)
+
+struct zevio_gpio {
+ struct gpio_chip chip;
+ spinlock_t lock;
+ void __iomem *regs;
+};
+
+static inline u32 zevio_gpio_port_get(struct zevio_gpio *c, unsigned pin,
+ unsigned port_offset)
+{
+ unsigned section_offset = ((pin >> 3) & 3)*ZEVIO_GPIO_SECTION_SIZE;
+ return readl(IOMEM(c->regs + section_offset + port_offset));
+}
+
+static inline void zevio_gpio_port_set(struct zevio_gpio *c, unsigned pin,
+ unsigned port_offset, u32 val)
+{
+ unsigned section_offset = ((pin >> 3) & 3)*ZEVIO_GPIO_SECTION_SIZE;
+ writel(val, IOMEM(c->regs + section_offset + port_offset));
+}
+
+/* Functions for struct gpio_chip */
+static int zevio_gpio_get(struct gpio_chip *chip, unsigned pin)
+{
+ struct zevio_gpio *controller = gpiochip_get_data(chip);
+ u32 val, dir;
+
+ spin_lock(&controller->lock);
+ dir = zevio_gpio_port_get(controller, pin, ZEVIO_GPIO_DIRECTION);
+ if (dir & BIT(ZEVIO_GPIO_BIT(pin)))
+ val = zevio_gpio_port_get(controller, pin, ZEVIO_GPIO_INPUT);
+ else
+ val = zevio_gpio_port_get(controller, pin, ZEVIO_GPIO_OUTPUT);
+ spin_unlock(&controller->lock);
+
+ return (val >> ZEVIO_GPIO_BIT(pin)) & 0x1;
+}
+
+static void zevio_gpio_set(struct gpio_chip *chip, unsigned pin, int value)
+{
+ struct zevio_gpio *controller = gpiochip_get_data(chip);
+ u32 val;
+
+ spin_lock(&controller->lock);
+ val = zevio_gpio_port_get(controller, pin, ZEVIO_GPIO_OUTPUT);
+ if (value)
+ val |= BIT(ZEVIO_GPIO_BIT(pin));
+ else
+ val &= ~BIT(ZEVIO_GPIO_BIT(pin));
+
+ zevio_gpio_port_set(controller, pin, ZEVIO_GPIO_OUTPUT, val);
+ spin_unlock(&controller->lock);
+}
+
+static int zevio_gpio_direction_input(struct gpio_chip *chip, unsigned pin)
+{
+ struct zevio_gpio *controller = gpiochip_get_data(chip);
+ u32 val;
+
+ spin_lock(&controller->lock);
+
+ val = zevio_gpio_port_get(controller, pin, ZEVIO_GPIO_DIRECTION);
+ val |= BIT(ZEVIO_GPIO_BIT(pin));
+ zevio_gpio_port_set(controller, pin, ZEVIO_GPIO_DIRECTION, val);
+
+ spin_unlock(&controller->lock);
+
+ return 0;
+}
+
+static int zevio_gpio_direction_output(struct gpio_chip *chip,
+ unsigned pin, int value)
+{
+ struct zevio_gpio *controller = gpiochip_get_data(chip);
+ u32 val;
+
+ spin_lock(&controller->lock);
+ val = zevio_gpio_port_get(controller, pin, ZEVIO_GPIO_OUTPUT);
+ if (value)
+ val |= BIT(ZEVIO_GPIO_BIT(pin));
+ else
+ val &= ~BIT(ZEVIO_GPIO_BIT(pin));
+
+ zevio_gpio_port_set(controller, pin, ZEVIO_GPIO_OUTPUT, val);
+ val = zevio_gpio_port_get(controller, pin, ZEVIO_GPIO_DIRECTION);
+ val &= ~BIT(ZEVIO_GPIO_BIT(pin));
+ zevio_gpio_port_set(controller, pin, ZEVIO_GPIO_DIRECTION, val);
+
+ spin_unlock(&controller->lock);
+
+ return 0;
+}
+
+static int zevio_gpio_to_irq(struct gpio_chip *chip, unsigned pin)
+{
+ /*
+ * TODO: Implement IRQs.
+ * Not implemented yet due to weird lockups
+ */
+
+ return -ENXIO;
+}
+
+static const struct gpio_chip zevio_gpio_chip = {
+ .direction_input = zevio_gpio_direction_input,
+ .direction_output = zevio_gpio_direction_output,
+ .set = zevio_gpio_set,
+ .get = zevio_gpio_get,
+ .to_irq = zevio_gpio_to_irq,
+ .base = 0,
+ .owner = THIS_MODULE,
+ .ngpio = 32,
+};
+
+/* Initialization */
+static int zevio_gpio_probe(struct platform_device *pdev)
+{
+ struct zevio_gpio *controller;
+ int status, i;
+
+ controller = devm_kzalloc(&pdev->dev, sizeof(*controller), GFP_KERNEL);
+ if (!controller)
+ return -ENOMEM;
+
+ /* Copy our reference */
+ controller->chip = zevio_gpio_chip;
+ controller->chip.parent = &pdev->dev;
+
+ controller->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(controller->regs))
+ return dev_err_probe(&pdev->dev, PTR_ERR(controller->regs),
+ "failed to ioremap memory resource\n");
+
+ status = devm_gpiochip_add_data(&pdev->dev, &controller->chip, controller);
+ if (status) {
+ dev_err(&pdev->dev, "failed to add gpiochip: %d\n", status);
+ return status;
+ }
+
+ spin_lock_init(&controller->lock);
+
+ /* Disable interrupts, they only cause errors */
+ for (i = 0; i < controller->chip.ngpio; i += 8)
+ zevio_gpio_port_set(controller, i, ZEVIO_GPIO_INT_MASK, 0xFF);
+
+ dev_dbg(controller->chip.parent, "ZEVIO GPIO controller set up!\n");
+
+ return 0;
+}
+
+static const struct of_device_id zevio_gpio_of_match[] = {
+ { .compatible = "lsi,zevio-gpio", },
+ { },
+};
+
+static struct platform_driver zevio_gpio_driver = {
+ .driver = {
+ .name = "gpio-zevio",
+ .of_match_table = zevio_gpio_of_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = zevio_gpio_probe,
+};
+builtin_platform_driver(zevio_gpio_driver);
diff --git a/drivers/gpio/gpio-zynq.c b/drivers/gpio/gpio-zynq.c
new file mode 100644
index 0000000000..324e942c06
--- /dev/null
+++ b/drivers/gpio/gpio-zynq.c
@@ -0,0 +1,1042 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Xilinx Zynq GPIO device driver
+ *
+ * Copyright (C) 2009 - 2014 Xilinx, Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+
+#define DRIVER_NAME "zynq-gpio"
+
+/* Maximum banks */
+#define ZYNQ_GPIO_MAX_BANK 4
+#define ZYNQMP_GPIO_MAX_BANK 6
+#define VERSAL_GPIO_MAX_BANK 4
+#define PMC_GPIO_MAX_BANK 5
+#define VERSAL_UNUSED_BANKS 2
+
+#define ZYNQ_GPIO_BANK0_NGPIO 32
+#define ZYNQ_GPIO_BANK1_NGPIO 22
+#define ZYNQ_GPIO_BANK2_NGPIO 32
+#define ZYNQ_GPIO_BANK3_NGPIO 32
+
+#define ZYNQMP_GPIO_BANK0_NGPIO 26
+#define ZYNQMP_GPIO_BANK1_NGPIO 26
+#define ZYNQMP_GPIO_BANK2_NGPIO 26
+#define ZYNQMP_GPIO_BANK3_NGPIO 32
+#define ZYNQMP_GPIO_BANK4_NGPIO 32
+#define ZYNQMP_GPIO_BANK5_NGPIO 32
+
+#define ZYNQ_GPIO_NR_GPIOS 118
+#define ZYNQMP_GPIO_NR_GPIOS 174
+
+#define ZYNQ_GPIO_BANK0_PIN_MIN(str) 0
+#define ZYNQ_GPIO_BANK0_PIN_MAX(str) (ZYNQ_GPIO_BANK0_PIN_MIN(str) + \
+ ZYNQ##str##_GPIO_BANK0_NGPIO - 1)
+#define ZYNQ_GPIO_BANK1_PIN_MIN(str) (ZYNQ_GPIO_BANK0_PIN_MAX(str) + 1)
+#define ZYNQ_GPIO_BANK1_PIN_MAX(str) (ZYNQ_GPIO_BANK1_PIN_MIN(str) + \
+ ZYNQ##str##_GPIO_BANK1_NGPIO - 1)
+#define ZYNQ_GPIO_BANK2_PIN_MIN(str) (ZYNQ_GPIO_BANK1_PIN_MAX(str) + 1)
+#define ZYNQ_GPIO_BANK2_PIN_MAX(str) (ZYNQ_GPIO_BANK2_PIN_MIN(str) + \
+ ZYNQ##str##_GPIO_BANK2_NGPIO - 1)
+#define ZYNQ_GPIO_BANK3_PIN_MIN(str) (ZYNQ_GPIO_BANK2_PIN_MAX(str) + 1)
+#define ZYNQ_GPIO_BANK3_PIN_MAX(str) (ZYNQ_GPIO_BANK3_PIN_MIN(str) + \
+ ZYNQ##str##_GPIO_BANK3_NGPIO - 1)
+#define ZYNQ_GPIO_BANK4_PIN_MIN(str) (ZYNQ_GPIO_BANK3_PIN_MAX(str) + 1)
+#define ZYNQ_GPIO_BANK4_PIN_MAX(str) (ZYNQ_GPIO_BANK4_PIN_MIN(str) + \
+ ZYNQ##str##_GPIO_BANK4_NGPIO - 1)
+#define ZYNQ_GPIO_BANK5_PIN_MIN(str) (ZYNQ_GPIO_BANK4_PIN_MAX(str) + 1)
+#define ZYNQ_GPIO_BANK5_PIN_MAX(str) (ZYNQ_GPIO_BANK5_PIN_MIN(str) + \
+ ZYNQ##str##_GPIO_BANK5_NGPIO - 1)
+
+/* Register offsets for the GPIO device */
+/* LSW Mask & Data -WO */
+#define ZYNQ_GPIO_DATA_LSW_OFFSET(BANK) (0x000 + (8 * BANK))
+/* MSW Mask & Data -WO */
+#define ZYNQ_GPIO_DATA_MSW_OFFSET(BANK) (0x004 + (8 * BANK))
+/* Data Register-RW */
+#define ZYNQ_GPIO_DATA_OFFSET(BANK) (0x040 + (4 * BANK))
+#define ZYNQ_GPIO_DATA_RO_OFFSET(BANK) (0x060 + (4 * BANK))
+/* Direction mode reg-RW */
+#define ZYNQ_GPIO_DIRM_OFFSET(BANK) (0x204 + (0x40 * BANK))
+/* Output enable reg-RW */
+#define ZYNQ_GPIO_OUTEN_OFFSET(BANK) (0x208 + (0x40 * BANK))
+/* Interrupt mask reg-RO */
+#define ZYNQ_GPIO_INTMASK_OFFSET(BANK) (0x20C + (0x40 * BANK))
+/* Interrupt enable reg-WO */
+#define ZYNQ_GPIO_INTEN_OFFSET(BANK) (0x210 + (0x40 * BANK))
+/* Interrupt disable reg-WO */
+#define ZYNQ_GPIO_INTDIS_OFFSET(BANK) (0x214 + (0x40 * BANK))
+/* Interrupt status reg-RO */
+#define ZYNQ_GPIO_INTSTS_OFFSET(BANK) (0x218 + (0x40 * BANK))
+/* Interrupt type reg-RW */
+#define ZYNQ_GPIO_INTTYPE_OFFSET(BANK) (0x21C + (0x40 * BANK))
+/* Interrupt polarity reg-RW */
+#define ZYNQ_GPIO_INTPOL_OFFSET(BANK) (0x220 + (0x40 * BANK))
+/* Interrupt on any, reg-RW */
+#define ZYNQ_GPIO_INTANY_OFFSET(BANK) (0x224 + (0x40 * BANK))
+
+/* Disable all interrupts mask */
+#define ZYNQ_GPIO_IXR_DISABLE_ALL 0xFFFFFFFF
+
+/* Mid pin number of a bank */
+#define ZYNQ_GPIO_MID_PIN_NUM 16
+
+/* GPIO upper 16 bit mask */
+#define ZYNQ_GPIO_UPPER_MASK 0xFFFF0000
+
+/* set to differentiate zynq from zynqmp, 0=zynqmp, 1=zynq */
+#define ZYNQ_GPIO_QUIRK_IS_ZYNQ BIT(0)
+#define GPIO_QUIRK_DATA_RO_BUG BIT(1)
+#define GPIO_QUIRK_VERSAL BIT(2)
+
+struct gpio_regs {
+ u32 datamsw[ZYNQMP_GPIO_MAX_BANK];
+ u32 datalsw[ZYNQMP_GPIO_MAX_BANK];
+ u32 dirm[ZYNQMP_GPIO_MAX_BANK];
+ u32 outen[ZYNQMP_GPIO_MAX_BANK];
+ u32 int_en[ZYNQMP_GPIO_MAX_BANK];
+ u32 int_dis[ZYNQMP_GPIO_MAX_BANK];
+ u32 int_type[ZYNQMP_GPIO_MAX_BANK];
+ u32 int_polarity[ZYNQMP_GPIO_MAX_BANK];
+ u32 int_any[ZYNQMP_GPIO_MAX_BANK];
+};
+
+/**
+ * struct zynq_gpio - gpio device private data structure
+ * @chip: instance of the gpio_chip
+ * @base_addr: base address of the GPIO device
+ * @clk: clock resource for this controller
+ * @irq: interrupt for the GPIO device
+ * @p_data: pointer to platform data
+ * @context: context registers
+ * @dirlock: lock used for direction in/out synchronization
+ */
+struct zynq_gpio {
+ struct gpio_chip chip;
+ void __iomem *base_addr;
+ struct clk *clk;
+ int irq;
+ const struct zynq_platform_data *p_data;
+ struct gpio_regs context;
+ spinlock_t dirlock; /* lock */
+};
+
+/**
+ * struct zynq_platform_data - zynq gpio platform data structure
+ * @label: string to store in gpio->label
+ * @quirks: Flags is used to identify the platform
+ * @ngpio: max number of gpio pins
+ * @max_bank: maximum number of gpio banks
+ * @bank_min: this array represents bank's min pin
+ * @bank_max: this array represents bank's max pin
+ */
+struct zynq_platform_data {
+ const char *label;
+ u32 quirks;
+ u16 ngpio;
+ int max_bank;
+ int bank_min[ZYNQMP_GPIO_MAX_BANK];
+ int bank_max[ZYNQMP_GPIO_MAX_BANK];
+};
+
+static const struct irq_chip zynq_gpio_level_irqchip;
+static const struct irq_chip zynq_gpio_edge_irqchip;
+
+/**
+ * zynq_gpio_is_zynq - test if HW is zynq or zynqmp
+ * @gpio: Pointer to driver data struct
+ *
+ * Return: 0 if zynqmp, 1 if zynq.
+ */
+static int zynq_gpio_is_zynq(struct zynq_gpio *gpio)
+{
+ return !!(gpio->p_data->quirks & ZYNQ_GPIO_QUIRK_IS_ZYNQ);
+}
+
+/**
+ * gpio_data_ro_bug - test if HW bug exists or not
+ * @gpio: Pointer to driver data struct
+ *
+ * Return: 0 if bug doesnot exist, 1 if bug exists.
+ */
+static int gpio_data_ro_bug(struct zynq_gpio *gpio)
+{
+ return !!(gpio->p_data->quirks & GPIO_QUIRK_DATA_RO_BUG);
+}
+
+/**
+ * zynq_gpio_get_bank_pin - Get the bank number and pin number within that bank
+ * for a given pin in the GPIO device
+ * @pin_num: gpio pin number within the device
+ * @bank_num: an output parameter used to return the bank number of the gpio
+ * pin
+ * @bank_pin_num: an output parameter used to return pin number within a bank
+ * for the given gpio pin
+ * @gpio: gpio device data structure
+ *
+ * Returns the bank number and pin offset within the bank.
+ */
+static inline void zynq_gpio_get_bank_pin(unsigned int pin_num,
+ unsigned int *bank_num,
+ unsigned int *bank_pin_num,
+ struct zynq_gpio *gpio)
+{
+ int bank;
+
+ for (bank = 0; bank < gpio->p_data->max_bank; bank++) {
+ if ((pin_num >= gpio->p_data->bank_min[bank]) &&
+ (pin_num <= gpio->p_data->bank_max[bank])) {
+ *bank_num = bank;
+ *bank_pin_num = pin_num -
+ gpio->p_data->bank_min[bank];
+ return;
+ }
+ if (gpio->p_data->quirks & GPIO_QUIRK_VERSAL)
+ bank = bank + VERSAL_UNUSED_BANKS;
+ }
+
+ /* default */
+ WARN(true, "invalid GPIO pin number: %u", pin_num);
+ *bank_num = 0;
+ *bank_pin_num = 0;
+}
+
+/**
+ * zynq_gpio_get_value - Get the state of the specified pin of GPIO device
+ * @chip: gpio_chip instance to be worked on
+ * @pin: gpio pin number within the device
+ *
+ * This function reads the state of the specified pin of the GPIO device.
+ *
+ * Return: 0 if the pin is low, 1 if pin is high.
+ */
+static int zynq_gpio_get_value(struct gpio_chip *chip, unsigned int pin)
+{
+ u32 data;
+ unsigned int bank_num, bank_pin_num;
+ struct zynq_gpio *gpio = gpiochip_get_data(chip);
+
+ zynq_gpio_get_bank_pin(pin, &bank_num, &bank_pin_num, gpio);
+
+ if (gpio_data_ro_bug(gpio)) {
+ if (zynq_gpio_is_zynq(gpio)) {
+ if (bank_num <= 1) {
+ data = readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_DATA_RO_OFFSET(bank_num));
+ } else {
+ data = readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_DATA_OFFSET(bank_num));
+ }
+ } else {
+ if (bank_num <= 2) {
+ data = readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_DATA_RO_OFFSET(bank_num));
+ } else {
+ data = readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_DATA_OFFSET(bank_num));
+ }
+ }
+ } else {
+ data = readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_DATA_RO_OFFSET(bank_num));
+ }
+ return (data >> bank_pin_num) & 1;
+}
+
+/**
+ * zynq_gpio_set_value - Modify the state of the pin with specified value
+ * @chip: gpio_chip instance to be worked on
+ * @pin: gpio pin number within the device
+ * @state: value used to modify the state of the specified pin
+ *
+ * This function calculates the register offset (i.e to lower 16 bits or
+ * upper 16 bits) based on the given pin number and sets the state of a
+ * gpio pin to the specified value. The state is either 0 or non-zero.
+ */
+static void zynq_gpio_set_value(struct gpio_chip *chip, unsigned int pin,
+ int state)
+{
+ unsigned int reg_offset, bank_num, bank_pin_num;
+ struct zynq_gpio *gpio = gpiochip_get_data(chip);
+
+ zynq_gpio_get_bank_pin(pin, &bank_num, &bank_pin_num, gpio);
+
+ if (bank_pin_num >= ZYNQ_GPIO_MID_PIN_NUM) {
+ /* only 16 data bits in bit maskable reg */
+ bank_pin_num -= ZYNQ_GPIO_MID_PIN_NUM;
+ reg_offset = ZYNQ_GPIO_DATA_MSW_OFFSET(bank_num);
+ } else {
+ reg_offset = ZYNQ_GPIO_DATA_LSW_OFFSET(bank_num);
+ }
+
+ /*
+ * get the 32 bit value to be written to the mask/data register where
+ * the upper 16 bits is the mask and lower 16 bits is the data
+ */
+ state = !!state;
+ state = ~(1 << (bank_pin_num + ZYNQ_GPIO_MID_PIN_NUM)) &
+ ((state << bank_pin_num) | ZYNQ_GPIO_UPPER_MASK);
+
+ writel_relaxed(state, gpio->base_addr + reg_offset);
+}
+
+/**
+ * zynq_gpio_dir_in - Set the direction of the specified GPIO pin as input
+ * @chip: gpio_chip instance to be worked on
+ * @pin: gpio pin number within the device
+ *
+ * This function uses the read-modify-write sequence to set the direction of
+ * the gpio pin as input.
+ *
+ * Return: 0 always
+ */
+static int zynq_gpio_dir_in(struct gpio_chip *chip, unsigned int pin)
+{
+ u32 reg;
+ unsigned int bank_num, bank_pin_num;
+ unsigned long flags;
+ struct zynq_gpio *gpio = gpiochip_get_data(chip);
+
+ zynq_gpio_get_bank_pin(pin, &bank_num, &bank_pin_num, gpio);
+
+ /*
+ * On zynq bank 0 pins 7 and 8 are special and cannot be used
+ * as inputs.
+ */
+ if (zynq_gpio_is_zynq(gpio) && bank_num == 0 &&
+ (bank_pin_num == 7 || bank_pin_num == 8))
+ return -EINVAL;
+
+ /* clear the bit in direction mode reg to set the pin as input */
+ spin_lock_irqsave(&gpio->dirlock, flags);
+ reg = readl_relaxed(gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
+ reg &= ~BIT(bank_pin_num);
+ writel_relaxed(reg, gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
+ spin_unlock_irqrestore(&gpio->dirlock, flags);
+
+ return 0;
+}
+
+/**
+ * zynq_gpio_dir_out - Set the direction of the specified GPIO pin as output
+ * @chip: gpio_chip instance to be worked on
+ * @pin: gpio pin number within the device
+ * @state: value to be written to specified pin
+ *
+ * This function sets the direction of specified GPIO pin as output, configures
+ * the Output Enable register for the pin and uses zynq_gpio_set to set
+ * the state of the pin to the value specified.
+ *
+ * Return: 0 always
+ */
+static int zynq_gpio_dir_out(struct gpio_chip *chip, unsigned int pin,
+ int state)
+{
+ u32 reg;
+ unsigned int bank_num, bank_pin_num;
+ unsigned long flags;
+ struct zynq_gpio *gpio = gpiochip_get_data(chip);
+
+ zynq_gpio_get_bank_pin(pin, &bank_num, &bank_pin_num, gpio);
+
+ /* set the GPIO pin as output */
+ spin_lock_irqsave(&gpio->dirlock, flags);
+ reg = readl_relaxed(gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
+ reg |= BIT(bank_pin_num);
+ writel_relaxed(reg, gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
+
+ /* configure the output enable reg for the pin */
+ reg = readl_relaxed(gpio->base_addr + ZYNQ_GPIO_OUTEN_OFFSET(bank_num));
+ reg |= BIT(bank_pin_num);
+ writel_relaxed(reg, gpio->base_addr + ZYNQ_GPIO_OUTEN_OFFSET(bank_num));
+ spin_unlock_irqrestore(&gpio->dirlock, flags);
+
+ /* set the state of the pin */
+ zynq_gpio_set_value(chip, pin, state);
+ return 0;
+}
+
+/**
+ * zynq_gpio_get_direction - Read the direction of the specified GPIO pin
+ * @chip: gpio_chip instance to be worked on
+ * @pin: gpio pin number within the device
+ *
+ * This function returns the direction of the specified GPIO.
+ *
+ * Return: GPIO_LINE_DIRECTION_OUT or GPIO_LINE_DIRECTION_IN
+ */
+static int zynq_gpio_get_direction(struct gpio_chip *chip, unsigned int pin)
+{
+ u32 reg;
+ unsigned int bank_num, bank_pin_num;
+ struct zynq_gpio *gpio = gpiochip_get_data(chip);
+
+ zynq_gpio_get_bank_pin(pin, &bank_num, &bank_pin_num, gpio);
+
+ reg = readl_relaxed(gpio->base_addr + ZYNQ_GPIO_DIRM_OFFSET(bank_num));
+
+ if (reg & BIT(bank_pin_num))
+ return GPIO_LINE_DIRECTION_OUT;
+
+ return GPIO_LINE_DIRECTION_IN;
+}
+
+/**
+ * zynq_gpio_irq_mask - Disable the interrupts for a gpio pin
+ * @irq_data: per irq and chip data passed down to chip functions
+ *
+ * This function calculates gpio pin number from irq number and sets the
+ * bit in the Interrupt Disable register of the corresponding bank to disable
+ * interrupts for that pin.
+ */
+static void zynq_gpio_irq_mask(struct irq_data *irq_data)
+{
+ unsigned int device_pin_num, bank_num, bank_pin_num;
+ const unsigned long offset = irqd_to_hwirq(irq_data);
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(irq_data);
+ struct zynq_gpio *gpio =
+ gpiochip_get_data(irq_data_get_irq_chip_data(irq_data));
+
+ gpiochip_disable_irq(chip, offset);
+ device_pin_num = irq_data->hwirq;
+ zynq_gpio_get_bank_pin(device_pin_num, &bank_num, &bank_pin_num, gpio);
+ writel_relaxed(BIT(bank_pin_num),
+ gpio->base_addr + ZYNQ_GPIO_INTDIS_OFFSET(bank_num));
+}
+
+/**
+ * zynq_gpio_irq_unmask - Enable the interrupts for a gpio pin
+ * @irq_data: irq data containing irq number of gpio pin for the interrupt
+ * to enable
+ *
+ * This function calculates the gpio pin number from irq number and sets the
+ * bit in the Interrupt Enable register of the corresponding bank to enable
+ * interrupts for that pin.
+ */
+static void zynq_gpio_irq_unmask(struct irq_data *irq_data)
+{
+ unsigned int device_pin_num, bank_num, bank_pin_num;
+ const unsigned long offset = irqd_to_hwirq(irq_data);
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(irq_data);
+ struct zynq_gpio *gpio =
+ gpiochip_get_data(irq_data_get_irq_chip_data(irq_data));
+
+ gpiochip_enable_irq(chip, offset);
+ device_pin_num = irq_data->hwirq;
+ zynq_gpio_get_bank_pin(device_pin_num, &bank_num, &bank_pin_num, gpio);
+ writel_relaxed(BIT(bank_pin_num),
+ gpio->base_addr + ZYNQ_GPIO_INTEN_OFFSET(bank_num));
+}
+
+/**
+ * zynq_gpio_irq_ack - Acknowledge the interrupt of a gpio pin
+ * @irq_data: irq data containing irq number of gpio pin for the interrupt
+ * to ack
+ *
+ * This function calculates gpio pin number from irq number and sets the bit
+ * in the Interrupt Status Register of the corresponding bank, to ACK the irq.
+ */
+static void zynq_gpio_irq_ack(struct irq_data *irq_data)
+{
+ unsigned int device_pin_num, bank_num, bank_pin_num;
+ struct zynq_gpio *gpio =
+ gpiochip_get_data(irq_data_get_irq_chip_data(irq_data));
+
+ device_pin_num = irq_data->hwirq;
+ zynq_gpio_get_bank_pin(device_pin_num, &bank_num, &bank_pin_num, gpio);
+ writel_relaxed(BIT(bank_pin_num),
+ gpio->base_addr + ZYNQ_GPIO_INTSTS_OFFSET(bank_num));
+}
+
+/**
+ * zynq_gpio_irq_enable - Enable the interrupts for a gpio pin
+ * @irq_data: irq data containing irq number of gpio pin for the interrupt
+ * to enable
+ *
+ * Clears the INTSTS bit and unmasks the given interrupt.
+ */
+static void zynq_gpio_irq_enable(struct irq_data *irq_data)
+{
+ /*
+ * The Zynq GPIO controller does not disable interrupt detection when
+ * the interrupt is masked and only disables the propagation of the
+ * interrupt. This means when the controller detects an interrupt
+ * condition while the interrupt is logically disabled it will propagate
+ * that interrupt event once the interrupt is enabled. This will cause
+ * the interrupt consumer to see spurious interrupts to prevent this
+ * first make sure that the interrupt is not asserted and then enable
+ * it.
+ */
+ zynq_gpio_irq_ack(irq_data);
+ zynq_gpio_irq_unmask(irq_data);
+}
+
+/**
+ * zynq_gpio_set_irq_type - Set the irq type for a gpio pin
+ * @irq_data: irq data containing irq number of gpio pin
+ * @type: interrupt type that is to be set for the gpio pin
+ *
+ * This function gets the gpio pin number and its bank from the gpio pin number
+ * and configures the INT_TYPE, INT_POLARITY and INT_ANY registers.
+ *
+ * Return: 0, negative error otherwise.
+ * TYPE-EDGE_RISING, INT_TYPE - 1, INT_POLARITY - 1, INT_ANY - 0;
+ * TYPE-EDGE_FALLING, INT_TYPE - 1, INT_POLARITY - 0, INT_ANY - 0;
+ * TYPE-EDGE_BOTH, INT_TYPE - 1, INT_POLARITY - NA, INT_ANY - 1;
+ * TYPE-LEVEL_HIGH, INT_TYPE - 0, INT_POLARITY - 1, INT_ANY - NA;
+ * TYPE-LEVEL_LOW, INT_TYPE - 0, INT_POLARITY - 0, INT_ANY - NA
+ */
+static int zynq_gpio_set_irq_type(struct irq_data *irq_data, unsigned int type)
+{
+ u32 int_type, int_pol, int_any;
+ unsigned int device_pin_num, bank_num, bank_pin_num;
+ struct zynq_gpio *gpio =
+ gpiochip_get_data(irq_data_get_irq_chip_data(irq_data));
+
+ device_pin_num = irq_data->hwirq;
+ zynq_gpio_get_bank_pin(device_pin_num, &bank_num, &bank_pin_num, gpio);
+
+ int_type = readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_INTTYPE_OFFSET(bank_num));
+ int_pol = readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_INTPOL_OFFSET(bank_num));
+ int_any = readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_INTANY_OFFSET(bank_num));
+
+ /*
+ * based on the type requested, configure the INT_TYPE, INT_POLARITY
+ * and INT_ANY registers
+ */
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ int_type |= BIT(bank_pin_num);
+ int_pol |= BIT(bank_pin_num);
+ int_any &= ~BIT(bank_pin_num);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ int_type |= BIT(bank_pin_num);
+ int_pol &= ~BIT(bank_pin_num);
+ int_any &= ~BIT(bank_pin_num);
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ int_type |= BIT(bank_pin_num);
+ int_any |= BIT(bank_pin_num);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ int_type &= ~BIT(bank_pin_num);
+ int_pol |= BIT(bank_pin_num);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ int_type &= ~BIT(bank_pin_num);
+ int_pol &= ~BIT(bank_pin_num);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel_relaxed(int_type,
+ gpio->base_addr + ZYNQ_GPIO_INTTYPE_OFFSET(bank_num));
+ writel_relaxed(int_pol,
+ gpio->base_addr + ZYNQ_GPIO_INTPOL_OFFSET(bank_num));
+ writel_relaxed(int_any,
+ gpio->base_addr + ZYNQ_GPIO_INTANY_OFFSET(bank_num));
+
+ if (type & IRQ_TYPE_LEVEL_MASK)
+ irq_set_chip_handler_name_locked(irq_data,
+ &zynq_gpio_level_irqchip,
+ handle_fasteoi_irq, NULL);
+ else
+ irq_set_chip_handler_name_locked(irq_data,
+ &zynq_gpio_edge_irqchip,
+ handle_level_irq, NULL);
+
+ return 0;
+}
+
+static int zynq_gpio_set_wake(struct irq_data *data, unsigned int on)
+{
+ struct zynq_gpio *gpio =
+ gpiochip_get_data(irq_data_get_irq_chip_data(data));
+
+ irq_set_irq_wake(gpio->irq, on);
+
+ return 0;
+}
+
+static int zynq_gpio_irq_reqres(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(chip->parent);
+ if (ret < 0)
+ return ret;
+
+ return gpiochip_reqres_irq(chip, d->hwirq);
+}
+
+static void zynq_gpio_irq_relres(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+
+ gpiochip_relres_irq(chip, d->hwirq);
+ pm_runtime_put(chip->parent);
+}
+
+/* irq chip descriptor */
+static const struct irq_chip zynq_gpio_level_irqchip = {
+ .name = DRIVER_NAME,
+ .irq_enable = zynq_gpio_irq_enable,
+ .irq_eoi = zynq_gpio_irq_ack,
+ .irq_mask = zynq_gpio_irq_mask,
+ .irq_unmask = zynq_gpio_irq_unmask,
+ .irq_set_type = zynq_gpio_set_irq_type,
+ .irq_set_wake = zynq_gpio_set_wake,
+ .irq_request_resources = zynq_gpio_irq_reqres,
+ .irq_release_resources = zynq_gpio_irq_relres,
+ .flags = IRQCHIP_EOI_THREADED | IRQCHIP_EOI_IF_HANDLED |
+ IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE,
+};
+
+static const struct irq_chip zynq_gpio_edge_irqchip = {
+ .name = DRIVER_NAME,
+ .irq_enable = zynq_gpio_irq_enable,
+ .irq_ack = zynq_gpio_irq_ack,
+ .irq_mask = zynq_gpio_irq_mask,
+ .irq_unmask = zynq_gpio_irq_unmask,
+ .irq_set_type = zynq_gpio_set_irq_type,
+ .irq_set_wake = zynq_gpio_set_wake,
+ .irq_request_resources = zynq_gpio_irq_reqres,
+ .irq_release_resources = zynq_gpio_irq_relres,
+ .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE,
+};
+
+static void zynq_gpio_handle_bank_irq(struct zynq_gpio *gpio,
+ unsigned int bank_num,
+ unsigned long pending)
+{
+ unsigned int bank_offset = gpio->p_data->bank_min[bank_num];
+ struct irq_domain *irqdomain = gpio->chip.irq.domain;
+ int offset;
+
+ if (!pending)
+ return;
+
+ for_each_set_bit(offset, &pending, 32)
+ generic_handle_domain_irq(irqdomain, offset + bank_offset);
+}
+
+/**
+ * zynq_gpio_irqhandler - IRQ handler for the gpio banks of a gpio device
+ * @desc: irq descriptor instance of the 'irq'
+ *
+ * This function reads the Interrupt Status Register of each bank to get the
+ * gpio pin number which has triggered an interrupt. It then acks the triggered
+ * interrupt and calls the pin specific handler set by the higher layer
+ * application for that pin.
+ * Note: A bug is reported if no handler is set for the gpio pin.
+ */
+static void zynq_gpio_irqhandler(struct irq_desc *desc)
+{
+ u32 int_sts, int_enb;
+ unsigned int bank_num;
+ struct zynq_gpio *gpio =
+ gpiochip_get_data(irq_desc_get_handler_data(desc));
+ struct irq_chip *irqchip = irq_desc_get_chip(desc);
+
+ chained_irq_enter(irqchip, desc);
+
+ for (bank_num = 0; bank_num < gpio->p_data->max_bank; bank_num++) {
+ int_sts = readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_INTSTS_OFFSET(bank_num));
+ int_enb = readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_INTMASK_OFFSET(bank_num));
+ zynq_gpio_handle_bank_irq(gpio, bank_num, int_sts & ~int_enb);
+ if (gpio->p_data->quirks & GPIO_QUIRK_VERSAL)
+ bank_num = bank_num + VERSAL_UNUSED_BANKS;
+ }
+
+ chained_irq_exit(irqchip, desc);
+}
+
+static void zynq_gpio_save_context(struct zynq_gpio *gpio)
+{
+ unsigned int bank_num;
+
+ for (bank_num = 0; bank_num < gpio->p_data->max_bank; bank_num++) {
+ gpio->context.datalsw[bank_num] =
+ readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_DATA_LSW_OFFSET(bank_num));
+ gpio->context.datamsw[bank_num] =
+ readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_DATA_MSW_OFFSET(bank_num));
+ gpio->context.dirm[bank_num] = readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_DIRM_OFFSET(bank_num));
+ gpio->context.int_en[bank_num] = readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_INTMASK_OFFSET(bank_num));
+ gpio->context.int_type[bank_num] =
+ readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_INTTYPE_OFFSET(bank_num));
+ gpio->context.int_polarity[bank_num] =
+ readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_INTPOL_OFFSET(bank_num));
+ gpio->context.int_any[bank_num] =
+ readl_relaxed(gpio->base_addr +
+ ZYNQ_GPIO_INTANY_OFFSET(bank_num));
+ if (gpio->p_data->quirks & GPIO_QUIRK_VERSAL)
+ bank_num = bank_num + VERSAL_UNUSED_BANKS;
+ }
+}
+
+static void zynq_gpio_restore_context(struct zynq_gpio *gpio)
+{
+ unsigned int bank_num;
+
+ for (bank_num = 0; bank_num < gpio->p_data->max_bank; bank_num++) {
+ writel_relaxed(ZYNQ_GPIO_IXR_DISABLE_ALL, gpio->base_addr +
+ ZYNQ_GPIO_INTDIS_OFFSET(bank_num));
+ writel_relaxed(gpio->context.datalsw[bank_num],
+ gpio->base_addr +
+ ZYNQ_GPIO_DATA_LSW_OFFSET(bank_num));
+ writel_relaxed(gpio->context.datamsw[bank_num],
+ gpio->base_addr +
+ ZYNQ_GPIO_DATA_MSW_OFFSET(bank_num));
+ writel_relaxed(gpio->context.dirm[bank_num],
+ gpio->base_addr +
+ ZYNQ_GPIO_DIRM_OFFSET(bank_num));
+ writel_relaxed(gpio->context.int_type[bank_num],
+ gpio->base_addr +
+ ZYNQ_GPIO_INTTYPE_OFFSET(bank_num));
+ writel_relaxed(gpio->context.int_polarity[bank_num],
+ gpio->base_addr +
+ ZYNQ_GPIO_INTPOL_OFFSET(bank_num));
+ writel_relaxed(gpio->context.int_any[bank_num],
+ gpio->base_addr +
+ ZYNQ_GPIO_INTANY_OFFSET(bank_num));
+ writel_relaxed(~(gpio->context.int_en[bank_num]),
+ gpio->base_addr +
+ ZYNQ_GPIO_INTEN_OFFSET(bank_num));
+ if (gpio->p_data->quirks & GPIO_QUIRK_VERSAL)
+ bank_num = bank_num + VERSAL_UNUSED_BANKS;
+ }
+}
+
+static int __maybe_unused zynq_gpio_suspend(struct device *dev)
+{
+ struct zynq_gpio *gpio = dev_get_drvdata(dev);
+ struct irq_data *data = irq_get_irq_data(gpio->irq);
+
+ if (!data) {
+ dev_err(dev, "irq_get_irq_data() failed\n");
+ return -EINVAL;
+ }
+
+ if (!device_may_wakeup(dev))
+ disable_irq(gpio->irq);
+
+ if (!irqd_is_wakeup_set(data)) {
+ zynq_gpio_save_context(gpio);
+ return pm_runtime_force_suspend(dev);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused zynq_gpio_resume(struct device *dev)
+{
+ struct zynq_gpio *gpio = dev_get_drvdata(dev);
+ struct irq_data *data = irq_get_irq_data(gpio->irq);
+ int ret;
+
+ if (!data) {
+ dev_err(dev, "irq_get_irq_data() failed\n");
+ return -EINVAL;
+ }
+
+ if (!device_may_wakeup(dev))
+ enable_irq(gpio->irq);
+
+ if (!irqd_is_wakeup_set(data)) {
+ ret = pm_runtime_force_resume(dev);
+ zynq_gpio_restore_context(gpio);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused zynq_gpio_runtime_suspend(struct device *dev)
+{
+ struct zynq_gpio *gpio = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(gpio->clk);
+
+ return 0;
+}
+
+static int __maybe_unused zynq_gpio_runtime_resume(struct device *dev)
+{
+ struct zynq_gpio *gpio = dev_get_drvdata(dev);
+
+ return clk_prepare_enable(gpio->clk);
+}
+
+static int zynq_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ int ret;
+
+ ret = pm_runtime_get_sync(chip->parent);
+
+ /*
+ * If the device is already active pm_runtime_get() will return 1 on
+ * success, but gpio_request still needs to return 0.
+ */
+ return ret < 0 ? ret : 0;
+}
+
+static void zynq_gpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+ pm_runtime_put(chip->parent);
+}
+
+static const struct dev_pm_ops zynq_gpio_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(zynq_gpio_suspend, zynq_gpio_resume)
+ SET_RUNTIME_PM_OPS(zynq_gpio_runtime_suspend,
+ zynq_gpio_runtime_resume, NULL)
+};
+
+static const struct zynq_platform_data versal_gpio_def = {
+ .label = "versal_gpio",
+ .quirks = GPIO_QUIRK_VERSAL,
+ .ngpio = 58,
+ .max_bank = VERSAL_GPIO_MAX_BANK,
+ .bank_min[0] = 0,
+ .bank_max[0] = 25, /* 0 to 25 are connected to MIOs (26 pins) */
+ .bank_min[3] = 26,
+ .bank_max[3] = 57, /* Bank 3 is connected to FMIOs (32 pins) */
+};
+
+static const struct zynq_platform_data pmc_gpio_def = {
+ .label = "pmc_gpio",
+ .ngpio = 116,
+ .max_bank = PMC_GPIO_MAX_BANK,
+ .bank_min[0] = 0,
+ .bank_max[0] = 25, /* 0 to 25 are connected to MIOs (26 pins) */
+ .bank_min[1] = 26,
+ .bank_max[1] = 51, /* Bank 1 are connected to MIOs (26 pins) */
+ .bank_min[3] = 52,
+ .bank_max[3] = 83, /* Bank 3 is connected to EMIOs (32 pins) */
+ .bank_min[4] = 84,
+ .bank_max[4] = 115, /* Bank 4 is connected to EMIOs (32 pins) */
+};
+
+static const struct zynq_platform_data zynqmp_gpio_def = {
+ .label = "zynqmp_gpio",
+ .quirks = GPIO_QUIRK_DATA_RO_BUG,
+ .ngpio = ZYNQMP_GPIO_NR_GPIOS,
+ .max_bank = ZYNQMP_GPIO_MAX_BANK,
+ .bank_min[0] = ZYNQ_GPIO_BANK0_PIN_MIN(MP),
+ .bank_max[0] = ZYNQ_GPIO_BANK0_PIN_MAX(MP),
+ .bank_min[1] = ZYNQ_GPIO_BANK1_PIN_MIN(MP),
+ .bank_max[1] = ZYNQ_GPIO_BANK1_PIN_MAX(MP),
+ .bank_min[2] = ZYNQ_GPIO_BANK2_PIN_MIN(MP),
+ .bank_max[2] = ZYNQ_GPIO_BANK2_PIN_MAX(MP),
+ .bank_min[3] = ZYNQ_GPIO_BANK3_PIN_MIN(MP),
+ .bank_max[3] = ZYNQ_GPIO_BANK3_PIN_MAX(MP),
+ .bank_min[4] = ZYNQ_GPIO_BANK4_PIN_MIN(MP),
+ .bank_max[4] = ZYNQ_GPIO_BANK4_PIN_MAX(MP),
+ .bank_min[5] = ZYNQ_GPIO_BANK5_PIN_MIN(MP),
+ .bank_max[5] = ZYNQ_GPIO_BANK5_PIN_MAX(MP),
+};
+
+static const struct zynq_platform_data zynq_gpio_def = {
+ .label = "zynq_gpio",
+ .quirks = ZYNQ_GPIO_QUIRK_IS_ZYNQ | GPIO_QUIRK_DATA_RO_BUG,
+ .ngpio = ZYNQ_GPIO_NR_GPIOS,
+ .max_bank = ZYNQ_GPIO_MAX_BANK,
+ .bank_min[0] = ZYNQ_GPIO_BANK0_PIN_MIN(),
+ .bank_max[0] = ZYNQ_GPIO_BANK0_PIN_MAX(),
+ .bank_min[1] = ZYNQ_GPIO_BANK1_PIN_MIN(),
+ .bank_max[1] = ZYNQ_GPIO_BANK1_PIN_MAX(),
+ .bank_min[2] = ZYNQ_GPIO_BANK2_PIN_MIN(),
+ .bank_max[2] = ZYNQ_GPIO_BANK2_PIN_MAX(),
+ .bank_min[3] = ZYNQ_GPIO_BANK3_PIN_MIN(),
+ .bank_max[3] = ZYNQ_GPIO_BANK3_PIN_MAX(),
+};
+
+static const struct of_device_id zynq_gpio_of_match[] = {
+ { .compatible = "xlnx,zynq-gpio-1.0", .data = &zynq_gpio_def },
+ { .compatible = "xlnx,zynqmp-gpio-1.0", .data = &zynqmp_gpio_def },
+ { .compatible = "xlnx,versal-gpio-1.0", .data = &versal_gpio_def },
+ { .compatible = "xlnx,pmc-gpio-1.0", .data = &pmc_gpio_def },
+ { /* end of table */ }
+};
+MODULE_DEVICE_TABLE(of, zynq_gpio_of_match);
+
+/**
+ * zynq_gpio_probe - Initialization method for a zynq_gpio device
+ * @pdev: platform device instance
+ *
+ * This function allocates memory resources for the gpio device and registers
+ * all the banks of the device. It will also set up interrupts for the gpio
+ * pins.
+ * Note: Interrupts are disabled for all the banks during initialization.
+ *
+ * Return: 0 on success, negative error otherwise.
+ */
+static int zynq_gpio_probe(struct platform_device *pdev)
+{
+ int ret, bank_num;
+ struct zynq_gpio *gpio;
+ struct gpio_chip *chip;
+ struct gpio_irq_chip *girq;
+ const struct of_device_id *match;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ match = of_match_node(zynq_gpio_of_match, pdev->dev.of_node);
+ if (!match) {
+ dev_err(&pdev->dev, "of_match_node() failed\n");
+ return -EINVAL;
+ }
+ gpio->p_data = match->data;
+ platform_set_drvdata(pdev, gpio);
+
+ gpio->base_addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gpio->base_addr))
+ return PTR_ERR(gpio->base_addr);
+
+ gpio->irq = platform_get_irq(pdev, 0);
+ if (gpio->irq < 0)
+ return gpio->irq;
+
+ /* configure the gpio chip */
+ chip = &gpio->chip;
+ chip->label = gpio->p_data->label;
+ chip->owner = THIS_MODULE;
+ chip->parent = &pdev->dev;
+ chip->get = zynq_gpio_get_value;
+ chip->set = zynq_gpio_set_value;
+ chip->request = zynq_gpio_request;
+ chip->free = zynq_gpio_free;
+ chip->direction_input = zynq_gpio_dir_in;
+ chip->direction_output = zynq_gpio_dir_out;
+ chip->get_direction = zynq_gpio_get_direction;
+ chip->base = of_alias_get_id(pdev->dev.of_node, "gpio");
+ chip->ngpio = gpio->p_data->ngpio;
+
+ /* Retrieve GPIO clock */
+ gpio->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(gpio->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(gpio->clk), "input clock not found.\n");
+
+ ret = clk_prepare_enable(gpio->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to enable clock.\n");
+ return ret;
+ }
+
+ spin_lock_init(&gpio->dirlock);
+
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ ret = pm_runtime_resume_and_get(&pdev->dev);
+ if (ret < 0)
+ goto err_pm_dis;
+
+ /* disable interrupts for all banks */
+ for (bank_num = 0; bank_num < gpio->p_data->max_bank; bank_num++) {
+ writel_relaxed(ZYNQ_GPIO_IXR_DISABLE_ALL, gpio->base_addr +
+ ZYNQ_GPIO_INTDIS_OFFSET(bank_num));
+ if (gpio->p_data->quirks & GPIO_QUIRK_VERSAL)
+ bank_num = bank_num + VERSAL_UNUSED_BANKS;
+ }
+
+ /* Set up the GPIO irqchip */
+ girq = &chip->irq;
+ gpio_irq_chip_set_chip(girq, &zynq_gpio_edge_irqchip);
+ girq->parent_handler = zynq_gpio_irqhandler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(&pdev->dev, 1,
+ sizeof(*girq->parents),
+ GFP_KERNEL);
+ if (!girq->parents) {
+ ret = -ENOMEM;
+ goto err_pm_put;
+ }
+ girq->parents[0] = gpio->irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_level_irq;
+
+ /* report a bug if gpio chip registration fails */
+ ret = gpiochip_add_data(chip, gpio);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to add gpio chip\n");
+ goto err_pm_put;
+ }
+
+ irq_set_status_flags(gpio->irq, IRQ_DISABLE_UNLAZY);
+ device_init_wakeup(&pdev->dev, 1);
+ pm_runtime_put(&pdev->dev);
+
+ return 0;
+
+err_pm_put:
+ pm_runtime_put(&pdev->dev);
+err_pm_dis:
+ pm_runtime_disable(&pdev->dev);
+ clk_disable_unprepare(gpio->clk);
+
+ return ret;
+}
+
+/**
+ * zynq_gpio_remove - Driver removal function
+ * @pdev: platform device instance
+ *
+ * Return: 0 always
+ */
+static int zynq_gpio_remove(struct platform_device *pdev)
+{
+ struct zynq_gpio *gpio = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (ret < 0)
+ dev_warn(&pdev->dev, "pm_runtime_get_sync() Failed\n");
+ gpiochip_remove(&gpio->chip);
+ clk_disable_unprepare(gpio->clk);
+ device_set_wakeup_capable(&pdev->dev, 0);
+ pm_runtime_disable(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver zynq_gpio_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .pm = &zynq_gpio_dev_pm_ops,
+ .of_match_table = zynq_gpio_of_match,
+ },
+ .probe = zynq_gpio_probe,
+ .remove = zynq_gpio_remove,
+};
+
+module_platform_driver(zynq_gpio_driver);
+
+MODULE_AUTHOR("Xilinx Inc.");
+MODULE_DESCRIPTION("Zynq GPIO driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-zynqmp-modepin.c b/drivers/gpio/gpio-zynqmp-modepin.c
new file mode 100644
index 0000000000..a0d69387c1
--- /dev/null
+++ b/drivers/gpio/gpio-zynqmp-modepin.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the ps-mode pin configuration.
+ *
+ * Copyright (c) 2021 Xilinx, Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/firmware/xlnx-zynqmp.h>
+
+/* 4-bit boot mode pins */
+#define MODE_PINS 4
+
+/**
+ * modepin_gpio_get_value - Get the state of the specified pin of GPIO device
+ * @chip: gpio_chip instance to be worked on
+ * @pin: gpio pin number within the device
+ *
+ * This function reads the state of the specified pin of the GPIO device.
+ *
+ * Return: 0 if the pin is low, 1 if pin is high, -EINVAL wrong pin configured
+ * or error value.
+ */
+static int modepin_gpio_get_value(struct gpio_chip *chip, unsigned int pin)
+{
+ u32 regval = 0;
+ int ret;
+
+ ret = zynqmp_pm_bootmode_read(&regval);
+ if (ret)
+ return ret;
+
+ /* When [0:3] corresponding bit is set, then read output bit [8:11],
+ * if the bit is clear then read input bit [4:7] for status or value.
+ */
+ if (regval & BIT(pin))
+ return !!(regval & BIT(pin + 8));
+ else
+ return !!(regval & BIT(pin + 4));
+}
+
+/**
+ * modepin_gpio_set_value - Modify the state of the pin with specified value
+ * @chip: gpio_chip instance to be worked on
+ * @pin: gpio pin number within the device
+ * @state: value used to modify the state of the specified pin
+ *
+ * This function reads the state of the specified pin of the GPIO device, mask
+ * with the capture state of GPIO pin, and update pin of GPIO device.
+ *
+ * Return: None.
+ */
+static void modepin_gpio_set_value(struct gpio_chip *chip, unsigned int pin,
+ int state)
+{
+ u32 bootpin_val = 0;
+ int ret;
+
+ zynqmp_pm_bootmode_read(&bootpin_val);
+
+ /* Configure pin as an output by set bit [0:3] */
+ bootpin_val |= BIT(pin);
+
+ if (state)
+ bootpin_val |= BIT(pin + 8);
+ else
+ bootpin_val &= ~BIT(pin + 8);
+
+ /* Configure bootpin value */
+ ret = zynqmp_pm_bootmode_write(bootpin_val);
+ if (ret)
+ pr_err("modepin: set value error %d for pin %d\n", ret, pin);
+}
+
+/**
+ * modepin_gpio_dir_in - Set the direction of the specified GPIO pin as input
+ * @chip: gpio_chip instance to be worked on
+ * @pin: gpio pin number within the device
+ *
+ * Return: 0 always
+ */
+static int modepin_gpio_dir_in(struct gpio_chip *chip, unsigned int pin)
+{
+ return 0;
+}
+
+/**
+ * modepin_gpio_dir_out - Set the direction of the specified GPIO pin as output
+ * @chip: gpio_chip instance to be worked on
+ * @pin: gpio pin number within the device
+ * @state: value to be written to specified pin
+ *
+ * Return: 0 always
+ */
+static int modepin_gpio_dir_out(struct gpio_chip *chip, unsigned int pin,
+ int state)
+{
+ return 0;
+}
+
+/**
+ * modepin_gpio_probe - Initialization method for modepin_gpio
+ * @pdev: platform device instance
+ *
+ * Return: 0 on success, negative error otherwise.
+ */
+static int modepin_gpio_probe(struct platform_device *pdev)
+{
+ struct gpio_chip *chip;
+ int status;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, chip);
+
+ /* configure the gpio chip */
+ chip->base = -1;
+ chip->ngpio = MODE_PINS;
+ chip->owner = THIS_MODULE;
+ chip->parent = &pdev->dev;
+ chip->get = modepin_gpio_get_value;
+ chip->set = modepin_gpio_set_value;
+ chip->direction_input = modepin_gpio_dir_in;
+ chip->direction_output = modepin_gpio_dir_out;
+ chip->label = dev_name(&pdev->dev);
+
+ /* modepin gpio registration */
+ status = devm_gpiochip_add_data(&pdev->dev, chip, chip);
+ if (status)
+ return dev_err_probe(&pdev->dev, status,
+ "Failed to add GPIO chip\n");
+
+ return status;
+}
+
+static const struct of_device_id modepin_platform_id[] = {
+ { .compatible = "xlnx,zynqmp-gpio-modepin", },
+ { }
+};
+
+static struct platform_driver modepin_platform_driver = {
+ .driver = {
+ .name = "modepin-gpio",
+ .of_match_table = modepin_platform_id,
+ },
+ .probe = modepin_gpio_probe,
+};
+
+module_platform_driver(modepin_platform_driver);
+
+MODULE_AUTHOR("Piyush Mehta <piyush.mehta@xilinx.com>");
+MODULE_DESCRIPTION("ZynqMP Boot PS_MODE Configuration");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c
new file mode 100644
index 0000000000..4ab33d55ae
--- /dev/null
+++ b/drivers/gpio/gpiolib-acpi.c
@@ -0,0 +1,1721 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ACPI helpers for GPIO API
+ *
+ * Copyright (C) 2012, Intel Corporation
+ * Authors: Mathias Nyman <mathias.nyman@linux.intel.com>
+ * Mika Westerberg <mika.westerberg@linux.intel.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mutex.h>
+#include <linux/pinctrl/pinctrl.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/machine.h>
+
+#include "gpiolib.h"
+#include "gpiolib-acpi.h"
+
+static int run_edge_events_on_boot = -1;
+module_param(run_edge_events_on_boot, int, 0444);
+MODULE_PARM_DESC(run_edge_events_on_boot,
+ "Run edge _AEI event-handlers at boot: 0=no, 1=yes, -1=auto");
+
+static char *ignore_wake;
+module_param(ignore_wake, charp, 0444);
+MODULE_PARM_DESC(ignore_wake,
+ "controller@pin combos on which to ignore the ACPI wake flag "
+ "ignore_wake=controller@pin[,controller@pin[,...]]");
+
+static char *ignore_interrupt;
+module_param(ignore_interrupt, charp, 0444);
+MODULE_PARM_DESC(ignore_interrupt,
+ "controller@pin combos on which to ignore interrupt "
+ "ignore_interrupt=controller@pin[,controller@pin[,...]]");
+
+struct acpi_gpiolib_dmi_quirk {
+ bool no_edge_events_on_boot;
+ char *ignore_wake;
+ char *ignore_interrupt;
+};
+
+/**
+ * struct acpi_gpio_event - ACPI GPIO event handler data
+ *
+ * @node: list-entry of the events list of the struct acpi_gpio_chip
+ * @handle: handle of ACPI method to execute when the IRQ triggers
+ * @handler: handler function to pass to request_irq() when requesting the IRQ
+ * @pin: GPIO pin number on the struct gpio_chip
+ * @irq: Linux IRQ number for the event, for request_irq() / free_irq()
+ * @irqflags: flags to pass to request_irq() when requesting the IRQ
+ * @irq_is_wake: If the ACPI flags indicate the IRQ is a wakeup source
+ * @irq_requested:True if request_irq() has been done
+ * @desc: struct gpio_desc for the GPIO pin for this event
+ */
+struct acpi_gpio_event {
+ struct list_head node;
+ acpi_handle handle;
+ irq_handler_t handler;
+ unsigned int pin;
+ unsigned int irq;
+ unsigned long irqflags;
+ bool irq_is_wake;
+ bool irq_requested;
+ struct gpio_desc *desc;
+};
+
+struct acpi_gpio_connection {
+ struct list_head node;
+ unsigned int pin;
+ struct gpio_desc *desc;
+};
+
+struct acpi_gpio_chip {
+ /*
+ * ACPICA requires that the first field of the context parameter
+ * passed to acpi_install_address_space_handler() is large enough
+ * to hold struct acpi_connection_info.
+ */
+ struct acpi_connection_info conn_info;
+ struct list_head conns;
+ struct mutex conn_lock;
+ struct gpio_chip *chip;
+ struct list_head events;
+ struct list_head deferred_req_irqs_list_entry;
+};
+
+/**
+ * struct acpi_gpio_info - ACPI GPIO specific information
+ * @adev: reference to ACPI device which consumes GPIO resource
+ * @flags: GPIO initialization flags
+ * @gpioint: if %true this GPIO is of type GpioInt otherwise type is GpioIo
+ * @pin_config: pin bias as provided by ACPI
+ * @polarity: interrupt polarity as provided by ACPI
+ * @triggering: triggering type as provided by ACPI
+ * @wake_capable: wake capability as provided by ACPI
+ * @debounce: debounce timeout as provided by ACPI
+ * @quirks: Linux specific quirks as provided by struct acpi_gpio_mapping
+ */
+struct acpi_gpio_info {
+ struct acpi_device *adev;
+ enum gpiod_flags flags;
+ bool gpioint;
+ int pin_config;
+ int polarity;
+ int triggering;
+ bool wake_capable;
+ unsigned int debounce;
+ unsigned int quirks;
+};
+
+/*
+ * For GPIO chips which call acpi_gpiochip_request_interrupts() before late_init
+ * (so builtin drivers) we register the ACPI GpioInt IRQ handlers from a
+ * late_initcall_sync() handler, so that other builtin drivers can register their
+ * OpRegions before the event handlers can run. This list contains GPIO chips
+ * for which the acpi_gpiochip_request_irqs() call has been deferred.
+ */
+static DEFINE_MUTEX(acpi_gpio_deferred_req_irqs_lock);
+static LIST_HEAD(acpi_gpio_deferred_req_irqs_list);
+static bool acpi_gpio_deferred_req_irqs_done;
+
+static int acpi_gpiochip_find(struct gpio_chip *gc, void *data)
+{
+ return device_match_acpi_handle(&gc->gpiodev->dev, data);
+}
+
+/**
+ * acpi_get_gpiod() - Translate ACPI GPIO pin to GPIO descriptor usable with GPIO API
+ * @path: ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1")
+ * @pin: ACPI GPIO pin number (0-based, controller-relative)
+ *
+ * Return: GPIO descriptor to use with Linux generic GPIO API, or ERR_PTR
+ * error value. Specifically returns %-EPROBE_DEFER if the referenced GPIO
+ * controller does not have GPIO chip registered at the moment. This is to
+ * support probe deferral.
+ */
+static struct gpio_desc *acpi_get_gpiod(char *path, unsigned int pin)
+{
+ struct gpio_chip *chip;
+ acpi_handle handle;
+ acpi_status status;
+
+ status = acpi_get_handle(NULL, path, &handle);
+ if (ACPI_FAILURE(status))
+ return ERR_PTR(-ENODEV);
+
+ chip = gpiochip_find(handle, acpi_gpiochip_find);
+ if (!chip)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return gpiochip_get_desc(chip, pin);
+}
+
+/**
+ * acpi_get_and_request_gpiod - Translate ACPI GPIO pin to GPIO descriptor and
+ * hold a refcount to the GPIO device.
+ * @path: ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1")
+ * @pin: ACPI GPIO pin number (0-based, controller-relative)
+ * @label: Label to pass to gpiod_request()
+ *
+ * This function is a simple pass-through to acpi_get_gpiod(), except that
+ * as it is intended for use outside of the GPIO layer (in a similar fashion to
+ * gpiod_get_index() for example) it also holds a reference to the GPIO device.
+ */
+struct gpio_desc *acpi_get_and_request_gpiod(char *path, unsigned int pin, char *label)
+{
+ struct gpio_desc *gpio;
+ int ret;
+
+ gpio = acpi_get_gpiod(path, pin);
+ if (IS_ERR(gpio))
+ return gpio;
+
+ ret = gpiod_request(gpio, label);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return gpio;
+}
+EXPORT_SYMBOL_GPL(acpi_get_and_request_gpiod);
+
+static irqreturn_t acpi_gpio_irq_handler(int irq, void *data)
+{
+ struct acpi_gpio_event *event = data;
+
+ acpi_evaluate_object(event->handle, NULL, NULL, NULL);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t acpi_gpio_irq_handler_evt(int irq, void *data)
+{
+ struct acpi_gpio_event *event = data;
+
+ acpi_execute_simple_method(event->handle, NULL, event->pin);
+
+ return IRQ_HANDLED;
+}
+
+static void acpi_gpio_chip_dh(acpi_handle handle, void *data)
+{
+ /* The address of this function is used as a key. */
+}
+
+bool acpi_gpio_get_irq_resource(struct acpi_resource *ares,
+ struct acpi_resource_gpio **agpio)
+{
+ struct acpi_resource_gpio *gpio;
+
+ if (ares->type != ACPI_RESOURCE_TYPE_GPIO)
+ return false;
+
+ gpio = &ares->data.gpio;
+ if (gpio->connection_type != ACPI_RESOURCE_GPIO_TYPE_INT)
+ return false;
+
+ *agpio = gpio;
+ return true;
+}
+EXPORT_SYMBOL_GPL(acpi_gpio_get_irq_resource);
+
+/**
+ * acpi_gpio_get_io_resource - Fetch details of an ACPI resource if it is a GPIO
+ * I/O resource or return False if not.
+ * @ares: Pointer to the ACPI resource to fetch
+ * @agpio: Pointer to a &struct acpi_resource_gpio to store the output pointer
+ */
+bool acpi_gpio_get_io_resource(struct acpi_resource *ares,
+ struct acpi_resource_gpio **agpio)
+{
+ struct acpi_resource_gpio *gpio;
+
+ if (ares->type != ACPI_RESOURCE_TYPE_GPIO)
+ return false;
+
+ gpio = &ares->data.gpio;
+ if (gpio->connection_type != ACPI_RESOURCE_GPIO_TYPE_IO)
+ return false;
+
+ *agpio = gpio;
+ return true;
+}
+EXPORT_SYMBOL_GPL(acpi_gpio_get_io_resource);
+
+static void acpi_gpiochip_request_irq(struct acpi_gpio_chip *acpi_gpio,
+ struct acpi_gpio_event *event)
+{
+ struct device *parent = acpi_gpio->chip->parent;
+ int ret, value;
+
+ ret = request_threaded_irq(event->irq, NULL, event->handler,
+ event->irqflags | IRQF_ONESHOT, "ACPI:Event", event);
+ if (ret) {
+ dev_err(parent, "Failed to setup interrupt handler for %d\n", event->irq);
+ return;
+ }
+
+ if (event->irq_is_wake)
+ enable_irq_wake(event->irq);
+
+ event->irq_requested = true;
+
+ /* Make sure we trigger the initial state of edge-triggered IRQs */
+ if (run_edge_events_on_boot &&
+ (event->irqflags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING))) {
+ value = gpiod_get_raw_value_cansleep(event->desc);
+ if (((event->irqflags & IRQF_TRIGGER_RISING) && value == 1) ||
+ ((event->irqflags & IRQF_TRIGGER_FALLING) && value == 0))
+ event->handler(event->irq, event);
+ }
+}
+
+static void acpi_gpiochip_request_irqs(struct acpi_gpio_chip *acpi_gpio)
+{
+ struct acpi_gpio_event *event;
+
+ list_for_each_entry(event, &acpi_gpio->events, node)
+ acpi_gpiochip_request_irq(acpi_gpio, event);
+}
+
+static enum gpiod_flags
+acpi_gpio_to_gpiod_flags(const struct acpi_resource_gpio *agpio, int polarity)
+{
+ /* GpioInt() implies input configuration */
+ if (agpio->connection_type == ACPI_RESOURCE_GPIO_TYPE_INT)
+ return GPIOD_IN;
+
+ switch (agpio->io_restriction) {
+ case ACPI_IO_RESTRICT_INPUT:
+ return GPIOD_IN;
+ case ACPI_IO_RESTRICT_OUTPUT:
+ /*
+ * ACPI GPIO resources don't contain an initial value for the
+ * GPIO. Therefore we deduce that value from the pull field
+ * and the polarity instead. If the pin is pulled up we assume
+ * default to be high, if it is pulled down we assume default
+ * to be low, otherwise we leave pin untouched. For active low
+ * polarity values will be switched. See also
+ * Documentation/firmware-guide/acpi/gpio-properties.rst.
+ */
+ switch (agpio->pin_config) {
+ case ACPI_PIN_CONFIG_PULLUP:
+ return polarity == GPIO_ACTIVE_LOW ? GPIOD_OUT_LOW : GPIOD_OUT_HIGH;
+ case ACPI_PIN_CONFIG_PULLDOWN:
+ return polarity == GPIO_ACTIVE_LOW ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Assume that the BIOS has configured the direction and pull
+ * accordingly.
+ */
+ return GPIOD_ASIS;
+}
+
+static struct gpio_desc *acpi_request_own_gpiod(struct gpio_chip *chip,
+ struct acpi_resource_gpio *agpio,
+ unsigned int index,
+ const char *label)
+{
+ int polarity = GPIO_ACTIVE_HIGH;
+ enum gpiod_flags flags = acpi_gpio_to_gpiod_flags(agpio, polarity);
+ unsigned int pin = agpio->pin_table[index];
+ struct gpio_desc *desc;
+ int ret;
+
+ desc = gpiochip_request_own_desc(chip, pin, label, polarity, flags);
+ if (IS_ERR(desc))
+ return desc;
+
+ /* ACPI uses hundredths of milliseconds units */
+ ret = gpio_set_debounce_timeout(desc, agpio->debounce_timeout * 10);
+ if (ret)
+ dev_warn(chip->parent,
+ "Failed to set debounce-timeout for pin 0x%04X, err %d\n",
+ pin, ret);
+
+ return desc;
+}
+
+static bool acpi_gpio_in_ignore_list(const char *ignore_list, const char *controller_in,
+ unsigned int pin_in)
+{
+ const char *controller, *pin_str;
+ unsigned int pin;
+ char *endp;
+ int len;
+
+ controller = ignore_list;
+ while (controller) {
+ pin_str = strchr(controller, '@');
+ if (!pin_str)
+ goto err;
+
+ len = pin_str - controller;
+ if (len == strlen(controller_in) &&
+ strncmp(controller, controller_in, len) == 0) {
+ pin = simple_strtoul(pin_str + 1, &endp, 10);
+ if (*endp != 0 && *endp != ',')
+ goto err;
+
+ if (pin == pin_in)
+ return true;
+ }
+
+ controller = strchr(controller, ',');
+ if (controller)
+ controller++;
+ }
+
+ return false;
+err:
+ pr_err_once("Error: Invalid value for gpiolib_acpi.ignore_...: %s\n", ignore_list);
+ return false;
+}
+
+static bool acpi_gpio_irq_is_wake(struct device *parent,
+ const struct acpi_resource_gpio *agpio)
+{
+ unsigned int pin = agpio->pin_table[0];
+
+ if (agpio->wake_capable != ACPI_WAKE_CAPABLE)
+ return false;
+
+ if (acpi_gpio_in_ignore_list(ignore_wake, dev_name(parent), pin)) {
+ dev_info(parent, "Ignoring wakeup on pin %u\n", pin);
+ return false;
+ }
+
+ return true;
+}
+
+/* Always returns AE_OK so that we keep looping over the resources */
+static acpi_status acpi_gpiochip_alloc_event(struct acpi_resource *ares,
+ void *context)
+{
+ struct acpi_gpio_chip *acpi_gpio = context;
+ struct gpio_chip *chip = acpi_gpio->chip;
+ struct acpi_resource_gpio *agpio;
+ acpi_handle handle, evt_handle;
+ struct acpi_gpio_event *event;
+ irq_handler_t handler = NULL;
+ struct gpio_desc *desc;
+ unsigned int pin;
+ int ret, irq;
+
+ if (!acpi_gpio_get_irq_resource(ares, &agpio))
+ return AE_OK;
+
+ handle = ACPI_HANDLE(chip->parent);
+ pin = agpio->pin_table[0];
+
+ if (pin <= 255) {
+ char ev_name[8];
+ sprintf(ev_name, "_%c%02X",
+ agpio->triggering == ACPI_EDGE_SENSITIVE ? 'E' : 'L',
+ pin);
+ if (ACPI_SUCCESS(acpi_get_handle(handle, ev_name, &evt_handle)))
+ handler = acpi_gpio_irq_handler;
+ }
+ if (!handler) {
+ if (ACPI_SUCCESS(acpi_get_handle(handle, "_EVT", &evt_handle)))
+ handler = acpi_gpio_irq_handler_evt;
+ }
+ if (!handler)
+ return AE_OK;
+
+ desc = acpi_request_own_gpiod(chip, agpio, 0, "ACPI:Event");
+ if (IS_ERR(desc)) {
+ dev_err(chip->parent,
+ "Failed to request GPIO for pin 0x%04X, err %ld\n",
+ pin, PTR_ERR(desc));
+ return AE_OK;
+ }
+
+ ret = gpiochip_lock_as_irq(chip, pin);
+ if (ret) {
+ dev_err(chip->parent,
+ "Failed to lock GPIO pin 0x%04X as interrupt, err %d\n",
+ pin, ret);
+ goto fail_free_desc;
+ }
+
+ irq = gpiod_to_irq(desc);
+ if (irq < 0) {
+ dev_err(chip->parent,
+ "Failed to translate GPIO pin 0x%04X to IRQ, err %d\n",
+ pin, irq);
+ goto fail_unlock_irq;
+ }
+
+ if (acpi_gpio_in_ignore_list(ignore_interrupt, dev_name(chip->parent), pin)) {
+ dev_info(chip->parent, "Ignoring interrupt on pin %u\n", pin);
+ return AE_OK;
+ }
+
+ event = kzalloc(sizeof(*event), GFP_KERNEL);
+ if (!event)
+ goto fail_unlock_irq;
+
+ event->irqflags = IRQF_ONESHOT;
+ if (agpio->triggering == ACPI_LEVEL_SENSITIVE) {
+ if (agpio->polarity == ACPI_ACTIVE_HIGH)
+ event->irqflags |= IRQF_TRIGGER_HIGH;
+ else
+ event->irqflags |= IRQF_TRIGGER_LOW;
+ } else {
+ switch (agpio->polarity) {
+ case ACPI_ACTIVE_HIGH:
+ event->irqflags |= IRQF_TRIGGER_RISING;
+ break;
+ case ACPI_ACTIVE_LOW:
+ event->irqflags |= IRQF_TRIGGER_FALLING;
+ break;
+ default:
+ event->irqflags |= IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING;
+ break;
+ }
+ }
+
+ event->handle = evt_handle;
+ event->handler = handler;
+ event->irq = irq;
+ event->irq_is_wake = acpi_gpio_irq_is_wake(chip->parent, agpio);
+ event->pin = pin;
+ event->desc = desc;
+
+ list_add_tail(&event->node, &acpi_gpio->events);
+
+ return AE_OK;
+
+fail_unlock_irq:
+ gpiochip_unlock_as_irq(chip, pin);
+fail_free_desc:
+ gpiochip_free_own_desc(desc);
+
+ return AE_OK;
+}
+
+/**
+ * acpi_gpiochip_request_interrupts() - Register isr for gpio chip ACPI events
+ * @chip: GPIO chip
+ *
+ * ACPI5 platforms can use GPIO signaled ACPI events. These GPIO interrupts are
+ * handled by ACPI event methods which need to be called from the GPIO
+ * chip's interrupt handler. acpi_gpiochip_request_interrupts() finds out which
+ * GPIO pins have ACPI event methods and assigns interrupt handlers that calls
+ * the ACPI event methods for those pins.
+ */
+void acpi_gpiochip_request_interrupts(struct gpio_chip *chip)
+{
+ struct acpi_gpio_chip *acpi_gpio;
+ acpi_handle handle;
+ acpi_status status;
+ bool defer;
+
+ if (!chip->parent || !chip->to_irq)
+ return;
+
+ handle = ACPI_HANDLE(chip->parent);
+ if (!handle)
+ return;
+
+ status = acpi_get_data(handle, acpi_gpio_chip_dh, (void **)&acpi_gpio);
+ if (ACPI_FAILURE(status))
+ return;
+
+ if (acpi_quirk_skip_gpio_event_handlers())
+ return;
+
+ acpi_walk_resources(handle, METHOD_NAME__AEI,
+ acpi_gpiochip_alloc_event, acpi_gpio);
+
+ mutex_lock(&acpi_gpio_deferred_req_irqs_lock);
+ defer = !acpi_gpio_deferred_req_irqs_done;
+ if (defer)
+ list_add(&acpi_gpio->deferred_req_irqs_list_entry,
+ &acpi_gpio_deferred_req_irqs_list);
+ mutex_unlock(&acpi_gpio_deferred_req_irqs_lock);
+
+ if (defer)
+ return;
+
+ acpi_gpiochip_request_irqs(acpi_gpio);
+}
+EXPORT_SYMBOL_GPL(acpi_gpiochip_request_interrupts);
+
+/**
+ * acpi_gpiochip_free_interrupts() - Free GPIO ACPI event interrupts.
+ * @chip: GPIO chip
+ *
+ * Free interrupts associated with GPIO ACPI event method for the given
+ * GPIO chip.
+ */
+void acpi_gpiochip_free_interrupts(struct gpio_chip *chip)
+{
+ struct acpi_gpio_chip *acpi_gpio;
+ struct acpi_gpio_event *event, *ep;
+ acpi_handle handle;
+ acpi_status status;
+
+ if (!chip->parent || !chip->to_irq)
+ return;
+
+ handle = ACPI_HANDLE(chip->parent);
+ if (!handle)
+ return;
+
+ status = acpi_get_data(handle, acpi_gpio_chip_dh, (void **)&acpi_gpio);
+ if (ACPI_FAILURE(status))
+ return;
+
+ mutex_lock(&acpi_gpio_deferred_req_irqs_lock);
+ if (!list_empty(&acpi_gpio->deferred_req_irqs_list_entry))
+ list_del_init(&acpi_gpio->deferred_req_irqs_list_entry);
+ mutex_unlock(&acpi_gpio_deferred_req_irqs_lock);
+
+ list_for_each_entry_safe_reverse(event, ep, &acpi_gpio->events, node) {
+ if (event->irq_requested) {
+ if (event->irq_is_wake)
+ disable_irq_wake(event->irq);
+
+ free_irq(event->irq, event);
+ }
+
+ gpiochip_unlock_as_irq(chip, event->pin);
+ gpiochip_free_own_desc(event->desc);
+ list_del(&event->node);
+ kfree(event);
+ }
+}
+EXPORT_SYMBOL_GPL(acpi_gpiochip_free_interrupts);
+
+int acpi_dev_add_driver_gpios(struct acpi_device *adev,
+ const struct acpi_gpio_mapping *gpios)
+{
+ if (adev && gpios) {
+ adev->driver_gpios = gpios;
+ return 0;
+ }
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(acpi_dev_add_driver_gpios);
+
+void acpi_dev_remove_driver_gpios(struct acpi_device *adev)
+{
+ if (adev)
+ adev->driver_gpios = NULL;
+}
+EXPORT_SYMBOL_GPL(acpi_dev_remove_driver_gpios);
+
+static void acpi_dev_release_driver_gpios(void *adev)
+{
+ acpi_dev_remove_driver_gpios(adev);
+}
+
+int devm_acpi_dev_add_driver_gpios(struct device *dev,
+ const struct acpi_gpio_mapping *gpios)
+{
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+ int ret;
+
+ ret = acpi_dev_add_driver_gpios(adev, gpios);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, acpi_dev_release_driver_gpios, adev);
+}
+EXPORT_SYMBOL_GPL(devm_acpi_dev_add_driver_gpios);
+
+static bool acpi_get_driver_gpio_data(struct acpi_device *adev,
+ const char *name, int index,
+ struct fwnode_reference_args *args,
+ unsigned int *quirks)
+{
+ const struct acpi_gpio_mapping *gm;
+
+ if (!adev || !adev->driver_gpios)
+ return false;
+
+ for (gm = adev->driver_gpios; gm->name; gm++)
+ if (!strcmp(name, gm->name) && gm->data && index < gm->size) {
+ const struct acpi_gpio_params *par = gm->data + index;
+
+ args->fwnode = acpi_fwnode_handle(adev);
+ args->args[0] = par->crs_entry_index;
+ args->args[1] = par->line_index;
+ args->args[2] = par->active_low;
+ args->nargs = 3;
+
+ *quirks = gm->quirks;
+ return true;
+ }
+
+ return false;
+}
+
+static int
+__acpi_gpio_update_gpiod_flags(enum gpiod_flags *flags, enum gpiod_flags update)
+{
+ const enum gpiod_flags mask =
+ GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT |
+ GPIOD_FLAGS_BIT_DIR_VAL;
+ int ret = 0;
+
+ /*
+ * Check if the BIOS has IoRestriction with explicitly set direction
+ * and update @flags accordingly. Otherwise use whatever caller asked
+ * for.
+ */
+ if (update & GPIOD_FLAGS_BIT_DIR_SET) {
+ enum gpiod_flags diff = *flags ^ update;
+
+ /*
+ * Check if caller supplied incompatible GPIO initialization
+ * flags.
+ *
+ * Return %-EINVAL to notify that firmware has different
+ * settings and we are going to use them.
+ */
+ if (((*flags & GPIOD_FLAGS_BIT_DIR_SET) && (diff & GPIOD_FLAGS_BIT_DIR_OUT)) ||
+ ((*flags & GPIOD_FLAGS_BIT_DIR_OUT) && (diff & GPIOD_FLAGS_BIT_DIR_VAL)))
+ ret = -EINVAL;
+ *flags = (*flags & ~mask) | (update & mask);
+ }
+ return ret;
+}
+
+static int acpi_gpio_update_gpiod_flags(enum gpiod_flags *flags,
+ struct acpi_gpio_info *info)
+{
+ struct device *dev = &info->adev->dev;
+ enum gpiod_flags old = *flags;
+ int ret;
+
+ ret = __acpi_gpio_update_gpiod_flags(&old, info->flags);
+ if (info->quirks & ACPI_GPIO_QUIRK_NO_IO_RESTRICTION) {
+ if (ret)
+ dev_warn(dev, FW_BUG "GPIO not in correct mode, fixing\n");
+ } else {
+ if (ret)
+ dev_dbg(dev, "Override GPIO initialization flags\n");
+ *flags = old;
+ }
+
+ return ret;
+}
+
+static int acpi_gpio_update_gpiod_lookup_flags(unsigned long *lookupflags,
+ struct acpi_gpio_info *info)
+{
+ switch (info->pin_config) {
+ case ACPI_PIN_CONFIG_PULLUP:
+ *lookupflags |= GPIO_PULL_UP;
+ break;
+ case ACPI_PIN_CONFIG_PULLDOWN:
+ *lookupflags |= GPIO_PULL_DOWN;
+ break;
+ case ACPI_PIN_CONFIG_NOPULL:
+ *lookupflags |= GPIO_PULL_DISABLE;
+ break;
+ default:
+ break;
+ }
+
+ if (info->polarity == GPIO_ACTIVE_LOW)
+ *lookupflags |= GPIO_ACTIVE_LOW;
+
+ return 0;
+}
+
+struct acpi_gpio_lookup {
+ struct acpi_gpio_info info;
+ int index;
+ u16 pin_index;
+ bool active_low;
+ struct gpio_desc *desc;
+ int n;
+};
+
+static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data)
+{
+ struct acpi_gpio_lookup *lookup = data;
+
+ if (ares->type != ACPI_RESOURCE_TYPE_GPIO)
+ return 1;
+
+ if (!lookup->desc) {
+ const struct acpi_resource_gpio *agpio = &ares->data.gpio;
+ bool gpioint = agpio->connection_type == ACPI_RESOURCE_GPIO_TYPE_INT;
+ struct gpio_desc *desc;
+ u16 pin_index;
+
+ if (lookup->info.quirks & ACPI_GPIO_QUIRK_ONLY_GPIOIO && gpioint)
+ lookup->index++;
+
+ if (lookup->n++ != lookup->index)
+ return 1;
+
+ pin_index = lookup->pin_index;
+ if (pin_index >= agpio->pin_table_length)
+ return 1;
+
+ if (lookup->info.quirks & ACPI_GPIO_QUIRK_ABSOLUTE_NUMBER)
+ desc = gpio_to_desc(agpio->pin_table[pin_index]);
+ else
+ desc = acpi_get_gpiod(agpio->resource_source.string_ptr,
+ agpio->pin_table[pin_index]);
+ lookup->desc = desc;
+ lookup->info.pin_config = agpio->pin_config;
+ lookup->info.debounce = agpio->debounce_timeout;
+ lookup->info.gpioint = gpioint;
+ lookup->info.wake_capable = acpi_gpio_irq_is_wake(&lookup->info.adev->dev, agpio);
+
+ /*
+ * Polarity and triggering are only specified for GpioInt
+ * resource.
+ * Note: we expect here:
+ * - ACPI_ACTIVE_LOW == GPIO_ACTIVE_LOW
+ * - ACPI_ACTIVE_HIGH == GPIO_ACTIVE_HIGH
+ */
+ if (lookup->info.gpioint) {
+ lookup->info.polarity = agpio->polarity;
+ lookup->info.triggering = agpio->triggering;
+ } else {
+ lookup->info.polarity = lookup->active_low;
+ }
+
+ lookup->info.flags = acpi_gpio_to_gpiod_flags(agpio, lookup->info.polarity);
+ }
+
+ return 1;
+}
+
+static int acpi_gpio_resource_lookup(struct acpi_gpio_lookup *lookup,
+ struct acpi_gpio_info *info)
+{
+ struct acpi_device *adev = lookup->info.adev;
+ struct list_head res_list;
+ int ret;
+
+ INIT_LIST_HEAD(&res_list);
+
+ ret = acpi_dev_get_resources(adev, &res_list,
+ acpi_populate_gpio_lookup,
+ lookup);
+ if (ret < 0)
+ return ret;
+
+ acpi_dev_free_resource_list(&res_list);
+
+ if (!lookup->desc)
+ return -ENOENT;
+
+ if (info)
+ *info = lookup->info;
+ return 0;
+}
+
+static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode,
+ const char *propname, int index,
+ struct acpi_gpio_lookup *lookup)
+{
+ struct fwnode_reference_args args;
+ unsigned int quirks = 0;
+ int ret;
+
+ memset(&args, 0, sizeof(args));
+ ret = __acpi_node_get_property_reference(fwnode, propname, index, 3,
+ &args);
+ if (ret) {
+ struct acpi_device *adev;
+
+ adev = to_acpi_device_node(fwnode);
+ if (!acpi_get_driver_gpio_data(adev, propname, index, &args, &quirks))
+ return ret;
+ }
+ /*
+ * The property was found and resolved, so need to lookup the GPIO based
+ * on returned args.
+ */
+ if (!to_acpi_device_node(args.fwnode))
+ return -EINVAL;
+ if (args.nargs != 3)
+ return -EPROTO;
+
+ lookup->index = args.args[0];
+ lookup->pin_index = args.args[1];
+ lookup->active_low = !!args.args[2];
+
+ lookup->info.adev = to_acpi_device_node(args.fwnode);
+ lookup->info.quirks = quirks;
+
+ return 0;
+}
+
+/**
+ * acpi_get_gpiod_by_index() - get a GPIO descriptor from device resources
+ * @adev: pointer to a ACPI device to get GPIO from
+ * @propname: Property name of the GPIO (optional)
+ * @index: index of GpioIo/GpioInt resource (starting from %0)
+ * @info: info pointer to fill in (optional)
+ *
+ * Function goes through ACPI resources for @adev and based on @index looks
+ * up a GpioIo/GpioInt resource, translates it to the Linux GPIO descriptor,
+ * and returns it. @index matches GpioIo/GpioInt resources only so if there
+ * are total %3 GPIO resources, the index goes from %0 to %2.
+ *
+ * If @propname is specified the GPIO is looked using device property. In
+ * that case @index is used to select the GPIO entry in the property value
+ * (in case of multiple).
+ *
+ * If the GPIO cannot be translated or there is an error, an ERR_PTR is
+ * returned.
+ *
+ * Note: if the GPIO resource has multiple entries in the pin list, this
+ * function only returns the first.
+ */
+static struct gpio_desc *acpi_get_gpiod_by_index(struct acpi_device *adev,
+ const char *propname,
+ int index,
+ struct acpi_gpio_info *info)
+{
+ struct acpi_gpio_lookup lookup;
+ int ret;
+
+ if (!adev)
+ return ERR_PTR(-ENODEV);
+
+ memset(&lookup, 0, sizeof(lookup));
+ lookup.index = index;
+
+ if (propname) {
+ dev_dbg(&adev->dev, "GPIO: looking up %s\n", propname);
+
+ ret = acpi_gpio_property_lookup(acpi_fwnode_handle(adev),
+ propname, index, &lookup);
+ if (ret)
+ return ERR_PTR(ret);
+
+ dev_dbg(&adev->dev, "GPIO: _DSD returned %s %d %u %u\n",
+ dev_name(&lookup.info.adev->dev), lookup.index,
+ lookup.pin_index, lookup.active_low);
+ } else {
+ dev_dbg(&adev->dev, "GPIO: looking up %d in _CRS\n", index);
+ lookup.info.adev = adev;
+ }
+
+ ret = acpi_gpio_resource_lookup(&lookup, info);
+ return ret ? ERR_PTR(ret) : lookup.desc;
+}
+
+/**
+ * acpi_get_gpiod_from_data() - get a GPIO descriptor from ACPI data node
+ * @fwnode: pointer to an ACPI firmware node to get the GPIO information from
+ * @propname: Property name of the GPIO
+ * @index: index of GpioIo/GpioInt resource (starting from %0)
+ * @info: info pointer to fill in (optional)
+ *
+ * This function uses the property-based GPIO lookup to get to the GPIO
+ * resource with the relevant information from a data-only ACPI firmware node
+ * and uses that to obtain the GPIO descriptor to return.
+ *
+ * If the GPIO cannot be translated or there is an error an ERR_PTR is
+ * returned.
+ */
+static struct gpio_desc *acpi_get_gpiod_from_data(struct fwnode_handle *fwnode,
+ const char *propname,
+ int index,
+ struct acpi_gpio_info *info)
+{
+ struct acpi_gpio_lookup lookup;
+ int ret;
+
+ if (!is_acpi_data_node(fwnode))
+ return ERR_PTR(-ENODEV);
+
+ if (!propname)
+ return ERR_PTR(-EINVAL);
+
+ memset(&lookup, 0, sizeof(lookup));
+ lookup.index = index;
+
+ ret = acpi_gpio_property_lookup(fwnode, propname, index, &lookup);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = acpi_gpio_resource_lookup(&lookup, info);
+ return ret ? ERR_PTR(ret) : lookup.desc;
+}
+
+static bool acpi_can_fallback_to_crs(struct acpi_device *adev,
+ const char *con_id)
+{
+ /* Never allow fallback if the device has properties */
+ if (acpi_dev_has_props(adev) || adev->driver_gpios)
+ return false;
+
+ return con_id == NULL;
+}
+
+struct gpio_desc *acpi_find_gpio(struct fwnode_handle *fwnode,
+ const char *con_id,
+ unsigned int idx,
+ enum gpiod_flags *dflags,
+ unsigned long *lookupflags)
+{
+ struct acpi_device *adev = to_acpi_device_node(fwnode);
+ struct acpi_gpio_info info;
+ struct gpio_desc *desc;
+ char propname[32];
+ int i;
+
+ /* Try first from _DSD */
+ for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {
+ if (con_id) {
+ snprintf(propname, sizeof(propname), "%s-%s",
+ con_id, gpio_suffixes[i]);
+ } else {
+ snprintf(propname, sizeof(propname), "%s",
+ gpio_suffixes[i]);
+ }
+
+ if (adev)
+ desc = acpi_get_gpiod_by_index(adev,
+ propname, idx, &info);
+ else
+ desc = acpi_get_gpiod_from_data(fwnode,
+ propname, idx, &info);
+ if (!IS_ERR(desc))
+ break;
+ if (PTR_ERR(desc) == -EPROBE_DEFER)
+ return ERR_CAST(desc);
+ }
+
+ /* Then from plain _CRS GPIOs */
+ if (IS_ERR(desc)) {
+ if (!adev || !acpi_can_fallback_to_crs(adev, con_id))
+ return ERR_PTR(-ENOENT);
+
+ desc = acpi_get_gpiod_by_index(adev, NULL, idx, &info);
+ if (IS_ERR(desc))
+ return desc;
+ }
+
+ if (info.gpioint &&
+ (*dflags == GPIOD_OUT_LOW || *dflags == GPIOD_OUT_HIGH)) {
+ dev_dbg(&adev->dev, "refusing GpioInt() entry when doing GPIOD_OUT_* lookup\n");
+ return ERR_PTR(-ENOENT);
+ }
+
+ acpi_gpio_update_gpiod_flags(dflags, &info);
+ acpi_gpio_update_gpiod_lookup_flags(lookupflags, &info);
+ return desc;
+}
+
+/**
+ * acpi_dev_gpio_irq_wake_get_by() - Find GpioInt and translate it to Linux IRQ number
+ * @adev: pointer to a ACPI device to get IRQ from
+ * @name: optional name of GpioInt resource
+ * @index: index of GpioInt resource (starting from %0)
+ * @wake_capable: Set to true if the IRQ is wake capable
+ *
+ * If the device has one or more GpioInt resources, this function can be
+ * used to translate from the GPIO offset in the resource to the Linux IRQ
+ * number.
+ *
+ * The function is idempotent, though each time it runs it will configure GPIO
+ * pin direction according to the flags in GpioInt resource.
+ *
+ * The function takes optional @name parameter. If the resource has a property
+ * name, then only those will be taken into account.
+ *
+ * The GPIO is considered wake capable if the GpioInt resource specifies
+ * SharedAndWake or ExclusiveAndWake.
+ *
+ * Return: Linux IRQ number (> %0) on success, negative errno on failure.
+ */
+int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *name, int index,
+ bool *wake_capable)
+{
+ int idx, i;
+ unsigned int irq_flags;
+ int ret;
+
+ for (i = 0, idx = 0; idx <= index; i++) {
+ struct acpi_gpio_info info;
+ struct gpio_desc *desc;
+
+ desc = acpi_get_gpiod_by_index(adev, name, i, &info);
+
+ /* Ignore -EPROBE_DEFER, it only matters if idx matches */
+ if (IS_ERR(desc) && PTR_ERR(desc) != -EPROBE_DEFER)
+ return PTR_ERR(desc);
+
+ if (info.gpioint && idx++ == index) {
+ unsigned long lflags = GPIO_LOOKUP_FLAGS_DEFAULT;
+ enum gpiod_flags dflags = GPIOD_ASIS;
+ char label[32];
+ int irq;
+
+ if (IS_ERR(desc))
+ return PTR_ERR(desc);
+
+ irq = gpiod_to_irq(desc);
+ if (irq < 0)
+ return irq;
+
+ acpi_gpio_update_gpiod_flags(&dflags, &info);
+ acpi_gpio_update_gpiod_lookup_flags(&lflags, &info);
+
+ snprintf(label, sizeof(label), "GpioInt() %d", index);
+ ret = gpiod_configure_flags(desc, label, lflags, dflags);
+ if (ret < 0)
+ return ret;
+
+ /* ACPI uses hundredths of milliseconds units */
+ ret = gpio_set_debounce_timeout(desc, info.debounce * 10);
+ if (ret)
+ return ret;
+
+ irq_flags = acpi_dev_get_irq_type(info.triggering,
+ info.polarity);
+
+ /*
+ * If the IRQ is not already in use then set type
+ * if specified and different than the current one.
+ */
+ if (can_request_irq(irq, irq_flags)) {
+ if (irq_flags != IRQ_TYPE_NONE &&
+ irq_flags != irq_get_trigger_type(irq))
+ irq_set_irq_type(irq, irq_flags);
+ } else {
+ dev_dbg(&adev->dev, "IRQ %d already in use\n", irq);
+ }
+
+ /* avoid suspend issues with GPIOs when systems are using S3 */
+ if (wake_capable && acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)
+ *wake_capable = info.wake_capable;
+
+ return irq;
+ }
+
+ }
+ return -ENOENT;
+}
+EXPORT_SYMBOL_GPL(acpi_dev_gpio_irq_wake_get_by);
+
+static acpi_status
+acpi_gpio_adr_space_handler(u32 function, acpi_physical_address address,
+ u32 bits, u64 *value, void *handler_context,
+ void *region_context)
+{
+ struct acpi_gpio_chip *achip = region_context;
+ struct gpio_chip *chip = achip->chip;
+ struct acpi_resource_gpio *agpio;
+ struct acpi_resource *ares;
+ u16 pin_index = address;
+ acpi_status status;
+ int length;
+ int i;
+
+ status = acpi_buffer_to_resource(achip->conn_info.connection,
+ achip->conn_info.length, &ares);
+ if (ACPI_FAILURE(status))
+ return status;
+
+ if (WARN_ON(ares->type != ACPI_RESOURCE_TYPE_GPIO)) {
+ ACPI_FREE(ares);
+ return AE_BAD_PARAMETER;
+ }
+
+ agpio = &ares->data.gpio;
+
+ if (WARN_ON(agpio->io_restriction == ACPI_IO_RESTRICT_INPUT &&
+ function == ACPI_WRITE)) {
+ ACPI_FREE(ares);
+ return AE_BAD_PARAMETER;
+ }
+
+ length = min_t(u16, agpio->pin_table_length, pin_index + bits);
+ for (i = pin_index; i < length; ++i) {
+ unsigned int pin = agpio->pin_table[i];
+ struct acpi_gpio_connection *conn;
+ struct gpio_desc *desc;
+ bool found;
+
+ mutex_lock(&achip->conn_lock);
+
+ found = false;
+ list_for_each_entry(conn, &achip->conns, node) {
+ if (conn->pin == pin) {
+ found = true;
+ desc = conn->desc;
+ break;
+ }
+ }
+
+ /*
+ * The same GPIO can be shared between operation region and
+ * event but only if the access here is ACPI_READ. In that
+ * case we "borrow" the event GPIO instead.
+ */
+ if (!found && agpio->shareable == ACPI_SHARED &&
+ function == ACPI_READ) {
+ struct acpi_gpio_event *event;
+
+ list_for_each_entry(event, &achip->events, node) {
+ if (event->pin == pin) {
+ desc = event->desc;
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ desc = acpi_request_own_gpiod(chip, agpio, i, "ACPI:OpRegion");
+ if (IS_ERR(desc)) {
+ mutex_unlock(&achip->conn_lock);
+ status = AE_ERROR;
+ goto out;
+ }
+
+ conn = kzalloc(sizeof(*conn), GFP_KERNEL);
+ if (!conn) {
+ gpiochip_free_own_desc(desc);
+ mutex_unlock(&achip->conn_lock);
+ status = AE_NO_MEMORY;
+ goto out;
+ }
+
+ conn->pin = pin;
+ conn->desc = desc;
+ list_add_tail(&conn->node, &achip->conns);
+ }
+
+ mutex_unlock(&achip->conn_lock);
+
+ if (function == ACPI_WRITE)
+ gpiod_set_raw_value_cansleep(desc, !!(*value & BIT(i)));
+ else
+ *value |= (u64)gpiod_get_raw_value_cansleep(desc) << i;
+ }
+
+out:
+ ACPI_FREE(ares);
+ return status;
+}
+
+static void acpi_gpiochip_request_regions(struct acpi_gpio_chip *achip)
+{
+ struct gpio_chip *chip = achip->chip;
+ acpi_handle handle = ACPI_HANDLE(chip->parent);
+ acpi_status status;
+
+ INIT_LIST_HEAD(&achip->conns);
+ mutex_init(&achip->conn_lock);
+ status = acpi_install_address_space_handler(handle, ACPI_ADR_SPACE_GPIO,
+ acpi_gpio_adr_space_handler,
+ NULL, achip);
+ if (ACPI_FAILURE(status))
+ dev_err(chip->parent,
+ "Failed to install GPIO OpRegion handler\n");
+}
+
+static void acpi_gpiochip_free_regions(struct acpi_gpio_chip *achip)
+{
+ struct gpio_chip *chip = achip->chip;
+ acpi_handle handle = ACPI_HANDLE(chip->parent);
+ struct acpi_gpio_connection *conn, *tmp;
+ acpi_status status;
+
+ status = acpi_remove_address_space_handler(handle, ACPI_ADR_SPACE_GPIO,
+ acpi_gpio_adr_space_handler);
+ if (ACPI_FAILURE(status)) {
+ dev_err(chip->parent,
+ "Failed to remove GPIO OpRegion handler\n");
+ return;
+ }
+
+ list_for_each_entry_safe_reverse(conn, tmp, &achip->conns, node) {
+ gpiochip_free_own_desc(conn->desc);
+ list_del(&conn->node);
+ kfree(conn);
+ }
+}
+
+static struct gpio_desc *
+acpi_gpiochip_parse_own_gpio(struct acpi_gpio_chip *achip,
+ struct fwnode_handle *fwnode,
+ const char **name,
+ unsigned long *lflags,
+ enum gpiod_flags *dflags)
+{
+ struct gpio_chip *chip = achip->chip;
+ struct gpio_desc *desc;
+ u32 gpios[2];
+ int ret;
+
+ *lflags = GPIO_LOOKUP_FLAGS_DEFAULT;
+ *dflags = GPIOD_ASIS;
+ *name = NULL;
+
+ ret = fwnode_property_read_u32_array(fwnode, "gpios", gpios,
+ ARRAY_SIZE(gpios));
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ desc = gpiochip_get_desc(chip, gpios[0]);
+ if (IS_ERR(desc))
+ return desc;
+
+ if (gpios[1])
+ *lflags |= GPIO_ACTIVE_LOW;
+
+ if (fwnode_property_present(fwnode, "input"))
+ *dflags |= GPIOD_IN;
+ else if (fwnode_property_present(fwnode, "output-low"))
+ *dflags |= GPIOD_OUT_LOW;
+ else if (fwnode_property_present(fwnode, "output-high"))
+ *dflags |= GPIOD_OUT_HIGH;
+ else
+ return ERR_PTR(-EINVAL);
+
+ fwnode_property_read_string(fwnode, "line-name", name);
+
+ return desc;
+}
+
+static void acpi_gpiochip_scan_gpios(struct acpi_gpio_chip *achip)
+{
+ struct gpio_chip *chip = achip->chip;
+ struct fwnode_handle *fwnode;
+
+ device_for_each_child_node(chip->parent, fwnode) {
+ unsigned long lflags;
+ enum gpiod_flags dflags;
+ struct gpio_desc *desc;
+ const char *name;
+ int ret;
+
+ if (!fwnode_property_present(fwnode, "gpio-hog"))
+ continue;
+
+ desc = acpi_gpiochip_parse_own_gpio(achip, fwnode, &name,
+ &lflags, &dflags);
+ if (IS_ERR(desc))
+ continue;
+
+ ret = gpiod_hog(desc, name, lflags, dflags);
+ if (ret) {
+ dev_err(chip->parent, "Failed to hog GPIO\n");
+ fwnode_handle_put(fwnode);
+ return;
+ }
+ }
+}
+
+void acpi_gpiochip_add(struct gpio_chip *chip)
+{
+ struct acpi_gpio_chip *acpi_gpio;
+ struct acpi_device *adev;
+ acpi_status status;
+
+ if (!chip || !chip->parent)
+ return;
+
+ adev = ACPI_COMPANION(chip->parent);
+ if (!adev)
+ return;
+
+ acpi_gpio = kzalloc(sizeof(*acpi_gpio), GFP_KERNEL);
+ if (!acpi_gpio) {
+ dev_err(chip->parent,
+ "Failed to allocate memory for ACPI GPIO chip\n");
+ return;
+ }
+
+ acpi_gpio->chip = chip;
+ INIT_LIST_HEAD(&acpi_gpio->events);
+ INIT_LIST_HEAD(&acpi_gpio->deferred_req_irqs_list_entry);
+
+ status = acpi_attach_data(adev->handle, acpi_gpio_chip_dh, acpi_gpio);
+ if (ACPI_FAILURE(status)) {
+ dev_err(chip->parent, "Failed to attach ACPI GPIO chip\n");
+ kfree(acpi_gpio);
+ return;
+ }
+
+ acpi_gpiochip_request_regions(acpi_gpio);
+ acpi_gpiochip_scan_gpios(acpi_gpio);
+ acpi_dev_clear_dependencies(adev);
+}
+
+void acpi_gpiochip_remove(struct gpio_chip *chip)
+{
+ struct acpi_gpio_chip *acpi_gpio;
+ acpi_handle handle;
+ acpi_status status;
+
+ if (!chip || !chip->parent)
+ return;
+
+ handle = ACPI_HANDLE(chip->parent);
+ if (!handle)
+ return;
+
+ status = acpi_get_data(handle, acpi_gpio_chip_dh, (void **)&acpi_gpio);
+ if (ACPI_FAILURE(status)) {
+ dev_warn(chip->parent, "Failed to retrieve ACPI GPIO chip\n");
+ return;
+ }
+
+ acpi_gpiochip_free_regions(acpi_gpio);
+
+ acpi_detach_data(handle, acpi_gpio_chip_dh);
+ kfree(acpi_gpio);
+}
+
+static int acpi_gpio_package_count(const union acpi_object *obj)
+{
+ const union acpi_object *element = obj->package.elements;
+ const union acpi_object *end = element + obj->package.count;
+ unsigned int count = 0;
+
+ while (element < end) {
+ switch (element->type) {
+ case ACPI_TYPE_LOCAL_REFERENCE:
+ element += 3;
+ fallthrough;
+ case ACPI_TYPE_INTEGER:
+ element++;
+ count++;
+ break;
+
+ default:
+ return -EPROTO;
+ }
+ }
+
+ return count;
+}
+
+static int acpi_find_gpio_count(struct acpi_resource *ares, void *data)
+{
+ unsigned int *count = data;
+
+ if (ares->type == ACPI_RESOURCE_TYPE_GPIO)
+ *count += ares->data.gpio.pin_table_length;
+
+ return 1;
+}
+
+/**
+ * acpi_gpio_count - count the GPIOs associated with a device / function
+ * @dev: GPIO consumer, can be %NULL for system-global GPIOs
+ * @con_id: function within the GPIO consumer
+ *
+ * Return:
+ * The number of GPIOs associated with a device / function or %-ENOENT,
+ * if no GPIO has been assigned to the requested function.
+ */
+int acpi_gpio_count(struct device *dev, const char *con_id)
+{
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+ const union acpi_object *obj;
+ const struct acpi_gpio_mapping *gm;
+ int count = -ENOENT;
+ int ret;
+ char propname[32];
+ unsigned int i;
+
+ /* Try first from _DSD */
+ for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {
+ if (con_id)
+ snprintf(propname, sizeof(propname), "%s-%s",
+ con_id, gpio_suffixes[i]);
+ else
+ snprintf(propname, sizeof(propname), "%s",
+ gpio_suffixes[i]);
+
+ ret = acpi_dev_get_property(adev, propname, ACPI_TYPE_ANY,
+ &obj);
+ if (ret == 0) {
+ if (obj->type == ACPI_TYPE_LOCAL_REFERENCE)
+ count = 1;
+ else if (obj->type == ACPI_TYPE_PACKAGE)
+ count = acpi_gpio_package_count(obj);
+ } else if (adev->driver_gpios) {
+ for (gm = adev->driver_gpios; gm->name; gm++)
+ if (strcmp(propname, gm->name) == 0) {
+ count = gm->size;
+ break;
+ }
+ }
+ if (count > 0)
+ break;
+ }
+
+ /* Then from plain _CRS GPIOs */
+ if (count < 0) {
+ struct list_head resource_list;
+ unsigned int crs_count = 0;
+
+ if (!acpi_can_fallback_to_crs(adev, con_id))
+ return count;
+
+ INIT_LIST_HEAD(&resource_list);
+ acpi_dev_get_resources(adev, &resource_list,
+ acpi_find_gpio_count, &crs_count);
+ acpi_dev_free_resource_list(&resource_list);
+ if (crs_count > 0)
+ count = crs_count;
+ }
+ return count ? count : -ENOENT;
+}
+
+/* Run deferred acpi_gpiochip_request_irqs() */
+static int __init acpi_gpio_handle_deferred_request_irqs(void)
+{
+ struct acpi_gpio_chip *acpi_gpio, *tmp;
+
+ mutex_lock(&acpi_gpio_deferred_req_irqs_lock);
+ list_for_each_entry_safe(acpi_gpio, tmp,
+ &acpi_gpio_deferred_req_irqs_list,
+ deferred_req_irqs_list_entry)
+ acpi_gpiochip_request_irqs(acpi_gpio);
+
+ acpi_gpio_deferred_req_irqs_done = true;
+ mutex_unlock(&acpi_gpio_deferred_req_irqs_lock);
+
+ return 0;
+}
+/* We must use _sync so that this runs after the first deferred_probe run */
+late_initcall_sync(acpi_gpio_handle_deferred_request_irqs);
+
+static const struct dmi_system_id gpiolib_acpi_quirks[] __initconst = {
+ {
+ /*
+ * The Minix Neo Z83-4 has a micro-USB-B id-pin handler for
+ * a non existing micro-USB-B connector which puts the HDMI
+ * DDC pins in GPIO mode, breaking HDMI support.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MINIX"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"),
+ },
+ .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
+ .no_edge_events_on_boot = true,
+ },
+ },
+ {
+ /*
+ * The Terra Pad 1061 has a micro-USB-B id-pin handler, which
+ * instead of controlling the actual micro-USB-B turns the 5V
+ * boost for its USB-A connector off. The actual micro-USB-B
+ * connector is wired for charging only.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Wortmann_AG"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TERRA_PAD_1061"),
+ },
+ .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
+ .no_edge_events_on_boot = true,
+ },
+ },
+ {
+ /*
+ * The Dell Venue 10 Pro 5055, with Bay Trail SoC + TI PMIC uses an
+ * external embedded-controller connected via I2C + an ACPI GPIO
+ * event handler on INT33FFC:02 pin 12, causing spurious wakeups.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Venue 10 Pro 5055"),
+ },
+ .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
+ .ignore_wake = "INT33FC:02@12",
+ },
+ },
+ {
+ /*
+ * HP X2 10 models with Cherry Trail SoC + TI PMIC use an
+ * external embedded-controller connected via I2C + an ACPI GPIO
+ * event handler on INT33FF:01 pin 0, causing spurious wakeups.
+ * When suspending by closing the LID, the power to the USB
+ * keyboard is turned off, causing INT0002 ACPI events to
+ * trigger once the XHCI controller notices the keyboard is
+ * gone. So INT0002 events cause spurious wakeups too. Ignoring
+ * EC wakes breaks wakeup when opening the lid, the user needs
+ * to press the power-button to wakeup the system. The
+ * alternative is suspend simply not working, which is worse.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP x2 Detachable 10-p0XX"),
+ },
+ .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
+ .ignore_wake = "INT33FF:01@0,INT0002:00@2",
+ },
+ },
+ {
+ /*
+ * HP X2 10 models with Bay Trail SoC + AXP288 PMIC use an
+ * external embedded-controller connected via I2C + an ACPI GPIO
+ * event handler on INT33FC:02 pin 28, causing spurious wakeups.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"),
+ DMI_MATCH(DMI_BOARD_NAME, "815D"),
+ },
+ .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
+ .ignore_wake = "INT33FC:02@28",
+ },
+ },
+ {
+ /*
+ * HP X2 10 models with Cherry Trail SoC + AXP288 PMIC use an
+ * external embedded-controller connected via I2C + an ACPI GPIO
+ * event handler on INT33FF:01 pin 0, causing spurious wakeups.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"),
+ DMI_MATCH(DMI_BOARD_NAME, "813E"),
+ },
+ .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
+ .ignore_wake = "INT33FF:01@0",
+ },
+ },
+ {
+ /*
+ * Interrupt storm caused from edge triggered floating pin
+ * Found in BIOS UX325UAZ.300
+ * https://bugzilla.kernel.org/show_bug.cgi?id=216208
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX325UAZ_UM325UAZ"),
+ },
+ .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
+ .ignore_interrupt = "AMDI0030:00@18",
+ },
+ },
+ {
+ /*
+ * Spurious wakeups from TP_ATTN# pin
+ * Found in BIOS 1.7.8
+ * https://gitlab.freedesktop.org/drm/amd/-/issues/1722#note_1720627
+ */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "NL5xNU"),
+ },
+ .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
+ .ignore_wake = "ELAN0415:00@9",
+ },
+ },
+ {
+ /*
+ * Spurious wakeups from TP_ATTN# pin
+ * Found in BIOS 1.7.8
+ * https://gitlab.freedesktop.org/drm/amd/-/issues/1722#note_1720627
+ */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "NL5xRU"),
+ },
+ .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
+ .ignore_wake = "ELAN0415:00@9",
+ },
+ },
+ {
+ /*
+ * Spurious wakeups from TP_ATTN# pin
+ * Found in BIOS 1.7.7
+ */
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "NH5xAx"),
+ },
+ .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
+ .ignore_wake = "SYNA1202:00@16",
+ },
+ },
+ {
+ /*
+ * On the Peaq C1010 2-in-1 INT33FC:00 pin 3 is connected to
+ * a "dolby" button. At the ACPI level an _AEI event-handler
+ * is connected which sets an ACPI variable to 1 on both
+ * edges. This variable can be polled + cleared to 0 using
+ * WMI. But since the variable is set on both edges the WMI
+ * interface is pretty useless even when polling.
+ * So instead the x86-android-tablets code instantiates
+ * a gpio-keys platform device for it.
+ * Ignore the _AEI handler for the pin, so that it is not busy.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "PEAQ"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "PEAQ PMM C1010 MD99187"),
+ },
+ .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
+ .ignore_interrupt = "INT33FC:00@3",
+ },
+ },
+ {
+ /*
+ * Spurious wakeups from TP_ATTN# pin
+ * Found in BIOS 0.35
+ * https://gitlab.freedesktop.org/drm/amd/-/issues/3073
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"),
+ },
+ .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
+ .ignore_wake = "PNP0C50:00@8",
+ },
+ },
+ {} /* Terminating entry */
+};
+
+static int __init acpi_gpio_setup_params(void)
+{
+ const struct acpi_gpiolib_dmi_quirk *quirk = NULL;
+ const struct dmi_system_id *id;
+
+ id = dmi_first_match(gpiolib_acpi_quirks);
+ if (id)
+ quirk = id->driver_data;
+
+ if (run_edge_events_on_boot < 0) {
+ if (quirk && quirk->no_edge_events_on_boot)
+ run_edge_events_on_boot = 0;
+ else
+ run_edge_events_on_boot = 1;
+ }
+
+ if (ignore_wake == NULL && quirk && quirk->ignore_wake)
+ ignore_wake = quirk->ignore_wake;
+
+ if (ignore_interrupt == NULL && quirk && quirk->ignore_interrupt)
+ ignore_interrupt = quirk->ignore_interrupt;
+
+ return 0;
+}
+
+/* Directly after dmi_setup() which runs as core_initcall() */
+postcore_initcall(acpi_gpio_setup_params);
diff --git a/drivers/gpio/gpiolib-acpi.h b/drivers/gpio/gpiolib-acpi.h
new file mode 100644
index 0000000000..0fcd7e14d7
--- /dev/null
+++ b/drivers/gpio/gpiolib-acpi.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ACPI helpers for GPIO API
+ *
+ * Copyright (C) 2012,2019 Intel Corporation
+ */
+
+#ifndef GPIOLIB_ACPI_H
+#define GPIOLIB_ACPI_H
+
+#include <linux/err.h>
+#include <linux/types.h>
+
+#include <linux/gpio/consumer.h>
+
+struct device;
+struct fwnode_handle;
+
+struct gpio_chip;
+struct gpio_desc;
+struct gpio_device;
+
+#ifdef CONFIG_ACPI
+void acpi_gpiochip_add(struct gpio_chip *chip);
+void acpi_gpiochip_remove(struct gpio_chip *chip);
+
+void acpi_gpiochip_request_interrupts(struct gpio_chip *chip);
+void acpi_gpiochip_free_interrupts(struct gpio_chip *chip);
+
+struct gpio_desc *acpi_find_gpio(struct fwnode_handle *fwnode,
+ const char *con_id,
+ unsigned int idx,
+ enum gpiod_flags *dflags,
+ unsigned long *lookupflags);
+
+int acpi_gpio_count(struct device *dev, const char *con_id);
+#else
+static inline void acpi_gpiochip_add(struct gpio_chip *chip) { }
+static inline void acpi_gpiochip_remove(struct gpio_chip *chip) { }
+
+static inline void
+acpi_gpiochip_request_interrupts(struct gpio_chip *chip) { }
+
+static inline void
+acpi_gpiochip_free_interrupts(struct gpio_chip *chip) { }
+
+static inline struct gpio_desc *
+acpi_find_gpio(struct fwnode_handle *fwnode, const char *con_id,
+ unsigned int idx, enum gpiod_flags *dflags,
+ unsigned long *lookupflags)
+{
+ return ERR_PTR(-ENOENT);
+}
+static inline int acpi_gpio_count(struct device *dev, const char *con_id)
+{
+ return -ENODEV;
+}
+#endif
+
+#endif /* GPIOLIB_ACPI_H */
diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
new file mode 100644
index 0000000000..4f3e66ece7
--- /dev/null
+++ b/drivers/gpio/gpiolib-cdev.c
@@ -0,0 +1,2823 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/anon_inodes.h>
+#include <linux/atomic.h>
+#include <linux/bitmap.h>
+#include <linux/build_bug.h>
+#include <linux/cdev.h>
+#include <linux/compat.h>
+#include <linux/compiler.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/file.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/hte.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/kernel.h>
+#include <linux/kfifo.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/poll.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/timekeeping.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+
+#include <uapi/linux/gpio.h>
+
+#include "gpiolib.h"
+#include "gpiolib-cdev.h"
+
+/*
+ * Array sizes must ensure 64-bit alignment and not create holes in the
+ * struct packing.
+ */
+static_assert(IS_ALIGNED(GPIO_V2_LINES_MAX, 2));
+static_assert(IS_ALIGNED(GPIO_MAX_NAME_SIZE, 8));
+
+/*
+ * Check that uAPI structs are 64-bit aligned for 32/64-bit compatibility
+ */
+static_assert(IS_ALIGNED(sizeof(struct gpio_v2_line_attribute), 8));
+static_assert(IS_ALIGNED(sizeof(struct gpio_v2_line_config_attribute), 8));
+static_assert(IS_ALIGNED(sizeof(struct gpio_v2_line_config), 8));
+static_assert(IS_ALIGNED(sizeof(struct gpio_v2_line_request), 8));
+static_assert(IS_ALIGNED(sizeof(struct gpio_v2_line_info), 8));
+static_assert(IS_ALIGNED(sizeof(struct gpio_v2_line_info_changed), 8));
+static_assert(IS_ALIGNED(sizeof(struct gpio_v2_line_event), 8));
+static_assert(IS_ALIGNED(sizeof(struct gpio_v2_line_values), 8));
+
+/* Character device interface to GPIO.
+ *
+ * The GPIO character device, /dev/gpiochipN, provides userspace an
+ * interface to gpiolib GPIOs via ioctl()s.
+ */
+
+typedef __poll_t (*poll_fn)(struct file *, struct poll_table_struct *);
+typedef long (*ioctl_fn)(struct file *, unsigned int, unsigned long);
+typedef ssize_t (*read_fn)(struct file *, char __user *,
+ size_t count, loff_t *);
+
+static __poll_t call_poll_locked(struct file *file,
+ struct poll_table_struct *wait,
+ struct gpio_device *gdev, poll_fn func)
+{
+ __poll_t ret;
+
+ down_read(&gdev->sem);
+ ret = func(file, wait);
+ up_read(&gdev->sem);
+
+ return ret;
+}
+
+static long call_ioctl_locked(struct file *file, unsigned int cmd,
+ unsigned long arg, struct gpio_device *gdev,
+ ioctl_fn func)
+{
+ long ret;
+
+ down_read(&gdev->sem);
+ ret = func(file, cmd, arg);
+ up_read(&gdev->sem);
+
+ return ret;
+}
+
+static ssize_t call_read_locked(struct file *file, char __user *buf,
+ size_t count, loff_t *f_ps,
+ struct gpio_device *gdev, read_fn func)
+{
+ ssize_t ret;
+
+ down_read(&gdev->sem);
+ ret = func(file, buf, count, f_ps);
+ up_read(&gdev->sem);
+
+ return ret;
+}
+
+/*
+ * GPIO line handle management
+ */
+
+#ifdef CONFIG_GPIO_CDEV_V1
+/**
+ * struct linehandle_state - contains the state of a userspace handle
+ * @gdev: the GPIO device the handle pertains to
+ * @label: consumer label used to tag descriptors
+ * @descs: the GPIO descriptors held by this handle
+ * @num_descs: the number of descriptors held in the descs array
+ */
+struct linehandle_state {
+ struct gpio_device *gdev;
+ const char *label;
+ struct gpio_desc *descs[GPIOHANDLES_MAX];
+ u32 num_descs;
+};
+
+#define GPIOHANDLE_REQUEST_VALID_FLAGS \
+ (GPIOHANDLE_REQUEST_INPUT | \
+ GPIOHANDLE_REQUEST_OUTPUT | \
+ GPIOHANDLE_REQUEST_ACTIVE_LOW | \
+ GPIOHANDLE_REQUEST_BIAS_PULL_UP | \
+ GPIOHANDLE_REQUEST_BIAS_PULL_DOWN | \
+ GPIOHANDLE_REQUEST_BIAS_DISABLE | \
+ GPIOHANDLE_REQUEST_OPEN_DRAIN | \
+ GPIOHANDLE_REQUEST_OPEN_SOURCE)
+
+static int linehandle_validate_flags(u32 flags)
+{
+ /* Return an error if an unknown flag is set */
+ if (flags & ~GPIOHANDLE_REQUEST_VALID_FLAGS)
+ return -EINVAL;
+
+ /*
+ * Do not allow both INPUT & OUTPUT flags to be set as they are
+ * contradictory.
+ */
+ if ((flags & GPIOHANDLE_REQUEST_INPUT) &&
+ (flags & GPIOHANDLE_REQUEST_OUTPUT))
+ return -EINVAL;
+
+ /*
+ * Do not allow OPEN_SOURCE & OPEN_DRAIN flags in a single request. If
+ * the hardware actually supports enabling both at the same time the
+ * electrical result would be disastrous.
+ */
+ if ((flags & GPIOHANDLE_REQUEST_OPEN_DRAIN) &&
+ (flags & GPIOHANDLE_REQUEST_OPEN_SOURCE))
+ return -EINVAL;
+
+ /* OPEN_DRAIN and OPEN_SOURCE flags only make sense for output mode. */
+ if (!(flags & GPIOHANDLE_REQUEST_OUTPUT) &&
+ ((flags & GPIOHANDLE_REQUEST_OPEN_DRAIN) ||
+ (flags & GPIOHANDLE_REQUEST_OPEN_SOURCE)))
+ return -EINVAL;
+
+ /* Bias flags only allowed for input or output mode. */
+ if (!((flags & GPIOHANDLE_REQUEST_INPUT) ||
+ (flags & GPIOHANDLE_REQUEST_OUTPUT)) &&
+ ((flags & GPIOHANDLE_REQUEST_BIAS_DISABLE) ||
+ (flags & GPIOHANDLE_REQUEST_BIAS_PULL_UP) ||
+ (flags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN)))
+ return -EINVAL;
+
+ /* Only one bias flag can be set. */
+ if (((flags & GPIOHANDLE_REQUEST_BIAS_DISABLE) &&
+ (flags & (GPIOHANDLE_REQUEST_BIAS_PULL_DOWN |
+ GPIOHANDLE_REQUEST_BIAS_PULL_UP))) ||
+ ((flags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN) &&
+ (flags & GPIOHANDLE_REQUEST_BIAS_PULL_UP)))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void linehandle_flags_to_desc_flags(u32 lflags, unsigned long *flagsp)
+{
+ assign_bit(FLAG_ACTIVE_LOW, flagsp,
+ lflags & GPIOHANDLE_REQUEST_ACTIVE_LOW);
+ assign_bit(FLAG_OPEN_DRAIN, flagsp,
+ lflags & GPIOHANDLE_REQUEST_OPEN_DRAIN);
+ assign_bit(FLAG_OPEN_SOURCE, flagsp,
+ lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE);
+ assign_bit(FLAG_PULL_UP, flagsp,
+ lflags & GPIOHANDLE_REQUEST_BIAS_PULL_UP);
+ assign_bit(FLAG_PULL_DOWN, flagsp,
+ lflags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN);
+ assign_bit(FLAG_BIAS_DISABLE, flagsp,
+ lflags & GPIOHANDLE_REQUEST_BIAS_DISABLE);
+}
+
+static long linehandle_set_config(struct linehandle_state *lh,
+ void __user *ip)
+{
+ struct gpiohandle_config gcnf;
+ struct gpio_desc *desc;
+ int i, ret;
+ u32 lflags;
+
+ if (copy_from_user(&gcnf, ip, sizeof(gcnf)))
+ return -EFAULT;
+
+ lflags = gcnf.flags;
+ ret = linehandle_validate_flags(lflags);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < lh->num_descs; i++) {
+ desc = lh->descs[i];
+ linehandle_flags_to_desc_flags(gcnf.flags, &desc->flags);
+
+ /*
+ * Lines have to be requested explicitly for input
+ * or output, else the line will be treated "as is".
+ */
+ if (lflags & GPIOHANDLE_REQUEST_OUTPUT) {
+ int val = !!gcnf.default_values[i];
+
+ ret = gpiod_direction_output(desc, val);
+ if (ret)
+ return ret;
+ } else if (lflags & GPIOHANDLE_REQUEST_INPUT) {
+ ret = gpiod_direction_input(desc);
+ if (ret)
+ return ret;
+ }
+
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
+ }
+ return 0;
+}
+
+static long linehandle_ioctl_unlocked(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct linehandle_state *lh = file->private_data;
+ void __user *ip = (void __user *)arg;
+ struct gpiohandle_data ghd;
+ DECLARE_BITMAP(vals, GPIOHANDLES_MAX);
+ unsigned int i;
+ int ret;
+
+ if (!lh->gdev->chip)
+ return -ENODEV;
+
+ switch (cmd) {
+ case GPIOHANDLE_GET_LINE_VALUES_IOCTL:
+ /* NOTE: It's okay to read values of output lines */
+ ret = gpiod_get_array_value_complex(false, true,
+ lh->num_descs, lh->descs,
+ NULL, vals);
+ if (ret)
+ return ret;
+
+ memset(&ghd, 0, sizeof(ghd));
+ for (i = 0; i < lh->num_descs; i++)
+ ghd.values[i] = test_bit(i, vals);
+
+ if (copy_to_user(ip, &ghd, sizeof(ghd)))
+ return -EFAULT;
+
+ return 0;
+ case GPIOHANDLE_SET_LINE_VALUES_IOCTL:
+ /*
+ * All line descriptors were created at once with the same
+ * flags so just check if the first one is really output.
+ */
+ if (!test_bit(FLAG_IS_OUT, &lh->descs[0]->flags))
+ return -EPERM;
+
+ if (copy_from_user(&ghd, ip, sizeof(ghd)))
+ return -EFAULT;
+
+ /* Clamp all values to [0,1] */
+ for (i = 0; i < lh->num_descs; i++)
+ __assign_bit(i, vals, ghd.values[i]);
+
+ /* Reuse the array setting function */
+ return gpiod_set_array_value_complex(false,
+ true,
+ lh->num_descs,
+ lh->descs,
+ NULL,
+ vals);
+ case GPIOHANDLE_SET_CONFIG_IOCTL:
+ return linehandle_set_config(lh, ip);
+ default:
+ return -EINVAL;
+ }
+}
+
+static long linehandle_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct linehandle_state *lh = file->private_data;
+
+ return call_ioctl_locked(file, cmd, arg, lh->gdev,
+ linehandle_ioctl_unlocked);
+}
+
+#ifdef CONFIG_COMPAT
+static long linehandle_ioctl_compat(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ return linehandle_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+static void linehandle_free(struct linehandle_state *lh)
+{
+ int i;
+
+ for (i = 0; i < lh->num_descs; i++)
+ if (lh->descs[i])
+ gpiod_free(lh->descs[i]);
+ kfree(lh->label);
+ gpio_device_put(lh->gdev);
+ kfree(lh);
+}
+
+static int linehandle_release(struct inode *inode, struct file *file)
+{
+ linehandle_free(file->private_data);
+ return 0;
+}
+
+static const struct file_operations linehandle_fileops = {
+ .release = linehandle_release,
+ .owner = THIS_MODULE,
+ .llseek = noop_llseek,
+ .unlocked_ioctl = linehandle_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = linehandle_ioctl_compat,
+#endif
+};
+
+static int linehandle_create(struct gpio_device *gdev, void __user *ip)
+{
+ struct gpiohandle_request handlereq;
+ struct linehandle_state *lh;
+ struct file *file;
+ int fd, i, ret;
+ u32 lflags;
+
+ if (copy_from_user(&handlereq, ip, sizeof(handlereq)))
+ return -EFAULT;
+ if ((handlereq.lines == 0) || (handlereq.lines > GPIOHANDLES_MAX))
+ return -EINVAL;
+
+ lflags = handlereq.flags;
+
+ ret = linehandle_validate_flags(lflags);
+ if (ret)
+ return ret;
+
+ lh = kzalloc(sizeof(*lh), GFP_KERNEL);
+ if (!lh)
+ return -ENOMEM;
+ lh->gdev = gpio_device_get(gdev);
+
+ if (handlereq.consumer_label[0] != '\0') {
+ /* label is only initialized if consumer_label is set */
+ lh->label = kstrndup(handlereq.consumer_label,
+ sizeof(handlereq.consumer_label) - 1,
+ GFP_KERNEL);
+ if (!lh->label) {
+ ret = -ENOMEM;
+ goto out_free_lh;
+ }
+ }
+
+ lh->num_descs = handlereq.lines;
+
+ /* Request each GPIO */
+ for (i = 0; i < handlereq.lines; i++) {
+ u32 offset = handlereq.lineoffsets[i];
+ struct gpio_desc *desc = gpiochip_get_desc(gdev->chip, offset);
+
+ if (IS_ERR(desc)) {
+ ret = PTR_ERR(desc);
+ goto out_free_lh;
+ }
+
+ ret = gpiod_request_user(desc, lh->label);
+ if (ret)
+ goto out_free_lh;
+ lh->descs[i] = desc;
+ linehandle_flags_to_desc_flags(handlereq.flags, &desc->flags);
+
+ ret = gpiod_set_transitory(desc, false);
+ if (ret < 0)
+ goto out_free_lh;
+
+ /*
+ * Lines have to be requested explicitly for input
+ * or output, else the line will be treated "as is".
+ */
+ if (lflags & GPIOHANDLE_REQUEST_OUTPUT) {
+ int val = !!handlereq.default_values[i];
+
+ ret = gpiod_direction_output(desc, val);
+ if (ret)
+ goto out_free_lh;
+ } else if (lflags & GPIOHANDLE_REQUEST_INPUT) {
+ ret = gpiod_direction_input(desc);
+ if (ret)
+ goto out_free_lh;
+ }
+
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED);
+
+ dev_dbg(&gdev->dev, "registered chardev handle for line %d\n",
+ offset);
+ }
+
+ fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ ret = fd;
+ goto out_free_lh;
+ }
+
+ file = anon_inode_getfile("gpio-linehandle",
+ &linehandle_fileops,
+ lh,
+ O_RDONLY | O_CLOEXEC);
+ if (IS_ERR(file)) {
+ ret = PTR_ERR(file);
+ goto out_put_unused_fd;
+ }
+
+ handlereq.fd = fd;
+ if (copy_to_user(ip, &handlereq, sizeof(handlereq))) {
+ /*
+ * fput() will trigger the release() callback, so do not go onto
+ * the regular error cleanup path here.
+ */
+ fput(file);
+ put_unused_fd(fd);
+ return -EFAULT;
+ }
+
+ fd_install(fd, file);
+
+ dev_dbg(&gdev->dev, "registered chardev handle for %d lines\n",
+ lh->num_descs);
+
+ return 0;
+
+out_put_unused_fd:
+ put_unused_fd(fd);
+out_free_lh:
+ linehandle_free(lh);
+ return ret;
+}
+#endif /* CONFIG_GPIO_CDEV_V1 */
+
+/**
+ * struct line - contains the state of a requested line
+ * @desc: the GPIO descriptor for this line.
+ * @req: the corresponding line request
+ * @irq: the interrupt triggered in response to events on this GPIO
+ * @edflags: the edge flags, GPIO_V2_LINE_FLAG_EDGE_RISING and/or
+ * GPIO_V2_LINE_FLAG_EDGE_FALLING, indicating the edge detection applied
+ * @timestamp_ns: cache for the timestamp storing it between hardirq and
+ * IRQ thread, used to bring the timestamp close to the actual event
+ * @req_seqno: the seqno for the current edge event in the sequence of
+ * events for the corresponding line request. This is drawn from the @req.
+ * @line_seqno: the seqno for the current edge event in the sequence of
+ * events for this line.
+ * @work: the worker that implements software debouncing
+ * @sw_debounced: flag indicating if the software debouncer is active
+ * @level: the current debounced physical level of the line
+ * @hdesc: the Hardware Timestamp Engine (HTE) descriptor
+ * @raw_level: the line level at the time of event
+ * @total_discard_seq: the running counter of the discarded events
+ * @last_seqno: the last sequence number before debounce period expires
+ */
+struct line {
+ struct gpio_desc *desc;
+ /*
+ * -- edge detector specific fields --
+ */
+ struct linereq *req;
+ unsigned int irq;
+ /*
+ * The flags for the active edge detector configuration.
+ *
+ * edflags is set by linereq_create(), linereq_free(), and
+ * linereq_set_config_unlocked(), which are themselves mutually
+ * exclusive, and is accessed by edge_irq_thread(),
+ * process_hw_ts_thread() and debounce_work_func(),
+ * which can all live with a slightly stale value.
+ */
+ u64 edflags;
+ /*
+ * timestamp_ns and req_seqno are accessed only by
+ * edge_irq_handler() and edge_irq_thread(), which are themselves
+ * mutually exclusive, so no additional protection is necessary.
+ */
+ u64 timestamp_ns;
+ u32 req_seqno;
+ /*
+ * line_seqno is accessed by either edge_irq_thread() or
+ * debounce_work_func(), which are themselves mutually exclusive,
+ * so no additional protection is necessary.
+ */
+ u32 line_seqno;
+ /*
+ * -- debouncer specific fields --
+ */
+ struct delayed_work work;
+ /*
+ * sw_debounce is accessed by linereq_set_config(), which is the
+ * only setter, and linereq_get_values(), which can live with a
+ * slightly stale value.
+ */
+ unsigned int sw_debounced;
+ /*
+ * level is accessed by debounce_work_func(), which is the only
+ * setter, and linereq_get_values() which can live with a slightly
+ * stale value.
+ */
+ unsigned int level;
+#ifdef CONFIG_HTE
+ struct hte_ts_desc hdesc;
+ /*
+ * HTE provider sets line level at the time of event. The valid
+ * value is 0 or 1 and negative value for an error.
+ */
+ int raw_level;
+ /*
+ * when sw_debounce is set on HTE enabled line, this is running
+ * counter of the discarded events.
+ */
+ u32 total_discard_seq;
+ /*
+ * when sw_debounce is set on HTE enabled line, this variable records
+ * last sequence number before debounce period expires.
+ */
+ u32 last_seqno;
+#endif /* CONFIG_HTE */
+};
+
+/**
+ * struct linereq - contains the state of a userspace line request
+ * @gdev: the GPIO device the line request pertains to
+ * @label: consumer label used to tag GPIO descriptors
+ * @num_lines: the number of lines in the lines array
+ * @wait: wait queue that handles blocking reads of events
+ * @device_unregistered_nb: notifier block for receiving gdev unregister events
+ * @event_buffer_size: the number of elements allocated in @events
+ * @events: KFIFO for the GPIO events
+ * @seqno: the sequence number for edge events generated on all lines in
+ * this line request. Note that this is not used when @num_lines is 1, as
+ * the line_seqno is then the same and is cheaper to calculate.
+ * @config_mutex: mutex for serializing ioctl() calls to ensure consistency
+ * of configuration, particularly multi-step accesses to desc flags.
+ * @lines: the lines held by this line request, with @num_lines elements.
+ */
+struct linereq {
+ struct gpio_device *gdev;
+ const char *label;
+ u32 num_lines;
+ wait_queue_head_t wait;
+ struct notifier_block device_unregistered_nb;
+ u32 event_buffer_size;
+ DECLARE_KFIFO_PTR(events, struct gpio_v2_line_event);
+ atomic_t seqno;
+ struct mutex config_mutex;
+ struct line lines[];
+};
+
+#define GPIO_V2_LINE_BIAS_FLAGS \
+ (GPIO_V2_LINE_FLAG_BIAS_PULL_UP | \
+ GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN | \
+ GPIO_V2_LINE_FLAG_BIAS_DISABLED)
+
+#define GPIO_V2_LINE_DIRECTION_FLAGS \
+ (GPIO_V2_LINE_FLAG_INPUT | \
+ GPIO_V2_LINE_FLAG_OUTPUT)
+
+#define GPIO_V2_LINE_DRIVE_FLAGS \
+ (GPIO_V2_LINE_FLAG_OPEN_DRAIN | \
+ GPIO_V2_LINE_FLAG_OPEN_SOURCE)
+
+#define GPIO_V2_LINE_EDGE_FLAGS \
+ (GPIO_V2_LINE_FLAG_EDGE_RISING | \
+ GPIO_V2_LINE_FLAG_EDGE_FALLING)
+
+#define GPIO_V2_LINE_FLAG_EDGE_BOTH GPIO_V2_LINE_EDGE_FLAGS
+
+#define GPIO_V2_LINE_VALID_FLAGS \
+ (GPIO_V2_LINE_FLAG_ACTIVE_LOW | \
+ GPIO_V2_LINE_DIRECTION_FLAGS | \
+ GPIO_V2_LINE_DRIVE_FLAGS | \
+ GPIO_V2_LINE_EDGE_FLAGS | \
+ GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME | \
+ GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE | \
+ GPIO_V2_LINE_BIAS_FLAGS)
+
+/* subset of flags relevant for edge detector configuration */
+#define GPIO_V2_LINE_EDGE_DETECTOR_FLAGS \
+ (GPIO_V2_LINE_FLAG_ACTIVE_LOW | \
+ GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE | \
+ GPIO_V2_LINE_EDGE_FLAGS)
+
+static int linereq_unregistered_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct linereq *lr = container_of(nb, struct linereq,
+ device_unregistered_nb);
+
+ wake_up_poll(&lr->wait, EPOLLIN | EPOLLERR);
+
+ return NOTIFY_OK;
+}
+
+static void linereq_put_event(struct linereq *lr,
+ struct gpio_v2_line_event *le)
+{
+ bool overflow = false;
+
+ spin_lock(&lr->wait.lock);
+ if (kfifo_is_full(&lr->events)) {
+ overflow = true;
+ kfifo_skip(&lr->events);
+ }
+ kfifo_in(&lr->events, le, 1);
+ spin_unlock(&lr->wait.lock);
+ if (!overflow)
+ wake_up_poll(&lr->wait, EPOLLIN);
+ else
+ pr_debug_ratelimited("event FIFO is full - event dropped\n");
+}
+
+static u64 line_event_timestamp(struct line *line)
+{
+ if (test_bit(FLAG_EVENT_CLOCK_REALTIME, &line->desc->flags))
+ return ktime_get_real_ns();
+ else if (IS_ENABLED(CONFIG_HTE) &&
+ test_bit(FLAG_EVENT_CLOCK_HTE, &line->desc->flags))
+ return line->timestamp_ns;
+
+ return ktime_get_ns();
+}
+
+static u32 line_event_id(int level)
+{
+ return level ? GPIO_V2_LINE_EVENT_RISING_EDGE :
+ GPIO_V2_LINE_EVENT_FALLING_EDGE;
+}
+
+#ifdef CONFIG_HTE
+
+static enum hte_return process_hw_ts_thread(void *p)
+{
+ struct line *line;
+ struct linereq *lr;
+ struct gpio_v2_line_event le;
+ u64 edflags;
+ int level;
+
+ if (!p)
+ return HTE_CB_HANDLED;
+
+ line = p;
+ lr = line->req;
+
+ memset(&le, 0, sizeof(le));
+
+ le.timestamp_ns = line->timestamp_ns;
+ edflags = READ_ONCE(line->edflags);
+
+ switch (edflags & GPIO_V2_LINE_EDGE_FLAGS) {
+ case GPIO_V2_LINE_FLAG_EDGE_BOTH:
+ level = (line->raw_level >= 0) ?
+ line->raw_level :
+ gpiod_get_raw_value_cansleep(line->desc);
+
+ if (edflags & GPIO_V2_LINE_FLAG_ACTIVE_LOW)
+ level = !level;
+
+ le.id = line_event_id(level);
+ break;
+ case GPIO_V2_LINE_FLAG_EDGE_RISING:
+ le.id = GPIO_V2_LINE_EVENT_RISING_EDGE;
+ break;
+ case GPIO_V2_LINE_FLAG_EDGE_FALLING:
+ le.id = GPIO_V2_LINE_EVENT_FALLING_EDGE;
+ break;
+ default:
+ return HTE_CB_HANDLED;
+ }
+ le.line_seqno = line->line_seqno;
+ le.seqno = (lr->num_lines == 1) ? le.line_seqno : line->req_seqno;
+ le.offset = gpio_chip_hwgpio(line->desc);
+
+ linereq_put_event(lr, &le);
+
+ return HTE_CB_HANDLED;
+}
+
+static enum hte_return process_hw_ts(struct hte_ts_data *ts, void *p)
+{
+ struct line *line;
+ struct linereq *lr;
+ int diff_seqno = 0;
+
+ if (!ts || !p)
+ return HTE_CB_HANDLED;
+
+ line = p;
+ line->timestamp_ns = ts->tsc;
+ line->raw_level = ts->raw_level;
+ lr = line->req;
+
+ if (READ_ONCE(line->sw_debounced)) {
+ line->total_discard_seq++;
+ line->last_seqno = ts->seq;
+ mod_delayed_work(system_wq, &line->work,
+ usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us)));
+ } else {
+ if (unlikely(ts->seq < line->line_seqno))
+ return HTE_CB_HANDLED;
+
+ diff_seqno = ts->seq - line->line_seqno;
+ line->line_seqno = ts->seq;
+ if (lr->num_lines != 1)
+ line->req_seqno = atomic_add_return(diff_seqno,
+ &lr->seqno);
+
+ return HTE_RUN_SECOND_CB;
+ }
+
+ return HTE_CB_HANDLED;
+}
+
+static int hte_edge_setup(struct line *line, u64 eflags)
+{
+ int ret;
+ unsigned long flags = 0;
+ struct hte_ts_desc *hdesc = &line->hdesc;
+
+ if (eflags & GPIO_V2_LINE_FLAG_EDGE_RISING)
+ flags |= test_bit(FLAG_ACTIVE_LOW, &line->desc->flags) ?
+ HTE_FALLING_EDGE_TS :
+ HTE_RISING_EDGE_TS;
+ if (eflags & GPIO_V2_LINE_FLAG_EDGE_FALLING)
+ flags |= test_bit(FLAG_ACTIVE_LOW, &line->desc->flags) ?
+ HTE_RISING_EDGE_TS :
+ HTE_FALLING_EDGE_TS;
+
+ line->total_discard_seq = 0;
+
+ hte_init_line_attr(hdesc, desc_to_gpio(line->desc), flags, NULL,
+ line->desc);
+
+ ret = hte_ts_get(NULL, hdesc, 0);
+ if (ret)
+ return ret;
+
+ return hte_request_ts_ns(hdesc, process_hw_ts, process_hw_ts_thread,
+ line);
+}
+
+#else
+
+static int hte_edge_setup(struct line *line, u64 eflags)
+{
+ return 0;
+}
+#endif /* CONFIG_HTE */
+
+static irqreturn_t edge_irq_thread(int irq, void *p)
+{
+ struct line *line = p;
+ struct linereq *lr = line->req;
+ struct gpio_v2_line_event le;
+
+ /* Do not leak kernel stack to userspace */
+ memset(&le, 0, sizeof(le));
+
+ if (line->timestamp_ns) {
+ le.timestamp_ns = line->timestamp_ns;
+ } else {
+ /*
+ * We may be running from a nested threaded interrupt in
+ * which case we didn't get the timestamp from
+ * edge_irq_handler().
+ */
+ le.timestamp_ns = line_event_timestamp(line);
+ if (lr->num_lines != 1)
+ line->req_seqno = atomic_inc_return(&lr->seqno);
+ }
+ line->timestamp_ns = 0;
+
+ switch (READ_ONCE(line->edflags) & GPIO_V2_LINE_EDGE_FLAGS) {
+ case GPIO_V2_LINE_FLAG_EDGE_BOTH:
+ le.id = line_event_id(gpiod_get_value_cansleep(line->desc));
+ break;
+ case GPIO_V2_LINE_FLAG_EDGE_RISING:
+ le.id = GPIO_V2_LINE_EVENT_RISING_EDGE;
+ break;
+ case GPIO_V2_LINE_FLAG_EDGE_FALLING:
+ le.id = GPIO_V2_LINE_EVENT_FALLING_EDGE;
+ break;
+ default:
+ return IRQ_NONE;
+ }
+ line->line_seqno++;
+ le.line_seqno = line->line_seqno;
+ le.seqno = (lr->num_lines == 1) ? le.line_seqno : line->req_seqno;
+ le.offset = gpio_chip_hwgpio(line->desc);
+
+ linereq_put_event(lr, &le);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t edge_irq_handler(int irq, void *p)
+{
+ struct line *line = p;
+ struct linereq *lr = line->req;
+
+ /*
+ * Just store the timestamp in hardirq context so we get it as
+ * close in time as possible to the actual event.
+ */
+ line->timestamp_ns = line_event_timestamp(line);
+
+ if (lr->num_lines != 1)
+ line->req_seqno = atomic_inc_return(&lr->seqno);
+
+ return IRQ_WAKE_THREAD;
+}
+
+/*
+ * returns the current debounced logical value.
+ */
+static bool debounced_value(struct line *line)
+{
+ bool value;
+
+ /*
+ * minor race - debouncer may be stopped here, so edge_detector_stop()
+ * must leave the value unchanged so the following will read the level
+ * from when the debouncer was last running.
+ */
+ value = READ_ONCE(line->level);
+
+ if (test_bit(FLAG_ACTIVE_LOW, &line->desc->flags))
+ value = !value;
+
+ return value;
+}
+
+static irqreturn_t debounce_irq_handler(int irq, void *p)
+{
+ struct line *line = p;
+
+ mod_delayed_work(system_wq, &line->work,
+ usecs_to_jiffies(READ_ONCE(line->desc->debounce_period_us)));
+
+ return IRQ_HANDLED;
+}
+
+static void debounce_work_func(struct work_struct *work)
+{
+ struct gpio_v2_line_event le;
+ struct line *line = container_of(work, struct line, work.work);
+ struct linereq *lr;
+ u64 eflags, edflags = READ_ONCE(line->edflags);
+ int level = -1;
+#ifdef CONFIG_HTE
+ int diff_seqno;
+
+ if (edflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE)
+ level = line->raw_level;
+#endif
+ if (level < 0)
+ level = gpiod_get_raw_value_cansleep(line->desc);
+ if (level < 0) {
+ pr_debug_ratelimited("debouncer failed to read line value\n");
+ return;
+ }
+
+ if (READ_ONCE(line->level) == level)
+ return;
+
+ WRITE_ONCE(line->level, level);
+
+ /* -- edge detection -- */
+ eflags = edflags & GPIO_V2_LINE_EDGE_FLAGS;
+ if (!eflags)
+ return;
+
+ /* switch from physical level to logical - if they differ */
+ if (edflags & GPIO_V2_LINE_FLAG_ACTIVE_LOW)
+ level = !level;
+
+ /* ignore edges that are not being monitored */
+ if (((eflags == GPIO_V2_LINE_FLAG_EDGE_RISING) && !level) ||
+ ((eflags == GPIO_V2_LINE_FLAG_EDGE_FALLING) && level))
+ return;
+
+ /* Do not leak kernel stack to userspace */
+ memset(&le, 0, sizeof(le));
+
+ lr = line->req;
+ le.timestamp_ns = line_event_timestamp(line);
+ le.offset = gpio_chip_hwgpio(line->desc);
+#ifdef CONFIG_HTE
+ if (edflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE) {
+ /* discard events except the last one */
+ line->total_discard_seq -= 1;
+ diff_seqno = line->last_seqno - line->total_discard_seq -
+ line->line_seqno;
+ line->line_seqno = line->last_seqno - line->total_discard_seq;
+ le.line_seqno = line->line_seqno;
+ le.seqno = (lr->num_lines == 1) ?
+ le.line_seqno : atomic_add_return(diff_seqno, &lr->seqno);
+ } else
+#endif /* CONFIG_HTE */
+ {
+ line->line_seqno++;
+ le.line_seqno = line->line_seqno;
+ le.seqno = (lr->num_lines == 1) ?
+ le.line_seqno : atomic_inc_return(&lr->seqno);
+ }
+
+ le.id = line_event_id(level);
+
+ linereq_put_event(lr, &le);
+}
+
+static int debounce_setup(struct line *line, unsigned int debounce_period_us)
+{
+ unsigned long irqflags;
+ int ret, level, irq;
+
+ /* try hardware */
+ ret = gpiod_set_debounce(line->desc, debounce_period_us);
+ if (!ret) {
+ WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us);
+ return ret;
+ }
+ if (ret != -ENOTSUPP)
+ return ret;
+
+ if (debounce_period_us) {
+ /* setup software debounce */
+ level = gpiod_get_raw_value_cansleep(line->desc);
+ if (level < 0)
+ return level;
+
+ if (!(IS_ENABLED(CONFIG_HTE) &&
+ test_bit(FLAG_EVENT_CLOCK_HTE, &line->desc->flags))) {
+ irq = gpiod_to_irq(line->desc);
+ if (irq < 0)
+ return -ENXIO;
+
+ irqflags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
+ ret = request_irq(irq, debounce_irq_handler, irqflags,
+ line->req->label, line);
+ if (ret)
+ return ret;
+ line->irq = irq;
+ } else {
+ ret = hte_edge_setup(line, GPIO_V2_LINE_FLAG_EDGE_BOTH);
+ if (ret)
+ return ret;
+ }
+
+ WRITE_ONCE(line->level, level);
+ WRITE_ONCE(line->sw_debounced, 1);
+ }
+ return 0;
+}
+
+static bool gpio_v2_line_config_debounced(struct gpio_v2_line_config *lc,
+ unsigned int line_idx)
+{
+ unsigned int i;
+ u64 mask = BIT_ULL(line_idx);
+
+ for (i = 0; i < lc->num_attrs; i++) {
+ if ((lc->attrs[i].attr.id == GPIO_V2_LINE_ATTR_ID_DEBOUNCE) &&
+ (lc->attrs[i].mask & mask))
+ return true;
+ }
+ return false;
+}
+
+static u32 gpio_v2_line_config_debounce_period(struct gpio_v2_line_config *lc,
+ unsigned int line_idx)
+{
+ unsigned int i;
+ u64 mask = BIT_ULL(line_idx);
+
+ for (i = 0; i < lc->num_attrs; i++) {
+ if ((lc->attrs[i].attr.id == GPIO_V2_LINE_ATTR_ID_DEBOUNCE) &&
+ (lc->attrs[i].mask & mask))
+ return lc->attrs[i].attr.debounce_period_us;
+ }
+ return 0;
+}
+
+static void edge_detector_stop(struct line *line)
+{
+ if (line->irq) {
+ free_irq(line->irq, line);
+ line->irq = 0;
+ }
+
+#ifdef CONFIG_HTE
+ if (READ_ONCE(line->edflags) & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE)
+ hte_ts_put(&line->hdesc);
+#endif
+
+ cancel_delayed_work_sync(&line->work);
+ WRITE_ONCE(line->sw_debounced, 0);
+ WRITE_ONCE(line->edflags, 0);
+ if (line->desc)
+ WRITE_ONCE(line->desc->debounce_period_us, 0);
+ /* do not change line->level - see comment in debounced_value() */
+}
+
+static int edge_detector_setup(struct line *line,
+ struct gpio_v2_line_config *lc,
+ unsigned int line_idx, u64 edflags)
+{
+ u32 debounce_period_us;
+ unsigned long irqflags = 0;
+ u64 eflags;
+ int irq, ret;
+
+ eflags = edflags & GPIO_V2_LINE_EDGE_FLAGS;
+ if (eflags && !kfifo_initialized(&line->req->events)) {
+ ret = kfifo_alloc(&line->req->events,
+ line->req->event_buffer_size, GFP_KERNEL);
+ if (ret)
+ return ret;
+ }
+ if (gpio_v2_line_config_debounced(lc, line_idx)) {
+ debounce_period_us = gpio_v2_line_config_debounce_period(lc, line_idx);
+ ret = debounce_setup(line, debounce_period_us);
+ if (ret)
+ return ret;
+ WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us);
+ }
+
+ /* detection disabled or sw debouncer will provide edge detection */
+ if (!eflags || READ_ONCE(line->sw_debounced))
+ return 0;
+
+ if (IS_ENABLED(CONFIG_HTE) &&
+ (edflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE))
+ return hte_edge_setup(line, edflags);
+
+ irq = gpiod_to_irq(line->desc);
+ if (irq < 0)
+ return -ENXIO;
+
+ if (eflags & GPIO_V2_LINE_FLAG_EDGE_RISING)
+ irqflags |= test_bit(FLAG_ACTIVE_LOW, &line->desc->flags) ?
+ IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
+ if (eflags & GPIO_V2_LINE_FLAG_EDGE_FALLING)
+ irqflags |= test_bit(FLAG_ACTIVE_LOW, &line->desc->flags) ?
+ IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING;
+ irqflags |= IRQF_ONESHOT;
+
+ /* Request a thread to read the events */
+ ret = request_threaded_irq(irq, edge_irq_handler, edge_irq_thread,
+ irqflags, line->req->label, line);
+ if (ret)
+ return ret;
+
+ line->irq = irq;
+ return 0;
+}
+
+static int edge_detector_update(struct line *line,
+ struct gpio_v2_line_config *lc,
+ unsigned int line_idx, u64 edflags)
+{
+ u64 active_edflags = READ_ONCE(line->edflags);
+ unsigned int debounce_period_us =
+ gpio_v2_line_config_debounce_period(lc, line_idx);
+
+ if ((active_edflags == edflags) &&
+ (READ_ONCE(line->desc->debounce_period_us) == debounce_period_us))
+ return 0;
+
+ /* sw debounced and still will be...*/
+ if (debounce_period_us && READ_ONCE(line->sw_debounced)) {
+ WRITE_ONCE(line->desc->debounce_period_us, debounce_period_us);
+ return 0;
+ }
+
+ /* reconfiguring edge detection or sw debounce being disabled */
+ if ((line->irq && !READ_ONCE(line->sw_debounced)) ||
+ (active_edflags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE) ||
+ (!debounce_period_us && READ_ONCE(line->sw_debounced)))
+ edge_detector_stop(line);
+
+ return edge_detector_setup(line, lc, line_idx, edflags);
+}
+
+static u64 gpio_v2_line_config_flags(struct gpio_v2_line_config *lc,
+ unsigned int line_idx)
+{
+ unsigned int i;
+ u64 mask = BIT_ULL(line_idx);
+
+ for (i = 0; i < lc->num_attrs; i++) {
+ if ((lc->attrs[i].attr.id == GPIO_V2_LINE_ATTR_ID_FLAGS) &&
+ (lc->attrs[i].mask & mask))
+ return lc->attrs[i].attr.flags;
+ }
+ return lc->flags;
+}
+
+static int gpio_v2_line_config_output_value(struct gpio_v2_line_config *lc,
+ unsigned int line_idx)
+{
+ unsigned int i;
+ u64 mask = BIT_ULL(line_idx);
+
+ for (i = 0; i < lc->num_attrs; i++) {
+ if ((lc->attrs[i].attr.id == GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES) &&
+ (lc->attrs[i].mask & mask))
+ return !!(lc->attrs[i].attr.values & mask);
+ }
+ return 0;
+}
+
+static int gpio_v2_line_flags_validate(u64 flags)
+{
+ /* Return an error if an unknown flag is set */
+ if (flags & ~GPIO_V2_LINE_VALID_FLAGS)
+ return -EINVAL;
+
+ if (!IS_ENABLED(CONFIG_HTE) &&
+ (flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE))
+ return -EOPNOTSUPP;
+
+ /*
+ * Do not allow both INPUT and OUTPUT flags to be set as they are
+ * contradictory.
+ */
+ if ((flags & GPIO_V2_LINE_FLAG_INPUT) &&
+ (flags & GPIO_V2_LINE_FLAG_OUTPUT))
+ return -EINVAL;
+
+ /* Only allow one event clock source */
+ if (IS_ENABLED(CONFIG_HTE) &&
+ (flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME) &&
+ (flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE))
+ return -EINVAL;
+
+ /* Edge detection requires explicit input. */
+ if ((flags & GPIO_V2_LINE_EDGE_FLAGS) &&
+ !(flags & GPIO_V2_LINE_FLAG_INPUT))
+ return -EINVAL;
+
+ /*
+ * Do not allow OPEN_SOURCE and OPEN_DRAIN flags in a single
+ * request. If the hardware actually supports enabling both at the
+ * same time the electrical result would be disastrous.
+ */
+ if ((flags & GPIO_V2_LINE_FLAG_OPEN_DRAIN) &&
+ (flags & GPIO_V2_LINE_FLAG_OPEN_SOURCE))
+ return -EINVAL;
+
+ /* Drive requires explicit output direction. */
+ if ((flags & GPIO_V2_LINE_DRIVE_FLAGS) &&
+ !(flags & GPIO_V2_LINE_FLAG_OUTPUT))
+ return -EINVAL;
+
+ /* Bias requires explicit direction. */
+ if ((flags & GPIO_V2_LINE_BIAS_FLAGS) &&
+ !(flags & GPIO_V2_LINE_DIRECTION_FLAGS))
+ return -EINVAL;
+
+ /* Only one bias flag can be set. */
+ if (((flags & GPIO_V2_LINE_FLAG_BIAS_DISABLED) &&
+ (flags & (GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN |
+ GPIO_V2_LINE_FLAG_BIAS_PULL_UP))) ||
+ ((flags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN) &&
+ (flags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP)))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int gpio_v2_line_config_validate(struct gpio_v2_line_config *lc,
+ unsigned int num_lines)
+{
+ unsigned int i;
+ u64 flags;
+ int ret;
+
+ if (lc->num_attrs > GPIO_V2_LINE_NUM_ATTRS_MAX)
+ return -EINVAL;
+
+ if (memchr_inv(lc->padding, 0, sizeof(lc->padding)))
+ return -EINVAL;
+
+ for (i = 0; i < num_lines; i++) {
+ flags = gpio_v2_line_config_flags(lc, i);
+ ret = gpio_v2_line_flags_validate(flags);
+ if (ret)
+ return ret;
+
+ /* debounce requires explicit input */
+ if (gpio_v2_line_config_debounced(lc, i) &&
+ !(flags & GPIO_V2_LINE_FLAG_INPUT))
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void gpio_v2_line_config_flags_to_desc_flags(u64 flags,
+ unsigned long *flagsp)
+{
+ assign_bit(FLAG_ACTIVE_LOW, flagsp,
+ flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW);
+
+ if (flags & GPIO_V2_LINE_FLAG_OUTPUT)
+ set_bit(FLAG_IS_OUT, flagsp);
+ else if (flags & GPIO_V2_LINE_FLAG_INPUT)
+ clear_bit(FLAG_IS_OUT, flagsp);
+
+ assign_bit(FLAG_EDGE_RISING, flagsp,
+ flags & GPIO_V2_LINE_FLAG_EDGE_RISING);
+ assign_bit(FLAG_EDGE_FALLING, flagsp,
+ flags & GPIO_V2_LINE_FLAG_EDGE_FALLING);
+
+ assign_bit(FLAG_OPEN_DRAIN, flagsp,
+ flags & GPIO_V2_LINE_FLAG_OPEN_DRAIN);
+ assign_bit(FLAG_OPEN_SOURCE, flagsp,
+ flags & GPIO_V2_LINE_FLAG_OPEN_SOURCE);
+
+ assign_bit(FLAG_PULL_UP, flagsp,
+ flags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP);
+ assign_bit(FLAG_PULL_DOWN, flagsp,
+ flags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN);
+ assign_bit(FLAG_BIAS_DISABLE, flagsp,
+ flags & GPIO_V2_LINE_FLAG_BIAS_DISABLED);
+
+ assign_bit(FLAG_EVENT_CLOCK_REALTIME, flagsp,
+ flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME);
+ assign_bit(FLAG_EVENT_CLOCK_HTE, flagsp,
+ flags & GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE);
+}
+
+static long linereq_get_values(struct linereq *lr, void __user *ip)
+{
+ struct gpio_v2_line_values lv;
+ DECLARE_BITMAP(vals, GPIO_V2_LINES_MAX);
+ struct gpio_desc **descs;
+ unsigned int i, didx, num_get;
+ bool val;
+ int ret;
+
+ /* NOTE: It's ok to read values of output lines. */
+ if (copy_from_user(&lv, ip, sizeof(lv)))
+ return -EFAULT;
+
+ for (num_get = 0, i = 0; i < lr->num_lines; i++) {
+ if (lv.mask & BIT_ULL(i)) {
+ num_get++;
+ descs = &lr->lines[i].desc;
+ }
+ }
+
+ if (num_get == 0)
+ return -EINVAL;
+
+ if (num_get != 1) {
+ descs = kmalloc_array(num_get, sizeof(*descs), GFP_KERNEL);
+ if (!descs)
+ return -ENOMEM;
+ for (didx = 0, i = 0; i < lr->num_lines; i++) {
+ if (lv.mask & BIT_ULL(i)) {
+ descs[didx] = lr->lines[i].desc;
+ didx++;
+ }
+ }
+ }
+ ret = gpiod_get_array_value_complex(false, true, num_get,
+ descs, NULL, vals);
+
+ if (num_get != 1)
+ kfree(descs);
+ if (ret)
+ return ret;
+
+ lv.bits = 0;
+ for (didx = 0, i = 0; i < lr->num_lines; i++) {
+ if (lv.mask & BIT_ULL(i)) {
+ if (lr->lines[i].sw_debounced)
+ val = debounced_value(&lr->lines[i]);
+ else
+ val = test_bit(didx, vals);
+ if (val)
+ lv.bits |= BIT_ULL(i);
+ didx++;
+ }
+ }
+
+ if (copy_to_user(ip, &lv, sizeof(lv)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long linereq_set_values_unlocked(struct linereq *lr,
+ struct gpio_v2_line_values *lv)
+{
+ DECLARE_BITMAP(vals, GPIO_V2_LINES_MAX);
+ struct gpio_desc **descs;
+ unsigned int i, didx, num_set;
+ int ret;
+
+ bitmap_zero(vals, GPIO_V2_LINES_MAX);
+ for (num_set = 0, i = 0; i < lr->num_lines; i++) {
+ if (lv->mask & BIT_ULL(i)) {
+ if (!test_bit(FLAG_IS_OUT, &lr->lines[i].desc->flags))
+ return -EPERM;
+ if (lv->bits & BIT_ULL(i))
+ __set_bit(num_set, vals);
+ num_set++;
+ descs = &lr->lines[i].desc;
+ }
+ }
+ if (num_set == 0)
+ return -EINVAL;
+
+ if (num_set != 1) {
+ /* build compacted desc array and values */
+ descs = kmalloc_array(num_set, sizeof(*descs), GFP_KERNEL);
+ if (!descs)
+ return -ENOMEM;
+ for (didx = 0, i = 0; i < lr->num_lines; i++) {
+ if (lv->mask & BIT_ULL(i)) {
+ descs[didx] = lr->lines[i].desc;
+ didx++;
+ }
+ }
+ }
+ ret = gpiod_set_array_value_complex(false, true, num_set,
+ descs, NULL, vals);
+
+ if (num_set != 1)
+ kfree(descs);
+ return ret;
+}
+
+static long linereq_set_values(struct linereq *lr, void __user *ip)
+{
+ struct gpio_v2_line_values lv;
+ int ret;
+
+ if (copy_from_user(&lv, ip, sizeof(lv)))
+ return -EFAULT;
+
+ mutex_lock(&lr->config_mutex);
+
+ ret = linereq_set_values_unlocked(lr, &lv);
+
+ mutex_unlock(&lr->config_mutex);
+
+ return ret;
+}
+
+static long linereq_set_config_unlocked(struct linereq *lr,
+ struct gpio_v2_line_config *lc)
+{
+ struct gpio_desc *desc;
+ struct line *line;
+ unsigned int i;
+ u64 flags, edflags;
+ int ret;
+
+ for (i = 0; i < lr->num_lines; i++) {
+ line = &lr->lines[i];
+ desc = lr->lines[i].desc;
+ flags = gpio_v2_line_config_flags(lc, i);
+ gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags);
+ edflags = flags & GPIO_V2_LINE_EDGE_DETECTOR_FLAGS;
+ /*
+ * Lines have to be requested explicitly for input
+ * or output, else the line will be treated "as is".
+ */
+ if (flags & GPIO_V2_LINE_FLAG_OUTPUT) {
+ int val = gpio_v2_line_config_output_value(lc, i);
+
+ edge_detector_stop(line);
+ ret = gpiod_direction_output(desc, val);
+ if (ret)
+ return ret;
+ } else if (flags & GPIO_V2_LINE_FLAG_INPUT) {
+ ret = gpiod_direction_input(desc);
+ if (ret)
+ return ret;
+
+ ret = edge_detector_update(line, lc, i, edflags);
+ if (ret)
+ return ret;
+ }
+
+ WRITE_ONCE(line->edflags, edflags);
+
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG);
+ }
+ return 0;
+}
+
+static long linereq_set_config(struct linereq *lr, void __user *ip)
+{
+ struct gpio_v2_line_config lc;
+ int ret;
+
+ if (copy_from_user(&lc, ip, sizeof(lc)))
+ return -EFAULT;
+
+ ret = gpio_v2_line_config_validate(&lc, lr->num_lines);
+ if (ret)
+ return ret;
+
+ mutex_lock(&lr->config_mutex);
+
+ ret = linereq_set_config_unlocked(lr, &lc);
+
+ mutex_unlock(&lr->config_mutex);
+
+ return ret;
+}
+
+static long linereq_ioctl_unlocked(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct linereq *lr = file->private_data;
+ void __user *ip = (void __user *)arg;
+
+ if (!lr->gdev->chip)
+ return -ENODEV;
+
+ switch (cmd) {
+ case GPIO_V2_LINE_GET_VALUES_IOCTL:
+ return linereq_get_values(lr, ip);
+ case GPIO_V2_LINE_SET_VALUES_IOCTL:
+ return linereq_set_values(lr, ip);
+ case GPIO_V2_LINE_SET_CONFIG_IOCTL:
+ return linereq_set_config(lr, ip);
+ default:
+ return -EINVAL;
+ }
+}
+
+static long linereq_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct linereq *lr = file->private_data;
+
+ return call_ioctl_locked(file, cmd, arg, lr->gdev,
+ linereq_ioctl_unlocked);
+}
+
+#ifdef CONFIG_COMPAT
+static long linereq_ioctl_compat(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ return linereq_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+static __poll_t linereq_poll_unlocked(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct linereq *lr = file->private_data;
+ __poll_t events = 0;
+
+ if (!lr->gdev->chip)
+ return EPOLLHUP | EPOLLERR;
+
+ poll_wait(file, &lr->wait, wait);
+
+ if (!kfifo_is_empty_spinlocked_noirqsave(&lr->events,
+ &lr->wait.lock))
+ events = EPOLLIN | EPOLLRDNORM;
+
+ return events;
+}
+
+static __poll_t linereq_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct linereq *lr = file->private_data;
+
+ return call_poll_locked(file, wait, lr->gdev, linereq_poll_unlocked);
+}
+
+static ssize_t linereq_read_unlocked(struct file *file, char __user *buf,
+ size_t count, loff_t *f_ps)
+{
+ struct linereq *lr = file->private_data;
+ struct gpio_v2_line_event le;
+ ssize_t bytes_read = 0;
+ int ret;
+
+ if (!lr->gdev->chip)
+ return -ENODEV;
+
+ if (count < sizeof(le))
+ return -EINVAL;
+
+ do {
+ spin_lock(&lr->wait.lock);
+ if (kfifo_is_empty(&lr->events)) {
+ if (bytes_read) {
+ spin_unlock(&lr->wait.lock);
+ return bytes_read;
+ }
+
+ if (file->f_flags & O_NONBLOCK) {
+ spin_unlock(&lr->wait.lock);
+ return -EAGAIN;
+ }
+
+ ret = wait_event_interruptible_locked(lr->wait,
+ !kfifo_is_empty(&lr->events));
+ if (ret) {
+ spin_unlock(&lr->wait.lock);
+ return ret;
+ }
+ }
+
+ ret = kfifo_out(&lr->events, &le, 1);
+ spin_unlock(&lr->wait.lock);
+ if (ret != 1) {
+ /*
+ * This should never happen - we were holding the
+ * lock from the moment we learned the fifo is no
+ * longer empty until now.
+ */
+ ret = -EIO;
+ break;
+ }
+
+ if (copy_to_user(buf + bytes_read, &le, sizeof(le)))
+ return -EFAULT;
+ bytes_read += sizeof(le);
+ } while (count >= bytes_read + sizeof(le));
+
+ return bytes_read;
+}
+
+static ssize_t linereq_read(struct file *file, char __user *buf,
+ size_t count, loff_t *f_ps)
+{
+ struct linereq *lr = file->private_data;
+
+ return call_read_locked(file, buf, count, f_ps, lr->gdev,
+ linereq_read_unlocked);
+}
+
+static void linereq_free(struct linereq *lr)
+{
+ unsigned int i;
+
+ if (lr->device_unregistered_nb.notifier_call)
+ blocking_notifier_chain_unregister(&lr->gdev->device_notifier,
+ &lr->device_unregistered_nb);
+
+ for (i = 0; i < lr->num_lines; i++) {
+ if (lr->lines[i].desc) {
+ edge_detector_stop(&lr->lines[i]);
+ gpiod_free(lr->lines[i].desc);
+ }
+ }
+ kfifo_free(&lr->events);
+ kfree(lr->label);
+ gpio_device_put(lr->gdev);
+ kfree(lr);
+}
+
+static int linereq_release(struct inode *inode, struct file *file)
+{
+ struct linereq *lr = file->private_data;
+
+ linereq_free(lr);
+ return 0;
+}
+
+#ifdef CONFIG_PROC_FS
+static void linereq_show_fdinfo(struct seq_file *out, struct file *file)
+{
+ struct linereq *lr = file->private_data;
+ struct device *dev = &lr->gdev->dev;
+ u16 i;
+
+ seq_printf(out, "gpio-chip:\t%s\n", dev_name(dev));
+
+ for (i = 0; i < lr->num_lines; i++)
+ seq_printf(out, "gpio-line:\t%d\n",
+ gpio_chip_hwgpio(lr->lines[i].desc));
+}
+#endif
+
+static const struct file_operations line_fileops = {
+ .release = linereq_release,
+ .read = linereq_read,
+ .poll = linereq_poll,
+ .owner = THIS_MODULE,
+ .llseek = noop_llseek,
+ .unlocked_ioctl = linereq_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = linereq_ioctl_compat,
+#endif
+#ifdef CONFIG_PROC_FS
+ .show_fdinfo = linereq_show_fdinfo,
+#endif
+};
+
+static int linereq_create(struct gpio_device *gdev, void __user *ip)
+{
+ struct gpio_v2_line_request ulr;
+ struct gpio_v2_line_config *lc;
+ struct linereq *lr;
+ struct file *file;
+ u64 flags, edflags;
+ unsigned int i;
+ int fd, ret;
+
+ if (copy_from_user(&ulr, ip, sizeof(ulr)))
+ return -EFAULT;
+
+ if ((ulr.num_lines == 0) || (ulr.num_lines > GPIO_V2_LINES_MAX))
+ return -EINVAL;
+
+ if (memchr_inv(ulr.padding, 0, sizeof(ulr.padding)))
+ return -EINVAL;
+
+ lc = &ulr.config;
+ ret = gpio_v2_line_config_validate(lc, ulr.num_lines);
+ if (ret)
+ return ret;
+
+ lr = kzalloc(struct_size(lr, lines, ulr.num_lines), GFP_KERNEL);
+ if (!lr)
+ return -ENOMEM;
+
+ lr->gdev = gpio_device_get(gdev);
+
+ for (i = 0; i < ulr.num_lines; i++) {
+ lr->lines[i].req = lr;
+ WRITE_ONCE(lr->lines[i].sw_debounced, 0);
+ INIT_DELAYED_WORK(&lr->lines[i].work, debounce_work_func);
+ }
+
+ if (ulr.consumer[0] != '\0') {
+ /* label is only initialized if consumer is set */
+ lr->label = kstrndup(ulr.consumer, sizeof(ulr.consumer) - 1,
+ GFP_KERNEL);
+ if (!lr->label) {
+ ret = -ENOMEM;
+ goto out_free_linereq;
+ }
+ }
+
+ mutex_init(&lr->config_mutex);
+ init_waitqueue_head(&lr->wait);
+ lr->event_buffer_size = ulr.event_buffer_size;
+ if (lr->event_buffer_size == 0)
+ lr->event_buffer_size = ulr.num_lines * 16;
+ else if (lr->event_buffer_size > GPIO_V2_LINES_MAX * 16)
+ lr->event_buffer_size = GPIO_V2_LINES_MAX * 16;
+
+ atomic_set(&lr->seqno, 0);
+ lr->num_lines = ulr.num_lines;
+
+ /* Request each GPIO */
+ for (i = 0; i < ulr.num_lines; i++) {
+ u32 offset = ulr.offsets[i];
+ struct gpio_desc *desc = gpiochip_get_desc(gdev->chip, offset);
+
+ if (IS_ERR(desc)) {
+ ret = PTR_ERR(desc);
+ goto out_free_linereq;
+ }
+
+ ret = gpiod_request_user(desc, lr->label);
+ if (ret)
+ goto out_free_linereq;
+
+ lr->lines[i].desc = desc;
+ flags = gpio_v2_line_config_flags(lc, i);
+ gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags);
+
+ ret = gpiod_set_transitory(desc, false);
+ if (ret < 0)
+ goto out_free_linereq;
+
+ edflags = flags & GPIO_V2_LINE_EDGE_DETECTOR_FLAGS;
+ /*
+ * Lines have to be requested explicitly for input
+ * or output, else the line will be treated "as is".
+ */
+ if (flags & GPIO_V2_LINE_FLAG_OUTPUT) {
+ int val = gpio_v2_line_config_output_value(lc, i);
+
+ ret = gpiod_direction_output(desc, val);
+ if (ret)
+ goto out_free_linereq;
+ } else if (flags & GPIO_V2_LINE_FLAG_INPUT) {
+ ret = gpiod_direction_input(desc);
+ if (ret)
+ goto out_free_linereq;
+
+ ret = edge_detector_setup(&lr->lines[i], lc, i,
+ edflags);
+ if (ret)
+ goto out_free_linereq;
+ }
+
+ lr->lines[i].edflags = edflags;
+
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED);
+
+ dev_dbg(&gdev->dev, "registered chardev handle for line %d\n",
+ offset);
+ }
+
+ lr->device_unregistered_nb.notifier_call = linereq_unregistered_notify;
+ ret = blocking_notifier_chain_register(&gdev->device_notifier,
+ &lr->device_unregistered_nb);
+ if (ret)
+ goto out_free_linereq;
+
+ fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ ret = fd;
+ goto out_free_linereq;
+ }
+
+ file = anon_inode_getfile("gpio-line", &line_fileops, lr,
+ O_RDONLY | O_CLOEXEC);
+ if (IS_ERR(file)) {
+ ret = PTR_ERR(file);
+ goto out_put_unused_fd;
+ }
+
+ ulr.fd = fd;
+ if (copy_to_user(ip, &ulr, sizeof(ulr))) {
+ /*
+ * fput() will trigger the release() callback, so do not go onto
+ * the regular error cleanup path here.
+ */
+ fput(file);
+ put_unused_fd(fd);
+ return -EFAULT;
+ }
+
+ fd_install(fd, file);
+
+ dev_dbg(&gdev->dev, "registered chardev handle for %d lines\n",
+ lr->num_lines);
+
+ return 0;
+
+out_put_unused_fd:
+ put_unused_fd(fd);
+out_free_linereq:
+ linereq_free(lr);
+ return ret;
+}
+
+#ifdef CONFIG_GPIO_CDEV_V1
+
+/*
+ * GPIO line event management
+ */
+
+/**
+ * struct lineevent_state - contains the state of a userspace event
+ * @gdev: the GPIO device the event pertains to
+ * @label: consumer label used to tag descriptors
+ * @desc: the GPIO descriptor held by this event
+ * @eflags: the event flags this line was requested with
+ * @irq: the interrupt that trigger in response to events on this GPIO
+ * @wait: wait queue that handles blocking reads of events
+ * @device_unregistered_nb: notifier block for receiving gdev unregister events
+ * @events: KFIFO for the GPIO events
+ * @timestamp: cache for the timestamp storing it between hardirq
+ * and IRQ thread, used to bring the timestamp close to the actual
+ * event
+ */
+struct lineevent_state {
+ struct gpio_device *gdev;
+ const char *label;
+ struct gpio_desc *desc;
+ u32 eflags;
+ int irq;
+ wait_queue_head_t wait;
+ struct notifier_block device_unregistered_nb;
+ DECLARE_KFIFO(events, struct gpioevent_data, 16);
+ u64 timestamp;
+};
+
+#define GPIOEVENT_REQUEST_VALID_FLAGS \
+ (GPIOEVENT_REQUEST_RISING_EDGE | \
+ GPIOEVENT_REQUEST_FALLING_EDGE)
+
+static __poll_t lineevent_poll_unlocked(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct lineevent_state *le = file->private_data;
+ __poll_t events = 0;
+
+ if (!le->gdev->chip)
+ return EPOLLHUP | EPOLLERR;
+
+ poll_wait(file, &le->wait, wait);
+
+ if (!kfifo_is_empty_spinlocked_noirqsave(&le->events, &le->wait.lock))
+ events = EPOLLIN | EPOLLRDNORM;
+
+ return events;
+}
+
+static __poll_t lineevent_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct lineevent_state *le = file->private_data;
+
+ return call_poll_locked(file, wait, le->gdev, lineevent_poll_unlocked);
+}
+
+static int lineevent_unregistered_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct lineevent_state *le = container_of(nb, struct lineevent_state,
+ device_unregistered_nb);
+
+ wake_up_poll(&le->wait, EPOLLIN | EPOLLERR);
+
+ return NOTIFY_OK;
+}
+
+struct compat_gpioeevent_data {
+ compat_u64 timestamp;
+ u32 id;
+};
+
+static ssize_t lineevent_read_unlocked(struct file *file, char __user *buf,
+ size_t count, loff_t *f_ps)
+{
+ struct lineevent_state *le = file->private_data;
+ struct gpioevent_data ge;
+ ssize_t bytes_read = 0;
+ ssize_t ge_size;
+ int ret;
+
+ if (!le->gdev->chip)
+ return -ENODEV;
+
+ /*
+ * When compatible system call is being used the struct gpioevent_data,
+ * in case of at least ia32, has different size due to the alignment
+ * differences. Because we have first member 64 bits followed by one of
+ * 32 bits there is no gap between them. The only difference is the
+ * padding at the end of the data structure. Hence, we calculate the
+ * actual sizeof() and pass this as an argument to copy_to_user() to
+ * drop unneeded bytes from the output.
+ */
+ if (compat_need_64bit_alignment_fixup())
+ ge_size = sizeof(struct compat_gpioeevent_data);
+ else
+ ge_size = sizeof(struct gpioevent_data);
+ if (count < ge_size)
+ return -EINVAL;
+
+ do {
+ spin_lock(&le->wait.lock);
+ if (kfifo_is_empty(&le->events)) {
+ if (bytes_read) {
+ spin_unlock(&le->wait.lock);
+ return bytes_read;
+ }
+
+ if (file->f_flags & O_NONBLOCK) {
+ spin_unlock(&le->wait.lock);
+ return -EAGAIN;
+ }
+
+ ret = wait_event_interruptible_locked(le->wait,
+ !kfifo_is_empty(&le->events));
+ if (ret) {
+ spin_unlock(&le->wait.lock);
+ return ret;
+ }
+ }
+
+ ret = kfifo_out(&le->events, &ge, 1);
+ spin_unlock(&le->wait.lock);
+ if (ret != 1) {
+ /*
+ * This should never happen - we were holding the lock
+ * from the moment we learned the fifo is no longer
+ * empty until now.
+ */
+ ret = -EIO;
+ break;
+ }
+
+ if (copy_to_user(buf + bytes_read, &ge, ge_size))
+ return -EFAULT;
+ bytes_read += ge_size;
+ } while (count >= bytes_read + ge_size);
+
+ return bytes_read;
+}
+
+static ssize_t lineevent_read(struct file *file, char __user *buf,
+ size_t count, loff_t *f_ps)
+{
+ struct lineevent_state *le = file->private_data;
+
+ return call_read_locked(file, buf, count, f_ps, le->gdev,
+ lineevent_read_unlocked);
+}
+
+static void lineevent_free(struct lineevent_state *le)
+{
+ if (le->device_unregistered_nb.notifier_call)
+ blocking_notifier_chain_unregister(&le->gdev->device_notifier,
+ &le->device_unregistered_nb);
+ if (le->irq)
+ free_irq(le->irq, le);
+ if (le->desc)
+ gpiod_free(le->desc);
+ kfree(le->label);
+ gpio_device_put(le->gdev);
+ kfree(le);
+}
+
+static int lineevent_release(struct inode *inode, struct file *file)
+{
+ lineevent_free(file->private_data);
+ return 0;
+}
+
+static long lineevent_ioctl_unlocked(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct lineevent_state *le = file->private_data;
+ void __user *ip = (void __user *)arg;
+ struct gpiohandle_data ghd;
+
+ if (!le->gdev->chip)
+ return -ENODEV;
+
+ /*
+ * We can get the value for an event line but not set it,
+ * because it is input by definition.
+ */
+ if (cmd == GPIOHANDLE_GET_LINE_VALUES_IOCTL) {
+ int val;
+
+ memset(&ghd, 0, sizeof(ghd));
+
+ val = gpiod_get_value_cansleep(le->desc);
+ if (val < 0)
+ return val;
+ ghd.values[0] = val;
+
+ if (copy_to_user(ip, &ghd, sizeof(ghd)))
+ return -EFAULT;
+
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static long lineevent_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct lineevent_state *le = file->private_data;
+
+ return call_ioctl_locked(file, cmd, arg, le->gdev,
+ lineevent_ioctl_unlocked);
+}
+
+#ifdef CONFIG_COMPAT
+static long lineevent_ioctl_compat(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ return lineevent_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+static const struct file_operations lineevent_fileops = {
+ .release = lineevent_release,
+ .read = lineevent_read,
+ .poll = lineevent_poll,
+ .owner = THIS_MODULE,
+ .llseek = noop_llseek,
+ .unlocked_ioctl = lineevent_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = lineevent_ioctl_compat,
+#endif
+};
+
+static irqreturn_t lineevent_irq_thread(int irq, void *p)
+{
+ struct lineevent_state *le = p;
+ struct gpioevent_data ge;
+ int ret;
+
+ /* Do not leak kernel stack to userspace */
+ memset(&ge, 0, sizeof(ge));
+
+ /*
+ * We may be running from a nested threaded interrupt in which case
+ * we didn't get the timestamp from lineevent_irq_handler().
+ */
+ if (!le->timestamp)
+ ge.timestamp = ktime_get_ns();
+ else
+ ge.timestamp = le->timestamp;
+
+ if (le->eflags & GPIOEVENT_REQUEST_RISING_EDGE
+ && le->eflags & GPIOEVENT_REQUEST_FALLING_EDGE) {
+ int level = gpiod_get_value_cansleep(le->desc);
+
+ if (level)
+ /* Emit low-to-high event */
+ ge.id = GPIOEVENT_EVENT_RISING_EDGE;
+ else
+ /* Emit high-to-low event */
+ ge.id = GPIOEVENT_EVENT_FALLING_EDGE;
+ } else if (le->eflags & GPIOEVENT_REQUEST_RISING_EDGE) {
+ /* Emit low-to-high event */
+ ge.id = GPIOEVENT_EVENT_RISING_EDGE;
+ } else if (le->eflags & GPIOEVENT_REQUEST_FALLING_EDGE) {
+ /* Emit high-to-low event */
+ ge.id = GPIOEVENT_EVENT_FALLING_EDGE;
+ } else {
+ return IRQ_NONE;
+ }
+
+ ret = kfifo_in_spinlocked_noirqsave(&le->events, &ge,
+ 1, &le->wait.lock);
+ if (ret)
+ wake_up_poll(&le->wait, EPOLLIN);
+ else
+ pr_debug_ratelimited("event FIFO is full - event dropped\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t lineevent_irq_handler(int irq, void *p)
+{
+ struct lineevent_state *le = p;
+
+ /*
+ * Just store the timestamp in hardirq context so we get it as
+ * close in time as possible to the actual event.
+ */
+ le->timestamp = ktime_get_ns();
+
+ return IRQ_WAKE_THREAD;
+}
+
+static int lineevent_create(struct gpio_device *gdev, void __user *ip)
+{
+ struct gpioevent_request eventreq;
+ struct lineevent_state *le;
+ struct gpio_desc *desc;
+ struct file *file;
+ u32 offset;
+ u32 lflags;
+ u32 eflags;
+ int fd;
+ int ret;
+ int irq, irqflags = 0;
+
+ if (copy_from_user(&eventreq, ip, sizeof(eventreq)))
+ return -EFAULT;
+
+ offset = eventreq.lineoffset;
+ lflags = eventreq.handleflags;
+ eflags = eventreq.eventflags;
+
+ desc = gpiochip_get_desc(gdev->chip, offset);
+ if (IS_ERR(desc))
+ return PTR_ERR(desc);
+
+ /* Return an error if a unknown flag is set */
+ if ((lflags & ~GPIOHANDLE_REQUEST_VALID_FLAGS) ||
+ (eflags & ~GPIOEVENT_REQUEST_VALID_FLAGS))
+ return -EINVAL;
+
+ /* This is just wrong: we don't look for events on output lines */
+ if ((lflags & GPIOHANDLE_REQUEST_OUTPUT) ||
+ (lflags & GPIOHANDLE_REQUEST_OPEN_DRAIN) ||
+ (lflags & GPIOHANDLE_REQUEST_OPEN_SOURCE))
+ return -EINVAL;
+
+ /* Only one bias flag can be set. */
+ if (((lflags & GPIOHANDLE_REQUEST_BIAS_DISABLE) &&
+ (lflags & (GPIOHANDLE_REQUEST_BIAS_PULL_DOWN |
+ GPIOHANDLE_REQUEST_BIAS_PULL_UP))) ||
+ ((lflags & GPIOHANDLE_REQUEST_BIAS_PULL_DOWN) &&
+ (lflags & GPIOHANDLE_REQUEST_BIAS_PULL_UP)))
+ return -EINVAL;
+
+ le = kzalloc(sizeof(*le), GFP_KERNEL);
+ if (!le)
+ return -ENOMEM;
+ le->gdev = gpio_device_get(gdev);
+
+ if (eventreq.consumer_label[0] != '\0') {
+ /* label is only initialized if consumer_label is set */
+ le->label = kstrndup(eventreq.consumer_label,
+ sizeof(eventreq.consumer_label) - 1,
+ GFP_KERNEL);
+ if (!le->label) {
+ ret = -ENOMEM;
+ goto out_free_le;
+ }
+ }
+
+ ret = gpiod_request_user(desc, le->label);
+ if (ret)
+ goto out_free_le;
+ le->desc = desc;
+ le->eflags = eflags;
+
+ linehandle_flags_to_desc_flags(lflags, &desc->flags);
+
+ ret = gpiod_direction_input(desc);
+ if (ret)
+ goto out_free_le;
+
+ gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED);
+
+ irq = gpiod_to_irq(desc);
+ if (irq <= 0) {
+ ret = -ENODEV;
+ goto out_free_le;
+ }
+
+ if (eflags & GPIOEVENT_REQUEST_RISING_EDGE)
+ irqflags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ?
+ IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
+ if (eflags & GPIOEVENT_REQUEST_FALLING_EDGE)
+ irqflags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ?
+ IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING;
+ irqflags |= IRQF_ONESHOT;
+
+ INIT_KFIFO(le->events);
+ init_waitqueue_head(&le->wait);
+
+ le->device_unregistered_nb.notifier_call = lineevent_unregistered_notify;
+ ret = blocking_notifier_chain_register(&gdev->device_notifier,
+ &le->device_unregistered_nb);
+ if (ret)
+ goto out_free_le;
+
+ /* Request a thread to read the events */
+ ret = request_threaded_irq(irq,
+ lineevent_irq_handler,
+ lineevent_irq_thread,
+ irqflags,
+ le->label,
+ le);
+ if (ret)
+ goto out_free_le;
+
+ le->irq = irq;
+
+ fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ ret = fd;
+ goto out_free_le;
+ }
+
+ file = anon_inode_getfile("gpio-event",
+ &lineevent_fileops,
+ le,
+ O_RDONLY | O_CLOEXEC);
+ if (IS_ERR(file)) {
+ ret = PTR_ERR(file);
+ goto out_put_unused_fd;
+ }
+
+ eventreq.fd = fd;
+ if (copy_to_user(ip, &eventreq, sizeof(eventreq))) {
+ /*
+ * fput() will trigger the release() callback, so do not go onto
+ * the regular error cleanup path here.
+ */
+ fput(file);
+ put_unused_fd(fd);
+ return -EFAULT;
+ }
+
+ fd_install(fd, file);
+
+ return 0;
+
+out_put_unused_fd:
+ put_unused_fd(fd);
+out_free_le:
+ lineevent_free(le);
+ return ret;
+}
+
+static void gpio_v2_line_info_to_v1(struct gpio_v2_line_info *info_v2,
+ struct gpioline_info *info_v1)
+{
+ u64 flagsv2 = info_v2->flags;
+
+ memcpy(info_v1->name, info_v2->name, sizeof(info_v1->name));
+ memcpy(info_v1->consumer, info_v2->consumer, sizeof(info_v1->consumer));
+ info_v1->line_offset = info_v2->offset;
+ info_v1->flags = 0;
+
+ if (flagsv2 & GPIO_V2_LINE_FLAG_USED)
+ info_v1->flags |= GPIOLINE_FLAG_KERNEL;
+
+ if (flagsv2 & GPIO_V2_LINE_FLAG_OUTPUT)
+ info_v1->flags |= GPIOLINE_FLAG_IS_OUT;
+
+ if (flagsv2 & GPIO_V2_LINE_FLAG_ACTIVE_LOW)
+ info_v1->flags |= GPIOLINE_FLAG_ACTIVE_LOW;
+
+ if (flagsv2 & GPIO_V2_LINE_FLAG_OPEN_DRAIN)
+ info_v1->flags |= GPIOLINE_FLAG_OPEN_DRAIN;
+ if (flagsv2 & GPIO_V2_LINE_FLAG_OPEN_SOURCE)
+ info_v1->flags |= GPIOLINE_FLAG_OPEN_SOURCE;
+
+ if (flagsv2 & GPIO_V2_LINE_FLAG_BIAS_PULL_UP)
+ info_v1->flags |= GPIOLINE_FLAG_BIAS_PULL_UP;
+ if (flagsv2 & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN)
+ info_v1->flags |= GPIOLINE_FLAG_BIAS_PULL_DOWN;
+ if (flagsv2 & GPIO_V2_LINE_FLAG_BIAS_DISABLED)
+ info_v1->flags |= GPIOLINE_FLAG_BIAS_DISABLE;
+}
+
+static void gpio_v2_line_info_changed_to_v1(
+ struct gpio_v2_line_info_changed *lic_v2,
+ struct gpioline_info_changed *lic_v1)
+{
+ memset(lic_v1, 0, sizeof(*lic_v1));
+ gpio_v2_line_info_to_v1(&lic_v2->info, &lic_v1->info);
+ lic_v1->timestamp = lic_v2->timestamp_ns;
+ lic_v1->event_type = lic_v2->event_type;
+}
+
+#endif /* CONFIG_GPIO_CDEV_V1 */
+
+static void gpio_desc_to_lineinfo(struct gpio_desc *desc,
+ struct gpio_v2_line_info *info)
+{
+ struct gpio_chip *gc = desc->gdev->chip;
+ bool ok_for_pinctrl;
+ unsigned long flags;
+ u32 debounce_period_us;
+ unsigned int num_attrs = 0;
+
+ memset(info, 0, sizeof(*info));
+ info->offset = gpio_chip_hwgpio(desc);
+
+ /*
+ * This function takes a mutex so we must check this before taking
+ * the spinlock.
+ *
+ * FIXME: find a non-racy way to retrieve this information. Maybe a
+ * lock common to both frameworks?
+ */
+ ok_for_pinctrl =
+ pinctrl_gpio_can_use_line(gc->base + info->offset);
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ if (desc->name)
+ strscpy(info->name, desc->name, sizeof(info->name));
+
+ if (desc->label)
+ strscpy(info->consumer, desc->label, sizeof(info->consumer));
+
+ /*
+ * Userspace only need to know that the kernel is using this GPIO so
+ * it can't use it.
+ */
+ info->flags = 0;
+ if (test_bit(FLAG_REQUESTED, &desc->flags) ||
+ test_bit(FLAG_IS_HOGGED, &desc->flags) ||
+ test_bit(FLAG_USED_AS_IRQ, &desc->flags) ||
+ test_bit(FLAG_EXPORT, &desc->flags) ||
+ test_bit(FLAG_SYSFS, &desc->flags) ||
+ !gpiochip_line_is_valid(gc, info->offset) ||
+ !ok_for_pinctrl)
+ info->flags |= GPIO_V2_LINE_FLAG_USED;
+
+ if (test_bit(FLAG_IS_OUT, &desc->flags))
+ info->flags |= GPIO_V2_LINE_FLAG_OUTPUT;
+ else
+ info->flags |= GPIO_V2_LINE_FLAG_INPUT;
+
+ if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
+ info->flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW;
+
+ if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))
+ info->flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN;
+ if (test_bit(FLAG_OPEN_SOURCE, &desc->flags))
+ info->flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE;
+
+ if (test_bit(FLAG_BIAS_DISABLE, &desc->flags))
+ info->flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED;
+ if (test_bit(FLAG_PULL_DOWN, &desc->flags))
+ info->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN;
+ if (test_bit(FLAG_PULL_UP, &desc->flags))
+ info->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP;
+
+ if (test_bit(FLAG_EDGE_RISING, &desc->flags))
+ info->flags |= GPIO_V2_LINE_FLAG_EDGE_RISING;
+ if (test_bit(FLAG_EDGE_FALLING, &desc->flags))
+ info->flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING;
+
+ if (test_bit(FLAG_EVENT_CLOCK_REALTIME, &desc->flags))
+ info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME;
+ else if (test_bit(FLAG_EVENT_CLOCK_HTE, &desc->flags))
+ info->flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_HTE;
+
+ debounce_period_us = READ_ONCE(desc->debounce_period_us);
+ if (debounce_period_us) {
+ info->attrs[num_attrs].id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
+ info->attrs[num_attrs].debounce_period_us = debounce_period_us;
+ num_attrs++;
+ }
+ info->num_attrs = num_attrs;
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+}
+
+struct gpio_chardev_data {
+ struct gpio_device *gdev;
+ wait_queue_head_t wait;
+ DECLARE_KFIFO(events, struct gpio_v2_line_info_changed, 32);
+ struct notifier_block lineinfo_changed_nb;
+ struct notifier_block device_unregistered_nb;
+ unsigned long *watched_lines;
+#ifdef CONFIG_GPIO_CDEV_V1
+ atomic_t watch_abi_version;
+#endif
+};
+
+static int chipinfo_get(struct gpio_chardev_data *cdev, void __user *ip)
+{
+ struct gpio_device *gdev = cdev->gdev;
+ struct gpiochip_info chipinfo;
+
+ memset(&chipinfo, 0, sizeof(chipinfo));
+
+ strscpy(chipinfo.name, dev_name(&gdev->dev), sizeof(chipinfo.name));
+ strscpy(chipinfo.label, gdev->label, sizeof(chipinfo.label));
+ chipinfo.lines = gdev->ngpio;
+ if (copy_to_user(ip, &chipinfo, sizeof(chipinfo)))
+ return -EFAULT;
+ return 0;
+}
+
+#ifdef CONFIG_GPIO_CDEV_V1
+/*
+ * returns 0 if the versions match, else the previously selected ABI version
+ */
+static int lineinfo_ensure_abi_version(struct gpio_chardev_data *cdata,
+ unsigned int version)
+{
+ int abiv = atomic_cmpxchg(&cdata->watch_abi_version, 0, version);
+
+ if (abiv == version)
+ return 0;
+
+ return abiv;
+}
+
+static int lineinfo_get_v1(struct gpio_chardev_data *cdev, void __user *ip,
+ bool watch)
+{
+ struct gpio_desc *desc;
+ struct gpioline_info lineinfo;
+ struct gpio_v2_line_info lineinfo_v2;
+
+ if (copy_from_user(&lineinfo, ip, sizeof(lineinfo)))
+ return -EFAULT;
+
+ /* this doubles as a range check on line_offset */
+ desc = gpiochip_get_desc(cdev->gdev->chip, lineinfo.line_offset);
+ if (IS_ERR(desc))
+ return PTR_ERR(desc);
+
+ if (watch) {
+ if (lineinfo_ensure_abi_version(cdev, 1))
+ return -EPERM;
+
+ if (test_and_set_bit(lineinfo.line_offset, cdev->watched_lines))
+ return -EBUSY;
+ }
+
+ gpio_desc_to_lineinfo(desc, &lineinfo_v2);
+ gpio_v2_line_info_to_v1(&lineinfo_v2, &lineinfo);
+
+ if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) {
+ if (watch)
+ clear_bit(lineinfo.line_offset, cdev->watched_lines);
+ return -EFAULT;
+ }
+
+ return 0;
+}
+#endif
+
+static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip,
+ bool watch)
+{
+ struct gpio_desc *desc;
+ struct gpio_v2_line_info lineinfo;
+
+ if (copy_from_user(&lineinfo, ip, sizeof(lineinfo)))
+ return -EFAULT;
+
+ if (memchr_inv(lineinfo.padding, 0, sizeof(lineinfo.padding)))
+ return -EINVAL;
+
+ desc = gpiochip_get_desc(cdev->gdev->chip, lineinfo.offset);
+ if (IS_ERR(desc))
+ return PTR_ERR(desc);
+
+ if (watch) {
+#ifdef CONFIG_GPIO_CDEV_V1
+ if (lineinfo_ensure_abi_version(cdev, 2))
+ return -EPERM;
+#endif
+ if (test_and_set_bit(lineinfo.offset, cdev->watched_lines))
+ return -EBUSY;
+ }
+ gpio_desc_to_lineinfo(desc, &lineinfo);
+
+ if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) {
+ if (watch)
+ clear_bit(lineinfo.offset, cdev->watched_lines);
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int lineinfo_unwatch(struct gpio_chardev_data *cdev, void __user *ip)
+{
+ __u32 offset;
+
+ if (copy_from_user(&offset, ip, sizeof(offset)))
+ return -EFAULT;
+
+ if (offset >= cdev->gdev->ngpio)
+ return -EINVAL;
+
+ if (!test_and_clear_bit(offset, cdev->watched_lines))
+ return -EBUSY;
+
+ return 0;
+}
+
+static long gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct gpio_chardev_data *cdev = file->private_data;
+ struct gpio_device *gdev = cdev->gdev;
+ void __user *ip = (void __user *)arg;
+
+ /* We fail any subsequent ioctl():s when the chip is gone */
+ if (!gdev->chip)
+ return -ENODEV;
+
+ /* Fill in the struct and pass to userspace */
+ switch (cmd) {
+ case GPIO_GET_CHIPINFO_IOCTL:
+ return chipinfo_get(cdev, ip);
+#ifdef CONFIG_GPIO_CDEV_V1
+ case GPIO_GET_LINEHANDLE_IOCTL:
+ return linehandle_create(gdev, ip);
+ case GPIO_GET_LINEEVENT_IOCTL:
+ return lineevent_create(gdev, ip);
+ case GPIO_GET_LINEINFO_IOCTL:
+ return lineinfo_get_v1(cdev, ip, false);
+ case GPIO_GET_LINEINFO_WATCH_IOCTL:
+ return lineinfo_get_v1(cdev, ip, true);
+#endif /* CONFIG_GPIO_CDEV_V1 */
+ case GPIO_V2_GET_LINEINFO_IOCTL:
+ return lineinfo_get(cdev, ip, false);
+ case GPIO_V2_GET_LINEINFO_WATCH_IOCTL:
+ return lineinfo_get(cdev, ip, true);
+ case GPIO_V2_GET_LINE_IOCTL:
+ return linereq_create(gdev, ip);
+ case GPIO_GET_LINEINFO_UNWATCH_IOCTL:
+ return lineinfo_unwatch(cdev, ip);
+ default:
+ return -EINVAL;
+ }
+}
+
+/*
+ * gpio_ioctl() - ioctl handler for the GPIO chardev
+ */
+static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct gpio_chardev_data *cdev = file->private_data;
+
+ return call_ioctl_locked(file, cmd, arg, cdev->gdev,
+ gpio_ioctl_unlocked);
+}
+
+#ifdef CONFIG_COMPAT
+static long gpio_ioctl_compat(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ return gpio_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+static int lineinfo_changed_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct gpio_chardev_data *cdev =
+ container_of(nb, struct gpio_chardev_data, lineinfo_changed_nb);
+ struct gpio_v2_line_info_changed chg;
+ struct gpio_desc *desc = data;
+ int ret;
+
+ if (!test_bit(gpio_chip_hwgpio(desc), cdev->watched_lines))
+ return NOTIFY_DONE;
+
+ memset(&chg, 0, sizeof(chg));
+ chg.event_type = action;
+ chg.timestamp_ns = ktime_get_ns();
+ gpio_desc_to_lineinfo(desc, &chg.info);
+
+ ret = kfifo_in_spinlocked(&cdev->events, &chg, 1, &cdev->wait.lock);
+ if (ret)
+ wake_up_poll(&cdev->wait, EPOLLIN);
+ else
+ pr_debug_ratelimited("lineinfo event FIFO is full - event dropped\n");
+
+ return NOTIFY_OK;
+}
+
+static int gpio_device_unregistered_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct gpio_chardev_data *cdev = container_of(nb,
+ struct gpio_chardev_data,
+ device_unregistered_nb);
+
+ wake_up_poll(&cdev->wait, EPOLLIN | EPOLLERR);
+
+ return NOTIFY_OK;
+}
+
+static __poll_t lineinfo_watch_poll_unlocked(struct file *file,
+ struct poll_table_struct *pollt)
+{
+ struct gpio_chardev_data *cdev = file->private_data;
+ __poll_t events = 0;
+
+ if (!cdev->gdev->chip)
+ return EPOLLHUP | EPOLLERR;
+
+ poll_wait(file, &cdev->wait, pollt);
+
+ if (!kfifo_is_empty_spinlocked_noirqsave(&cdev->events,
+ &cdev->wait.lock))
+ events = EPOLLIN | EPOLLRDNORM;
+
+ return events;
+}
+
+static __poll_t lineinfo_watch_poll(struct file *file,
+ struct poll_table_struct *pollt)
+{
+ struct gpio_chardev_data *cdev = file->private_data;
+
+ return call_poll_locked(file, pollt, cdev->gdev,
+ lineinfo_watch_poll_unlocked);
+}
+
+static ssize_t lineinfo_watch_read_unlocked(struct file *file, char __user *buf,
+ size_t count, loff_t *off)
+{
+ struct gpio_chardev_data *cdev = file->private_data;
+ struct gpio_v2_line_info_changed event;
+ ssize_t bytes_read = 0;
+ int ret;
+ size_t event_size;
+
+ if (!cdev->gdev->chip)
+ return -ENODEV;
+
+#ifndef CONFIG_GPIO_CDEV_V1
+ event_size = sizeof(struct gpio_v2_line_info_changed);
+ if (count < event_size)
+ return -EINVAL;
+#endif
+
+ do {
+ spin_lock(&cdev->wait.lock);
+ if (kfifo_is_empty(&cdev->events)) {
+ if (bytes_read) {
+ spin_unlock(&cdev->wait.lock);
+ return bytes_read;
+ }
+
+ if (file->f_flags & O_NONBLOCK) {
+ spin_unlock(&cdev->wait.lock);
+ return -EAGAIN;
+ }
+
+ ret = wait_event_interruptible_locked(cdev->wait,
+ !kfifo_is_empty(&cdev->events));
+ if (ret) {
+ spin_unlock(&cdev->wait.lock);
+ return ret;
+ }
+ }
+#ifdef CONFIG_GPIO_CDEV_V1
+ /* must be after kfifo check so watch_abi_version is set */
+ if (atomic_read(&cdev->watch_abi_version) == 2)
+ event_size = sizeof(struct gpio_v2_line_info_changed);
+ else
+ event_size = sizeof(struct gpioline_info_changed);
+ if (count < event_size) {
+ spin_unlock(&cdev->wait.lock);
+ return -EINVAL;
+ }
+#endif
+ ret = kfifo_out(&cdev->events, &event, 1);
+ spin_unlock(&cdev->wait.lock);
+ if (ret != 1) {
+ ret = -EIO;
+ break;
+ /* We should never get here. See lineevent_read(). */
+ }
+
+#ifdef CONFIG_GPIO_CDEV_V1
+ if (event_size == sizeof(struct gpio_v2_line_info_changed)) {
+ if (copy_to_user(buf + bytes_read, &event, event_size))
+ return -EFAULT;
+ } else {
+ struct gpioline_info_changed event_v1;
+
+ gpio_v2_line_info_changed_to_v1(&event, &event_v1);
+ if (copy_to_user(buf + bytes_read, &event_v1,
+ event_size))
+ return -EFAULT;
+ }
+#else
+ if (copy_to_user(buf + bytes_read, &event, event_size))
+ return -EFAULT;
+#endif
+ bytes_read += event_size;
+ } while (count >= bytes_read + sizeof(event));
+
+ return bytes_read;
+}
+
+static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
+ size_t count, loff_t *off)
+{
+ struct gpio_chardev_data *cdev = file->private_data;
+
+ return call_read_locked(file, buf, count, off, cdev->gdev,
+ lineinfo_watch_read_unlocked);
+}
+
+/**
+ * gpio_chrdev_open() - open the chardev for ioctl operations
+ * @inode: inode for this chardev
+ * @file: file struct for storing private data
+ * Returns 0 on success
+ */
+static int gpio_chrdev_open(struct inode *inode, struct file *file)
+{
+ struct gpio_device *gdev = container_of(inode->i_cdev,
+ struct gpio_device, chrdev);
+ struct gpio_chardev_data *cdev;
+ int ret = -ENOMEM;
+
+ down_read(&gdev->sem);
+
+ /* Fail on open if the backing gpiochip is gone */
+ if (!gdev->chip) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+
+ cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
+ if (!cdev)
+ goto out_unlock;
+
+ cdev->watched_lines = bitmap_zalloc(gdev->chip->ngpio, GFP_KERNEL);
+ if (!cdev->watched_lines)
+ goto out_free_cdev;
+
+ init_waitqueue_head(&cdev->wait);
+ INIT_KFIFO(cdev->events);
+ cdev->gdev = gpio_device_get(gdev);
+
+ cdev->lineinfo_changed_nb.notifier_call = lineinfo_changed_notify;
+ ret = blocking_notifier_chain_register(&gdev->line_state_notifier,
+ &cdev->lineinfo_changed_nb);
+ if (ret)
+ goto out_free_bitmap;
+
+ cdev->device_unregistered_nb.notifier_call =
+ gpio_device_unregistered_notify;
+ ret = blocking_notifier_chain_register(&gdev->device_notifier,
+ &cdev->device_unregistered_nb);
+ if (ret)
+ goto out_unregister_line_notifier;
+
+ file->private_data = cdev;
+
+ ret = nonseekable_open(inode, file);
+ if (ret)
+ goto out_unregister_device_notifier;
+
+ up_read(&gdev->sem);
+
+ return ret;
+
+out_unregister_device_notifier:
+ blocking_notifier_chain_unregister(&gdev->device_notifier,
+ &cdev->device_unregistered_nb);
+out_unregister_line_notifier:
+ blocking_notifier_chain_unregister(&gdev->line_state_notifier,
+ &cdev->lineinfo_changed_nb);
+out_free_bitmap:
+ gpio_device_put(gdev);
+ bitmap_free(cdev->watched_lines);
+out_free_cdev:
+ kfree(cdev);
+out_unlock:
+ up_read(&gdev->sem);
+ return ret;
+}
+
+/**
+ * gpio_chrdev_release() - close chardev after ioctl operations
+ * @inode: inode for this chardev
+ * @file: file struct for storing private data
+ * Returns 0 on success
+ */
+static int gpio_chrdev_release(struct inode *inode, struct file *file)
+{
+ struct gpio_chardev_data *cdev = file->private_data;
+ struct gpio_device *gdev = cdev->gdev;
+
+ bitmap_free(cdev->watched_lines);
+ blocking_notifier_chain_unregister(&gdev->device_notifier,
+ &cdev->device_unregistered_nb);
+ blocking_notifier_chain_unregister(&gdev->line_state_notifier,
+ &cdev->lineinfo_changed_nb);
+ gpio_device_put(gdev);
+ kfree(cdev);
+
+ return 0;
+}
+
+static const struct file_operations gpio_fileops = {
+ .release = gpio_chrdev_release,
+ .open = gpio_chrdev_open,
+ .poll = lineinfo_watch_poll,
+ .read = lineinfo_watch_read,
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .unlocked_ioctl = gpio_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = gpio_ioctl_compat,
+#endif
+};
+
+int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt)
+{
+ int ret;
+
+ cdev_init(&gdev->chrdev, &gpio_fileops);
+ gdev->chrdev.owner = THIS_MODULE;
+ gdev->dev.devt = MKDEV(MAJOR(devt), gdev->id);
+
+ ret = cdev_device_add(&gdev->chrdev, &gdev->dev);
+ if (ret)
+ return ret;
+
+ chip_dbg(gdev->chip, "added GPIO chardev (%d:%d)\n",
+ MAJOR(devt), gdev->id);
+
+ return 0;
+}
+
+void gpiolib_cdev_unregister(struct gpio_device *gdev)
+{
+ cdev_device_del(&gdev->chrdev, &gdev->dev);
+ blocking_notifier_call_chain(&gdev->device_notifier, 0, NULL);
+}
diff --git a/drivers/gpio/gpiolib-cdev.h b/drivers/gpio/gpiolib-cdev.h
new file mode 100644
index 0000000000..b42644cbff
--- /dev/null
+++ b/drivers/gpio/gpiolib-cdev.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef GPIOLIB_CDEV_H
+#define GPIOLIB_CDEV_H
+
+#include <linux/types.h>
+
+struct gpio_device;
+
+int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt);
+void gpiolib_cdev_unregister(struct gpio_device *gdev);
+
+#endif /* GPIOLIB_CDEV_H */
diff --git a/drivers/gpio/gpiolib-devres.c b/drivers/gpio/gpiolib-devres.c
new file mode 100644
index 0000000000..fe9ce6b19f
--- /dev/null
+++ b/drivers/gpio/gpiolib-devres.c
@@ -0,0 +1,429 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * devres.c - managed gpio resources
+ * This file is based on kernel/irq/devres.c
+ *
+ * Copyright (c) 2011 John Crispin <john@phrozen.org>
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/device.h>
+#include <linux/gfp.h>
+
+#include "gpiolib.h"
+
+static void devm_gpiod_release(struct device *dev, void *res)
+{
+ struct gpio_desc **desc = res;
+
+ gpiod_put(*desc);
+}
+
+static int devm_gpiod_match(struct device *dev, void *res, void *data)
+{
+ struct gpio_desc **this = res, **gpio = data;
+
+ return *this == *gpio;
+}
+
+static void devm_gpiod_release_array(struct device *dev, void *res)
+{
+ struct gpio_descs **descs = res;
+
+ gpiod_put_array(*descs);
+}
+
+static int devm_gpiod_match_array(struct device *dev, void *res, void *data)
+{
+ struct gpio_descs **this = res, **gpios = data;
+
+ return *this == *gpios;
+}
+
+/**
+ * devm_gpiod_get - Resource-managed gpiod_get()
+ * @dev: GPIO consumer
+ * @con_id: function within the GPIO consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * Managed gpiod_get(). GPIO descriptors returned from this function are
+ * automatically disposed on driver detach. See gpiod_get() for detailed
+ * information about behavior and return values.
+ */
+struct gpio_desc *__must_check devm_gpiod_get(struct device *dev,
+ const char *con_id,
+ enum gpiod_flags flags)
+{
+ return devm_gpiod_get_index(dev, con_id, 0, flags);
+}
+EXPORT_SYMBOL_GPL(devm_gpiod_get);
+
+/**
+ * devm_gpiod_get_optional - Resource-managed gpiod_get_optional()
+ * @dev: GPIO consumer
+ * @con_id: function within the GPIO consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * Managed gpiod_get_optional(). GPIO descriptors returned from this function
+ * are automatically disposed on driver detach. See gpiod_get_optional() for
+ * detailed information about behavior and return values.
+ */
+struct gpio_desc *__must_check devm_gpiod_get_optional(struct device *dev,
+ const char *con_id,
+ enum gpiod_flags flags)
+{
+ return devm_gpiod_get_index_optional(dev, con_id, 0, flags);
+}
+EXPORT_SYMBOL_GPL(devm_gpiod_get_optional);
+
+/**
+ * devm_gpiod_get_index - Resource-managed gpiod_get_index()
+ * @dev: GPIO consumer
+ * @con_id: function within the GPIO consumer
+ * @idx: index of the GPIO to obtain in the consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * Managed gpiod_get_index(). GPIO descriptors returned from this function are
+ * automatically disposed on driver detach. See gpiod_get_index() for detailed
+ * information about behavior and return values.
+ */
+struct gpio_desc *__must_check devm_gpiod_get_index(struct device *dev,
+ const char *con_id,
+ unsigned int idx,
+ enum gpiod_flags flags)
+{
+ struct gpio_desc **dr;
+ struct gpio_desc *desc;
+
+ desc = gpiod_get_index(dev, con_id, idx, flags);
+ if (IS_ERR(desc))
+ return desc;
+
+ /*
+ * For non-exclusive GPIO descriptors, check if this descriptor is
+ * already under resource management by this device.
+ */
+ if (flags & GPIOD_FLAGS_BIT_NONEXCLUSIVE) {
+ struct devres *dres;
+
+ dres = devres_find(dev, devm_gpiod_release,
+ devm_gpiod_match, &desc);
+ if (dres)
+ return desc;
+ }
+
+ dr = devres_alloc(devm_gpiod_release, sizeof(struct gpio_desc *),
+ GFP_KERNEL);
+ if (!dr) {
+ gpiod_put(desc);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ *dr = desc;
+ devres_add(dev, dr);
+
+ return desc;
+}
+EXPORT_SYMBOL_GPL(devm_gpiod_get_index);
+
+/**
+ * devm_fwnode_gpiod_get_index - get a GPIO descriptor from a given node
+ * @dev: GPIO consumer
+ * @fwnode: firmware node containing GPIO reference
+ * @con_id: function within the GPIO consumer
+ * @index: index of the GPIO to obtain in the consumer
+ * @flags: GPIO initialization flags
+ * @label: label to attach to the requested GPIO
+ *
+ * GPIO descriptors returned from this function are automatically disposed on
+ * driver detach.
+ *
+ * On successful request the GPIO pin is configured in accordance with
+ * provided @flags.
+ */
+struct gpio_desc *devm_fwnode_gpiod_get_index(struct device *dev,
+ struct fwnode_handle *fwnode,
+ const char *con_id, int index,
+ enum gpiod_flags flags,
+ const char *label)
+{
+ struct gpio_desc **dr;
+ struct gpio_desc *desc;
+
+ dr = devres_alloc(devm_gpiod_release, sizeof(struct gpio_desc *),
+ GFP_KERNEL);
+ if (!dr)
+ return ERR_PTR(-ENOMEM);
+
+ desc = fwnode_gpiod_get_index(fwnode, con_id, index, flags, label);
+ if (IS_ERR(desc)) {
+ devres_free(dr);
+ return desc;
+ }
+
+ *dr = desc;
+ devres_add(dev, dr);
+
+ return desc;
+}
+EXPORT_SYMBOL_GPL(devm_fwnode_gpiod_get_index);
+
+/**
+ * devm_gpiod_get_index_optional - Resource-managed gpiod_get_index_optional()
+ * @dev: GPIO consumer
+ * @con_id: function within the GPIO consumer
+ * @index: index of the GPIO to obtain in the consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * Managed gpiod_get_index_optional(). GPIO descriptors returned from this
+ * function are automatically disposed on driver detach. See
+ * gpiod_get_index_optional() for detailed information about behavior and
+ * return values.
+ */
+struct gpio_desc *__must_check devm_gpiod_get_index_optional(struct device *dev,
+ const char *con_id,
+ unsigned int index,
+ enum gpiod_flags flags)
+{
+ struct gpio_desc *desc;
+
+ desc = devm_gpiod_get_index(dev, con_id, index, flags);
+ if (gpiod_not_found(desc))
+ return NULL;
+
+ return desc;
+}
+EXPORT_SYMBOL_GPL(devm_gpiod_get_index_optional);
+
+/**
+ * devm_gpiod_get_array - Resource-managed gpiod_get_array()
+ * @dev: GPIO consumer
+ * @con_id: function within the GPIO consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * Managed gpiod_get_array(). GPIO descriptors returned from this function are
+ * automatically disposed on driver detach. See gpiod_get_array() for detailed
+ * information about behavior and return values.
+ */
+struct gpio_descs *__must_check devm_gpiod_get_array(struct device *dev,
+ const char *con_id,
+ enum gpiod_flags flags)
+{
+ struct gpio_descs **dr;
+ struct gpio_descs *descs;
+
+ dr = devres_alloc(devm_gpiod_release_array,
+ sizeof(struct gpio_descs *), GFP_KERNEL);
+ if (!dr)
+ return ERR_PTR(-ENOMEM);
+
+ descs = gpiod_get_array(dev, con_id, flags);
+ if (IS_ERR(descs)) {
+ devres_free(dr);
+ return descs;
+ }
+
+ *dr = descs;
+ devres_add(dev, dr);
+
+ return descs;
+}
+EXPORT_SYMBOL_GPL(devm_gpiod_get_array);
+
+/**
+ * devm_gpiod_get_array_optional - Resource-managed gpiod_get_array_optional()
+ * @dev: GPIO consumer
+ * @con_id: function within the GPIO consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * Managed gpiod_get_array_optional(). GPIO descriptors returned from this
+ * function are automatically disposed on driver detach.
+ * See gpiod_get_array_optional() for detailed information about behavior and
+ * return values.
+ */
+struct gpio_descs *__must_check
+devm_gpiod_get_array_optional(struct device *dev, const char *con_id,
+ enum gpiod_flags flags)
+{
+ struct gpio_descs *descs;
+
+ descs = devm_gpiod_get_array(dev, con_id, flags);
+ if (gpiod_not_found(descs))
+ return NULL;
+
+ return descs;
+}
+EXPORT_SYMBOL_GPL(devm_gpiod_get_array_optional);
+
+/**
+ * devm_gpiod_put - Resource-managed gpiod_put()
+ * @dev: GPIO consumer
+ * @desc: GPIO descriptor to dispose of
+ *
+ * Dispose of a GPIO descriptor obtained with devm_gpiod_get() or
+ * devm_gpiod_get_index(). Normally this function will not be called as the GPIO
+ * will be disposed of by the resource management code.
+ */
+void devm_gpiod_put(struct device *dev, struct gpio_desc *desc)
+{
+ WARN_ON(devres_release(dev, devm_gpiod_release, devm_gpiod_match,
+ &desc));
+}
+EXPORT_SYMBOL_GPL(devm_gpiod_put);
+
+/**
+ * devm_gpiod_unhinge - Remove resource management from a gpio descriptor
+ * @dev: GPIO consumer
+ * @desc: GPIO descriptor to remove resource management from
+ *
+ * Remove resource management from a GPIO descriptor. This is needed when
+ * you want to hand over lifecycle management of a descriptor to another
+ * mechanism.
+ */
+
+void devm_gpiod_unhinge(struct device *dev, struct gpio_desc *desc)
+{
+ int ret;
+
+ if (IS_ERR_OR_NULL(desc))
+ return;
+ ret = devres_destroy(dev, devm_gpiod_release,
+ devm_gpiod_match, &desc);
+ /*
+ * If the GPIO descriptor is requested as nonexclusive, we
+ * may call this function several times on the same descriptor
+ * so it is OK if devres_destroy() returns -ENOENT.
+ */
+ if (ret == -ENOENT)
+ return;
+ /* Anything else we should warn about */
+ WARN_ON(ret);
+}
+EXPORT_SYMBOL_GPL(devm_gpiod_unhinge);
+
+/**
+ * devm_gpiod_put_array - Resource-managed gpiod_put_array()
+ * @dev: GPIO consumer
+ * @descs: GPIO descriptor array to dispose of
+ *
+ * Dispose of an array of GPIO descriptors obtained with devm_gpiod_get_array().
+ * Normally this function will not be called as the GPIOs will be disposed of
+ * by the resource management code.
+ */
+void devm_gpiod_put_array(struct device *dev, struct gpio_descs *descs)
+{
+ WARN_ON(devres_release(dev, devm_gpiod_release_array,
+ devm_gpiod_match_array, &descs));
+}
+EXPORT_SYMBOL_GPL(devm_gpiod_put_array);
+
+static void devm_gpio_release(struct device *dev, void *res)
+{
+ unsigned *gpio = res;
+
+ gpio_free(*gpio);
+}
+
+/**
+ * devm_gpio_request - request a GPIO for a managed device
+ * @dev: device to request the GPIO for
+ * @gpio: GPIO to allocate
+ * @label: the name of the requested GPIO
+ *
+ * Except for the extra @dev argument, this function takes the
+ * same arguments and performs the same function as
+ * gpio_request(). GPIOs requested with this function will be
+ * automatically freed on driver detach.
+ */
+int devm_gpio_request(struct device *dev, unsigned gpio, const char *label)
+{
+ unsigned *dr;
+ int rc;
+
+ dr = devres_alloc(devm_gpio_release, sizeof(unsigned), GFP_KERNEL);
+ if (!dr)
+ return -ENOMEM;
+
+ rc = gpio_request(gpio, label);
+ if (rc) {
+ devres_free(dr);
+ return rc;
+ }
+
+ *dr = gpio;
+ devres_add(dev, dr);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devm_gpio_request);
+
+/**
+ * devm_gpio_request_one - request a single GPIO with initial setup
+ * @dev: device to request for
+ * @gpio: the GPIO number
+ * @flags: GPIO configuration as specified by GPIOF_*
+ * @label: a literal description string of this GPIO
+ */
+int devm_gpio_request_one(struct device *dev, unsigned gpio,
+ unsigned long flags, const char *label)
+{
+ unsigned *dr;
+ int rc;
+
+ dr = devres_alloc(devm_gpio_release, sizeof(unsigned), GFP_KERNEL);
+ if (!dr)
+ return -ENOMEM;
+
+ rc = gpio_request_one(gpio, flags, label);
+ if (rc) {
+ devres_free(dr);
+ return rc;
+ }
+
+ *dr = gpio;
+ devres_add(dev, dr);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devm_gpio_request_one);
+
+static void devm_gpio_chip_release(void *data)
+{
+ struct gpio_chip *gc = data;
+
+ gpiochip_remove(gc);
+}
+
+/**
+ * devm_gpiochip_add_data_with_key() - Resource managed gpiochip_add_data_with_key()
+ * @dev: pointer to the device that gpio_chip belongs to.
+ * @gc: the GPIO chip to register
+ * @data: driver-private data associated with this chip
+ * @lock_key: lockdep class for IRQ lock
+ * @request_key: lockdep class for IRQ request
+ *
+ * Context: potentially before irqs will work
+ *
+ * The gpio chip automatically be released when the device is unbound.
+ *
+ * Returns:
+ * A negative errno if the chip can't be registered, such as because the
+ * gc->base is invalid or already associated with a different chip.
+ * Otherwise it returns zero as a success code.
+ */
+int devm_gpiochip_add_data_with_key(struct device *dev, struct gpio_chip *gc, void *data,
+ struct lock_class_key *lock_key,
+ struct lock_class_key *request_key)
+{
+ int ret;
+
+ ret = gpiochip_add_data_with_key(gc, data, lock_key, request_key);
+ if (ret < 0)
+ return ret;
+
+ return devm_add_action_or_reset(dev, devm_gpio_chip_release, gc);
+}
+EXPORT_SYMBOL_GPL(devm_gpiochip_add_data_with_key);
diff --git a/drivers/gpio/gpiolib-legacy.c b/drivers/gpio/gpiolib-legacy.c
new file mode 100644
index 0000000000..97f4b498e3
--- /dev/null
+++ b/drivers/gpio/gpiolib-legacy.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+
+#include <linux/gpio.h>
+
+#include "gpiolib.h"
+
+void gpio_free(unsigned gpio)
+{
+ gpiod_free(gpio_to_desc(gpio));
+}
+EXPORT_SYMBOL_GPL(gpio_free);
+
+/**
+ * gpio_request_one - request a single GPIO with initial configuration
+ * @gpio: the GPIO number
+ * @flags: GPIO configuration as specified by GPIOF_*
+ * @label: a literal description string of this GPIO
+ */
+int gpio_request_one(unsigned gpio, unsigned long flags, const char *label)
+{
+ struct gpio_desc *desc;
+ int err;
+
+ desc = gpio_to_desc(gpio);
+
+ /* Compatibility: assume unavailable "valid" GPIOs will appear later */
+ if (!desc && gpio_is_valid(gpio))
+ return -EPROBE_DEFER;
+
+ err = gpiod_request(desc, label);
+ if (err)
+ return err;
+
+ if (flags & GPIOF_ACTIVE_LOW)
+ set_bit(FLAG_ACTIVE_LOW, &desc->flags);
+
+ if (flags & GPIOF_DIR_IN)
+ err = gpiod_direction_input(desc);
+ else
+ err = gpiod_direction_output_raw(desc,
+ (flags & GPIOF_INIT_HIGH) ? 1 : 0);
+
+ if (err)
+ goto free_gpio;
+
+ return 0;
+
+ free_gpio:
+ gpiod_free(desc);
+ return err;
+}
+EXPORT_SYMBOL_GPL(gpio_request_one);
+
+int gpio_request(unsigned gpio, const char *label)
+{
+ struct gpio_desc *desc = gpio_to_desc(gpio);
+
+ /* Compatibility: assume unavailable "valid" GPIOs will appear later */
+ if (!desc && gpio_is_valid(gpio))
+ return -EPROBE_DEFER;
+
+ return gpiod_request(desc, label);
+}
+EXPORT_SYMBOL_GPL(gpio_request);
+
+/**
+ * gpio_request_array - request multiple GPIOs in a single call
+ * @array: array of the 'struct gpio'
+ * @num: how many GPIOs in the array
+ */
+int gpio_request_array(const struct gpio *array, size_t num)
+{
+ int i, err;
+
+ for (i = 0; i < num; i++, array++) {
+ err = gpio_request_one(array->gpio, array->flags, array->label);
+ if (err)
+ goto err_free;
+ }
+ return 0;
+
+err_free:
+ while (i--)
+ gpio_free((--array)->gpio);
+ return err;
+}
+EXPORT_SYMBOL_GPL(gpio_request_array);
+
+/**
+ * gpio_free_array - release multiple GPIOs in a single call
+ * @array: array of the 'struct gpio'
+ * @num: how many GPIOs in the array
+ */
+void gpio_free_array(const struct gpio *array, size_t num)
+{
+ while (num--)
+ gpio_free((array++)->gpio);
+}
+EXPORT_SYMBOL_GPL(gpio_free_array);
diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c
new file mode 100644
index 0000000000..d9525d95e8
--- /dev/null
+++ b/drivers/gpio/gpiolib-of.c
@@ -0,0 +1,1113 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * OF helpers for the GPIO API
+ *
+ * Copyright (c) 2007-2008 MontaVista Software, Inc.
+ *
+ * Author: Anton Vorontsov <avorontsov@ru.mvista.com>
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/machine.h>
+
+#include "gpiolib.h"
+#include "gpiolib-of.h"
+
+/*
+ * This is Linux-specific flags. By default controllers' and Linux' mapping
+ * match, but GPIO controllers are free to translate their own flags to
+ * Linux-specific in their .xlate callback. Though, 1:1 mapping is recommended.
+ */
+enum of_gpio_flags {
+ OF_GPIO_ACTIVE_LOW = 0x1,
+ OF_GPIO_SINGLE_ENDED = 0x2,
+ OF_GPIO_OPEN_DRAIN = 0x4,
+ OF_GPIO_TRANSITORY = 0x8,
+ OF_GPIO_PULL_UP = 0x10,
+ OF_GPIO_PULL_DOWN = 0x20,
+ OF_GPIO_PULL_DISABLE = 0x40,
+};
+
+/**
+ * of_gpio_named_count() - Count GPIOs for a device
+ * @np: device node to count GPIOs for
+ * @propname: property name containing gpio specifier(s)
+ *
+ * The function returns the count of GPIOs specified for a node.
+ * Note that the empty GPIO specifiers count too. Returns either
+ * Number of gpios defined in property,
+ * -EINVAL for an incorrectly formed gpios property, or
+ * -ENOENT for a missing gpios property
+ *
+ * Example:
+ * gpios = <0
+ * &gpio1 1 2
+ * 0
+ * &gpio2 3 4>;
+ *
+ * The above example defines four GPIOs, two of which are not specified.
+ * This function will return '4'
+ */
+static int of_gpio_named_count(const struct device_node *np,
+ const char *propname)
+{
+ return of_count_phandle_with_args(np, propname, "#gpio-cells");
+}
+
+/**
+ * of_gpio_spi_cs_get_count() - special GPIO counting for SPI
+ * @dev: Consuming device
+ * @con_id: Function within the GPIO consumer
+ *
+ * Some elder GPIO controllers need special quirks. Currently we handle
+ * the Freescale and PPC GPIO controller with bindings that doesn't use the
+ * established "cs-gpios" for chip selects but instead rely on
+ * "gpios" for the chip select lines. If we detect this, we redirect
+ * the counting of "cs-gpios" to count "gpios" transparent to the
+ * driver.
+ */
+static int of_gpio_spi_cs_get_count(struct device *dev, const char *con_id)
+{
+ struct device_node *np = dev->of_node;
+
+ if (!IS_ENABLED(CONFIG_SPI_MASTER))
+ return 0;
+ if (!con_id || strcmp(con_id, "cs"))
+ return 0;
+ if (!of_device_is_compatible(np, "fsl,spi") &&
+ !of_device_is_compatible(np, "aeroflexgaisler,spictrl") &&
+ !of_device_is_compatible(np, "ibm,ppc4xx-spi"))
+ return 0;
+ return of_gpio_named_count(np, "gpios");
+}
+
+int of_gpio_get_count(struct device *dev, const char *con_id)
+{
+ int ret;
+ char propname[32];
+ unsigned int i;
+
+ ret = of_gpio_spi_cs_get_count(dev, con_id);
+ if (ret > 0)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {
+ if (con_id)
+ snprintf(propname, sizeof(propname), "%s-%s",
+ con_id, gpio_suffixes[i]);
+ else
+ snprintf(propname, sizeof(propname), "%s",
+ gpio_suffixes[i]);
+
+ ret = of_gpio_named_count(dev->of_node, propname);
+ if (ret > 0)
+ break;
+ }
+ return ret ? ret : -ENOENT;
+}
+
+static int of_gpiochip_match_node_and_xlate(struct gpio_chip *chip, void *data)
+{
+ struct of_phandle_args *gpiospec = data;
+
+ return device_match_of_node(&chip->gpiodev->dev, gpiospec->np) &&
+ chip->of_xlate &&
+ chip->of_xlate(chip, gpiospec, NULL) >= 0;
+}
+
+static struct gpio_chip *of_find_gpiochip_by_xlate(
+ struct of_phandle_args *gpiospec)
+{
+ return gpiochip_find(gpiospec, of_gpiochip_match_node_and_xlate);
+}
+
+static struct gpio_desc *of_xlate_and_get_gpiod_flags(struct gpio_chip *chip,
+ struct of_phandle_args *gpiospec,
+ enum of_gpio_flags *flags)
+{
+ int ret;
+
+ if (chip->of_gpio_n_cells != gpiospec->args_count)
+ return ERR_PTR(-EINVAL);
+
+ ret = chip->of_xlate(chip, gpiospec, flags);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ return gpiochip_get_desc(chip, ret);
+}
+
+/*
+ * Overrides stated polarity of a gpio line and warns when there is a
+ * discrepancy.
+ */
+static void of_gpio_quirk_polarity(const struct device_node *np,
+ bool active_high,
+ enum of_gpio_flags *flags)
+{
+ if (active_high) {
+ if (*flags & OF_GPIO_ACTIVE_LOW) {
+ pr_warn("%s GPIO handle specifies active low - ignored\n",
+ of_node_full_name(np));
+ *flags &= ~OF_GPIO_ACTIVE_LOW;
+ }
+ } else {
+ if (!(*flags & OF_GPIO_ACTIVE_LOW))
+ pr_info("%s enforce active low on GPIO handle\n",
+ of_node_full_name(np));
+ *flags |= OF_GPIO_ACTIVE_LOW;
+ }
+}
+
+/*
+ * This quirk does static polarity overrides in cases where existing
+ * DTS specified incorrect polarity.
+ */
+static void of_gpio_try_fixup_polarity(const struct device_node *np,
+ const char *propname,
+ enum of_gpio_flags *flags)
+{
+ static const struct {
+ const char *compatible;
+ const char *propname;
+ bool active_high;
+ } gpios[] = {
+#if !IS_ENABLED(CONFIG_LCD_HX8357)
+ /*
+ * Himax LCD controllers used incorrectly named
+ * "gpios-reset" property and also specified wrong
+ * polarity.
+ */
+ { "himax,hx8357", "gpios-reset", false },
+ { "himax,hx8369", "gpios-reset", false },
+#endif
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(gpios); i++) {
+ if (of_device_is_compatible(np, gpios[i].compatible) &&
+ !strcmp(propname, gpios[i].propname)) {
+ of_gpio_quirk_polarity(np, gpios[i].active_high, flags);
+ break;
+ }
+ }
+}
+
+static void of_gpio_set_polarity_by_property(const struct device_node *np,
+ const char *propname,
+ enum of_gpio_flags *flags)
+{
+ const struct device_node *np_compat = np;
+ const struct device_node *np_propname = np;
+ static const struct {
+ const char *compatible;
+ const char *gpio_propname;
+ const char *polarity_propname;
+ } gpios[] = {
+#if IS_ENABLED(CONFIG_FEC)
+ /* Freescale Fast Ethernet Controller */
+ { "fsl,imx25-fec", "phy-reset-gpios", "phy-reset-active-high" },
+ { "fsl,imx27-fec", "phy-reset-gpios", "phy-reset-active-high" },
+ { "fsl,imx28-fec", "phy-reset-gpios", "phy-reset-active-high" },
+ { "fsl,imx6q-fec", "phy-reset-gpios", "phy-reset-active-high" },
+ { "fsl,mvf600-fec", "phy-reset-gpios", "phy-reset-active-high" },
+ { "fsl,imx6sx-fec", "phy-reset-gpios", "phy-reset-active-high" },
+ { "fsl,imx6ul-fec", "phy-reset-gpios", "phy-reset-active-high" },
+ { "fsl,imx8mq-fec", "phy-reset-gpios", "phy-reset-active-high" },
+ { "fsl,imx8qm-fec", "phy-reset-gpios", "phy-reset-active-high" },
+ { "fsl,s32v234-fec", "phy-reset-gpios", "phy-reset-active-high" },
+#endif
+#if IS_ENABLED(CONFIG_PCI_IMX6)
+ { "fsl,imx6q-pcie", "reset-gpio", "reset-gpio-active-high" },
+ { "fsl,imx6sx-pcie", "reset-gpio", "reset-gpio-active-high" },
+ { "fsl,imx6qp-pcie", "reset-gpio", "reset-gpio-active-high" },
+ { "fsl,imx7d-pcie", "reset-gpio", "reset-gpio-active-high" },
+ { "fsl,imx8mq-pcie", "reset-gpio", "reset-gpio-active-high" },
+ { "fsl,imx8mm-pcie", "reset-gpio", "reset-gpio-active-high" },
+ { "fsl,imx8mp-pcie", "reset-gpio", "reset-gpio-active-high" },
+#endif
+
+ /*
+ * The regulator GPIO handles are specified such that the
+ * presence or absence of "enable-active-high" solely controls
+ * the polarity of the GPIO line. Any phandle flags must
+ * be actively ignored.
+ */
+#if IS_ENABLED(CONFIG_REGULATOR_FIXED_VOLTAGE)
+ { "regulator-fixed", "gpios", "enable-active-high" },
+ { "regulator-fixed", "gpio", "enable-active-high" },
+ { "reg-fixed-voltage", "gpios", "enable-active-high" },
+ { "reg-fixed-voltage", "gpio", "enable-active-high" },
+#endif
+#if IS_ENABLED(CONFIG_REGULATOR_GPIO)
+ { "regulator-gpio", "enable-gpio", "enable-active-high" },
+ { "regulator-gpio", "enable-gpios", "enable-active-high" },
+#endif
+#if IS_ENABLED(CONFIG_MMC_ATMELMCI)
+ { "atmel,hsmci", "cd-gpios", "cd-inverted" },
+#endif
+ };
+ unsigned int i;
+ bool active_high;
+
+#if IS_ENABLED(CONFIG_MMC_ATMELMCI)
+ /*
+ * The Atmel HSMCI has compatible property in the parent node and
+ * gpio property in a child node
+ */
+ if (of_device_is_compatible(np->parent, "atmel,hsmci")) {
+ np_compat = np->parent;
+ np_propname = np;
+ }
+#endif
+
+ for (i = 0; i < ARRAY_SIZE(gpios); i++) {
+ if (of_device_is_compatible(np_compat, gpios[i].compatible) &&
+ !strcmp(propname, gpios[i].gpio_propname)) {
+ active_high = of_property_read_bool(np_propname,
+ gpios[i].polarity_propname);
+ of_gpio_quirk_polarity(np, active_high, flags);
+ break;
+ }
+ }
+}
+
+static void of_gpio_flags_quirks(const struct device_node *np,
+ const char *propname,
+ enum of_gpio_flags *flags,
+ int index)
+{
+ of_gpio_try_fixup_polarity(np, propname, flags);
+ of_gpio_set_polarity_by_property(np, propname, flags);
+
+ /*
+ * Legacy open drain handling for fixed voltage regulators.
+ */
+ if (IS_ENABLED(CONFIG_REGULATOR) &&
+ of_device_is_compatible(np, "reg-fixed-voltage") &&
+ of_property_read_bool(np, "gpio-open-drain")) {
+ *flags |= (OF_GPIO_SINGLE_ENDED | OF_GPIO_OPEN_DRAIN);
+ pr_info("%s uses legacy open drain flag - update the DTS if you can\n",
+ of_node_full_name(np));
+ }
+
+ /*
+ * Legacy handling of SPI active high chip select. If we have a
+ * property named "cs-gpios" we need to inspect the child node
+ * to determine if the flags should have inverted semantics.
+ */
+ if (IS_ENABLED(CONFIG_SPI_MASTER) && !strcmp(propname, "cs-gpios") &&
+ of_property_read_bool(np, "cs-gpios")) {
+ struct device_node *child;
+ u32 cs;
+ int ret;
+
+ for_each_child_of_node(np, child) {
+ ret = of_property_read_u32(child, "reg", &cs);
+ if (ret)
+ continue;
+ if (cs == index) {
+ /*
+ * SPI children have active low chip selects
+ * by default. This can be specified negatively
+ * by just omitting "spi-cs-high" in the
+ * device node, or actively by tagging on
+ * GPIO_ACTIVE_LOW as flag in the device
+ * tree. If the line is simultaneously
+ * tagged as active low in the device tree
+ * and has the "spi-cs-high" set, we get a
+ * conflict and the "spi-cs-high" flag will
+ * take precedence.
+ */
+ bool active_high = of_property_read_bool(child,
+ "spi-cs-high");
+ of_gpio_quirk_polarity(child, active_high,
+ flags);
+ of_node_put(child);
+ break;
+ }
+ }
+ }
+
+ /* Legacy handling of stmmac's active-low PHY reset line */
+ if (IS_ENABLED(CONFIG_STMMAC_ETH) &&
+ !strcmp(propname, "snps,reset-gpio") &&
+ of_property_read_bool(np, "snps,reset-active-low"))
+ *flags |= OF_GPIO_ACTIVE_LOW;
+}
+
+/**
+ * of_get_named_gpiod_flags() - Get a GPIO descriptor and flags for GPIO API
+ * @np: device node to get GPIO from
+ * @propname: property name containing gpio specifier(s)
+ * @index: index of the GPIO
+ * @flags: a flags pointer to fill in
+ *
+ * Returns GPIO descriptor to use with Linux GPIO API, or one of the errno
+ * value on the error condition. If @flags is not NULL the function also fills
+ * in flags for the GPIO.
+ */
+static struct gpio_desc *of_get_named_gpiod_flags(const struct device_node *np,
+ const char *propname, int index, enum of_gpio_flags *flags)
+{
+ struct of_phandle_args gpiospec;
+ struct gpio_chip *chip;
+ struct gpio_desc *desc;
+ int ret;
+
+ ret = of_parse_phandle_with_args_map(np, propname, "gpio", index,
+ &gpiospec);
+ if (ret) {
+ pr_debug("%s: can't parse '%s' property of node '%pOF[%d]'\n",
+ __func__, propname, np, index);
+ return ERR_PTR(ret);
+ }
+
+ chip = of_find_gpiochip_by_xlate(&gpiospec);
+ if (!chip) {
+ desc = ERR_PTR(-EPROBE_DEFER);
+ goto out;
+ }
+
+ desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, flags);
+ if (IS_ERR(desc))
+ goto out;
+
+ if (flags)
+ of_gpio_flags_quirks(np, propname, flags, index);
+
+ pr_debug("%s: parsed '%s' property of node '%pOF[%d]' - status (%d)\n",
+ __func__, propname, np, index,
+ PTR_ERR_OR_ZERO(desc));
+
+out:
+ of_node_put(gpiospec.np);
+
+ return desc;
+}
+
+/**
+ * of_get_named_gpio() - Get a GPIO number to use with GPIO API
+ * @np: device node to get GPIO from
+ * @propname: Name of property containing gpio specifier(s)
+ * @index: index of the GPIO
+ *
+ * Returns GPIO number to use with Linux generic GPIO API, or one of the errno
+ * value on the error condition.
+ */
+int of_get_named_gpio(const struct device_node *np, const char *propname,
+ int index)
+{
+ struct gpio_desc *desc;
+
+ desc = of_get_named_gpiod_flags(np, propname, index, NULL);
+
+ if (IS_ERR(desc))
+ return PTR_ERR(desc);
+ else
+ return desc_to_gpio(desc);
+}
+EXPORT_SYMBOL_GPL(of_get_named_gpio);
+
+/* Converts gpio_lookup_flags into bitmask of GPIO_* values */
+static unsigned long of_convert_gpio_flags(enum of_gpio_flags flags)
+{
+ unsigned long lflags = GPIO_LOOKUP_FLAGS_DEFAULT;
+
+ if (flags & OF_GPIO_ACTIVE_LOW)
+ lflags |= GPIO_ACTIVE_LOW;
+
+ if (flags & OF_GPIO_SINGLE_ENDED) {
+ if (flags & OF_GPIO_OPEN_DRAIN)
+ lflags |= GPIO_OPEN_DRAIN;
+ else
+ lflags |= GPIO_OPEN_SOURCE;
+ }
+
+ if (flags & OF_GPIO_TRANSITORY)
+ lflags |= GPIO_TRANSITORY;
+
+ if (flags & OF_GPIO_PULL_UP)
+ lflags |= GPIO_PULL_UP;
+
+ if (flags & OF_GPIO_PULL_DOWN)
+ lflags |= GPIO_PULL_DOWN;
+
+ if (flags & OF_GPIO_PULL_DISABLE)
+ lflags |= GPIO_PULL_DISABLE;
+
+ return lflags;
+}
+
+static struct gpio_desc *of_find_gpio_rename(struct device_node *np,
+ const char *con_id,
+ unsigned int idx,
+ enum of_gpio_flags *of_flags)
+{
+ static const struct of_rename_gpio {
+ const char *con_id;
+ const char *legacy_id; /* NULL - same as con_id */
+ /*
+ * Compatible string can be set to NULL in case where
+ * matching to a particular compatible is not practical,
+ * but it should only be done for gpio names that have
+ * vendor prefix to reduce risk of false positives.
+ * Addition of such entries is strongly discouraged.
+ */
+ const char *compatible;
+ } gpios[] = {
+#if !IS_ENABLED(CONFIG_LCD_HX8357)
+ /* Himax LCD controllers used "gpios-reset" */
+ { "reset", "gpios-reset", "himax,hx8357" },
+ { "reset", "gpios-reset", "himax,hx8369" },
+#endif
+#if IS_ENABLED(CONFIG_MFD_ARIZONA)
+ { "wlf,reset", NULL, NULL },
+#endif
+#if IS_ENABLED(CONFIG_RTC_DRV_MOXART)
+ { "rtc-data", "gpio-rtc-data", "moxa,moxart-rtc" },
+ { "rtc-sclk", "gpio-rtc-sclk", "moxa,moxart-rtc" },
+ { "rtc-reset", "gpio-rtc-reset", "moxa,moxart-rtc" },
+#endif
+#if IS_ENABLED(CONFIG_NFC_MRVL_I2C)
+ { "reset", "reset-n-io", "marvell,nfc-i2c" },
+#endif
+#if IS_ENABLED(CONFIG_NFC_MRVL_SPI)
+ { "reset", "reset-n-io", "marvell,nfc-spi" },
+#endif
+#if IS_ENABLED(CONFIG_NFC_MRVL_UART)
+ { "reset", "reset-n-io", "marvell,nfc-uart" },
+ { "reset", "reset-n-io", "mrvl,nfc-uart" },
+#endif
+#if !IS_ENABLED(CONFIG_PCI_LANTIQ)
+ /* MIPS Lantiq PCI */
+ { "reset", "gpios-reset", "lantiq,pci-xway" },
+#endif
+
+ /*
+ * Some regulator bindings happened before we managed to
+ * establish that GPIO properties should be named
+ * "foo-gpios" so we have this special kludge for them.
+ */
+#if IS_ENABLED(CONFIG_REGULATOR_ARIZONA_LDO1)
+ { "wlf,ldoena", NULL, NULL }, /* Arizona */
+#endif
+#if IS_ENABLED(CONFIG_REGULATOR_WM8994)
+ { "wlf,ldo1ena", NULL, NULL }, /* WM8994 */
+ { "wlf,ldo2ena", NULL, NULL }, /* WM8994 */
+#endif
+
+#if IS_ENABLED(CONFIG_SND_SOC_CS42L56)
+ { "reset", "cirrus,gpio-nreset", "cirrus,cs42l56" },
+#endif
+#if IS_ENABLED(CONFIG_SND_SOC_MT2701_CS42448)
+ { "i2s1-in-sel-gpio1", NULL, "mediatek,mt2701-cs42448-machine" },
+ { "i2s1-in-sel-gpio2", NULL, "mediatek,mt2701-cs42448-machine" },
+#endif
+#if IS_ENABLED(CONFIG_SND_SOC_TLV320AIC3X)
+ { "reset", "gpio-reset", "ti,tlv320aic3x" },
+ { "reset", "gpio-reset", "ti,tlv320aic33" },
+ { "reset", "gpio-reset", "ti,tlv320aic3007" },
+ { "reset", "gpio-reset", "ti,tlv320aic3104" },
+ { "reset", "gpio-reset", "ti,tlv320aic3106" },
+#endif
+#if IS_ENABLED(CONFIG_SPI_GPIO)
+ /*
+ * The SPI GPIO bindings happened before we managed to
+ * establish that GPIO properties should be named
+ * "foo-gpios" so we have this special kludge for them.
+ */
+ { "miso", "gpio-miso", "spi-gpio" },
+ { "mosi", "gpio-mosi", "spi-gpio" },
+ { "sck", "gpio-sck", "spi-gpio" },
+#endif
+
+ /*
+ * The old Freescale bindings use simply "gpios" as name
+ * for the chip select lines rather than "cs-gpios" like
+ * all other SPI hardware. Allow this specifically for
+ * Freescale and PPC devices.
+ */
+#if IS_ENABLED(CONFIG_SPI_FSL_SPI)
+ { "cs", "gpios", "fsl,spi" },
+ { "cs", "gpios", "aeroflexgaisler,spictrl" },
+#endif
+#if IS_ENABLED(CONFIG_SPI_PPC4xx)
+ { "cs", "gpios", "ibm,ppc4xx-spi" },
+#endif
+
+#if IS_ENABLED(CONFIG_TYPEC_FUSB302)
+ /*
+ * Fairchild FUSB302 host is using undocumented "fcs,int_n"
+ * property without the compulsory "-gpios" suffix.
+ */
+ { "fcs,int_n", NULL, "fcs,fusb302" },
+#endif
+ };
+ struct gpio_desc *desc;
+ const char *legacy_id;
+ unsigned int i;
+
+ if (!con_id)
+ return ERR_PTR(-ENOENT);
+
+ for (i = 0; i < ARRAY_SIZE(gpios); i++) {
+ if (strcmp(con_id, gpios[i].con_id))
+ continue;
+
+ if (gpios[i].compatible &&
+ !of_device_is_compatible(np, gpios[i].compatible))
+ continue;
+
+ legacy_id = gpios[i].legacy_id ?: gpios[i].con_id;
+ desc = of_get_named_gpiod_flags(np, legacy_id, idx, of_flags);
+ if (!gpiod_not_found(desc)) {
+ pr_info("%s uses legacy gpio name '%s' instead of '%s-gpios'\n",
+ of_node_full_name(np), legacy_id, con_id);
+ return desc;
+ }
+ }
+
+ return ERR_PTR(-ENOENT);
+}
+
+static struct gpio_desc *of_find_mt2701_gpio(struct device_node *np,
+ const char *con_id,
+ unsigned int idx,
+ enum of_gpio_flags *of_flags)
+{
+ struct gpio_desc *desc;
+ const char *legacy_id;
+
+ if (!IS_ENABLED(CONFIG_SND_SOC_MT2701_CS42448))
+ return ERR_PTR(-ENOENT);
+
+ if (!of_device_is_compatible(np, "mediatek,mt2701-cs42448-machine"))
+ return ERR_PTR(-ENOENT);
+
+ if (!con_id || strcmp(con_id, "i2s1-in-sel"))
+ return ERR_PTR(-ENOENT);
+
+ if (idx == 0)
+ legacy_id = "i2s1-in-sel-gpio1";
+ else if (idx == 1)
+ legacy_id = "i2s1-in-sel-gpio2";
+ else
+ return ERR_PTR(-ENOENT);
+
+ desc = of_get_named_gpiod_flags(np, legacy_id, 0, of_flags);
+ if (!gpiod_not_found(desc))
+ pr_info("%s is using legacy gpio name '%s' instead of '%s-gpios'\n",
+ of_node_full_name(np), legacy_id, con_id);
+
+ return desc;
+}
+
+typedef struct gpio_desc *(*of_find_gpio_quirk)(struct device_node *np,
+ const char *con_id,
+ unsigned int idx,
+ enum of_gpio_flags *of_flags);
+static const of_find_gpio_quirk of_find_gpio_quirks[] = {
+ of_find_gpio_rename,
+ of_find_mt2701_gpio,
+ NULL
+};
+
+struct gpio_desc *of_find_gpio(struct device_node *np, const char *con_id,
+ unsigned int idx, unsigned long *flags)
+{
+ char prop_name[32]; /* 32 is max size of property name */
+ enum of_gpio_flags of_flags;
+ const of_find_gpio_quirk *q;
+ struct gpio_desc *desc;
+ unsigned int i;
+
+ /* Try GPIO property "foo-gpios" and "foo-gpio" */
+ for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {
+ if (con_id)
+ snprintf(prop_name, sizeof(prop_name), "%s-%s", con_id,
+ gpio_suffixes[i]);
+ else
+ snprintf(prop_name, sizeof(prop_name), "%s",
+ gpio_suffixes[i]);
+
+ desc = of_get_named_gpiod_flags(np, prop_name, idx, &of_flags);
+
+ if (!gpiod_not_found(desc))
+ break;
+ }
+
+ /* Properly named GPIO was not found, try workarounds */
+ for (q = of_find_gpio_quirks; gpiod_not_found(desc) && *q; q++)
+ desc = (*q)(np, con_id, idx, &of_flags);
+
+ if (IS_ERR(desc))
+ return desc;
+
+ *flags = of_convert_gpio_flags(of_flags);
+
+ return desc;
+}
+
+/**
+ * of_parse_own_gpio() - Get a GPIO hog descriptor, names and flags for GPIO API
+ * @np: device node to get GPIO from
+ * @chip: GPIO chip whose hog is parsed
+ * @idx: Index of the GPIO to parse
+ * @name: GPIO line name
+ * @lflags: bitmask of gpio_lookup_flags GPIO_* values - returned from
+ * of_find_gpio() or of_parse_own_gpio()
+ * @dflags: gpiod_flags - optional GPIO initialization flags
+ *
+ * Returns GPIO descriptor to use with Linux GPIO API, or one of the errno
+ * value on the error condition.
+ */
+static struct gpio_desc *of_parse_own_gpio(struct device_node *np,
+ struct gpio_chip *chip,
+ unsigned int idx, const char **name,
+ unsigned long *lflags,
+ enum gpiod_flags *dflags)
+{
+ struct device_node *chip_np;
+ enum of_gpio_flags xlate_flags;
+ struct of_phandle_args gpiospec;
+ struct gpio_desc *desc;
+ unsigned int i;
+ u32 tmp;
+ int ret;
+
+ chip_np = dev_of_node(&chip->gpiodev->dev);
+ if (!chip_np)
+ return ERR_PTR(-EINVAL);
+
+ xlate_flags = 0;
+ *lflags = GPIO_LOOKUP_FLAGS_DEFAULT;
+ *dflags = GPIOD_ASIS;
+
+ ret = of_property_read_u32(chip_np, "#gpio-cells", &tmp);
+ if (ret)
+ return ERR_PTR(ret);
+
+ gpiospec.np = chip_np;
+ gpiospec.args_count = tmp;
+
+ for (i = 0; i < tmp; i++) {
+ ret = of_property_read_u32_index(np, "gpios", idx * tmp + i,
+ &gpiospec.args[i]);
+ if (ret)
+ return ERR_PTR(ret);
+ }
+
+ desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, &xlate_flags);
+ if (IS_ERR(desc))
+ return desc;
+
+ *lflags = of_convert_gpio_flags(xlate_flags);
+
+ if (of_property_read_bool(np, "input"))
+ *dflags |= GPIOD_IN;
+ else if (of_property_read_bool(np, "output-low"))
+ *dflags |= GPIOD_OUT_LOW;
+ else if (of_property_read_bool(np, "output-high"))
+ *dflags |= GPIOD_OUT_HIGH;
+ else {
+ pr_warn("GPIO line %d (%pOFn): no hogging state specified, bailing out\n",
+ desc_to_gpio(desc), np);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (name && of_property_read_string(np, "line-name", name))
+ *name = np->name;
+
+ return desc;
+}
+
+/**
+ * of_gpiochip_add_hog - Add all hogs in a hog device node
+ * @chip: gpio chip to act on
+ * @hog: device node describing the hogs
+ *
+ * Returns error if it fails otherwise 0 on success.
+ */
+static int of_gpiochip_add_hog(struct gpio_chip *chip, struct device_node *hog)
+{
+ enum gpiod_flags dflags;
+ struct gpio_desc *desc;
+ unsigned long lflags;
+ const char *name;
+ unsigned int i;
+ int ret;
+
+ for (i = 0;; i++) {
+ desc = of_parse_own_gpio(hog, chip, i, &name, &lflags, &dflags);
+ if (IS_ERR(desc))
+ break;
+
+ ret = gpiod_hog(desc, name, lflags, dflags);
+ if (ret < 0)
+ return ret;
+
+#ifdef CONFIG_OF_DYNAMIC
+ desc->hog = hog;
+#endif
+ }
+
+ return 0;
+}
+
+/**
+ * of_gpiochip_scan_gpios - Scan gpio-controller for gpio definitions
+ * @chip: gpio chip to act on
+ *
+ * This is only used by of_gpiochip_add to request/set GPIO initial
+ * configuration.
+ * It returns error if it fails otherwise 0 on success.
+ */
+static int of_gpiochip_scan_gpios(struct gpio_chip *chip)
+{
+ struct device_node *np;
+ int ret;
+
+ for_each_available_child_of_node(dev_of_node(&chip->gpiodev->dev), np) {
+ if (!of_property_read_bool(np, "gpio-hog"))
+ continue;
+
+ ret = of_gpiochip_add_hog(chip, np);
+ if (ret < 0) {
+ of_node_put(np);
+ return ret;
+ }
+
+ of_node_set_flag(np, OF_POPULATED);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_OF_DYNAMIC
+/**
+ * of_gpiochip_remove_hog - Remove all hogs in a hog device node
+ * @chip: gpio chip to act on
+ * @hog: device node describing the hogs
+ */
+static void of_gpiochip_remove_hog(struct gpio_chip *chip,
+ struct device_node *hog)
+{
+ struct gpio_desc *desc;
+
+ for_each_gpio_desc_with_flag(chip, desc, FLAG_IS_HOGGED)
+ if (desc->hog == hog)
+ gpiochip_free_own_desc(desc);
+}
+
+static int of_gpiochip_match_node(struct gpio_chip *chip, void *data)
+{
+ return device_match_of_node(&chip->gpiodev->dev, data);
+}
+
+static struct gpio_chip *of_find_gpiochip_by_node(struct device_node *np)
+{
+ return gpiochip_find(np, of_gpiochip_match_node);
+}
+
+static int of_gpio_notify(struct notifier_block *nb, unsigned long action,
+ void *arg)
+{
+ struct of_reconfig_data *rd = arg;
+ struct gpio_chip *chip;
+ int ret;
+
+ /*
+ * This only supports adding and removing complete gpio-hog nodes.
+ * Modifying an existing gpio-hog node is not supported (except for
+ * changing its "status" property, which is treated the same as
+ * addition/removal).
+ */
+ switch (of_reconfig_get_state_change(action, arg)) {
+ case OF_RECONFIG_CHANGE_ADD:
+ if (!of_property_read_bool(rd->dn, "gpio-hog"))
+ return NOTIFY_OK; /* not for us */
+
+ if (of_node_test_and_set_flag(rd->dn, OF_POPULATED))
+ return NOTIFY_OK;
+
+ chip = of_find_gpiochip_by_node(rd->dn->parent);
+ if (chip == NULL)
+ return NOTIFY_OK; /* not for us */
+
+ ret = of_gpiochip_add_hog(chip, rd->dn);
+ if (ret < 0) {
+ pr_err("%s: failed to add hogs for %pOF\n", __func__,
+ rd->dn);
+ of_node_clear_flag(rd->dn, OF_POPULATED);
+ return notifier_from_errno(ret);
+ }
+ break;
+
+ case OF_RECONFIG_CHANGE_REMOVE:
+ if (!of_node_check_flag(rd->dn, OF_POPULATED))
+ return NOTIFY_OK; /* already depopulated */
+
+ chip = of_find_gpiochip_by_node(rd->dn->parent);
+ if (chip == NULL)
+ return NOTIFY_OK; /* not for us */
+
+ of_gpiochip_remove_hog(chip, rd->dn);
+ of_node_clear_flag(rd->dn, OF_POPULATED);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+struct notifier_block gpio_of_notifier = {
+ .notifier_call = of_gpio_notify,
+};
+#endif /* CONFIG_OF_DYNAMIC */
+
+/**
+ * of_gpio_simple_xlate - translate gpiospec to the GPIO number and flags
+ * @gc: pointer to the gpio_chip structure
+ * @gpiospec: GPIO specifier as found in the device tree
+ * @flags: a flags pointer to fill in
+ *
+ * This is simple translation function, suitable for the most 1:1 mapped
+ * GPIO chips. This function performs only one sanity check: whether GPIO
+ * is less than ngpios (that is specified in the gpio_chip).
+ */
+static int of_gpio_simple_xlate(struct gpio_chip *gc,
+ const struct of_phandle_args *gpiospec,
+ u32 *flags)
+{
+ /*
+ * We're discouraging gpio_cells < 2, since that way you'll have to
+ * write your own xlate function (that will have to retrieve the GPIO
+ * number and the flags from a single gpio cell -- this is possible,
+ * but not recommended).
+ */
+ if (gc->of_gpio_n_cells < 2) {
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ if (WARN_ON(gpiospec->args_count < gc->of_gpio_n_cells))
+ return -EINVAL;
+
+ if (gpiospec->args[0] >= gc->ngpio)
+ return -EINVAL;
+
+ if (flags)
+ *flags = gpiospec->args[1];
+
+ return gpiospec->args[0];
+}
+
+#if IS_ENABLED(CONFIG_OF_GPIO_MM_GPIOCHIP)
+#include <linux/gpio/legacy-of-mm-gpiochip.h>
+/**
+ * of_mm_gpiochip_add_data - Add memory mapped GPIO chip (bank)
+ * @np: device node of the GPIO chip
+ * @mm_gc: pointer to the of_mm_gpio_chip allocated structure
+ * @data: driver data to store in the struct gpio_chip
+ *
+ * To use this function you should allocate and fill mm_gc with:
+ *
+ * 1) In the gpio_chip structure:
+ * - all the callbacks
+ * - of_gpio_n_cells
+ * - of_xlate callback (optional)
+ *
+ * 3) In the of_mm_gpio_chip structure:
+ * - save_regs callback (optional)
+ *
+ * If succeeded, this function will map bank's memory and will
+ * do all necessary work for you. Then you'll able to use .regs
+ * to manage GPIOs from the callbacks.
+ */
+int of_mm_gpiochip_add_data(struct device_node *np,
+ struct of_mm_gpio_chip *mm_gc,
+ void *data)
+{
+ int ret = -ENOMEM;
+ struct gpio_chip *gc = &mm_gc->gc;
+
+ gc->label = kasprintf(GFP_KERNEL, "%pOF", np);
+ if (!gc->label)
+ goto err0;
+
+ mm_gc->regs = of_iomap(np, 0);
+ if (!mm_gc->regs)
+ goto err1;
+
+ gc->base = -1;
+
+ if (mm_gc->save_regs)
+ mm_gc->save_regs(mm_gc);
+
+ fwnode_handle_put(mm_gc->gc.fwnode);
+ mm_gc->gc.fwnode = fwnode_handle_get(of_fwnode_handle(np));
+
+ ret = gpiochip_add_data(gc, data);
+ if (ret)
+ goto err2;
+
+ return 0;
+err2:
+ of_node_put(np);
+ iounmap(mm_gc->regs);
+err1:
+ kfree(gc->label);
+err0:
+ pr_err("%pOF: GPIO chip registration failed with status %d\n", np, ret);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(of_mm_gpiochip_add_data);
+
+/**
+ * of_mm_gpiochip_remove - Remove memory mapped GPIO chip (bank)
+ * @mm_gc: pointer to the of_mm_gpio_chip allocated structure
+ */
+void of_mm_gpiochip_remove(struct of_mm_gpio_chip *mm_gc)
+{
+ struct gpio_chip *gc = &mm_gc->gc;
+
+ gpiochip_remove(gc);
+ iounmap(mm_gc->regs);
+ kfree(gc->label);
+}
+EXPORT_SYMBOL_GPL(of_mm_gpiochip_remove);
+#endif
+
+#ifdef CONFIG_PINCTRL
+static int of_gpiochip_add_pin_range(struct gpio_chip *chip)
+{
+ struct of_phandle_args pinspec;
+ struct pinctrl_dev *pctldev;
+ struct device_node *np;
+ int index = 0, ret;
+ const char *name;
+ static const char group_names_propname[] = "gpio-ranges-group-names";
+ struct property *group_names;
+
+ np = dev_of_node(&chip->gpiodev->dev);
+ if (!np)
+ return 0;
+
+ group_names = of_find_property(np, group_names_propname, NULL);
+
+ for (;; index++) {
+ ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3,
+ index, &pinspec);
+ if (ret)
+ break;
+
+ pctldev = of_pinctrl_get(pinspec.np);
+ of_node_put(pinspec.np);
+ if (!pctldev)
+ return -EPROBE_DEFER;
+
+ if (pinspec.args[2]) {
+ if (group_names) {
+ of_property_read_string_index(np,
+ group_names_propname,
+ index, &name);
+ if (strlen(name)) {
+ pr_err("%pOF: Group name of numeric GPIO ranges must be the empty string.\n",
+ np);
+ break;
+ }
+ }
+ /* npins != 0: linear range */
+ ret = gpiochip_add_pin_range(chip,
+ pinctrl_dev_get_devname(pctldev),
+ pinspec.args[0],
+ pinspec.args[1],
+ pinspec.args[2]);
+ if (ret)
+ return ret;
+ } else {
+ /* npins == 0: special range */
+ if (pinspec.args[1]) {
+ pr_err("%pOF: Illegal gpio-range format.\n",
+ np);
+ break;
+ }
+
+ if (!group_names) {
+ pr_err("%pOF: GPIO group range requested but no %s property.\n",
+ np, group_names_propname);
+ break;
+ }
+
+ ret = of_property_read_string_index(np,
+ group_names_propname,
+ index, &name);
+ if (ret)
+ break;
+
+ if (!strlen(name)) {
+ pr_err("%pOF: Group name of GPIO group range cannot be the empty string.\n",
+ np);
+ break;
+ }
+
+ ret = gpiochip_add_pingroup_range(chip, pctldev,
+ pinspec.args[0], name);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+#else
+static int of_gpiochip_add_pin_range(struct gpio_chip *chip) { return 0; }
+#endif
+
+int of_gpiochip_add(struct gpio_chip *chip)
+{
+ struct device_node *np;
+ int ret;
+
+ np = dev_of_node(&chip->gpiodev->dev);
+ if (!np)
+ return 0;
+
+ if (!chip->of_xlate) {
+ chip->of_gpio_n_cells = 2;
+ chip->of_xlate = of_gpio_simple_xlate;
+ }
+
+ if (chip->of_gpio_n_cells > MAX_PHANDLE_ARGS)
+ return -EINVAL;
+
+ ret = of_gpiochip_add_pin_range(chip);
+ if (ret)
+ return ret;
+
+ of_node_get(np);
+
+ ret = of_gpiochip_scan_gpios(chip);
+ if (ret)
+ of_node_put(np);
+
+ return ret;
+}
+
+void of_gpiochip_remove(struct gpio_chip *chip)
+{
+ of_node_put(dev_of_node(&chip->gpiodev->dev));
+}
diff --git a/drivers/gpio/gpiolib-of.h b/drivers/gpio/gpiolib-of.h
new file mode 100644
index 0000000000..6b3a5347c5
--- /dev/null
+++ b/drivers/gpio/gpiolib-of.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef GPIOLIB_OF_H
+#define GPIOLIB_OF_H
+
+#include <linux/err.h>
+#include <linux/types.h>
+
+#include <linux/notifier.h>
+
+struct device;
+
+struct gpio_chip;
+struct gpio_desc;
+struct gpio_device;
+
+#ifdef CONFIG_OF_GPIO
+struct gpio_desc *of_find_gpio(struct device_node *np,
+ const char *con_id,
+ unsigned int idx,
+ unsigned long *lookupflags);
+int of_gpiochip_add(struct gpio_chip *gc);
+void of_gpiochip_remove(struct gpio_chip *gc);
+int of_gpio_get_count(struct device *dev, const char *con_id);
+#else
+static inline struct gpio_desc *of_find_gpio(struct device_node *np,
+ const char *con_id,
+ unsigned int idx,
+ unsigned long *lookupflags)
+{
+ return ERR_PTR(-ENOENT);
+}
+static inline int of_gpiochip_add(struct gpio_chip *gc) { return 0; }
+static inline void of_gpiochip_remove(struct gpio_chip *gc) { }
+static inline int of_gpio_get_count(struct device *dev, const char *con_id)
+{
+ return 0;
+}
+#endif /* CONFIG_OF_GPIO */
+
+extern struct notifier_block gpio_of_notifier;
+
+#endif /* GPIOLIB_OF_H */
diff --git a/drivers/gpio/gpiolib-swnode.c b/drivers/gpio/gpiolib-swnode.c
new file mode 100644
index 0000000000..b5a6eaf372
--- /dev/null
+++ b/drivers/gpio/gpiolib-swnode.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Software Node helpers for the GPIO API
+ *
+ * Copyright 2022 Google LLC
+ */
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/property.h>
+#include <linux/string.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+
+#include "gpiolib.h"
+#include "gpiolib-swnode.h"
+
+static void swnode_format_propname(const char *con_id, char *propname,
+ size_t max_size)
+{
+ /*
+ * Note we do not need to try both -gpios and -gpio suffixes,
+ * as, unlike OF and ACPI, we can fix software nodes to conform
+ * to the proper binding.
+ */
+ if (con_id)
+ snprintf(propname, max_size, "%s-gpios", con_id);
+ else
+ strscpy(propname, "gpios", max_size);
+}
+
+static int swnode_gpiochip_match_name(struct gpio_chip *chip, void *data)
+{
+ return !strcmp(chip->label, data);
+}
+
+static struct gpio_chip *swnode_get_chip(struct fwnode_handle *fwnode)
+{
+ const struct software_node *chip_node;
+ struct gpio_chip *chip;
+
+ chip_node = to_software_node(fwnode);
+ if (!chip_node || !chip_node->name)
+ return ERR_PTR(-EINVAL);
+
+ chip = gpiochip_find((void *)chip_node->name, swnode_gpiochip_match_name);
+ return chip ?: ERR_PTR(-EPROBE_DEFER);
+}
+
+struct gpio_desc *swnode_find_gpio(struct fwnode_handle *fwnode,
+ const char *con_id, unsigned int idx,
+ unsigned long *flags)
+{
+ const struct software_node *swnode;
+ struct fwnode_reference_args args;
+ struct gpio_chip *chip;
+ struct gpio_desc *desc;
+ char propname[32]; /* 32 is max size of property name */
+ int error;
+
+ swnode = to_software_node(fwnode);
+ if (!swnode)
+ return ERR_PTR(-EINVAL);
+
+ swnode_format_propname(con_id, propname, sizeof(propname));
+
+ /*
+ * We expect all swnode-described GPIOs have GPIO number and
+ * polarity arguments, hence nargs is set to 2.
+ */
+ error = fwnode_property_get_reference_args(fwnode, propname, NULL, 2, idx, &args);
+ if (error) {
+ pr_debug("%s: can't parse '%s' property of node '%pfwP[%d]'\n",
+ __func__, propname, fwnode, idx);
+ return ERR_PTR(error);
+ }
+
+ chip = swnode_get_chip(args.fwnode);
+ fwnode_handle_put(args.fwnode);
+ if (IS_ERR(chip))
+ return ERR_CAST(chip);
+
+ desc = gpiochip_get_desc(chip, args.args[0]);
+ *flags = args.args[1]; /* We expect native GPIO flags */
+
+ pr_debug("%s: parsed '%s' property of node '%pfwP[%d]' - status (%d)\n",
+ __func__, propname, fwnode, idx, PTR_ERR_OR_ZERO(desc));
+
+ return desc;
+}
+
+/**
+ * swnode_gpio_count - count the GPIOs associated with a device / function
+ * @fwnode: firmware node of the GPIO consumer, can be %NULL for
+ * system-global GPIOs
+ * @con_id: function within the GPIO consumer
+ *
+ * Return:
+ * The number of GPIOs associated with a device / function or %-ENOENT,
+ * if no GPIO has been assigned to the requested function.
+ */
+int swnode_gpio_count(const struct fwnode_handle *fwnode, const char *con_id)
+{
+ struct fwnode_reference_args args;
+ char propname[32];
+ int count;
+
+ swnode_format_propname(con_id, propname, sizeof(propname));
+
+ /*
+ * This is not very efficient, but GPIO lists usually have only
+ * 1 or 2 entries.
+ */
+ count = 0;
+ while (fwnode_property_get_reference_args(fwnode, propname, NULL, 0,
+ count, &args) == 0) {
+ fwnode_handle_put(args.fwnode);
+ count++;
+ }
+
+ return count ?: -ENOENT;
+}
diff --git a/drivers/gpio/gpiolib-swnode.h b/drivers/gpio/gpiolib-swnode.h
new file mode 100644
index 0000000000..af849e56f6
--- /dev/null
+++ b/drivers/gpio/gpiolib-swnode.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef GPIOLIB_SWNODE_H
+#define GPIOLIB_SWNODE_H
+
+struct fwnode_handle;
+struct gpio_desc;
+
+struct gpio_desc *swnode_find_gpio(struct fwnode_handle *fwnode,
+ const char *con_id, unsigned int idx,
+ unsigned long *flags);
+int swnode_gpio_count(const struct fwnode_handle *fwnode, const char *con_id);
+
+#endif /* GPIOLIB_SWNODE_H */
diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c
new file mode 100644
index 0000000000..12d853845b
--- /dev/null
+++ b/drivers/gpio/gpiolib-sysfs.c
@@ -0,0 +1,832 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/idr.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/kstrtox.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+
+#include "gpiolib.h"
+#include "gpiolib-sysfs.h"
+
+struct kernfs_node;
+
+#define GPIO_IRQF_TRIGGER_NONE 0
+#define GPIO_IRQF_TRIGGER_FALLING BIT(0)
+#define GPIO_IRQF_TRIGGER_RISING BIT(1)
+#define GPIO_IRQF_TRIGGER_BOTH (GPIO_IRQF_TRIGGER_FALLING | \
+ GPIO_IRQF_TRIGGER_RISING)
+
+struct gpiod_data {
+ struct gpio_desc *desc;
+
+ struct mutex mutex;
+ struct kernfs_node *value_kn;
+ int irq;
+ unsigned char irq_flags;
+
+ bool direction_can_change;
+};
+
+/*
+ * Lock to serialise gpiod export and unexport, and prevent re-export of
+ * gpiod whose chip is being unregistered.
+ */
+static DEFINE_MUTEX(sysfs_lock);
+
+/*
+ * /sys/class/gpio/gpioN... only for GPIOs that are exported
+ * /direction
+ * * MAY BE OMITTED if kernel won't allow direction changes
+ * * is read/write as "in" or "out"
+ * * may also be written as "high" or "low", initializing
+ * output value as specified ("out" implies "low")
+ * /value
+ * * always readable, subject to hardware behavior
+ * * may be writable, as zero/nonzero
+ * /edge
+ * * configures behavior of poll(2) on /value
+ * * available only if pin can generate IRQs on input
+ * * is read/write as "none", "falling", "rising", or "both"
+ * /active_low
+ * * configures polarity of /value
+ * * is read/write as zero/nonzero
+ * * also affects existing and subsequent "falling" and "rising"
+ * /edge configuration
+ */
+
+static ssize_t direction_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gpiod_data *data = dev_get_drvdata(dev);
+ struct gpio_desc *desc = data->desc;
+ int value;
+
+ mutex_lock(&data->mutex);
+
+ gpiod_get_direction(desc);
+ value = !!test_bit(FLAG_IS_OUT, &desc->flags);
+
+ mutex_unlock(&data->mutex);
+
+ return sysfs_emit(buf, "%s\n", value ? "out" : "in");
+}
+
+static ssize_t direction_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct gpiod_data *data = dev_get_drvdata(dev);
+ struct gpio_desc *desc = data->desc;
+ ssize_t status;
+
+ mutex_lock(&data->mutex);
+
+ if (sysfs_streq(buf, "high"))
+ status = gpiod_direction_output_raw(desc, 1);
+ else if (sysfs_streq(buf, "out") || sysfs_streq(buf, "low"))
+ status = gpiod_direction_output_raw(desc, 0);
+ else if (sysfs_streq(buf, "in"))
+ status = gpiod_direction_input(desc);
+ else
+ status = -EINVAL;
+
+ mutex_unlock(&data->mutex);
+
+ return status ? : size;
+}
+static DEVICE_ATTR_RW(direction);
+
+static ssize_t value_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gpiod_data *data = dev_get_drvdata(dev);
+ struct gpio_desc *desc = data->desc;
+ ssize_t status;
+
+ mutex_lock(&data->mutex);
+
+ status = gpiod_get_value_cansleep(desc);
+
+ mutex_unlock(&data->mutex);
+
+ if (status < 0)
+ return status;
+
+ return sysfs_emit(buf, "%zd\n", status);
+}
+
+static ssize_t value_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct gpiod_data *data = dev_get_drvdata(dev);
+ struct gpio_desc *desc = data->desc;
+ ssize_t status;
+ long value;
+
+ status = kstrtol(buf, 0, &value);
+
+ mutex_lock(&data->mutex);
+
+ if (!test_bit(FLAG_IS_OUT, &desc->flags)) {
+ status = -EPERM;
+ } else if (status == 0) {
+ gpiod_set_value_cansleep(desc, value);
+ status = size;
+ }
+
+ mutex_unlock(&data->mutex);
+
+ return status;
+}
+static DEVICE_ATTR_PREALLOC(value, S_IWUSR | S_IRUGO, value_show, value_store);
+
+static irqreturn_t gpio_sysfs_irq(int irq, void *priv)
+{
+ struct gpiod_data *data = priv;
+
+ sysfs_notify_dirent(data->value_kn);
+
+ return IRQ_HANDLED;
+}
+
+/* Caller holds gpiod-data mutex. */
+static int gpio_sysfs_request_irq(struct device *dev, unsigned char flags)
+{
+ struct gpiod_data *data = dev_get_drvdata(dev);
+ struct gpio_desc *desc = data->desc;
+ unsigned long irq_flags;
+ int ret;
+
+ data->irq = gpiod_to_irq(desc);
+ if (data->irq < 0)
+ return -EIO;
+
+ data->value_kn = sysfs_get_dirent(dev->kobj.sd, "value");
+ if (!data->value_kn)
+ return -ENODEV;
+
+ irq_flags = IRQF_SHARED;
+ if (flags & GPIO_IRQF_TRIGGER_FALLING)
+ irq_flags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ?
+ IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING;
+ if (flags & GPIO_IRQF_TRIGGER_RISING)
+ irq_flags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ?
+ IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
+
+ /*
+ * FIXME: This should be done in the irq_request_resources callback
+ * when the irq is requested, but a few drivers currently fail
+ * to do so.
+ *
+ * Remove this redundant call (along with the corresponding
+ * unlock) when those drivers have been fixed.
+ */
+ ret = gpiochip_lock_as_irq(desc->gdev->chip, gpio_chip_hwgpio(desc));
+ if (ret < 0)
+ goto err_put_kn;
+
+ ret = request_any_context_irq(data->irq, gpio_sysfs_irq, irq_flags,
+ "gpiolib", data);
+ if (ret < 0)
+ goto err_unlock;
+
+ data->irq_flags = flags;
+
+ return 0;
+
+err_unlock:
+ gpiochip_unlock_as_irq(desc->gdev->chip, gpio_chip_hwgpio(desc));
+err_put_kn:
+ sysfs_put(data->value_kn);
+
+ return ret;
+}
+
+/*
+ * Caller holds gpiod-data mutex (unless called after class-device
+ * deregistration).
+ */
+static void gpio_sysfs_free_irq(struct device *dev)
+{
+ struct gpiod_data *data = dev_get_drvdata(dev);
+ struct gpio_desc *desc = data->desc;
+
+ data->irq_flags = 0;
+ free_irq(data->irq, data);
+ gpiochip_unlock_as_irq(desc->gdev->chip, gpio_chip_hwgpio(desc));
+ sysfs_put(data->value_kn);
+}
+
+static const char * const trigger_names[] = {
+ [GPIO_IRQF_TRIGGER_NONE] = "none",
+ [GPIO_IRQF_TRIGGER_FALLING] = "falling",
+ [GPIO_IRQF_TRIGGER_RISING] = "rising",
+ [GPIO_IRQF_TRIGGER_BOTH] = "both",
+};
+
+static ssize_t edge_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gpiod_data *data = dev_get_drvdata(dev);
+ int flags;
+
+ mutex_lock(&data->mutex);
+
+ flags = data->irq_flags;
+
+ mutex_unlock(&data->mutex);
+
+ if (flags >= ARRAY_SIZE(trigger_names))
+ return 0;
+
+ return sysfs_emit(buf, "%s\n", trigger_names[flags]);
+}
+
+static ssize_t edge_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct gpiod_data *data = dev_get_drvdata(dev);
+ ssize_t status = size;
+ int flags;
+
+ flags = sysfs_match_string(trigger_names, buf);
+ if (flags < 0)
+ return flags;
+
+ mutex_lock(&data->mutex);
+
+ if (flags == data->irq_flags) {
+ status = size;
+ goto out_unlock;
+ }
+
+ if (data->irq_flags)
+ gpio_sysfs_free_irq(dev);
+
+ if (flags) {
+ status = gpio_sysfs_request_irq(dev, flags);
+ if (!status)
+ status = size;
+ }
+
+out_unlock:
+ mutex_unlock(&data->mutex);
+
+ return status;
+}
+static DEVICE_ATTR_RW(edge);
+
+/* Caller holds gpiod-data mutex. */
+static int gpio_sysfs_set_active_low(struct device *dev, int value)
+{
+ struct gpiod_data *data = dev_get_drvdata(dev);
+ struct gpio_desc *desc = data->desc;
+ int status = 0;
+ unsigned int flags = data->irq_flags;
+
+ if (!!test_bit(FLAG_ACTIVE_LOW, &desc->flags) == !!value)
+ return 0;
+
+ assign_bit(FLAG_ACTIVE_LOW, &desc->flags, value);
+
+ /* reconfigure poll(2) support if enabled on one edge only */
+ if (flags == GPIO_IRQF_TRIGGER_FALLING ||
+ flags == GPIO_IRQF_TRIGGER_RISING) {
+ gpio_sysfs_free_irq(dev);
+ status = gpio_sysfs_request_irq(dev, flags);
+ }
+
+ return status;
+}
+
+static ssize_t active_low_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gpiod_data *data = dev_get_drvdata(dev);
+ struct gpio_desc *desc = data->desc;
+ int value;
+
+ mutex_lock(&data->mutex);
+
+ value = !!test_bit(FLAG_ACTIVE_LOW, &desc->flags);
+
+ mutex_unlock(&data->mutex);
+
+ return sysfs_emit(buf, "%d\n", value);
+}
+
+static ssize_t active_low_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct gpiod_data *data = dev_get_drvdata(dev);
+ ssize_t status;
+ long value;
+
+ status = kstrtol(buf, 0, &value);
+ if (status)
+ return status;
+
+ mutex_lock(&data->mutex);
+
+ status = gpio_sysfs_set_active_low(dev, value);
+
+ mutex_unlock(&data->mutex);
+
+ return status ? : size;
+}
+static DEVICE_ATTR_RW(active_low);
+
+static umode_t gpio_is_visible(struct kobject *kobj, struct attribute *attr,
+ int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct gpiod_data *data = dev_get_drvdata(dev);
+ struct gpio_desc *desc = data->desc;
+ umode_t mode = attr->mode;
+ bool show_direction = data->direction_can_change;
+
+ if (attr == &dev_attr_direction.attr) {
+ if (!show_direction)
+ mode = 0;
+ } else if (attr == &dev_attr_edge.attr) {
+ if (gpiod_to_irq(desc) < 0)
+ mode = 0;
+ if (!show_direction && test_bit(FLAG_IS_OUT, &desc->flags))
+ mode = 0;
+ }
+
+ return mode;
+}
+
+static struct attribute *gpio_attrs[] = {
+ &dev_attr_direction.attr,
+ &dev_attr_edge.attr,
+ &dev_attr_value.attr,
+ &dev_attr_active_low.attr,
+ NULL,
+};
+
+static const struct attribute_group gpio_group = {
+ .attrs = gpio_attrs,
+ .is_visible = gpio_is_visible,
+};
+
+static const struct attribute_group *gpio_groups[] = {
+ &gpio_group,
+ NULL
+};
+
+/*
+ * /sys/class/gpio/gpiochipN/
+ * /base ... matching gpio_chip.base (N)
+ * /label ... matching gpio_chip.label
+ * /ngpio ... matching gpio_chip.ngpio
+ */
+
+static ssize_t base_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const struct gpio_chip *chip = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", chip->base);
+}
+static DEVICE_ATTR_RO(base);
+
+static ssize_t label_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const struct gpio_chip *chip = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%s\n", chip->label ?: "");
+}
+static DEVICE_ATTR_RO(label);
+
+static ssize_t ngpio_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const struct gpio_chip *chip = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", chip->ngpio);
+}
+static DEVICE_ATTR_RO(ngpio);
+
+static struct attribute *gpiochip_attrs[] = {
+ &dev_attr_base.attr,
+ &dev_attr_label.attr,
+ &dev_attr_ngpio.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(gpiochip);
+
+/*
+ * /sys/class/gpio/export ... write-only
+ * integer N ... number of GPIO to export (full access)
+ * /sys/class/gpio/unexport ... write-only
+ * integer N ... number of GPIO to unexport
+ */
+static ssize_t export_store(const struct class *class,
+ const struct class_attribute *attr,
+ const char *buf, size_t len)
+{
+ long gpio;
+ struct gpio_desc *desc;
+ int status;
+ struct gpio_chip *gc;
+ int offset;
+
+ status = kstrtol(buf, 0, &gpio);
+ if (status < 0)
+ goto done;
+
+ desc = gpio_to_desc(gpio);
+ /* reject invalid GPIOs */
+ if (!desc) {
+ pr_warn("%s: invalid GPIO %ld\n", __func__, gpio);
+ return -EINVAL;
+ }
+ gc = desc->gdev->chip;
+ offset = gpio_chip_hwgpio(desc);
+ if (!gpiochip_line_is_valid(gc, offset)) {
+ pr_warn("%s: GPIO %ld masked\n", __func__, gpio);
+ return -EINVAL;
+ }
+
+ /* No extra locking here; FLAG_SYSFS just signifies that the
+ * request and export were done by on behalf of userspace, so
+ * they may be undone on its behalf too.
+ */
+
+ status = gpiod_request_user(desc, "sysfs");
+ if (status)
+ goto done;
+
+ status = gpiod_set_transitory(desc, false);
+ if (status) {
+ gpiod_free(desc);
+ goto done;
+ }
+
+ status = gpiod_export(desc, true);
+ if (status < 0)
+ gpiod_free(desc);
+ else
+ set_bit(FLAG_SYSFS, &desc->flags);
+
+done:
+ if (status)
+ pr_debug("%s: status %d\n", __func__, status);
+ return status ? : len;
+}
+static CLASS_ATTR_WO(export);
+
+static ssize_t unexport_store(const struct class *class,
+ const struct class_attribute *attr,
+ const char *buf, size_t len)
+{
+ long gpio;
+ struct gpio_desc *desc;
+ int status;
+
+ status = kstrtol(buf, 0, &gpio);
+ if (status < 0)
+ goto done;
+
+ desc = gpio_to_desc(gpio);
+ /* reject bogus commands (gpiod_unexport() ignores them) */
+ if (!desc) {
+ pr_warn("%s: invalid GPIO %ld\n", __func__, gpio);
+ return -EINVAL;
+ }
+
+ status = -EINVAL;
+
+ /* No extra locking here; FLAG_SYSFS just signifies that the
+ * request and export were done by on behalf of userspace, so
+ * they may be undone on its behalf too.
+ */
+ if (test_and_clear_bit(FLAG_SYSFS, &desc->flags)) {
+ gpiod_unexport(desc);
+ gpiod_free(desc);
+ status = 0;
+ }
+done:
+ if (status)
+ pr_debug("%s: status %d\n", __func__, status);
+ return status ? : len;
+}
+static CLASS_ATTR_WO(unexport);
+
+static struct attribute *gpio_class_attrs[] = {
+ &class_attr_export.attr,
+ &class_attr_unexport.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(gpio_class);
+
+static struct class gpio_class = {
+ .name = "gpio",
+ .class_groups = gpio_class_groups,
+};
+
+
+/**
+ * gpiod_export - export a GPIO through sysfs
+ * @desc: GPIO to make available, already requested
+ * @direction_may_change: true if userspace may change GPIO direction
+ * Context: arch_initcall or later
+ *
+ * When drivers want to make a GPIO accessible to userspace after they
+ * have requested it -- perhaps while debugging, or as part of their
+ * public interface -- they may use this routine. If the GPIO can
+ * change direction (some can't) and the caller allows it, userspace
+ * will see "direction" sysfs attribute which may be used to change
+ * the gpio's direction. A "value" attribute will always be provided.
+ *
+ * Returns zero on success, else an error.
+ */
+int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
+{
+ struct gpio_chip *chip;
+ struct gpio_device *gdev;
+ struct gpiod_data *data;
+ unsigned long flags;
+ int status;
+ const char *ioname = NULL;
+ struct device *dev;
+ int offset;
+
+ /* can't export until sysfs is available ... */
+ if (!class_is_registered(&gpio_class)) {
+ pr_debug("%s: called too early!\n", __func__);
+ return -ENOENT;
+ }
+
+ if (!desc) {
+ pr_debug("%s: invalid gpio descriptor\n", __func__);
+ return -EINVAL;
+ }
+
+ gdev = desc->gdev;
+ chip = gdev->chip;
+
+ mutex_lock(&sysfs_lock);
+
+ /* check if chip is being removed */
+ if (!chip || !gdev->mockdev) {
+ status = -ENODEV;
+ goto err_unlock;
+ }
+
+ spin_lock_irqsave(&gpio_lock, flags);
+ if (!test_bit(FLAG_REQUESTED, &desc->flags) ||
+ test_bit(FLAG_EXPORT, &desc->flags)) {
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ gpiod_dbg(desc, "%s: unavailable (requested=%d, exported=%d)\n",
+ __func__,
+ test_bit(FLAG_REQUESTED, &desc->flags),
+ test_bit(FLAG_EXPORT, &desc->flags));
+ status = -EPERM;
+ goto err_unlock;
+ }
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ status = -ENOMEM;
+ goto err_unlock;
+ }
+
+ data->desc = desc;
+ mutex_init(&data->mutex);
+ if (chip->direction_input && chip->direction_output)
+ data->direction_can_change = direction_may_change;
+ else
+ data->direction_can_change = false;
+
+ offset = gpio_chip_hwgpio(desc);
+ if (chip->names && chip->names[offset])
+ ioname = chip->names[offset];
+
+ dev = device_create_with_groups(&gpio_class, &gdev->dev,
+ MKDEV(0, 0), data, gpio_groups,
+ ioname ? ioname : "gpio%u",
+ desc_to_gpio(desc));
+ if (IS_ERR(dev)) {
+ status = PTR_ERR(dev);
+ goto err_free_data;
+ }
+
+ set_bit(FLAG_EXPORT, &desc->flags);
+ mutex_unlock(&sysfs_lock);
+ return 0;
+
+err_free_data:
+ kfree(data);
+err_unlock:
+ mutex_unlock(&sysfs_lock);
+ gpiod_dbg(desc, "%s: status %d\n", __func__, status);
+ return status;
+}
+EXPORT_SYMBOL_GPL(gpiod_export);
+
+static int match_export(struct device *dev, const void *desc)
+{
+ struct gpiod_data *data = dev_get_drvdata(dev);
+
+ return data->desc == desc;
+}
+
+/**
+ * gpiod_export_link - create a sysfs link to an exported GPIO node
+ * @dev: device under which to create symlink
+ * @name: name of the symlink
+ * @desc: GPIO to create symlink to, already exported
+ *
+ * Set up a symlink from /sys/.../dev/name to /sys/class/gpio/gpioN
+ * node. Caller is responsible for unlinking.
+ *
+ * Returns zero on success, else an error.
+ */
+int gpiod_export_link(struct device *dev, const char *name,
+ struct gpio_desc *desc)
+{
+ struct device *cdev;
+ int ret;
+
+ if (!desc) {
+ pr_warn("%s: invalid GPIO\n", __func__);
+ return -EINVAL;
+ }
+
+ cdev = class_find_device(&gpio_class, NULL, desc, match_export);
+ if (!cdev)
+ return -ENODEV;
+
+ ret = sysfs_create_link(&dev->kobj, &cdev->kobj, name);
+ put_device(cdev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gpiod_export_link);
+
+/**
+ * gpiod_unexport - reverse effect of gpiod_export()
+ * @desc: GPIO to make unavailable
+ *
+ * This is implicit on gpiod_free().
+ */
+void gpiod_unexport(struct gpio_desc *desc)
+{
+ struct gpiod_data *data;
+ struct device *dev;
+
+ if (!desc) {
+ pr_warn("%s: invalid GPIO\n", __func__);
+ return;
+ }
+
+ mutex_lock(&sysfs_lock);
+
+ if (!test_bit(FLAG_EXPORT, &desc->flags))
+ goto err_unlock;
+
+ dev = class_find_device(&gpio_class, NULL, desc, match_export);
+ if (!dev)
+ goto err_unlock;
+
+ data = dev_get_drvdata(dev);
+
+ clear_bit(FLAG_EXPORT, &desc->flags);
+
+ device_unregister(dev);
+
+ /*
+ * Release irq after deregistration to prevent race with edge_store.
+ */
+ if (data->irq_flags)
+ gpio_sysfs_free_irq(dev);
+
+ mutex_unlock(&sysfs_lock);
+
+ put_device(dev);
+ kfree(data);
+
+ return;
+
+err_unlock:
+ mutex_unlock(&sysfs_lock);
+}
+EXPORT_SYMBOL_GPL(gpiod_unexport);
+
+int gpiochip_sysfs_register(struct gpio_device *gdev)
+{
+ struct device *dev;
+ struct device *parent;
+ struct gpio_chip *chip = gdev->chip;
+
+ /*
+ * Many systems add gpio chips for SOC support very early,
+ * before driver model support is available. In those cases we
+ * register later, in gpiolib_sysfs_init() ... here we just
+ * verify that _some_ field of gpio_class got initialized.
+ */
+ if (!class_is_registered(&gpio_class))
+ return 0;
+
+ /*
+ * For sysfs backward compatibility we need to preserve this
+ * preferred parenting to the gpio_chip parent field, if set.
+ */
+ if (chip->parent)
+ parent = chip->parent;
+ else
+ parent = &gdev->dev;
+
+ /* use chip->base for the ID; it's already known to be unique */
+ dev = device_create_with_groups(&gpio_class, parent, MKDEV(0, 0), chip,
+ gpiochip_groups, GPIOCHIP_NAME "%d",
+ chip->base);
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+
+ mutex_lock(&sysfs_lock);
+ gdev->mockdev = dev;
+ mutex_unlock(&sysfs_lock);
+
+ return 0;
+}
+
+void gpiochip_sysfs_unregister(struct gpio_device *gdev)
+{
+ struct gpio_desc *desc;
+ struct gpio_chip *chip = gdev->chip;
+
+ if (!gdev->mockdev)
+ return;
+
+ device_unregister(gdev->mockdev);
+
+ /* prevent further gpiod exports */
+ mutex_lock(&sysfs_lock);
+ gdev->mockdev = NULL;
+ mutex_unlock(&sysfs_lock);
+
+ /* unregister gpiod class devices owned by sysfs */
+ for_each_gpio_desc_with_flag(chip, desc, FLAG_SYSFS) {
+ gpiod_unexport(desc);
+ gpiod_free(desc);
+ }
+}
+
+static int __init gpiolib_sysfs_init(void)
+{
+ int status;
+ unsigned long flags;
+ struct gpio_device *gdev;
+
+ status = class_register(&gpio_class);
+ if (status < 0)
+ return status;
+
+ /* Scan and register the gpio_chips which registered very
+ * early (e.g. before the class_register above was called).
+ *
+ * We run before arch_initcall() so chip->dev nodes can have
+ * registered, and so arch_initcall() can always gpiod_export().
+ */
+ spin_lock_irqsave(&gpio_lock, flags);
+ list_for_each_entry(gdev, &gpio_devices, list) {
+ if (gdev->mockdev)
+ continue;
+
+ /*
+ * TODO we yield gpio_lock here because
+ * gpiochip_sysfs_register() acquires a mutex. This is unsafe
+ * and needs to be fixed.
+ *
+ * Also it would be nice to use gpio_device_find() here so we
+ * can keep gpio_chips local to gpiolib.c, but the yield of
+ * gpio_lock prevents us from doing this.
+ */
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ status = gpiochip_sysfs_register(gdev);
+ spin_lock_irqsave(&gpio_lock, flags);
+ }
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ return status;
+}
+postcore_initcall(gpiolib_sysfs_init);
diff --git a/drivers/gpio/gpiolib-sysfs.h b/drivers/gpio/gpiolib-sysfs.h
new file mode 100644
index 0000000000..0f213bdb47
--- /dev/null
+++ b/drivers/gpio/gpiolib-sysfs.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef GPIOLIB_SYSFS_H
+#define GPIOLIB_SYSFS_H
+
+#ifdef CONFIG_GPIO_SYSFS
+
+struct gpio_device;
+
+int gpiochip_sysfs_register(struct gpio_device *gdev);
+void gpiochip_sysfs_unregister(struct gpio_device *gdev);
+
+#else
+
+static inline int gpiochip_sysfs_register(struct gpio_device *gdev)
+{
+ return 0;
+}
+
+static inline void gpiochip_sysfs_unregister(struct gpio_device *gdev)
+{
+}
+
+#endif /* CONFIG_GPIO_SYSFS */
+
+#endif /* GPIOLIB_SYSFS_H */
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
new file mode 100644
index 0000000000..71492d213e
--- /dev/null
+++ b/drivers/gpio/gpiolib.c
@@ -0,0 +1,4719 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/acpi.h>
+#include <linux/bitmap.h>
+#include <linux/compat.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/machine.h>
+
+#include <uapi/linux/gpio.h>
+
+#include "gpiolib-acpi.h"
+#include "gpiolib-cdev.h"
+#include "gpiolib-of.h"
+#include "gpiolib-swnode.h"
+#include "gpiolib-sysfs.h"
+#include "gpiolib.h"
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/gpio.h>
+
+/* Implementation infrastructure for GPIO interfaces.
+ *
+ * The GPIO programming interface allows for inlining speed-critical
+ * get/set operations for common cases, so that access to SOC-integrated
+ * GPIOs can sometimes cost only an instruction or two per bit.
+ */
+
+
+/* When debugging, extend minimal trust to callers and platform code.
+ * Also emit diagnostic messages that may help initial bringup, when
+ * board setup or driver bugs are most common.
+ *
+ * Otherwise, minimize overhead in what may be bitbanging codepaths.
+ */
+#ifdef DEBUG
+#define extra_checks 1
+#else
+#define extra_checks 0
+#endif
+
+/* Device and char device-related information */
+static DEFINE_IDA(gpio_ida);
+static dev_t gpio_devt;
+#define GPIO_DEV_MAX 256 /* 256 GPIO chip devices supported */
+
+static int gpio_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
+
+ /*
+ * Only match if the fwnode doesn't already have a proper struct device
+ * created for it.
+ */
+ if (fwnode && fwnode->dev != dev)
+ return 0;
+ return 1;
+}
+
+static struct bus_type gpio_bus_type = {
+ .name = "gpio",
+ .match = gpio_bus_match,
+};
+
+/*
+ * Number of GPIOs to use for the fast path in set array
+ */
+#define FASTPATH_NGPIO CONFIG_GPIOLIB_FASTPATH_LIMIT
+
+/* gpio_lock prevents conflicts during gpio_desc[] table updates.
+ * While any GPIO is requested, its gpio_chip is not removable;
+ * each GPIO's "requested" flag serves as a lock and refcount.
+ */
+DEFINE_SPINLOCK(gpio_lock);
+
+static DEFINE_MUTEX(gpio_lookup_lock);
+static LIST_HEAD(gpio_lookup_list);
+LIST_HEAD(gpio_devices);
+
+static DEFINE_MUTEX(gpio_machine_hogs_mutex);
+static LIST_HEAD(gpio_machine_hogs);
+
+static void gpiochip_free_hogs(struct gpio_chip *gc);
+static int gpiochip_add_irqchip(struct gpio_chip *gc,
+ struct lock_class_key *lock_key,
+ struct lock_class_key *request_key);
+static void gpiochip_irqchip_remove(struct gpio_chip *gc);
+static int gpiochip_irqchip_init_hw(struct gpio_chip *gc);
+static int gpiochip_irqchip_init_valid_mask(struct gpio_chip *gc);
+static void gpiochip_irqchip_free_valid_mask(struct gpio_chip *gc);
+
+static bool gpiolib_initialized;
+
+static inline void desc_set_label(struct gpio_desc *d, const char *label)
+{
+ d->label = label;
+}
+
+/**
+ * gpio_to_desc - Convert a GPIO number to its descriptor
+ * @gpio: global GPIO number
+ *
+ * Returns:
+ * The GPIO descriptor associated with the given GPIO, or %NULL if no GPIO
+ * with the given number exists in the system.
+ */
+struct gpio_desc *gpio_to_desc(unsigned gpio)
+{
+ struct gpio_device *gdev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ list_for_each_entry(gdev, &gpio_devices, list) {
+ if (gdev->base <= gpio &&
+ gdev->base + gdev->ngpio > gpio) {
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ return &gdev->descs[gpio - gdev->base];
+ }
+ }
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ if (!gpio_is_valid(gpio))
+ pr_warn("invalid GPIO %d\n", gpio);
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(gpio_to_desc);
+
+/**
+ * gpiochip_get_desc - get the GPIO descriptor corresponding to the given
+ * hardware number for this chip
+ * @gc: GPIO chip
+ * @hwnum: hardware number of the GPIO for this chip
+ *
+ * Returns:
+ * A pointer to the GPIO descriptor or ``ERR_PTR(-EINVAL)`` if no GPIO exists
+ * in the given chip for the specified hardware number.
+ */
+struct gpio_desc *gpiochip_get_desc(struct gpio_chip *gc,
+ unsigned int hwnum)
+{
+ struct gpio_device *gdev = gc->gpiodev;
+
+ if (hwnum >= gdev->ngpio)
+ return ERR_PTR(-EINVAL);
+
+ return &gdev->descs[hwnum];
+}
+EXPORT_SYMBOL_GPL(gpiochip_get_desc);
+
+/**
+ * desc_to_gpio - convert a GPIO descriptor to the integer namespace
+ * @desc: GPIO descriptor
+ *
+ * This should disappear in the future but is needed since we still
+ * use GPIO numbers for error messages and sysfs nodes.
+ *
+ * Returns:
+ * The global GPIO number for the GPIO specified by its descriptor.
+ */
+int desc_to_gpio(const struct gpio_desc *desc)
+{
+ return desc->gdev->base + (desc - &desc->gdev->descs[0]);
+}
+EXPORT_SYMBOL_GPL(desc_to_gpio);
+
+
+/**
+ * gpiod_to_chip - Return the GPIO chip to which a GPIO descriptor belongs
+ * @desc: descriptor to return the chip of
+ */
+struct gpio_chip *gpiod_to_chip(const struct gpio_desc *desc)
+{
+ if (!desc || !desc->gdev)
+ return NULL;
+ return desc->gdev->chip;
+}
+EXPORT_SYMBOL_GPL(gpiod_to_chip);
+
+/* dynamic allocation of GPIOs, e.g. on a hotplugged device */
+static int gpiochip_find_base(int ngpio)
+{
+ struct gpio_device *gdev;
+ int base = GPIO_DYNAMIC_BASE;
+
+ list_for_each_entry(gdev, &gpio_devices, list) {
+ /* found a free space? */
+ if (gdev->base >= base + ngpio)
+ break;
+ /* nope, check the space right after the chip */
+ base = gdev->base + gdev->ngpio;
+ if (base < GPIO_DYNAMIC_BASE)
+ base = GPIO_DYNAMIC_BASE;
+ }
+
+ if (gpio_is_valid(base)) {
+ pr_debug("%s: found new base at %d\n", __func__, base);
+ return base;
+ } else {
+ pr_err("%s: cannot find free range\n", __func__);
+ return -ENOSPC;
+ }
+}
+
+/**
+ * gpiod_get_direction - return the current direction of a GPIO
+ * @desc: GPIO to get the direction of
+ *
+ * Returns 0 for output, 1 for input, or an error code in case of error.
+ *
+ * This function may sleep if gpiod_cansleep() is true.
+ */
+int gpiod_get_direction(struct gpio_desc *desc)
+{
+ struct gpio_chip *gc;
+ unsigned int offset;
+ int ret;
+
+ gc = gpiod_to_chip(desc);
+ offset = gpio_chip_hwgpio(desc);
+
+ /*
+ * Open drain emulation using input mode may incorrectly report
+ * input here, fix that up.
+ */
+ if (test_bit(FLAG_OPEN_DRAIN, &desc->flags) &&
+ test_bit(FLAG_IS_OUT, &desc->flags))
+ return 0;
+
+ if (!gc->get_direction)
+ return -ENOTSUPP;
+
+ ret = gc->get_direction(gc, offset);
+ if (ret < 0)
+ return ret;
+
+ /* GPIOF_DIR_IN or other positive, otherwise GPIOF_DIR_OUT */
+ if (ret > 0)
+ ret = 1;
+
+ assign_bit(FLAG_IS_OUT, &desc->flags, !ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gpiod_get_direction);
+
+/*
+ * Add a new chip to the global chips list, keeping the list of chips sorted
+ * by range(means [base, base + ngpio - 1]) order.
+ *
+ * Return -EBUSY if the new chip overlaps with some other chip's integer
+ * space.
+ */
+static int gpiodev_add_to_list(struct gpio_device *gdev)
+{
+ struct gpio_device *prev, *next;
+
+ if (list_empty(&gpio_devices)) {
+ /* initial entry in list */
+ list_add_tail(&gdev->list, &gpio_devices);
+ return 0;
+ }
+
+ next = list_first_entry(&gpio_devices, struct gpio_device, list);
+ if (gdev->base + gdev->ngpio <= next->base) {
+ /* add before first entry */
+ list_add(&gdev->list, &gpio_devices);
+ return 0;
+ }
+
+ prev = list_last_entry(&gpio_devices, struct gpio_device, list);
+ if (prev->base + prev->ngpio <= gdev->base) {
+ /* add behind last entry */
+ list_add_tail(&gdev->list, &gpio_devices);
+ return 0;
+ }
+
+ list_for_each_entry_safe(prev, next, &gpio_devices, list) {
+ /* at the end of the list */
+ if (&next->list == &gpio_devices)
+ break;
+
+ /* add between prev and next */
+ if (prev->base + prev->ngpio <= gdev->base
+ && gdev->base + gdev->ngpio <= next->base) {
+ list_add(&gdev->list, &prev->list);
+ return 0;
+ }
+ }
+
+ return -EBUSY;
+}
+
+/*
+ * Convert a GPIO name to its descriptor
+ * Note that there is no guarantee that GPIO names are globally unique!
+ * Hence this function will return, if it exists, a reference to the first GPIO
+ * line found that matches the given name.
+ */
+static struct gpio_desc *gpio_name_to_desc(const char * const name)
+{
+ struct gpio_device *gdev;
+ unsigned long flags;
+
+ if (!name)
+ return NULL;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ list_for_each_entry(gdev, &gpio_devices, list) {
+ struct gpio_desc *desc;
+
+ for_each_gpio_desc(gdev->chip, desc) {
+ if (desc->name && !strcmp(desc->name, name)) {
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ return desc;
+ }
+ }
+ }
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ return NULL;
+}
+
+/*
+ * Take the names from gc->names and assign them to their GPIO descriptors.
+ * Warn if a name is already used for a GPIO line on a different GPIO chip.
+ *
+ * Note that:
+ * 1. Non-unique names are still accepted,
+ * 2. Name collisions within the same GPIO chip are not reported.
+ */
+static int gpiochip_set_desc_names(struct gpio_chip *gc)
+{
+ struct gpio_device *gdev = gc->gpiodev;
+ int i;
+
+ /* First check all names if they are unique */
+ for (i = 0; i != gc->ngpio; ++i) {
+ struct gpio_desc *gpio;
+
+ gpio = gpio_name_to_desc(gc->names[i]);
+ if (gpio)
+ dev_warn(&gdev->dev,
+ "Detected name collision for GPIO name '%s'\n",
+ gc->names[i]);
+ }
+
+ /* Then add all names to the GPIO descriptors */
+ for (i = 0; i != gc->ngpio; ++i)
+ gdev->descs[i].name = gc->names[i];
+
+ return 0;
+}
+
+/*
+ * gpiochip_set_names - Set GPIO line names using device properties
+ * @chip: GPIO chip whose lines should be named, if possible
+ *
+ * Looks for device property "gpio-line-names" and if it exists assigns
+ * GPIO line names for the chip. The memory allocated for the assigned
+ * names belong to the underlying firmware node and should not be released
+ * by the caller.
+ */
+static int gpiochip_set_names(struct gpio_chip *chip)
+{
+ struct gpio_device *gdev = chip->gpiodev;
+ struct device *dev = &gdev->dev;
+ const char **names;
+ int ret, i;
+ int count;
+
+ count = device_property_string_array_count(dev, "gpio-line-names");
+ if (count < 0)
+ return 0;
+
+ /*
+ * When offset is set in the driver side we assume the driver internally
+ * is using more than one gpiochip per the same device. We have to stop
+ * setting friendly names if the specified ones with 'gpio-line-names'
+ * are less than the offset in the device itself. This means all the
+ * lines are not present for every single pin within all the internal
+ * gpiochips.
+ */
+ if (count <= chip->offset) {
+ dev_warn(dev, "gpio-line-names too short (length %d), cannot map names for the gpiochip at offset %u\n",
+ count, chip->offset);
+ return 0;
+ }
+
+ names = kcalloc(count, sizeof(*names), GFP_KERNEL);
+ if (!names)
+ return -ENOMEM;
+
+ ret = device_property_read_string_array(dev, "gpio-line-names",
+ names, count);
+ if (ret < 0) {
+ dev_warn(dev, "failed to read GPIO line names\n");
+ kfree(names);
+ return ret;
+ }
+
+ /*
+ * When more that one gpiochip per device is used, 'count' can
+ * contain at most number gpiochips x chip->ngpio. We have to
+ * correctly distribute all defined lines taking into account
+ * chip->offset as starting point from where we will assign
+ * the names to pins from the 'names' array. Since property
+ * 'gpio-line-names' cannot contains gaps, we have to be sure
+ * we only assign those pins that really exists since chip->ngpio
+ * can be different of the chip->offset.
+ */
+ count = (count > chip->offset) ? count - chip->offset : count;
+ if (count > chip->ngpio)
+ count = chip->ngpio;
+
+ for (i = 0; i < count; i++) {
+ /*
+ * Allow overriding "fixed" names provided by the GPIO
+ * provider. The "fixed" names are more often than not
+ * generic and less informative than the names given in
+ * device properties.
+ */
+ if (names[chip->offset + i] && names[chip->offset + i][0])
+ gdev->descs[i].name = names[chip->offset + i];
+ }
+
+ kfree(names);
+
+ return 0;
+}
+
+static unsigned long *gpiochip_allocate_mask(struct gpio_chip *gc)
+{
+ unsigned long *p;
+
+ p = bitmap_alloc(gc->ngpio, GFP_KERNEL);
+ if (!p)
+ return NULL;
+
+ /* Assume by default all GPIOs are valid */
+ bitmap_fill(p, gc->ngpio);
+
+ return p;
+}
+
+static void gpiochip_free_mask(unsigned long **p)
+{
+ bitmap_free(*p);
+ *p = NULL;
+}
+
+static unsigned int gpiochip_count_reserved_ranges(struct gpio_chip *gc)
+{
+ struct device *dev = &gc->gpiodev->dev;
+ int size;
+
+ /* Format is "start, count, ..." */
+ size = device_property_count_u32(dev, "gpio-reserved-ranges");
+ if (size > 0 && size % 2 == 0)
+ return size;
+
+ return 0;
+}
+
+static int gpiochip_apply_reserved_ranges(struct gpio_chip *gc)
+{
+ struct device *dev = &gc->gpiodev->dev;
+ unsigned int size;
+ u32 *ranges;
+ int ret;
+
+ size = gpiochip_count_reserved_ranges(gc);
+ if (size == 0)
+ return 0;
+
+ ranges = kmalloc_array(size, sizeof(*ranges), GFP_KERNEL);
+ if (!ranges)
+ return -ENOMEM;
+
+ ret = device_property_read_u32_array(dev, "gpio-reserved-ranges",
+ ranges, size);
+ if (ret) {
+ kfree(ranges);
+ return ret;
+ }
+
+ while (size) {
+ u32 count = ranges[--size];
+ u32 start = ranges[--size];
+
+ if (start >= gc->ngpio || start + count > gc->ngpio)
+ continue;
+
+ bitmap_clear(gc->valid_mask, start, count);
+ }
+
+ kfree(ranges);
+ return 0;
+}
+
+static int gpiochip_init_valid_mask(struct gpio_chip *gc)
+{
+ int ret;
+
+ if (!(gpiochip_count_reserved_ranges(gc) || gc->init_valid_mask))
+ return 0;
+
+ gc->valid_mask = gpiochip_allocate_mask(gc);
+ if (!gc->valid_mask)
+ return -ENOMEM;
+
+ ret = gpiochip_apply_reserved_ranges(gc);
+ if (ret)
+ return ret;
+
+ if (gc->init_valid_mask)
+ return gc->init_valid_mask(gc,
+ gc->valid_mask,
+ gc->ngpio);
+
+ return 0;
+}
+
+static void gpiochip_free_valid_mask(struct gpio_chip *gc)
+{
+ gpiochip_free_mask(&gc->valid_mask);
+}
+
+static int gpiochip_add_pin_ranges(struct gpio_chip *gc)
+{
+ /*
+ * Device Tree platforms are supposed to use "gpio-ranges"
+ * property. This check ensures that the ->add_pin_ranges()
+ * won't be called for them.
+ */
+ if (device_property_present(&gc->gpiodev->dev, "gpio-ranges"))
+ return 0;
+
+ if (gc->add_pin_ranges)
+ return gc->add_pin_ranges(gc);
+
+ return 0;
+}
+
+bool gpiochip_line_is_valid(const struct gpio_chip *gc,
+ unsigned int offset)
+{
+ /* No mask means all valid */
+ if (likely(!gc->valid_mask))
+ return true;
+ return test_bit(offset, gc->valid_mask);
+}
+EXPORT_SYMBOL_GPL(gpiochip_line_is_valid);
+
+static void gpiodev_release(struct device *dev)
+{
+ struct gpio_device *gdev = to_gpio_device(dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+ list_del(&gdev->list);
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ ida_free(&gpio_ida, gdev->id);
+ kfree_const(gdev->label);
+ kfree(gdev->descs);
+ kfree(gdev);
+}
+
+#ifdef CONFIG_GPIO_CDEV
+#define gcdev_register(gdev, devt) gpiolib_cdev_register((gdev), (devt))
+#define gcdev_unregister(gdev) gpiolib_cdev_unregister((gdev))
+#else
+/*
+ * gpiolib_cdev_register() indirectly calls device_add(), which is still
+ * required even when cdev is not selected.
+ */
+#define gcdev_register(gdev, devt) device_add(&(gdev)->dev)
+#define gcdev_unregister(gdev) device_del(&(gdev)->dev)
+#endif
+
+static int gpiochip_setup_dev(struct gpio_device *gdev)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(&gdev->dev);
+ int ret;
+
+ /*
+ * If fwnode doesn't belong to another device, it's safe to clear its
+ * initialized flag.
+ */
+ if (fwnode && !fwnode->dev)
+ fwnode_dev_initialized(fwnode, false);
+
+ ret = gcdev_register(gdev, gpio_devt);
+ if (ret)
+ return ret;
+
+ /* From this point, the .release() function cleans up gpio_device */
+ gdev->dev.release = gpiodev_release;
+
+ ret = gpiochip_sysfs_register(gdev);
+ if (ret)
+ goto err_remove_device;
+
+ dev_dbg(&gdev->dev, "registered GPIOs %d to %d on %s\n", gdev->base,
+ gdev->base + gdev->ngpio - 1, gdev->chip->label ? : "generic");
+
+ return 0;
+
+err_remove_device:
+ gcdev_unregister(gdev);
+ return ret;
+}
+
+static void gpiochip_machine_hog(struct gpio_chip *gc, struct gpiod_hog *hog)
+{
+ struct gpio_desc *desc;
+ int rv;
+
+ desc = gpiochip_get_desc(gc, hog->chip_hwnum);
+ if (IS_ERR(desc)) {
+ chip_err(gc, "%s: unable to get GPIO desc: %ld\n", __func__,
+ PTR_ERR(desc));
+ return;
+ }
+
+ if (test_bit(FLAG_IS_HOGGED, &desc->flags))
+ return;
+
+ rv = gpiod_hog(desc, hog->line_name, hog->lflags, hog->dflags);
+ if (rv)
+ gpiod_err(desc, "%s: unable to hog GPIO line (%s:%u): %d\n",
+ __func__, gc->label, hog->chip_hwnum, rv);
+}
+
+static void machine_gpiochip_add(struct gpio_chip *gc)
+{
+ struct gpiod_hog *hog;
+
+ mutex_lock(&gpio_machine_hogs_mutex);
+
+ list_for_each_entry(hog, &gpio_machine_hogs, list) {
+ if (!strcmp(gc->label, hog->chip_label))
+ gpiochip_machine_hog(gc, hog);
+ }
+
+ mutex_unlock(&gpio_machine_hogs_mutex);
+}
+
+static void gpiochip_setup_devs(void)
+{
+ struct gpio_device *gdev;
+ int ret;
+
+ list_for_each_entry(gdev, &gpio_devices, list) {
+ ret = gpiochip_setup_dev(gdev);
+ if (ret)
+ dev_err(&gdev->dev,
+ "Failed to initialize gpio device (%d)\n", ret);
+ }
+}
+
+static void gpiochip_set_data(struct gpio_chip *gc, void *data)
+{
+ gc->gpiodev->data = data;
+}
+
+/**
+ * gpiochip_get_data() - get per-subdriver data for the chip
+ * @gc: GPIO chip
+ *
+ * Returns:
+ * The per-subdriver data for the chip.
+ */
+void *gpiochip_get_data(struct gpio_chip *gc)
+{
+ return gc->gpiodev->data;
+}
+EXPORT_SYMBOL_GPL(gpiochip_get_data);
+
+int gpiochip_get_ngpios(struct gpio_chip *gc, struct device *dev)
+{
+ u32 ngpios = gc->ngpio;
+ int ret;
+
+ if (ngpios == 0) {
+ ret = device_property_read_u32(dev, "ngpios", &ngpios);
+ if (ret == -ENODATA)
+ /*
+ * -ENODATA means that there is no property found and
+ * we want to issue the error message to the user.
+ * Besides that, we want to return different error code
+ * to state that supplied value is not valid.
+ */
+ ngpios = 0;
+ else if (ret)
+ return ret;
+
+ gc->ngpio = ngpios;
+ }
+
+ if (gc->ngpio == 0) {
+ chip_err(gc, "tried to insert a GPIO chip with zero lines\n");
+ return -EINVAL;
+ }
+
+ if (gc->ngpio > FASTPATH_NGPIO)
+ chip_warn(gc, "line cnt %u is greater than fast path cnt %u\n",
+ gc->ngpio, FASTPATH_NGPIO);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gpiochip_get_ngpios);
+
+int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
+ struct lock_class_key *lock_key,
+ struct lock_class_key *request_key)
+{
+ struct gpio_device *gdev;
+ unsigned long flags;
+ unsigned int i;
+ int base = 0;
+ int ret = 0;
+
+ /*
+ * First: allocate and populate the internal stat container, and
+ * set up the struct device.
+ */
+ gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
+ if (!gdev)
+ return -ENOMEM;
+ gdev->dev.bus = &gpio_bus_type;
+ gdev->dev.parent = gc->parent;
+ gdev->chip = gc;
+
+ gc->gpiodev = gdev;
+ gpiochip_set_data(gc, data);
+
+ /*
+ * If the calling driver did not initialize firmware node,
+ * do it here using the parent device, if any.
+ */
+ if (gc->fwnode)
+ device_set_node(&gdev->dev, gc->fwnode);
+ else if (gc->parent)
+ device_set_node(&gdev->dev, dev_fwnode(gc->parent));
+
+ gdev->id = ida_alloc(&gpio_ida, GFP_KERNEL);
+ if (gdev->id < 0) {
+ ret = gdev->id;
+ goto err_free_gdev;
+ }
+
+ ret = dev_set_name(&gdev->dev, GPIOCHIP_NAME "%d", gdev->id);
+ if (ret)
+ goto err_free_ida;
+
+ device_initialize(&gdev->dev);
+ if (gc->parent && gc->parent->driver)
+ gdev->owner = gc->parent->driver->owner;
+ else if (gc->owner)
+ /* TODO: remove chip->owner */
+ gdev->owner = gc->owner;
+ else
+ gdev->owner = THIS_MODULE;
+
+ ret = gpiochip_get_ngpios(gc, &gdev->dev);
+ if (ret)
+ goto err_free_dev_name;
+
+ gdev->descs = kcalloc(gc->ngpio, sizeof(*gdev->descs), GFP_KERNEL);
+ if (!gdev->descs) {
+ ret = -ENOMEM;
+ goto err_free_dev_name;
+ }
+
+ gdev->label = kstrdup_const(gc->label ?: "unknown", GFP_KERNEL);
+ if (!gdev->label) {
+ ret = -ENOMEM;
+ goto err_free_descs;
+ }
+
+ gdev->ngpio = gc->ngpio;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ /*
+ * TODO: this allocates a Linux GPIO number base in the global
+ * GPIO numberspace for this chip. In the long run we want to
+ * get *rid* of this numberspace and use only descriptors, but
+ * it may be a pipe dream. It will not happen before we get rid
+ * of the sysfs interface anyways.
+ */
+ base = gc->base;
+ if (base < 0) {
+ base = gpiochip_find_base(gc->ngpio);
+ if (base < 0) {
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ ret = base;
+ base = 0;
+ goto err_free_label;
+ }
+ /*
+ * TODO: it should not be necessary to reflect the assigned
+ * base outside of the GPIO subsystem. Go over drivers and
+ * see if anyone makes use of this, else drop this and assign
+ * a poison instead.
+ */
+ gc->base = base;
+ } else {
+ dev_warn(&gdev->dev,
+ "Static allocation of GPIO base is deprecated, use dynamic allocation.\n");
+ }
+ gdev->base = base;
+
+ ret = gpiodev_add_to_list(gdev);
+ if (ret) {
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ chip_err(gc, "GPIO integer space overlap, cannot add chip\n");
+ goto err_free_label;
+ }
+
+ for (i = 0; i < gc->ngpio; i++)
+ gdev->descs[i].gdev = gdev;
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ BLOCKING_INIT_NOTIFIER_HEAD(&gdev->line_state_notifier);
+ BLOCKING_INIT_NOTIFIER_HEAD(&gdev->device_notifier);
+ init_rwsem(&gdev->sem);
+
+#ifdef CONFIG_PINCTRL
+ INIT_LIST_HEAD(&gdev->pin_ranges);
+#endif
+
+ if (gc->names) {
+ ret = gpiochip_set_desc_names(gc);
+ if (ret)
+ goto err_remove_from_list;
+ }
+ ret = gpiochip_set_names(gc);
+ if (ret)
+ goto err_remove_from_list;
+
+ ret = gpiochip_init_valid_mask(gc);
+ if (ret)
+ goto err_remove_from_list;
+
+ ret = of_gpiochip_add(gc);
+ if (ret)
+ goto err_free_gpiochip_mask;
+
+ for (i = 0; i < gc->ngpio; i++) {
+ struct gpio_desc *desc = &gdev->descs[i];
+
+ if (gc->get_direction && gpiochip_line_is_valid(gc, i)) {
+ assign_bit(FLAG_IS_OUT,
+ &desc->flags, !gc->get_direction(gc, i));
+ } else {
+ assign_bit(FLAG_IS_OUT,
+ &desc->flags, !gc->direction_input);
+ }
+ }
+
+ ret = gpiochip_add_pin_ranges(gc);
+ if (ret)
+ goto err_remove_of_chip;
+
+ acpi_gpiochip_add(gc);
+
+ machine_gpiochip_add(gc);
+
+ ret = gpiochip_irqchip_init_valid_mask(gc);
+ if (ret)
+ goto err_remove_acpi_chip;
+
+ ret = gpiochip_irqchip_init_hw(gc);
+ if (ret)
+ goto err_remove_acpi_chip;
+
+ ret = gpiochip_add_irqchip(gc, lock_key, request_key);
+ if (ret)
+ goto err_remove_irqchip_mask;
+
+ /*
+ * By first adding the chardev, and then adding the device,
+ * we get a device node entry in sysfs under
+ * /sys/bus/gpio/devices/gpiochipN/dev that can be used for
+ * coldplug of device nodes and other udev business.
+ * We can do this only if gpiolib has been initialized.
+ * Otherwise, defer until later.
+ */
+ if (gpiolib_initialized) {
+ ret = gpiochip_setup_dev(gdev);
+ if (ret)
+ goto err_remove_irqchip;
+ }
+ return 0;
+
+err_remove_irqchip:
+ gpiochip_irqchip_remove(gc);
+err_remove_irqchip_mask:
+ gpiochip_irqchip_free_valid_mask(gc);
+err_remove_acpi_chip:
+ acpi_gpiochip_remove(gc);
+err_remove_of_chip:
+ gpiochip_free_hogs(gc);
+ of_gpiochip_remove(gc);
+err_free_gpiochip_mask:
+ gpiochip_remove_pin_ranges(gc);
+ gpiochip_free_valid_mask(gc);
+ if (gdev->dev.release) {
+ /* release() has been registered by gpiochip_setup_dev() */
+ gpio_device_put(gdev);
+ goto err_print_message;
+ }
+err_remove_from_list:
+ spin_lock_irqsave(&gpio_lock, flags);
+ list_del(&gdev->list);
+ spin_unlock_irqrestore(&gpio_lock, flags);
+err_free_label:
+ kfree_const(gdev->label);
+err_free_descs:
+ kfree(gdev->descs);
+err_free_dev_name:
+ kfree(dev_name(&gdev->dev));
+err_free_ida:
+ ida_free(&gpio_ida, gdev->id);
+err_free_gdev:
+ kfree(gdev);
+err_print_message:
+ /* failures here can mean systems won't boot... */
+ if (ret != -EPROBE_DEFER) {
+ pr_err("%s: GPIOs %d..%d (%s) failed to register, %d\n", __func__,
+ base, base + (int)gc->ngpio - 1,
+ gc->label ? : "generic", ret);
+ }
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gpiochip_add_data_with_key);
+
+/**
+ * gpiochip_remove() - unregister a gpio_chip
+ * @gc: the chip to unregister
+ *
+ * A gpio_chip with any GPIOs still requested may not be removed.
+ */
+void gpiochip_remove(struct gpio_chip *gc)
+{
+ struct gpio_device *gdev = gc->gpiodev;
+ unsigned long flags;
+ unsigned int i;
+
+ down_write(&gdev->sem);
+
+ /* FIXME: should the legacy sysfs handling be moved to gpio_device? */
+ gpiochip_sysfs_unregister(gdev);
+ gpiochip_free_hogs(gc);
+ /* Numb the device, cancelling all outstanding operations */
+ gdev->chip = NULL;
+ gpiochip_irqchip_remove(gc);
+ acpi_gpiochip_remove(gc);
+ of_gpiochip_remove(gc);
+ gpiochip_remove_pin_ranges(gc);
+ gpiochip_free_valid_mask(gc);
+ /*
+ * We accept no more calls into the driver from this point, so
+ * NULL the driver data pointer.
+ */
+ gpiochip_set_data(gc, NULL);
+
+ spin_lock_irqsave(&gpio_lock, flags);
+ for (i = 0; i < gdev->ngpio; i++) {
+ if (gpiochip_is_requested(gc, i))
+ break;
+ }
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ if (i != gdev->ngpio)
+ dev_crit(&gdev->dev,
+ "REMOVING GPIOCHIP WITH GPIOS STILL REQUESTED\n");
+
+ /*
+ * The gpiochip side puts its use of the device to rest here:
+ * if there are no userspace clients, the chardev and device will
+ * be removed, else it will be dangling until the last user is
+ * gone.
+ */
+ gcdev_unregister(gdev);
+ up_write(&gdev->sem);
+ gpio_device_put(gdev);
+}
+EXPORT_SYMBOL_GPL(gpiochip_remove);
+
+/*
+ * FIXME: This will be removed soon.
+ *
+ * This function is depracated, don't use.
+ */
+struct gpio_chip *gpiochip_find(void *data,
+ int (*match)(struct gpio_chip *gc,
+ void *data))
+{
+ struct gpio_device *gdev;
+ struct gpio_chip *gc = NULL;
+
+ gdev = gpio_device_find(data, match);
+ if (gdev) {
+ gc = gdev->chip;
+ gpio_device_put(gdev);
+ }
+
+ return gc;
+}
+EXPORT_SYMBOL_GPL(gpiochip_find);
+
+/**
+ * gpio_device_find() - find a specific GPIO device
+ * @data: data to pass to match function
+ * @match: Callback function to check gpio_chip
+ *
+ * Returns:
+ * New reference to struct gpio_device.
+ *
+ * Similar to bus_find_device(). It returns a reference to a gpio_device as
+ * determined by a user supplied @match callback. The callback should return
+ * 0 if the device doesn't match and non-zero if it does. If the callback
+ * returns non-zero, this function will return to the caller and not iterate
+ * over any more gpio_devices.
+ *
+ * The callback takes the GPIO chip structure as argument. During the execution
+ * of the callback function the chip is protected from being freed. TODO: This
+ * actually has yet to be implemented.
+ *
+ * If the function returns non-NULL, the returned reference must be freed by
+ * the caller using gpio_device_put().
+ */
+struct gpio_device *gpio_device_find(void *data,
+ int (*match)(struct gpio_chip *gc,
+ void *data))
+{
+ struct gpio_device *gdev;
+
+ /*
+ * Not yet but in the future the spinlock below will become a mutex.
+ * Annotate this function before anyone tries to use it in interrupt
+ * context like it happened with gpiochip_find().
+ */
+ might_sleep();
+
+ guard(spinlock_irqsave)(&gpio_lock);
+
+ list_for_each_entry(gdev, &gpio_devices, list) {
+ if (gdev->chip && match(gdev->chip, data))
+ return gpio_device_get(gdev);
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(gpio_device_find);
+
+static int gpiochip_match_name(struct gpio_chip *gc, void *data)
+{
+ const char *name = data;
+
+ return !strcmp(gc->label, name);
+}
+
+static struct gpio_chip *find_chip_by_name(const char *name)
+{
+ return gpiochip_find((void *)name, gpiochip_match_name);
+}
+
+/**
+ * gpio_device_get() - Increase the reference count of this GPIO device
+ * @gdev: GPIO device to increase the refcount for
+ *
+ * Returns:
+ * Pointer to @gdev.
+ */
+struct gpio_device *gpio_device_get(struct gpio_device *gdev)
+{
+ return to_gpio_device(get_device(&gdev->dev));
+}
+EXPORT_SYMBOL_GPL(gpio_device_get);
+
+/**
+ * gpio_device_put() - Decrease the reference count of this GPIO device and
+ * possibly free all resources associated with it.
+ * @gdev: GPIO device to decrease the reference count for
+ */
+void gpio_device_put(struct gpio_device *gdev)
+{
+ put_device(&gdev->dev);
+}
+EXPORT_SYMBOL_GPL(gpio_device_put);
+
+#ifdef CONFIG_GPIOLIB_IRQCHIP
+
+/*
+ * The following is irqchip helper code for gpiochips.
+ */
+
+static int gpiochip_irqchip_init_hw(struct gpio_chip *gc)
+{
+ struct gpio_irq_chip *girq = &gc->irq;
+
+ if (!girq->init_hw)
+ return 0;
+
+ return girq->init_hw(gc);
+}
+
+static int gpiochip_irqchip_init_valid_mask(struct gpio_chip *gc)
+{
+ struct gpio_irq_chip *girq = &gc->irq;
+
+ if (!girq->init_valid_mask)
+ return 0;
+
+ girq->valid_mask = gpiochip_allocate_mask(gc);
+ if (!girq->valid_mask)
+ return -ENOMEM;
+
+ girq->init_valid_mask(gc, girq->valid_mask, gc->ngpio);
+
+ return 0;
+}
+
+static void gpiochip_irqchip_free_valid_mask(struct gpio_chip *gc)
+{
+ gpiochip_free_mask(&gc->irq.valid_mask);
+}
+
+bool gpiochip_irqchip_irq_valid(const struct gpio_chip *gc,
+ unsigned int offset)
+{
+ if (!gpiochip_line_is_valid(gc, offset))
+ return false;
+ /* No mask means all valid */
+ if (likely(!gc->irq.valid_mask))
+ return true;
+ return test_bit(offset, gc->irq.valid_mask);
+}
+EXPORT_SYMBOL_GPL(gpiochip_irqchip_irq_valid);
+
+#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
+
+/**
+ * gpiochip_set_hierarchical_irqchip() - connects a hierarchical irqchip
+ * to a gpiochip
+ * @gc: the gpiochip to set the irqchip hierarchical handler to
+ * @irqchip: the irqchip to handle this level of the hierarchy, the interrupt
+ * will then percolate up to the parent
+ */
+static void gpiochip_set_hierarchical_irqchip(struct gpio_chip *gc,
+ struct irq_chip *irqchip)
+{
+ /* DT will deal with mapping each IRQ as we go along */
+ if (is_of_node(gc->irq.fwnode))
+ return;
+
+ /*
+ * This is for legacy and boardfile "irqchip" fwnodes: allocate
+ * irqs upfront instead of dynamically since we don't have the
+ * dynamic type of allocation that hardware description languages
+ * provide. Once all GPIO drivers using board files are gone from
+ * the kernel we can delete this code, but for a transitional period
+ * it is necessary to keep this around.
+ */
+ if (is_fwnode_irqchip(gc->irq.fwnode)) {
+ int i;
+ int ret;
+
+ for (i = 0; i < gc->ngpio; i++) {
+ struct irq_fwspec fwspec;
+ unsigned int parent_hwirq;
+ unsigned int parent_type;
+ struct gpio_irq_chip *girq = &gc->irq;
+
+ /*
+ * We call the child to parent translation function
+ * only to check if the child IRQ is valid or not.
+ * Just pick the rising edge type here as that is what
+ * we likely need to support.
+ */
+ ret = girq->child_to_parent_hwirq(gc, i,
+ IRQ_TYPE_EDGE_RISING,
+ &parent_hwirq,
+ &parent_type);
+ if (ret) {
+ chip_err(gc, "skip set-up on hwirq %d\n",
+ i);
+ continue;
+ }
+
+ fwspec.fwnode = gc->irq.fwnode;
+ /* This is the hwirq for the GPIO line side of things */
+ fwspec.param[0] = girq->child_offset_to_irq(gc, i);
+ /* Just pick something */
+ fwspec.param[1] = IRQ_TYPE_EDGE_RISING;
+ fwspec.param_count = 2;
+ ret = irq_domain_alloc_irqs(gc->irq.domain, 1,
+ NUMA_NO_NODE, &fwspec);
+ if (ret < 0) {
+ chip_err(gc,
+ "can not allocate irq for GPIO line %d parent hwirq %d in hierarchy domain: %d\n",
+ i, parent_hwirq,
+ ret);
+ }
+ }
+ }
+
+ chip_err(gc, "%s unknown fwnode type proceed anyway\n", __func__);
+
+ return;
+}
+
+static int gpiochip_hierarchy_irq_domain_translate(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ unsigned long *hwirq,
+ unsigned int *type)
+{
+ /* We support standard DT translation */
+ if (is_of_node(fwspec->fwnode) && fwspec->param_count == 2) {
+ return irq_domain_translate_twocell(d, fwspec, hwirq, type);
+ }
+
+ /* This is for board files and others not using DT */
+ if (is_fwnode_irqchip(fwspec->fwnode)) {
+ int ret;
+
+ ret = irq_domain_translate_twocell(d, fwspec, hwirq, type);
+ if (ret)
+ return ret;
+ WARN_ON(*type == IRQ_TYPE_NONE);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int gpiochip_hierarchy_irq_domain_alloc(struct irq_domain *d,
+ unsigned int irq,
+ unsigned int nr_irqs,
+ void *data)
+{
+ struct gpio_chip *gc = d->host_data;
+ irq_hw_number_t hwirq;
+ unsigned int type = IRQ_TYPE_NONE;
+ struct irq_fwspec *fwspec = data;
+ union gpio_irq_fwspec gpio_parent_fwspec = {};
+ unsigned int parent_hwirq;
+ unsigned int parent_type;
+ struct gpio_irq_chip *girq = &gc->irq;
+ int ret;
+
+ /*
+ * The nr_irqs parameter is always one except for PCI multi-MSI
+ * so this should not happen.
+ */
+ WARN_ON(nr_irqs != 1);
+
+ ret = gc->irq.child_irq_domain_ops.translate(d, fwspec, &hwirq, &type);
+ if (ret)
+ return ret;
+
+ chip_dbg(gc, "allocate IRQ %d, hwirq %lu\n", irq, hwirq);
+
+ ret = girq->child_to_parent_hwirq(gc, hwirq, type,
+ &parent_hwirq, &parent_type);
+ if (ret) {
+ chip_err(gc, "can't look up hwirq %lu\n", hwirq);
+ return ret;
+ }
+ chip_dbg(gc, "found parent hwirq %u\n", parent_hwirq);
+
+ /*
+ * We set handle_bad_irq because the .set_type() should
+ * always be invoked and set the right type of handler.
+ */
+ irq_domain_set_info(d,
+ irq,
+ hwirq,
+ gc->irq.chip,
+ gc,
+ girq->handler,
+ NULL, NULL);
+ irq_set_probe(irq);
+
+ /* This parent only handles asserted level IRQs */
+ ret = girq->populate_parent_alloc_arg(gc, &gpio_parent_fwspec,
+ parent_hwirq, parent_type);
+ if (ret)
+ return ret;
+
+ chip_dbg(gc, "alloc_irqs_parent for %d parent hwirq %d\n",
+ irq, parent_hwirq);
+ irq_set_lockdep_class(irq, gc->irq.lock_key, gc->irq.request_key);
+ ret = irq_domain_alloc_irqs_parent(d, irq, 1, &gpio_parent_fwspec);
+ /*
+ * If the parent irqdomain is msi, the interrupts have already
+ * been allocated, so the EEXIST is good.
+ */
+ if (irq_domain_is_msi(d->parent) && (ret == -EEXIST))
+ ret = 0;
+ if (ret)
+ chip_err(gc,
+ "failed to allocate parent hwirq %d for hwirq %lu\n",
+ parent_hwirq, hwirq);
+
+ return ret;
+}
+
+static unsigned int gpiochip_child_offset_to_irq_noop(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ return offset;
+}
+
+static void gpiochip_hierarchy_setup_domain_ops(struct irq_domain_ops *ops)
+{
+ ops->activate = gpiochip_irq_domain_activate;
+ ops->deactivate = gpiochip_irq_domain_deactivate;
+ ops->alloc = gpiochip_hierarchy_irq_domain_alloc;
+
+ /*
+ * We only allow overriding the translate() and free() functions for
+ * hierarchical chips, and this should only be done if the user
+ * really need something other than 1:1 translation for translate()
+ * callback and free if user wants to free up any resources which
+ * were allocated during callbacks, for example populate_parent_alloc_arg.
+ */
+ if (!ops->translate)
+ ops->translate = gpiochip_hierarchy_irq_domain_translate;
+ if (!ops->free)
+ ops->free = irq_domain_free_irqs_common;
+}
+
+static struct irq_domain *gpiochip_hierarchy_create_domain(struct gpio_chip *gc)
+{
+ struct irq_domain *domain;
+
+ if (!gc->irq.child_to_parent_hwirq ||
+ !gc->irq.fwnode) {
+ chip_err(gc, "missing irqdomain vital data\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (!gc->irq.child_offset_to_irq)
+ gc->irq.child_offset_to_irq = gpiochip_child_offset_to_irq_noop;
+
+ if (!gc->irq.populate_parent_alloc_arg)
+ gc->irq.populate_parent_alloc_arg =
+ gpiochip_populate_parent_fwspec_twocell;
+
+ gpiochip_hierarchy_setup_domain_ops(&gc->irq.child_irq_domain_ops);
+
+ domain = irq_domain_create_hierarchy(
+ gc->irq.parent_domain,
+ 0,
+ gc->ngpio,
+ gc->irq.fwnode,
+ &gc->irq.child_irq_domain_ops,
+ gc);
+
+ if (!domain)
+ return ERR_PTR(-ENOMEM);
+
+ gpiochip_set_hierarchical_irqchip(gc, gc->irq.chip);
+
+ return domain;
+}
+
+static bool gpiochip_hierarchy_is_hierarchical(struct gpio_chip *gc)
+{
+ return !!gc->irq.parent_domain;
+}
+
+int gpiochip_populate_parent_fwspec_twocell(struct gpio_chip *gc,
+ union gpio_irq_fwspec *gfwspec,
+ unsigned int parent_hwirq,
+ unsigned int parent_type)
+{
+ struct irq_fwspec *fwspec = &gfwspec->fwspec;
+
+ fwspec->fwnode = gc->irq.parent_domain->fwnode;
+ fwspec->param_count = 2;
+ fwspec->param[0] = parent_hwirq;
+ fwspec->param[1] = parent_type;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gpiochip_populate_parent_fwspec_twocell);
+
+int gpiochip_populate_parent_fwspec_fourcell(struct gpio_chip *gc,
+ union gpio_irq_fwspec *gfwspec,
+ unsigned int parent_hwirq,
+ unsigned int parent_type)
+{
+ struct irq_fwspec *fwspec = &gfwspec->fwspec;
+
+ fwspec->fwnode = gc->irq.parent_domain->fwnode;
+ fwspec->param_count = 4;
+ fwspec->param[0] = 0;
+ fwspec->param[1] = parent_hwirq;
+ fwspec->param[2] = 0;
+ fwspec->param[3] = parent_type;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gpiochip_populate_parent_fwspec_fourcell);
+
+#else
+
+static struct irq_domain *gpiochip_hierarchy_create_domain(struct gpio_chip *gc)
+{
+ return ERR_PTR(-EINVAL);
+}
+
+static bool gpiochip_hierarchy_is_hierarchical(struct gpio_chip *gc)
+{
+ return false;
+}
+
+#endif /* CONFIG_IRQ_DOMAIN_HIERARCHY */
+
+/**
+ * gpiochip_irq_map() - maps an IRQ into a GPIO irqchip
+ * @d: the irqdomain used by this irqchip
+ * @irq: the global irq number used by this GPIO irqchip irq
+ * @hwirq: the local IRQ/GPIO line offset on this gpiochip
+ *
+ * This function will set up the mapping for a certain IRQ line on a
+ * gpiochip by assigning the gpiochip as chip data, and using the irqchip
+ * stored inside the gpiochip.
+ */
+int gpiochip_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hwirq)
+{
+ struct gpio_chip *gc = d->host_data;
+ int ret = 0;
+
+ if (!gpiochip_irqchip_irq_valid(gc, hwirq))
+ return -ENXIO;
+
+ irq_set_chip_data(irq, gc);
+ /*
+ * This lock class tells lockdep that GPIO irqs are in a different
+ * category than their parents, so it won't report false recursion.
+ */
+ irq_set_lockdep_class(irq, gc->irq.lock_key, gc->irq.request_key);
+ irq_set_chip_and_handler(irq, gc->irq.chip, gc->irq.handler);
+ /* Chips that use nested thread handlers have them marked */
+ if (gc->irq.threaded)
+ irq_set_nested_thread(irq, 1);
+ irq_set_noprobe(irq);
+
+ if (gc->irq.num_parents == 1)
+ ret = irq_set_parent(irq, gc->irq.parents[0]);
+ else if (gc->irq.map)
+ ret = irq_set_parent(irq, gc->irq.map[hwirq]);
+
+ if (ret < 0)
+ return ret;
+
+ /*
+ * No set-up of the hardware will happen if IRQ_TYPE_NONE
+ * is passed as default type.
+ */
+ if (gc->irq.default_type != IRQ_TYPE_NONE)
+ irq_set_irq_type(irq, gc->irq.default_type);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gpiochip_irq_map);
+
+void gpiochip_irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+ struct gpio_chip *gc = d->host_data;
+
+ if (gc->irq.threaded)
+ irq_set_nested_thread(irq, 0);
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+}
+EXPORT_SYMBOL_GPL(gpiochip_irq_unmap);
+
+static const struct irq_domain_ops gpiochip_domain_ops = {
+ .map = gpiochip_irq_map,
+ .unmap = gpiochip_irq_unmap,
+ /* Virtually all GPIO irqchips are twocell:ed */
+ .xlate = irq_domain_xlate_twocell,
+};
+
+static struct irq_domain *gpiochip_simple_create_domain(struct gpio_chip *gc)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(&gc->gpiodev->dev);
+ struct irq_domain *domain;
+
+ domain = irq_domain_create_simple(fwnode, gc->ngpio, gc->irq.first,
+ &gpiochip_domain_ops, gc);
+ if (!domain)
+ return ERR_PTR(-EINVAL);
+
+ return domain;
+}
+
+/*
+ * TODO: move these activate/deactivate in under the hierarchicial
+ * irqchip implementation as static once SPMI and SSBI (all external
+ * users) are phased over.
+ */
+/**
+ * gpiochip_irq_domain_activate() - Lock a GPIO to be used as an IRQ
+ * @domain: The IRQ domain used by this IRQ chip
+ * @data: Outermost irq_data associated with the IRQ
+ * @reserve: If set, only reserve an interrupt vector instead of assigning one
+ *
+ * This function is a wrapper that calls gpiochip_lock_as_irq() and is to be
+ * used as the activate function for the &struct irq_domain_ops. The host_data
+ * for the IRQ domain must be the &struct gpio_chip.
+ */
+int gpiochip_irq_domain_activate(struct irq_domain *domain,
+ struct irq_data *data, bool reserve)
+{
+ struct gpio_chip *gc = domain->host_data;
+ unsigned int hwirq = irqd_to_hwirq(data);
+
+ return gpiochip_lock_as_irq(gc, hwirq);
+}
+EXPORT_SYMBOL_GPL(gpiochip_irq_domain_activate);
+
+/**
+ * gpiochip_irq_domain_deactivate() - Unlock a GPIO used as an IRQ
+ * @domain: The IRQ domain used by this IRQ chip
+ * @data: Outermost irq_data associated with the IRQ
+ *
+ * This function is a wrapper that will call gpiochip_unlock_as_irq() and is to
+ * be used as the deactivate function for the &struct irq_domain_ops. The
+ * host_data for the IRQ domain must be the &struct gpio_chip.
+ */
+void gpiochip_irq_domain_deactivate(struct irq_domain *domain,
+ struct irq_data *data)
+{
+ struct gpio_chip *gc = domain->host_data;
+ unsigned int hwirq = irqd_to_hwirq(data);
+
+ return gpiochip_unlock_as_irq(gc, hwirq);
+}
+EXPORT_SYMBOL_GPL(gpiochip_irq_domain_deactivate);
+
+static int gpiochip_to_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ struct irq_domain *domain = gc->irq.domain;
+
+#ifdef CONFIG_GPIOLIB_IRQCHIP
+ /*
+ * Avoid race condition with other code, which tries to lookup
+ * an IRQ before the irqchip has been properly registered,
+ * i.e. while gpiochip is still being brought up.
+ */
+ if (!gc->irq.initialized)
+ return -EPROBE_DEFER;
+#endif
+
+ if (!gpiochip_irqchip_irq_valid(gc, offset))
+ return -ENXIO;
+
+#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
+ if (irq_domain_is_hierarchy(domain)) {
+ struct irq_fwspec spec;
+
+ spec.fwnode = domain->fwnode;
+ spec.param_count = 2;
+ spec.param[0] = gc->irq.child_offset_to_irq(gc, offset);
+ spec.param[1] = IRQ_TYPE_NONE;
+
+ return irq_create_fwspec_mapping(&spec);
+ }
+#endif
+
+ return irq_create_mapping(domain, offset);
+}
+
+int gpiochip_irq_reqres(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ unsigned int hwirq = irqd_to_hwirq(d);
+
+ return gpiochip_reqres_irq(gc, hwirq);
+}
+EXPORT_SYMBOL(gpiochip_irq_reqres);
+
+void gpiochip_irq_relres(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ unsigned int hwirq = irqd_to_hwirq(d);
+
+ gpiochip_relres_irq(gc, hwirq);
+}
+EXPORT_SYMBOL(gpiochip_irq_relres);
+
+static void gpiochip_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ unsigned int hwirq = irqd_to_hwirq(d);
+
+ if (gc->irq.irq_mask)
+ gc->irq.irq_mask(d);
+ gpiochip_disable_irq(gc, hwirq);
+}
+
+static void gpiochip_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ unsigned int hwirq = irqd_to_hwirq(d);
+
+ gpiochip_enable_irq(gc, hwirq);
+ if (gc->irq.irq_unmask)
+ gc->irq.irq_unmask(d);
+}
+
+static void gpiochip_irq_enable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ unsigned int hwirq = irqd_to_hwirq(d);
+
+ gpiochip_enable_irq(gc, hwirq);
+ gc->irq.irq_enable(d);
+}
+
+static void gpiochip_irq_disable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ unsigned int hwirq = irqd_to_hwirq(d);
+
+ gc->irq.irq_disable(d);
+ gpiochip_disable_irq(gc, hwirq);
+}
+
+static void gpiochip_set_irq_hooks(struct gpio_chip *gc)
+{
+ struct irq_chip *irqchip = gc->irq.chip;
+
+ if (irqchip->flags & IRQCHIP_IMMUTABLE)
+ return;
+
+ chip_warn(gc, "not an immutable chip, please consider fixing it!\n");
+
+ if (!irqchip->irq_request_resources &&
+ !irqchip->irq_release_resources) {
+ irqchip->irq_request_resources = gpiochip_irq_reqres;
+ irqchip->irq_release_resources = gpiochip_irq_relres;
+ }
+ if (WARN_ON(gc->irq.irq_enable))
+ return;
+ /* Check if the irqchip already has this hook... */
+ if (irqchip->irq_enable == gpiochip_irq_enable ||
+ irqchip->irq_mask == gpiochip_irq_mask) {
+ /*
+ * ...and if so, give a gentle warning that this is bad
+ * practice.
+ */
+ chip_info(gc,
+ "detected irqchip that is shared with multiple gpiochips: please fix the driver.\n");
+ return;
+ }
+
+ if (irqchip->irq_disable) {
+ gc->irq.irq_disable = irqchip->irq_disable;
+ irqchip->irq_disable = gpiochip_irq_disable;
+ } else {
+ gc->irq.irq_mask = irqchip->irq_mask;
+ irqchip->irq_mask = gpiochip_irq_mask;
+ }
+
+ if (irqchip->irq_enable) {
+ gc->irq.irq_enable = irqchip->irq_enable;
+ irqchip->irq_enable = gpiochip_irq_enable;
+ } else {
+ gc->irq.irq_unmask = irqchip->irq_unmask;
+ irqchip->irq_unmask = gpiochip_irq_unmask;
+ }
+}
+
+static int gpiochip_irqchip_add_allocated_domain(struct gpio_chip *gc,
+ struct irq_domain *domain,
+ bool allocated_externally)
+{
+ if (!domain)
+ return -EINVAL;
+
+ if (gc->to_irq)
+ chip_warn(gc, "to_irq is redefined in %s and you shouldn't rely on it\n", __func__);
+
+ gc->to_irq = gpiochip_to_irq;
+ gc->irq.domain = domain;
+ gc->irq.domain_is_allocated_externally = allocated_externally;
+
+ /*
+ * Using barrier() here to prevent compiler from reordering
+ * gc->irq.initialized before adding irqdomain.
+ */
+ barrier();
+
+ gc->irq.initialized = true;
+
+ return 0;
+}
+
+/**
+ * gpiochip_add_irqchip() - adds an IRQ chip to a GPIO chip
+ * @gc: the GPIO chip to add the IRQ chip to
+ * @lock_key: lockdep class for IRQ lock
+ * @request_key: lockdep class for IRQ request
+ */
+static int gpiochip_add_irqchip(struct gpio_chip *gc,
+ struct lock_class_key *lock_key,
+ struct lock_class_key *request_key)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(&gc->gpiodev->dev);
+ struct irq_chip *irqchip = gc->irq.chip;
+ struct irq_domain *domain;
+ unsigned int type;
+ unsigned int i;
+ int ret;
+
+ if (!irqchip)
+ return 0;
+
+ if (gc->irq.parent_handler && gc->can_sleep) {
+ chip_err(gc, "you cannot have chained interrupts on a chip that may sleep\n");
+ return -EINVAL;
+ }
+
+ type = gc->irq.default_type;
+
+ /*
+ * Specifying a default trigger is a terrible idea if DT or ACPI is
+ * used to configure the interrupts, as you may end up with
+ * conflicting triggers. Tell the user, and reset to NONE.
+ */
+ if (WARN(fwnode && type != IRQ_TYPE_NONE,
+ "%pfw: Ignoring %u default trigger\n", fwnode, type))
+ type = IRQ_TYPE_NONE;
+
+ gc->irq.default_type = type;
+ gc->irq.lock_key = lock_key;
+ gc->irq.request_key = request_key;
+
+ /* If a parent irqdomain is provided, let's build a hierarchy */
+ if (gpiochip_hierarchy_is_hierarchical(gc)) {
+ domain = gpiochip_hierarchy_create_domain(gc);
+ } else {
+ domain = gpiochip_simple_create_domain(gc);
+ }
+ if (IS_ERR(domain))
+ return PTR_ERR(domain);
+
+ if (gc->irq.parent_handler) {
+ for (i = 0; i < gc->irq.num_parents; i++) {
+ void *data;
+
+ if (gc->irq.per_parent_data)
+ data = gc->irq.parent_handler_data_array[i];
+ else
+ data = gc->irq.parent_handler_data ?: gc;
+
+ /*
+ * The parent IRQ chip is already using the chip_data
+ * for this IRQ chip, so our callbacks simply use the
+ * handler_data.
+ */
+ irq_set_chained_handler_and_data(gc->irq.parents[i],
+ gc->irq.parent_handler,
+ data);
+ }
+ }
+
+ gpiochip_set_irq_hooks(gc);
+
+ ret = gpiochip_irqchip_add_allocated_domain(gc, domain, false);
+ if (ret)
+ return ret;
+
+ acpi_gpiochip_request_interrupts(gc);
+
+ return 0;
+}
+
+/**
+ * gpiochip_irqchip_remove() - removes an irqchip added to a gpiochip
+ * @gc: the gpiochip to remove the irqchip from
+ *
+ * This is called only from gpiochip_remove()
+ */
+static void gpiochip_irqchip_remove(struct gpio_chip *gc)
+{
+ struct irq_chip *irqchip = gc->irq.chip;
+ unsigned int offset;
+
+ acpi_gpiochip_free_interrupts(gc);
+
+ if (irqchip && gc->irq.parent_handler) {
+ struct gpio_irq_chip *irq = &gc->irq;
+ unsigned int i;
+
+ for (i = 0; i < irq->num_parents; i++)
+ irq_set_chained_handler_and_data(irq->parents[i],
+ NULL, NULL);
+ }
+
+ /* Remove all IRQ mappings and delete the domain */
+ if (!gc->irq.domain_is_allocated_externally && gc->irq.domain) {
+ unsigned int irq;
+
+ for (offset = 0; offset < gc->ngpio; offset++) {
+ if (!gpiochip_irqchip_irq_valid(gc, offset))
+ continue;
+
+ irq = irq_find_mapping(gc->irq.domain, offset);
+ irq_dispose_mapping(irq);
+ }
+
+ irq_domain_remove(gc->irq.domain);
+ }
+
+ if (irqchip && !(irqchip->flags & IRQCHIP_IMMUTABLE)) {
+ if (irqchip->irq_request_resources == gpiochip_irq_reqres) {
+ irqchip->irq_request_resources = NULL;
+ irqchip->irq_release_resources = NULL;
+ }
+ if (irqchip->irq_enable == gpiochip_irq_enable) {
+ irqchip->irq_enable = gc->irq.irq_enable;
+ irqchip->irq_disable = gc->irq.irq_disable;
+ }
+ }
+ gc->irq.irq_enable = NULL;
+ gc->irq.irq_disable = NULL;
+ gc->irq.chip = NULL;
+
+ gpiochip_irqchip_free_valid_mask(gc);
+}
+
+/**
+ * gpiochip_irqchip_add_domain() - adds an irqdomain to a gpiochip
+ * @gc: the gpiochip to add the irqchip to
+ * @domain: the irqdomain to add to the gpiochip
+ *
+ * This function adds an IRQ domain to the gpiochip.
+ */
+int gpiochip_irqchip_add_domain(struct gpio_chip *gc,
+ struct irq_domain *domain)
+{
+ return gpiochip_irqchip_add_allocated_domain(gc, domain, true);
+}
+EXPORT_SYMBOL_GPL(gpiochip_irqchip_add_domain);
+
+#else /* CONFIG_GPIOLIB_IRQCHIP */
+
+static inline int gpiochip_add_irqchip(struct gpio_chip *gc,
+ struct lock_class_key *lock_key,
+ struct lock_class_key *request_key)
+{
+ return 0;
+}
+static void gpiochip_irqchip_remove(struct gpio_chip *gc) {}
+
+static inline int gpiochip_irqchip_init_hw(struct gpio_chip *gc)
+{
+ return 0;
+}
+
+static inline int gpiochip_irqchip_init_valid_mask(struct gpio_chip *gc)
+{
+ return 0;
+}
+static inline void gpiochip_irqchip_free_valid_mask(struct gpio_chip *gc)
+{ }
+
+#endif /* CONFIG_GPIOLIB_IRQCHIP */
+
+/**
+ * gpiochip_generic_request() - request the gpio function for a pin
+ * @gc: the gpiochip owning the GPIO
+ * @offset: the offset of the GPIO to request for GPIO function
+ */
+int gpiochip_generic_request(struct gpio_chip *gc, unsigned int offset)
+{
+#ifdef CONFIG_PINCTRL
+ if (list_empty(&gc->gpiodev->pin_ranges))
+ return 0;
+#endif
+
+ return pinctrl_gpio_request(gc->gpiodev->base + offset);
+}
+EXPORT_SYMBOL_GPL(gpiochip_generic_request);
+
+/**
+ * gpiochip_generic_free() - free the gpio function from a pin
+ * @gc: the gpiochip to request the gpio function for
+ * @offset: the offset of the GPIO to free from GPIO function
+ */
+void gpiochip_generic_free(struct gpio_chip *gc, unsigned int offset)
+{
+#ifdef CONFIG_PINCTRL
+ if (list_empty(&gc->gpiodev->pin_ranges))
+ return;
+#endif
+
+ pinctrl_gpio_free(gc->gpiodev->base + offset);
+}
+EXPORT_SYMBOL_GPL(gpiochip_generic_free);
+
+/**
+ * gpiochip_generic_config() - apply configuration for a pin
+ * @gc: the gpiochip owning the GPIO
+ * @offset: the offset of the GPIO to apply the configuration
+ * @config: the configuration to be applied
+ */
+int gpiochip_generic_config(struct gpio_chip *gc, unsigned int offset,
+ unsigned long config)
+{
+ return pinctrl_gpio_set_config(gc->gpiodev->base + offset, config);
+}
+EXPORT_SYMBOL_GPL(gpiochip_generic_config);
+
+#ifdef CONFIG_PINCTRL
+
+/**
+ * gpiochip_add_pingroup_range() - add a range for GPIO <-> pin mapping
+ * @gc: the gpiochip to add the range for
+ * @pctldev: the pin controller to map to
+ * @gpio_offset: the start offset in the current gpio_chip number space
+ * @pin_group: name of the pin group inside the pin controller
+ *
+ * Calling this function directly from a DeviceTree-supported
+ * pinctrl driver is DEPRECATED. Please see Section 2.1 of
+ * Documentation/devicetree/bindings/gpio/gpio.txt on how to
+ * bind pinctrl and gpio drivers via the "gpio-ranges" property.
+ */
+int gpiochip_add_pingroup_range(struct gpio_chip *gc,
+ struct pinctrl_dev *pctldev,
+ unsigned int gpio_offset, const char *pin_group)
+{
+ struct gpio_pin_range *pin_range;
+ struct gpio_device *gdev = gc->gpiodev;
+ int ret;
+
+ pin_range = kzalloc(sizeof(*pin_range), GFP_KERNEL);
+ if (!pin_range) {
+ chip_err(gc, "failed to allocate pin ranges\n");
+ return -ENOMEM;
+ }
+
+ /* Use local offset as range ID */
+ pin_range->range.id = gpio_offset;
+ pin_range->range.gc = gc;
+ pin_range->range.name = gc->label;
+ pin_range->range.base = gdev->base + gpio_offset;
+ pin_range->pctldev = pctldev;
+
+ ret = pinctrl_get_group_pins(pctldev, pin_group,
+ &pin_range->range.pins,
+ &pin_range->range.npins);
+ if (ret < 0) {
+ kfree(pin_range);
+ return ret;
+ }
+
+ pinctrl_add_gpio_range(pctldev, &pin_range->range);
+
+ chip_dbg(gc, "created GPIO range %d->%d ==> %s PINGRP %s\n",
+ gpio_offset, gpio_offset + pin_range->range.npins - 1,
+ pinctrl_dev_get_devname(pctldev), pin_group);
+
+ list_add_tail(&pin_range->node, &gdev->pin_ranges);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gpiochip_add_pingroup_range);
+
+/**
+ * gpiochip_add_pin_range() - add a range for GPIO <-> pin mapping
+ * @gc: the gpiochip to add the range for
+ * @pinctl_name: the dev_name() of the pin controller to map to
+ * @gpio_offset: the start offset in the current gpio_chip number space
+ * @pin_offset: the start offset in the pin controller number space
+ * @npins: the number of pins from the offset of each pin space (GPIO and
+ * pin controller) to accumulate in this range
+ *
+ * Returns:
+ * 0 on success, or a negative error-code on failure.
+ *
+ * Calling this function directly from a DeviceTree-supported
+ * pinctrl driver is DEPRECATED. Please see Section 2.1 of
+ * Documentation/devicetree/bindings/gpio/gpio.txt on how to
+ * bind pinctrl and gpio drivers via the "gpio-ranges" property.
+ */
+int gpiochip_add_pin_range(struct gpio_chip *gc, const char *pinctl_name,
+ unsigned int gpio_offset, unsigned int pin_offset,
+ unsigned int npins)
+{
+ struct gpio_pin_range *pin_range;
+ struct gpio_device *gdev = gc->gpiodev;
+ int ret;
+
+ pin_range = kzalloc(sizeof(*pin_range), GFP_KERNEL);
+ if (!pin_range) {
+ chip_err(gc, "failed to allocate pin ranges\n");
+ return -ENOMEM;
+ }
+
+ /* Use local offset as range ID */
+ pin_range->range.id = gpio_offset;
+ pin_range->range.gc = gc;
+ pin_range->range.name = gc->label;
+ pin_range->range.base = gdev->base + gpio_offset;
+ pin_range->range.pin_base = pin_offset;
+ pin_range->range.npins = npins;
+ pin_range->pctldev = pinctrl_find_and_add_gpio_range(pinctl_name,
+ &pin_range->range);
+ if (IS_ERR(pin_range->pctldev)) {
+ ret = PTR_ERR(pin_range->pctldev);
+ chip_err(gc, "could not create pin range\n");
+ kfree(pin_range);
+ return ret;
+ }
+ chip_dbg(gc, "created GPIO range %d->%d ==> %s PIN %d->%d\n",
+ gpio_offset, gpio_offset + npins - 1,
+ pinctl_name,
+ pin_offset, pin_offset + npins - 1);
+
+ list_add_tail(&pin_range->node, &gdev->pin_ranges);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gpiochip_add_pin_range);
+
+/**
+ * gpiochip_remove_pin_ranges() - remove all the GPIO <-> pin mappings
+ * @gc: the chip to remove all the mappings for
+ */
+void gpiochip_remove_pin_ranges(struct gpio_chip *gc)
+{
+ struct gpio_pin_range *pin_range, *tmp;
+ struct gpio_device *gdev = gc->gpiodev;
+
+ list_for_each_entry_safe(pin_range, tmp, &gdev->pin_ranges, node) {
+ list_del(&pin_range->node);
+ pinctrl_remove_gpio_range(pin_range->pctldev,
+ &pin_range->range);
+ kfree(pin_range);
+ }
+}
+EXPORT_SYMBOL_GPL(gpiochip_remove_pin_ranges);
+
+#endif /* CONFIG_PINCTRL */
+
+/* These "optional" allocation calls help prevent drivers from stomping
+ * on each other, and help provide better diagnostics in debugfs.
+ * They're called even less than the "set direction" calls.
+ */
+static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
+{
+ struct gpio_chip *gc = desc->gdev->chip;
+ int ret;
+ unsigned long flags;
+ unsigned offset;
+
+ if (label) {
+ label = kstrdup_const(label, GFP_KERNEL);
+ if (!label)
+ return -ENOMEM;
+ }
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ /* NOTE: gpio_request() can be called in early boot,
+ * before IRQs are enabled, for non-sleeping (SOC) GPIOs.
+ */
+
+ if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0) {
+ desc_set_label(desc, label ? : "?");
+ } else {
+ ret = -EBUSY;
+ goto out_free_unlock;
+ }
+
+ if (gc->request) {
+ /* gc->request may sleep */
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ offset = gpio_chip_hwgpio(desc);
+ if (gpiochip_line_is_valid(gc, offset))
+ ret = gc->request(gc, offset);
+ else
+ ret = -EINVAL;
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ if (ret) {
+ desc_set_label(desc, NULL);
+ clear_bit(FLAG_REQUESTED, &desc->flags);
+ goto out_free_unlock;
+ }
+ }
+ if (gc->get_direction) {
+ /* gc->get_direction may sleep */
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ gpiod_get_direction(desc);
+ spin_lock_irqsave(&gpio_lock, flags);
+ }
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ return 0;
+
+out_free_unlock:
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ kfree_const(label);
+ return ret;
+}
+
+/*
+ * This descriptor validation needs to be inserted verbatim into each
+ * function taking a descriptor, so we need to use a preprocessor
+ * macro to avoid endless duplication. If the desc is NULL it is an
+ * optional GPIO and calls should just bail out.
+ */
+static int validate_desc(const struct gpio_desc *desc, const char *func)
+{
+ if (!desc)
+ return 0;
+ if (IS_ERR(desc)) {
+ pr_warn("%s: invalid GPIO (errorpointer)\n", func);
+ return PTR_ERR(desc);
+ }
+ if (!desc->gdev) {
+ pr_warn("%s: invalid GPIO (no device)\n", func);
+ return -EINVAL;
+ }
+ if (!desc->gdev->chip) {
+ dev_warn(&desc->gdev->dev,
+ "%s: backing chip is gone\n", func);
+ return 0;
+ }
+ return 1;
+}
+
+#define VALIDATE_DESC(desc) do { \
+ int __valid = validate_desc(desc, __func__); \
+ if (__valid <= 0) \
+ return __valid; \
+ } while (0)
+
+#define VALIDATE_DESC_VOID(desc) do { \
+ int __valid = validate_desc(desc, __func__); \
+ if (__valid <= 0) \
+ return; \
+ } while (0)
+
+int gpiod_request(struct gpio_desc *desc, const char *label)
+{
+ int ret = -EPROBE_DEFER;
+
+ VALIDATE_DESC(desc);
+
+ if (try_module_get(desc->gdev->owner)) {
+ ret = gpiod_request_commit(desc, label);
+ if (ret)
+ module_put(desc->gdev->owner);
+ else
+ gpio_device_get(desc->gdev);
+ }
+
+ if (ret)
+ gpiod_dbg(desc, "%s: status %d\n", __func__, ret);
+
+ return ret;
+}
+
+static bool gpiod_free_commit(struct gpio_desc *desc)
+{
+ bool ret = false;
+ unsigned long flags;
+ struct gpio_chip *gc;
+
+ might_sleep();
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ gc = desc->gdev->chip;
+ if (gc && test_bit(FLAG_REQUESTED, &desc->flags)) {
+ if (gc->free) {
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ might_sleep_if(gc->can_sleep);
+ gc->free(gc, gpio_chip_hwgpio(desc));
+ spin_lock_irqsave(&gpio_lock, flags);
+ }
+ kfree_const(desc->label);
+ desc_set_label(desc, NULL);
+ clear_bit(FLAG_ACTIVE_LOW, &desc->flags);
+ clear_bit(FLAG_REQUESTED, &desc->flags);
+ clear_bit(FLAG_OPEN_DRAIN, &desc->flags);
+ clear_bit(FLAG_OPEN_SOURCE, &desc->flags);
+ clear_bit(FLAG_PULL_UP, &desc->flags);
+ clear_bit(FLAG_PULL_DOWN, &desc->flags);
+ clear_bit(FLAG_BIAS_DISABLE, &desc->flags);
+ clear_bit(FLAG_EDGE_RISING, &desc->flags);
+ clear_bit(FLAG_EDGE_FALLING, &desc->flags);
+ clear_bit(FLAG_IS_HOGGED, &desc->flags);
+#ifdef CONFIG_OF_DYNAMIC
+ desc->hog = NULL;
+#endif
+#ifdef CONFIG_GPIO_CDEV
+ WRITE_ONCE(desc->debounce_period_us, 0);
+#endif
+ ret = true;
+ }
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ gpiod_line_state_notify(desc, GPIOLINE_CHANGED_RELEASED);
+
+ return ret;
+}
+
+void gpiod_free(struct gpio_desc *desc)
+{
+ /*
+ * We must not use VALIDATE_DESC_VOID() as the underlying gdev->chip
+ * may already be NULL but we still want to put the references.
+ */
+ if (!desc)
+ return;
+
+ if (!gpiod_free_commit(desc))
+ WARN_ON(extra_checks);
+
+ module_put(desc->gdev->owner);
+ gpio_device_put(desc->gdev);
+}
+
+/**
+ * gpiochip_is_requested - return string iff signal was requested
+ * @gc: controller managing the signal
+ * @offset: of signal within controller's 0..(ngpio - 1) range
+ *
+ * Returns NULL if the GPIO is not currently requested, else a string.
+ * The string returned is the label passed to gpio_request(); if none has been
+ * passed it is a meaningless, non-NULL constant.
+ *
+ * This function is for use by GPIO controller drivers. The label can
+ * help with diagnostics, and knowing that the signal is used as a GPIO
+ * can help avoid accidentally multiplexing it to another controller.
+ */
+const char *gpiochip_is_requested(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_desc *desc;
+
+ desc = gpiochip_get_desc(gc, offset);
+ if (IS_ERR(desc))
+ return NULL;
+
+ if (test_bit(FLAG_REQUESTED, &desc->flags) == 0)
+ return NULL;
+ return desc->label;
+}
+EXPORT_SYMBOL_GPL(gpiochip_is_requested);
+
+/**
+ * gpiochip_request_own_desc - Allow GPIO chip to request its own descriptor
+ * @gc: GPIO chip
+ * @hwnum: hardware number of the GPIO for which to request the descriptor
+ * @label: label for the GPIO
+ * @lflags: lookup flags for this GPIO or 0 if default, this can be used to
+ * specify things like line inversion semantics with the machine flags
+ * such as GPIO_OUT_LOW
+ * @dflags: descriptor request flags for this GPIO or 0 if default, this
+ * can be used to specify consumer semantics such as open drain
+ *
+ * Function allows GPIO chip drivers to request and use their own GPIO
+ * descriptors via gpiolib API. Difference to gpiod_request() is that this
+ * function will not increase reference count of the GPIO chip module. This
+ * allows the GPIO chip module to be unloaded as needed (we assume that the
+ * GPIO chip driver handles freeing the GPIOs it has requested).
+ *
+ * Returns:
+ * A pointer to the GPIO descriptor, or an ERR_PTR()-encoded negative error
+ * code on failure.
+ */
+struct gpio_desc *gpiochip_request_own_desc(struct gpio_chip *gc,
+ unsigned int hwnum,
+ const char *label,
+ enum gpio_lookup_flags lflags,
+ enum gpiod_flags dflags)
+{
+ struct gpio_desc *desc = gpiochip_get_desc(gc, hwnum);
+ int ret;
+
+ if (IS_ERR(desc)) {
+ chip_err(gc, "failed to get GPIO descriptor\n");
+ return desc;
+ }
+
+ ret = gpiod_request_commit(desc, label);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ ret = gpiod_configure_flags(desc, label, lflags, dflags);
+ if (ret) {
+ chip_err(gc, "setup of own GPIO %s failed\n", label);
+ gpiod_free_commit(desc);
+ return ERR_PTR(ret);
+ }
+
+ return desc;
+}
+EXPORT_SYMBOL_GPL(gpiochip_request_own_desc);
+
+/**
+ * gpiochip_free_own_desc - Free GPIO requested by the chip driver
+ * @desc: GPIO descriptor to free
+ *
+ * Function frees the given GPIO requested previously with
+ * gpiochip_request_own_desc().
+ */
+void gpiochip_free_own_desc(struct gpio_desc *desc)
+{
+ if (desc)
+ gpiod_free_commit(desc);
+}
+EXPORT_SYMBOL_GPL(gpiochip_free_own_desc);
+
+/*
+ * Drivers MUST set GPIO direction before making get/set calls. In
+ * some cases this is done in early boot, before IRQs are enabled.
+ *
+ * As a rule these aren't called more than once (except for drivers
+ * using the open-drain emulation idiom) so these are natural places
+ * to accumulate extra debugging checks. Note that we can't (yet)
+ * rely on gpio_request() having been called beforehand.
+ */
+
+static int gpio_do_set_config(struct gpio_chip *gc, unsigned int offset,
+ unsigned long config)
+{
+ if (!gc->set_config)
+ return -ENOTSUPP;
+
+ return gc->set_config(gc, offset, config);
+}
+
+static int gpio_set_config_with_argument(struct gpio_desc *desc,
+ enum pin_config_param mode,
+ u32 argument)
+{
+ struct gpio_chip *gc = desc->gdev->chip;
+ unsigned long config;
+
+ config = pinconf_to_config_packed(mode, argument);
+ return gpio_do_set_config(gc, gpio_chip_hwgpio(desc), config);
+}
+
+static int gpio_set_config_with_argument_optional(struct gpio_desc *desc,
+ enum pin_config_param mode,
+ u32 argument)
+{
+ struct device *dev = &desc->gdev->dev;
+ int gpio = gpio_chip_hwgpio(desc);
+ int ret;
+
+ ret = gpio_set_config_with_argument(desc, mode, argument);
+ if (ret != -ENOTSUPP)
+ return ret;
+
+ switch (mode) {
+ case PIN_CONFIG_PERSIST_STATE:
+ dev_dbg(dev, "Persistence not supported for GPIO %d\n", gpio);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int gpio_set_config(struct gpio_desc *desc, enum pin_config_param mode)
+{
+ return gpio_set_config_with_argument(desc, mode, 0);
+}
+
+static int gpio_set_bias(struct gpio_desc *desc)
+{
+ enum pin_config_param bias;
+ unsigned int arg;
+
+ if (test_bit(FLAG_BIAS_DISABLE, &desc->flags))
+ bias = PIN_CONFIG_BIAS_DISABLE;
+ else if (test_bit(FLAG_PULL_UP, &desc->flags))
+ bias = PIN_CONFIG_BIAS_PULL_UP;
+ else if (test_bit(FLAG_PULL_DOWN, &desc->flags))
+ bias = PIN_CONFIG_BIAS_PULL_DOWN;
+ else
+ return 0;
+
+ switch (bias) {
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ case PIN_CONFIG_BIAS_PULL_UP:
+ arg = 1;
+ break;
+
+ default:
+ arg = 0;
+ break;
+ }
+
+ return gpio_set_config_with_argument_optional(desc, bias, arg);
+}
+
+/**
+ * gpio_set_debounce_timeout() - Set debounce timeout
+ * @desc: GPIO descriptor to set the debounce timeout
+ * @debounce: Debounce timeout in microseconds
+ *
+ * The function calls the certain GPIO driver to set debounce timeout
+ * in the hardware.
+ *
+ * Returns 0 on success, or negative error code otherwise.
+ */
+int gpio_set_debounce_timeout(struct gpio_desc *desc, unsigned int debounce)
+{
+ return gpio_set_config_with_argument_optional(desc,
+ PIN_CONFIG_INPUT_DEBOUNCE,
+ debounce);
+}
+
+/**
+ * gpiod_direction_input - set the GPIO direction to input
+ * @desc: GPIO to set to input
+ *
+ * Set the direction of the passed GPIO to input, such as gpiod_get_value() can
+ * be called safely on it.
+ *
+ * Return 0 in case of success, else an error code.
+ */
+int gpiod_direction_input(struct gpio_desc *desc)
+{
+ struct gpio_chip *gc;
+ int ret = 0;
+
+ VALIDATE_DESC(desc);
+ gc = desc->gdev->chip;
+
+ /*
+ * It is legal to have no .get() and .direction_input() specified if
+ * the chip is output-only, but you can't specify .direction_input()
+ * and not support the .get() operation, that doesn't make sense.
+ */
+ if (!gc->get && gc->direction_input) {
+ gpiod_warn(desc,
+ "%s: missing get() but have direction_input()\n",
+ __func__);
+ return -EIO;
+ }
+
+ /*
+ * If we have a .direction_input() callback, things are simple,
+ * just call it. Else we are some input-only chip so try to check the
+ * direction (if .get_direction() is supported) else we silently
+ * assume we are in input mode after this.
+ */
+ if (gc->direction_input) {
+ ret = gc->direction_input(gc, gpio_chip_hwgpio(desc));
+ } else if (gc->get_direction &&
+ (gc->get_direction(gc, gpio_chip_hwgpio(desc)) != 1)) {
+ gpiod_warn(desc,
+ "%s: missing direction_input() operation and line is output\n",
+ __func__);
+ return -EIO;
+ }
+ if (ret == 0) {
+ clear_bit(FLAG_IS_OUT, &desc->flags);
+ ret = gpio_set_bias(desc);
+ }
+
+ trace_gpio_direction(desc_to_gpio(desc), 1, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gpiod_direction_input);
+
+static int gpiod_direction_output_raw_commit(struct gpio_desc *desc, int value)
+{
+ struct gpio_chip *gc = desc->gdev->chip;
+ int val = !!value;
+ int ret = 0;
+
+ /*
+ * It's OK not to specify .direction_output() if the gpiochip is
+ * output-only, but if there is then not even a .set() operation it
+ * is pretty tricky to drive the output line.
+ */
+ if (!gc->set && !gc->direction_output) {
+ gpiod_warn(desc,
+ "%s: missing set() and direction_output() operations\n",
+ __func__);
+ return -EIO;
+ }
+
+ if (gc->direction_output) {
+ ret = gc->direction_output(gc, gpio_chip_hwgpio(desc), val);
+ } else {
+ /* Check that we are in output mode if we can */
+ if (gc->get_direction &&
+ gc->get_direction(gc, gpio_chip_hwgpio(desc))) {
+ gpiod_warn(desc,
+ "%s: missing direction_output() operation\n",
+ __func__);
+ return -EIO;
+ }
+ /*
+ * If we can't actively set the direction, we are some
+ * output-only chip, so just drive the output as desired.
+ */
+ gc->set(gc, gpio_chip_hwgpio(desc), val);
+ }
+
+ if (!ret)
+ set_bit(FLAG_IS_OUT, &desc->flags);
+ trace_gpio_value(desc_to_gpio(desc), 0, val);
+ trace_gpio_direction(desc_to_gpio(desc), 0, ret);
+ return ret;
+}
+
+/**
+ * gpiod_direction_output_raw - set the GPIO direction to output
+ * @desc: GPIO to set to output
+ * @value: initial output value of the GPIO
+ *
+ * Set the direction of the passed GPIO to output, such as gpiod_set_value() can
+ * be called safely on it. The initial value of the output must be specified
+ * as raw value on the physical line without regard for the ACTIVE_LOW status.
+ *
+ * Return 0 in case of success, else an error code.
+ */
+int gpiod_direction_output_raw(struct gpio_desc *desc, int value)
+{
+ VALIDATE_DESC(desc);
+ return gpiod_direction_output_raw_commit(desc, value);
+}
+EXPORT_SYMBOL_GPL(gpiod_direction_output_raw);
+
+/**
+ * gpiod_direction_output - set the GPIO direction to output
+ * @desc: GPIO to set to output
+ * @value: initial output value of the GPIO
+ *
+ * Set the direction of the passed GPIO to output, such as gpiod_set_value() can
+ * be called safely on it. The initial value of the output must be specified
+ * as the logical value of the GPIO, i.e. taking its ACTIVE_LOW status into
+ * account.
+ *
+ * Return 0 in case of success, else an error code.
+ */
+int gpiod_direction_output(struct gpio_desc *desc, int value)
+{
+ int ret;
+
+ VALIDATE_DESC(desc);
+ if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
+ value = !value;
+ else
+ value = !!value;
+
+ /* GPIOs used for enabled IRQs shall not be set as output */
+ if (test_bit(FLAG_USED_AS_IRQ, &desc->flags) &&
+ test_bit(FLAG_IRQ_IS_ENABLED, &desc->flags)) {
+ gpiod_err(desc,
+ "%s: tried to set a GPIO tied to an IRQ as output\n",
+ __func__);
+ return -EIO;
+ }
+
+ if (test_bit(FLAG_OPEN_DRAIN, &desc->flags)) {
+ /* First see if we can enable open drain in hardware */
+ ret = gpio_set_config(desc, PIN_CONFIG_DRIVE_OPEN_DRAIN);
+ if (!ret)
+ goto set_output_value;
+ /* Emulate open drain by not actively driving the line high */
+ if (value) {
+ ret = gpiod_direction_input(desc);
+ goto set_output_flag;
+ }
+ } else if (test_bit(FLAG_OPEN_SOURCE, &desc->flags)) {
+ ret = gpio_set_config(desc, PIN_CONFIG_DRIVE_OPEN_SOURCE);
+ if (!ret)
+ goto set_output_value;
+ /* Emulate open source by not actively driving the line low */
+ if (!value) {
+ ret = gpiod_direction_input(desc);
+ goto set_output_flag;
+ }
+ } else {
+ gpio_set_config(desc, PIN_CONFIG_DRIVE_PUSH_PULL);
+ }
+
+set_output_value:
+ ret = gpio_set_bias(desc);
+ if (ret)
+ return ret;
+ return gpiod_direction_output_raw_commit(desc, value);
+
+set_output_flag:
+ /*
+ * When emulating open-source or open-drain functionalities by not
+ * actively driving the line (setting mode to input) we still need to
+ * set the IS_OUT flag or otherwise we won't be able to set the line
+ * value anymore.
+ */
+ if (ret == 0)
+ set_bit(FLAG_IS_OUT, &desc->flags);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gpiod_direction_output);
+
+/**
+ * gpiod_enable_hw_timestamp_ns - Enable hardware timestamp in nanoseconds.
+ *
+ * @desc: GPIO to enable.
+ * @flags: Flags related to GPIO edge.
+ *
+ * Return 0 in case of success, else negative error code.
+ */
+int gpiod_enable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags)
+{
+ int ret = 0;
+ struct gpio_chip *gc;
+
+ VALIDATE_DESC(desc);
+
+ gc = desc->gdev->chip;
+ if (!gc->en_hw_timestamp) {
+ gpiod_warn(desc, "%s: hw ts not supported\n", __func__);
+ return -ENOTSUPP;
+ }
+
+ ret = gc->en_hw_timestamp(gc, gpio_chip_hwgpio(desc), flags);
+ if (ret)
+ gpiod_warn(desc, "%s: hw ts request failed\n", __func__);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gpiod_enable_hw_timestamp_ns);
+
+/**
+ * gpiod_disable_hw_timestamp_ns - Disable hardware timestamp.
+ *
+ * @desc: GPIO to disable.
+ * @flags: Flags related to GPIO edge, same value as used during enable call.
+ *
+ * Return 0 in case of success, else negative error code.
+ */
+int gpiod_disable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags)
+{
+ int ret = 0;
+ struct gpio_chip *gc;
+
+ VALIDATE_DESC(desc);
+
+ gc = desc->gdev->chip;
+ if (!gc->dis_hw_timestamp) {
+ gpiod_warn(desc, "%s: hw ts not supported\n", __func__);
+ return -ENOTSUPP;
+ }
+
+ ret = gc->dis_hw_timestamp(gc, gpio_chip_hwgpio(desc), flags);
+ if (ret)
+ gpiod_warn(desc, "%s: hw ts release failed\n", __func__);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gpiod_disable_hw_timestamp_ns);
+
+/**
+ * gpiod_set_config - sets @config for a GPIO
+ * @desc: descriptor of the GPIO for which to set the configuration
+ * @config: Same packed config format as generic pinconf
+ *
+ * Returns:
+ * 0 on success, %-ENOTSUPP if the controller doesn't support setting the
+ * configuration.
+ */
+int gpiod_set_config(struct gpio_desc *desc, unsigned long config)
+{
+ struct gpio_chip *gc;
+
+ VALIDATE_DESC(desc);
+ gc = desc->gdev->chip;
+
+ return gpio_do_set_config(gc, gpio_chip_hwgpio(desc), config);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_config);
+
+/**
+ * gpiod_set_debounce - sets @debounce time for a GPIO
+ * @desc: descriptor of the GPIO for which to set debounce time
+ * @debounce: debounce time in microseconds
+ *
+ * Returns:
+ * 0 on success, %-ENOTSUPP if the controller doesn't support setting the
+ * debounce time.
+ */
+int gpiod_set_debounce(struct gpio_desc *desc, unsigned int debounce)
+{
+ unsigned long config;
+
+ config = pinconf_to_config_packed(PIN_CONFIG_INPUT_DEBOUNCE, debounce);
+ return gpiod_set_config(desc, config);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_debounce);
+
+/**
+ * gpiod_set_transitory - Lose or retain GPIO state on suspend or reset
+ * @desc: descriptor of the GPIO for which to configure persistence
+ * @transitory: True to lose state on suspend or reset, false for persistence
+ *
+ * Returns:
+ * 0 on success, otherwise a negative error code.
+ */
+int gpiod_set_transitory(struct gpio_desc *desc, bool transitory)
+{
+ VALIDATE_DESC(desc);
+ /*
+ * Handle FLAG_TRANSITORY first, enabling queries to gpiolib for
+ * persistence state.
+ */
+ assign_bit(FLAG_TRANSITORY, &desc->flags, transitory);
+
+ /* If the driver supports it, set the persistence state now */
+ return gpio_set_config_with_argument_optional(desc,
+ PIN_CONFIG_PERSIST_STATE,
+ !transitory);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_transitory);
+
+/**
+ * gpiod_is_active_low - test whether a GPIO is active-low or not
+ * @desc: the gpio descriptor to test
+ *
+ * Returns 1 if the GPIO is active-low, 0 otherwise.
+ */
+int gpiod_is_active_low(const struct gpio_desc *desc)
+{
+ VALIDATE_DESC(desc);
+ return test_bit(FLAG_ACTIVE_LOW, &desc->flags);
+}
+EXPORT_SYMBOL_GPL(gpiod_is_active_low);
+
+/**
+ * gpiod_toggle_active_low - toggle whether a GPIO is active-low or not
+ * @desc: the gpio descriptor to change
+ */
+void gpiod_toggle_active_low(struct gpio_desc *desc)
+{
+ VALIDATE_DESC_VOID(desc);
+ change_bit(FLAG_ACTIVE_LOW, &desc->flags);
+}
+EXPORT_SYMBOL_GPL(gpiod_toggle_active_low);
+
+static int gpio_chip_get_value(struct gpio_chip *gc, const struct gpio_desc *desc)
+{
+ return gc->get ? gc->get(gc, gpio_chip_hwgpio(desc)) : -EIO;
+}
+
+/* I/O calls are only valid after configuration completed; the relevant
+ * "is this a valid GPIO" error checks should already have been done.
+ *
+ * "Get" operations are often inlinable as reading a pin value register,
+ * and masking the relevant bit in that register.
+ *
+ * When "set" operations are inlinable, they involve writing that mask to
+ * one register to set a low value, or a different register to set it high.
+ * Otherwise locking is needed, so there may be little value to inlining.
+ *
+ *------------------------------------------------------------------------
+ *
+ * IMPORTANT!!! The hot paths -- get/set value -- assume that callers
+ * have requested the GPIO. That can include implicit requesting by
+ * a direction setting call. Marking a gpio as requested locks its chip
+ * in memory, guaranteeing that these table lookups need no more locking
+ * and that gpiochip_remove() will fail.
+ *
+ * REVISIT when debugging, consider adding some instrumentation to ensure
+ * that the GPIO was actually requested.
+ */
+
+static int gpiod_get_raw_value_commit(const struct gpio_desc *desc)
+{
+ struct gpio_chip *gc;
+ int value;
+
+ gc = desc->gdev->chip;
+ value = gpio_chip_get_value(gc, desc);
+ value = value < 0 ? value : !!value;
+ trace_gpio_value(desc_to_gpio(desc), 1, value);
+ return value;
+}
+
+static int gpio_chip_get_multiple(struct gpio_chip *gc,
+ unsigned long *mask, unsigned long *bits)
+{
+ if (gc->get_multiple)
+ return gc->get_multiple(gc, mask, bits);
+ if (gc->get) {
+ int i, value;
+
+ for_each_set_bit(i, mask, gc->ngpio) {
+ value = gc->get(gc, i);
+ if (value < 0)
+ return value;
+ __assign_bit(i, bits, value);
+ }
+ return 0;
+ }
+ return -EIO;
+}
+
+int gpiod_get_array_value_complex(bool raw, bool can_sleep,
+ unsigned int array_size,
+ struct gpio_desc **desc_array,
+ struct gpio_array *array_info,
+ unsigned long *value_bitmap)
+{
+ int ret, i = 0;
+
+ /*
+ * Validate array_info against desc_array and its size.
+ * It should immediately follow desc_array if both
+ * have been obtained from the same gpiod_get_array() call.
+ */
+ if (array_info && array_info->desc == desc_array &&
+ array_size <= array_info->size &&
+ (void *)array_info == desc_array + array_info->size) {
+ if (!can_sleep)
+ WARN_ON(array_info->chip->can_sleep);
+
+ ret = gpio_chip_get_multiple(array_info->chip,
+ array_info->get_mask,
+ value_bitmap);
+ if (ret)
+ return ret;
+
+ if (!raw && !bitmap_empty(array_info->invert_mask, array_size))
+ bitmap_xor(value_bitmap, value_bitmap,
+ array_info->invert_mask, array_size);
+
+ i = find_first_zero_bit(array_info->get_mask, array_size);
+ if (i == array_size)
+ return 0;
+ } else {
+ array_info = NULL;
+ }
+
+ while (i < array_size) {
+ struct gpio_chip *gc = desc_array[i]->gdev->chip;
+ DECLARE_BITMAP(fastpath_mask, FASTPATH_NGPIO);
+ DECLARE_BITMAP(fastpath_bits, FASTPATH_NGPIO);
+ unsigned long *mask, *bits;
+ int first, j;
+
+ if (likely(gc->ngpio <= FASTPATH_NGPIO)) {
+ mask = fastpath_mask;
+ bits = fastpath_bits;
+ } else {
+ gfp_t flags = can_sleep ? GFP_KERNEL : GFP_ATOMIC;
+
+ mask = bitmap_alloc(gc->ngpio, flags);
+ if (!mask)
+ return -ENOMEM;
+
+ bits = bitmap_alloc(gc->ngpio, flags);
+ if (!bits) {
+ bitmap_free(mask);
+ return -ENOMEM;
+ }
+ }
+
+ bitmap_zero(mask, gc->ngpio);
+
+ if (!can_sleep)
+ WARN_ON(gc->can_sleep);
+
+ /* collect all inputs belonging to the same chip */
+ first = i;
+ do {
+ const struct gpio_desc *desc = desc_array[i];
+ int hwgpio = gpio_chip_hwgpio(desc);
+
+ __set_bit(hwgpio, mask);
+ i++;
+
+ if (array_info)
+ i = find_next_zero_bit(array_info->get_mask,
+ array_size, i);
+ } while ((i < array_size) &&
+ (desc_array[i]->gdev->chip == gc));
+
+ ret = gpio_chip_get_multiple(gc, mask, bits);
+ if (ret) {
+ if (mask != fastpath_mask)
+ bitmap_free(mask);
+ if (bits != fastpath_bits)
+ bitmap_free(bits);
+ return ret;
+ }
+
+ for (j = first; j < i; ) {
+ const struct gpio_desc *desc = desc_array[j];
+ int hwgpio = gpio_chip_hwgpio(desc);
+ int value = test_bit(hwgpio, bits);
+
+ if (!raw && test_bit(FLAG_ACTIVE_LOW, &desc->flags))
+ value = !value;
+ __assign_bit(j, value_bitmap, value);
+ trace_gpio_value(desc_to_gpio(desc), 1, value);
+ j++;
+
+ if (array_info)
+ j = find_next_zero_bit(array_info->get_mask, i,
+ j);
+ }
+
+ if (mask != fastpath_mask)
+ bitmap_free(mask);
+ if (bits != fastpath_bits)
+ bitmap_free(bits);
+ }
+ return 0;
+}
+
+/**
+ * gpiod_get_raw_value() - return a gpio's raw value
+ * @desc: gpio whose value will be returned
+ *
+ * Return the GPIO's raw value, i.e. the value of the physical line disregarding
+ * its ACTIVE_LOW status, or negative errno on failure.
+ *
+ * This function can be called from contexts where we cannot sleep, and will
+ * complain if the GPIO chip functions potentially sleep.
+ */
+int gpiod_get_raw_value(const struct gpio_desc *desc)
+{
+ VALIDATE_DESC(desc);
+ /* Should be using gpiod_get_raw_value_cansleep() */
+ WARN_ON(desc->gdev->chip->can_sleep);
+ return gpiod_get_raw_value_commit(desc);
+}
+EXPORT_SYMBOL_GPL(gpiod_get_raw_value);
+
+/**
+ * gpiod_get_value() - return a gpio's value
+ * @desc: gpio whose value will be returned
+ *
+ * Return the GPIO's logical value, i.e. taking the ACTIVE_LOW status into
+ * account, or negative errno on failure.
+ *
+ * This function can be called from contexts where we cannot sleep, and will
+ * complain if the GPIO chip functions potentially sleep.
+ */
+int gpiod_get_value(const struct gpio_desc *desc)
+{
+ int value;
+
+ VALIDATE_DESC(desc);
+ /* Should be using gpiod_get_value_cansleep() */
+ WARN_ON(desc->gdev->chip->can_sleep);
+
+ value = gpiod_get_raw_value_commit(desc);
+ if (value < 0)
+ return value;
+
+ if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
+ value = !value;
+
+ return value;
+}
+EXPORT_SYMBOL_GPL(gpiod_get_value);
+
+/**
+ * gpiod_get_raw_array_value() - read raw values from an array of GPIOs
+ * @array_size: number of elements in the descriptor array / value bitmap
+ * @desc_array: array of GPIO descriptors whose values will be read
+ * @array_info: information on applicability of fast bitmap processing path
+ * @value_bitmap: bitmap to store the read values
+ *
+ * Read the raw values of the GPIOs, i.e. the values of the physical lines
+ * without regard for their ACTIVE_LOW status. Return 0 in case of success,
+ * else an error code.
+ *
+ * This function can be called from contexts where we cannot sleep,
+ * and it will complain if the GPIO chip functions potentially sleep.
+ */
+int gpiod_get_raw_array_value(unsigned int array_size,
+ struct gpio_desc **desc_array,
+ struct gpio_array *array_info,
+ unsigned long *value_bitmap)
+{
+ if (!desc_array)
+ return -EINVAL;
+ return gpiod_get_array_value_complex(true, false, array_size,
+ desc_array, array_info,
+ value_bitmap);
+}
+EXPORT_SYMBOL_GPL(gpiod_get_raw_array_value);
+
+/**
+ * gpiod_get_array_value() - read values from an array of GPIOs
+ * @array_size: number of elements in the descriptor array / value bitmap
+ * @desc_array: array of GPIO descriptors whose values will be read
+ * @array_info: information on applicability of fast bitmap processing path
+ * @value_bitmap: bitmap to store the read values
+ *
+ * Read the logical values of the GPIOs, i.e. taking their ACTIVE_LOW status
+ * into account. Return 0 in case of success, else an error code.
+ *
+ * This function can be called from contexts where we cannot sleep,
+ * and it will complain if the GPIO chip functions potentially sleep.
+ */
+int gpiod_get_array_value(unsigned int array_size,
+ struct gpio_desc **desc_array,
+ struct gpio_array *array_info,
+ unsigned long *value_bitmap)
+{
+ if (!desc_array)
+ return -EINVAL;
+ return gpiod_get_array_value_complex(false, false, array_size,
+ desc_array, array_info,
+ value_bitmap);
+}
+EXPORT_SYMBOL_GPL(gpiod_get_array_value);
+
+/*
+ * gpio_set_open_drain_value_commit() - Set the open drain gpio's value.
+ * @desc: gpio descriptor whose state need to be set.
+ * @value: Non-zero for setting it HIGH otherwise it will set to LOW.
+ */
+static void gpio_set_open_drain_value_commit(struct gpio_desc *desc, bool value)
+{
+ int ret = 0;
+ struct gpio_chip *gc = desc->gdev->chip;
+ int offset = gpio_chip_hwgpio(desc);
+
+ if (value) {
+ ret = gc->direction_input(gc, offset);
+ } else {
+ ret = gc->direction_output(gc, offset, 0);
+ if (!ret)
+ set_bit(FLAG_IS_OUT, &desc->flags);
+ }
+ trace_gpio_direction(desc_to_gpio(desc), value, ret);
+ if (ret < 0)
+ gpiod_err(desc,
+ "%s: Error in set_value for open drain err %d\n",
+ __func__, ret);
+}
+
+/*
+ * _gpio_set_open_source_value() - Set the open source gpio's value.
+ * @desc: gpio descriptor whose state need to be set.
+ * @value: Non-zero for setting it HIGH otherwise it will set to LOW.
+ */
+static void gpio_set_open_source_value_commit(struct gpio_desc *desc, bool value)
+{
+ int ret = 0;
+ struct gpio_chip *gc = desc->gdev->chip;
+ int offset = gpio_chip_hwgpio(desc);
+
+ if (value) {
+ ret = gc->direction_output(gc, offset, 1);
+ if (!ret)
+ set_bit(FLAG_IS_OUT, &desc->flags);
+ } else {
+ ret = gc->direction_input(gc, offset);
+ }
+ trace_gpio_direction(desc_to_gpio(desc), !value, ret);
+ if (ret < 0)
+ gpiod_err(desc,
+ "%s: Error in set_value for open source err %d\n",
+ __func__, ret);
+}
+
+static void gpiod_set_raw_value_commit(struct gpio_desc *desc, bool value)
+{
+ struct gpio_chip *gc;
+
+ gc = desc->gdev->chip;
+ trace_gpio_value(desc_to_gpio(desc), 0, value);
+ gc->set(gc, gpio_chip_hwgpio(desc), value);
+}
+
+/*
+ * set multiple outputs on the same chip;
+ * use the chip's set_multiple function if available;
+ * otherwise set the outputs sequentially;
+ * @chip: the GPIO chip we operate on
+ * @mask: bit mask array; one bit per output; BITS_PER_LONG bits per word
+ * defines which outputs are to be changed
+ * @bits: bit value array; one bit per output; BITS_PER_LONG bits per word
+ * defines the values the outputs specified by mask are to be set to
+ */
+static void gpio_chip_set_multiple(struct gpio_chip *gc,
+ unsigned long *mask, unsigned long *bits)
+{
+ if (gc->set_multiple) {
+ gc->set_multiple(gc, mask, bits);
+ } else {
+ unsigned int i;
+
+ /* set outputs if the corresponding mask bit is set */
+ for_each_set_bit(i, mask, gc->ngpio)
+ gc->set(gc, i, test_bit(i, bits));
+ }
+}
+
+int gpiod_set_array_value_complex(bool raw, bool can_sleep,
+ unsigned int array_size,
+ struct gpio_desc **desc_array,
+ struct gpio_array *array_info,
+ unsigned long *value_bitmap)
+{
+ int i = 0;
+
+ /*
+ * Validate array_info against desc_array and its size.
+ * It should immediately follow desc_array if both
+ * have been obtained from the same gpiod_get_array() call.
+ */
+ if (array_info && array_info->desc == desc_array &&
+ array_size <= array_info->size &&
+ (void *)array_info == desc_array + array_info->size) {
+ if (!can_sleep)
+ WARN_ON(array_info->chip->can_sleep);
+
+ if (!raw && !bitmap_empty(array_info->invert_mask, array_size))
+ bitmap_xor(value_bitmap, value_bitmap,
+ array_info->invert_mask, array_size);
+
+ gpio_chip_set_multiple(array_info->chip, array_info->set_mask,
+ value_bitmap);
+
+ i = find_first_zero_bit(array_info->set_mask, array_size);
+ if (i == array_size)
+ return 0;
+ } else {
+ array_info = NULL;
+ }
+
+ while (i < array_size) {
+ struct gpio_chip *gc = desc_array[i]->gdev->chip;
+ DECLARE_BITMAP(fastpath_mask, FASTPATH_NGPIO);
+ DECLARE_BITMAP(fastpath_bits, FASTPATH_NGPIO);
+ unsigned long *mask, *bits;
+ int count = 0;
+
+ if (likely(gc->ngpio <= FASTPATH_NGPIO)) {
+ mask = fastpath_mask;
+ bits = fastpath_bits;
+ } else {
+ gfp_t flags = can_sleep ? GFP_KERNEL : GFP_ATOMIC;
+
+ mask = bitmap_alloc(gc->ngpio, flags);
+ if (!mask)
+ return -ENOMEM;
+
+ bits = bitmap_alloc(gc->ngpio, flags);
+ if (!bits) {
+ bitmap_free(mask);
+ return -ENOMEM;
+ }
+ }
+
+ bitmap_zero(mask, gc->ngpio);
+
+ if (!can_sleep)
+ WARN_ON(gc->can_sleep);
+
+ do {
+ struct gpio_desc *desc = desc_array[i];
+ int hwgpio = gpio_chip_hwgpio(desc);
+ int value = test_bit(i, value_bitmap);
+
+ /*
+ * Pins applicable for fast input but not for
+ * fast output processing may have been already
+ * inverted inside the fast path, skip them.
+ */
+ if (!raw && !(array_info &&
+ test_bit(i, array_info->invert_mask)) &&
+ test_bit(FLAG_ACTIVE_LOW, &desc->flags))
+ value = !value;
+ trace_gpio_value(desc_to_gpio(desc), 0, value);
+ /*
+ * collect all normal outputs belonging to the same chip
+ * open drain and open source outputs are set individually
+ */
+ if (test_bit(FLAG_OPEN_DRAIN, &desc->flags) && !raw) {
+ gpio_set_open_drain_value_commit(desc, value);
+ } else if (test_bit(FLAG_OPEN_SOURCE, &desc->flags) && !raw) {
+ gpio_set_open_source_value_commit(desc, value);
+ } else {
+ __set_bit(hwgpio, mask);
+ __assign_bit(hwgpio, bits, value);
+ count++;
+ }
+ i++;
+
+ if (array_info)
+ i = find_next_zero_bit(array_info->set_mask,
+ array_size, i);
+ } while ((i < array_size) &&
+ (desc_array[i]->gdev->chip == gc));
+ /* push collected bits to outputs */
+ if (count != 0)
+ gpio_chip_set_multiple(gc, mask, bits);
+
+ if (mask != fastpath_mask)
+ bitmap_free(mask);
+ if (bits != fastpath_bits)
+ bitmap_free(bits);
+ }
+ return 0;
+}
+
+/**
+ * gpiod_set_raw_value() - assign a gpio's raw value
+ * @desc: gpio whose value will be assigned
+ * @value: value to assign
+ *
+ * Set the raw value of the GPIO, i.e. the value of its physical line without
+ * regard for its ACTIVE_LOW status.
+ *
+ * This function can be called from contexts where we cannot sleep, and will
+ * complain if the GPIO chip functions potentially sleep.
+ */
+void gpiod_set_raw_value(struct gpio_desc *desc, int value)
+{
+ VALIDATE_DESC_VOID(desc);
+ /* Should be using gpiod_set_raw_value_cansleep() */
+ WARN_ON(desc->gdev->chip->can_sleep);
+ gpiod_set_raw_value_commit(desc, value);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_raw_value);
+
+/**
+ * gpiod_set_value_nocheck() - set a GPIO line value without checking
+ * @desc: the descriptor to set the value on
+ * @value: value to set
+ *
+ * This sets the value of a GPIO line backing a descriptor, applying
+ * different semantic quirks like active low and open drain/source
+ * handling.
+ */
+static void gpiod_set_value_nocheck(struct gpio_desc *desc, int value)
+{
+ if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
+ value = !value;
+ if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))
+ gpio_set_open_drain_value_commit(desc, value);
+ else if (test_bit(FLAG_OPEN_SOURCE, &desc->flags))
+ gpio_set_open_source_value_commit(desc, value);
+ else
+ gpiod_set_raw_value_commit(desc, value);
+}
+
+/**
+ * gpiod_set_value() - assign a gpio's value
+ * @desc: gpio whose value will be assigned
+ * @value: value to assign
+ *
+ * Set the logical value of the GPIO, i.e. taking its ACTIVE_LOW,
+ * OPEN_DRAIN and OPEN_SOURCE flags into account.
+ *
+ * This function can be called from contexts where we cannot sleep, and will
+ * complain if the GPIO chip functions potentially sleep.
+ */
+void gpiod_set_value(struct gpio_desc *desc, int value)
+{
+ VALIDATE_DESC_VOID(desc);
+ /* Should be using gpiod_set_value_cansleep() */
+ WARN_ON(desc->gdev->chip->can_sleep);
+ gpiod_set_value_nocheck(desc, value);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_value);
+
+/**
+ * gpiod_set_raw_array_value() - assign values to an array of GPIOs
+ * @array_size: number of elements in the descriptor array / value bitmap
+ * @desc_array: array of GPIO descriptors whose values will be assigned
+ * @array_info: information on applicability of fast bitmap processing path
+ * @value_bitmap: bitmap of values to assign
+ *
+ * Set the raw values of the GPIOs, i.e. the values of the physical lines
+ * without regard for their ACTIVE_LOW status.
+ *
+ * This function can be called from contexts where we cannot sleep, and will
+ * complain if the GPIO chip functions potentially sleep.
+ */
+int gpiod_set_raw_array_value(unsigned int array_size,
+ struct gpio_desc **desc_array,
+ struct gpio_array *array_info,
+ unsigned long *value_bitmap)
+{
+ if (!desc_array)
+ return -EINVAL;
+ return gpiod_set_array_value_complex(true, false, array_size,
+ desc_array, array_info, value_bitmap);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_raw_array_value);
+
+/**
+ * gpiod_set_array_value() - assign values to an array of GPIOs
+ * @array_size: number of elements in the descriptor array / value bitmap
+ * @desc_array: array of GPIO descriptors whose values will be assigned
+ * @array_info: information on applicability of fast bitmap processing path
+ * @value_bitmap: bitmap of values to assign
+ *
+ * Set the logical values of the GPIOs, i.e. taking their ACTIVE_LOW status
+ * into account.
+ *
+ * This function can be called from contexts where we cannot sleep, and will
+ * complain if the GPIO chip functions potentially sleep.
+ */
+int gpiod_set_array_value(unsigned int array_size,
+ struct gpio_desc **desc_array,
+ struct gpio_array *array_info,
+ unsigned long *value_bitmap)
+{
+ if (!desc_array)
+ return -EINVAL;
+ return gpiod_set_array_value_complex(false, false, array_size,
+ desc_array, array_info,
+ value_bitmap);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_array_value);
+
+/**
+ * gpiod_cansleep() - report whether gpio value access may sleep
+ * @desc: gpio to check
+ *
+ */
+int gpiod_cansleep(const struct gpio_desc *desc)
+{
+ VALIDATE_DESC(desc);
+ return desc->gdev->chip->can_sleep;
+}
+EXPORT_SYMBOL_GPL(gpiod_cansleep);
+
+/**
+ * gpiod_set_consumer_name() - set the consumer name for the descriptor
+ * @desc: gpio to set the consumer name on
+ * @name: the new consumer name
+ */
+int gpiod_set_consumer_name(struct gpio_desc *desc, const char *name)
+{
+ VALIDATE_DESC(desc);
+ if (name) {
+ name = kstrdup_const(name, GFP_KERNEL);
+ if (!name)
+ return -ENOMEM;
+ }
+
+ kfree_const(desc->label);
+ desc_set_label(desc, name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gpiod_set_consumer_name);
+
+/**
+ * gpiod_to_irq() - return the IRQ corresponding to a GPIO
+ * @desc: gpio whose IRQ will be returned (already requested)
+ *
+ * Return the IRQ corresponding to the passed GPIO, or an error code in case of
+ * error.
+ */
+int gpiod_to_irq(const struct gpio_desc *desc)
+{
+ struct gpio_chip *gc;
+ int offset;
+
+ /*
+ * Cannot VALIDATE_DESC() here as gpiod_to_irq() consumer semantics
+ * requires this function to not return zero on an invalid descriptor
+ * but rather a negative error number.
+ */
+ if (!desc || IS_ERR(desc) || !desc->gdev || !desc->gdev->chip)
+ return -EINVAL;
+
+ gc = desc->gdev->chip;
+ offset = gpio_chip_hwgpio(desc);
+ if (gc->to_irq) {
+ int retirq = gc->to_irq(gc, offset);
+
+ /* Zero means NO_IRQ */
+ if (!retirq)
+ return -ENXIO;
+
+ return retirq;
+ }
+#ifdef CONFIG_GPIOLIB_IRQCHIP
+ if (gc->irq.chip) {
+ /*
+ * Avoid race condition with other code, which tries to lookup
+ * an IRQ before the irqchip has been properly registered,
+ * i.e. while gpiochip is still being brought up.
+ */
+ return -EPROBE_DEFER;
+ }
+#endif
+ return -ENXIO;
+}
+EXPORT_SYMBOL_GPL(gpiod_to_irq);
+
+/**
+ * gpiochip_lock_as_irq() - lock a GPIO to be used as IRQ
+ * @gc: the chip the GPIO to lock belongs to
+ * @offset: the offset of the GPIO to lock as IRQ
+ *
+ * This is used directly by GPIO drivers that want to lock down
+ * a certain GPIO line to be used for IRQs.
+ */
+int gpiochip_lock_as_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_desc *desc;
+
+ desc = gpiochip_get_desc(gc, offset);
+ if (IS_ERR(desc))
+ return PTR_ERR(desc);
+
+ /*
+ * If it's fast: flush the direction setting if something changed
+ * behind our back
+ */
+ if (!gc->can_sleep && gc->get_direction) {
+ int dir = gpiod_get_direction(desc);
+
+ if (dir < 0) {
+ chip_err(gc, "%s: cannot get GPIO direction\n",
+ __func__);
+ return dir;
+ }
+ }
+
+ /* To be valid for IRQ the line needs to be input or open drain */
+ if (test_bit(FLAG_IS_OUT, &desc->flags) &&
+ !test_bit(FLAG_OPEN_DRAIN, &desc->flags)) {
+ chip_err(gc,
+ "%s: tried to flag a GPIO set as output for IRQ\n",
+ __func__);
+ return -EIO;
+ }
+
+ set_bit(FLAG_USED_AS_IRQ, &desc->flags);
+ set_bit(FLAG_IRQ_IS_ENABLED, &desc->flags);
+
+ /*
+ * If the consumer has not set up a label (such as when the
+ * IRQ is referenced from .to_irq()) we set up a label here
+ * so it is clear this is used as an interrupt.
+ */
+ if (!desc->label)
+ desc_set_label(desc, "interrupt");
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gpiochip_lock_as_irq);
+
+/**
+ * gpiochip_unlock_as_irq() - unlock a GPIO used as IRQ
+ * @gc: the chip the GPIO to lock belongs to
+ * @offset: the offset of the GPIO to lock as IRQ
+ *
+ * This is used directly by GPIO drivers that want to indicate
+ * that a certain GPIO is no longer used exclusively for IRQ.
+ */
+void gpiochip_unlock_as_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_desc *desc;
+
+ desc = gpiochip_get_desc(gc, offset);
+ if (IS_ERR(desc))
+ return;
+
+ clear_bit(FLAG_USED_AS_IRQ, &desc->flags);
+ clear_bit(FLAG_IRQ_IS_ENABLED, &desc->flags);
+
+ /* If we only had this marking, erase it */
+ if (desc->label && !strcmp(desc->label, "interrupt"))
+ desc_set_label(desc, NULL);
+}
+EXPORT_SYMBOL_GPL(gpiochip_unlock_as_irq);
+
+void gpiochip_disable_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_desc *desc = gpiochip_get_desc(gc, offset);
+
+ if (!IS_ERR(desc) &&
+ !WARN_ON(!test_bit(FLAG_USED_AS_IRQ, &desc->flags)))
+ clear_bit(FLAG_IRQ_IS_ENABLED, &desc->flags);
+}
+EXPORT_SYMBOL_GPL(gpiochip_disable_irq);
+
+void gpiochip_enable_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ struct gpio_desc *desc = gpiochip_get_desc(gc, offset);
+
+ if (!IS_ERR(desc) &&
+ !WARN_ON(!test_bit(FLAG_USED_AS_IRQ, &desc->flags))) {
+ /*
+ * We must not be output when using IRQ UNLESS we are
+ * open drain.
+ */
+ WARN_ON(test_bit(FLAG_IS_OUT, &desc->flags) &&
+ !test_bit(FLAG_OPEN_DRAIN, &desc->flags));
+ set_bit(FLAG_IRQ_IS_ENABLED, &desc->flags);
+ }
+}
+EXPORT_SYMBOL_GPL(gpiochip_enable_irq);
+
+bool gpiochip_line_is_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ if (offset >= gc->ngpio)
+ return false;
+
+ return test_bit(FLAG_USED_AS_IRQ, &gc->gpiodev->descs[offset].flags);
+}
+EXPORT_SYMBOL_GPL(gpiochip_line_is_irq);
+
+int gpiochip_reqres_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ int ret;
+
+ if (!try_module_get(gc->gpiodev->owner))
+ return -ENODEV;
+
+ ret = gpiochip_lock_as_irq(gc, offset);
+ if (ret) {
+ chip_err(gc, "unable to lock HW IRQ %u for IRQ\n", offset);
+ module_put(gc->gpiodev->owner);
+ return ret;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gpiochip_reqres_irq);
+
+void gpiochip_relres_irq(struct gpio_chip *gc, unsigned int offset)
+{
+ gpiochip_unlock_as_irq(gc, offset);
+ module_put(gc->gpiodev->owner);
+}
+EXPORT_SYMBOL_GPL(gpiochip_relres_irq);
+
+bool gpiochip_line_is_open_drain(struct gpio_chip *gc, unsigned int offset)
+{
+ if (offset >= gc->ngpio)
+ return false;
+
+ return test_bit(FLAG_OPEN_DRAIN, &gc->gpiodev->descs[offset].flags);
+}
+EXPORT_SYMBOL_GPL(gpiochip_line_is_open_drain);
+
+bool gpiochip_line_is_open_source(struct gpio_chip *gc, unsigned int offset)
+{
+ if (offset >= gc->ngpio)
+ return false;
+
+ return test_bit(FLAG_OPEN_SOURCE, &gc->gpiodev->descs[offset].flags);
+}
+EXPORT_SYMBOL_GPL(gpiochip_line_is_open_source);
+
+bool gpiochip_line_is_persistent(struct gpio_chip *gc, unsigned int offset)
+{
+ if (offset >= gc->ngpio)
+ return false;
+
+ return !test_bit(FLAG_TRANSITORY, &gc->gpiodev->descs[offset].flags);
+}
+EXPORT_SYMBOL_GPL(gpiochip_line_is_persistent);
+
+/**
+ * gpiod_get_raw_value_cansleep() - return a gpio's raw value
+ * @desc: gpio whose value will be returned
+ *
+ * Return the GPIO's raw value, i.e. the value of the physical line disregarding
+ * its ACTIVE_LOW status, or negative errno on failure.
+ *
+ * This function is to be called from contexts that can sleep.
+ */
+int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc)
+{
+ might_sleep_if(extra_checks);
+ VALIDATE_DESC(desc);
+ return gpiod_get_raw_value_commit(desc);
+}
+EXPORT_SYMBOL_GPL(gpiod_get_raw_value_cansleep);
+
+/**
+ * gpiod_get_value_cansleep() - return a gpio's value
+ * @desc: gpio whose value will be returned
+ *
+ * Return the GPIO's logical value, i.e. taking the ACTIVE_LOW status into
+ * account, or negative errno on failure.
+ *
+ * This function is to be called from contexts that can sleep.
+ */
+int gpiod_get_value_cansleep(const struct gpio_desc *desc)
+{
+ int value;
+
+ might_sleep_if(extra_checks);
+ VALIDATE_DESC(desc);
+ value = gpiod_get_raw_value_commit(desc);
+ if (value < 0)
+ return value;
+
+ if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
+ value = !value;
+
+ return value;
+}
+EXPORT_SYMBOL_GPL(gpiod_get_value_cansleep);
+
+/**
+ * gpiod_get_raw_array_value_cansleep() - read raw values from an array of GPIOs
+ * @array_size: number of elements in the descriptor array / value bitmap
+ * @desc_array: array of GPIO descriptors whose values will be read
+ * @array_info: information on applicability of fast bitmap processing path
+ * @value_bitmap: bitmap to store the read values
+ *
+ * Read the raw values of the GPIOs, i.e. the values of the physical lines
+ * without regard for their ACTIVE_LOW status. Return 0 in case of success,
+ * else an error code.
+ *
+ * This function is to be called from contexts that can sleep.
+ */
+int gpiod_get_raw_array_value_cansleep(unsigned int array_size,
+ struct gpio_desc **desc_array,
+ struct gpio_array *array_info,
+ unsigned long *value_bitmap)
+{
+ might_sleep_if(extra_checks);
+ if (!desc_array)
+ return -EINVAL;
+ return gpiod_get_array_value_complex(true, true, array_size,
+ desc_array, array_info,
+ value_bitmap);
+}
+EXPORT_SYMBOL_GPL(gpiod_get_raw_array_value_cansleep);
+
+/**
+ * gpiod_get_array_value_cansleep() - read values from an array of GPIOs
+ * @array_size: number of elements in the descriptor array / value bitmap
+ * @desc_array: array of GPIO descriptors whose values will be read
+ * @array_info: information on applicability of fast bitmap processing path
+ * @value_bitmap: bitmap to store the read values
+ *
+ * Read the logical values of the GPIOs, i.e. taking their ACTIVE_LOW status
+ * into account. Return 0 in case of success, else an error code.
+ *
+ * This function is to be called from contexts that can sleep.
+ */
+int gpiod_get_array_value_cansleep(unsigned int array_size,
+ struct gpio_desc **desc_array,
+ struct gpio_array *array_info,
+ unsigned long *value_bitmap)
+{
+ might_sleep_if(extra_checks);
+ if (!desc_array)
+ return -EINVAL;
+ return gpiod_get_array_value_complex(false, true, array_size,
+ desc_array, array_info,
+ value_bitmap);
+}
+EXPORT_SYMBOL_GPL(gpiod_get_array_value_cansleep);
+
+/**
+ * gpiod_set_raw_value_cansleep() - assign a gpio's raw value
+ * @desc: gpio whose value will be assigned
+ * @value: value to assign
+ *
+ * Set the raw value of the GPIO, i.e. the value of its physical line without
+ * regard for its ACTIVE_LOW status.
+ *
+ * This function is to be called from contexts that can sleep.
+ */
+void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value)
+{
+ might_sleep_if(extra_checks);
+ VALIDATE_DESC_VOID(desc);
+ gpiod_set_raw_value_commit(desc, value);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_raw_value_cansleep);
+
+/**
+ * gpiod_set_value_cansleep() - assign a gpio's value
+ * @desc: gpio whose value will be assigned
+ * @value: value to assign
+ *
+ * Set the logical value of the GPIO, i.e. taking its ACTIVE_LOW status into
+ * account
+ *
+ * This function is to be called from contexts that can sleep.
+ */
+void gpiod_set_value_cansleep(struct gpio_desc *desc, int value)
+{
+ might_sleep_if(extra_checks);
+ VALIDATE_DESC_VOID(desc);
+ gpiod_set_value_nocheck(desc, value);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_value_cansleep);
+
+/**
+ * gpiod_set_raw_array_value_cansleep() - assign values to an array of GPIOs
+ * @array_size: number of elements in the descriptor array / value bitmap
+ * @desc_array: array of GPIO descriptors whose values will be assigned
+ * @array_info: information on applicability of fast bitmap processing path
+ * @value_bitmap: bitmap of values to assign
+ *
+ * Set the raw values of the GPIOs, i.e. the values of the physical lines
+ * without regard for their ACTIVE_LOW status.
+ *
+ * This function is to be called from contexts that can sleep.
+ */
+int gpiod_set_raw_array_value_cansleep(unsigned int array_size,
+ struct gpio_desc **desc_array,
+ struct gpio_array *array_info,
+ unsigned long *value_bitmap)
+{
+ might_sleep_if(extra_checks);
+ if (!desc_array)
+ return -EINVAL;
+ return gpiod_set_array_value_complex(true, true, array_size, desc_array,
+ array_info, value_bitmap);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_raw_array_value_cansleep);
+
+/**
+ * gpiod_add_lookup_tables() - register GPIO device consumers
+ * @tables: list of tables of consumers to register
+ * @n: number of tables in the list
+ */
+void gpiod_add_lookup_tables(struct gpiod_lookup_table **tables, size_t n)
+{
+ unsigned int i;
+
+ mutex_lock(&gpio_lookup_lock);
+
+ for (i = 0; i < n; i++)
+ list_add_tail(&tables[i]->list, &gpio_lookup_list);
+
+ mutex_unlock(&gpio_lookup_lock);
+}
+
+/**
+ * gpiod_set_array_value_cansleep() - assign values to an array of GPIOs
+ * @array_size: number of elements in the descriptor array / value bitmap
+ * @desc_array: array of GPIO descriptors whose values will be assigned
+ * @array_info: information on applicability of fast bitmap processing path
+ * @value_bitmap: bitmap of values to assign
+ *
+ * Set the logical values of the GPIOs, i.e. taking their ACTIVE_LOW status
+ * into account.
+ *
+ * This function is to be called from contexts that can sleep.
+ */
+int gpiod_set_array_value_cansleep(unsigned int array_size,
+ struct gpio_desc **desc_array,
+ struct gpio_array *array_info,
+ unsigned long *value_bitmap)
+{
+ might_sleep_if(extra_checks);
+ if (!desc_array)
+ return -EINVAL;
+ return gpiod_set_array_value_complex(false, true, array_size,
+ desc_array, array_info,
+ value_bitmap);
+}
+EXPORT_SYMBOL_GPL(gpiod_set_array_value_cansleep);
+
+void gpiod_line_state_notify(struct gpio_desc *desc, unsigned long action)
+{
+ blocking_notifier_call_chain(&desc->gdev->line_state_notifier,
+ action, desc);
+}
+
+/**
+ * gpiod_add_lookup_table() - register GPIO device consumers
+ * @table: table of consumers to register
+ */
+void gpiod_add_lookup_table(struct gpiod_lookup_table *table)
+{
+ gpiod_add_lookup_tables(&table, 1);
+}
+EXPORT_SYMBOL_GPL(gpiod_add_lookup_table);
+
+/**
+ * gpiod_remove_lookup_table() - unregister GPIO device consumers
+ * @table: table of consumers to unregister
+ */
+void gpiod_remove_lookup_table(struct gpiod_lookup_table *table)
+{
+ /* Nothing to remove */
+ if (!table)
+ return;
+
+ mutex_lock(&gpio_lookup_lock);
+
+ list_del(&table->list);
+
+ mutex_unlock(&gpio_lookup_lock);
+}
+EXPORT_SYMBOL_GPL(gpiod_remove_lookup_table);
+
+/**
+ * gpiod_add_hogs() - register a set of GPIO hogs from machine code
+ * @hogs: table of gpio hog entries with a zeroed sentinel at the end
+ */
+void gpiod_add_hogs(struct gpiod_hog *hogs)
+{
+ struct gpio_chip *gc;
+ struct gpiod_hog *hog;
+
+ mutex_lock(&gpio_machine_hogs_mutex);
+
+ for (hog = &hogs[0]; hog->chip_label; hog++) {
+ list_add_tail(&hog->list, &gpio_machine_hogs);
+
+ /*
+ * The chip may have been registered earlier, so check if it
+ * exists and, if so, try to hog the line now.
+ */
+ gc = find_chip_by_name(hog->chip_label);
+ if (gc)
+ gpiochip_machine_hog(gc, hog);
+ }
+
+ mutex_unlock(&gpio_machine_hogs_mutex);
+}
+EXPORT_SYMBOL_GPL(gpiod_add_hogs);
+
+void gpiod_remove_hogs(struct gpiod_hog *hogs)
+{
+ struct gpiod_hog *hog;
+
+ mutex_lock(&gpio_machine_hogs_mutex);
+ for (hog = &hogs[0]; hog->chip_label; hog++)
+ list_del(&hog->list);
+ mutex_unlock(&gpio_machine_hogs_mutex);
+}
+EXPORT_SYMBOL_GPL(gpiod_remove_hogs);
+
+static struct gpiod_lookup_table *gpiod_find_lookup_table(struct device *dev)
+{
+ const char *dev_id = dev ? dev_name(dev) : NULL;
+ struct gpiod_lookup_table *table;
+
+ mutex_lock(&gpio_lookup_lock);
+
+ list_for_each_entry(table, &gpio_lookup_list, list) {
+ if (table->dev_id && dev_id) {
+ /*
+ * Valid strings on both ends, must be identical to have
+ * a match
+ */
+ if (!strcmp(table->dev_id, dev_id))
+ goto found;
+ } else {
+ /*
+ * One of the pointers is NULL, so both must be to have
+ * a match
+ */
+ if (dev_id == table->dev_id)
+ goto found;
+ }
+ }
+ table = NULL;
+
+found:
+ mutex_unlock(&gpio_lookup_lock);
+ return table;
+}
+
+static struct gpio_desc *gpiod_find(struct device *dev, const char *con_id,
+ unsigned int idx, unsigned long *flags)
+{
+ struct gpio_desc *desc = ERR_PTR(-ENOENT);
+ struct gpiod_lookup_table *table;
+ struct gpiod_lookup *p;
+
+ table = gpiod_find_lookup_table(dev);
+ if (!table)
+ return desc;
+
+ for (p = &table->table[0]; p->key; p++) {
+ struct gpio_chip *gc;
+
+ /* idx must always match exactly */
+ if (p->idx != idx)
+ continue;
+
+ /* If the lookup entry has a con_id, require exact match */
+ if (p->con_id && (!con_id || strcmp(p->con_id, con_id)))
+ continue;
+
+ if (p->chip_hwnum == U16_MAX) {
+ desc = gpio_name_to_desc(p->key);
+ if (desc) {
+ *flags = p->flags;
+ return desc;
+ }
+
+ dev_warn(dev, "cannot find GPIO line %s, deferring\n",
+ p->key);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ gc = find_chip_by_name(p->key);
+
+ if (!gc) {
+ /*
+ * As the lookup table indicates a chip with
+ * p->key should exist, assume it may
+ * still appear later and let the interested
+ * consumer be probed again or let the Deferred
+ * Probe infrastructure handle the error.
+ */
+ dev_warn(dev, "cannot find GPIO chip %s, deferring\n",
+ p->key);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ if (gc->ngpio <= p->chip_hwnum) {
+ dev_err(dev,
+ "requested GPIO %u (%u) is out of range [0..%u] for chip %s\n",
+ idx, p->chip_hwnum, gc->ngpio - 1,
+ gc->label);
+ return ERR_PTR(-EINVAL);
+ }
+
+ desc = gpiochip_get_desc(gc, p->chip_hwnum);
+ *flags = p->flags;
+
+ return desc;
+ }
+
+ return desc;
+}
+
+static int platform_gpio_count(struct device *dev, const char *con_id)
+{
+ struct gpiod_lookup_table *table;
+ struct gpiod_lookup *p;
+ unsigned int count = 0;
+
+ table = gpiod_find_lookup_table(dev);
+ if (!table)
+ return -ENOENT;
+
+ for (p = &table->table[0]; p->key; p++) {
+ if ((con_id && p->con_id && !strcmp(con_id, p->con_id)) ||
+ (!con_id && !p->con_id))
+ count++;
+ }
+ if (!count)
+ return -ENOENT;
+
+ return count;
+}
+
+static struct gpio_desc *gpiod_find_by_fwnode(struct fwnode_handle *fwnode,
+ struct device *consumer,
+ const char *con_id,
+ unsigned int idx,
+ enum gpiod_flags *flags,
+ unsigned long *lookupflags)
+{
+ struct gpio_desc *desc = ERR_PTR(-ENOENT);
+
+ if (is_of_node(fwnode)) {
+ dev_dbg(consumer, "using DT '%pfw' for '%s' GPIO lookup\n",
+ fwnode, con_id);
+ desc = of_find_gpio(to_of_node(fwnode), con_id, idx, lookupflags);
+ } else if (is_acpi_node(fwnode)) {
+ dev_dbg(consumer, "using ACPI '%pfw' for '%s' GPIO lookup\n",
+ fwnode, con_id);
+ desc = acpi_find_gpio(fwnode, con_id, idx, flags, lookupflags);
+ } else if (is_software_node(fwnode)) {
+ dev_dbg(consumer, "using swnode '%pfw' for '%s' GPIO lookup\n",
+ fwnode, con_id);
+ desc = swnode_find_gpio(fwnode, con_id, idx, lookupflags);
+ }
+
+ return desc;
+}
+
+static struct gpio_desc *gpiod_find_and_request(struct device *consumer,
+ struct fwnode_handle *fwnode,
+ const char *con_id,
+ unsigned int idx,
+ enum gpiod_flags flags,
+ const char *label,
+ bool platform_lookup_allowed)
+{
+ unsigned long lookupflags = GPIO_LOOKUP_FLAGS_DEFAULT;
+ struct gpio_desc *desc;
+ int ret;
+
+ desc = gpiod_find_by_fwnode(fwnode, consumer, con_id, idx, &flags, &lookupflags);
+ if (gpiod_not_found(desc) && platform_lookup_allowed) {
+ /*
+ * Either we are not using DT or ACPI, or their lookup did not
+ * return a result. In that case, use platform lookup as a
+ * fallback.
+ */
+ dev_dbg(consumer, "using lookup tables for GPIO lookup\n");
+ desc = gpiod_find(consumer, con_id, idx, &lookupflags);
+ }
+
+ if (IS_ERR(desc)) {
+ dev_dbg(consumer, "No GPIO consumer %s found\n", con_id);
+ return desc;
+ }
+
+ /*
+ * If a connection label was passed use that, else attempt to use
+ * the device name as label
+ */
+ ret = gpiod_request(desc, label);
+ if (ret) {
+ if (!(ret == -EBUSY && flags & GPIOD_FLAGS_BIT_NONEXCLUSIVE))
+ return ERR_PTR(ret);
+
+ /*
+ * This happens when there are several consumers for
+ * the same GPIO line: we just return here without
+ * further initialization. It is a bit of a hack.
+ * This is necessary to support fixed regulators.
+ *
+ * FIXME: Make this more sane and safe.
+ */
+ dev_info(consumer,
+ "nonexclusive access to GPIO for %s\n", con_id);
+ return desc;
+ }
+
+ ret = gpiod_configure_flags(desc, con_id, lookupflags, flags);
+ if (ret < 0) {
+ dev_dbg(consumer, "setup of GPIO %s failed\n", con_id);
+ gpiod_put(desc);
+ return ERR_PTR(ret);
+ }
+
+ gpiod_line_state_notify(desc, GPIOLINE_CHANGED_REQUESTED);
+
+ return desc;
+}
+
+/**
+ * fwnode_gpiod_get_index - obtain a GPIO from firmware node
+ * @fwnode: handle of the firmware node
+ * @con_id: function within the GPIO consumer
+ * @index: index of the GPIO to obtain for the consumer
+ * @flags: GPIO initialization flags
+ * @label: label to attach to the requested GPIO
+ *
+ * This function can be used for drivers that get their configuration
+ * from opaque firmware.
+ *
+ * The function properly finds the corresponding GPIO using whatever is the
+ * underlying firmware interface and then makes sure that the GPIO
+ * descriptor is requested before it is returned to the caller.
+ *
+ * Returns:
+ * On successful request the GPIO pin is configured in accordance with
+ * provided @flags.
+ *
+ * In case of error an ERR_PTR() is returned.
+ */
+struct gpio_desc *fwnode_gpiod_get_index(struct fwnode_handle *fwnode,
+ const char *con_id,
+ int index,
+ enum gpiod_flags flags,
+ const char *label)
+{
+ return gpiod_find_and_request(NULL, fwnode, con_id, index, flags, label, false);
+}
+EXPORT_SYMBOL_GPL(fwnode_gpiod_get_index);
+
+/**
+ * gpiod_count - return the number of GPIOs associated with a device / function
+ * or -ENOENT if no GPIO has been assigned to the requested function
+ * @dev: GPIO consumer, can be NULL for system-global GPIOs
+ * @con_id: function within the GPIO consumer
+ */
+int gpiod_count(struct device *dev, const char *con_id)
+{
+ const struct fwnode_handle *fwnode = dev ? dev_fwnode(dev) : NULL;
+ int count = -ENOENT;
+
+ if (is_of_node(fwnode))
+ count = of_gpio_get_count(dev, con_id);
+ else if (is_acpi_node(fwnode))
+ count = acpi_gpio_count(dev, con_id);
+ else if (is_software_node(fwnode))
+ count = swnode_gpio_count(fwnode, con_id);
+
+ if (count < 0)
+ count = platform_gpio_count(dev, con_id);
+
+ return count;
+}
+EXPORT_SYMBOL_GPL(gpiod_count);
+
+/**
+ * gpiod_get - obtain a GPIO for a given GPIO function
+ * @dev: GPIO consumer, can be NULL for system-global GPIOs
+ * @con_id: function within the GPIO consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * Return the GPIO descriptor corresponding to the function con_id of device
+ * dev, -ENOENT if no GPIO has been assigned to the requested function, or
+ * another IS_ERR() code if an error occurred while trying to acquire the GPIO.
+ */
+struct gpio_desc *__must_check gpiod_get(struct device *dev, const char *con_id,
+ enum gpiod_flags flags)
+{
+ return gpiod_get_index(dev, con_id, 0, flags);
+}
+EXPORT_SYMBOL_GPL(gpiod_get);
+
+/**
+ * gpiod_get_optional - obtain an optional GPIO for a given GPIO function
+ * @dev: GPIO consumer, can be NULL for system-global GPIOs
+ * @con_id: function within the GPIO consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * This is equivalent to gpiod_get(), except that when no GPIO was assigned to
+ * the requested function it will return NULL. This is convenient for drivers
+ * that need to handle optional GPIOs.
+ */
+struct gpio_desc *__must_check gpiod_get_optional(struct device *dev,
+ const char *con_id,
+ enum gpiod_flags flags)
+{
+ return gpiod_get_index_optional(dev, con_id, 0, flags);
+}
+EXPORT_SYMBOL_GPL(gpiod_get_optional);
+
+
+/**
+ * gpiod_configure_flags - helper function to configure a given GPIO
+ * @desc: gpio whose value will be assigned
+ * @con_id: function within the GPIO consumer
+ * @lflags: bitmask of gpio_lookup_flags GPIO_* values - returned from
+ * of_find_gpio() or of_get_gpio_hog()
+ * @dflags: gpiod_flags - optional GPIO initialization flags
+ *
+ * Return 0 on success, -ENOENT if no GPIO has been assigned to the
+ * requested function and/or index, or another IS_ERR() code if an error
+ * occurred while trying to acquire the GPIO.
+ */
+int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id,
+ unsigned long lflags, enum gpiod_flags dflags)
+{
+ int ret;
+
+ if (lflags & GPIO_ACTIVE_LOW)
+ set_bit(FLAG_ACTIVE_LOW, &desc->flags);
+
+ if (lflags & GPIO_OPEN_DRAIN)
+ set_bit(FLAG_OPEN_DRAIN, &desc->flags);
+ else if (dflags & GPIOD_FLAGS_BIT_OPEN_DRAIN) {
+ /*
+ * This enforces open drain mode from the consumer side.
+ * This is necessary for some busses like I2C, but the lookup
+ * should *REALLY* have specified them as open drain in the
+ * first place, so print a little warning here.
+ */
+ set_bit(FLAG_OPEN_DRAIN, &desc->flags);
+ gpiod_warn(desc,
+ "enforced open drain please flag it properly in DT/ACPI DSDT/board file\n");
+ }
+
+ if (lflags & GPIO_OPEN_SOURCE)
+ set_bit(FLAG_OPEN_SOURCE, &desc->flags);
+
+ if (((lflags & GPIO_PULL_UP) && (lflags & GPIO_PULL_DOWN)) ||
+ ((lflags & GPIO_PULL_UP) && (lflags & GPIO_PULL_DISABLE)) ||
+ ((lflags & GPIO_PULL_DOWN) && (lflags & GPIO_PULL_DISABLE))) {
+ gpiod_err(desc,
+ "multiple pull-up, pull-down or pull-disable enabled, invalid configuration\n");
+ return -EINVAL;
+ }
+
+ if (lflags & GPIO_PULL_UP)
+ set_bit(FLAG_PULL_UP, &desc->flags);
+ else if (lflags & GPIO_PULL_DOWN)
+ set_bit(FLAG_PULL_DOWN, &desc->flags);
+ else if (lflags & GPIO_PULL_DISABLE)
+ set_bit(FLAG_BIAS_DISABLE, &desc->flags);
+
+ ret = gpiod_set_transitory(desc, (lflags & GPIO_TRANSITORY));
+ if (ret < 0)
+ return ret;
+
+ /* No particular flag request, return here... */
+ if (!(dflags & GPIOD_FLAGS_BIT_DIR_SET)) {
+ gpiod_dbg(desc, "no flags found for %s\n", con_id);
+ return 0;
+ }
+
+ /* Process flags */
+ if (dflags & GPIOD_FLAGS_BIT_DIR_OUT)
+ ret = gpiod_direction_output(desc,
+ !!(dflags & GPIOD_FLAGS_BIT_DIR_VAL));
+ else
+ ret = gpiod_direction_input(desc);
+
+ return ret;
+}
+
+/**
+ * gpiod_get_index - obtain a GPIO from a multi-index GPIO function
+ * @dev: GPIO consumer, can be NULL for system-global GPIOs
+ * @con_id: function within the GPIO consumer
+ * @idx: index of the GPIO to obtain in the consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * This variant of gpiod_get() allows to access GPIOs other than the first
+ * defined one for functions that define several GPIOs.
+ *
+ * Return a valid GPIO descriptor, -ENOENT if no GPIO has been assigned to the
+ * requested function and/or index, or another IS_ERR() code if an error
+ * occurred while trying to acquire the GPIO.
+ */
+struct gpio_desc *__must_check gpiod_get_index(struct device *dev,
+ const char *con_id,
+ unsigned int idx,
+ enum gpiod_flags flags)
+{
+ struct fwnode_handle *fwnode = dev ? dev_fwnode(dev) : NULL;
+ const char *devname = dev ? dev_name(dev) : "?";
+ const char *label = con_id ?: devname;
+
+ return gpiod_find_and_request(dev, fwnode, con_id, idx, flags, label, true);
+}
+EXPORT_SYMBOL_GPL(gpiod_get_index);
+
+/**
+ * gpiod_get_index_optional - obtain an optional GPIO from a multi-index GPIO
+ * function
+ * @dev: GPIO consumer, can be NULL for system-global GPIOs
+ * @con_id: function within the GPIO consumer
+ * @index: index of the GPIO to obtain in the consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * This is equivalent to gpiod_get_index(), except that when no GPIO with the
+ * specified index was assigned to the requested function it will return NULL.
+ * This is convenient for drivers that need to handle optional GPIOs.
+ */
+struct gpio_desc *__must_check gpiod_get_index_optional(struct device *dev,
+ const char *con_id,
+ unsigned int index,
+ enum gpiod_flags flags)
+{
+ struct gpio_desc *desc;
+
+ desc = gpiod_get_index(dev, con_id, index, flags);
+ if (gpiod_not_found(desc))
+ return NULL;
+
+ return desc;
+}
+EXPORT_SYMBOL_GPL(gpiod_get_index_optional);
+
+/**
+ * gpiod_hog - Hog the specified GPIO desc given the provided flags
+ * @desc: gpio whose value will be assigned
+ * @name: gpio line name
+ * @lflags: bitmask of gpio_lookup_flags GPIO_* values - returned from
+ * of_find_gpio() or of_get_gpio_hog()
+ * @dflags: gpiod_flags - optional GPIO initialization flags
+ */
+int gpiod_hog(struct gpio_desc *desc, const char *name,
+ unsigned long lflags, enum gpiod_flags dflags)
+{
+ struct gpio_chip *gc;
+ struct gpio_desc *local_desc;
+ int hwnum;
+ int ret;
+
+ gc = gpiod_to_chip(desc);
+ hwnum = gpio_chip_hwgpio(desc);
+
+ local_desc = gpiochip_request_own_desc(gc, hwnum, name,
+ lflags, dflags);
+ if (IS_ERR(local_desc)) {
+ ret = PTR_ERR(local_desc);
+ pr_err("requesting hog GPIO %s (chip %s, offset %d) failed, %d\n",
+ name, gc->label, hwnum, ret);
+ return ret;
+ }
+
+ /* Mark GPIO as hogged so it can be identified and removed later */
+ set_bit(FLAG_IS_HOGGED, &desc->flags);
+
+ gpiod_dbg(desc, "hogged as %s%s\n",
+ (dflags & GPIOD_FLAGS_BIT_DIR_OUT) ? "output" : "input",
+ (dflags & GPIOD_FLAGS_BIT_DIR_OUT) ?
+ (dflags & GPIOD_FLAGS_BIT_DIR_VAL) ? "/high" : "/low" : "");
+
+ return 0;
+}
+
+/**
+ * gpiochip_free_hogs - Scan gpio-controller chip and release GPIO hog
+ * @gc: gpio chip to act on
+ */
+static void gpiochip_free_hogs(struct gpio_chip *gc)
+{
+ struct gpio_desc *desc;
+
+ for_each_gpio_desc_with_flag(gc, desc, FLAG_IS_HOGGED)
+ gpiochip_free_own_desc(desc);
+}
+
+/**
+ * gpiod_get_array - obtain multiple GPIOs from a multi-index GPIO function
+ * @dev: GPIO consumer, can be NULL for system-global GPIOs
+ * @con_id: function within the GPIO consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * This function acquires all the GPIOs defined under a given function.
+ *
+ * Return a struct gpio_descs containing an array of descriptors, -ENOENT if
+ * no GPIO has been assigned to the requested function, or another IS_ERR()
+ * code if an error occurred while trying to acquire the GPIOs.
+ */
+struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
+ const char *con_id,
+ enum gpiod_flags flags)
+{
+ struct gpio_desc *desc;
+ struct gpio_descs *descs;
+ struct gpio_array *array_info = NULL;
+ struct gpio_chip *gc;
+ int count, bitmap_size;
+ size_t descs_size;
+
+ count = gpiod_count(dev, con_id);
+ if (count < 0)
+ return ERR_PTR(count);
+
+ descs_size = struct_size(descs, desc, count);
+ descs = kzalloc(descs_size, GFP_KERNEL);
+ if (!descs)
+ return ERR_PTR(-ENOMEM);
+
+ for (descs->ndescs = 0; descs->ndescs < count; descs->ndescs++) {
+ desc = gpiod_get_index(dev, con_id, descs->ndescs, flags);
+ if (IS_ERR(desc)) {
+ gpiod_put_array(descs);
+ return ERR_CAST(desc);
+ }
+
+ descs->desc[descs->ndescs] = desc;
+
+ gc = gpiod_to_chip(desc);
+ /*
+ * If pin hardware number of array member 0 is also 0, select
+ * its chip as a candidate for fast bitmap processing path.
+ */
+ if (descs->ndescs == 0 && gpio_chip_hwgpio(desc) == 0) {
+ struct gpio_descs *array;
+
+ bitmap_size = BITS_TO_LONGS(gc->ngpio > count ?
+ gc->ngpio : count);
+
+ array = krealloc(descs, descs_size +
+ struct_size(array_info, invert_mask, 3 * bitmap_size),
+ GFP_KERNEL | __GFP_ZERO);
+ if (!array) {
+ gpiod_put_array(descs);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ descs = array;
+
+ array_info = (void *)descs + descs_size;
+ array_info->get_mask = array_info->invert_mask +
+ bitmap_size;
+ array_info->set_mask = array_info->get_mask +
+ bitmap_size;
+
+ array_info->desc = descs->desc;
+ array_info->size = count;
+ array_info->chip = gc;
+ bitmap_set(array_info->get_mask, descs->ndescs,
+ count - descs->ndescs);
+ bitmap_set(array_info->set_mask, descs->ndescs,
+ count - descs->ndescs);
+ descs->info = array_info;
+ }
+
+ /* If there is no cache for fast bitmap processing path, continue */
+ if (!array_info)
+ continue;
+
+ /* Unmark array members which don't belong to the 'fast' chip */
+ if (array_info->chip != gc) {
+ __clear_bit(descs->ndescs, array_info->get_mask);
+ __clear_bit(descs->ndescs, array_info->set_mask);
+ }
+ /*
+ * Detect array members which belong to the 'fast' chip
+ * but their pins are not in hardware order.
+ */
+ else if (gpio_chip_hwgpio(desc) != descs->ndescs) {
+ /*
+ * Don't use fast path if all array members processed so
+ * far belong to the same chip as this one but its pin
+ * hardware number is different from its array index.
+ */
+ if (bitmap_full(array_info->get_mask, descs->ndescs)) {
+ array_info = NULL;
+ } else {
+ __clear_bit(descs->ndescs,
+ array_info->get_mask);
+ __clear_bit(descs->ndescs,
+ array_info->set_mask);
+ }
+ } else {
+ /* Exclude open drain or open source from fast output */
+ if (gpiochip_line_is_open_drain(gc, descs->ndescs) ||
+ gpiochip_line_is_open_source(gc, descs->ndescs))
+ __clear_bit(descs->ndescs,
+ array_info->set_mask);
+ /* Identify 'fast' pins which require invertion */
+ if (gpiod_is_active_low(desc))
+ __set_bit(descs->ndescs,
+ array_info->invert_mask);
+ }
+ }
+ if (array_info)
+ dev_dbg(dev,
+ "GPIO array info: chip=%s, size=%d, get_mask=%lx, set_mask=%lx, invert_mask=%lx\n",
+ array_info->chip->label, array_info->size,
+ *array_info->get_mask, *array_info->set_mask,
+ *array_info->invert_mask);
+ return descs;
+}
+EXPORT_SYMBOL_GPL(gpiod_get_array);
+
+/**
+ * gpiod_get_array_optional - obtain multiple GPIOs from a multi-index GPIO
+ * function
+ * @dev: GPIO consumer, can be NULL for system-global GPIOs
+ * @con_id: function within the GPIO consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * This is equivalent to gpiod_get_array(), except that when no GPIO was
+ * assigned to the requested function it will return NULL.
+ */
+struct gpio_descs *__must_check gpiod_get_array_optional(struct device *dev,
+ const char *con_id,
+ enum gpiod_flags flags)
+{
+ struct gpio_descs *descs;
+
+ descs = gpiod_get_array(dev, con_id, flags);
+ if (gpiod_not_found(descs))
+ return NULL;
+
+ return descs;
+}
+EXPORT_SYMBOL_GPL(gpiod_get_array_optional);
+
+/**
+ * gpiod_put - dispose of a GPIO descriptor
+ * @desc: GPIO descriptor to dispose of
+ *
+ * No descriptor can be used after gpiod_put() has been called on it.
+ */
+void gpiod_put(struct gpio_desc *desc)
+{
+ if (desc)
+ gpiod_free(desc);
+}
+EXPORT_SYMBOL_GPL(gpiod_put);
+
+/**
+ * gpiod_put_array - dispose of multiple GPIO descriptors
+ * @descs: struct gpio_descs containing an array of descriptors
+ */
+void gpiod_put_array(struct gpio_descs *descs)
+{
+ unsigned int i;
+
+ for (i = 0; i < descs->ndescs; i++)
+ gpiod_put(descs->desc[i]);
+
+ kfree(descs);
+}
+EXPORT_SYMBOL_GPL(gpiod_put_array);
+
+static int gpio_stub_drv_probe(struct device *dev)
+{
+ /*
+ * The DT node of some GPIO chips have a "compatible" property, but
+ * never have a struct device added and probed by a driver to register
+ * the GPIO chip with gpiolib. In such cases, fw_devlink=on will cause
+ * the consumers of the GPIO chip to get probe deferred forever because
+ * they will be waiting for a device associated with the GPIO chip
+ * firmware node to get added and bound to a driver.
+ *
+ * To allow these consumers to probe, we associate the struct
+ * gpio_device of the GPIO chip with the firmware node and then simply
+ * bind it to this stub driver.
+ */
+ return 0;
+}
+
+static struct device_driver gpio_stub_drv = {
+ .name = "gpio_stub_drv",
+ .bus = &gpio_bus_type,
+ .probe = gpio_stub_drv_probe,
+};
+
+static int __init gpiolib_dev_init(void)
+{
+ int ret;
+
+ /* Register GPIO sysfs bus */
+ ret = bus_register(&gpio_bus_type);
+ if (ret < 0) {
+ pr_err("gpiolib: could not register GPIO bus type\n");
+ return ret;
+ }
+
+ ret = driver_register(&gpio_stub_drv);
+ if (ret < 0) {
+ pr_err("gpiolib: could not register GPIO stub driver\n");
+ bus_unregister(&gpio_bus_type);
+ return ret;
+ }
+
+ ret = alloc_chrdev_region(&gpio_devt, 0, GPIO_DEV_MAX, GPIOCHIP_NAME);
+ if (ret < 0) {
+ pr_err("gpiolib: failed to allocate char dev region\n");
+ driver_unregister(&gpio_stub_drv);
+ bus_unregister(&gpio_bus_type);
+ return ret;
+ }
+
+ gpiolib_initialized = true;
+ gpiochip_setup_devs();
+
+#if IS_ENABLED(CONFIG_OF_DYNAMIC) && IS_ENABLED(CONFIG_OF_GPIO)
+ WARN_ON(of_reconfig_notifier_register(&gpio_of_notifier));
+#endif /* CONFIG_OF_DYNAMIC && CONFIG_OF_GPIO */
+
+ return ret;
+}
+core_initcall(gpiolib_dev_init);
+
+#ifdef CONFIG_DEBUG_FS
+
+static void gpiolib_dbg_show(struct seq_file *s, struct gpio_device *gdev)
+{
+ struct gpio_chip *gc = gdev->chip;
+ struct gpio_desc *desc;
+ unsigned gpio = gdev->base;
+ int value;
+ bool is_out;
+ bool is_irq;
+ bool active_low;
+
+ for_each_gpio_desc(gc, desc) {
+ if (test_bit(FLAG_REQUESTED, &desc->flags)) {
+ gpiod_get_direction(desc);
+ is_out = test_bit(FLAG_IS_OUT, &desc->flags);
+ value = gpio_chip_get_value(gc, desc);
+ is_irq = test_bit(FLAG_USED_AS_IRQ, &desc->flags);
+ active_low = test_bit(FLAG_ACTIVE_LOW, &desc->flags);
+ seq_printf(s, " gpio-%-3d (%-20.20s|%-20.20s) %s %s %s%s\n",
+ gpio, desc->name ?: "", desc->label,
+ is_out ? "out" : "in ",
+ value >= 0 ? (value ? "hi" : "lo") : "? ",
+ is_irq ? "IRQ " : "",
+ active_low ? "ACTIVE LOW" : "");
+ } else if (desc->name) {
+ seq_printf(s, " gpio-%-3d (%-20.20s)\n", gpio, desc->name);
+ }
+
+ gpio++;
+ }
+}
+
+static void *gpiolib_seq_start(struct seq_file *s, loff_t *pos)
+{
+ unsigned long flags;
+ struct gpio_device *gdev = NULL;
+ loff_t index = *pos;
+
+ s->private = "";
+
+ spin_lock_irqsave(&gpio_lock, flags);
+ list_for_each_entry(gdev, &gpio_devices, list)
+ if (index-- == 0) {
+ spin_unlock_irqrestore(&gpio_lock, flags);
+ return gdev;
+ }
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ return NULL;
+}
+
+static void *gpiolib_seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+ unsigned long flags;
+ struct gpio_device *gdev = v;
+ void *ret = NULL;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+ if (list_is_last(&gdev->list, &gpio_devices))
+ ret = NULL;
+ else
+ ret = list_first_entry(&gdev->list, struct gpio_device, list);
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ s->private = "\n";
+ ++*pos;
+
+ return ret;
+}
+
+static void gpiolib_seq_stop(struct seq_file *s, void *v)
+{
+}
+
+static int gpiolib_seq_show(struct seq_file *s, void *v)
+{
+ struct gpio_device *gdev = v;
+ struct gpio_chip *gc = gdev->chip;
+ struct device *parent;
+
+ if (!gc) {
+ seq_printf(s, "%s%s: (dangling chip)", (char *)s->private,
+ dev_name(&gdev->dev));
+ return 0;
+ }
+
+ seq_printf(s, "%s%s: GPIOs %d-%d", (char *)s->private,
+ dev_name(&gdev->dev),
+ gdev->base, gdev->base + gdev->ngpio - 1);
+ parent = gc->parent;
+ if (parent)
+ seq_printf(s, ", parent: %s/%s",
+ parent->bus ? parent->bus->name : "no-bus",
+ dev_name(parent));
+ if (gc->label)
+ seq_printf(s, ", %s", gc->label);
+ if (gc->can_sleep)
+ seq_printf(s, ", can sleep");
+ seq_printf(s, ":\n");
+
+ if (gc->dbg_show)
+ gc->dbg_show(s, gc);
+ else
+ gpiolib_dbg_show(s, gdev);
+
+ return 0;
+}
+
+static const struct seq_operations gpiolib_sops = {
+ .start = gpiolib_seq_start,
+ .next = gpiolib_seq_next,
+ .stop = gpiolib_seq_stop,
+ .show = gpiolib_seq_show,
+};
+DEFINE_SEQ_ATTRIBUTE(gpiolib);
+
+static int __init gpiolib_debugfs_init(void)
+{
+ /* /sys/kernel/debug/gpio */
+ debugfs_create_file("gpio", 0444, NULL, NULL, &gpiolib_fops);
+ return 0;
+}
+subsys_initcall(gpiolib_debugfs_init);
+
+#endif /* DEBUG_FS */
diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
new file mode 100644
index 0000000000..aa19260836
--- /dev/null
+++ b/drivers/gpio/gpiolib.h
@@ -0,0 +1,261 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Internal GPIO functions.
+ *
+ * Copyright (C) 2013, Intel Corporation
+ * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
+ */
+
+#ifndef GPIOLIB_H
+#define GPIOLIB_H
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h> /* for enum gpiod_flags */
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/rwsem.h>
+
+#define GPIOCHIP_NAME "gpiochip"
+
+/**
+ * struct gpio_device - internal state container for GPIO devices
+ * @dev: the GPIO device struct
+ * @chrdev: character device for the GPIO device
+ * @id: numerical ID number for the GPIO chip
+ * @mockdev: class device used by the deprecated sysfs interface (may be
+ * NULL)
+ * @owner: helps prevent removal of modules exporting active GPIOs
+ * @chip: pointer to the corresponding gpiochip, holding static
+ * data for this device
+ * @descs: array of ngpio descriptors.
+ * @ngpio: the number of GPIO lines on this GPIO device, equal to the size
+ * of the @descs array.
+ * @base: GPIO base in the DEPRECATED global Linux GPIO numberspace, assigned
+ * at device creation time.
+ * @label: a descriptive name for the GPIO device, such as the part number
+ * or name of the IP component in a System on Chip.
+ * @data: per-instance data assigned by the driver
+ * @list: links gpio_device:s together for traversal
+ * @line_state_notifier: used to notify subscribers about lines being
+ * requested, released or reconfigured
+ * @device_notifier: used to notify character device wait queues about the GPIO
+ * device being unregistered
+ * @sem: protects the structure from a NULL-pointer dereference of @chip by
+ * user-space operations when the device gets unregistered during
+ * a hot-unplug event
+ * @pin_ranges: range of pins served by the GPIO driver
+ *
+ * This state container holds most of the runtime variable data
+ * for a GPIO device and can hold references and live on after the
+ * GPIO chip has been removed, if it is still being used from
+ * userspace.
+ */
+struct gpio_device {
+ struct device dev;
+ struct cdev chrdev;
+ int id;
+ struct device *mockdev;
+ struct module *owner;
+ struct gpio_chip *chip;
+ struct gpio_desc *descs;
+ int base;
+ u16 ngpio;
+ const char *label;
+ void *data;
+ struct list_head list;
+ struct blocking_notifier_head line_state_notifier;
+ struct blocking_notifier_head device_notifier;
+ struct rw_semaphore sem;
+
+#ifdef CONFIG_PINCTRL
+ /*
+ * If CONFIG_PINCTRL is enabled, then gpio controllers can optionally
+ * describe the actual pin range which they serve in an SoC. This
+ * information would be used by pinctrl subsystem to configure
+ * corresponding pins for gpio usage.
+ */
+ struct list_head pin_ranges;
+#endif
+};
+
+static inline struct gpio_device *to_gpio_device(struct device *dev)
+{
+ return container_of(dev, struct gpio_device, dev);
+}
+
+/* gpio suffixes used for ACPI and device tree lookup */
+static __maybe_unused const char * const gpio_suffixes[] = { "gpios", "gpio" };
+
+/**
+ * struct gpio_array - Opaque descriptor for a structure of GPIO array attributes
+ *
+ * @desc: Array of pointers to the GPIO descriptors
+ * @size: Number of elements in desc
+ * @chip: Parent GPIO chip
+ * @get_mask: Get mask used in fastpath
+ * @set_mask: Set mask used in fastpath
+ * @invert_mask: Invert mask used in fastpath
+ *
+ * This structure is attached to struct gpiod_descs obtained from
+ * gpiod_get_array() and can be passed back to get/set array functions in order
+ * to activate fast processing path if applicable.
+ */
+struct gpio_array {
+ struct gpio_desc **desc;
+ unsigned int size;
+ struct gpio_chip *chip;
+ unsigned long *get_mask;
+ unsigned long *set_mask;
+ unsigned long invert_mask[];
+};
+
+struct gpio_desc *gpiochip_get_desc(struct gpio_chip *gc, unsigned int hwnum);
+
+#define for_each_gpio_desc(gc, desc) \
+ for (unsigned int __i = 0; \
+ __i < gc->ngpio && (desc = gpiochip_get_desc(gc, __i)); \
+ __i++) \
+
+#define for_each_gpio_desc_with_flag(gc, desc, flag) \
+ for_each_gpio_desc(gc, desc) \
+ if (!test_bit(flag, &desc->flags)) {} else
+
+int gpiod_get_array_value_complex(bool raw, bool can_sleep,
+ unsigned int array_size,
+ struct gpio_desc **desc_array,
+ struct gpio_array *array_info,
+ unsigned long *value_bitmap);
+int gpiod_set_array_value_complex(bool raw, bool can_sleep,
+ unsigned int array_size,
+ struct gpio_desc **desc_array,
+ struct gpio_array *array_info,
+ unsigned long *value_bitmap);
+
+extern spinlock_t gpio_lock;
+extern struct list_head gpio_devices;
+
+void gpiod_line_state_notify(struct gpio_desc *desc, unsigned long action);
+
+/**
+ * struct gpio_desc - Opaque descriptor for a GPIO
+ *
+ * @gdev: Pointer to the parent GPIO device
+ * @flags: Binary descriptor flags
+ * @label: Name of the consumer
+ * @name: Line name
+ * @hog: Pointer to the device node that hogs this line (if any)
+ * @debounce_period_us: Debounce period in microseconds
+ *
+ * These are obtained using gpiod_get() and are preferable to the old
+ * integer-based handles.
+ *
+ * Contrary to integers, a pointer to a &struct gpio_desc is guaranteed to be
+ * valid until the GPIO is released.
+ */
+struct gpio_desc {
+ struct gpio_device *gdev;
+ unsigned long flags;
+/* flag symbols are bit numbers */
+#define FLAG_REQUESTED 0
+#define FLAG_IS_OUT 1
+#define FLAG_EXPORT 2 /* protected by sysfs_lock */
+#define FLAG_SYSFS 3 /* exported via /sys/class/gpio/control */
+#define FLAG_ACTIVE_LOW 6 /* value has active low */
+#define FLAG_OPEN_DRAIN 7 /* Gpio is open drain type */
+#define FLAG_OPEN_SOURCE 8 /* Gpio is open source type */
+#define FLAG_USED_AS_IRQ 9 /* GPIO is connected to an IRQ */
+#define FLAG_IRQ_IS_ENABLED 10 /* GPIO is connected to an enabled IRQ */
+#define FLAG_IS_HOGGED 11 /* GPIO is hogged */
+#define FLAG_TRANSITORY 12 /* GPIO may lose value in sleep or reset */
+#define FLAG_PULL_UP 13 /* GPIO has pull up enabled */
+#define FLAG_PULL_DOWN 14 /* GPIO has pull down enabled */
+#define FLAG_BIAS_DISABLE 15 /* GPIO has pull disabled */
+#define FLAG_EDGE_RISING 16 /* GPIO CDEV detects rising edge events */
+#define FLAG_EDGE_FALLING 17 /* GPIO CDEV detects falling edge events */
+#define FLAG_EVENT_CLOCK_REALTIME 18 /* GPIO CDEV reports REALTIME timestamps in events */
+#define FLAG_EVENT_CLOCK_HTE 19 /* GPIO CDEV reports hardware timestamps in events */
+
+ /* Connection label */
+ const char *label;
+ /* Name of the GPIO */
+ const char *name;
+#ifdef CONFIG_OF_DYNAMIC
+ struct device_node *hog;
+#endif
+#ifdef CONFIG_GPIO_CDEV
+ /* debounce period in microseconds */
+ unsigned int debounce_period_us;
+#endif
+};
+
+#define gpiod_not_found(desc) (IS_ERR(desc) && PTR_ERR(desc) == -ENOENT)
+
+int gpiod_request(struct gpio_desc *desc, const char *label);
+void gpiod_free(struct gpio_desc *desc);
+
+static inline int gpiod_request_user(struct gpio_desc *desc, const char *label)
+{
+ int ret;
+
+ ret = gpiod_request(desc, label);
+ if (ret == -EPROBE_DEFER)
+ ret = -ENODEV;
+
+ return ret;
+}
+
+int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id,
+ unsigned long lflags, enum gpiod_flags dflags);
+int gpio_set_debounce_timeout(struct gpio_desc *desc, unsigned int debounce);
+int gpiod_hog(struct gpio_desc *desc, const char *name,
+ unsigned long lflags, enum gpiod_flags dflags);
+int gpiochip_get_ngpios(struct gpio_chip *gc, struct device *dev);
+
+/*
+ * Return the GPIO number of the passed descriptor relative to its chip
+ */
+static inline int gpio_chip_hwgpio(const struct gpio_desc *desc)
+{
+ return desc - &desc->gdev->descs[0];
+}
+
+/* With descriptor prefix */
+
+#define gpiod_emerg(desc, fmt, ...) \
+ pr_emerg("gpio-%d (%s): " fmt, desc_to_gpio(desc), desc->label ? : "?",\
+ ##__VA_ARGS__)
+#define gpiod_crit(desc, fmt, ...) \
+ pr_crit("gpio-%d (%s): " fmt, desc_to_gpio(desc), desc->label ? : "?", \
+ ##__VA_ARGS__)
+#define gpiod_err(desc, fmt, ...) \
+ pr_err("gpio-%d (%s): " fmt, desc_to_gpio(desc), desc->label ? : "?", \
+ ##__VA_ARGS__)
+#define gpiod_warn(desc, fmt, ...) \
+ pr_warn("gpio-%d (%s): " fmt, desc_to_gpio(desc), desc->label ? : "?", \
+ ##__VA_ARGS__)
+#define gpiod_info(desc, fmt, ...) \
+ pr_info("gpio-%d (%s): " fmt, desc_to_gpio(desc), desc->label ? : "?", \
+ ##__VA_ARGS__)
+#define gpiod_dbg(desc, fmt, ...) \
+ pr_debug("gpio-%d (%s): " fmt, desc_to_gpio(desc), desc->label ? : "?",\
+ ##__VA_ARGS__)
+
+/* With chip prefix */
+
+#define chip_emerg(gc, fmt, ...) \
+ dev_emerg(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__)
+#define chip_crit(gc, fmt, ...) \
+ dev_crit(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__)
+#define chip_err(gc, fmt, ...) \
+ dev_err(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__)
+#define chip_warn(gc, fmt, ...) \
+ dev_warn(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__)
+#define chip_info(gc, fmt, ...) \
+ dev_info(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__)
+#define chip_dbg(gc, fmt, ...) \
+ dev_dbg(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__)
+
+#endif /* GPIOLIB_H */