Merge tag 'gpio-updates-for-v7.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux

Pull gpio updates from Bartosz Golaszewski:
 "For this merge window we have two new drivers: support for
  GPIO-signalled ACPI events on Intel platforms and a generic
  GPIO-over-pinctrl driver using the ARM SCMI protocol for
  controlling pins.

  Several things have been reworked in GPIO core: we unduplicated GPIO
  hog handling, reduced the number of SRCU locks and dereferences,
  improved support for software-node-based lookup and removed more
  legacy code after converting remaining users to modern alternatives.

  There's also a number of driver reworks and refactoring, documentation
  updates, some bug-fixes and new tests.

  GPIO core:
   - defer probe on software node lookups when the remote software node
     exists but has not been registered as a firmware node yet
   - unify GPIO hog handling by moving code duplicated in OF and ACPI
     modules into GPIO core and allow setting up hogs with software
     nodes
   - allow matching GPIO controllers by secondary firmware node if
     matching by primary does not succeed
   - demote deferral warnings to debug level as they are quite normal
     when using software nodes which don't support fw_devlink yet
   - disable the legacy GPIO character device uAPI v1 supprt in Kconfig
     by default
   - rework several core functions in preparation for the upcoming
     Revocable helper library for protecting resources against sudden
     removal, this reduces the number of SRCU dereferences in GPIO core
   - simplify file descriptor logic in GPIO character device code by
     using FD_PREPARE()
   - introduce a header defining symbols used by both GPIO consumers and
     providers to avoid having to include provider-specific headers from
     drivers which only consume GPIOs
   - replace snprintf() with strscpy() where formatting is not required

  New drivers:
   - add the gpio-by-pinctrl generic driver using the ARM SCMI protocol
     to control GPIOs (along with SCMI changes pulled from the pinctrl
     tree)
   - add a driver providing support for handling of platform events via
     GPIO-signalled ACPI events (used on Intel Nova Lake and later
     platforms)

  Driver changes:
   - extend the gpio-kempld driver with support for more recent models,
     interrupts and setting/getting multiple values at once
   - improve interrupt handling in gpio-brcmstb
   - add support for multi-SoC systems in gpio-tegra186
   - make sure we return correct values from the .get() callbacks in
     several GPIO drivers by normalizing any values other than 0, 1 or
     negative error numbers
   - use flexible arrays in several drivers to reduce the number of
     required memory allocations
   - simplify synchronous waiting for virtual drivers to probe and
     remove the dedicated, a bit overengineered helper library
     dev-sync-probe
   - remove unneeded Kconfig dependencies on OF_GPIO in several drivers
     and subsystems
   - convert the two remaining users of of_get_named_gpio() to using
     GPIO descriptors and remove the (no longer used) function along
     with the header that declares it
   - add missing includes in gpio-mmio
   - shrink and simplify code in gpio-max732x by using guard(mutex)
   - remove duplicated code handling the 'ngpios' property from
     gpio-ts4800, it's already handled in GPIO core
   - use correct variable type in gpio-aspeed
   - add support for a new model in gpio-realtek-otto
   - allow to specify the active-low setting of simulated hogs over the
     configfs interface (in addition to existing devicetree support) in
     gpio-sim

  Bug fixes:
   - clear the OF_POPULATED flag on hog nodes in GPIO chip remove path
     on OF systems
   - fix resource leaks in error path in gpiochip_add_data_with_key()
   - drop redundant device reference in gpio-mpsse

  Tests:
   - add selftests for use-after-free cases in GPIO character device
     code

  DT bindings:
   - add a DT binding document for SCMI based, gpio-over-pinctrl devices
   - fix interrupt description in microchip,mpfs-gpio
   - add new compatible for gpio-realtek-otto
   - describe the resets of the mpfs-gpio controller
   - fix maintainer's email in gpio-delay bindings
   - remove the binding document for cavium,thunder-8890 as the
     corresponding device is bound over PCI and not firmware nodes

  Documentation:
   - update the recommended way of converting legacy boards to using
     software nodes for GPIO description
   - describe GPIO line value semantics
   - misc updates to kerneldocs

  Misc:
   - convert OMAP1 ams-delta board to using GPIO hogs described with
     software nodes"

* tag 'gpio-updates-for-v7.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux: (79 commits)
  gpio: swnode: defer probe on references to unregistered software nodes
  dt-bindings: gpio: cavium,thunder-8890: Remove DT binding
  Documentation: gpio: update the preferred method for using software node lookup
  gpio: gpio-by-pinctrl: s/used to do/is used to do/
  gpio: aspeed: fix unsigned long int declaration
  gpio: rockchip: convert to dynamic GPIO base allocation
  gpio: remove dev-sync-probe
  gpio: virtuser: stop using dev-sync-probe
  gpio: aggregator: stop using dev-sync-probe
  gpio: sim: stop using dev-sync-probe
  gpio: Add Intel Nova Lake ACPI GPIO events driver
  gpiolib: Make deferral warnings debug messages
  gpiolib: fix hogs with multiple lines
  gpio: fix up CONFIG_OF dependencies
  gpio: gpio-by-pinctrl: add pinctrl based generic GPIO driver
  gpio: dt-bindings: Add GPIO on top of generic pin control
  firmware: arm_scmi: Allow PINCTRL_REQUEST to return EOPNOTSUPP
  pinctrl: scmi: ignore PIN_CONFIG_PERSIST_STATE
  pinctrl: scmi: Delete PIN_CONFIG_OUTPUT_IMPEDANCE_OHMS support
  pinctrl: scmi: Add SCMI_PIN_INPUT_VALUE
  ...
This commit is contained in:
Linus Torvalds
2026-04-13 20:10:58 -07:00
77 changed files with 2078 additions and 1278 deletions

View File

@@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml#
title: GPIO delay controller
maintainers:
- Alexander Stein <linux@ew.tq-group.com>
- Alexander Stein <alexander.stein@ew.tq-group.com>
description: |
This binding describes an electrical setup where setting an GPIO output

View File

@@ -1,27 +0,0 @@
Cavium ThunderX/OCTEON-TX GPIO controller bindings
Required Properties:
- reg: The controller bus address.
- gpio-controller: Marks the device node as a GPIO controller.
- #gpio-cells: Must be 2.
- First cell is the GPIO pin number relative to the controller.
- Second cell is a standard generic flag bitfield as described in gpio.txt.
Optional Properties:
- compatible: "cavium,thunder-8890-gpio", unused as PCI driver binding is used.
- interrupt-controller: Marks the device node as an interrupt controller.
- #interrupt-cells: Must be present and have value of 2 if
"interrupt-controller" is present.
- First cell is the GPIO pin number relative to the controller.
- Second cell is triggering flags as defined in interrupts.txt.
Example:
gpio_6_0: gpio@6,0 {
compatible = "cavium,thunder-8890-gpio";
reg = <0x3000 0 0 0 0>; /* DEVFN = 0x30 (6:0) */
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};

View File

@@ -33,6 +33,9 @@ properties:
clocks:
maxItems: 1
resets:
maxItems: 1
"#gpio-cells":
const: 2
@@ -62,6 +65,11 @@ allOf:
contains:
const: microchip,mpfs-gpio
then:
properties:
ngpios:
enum: [14, 24, 32]
interrupts:
minItems: 14
required:
- interrupts
- "#interrupt-cells"
@@ -82,18 +90,19 @@ examples:
compatible = "microchip,mpfs-gpio";
reg = <0x20122000 0x1000>;
clocks = <&clkcfg 25>;
interrupt-parent = <&plic>;
interrupt-parent = <&irqmux>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <32>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <53>, <53>, <53>, <53>,
<53>, <53>, <53>, <53>,
<53>, <53>, <53>, <53>,
<53>, <53>, <53>, <53>,
<53>, <53>, <53>, <53>,
<53>, <53>, <53>, <53>,
<53>, <53>, <53>, <53>,
<53>, <53>, <53>, <53>;
interrupts = <64>, <65>, <66>, <67>,
<68>, <69>, <70>, <71>,
<72>, <73>, <74>, <75>,
<76>, <77>, <78>, <79>,
<80>, <81>, <82>, <83>,
<84>, <85>, <86>, <87>,
<88>, <89>, <90>, <91>,
<92>, <93>, <94>, <95>;
};
...

View File

@@ -0,0 +1,59 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/gpio/pin-control-gpio.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Pin control based generic GPIO controller
description:
The pin control-based GPIO will facilitate a pin controller's ability
to drive electric lines high/low and other generic properties of a
pin controller to perform general-purpose one-bit binary I/O.
maintainers:
- Dan Carpenter <dan.carpenter@linaro.org>
properties:
compatible:
const: scmi-pinctrl-gpio
gpio-controller: true
"#gpio-cells":
const: 2
gpio-line-names: true
gpio-ranges: true
ngpios: true
patternProperties:
"^.+-hog(-[0-9]+)?$":
type: object
required:
- gpio-hog
required:
- compatible
- gpio-controller
- "#gpio-cells"
- gpio-ranges
- ngpios
additionalProperties: false
examples:
- |
gpio {
compatible = "scmi-pinctrl-gpio";
gpio-controller;
#gpio-cells = <2>;
ngpios = <4>;
gpio-line-names = "gpio_5_17", "gpio_5_20", "gpio_5_22", "gpio_2_1";
gpio-ranges = <&scmi_pinctrl 0 30 4>;
pinctrl-names = "default";
pinctrl-0 = <&keys_pins>;
};

View File

@@ -30,6 +30,7 @@ properties:
- realtek,rtl8390-gpio
- realtek,rtl9300-gpio
- realtek,rtl9310-gpio
- realtek,rtl9607-gpio
- const: realtek,otto-gpio
reg: true

View File

@@ -108,9 +108,8 @@ macro, which ties a software node representing the GPIO controller with
consumer device. It allows consumers to use regular gpiolib APIs, such as
gpiod_get(), gpiod_get_optional().
The software node representing a GPIO controller need not be attached to the
GPIO controller device. The only requirement is that the node must be
registered and its name must match the GPIO controller's label.
The software node representing a GPIO controller must be attached to the
GPIO controller device - either as the primary or the secondary firmware node.
For example, here is how to describe a single GPIO-connected LED. This is an
alternative to using platform_data on legacy systems.
@@ -122,8 +121,7 @@ alternative to using platform_data on legacy systems.
#include <linux/gpio/property.h>
/*
* 1. Define a node for the GPIO controller. Its .name must match the
* controller's label.
* 1. Define a node for the GPIO controller.
*/
static const struct software_node gpio_controller_node = {
.name = "gpio-foo",
@@ -153,6 +151,21 @@ alternative to using platform_data on legacy systems.
};
software_node_register_node_group(swnodes);
/*
* 5. Attach the GPIO controller's software node to the device and
* register it.
*/
static void gpio_foo_register(void)
{
struct platform_device_info pdev_info = {
.name = "gpio-foo",
.id = PLATFORM_DEVID_NONE,
.swnode = &gpio_controller_node
};
platform_device_register_full(&pdev_info);
}
// Then register a platform_device for "leds-gpio" and associate
// it with &led_device_swnode via .fwnode.
@@ -239,22 +252,6 @@ mapping and is thus transparent to GPIO consumers.
A set of functions such as gpiod_set_value() is available to work with
the new descriptor-oriented interface.
Boards using platform data can also hog GPIO lines by defining GPIO hog tables.
.. code-block:: c
struct gpiod_hog gpio_hog_table[] = {
GPIO_HOG("gpio.0", 10, "foo", GPIO_ACTIVE_LOW, GPIOD_OUT_HIGH),
{ }
};
And the table can be added to the board code as follows::
gpiod_add_hogs(gpio_hog_table);
The line will be hogged as soon as the gpiochip is created or - in case the
chip was created earlier - when the hog table is registered.
Arrays of pins
--------------
In addition to requesting pins belonging to a function one by one, a device may

View File

@@ -87,6 +87,33 @@ atomic context on realtime kernels (inside hard IRQ handlers and similar
contexts). Normally this should not be required.
GPIO level semantics
--------------------
The gpip_chip .get/set[_multiple]() line values are clamped to the boolean
space [0, 1], low level or high level.
Low and high values are defined as physical low on the line in/out to the
connector such as a physical pad, pin or rail.
The GPIO library has internal logic to handle lines that are active low, such
as indicated by overstrike or #name in a schematic, and the driver should not
try to second-guess the logic value of a line.
The way GPIO values are handled by the consumers is that the library present
the *logical* value to the consumer. A line is *asserted* if its *logical*
value is 1, and *de-asserted* if its logical value is 0. If inversion is
required, this is handled by gpiolib and configured using hardware descriptions
such as device tree or ACPI that can clearly indicate if a line is active
high or low.
Since electronics commonly insert inverters as driving stages or protection
buffers in front of a GPIO line it is necessary that this semantic is part
of the hardware description, so that consumers such as kernel drivers need
not worry about this, and can for example assert a RESET line tied to a GPIO
pin by setting it to logic 1 even if it is physically active low.
GPIO electrical configuration
-----------------------------

View File

@@ -36,12 +36,10 @@ Requirements for GPIO Properties
When using software nodes to describe GPIO connections, the following
requirements must be met for the GPIO core to correctly resolve the reference:
1. **The GPIO controller's software node "name" must match the controller's
"label".** The gpiolib core uses this name to find the corresponding
struct gpio_chip at runtime.
This software node has to be registered, but need not be attached to the
device representing the GPIO controller that is providing the GPIO in
question. It may be left as a "free floating" node.
1. **The GPIO controller's software node must be registered and attached to
the controller's ``struct device`` either as its primary or secondary
firmware node.** The gpiolib core uses the address of the firmware node to
find the corresponding ``struct gpio_chip`` at runtime.
2. **The GPIO property must be a reference.** The ``PROPERTY_ENTRY_GPIO()``
macro handles this as it is an alias for ``PROPERTY_ENTRY_REF()``.
@@ -121,13 +119,21 @@ A typical legacy board file might look like this:
/* Device registration */
static int __init myboard_init(void)
{
struct platform_device_info pdev_info = {
.name = MYBOARD_GPIO_CONTROLLER,
.id = PLATFORM_DEVID_NONE,
.swnode = &gpio_controller_node
};
gpiod_add_lookup_table(&myboard_leds_gpios);
gpiod_add_lookup_table(&myboard_buttons_gpios);
platform_device_register_full(&pdev_info);
platform_device_register_data(NULL, "leds-gpio", -1,
&myboard_leds_pdata, sizeof(myboard_leds_pdata));
platform_device_register_data(NULL, "gpio-keys", -1,
&myboard_buttons_pdata, sizeof(myboard_buttons_pdata));
&myboard_buttons_pdata,
sizeof(myboard_buttons_pdata));
return 0;
}
@@ -141,8 +147,7 @@ Step 1: Define the GPIO Controller Node
***************************************
First, define a software node that represents the GPIO controller that the
LEDs and buttons are connected to. The ``name`` of this node must match the
name of the driver for the GPIO controller (e.g., "gpio-foo").
LEDs and buttons are connected to. The ``name`` of this node is optional.
.. code-block:: c
@@ -257,6 +262,16 @@ software nodes using the ``fwnode`` field in struct platform_device_info.
if (error)
return error;
memset(&pdev_info, 0, sizeof(pdev_info));
pdev_info.name = MYBOARD_GPIO_CONTROLLER;
pdev_info.id = PLATFORM_DEVID_NONE;
pdev_info.swnode = &myboard_gpio_controller_node;
gpio_pdev = platform_device_register_full(&pdev_info);
if (IS_ERR(gpio_pdev)) {
error = PTR_ERR(gpio_pdev);
goto err_unregister_nodes;
}
memset(&pdev_info, 0, sizeof(pdev_info));
pdev_info.name = "leds-gpio";
pdev_info.id = PLATFORM_DEVID_NONE;
@@ -264,6 +279,7 @@ software nodes using the ``fwnode`` field in struct platform_device_info.
leds_pdev = platform_device_register_full(&pdev_info);
if (IS_ERR(leds_pdev)) {
error = PTR_ERR(leds_pdev);
platform_device_unregister(gpio_pdev);
goto err_unregister_nodes;
}
@@ -274,6 +290,7 @@ software nodes using the ``fwnode`` field in struct platform_device_info.
keys_pdev = platform_device_register_full(&pdev_info);
if (IS_ERR(keys_pdev)) {
error = PTR_ERR(keys_pdev);
platform_device_unregister(gpio_pdev);
platform_device_unregister(leds_pdev);
goto err_unregister_nodes;
}
@@ -289,6 +306,7 @@ software nodes using the ``fwnode`` field in struct platform_device_info.
{
platform_device_unregister(keys_pdev);
platform_device_unregister(leds_pdev);
platform_device_unregister(gpio_pdev);
software_node_unregister_node_group(myboard_swnodes);
}

View File

@@ -10982,7 +10982,6 @@ F: drivers/gpio/
F: include/dt-bindings/gpio/
F: include/linux/gpio.h
F: include/linux/gpio/
F: include/linux/of_gpio.h
K: (devm_)?gpio_(request|free|direction|get|set)
K: GPIOD_FLAGS_BIT_NONEXCLUSIVE
K: devm_gpiod_unhinge
@@ -12872,6 +12871,13 @@ F: drivers/gpio/gpio-sodaville.c
F: drivers/gpio/gpio-tangier.c
F: drivers/gpio/gpio-tangier.h
INTEL GPIO GPE DRIVER
M: Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>
R: Mika Westerberg <westeri@kernel.org>
L: linux-gpio@vger.kernel.org
S: Supported
F: drivers/gpio/gpio-novalake-events.c
INTEL GVT-g DRIVERS (Intel GPU Virtualization)
R: Zhenyu Wang <zhenyuw.linux@gmail.com>
R: Zhi Wang <zhi.wang.linux@gmail.com>

View File

@@ -7,7 +7,6 @@ menuconfig ARC_PLAT_AXS10X
bool "Synopsys ARC AXS10x Software Development Platforms"
select DW_APB_ICTL
select GPIO_DWAPB
select OF_GPIO
select HAVE_PCI
select GENERIC_IRQ_CHIP
select GPIOLIB

View File

@@ -556,10 +556,30 @@ static struct gpiod_lookup_table *ams_delta_gpio_tables[] __initdata = {
&ams_delta_nand_gpio_table,
};
static struct gpiod_hog ams_delta_gpio_hogs[] = {
GPIO_HOG(LATCH2_LABEL, LATCH2_PIN_KEYBRD_DATAOUT, "keybrd_dataout",
GPIO_ACTIVE_HIGH, GPIOD_OUT_LOW),
{},
static const struct software_node latch2_gpio_swnode = {
.name = LATCH2_LABEL,
};
static const u32 latch2_hog_gpios[] = { LATCH2_PIN_KEYBRD_DATAOUT, 0 };
static const struct property_entry latch2_gpio_hog_props[] = {
PROPERTY_ENTRY_BOOL("gpio-hog"),
PROPERTY_ENTRY_U32_ARRAY("gpios", latch2_hog_gpios),
PROPERTY_ENTRY_STRING("line-name", "keybrd_dataout"),
PROPERTY_ENTRY_BOOL("output-low"),
{ }
};
static const struct software_node latch2_gpio_hog_swnode = {
.parent = &latch2_gpio_swnode,
.name = "latch2-hog",
.properties = latch2_gpio_hog_props,
};
static const struct software_node *const latch2_gpio_swnodes[] = {
&latch2_gpio_swnode,
&latch2_gpio_hog_swnode,
NULL
};
static struct plat_serial8250_port ams_delta_modem_ports[];
@@ -684,7 +704,6 @@ static void __init ams_delta_init(void)
omap_gpio_deps_init();
ams_delta_latch2_init();
gpiod_add_hogs(ams_delta_gpio_hogs);
omap_serial_init();
omap_register_i2c_bus(1, 100, NULL, 0);
@@ -693,6 +712,9 @@ static void __init ams_delta_init(void)
platform_add_devices(ams_delta_devices, ARRAY_SIZE(ams_delta_devices));
platform_device_register_full(&latch1_gpio_devinfo);
software_node_register_node_group(latch2_gpio_swnodes);
latch2_gpio_devinfo.fwnode = software_node_fwnode(&latch2_gpio_swnode);
platform_device_register_full(&latch2_gpio_devinfo);
/*

View File

@@ -217,7 +217,6 @@ config GE_IMP3A
config SGY_CTS1000
tristate "Servergy CTS-1000 support"
select GPIOLIB
select OF_GPIO
depends on CORENET_GENERIC
help
Enable this to support functionality in Servergy's CTS-1000 systems.

View File

@@ -578,6 +578,8 @@ static int scmi_pinctrl_request_free(const struct scmi_protocol_handle *ph,
tx->flags = cpu_to_le32(type);
ret = ph->xops->do_xfer(ph, t);
if (ret == -EOPNOTSUPP)
ret = 0;
ph->xops->xfer_put(ph, t);
return ret;

View File

@@ -95,7 +95,6 @@ config GPIO_CDEV
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.
@@ -103,8 +102,6 @@ config GPIO_CDEV_V1
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
@@ -145,7 +142,7 @@ menu "Memory mapped GPIO drivers"
config GPIO_74XX_MMIO
tristate "GPIO driver for 74xx-ICs with MMIO access"
depends on OF_GPIO
depends on OF
select GPIO_GENERIC
help
Say yes here to support GPIO functionality for 74xx-compatible ICs
@@ -175,14 +172,14 @@ config GPIO_AMDPT
config GPIO_ASPEED
tristate "Aspeed GPIO support"
depends on (ARCH_ASPEED || COMPILE_TEST) && OF_GPIO
depends on ARCH_ASPEED || COMPILE_TEST
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
depends on ARCH_ASPEED || COMPILE_TEST
select GPIO_GENERIC
select GPIOLIB_IRQCHIP
help
@@ -201,7 +198,6 @@ config GPIO_ATH79
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)
@@ -218,7 +214,7 @@ config GPIO_BCM_KONA
config GPIO_BCM_XGS_IPROC
tristate "BRCM XGS iProc GPIO support"
depends on OF_GPIO && (ARCH_BCM_IPROC || COMPILE_TEST)
depends on ARCH_BCM_IPROC || COMPILE_TEST
select GPIO_GENERIC
select GPIOLIB_IRQCHIP
default ARCH_BCM_IPROC
@@ -229,7 +225,6 @@ config GPIO_BLZP1600
tristate "Blaize BLZP1600 GPIO support"
default y if ARCH_BLAIZE
depends on ARCH_BLAIZE || COMPILE_TEST
depends on OF_GPIO
select GPIO_GENERIC
select GPIOLIB_IRQCHIP
help
@@ -240,15 +235,29 @@ config GPIO_BLZP1600
config GPIO_BRCMSTB
tristate "BRCMSTB GPIO support"
default y if (ARCH_BRCMSTB || BMIPS_GENERIC)
depends on OF_GPIO && (ARCH_BRCMSTB || ARCH_BCM2835 || BMIPS_GENERIC || COMPILE_TEST)
depends on ARCH_BRCMSTB || ARCH_BCM2835 || BMIPS_GENERIC || COMPILE_TEST
depends on OF
select GPIO_GENERIC
select IRQ_DOMAIN
help
Say yes here to enable GPIO support for Broadcom STB (BCM7XXX) SoCs.
config GPIO_BY_PINCTRL
tristate "GPIO support based on a pure pin control backend"
depends on GPIOLIB
help
Support for generic GPIO handling based on top of pin control.
Traditionally, firmware creates a GPIO interface or a pin
controller interface and we have a driver to support it. But
in SCMI, the pin control interface is generic and we can
create a simple GPIO device based on the pin control interface
without doing anything custom.
This driver is used to access GPIOs over the ARM SCMI protocol.
config GPIO_CADENCE
tristate "Cadence GPIO support"
depends on OF_GPIO
depends on OF
select GPIO_GENERIC
select GPIOLIB_IRQCHIP
help
@@ -280,14 +289,13 @@ config GPIO_DWAPB
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
depends on ARCH_EMEV2 || COMPILE_TEST
help
Say yes here to support GPIO on Renesas Emma Mobile SoCs.
@@ -329,7 +337,7 @@ config GPIO_GE_FPGA
config GPIO_FTGPIO010
bool "Faraday FTGPIO010 GPIO"
depends on OF_GPIO
depends on OF
select GPIO_GENERIC
select GPIOLIB_IRQCHIP
default (ARCH_GEMINI || ARCH_MOXART)
@@ -383,7 +391,7 @@ config GPIO_HISI
config GPIO_HLWD
tristate "Nintendo Wii (Hollywood) GPIO"
depends on OF_GPIO
depends on OF
select GPIO_GENERIC
select GPIOLIB_IRQCHIP
help
@@ -434,7 +442,6 @@ config GPIO_LOONGSON
config GPIO_LOONGSON_64BIT
tristate "Loongson 64 bit GPIO support"
depends on LOONGARCH || COMPILE_TEST
depends on OF_GPIO
select GPIO_GENERIC
select GPIOLIB_IRQCHIP
help
@@ -447,7 +454,7 @@ config GPIO_LOONGSON_64BIT
config GPIO_LPC18XX
tristate "NXP LPC18XX/43XX GPIO support"
default y if ARCH_LPC18XX
depends on OF_GPIO && (ARCH_LPC18XX || COMPILE_TEST)
depends on ARCH_LPC18XX || COMPILE_TEST
select IRQ_DOMAIN_HIERARCHY
select GPIOLIB_IRQCHIP
help
@@ -456,7 +463,8 @@ config GPIO_LPC18XX
config GPIO_LPC32XX
tristate "NXP LPC32XX GPIO support"
depends on OF_GPIO && (ARCH_LPC32XX || COMPILE_TEST)
depends on ARCH_LPC32XX || COMPILE_TEST
depends on OF
help
Select this option to enable GPIO driver for
NXP LPC32XX devices.
@@ -499,7 +507,7 @@ config GPIO_MPC8XXX
config GPIO_MT7621
bool "Mediatek MT7621 GPIO Support"
depends on SOC_MT7620 || SOC_MT7621 || COMPILE_TEST
depends on OF_GPIO
depends on OF
select GPIO_GENERIC
select GPIOLIB_IRQCHIP
help
@@ -592,7 +600,6 @@ config GPIO_RCAR
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
@@ -641,8 +648,8 @@ config GPIO_RTD
config GPIO_SAMA5D2_PIOBU
tristate "SAMA5D2 PIOBU GPIO support"
depends on OF
depends on MFD_SYSCON
depends on OF_GPIO
depends on ARCH_AT91 || COMPILE_TEST
select GPIO_SYSCON
help
@@ -654,7 +661,7 @@ config GPIO_SAMA5D2_PIOBU
config GPIO_SIFIVE
tristate "SiFive GPIO support"
depends on OF_GPIO
depends on OF
select IRQ_DOMAIN_HIERARCHY
select GPIO_GENERIC
select GPIOLIB_IRQCHIP
@@ -673,7 +680,6 @@ config GPIO_SIOX
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
@@ -683,7 +689,7 @@ config GPIO_SNPS_CREG
config GPIO_SPACEMIT_K1
tristate "SPACEMIT K1 GPIO support"
depends on ARCH_SPACEMIT || COMPILE_TEST
depends on OF_GPIO
depends on OF
select GPIO_GENERIC
select GPIOLIB_IRQCHIP
help
@@ -699,7 +705,6 @@ config GPIO_SPEAR_SPICS
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.
@@ -707,7 +712,6 @@ config GPIO_SPRD
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,
@@ -742,7 +746,6 @@ 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
@@ -752,7 +755,7 @@ config GPIO_TEGRA186
tristate "NVIDIA Tegra186 GPIO support"
default ARCH_TEGRA_186_SOC || ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC
depends on ARCH_TEGRA_186_SOC || ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC || COMPILE_TEST
depends on OF_GPIO
depends on OF
select GPIOLIB_IRQCHIP
select IRQ_DOMAIN_HIERARCHY
help
@@ -760,7 +763,6 @@ config GPIO_TEGRA186
config GPIO_TS4800
tristate "TS-4800 DIO blocks and compatibles"
depends on OF_GPIO
depends on SOC_IMX51 || COMPILE_TEST
select GPIO_GENERIC
help
@@ -780,7 +782,6 @@ config GPIO_THUNDERX
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.
@@ -797,7 +798,6 @@ config GPIO_VF610
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
@@ -806,14 +806,14 @@ config GPIO_VISCONTI
config GPIO_WCD934X
tristate "Qualcomm Technologies Inc WCD9340/WCD9341 GPIO controller driver"
depends on MFD_WCD934X && OF_GPIO
depends on MFD_WCD934X
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
depends on ARM64
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
@@ -1057,6 +1057,32 @@ config GPIO_SCH
The Intel Quark X1000 SoC has 2 GPIOs powered by the core
power well and 6 from the suspend power well.
config GPIO_NOVALAKE
tristate "Intel Nova Lake GPIO-signaled ACPI events support"
depends on (X86 || COMPILE_TEST) && ACPI
select GPIOLIB_IRQCHIP
help
Select this to enable GPIO-signaled ACPI events support on platforms
with the following SoCs:
- Intel Nova Lake
This driver adds support for new mode of handling platform events,
through the use of GPIO-signaled ACPI events. Main purpose is to
handle platform IRQs that originate in PCH components, for example
interrupt triggered by Power Management Event (PME).
This driver, at this time, is not required to handle platform events.
Listed platform(s) will stay in legacy mode, handling ACPI events as
in previous generations. However, future platforms will eventually
switch to new handling mode, requiring this driver to run events
properly.
Driver supports up to 128 GPIO pins per GPE block.
To compile this driver as a module, choose M here: the module will
be called gpio-novalake-events.
config GPIO_SCH311X
tristate "SMSC SCH311x SuperI/O GPIO"
help
@@ -1111,7 +1137,7 @@ menu "I2C GPIO expanders"
config GPIO_ADNP
tristate "Avionic Design N-bit GPIO expander"
depends on OF_GPIO
depends on OF
select GPIOLIB_IRQCHIP
help
This option enables support for N GPIOs found on Avionic Design
@@ -1144,7 +1170,7 @@ config GPIO_DS4520
config GPIO_GW_PLD
tristate "Gateworks PLD GPIO Expander"
depends on OF_GPIO
depends on OF
help
Say yes here to provide access to the Gateworks I2C PLD GPIO
Expander. This is used at least on the Cambria GW2358-4.
@@ -1440,6 +1466,7 @@ config GPIO_JANZ_TTL
config GPIO_KEMPLD
tristate "Kontron ETX / COMexpress GPIO"
depends on MFD_KEMPLD
select GPIOLIB_IRQCHIP
help
This enables support for the PLD GPIO interface on some Kontron ETX
and COMexpress (ETXexpress) modules.
@@ -1567,7 +1594,6 @@ config GPIO_PALMAS
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.
@@ -1606,7 +1632,6 @@ config GPIO_SL28CPLD
config GPIO_STMPE
tristate "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
@@ -1615,7 +1640,6 @@ config GPIO_STMPE
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
@@ -1986,7 +2010,6 @@ menu "Virtual GPIO drivers"
config GPIO_AGGREGATOR
tristate "GPIO Aggregator"
select CONFIGFS_FS
select DEV_SYNC_PROBE
help
Say yes here to enable the GPIO Aggregator, which provides a way to
aggregate existing GPIO lines into a new virtual GPIO chip.
@@ -2005,7 +2028,7 @@ config GPIO_LATCH
config GPIO_LINE_MUX
tristate "GPIO line mux driver"
depends on OF_GPIO
depends on OF
select MULTIPLEXER
help
Say Y here to support the GPIO line mux, which can provide virtual
@@ -2038,7 +2061,6 @@ config GPIO_SIM
tristate "GPIO Simulator Module"
select IRQ_SIM
select CONFIGFS_FS
select DEV_SYNC_PROBE
help
This enables the GPIO simulator - a configfs-based GPIO testing
driver.
@@ -2076,7 +2098,6 @@ config GPIO_VIRTUSER
select DEBUG_FS
select CONFIGFS_FS
select IRQ_WORK
select DEV_SYNC_PROBE
help
Say Y here to enable the configurable, configfs-based virtual GPIO
consumer testing driver.
@@ -2087,6 +2108,3 @@ config GPIO_VIRTUSER
endmenu
endif
config DEV_SYNC_PROBE
tristate

View File

@@ -21,9 +21,6 @@ obj-$(CONFIG_GPIO_GENERIC) += gpio-generic.o
# directly supported by gpio-generic
gpio-generic-$(CONFIG_GPIO_GENERIC) += gpio-mmio.o
# Utilities for drivers that need synchronous fake device creation
obj-$(CONFIG_DEV_SYNC_PROBE) += dev-sync-probe.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
@@ -51,6 +48,7 @@ obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o
obj-$(CONFIG_GPIO_BLZP1600) += gpio-blzp1600.o
obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o
obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o
obj-$(CONFIG_GPIO_BY_PINCTRL) += gpio-by-pinctrl.o
obj-$(CONFIG_GPIO_CADENCE) += gpio-cadence.o
obj-$(CONFIG_GPIO_CGBC) += gpio-cgbc.o
obj-$(CONFIG_GPIO_CLPS711X) += gpio-clps711x.o
@@ -134,6 +132,7 @@ obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o
obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o
obj-$(CONFIG_GPIO_NCT6694) += gpio-nct6694.o
obj-$(CONFIG_GPIO_NOMADIK) += gpio-nomadik.o
obj-$(CONFIG_GPIO_NOVALAKE) += gpio-novalake-events.o
obj-$(CONFIG_GPIO_NPCM_SGPIO) += gpio-npcm-sgpio.o
obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o
obj-$(CONFIG_GPIO_OMAP) += gpio-omap.o

View File

@@ -58,34 +58,6 @@ Work items:
-------------------------------------------------------------------------------
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 involves
changing board files, 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.
-------------------------------------------------------------------------------
Collect drivers
Collect GPIO drivers from arch/* and other places that should be placed

View File

@@ -1,97 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Common code for drivers creating fake platform devices.
*
* Provides synchronous device creation: waits for probe completion and
* returns the probe success or error status to the device creator.
*
* Copyright (C) 2021 Bartosz Golaszewski <brgl@bgdev.pl>
* Copyright (C) 2025 Koichiro Den <koichiro.den@canonical.com>
*/
#include <linux/device.h>
#include <linux/slab.h>
#include "dev-sync-probe.h"
static int dev_sync_probe_notifier_call(struct notifier_block *nb,
unsigned long action, void *data)
{
struct dev_sync_probe_data *pdata;
struct device *dev = data;
pdata = container_of(nb, struct dev_sync_probe_data, bus_notifier);
if (!device_match_name(dev, pdata->name))
return NOTIFY_DONE;
switch (action) {
case BUS_NOTIFY_BOUND_DRIVER:
pdata->driver_bound = true;
break;
case BUS_NOTIFY_DRIVER_NOT_BOUND:
pdata->driver_bound = false;
break;
default:
return NOTIFY_DONE;
}
complete(&pdata->probe_completion);
return NOTIFY_OK;
}
void dev_sync_probe_init(struct dev_sync_probe_data *data)
{
memset(data, 0, sizeof(*data));
init_completion(&data->probe_completion);
data->bus_notifier.notifier_call = dev_sync_probe_notifier_call;
}
EXPORT_SYMBOL_GPL(dev_sync_probe_init);
int dev_sync_probe_register(struct dev_sync_probe_data *data,
struct platform_device_info *pdevinfo)
{
struct platform_device *pdev;
char *name;
name = kasprintf(GFP_KERNEL, "%s.%d", pdevinfo->name, pdevinfo->id);
if (!name)
return -ENOMEM;
data->driver_bound = false;
data->name = name;
reinit_completion(&data->probe_completion);
bus_register_notifier(&platform_bus_type, &data->bus_notifier);
pdev = platform_device_register_full(pdevinfo);
if (IS_ERR(pdev)) {
bus_unregister_notifier(&platform_bus_type, &data->bus_notifier);
kfree(data->name);
return PTR_ERR(pdev);
}
wait_for_completion(&data->probe_completion);
bus_unregister_notifier(&platform_bus_type, &data->bus_notifier);
if (!data->driver_bound) {
platform_device_unregister(pdev);
kfree(data->name);
return -ENXIO;
}
data->pdev = pdev;
return 0;
}
EXPORT_SYMBOL_GPL(dev_sync_probe_register);
void dev_sync_probe_unregister(struct dev_sync_probe_data *data)
{
platform_device_unregister(data->pdev);
kfree(data->name);
data->pdev = NULL;
}
EXPORT_SYMBOL_GPL(dev_sync_probe_unregister);
MODULE_AUTHOR("Bartosz Golaszewski <brgl@bgdev.pl>");
MODULE_AUTHOR("Koichiro Den <koichiro.den@canonical.com>");
MODULE_DESCRIPTION("Utilities for synchronous fake device creation");
MODULE_LICENSE("GPL");

View File

@@ -1,25 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef DEV_SYNC_PROBE_H
#define DEV_SYNC_PROBE_H
#include <linux/completion.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
struct dev_sync_probe_data {
struct platform_device *pdev;
const char *name;
/* Synchronize with probe */
struct notifier_block bus_notifier;
struct completion probe_completion;
bool driver_bound;
};
void dev_sync_probe_init(struct dev_sync_probe_data *data);
int dev_sync_probe_register(struct dev_sync_probe_data *data,
struct platform_device_info *pdevinfo);
void dev_sync_probe_unregister(struct dev_sync_probe_data *data);
#endif /* DEV_SYNC_PROBE_H */

View File

@@ -32,8 +32,6 @@
#include <linux/gpio/forwarder.h>
#include <linux/gpio/machine.h>
#include "dev-sync-probe.h"
#define AGGREGATOR_MAX_GPIOS 512
#define AGGREGATOR_LEGACY_PREFIX "_sysfs"
@@ -42,7 +40,7 @@
*/
struct gpio_aggregator {
struct dev_sync_probe_data probe_data;
struct platform_device *pdev;
struct config_group group;
struct gpiod_lookup_table *lookups;
struct mutex lock;
@@ -135,7 +133,7 @@ static bool gpio_aggregator_is_active(struct gpio_aggregator *aggr)
{
lockdep_assert_held(&aggr->lock);
return aggr->probe_data.pdev && platform_get_drvdata(aggr->probe_data.pdev);
return aggr->pdev && platform_get_drvdata(aggr->pdev);
}
/* Only aggregators created via legacy sysfs can be "activating". */
@@ -143,7 +141,7 @@ static bool gpio_aggregator_is_activating(struct gpio_aggregator *aggr)
{
lockdep_assert_held(&aggr->lock);
return aggr->probe_data.pdev && !platform_get_drvdata(aggr->probe_data.pdev);
return aggr->pdev && !platform_get_drvdata(aggr->pdev);
}
static size_t gpio_aggregator_count_lines(struct gpio_aggregator *aggr)
@@ -909,6 +907,7 @@ static int gpio_aggregator_activate(struct gpio_aggregator *aggr)
{
struct platform_device_info pdevinfo;
struct gpio_aggregator_line *line;
struct platform_device *pdev;
struct fwnode_handle *swnode;
unsigned int n = 0;
int ret = 0;
@@ -962,12 +961,23 @@ static int gpio_aggregator_activate(struct gpio_aggregator *aggr)
gpiod_add_lookup_table(aggr->lookups);
ret = dev_sync_probe_register(&aggr->probe_data, &pdevinfo);
if (ret)
pdev = platform_device_register_full(&pdevinfo);
if (IS_ERR(pdev)) {
ret = PTR_ERR(pdev);
goto err_remove_lookup_table;
}
wait_for_device_probe();
if (!device_is_bound(&pdev->dev)) {
ret = -ENXIO;
goto err_unregister_pdev;
}
aggr->pdev = pdev;
return 0;
err_unregister_pdev:
platform_device_unregister(pdev);
err_remove_lookup_table:
kfree(aggr->lookups->dev_id);
gpiod_remove_lookup_table(aggr->lookups);
@@ -981,7 +991,8 @@ err_remove_lookups:
static void gpio_aggregator_deactivate(struct gpio_aggregator *aggr)
{
dev_sync_probe_unregister(&aggr->probe_data);
platform_device_unregister(aggr->pdev);
aggr->pdev = NULL;
gpiod_remove_lookup_table(aggr->lookups);
kfree(aggr->lookups->dev_id);
kfree(aggr->lookups);
@@ -1145,7 +1156,7 @@ gpio_aggregator_device_dev_name_show(struct config_item *item, char *page)
guard(mutex)(&aggr->lock);
pdev = aggr->probe_data.pdev;
pdev = aggr->pdev;
if (pdev)
return sysfs_emit(page, "%s\n", dev_name(&pdev->dev));
@@ -1322,7 +1333,6 @@ gpio_aggregator_make_group(struct config_group *group, const char *name)
return ERR_PTR(ret);
config_group_init_type_name(&aggr->group, name, &gpio_aggregator_device_type);
dev_sync_probe_init(&aggr->probe_data);
return &aggr->group;
}
@@ -1471,12 +1481,6 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver,
scnprintf(name, sizeof(name), "%s.%d", AGGREGATOR_LEGACY_PREFIX, aggr->id);
config_group_init_type_name(&aggr->group, name, &gpio_aggregator_device_type);
/*
* Since the device created by sysfs might be toggled via configfs
* 'live' attribute later, this initialization is needed.
*/
dev_sync_probe_init(&aggr->probe_data);
/* Expose to configfs */
res = configfs_register_group(&gpio_aggregator_subsys.su_group,
&aggr->group);
@@ -1495,7 +1499,7 @@ static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver,
goto remove_table;
}
aggr->probe_data.pdev = pdev;
aggr->pdev = pdev;
module_put(THIS_MODULE);
return count;

View File

@@ -655,7 +655,7 @@ static void aspeed_init_irq_valid_mask(struct gpio_chip *gc,
while (!is_bank_props_sentinel(props)) {
unsigned int offset;
const unsigned long int input = props->input;
const unsigned long input = props->input;
/* Pretty crummy approach, but similar to GPIO core */
for_each_clear_bit(offset, &input, 32) {

View File

@@ -58,15 +58,6 @@
#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;
@@ -90,6 +81,15 @@ struct bcm_kona_gpio_bank {
struct bcm_kona_gpio *kona_gpio;
};
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[] __counted_by(num_bank);
};
static inline void bcm_kona_gpio_write_lock_regs(void __iomem *reg_base,
int bank_id, u32 lockcode)
{
@@ -584,12 +584,6 @@ static int bcm_kona_gpio_probe(struct platform_device *pdev)
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");
@@ -597,6 +591,11 @@ static int bcm_kona_gpio_probe(struct platform_device *pdev)
} else if (ret < 0) {
return dev_err_probe(dev, ret, "Couldn't determine GPIO banks\n");
}
kona_gpio = devm_kzalloc(dev, struct_size(kona_gpio, banks, ret), GFP_KERNEL);
if (!kona_gpio)
return -ENOMEM;
kona_gpio->num_bank = ret;
if (kona_gpio->num_bank > GPIO_MAX_BANK_NUM) {
@@ -604,13 +603,9 @@ static int bcm_kona_gpio_probe(struct platform_device *pdev)
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;
kona_gpio->gpio_chip = template_chip;
chip = &kona_gpio->gpio_chip;
chip->parent = dev;
chip->ngpio = kona_gpio->num_bank * GPIO_PER_BANK;

View File

@@ -69,7 +69,7 @@ static int bd9571mwv_gpio_get(struct gpio_chip *chip, unsigned int offset)
if (ret < 0)
return ret;
return val & BIT(offset);
return !!(val & BIT(offset));
}
static int bd9571mwv_gpio_set(struct gpio_chip *chip, unsigned int offset,

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (C) 2015-2017 Broadcom
// Copyright (C) 2015-2017, 2026 Broadcom
#include <linux/bitops.h>
#include <linux/gpio/driver.h>
@@ -54,6 +54,7 @@ struct brcmstb_gpio_priv {
int parent_irq;
int num_gpios;
int parent_wake_irq;
bool suspended;
};
#define MAX_GPIO_PER_BANK 32
@@ -95,15 +96,13 @@ static int brcmstb_gpio_hwirq_to_offset(irq_hw_number_t hwirq,
return hwirq - bank->chip.gc.offset;
}
static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank,
unsigned int hwirq, bool enable)
static void __brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank,
irq_hw_number_t hwirq, bool enable)
{
struct brcmstb_gpio_priv *priv = bank->parent_priv;
u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(hwirq, bank));
u32 imask;
guard(gpio_generic_lock_irqsave)(&bank->chip);
imask = gpio_generic_read_reg(&bank->chip,
priv->reg_base + GIO_MASK(bank->id));
if (enable)
@@ -114,6 +113,13 @@ static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank,
priv->reg_base + GIO_MASK(bank->id), imask);
}
static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank,
irq_hw_number_t hwirq, bool enable)
{
guard(gpio_generic_lock_irqsave)(&bank->chip);
__brcmstb_gpio_set_imask(bank, hwirq, enable);
}
static int brcmstb_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
{
struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc);
@@ -132,7 +138,21 @@ 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);
brcmstb_gpio_set_imask(bank, irqd_to_hwirq(d), false);
}
static void brcmstb_gpio_irq_mask_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;
irq_hw_number_t hwirq = irqd_to_hwirq(d);
u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(hwirq, bank));
guard(gpio_generic_lock_irqsave)(&bank->chip);
__brcmstb_gpio_set_imask(bank, hwirq, false);
gpio_generic_write_reg(&bank->chip,
priv->reg_base + GIO_STAT(bank->id), mask);
}
static void brcmstb_gpio_irq_unmask(struct irq_data *d)
@@ -140,7 +160,7 @@ 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);
brcmstb_gpio_set_imask(bank, irqd_to_hwirq(d), true);
}
static void brcmstb_gpio_irq_ack(struct irq_data *d)
@@ -148,7 +168,7 @@ 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));
u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(irqd_to_hwirq(d), bank));
gpio_generic_write_reg(&bank->chip,
priv->reg_base + GIO_STAT(bank->id), mask);
@@ -159,7 +179,7 @@ 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 mask = BIT(brcmstb_gpio_hwirq_to_offset(irqd_to_hwirq(d), bank));
u32 edge_insensitive, iedge_insensitive;
u32 edge_config, iedge_config;
u32 level, ilevel;
@@ -221,6 +241,9 @@ static int brcmstb_gpio_priv_set_wake(struct brcmstb_gpio_priv *priv,
{
int ret = 0;
if (priv->parent_wake_irq == priv->parent_irq)
return ret;
if (enable)
ret = enable_irq_wake(priv->parent_wake_irq);
else
@@ -236,7 +259,7 @@ 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));
u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(irqd_to_hwirq(d), bank));
/*
* Do not do anything specific for now, suspend/resume callbacks will
@@ -271,6 +294,11 @@ static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank)
while ((status = brcmstb_gpio_get_active_irqs(bank))) {
unsigned int offset;
if (priv->suspended && bank->wake_active & status) {
priv->suspended = false;
pm_wakeup_event(&priv->pdev->dev, 0);
}
for_each_set_bit(offset, &status, 32) {
if (offset >= bank->width)
dev_warn(&priv->pdev->dev,
@@ -444,18 +472,18 @@ static int brcmstb_gpio_irq_setup(struct platform_device *pdev,
}
if (of_property_read_bool(np, "wakeup-source")) {
/*
* 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);
priv->parent_wake_irq = platform_get_irq(pdev, 1);
if (priv->parent_wake_irq < 0) {
priv->parent_wake_irq = 0;
priv->parent_wake_irq = priv->parent_irq;
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,
@@ -466,18 +494,17 @@ static int brcmstb_gpio_irq_setup(struct platform_device *pdev,
goto out_free_domain;
}
}
priv->irq_chip.irq_set_wake = brcmstb_gpio_irq_set_wake;
}
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_mask_ack = brcmstb_gpio_irq_mask_ack;
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);
@@ -500,16 +527,11 @@ static void brcmstb_gpio_bank_save(struct brcmstb_gpio_priv *priv,
priv->reg_base + GIO_BANK_OFF(bank->id, i));
}
static void brcmstb_gpio_quiesce(struct device *dev, bool save)
static void brcmstb_gpio_quiesce(struct brcmstb_gpio_priv *priv, bool save)
{
struct brcmstb_gpio_priv *priv = dev_get_drvdata(dev);
struct brcmstb_gpio_bank *bank;
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) {
if (save)
brcmstb_gpio_bank_save(priv, bank);
@@ -527,8 +549,13 @@ static void brcmstb_gpio_quiesce(struct device *dev, bool save)
static void brcmstb_gpio_shutdown(struct platform_device *pdev)
{
struct brcmstb_gpio_priv *priv = dev_get_drvdata(&pdev->dev);
if (priv->parent_irq > 0)
disable_irq(priv->parent_irq);
/* Enable GPIO for S5 cold boot */
brcmstb_gpio_quiesce(&pdev->dev, false);
brcmstb_gpio_quiesce(priv, false);
}
static void brcmstb_gpio_bank_restore(struct brcmstb_gpio_priv *priv,
@@ -544,7 +571,30 @@ static void brcmstb_gpio_bank_restore(struct brcmstb_gpio_priv *priv,
static int brcmstb_gpio_suspend(struct device *dev)
{
brcmstb_gpio_quiesce(dev, true);
struct brcmstb_gpio_priv *priv = dev_get_drvdata(dev);
if (priv->parent_irq > 0)
priv->suspended = true;
return 0;
}
static int brcmstb_gpio_suspend_noirq(struct device *dev)
{
struct brcmstb_gpio_priv *priv = dev_get_drvdata(dev);
/* Catch any wakeup sources occurring between suspend and noirq */
if (!priv->suspended)
return -EBUSY;
if (priv->parent_irq > 0)
disable_irq(priv->parent_irq);
brcmstb_gpio_quiesce(priv, true);
if (priv->parent_wake_irq)
enable_irq(priv->parent_irq);
return 0;
}
@@ -552,25 +602,24 @@ 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);
if (priv->parent_wake_irq)
disable_irq(priv->parent_irq);
priv->suspended = false;
list_for_each_entry(bank, &priv->bank_list, node)
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)
if (priv->parent_irq > 0)
enable_irq(priv->parent_irq);
return 0;
}
static const struct dev_pm_ops brcmstb_gpio_pm_ops = {
.suspend_noirq = pm_sleep_ptr(brcmstb_gpio_suspend),
.suspend = pm_sleep_ptr(brcmstb_gpio_suspend),
.suspend_noirq = pm_sleep_ptr(brcmstb_gpio_suspend_noirq),
.resume_noirq = pm_sleep_ptr(brcmstb_gpio_resume),
};

View File

@@ -0,0 +1,101 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (C) 2026 Linaro Inc.
// Author: AKASHI takahiro <takahiro.akashi@linaro.org>
#include <linux/errno.h>
#include <linux/gpio/driver.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include "gpiolib.h"
static int pin_control_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
{
unsigned long config;
int ret;
config = PIN_CONFIG_OUTPUT_ENABLE;
ret = pinctrl_gpio_get_config(gc, offset, &config);
if (ret)
return ret;
if (config)
return GPIO_LINE_DIRECTION_OUT;
return GPIO_LINE_DIRECTION_IN;
}
static int pin_control_gpio_direction_output(struct gpio_chip *chip,
unsigned int offset, int val)
{
return pinctrl_gpio_direction_output(chip, offset);
}
static int pin_control_gpio_get(struct gpio_chip *chip, unsigned int offset)
{
unsigned long config;
int ret;
config = PIN_CONFIG_LEVEL;
ret = pinctrl_gpio_get_config(chip, offset, &config);
if (ret)
return ret;
return !!config;
}
static int pin_control_gpio_set(struct gpio_chip *chip, unsigned int offset,
int val)
{
unsigned long config;
config = pinconf_to_config_packed(PIN_CONFIG_LEVEL, val);
return pinctrl_gpio_set_config(chip, offset, config);
}
static int pin_control_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct gpio_chip *chip;
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->label = dev_name(dev);
chip->parent = dev;
chip->base = -1;
chip->request = gpiochip_generic_request;
chip->free = gpiochip_generic_free;
chip->get_direction = pin_control_gpio_get_direction;
chip->direction_input = pinctrl_gpio_direction_input;
chip->direction_output = pin_control_gpio_direction_output;
chip->get = pin_control_gpio_get;
chip->set = pin_control_gpio_set;
chip->set_config = gpiochip_generic_config;
return devm_gpiochip_add_data(dev, chip, NULL);
}
static const struct of_device_id pin_control_gpio_match[] = {
{ .compatible = "scmi-pinctrl-gpio" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, pin_control_gpio_match);
static struct platform_driver pin_control_gpio_driver = {
.probe = pin_control_gpio_probe,
.driver = {
.name = "pin-control-gpio",
.of_match_table = pin_control_gpio_match,
},
};
module_platform_driver(pin_control_gpio_driver);
MODULE_AUTHOR("AKASHI Takahiro <takahiro.akashi@linaro.org>");
MODULE_DESCRIPTION("Pinctrl based GPIO driver");
MODULE_LICENSE("GPL");

View File

@@ -47,8 +47,8 @@ static int cgbc_gpio_get(struct gpio_chip *chip, unsigned int offset)
if (ret)
return ret;
else
return (int)(val & (u8)BIT(offset));
return !!(val & BIT(offset));
}
static int __cgbc_gpio_set(struct gpio_chip *chip, unsigned int offset,

View File

@@ -39,10 +39,6 @@ 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;
@@ -285,30 +281,29 @@ static const char * const cs5535_gpio_names[] = {
"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 cs5535_gpio_chip *priv;
struct gpio_chip *gc;
struct resource *res;
int err = -EIO;
ulong mask_orig = mask;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
gc = &priv->chip;
gc->owner = THIS_MODULE;
gc->label = DRV_NAME;
gc->ngpio = 32;
gc->names = cs5535_gpio_names;
gc->request = chip_gpio_request;
gc->get = chip_gpio_get;
gc->set = chip_gpio_set;
gc->direction_input = chip_direction_input;
gc->direction_output = chip_direction_output;
/* 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
@@ -329,9 +324,9 @@ static int cs5535_gpio_probe(struct platform_device *pdev)
}
/* set up the driver-specific struct */
cs5535_gpio_chip.base = res->start;
cs5535_gpio_chip.pdev = pdev;
spin_lock_init(&cs5535_gpio_chip.lock);
priv->base = res->start;
priv->pdev = pdev;
spin_lock_init(&priv->lock);
dev_info(&pdev->dev, "reserved resource region %pR\n", res);
@@ -347,8 +342,7 @@ static int cs5535_gpio_probe(struct platform_device *pdev)
mask_orig, mask);
/* finally, register with the generic GPIO API */
return devm_gpiochip_add_data(&pdev->dev, &cs5535_gpio_chip.chip,
&cs5535_gpio_chip);
return devm_gpiochip_add_data(&pdev->dev, gc, priv);
}
static struct platform_driver cs5535_gpio_driver = {

View File

@@ -55,7 +55,7 @@ static int da9055_gpio_get(struct gpio_chip *gc, unsigned offset)
return ret;
}
return ret & (1 << offset);
return !!(ret & (1 << offset));
}

View File

@@ -75,8 +75,8 @@ struct dwapb_port_property {
};
struct dwapb_platform_data {
struct dwapb_port_property *properties;
unsigned int nports;
struct dwapb_port_property properties[] __counted_by(nports);
};
/* Store GPIO context across system-wide suspend/resume transitions */
@@ -114,11 +114,11 @@ static inline struct dwapb_gpio *to_dwapb_gpio(struct gpio_chip *gc)
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];
struct dwapb_gpio_port ports[] __counted_by(nr_ports);
};
static inline u32 gpio_reg_v2_convert(unsigned int offset)
@@ -585,14 +585,10 @@ static struct dwapb_platform_data *dwapb_gpio_get_pdata(struct device *dev)
if (nports == 0)
return ERR_PTR(-ENODEV);
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
pdata = devm_kzalloc(dev, struct_size(pdata, properties, nports), 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;
@@ -714,22 +710,17 @@ static int dwapb_gpio_probe(struct platform_device *pdev)
if (IS_ERR(pdata))
return PTR_ERR(pdata);
gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
gpio = devm_kzalloc(&pdev->dev, struct_size(gpio, ports, pdata->nports), GFP_KERNEL);
if (!gpio)
return -ENOMEM;
gpio->dev = &pdev->dev;
gpio->nr_ports = pdata->nports;
gpio->dev = &pdev->dev;
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);

View File

@@ -46,8 +46,8 @@ struct egpio_info {
uint chained_irq;
/* egpio info */
struct egpio_chip *chip;
int nchips;
struct egpio_chip chip[] __counted_by(nchips);
};
static inline void egpio_writew(u16 value, struct egpio_info *ei, int reg)
@@ -270,10 +270,12 @@ static int __init egpio_probe(struct platform_device *pdev)
int i;
/* Initialize ei data structure. */
ei = devm_kzalloc(&pdev->dev, sizeof(*ei), GFP_KERNEL);
ei = devm_kzalloc(&pdev->dev, struct_size(ei, chip, pdata->num_chips), GFP_KERNEL);
if (!ei)
return -ENOMEM;
ei->nchips = pdata->num_chips;
spin_lock_init(&ei->lock);
/* Find chained irq */
@@ -302,13 +304,6 @@ static int __init egpio_probe(struct platform_device *pdev)
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;

View File

@@ -11,20 +11,35 @@
#include <linux/module.h>
#include <linux/bitops.h>
#include <linux/errno.h>
#include <linux/interrupt.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_DIR 0x40
#define KEMPLD_GPIO_LVL 0x42
#define KEMPLD_GPIO_STS 0x44
#define KEMPLD_GPIO_EVT_LVL_EDGE 0x46
#define KEMPLD_GPIO_EVT_LOW_HIGH 0x48
#define KEMPLD_GPIO_IEN 0x4A
#define KEMPLD_GPIO_OUT_LVL 0x4E
/* The IRQ to use if none was configured in the BIOS */
static unsigned int gpio_irq;
module_param_hw(gpio_irq, uint, irq, 0444);
MODULE_PARM_DESC(gpio_irq, "Set legacy GPIO IRQ (1-15)");
struct kempld_gpio_data {
struct gpio_chip chip;
struct kempld_device_data *pld;
u8 out_lvl_reg;
struct mutex irq_lock;
u16 ien;
u16 evt_low_high;
u16 evt_lvl_edge;
};
/*
@@ -32,24 +47,25 @@ struct kempld_gpio_data {
* 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 reg, unsigned int bit, bool val)
{
u8 status;
status = kempld_read8(pld, reg);
status = kempld_read8(pld, reg + (bit / 8));
if (val)
status |= KEMPLD_GPIO_MASK(bit);
else
status &= ~KEMPLD_GPIO_MASK(bit);
kempld_write8(pld, reg, status);
kempld_write8(pld, reg + (bit / 8), status);
}
static int kempld_gpio_get_bit(struct kempld_device_data *pld, u8 reg, u8 bit)
static int kempld_gpio_get_bit(struct kempld_device_data *pld,
u8 reg, unsigned int bit)
{
u8 status;
kempld_get_mutex(pld);
status = kempld_read8(pld, reg);
status = kempld_read8(pld, reg + (bit / 8));
kempld_release_mutex(pld);
return !!(status & KEMPLD_GPIO_MASK(bit));
@@ -60,7 +76,34 @@ 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);
return !!kempld_gpio_get_bit(pld, KEMPLD_GPIO_LVL, offset);
}
static int kempld_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask,
unsigned long *bits)
{
struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
struct kempld_device_data *pld = gpio->pld;
u8 reg = KEMPLD_GPIO_LVL;
unsigned int shift;
bits[0] &= ~mask[0];
kempld_get_mutex(pld);
/* Try to reduce to a single 8 bits access if possible */
for (shift = 0; shift < gpio->chip.ngpio; shift += 8, reg++) {
unsigned long msk = (mask[0] >> shift) & 0xff;
if (!msk)
continue;
bits[0] |= (kempld_read8(pld, reg) & msk) << shift;
}
kempld_release_mutex(pld);
return 0;
}
static int kempld_gpio_set(struct gpio_chip *chip, unsigned int offset,
@@ -70,7 +113,38 @@ static int kempld_gpio_set(struct gpio_chip *chip, unsigned int offset,
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, gpio->out_lvl_reg, offset, value);
kempld_release_mutex(pld);
return 0;
}
static int kempld_gpio_set_multiple(struct gpio_chip *chip,
unsigned long *mask, unsigned long *bits)
{
struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
struct kempld_device_data *pld = gpio->pld;
u8 reg = gpio->out_lvl_reg;
unsigned int shift;
kempld_get_mutex(pld);
/* Try to reduce to a single 8 bits access if possible */
for (shift = 0; shift < gpio->chip.ngpio; shift += 8, reg++) {
u8 val, msk = mask[0] >> shift;
if (!msk)
continue;
if (msk != 0xFF)
val = kempld_read8(pld, reg) & ~msk;
else
val = 0;
val |= (bits[0] >> shift) & msk;
kempld_write8(pld, reg, val);
}
kempld_release_mutex(pld);
return 0;
@@ -82,7 +156,7 @@ static int kempld_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
struct kempld_device_data *pld = gpio->pld;
kempld_get_mutex(pld);
kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR_NUM(offset), offset, 0);
kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR, offset, 0);
kempld_release_mutex(pld);
return 0;
@@ -95,8 +169,8 @@ static int kempld_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
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_gpio_bitop(pld, gpio->out_lvl_reg, offset, value);
kempld_gpio_bitop(pld, KEMPLD_GPIO_DIR, offset, 1);
kempld_release_mutex(pld);
return 0;
@@ -107,7 +181,7 @@ 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))
if (kempld_gpio_get_bit(pld, KEMPLD_GPIO_DIR, offset))
return GPIO_LINE_DIRECTION_OUT;
return GPIO_LINE_DIRECTION_IN;
@@ -133,6 +207,180 @@ static int kempld_gpio_pincount(struct kempld_device_data *pld)
return evt ? __ffs(evt) : 16;
}
static void kempld_irq_mask(struct irq_data *data)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
gpio->ien &= ~BIT(irqd_to_hwirq(data));
gpiochip_disable_irq(chip, irqd_to_hwirq(data));
}
static void kempld_irq_unmask(struct irq_data *data)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
gpiochip_enable_irq(chip, irqd_to_hwirq(data));
gpio->ien |= BIT(irqd_to_hwirq(data));
}
static int kempld_irq_set_type(struct irq_data *data, unsigned int type)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
switch (type) {
case IRQ_TYPE_EDGE_RISING:
gpio->evt_low_high |= BIT(data->hwirq);
gpio->evt_lvl_edge |= BIT(data->hwirq);
break;
case IRQ_TYPE_EDGE_FALLING:
gpio->evt_low_high &= ~BIT(data->hwirq);
gpio->evt_lvl_edge |= BIT(data->hwirq);
break;
case IRQ_TYPE_LEVEL_HIGH:
gpio->evt_low_high |= BIT(data->hwirq);
gpio->evt_lvl_edge &= ~BIT(data->hwirq);
break;
case IRQ_TYPE_LEVEL_LOW:
gpio->evt_low_high &= ~BIT(data->hwirq);
gpio->evt_lvl_edge &= ~BIT(data->hwirq);
break;
default:
return -EINVAL;
}
return 0;
}
static void kempld_irq_bus_lock(struct irq_data *data)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
mutex_lock(&gpio->irq_lock);
}
static void kempld_irq_bus_sync_unlock(struct irq_data *data)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
struct kempld_device_data *pld = gpio->pld;
kempld_get_mutex(pld);
kempld_write16(pld, KEMPLD_GPIO_EVT_LVL_EDGE, gpio->evt_lvl_edge);
kempld_write16(pld, KEMPLD_GPIO_EVT_LOW_HIGH, gpio->evt_low_high);
kempld_write16(pld, KEMPLD_GPIO_IEN, gpio->ien);
kempld_release_mutex(pld);
mutex_unlock(&gpio->irq_lock);
}
static const struct irq_chip kempld_irqchip = {
.name = "kempld-gpio",
.irq_mask = kempld_irq_mask,
.irq_unmask = kempld_irq_unmask,
.irq_set_type = kempld_irq_set_type,
.irq_bus_lock = kempld_irq_bus_lock,
.irq_bus_sync_unlock = kempld_irq_bus_sync_unlock,
.flags = IRQCHIP_IMMUTABLE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static irqreturn_t kempld_gpio_irq_handler(int irq, void *data)
{
struct kempld_gpio_data *gpio = data;
struct gpio_chip *chip = &gpio->chip;
unsigned int pin, child_irq;
unsigned long status;
kempld_get_mutex(gpio->pld);
status = kempld_read16(gpio->pld, KEMPLD_GPIO_STS);
if (status)
kempld_write16(gpio->pld, KEMPLD_GPIO_STS, status);
kempld_release_mutex(gpio->pld);
status &= gpio->ien;
if (!status)
return IRQ_NONE;
for_each_set_bit(pin, &status, chip->ngpio) {
child_irq = irq_find_mapping(chip->irq.domain, pin);
handle_nested_irq(child_irq);
}
return IRQ_HANDLED;
}
static int kempld_gpio_irq_init(struct device *dev,
struct kempld_gpio_data *gpio)
{
struct kempld_device_data *pld = gpio->pld;
struct gpio_chip *chip = &gpio->chip;
struct gpio_irq_chip *girq;
unsigned int irq;
int ret;
/* Get the IRQ configured by the BIOS in the PLD */
kempld_get_mutex(pld);
irq = kempld_read8(pld, KEMPLD_IRQ_GPIO);
kempld_release_mutex(pld);
if (irq == 0xff) {
dev_info(dev, "GPIO controller has no IRQ support\n");
return 0;
}
/* Allow overriding the IRQ with the module parameter */
if (gpio_irq > 0) {
dev_warn(dev, "Forcing IRQ to %d\n", gpio_irq);
irq &= ~KEMPLD_IRQ_GPIO_MASK;
irq |= gpio_irq & KEMPLD_IRQ_GPIO_MASK;
}
if (!(irq & KEMPLD_IRQ_GPIO_MASK)) {
dev_warn(dev, "No IRQ configured\n");
return 0;
}
/* Get the current config, disable all child interrupts, clear them
* and set the parent IRQ
*/
kempld_get_mutex(pld);
gpio->evt_low_high = kempld_read16(pld, KEMPLD_GPIO_EVT_LOW_HIGH);
gpio->evt_lvl_edge = kempld_read16(pld, KEMPLD_GPIO_EVT_LVL_EDGE);
kempld_write16(pld, KEMPLD_GPIO_IEN, 0);
kempld_write16(pld, KEMPLD_GPIO_STS, 0xFFFF);
kempld_write16(pld, KEMPLD_IRQ_GPIO, irq);
kempld_release_mutex(pld);
girq = &chip->irq;
gpio_irq_chip_set_chip(girq, &kempld_irqchip);
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;
mutex_init(&gpio->irq_lock);
ret = devm_request_threaded_irq(dev, irq & KEMPLD_IRQ_GPIO_MASK,
NULL, kempld_gpio_irq_handler,
IRQF_ONESHOT, chip->label,
gpio);
if (ret) {
dev_err(dev, "failed to request irq %d\n", irq);
return ret;
}
return 0;
}
static int kempld_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -152,6 +400,15 @@ static int kempld_gpio_probe(struct platform_device *pdev)
if (!gpio)
return -ENOMEM;
/* Starting with version 2.8 there is a dedicated register for the
* output state, earlier versions share the register used to read
* the line level.
*/
if (pld->info.spec_major > 2 || pld->info.spec_minor >= 8)
gpio->out_lvl_reg = KEMPLD_GPIO_OUT_LVL;
else
gpio->out_lvl_reg = KEMPLD_GPIO_LVL;
gpio->pld = pld;
platform_set_drvdata(pdev, gpio);
@@ -169,13 +426,19 @@ static int kempld_gpio_probe(struct platform_device *pdev)
chip->direction_output = kempld_gpio_direction_output;
chip->get_direction = kempld_gpio_get_direction;
chip->get = kempld_gpio_get;
chip->get_multiple = kempld_gpio_get_multiple;
chip->set = kempld_gpio_set;
chip->set_multiple = kempld_gpio_set_multiple;
chip->ngpio = kempld_gpio_pincount(pld);
if (chip->ngpio == 0) {
dev_err(dev, "No GPIO pins detected\n");
return -ENODEV;
}
ret = kempld_gpio_irq_init(dev, gpio);
if (ret)
return ret;
ret = devm_gpiochip_add_data(dev, chip, gpio);
if (ret) {
dev_err(dev, "Could not register GPIO chip\n");

View File

@@ -55,7 +55,7 @@ static int lp873x_gpio_get(struct gpio_chip *chip, unsigned int offset)
if (ret < 0)
return ret;
return val & BIT(offset * BITS_PER_GPO);
return !!(val & BIT(offset * BITS_PER_GPO));
}
static int lp873x_gpio_set(struct gpio_chip *chip, unsigned int offset,

View File

@@ -10,14 +10,18 @@
* Derived from drivers/gpio/pca953x.c
*/
#include <linux/module.h>
#include <linux/cleanup.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/gpio/driver.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_data/max732x.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
@@ -207,22 +211,20 @@ static void max732x_gpio_set_mask(struct gpio_chip *gc, unsigned off, int mask,
uint8_t reg_out;
int ret;
mutex_lock(&chip->lock);
guard(mutex)(&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;
return;
/* 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 int max732x_gpio_set_value(struct gpio_chip *gc, unsigned int off,
@@ -329,7 +331,7 @@ static void max732x_irq_update_mask(struct max732x_chip *chip)
if (chip->irq_features == INT_NO_MASK)
return;
mutex_lock(&chip->lock);
guard(mutex)(&chip->lock);
switch (chip->irq_features) {
case INT_INDEP_MASK:
@@ -342,8 +344,6 @@ static void max732x_irq_update_mask(struct max732x_chip *chip)
max732x_writeb(chip, 1, (uint8_t)msg);
break;
}
mutex_unlock(&chip->lock);
}
static void max732x_irq_mask(struct irq_data *d)

View File

@@ -42,18 +42,16 @@ o ` ~~~~\___/~~~~ ` controller in FPGA is ,.`
#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/compiler.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/limits.h>
#include <linux/log2.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>

View File

@@ -548,13 +548,6 @@ static void gpio_mpsse_ida_remove(void *data)
ida_free(&gpio_mpsse_ida, priv->id);
}
static void gpio_mpsse_usb_put_dev(void *data)
{
struct mpsse_priv *priv = data;
usb_put_dev(priv->udev);
}
static int mpsse_init_valid_mask(struct gpio_chip *chip,
unsigned long *valid_mask,
unsigned int ngpios)
@@ -598,11 +591,7 @@ static int gpio_mpsse_probe(struct usb_interface *interface,
INIT_LIST_HEAD(&priv->workers);
priv->udev = usb_get_dev(interface_to_usbdev(interface));
err = devm_add_action_or_reset(dev, gpio_mpsse_usb_put_dev, priv);
if (err)
return err;
priv->udev = interface_to_usbdev(interface);
priv->intf = interface;
priv->intf_id = interface->cur_altsetting->desc.bInterfaceNumber;

View File

@@ -0,0 +1,323 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Intel Nova Lake GPIO-signaled ACPI events driver
*
* Copyright (c) 2026, Intel Corporation.
*
* Author: Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>
*
* Intel client platforms released in 2026 and later (starting with Intel Nova
* Lake) support two modes of handling ACPI General Purpose Events (GPE):
* exposed GPIO interrupt mode and legacy mode.
*
* By default, the platform uses legacy mode, handling GPEs as usual. If this
* driver is installed, it signals to the platform (on every boot) that exposed
* GPIO interrupt mode is supported. The platform then switches to exposed
* mode, which takes effect on next boot. From the user perspective, this
* change is transparent.
*
* However, if driver is uninstalled while in exposed interrupt mode, GPEs will
* _not_ be handled until platform falls back to legacy mode. This means that
* USB keyboard, mouse might not function properly for the fallback duration.
* Fallback requires two reboots to take effect: on first reboot, platform no
* longer receives signal from this driver and switches to legacy mode, which
* takes effect on second boot.
*
* Example ACPI event: Power Management Event coming from motherboard PCH,
* waking system from sleep following USB mouse hotplug.
*
* This driver supports up to 128 GPIO pins in each GPE block, per ACPI
* specification v6.6 section 5.6.4.
*/
#include <linux/acpi.h>
#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/gfp_types.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/uuid.h>
#include <linux/gpio/driver.h>
/*
* GPE block has two registers, each register takes half the block size.
* Convert size to bits to get total GPIO pin count.
*/
#define GPE_BLK_REG_SIZE(block_size) ((block_size) / 2)
#define GPE_REG_PIN_COUNT(block_size) BYTES_TO_BITS(GPE_BLK_REG_SIZE(block_size))
#define GPE_STS_REG_OFFSET 0
#define GPE_EN_REG_OFFSET(block_size) GPE_BLK_REG_SIZE(block_size)
/**
* struct nvl_gpio - Intel Nova Lake GPIO driver state
* @gc: GPIO controller interface
* @reg_base: Base address of the GPE registers
* @lock: Guard register access
* @blk_size: GPE block length
*/
struct nvl_gpio {
struct gpio_chip gc;
void __iomem *reg_base;
raw_spinlock_t lock;
size_t blk_size;
};
static void __iomem *nvl_gpio_get_byte_addr(struct nvl_gpio *priv,
unsigned int reg_offset,
unsigned long gpio)
{
return priv->reg_base + reg_offset + gpio;
}
static int nvl_gpio_get(struct gpio_chip *gc, unsigned int gpio)
{
struct nvl_gpio *priv = gpiochip_get_data(gc);
unsigned int byte_idx = gpio / BITS_PER_BYTE;
unsigned int bit_idx = gpio % BITS_PER_BYTE;
void __iomem *addr;
u8 reg;
addr = nvl_gpio_get_byte_addr(priv, GPE_STS_REG_OFFSET, byte_idx);
guard(raw_spinlock_irqsave)(&priv->lock);
reg = ioread8(addr);
return !!(reg & BIT(bit_idx));
}
static const struct gpio_chip nvl_gpio_chip = {
.owner = THIS_MODULE,
.get = nvl_gpio_get,
};
static int nvl_gpio_irq_set_type(struct irq_data *d, unsigned int type)
{
if (type & IRQ_TYPE_EDGE_BOTH)
irq_set_handler_locked(d, handle_edge_irq);
else if (type & IRQ_TYPE_LEVEL_MASK)
irq_set_handler_locked(d, handle_level_irq);
return 0;
}
static void nvl_gpio_irq_mask_unmask(struct gpio_chip *gc, unsigned long hwirq,
bool mask)
{
struct nvl_gpio *priv = gpiochip_get_data(gc);
unsigned int byte_idx = hwirq / BITS_PER_BYTE;
unsigned int bit_idx = hwirq % BITS_PER_BYTE;
void __iomem *addr;
u8 reg;
addr = nvl_gpio_get_byte_addr(priv, GPE_EN_REG_OFFSET(priv->blk_size), byte_idx);
guard(raw_spinlock_irqsave)(&priv->lock);
reg = ioread8(addr);
if (mask)
reg &= ~BIT(bit_idx);
else
reg |= BIT(bit_idx);
iowrite8(reg, addr);
}
static void nvl_gpio_irq_unmask(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
irq_hw_number_t hwirq = irqd_to_hwirq(d);
gpiochip_enable_irq(gc, hwirq);
nvl_gpio_irq_mask_unmask(gc, hwirq, false);
}
static void nvl_gpio_irq_mask(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
irq_hw_number_t hwirq = irqd_to_hwirq(d);
nvl_gpio_irq_mask_unmask(gc, hwirq, true);
gpiochip_disable_irq(gc, hwirq);
}
static void nvl_gpio_irq_ack(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct nvl_gpio *priv = gpiochip_get_data(gc);
irq_hw_number_t hwirq = irqd_to_hwirq(d);
unsigned int byte_idx = hwirq / BITS_PER_BYTE;
unsigned int bit_idx = hwirq % BITS_PER_BYTE;
void __iomem *addr;
u8 reg;
addr = nvl_gpio_get_byte_addr(priv, GPE_STS_REG_OFFSET, byte_idx);
guard(raw_spinlock_irqsave)(&priv->lock);
reg = ioread8(addr);
reg |= BIT(bit_idx);
iowrite8(reg, addr);
}
static const struct irq_chip nvl_gpio_irq_chip = {
.name = "gpio-novalake",
.irq_ack = nvl_gpio_irq_ack,
.irq_mask = nvl_gpio_irq_mask,
.irq_unmask = nvl_gpio_irq_unmask,
.irq_set_type = nvl_gpio_irq_set_type,
.flags = IRQCHIP_IMMUTABLE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static irqreturn_t nvl_gpio_irq(int irq, void *data)
{
struct nvl_gpio *priv = data;
const size_t block_size = priv->blk_size;
unsigned int handled = 0;
for (unsigned int i = 0; i < block_size; i++) {
const void __iomem *reg = priv->reg_base + i;
unsigned long pending;
unsigned long enabled;
unsigned int bit_idx;
scoped_guard(raw_spinlock, &priv->lock) {
pending = ioread8(reg + GPE_STS_REG_OFFSET);
enabled = ioread8(reg + GPE_EN_REG_OFFSET(block_size));
}
pending &= enabled;
for_each_set_bit(bit_idx, &pending, BITS_PER_BYTE) {
unsigned int hwirq = i * BITS_PER_BYTE + bit_idx;
generic_handle_domain_irq(priv->gc.irq.domain, hwirq);
}
handled += pending ? 1 : 0;
}
return IRQ_RETVAL(handled);
}
/* UUID for GPE device _DSM: 079406e6-bdea-49cf-8563-03e2811901cb */
static const guid_t nvl_gpe_dsm_guid =
GUID_INIT(0x079406e6, 0xbdea, 0x49cf,
0x85, 0x63, 0x03, 0xe2, 0x81, 0x19, 0x01, 0xcb);
#define DSM_GPE_MODE_REV 1
#define DSM_GPE_MODE_FN_INDEX 1
#define DSM_ENABLE_GPE_MODE 1
static int nvl_acpi_enable_gpe_mode(struct device *dev)
{
union acpi_object argv4[2];
union acpi_object *obj;
argv4[0].type = ACPI_TYPE_PACKAGE;
argv4[0].package.count = 1;
argv4[0].package.elements = &argv4[1];
argv4[1].integer.type = ACPI_TYPE_INTEGER;
argv4[1].integer.value = DSM_ENABLE_GPE_MODE;
obj = acpi_evaluate_dsm_typed(ACPI_HANDLE(dev), &nvl_gpe_dsm_guid,
DSM_GPE_MODE_REV, DSM_GPE_MODE_FN_INDEX,
argv4, ACPI_TYPE_BUFFER);
if (!obj)
return -EIO;
ACPI_FREE(obj);
return 0;
}
static int nvl_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
resource_size_t ioresource_size;
struct gpio_irq_chip *girq;
struct nvl_gpio *priv;
struct resource *res;
void __iomem *regs;
int ret, irq;
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
if (!res)
return -ENXIO;
/*
* GPE block length should be non-negative multiple of two and allow up
* to 128 pins. ACPI v6.6 section 5.2.9 and 5.6.4.
*/
ioresource_size = resource_size(res);
if (!ioresource_size || ioresource_size % 2 || ioresource_size > 0x20)
return dev_err_probe(dev, -EINVAL,
"invalid GPE block length, resource: %pR\n",
res);
regs = devm_ioport_map(dev, res->start, ioresource_size);
if (!regs)
return -ENOMEM;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
raw_spin_lock_init(&priv->lock);
priv->reg_base = regs;
priv->blk_size = ioresource_size;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(dev, irq, nvl_gpio_irq, IRQF_SHARED, dev_name(dev), priv);
if (ret)
return ret;
priv->gc = nvl_gpio_chip;
priv->gc.label = dev_name(dev);
priv->gc.parent = dev;
priv->gc.ngpio = GPE_REG_PIN_COUNT(priv->blk_size);
priv->gc.base = -1;
girq = &priv->gc.irq;
gpio_irq_chip_set_chip(girq, &nvl_gpio_irq_chip);
girq->parent_handler = NULL;
girq->num_parents = 0;
girq->parents = NULL;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_bad_irq;
ret = devm_gpiochip_add_data(dev, &priv->gc, priv);
if (ret)
return ret;
return nvl_acpi_enable_gpe_mode(dev);
}
static const struct acpi_device_id nvl_gpio_acpi_match[] = {
{ "INTC1114" },
{}
};
MODULE_DEVICE_TABLE(acpi, nvl_gpio_acpi_match);
static struct platform_driver nvl_gpio_driver = {
.driver = {
.name = "gpio-novalake-events",
.acpi_match_table = nvl_gpio_acpi_match,
},
.probe = nvl_gpio_probe,
};
module_platform_driver(nvl_gpio_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alan Borzeszkowski <alan.borzeszkowski@linux.intel.com>");
MODULE_DESCRIPTION("Intel Nova Lake ACPI GPIO events driver");

View File

@@ -351,6 +351,10 @@ static const struct of_device_id realtek_gpio_of_match[] = {
{
.compatible = "realtek,rtl9310-gpio",
},
{
.compatible = "realtek,rtl9607-gpio",
.data = (void *)GPIO_PORTS_REVERSED,
},
{}
};
MODULE_DEVICE_TABLE(of, realtek_gpio_of_match);

View File

@@ -582,7 +582,7 @@ static int rockchip_gpiolib_register(struct rockchip_pin_bank *bank)
bank->gpio_chip = rockchip_gpiolib_chip;
gc = &bank->gpio_chip;
gc->base = bank->pin_base;
gc->base = -1;
gc->ngpio = bank->nr_pins;
gc->label = bank->name;
gc->parent = bank->dev;

View File

@@ -36,10 +36,9 @@
#include <linux/sysfs.h>
#include <linux/types.h>
#include "dev-sync-probe.h"
#define GPIO_SIM_NGPIO_MAX 1024
#define GPIO_SIM_PROP_MAX 5 /* Max 4 properties + sentinel. */
#define GPIO_SIM_HOG_PROP_MAX 5
#define GPIO_SIM_NUM_ATTRS 3 /* value, pull and sentinel */
static DEFINE_IDA(gpio_sim_ida);
@@ -545,7 +544,7 @@ static struct platform_driver gpio_sim_driver = {
};
struct gpio_sim_device {
struct dev_sync_probe_data probe_data;
struct platform_device *pdev;
struct config_group group;
int id;
@@ -561,8 +560,6 @@ struct gpio_sim_device {
*/
struct mutex lock;
struct gpiod_hog *hogs;
struct list_head bank_list;
};
@@ -655,6 +652,7 @@ struct gpio_sim_hog {
char *name;
int dir;
bool active_low;
};
static struct gpio_sim_hog *to_gpio_sim_hog(struct config_item *item)
@@ -673,7 +671,7 @@ static bool gpio_sim_device_is_live(struct gpio_sim_device *dev)
{
lockdep_assert_held(&dev->lock);
return !!dev->probe_data.pdev;
return !!dev->pdev;
}
static char *gpio_sim_strdup_trimmed(const char *str, size_t count)
@@ -695,7 +693,7 @@ static ssize_t gpio_sim_device_config_dev_name_show(struct config_item *item,
guard(mutex)(&dev->lock);
pdev = dev->probe_data.pdev;
pdev = dev->pdev;
if (pdev)
return sprintf(page, "%s\n", dev_name(&pdev->dev));
@@ -774,102 +772,6 @@ static void gpio_sim_set_reserved_ranges(struct gpio_sim_bank *bank,
}
}
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 = kzalloc_objs(*dev->hogs, num_hogs + 1);
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)
@@ -917,12 +819,61 @@ gpio_sim_make_bank_swnode(struct gpio_sim_bank *bank,
return fwnode_create_software_node(properties, parent);
}
static int gpio_sim_bank_add_hogs(struct gpio_sim_bank *bank)
{
struct property_entry properties[GPIO_SIM_HOG_PROP_MAX];
struct fwnode_handle *swnode;
struct gpio_sim_line *line;
struct gpio_sim_hog *hog;
unsigned int idx;
u32 gpios[2];
list_for_each_entry(line, &bank->line_list, siblings) {
if (!line->hog)
continue;
hog = line->hog;
gpios[0] = line->offset;
gpios[1] = hog->active_low ? 1 : 0;
memset(properties, 0, sizeof(properties));
idx = 0;
properties[idx++] = PROPERTY_ENTRY_BOOL("gpio-hog");
properties[idx++] = PROPERTY_ENTRY_U32_ARRAY("gpios", gpios);
properties[idx++] = PROPERTY_ENTRY_STRING("line-name", hog->name);
switch (hog->dir) {
case GPIOD_IN:
properties[idx++] = PROPERTY_ENTRY_BOOL("input");
break;
case GPIOD_OUT_HIGH:
properties[idx++] = PROPERTY_ENTRY_BOOL("output-high");
break;
case GPIOD_OUT_LOW:
properties[idx++] = PROPERTY_ENTRY_BOOL("output-low");
break;
default:
/* Would have been validated at configfs store. */
WARN(1, "Unexpected hog direction value: %d", hog->dir);
return -EINVAL;
}
swnode = fwnode_create_software_node(properties, bank->swnode);
if (IS_ERR(swnode))
return PTR_ERR(swnode);
}
return 0;
}
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);
gpio_sim_remove_swnode_recursive(child);
fwnode_remove_software_node(swnode);
}
@@ -947,6 +898,7 @@ static bool gpio_sim_bank_labels_non_unique(struct gpio_sim_device *dev)
static int gpio_sim_device_activate(struct gpio_sim_device *dev)
{
struct platform_device_info pdevinfo;
struct platform_device *pdev;
struct fwnode_handle *swnode;
struct gpio_sim_bank *bank;
int ret;
@@ -974,29 +926,39 @@ static int gpio_sim_device_activate(struct gpio_sim_device *dev)
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;
goto err_remove_swnode;
}
}
ret = gpio_sim_add_hogs(dev);
if (ret) {
gpio_sim_remove_swnode_recursive(swnode);
return ret;
ret = gpio_sim_bank_add_hogs(bank);
if (ret)
goto err_remove_swnode;
}
pdevinfo.name = "gpio-sim";
pdevinfo.fwnode = swnode;
pdevinfo.id = dev->id;
ret = dev_sync_probe_register(&dev->probe_data, &pdevinfo);
if (ret) {
gpio_sim_remove_hogs(dev);
gpio_sim_remove_swnode_recursive(swnode);
return ret;
pdev = platform_device_register_full(&pdevinfo);
if (IS_ERR(pdev)) {
ret = PTR_ERR(pdev);
goto err_remove_swnode;
}
wait_for_device_probe();
if (!device_is_bound(&pdev->dev)) {
ret = -ENXIO;
goto err_unregister_pdev;
}
dev->pdev = pdev;
return 0;
err_unregister_pdev:
platform_device_unregister(pdev);
err_remove_swnode:
gpio_sim_remove_swnode_recursive(swnode);
return ret;
}
static void gpio_sim_device_deactivate(struct gpio_sim_device *dev)
@@ -1005,9 +967,9 @@ static void gpio_sim_device_deactivate(struct gpio_sim_device *dev)
lockdep_assert_held(&dev->lock);
swnode = dev_fwnode(&dev->probe_data.pdev->dev);
dev_sync_probe_unregister(&dev->probe_data);
gpio_sim_remove_hogs(dev);
swnode = dev_fwnode(&dev->pdev->dev);
platform_device_unregister(dev->pdev);
dev->pdev = NULL;
gpio_sim_remove_swnode_recursive(swnode);
}
@@ -1109,7 +1071,7 @@ static ssize_t gpio_sim_bank_config_chip_name_show(struct config_item *item,
guard(mutex)(&dev->lock);
if (gpio_sim_device_is_live(dev))
return device_for_each_child(&dev->probe_data.pdev->dev, &ctx,
return device_for_each_child(&dev->pdev->dev, &ctx,
gpio_sim_emit_chip_name);
return sprintf(page, "none\n");
@@ -1365,9 +1327,46 @@ gpio_sim_hog_config_direction_store(struct config_item *item,
CONFIGFS_ATTR(gpio_sim_hog_config_, direction);
static ssize_t gpio_sim_hog_config_active_low_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, "%c\n", hog->active_low ? '1' : '0');
}
static ssize_t
gpio_sim_hog_config_active_low_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);
bool active_low;
int ret;
guard(mutex)(&dev->lock);
if (gpio_sim_device_is_live(dev))
return -EBUSY;
ret = kstrtobool(page, &active_low);
if (ret)
return ret;
hog->active_low = active_low;
return count;
}
CONFIGFS_ATTR(gpio_sim_hog_config_, active_low);
static struct configfs_attribute *gpio_sim_hog_config_attrs[] = {
&gpio_sim_hog_config_attr_name,
&gpio_sim_hog_config_attr_direction,
&gpio_sim_hog_config_attr_active_low,
NULL
};
@@ -1583,8 +1582,6 @@ gpio_sim_config_make_device_group(struct config_group *group, const char *name)
mutex_init(&dev->lock);
INIT_LIST_HEAD(&dev->bank_list);
dev_sync_probe_init(&dev->probe_data);
return &no_free_ptr(dev)->group;
}

View File

@@ -102,7 +102,7 @@ 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));
return !!(xway_stp_r32(chip->virt, XWAY_STP_CPU0) & BIT(gpio));
}
/**

View File

@@ -125,7 +125,6 @@ struct tegra_gpio_soc {
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;
@@ -133,6 +132,8 @@ struct tegra_gpio {
void __iomem *secure;
void __iomem *base;
unsigned int irq[] __counted_by(num_irq);
};
static const struct tegra_gpio_port *
@@ -857,12 +858,18 @@ static int tegra186_gpio_probe(struct platform_device *pdev)
struct device_node *np;
struct resource *res;
char **names;
int err;
int node, err;
gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
err = platform_irq_count(pdev);
if (err < 0)
return err;
gpio = devm_kzalloc(&pdev->dev, struct_size(gpio, irq, err), GFP_KERNEL);
if (!gpio)
return -ENOMEM;
gpio->num_irq = err;
gpio->soc = device_get_match_data(&pdev->dev);
gpio->gpio.label = gpio->soc->name;
gpio->gpio.parent = &pdev->dev;
@@ -889,21 +896,10 @@ static int tegra186_gpio_probe(struct platform_device *pdev)
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)
@@ -937,16 +933,22 @@ static int tegra186_gpio_probe(struct platform_device *pdev)
if (!names)
return -ENOMEM;
node = dev_to_node(&pdev->dev);
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++) {
if (gpio->soc->prefix)
name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL, "%s-P%s.%02x",
gpio->soc->prefix, port->name, j);
if (node >= 0)
name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL,
"%d-%sP%s.%02x", node,
gpio->soc->prefix ?: "",
port->name, j);
else
name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL, "P%s.%02x",
name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL,
"%sP%s.%02x",
gpio->soc->prefix ?: "",
port->name, j);
if (!name)
return -ENOMEM;
@@ -1373,6 +1375,9 @@ static const struct tegra_gpio_soc tegra256_main_soc = {
.has_vm_support = true,
};
/* Macro to define GPIO name prefix with separator */
#define TEGRA_GPIO_PREFIX(_x) _x "-"
#define TEGRA410_COMPUTE_GPIO_PORT(_name, _bank, _port, _pins) \
TEGRA_GPIO_PORT(TEGRA410_COMPUTE, _name, _bank, _port, _pins)
@@ -1388,7 +1393,7 @@ static const struct tegra_gpio_soc tegra410_compute_soc = {
.num_ports = ARRAY_SIZE(tegra410_compute_ports),
.ports = tegra410_compute_ports,
.name = "tegra410-gpio-compute",
.prefix = "COMPUTE",
.prefix = TEGRA_GPIO_PREFIX("COMPUTE"),
.num_irqs_per_bank = 8,
.instance = 0,
};
@@ -1418,7 +1423,7 @@ static const struct tegra_gpio_soc tegra410_system_soc = {
.num_ports = ARRAY_SIZE(tegra410_system_ports),
.ports = tegra410_system_ports,
.name = "tegra410-gpio-system",
.prefix = "SYSTEM",
.prefix = TEGRA_GPIO_PREFIX("SYSTEM"),
.num_irqs_per_bank = 8,
.instance = 0,
};

View File

@@ -50,7 +50,7 @@ static int tps65086_gpio_get(struct gpio_chip *chip, unsigned offset)
if (ret < 0)
return ret;
return val & BIT(4 + offset);
return !!(val & BIT(4 + offset));
}
static int tps65086_gpio_set(struct gpio_chip *chip, unsigned int offset,

View File

@@ -11,7 +11,6 @@
#include <linux/platform_device.h>
#include <linux/property.h>
#define DEFAULT_PIN_NUMBER 16
#define INPUT_REG_OFFSET 0x00
#define OUTPUT_REG_OFFSET 0x02
#define DIRECTION_REG_OFFSET 0x04
@@ -23,7 +22,6 @@ static int ts4800_gpio_probe(struct platform_device *pdev)
struct gpio_generic_chip *chip;
void __iomem *base_addr;
int retval;
u32 ngpios;
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
@@ -33,12 +31,6 @@ static int ts4800_gpio_probe(struct platform_device *pdev)
if (IS_ERR(base_addr))
return PTR_ERR(base_addr);
retval = device_property_read_u32(dev, "ngpios", &ngpios);
if (retval == -EINVAL)
ngpios = DEFAULT_PIN_NUMBER;
else if (retval)
return retval;
config = (struct gpio_generic_chip_config) {
.dev = dev,
.sz = 2,
@@ -52,8 +44,6 @@ static int ts4800_gpio_probe(struct platform_device *pdev)
return dev_err_probe(dev, retval,
"failed to initialize the generic GPIO chip\n");
chip->gc.ngpio = ngpios;
return devm_gpiochip_add_data(dev, &chip->gc, NULL);
}

View File

@@ -288,7 +288,7 @@ static int vprbrd_gpiob_get(struct gpio_chip *chip,
/* if io is set to output, just return the saved value */
if (gpio->gpiob_out & (1 << offset))
return gpio->gpiob_val & (1 << offset);
return !!(gpio->gpiob_val & (1 << offset));
mutex_lock(&vb->lock);

View File

@@ -36,8 +36,6 @@
#include <linux/string_helpers.h>
#include <linux/types.h>
#include "dev-sync-probe.h"
#define GPIO_VIRTUSER_NAME_BUF_LEN 32
static DEFINE_IDA(gpio_virtuser_ida);
@@ -978,7 +976,7 @@ static struct platform_driver gpio_virtuser_driver = {
};
struct gpio_virtuser_device {
struct dev_sync_probe_data probe_data;
struct platform_device *pdev;
struct config_group group;
int id;
@@ -1002,7 +1000,7 @@ gpio_virtuser_device_is_live(struct gpio_virtuser_device *dev)
{
lockdep_assert_held(&dev->lock);
return !!dev->probe_data.pdev;
return !!dev->pdev;
}
struct gpio_virtuser_lookup {
@@ -1342,7 +1340,7 @@ gpio_virtuser_device_config_dev_name_show(struct config_item *item,
guard(mutex)(&dev->lock);
pdev = dev->probe_data.pdev;
pdev = dev->pdev;
if (pdev)
return sprintf(page, "%s\n", dev_name(&pdev->dev));
@@ -1450,6 +1448,7 @@ static int
gpio_virtuser_device_activate(struct gpio_virtuser_device *dev)
{
struct platform_device_info pdevinfo;
struct platform_device *pdev;
struct fwnode_handle *swnode;
int ret;
@@ -1471,12 +1470,23 @@ gpio_virtuser_device_activate(struct gpio_virtuser_device *dev)
if (ret)
goto err_remove_swnode;
ret = dev_sync_probe_register(&dev->probe_data, &pdevinfo);
if (ret)
pdev = platform_device_register_full(&pdevinfo);
if (IS_ERR(pdev)) {
ret = PTR_ERR(pdev);
goto err_remove_lookup_table;
}
wait_for_device_probe();
if (!device_is_bound(&pdev->dev)) {
ret = -ENXIO;
goto err_unregister_pdev;
}
dev->pdev = pdev;
return 0;
err_unregister_pdev:
platform_device_unregister(pdev);
err_remove_lookup_table:
gpio_virtuser_remove_lookup_table(dev);
err_remove_swnode:
@@ -1492,8 +1502,9 @@ gpio_virtuser_device_deactivate(struct gpio_virtuser_device *dev)
lockdep_assert_held(&dev->lock);
swnode = dev_fwnode(&dev->probe_data.pdev->dev);
dev_sync_probe_unregister(&dev->probe_data);
swnode = dev_fwnode(&dev->pdev->dev);
platform_device_unregister(dev->pdev);
dev->pdev = NULL;
gpio_virtuser_remove_lookup_table(dev);
fwnode_remove_software_node(swnode);
}
@@ -1723,7 +1734,6 @@ gpio_virtuser_config_make_device_group(struct config_group *group,
&gpio_virtuser_device_config_group_type);
mutex_init(&dev->lock);
INIT_LIST_HEAD(&dev->lookup_list);
dev_sync_probe_init(&dev->probe_data);
return &no_free_ptr(dev)->group;
}

View File

@@ -1220,75 +1220,6 @@ static void acpi_gpiochip_free_regions(struct acpi_gpio_chip *achip)
}
}
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;
device_for_each_child_node_scoped(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");
return;
}
}
}
void acpi_gpiochip_add(struct gpio_chip *chip)
{
struct acpi_gpio_chip *acpi_gpio;
@@ -1321,7 +1252,6 @@ void acpi_gpiochip_add(struct gpio_chip *chip)
}
acpi_gpiochip_request_regions(acpi_gpio);
acpi_gpiochip_scan_gpios(acpi_gpio);
acpi_dev_clear_dependencies(adev);
}

View File

@@ -1586,15 +1586,16 @@ static const struct file_operations line_fileops = {
#endif
};
DEFINE_FREE(linereq_free, struct linereq *, if (!IS_ERR_OR_NULL(_T)) linereq_free(_T))
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;
struct linereq *lr __free(linereq_free) = NULL;
u64 flags, edflags;
unsigned int i;
int fd, ret;
int ret;
if (copy_from_user(&ulr, ip, sizeof(ulr)))
return -EFAULT;
@@ -1627,10 +1628,8 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
/* 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;
}
if (!lr->label)
return -ENOMEM;
}
mutex_init(&lr->config_mutex);
@@ -1649,14 +1648,12 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
u32 offset = ulr.offsets[i];
struct gpio_desc *desc = gpio_device_get_desc(gdev, offset);
if (IS_ERR(desc)) {
ret = PTR_ERR(desc);
goto out_free_linereq;
}
if (IS_ERR(desc))
return PTR_ERR(desc);
ret = gpiod_request_user(desc, lr->label);
if (ret)
goto out_free_linereq;
return ret;
lr->lines[i].desc = desc;
flags = gpio_v2_line_config_flags(lc, i);
@@ -1664,7 +1661,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
ret = gpiod_set_transitory(desc, false);
if (ret < 0)
goto out_free_linereq;
return ret;
edflags = flags & GPIO_V2_LINE_EDGE_DETECTOR_FLAGS;
/*
@@ -1676,16 +1673,16 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
ret = gpiod_direction_output_nonotify(desc, val);
if (ret)
goto out_free_linereq;
return ret;
} else if (flags & GPIO_V2_LINE_FLAG_INPUT) {
ret = gpiod_direction_input_nonotify(desc);
if (ret)
goto out_free_linereq;
return ret;
ret = edge_detector_setup(&lr->lines[i], lc, i,
edflags);
if (ret)
goto out_free_linereq;
return ret;
}
lr->lines[i].edflags = edflags;
@@ -1700,44 +1697,25 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
ret = blocking_notifier_chain_register(&gdev->device_notifier,
&lr->device_unregistered_nb);
if (ret)
goto out_free_linereq;
return ret;
fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
if (fd < 0) {
ret = fd;
goto out_free_linereq;
}
FD_PREPARE(fdf, O_RDONLY | O_CLOEXEC,
anon_inode_getfile("gpio-line", &line_fileops, lr,
O_RDONLY | O_CLOEXEC));
if (fdf.err)
return fdf.err;
retain_and_null_ptr(lr);
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);
ulr.fd = fd_prepare_fd(fdf);
if (copy_to_user(ip, &ulr, sizeof(ulr)))
return -EFAULT;
}
fd_install(fd, file);
fd_publish(fdf);
dev_dbg(&gdev->dev, "registered chardev handle for %d lines\n",
lr->num_lines);
ulr.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
@@ -2010,16 +1988,16 @@ static irqreturn_t lineevent_irq_handler(int irq, void *p)
return IRQ_WAKE_THREAD;
}
DEFINE_FREE(lineevent_free, struct lineevent_state *, if (!IS_ERR_OR_NULL(_T)) lineevent_free(_T))
static int lineevent_create(struct gpio_device *gdev, void __user *ip)
{
struct gpioevent_request eventreq;
struct lineevent_state *le;
struct lineevent_state *le __free(lineevent_free) = NULL;
struct gpio_desc *desc;
struct file *file;
u32 offset;
u32 lflags;
u32 eflags;
int fd;
int ret;
int irq, irqflags = 0;
char *label;
@@ -2064,15 +2042,13 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
le->label = kstrndup(eventreq.consumer_label,
sizeof(eventreq.consumer_label) - 1,
GFP_KERNEL);
if (!le->label) {
ret = -ENOMEM;
goto out_free_le;
}
if (!le->label)
return -ENOMEM;
}
ret = gpiod_request_user(desc, le->label);
if (ret)
goto out_free_le;
return ret;
le->desc = desc;
le->eflags = eflags;
@@ -2080,15 +2056,13 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
ret = gpiod_direction_input(desc);
if (ret)
goto out_free_le;
return ret;
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 (irq <= 0)
return -ENODEV;
if (eflags & GPIOEVENT_REQUEST_RISING_EDGE)
irqflags |= test_bit(GPIOD_FLAG_ACTIVE_LOW, &desc->flags) ?
@@ -2105,13 +2079,11 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
ret = blocking_notifier_chain_register(&gdev->device_notifier,
&le->device_unregistered_nb);
if (ret)
goto out_free_le;
return ret;
label = make_irq_label(le->label);
if (IS_ERR(label)) {
ret = PTR_ERR(label);
goto out_free_le;
}
if (IS_ERR(label))
return PTR_ERR(label);
/* Request a thread to read the events */
ret = request_threaded_irq(irq,
@@ -2122,46 +2094,25 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
le);
if (ret) {
free_irq_label(label);
goto out_free_le;
return ret;
}
le->irq = irq;
fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
if (fd < 0) {
ret = fd;
goto out_free_le;
}
FD_PREPARE(fdf, O_RDONLY | O_CLOEXEC,
anon_inode_getfile("gpio-event", &lineevent_fileops, le,
O_RDONLY | O_CLOEXEC));
if (fdf.err)
return fdf.err;
retain_and_null_ptr(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);
eventreq.fd = fd_prepare_fd(fdf);
if (copy_to_user(ip, &eventreq, sizeof(eventreq)))
return -EFAULT;
}
fd_install(fd, file);
fd_publish(fdf);
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,
@@ -2689,12 +2640,6 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file)
struct gpio_chardev_data *cdev;
int ret = -ENOMEM;
guard(srcu)(&gdev->srcu);
/* Fail on open if the backing gpiochip is gone */
if (!rcu_access_pointer(gdev->chip))
return -ENODEV;
cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
if (!cdev)
return -ENOMEM;
@@ -2782,9 +2727,9 @@ static const struct file_operations gpio_fileops = {
#endif
};
int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt)
int gpiolib_cdev_register(struct gpio_chip *gc, dev_t devt)
{
struct gpio_chip *gc;
struct gpio_device *gdev = gc->gpiodev;
int ret;
cdev_init(&gdev->chrdev, &gpio_fileops);
@@ -2802,14 +2747,6 @@ int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt)
return ret;
}
guard(srcu)(&gdev->srcu);
gc = srcu_dereference(gdev->chip, &gdev->srcu);
if (!gc) {
cdev_device_del(&gdev->chrdev, &gdev->dev);
destroy_workqueue(gdev->line_state_wq);
return -ENODEV;
}
gpiochip_dbg(gc, "added GPIO chardev (%d:%d)\n", MAJOR(devt), gdev->id);
return 0;

View File

@@ -7,7 +7,7 @@
struct gpio_device;
int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt);
int gpiolib_cdev_register(struct gpio_chip *gc, dev_t devt);
void gpiolib_cdev_unregister(struct gpio_device *gdev);
#endif /* GPIOLIB_CDEV_H */

View File

@@ -10,11 +10,11 @@
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fwnode.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>
@@ -446,32 +446,6 @@ out:
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
*
* **DEPRECATED** This function is deprecated and must not be used in new code.
*
* 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)
{
@@ -542,6 +516,10 @@ static struct gpio_desc *of_find_gpio_rename(struct device_node *np,
{ "reset", "reset-n-io", "marvell,nfc-uart" },
{ "reset", "reset-n-io", "mrvl,nfc-uart" },
#endif
#if IS_ENABLED(CONFIG_NFC_S3FWRN5_I2C)
{ "en", "s3fwrn5,en-gpios", "samsung,s3fwrn5-i2c" },
{ "wake", "s3fwrn5,fw-gpios", "samsung,s3fwrn5-i2c" },
#endif
#if IS_ENABLED(CONFIG_PCI_LANTIQ)
/* MIPS Lantiq PCI */
{ "reset", "gpio-reset", "lantiq,pci-xway" },
@@ -735,139 +713,26 @@ struct gpio_desc *of_find_gpio(struct device_node *np, const char *con_id,
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)
int of_gpiochip_get_lflags(struct gpio_chip *chip,
struct fwnode_reference_args *gpiospec,
unsigned long *lflags)
{
struct device_node *chip_np;
enum of_gpio_flags xlate_flags;
struct of_phandle_args gpiospec;
struct of_phandle_args args;
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);
args.np = to_of_node(gpiospec->fwnode);
args.args_count = gpiospec->nargs;
xlate_flags = 0;
*lflags = GPIO_LOOKUP_FLAGS_DEFAULT;
*dflags = GPIOD_ASIS;
for (int i = 0; i < args.args_count; i++)
args.args[i] = gpiospec->args[i];
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);
desc = of_xlate_and_get_gpiod_flags(chip, &args, &xlate_flags);
if (IS_ERR(desc))
return desc;
return PTR_ERR(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:
* 0 on success, or negative errno on failure.
*/
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
WRITE_ONCE(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.
*
* Returns:
* 0 on success, or negative errno on failure.
*/
static int of_gpiochip_scan_gpios(struct gpio_chip *chip)
{
int ret;
for_each_available_child_of_node_scoped(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)
return ret;
of_node_set_flag(np, OF_POPULATED);
}
return 0;
}
@@ -922,7 +787,7 @@ static int of_gpio_notify(struct notifier_block *nb, unsigned long action,
if (!gdev)
return NOTIFY_DONE; /* not for us */
ret = of_gpiochip_add_hog(gpio_device_get_chip(gdev), rd->dn);
ret = gpiochip_add_hog(gpio_device_get_chip(gdev), of_fwnode_handle(rd->dn));
if (ret < 0) {
pr_err("%s: failed to add hogs for %pOF\n", __func__,
rd->dn);
@@ -1201,16 +1066,24 @@ int of_gpiochip_add(struct gpio_chip *chip)
of_node_get(np);
ret = of_gpiochip_scan_gpios(chip);
if (ret)
of_node_put(np);
for_each_available_child_of_node_scoped(np, child) {
if (of_property_read_bool(child, "gpio-hog"))
of_node_set_flag(child, OF_POPULATED);
}
return ret;
}
void of_gpiochip_remove(struct gpio_chip *chip)
{
of_node_put(dev_of_node(&chip->gpiodev->dev));
struct device_node *np = dev_of_node(&chip->gpiodev->dev);
for_each_child_of_node_scoped(np, child) {
if (of_property_present(child, "gpio-hog"))
of_node_clear_flag(child, OF_POPULATED);
}
of_node_put(np);
}
bool of_gpiochip_instance_match(struct gpio_chip *gc, unsigned int index)

View File

@@ -10,6 +10,7 @@
struct device_node;
struct fwnode_handle;
struct fwnode_reference_args;
struct gpio_chip;
struct gpio_desc;
@@ -24,6 +25,9 @@ int of_gpiochip_add(struct gpio_chip *gc);
void of_gpiochip_remove(struct gpio_chip *gc);
bool of_gpiochip_instance_match(struct gpio_chip *gc, unsigned int index);
int of_gpio_count(const struct fwnode_handle *fwnode, const char *con_id);
int of_gpiochip_get_lflags(struct gpio_chip *chip,
struct fwnode_reference_args *gpiospec,
unsigned long *lflags);
#else
static inline struct gpio_desc *of_find_gpio(struct device_node *np,
const char *con_id,
@@ -44,6 +48,12 @@ static inline int of_gpio_count(const struct fwnode_handle *fwnode,
{
return 0;
}
static inline int of_gpiochip_get_lflags(struct gpio_chip *chip,
struct fwnode_reference_args *gpiospec,
unsigned long *lflags)
{
return -ENOENT;
}
#endif /* CONFIG_OF_GPIO */
extern struct notifier_block gpio_of_notifier;

View File

@@ -93,6 +93,14 @@ struct gpio_desc *swnode_find_gpio(struct fwnode_handle *fwnode,
ret = swnode_gpio_get_reference(fwnode, propname, idx, &args);
if (ret == 0)
break;
if (ret == -ENOTCONN)
/*
* -ENOTCONN for a software node reference lookup means
* that a remote struct software_node exists but has
* not yet been registered as a firmware node. Defer
* until this happens.
*/
return ERR_PTR(-EPROBE_DEFER);
}
if (ret) {
pr_debug("%s: can't parse '%s' property of node '%pfwP[%d]'\n",

View File

@@ -983,10 +983,10 @@ void gpiod_unexport(struct gpio_desc *desc)
}
EXPORT_SYMBOL_GPL(gpiod_unexport);
int gpiochip_sysfs_register(struct gpio_device *gdev)
int gpiochip_sysfs_register(struct gpio_chip *gc)
{
struct gpio_device *gdev = gc->gpiodev;
struct gpiodev_data *data;
struct gpio_chip *chip;
struct device *parent;
int err;
@@ -999,18 +999,12 @@ int gpiochip_sysfs_register(struct gpio_device *gdev)
if (!class_is_registered(&gpio_class))
return 0;
guard(srcu)(&gdev->srcu);
chip = srcu_dereference(gdev->chip, &gdev->srcu);
if (!chip)
return -ENODEV;
/*
* 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;
if (gc->parent)
parent = gc->parent;
else
parent = &gdev->dev;
@@ -1029,7 +1023,7 @@ int gpiochip_sysfs_register(struct gpio_device *gdev)
MKDEV(0, 0), data,
gpiochip_groups,
GPIOCHIP_NAME "%d",
chip->base);
gc->base);
if (IS_ERR(data->cdev_base)) {
err = PTR_ERR(data->cdev_base);
kfree(data);
@@ -1053,11 +1047,11 @@ int gpiochip_sysfs_register(struct gpio_device *gdev)
return 0;
}
void gpiochip_sysfs_unregister(struct gpio_device *gdev)
void gpiochip_sysfs_unregister(struct gpio_chip *gc)
{
struct gpio_device *gdev = gc->gpiodev;
struct gpiodev_data *data;
struct gpio_desc *desc;
struct gpio_chip *chip;
guard(mutex)(&sysfs_lock);
@@ -1065,13 +1059,8 @@ void gpiochip_sysfs_unregister(struct gpio_device *gdev)
if (!data)
return;
guard(srcu)(&gdev->srcu);
chip = srcu_dereference(gdev->chip, &gdev->srcu);
if (!chip)
return;
/* unregister gpiod class devices owned by sysfs */
for_each_gpio_desc_with_flag(chip, desc, GPIOD_FLAG_SYSFS) {
for_each_gpio_desc_with_flag(gc, desc, GPIOD_FLAG_SYSFS) {
gpiod_unexport_unlocked(desc);
gpiod_free(desc);
}
@@ -1090,10 +1079,9 @@ void gpiochip_sysfs_unregister(struct gpio_device *gdev)
*/
static int gpiofind_sysfs_register(struct gpio_chip *gc, const void *data)
{
struct gpio_device *gdev = gc->gpiodev;
int ret;
ret = gpiochip_sysfs_register(gdev);
ret = gpiochip_sysfs_register(gc);
if (ret)
gpiochip_err(gc, "failed to register the sysfs entry: %d\n", ret);

View File

@@ -7,17 +7,17 @@ struct gpio_device;
#ifdef CONFIG_GPIO_SYSFS
int gpiochip_sysfs_register(struct gpio_device *gdev);
void gpiochip_sysfs_unregister(struct gpio_device *gdev);
int gpiochip_sysfs_register(struct gpio_chip *gc);
void gpiochip_sysfs_unregister(struct gpio_chip *gc);
#else
static inline int gpiochip_sysfs_register(struct gpio_device *gdev)
static inline int gpiochip_sysfs_register(struct gpio_chip *gc)
{
return 0;
}
static inline void gpiochip_sysfs_unregister(struct gpio_device *gdev)
static inline void gpiochip_sysfs_unregister(struct gpio_chip *gc)
{
}

View File

@@ -11,6 +11,7 @@
#include <linux/errno.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/fwnode.h>
#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
@@ -102,9 +103,6 @@ static DEFINE_MUTEX(gpio_devices_lock);
/* Ensures coherence during read-only accesses to the list of GPIO devices. */
DEFINE_STATIC_SRCU(gpio_devices_srcu);
static DEFINE_MUTEX(gpio_machine_hogs_mutex);
static LIST_HEAD(gpio_machine_hogs);
const char *const gpio_suffixes[] = { "gpios", "gpio", NULL };
static void gpiochip_free_hogs(struct gpio_chip *gc);
@@ -340,7 +338,15 @@ struct gpio_chip *gpio_device_get_chip(struct gpio_device *gdev)
}
EXPORT_SYMBOL_GPL(gpio_device_get_chip);
/* dynamic allocation of GPIOs, e.g. on a hotplugged device */
/**
* gpiochip_find_base_unlocked() - Find a global GPIO number base
* @ngpio: Number of consecutive GPIOs to number
*
* Finds and allocates a consecutive range of unsigned integers representing
* the GPIOs on the system. Using this numberspace outside of gpiolibs
* internals is STRONGLY DISCOURAGED, drivers and consumers should NOT concern
* themselves with this numberspace.
*/
static int gpiochip_find_base_unlocked(u16 ngpio)
{
unsigned int base = GPIO_DYNAMIC_BASE;
@@ -881,14 +887,14 @@ static const struct device_type gpio_dev_type = {
};
#ifdef CONFIG_GPIO_CDEV
#define gcdev_register(gdev, devt) gpiolib_cdev_register((gdev), (devt))
#define gcdev_register(gc, devt) gpiolib_cdev_register((gc), (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_register(gc, devt) device_add(&(gc)->gpiodev->dev)
#define gcdev_unregister(gdev) device_del(&(gdev)->dev)
#endif
@@ -896,8 +902,9 @@ static const struct device_type gpio_dev_type = {
* An initial reference count has been held in gpiochip_add_data_with_key().
* The caller should drop the reference via gpio_device_put() on errors.
*/
static int gpiochip_setup_dev(struct gpio_device *gdev)
static int gpiochip_setup_dev(struct gpio_chip *gc)
{
struct gpio_device *gdev = gc->gpiodev;
struct fwnode_handle *fwnode = dev_fwnode(&gdev->dev);
int ret;
@@ -908,11 +915,11 @@ static int gpiochip_setup_dev(struct gpio_device *gdev)
if (fwnode && !fwnode->dev)
fwnode_dev_initialized(fwnode, false);
ret = gcdev_register(gdev, gpio_devt);
ret = gcdev_register(gc, gpio_devt);
if (ret)
return ret;
ret = gpiochip_sysfs_register(gdev);
ret = gpiochip_sysfs_register(gc);
if (ret)
goto err_remove_device;
@@ -926,46 +933,131 @@ err_remove_device:
return ret;
}
static void gpiochip_machine_hog(struct gpio_chip *gc, struct gpiod_hog *hog)
int gpiochip_add_hog(struct gpio_chip *gc, struct fwnode_handle *fwnode)
{
struct fwnode_handle *gc_node = dev_fwnode(&gc->gpiodev->dev);
struct fwnode_reference_args gpiospec;
enum gpiod_flags dflags;
const char *name = NULL;
struct gpio_desc *desc;
int rv;
unsigned int num_hogs;
unsigned long lflags;
int ret, argc;
/*
* For devicetree-based systems, this needs to be defined in bindings
* and there's no real default value. For other firmware descriptions
* it makes the most sense to use 2 cells for the GPIO offset and
* request flags.
*/
u32 cells = 2;
desc = gpiochip_get_desc(gc, hog->chip_hwnum);
if (IS_ERR(desc)) {
gpiochip_err(gc, "%s: unable to get GPIO desc: %ld\n",
__func__, PTR_ERR(desc));
return;
lflags = GPIO_LOOKUP_FLAGS_DEFAULT;
dflags = GPIOD_ASIS;
name = NULL;
argc = fwnode_property_count_u32(fwnode, "gpios");
if (argc < 0)
return argc;
ret = fwnode_property_read_u32(gc_node, "#gpio-cells", &cells);
if (ret && is_of_node(fwnode))
return ret;
if (argc % cells)
return -EINVAL;
num_hogs = argc / cells;
u32 *gpios __free(kfree) = kzalloc_objs(*gpios, argc);
if (!gpios)
return -ENOMEM;
ret = fwnode_property_read_u32_array(fwnode, "gpios", gpios, argc);
if (ret < 0)
return ret;
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 -EINVAL;
fwnode_property_read_string(fwnode, "line-name", &name);
for (unsigned int i = 0; i < num_hogs; i++) {
if (is_of_node(fwnode)) {
/*
* OF-nodes need some additional special handling for
* translating of devicetree flags.
*/
memset(&gpiospec, 0, sizeof(gpiospec));
gpiospec.fwnode = fwnode;
gpiospec.nargs = cells;
for (unsigned int j = 0; j < cells; j++)
gpiospec.args[j] = gpios[i * cells + j];
ret = of_gpiochip_get_lflags(gc, &gpiospec, &lflags);
if (ret)
return ret;
} else {
/*
* GPIO_ACTIVE_LOW is currently the only lookup flag
* supported for non-OF firmware nodes.
*/
if (gpios[i * cells + 1])
lflags |= GPIO_ACTIVE_LOW;
}
desc = gpiochip_get_desc(gc, gpios[i * cells]);
if (IS_ERR(desc))
return PTR_ERR(desc);
ret = gpiod_hog(desc, name, lflags, dflags);
if (ret)
return ret;
}
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);
return 0;
}
static void machine_gpiochip_add(struct gpio_chip *gc)
static int gpiochip_hog_lines(struct gpio_chip *gc)
{
struct gpiod_hog *hog;
int ret;
guard(mutex)(&gpio_machine_hogs_mutex);
device_for_each_child_node_scoped(&gc->gpiodev->dev, fwnode) {
if (!fwnode_property_present(fwnode, "gpio-hog"))
continue;
list_for_each_entry(hog, &gpio_machine_hogs, list) {
if (!strcmp(gc->label, hog->chip_label))
gpiochip_machine_hog(gc, hog);
ret = gpiochip_add_hog(gc, fwnode);
if (ret)
return ret;
}
return 0;
}
static void gpiochip_setup_devs(void)
{
struct gpio_device *gdev;
struct gpio_chip *gc;
int ret;
guard(srcu)(&gpio_devices_srcu);
list_for_each_entry_srcu(gdev, &gpio_devices, list,
srcu_read_lock_held(&gpio_devices_srcu)) {
ret = gpiochip_setup_dev(gdev);
guard(srcu)(&gdev->srcu);
gc = srcu_dereference(gdev->chip, &gdev->srcu);
if (!gc) {
dev_err(&gdev->dev, "Underlying GPIO chip is gone\n");
continue;
}
ret = gpiochip_setup_dev(gc);
if (ret) {
gpio_device_put(gdev);
dev_err(&gdev->dev,
@@ -1197,7 +1289,9 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
acpi_gpiochip_add(gc);
machine_gpiochip_add(gc);
ret = gpiochip_hog_lines(gc);
if (ret)
goto err_remove_of_chip;
ret = gpiochip_irqchip_init_valid_mask(gc);
if (ret)
@@ -1224,7 +1318,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
* Otherwise, defer until later.
*/
if (gpiolib_initialized) {
ret = gpiochip_setup_dev(gdev);
ret = gpiochip_setup_dev(gc);
if (ret)
goto err_teardown_shared;
}
@@ -1284,7 +1378,7 @@ void gpiochip_remove(struct gpio_chip *gc)
struct gpio_device *gdev = gc->gpiodev;
/* FIXME: should the legacy sysfs handling be moved to gpio_device? */
gpiochip_sysfs_unregister(gdev);
gpiochip_sysfs_unregister(gc);
gpiochip_free_hogs(gc);
gpiochip_free_remaining_irqs(gc);
@@ -1389,7 +1483,16 @@ EXPORT_SYMBOL_GPL(gpio_device_find_by_label);
static int gpio_chip_match_by_fwnode(struct gpio_chip *gc, const void *fwnode)
{
return device_match_fwnode(&gc->gpiodev->dev, fwnode);
struct device *dev = &gc->gpiodev->dev;
struct fwnode_handle *node = dev_fwnode(dev);
if (IS_ERR(fwnode))
return 0;
if (device_match_fwnode(dev, fwnode))
return 1;
return node && node->secondary == fwnode;
}
/**
@@ -4469,42 +4572,6 @@ void gpiod_remove_lookup_table(struct gpiod_lookup_table *table)
}
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 gpiod_hog *hog;
guard(mutex)(&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.
*/
struct gpio_device *gdev __free(gpio_device_put) =
gpio_device_find_by_label(hog->chip_label);
if (gdev)
gpiochip_machine_hog(gpio_device_get_chip(gdev), hog);
}
}
EXPORT_SYMBOL_GPL(gpiod_add_hogs);
void gpiod_remove_hogs(struct gpiod_hog *hogs)
{
struct gpiod_hog *hog;
guard(mutex)(&gpio_machine_hogs_mutex);
for (hog = &hogs[0]; hog->chip_label; hog++)
list_del(&hog->list);
}
EXPORT_SYMBOL_GPL(gpiod_remove_hogs);
static bool gpiod_match_lookup_table(struct device *dev,
const struct gpiod_lookup_table *table)
{
@@ -4557,8 +4624,8 @@ static struct gpio_desc *gpio_desc_table_match(struct device *dev, const char *c
return desc;
}
dev_warn(dev, "cannot find GPIO line %s, deferring\n",
p->key);
dev_dbg(dev, "cannot find GPIO line %s, deferring\n",
p->key);
return ERR_PTR(-EPROBE_DEFER);
}
@@ -4572,8 +4639,8 @@ static struct gpio_desc *gpio_desc_table_match(struct device *dev, const char *c
* 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);
dev_dbg(dev, "cannot find GPIO chip %s, deferring\n",
p->key);
return ERR_PTR(-EPROBE_DEFER);
}
@@ -5317,23 +5384,14 @@ core_initcall(gpiolib_dev_init);
#ifdef CONFIG_DEBUG_FS
static void gpiolib_dbg_show(struct seq_file *s, struct gpio_device *gdev)
static void gpiolib_dbg_show(struct seq_file *s, struct gpio_chip *gc)
{
bool active_low, is_irq, is_out;
struct gpio_desc *desc;
unsigned int gpio = 0;
struct gpio_chip *gc;
unsigned long flags;
int value;
guard(srcu)(&gdev->srcu);
gc = srcu_dereference(gdev->chip, &gdev->srcu);
if (!gc) {
seq_puts(s, "Underlying GPIO chip is gone\n");
return;
}
for_each_gpio_desc(gc, desc) {
guard(srcu)(&desc->gdev->desc_srcu);
flags = READ_ONCE(desc->flags);
@@ -5446,7 +5504,7 @@ static int gpiolib_seq_show(struct seq_file *s, void *v)
if (gc->dbg_show)
gc->dbg_show(s, gc);
else
gpiolib_dbg_show(s, gdev);
gpiolib_dbg_show(s, gc);
return 0;
}

View File

@@ -17,11 +17,14 @@
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/srcu.h>
#include <linux/workqueue.h>
#define GPIOCHIP_NAME "gpiochip"
struct fwnode_handle;
/**
* struct gpio_device - internal state container for GPIO devices
* @dev: the GPIO device struct
@@ -107,7 +110,7 @@ extern const char *const gpio_suffixes[];
if (con_id) \
snprintf(propname, sizeof(propname), "%s-%s", con_id, __gs); \
else \
snprintf(propname, sizeof(propname), "%s", __gs); \
strscpy(propname, __gs); \
1; \
}); \
__suffixes++)
@@ -273,6 +276,7 @@ int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id,
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_add_hog(struct gpio_chip *gc, struct fwnode_handle *fwnode);
int gpiochip_get_ngpios(struct gpio_chip *gc, struct device *dev);
struct gpio_desc *gpiochip_get_desc(struct gpio_chip *gc, unsigned int hwnum);
const char *gpiod_get_label(struct gpio_desc *desc);

View File

@@ -6,9 +6,9 @@
*/
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/nfc.h>
#include <net/nfc/nci.h>
#include <net/nfc/nci_core.h>
@@ -112,13 +112,12 @@ struct nfcmrvl_private *nfcmrvl_nci_register_dev(enum nfcmrvl_phy phy,
memcpy(&priv->config, pdata, sizeof(*pdata));
if (gpio_is_valid(priv->config.reset_n_io)) {
rc = gpio_request_one(priv->config.reset_n_io,
GPIOF_OUT_INIT_LOW,
"nfcmrvl_reset_n");
if (rc < 0) {
priv->config.reset_n_io = -EINVAL;
nfc_err(dev, "failed to request reset_n io\n");
if (!priv->config.reset_gpio) {
priv->config.reset_gpio =
devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(priv->config.reset_gpio)) {
priv->config.reset_gpio = NULL;
nfc_err(dev, "failed to get reset gpio\n");
}
}
@@ -144,7 +143,7 @@ struct nfcmrvl_private *nfcmrvl_nci_register_dev(enum nfcmrvl_phy phy,
if (!priv->ndev) {
nfc_err(dev, "nci_allocate_device failed\n");
rc = -ENOMEM;
goto error_free_gpio;
goto error_free;
}
rc = nfcmrvl_fw_dnld_init(priv);
@@ -171,9 +170,7 @@ error_fw_dnld_deinit:
nfcmrvl_fw_dnld_deinit(priv);
error_free_dev:
nci_free_device(priv->ndev);
error_free_gpio:
if (gpio_is_valid(priv->config.reset_n_io))
gpio_free(priv->config.reset_n_io);
error_free:
kfree(priv);
return ERR_PTR(rc);
}
@@ -189,9 +186,6 @@ void nfcmrvl_nci_unregister_dev(struct nfcmrvl_private *priv)
nfcmrvl_fw_dnld_deinit(priv);
if (gpio_is_valid(priv->config.reset_n_io))
gpio_free(priv->config.reset_n_io);
nci_free_device(ndev);
kfree(priv);
}
@@ -233,34 +227,25 @@ void nfcmrvl_chip_reset(struct nfcmrvl_private *priv)
/* Reset possible fault of previous session */
clear_bit(NFCMRVL_PHY_ERROR, &priv->flags);
if (gpio_is_valid(priv->config.reset_n_io)) {
if (priv->config.reset_gpio) {
nfc_info(priv->dev, "reset the chip\n");
gpio_set_value(priv->config.reset_n_io, 0);
gpiod_set_value(priv->config.reset_gpio, 1);
usleep_range(5000, 10000);
gpio_set_value(priv->config.reset_n_io, 1);
gpiod_set_value(priv->config.reset_gpio, 0);
} else
nfc_info(priv->dev, "no reset available on this interface\n");
}
void nfcmrvl_chip_halt(struct nfcmrvl_private *priv)
{
if (gpio_is_valid(priv->config.reset_n_io))
gpio_set_value(priv->config.reset_n_io, 0);
if (priv->config.reset_gpio)
gpiod_set_value(priv->config.reset_gpio, 1);
}
int nfcmrvl_parse_dt(struct device_node *node,
struct nfcmrvl_platform_data *pdata)
{
int reset_n_io;
reset_n_io = of_get_named_gpio(node, "reset-n-io", 0);
if (reset_n_io < 0) {
pr_info("no reset-n-io config\n");
} else if (!gpio_is_valid(reset_n_io)) {
pr_err("invalid reset-n-io GPIO\n");
return reset_n_io;
}
pdata->reset_n_io = reset_n_io;
pdata->reset_gpio = NULL;
pdata->hci_muxed = of_property_read_bool(node, "hci-muxed");
return 0;

View File

@@ -10,6 +10,8 @@
#include "fw_dnld.h"
struct gpio_desc;
/* Define private flags: */
#define NFCMRVL_NCI_RUNNING 1
#define NFCMRVL_PHY_ERROR 2
@@ -54,7 +56,7 @@ struct nfcmrvl_platform_data {
*/
/* GPIO that is wired to RESET_N signal */
int reset_n_io;
struct gpio_desc *reset_gpio;
/* Tell if transport is muxed in HCI one */
bool hci_muxed;

View File

@@ -8,6 +8,7 @@
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/printk.h>
@@ -20,7 +21,6 @@
static unsigned int hci_muxed;
static unsigned int flow_control;
static unsigned int break_control;
static int reset_n_io = -EINVAL;
/*
* NFCMRVL NCI OPS
@@ -62,9 +62,11 @@ static const struct nfcmrvl_if_ops uart_ops = {
};
static int nfcmrvl_uart_parse_dt(struct device_node *node,
struct nfcmrvl_platform_data *pdata)
struct nfcmrvl_platform_data *pdata,
struct device *dev)
{
struct device_node *matched_node;
struct gpio_desc *reset_gpio;
int ret;
matched_node = of_get_compatible_child(node, "marvell,nfc-uart");
@@ -84,6 +86,16 @@ static int nfcmrvl_uart_parse_dt(struct device_node *node,
pdata->flow_control = of_property_read_bool(matched_node, "flow-control");
pdata->break_control = of_property_read_bool(matched_node, "break-control");
reset_gpio = devm_fwnode_gpiod_get_optional(dev,
of_fwnode_handle(matched_node),
"reset", GPIOD_OUT_HIGH,
"nfcmrvl_reset_n");
if (IS_ERR(reset_gpio)) {
of_node_put(matched_node);
return PTR_ERR(reset_gpio);
}
pdata->reset_gpio = reset_gpio;
of_node_put(matched_node);
return 0;
@@ -107,13 +119,13 @@ static int nfcmrvl_nci_uart_open(struct nci_uart *nu)
*/
if (dev && dev->parent && dev->parent->of_node)
if (nfcmrvl_uart_parse_dt(dev->parent->of_node, &config) == 0)
if (nfcmrvl_uart_parse_dt(dev->parent->of_node, &config, dev) == 0)
pdata = &config;
if (!pdata) {
pr_info("No platform data / DT -> fallback to module params\n");
config.hci_muxed = hci_muxed;
config.reset_n_io = reset_n_io;
config.reset_gpio = NULL;
config.flow_control = flow_control;
config.break_control = break_control;
pdata = &config;
@@ -201,6 +213,3 @@ MODULE_PARM_DESC(break_control, "Tell if UART driver must drive break signal.");
module_param(hci_muxed, uint, 0);
MODULE_PARM_DESC(hci_muxed, "Tell if transport is muxed in HCI one.");
module_param(reset_n_io, int, 0);
MODULE_PARM_DESC(reset_n_io, "GPIO that is wired to RESET_N signal.");

View File

@@ -294,7 +294,7 @@ static int nfcmrvl_probe(struct usb_interface *intf,
/* No configuration for USB */
memset(&config, 0, sizeof(config));
config.reset_n_io = -EINVAL;
config.reset_gpio = NULL;
nfc_info(&udev->dev, "intf %p id %p\n", intf, id);

View File

@@ -8,10 +8,8 @@
#include <linux/clk.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/module.h>
#include <net/nfc/nfc.h>
@@ -146,37 +144,6 @@ out:
return IRQ_HANDLED;
}
static int s3fwrn5_i2c_parse_dt(struct i2c_client *client)
{
struct s3fwrn5_i2c_phy *phy = i2c_get_clientdata(client);
struct device_node *np = client->dev.of_node;
if (!np)
return -ENODEV;
phy->common.gpio_en = of_get_named_gpio(np, "en-gpios", 0);
if (!gpio_is_valid(phy->common.gpio_en)) {
/* Support also deprecated property */
phy->common.gpio_en = of_get_named_gpio(np,
"s3fwrn5,en-gpios",
0);
if (!gpio_is_valid(phy->common.gpio_en))
return -ENODEV;
}
phy->common.gpio_fw_wake = of_get_named_gpio(np, "wake-gpios", 0);
if (!gpio_is_valid(phy->common.gpio_fw_wake)) {
/* Support also deprecated property */
phy->common.gpio_fw_wake = of_get_named_gpio(np,
"s3fwrn5,fw-gpios",
0);
if (!gpio_is_valid(phy->common.gpio_fw_wake))
return -ENODEV;
}
return 0;
}
static int s3fwrn5_i2c_probe(struct i2c_client *client)
{
struct s3fwrn5_i2c_phy *phy;
@@ -193,20 +160,13 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client)
phy->i2c_dev = client;
i2c_set_clientdata(client, phy);
ret = s3fwrn5_i2c_parse_dt(client);
if (ret < 0)
return ret;
phy->common.gpio_en = devm_gpiod_get(&client->dev, "en", GPIOD_OUT_HIGH);
if (IS_ERR(phy->common.gpio_en))
return PTR_ERR(phy->common.gpio_en);
ret = devm_gpio_request_one(&phy->i2c_dev->dev, phy->common.gpio_en,
GPIOF_OUT_INIT_HIGH, "s3fwrn5_en");
if (ret < 0)
return ret;
ret = devm_gpio_request_one(&phy->i2c_dev->dev,
phy->common.gpio_fw_wake,
GPIOF_OUT_INIT_LOW, "s3fwrn5_fw_wake");
if (ret < 0)
return ret;
phy->common.gpio_fw_wake = devm_gpiod_get(&client->dev, "wake", GPIOD_OUT_LOW);
if (IS_ERR(phy->common.gpio_fw_wake))
return PTR_ERR(phy->common.gpio_fw_wake);
/*
* S3FWRN5 depends on a clock input ("XI" pin) to function properly.

View File

@@ -8,7 +8,6 @@
* Bongsu Jeon <bongsu.jeon@samsung.com>
*/
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/module.h>
@@ -19,7 +18,7 @@ void s3fwrn5_phy_set_wake(void *phy_id, bool wake)
struct phy_common *phy = phy_id;
mutex_lock(&phy->mutex);
gpio_set_value(phy->gpio_fw_wake, wake);
gpiod_set_value(phy->gpio_fw_wake, wake);
if (wake)
msleep(S3FWRN5_EN_WAIT_TIME);
mutex_unlock(&phy->mutex);
@@ -33,14 +32,14 @@ bool s3fwrn5_phy_power_ctrl(struct phy_common *phy, enum s3fwrn5_mode mode)
phy->mode = mode;
gpio_set_value(phy->gpio_en, 1);
gpio_set_value(phy->gpio_fw_wake, 0);
gpiod_set_value(phy->gpio_en, 1);
gpiod_set_value(phy->gpio_fw_wake, 0);
if (mode == S3FWRN5_MODE_FW)
gpio_set_value(phy->gpio_fw_wake, 1);
gpiod_set_value(phy->gpio_fw_wake, 1);
if (mode != S3FWRN5_MODE_COLD) {
msleep(S3FWRN5_EN_WAIT_TIME);
gpio_set_value(phy->gpio_en, 0);
gpiod_set_value(phy->gpio_en, 0);
msleep(S3FWRN5_EN_WAIT_TIME);
}

View File

@@ -12,6 +12,7 @@
#define __NFC_S3FWRN5_PHY_COMMON_H
#include <linux/mutex.h>
#include <linux/gpio/consumer.h>
#include <net/nfc/nci_core.h>
#include "s3fwrn5.h"
@@ -21,8 +22,8 @@
struct phy_common {
struct nci_dev *ndev;
int gpio_en;
int gpio_fw_wake;
struct gpio_desc *gpio_en;
struct gpio_desc *gpio_fw_wake;
struct mutex mutex;

View File

@@ -10,13 +10,12 @@
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/nfc.h>
#include <linux/netdevice.h>
#include <linux/of.h>
#include <linux/serdev.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/gpio/consumer.h>
#include "phy_common.h"
@@ -92,25 +91,6 @@ static const struct of_device_id s3fwrn82_uart_of_match[] = {
};
MODULE_DEVICE_TABLE(of, s3fwrn82_uart_of_match);
static int s3fwrn82_uart_parse_dt(struct serdev_device *serdev)
{
struct s3fwrn82_uart_phy *phy = serdev_device_get_drvdata(serdev);
struct device_node *np = serdev->dev.of_node;
if (!np)
return -ENODEV;
phy->common.gpio_en = of_get_named_gpio(np, "en-gpios", 0);
if (!gpio_is_valid(phy->common.gpio_en))
return -ENODEV;
phy->common.gpio_fw_wake = of_get_named_gpio(np, "wake-gpios", 0);
if (!gpio_is_valid(phy->common.gpio_fw_wake))
return -ENODEV;
return 0;
}
static int s3fwrn82_uart_probe(struct serdev_device *serdev)
{
struct s3fwrn82_uart_phy *phy;
@@ -144,20 +124,17 @@ static int s3fwrn82_uart_probe(struct serdev_device *serdev)
serdev_device_set_flow_control(serdev, false);
ret = s3fwrn82_uart_parse_dt(serdev);
if (ret < 0)
phy->common.gpio_en = devm_gpiod_get(&serdev->dev, "en", GPIOD_OUT_HIGH);
if (IS_ERR(phy->common.gpio_en)) {
ret = PTR_ERR(phy->common.gpio_en);
goto err_serdev;
}
ret = devm_gpio_request_one(&phy->ser_dev->dev, phy->common.gpio_en,
GPIOF_OUT_INIT_HIGH, "s3fwrn82_en");
if (ret < 0)
goto err_serdev;
ret = devm_gpio_request_one(&phy->ser_dev->dev,
phy->common.gpio_fw_wake,
GPIOF_OUT_INIT_LOW, "s3fwrn82_fw_wake");
if (ret < 0)
phy->common.gpio_fw_wake = devm_gpiod_get(&serdev->dev, "wake", GPIOD_OUT_LOW);
if (IS_ERR(phy->common.gpio_fw_wake)) {
ret = PTR_ERR(phy->common.gpio_fw_wake);
goto err_serdev;
}
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->ser_dev->dev,
&uart_phy_ops);

View File

@@ -30,6 +30,7 @@
#include <linux/pinctrl/consumer.h>
#include <linux/pinctrl/devinfo.h>
#include <linux/pinctrl/machine.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinctrl.h>
#include "core.h"
@@ -938,6 +939,36 @@ int pinctrl_gpio_set_config(struct gpio_chip *gc, unsigned int offset,
}
EXPORT_SYMBOL_GPL(pinctrl_gpio_set_config);
/**
* pinctrl_gpio_get_config() - Get the config for a given GPIO pin
* @gc: GPIO chip structure from the GPIO subsystem
* @offset: hardware offset of the GPIO relative to the controller
* @config: the configuration to query. On success it holds the result
* Return: 0 on success, negative errno otherwise
*/
int pinctrl_gpio_get_config(struct gpio_chip *gc, unsigned int offset, unsigned long *config)
{
struct pinctrl_gpio_range *range;
struct pinctrl_dev *pctldev;
int ret, pin;
ret = pinctrl_get_device_gpio_range(gc, offset, &pctldev, &range);
if (ret)
return ret;
mutex_lock(&pctldev->mutex);
pin = gpio_to_pin(range, gc, offset);
ret = pin_config_get_for_pin(pctldev, pin, config);
mutex_unlock(&pctldev->mutex);
if (ret)
return ret;
*config = pinconf_to_config_argument(*config);
return 0;
}
EXPORT_SYMBOL_GPL(pinctrl_gpio_get_config);
static struct pinctrl_state *find_state(struct pinctrl *p,
const char *name)
{

View File

@@ -74,6 +74,12 @@ static inline int pinconf_set_config(struct pinctrl_dev *pctldev, unsigned int p
return -ENOTSUPP;
}
static inline int pin_config_get_for_pin(struct pinctrl_dev *pctldev, unsigned int pin,
unsigned long *config)
{
return -ENOTSUPP;
}
#endif
#if defined(CONFIG_PINCONF) && defined(CONFIG_DEBUG_FS)

View File

@@ -251,15 +251,9 @@ static int pinctrl_scmi_map_pinconf_type(enum pin_config_param param,
case PIN_CONFIG_MODE_LOW_POWER:
*type = SCMI_PIN_LOW_POWER_MODE;
break;
case PIN_CONFIG_LEVEL:
*type = SCMI_PIN_OUTPUT_VALUE;
break;
case PIN_CONFIG_OUTPUT_ENABLE:
*type = SCMI_PIN_OUTPUT_MODE;
break;
case PIN_CONFIG_OUTPUT_IMPEDANCE_OHMS:
*type = SCMI_PIN_OUTPUT_VALUE;
break;
case PIN_CONFIG_POWER_SOURCE:
*type = SCMI_PIN_POWER_SOURCE;
break;
@@ -276,6 +270,28 @@ static int pinctrl_scmi_map_pinconf_type(enum pin_config_param param,
return 0;
}
static int pinctrl_scmi_map_pinconf_type_get(enum pin_config_param param,
enum scmi_pinctrl_conf_type *type)
{
if (param == PIN_CONFIG_LEVEL) {
*type = SCMI_PIN_INPUT_VALUE;
return 0;
}
return pinctrl_scmi_map_pinconf_type(param, type);
}
static int pinctrl_scmi_map_pinconf_type_set(enum pin_config_param param,
enum scmi_pinctrl_conf_type *type)
{
if (param == PIN_CONFIG_LEVEL) {
*type = SCMI_PIN_OUTPUT_VALUE;
return 0;
}
return pinctrl_scmi_map_pinconf_type(param, type);
}
static int pinctrl_scmi_pinconf_get(struct pinctrl_dev *pctldev,
unsigned int pin, unsigned long *config)
{
@@ -290,7 +306,7 @@ static int pinctrl_scmi_pinconf_get(struct pinctrl_dev *pctldev,
config_type = pinconf_to_config_param(*config);
ret = pinctrl_scmi_map_pinconf_type(config_type, &type);
ret = pinctrl_scmi_map_pinconf_type_get(config_type, &type);
if (ret)
return ret;
@@ -345,7 +361,7 @@ static int pinctrl_scmi_pinconf_set(struct pinctrl_dev *pctldev,
unsigned long *configs,
unsigned int num_configs)
{
int i, ret;
int i, cnt, ret;
struct scmi_pinctrl *pmx = pinctrl_dev_get_drvdata(pctldev);
enum scmi_pinctrl_conf_type config_type[SCMI_NUM_CONFIGS];
u32 config_value[SCMI_NUM_CONFIGS];
@@ -361,17 +377,21 @@ static int pinctrl_scmi_pinconf_set(struct pinctrl_dev *pctldev,
if (ret)
return ret;
cnt = 0;
for (i = 0; i < num_configs; i++) {
param = pinconf_to_config_param(configs[i]);
ret = pinctrl_scmi_map_pinconf_type(param, &p_config_type[i]);
if (param == PIN_CONFIG_PERSIST_STATE)
continue;
ret = pinctrl_scmi_map_pinconf_type_set(param, &p_config_type[cnt]);
if (ret) {
dev_err(pmx->dev, "Error map pinconf_type %d\n", ret);
goto free_config;
}
p_config_value[i] = pinconf_to_config_argument(configs[i]);
p_config_value[cnt] = pinconf_to_config_argument(configs[i]);
cnt++;
}
ret = pinctrl_ops->settings_conf(pmx->ph, pin, PIN_TYPE, num_configs,
ret = pinctrl_ops->settings_conf(pmx->ph, pin, PIN_TYPE, cnt,
p_config_type, p_config_value);
if (ret)
dev_err(pmx->dev, "Error parsing config %d\n", ret);
@@ -405,7 +425,7 @@ static int pinctrl_scmi_pinconf_group_set(struct pinctrl_dev *pctldev,
for (i = 0; i < num_configs; i++) {
param = pinconf_to_config_param(configs[i]);
ret = pinctrl_scmi_map_pinconf_type(param, &p_config_type[i]);
ret = pinctrl_scmi_map_pinconf_type_set(param, &p_config_type[i]);
if (ret) {
dev_err(pmx->dev, "Error map pinconf_type %d\n", ret);
goto free_config;
@@ -440,7 +460,7 @@ static int pinctrl_scmi_pinconf_group_get(struct pinctrl_dev *pctldev,
return -EINVAL;
config_type = pinconf_to_config_param(*config);
ret = pinctrl_scmi_map_pinconf_type(config_type, &type);
ret = pinctrl_scmi_map_pinconf_type_get(config_type, &type);
if (ret) {
dev_err(pmx->dev, "Error map pinconf_type %d\n", ret);
return ret;

View File

@@ -1232,7 +1232,6 @@ config REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY
depends on ARM || ARM64 || COMPILE_TEST
depends on BACKLIGHT_CLASS_DEVICE
depends on I2C
depends on OF_GPIO
select REGMAP_I2C
help
This driver supports ATTINY regulator on the Raspberry Pi 7-inch
@@ -1332,7 +1331,6 @@ config REGULATOR_RT5133
depends on I2C && GPIOLIB && OF
select REGMAP
select CRC8
select OF_GPIO
help
This driver adds support for RT5133 PMIC regulators.
RT5133 is an integrated chip. It includes 8 LDOs and 3 GPOs that

View File

@@ -6,6 +6,8 @@
#include <linux/err.h>
#include <linux/types.h>
#include "defs.h"
struct acpi_device;
struct device;
struct fwnode_handle;

View File

@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __LINUX_GPIO_DEFS_H
#define __LINUX_GPIO_DEFS_H
#define GPIO_LINE_DIRECTION_IN 1
#define GPIO_LINE_DIRECTION_OUT 0
#endif /* __LINUX_GPIO_DEFS_H */

View File

@@ -20,6 +20,8 @@
#include <asm/msi.h>
#endif
#include "defs.h"
struct device;
struct irq_chip;
struct irq_data;
@@ -42,9 +44,6 @@ union gpio_irq_fwspec {
#endif
};
#define GPIO_LINE_DIRECTION_IN 1
#define GPIO_LINE_DIRECTION_OUT 0
/**
* struct gpio_irq_chip - GPIO interrupt controller
*/
@@ -344,11 +343,17 @@ struct gpio_irq_chip {
* @direction_output: configures signal "offset" as output, returns 0 on
* success or a negative error number. This can be omitted on input-only
* or output-only gpio chips.
* @get: returns value for signal "offset", 0=low, 1=high, or negative error
* @get: returns value for signal "offset", 0=low, 1=high, or negative error.
* the low and high values are defined as physical low on the line
* in/out to the connector such as a physical pad, pin or rail. The GPIO
* library has internal logic to handle lines that are active low, such
* as indicated by overstrike or #name in a schematic, and the driver
* should not try to second-guess the logic value of a line.
* @get_multiple: reads values for multiple signals defined by "mask" and
* stores them in "bits", returns 0 on success or negative error
* @set: assigns output value for signal "offset", returns 0 on success or
* negative error value
* negative error value. The output value follows the same semantic
* rules as for @get.
* @set_multiple: assigns output values for multiple signals defined by
* "mask", returns 0 on success or negative error value
* @set_config: optional hook for all kinds of settings. Uses the same

View File

@@ -3,9 +3,15 @@
#ifndef __LINUX_GPIO_GENERIC_H
#define __LINUX_GPIO_GENERIC_H
#include <linux/bits.h>
#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/gpio/driver.h>
#include <linux/container_of.h>
#include <linux/errno.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/gpio/driver.h>
struct device;

View File

@@ -46,23 +46,6 @@ struct gpiod_lookup_table {
struct gpiod_lookup table[];
};
/**
* struct gpiod_hog - GPIO line hog table
* @chip_label: name of the chip the GPIO belongs to
* @chip_hwnum: hardware number (i.e. relative to the chip) of the GPIO
* @line_name: consumer name for the hogged line
* @lflags: bitmask of gpio_lookup_flags GPIO_* values
* @dflags: GPIO flags used to specify the direction and value
*/
struct gpiod_hog {
struct list_head list;
const char *chip_label;
u16 chip_hwnum;
const char *line_name;
unsigned long lflags;
int dflags;
};
/*
* Helper for lookup tables with just one single lookup for a device.
*/
@@ -95,24 +78,10 @@ static struct gpiod_lookup_table _name = { \
.flags = _flags, \
}
/*
* Simple definition of a single GPIO hog in an array.
*/
#define GPIO_HOG(_chip_label, _chip_hwnum, _line_name, _lflags, _dflags) \
(struct gpiod_hog) { \
.chip_label = _chip_label, \
.chip_hwnum = _chip_hwnum, \
.line_name = _line_name, \
.lflags = _lflags, \
.dflags = _dflags, \
}
#ifdef CONFIG_GPIOLIB
void gpiod_add_lookup_table(struct gpiod_lookup_table *table);
void gpiod_add_lookup_tables(struct gpiod_lookup_table **tables, size_t n);
void gpiod_remove_lookup_table(struct gpiod_lookup_table *table);
void gpiod_add_hogs(struct gpiod_hog *hogs);
void gpiod_remove_hogs(struct gpiod_hog *hogs);
#else /* ! CONFIG_GPIOLIB */
static inline
void gpiod_add_lookup_table(struct gpiod_lookup_table *table) {}
@@ -120,8 +89,6 @@ static inline
void gpiod_add_lookup_tables(struct gpiod_lookup_table **tables, size_t n) {}
static inline
void gpiod_remove_lookup_table(struct gpiod_lookup_table *table) {}
static inline void gpiod_add_hogs(struct gpiod_hog *hogs) {}
static inline void gpiod_remove_hogs(struct gpiod_hog *hogs) {}
#endif /* CONFIG_GPIOLIB */
#endif /* __LINUX_GPIO_MACHINE_H */

View File

@@ -37,6 +37,7 @@
#define KEMPLD_SPEC_GET_MINOR(x) (x & 0x0f)
#define KEMPLD_SPEC_GET_MAJOR(x) ((x >> 4) & 0x0f)
#define KEMPLD_IRQ_GPIO 0x35
#define KEMPLD_IRQ_GPIO_MASK 0x0f
#define KEMPLD_IRQ_I2C 0x36
#define KEMPLD_CFG 0x37
#define KEMPLD_CFG_GPIO_I2C_MUX (1 << 0)

View File

@@ -1,38 +0,0 @@
/* 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>
*/
#ifndef __LINUX_OF_GPIO_H
#define __LINUX_OF_GPIO_H
#include <linux/compiler.h>
#include <linux/gpio/driver.h>
#include <linux/gpio.h> /* FIXME: Shouldn't be here */
#include <linux/of.h>
struct device_node;
#ifdef CONFIG_OF_GPIO
extern int of_get_named_gpio(const struct device_node *np,
const char *list_name, int index);
#else /* CONFIG_OF_GPIO */
#include <linux/errno.h>
/* Drivers may not strictly depend on the GPIO support, so let them link. */
static inline int of_get_named_gpio(const struct device_node *np,
const char *propname, int index)
{
return -ENOSYS;
}
#endif /* CONFIG_OF_GPIO */
#endif /* __LINUX_OF_GPIO_H */

View File

@@ -35,6 +35,8 @@ int pinctrl_gpio_direction_output(struct gpio_chip *gc,
unsigned int offset);
int pinctrl_gpio_set_config(struct gpio_chip *gc, unsigned int offset,
unsigned long config);
int pinctrl_gpio_get_config(struct gpio_chip *gc, unsigned int offset,
unsigned long *config);
struct pinctrl * __must_check pinctrl_get(struct device *dev);
void pinctrl_put(struct pinctrl *p);
@@ -101,6 +103,13 @@ pinctrl_gpio_direction_output(struct gpio_chip *gc, unsigned int offset)
return 0;
}
static inline int
pinctrl_gpio_get_config(struct gpio_chip *gc, unsigned int offset,
unsigned long *config)
{
return 0;
}
static inline int
pinctrl_gpio_set_config(struct gpio_chip *gc, unsigned int offset,
unsigned long config)

View File

@@ -1,8 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
TEST_PROGS := gpio-mockup.sh gpio-sim.sh gpio-aggregator.sh
TEST_PROGS := gpio-mockup.sh gpio-sim.sh gpio-aggregator.sh gpio-cdev-uaf.sh
TEST_FILES := gpio-mockup-sysfs.sh
TEST_GEN_PROGS_EXTENDED := gpio-mockup-cdev gpio-chip-info gpio-line-name
TEST_GEN_PROGS_EXTENDED := gpio-mockup-cdev gpio-chip-info gpio-line-name \
gpio-cdev-uaf
CFLAGS += -O2 -g -Wall $(KHDR_INCLUDES)
include ../lib.mk

View File

@@ -0,0 +1,292 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* GPIO character device helper for UAF tests.
*
* Copyright 2026 Google LLC
*/
#include <errno.h>
#include <fcntl.h>
#include <linux/gpio.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define CONFIGFS_DIR "/sys/kernel/config/gpio-sim"
#define PROCFS_DIR "/proc"
static void print_usage(void)
{
printf("usage:\n");
printf(" gpio-cdev-uaf [chip|handle|event|req] [poll|read|ioctl]\n");
}
static int _create_chip(const char *name, int create)
{
char path[64];
snprintf(path, sizeof(path), CONFIGFS_DIR "/%s", name);
if (create)
return mkdir(path, 0755);
else
return rmdir(path);
}
static int create_chip(const char *name)
{
return _create_chip(name, 1);
}
static void remove_chip(const char *name)
{
_create_chip(name, 0);
}
static int _create_bank(const char *chip_name, const char *name, int create)
{
char path[64];
snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/%s", chip_name, name);
if (create)
return mkdir(path, 0755);
else
return rmdir(path);
}
static int create_bank(const char *chip_name, const char *name)
{
return _create_bank(chip_name, name, 1);
}
static void remove_bank(const char *chip_name, const char *name)
{
_create_bank(chip_name, name, 0);
}
static int _enable_chip(const char *name, int enable)
{
char path[64];
int fd, ret;
snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/live", name);
fd = open(path, O_WRONLY);
if (fd == -1)
return fd;
if (enable)
ret = write(fd, "1", 1);
else
ret = write(fd, "0", 1);
close(fd);
return ret == 1 ? 0 : -1;
}
static int enable_chip(const char *name)
{
return _enable_chip(name, 1);
}
static void disable_chip(const char *name)
{
_enable_chip(name, 0);
}
static int open_chip(const char *chip_name, const char *bank_name)
{
char path[64], dev_name[32];
int ret, fd;
ret = create_chip(chip_name);
if (ret) {
fprintf(stderr, "failed to create chip\n");
return ret;
}
ret = create_bank(chip_name, bank_name);
if (ret) {
fprintf(stderr, "failed to create bank\n");
goto err_remove_chip;
}
ret = enable_chip(chip_name);
if (ret) {
fprintf(stderr, "failed to enable chip\n");
goto err_remove_bank;
}
snprintf(path, sizeof(path), CONFIGFS_DIR "/%s/%s/chip_name",
chip_name, bank_name);
fd = open(path, O_RDONLY);
if (fd == -1) {
ret = fd;
fprintf(stderr, "failed to open %s\n", path);
goto err_disable_chip;
}
ret = read(fd, dev_name, sizeof(dev_name) - 1);
close(fd);
if (ret == -1) {
fprintf(stderr, "failed to read %s\n", path);
goto err_disable_chip;
}
dev_name[ret] = '\0';
if (ret && dev_name[ret - 1] == '\n')
dev_name[ret - 1] = '\0';
snprintf(path, sizeof(path), "/dev/%s", dev_name);
fd = open(path, O_RDWR);
if (fd == -1) {
ret = fd;
fprintf(stderr, "failed to open %s\n", path);
goto err_disable_chip;
}
return fd;
err_disable_chip:
disable_chip(chip_name);
err_remove_bank:
remove_bank(chip_name, bank_name);
err_remove_chip:
remove_chip(chip_name);
return ret;
}
static void close_chip(const char *chip_name, const char *bank_name)
{
disable_chip(chip_name);
remove_bank(chip_name, bank_name);
remove_chip(chip_name);
}
static int test_poll(int fd)
{
struct pollfd pfds;
pfds.fd = fd;
pfds.events = POLLIN;
pfds.revents = 0;
if (poll(&pfds, 1, 0) == -1)
return -1;
return (pfds.revents & ~(POLLHUP | POLLERR)) ? -1 : 0;
}
static int test_read(int fd)
{
char data;
if (read(fd, &data, 1) == -1 && errno == ENODEV)
return 0;
return -1;
}
static int test_ioctl(int fd)
{
if (ioctl(fd, 0, NULL) == -1 && errno == ENODEV)
return 0;
return -1;
}
int main(int argc, char **argv)
{
int cfd, fd, ret;
int (*test_func)(int);
if (argc != 3) {
print_usage();
return EXIT_FAILURE;
}
if (strcmp(argv[1], "chip") == 0 ||
strcmp(argv[1], "event") == 0 ||
strcmp(argv[1], "req") == 0) {
if (strcmp(argv[2], "poll") &&
strcmp(argv[2], "read") &&
strcmp(argv[2], "ioctl")) {
fprintf(stderr, "unknown command: %s\n", argv[2]);
return EXIT_FAILURE;
}
} else if (strcmp(argv[1], "handle") == 0) {
if (strcmp(argv[2], "ioctl")) {
fprintf(stderr, "unknown command: %s\n", argv[2]);
return EXIT_FAILURE;
}
} else {
fprintf(stderr, "unknown command: %s\n", argv[1]);
return EXIT_FAILURE;
}
if (strcmp(argv[2], "poll") == 0)
test_func = test_poll;
else if (strcmp(argv[2], "read") == 0)
test_func = test_read;
else /* strcmp(argv[2], "ioctl") == 0 */
test_func = test_ioctl;
cfd = open_chip("chip", "bank");
if (cfd == -1) {
fprintf(stderr, "failed to open chip\n");
return EXIT_FAILURE;
}
/* Step 1: Hold a FD to the test target. */
if (strcmp(argv[1], "chip") == 0) {
fd = cfd;
} else if (strcmp(argv[1], "handle") == 0) {
struct gpiohandle_request req = {0};
req.lines = 1;
if (ioctl(cfd, GPIO_GET_LINEHANDLE_IOCTL, &req) == -1) {
fprintf(stderr, "failed to get handle FD\n");
goto err_close_chip;
}
close(cfd);
fd = req.fd;
} else if (strcmp(argv[1], "event") == 0) {
struct gpioevent_request req = {0};
if (ioctl(cfd, GPIO_GET_LINEEVENT_IOCTL, &req) == -1) {
fprintf(stderr, "failed to get event FD\n");
goto err_close_chip;
}
close(cfd);
fd = req.fd;
} else { /* strcmp(argv[1], "req") == 0 */
struct gpio_v2_line_request req = {0};
req.num_lines = 1;
if (ioctl(cfd, GPIO_V2_GET_LINE_IOCTL, &req) == -1) {
fprintf(stderr, "failed to get req FD\n");
goto err_close_chip;
}
close(cfd);
fd = req.fd;
}
/* Step 2: Free the chip. */
close_chip("chip", "bank");
/* Step 3: Access the dangling FD to trigger UAF. */
ret = test_func(fd);
close(fd);
return ret ? EXIT_FAILURE : EXIT_SUCCESS;
err_close_chip:
close(cfd);
close_chip("chip", "bank");
return EXIT_FAILURE;
}

View File

@@ -0,0 +1,63 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright 2026 Google LLC
BASE_DIR=`dirname $0`
MODULE="gpio-cdev-uaf"
fail() {
echo "$*" >&2
echo "GPIO $MODULE test FAIL"
exit 1
}
skip() {
echo "$*" >&2
echo "GPIO $MODULE test SKIP"
exit 4
}
# Load the gpio-sim module. This will pull in configfs if needed too.
modprobe gpio-sim || skip "unable to load the gpio-sim module"
# Make sure configfs is mounted at /sys/kernel/config. Wait a bit if needed.
for _ in `seq 5`; do
mountpoint -q /sys/kernel/config && break
mount -t configfs none /sys/kernel/config
sleep 0.1
done
mountpoint -q /sys/kernel/config || \
skip "configfs not mounted at /sys/kernel/config"
echo "1. GPIO"
echo "1.1. poll"
$BASE_DIR/gpio-cdev-uaf chip poll || fail "failed to test chip poll"
echo "1.2. read"
$BASE_DIR/gpio-cdev-uaf chip read || fail "failed to test chip read"
echo "1.3. ioctl"
$BASE_DIR/gpio-cdev-uaf chip ioctl || fail "failed to test chip ioctl"
echo "2. linehandle"
echo "2.1. ioctl"
$BASE_DIR/gpio-cdev-uaf handle ioctl || fail "failed to test handle ioctl"
echo "3. lineevent"
echo "3.1. read"
$BASE_DIR/gpio-cdev-uaf event read || fail "failed to test event read"
echo "3.2. poll"
$BASE_DIR/gpio-cdev-uaf event poll || fail "failed to test event poll"
echo "3.3. ioctl"
$BASE_DIR/gpio-cdev-uaf event ioctl || fail "failed to test event ioctl"
echo "4. linereq"
echo "4.1. read"
$BASE_DIR/gpio-cdev-uaf req read || fail "failed to test req read"
echo "4.2. poll"
$BASE_DIR/gpio-cdev-uaf req poll || fail "failed to test req poll"
echo "4.3. ioctl"
$BASE_DIR/gpio-cdev-uaf req ioctl || fail "failed to test req ioctl"
echo "GPIO $MODULE test PASS"