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

Pull gpio updates from Bartosz Golaszewski:
 "We have three new drivers, some refactoring in the GPIO core, lots of
  various changes across many drivers, new configfs interface for the
  virtual gpio-aggregator module and DT-bindings updates.

  The treewide conversion of GPIO drivers to using the new value setter
  callbacks is ongoing with another round of GPIO drivers updated. You
  will also see these commits coming in from other subsystems as with
  the relevant changes merged into mainline last cycle, I've started
  converting GPIO providers located elsewhere than drivers/gpio/.

  GPIO core:
   - use more lock guards where applicable
   - refactor GPIO ACPI code and shrink it in the process by 8%
   - move GPIO ACPI quirks into a separate file
   - remove unneeded #ifdef
   - convert GPIO devres helpers to using devm_add_action() where
     applicable which shrinks and simplifies the code
   - refactor GPIO descriptor validation in GPIO consumer interfaces
   - don't allow setting values on input lines in the GPIO core which
     will take off the burden from GPIO drivers of checking this down
     the line
   - provide gpiod_is_equal() as a way of safely comparing two GPIO
     descriptors (the only current user is in regulator core)

  New drivers:
   - add the GPIO module for the max77759 multifunction device
   - add the GPIO driver for the VeriSilicon BLZP1600 GPIO controller
   - add the GPIO driver for the Spacemit K1 SoC

  Driver improvements:
   - convert more drivers to using the new GPIO line value setter
     callbacks
   - convert more drivers to making the irq_chip immutable as is
     recommended by the interrupt subsystem
   - extend build testing coverage by enabling more modules to be built
     with COMPILE_TEST=y
   - extend the gpio-aggregator module with a configfs interface that
     makes the setup easier for user-space than the existing
     driver-level sysfs attributes and also adds more advanced
     configuration features (such as referring to aggregated lines by
     their original names or modifying their names as exposed by the
     aggregated chip)
   - add a missing mutex_destroy() in gpio-imx-scu
   - add an OF polarity quirk for s5m8767
   - allow building gpio-vf610 as a loadable module
   - make gpio-mxc not hardcode its GPIO base number with GPIO SYSFS
     interface disabled (another small step towards getting rid of the
     global GPIO numberspace)
   - add support for level-triggered interrupts to gpio-pca953x
   - don't double-check the ngpios property in gpio-ds4520 as GPIO core
     already does it
   - don't double-check the number of GPIOs in gpio-imx-scu as GPIO core
     already does it
   - remove unused callbacks from gpio-max3191x

  DT bindings:
   - add device-tree bindings for max77759, spacemit,k1 and blzp1600
     (new drivers added this cycle)
   - document more properties for gpio-vf610 and gpio-tegra186
   - document a new pca95xx variant
   - fix style of examples in several GPIO DT-binding documents

  Misc:
   - TODO list updates"

* tag 'gpio-updates-for-v6.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux: (123 commits)
  gpio: timberdale: select GPIOLIB_IRQCHIP
  gpio: lpc18xx: select GPIOLIB_IRQCHIP
  gpio: grgpio: select GPIOLIB_IRQCHIP
  gpio: bcm-kona: select GPIOLIB_IRQCHIP
  dt-bindings: gpio: vf610: add ngpios and gpio-reserved-ranges
  gpio: davinci: select GPIOLIB_IRQCHIP
  gpiolib-acpi: Update file references in the Documentation and MAINTAINERS
  gpiolib: acpi: Move quirks to a separate file
  gpiolib: acpi: Add acpi_gpio_need_run_edge_events_on_boot() getter
  gpiolib: acpi: Handle deferred list via new API
  gpiolib: acpi: Make sure we fill struct acpi_gpio_info
  gpiolib: acpi: Switch to use enum in acpi_gpio_in_ignore_list()
  gpiolib: acpi: Use temporary variable for struct acpi_gpio_info
  gpiolib: remove unneeded #ifdef
  gpio: mpc8xxx: select GPIOLIB_IRQCHIP
  gpio: pxa: select GPIOLIB_IRQCHIP
  gpio: pxa: Make irq_chip immutable
  gpio: timberdale: Make irq_chip immutable
  gpio: xgene-sb: Make irq_chip immutable
  gpio: davinci: Make irq_chip immutable
  ...
This commit is contained in:
Linus Torvalds
2025-05-27 15:22:01 -07:00
94 changed files with 5509 additions and 1130 deletions

View File

@@ -69,6 +69,113 @@ write-only attribute files in sysfs.
$ echo gpio-aggregator.0 > delete_device
Aggregating GPIOs using Configfs
--------------------------------
**Group:** ``/config/gpio-aggregator``
This is the root directory of the gpio-aggregator configfs tree.
**Group:** ``/config/gpio-aggregator/<example-name>``
This directory represents a GPIO aggregator device. You can assign any
name to ``<example-name>`` (e.g. ``agg0``), except names starting with
``_sysfs`` prefix, which are reserved for auto-generated configfs
entries corresponding to devices created via Sysfs.
**Attribute:** ``/config/gpio-aggregator/<example-name>/live``
The ``live`` attribute allows to trigger the actual creation of the device
once it's fully configured. Accepted values are:
* ``1``, ``yes``, ``true`` : enable the virtual device
* ``0``, ``no``, ``false`` : disable the virtual device
**Attribute:** ``/config/gpio-aggregator/<example-name>/dev_name``
The read-only ``dev_name`` attribute exposes the name of the device as it
will appear in the system on the platform bus (e.g. ``gpio-aggregator.0``).
This is useful for identifying a character device for the newly created
aggregator. If it's ``gpio-aggregator.0``,
``/sys/devices/platform/gpio-aggregator.0/gpiochipX`` path tells you that the
GPIO device id is ``X``.
You must create subdirectories for each virtual line you want to
instantiate, named exactly as ``line0``, ``line1``, ..., ``lineY``, when
you want to instantiate ``Y+1`` (Y >= 0) lines. Configure all lines before
activating the device by setting ``live`` to 1.
**Group:** ``/config/gpio-aggregator/<example-name>/<lineY>/``
This directory represents a GPIO line to include in the aggregator.
**Attribute:** ``/config/gpio-aggregator/<example-name>/<lineY>/key``
**Attribute:** ``/config/gpio-aggregator/<example-name>/<lineY>/offset``
The default values after creating the ``<lineY>`` directory are:
* ``key`` : <empty>
* ``offset`` : -1
``key`` must always be explicitly configured, while ``offset`` depends.
Two configuration patterns exist for each ``<lineY>``:
(a). For lookup by GPIO line name:
* Set ``key`` to the line name.
* Ensure ``offset`` remains -1 (the default).
(b). For lookup by GPIO chip name and the line offset within the chip:
* Set ``key`` to the chip name.
* Set ``offset`` to the line offset (0 <= ``offset`` < 65535).
**Attribute:** ``/config/gpio-aggregator/<example-name>/<lineY>/name``
The ``name`` attribute sets a custom name for lineY. If left unset, the
line will remain unnamed.
Once the configuration is done, the ``'live'`` attribute must be set to 1
in order to instantiate the aggregator device. It can be set back to 0 to
destroy the virtual device. The module will synchronously wait for the new
aggregator device to be successfully probed and if this doesn't happen, writing
to ``'live'`` will result in an error. This is a different behaviour from the
case when you create it using sysfs ``new_device`` interface.
.. note::
For aggregators created via Sysfs, the configfs entries are
auto-generated and appear as ``/config/gpio-aggregator/_sysfs.<N>/``. You
cannot add or remove line directories with mkdir(2)/rmdir(2). To modify
lines, you must use the "delete_device" interface to tear down the
existing device and reconfigure it from scratch. However, you can still
toggle the aggregator with the ``live`` attribute and adjust the
``key``, ``offset``, and ``name`` attributes for each line when ``live``
is set to 0 by hand (i.e. it's not waiting for deferred probe).
Sample configuration commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: sh
# Create a directory for an aggregator device
$ mkdir /sys/kernel/config/gpio-aggregator/agg0
# Configure each line
$ mkdir /sys/kernel/config/gpio-aggregator/agg0/line0
$ echo gpiochip0 > /sys/kernel/config/gpio-aggregator/agg0/line0/key
$ echo 6 > /sys/kernel/config/gpio-aggregator/agg0/line0/offset
$ echo test0 > /sys/kernel/config/gpio-aggregator/agg0/line0/name
$ mkdir /sys/kernel/config/gpio-aggregator/agg0/line1
$ echo gpiochip0 > /sys/kernel/config/gpio-aggregator/agg0/line1/key
$ echo 7 > /sys/kernel/config/gpio-aggregator/agg0/line1/offset
$ echo test1 > /sys/kernel/config/gpio-aggregator/agg0/line1/name
# Activate the aggregator device
$ echo 1 > /sys/kernel/config/gpio-aggregator/agg0/live
Generic GPIO Driver
-------------------

View File

@@ -69,13 +69,13 @@ examples:
#include <dt-bindings/interrupt-controller/irq.h>
gpio@fffff400 {
compatible = "atmel,at91rm9200-gpio";
reg = <0xfffff400 0x200>;
interrupts = <2 IRQ_TYPE_LEVEL_HIGH 1>;
#gpio-cells = <2>;
gpio-controller;
interrupt-controller;
#interrupt-cells = <2>;
clocks = <&pmc PMC_TYPE_PERIPHERAL 2>;
compatible = "atmel,at91rm9200-gpio";
reg = <0xfffff400 0x200>;
interrupts = <2 IRQ_TYPE_LEVEL_HIGH 1>;
#gpio-cells = <2>;
gpio-controller;
interrupt-controller;
#interrupt-cells = <2>;
clocks = <&pmc PMC_TYPE_PERIPHERAL 2>;
};
...

View File

@@ -0,0 +1,77 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/gpio/blaize,blzp1600-gpio.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Blaize BLZP1600 GPIO controller
description:
Blaize BLZP1600 GPIO controller is an implementation of the VeriSilicon
APB GPIO v0.2 IP block. It has 32 ports each of which are intended to be
represented as child nodes with the generic GPIO-controller properties
as described in this binding's file.
maintainers:
- Nikolaos Pasaloukos <nikolaos.pasaloukos@blaize.com>
- James Cowgill <james.cowgill@blaize.com>
- Matt Redfearn <matt.redfearn@blaize.com>
- Neil Jones <neil.jones@blaize.com>
properties:
$nodename:
pattern: "^gpio@[0-9a-f]+$"
compatible:
enum:
- blaize,blzp1600-gpio
reg:
maxItems: 1
gpio-controller: true
'#gpio-cells':
const: 2
ngpios:
default: 32
minimum: 1
maximum: 32
interrupts:
maxItems: 1
gpio-line-names: true
interrupt-controller: true
'#interrupt-cells':
const: 2
required:
- compatible
- reg
- gpio-controller
- '#gpio-cells'
dependencies:
interrupt-controller: [ interrupts ]
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
gpio: gpio@4c0000 {
compatible = "blaize,blzp1600-gpio";
reg = <0x004c0000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <32>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>;
};
...

View File

@@ -71,15 +71,15 @@ unevaluatedProperties: false
examples:
- |
spi {
#address-cells = <1>;
#size-cells = <0>;
#address-cells = <1>;
#size-cells = <0>;
gpio5: gpio5@0 {
compatible = "fairchild,74hc595";
reg = <0>;
gpio-controller;
#gpio-cells = <2>;
registers-number = <4>;
spi-max-frequency = <100000>;
};
gpio5@0 {
compatible = "fairchild,74hc595";
reg = <0>;
gpio-controller;
#gpio-cells = <2>;
registers-number = <4>;
spi-max-frequency = <100000>;
};
};

View File

@@ -84,52 +84,52 @@ examples:
reg = <0x80018000 0x2000>;
gpio@0 {
compatible = "fsl,imx28-gpio";
reg = <0>;
interrupts = <127>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
compatible = "fsl,imx28-gpio";
reg = <0>;
interrupts = <127>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio@1 {
compatible = "fsl,imx28-gpio";
reg = <1>;
interrupts = <126>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
compatible = "fsl,imx28-gpio";
reg = <1>;
interrupts = <126>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio@2 {
compatible = "fsl,imx28-gpio";
reg = <2>;
interrupts = <125>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
compatible = "fsl,imx28-gpio";
reg = <2>;
interrupts = <125>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio@3 {
compatible = "fsl,imx28-gpio";
reg = <3>;
interrupts = <124>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
compatible = "fsl,imx28-gpio";
reg = <3>;
interrupts = <124>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio@4 {
compatible = "fsl,imx28-gpio";
reg = <4>;
interrupts = <123>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
compatible = "fsl,imx28-gpio";
reg = <4>;
interrupts = <123>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
};

View File

@@ -16,6 +16,9 @@ description: |+
properties:
compatible:
oneOf:
- items:
- const: toradex,ecgpiol16
- const: nxp,pcal6416
- items:
- const: diodes,pi4ioe5v6534q
- const: nxp,pcal6534
@@ -132,6 +135,7 @@ allOf:
- maxim,max7325
- maxim,max7326
- maxim,max7327
- toradex,ecgpiol16
then:
properties:
reset-gpios: false

View File

@@ -70,6 +70,13 @@ properties:
minItems: 1
maxItems: 4
gpio-reserved-ranges: true
ngpios:
minimum: 1
maximum: 32
default: 32
patternProperties:
"^.+-hog(-[0-9]+)?$":
type: object

View File

@@ -0,0 +1,44 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/gpio/maxim,max77759-gpio.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Maxim Integrated MAX77759 GPIO
maintainers:
- André Draszik <andre.draszik@linaro.org>
description: |
This module is part of the MAX77759 PMIC. For additional information, see
Documentation/devicetree/bindings/mfd/maxim,max77759.yaml.
The MAX77759 is a PMIC integrating, amongst others, a GPIO controller
including interrupt support for 2 GPIO lines.
properties:
compatible:
const: maxim,max77759-gpio
"#gpio-cells":
const: 2
gpio-controller: true
gpio-line-names:
minItems: 1
maxItems: 2
"#interrupt-cells":
const: 2
interrupt-controller: true
required:
- compatible
- "#gpio-cells"
- gpio-controller
- "#interrupt-cells"
- interrupt-controller
additionalProperties: false

View File

@@ -111,6 +111,9 @@ properties:
gpio-controller: true
gpio-ranges:
maxItems: 1
"#gpio-cells":
description: |
Indicates how many cells are used in a consumer's GPIO specifier. In the

View File

@@ -128,17 +128,17 @@ additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
#address-cells = <1>;
#size-cells = <0>;
pcf8575: gpio@20 {
compatible = "nxp,pcf8575";
reg = <0x20>;
interrupt-parent = <&irqpin2>;
interrupts = <3 0>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio@20 {
compatible = "nxp,pcf8575";
reg = <0x20>;
interrupt-parent = <&irqpin2>;
interrupts = <3 0>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
};

View File

@@ -81,7 +81,7 @@ dependencies:
examples:
- |
gpio@3500 {
gpio@3500 {
compatible = "realtek,rtl8380-gpio", "realtek,otto-gpio";
reg = <0x3500 0x1c>;
gpio-controller;
@@ -91,9 +91,9 @@ examples:
#interrupt-cells = <2>;
interrupt-parent = <&rtlintc>;
interrupts = <23>;
};
};
- |
gpio@3300 {
gpio@3300 {
compatible = "realtek,rtl9300-gpio", "realtek,otto-gpio";
reg = <0x3300 0x1c>, <0x3338 0x8>;
gpio-controller;
@@ -103,6 +103,6 @@ examples:
#interrupt-cells = <2>;
interrupt-parent = <&rtlintc>;
interrupts = <13>;
};
};
...

View File

@@ -57,14 +57,14 @@ examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
gpio0: gpio@e0050000 {
compatible = "renesas,em-gio";
reg = <0xe0050000 0x2c>, <0xe0050040 0x20>;
interrupts = <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pfc 0 0 32>;
ngpios = <32>;
interrupt-controller;
#interrupt-cells = <2>;
compatible = "renesas,em-gio";
reg = <0xe0050000 0x2c>, <0xe0050040 0x20>;
interrupts = <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pfc 0 0 32>;
ngpios = <32>;
interrupt-controller;
#interrupt-cells = <2>;
};

View File

@@ -138,16 +138,16 @@ examples:
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/power/r8a77470-sysc.h>
gpio3: gpio@e6053000 {
compatible = "renesas,gpio-r8a77470", "renesas,rcar-gen2-gpio";
reg = <0xe6053000 0x50>;
interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cpg CPG_MOD 909>;
power-domains = <&sysc R8A77470_PD_ALWAYS_ON>;
resets = <&cpg 909>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pfc 0 96 30>;
gpio-reserved-ranges = <17 10>;
interrupt-controller;
#interrupt-cells = <2>;
compatible = "renesas,gpio-r8a77470", "renesas,rcar-gen2-gpio";
reg = <0xe6053000 0x50>;
interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cpg CPG_MOD 909>;
power-domains = <&sysc R8A77470_PD_ALWAYS_ON>;
resets = <&cpg 909>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pfc 0 96 30>;
gpio-reserved-ranges = <17 10>;
interrupt-controller;
#interrupt-cells = <2>;
};

View File

@@ -76,8 +76,8 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/clock/sifive-fu540-prci.h>
gpio@10060000 {
#include <dt-bindings/clock/sifive-fu540-prci.h>
gpio@10060000 {
compatible = "sifive,fu540-c000-gpio", "sifive,gpio0";
interrupt-parent = <&plic>;
interrupts = <7>, <8>, <9>, <10>, <11>, <12>, <13>, <14>, <15>, <16>,
@@ -88,6 +88,6 @@ examples:
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
};
...

View File

@@ -0,0 +1,96 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/gpio/spacemit,k1-gpio.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: SpacemiT K1 GPIO controller
maintainers:
- Yixun Lan <dlan@gentoo.org>
description:
The controller's registers are organized as sets of eight 32-bit
registers with each set of port controlling 32 pins. A single
interrupt line is shared for all of the pins by the controller.
properties:
$nodename:
pattern: "^gpio@[0-9a-f]+$"
compatible:
const: spacemit,k1-gpio
reg:
maxItems: 1
clocks:
items:
- description: GPIO Core Clock
- description: GPIO Bus Clock
clock-names:
items:
- const: core
- const: bus
resets:
maxItems: 1
gpio-controller: true
"#gpio-cells":
const: 3
description:
The first two cells are the GPIO bank index and offset inside the bank,
the third cell should specify GPIO flag.
gpio-ranges: true
interrupts:
maxItems: 1
interrupt-controller: true
"#interrupt-cells":
const: 3
description:
The first two cells are the GPIO bank index and offset inside the bank,
the third cell should specify interrupt flag. The controller does not
support level interrupts, so flags of IRQ_TYPE_LEVEL_HIGH,
IRQ_TYPE_LEVEL_LOW should not be used.
Refer <dt-bindings/interrupt-controller/irq.h> for valid flags.
required:
- compatible
- reg
- clocks
- clock-names
- gpio-controller
- "#gpio-cells"
- interrupts
- interrupt-controller
- "#interrupt-cells"
- gpio-ranges
additionalProperties: false
examples:
- |
gpio@d4019000 {
compatible = "spacemit,k1-gpio";
reg = <0xd4019000 0x800>;
clocks =<&ccu 9>, <&ccu 61>;
clock-names = "core", "bus";
gpio-controller;
#gpio-cells = <3>;
interrupts = <58>;
interrupt-controller;
interrupt-parent = <&plic>;
#interrupt-cells = <3>;
gpio-ranges = <&pinctrl 0 0 0 32>,
<&pinctrl 1 0 32 32>,
<&pinctrl 2 0 64 32>,
<&pinctrl 3 0 96 32>;
};
...

View File

@@ -48,22 +48,22 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
soc {
soc {
#address-cells = <2>;
#size-cells = <2>;
gpio: gpio@28020000 {
compatible = "toshiba,gpio-tmpv7708";
reg = <0 0x28020000 0 0x1000>;
#gpio-cells = <0x2>;
gpio-ranges = <&pmux 0 0 32>;
gpio-controller;
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&gic>;
compatible = "toshiba,gpio-tmpv7708";
reg = <0 0x28020000 0 0x1000>;
#gpio-cells = <0x2>;
gpio-ranges = <&pmux 0 0 32>;
gpio-controller;
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&gic>;
};
};
};
...

View File

@@ -126,29 +126,29 @@ examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
gpio@a0020000 {
compatible = "xlnx,xps-gpio-1.00.a";
reg = <0xa0020000 0x10000>;
#gpio-cells = <2>;
#interrupt-cells = <0x2>;
clocks = <&zynqmp_clk 71>;
gpio-controller;
interrupt-controller;
interrupt-names = "ip2intc_irpt";
interrupt-parent = <&gic>;
interrupts = <0 89 4>;
xlnx,all-inputs = <0x0>;
xlnx,all-inputs-2 = <0x0>;
xlnx,all-outputs = <0x0>;
xlnx,all-outputs-2 = <0x0>;
xlnx,dout-default = <0x0>;
xlnx,dout-default-2 = <0x0>;
xlnx,gpio-width = <0x20>;
xlnx,gpio2-width = <0x20>;
xlnx,interrupt-present = <0x1>;
xlnx,is-dual = <0x1>;
xlnx,tri-default = <0xFFFFFFFF>;
xlnx,tri-default-2 = <0xFFFFFFFF>;
};
gpio@a0020000 {
compatible = "xlnx,xps-gpio-1.00.a";
reg = <0xa0020000 0x10000>;
#gpio-cells = <2>;
#interrupt-cells = <0x2>;
clocks = <&zynqmp_clk 71>;
gpio-controller;
interrupt-controller;
interrupt-names = "ip2intc_irpt";
interrupt-parent = <&gic>;
interrupts = <0 89 4>;
xlnx,all-inputs = <0x0>;
xlnx,all-inputs-2 = <0x0>;
xlnx,all-outputs = <0x0>;
xlnx,all-outputs-2 = <0x0>;
xlnx,dout-default = <0x0>;
xlnx,dout-default-2 = <0x0>;
xlnx,gpio-width = <0x20>;
xlnx,gpio2-width = <0x20>;
xlnx,interrupt-present = <0x1>;
xlnx,is-dual = <0x1>;
xlnx,tri-default = <0xFFFFFFFF>;
xlnx,tri-default-2 = <0xFFFFFFFF>;
};
...

View File

@@ -0,0 +1,99 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/mfd/maxim,max77759.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Maxim Integrated MAX77759 PMIC for USB Type-C applications
maintainers:
- André Draszik <andre.draszik@linaro.org>
description: |
This is a part of device tree bindings for the MAX77759 companion Power
Management IC for USB Type-C applications.
The MAX77759 includes Battery Charger, Fuel Gauge, temperature sensors, USB
Type-C Port Controller (TCPC), NVMEM, and a GPIO expander.
properties:
compatible:
const: maxim,max77759
interrupts:
maxItems: 1
interrupt-controller: true
"#interrupt-cells":
const: 2
reg:
maxItems: 1
gpio:
$ref: /schemas/gpio/maxim,max77759-gpio.yaml
nvmem-0:
$ref: /schemas/nvmem/maxim,max77759-nvmem.yaml
required:
- compatible
- interrupts
- reg
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
pmic@66 {
compatible = "maxim,max77759";
reg = <0x66>;
interrupts-extended = <&gpa8 3 IRQ_TYPE_LEVEL_LOW>;
interrupt-controller;
#interrupt-cells = <2>;
gpio {
compatible = "maxim,max77759-gpio";
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
nvmem-0 {
compatible = "maxim,max77759-nvmem";
nvmem-layout {
compatible = "fixed-layout";
#address-cells = <1>;
#size-cells = <1>;
reboot-mode@0 {
reg = <0x0 0x4>;
};
boot-reason@4 {
reg = <0x4 0x4>;
};
shutdown-user-flag@8 {
reg = <0x8 0x1>;
};
rsoc@10 {
reg = <0xa 0x2>;
};
};
};
};
};

View File

@@ -0,0 +1,32 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/nvmem/maxim,max77759-nvmem.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Maxim Integrated MAX77759 Non Volatile Memory
maintainers:
- André Draszik <andre.draszik@linaro.org>
description: |
This module is part of the MAX77759 PMIC. For additional information, see
Documentation/devicetree/bindings/mfd/maxim,max77759.yaml.
The MAX77759 is a PMIC integrating, amongst others, Non Volatile Memory
(NVMEM) with 30 bytes of storage which can be used by software to store
information or communicate with a boot loader.
properties:
compatible:
const: maxim,max77759-nvmem
wp-gpios: false
required:
- compatible
allOf:
- $ref: nvmem.yaml#
unevaluatedProperties: false

View File

@@ -27,7 +27,7 @@ Core
ACPI support
============
.. kernel-doc:: drivers/gpio/gpiolib-acpi.c
.. kernel-doc:: drivers/gpio/gpiolib-acpi-core.c
:export:
Device tree support

View File

@@ -42,7 +42,7 @@ ACPI支持
该API在以下内核代码中:
drivers/gpio/gpiolib-acpi.c
drivers/gpio/gpiolib-acpi-core.c
设备树支持
==========

View File

@@ -4204,6 +4204,16 @@ F: Documentation/ABI/stable/sysfs-class-bluetooth
F: include/net/bluetooth/
F: net/bluetooth/
BLZP1600 GPIO DRIVER
M: James Cowgill <james.cowgill@blaize.com>
M: Matt Redfearn <matt.redfearn@blaize.com>
M: Neil Jones <neil.jones@blaize.com>
M: Nikolaos Pasaloukos <nikolaos.pasaloukos@blaize.com>
L: linux-gpio@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/gpio/blaize,blzp1600-gpio.yaml
F: drivers/gpio/gpio-blzp1600.c
BONDING DRIVER
M: Jay Vosburgh <jv@jvosburgh.net>
L: netdev@vger.kernel.org
@@ -10123,7 +10133,7 @@ L: linux-acpi@vger.kernel.org
S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git
F: Documentation/firmware-guide/acpi/gpio-properties.rst
F: drivers/gpio/gpiolib-acpi.c
F: drivers/gpio/gpiolib-acpi-*.c
F: drivers/gpio/gpiolib-acpi.h
GPIO AGGREGATOR
@@ -14625,6 +14635,16 @@ F: Documentation/devicetree/bindings/mfd/maxim,max77714.yaml
F: drivers/mfd/max77714.c
F: include/linux/mfd/max77714.h
MAXIM MAX77759 PMIC MFD DRIVER
M: André Draszik <andre.draszik@linaro.org>
L: linux-kernel@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/*/maxim,max77759*.yaml
F: drivers/gpio/gpio-max77759.c
F: drivers/mfd/max77759.c
F: drivers/nvmem/max77759-nvmem.c
F: include/linux/mfd/max77759.h
MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER
M: Javier Martinez Canillas <javier@dowhile0.org>
L: linux-kernel@vger.kernel.org

View File

@@ -759,6 +759,17 @@ int __devm_add_action(struct device *dev, void (*action)(void *), void *data, co
}
EXPORT_SYMBOL_GPL(__devm_add_action);
bool devm_is_action_added(struct device *dev, void (*action)(void *), void *data)
{
struct action_devres devres = {
.data = data,
.action = action,
};
return devres_find(dev, devm_action_release, devm_action_match, &devres);
}
EXPORT_SYMBOL_GPL(devm_is_action_added);
/**
* devm_remove_action_nowarn() - removes previously added custom action
* @dev: Device that owns the action

View File

@@ -201,6 +201,7 @@ config GPIO_RASPBERRYPI_EXP
config GPIO_BCM_KONA
bool "Broadcom Kona GPIO"
depends on ARCH_BCM_MOBILE || COMPILE_TEST
select GPIOLIB_IRQCHIP
help
Turn on GPIO support for Broadcom "Kona" chips.
@@ -213,6 +214,18 @@ config GPIO_BCM_XGS_IPROC
help
Say yes here to enable GPIO support for Broadcom XGS iProc SoCs.
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
Say Y or M here to add support for the Blaize BLZP1600 GPIO device.
The controller is based on the Verisilicon Microelectronics GPIO APB v0.2
IP block.
config GPIO_BRCMSTB
tristate "BRCMSTB GPIO support"
default y if (ARCH_BRCMSTB || BMIPS_GENERIC)
@@ -241,6 +254,7 @@ config GPIO_DAVINCI
tristate "TI Davinci/Keystone GPIO support"
default y if ARCH_DAVINCI
depends on ((ARM || ARM64) && (ARCH_DAVINCI || ARCH_KEYSTONE || ARCH_K3)) || COMPILE_TEST
select GPIOLIB_IRQCHIP
help
Say yes here to enable GPIO support for TI Davinci/Keystone SoCs.
@@ -340,7 +354,7 @@ config GPIO_GRGPIO
tristate "Aeroflex Gaisler GRGPIO support"
depends on OF || COMPILE_TEST
select GPIO_GENERIC
select IRQ_DOMAIN
select GPIOLIB_IRQCHIP
help
Select this to support Aeroflex Gaisler GRGPIO cores from the GRLIB
VHDL IP core library.
@@ -368,8 +382,7 @@ config GPIO_HLWD
config GPIO_ICH
tristate "Intel ICH GPIO"
depends on X86
depends on LPC_ICH
depends on (X86 && LPC_ICH) || (COMPILE_TEST && HAS_IOPORT)
help
Say yes here to support the GPIO functionality of a number of Intel
ICH-based chipsets. Currently supported devices: ICH6, ICH7, ICH8
@@ -425,6 +438,7 @@ config GPIO_LPC18XX
default y if ARCH_LPC18XX
depends on OF_GPIO && (ARCH_LPC18XX || COMPILE_TEST)
select IRQ_DOMAIN_HIERARCHY
select GPIOLIB_IRQCHIP
help
Select this option to enable GPIO driver for
NXP LPC18XX/43XX devices.
@@ -468,7 +482,7 @@ config GPIO_MPC8XXX
FSL_SOC_BOOKE || PPC_86xx || ARCH_LAYERSCAPE || ARM || \
COMPILE_TEST
select GPIO_GENERIC
select IRQ_DOMAIN
select GPIOLIB_IRQCHIP
help
Say Y here if you're going to use hardware that connects to the
MPC512x/831x/834x/837x/8572/8610/QorIQ GPIOs.
@@ -540,7 +554,7 @@ config GPIO_OMAP
config GPIO_PL061
tristate "PrimeCell PL061 GPIO support"
depends on ARM_AMBA
depends on ARM_AMBA || COMPILE_TEST
select IRQ_DOMAIN
select GPIOLIB_IRQCHIP
help
@@ -555,6 +569,7 @@ config GPIO_POLARFIRE_SOC
config GPIO_PXA
bool "PXA GPIO support"
depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST
select GPIOLIB_IRQCHIP
help
Say yes here to support the PXA GPIO device.
@@ -604,7 +619,7 @@ config GPIO_ROCKCHIP
config GPIO_RTD
tristate "Realtek DHC GPIO support"
depends on ARCH_REALTEK
depends on ARCH_REALTEK || COMPILE_TEST
default y
select GPIOLIB_IRQCHIP
help
@@ -656,6 +671,15 @@ config GPIO_SNPS_CREG
where only several fields in register belong to GPIO lines and
each GPIO line owns a field with different length and on/off value.
config GPIO_SPACEMIT_K1
tristate "SPACEMIT K1 GPIO support"
depends on ARCH_SPACEMIT || COMPILE_TEST
depends on OF_GPIO
select GPIO_GENERIC
select GPIOLIB_IRQCHIP
help
Say yes here to support the SpacemiT's K1 GPIO device.
config GPIO_SPEAR_SPICS
bool "ST SPEAr13xx SPI Chip Select as GPIO support"
depends on PLAT_SPEAR
@@ -753,7 +777,7 @@ config GPIO_UNIPHIER
Say yes here to support UniPhier GPIOs.
config GPIO_VF610
bool "VF610 GPIO support"
tristate "VF610 GPIO support"
default y if SOC_VF610
depends on ARCH_MXC || COMPILE_TEST
select GPIOLIB_IRQCHIP
@@ -830,14 +854,14 @@ config GPIO_ZEVIO
config GPIO_ZYNQ
tristate "Xilinx Zynq GPIO support"
depends on ARCH_ZYNQ || ARCH_ZYNQMP
depends on ARCH_ZYNQ || ARCH_ZYNQMP || COMPILE_TEST
select GPIOLIB_IRQCHIP
help
Say yes here to support Xilinx Zynq GPIO controller.
config GPIO_ZYNQMP_MODEPIN
tristate "ZynqMP ps-mode pin GPIO configuration driver"
depends on ZYNQMP_FIRMWARE
depends on ZYNQMP_FIRMWARE || COMPILE_TEST
default ZYNQMP_FIRMWARE
help
Say yes here to support the ZynqMP ps-mode pin GPIO configuration
@@ -866,7 +890,7 @@ config GPIO_AMD_FCH
config GPIO_MSC313
bool "MStar MSC313 GPIO support"
depends on ARCH_MSTARV7
depends on ARCH_MSTARV7 || COMPILE_TEST
default ARCH_MSTARV7
select GPIOLIB_IRQCHIP
select IRQ_DOMAIN_HIERARCHY
@@ -1365,7 +1389,7 @@ config GPIO_DLN2
config HTC_EGPIO
bool "HTC EGPIO support"
depends on ARM
depends on ARM || COMPILE_TEST
help
This driver supports the CPLD egpio chip present on
several HTC phones. It provides basic support for input
@@ -1463,6 +1487,19 @@ config GPIO_MAX77650
GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor.
These chips have a single pin that can be configured as GPIO.
config GPIO_MAX77759
tristate "Maxim Integrated MAX77759 GPIO support"
depends on MFD_MAX77759
default MFD_MAX77759
select GPIOLIB_IRQCHIP
help
GPIO driver for MAX77759 PMIC from Maxim Integrated.
There are two GPIOs available on these chips in total, both of
which can also generate interrupts.
This driver can also be built as a module. If so, the module will be
called gpio-max77759.
config GPIO_PALMAS
bool "TI PALMAS series PMICs GPIO"
depends on MFD_PALMAS
@@ -1520,12 +1557,13 @@ config GPIO_TC3589X
config GPIO_TIMBERDALE
bool "Support for timberdale GPIO IP"
depends on MFD_TIMBERDALE
select GPIOLIB_IRQCHIP
help
Add support for the GPIO IP in the timberdale FPGA.
config GPIO_TN48M_CPLD
tristate "Delta Networks TN48M switch CPLD GPIO driver"
depends on MFD_TN48M_CPLD
depends on MFD_TN48M_CPLD || COMPILE_TEST
select GPIO_REGMAP
help
This enables support for the GPIOs found on the Delta
@@ -1869,6 +1907,8 @@ 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.

View File

@@ -10,6 +10,7 @@ obj-$(CONFIG_OF_GPIO) += gpiolib-of.o
obj-$(CONFIG_GPIO_CDEV) += gpiolib-cdev.o
obj-$(CONFIG_GPIO_SYSFS) += gpiolib-sysfs.o
obj-$(CONFIG_GPIO_ACPI) += gpiolib-acpi.o
gpiolib-acpi-y := gpiolib-acpi-core.o gpiolib-acpi-quirks.o
obj-$(CONFIG_GPIOLIB) += gpiolib-swnode.o
# Device drivers. Generally keep list sorted alphabetically
@@ -45,6 +46,7 @@ obj-$(CONFIG_GPIO_BCM_XGS_IPROC) += gpio-xgs-iproc.o
obj-$(CONFIG_GPIO_BD71815) += gpio-bd71815.o
obj-$(CONFIG_GPIO_BD71828) += gpio-bd71828.o
obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o
obj-$(CONFIG_GPIO_BLZP1600) += gpio-blzp1600.o
obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o
obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o
obj-$(CONFIG_GPIO_CADENCE) += gpio-cadence.o
@@ -105,6 +107,7 @@ obj-$(CONFIG_GPIO_MAX730X) += gpio-max730x.o
obj-$(CONFIG_GPIO_MAX732X) += gpio-max732x.o
obj-$(CONFIG_GPIO_MAX77620) += gpio-max77620.o
obj-$(CONFIG_GPIO_MAX77650) += gpio-max77650.o
obj-$(CONFIG_GPIO_MAX77759) += gpio-max77759.o
obj-$(CONFIG_GPIO_MB86S7X) += gpio-mb86s7x.o
obj-$(CONFIG_GPIO_MC33880) += gpio-mc33880.o
obj-$(CONFIG_GPIO_MENZ127) += gpio-menz127.o
@@ -159,6 +162,7 @@ obj-$(CONFIG_GPIO_SIOX) += gpio-siox.o
obj-$(CONFIG_GPIO_SL28CPLD) += gpio-sl28cpld.o
obj-$(CONFIG_GPIO_SLOPPY_LOGIC_ANALYZER) += gpio-sloppy-logic-analyzer.o
obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o
obj-$(CONFIG_GPIO_SPACEMIT_K1) += gpio-spacemit-k1.o
obj-$(CONFIG_GPIO_SPEAR_SPICS) += gpio-spear-spics.o
obj-$(CONFIG_GPIO_SPRD) += gpio-sprd.o
obj-$(CONFIG_GPIO_STMPE) += gpio-stmpe.o

View File

@@ -44,6 +44,13 @@ Work items:
to a machine description such as device tree, ACPI or fwnode that
implicitly does not use global GPIO numbers.
- Fix drivers to not read back struct gpio_chip::base. Some drivers do
that and would be broken by attempts to poison it or make it dynamic.
Example in AT91 pinctrl driver:
https://lore.kernel.org/all/1d00c056-3d61-4c22-bedd-3bae0bf1ddc4@pengutronix.de/
This particular driver is also DT-only, so with the above fixed, the
base can be made dynamic (set to -1) if CONFIG_GPIO_SYSFS is disabled.
- When this work is complete (will require some of the items in the
following ongoing work as well) we can delete the old global
numberspace accessors from <linux/gpio.h> and eventually delete

File diff suppressed because it is too large Load Diff

View File

@@ -516,6 +516,7 @@ static struct irq_chip bcm_gpio_irq_chip = {
.irq_set_type = bcm_kona_gpio_irq_set_type,
.irq_request_resources = bcm_kona_gpio_irq_reqres,
.irq_release_resources = bcm_kona_gpio_irq_relres,
.flags = IRQCHIP_IMMUTABLE,
};
static struct of_device_id const bcm_kona_gpio_of_match[] = {

View File

@@ -0,0 +1,281 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2019 VeriSilicon Limited.
* Copyright (C) 2025 Blaize, Inc.
*/
#include <linux/errno.h>
#include <linux/gpio/driver.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#define GPIO_DIR_REG 0x00
#define GPIO_CTRL_REG 0x04
#define GPIO_SET_REG 0x08
#define GPIO_CLR_REG 0x0C
#define GPIO_ODATA_REG 0x10
#define GPIO_IDATA_REG 0x14
#define GPIO_IEN_REG 0x18
#define GPIO_IS_REG 0x1C
#define GPIO_IBE_REG 0x20
#define GPIO_IEV_REG 0x24
#define GPIO_RIS_REG 0x28
#define GPIO_IM_REG 0x2C
#define GPIO_MIS_REG 0x30
#define GPIO_IC_REG 0x34
#define GPIO_DB_REG 0x38
#define GPIO_DFG_REG 0x3C
#define DRIVER_NAME "blzp1600-gpio"
struct blzp1600_gpio {
void __iomem *base;
struct gpio_chip gc;
int irq;
};
static inline struct blzp1600_gpio *get_blzp1600_gpio_from_irq_data(struct irq_data *d)
{
return gpiochip_get_data(irq_data_get_irq_chip_data(d));
}
static inline struct blzp1600_gpio *get_blzp1600_gpio_from_irq_desc(struct irq_desc *d)
{
return gpiochip_get_data(irq_desc_get_handler_data(d));
}
static inline u32 blzp1600_gpio_read(struct blzp1600_gpio *chip, unsigned int offset)
{
return readl_relaxed(chip->base + offset);
}
static inline void blzp1600_gpio_write(struct blzp1600_gpio *chip, unsigned int offset, u32 val)
{
writel_relaxed(val, chip->base + offset);
}
static inline void blzp1600_gpio_rmw(void __iomem *reg, u32 mask, bool set)
{
u32 val = readl_relaxed(reg);
if (set)
val |= mask;
else
val &= ~mask;
writel_relaxed(val, reg);
}
static void blzp1600_gpio_irq_mask(struct irq_data *d)
{
struct blzp1600_gpio *chip = get_blzp1600_gpio_from_irq_data(d);
guard(raw_spinlock_irqsave)(&chip->gc.bgpio_lock);
blzp1600_gpio_rmw(chip->base + GPIO_IM_REG, BIT(d->hwirq), 1);
}
static void blzp1600_gpio_irq_unmask(struct irq_data *d)
{
struct blzp1600_gpio *chip = get_blzp1600_gpio_from_irq_data(d);
guard(raw_spinlock_irqsave)(&chip->gc.bgpio_lock);
blzp1600_gpio_rmw(chip->base + GPIO_IM_REG, BIT(d->hwirq), 0);
}
static void blzp1600_gpio_irq_ack(struct irq_data *d)
{
struct blzp1600_gpio *chip = get_blzp1600_gpio_from_irq_data(d);
blzp1600_gpio_write(chip, GPIO_IC_REG, BIT(d->hwirq));
}
static void blzp1600_gpio_irq_enable(struct irq_data *d)
{
struct blzp1600_gpio *chip = get_blzp1600_gpio_from_irq_data(d);
gpiochip_enable_irq(&chip->gc, irqd_to_hwirq(d));
guard(raw_spinlock_irqsave)(&chip->gc.bgpio_lock);
blzp1600_gpio_rmw(chip->base + GPIO_DIR_REG, BIT(d->hwirq), 0);
blzp1600_gpio_rmw(chip->base + GPIO_IEN_REG, BIT(d->hwirq), 1);
}
static void blzp1600_gpio_irq_disable(struct irq_data *d)
{
struct blzp1600_gpio *chip = get_blzp1600_gpio_from_irq_data(d);
guard(raw_spinlock_irqsave)(&chip->gc.bgpio_lock);
blzp1600_gpio_rmw(chip->base + GPIO_IEN_REG, BIT(d->hwirq), 0);
gpiochip_disable_irq(&chip->gc, irqd_to_hwirq(d));
}
static int blzp1600_gpio_irq_set_type(struct irq_data *d, u32 type)
{
struct blzp1600_gpio *chip = get_blzp1600_gpio_from_irq_data(d);
u32 edge_level, single_both, fall_rise;
int mask = BIT(d->hwirq);
guard(raw_spinlock_irqsave)(&chip->gc.bgpio_lock);
edge_level = blzp1600_gpio_read(chip, GPIO_IS_REG);
single_both = blzp1600_gpio_read(chip, GPIO_IBE_REG);
fall_rise = blzp1600_gpio_read(chip, GPIO_IEV_REG);
switch (type) {
case IRQ_TYPE_EDGE_BOTH:
edge_level &= ~mask;
single_both |= mask;
break;
case IRQ_TYPE_EDGE_RISING:
edge_level &= ~mask;
single_both &= ~mask;
fall_rise |= mask;
break;
case IRQ_TYPE_EDGE_FALLING:
edge_level &= ~mask;
single_both &= ~mask;
fall_rise &= ~mask;
break;
case IRQ_TYPE_LEVEL_HIGH:
edge_level |= mask;
fall_rise |= mask;
break;
case IRQ_TYPE_LEVEL_LOW:
edge_level |= mask;
fall_rise &= ~mask;
break;
default:
return -EINVAL;
}
blzp1600_gpio_write(chip, GPIO_IS_REG, edge_level);
blzp1600_gpio_write(chip, GPIO_IBE_REG, single_both);
blzp1600_gpio_write(chip, GPIO_IEV_REG, fall_rise);
if (type & IRQ_TYPE_LEVEL_MASK)
irq_set_handler_locked(d, handle_level_irq);
else
irq_set_handler_locked(d, handle_edge_irq);
return 0;
}
static const struct irq_chip blzp1600_gpio_irqchip = {
.name = DRIVER_NAME,
.irq_ack = blzp1600_gpio_irq_ack,
.irq_mask = blzp1600_gpio_irq_mask,
.irq_unmask = blzp1600_gpio_irq_unmask,
.irq_set_type = blzp1600_gpio_irq_set_type,
.irq_enable = blzp1600_gpio_irq_enable,
.irq_disable = blzp1600_gpio_irq_disable,
.flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static void blzp1600_gpio_irqhandler(struct irq_desc *desc)
{
struct blzp1600_gpio *gpio = get_blzp1600_gpio_from_irq_desc(desc);
struct irq_chip *irqchip = irq_desc_get_chip(desc);
unsigned long irq_status;
int hwirq = 0;
chained_irq_enter(irqchip, desc);
irq_status = blzp1600_gpio_read(gpio, GPIO_RIS_REG);
for_each_set_bit(hwirq, &irq_status, gpio->gc.ngpio)
generic_handle_domain_irq(gpio->gc.irq.domain, hwirq);
chained_irq_exit(irqchip, desc);
}
static int blzp1600_gpio_set_debounce(struct gpio_chip *gc, unsigned int offset,
unsigned int debounce)
{
struct blzp1600_gpio *chip = gpiochip_get_data(gc);
guard(raw_spinlock_irqsave)(&chip->gc.bgpio_lock);
blzp1600_gpio_rmw(chip->base + GPIO_DB_REG, BIT(offset), debounce);
return 0;
}
static int blzp1600_gpio_set_config(struct gpio_chip *gc, unsigned int offset, unsigned long config)
{
u32 debounce;
if (pinconf_to_config_param(config) != PIN_CONFIG_INPUT_DEBOUNCE)
return -ENOTSUPP;
debounce = pinconf_to_config_argument(config);
return blzp1600_gpio_set_debounce(gc, offset, debounce);
}
static int blzp1600_gpio_probe(struct platform_device *pdev)
{
struct blzp1600_gpio *chip;
struct gpio_chip *gc;
int ret;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(chip->base))
return PTR_ERR(chip->base);
ret = bgpio_init(&chip->gc, &pdev->dev, 4, chip->base + GPIO_IDATA_REG,
chip->base + GPIO_SET_REG, chip->base + GPIO_CLR_REG,
chip->base + GPIO_DIR_REG, NULL, 0);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Failed to register generic gpio\n");
/* configure the gpio chip */
gc = &chip->gc;
gc->set_config = blzp1600_gpio_set_config;
if (device_property_present(&pdev->dev, "interrupt-controller")) {
struct gpio_irq_chip *girq;
chip->irq = platform_get_irq(pdev, 0);
if (chip->irq < 0)
return chip->irq;
girq = &gc->irq;
gpio_irq_chip_set_chip(girq, &blzp1600_gpio_irqchip);
girq->parent_handler = blzp1600_gpio_irqhandler;
girq->num_parents = 1;
girq->parents = devm_kcalloc(&pdev->dev, 1, sizeof(*girq->parents), GFP_KERNEL);
if (!girq->parents)
return -ENOMEM;
girq->parents[0] = chip->irq;
girq->default_type = IRQ_TYPE_NONE;
}
return devm_gpiochip_add_data(&pdev->dev, gc, chip);
}
static const struct of_device_id blzp1600_gpio_of_match[] = {
{ .compatible = "blaize,blzp1600-gpio", },
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, blzp1600_gpio_of_match);
static struct platform_driver blzp1600_gpio_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = blzp1600_gpio_of_match,
},
.probe = blzp1600_gpio_probe,
};
module_platform_driver(blzp1600_gpio_driver);
MODULE_AUTHOR("Nikolaos Pasaloukos <nikolaos.pasaloukos@blaize.com>");
MODULE_DESCRIPTION("Blaize BLZP1600 GPIO driver");
MODULE_LICENSE("GPL");

View File

@@ -68,15 +68,6 @@ static inline u32 __gpio_mask(unsigned gpio)
return 1 << (gpio % 32);
}
static inline struct davinci_gpio_regs __iomem *irq2regs(struct irq_data *d)
{
struct davinci_gpio_regs __iomem *g;
g = (__force struct davinci_gpio_regs __iomem *)irq_data_get_irq_chip_data(d);
return g;
}
static int davinci_gpio_irq_setup(struct platform_device *pdev);
/*--------------------------------------------------------------------------*/
@@ -255,19 +246,27 @@ static int davinci_gpio_probe(struct platform_device *pdev)
static void gpio_irq_mask(struct irq_data *d)
{
struct davinci_gpio_regs __iomem *g = irq2regs(d);
struct davinci_gpio_controller *chips = irq_data_get_irq_chip_data(d);
irq_hw_number_t hwirq = irqd_to_hwirq(d);
struct davinci_gpio_regs __iomem *g = chips->regs[hwirq / 32];
uintptr_t mask = (uintptr_t)irq_data_get_irq_handler_data(d);
writel_relaxed(mask, &g->clr_falling);
writel_relaxed(mask, &g->clr_rising);
gpiochip_disable_irq(&chips->chip, hwirq);
}
static void gpio_irq_unmask(struct irq_data *d)
{
struct davinci_gpio_regs __iomem *g = irq2regs(d);
struct davinci_gpio_controller *chips = irq_data_get_irq_chip_data(d);
irq_hw_number_t hwirq = irqd_to_hwirq(d);
struct davinci_gpio_regs __iomem *g = chips->regs[hwirq / 32];
uintptr_t mask = (uintptr_t)irq_data_get_irq_handler_data(d);
unsigned status = irqd_get_trigger_type(d);
gpiochip_enable_irq(&chips->chip, hwirq);
status &= IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING;
if (!status)
status = IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING;
@@ -286,12 +285,13 @@ static int gpio_irq_type(struct irq_data *d, unsigned trigger)
return 0;
}
static struct irq_chip gpio_irqchip = {
static const struct irq_chip gpio_irqchip = {
.name = "GPIO",
.irq_unmask = gpio_irq_unmask,
.irq_mask = gpio_irq_mask,
.irq_set_type = gpio_irq_type,
.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE,
.flags = IRQCHIP_IMMUTABLE | IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static void gpio_irq_handler(struct irq_desc *desc)
@@ -399,12 +399,11 @@ davinci_gpio_irq_map(struct irq_domain *d, unsigned int irq,
{
struct davinci_gpio_controller *chips =
(struct davinci_gpio_controller *)d->host_data;
struct davinci_gpio_regs __iomem *g = chips->regs[hw / 32];
irq_set_chip_and_handler_name(irq, &gpio_irqchip, handle_simple_irq,
"davinci_gpio");
irq_set_irq_type(irq, IRQ_TYPE_NONE);
irq_set_chip_data(irq, (__force void *)g);
irq_set_chip_data(irq, (__force void *)chips);
irq_set_handler_data(irq, (void *)(uintptr_t)__gpio_mask(hw));
return 0;

View File

@@ -220,11 +220,12 @@ static int dln2_gpio_get(struct gpio_chip *chip, unsigned int offset)
return dln2_gpio_pin_get_out_val(dln2, offset);
}
static void dln2_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
static int dln2_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
{
struct dln2_gpio *dln2 = gpiochip_get_data(chip);
dln2_gpio_pin_set_out_val(dln2, offset, value);
return dln2_gpio_pin_set_out_val(dln2, offset, value);
}
static int dln2_gpio_set_direction(struct gpio_chip *chip, unsigned offset,
@@ -468,7 +469,7 @@ static int dln2_gpio_probe(struct platform_device *pdev)
dln2->gpio.base = -1;
dln2->gpio.ngpio = pins;
dln2->gpio.can_sleep = true;
dln2->gpio.set = dln2_gpio_set;
dln2->gpio.set_rv = dln2_gpio_set;
dln2->gpio.get = dln2_gpio_get;
dln2->gpio.request = dln2_gpio_request;
dln2->gpio.free = dln2_gpio_free;

View File

@@ -25,7 +25,6 @@ static int ds4520_gpio_probe(struct i2c_client *client)
struct gpio_regmap_config config = { };
struct device *dev = &client->dev;
struct regmap *regmap;
u32 ngpio;
u32 base;
int ret;
@@ -33,10 +32,6 @@ static int ds4520_gpio_probe(struct i2c_client *client)
if (ret)
return dev_err_probe(dev, ret, "Missing 'reg' property.\n");
ret = device_property_read_u32(dev, "ngpios", &ngpio);
if (ret)
return dev_err_probe(dev, ret, "Missing 'ngpios' property.\n");
regmap = devm_regmap_init_i2c(client, &ds4520_regmap_config);
if (IS_ERR(regmap))
return dev_err_probe(dev, PTR_ERR(regmap),
@@ -44,7 +39,6 @@ static int ds4520_gpio_probe(struct i2c_client *client)
config.regmap = regmap;
config.parent = dev;
config.ngpio = ngpio;
config.reg_dat_base = base + DS4520_IO_STATUS0;
config.reg_set_base = base + DS4520_PULLUP0;

View File

@@ -203,9 +203,10 @@ static int sprd_eic_direction_input(struct gpio_chip *chip, unsigned int offset)
return 0;
}
static void sprd_eic_set(struct gpio_chip *chip, unsigned int offset, int value)
static int sprd_eic_set(struct gpio_chip *chip, unsigned int offset, int value)
{
/* EICs are always input, nothing need to do here. */
return 0;
}
static int sprd_eic_set_debounce(struct gpio_chip *chip, unsigned int offset,
@@ -662,7 +663,7 @@ static int sprd_eic_probe(struct platform_device *pdev)
sprd_eic->chip.request = sprd_eic_request;
sprd_eic->chip.free = sprd_eic_free;
sprd_eic->chip.set_config = sprd_eic_set_config;
sprd_eic->chip.set = sprd_eic_set;
sprd_eic->chip.set_rv = sprd_eic_set;
fallthrough;
case SPRD_EIC_ASYNC:
case SPRD_EIC_SYNC:

View File

@@ -204,13 +204,15 @@ static void __em_gio_set(struct gpio_chip *chip, unsigned int reg,
(BIT(shift + 16)) | (value << shift));
}
static void em_gio_set(struct gpio_chip *chip, unsigned offset, int value)
static int em_gio_set(struct gpio_chip *chip, unsigned int offset, int value)
{
/* output is split into two registers */
if (offset < 16)
__em_gio_set(chip, GIO_OL, offset, value);
else
__em_gio_set(chip, GIO_OH, offset - 16, value);
return 0;
}
static int em_gio_direction_output(struct gpio_chip *chip, unsigned offset,
@@ -304,7 +306,7 @@ static int em_gio_probe(struct platform_device *pdev)
gpio_chip->direction_input = em_gio_direction_input;
gpio_chip->get = em_gio_get;
gpio_chip->direction_output = em_gio_direction_output;
gpio_chip->set = em_gio_set;
gpio_chip->set_rv = em_gio_set;
gpio_chip->to_irq = em_gio_to_irq;
gpio_chip->request = pinctrl_gpio_request;
gpio_chip->free = em_gio_free;

View File

@@ -93,8 +93,8 @@ static int exar_get_value(struct gpio_chip *chip, unsigned int offset)
return !!(regmap_test_bits(exar_gpio->regmap, addr, BIT(bit)));
}
static void exar_set_value(struct gpio_chip *chip, unsigned int offset,
int value)
static int exar_set_value(struct gpio_chip *chip, unsigned int offset,
int value)
{
struct exar_gpio_chip *exar_gpio = gpiochip_get_data(chip);
unsigned int addr = exar_offset_to_lvl_addr(exar_gpio, offset);
@@ -105,7 +105,7 @@ static void exar_set_value(struct gpio_chip *chip, unsigned int offset,
* regmap_write_bits() forces value to be written when an external
* pull up/down might otherwise indicate value was already set.
*/
regmap_write_bits(exar_gpio->regmap, addr, BIT(bit), bit_value);
return regmap_write_bits(exar_gpio->regmap, addr, BIT(bit), bit_value);
}
static int exar_direction_output(struct gpio_chip *chip, unsigned int offset,
@@ -114,11 +114,13 @@ static int exar_direction_output(struct gpio_chip *chip, unsigned int offset,
struct exar_gpio_chip *exar_gpio = gpiochip_get_data(chip);
unsigned int addr = exar_offset_to_sel_addr(exar_gpio, offset);
unsigned int bit = exar_offset_to_bit(exar_gpio, offset);
int ret;
exar_set_value(chip, offset, value);
regmap_clear_bits(exar_gpio->regmap, addr, BIT(bit));
ret = exar_set_value(chip, offset, value);
if (ret)
return ret;
return 0;
return regmap_clear_bits(exar_gpio->regmap, addr, BIT(bit));
}
static int exar_direction_input(struct gpio_chip *chip, unsigned int offset)
@@ -209,7 +211,7 @@ static int gpio_exar_probe(struct platform_device *pdev)
exar_gpio->gpio_chip.direction_input = exar_direction_input;
exar_gpio->gpio_chip.get_direction = exar_get_direction;
exar_gpio->gpio_chip.get = exar_get_value;
exar_gpio->gpio_chip.set = exar_set_value;
exar_gpio->gpio_chip.set_rv = exar_set_value;
exar_gpio->gpio_chip.base = -1;
exar_gpio->gpio_chip.ngpio = ngpios;
exar_gpio->index = index;

View File

@@ -159,7 +159,8 @@ static int f7188x_gpio_direction_in(struct gpio_chip *chip, unsigned offset);
static int f7188x_gpio_get(struct gpio_chip *chip, unsigned offset);
static int f7188x_gpio_direction_out(struct gpio_chip *chip,
unsigned offset, int value);
static void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value);
static int f7188x_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value);
static int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset,
unsigned long config);
@@ -172,7 +173,7 @@ static int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset,
.direction_input = f7188x_gpio_direction_in, \
.get = f7188x_gpio_get, \
.direction_output = f7188x_gpio_direction_out, \
.set = f7188x_gpio_set, \
.set_rv = f7188x_gpio_set, \
.set_config = f7188x_gpio_set_config, \
.base = -1, \
.ngpio = _ngpio, \
@@ -391,7 +392,8 @@ static int f7188x_gpio_direction_out(struct gpio_chip *chip,
return 0;
}
static void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
static int f7188x_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
{
int err;
struct f7188x_gpio_bank *bank = gpiochip_get_data(chip);
@@ -400,7 +402,8 @@ static void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
err = superio_enter(sio->addr);
if (err)
return;
return err;
superio_select(sio->addr, sio->device);
data_out = superio_inb(sio->addr, f7188x_gpio_data_out(bank->regbase));
@@ -411,6 +414,8 @@ static void f7188x_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
superio_outb(sio->addr, f7188x_gpio_data_out(bank->regbase), data_out);
superio_exit(sio->addr);
return 0;
}
static int f7188x_gpio_set_config(struct gpio_chip *chip, unsigned offset,

View File

@@ -116,7 +116,7 @@ static int gnr_gpio_get(struct gpio_chip *gc, unsigned int gpio)
return !!(dw & GNR_CFG_DW_RXSTATE);
}
static void gnr_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value)
static int gnr_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value)
{
u32 clear = 0;
u32 set = 0;
@@ -126,7 +126,7 @@ static void gnr_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value)
else
clear = GNR_CFG_DW_TXSTATE;
gnr_gpio_configure_line(gc, gpio, clear, set);
return gnr_gpio_configure_line(gc, gpio, clear, set);
}
static int gnr_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio)
@@ -159,7 +159,7 @@ static const struct gpio_chip gnr_gpio_chip = {
.owner = THIS_MODULE,
.request = gnr_gpio_request,
.get = gnr_gpio_get,
.set = gnr_gpio_set,
.set_rv = gnr_gpio_set,
.get_direction = gnr_gpio_get_direction,
.direction_input = gnr_gpio_direction_input,
.direction_output = gnr_gpio_direction_output,

View File

@@ -170,6 +170,8 @@ static void grgpio_irq_mask(struct irq_data *d)
grgpio_set_imask(priv, offset, 0);
raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags);
gpiochip_disable_irq(&priv->gc, d->hwirq);
}
static void grgpio_irq_unmask(struct irq_data *d)
@@ -178,6 +180,7 @@ static void grgpio_irq_unmask(struct irq_data *d)
int offset = d->hwirq;
unsigned long flags;
gpiochip_enable_irq(&priv->gc, d->hwirq);
raw_spin_lock_irqsave(&priv->gc.bgpio_lock, flags);
grgpio_set_imask(priv, offset, 1);
@@ -185,11 +188,13 @@ static void grgpio_irq_unmask(struct irq_data *d)
raw_spin_unlock_irqrestore(&priv->gc.bgpio_lock, flags);
}
static struct irq_chip grgpio_irq_chip = {
static const struct irq_chip grgpio_irq_chip = {
.name = "grgpio",
.irq_mask = grgpio_irq_mask,
.irq_unmask = grgpio_irq_unmask,
.irq_set_type = grgpio_irq_set_type,
.flags = IRQCHIP_IMMUTABLE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static irqreturn_t grgpio_irq_handler(int irq, void *dev)

View File

@@ -62,9 +62,9 @@ static int gw_pld_output8(struct gpio_chip *gc, unsigned offset, int value)
return i2c_smbus_write_byte(gw->client, gw->out);
}
static void gw_pld_set8(struct gpio_chip *gc, unsigned offset, int value)
static int gw_pld_set8(struct gpio_chip *gc, unsigned int offset, int value)
{
gw_pld_output8(gc, offset, value);
return gw_pld_output8(gc, offset, value);
}
static int gw_pld_probe(struct i2c_client *client)
@@ -86,7 +86,7 @@ static int gw_pld_probe(struct i2c_client *client)
gw->chip.direction_input = gw_pld_input8;
gw->chip.get = gw_pld_get8;
gw->chip.direction_output = gw_pld_output8;
gw->chip.set = gw_pld_set8;
gw->chip.set_rv = gw_pld_set8;
gw->client = client;
/*

View File

@@ -170,7 +170,7 @@ static int egpio_direction_input(struct gpio_chip *chip, unsigned offset)
* Output pins
*/
static void egpio_set(struct gpio_chip *chip, unsigned offset, int value)
static int egpio_set(struct gpio_chip *chip, unsigned int offset, int value)
{
unsigned long flag;
struct egpio_chip *egpio;
@@ -198,6 +198,8 @@ static void egpio_set(struct gpio_chip *chip, unsigned offset, int value)
egpio->cached_values &= ~(1 << offset);
egpio_writew((egpio->cached_values >> shift) & ei->reg_mask, ei, reg);
spin_unlock_irqrestore(&ei->lock, flag);
return 0;
}
static int egpio_direction_output(struct gpio_chip *chip,
@@ -206,12 +208,10 @@ static int egpio_direction_output(struct gpio_chip *chip,
struct egpio_chip *egpio;
egpio = gpiochip_get_data(chip);
if (test_bit(offset, &egpio->is_out)) {
egpio_set(chip, offset, value);
return 0;
} else {
return -EINVAL;
}
if (test_bit(offset, &egpio->is_out))
return egpio_set(chip, offset, value);
return -EINVAL;
}
static int egpio_get_direction(struct gpio_chip *chip, unsigned offset)
@@ -324,7 +324,7 @@ static int __init egpio_probe(struct platform_device *pdev)
chip->parent = &pdev->dev;
chip->owner = THIS_MODULE;
chip->get = egpio_get;
chip->set = egpio_set;
chip->set_rv = egpio_set;
chip->direction_input = egpio_direction_input;
chip->direction_output = egpio_direction_output;
chip->get_direction = egpio_get_direction;

View File

@@ -175,12 +175,16 @@ static int ichx_gpio_direction_input(struct gpio_chip *gpio, unsigned int nr)
static int ichx_gpio_direction_output(struct gpio_chip *gpio, unsigned int nr,
int val)
{
int ret;
/* Disable blink hardware which is available for GPIOs from 0 to 31. */
if (nr < 32 && ichx_priv.desc->have_blink)
ichx_write_bit(GPO_BLINK, nr, 0, 0);
/* Set GPIO output value. */
ichx_write_bit(GPIO_LVL, nr, val, 0);
ret = ichx_write_bit(GPIO_LVL, nr, val, 0);
if (ret)
return ret;
/*
* Try setting pin as an output and verify it worked since many pins
@@ -252,9 +256,9 @@ static int ich6_gpio_request(struct gpio_chip *chip, unsigned int nr)
return ichx_gpio_request(chip, nr);
}
static void ichx_gpio_set(struct gpio_chip *chip, unsigned int nr, int val)
static int ichx_gpio_set(struct gpio_chip *chip, unsigned int nr, int val)
{
ichx_write_bit(GPIO_LVL, nr, val, 0);
return ichx_write_bit(GPIO_LVL, nr, val, 0);
}
static void ichx_gpiolib_setup(struct gpio_chip *chip)
@@ -269,7 +273,7 @@ static void ichx_gpiolib_setup(struct gpio_chip *chip)
chip->get = ichx_priv.desc->get ?
ichx_priv.desc->get : ichx_gpio_get;
chip->set = ichx_gpio_set;
chip->set_rv = ichx_gpio_set;
chip->get_direction = ichx_gpio_get_direction;
chip->direction_input = ichx_gpio_direction_input;
chip->direction_output = ichx_gpio_direction_output;

View File

@@ -6,8 +6,10 @@
* to control the PIN resources on SCU domain.
*/
#include <linux/cleanup.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/gpio/driver.h>
#include <linux/platform_device.h>
#include <linux/firmware/imx/svc/rm.h>
@@ -37,16 +39,11 @@ static int imx_scu_gpio_get(struct gpio_chip *chip, unsigned int offset)
int level;
int err;
if (offset >= chip->ngpio)
return -EINVAL;
mutex_lock(&priv->lock);
/* to read PIN state via scu api */
err = imx_sc_misc_get_control(priv->handle,
scu_rsrc_arr[offset], 0, &level);
mutex_unlock(&priv->lock);
scoped_guard(mutex, &priv->lock) {
/* to read PIN state via scu api */
err = imx_sc_misc_get_control(priv->handle,
scu_rsrc_arr[offset], 0, &level);
}
if (err) {
dev_err(priv->dev, "SCU get failed: %d\n", err);
return err;
@@ -55,31 +52,26 @@ static int imx_scu_gpio_get(struct gpio_chip *chip, unsigned int offset)
return level;
}
static void imx_scu_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
static int imx_scu_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
{
struct scu_gpio_priv *priv = gpiochip_get_data(chip);
int err;
if (offset >= chip->ngpio)
return;
mutex_lock(&priv->lock);
/* to set PIN output level via scu api */
err = imx_sc_misc_set_control(priv->handle,
scu_rsrc_arr[offset], 0, value);
mutex_unlock(&priv->lock);
scoped_guard(mutex, &priv->lock) {
/* to set PIN output level via scu api */
err = imx_sc_misc_set_control(priv->handle,
scu_rsrc_arr[offset], 0, value);
}
if (err)
dev_err(priv->dev, "SCU set (%d) failed: %d\n",
scu_rsrc_arr[offset], err);
return err;
}
static int imx_scu_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
{
if (offset >= chip->ngpio)
return -EINVAL;
return GPIO_LINE_DIRECTION_OUT;
}
@@ -99,7 +91,10 @@ static int imx_scu_gpio_probe(struct platform_device *pdev)
return ret;
priv->dev = dev;
mutex_init(&priv->lock);
ret = devm_mutex_init(&pdev->dev, &priv->lock);
if (ret)
return ret;
gc = &priv->chip;
gc->base = -1;
@@ -107,7 +102,7 @@ static int imx_scu_gpio_probe(struct platform_device *pdev)
gc->ngpio = ARRAY_SIZE(scu_rsrc_arr);
gc->label = dev_name(dev);
gc->get = imx_scu_gpio_get;
gc->set = imx_scu_gpio_set;
gc->set_rv = imx_scu_gpio_set;
gc->get_direction = imx_scu_gpio_get_direction;
platform_set_drvdata(pdev, priv);

View File

@@ -213,8 +213,7 @@ static int it87_gpio_direction_in(struct gpio_chip *chip, unsigned gpio_num)
return rc;
}
static void it87_gpio_set(struct gpio_chip *chip,
unsigned gpio_num, int val)
static int it87_gpio_set(struct gpio_chip *chip, unsigned int gpio_num, int val)
{
u8 mask, curr_vals;
u16 reg;
@@ -228,6 +227,8 @@ static void it87_gpio_set(struct gpio_chip *chip,
outb(curr_vals | mask, reg);
else
outb(curr_vals & ~mask, reg);
return 0;
}
static int it87_gpio_direction_out(struct gpio_chip *chip,
@@ -249,7 +250,9 @@ static int it87_gpio_direction_out(struct gpio_chip *chip,
/* set the output enable bit */
superio_set_mask(mask, group + it87_gpio->output_base);
it87_gpio_set(chip, gpio_num, val);
rc = it87_gpio_set(chip, gpio_num, val);
if (rc)
goto exit;
superio_exit();
@@ -264,7 +267,7 @@ static const struct gpio_chip it87_template_chip = {
.request = it87_gpio_request,
.get = it87_gpio_get,
.direction_input = it87_gpio_direction_in,
.set = it87_gpio_set,
.set_rv = it87_gpio_set,
.direction_output = it87_gpio_direction_out,
.base = -1
};

View File

@@ -76,7 +76,7 @@ static int ttl_get_value(struct gpio_chip *gpio, unsigned offset)
return !!ret;
}
static void ttl_set_value(struct gpio_chip *gpio, unsigned offset, int value)
static int ttl_set_value(struct gpio_chip *gpio, unsigned int offset, int value)
{
struct ttl_module *mod = dev_get_drvdata(gpio->parent);
void __iomem *port;
@@ -103,6 +103,8 @@ static void ttl_set_value(struct gpio_chip *gpio, unsigned offset, int value)
iowrite16be(*shadow, port);
spin_unlock(&mod->lock);
return 0;
}
static void ttl_write_reg(struct ttl_module *mod, u8 reg, u16 val)
@@ -169,7 +171,7 @@ static int ttl_probe(struct platform_device *pdev)
gpio->parent = &pdev->dev;
gpio->label = pdev->name;
gpio->get = ttl_get_value;
gpio->set = ttl_set_value;
gpio->set_rv = ttl_set_value;
gpio->owner = THIS_MODULE;
/* request dynamic allocation */

View File

@@ -63,7 +63,8 @@ static int kempld_gpio_get(struct gpio_chip *chip, unsigned offset)
return !!kempld_gpio_get_bit(pld, KEMPLD_GPIO_LVL_NUM(offset), offset);
}
static void kempld_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
static int kempld_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
{
struct kempld_gpio_data *gpio = gpiochip_get_data(chip);
struct kempld_device_data *pld = gpio->pld;
@@ -71,6 +72,8 @@ static void kempld_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
kempld_get_mutex(pld);
kempld_gpio_bitop(pld, KEMPLD_GPIO_LVL_NUM(offset), offset, value);
kempld_release_mutex(pld);
return 0;
}
static int kempld_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
@@ -166,7 +169,7 @@ 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->set = kempld_gpio_set;
chip->set_rv = kempld_gpio_set;
chip->ngpio = kempld_gpio_pincount(pld);
if (chip->ngpio == 0) {
dev_err(dev, "No GPIO pins detected\n");

View File

@@ -144,8 +144,8 @@ static int ljca_gpio_get_value(struct gpio_chip *chip, unsigned int offset)
return ljca_gpio_read(ljca_gpio, offset);
}
static void ljca_gpio_set_value(struct gpio_chip *chip, unsigned int offset,
int val)
static int ljca_gpio_set_value(struct gpio_chip *chip, unsigned int offset,
int val)
{
struct ljca_gpio_dev *ljca_gpio = gpiochip_get_data(chip);
int ret;
@@ -155,6 +155,8 @@ static void ljca_gpio_set_value(struct gpio_chip *chip, unsigned int offset,
dev_err(chip->parent,
"set value failed offset: %u val: %d ret: %d\n",
offset, val, ret);
return ret;
}
static int ljca_gpio_direction_input(struct gpio_chip *chip, unsigned int offset)
@@ -183,7 +185,10 @@ static int ljca_gpio_direction_output(struct gpio_chip *chip,
if (ret)
return ret;
ljca_gpio_set_value(chip, offset, val);
ret = ljca_gpio_set_value(chip, offset, val);
if (ret)
return ret;
set_bit(offset, ljca_gpio->output_enabled);
return 0;
@@ -432,7 +437,7 @@ static int ljca_gpio_probe(struct auxiliary_device *auxdev,
ljca_gpio->gc.direction_output = ljca_gpio_direction_output;
ljca_gpio->gc.get_direction = ljca_gpio_get_direction;
ljca_gpio->gc.get = ljca_gpio_get_value;
ljca_gpio->gc.set = ljca_gpio_set_value;
ljca_gpio->gc.set_rv = ljca_gpio_set_value;
ljca_gpio->gc.set_config = ljca_gpio_set_config;
ljca_gpio->gc.init_valid_mask = ljca_gpio_init_valid_mask;
ljca_gpio->gc.can_sleep = true;

View File

@@ -61,23 +61,22 @@ static int logicvc_gpio_get(struct gpio_chip *chip, unsigned offset)
return !!(value & bit);
}
static void logicvc_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
static int logicvc_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
{
struct logicvc_gpio *logicvc = gpiochip_get_data(chip);
unsigned int reg, bit;
logicvc_gpio_offset(logicvc, offset, &reg, &bit);
regmap_update_bits(logicvc->regmap, reg, bit, value ? bit : 0);
return regmap_update_bits(logicvc->regmap, reg, bit, value ? bit : 0);
}
static int logicvc_gpio_direction_output(struct gpio_chip *chip,
unsigned offset, int value)
{
/* Pins are always configured as output, so just set the value. */
logicvc_gpio_set(chip, offset, value);
return 0;
return logicvc_gpio_set(chip, offset, value);
}
static struct regmap_config logicvc_gpio_regmap_config = {
@@ -135,7 +134,7 @@ static int logicvc_gpio_probe(struct platform_device *pdev)
logicvc->chip.ngpio = LOGICVC_CTRL_GPIO_BITS +
LOGICVC_POWER_CTRL_GPIO_BITS;
logicvc->chip.get = logicvc_gpio_get;
logicvc->chip.set = logicvc_gpio_set;
logicvc->chip.set_rv = logicvc_gpio_set;
logicvc->chip.direction_output = logicvc_gpio_direction_output;
return devm_gpiochip_add_data(dev, &logicvc->chip, logicvc);

View File

@@ -105,7 +105,7 @@ static int loongson_gpio_get_direction(struct gpio_chip *chip, unsigned int pin)
return GPIO_LINE_DIRECTION_OUT;
}
static void loongson_gpio_set(struct gpio_chip *chip, unsigned int pin, int value)
static int loongson_gpio_set(struct gpio_chip *chip, unsigned int pin, int value)
{
unsigned long flags;
struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
@@ -113,6 +113,8 @@ static void loongson_gpio_set(struct gpio_chip *chip, unsigned int pin, int valu
spin_lock_irqsave(&lgpio->lock, flags);
loongson_commit_level(lgpio, pin, value);
spin_unlock_irqrestore(&lgpio->lock, flags);
return 0;
}
static int loongson_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
@@ -155,7 +157,7 @@ static int loongson_gpio_init(struct device *dev, struct loongson_gpio_chip *lgp
lgpio->chip.get = loongson_gpio_get;
lgpio->chip.get_direction = loongson_gpio_get_direction;
lgpio->chip.direction_output = loongson_gpio_direction_output;
lgpio->chip.set = loongson_gpio_set;
lgpio->chip.set_rv = loongson_gpio_set;
lgpio->chip.parent = dev;
spin_lock_init(&lgpio->lock);
}

View File

@@ -48,8 +48,8 @@ static int loongson_gpio_get_value(struct gpio_chip *chip, unsigned gpio)
return !!(val & BIT(gpio + LOONGSON_GPIO_IN_OFFSET));
}
static void loongson_gpio_set_value(struct gpio_chip *chip,
unsigned gpio, int value)
static int loongson_gpio_set_value(struct gpio_chip *chip, unsigned int gpio,
int value)
{
u32 val;
@@ -61,6 +61,8 @@ static void loongson_gpio_set_value(struct gpio_chip *chip,
val &= ~BIT(gpio);
LOONGSON_GPIODATA = val;
spin_unlock(&gpio_lock);
return 0;
}
static int loongson_gpio_direction_input(struct gpio_chip *chip, unsigned gpio)
@@ -104,7 +106,7 @@ static int loongson_gpio_probe(struct platform_device *pdev)
gc->base = 0;
gc->ngpio = LOONGSON_N_GPIO;
gc->get = loongson_gpio_get_value;
gc->set = loongson_gpio_set_value;
gc->set_rv = loongson_gpio_set_value;
gc->direction_input = loongson_gpio_direction_input;
gc->direction_output = loongson_gpio_direction_output;

View File

@@ -147,7 +147,8 @@ static int lp3943_gpio_get(struct gpio_chip *chip, unsigned int offset)
return lp3943_get_gpio_out_status(lp3943_gpio, chip, offset);
}
static void lp3943_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
static int lp3943_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
{
struct lp3943_gpio *lp3943_gpio = gpiochip_get_data(chip);
u8 data;
@@ -157,15 +158,19 @@ static void lp3943_gpio_set(struct gpio_chip *chip, unsigned int offset, int val
else
data = LP3943_GPIO_OUT_LOW;
lp3943_gpio_set_mode(lp3943_gpio, offset, data);
return lp3943_gpio_set_mode(lp3943_gpio, offset, data);
}
static int lp3943_gpio_direction_output(struct gpio_chip *chip, unsigned int offset,
int value)
{
struct lp3943_gpio *lp3943_gpio = gpiochip_get_data(chip);
int ret;
ret = lp3943_gpio_set(chip, offset, value);
if (ret)
return ret;
lp3943_gpio_set(chip, offset, value);
lp3943_gpio->input_mask &= ~BIT(offset);
return 0;
@@ -179,7 +184,7 @@ static const struct gpio_chip lp3943_gpio_chip = {
.direction_input = lp3943_gpio_direction_input,
.get = lp3943_gpio_get,
.direction_output = lp3943_gpio_direction_output,
.set = lp3943_gpio_set,
.set_rv = lp3943_gpio_set,
.base = -1,
.ngpio = LP3943_MAX_GPIO,
.can_sleep = 1,

View File

@@ -58,14 +58,14 @@ static int lp873x_gpio_get(struct gpio_chip *chip, unsigned int offset)
return val & BIT(offset * BITS_PER_GPO);
}
static void lp873x_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
static int lp873x_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
{
struct lp873x_gpio *gpio = gpiochip_get_data(chip);
regmap_update_bits(gpio->lp873->regmap, LP873X_REG_GPO_CTRL,
BIT(offset * BITS_PER_GPO),
value ? BIT(offset * BITS_PER_GPO) : 0);
return regmap_update_bits(gpio->lp873->regmap, LP873X_REG_GPO_CTRL,
BIT(offset * BITS_PER_GPO),
value ? BIT(offset * BITS_PER_GPO) : 0);
}
static int lp873x_gpio_request(struct gpio_chip *gc, unsigned int offset)
@@ -124,7 +124,7 @@ static const struct gpio_chip template_chip = {
.direction_input = lp873x_gpio_direction_input,
.direction_output = lp873x_gpio_direction_output,
.get = lp873x_gpio_get,
.set = lp873x_gpio_set,
.set_rv = lp873x_gpio_set,
.set_config = lp873x_gpio_set_config,
.base = -1,
.ngpio = 2,

View File

@@ -30,13 +30,13 @@ static int lp87565_gpio_get(struct gpio_chip *chip, unsigned int offset)
return !!(val & BIT(offset));
}
static void lp87565_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
static int lp87565_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
{
struct lp87565_gpio *gpio = gpiochip_get_data(chip);
regmap_update_bits(gpio->map, LP87565_REG_GPIO_OUT,
BIT(offset), value ? BIT(offset) : 0);
return regmap_update_bits(gpio->map, LP87565_REG_GPIO_OUT,
BIT(offset), value ? BIT(offset) : 0);
}
static int lp87565_gpio_get_direction(struct gpio_chip *chip,
@@ -69,8 +69,11 @@ static int lp87565_gpio_direction_output(struct gpio_chip *chip,
unsigned int offset, int value)
{
struct lp87565_gpio *gpio = gpiochip_get_data(chip);
int ret;
lp87565_gpio_set(chip, offset, value);
ret = lp87565_gpio_set(chip, offset, value);
if (ret)
return ret;
return regmap_update_bits(gpio->map,
LP87565_REG_GPIO_CONFIG,
@@ -136,7 +139,7 @@ static const struct gpio_chip template_chip = {
.direction_input = lp87565_gpio_direction_input,
.direction_output = lp87565_gpio_direction_output,
.get = lp87565_gpio_get,
.set = lp87565_gpio_set,
.set_rv = lp87565_gpio_set,
.set_config = lp87565_gpio_set_config,
.base = -1,
.ngpio = 3,

View File

@@ -42,6 +42,7 @@ struct lpc18xx_gpio_pin_ic {
void __iomem *base;
struct irq_domain *domain;
struct raw_spinlock lock;
struct gpio_chip *gpio;
};
struct lpc18xx_gpio_chip {
@@ -74,6 +75,7 @@ static void lpc18xx_gpio_pin_ic_mask(struct irq_data *d)
{
struct lpc18xx_gpio_pin_ic *ic = d->chip_data;
u32 type = irqd_get_trigger_type(d);
irq_hw_number_t hwirq = irqd_to_hwirq(d);
raw_spin_lock(&ic->lock);
@@ -88,12 +90,17 @@ static void lpc18xx_gpio_pin_ic_mask(struct irq_data *d)
raw_spin_unlock(&ic->lock);
irq_chip_mask_parent(d);
gpiochip_disable_irq(ic->gpio, hwirq);
}
static void lpc18xx_gpio_pin_ic_unmask(struct irq_data *d)
{
struct lpc18xx_gpio_pin_ic *ic = d->chip_data;
u32 type = irqd_get_trigger_type(d);
irq_hw_number_t hwirq = irqd_to_hwirq(d);
gpiochip_enable_irq(ic->gpio, hwirq);
raw_spin_lock(&ic->lock);
@@ -149,13 +156,14 @@ static int lpc18xx_gpio_pin_ic_set_type(struct irq_data *d, unsigned int type)
return 0;
}
static struct irq_chip lpc18xx_gpio_pin_ic = {
static const struct irq_chip lpc18xx_gpio_pin_ic = {
.name = "LPC18xx GPIO pin",
.irq_mask = lpc18xx_gpio_pin_ic_mask,
.irq_unmask = lpc18xx_gpio_pin_ic_unmask,
.irq_eoi = lpc18xx_gpio_pin_ic_eoi,
.irq_set_type = lpc18xx_gpio_pin_ic_set_type,
.flags = IRQCHIP_SET_TYPE_MASKED,
.flags = IRQCHIP_IMMUTABLE | IRQCHIP_SET_TYPE_MASKED,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static int lpc18xx_gpio_pin_ic_domain_alloc(struct irq_domain *domain,
@@ -249,6 +257,7 @@ static int lpc18xx_gpio_pin_ic_probe(struct lpc18xx_gpio_chip *gc)
goto free_iomap;
}
ic->gpio = &gc->gpio;
gc->pin_ic = ic;
return 0;
@@ -261,10 +270,14 @@ static int lpc18xx_gpio_pin_ic_probe(struct lpc18xx_gpio_chip *gc)
return ret;
}
static void lpc18xx_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
static int lpc18xx_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
{
struct lpc18xx_gpio_chip *gc = gpiochip_get_data(chip);
writeb(value ? 1 : 0, gc->base + offset);
return 0;
}
static int lpc18xx_gpio_get(struct gpio_chip *chip, unsigned offset)
@@ -314,7 +327,7 @@ static const struct gpio_chip lpc18xx_chip = {
.free = gpiochip_generic_free,
.direction_input = lpc18xx_gpio_direction_input,
.direction_output = lpc18xx_gpio_direction_output,
.set = lpc18xx_gpio_set,
.set_rv = lpc18xx_gpio_set,
.get = lpc18xx_gpio_get,
.ngpio = LPC18XX_MAX_PORTS * LPC18XX_PINS_PER_PORT,
.owner = THIS_MODULE,

View File

@@ -340,28 +340,34 @@ static int lpc32xx_gpio_dir_out_always(struct gpio_chip *chip, unsigned pin,
return 0;
}
static void lpc32xx_gpio_set_value_p012(struct gpio_chip *chip, unsigned pin,
int value)
static int lpc32xx_gpio_set_value_p012(struct gpio_chip *chip,
unsigned int pin, int value)
{
struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
__set_gpio_level_p012(group, pin, value);
return 0;
}
static void lpc32xx_gpio_set_value_p3(struct gpio_chip *chip, unsigned pin,
int value)
static int lpc32xx_gpio_set_value_p3(struct gpio_chip *chip,
unsigned int pin, int value)
{
struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
__set_gpio_level_p3(group, pin, value);
return 0;
}
static void lpc32xx_gpo_set_value(struct gpio_chip *chip, unsigned pin,
int value)
static int lpc32xx_gpo_set_value(struct gpio_chip *chip, unsigned int pin,
int value)
{
struct lpc32xx_gpio_chip *group = gpiochip_get_data(chip);
__set_gpo_level_p3(group, pin, value);
return 0;
}
static int lpc32xx_gpo_get_value(struct gpio_chip *chip, unsigned pin)
@@ -401,7 +407,7 @@ static struct lpc32xx_gpio_chip lpc32xx_gpiochip[] = {
.direction_input = lpc32xx_gpio_dir_input_p012,
.get = lpc32xx_gpio_get_value_p012,
.direction_output = lpc32xx_gpio_dir_output_p012,
.set = lpc32xx_gpio_set_value_p012,
.set_rv = lpc32xx_gpio_set_value_p012,
.request = lpc32xx_gpio_request,
.to_irq = lpc32xx_gpio_to_irq_p01,
.base = LPC32XX_GPIO_P0_GRP,
@@ -417,7 +423,7 @@ static struct lpc32xx_gpio_chip lpc32xx_gpiochip[] = {
.direction_input = lpc32xx_gpio_dir_input_p012,
.get = lpc32xx_gpio_get_value_p012,
.direction_output = lpc32xx_gpio_dir_output_p012,
.set = lpc32xx_gpio_set_value_p012,
.set_rv = lpc32xx_gpio_set_value_p012,
.request = lpc32xx_gpio_request,
.to_irq = lpc32xx_gpio_to_irq_p01,
.base = LPC32XX_GPIO_P1_GRP,
@@ -433,7 +439,7 @@ static struct lpc32xx_gpio_chip lpc32xx_gpiochip[] = {
.direction_input = lpc32xx_gpio_dir_input_p012,
.get = lpc32xx_gpio_get_value_p012,
.direction_output = lpc32xx_gpio_dir_output_p012,
.set = lpc32xx_gpio_set_value_p012,
.set_rv = lpc32xx_gpio_set_value_p012,
.request = lpc32xx_gpio_request,
.base = LPC32XX_GPIO_P2_GRP,
.ngpio = LPC32XX_GPIO_P2_MAX,
@@ -448,7 +454,7 @@ static struct lpc32xx_gpio_chip lpc32xx_gpiochip[] = {
.direction_input = lpc32xx_gpio_dir_input_p3,
.get = lpc32xx_gpio_get_value_p3,
.direction_output = lpc32xx_gpio_dir_output_p3,
.set = lpc32xx_gpio_set_value_p3,
.set_rv = lpc32xx_gpio_set_value_p3,
.request = lpc32xx_gpio_request,
.to_irq = lpc32xx_gpio_to_irq_gpio_p3,
.base = LPC32XX_GPIO_P3_GRP,
@@ -476,7 +482,7 @@ static struct lpc32xx_gpio_chip lpc32xx_gpiochip[] = {
.chip = {
.label = "gpo_p3",
.direction_output = lpc32xx_gpio_dir_out_always,
.set = lpc32xx_gpo_set_value,
.set_rv = lpc32xx_gpo_set_value,
.get = lpc32xx_gpo_get_value,
.request = lpc32xx_gpio_request,
.base = LPC32XX_GPO_P3_GRP,

View File

@@ -87,23 +87,17 @@ static int madera_gpio_direction_out(struct gpio_chip *chip,
MADERA_GP1_LVL_MASK, reg_val);
}
static void madera_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
static int madera_gpio_set(struct gpio_chip *chip, unsigned int offset,
int value)
{
struct madera_gpio *madera_gpio = gpiochip_get_data(chip);
struct madera *madera = madera_gpio->madera;
unsigned int reg_offset = 2 * offset;
unsigned int reg_val = value ? MADERA_GP1_LVL : 0;
int ret;
ret = regmap_update_bits(madera->regmap,
MADERA_GPIO1_CTRL_1 + reg_offset,
MADERA_GP1_LVL_MASK, reg_val);
/* set() doesn't return an error so log a warning */
if (ret)
dev_warn(madera->dev, "Failed to write to 0x%x (%d)\n",
MADERA_GPIO1_CTRL_1 + reg_offset, ret);
return regmap_update_bits(madera->regmap,
MADERA_GPIO1_CTRL_1 + reg_offset,
MADERA_GP1_LVL_MASK, reg_val);
}
static const struct gpio_chip madera_gpio_chip = {
@@ -115,7 +109,7 @@ static const struct gpio_chip madera_gpio_chip = {
.direction_input = madera_gpio_direction_in,
.get = madera_gpio_get,
.direction_output = madera_gpio_direction_out,
.set = madera_gpio_set,
.set_rv = madera_gpio_set,
.set_config = gpiochip_generic_config,
.can_sleep = true,
};

View File

@@ -103,19 +103,6 @@ static int max3191x_direction_input(struct gpio_chip *gpio, unsigned int offset)
return 0;
}
static int max3191x_direction_output(struct gpio_chip *gpio,
unsigned int offset, int value)
{
return -EINVAL;
}
static void max3191x_set(struct gpio_chip *gpio, unsigned int offset, int value)
{ }
static void max3191x_set_multiple(struct gpio_chip *gpio, unsigned long *mask,
unsigned long *bits)
{ }
static unsigned int max3191x_wordlen(struct max3191x_chip *max3191x)
{
return max3191x->mode == STATUS_BYTE_ENABLED ? 2 : 1;
@@ -421,9 +408,6 @@ static int max3191x_probe(struct spi_device *spi)
max3191x->gpio.get_direction = max3191x_get_direction;
max3191x->gpio.direction_input = max3191x_direction_input;
max3191x->gpio.direction_output = max3191x_direction_output;
max3191x->gpio.set = max3191x_set;
max3191x->gpio.set_multiple = max3191x_set_multiple;
max3191x->gpio.get = max3191x_get;
max3191x->gpio.get_multiple = max3191x_get_multiple;
max3191x->gpio.set_config = max3191x_set_config;

View File

@@ -143,18 +143,21 @@ static int max7301_get(struct gpio_chip *chip, unsigned offset)
return level;
}
static void max7301_set(struct gpio_chip *chip, unsigned offset, int value)
static int max7301_set(struct gpio_chip *chip, unsigned int offset, int value)
{
struct max7301 *ts = gpiochip_get_data(chip);
int ret;
/* First 4 pins are unused in the controller */
offset += 4;
mutex_lock(&ts->lock);
__max7301_set(ts, offset, value);
ret = __max7301_set(ts, offset, value);
mutex_unlock(&ts->lock);
return ret;
}
int __max730x_probe(struct max7301 *ts)
@@ -185,7 +188,7 @@ int __max730x_probe(struct max7301 *ts)
ts->chip.direction_input = max7301_direction_input;
ts->chip.get = max7301_get;
ts->chip.direction_output = max7301_direction_output;
ts->chip.set = max7301_set;
ts->chip.set_rv = max7301_set;
ts->chip.ngpio = PIN_NUMBER;
ts->chip.can_sleep = true;

View File

@@ -225,16 +225,19 @@ static void max732x_gpio_set_mask(struct gpio_chip *gc, unsigned off, int mask,
mutex_unlock(&chip->lock);
}
static void max732x_gpio_set_value(struct gpio_chip *gc, unsigned off, int val)
static int max732x_gpio_set_value(struct gpio_chip *gc, unsigned int off,
int val)
{
unsigned base = off & ~0x7;
uint8_t mask = 1u << (off & 0x7);
max732x_gpio_set_mask(gc, base, mask, val << (off & 0x7));
return 0;
}
static void max732x_gpio_set_multiple(struct gpio_chip *gc,
unsigned long *mask, unsigned long *bits)
static int max732x_gpio_set_multiple(struct gpio_chip *gc,
unsigned long *mask, unsigned long *bits)
{
unsigned mask_lo = mask[0] & 0xff;
unsigned mask_hi = (mask[0] >> 8) & 0xff;
@@ -243,6 +246,8 @@ static void max732x_gpio_set_multiple(struct gpio_chip *gc,
max732x_gpio_set_mask(gc, 0, mask_lo, bits[0] & 0xff);
if (mask_hi)
max732x_gpio_set_mask(gc, 8, mask_hi, (bits[0] >> 8) & 0xff);
return 0;
}
static int max732x_gpio_direction_input(struct gpio_chip *gc, unsigned off)
@@ -580,8 +585,8 @@ static int max732x_setup_gpio(struct max732x_chip *chip,
gc->direction_input = max732x_gpio_direction_input;
if (chip->dir_output) {
gc->direction_output = max732x_gpio_direction_output;
gc->set = max732x_gpio_set_value;
gc->set_multiple = max732x_gpio_set_multiple;
gc->set_rv = max732x_gpio_set_value;
gc->set_multiple_rv = max732x_gpio_set_multiple;
}
gc->get = max732x_gpio_get_value;
gc->can_sleep = true;

View File

@@ -223,20 +223,17 @@ static int max77620_gpio_set_debounce(struct max77620_gpio *mgpio,
return ret;
}
static void max77620_gpio_set(struct gpio_chip *gc, unsigned int offset,
int value)
static int max77620_gpio_set(struct gpio_chip *gc, unsigned int offset,
int value)
{
struct max77620_gpio *mgpio = gpiochip_get_data(gc);
u8 val;
int ret;
val = (value) ? MAX77620_CNFG_GPIO_OUTPUT_VAL_HIGH :
MAX77620_CNFG_GPIO_OUTPUT_VAL_LOW;
ret = regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset),
MAX77620_CNFG_GPIO_OUTPUT_VAL_MASK, val);
if (ret < 0)
dev_err(mgpio->dev, "CNFG_GPIO_OUT update failed: %d\n", ret);
return regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset),
MAX77620_CNFG_GPIO_OUTPUT_VAL_MASK, val);
}
static int max77620_gpio_set_config(struct gpio_chip *gc, unsigned int offset,
@@ -314,7 +311,7 @@ static int max77620_gpio_probe(struct platform_device *pdev)
mgpio->gpio_chip.direction_input = max77620_gpio_dir_input;
mgpio->gpio_chip.get = max77620_gpio_get;
mgpio->gpio_chip.direction_output = max77620_gpio_dir_output;
mgpio->gpio_chip.set = max77620_gpio_set;
mgpio->gpio_chip.set_rv = max77620_gpio_set;
mgpio->gpio_chip.set_config = max77620_gpio_set_config;
mgpio->gpio_chip.ngpio = MAX77620_GPIO_NR;
mgpio->gpio_chip.can_sleep = 1;

View File

@@ -0,0 +1,530 @@
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright 2020 Google Inc
// Copyright 2025 Linaro Ltd.
//
// GPIO driver for Maxim MAX77759
#include <linux/dev_printk.h>
#include <linux/device.h>
#include <linux/device/driver.h>
#include <linux/gpio/driver.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqreturn.h>
#include <linux/lockdep.h>
#include <linux/mfd/max77759.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/seq_file.h>
#define MAX77759_N_GPIOS ARRAY_SIZE(max77759_gpio_line_names)
static const char * const max77759_gpio_line_names[] = { "GPIO5", "GPIO6" };
struct max77759_gpio_chip {
struct regmap *map;
struct max77759 *max77759;
struct gpio_chip gc;
struct mutex maxq_lock; /* protect MaxQ r/m/w operations */
struct mutex irq_lock; /* protect irq bus */
int irq_mask;
int irq_mask_changed;
int irq_trig;
int irq_trig_changed;
};
#define MAX77759_GPIOx_TRIGGER(offs, val) (((val) & 1) << (offs))
#define MAX77759_GPIOx_TRIGGER_MASK(offs) MAX77759_GPIOx_TRIGGER(offs, ~0)
enum max77759_trigger_gpio_type {
MAX77759_GPIO_TRIGGER_RISING = 0,
MAX77759_GPIO_TRIGGER_FALLING = 1
};
#define MAX77759_GPIOx_DIR(offs, dir) (((dir) & 1) << (2 + (3 * (offs))))
#define MAX77759_GPIOx_DIR_MASK(offs) MAX77759_GPIOx_DIR(offs, ~0)
enum max77759_control_gpio_dir {
MAX77759_GPIO_DIR_IN = 0,
MAX77759_GPIO_DIR_OUT = 1
};
#define MAX77759_GPIOx_OUTVAL(offs, val) (((val) & 1) << (3 + (3 * (offs))))
#define MAX77759_GPIOx_OUTVAL_MASK(offs) MAX77759_GPIOx_OUTVAL(offs, ~0)
#define MAX77759_GPIOx_INVAL_MASK(offs) (BIT(4) << (3 * (offs)))
static int max77759_gpio_maxq_gpio_trigger_read(struct max77759_gpio_chip *chip)
{
DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, 1);
DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length, 2);
int ret;
cmd->cmd[0] = MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_READ;
ret = max77759_maxq_command(chip->max77759, cmd, rsp);
if (ret < 0)
return ret;
return rsp->rsp[1];
}
static int max77759_gpio_maxq_gpio_trigger_write(struct max77759_gpio_chip *chip,
u8 trigger)
{
DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, 2);
cmd->cmd[0] = MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_WRITE;
cmd->cmd[1] = trigger;
return max77759_maxq_command(chip->max77759, cmd, NULL);
}
static int max77759_gpio_maxq_gpio_control_read(struct max77759_gpio_chip *chip)
{
DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, 1);
DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length, 2);
int ret;
cmd->cmd[0] = MAX77759_MAXQ_OPCODE_GPIO_CONTROL_READ;
ret = max77759_maxq_command(chip->max77759, cmd, rsp);
if (ret < 0)
return ret;
return rsp->rsp[1];
}
static int max77759_gpio_maxq_gpio_control_write(struct max77759_gpio_chip *chip,
u8 ctrl)
{
DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, 2);
cmd->cmd[0] = MAX77759_MAXQ_OPCODE_GPIO_CONTROL_WRITE;
cmd->cmd[1] = ctrl;
return max77759_maxq_command(chip->max77759, cmd, NULL);
}
static int
max77759_gpio_direction_from_control(int ctrl, unsigned int offset)
{
enum max77759_control_gpio_dir dir;
dir = !!(ctrl & MAX77759_GPIOx_DIR_MASK(offset));
return ((dir == MAX77759_GPIO_DIR_OUT)
? GPIO_LINE_DIRECTION_OUT
: GPIO_LINE_DIRECTION_IN);
}
static int max77759_gpio_get_direction(struct gpio_chip *gc,
unsigned int offset)
{
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
int ctrl;
ctrl = max77759_gpio_maxq_gpio_control_read(chip);
if (ctrl < 0)
return ctrl;
return max77759_gpio_direction_from_control(ctrl, offset);
}
static int max77759_gpio_direction_helper(struct gpio_chip *gc,
unsigned int offset,
enum max77759_control_gpio_dir dir,
int value)
{
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
int ctrl, new_ctrl;
guard(mutex)(&chip->maxq_lock);
ctrl = max77759_gpio_maxq_gpio_control_read(chip);
if (ctrl < 0)
return ctrl;
new_ctrl = ctrl & ~MAX77759_GPIOx_DIR_MASK(offset);
new_ctrl |= MAX77759_GPIOx_DIR(offset, dir);
if (dir == MAX77759_GPIO_DIR_OUT) {
new_ctrl &= ~MAX77759_GPIOx_OUTVAL_MASK(offset);
new_ctrl |= MAX77759_GPIOx_OUTVAL(offset, value);
}
if (new_ctrl == ctrl)
return 0;
return max77759_gpio_maxq_gpio_control_write(chip, new_ctrl);
}
static int max77759_gpio_direction_input(struct gpio_chip *gc,
unsigned int offset)
{
return max77759_gpio_direction_helper(gc, offset,
MAX77759_GPIO_DIR_IN, -1);
}
static int max77759_gpio_direction_output(struct gpio_chip *gc,
unsigned int offset, int value)
{
return max77759_gpio_direction_helper(gc, offset,
MAX77759_GPIO_DIR_OUT, value);
}
static int max77759_gpio_get_value(struct gpio_chip *gc, unsigned int offset)
{
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
int ctrl, mask;
ctrl = max77759_gpio_maxq_gpio_control_read(chip);
if (ctrl < 0)
return ctrl;
/*
* The input status bit doesn't reflect the pin state when the GPIO is
* configured as an output. Check the direction, and inspect the input
* or output bit accordingly.
*/
mask = ((max77759_gpio_direction_from_control(ctrl, offset)
== GPIO_LINE_DIRECTION_IN)
? MAX77759_GPIOx_INVAL_MASK(offset)
: MAX77759_GPIOx_OUTVAL_MASK(offset));
return !!(ctrl & mask);
}
static int max77759_gpio_set_value(struct gpio_chip *gc,
unsigned int offset, int value)
{
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
int ctrl, new_ctrl;
guard(mutex)(&chip->maxq_lock);
ctrl = max77759_gpio_maxq_gpio_control_read(chip);
if (ctrl < 0)
return ctrl;
new_ctrl = ctrl & ~MAX77759_GPIOx_OUTVAL_MASK(offset);
new_ctrl |= MAX77759_GPIOx_OUTVAL(offset, value);
if (new_ctrl == ctrl)
return 0;
return max77759_gpio_maxq_gpio_control_write(chip, new_ctrl);
}
static void max77759_gpio_irq_mask(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
irq_hw_number_t hwirq = irqd_to_hwirq(d);
chip->irq_mask &= ~MAX77759_MAXQ_REG_UIC_INT1_GPIOxI_MASK(hwirq);
chip->irq_mask |= MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(hwirq, 1);
chip->irq_mask_changed |= MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(hwirq, 1);
gpiochip_disable_irq(gc, hwirq);
}
static void max77759_gpio_irq_unmask(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
irq_hw_number_t hwirq = irqd_to_hwirq(d);
gpiochip_enable_irq(gc, hwirq);
chip->irq_mask &= ~MAX77759_MAXQ_REG_UIC_INT1_GPIOxI_MASK(hwirq);
chip->irq_mask |= MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(hwirq, 0);
chip->irq_mask_changed |= MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(hwirq, 1);
}
static int max77759_gpio_set_irq_type(struct irq_data *d, unsigned int type)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
irq_hw_number_t hwirq = irqd_to_hwirq(d);
chip->irq_trig &= ~MAX77759_GPIOx_TRIGGER_MASK(hwirq);
switch (type) {
case IRQ_TYPE_EDGE_RISING:
chip->irq_trig |= MAX77759_GPIOx_TRIGGER(hwirq,
MAX77759_GPIO_TRIGGER_RISING);
break;
case IRQ_TYPE_EDGE_FALLING:
chip->irq_trig |= MAX77759_GPIOx_TRIGGER(hwirq,
MAX77759_GPIO_TRIGGER_FALLING);
break;
default:
return -EINVAL;
}
chip->irq_trig_changed |= MAX77759_GPIOx_TRIGGER(hwirq, 1);
return 0;
}
static void max77759_gpio_bus_lock(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
mutex_lock(&chip->irq_lock);
}
static int max77759_gpio_bus_sync_unlock_helper(struct gpio_chip *gc,
struct max77759_gpio_chip *chip)
__must_hold(&chip->maxq_lock)
{
int ctrl, trigger, new_trigger, new_ctrl;
unsigned long irq_trig_changed;
int offset;
int ret;
lockdep_assert_held(&chip->maxq_lock);
ctrl = max77759_gpio_maxq_gpio_control_read(chip);
trigger = max77759_gpio_maxq_gpio_trigger_read(chip);
if (ctrl < 0 || trigger < 0) {
dev_err(gc->parent, "failed to read current state: %d / %d\n",
ctrl, trigger);
return (ctrl < 0) ? ctrl : trigger;
}
new_trigger = trigger & ~chip->irq_trig_changed;
new_trigger |= (chip->irq_trig & chip->irq_trig_changed);
/* change GPIO direction if required */
new_ctrl = ctrl;
irq_trig_changed = chip->irq_trig_changed;
for_each_set_bit(offset, &irq_trig_changed, MAX77759_N_GPIOS) {
new_ctrl &= ~MAX77759_GPIOx_DIR_MASK(offset);
new_ctrl |= MAX77759_GPIOx_DIR(offset, MAX77759_GPIO_DIR_IN);
}
if (new_trigger != trigger) {
ret = max77759_gpio_maxq_gpio_trigger_write(chip, new_trigger);
if (ret) {
dev_err(gc->parent,
"failed to write new trigger: %d\n", ret);
return ret;
}
}
if (new_ctrl != ctrl) {
ret = max77759_gpio_maxq_gpio_control_write(chip, new_ctrl);
if (ret) {
dev_err(gc->parent,
"failed to write new control: %d\n", ret);
return ret;
}
}
chip->irq_trig_changed = 0;
return 0;
}
static void max77759_gpio_bus_sync_unlock(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct max77759_gpio_chip *chip = gpiochip_get_data(gc);
int ret;
scoped_guard(mutex, &chip->maxq_lock) {
ret = max77759_gpio_bus_sync_unlock_helper(gc, chip);
if (ret)
goto out_unlock;
}
ret = regmap_update_bits(chip->map,
MAX77759_MAXQ_REG_UIC_INT1_M,
chip->irq_mask_changed, chip->irq_mask);
if (ret) {
dev_err(gc->parent,
"failed to update UIC_INT1 irq mask: %d\n", ret);
goto out_unlock;
}
chip->irq_mask_changed = 0;
out_unlock:
mutex_unlock(&chip->irq_lock);
}
static void max77759_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
seq_puts(p, dev_name(gc->parent));
}
static const struct irq_chip max77759_gpio_irq_chip = {
.irq_mask = max77759_gpio_irq_mask,
.irq_unmask = max77759_gpio_irq_unmask,
.irq_set_type = max77759_gpio_set_irq_type,
.irq_bus_lock = max77759_gpio_bus_lock,
.irq_bus_sync_unlock = max77759_gpio_bus_sync_unlock,
.irq_print_chip = max77759_gpio_irq_print_chip,
.flags = IRQCHIP_IMMUTABLE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static irqreturn_t max77759_gpio_irqhandler(int irq, void *data)
{
struct max77759_gpio_chip *chip = data;
struct gpio_chip *gc = &chip->gc;
bool handled = false;
/* iterate until no interrupt is pending */
while (true) {
unsigned int uic_int1;
int ret;
unsigned long pending;
int offset;
ret = regmap_read(chip->map, MAX77759_MAXQ_REG_UIC_INT1,
&uic_int1);
if (ret < 0) {
dev_err_ratelimited(gc->parent,
"failed to read IRQ status: %d\n",
ret);
/*
* If !handled, we have looped not even once, which
* means we should return IRQ_NONE in that case (and
* of course IRQ_HANDLED otherwise).
*/
return IRQ_RETVAL(handled);
}
pending = uic_int1;
pending &= (MAX77759_MAXQ_REG_UIC_INT1_GPIO6I
| MAX77759_MAXQ_REG_UIC_INT1_GPIO5I);
if (!pending)
break;
for_each_set_bit(offset, &pending, MAX77759_N_GPIOS) {
/*
* ACK interrupt by writing 1 to bit 'offset', all
* others need to be written as 0. This needs to be
* done unconditionally hence regmap_set_bits() is
* inappropriate here.
*/
regmap_write(chip->map, MAX77759_MAXQ_REG_UIC_INT1,
BIT(offset));
handle_nested_irq(irq_find_mapping(gc->irq.domain,
offset));
handled = true;
}
}
return IRQ_RETVAL(handled);
}
static int max77759_gpio_probe(struct platform_device *pdev)
{
struct max77759_gpio_chip *chip;
int irq;
struct gpio_irq_chip *girq;
int ret;
unsigned long irq_flags;
struct irq_data *irqd;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->map = dev_get_regmap(pdev->dev.parent, "maxq");
if (!chip->map)
return dev_err_probe(&pdev->dev, -ENODEV, "Missing regmap\n");
irq = platform_get_irq_byname(pdev, "GPI");
if (irq < 0)
return dev_err_probe(&pdev->dev, irq, "Failed to get IRQ\n");
chip->max77759 = dev_get_drvdata(pdev->dev.parent);
ret = devm_mutex_init(&pdev->dev, &chip->maxq_lock);
if (ret)
return ret;
ret = devm_mutex_init(&pdev->dev, &chip->irq_lock);
if (ret)
return ret;
chip->gc.base = -1;
chip->gc.label = dev_name(&pdev->dev);
chip->gc.parent = &pdev->dev;
chip->gc.can_sleep = true;
chip->gc.names = max77759_gpio_line_names;
chip->gc.ngpio = MAX77759_N_GPIOS;
chip->gc.get_direction = max77759_gpio_get_direction;
chip->gc.direction_input = max77759_gpio_direction_input;
chip->gc.direction_output = max77759_gpio_direction_output;
chip->gc.get = max77759_gpio_get_value;
chip->gc.set_rv = max77759_gpio_set_value;
girq = &chip->gc.irq;
gpio_irq_chip_set_chip(girq, &max77759_gpio_irq_chip);
/* This will let us handle the parent IRQ in the driver */
girq->parent_handler = NULL;
girq->num_parents = 0;
girq->parents = NULL;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_simple_irq;
girq->threaded = true;
ret = devm_gpiochip_add_data(&pdev->dev, &chip->gc, chip);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret,
"Failed to add GPIO chip\n");
irq_flags = IRQF_ONESHOT | IRQF_SHARED;
irqd = irq_get_irq_data(irq);
if (irqd)
irq_flags |= irqd_get_trigger_type(irqd);
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
max77759_gpio_irqhandler, irq_flags,
dev_name(&pdev->dev), chip);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret,
"Failed to request IRQ\n");
return ret;
}
static const struct of_device_id max77759_gpio_of_id[] = {
{ .compatible = "maxim,max77759-gpio", },
{ }
};
MODULE_DEVICE_TABLE(of, max77759_gpio_of_id);
static const struct platform_device_id max77759_gpio_platform_id[] = {
{ "max77759-gpio", },
{ }
};
MODULE_DEVICE_TABLE(platform, max77759_gpio_platform_id);
static struct platform_driver max77759_gpio_driver = {
.driver = {
.name = "max77759-gpio",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
.of_match_table = max77759_gpio_of_id,
},
.probe = max77759_gpio_probe,
.id_table = max77759_gpio_platform_id,
};
module_platform_driver(max77759_gpio_driver);
MODULE_AUTHOR("André Draszik <andre.draszik@linaro.org>");
MODULE_DESCRIPTION("GPIO driver for Maxim MAX77759");
MODULE_LICENSE("GPL");

View File

@@ -119,7 +119,7 @@ static int mb86s70_gpio_get(struct gpio_chip *gc, unsigned gpio)
return !!(readl(gchip->base + PDR(gpio)) & OFFSET(gpio));
}
static void mb86s70_gpio_set(struct gpio_chip *gc, unsigned gpio, int value)
static int mb86s70_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value)
{
struct mb86s70_gpio_chip *gchip = gpiochip_get_data(gc);
unsigned long flags;
@@ -135,6 +135,8 @@ static void mb86s70_gpio_set(struct gpio_chip *gc, unsigned gpio, int value)
writel(val, gchip->base + PDR(gpio));
spin_unlock_irqrestore(&gchip->lock, flags);
return 0;
}
static int mb86s70_gpio_to_irq(struct gpio_chip *gc, unsigned int offset)
@@ -178,7 +180,7 @@ static int mb86s70_gpio_probe(struct platform_device *pdev)
gchip->gc.request = mb86s70_gpio_request;
gchip->gc.free = mb86s70_gpio_free;
gchip->gc.get = mb86s70_gpio_get;
gchip->gc.set = mb86s70_gpio_set;
gchip->gc.set_rv = mb86s70_gpio_set;
gchip->gc.to_irq = mb86s70_gpio_to_irq;
gchip->gc.label = dev_name(&pdev->dev);
gchip->gc.ngpio = 32;

View File

@@ -57,15 +57,18 @@ static int __mc33880_set(struct mc33880 *mc, unsigned offset, int value)
}
static void mc33880_set(struct gpio_chip *chip, unsigned offset, int value)
static int mc33880_set(struct gpio_chip *chip, unsigned int offset, int value)
{
struct mc33880 *mc = gpiochip_get_data(chip);
int ret;
mutex_lock(&mc->lock);
__mc33880_set(mc, offset, value);
ret = __mc33880_set(mc, offset, value);
mutex_unlock(&mc->lock);
return ret;
}
static int mc33880_probe(struct spi_device *spi)
@@ -100,7 +103,7 @@ static int mc33880_probe(struct spi_device *spi)
mc->spi = spi;
mc->chip.label = DRIVER_NAME;
mc->chip.set = mc33880_set;
mc->chip.set_rv = mc33880_set;
mc->chip.base = pdata->base;
mc->chip.ngpio = PIN_NUMBER;
mc->chip.can_sleep = true;

View File

@@ -89,7 +89,7 @@ struct ioh_gpio {
static const int num_ports[] = {6, 12, 16, 16, 15, 16, 16, 12};
static void ioh_gpio_set(struct gpio_chip *gpio, unsigned nr, int val)
static int ioh_gpio_set(struct gpio_chip *gpio, unsigned int nr, int val)
{
u32 reg_val;
struct ioh_gpio *chip = gpiochip_get_data(gpio);
@@ -104,6 +104,8 @@ static void ioh_gpio_set(struct gpio_chip *gpio, unsigned nr, int val)
iowrite32(reg_val, &chip->reg->regs[chip->ch].po);
spin_unlock_irqrestore(&chip->spinlock, flags);
return 0;
}
static int ioh_gpio_get(struct gpio_chip *gpio, unsigned nr)
@@ -222,7 +224,7 @@ static void ioh_gpio_setup(struct ioh_gpio *chip, int num_port)
gpio->direction_input = ioh_gpio_direction_input;
gpio->get = ioh_gpio_get;
gpio->direction_output = ioh_gpio_direction_output;
gpio->set = ioh_gpio_set;
gpio->set_rv = ioh_gpio_set;
gpio->dbg_show = NULL;
gpio->base = -1;
gpio->ngpio = num_port;

View File

@@ -123,9 +123,12 @@ static irqreturn_t mpc8xxx_gpio_irq_cascade(int irq, void *data)
static void mpc8xxx_irq_unmask(struct irq_data *d)
{
struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d);
irq_hw_number_t hwirq = irqd_to_hwirq(d);
struct gpio_chip *gc = &mpc8xxx_gc->gc;
unsigned long flags;
gpiochip_enable_irq(gc, hwirq);
raw_spin_lock_irqsave(&mpc8xxx_gc->lock, flags);
gc->write_reg(mpc8xxx_gc->regs + GPIO_IMR,
@@ -138,6 +141,7 @@ static void mpc8xxx_irq_unmask(struct irq_data *d)
static void mpc8xxx_irq_mask(struct irq_data *d)
{
struct mpc8xxx_gpio_chip *mpc8xxx_gc = irq_data_get_irq_chip_data(d);
irq_hw_number_t hwirq = irqd_to_hwirq(d);
struct gpio_chip *gc = &mpc8xxx_gc->gc;
unsigned long flags;
@@ -148,6 +152,8 @@ static void mpc8xxx_irq_mask(struct irq_data *d)
& ~mpc_pin2mask(irqd_to_hwirq(d)));
raw_spin_unlock_irqrestore(&mpc8xxx_gc->lock, flags);
gpiochip_disable_irq(gc, hwirq);
}
static void mpc8xxx_irq_ack(struct irq_data *d)
@@ -244,6 +250,8 @@ static struct irq_chip mpc8xxx_irq_chip = {
.irq_ack = mpc8xxx_irq_ack,
/* this might get overwritten in mpc8xxx_probe() */
.irq_set_type = mpc8xxx_irq_set_type,
.flags = IRQCHIP_IMMUTABLE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static int mpc8xxx_gpio_irq_map(struct irq_domain *h, unsigned int irq,

View File

@@ -490,7 +490,14 @@ static int mxc_gpio_probe(struct platform_device *pdev)
port->gc.request = mxc_gpio_request;
port->gc.free = mxc_gpio_free;
port->gc.to_irq = mxc_gpio_to_irq;
port->gc.base = of_alias_get_id(np, "gpio") * 32;
/*
* Driver is DT-only, so a fixed base needs only be maintained for legacy
* userspace with sysfs interface.
*/
if (IS_ENABLED(CONFIG_GPIO_SYSFS))
port->gc.base = of_alias_get_id(np, "gpio") * 32;
else /* silence boot time warning */
port->gc.base = -1;
err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port);
if (err)

View File

@@ -215,6 +215,8 @@ struct pca953x_chip {
DECLARE_BITMAP(irq_stat, MAX_LINE);
DECLARE_BITMAP(irq_trig_raise, MAX_LINE);
DECLARE_BITMAP(irq_trig_fall, MAX_LINE);
DECLARE_BITMAP(irq_trig_level_high, MAX_LINE);
DECLARE_BITMAP(irq_trig_level_low, MAX_LINE);
#endif
atomic_t wakeup_path;
@@ -774,6 +776,8 @@ static void pca953x_irq_bus_sync_unlock(struct irq_data *d)
pca953x_read_regs(chip, chip->regs->direction, reg_direction);
bitmap_or(irq_mask, chip->irq_trig_fall, chip->irq_trig_raise, gc->ngpio);
bitmap_or(irq_mask, irq_mask, chip->irq_trig_level_high, gc->ngpio);
bitmap_or(irq_mask, irq_mask, chip->irq_trig_level_low, gc->ngpio);
bitmap_complement(reg_direction, reg_direction, gc->ngpio);
bitmap_and(irq_mask, irq_mask, reg_direction, gc->ngpio);
@@ -791,13 +795,15 @@ static int pca953x_irq_set_type(struct irq_data *d, unsigned int type)
struct device *dev = &chip->client->dev;
irq_hw_number_t hwirq = irqd_to_hwirq(d);
if (!(type & IRQ_TYPE_EDGE_BOTH)) {
if (!(type & IRQ_TYPE_SENSE_MASK)) {
dev_err(dev, "irq %d: unsupported type %d\n", d->irq, type);
return -EINVAL;
}
assign_bit(hwirq, chip->irq_trig_fall, type & IRQ_TYPE_EDGE_FALLING);
assign_bit(hwirq, chip->irq_trig_raise, type & IRQ_TYPE_EDGE_RISING);
assign_bit(hwirq, chip->irq_trig_level_low, type & IRQ_TYPE_LEVEL_LOW);
assign_bit(hwirq, chip->irq_trig_level_high, type & IRQ_TYPE_LEVEL_HIGH);
return 0;
}
@@ -810,6 +816,8 @@ static void pca953x_irq_shutdown(struct irq_data *d)
clear_bit(hwirq, chip->irq_trig_raise);
clear_bit(hwirq, chip->irq_trig_fall);
clear_bit(hwirq, chip->irq_trig_level_low);
clear_bit(hwirq, chip->irq_trig_level_high);
}
static void pca953x_irq_print_chip(struct irq_data *data, struct seq_file *p)
@@ -840,6 +848,7 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin
DECLARE_BITMAP(cur_stat, MAX_LINE);
DECLARE_BITMAP(new_stat, MAX_LINE);
DECLARE_BITMAP(trigger, MAX_LINE);
DECLARE_BITMAP(edges, MAX_LINE);
int ret;
ret = pca953x_read_regs(chip, chip->regs->input, cur_stat);
@@ -857,13 +866,26 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin
bitmap_copy(chip->irq_stat, new_stat, gc->ngpio);
if (bitmap_empty(trigger, gc->ngpio))
return false;
if (bitmap_empty(chip->irq_trig_level_high, gc->ngpio) &&
bitmap_empty(chip->irq_trig_level_low, gc->ngpio)) {
if (bitmap_empty(trigger, gc->ngpio))
return false;
}
bitmap_and(cur_stat, chip->irq_trig_fall, old_stat, gc->ngpio);
bitmap_and(old_stat, chip->irq_trig_raise, new_stat, gc->ngpio);
bitmap_or(new_stat, old_stat, cur_stat, gc->ngpio);
bitmap_and(pending, new_stat, trigger, gc->ngpio);
bitmap_or(edges, old_stat, cur_stat, gc->ngpio);
bitmap_and(pending, edges, trigger, gc->ngpio);
bitmap_and(cur_stat, new_stat, chip->irq_trig_level_high, gc->ngpio);
bitmap_and(cur_stat, cur_stat, chip->irq_mask, gc->ngpio);
bitmap_or(pending, pending, cur_stat, gc->ngpio);
bitmap_complement(cur_stat, new_stat, gc->ngpio);
bitmap_and(cur_stat, cur_stat, reg_direction, gc->ngpio);
bitmap_and(old_stat, cur_stat, chip->irq_trig_level_low, gc->ngpio);
bitmap_and(old_stat, old_stat, chip->irq_mask, gc->ngpio);
bitmap_or(pending, pending, old_stat, gc->ngpio);
return !bitmap_empty(pending, gc->ngpio);
}

View File

@@ -497,6 +497,8 @@ static void pxa_mask_muxed_gpio(struct irq_data *d)
gfer = readl_relaxed(base + GFER_OFFSET) & ~GPIO_bit(gpio);
writel_relaxed(grer, base + GRER_OFFSET);
writel_relaxed(gfer, base + GFER_OFFSET);
gpiochip_disable_irq(&pchip->chip, gpio);
}
static int pxa_gpio_set_wake(struct irq_data *d, unsigned int on)
@@ -516,17 +518,21 @@ static void pxa_unmask_muxed_gpio(struct irq_data *d)
unsigned int gpio = irqd_to_hwirq(d);
struct pxa_gpio_bank *c = gpio_to_pxabank(&pchip->chip, gpio);
gpiochip_enable_irq(&pchip->chip, gpio);
c->irq_mask |= GPIO_bit(gpio);
update_edge_detect(c);
}
static struct irq_chip pxa_muxed_gpio_chip = {
static const struct irq_chip pxa_muxed_gpio_chip = {
.name = "GPIO",
.irq_ack = pxa_ack_muxed_gpio,
.irq_mask = pxa_mask_muxed_gpio,
.irq_unmask = pxa_unmask_muxed_gpio,
.irq_set_type = pxa_gpio_irq_type,
.irq_set_wake = pxa_gpio_set_wake,
.flags = IRQCHIP_IMMUTABLE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static int pxa_gpio_nums(struct platform_device *pdev)

View File

@@ -0,0 +1,293 @@
// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
* Copyright (C) 2023-2025 SpacemiT (Hangzhou) Technology Co. Ltd
* Copyright (C) 2025 Yixun Lan <dlan@gentoo.org>
*/
#include <linux/clk.h>
#include <linux/gpio/driver.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
/* register offset */
#define SPACEMIT_GPLR 0x00 /* port level - R */
#define SPACEMIT_GPDR 0x0c /* port direction - R/W */
#define SPACEMIT_GPSR 0x18 /* port set - W */
#define SPACEMIT_GPCR 0x24 /* port clear - W */
#define SPACEMIT_GRER 0x30 /* port rising edge R/W */
#define SPACEMIT_GFER 0x3c /* port falling edge R/W */
#define SPACEMIT_GEDR 0x48 /* edge detect status - R/W1C */
#define SPACEMIT_GSDR 0x54 /* (set) direction - W */
#define SPACEMIT_GCDR 0x60 /* (clear) direction - W */
#define SPACEMIT_GSRER 0x6c /* (set) rising edge detect enable - W */
#define SPACEMIT_GCRER 0x78 /* (clear) rising edge detect enable - W */
#define SPACEMIT_GSFER 0x84 /* (set) falling edge detect enable - W */
#define SPACEMIT_GCFER 0x90 /* (clear) falling edge detect enable - W */
#define SPACEMIT_GAPMASK 0x9c /* interrupt mask , 0 disable, 1 enable - R/W */
#define SPACEMIT_NR_BANKS 4
#define SPACEMIT_NR_GPIOS_PER_BANK 32
#define to_spacemit_gpio_bank(x) container_of((x), struct spacemit_gpio_bank, gc)
struct spacemit_gpio;
struct spacemit_gpio_bank {
struct gpio_chip gc;
struct spacemit_gpio *sg;
void __iomem *base;
u32 irq_mask;
u32 irq_rising_edge;
u32 irq_falling_edge;
};
struct spacemit_gpio {
struct device *dev;
struct spacemit_gpio_bank sgb[SPACEMIT_NR_BANKS];
};
static u32 spacemit_gpio_bank_index(struct spacemit_gpio_bank *gb)
{
return (u32)(gb - gb->sg->sgb);
}
static irqreturn_t spacemit_gpio_irq_handler(int irq, void *dev_id)
{
struct spacemit_gpio_bank *gb = dev_id;
unsigned long pending;
u32 n, gedr;
gedr = readl(gb->base + SPACEMIT_GEDR);
if (!gedr)
return IRQ_NONE;
writel(gedr, gb->base + SPACEMIT_GEDR);
pending = gedr & gb->irq_mask;
if (!pending)
return IRQ_NONE;
for_each_set_bit(n, &pending, BITS_PER_LONG)
handle_nested_irq(irq_find_mapping(gb->gc.irq.domain, n));
return IRQ_HANDLED;
}
static void spacemit_gpio_irq_ack(struct irq_data *d)
{
struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d);
writel(BIT(irqd_to_hwirq(d)), gb->base + SPACEMIT_GEDR);
}
static void spacemit_gpio_irq_mask(struct irq_data *d)
{
struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d);
u32 bit = BIT(irqd_to_hwirq(d));
gb->irq_mask &= ~bit;
writel(gb->irq_mask, gb->base + SPACEMIT_GAPMASK);
if (bit & gb->irq_rising_edge)
writel(bit, gb->base + SPACEMIT_GCRER);
if (bit & gb->irq_falling_edge)
writel(bit, gb->base + SPACEMIT_GCFER);
}
static void spacemit_gpio_irq_unmask(struct irq_data *d)
{
struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d);
u32 bit = BIT(irqd_to_hwirq(d));
gb->irq_mask |= bit;
if (bit & gb->irq_rising_edge)
writel(bit, gb->base + SPACEMIT_GSRER);
if (bit & gb->irq_falling_edge)
writel(bit, gb->base + SPACEMIT_GSFER);
writel(gb->irq_mask, gb->base + SPACEMIT_GAPMASK);
}
static int spacemit_gpio_irq_set_type(struct irq_data *d, unsigned int type)
{
struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d);
u32 bit = BIT(irqd_to_hwirq(d));
if (type & IRQ_TYPE_EDGE_RISING) {
gb->irq_rising_edge |= bit;
writel(bit, gb->base + SPACEMIT_GSRER);
} else {
gb->irq_rising_edge &= ~bit;
writel(bit, gb->base + SPACEMIT_GCRER);
}
if (type & IRQ_TYPE_EDGE_FALLING) {
gb->irq_falling_edge |= bit;
writel(bit, gb->base + SPACEMIT_GSFER);
} else {
gb->irq_falling_edge &= ~bit;
writel(bit, gb->base + SPACEMIT_GCFER);
}
return 0;
}
static void spacemit_gpio_irq_print_chip(struct irq_data *data, struct seq_file *p)
{
struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(data);
seq_printf(p, "%s-%d", dev_name(gb->gc.parent), spacemit_gpio_bank_index(gb));
}
static struct irq_chip spacemit_gpio_chip = {
.name = "k1-gpio-irqchip",
.irq_ack = spacemit_gpio_irq_ack,
.irq_mask = spacemit_gpio_irq_mask,
.irq_unmask = spacemit_gpio_irq_unmask,
.irq_set_type = spacemit_gpio_irq_set_type,
.irq_print_chip = spacemit_gpio_irq_print_chip,
.flags = IRQCHIP_IMMUTABLE | IRQCHIP_SKIP_SET_WAKE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static bool spacemit_of_node_instance_match(struct gpio_chip *gc, unsigned int i)
{
struct spacemit_gpio_bank *gb = gpiochip_get_data(gc);
struct spacemit_gpio *sg = gb->sg;
if (i >= SPACEMIT_NR_BANKS)
return false;
return (gc == &sg->sgb[i].gc);
}
static int spacemit_gpio_add_bank(struct spacemit_gpio *sg,
void __iomem *regs,
int index, int irq)
{
struct spacemit_gpio_bank *gb = &sg->sgb[index];
struct gpio_chip *gc = &gb->gc;
struct device *dev = sg->dev;
struct gpio_irq_chip *girq;
void __iomem *dat, *set, *clr, *dirin, *dirout;
int ret, bank_base[] = { 0x0, 0x4, 0x8, 0x100 };
gb->base = regs + bank_base[index];
dat = gb->base + SPACEMIT_GPLR;
set = gb->base + SPACEMIT_GPSR;
clr = gb->base + SPACEMIT_GPCR;
dirin = gb->base + SPACEMIT_GCDR;
dirout = gb->base + SPACEMIT_GSDR;
/* This registers 32 GPIO lines per bank */
ret = bgpio_init(gc, dev, 4, dat, set, clr, dirout, dirin,
BGPIOF_UNREADABLE_REG_SET | BGPIOF_UNREADABLE_REG_DIR);
if (ret)
return dev_err_probe(dev, ret, "failed to init gpio chip\n");
gb->sg = sg;
gc->label = dev_name(dev);
gc->request = gpiochip_generic_request;
gc->free = gpiochip_generic_free;
gc->ngpio = SPACEMIT_NR_GPIOS_PER_BANK;
gc->base = -1;
gc->of_gpio_n_cells = 3;
gc->of_node_instance_match = spacemit_of_node_instance_match;
girq = &gc->irq;
girq->threaded = true;
girq->handler = handle_simple_irq;
gpio_irq_chip_set_chip(girq, &spacemit_gpio_chip);
/* Disable Interrupt */
writel(0, gb->base + SPACEMIT_GAPMASK);
/* Disable Edge Detection Settings */
writel(0x0, gb->base + SPACEMIT_GRER);
writel(0x0, gb->base + SPACEMIT_GFER);
/* Clear Interrupt */
writel(0xffffffff, gb->base + SPACEMIT_GCRER);
writel(0xffffffff, gb->base + SPACEMIT_GCFER);
ret = devm_request_threaded_irq(dev, irq, NULL,
spacemit_gpio_irq_handler,
IRQF_ONESHOT | IRQF_SHARED,
gb->gc.label, gb);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to register IRQ\n");
ret = devm_gpiochip_add_data(dev, gc, gb);
if (ret)
return ret;
/* Distuingish IRQ domain, for selecting threecells mode */
irq_domain_update_bus_token(girq->domain, DOMAIN_BUS_WIRED);
return 0;
}
static int spacemit_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct spacemit_gpio *sg;
struct clk *core_clk, *bus_clk;
void __iomem *regs;
int i, irq, ret;
sg = devm_kzalloc(dev, sizeof(*sg), GFP_KERNEL);
if (!sg)
return -ENOMEM;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
sg->dev = dev;
core_clk = devm_clk_get_enabled(dev, "core");
if (IS_ERR(core_clk))
return dev_err_probe(dev, PTR_ERR(core_clk), "failed to get clock\n");
bus_clk = devm_clk_get_enabled(dev, "bus");
if (IS_ERR(bus_clk))
return dev_err_probe(dev, PTR_ERR(bus_clk), "failed to get bus clock\n");
for (i = 0; i < SPACEMIT_NR_BANKS; i++) {
ret = spacemit_gpio_add_bank(sg, regs, i, irq);
if (ret)
return ret;
}
return 0;
}
static const struct of_device_id spacemit_gpio_dt_ids[] = {
{ .compatible = "spacemit,k1-gpio" },
{ /* sentinel */ }
};
static struct platform_driver spacemit_gpio_driver = {
.probe = spacemit_gpio_probe,
.driver = {
.name = "k1-gpio",
.of_match_table = spacemit_gpio_dt_ids,
},
};
module_platform_driver(spacemit_gpio_driver);
MODULE_AUTHOR("Yixun Lan <dlan@gentoo.org>");
MODULE_DESCRIPTION("GPIO driver for SpacemiT K1 SoC");
MODULE_LICENSE("GPL");

View File

@@ -103,20 +103,26 @@ static void timbgpio_irq_disable(struct irq_data *d)
{
struct timbgpio *tgpio = irq_data_get_irq_chip_data(d);
int offset = d->irq - tgpio->irq_base;
irq_hw_number_t hwirq = irqd_to_hwirq(d);
unsigned long flags;
spin_lock_irqsave(&tgpio->lock, flags);
tgpio->last_ier &= ~(1UL << offset);
iowrite32(tgpio->last_ier, tgpio->membase + TGPIO_IER);
spin_unlock_irqrestore(&tgpio->lock, flags);
gpiochip_disable_irq(&tgpio->gpio, hwirq);
}
static void timbgpio_irq_enable(struct irq_data *d)
{
struct timbgpio *tgpio = irq_data_get_irq_chip_data(d);
int offset = d->irq - tgpio->irq_base;
irq_hw_number_t hwirq = irqd_to_hwirq(d);
unsigned long flags;
gpiochip_enable_irq(&tgpio->gpio, hwirq);
spin_lock_irqsave(&tgpio->lock, flags);
tgpio->last_ier |= 1UL << offset;
iowrite32(tgpio->last_ier, tgpio->membase + TGPIO_IER);
@@ -205,11 +211,13 @@ static void timbgpio_irq(struct irq_desc *desc)
iowrite32(tgpio->last_ier, tgpio->membase + TGPIO_IER);
}
static struct irq_chip timbgpio_irqchip = {
static const struct irq_chip timbgpio_irqchip = {
.name = "GPIO",
.irq_enable = timbgpio_irq_enable,
.irq_disable = timbgpio_irq_disable,
.irq_set_type = timbgpio_irq_type,
.flags = IRQCHIP_IMMUTABLE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static int timbgpio_probe(struct platform_device *pdev)

View File

@@ -345,4 +345,6 @@ static struct platform_driver vf610_gpio_driver = {
.probe = vf610_gpio_probe,
};
builtin_platform_driver(vf610_gpio_driver);
module_platform_driver(vf610_gpio_driver);
MODULE_DESCRIPTION("VF610 GPIO driver");
MODULE_LICENSE("GPL");

View File

@@ -103,12 +103,32 @@ static int xgene_gpio_sb_irq_set_type(struct irq_data *d, unsigned int type)
return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
}
static struct irq_chip xgene_gpio_sb_irq_chip = {
static void xgene_gpio_sb_irq_mask(struct irq_data *d)
{
struct xgene_gpio_sb *priv = irq_data_get_irq_chip_data(d);
irq_chip_mask_parent(d);
gpiochip_disable_irq(&priv->gc, d->hwirq);
}
static void xgene_gpio_sb_irq_unmask(struct irq_data *d)
{
struct xgene_gpio_sb *priv = irq_data_get_irq_chip_data(d);
gpiochip_enable_irq(&priv->gc, d->hwirq);
irq_chip_unmask_parent(d);
}
static const struct irq_chip xgene_gpio_sb_irq_chip = {
.name = "sbgpio",
.irq_eoi = irq_chip_eoi_parent,
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_mask = xgene_gpio_sb_irq_mask,
.irq_unmask = xgene_gpio_sb_irq_unmask,
.irq_set_type = xgene_gpio_sb_irq_set_type,
.flags = IRQCHIP_IMMUTABLE,
GPIOCHIP_IRQ_RESOURCE_HELPERS,
};
static int xgene_gpio_sb_to_irq(struct gpio_chip *gc, u32 gpio)

View File

@@ -23,29 +23,6 @@
#include "gpiolib.h"
#include "gpiolib-acpi.h"
static int run_edge_events_on_boot = -1;
module_param(run_edge_events_on_boot, int, 0444);
MODULE_PARM_DESC(run_edge_events_on_boot,
"Run edge _AEI event-handlers at boot: 0=no, 1=yes, -1=auto");
static char *ignore_wake;
module_param(ignore_wake, charp, 0444);
MODULE_PARM_DESC(ignore_wake,
"controller@pin combos on which to ignore the ACPI wake flag "
"ignore_wake=controller@pin[,controller@pin[,...]]");
static char *ignore_interrupt;
module_param(ignore_interrupt, charp, 0444);
MODULE_PARM_DESC(ignore_interrupt,
"controller@pin combos on which to ignore interrupt "
"ignore_interrupt=controller@pin[,controller@pin[,...]]");
struct acpi_gpiolib_dmi_quirk {
bool no_edge_events_on_boot;
char *ignore_wake;
char *ignore_interrupt;
};
/**
* struct acpi_gpio_event - ACPI GPIO event handler data
*
@@ -96,10 +73,10 @@ struct acpi_gpio_chip {
* @adev: reference to ACPI device which consumes GPIO resource
* @flags: GPIO initialization flags
* @gpioint: if %true this GPIO is of type GpioInt otherwise type is GpioIo
* @wake_capable: wake capability as provided by ACPI
* @pin_config: pin bias as provided by ACPI
* @polarity: interrupt polarity as provided by ACPI
* @triggering: triggering type as provided by ACPI
* @wake_capable: wake capability as provided by ACPI
* @debounce: debounce timeout as provided by ACPI
* @quirks: Linux specific quirks as provided by struct acpi_gpio_mapping
*/
@@ -107,25 +84,14 @@ struct acpi_gpio_info {
struct acpi_device *adev;
enum gpiod_flags flags;
bool gpioint;
bool wake_capable;
int pin_config;
int polarity;
int triggering;
bool wake_capable;
unsigned int debounce;
unsigned int quirks;
};
/*
* For GPIO chips which call acpi_gpiochip_request_interrupts() before late_init
* (so builtin drivers) we register the ACPI GpioInt IRQ handlers from a
* late_initcall_sync() handler, so that other builtin drivers can register their
* OpRegions before the event handlers can run. This list contains GPIO chips
* for which the acpi_gpiochip_request_irqs() call has been deferred.
*/
static DEFINE_MUTEX(acpi_gpio_deferred_req_irqs_lock);
static LIST_HEAD(acpi_gpio_deferred_req_irqs_list);
static bool acpi_gpio_deferred_req_irqs_done;
static int acpi_gpiochip_find(struct gpio_chip *gc, const void *data)
{
/* First check the actual GPIO device */
@@ -268,7 +234,7 @@ static void acpi_gpiochip_request_irq(struct acpi_gpio_chip *acpi_gpio,
event->irq_requested = true;
/* Make sure we trigger the initial state of edge-triggered IRQs */
if (run_edge_events_on_boot &&
if (acpi_gpio_need_run_edge_events_on_boot() &&
(event->irqflags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING))) {
value = gpiod_get_raw_value_cansleep(event->desc);
if (((event->irqflags & IRQF_TRIGGER_RISING) && value == 1) ||
@@ -350,42 +316,6 @@ static struct gpio_desc *acpi_request_own_gpiod(struct gpio_chip *chip,
return desc;
}
static bool acpi_gpio_in_ignore_list(const char *ignore_list, const char *controller_in,
unsigned int pin_in)
{
const char *controller, *pin_str;
unsigned int pin;
char *endp;
int len;
controller = ignore_list;
while (controller) {
pin_str = strchr(controller, '@');
if (!pin_str)
goto err;
len = pin_str - controller;
if (len == strlen(controller_in) &&
strncmp(controller, controller_in, len) == 0) {
pin = simple_strtoul(pin_str + 1, &endp, 10);
if (*endp != 0 && *endp != ',')
goto err;
if (pin == pin_in)
return true;
}
controller = strchr(controller, ',');
if (controller)
controller++;
}
return false;
err:
pr_err_once("Error: Invalid value for gpiolib_acpi.ignore_...: %s\n", ignore_list);
return false;
}
static bool acpi_gpio_irq_is_wake(struct device *parent,
const struct acpi_resource_gpio *agpio)
{
@@ -394,7 +324,7 @@ static bool acpi_gpio_irq_is_wake(struct device *parent,
if (agpio->wake_capable != ACPI_WAKE_CAPABLE)
return false;
if (acpi_gpio_in_ignore_list(ignore_wake, dev_name(parent), pin)) {
if (acpi_gpio_in_ignore_list(ACPI_GPIO_IGNORE_WAKE, dev_name(parent), pin)) {
dev_info(parent, "Ignoring wakeup on pin %u\n", pin);
return false;
}
@@ -437,7 +367,7 @@ static acpi_status acpi_gpiochip_alloc_event(struct acpi_resource *ares,
if (!handler)
return AE_OK;
if (acpi_gpio_in_ignore_list(ignore_interrupt, dev_name(chip->parent), pin)) {
if (acpi_gpio_in_ignore_list(ACPI_GPIO_IGNORE_INTERRUPT, dev_name(chip->parent), pin)) {
dev_info(chip->parent, "Ignoring interrupt on pin %u\n", pin);
return AE_OK;
}
@@ -525,7 +455,6 @@ void acpi_gpiochip_request_interrupts(struct gpio_chip *chip)
struct acpi_gpio_chip *acpi_gpio;
acpi_handle handle;
acpi_status status;
bool defer;
if (!chip->parent || !chip->to_irq)
return;
@@ -544,14 +473,7 @@ void acpi_gpiochip_request_interrupts(struct gpio_chip *chip)
acpi_walk_resources(handle, METHOD_NAME__AEI,
acpi_gpiochip_alloc_event, acpi_gpio);
mutex_lock(&acpi_gpio_deferred_req_irqs_lock);
defer = !acpi_gpio_deferred_req_irqs_done;
if (defer)
list_add(&acpi_gpio->deferred_req_irqs_list_entry,
&acpi_gpio_deferred_req_irqs_list);
mutex_unlock(&acpi_gpio_deferred_req_irqs_lock);
if (defer)
if (acpi_gpio_add_to_deferred_list(&acpi_gpio->deferred_req_irqs_list_entry))
return;
acpi_gpiochip_request_irqs(acpi_gpio);
@@ -583,10 +505,7 @@ void acpi_gpiochip_free_interrupts(struct gpio_chip *chip)
if (ACPI_FAILURE(status))
return;
mutex_lock(&acpi_gpio_deferred_req_irqs_lock);
if (!list_empty(&acpi_gpio->deferred_req_irqs_list_entry))
list_del_init(&acpi_gpio->deferred_req_irqs_list_entry);
mutex_unlock(&acpi_gpio_deferred_req_irqs_lock);
acpi_gpio_remove_from_deferred_list(&acpi_gpio->deferred_req_irqs_list_entry);
list_for_each_entry_safe_reverse(event, ep, &acpi_gpio->events, node) {
if (event->irq_requested) {
@@ -604,6 +523,14 @@ void acpi_gpiochip_free_interrupts(struct gpio_chip *chip)
}
EXPORT_SYMBOL_GPL(acpi_gpiochip_free_interrupts);
void __init acpi_gpio_process_deferred_list(struct list_head *list)
{
struct acpi_gpio_chip *acpi_gpio, *tmp;
list_for_each_entry_safe(acpi_gpio, tmp, list, deferred_req_irqs_list_entry)
acpi_gpiochip_request_irqs(acpi_gpio);
}
int acpi_dev_add_driver_gpios(struct acpi_device *adev,
const struct acpi_gpio_mapping *gpios)
{
@@ -653,12 +580,12 @@ static bool acpi_get_driver_gpio_data(struct acpi_device *adev,
for (gm = adev->driver_gpios; gm->name; gm++)
if (!strcmp(name, gm->name) && gm->data && index < gm->size) {
const struct acpi_gpio_params *par = gm->data + index;
const struct acpi_gpio_params *params = gm->data + index;
args->fwnode = acpi_fwnode_handle(adev);
args->args[0] = par->crs_entry_index;
args->args[1] = par->line_index;
args->args[2] = par->active_low;
args->args[0] = params->crs_entry_index;
args->args[1] = params->line_index;
args->args[2] = params->active_low;
args->nargs = 3;
*quirks = gm->quirks;
@@ -743,10 +670,8 @@ static int acpi_gpio_update_gpiod_lookup_flags(unsigned long *lookupflags,
}
struct acpi_gpio_lookup {
struct acpi_gpio_info info;
int index;
u16 pin_index;
bool active_low;
struct acpi_gpio_params params;
struct acpi_gpio_info *info;
struct gpio_desc *desc;
int n;
};
@@ -754,6 +679,8 @@ struct acpi_gpio_lookup {
static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data)
{
struct acpi_gpio_lookup *lookup = data;
struct acpi_gpio_params *params = &lookup->params;
struct acpi_gpio_info *info = lookup->info;
if (ares->type != ACPI_RESOURCE_TYPE_GPIO)
return 1;
@@ -764,26 +691,26 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data)
struct gpio_desc *desc;
u16 pin_index;
if (lookup->info.quirks & ACPI_GPIO_QUIRK_ONLY_GPIOIO && gpioint)
lookup->index++;
if (info->quirks & ACPI_GPIO_QUIRK_ONLY_GPIOIO && gpioint)
params->crs_entry_index++;
if (lookup->n++ != lookup->index)
if (lookup->n++ != params->crs_entry_index)
return 1;
pin_index = lookup->pin_index;
pin_index = params->line_index;
if (pin_index >= agpio->pin_table_length)
return 1;
if (lookup->info.quirks & ACPI_GPIO_QUIRK_ABSOLUTE_NUMBER)
if (info->quirks & ACPI_GPIO_QUIRK_ABSOLUTE_NUMBER)
desc = gpio_to_desc(agpio->pin_table[pin_index]);
else
desc = acpi_get_gpiod(agpio->resource_source.string_ptr,
agpio->pin_table[pin_index]);
lookup->desc = desc;
lookup->info.pin_config = agpio->pin_config;
lookup->info.debounce = agpio->debounce_timeout;
lookup->info.gpioint = gpioint;
lookup->info.wake_capable = acpi_gpio_irq_is_wake(&lookup->info.adev->dev, agpio);
info->pin_config = agpio->pin_config;
info->debounce = agpio->debounce_timeout;
info->gpioint = gpioint;
info->wake_capable = acpi_gpio_irq_is_wake(&info->adev->dev, agpio);
/*
* Polarity and triggering are only specified for GpioInt
@@ -792,23 +719,23 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data)
* - ACPI_ACTIVE_LOW == GPIO_ACTIVE_LOW
* - ACPI_ACTIVE_HIGH == GPIO_ACTIVE_HIGH
*/
if (lookup->info.gpioint) {
lookup->info.polarity = agpio->polarity;
lookup->info.triggering = agpio->triggering;
if (info->gpioint) {
info->polarity = agpio->polarity;
info->triggering = agpio->triggering;
} else {
lookup->info.polarity = lookup->active_low;
info->polarity = params->active_low;
}
lookup->info.flags = acpi_gpio_to_gpiod_flags(agpio, lookup->info.polarity);
info->flags = acpi_gpio_to_gpiod_flags(agpio, info->polarity);
}
return 1;
}
static int acpi_gpio_resource_lookup(struct acpi_gpio_lookup *lookup,
struct acpi_gpio_info *info)
static int acpi_gpio_resource_lookup(struct acpi_gpio_lookup *lookup)
{
struct acpi_device *adev = lookup->info.adev;
struct acpi_gpio_info *info = lookup->info;
struct acpi_device *adev = info->adev;
struct list_head res_list;
int ret;
@@ -825,22 +752,22 @@ static int acpi_gpio_resource_lookup(struct acpi_gpio_lookup *lookup,
if (!lookup->desc)
return -ENOENT;
if (info)
*info = lookup->info;
return 0;
}
static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode,
const char *propname, int index,
static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode, const char *propname,
struct acpi_gpio_lookup *lookup)
{
struct fwnode_reference_args args;
struct acpi_gpio_params *params = &lookup->params;
struct acpi_gpio_info *info = lookup->info;
unsigned int index = params->crs_entry_index;
unsigned int quirks = 0;
int ret;
memset(&args, 0, sizeof(args));
ret = __acpi_node_get_property_reference(fwnode, propname, index, 3,
&args);
ret = __acpi_node_get_property_reference(fwnode, propname, index, 3, &args);
if (ret) {
struct acpi_device *adev;
@@ -857,12 +784,12 @@ static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode,
if (args.nargs != 3)
return -EPROTO;
lookup->index = args.args[0];
lookup->pin_index = args.args[1];
lookup->active_low = !!args.args[2];
params->crs_entry_index = args.args[0];
params->line_index = args.args[1];
params->active_low = !!args.args[2];
lookup->info.adev = to_acpi_device_node(args.fwnode);
lookup->info.quirks = quirks;
info->adev = to_acpi_device_node(args.fwnode);
info->quirks = quirks;
return 0;
}
@@ -871,96 +798,83 @@ static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode,
* acpi_get_gpiod_by_index() - get a GPIO descriptor from device resources
* @adev: pointer to a ACPI device to get GPIO from
* @propname: Property name of the GPIO (optional)
* @index: index of GpioIo/GpioInt resource (starting from %0)
* @info: info pointer to fill in (optional)
* @lookup: pointer to struct acpi_gpio_lookup to fill in
*
* Function goes through ACPI resources for @adev and based on @index looks
* Function goes through ACPI resources for @adev and based on @lookup.index looks
* up a GpioIo/GpioInt resource, translates it to the Linux GPIO descriptor,
* and returns it. @index matches GpioIo/GpioInt resources only so if there
* are total %3 GPIO resources, the index goes from %0 to %2.
* and returns it. @lookup.index matches GpioIo/GpioInt resources only so if there
* are total 3 GPIO resources, the index goes from 0 to 2.
*
* If @propname is specified the GPIO is looked using device property. In
* that case @index is used to select the GPIO entry in the property value
* (in case of multiple).
*
* Returns:
* GPIO descriptor to use with Linux generic GPIO API.
* If the GPIO cannot be translated or there is an error an ERR_PTR is
* returned.
* 0 on success, negative errno on failure.
*
* The @lookup is filled with GPIO descriptor to use with Linux generic GPIO API.
* If the GPIO cannot be translated an error will be returned.
*
* Note: if the GPIO resource has multiple entries in the pin list, this
* function only returns the first.
*/
static struct gpio_desc *acpi_get_gpiod_by_index(struct acpi_device *adev,
const char *propname,
int index,
struct acpi_gpio_info *info)
static int acpi_get_gpiod_by_index(struct acpi_device *adev, const char *propname,
struct acpi_gpio_lookup *lookup)
{
struct acpi_gpio_lookup lookup;
struct acpi_gpio_params *params = &lookup->params;
struct acpi_gpio_info *info = lookup->info;
int ret;
memset(&lookup, 0, sizeof(lookup));
lookup.index = index;
if (propname) {
dev_dbg(&adev->dev, "GPIO: looking up %s\n", propname);
ret = acpi_gpio_property_lookup(acpi_fwnode_handle(adev),
propname, index, &lookup);
ret = acpi_gpio_property_lookup(acpi_fwnode_handle(adev), propname, lookup);
if (ret)
return ERR_PTR(ret);
return ret;
dev_dbg(&adev->dev, "GPIO: _DSD returned %s %d %u %u\n",
dev_name(&lookup.info.adev->dev), lookup.index,
lookup.pin_index, lookup.active_low);
dev_dbg(&adev->dev, "GPIO: _DSD returned %s %u %u %u\n",
dev_name(&info->adev->dev),
params->crs_entry_index, params->line_index, params->active_low);
} else {
dev_dbg(&adev->dev, "GPIO: looking up %d in _CRS\n", index);
lookup.info.adev = adev;
dev_dbg(&adev->dev, "GPIO: looking up %u in _CRS\n", params->crs_entry_index);
info->adev = adev;
}
ret = acpi_gpio_resource_lookup(&lookup, info);
return ret ? ERR_PTR(ret) : lookup.desc;
return acpi_gpio_resource_lookup(lookup);
}
/**
* acpi_get_gpiod_from_data() - get a GPIO descriptor from ACPI data node
* @fwnode: pointer to an ACPI firmware node to get the GPIO information from
* @propname: Property name of the GPIO
* @index: index of GpioIo/GpioInt resource (starting from %0)
* @info: info pointer to fill in (optional)
* @lookup: pointer to struct acpi_gpio_lookup to fill in
*
* This function uses the property-based GPIO lookup to get to the GPIO
* resource with the relevant information from a data-only ACPI firmware node
* and uses that to obtain the GPIO descriptor to return.
*
* Returns:
* GPIO descriptor to use with Linux generic GPIO API.
* If the GPIO cannot be translated or there is an error an ERR_PTR is
* returned.
* 0 on success, negative errno on failure.
*
* The @lookup is filled with GPIO descriptor to use with Linux generic GPIO API.
* If the GPIO cannot be translated an error will be returned.
*/
static struct gpio_desc *acpi_get_gpiod_from_data(struct fwnode_handle *fwnode,
const char *propname,
int index,
struct acpi_gpio_info *info)
static int acpi_get_gpiod_from_data(struct fwnode_handle *fwnode, const char *propname,
struct acpi_gpio_lookup *lookup)
{
struct acpi_gpio_lookup lookup;
int ret;
if (!is_acpi_data_node(fwnode))
return ERR_PTR(-ENODEV);
return -ENODEV;
if (!propname)
return ERR_PTR(-EINVAL);
return -EINVAL;
memset(&lookup, 0, sizeof(lookup));
lookup.index = index;
ret = acpi_gpio_property_lookup(fwnode, propname, index, &lookup);
ret = acpi_gpio_property_lookup(fwnode, propname, lookup);
if (ret)
return ERR_PTR(ret);
return ret;
ret = acpi_gpio_resource_lookup(&lookup, info);
return ret ? ERR_PTR(ret) : lookup.desc;
return acpi_gpio_resource_lookup(lookup);
}
static bool acpi_can_fallback_to_crs(struct acpi_device *adev,
@@ -982,17 +896,25 @@ __acpi_find_gpio(struct fwnode_handle *fwnode, const char *con_id, unsigned int
bool can_fallback, struct acpi_gpio_info *info)
{
struct acpi_device *adev = to_acpi_device_node(fwnode);
struct acpi_gpio_lookup lookup;
struct gpio_desc *desc;
char propname[32];
int ret;
memset(&lookup, 0, sizeof(lookup));
lookup.params.crs_entry_index = idx;
lookup.info = info;
/* Try first from _DSD */
for_each_gpio_property_name(propname, con_id) {
if (adev)
desc = acpi_get_gpiod_by_index(adev,
propname, idx, info);
ret = acpi_get_gpiod_by_index(adev, propname, &lookup);
else
desc = acpi_get_gpiod_from_data(fwnode,
propname, idx, info);
ret = acpi_get_gpiod_from_data(fwnode, propname, &lookup);
if (ret)
continue;
desc = lookup.desc;
if (PTR_ERR(desc) == -EPROBE_DEFER)
return desc;
@@ -1001,8 +923,13 @@ __acpi_find_gpio(struct fwnode_handle *fwnode, const char *con_id, unsigned int
}
/* Then from plain _CRS GPIOs */
if (can_fallback)
return acpi_get_gpiod_by_index(adev, NULL, idx, info);
if (can_fallback) {
ret = acpi_get_gpiod_by_index(adev, NULL, &lookup);
if (ret)
return ERR_PTR(ret);
return lookup.desc;
}
return ERR_PTR(-ENOENT);
}
@@ -1488,248 +1415,3 @@ int acpi_gpio_count(const struct fwnode_handle *fwnode, const char *con_id)
}
return count ? count : -ENOENT;
}
/* Run deferred acpi_gpiochip_request_irqs() */
static int __init acpi_gpio_handle_deferred_request_irqs(void)
{
struct acpi_gpio_chip *acpi_gpio, *tmp;
mutex_lock(&acpi_gpio_deferred_req_irqs_lock);
list_for_each_entry_safe(acpi_gpio, tmp,
&acpi_gpio_deferred_req_irqs_list,
deferred_req_irqs_list_entry)
acpi_gpiochip_request_irqs(acpi_gpio);
acpi_gpio_deferred_req_irqs_done = true;
mutex_unlock(&acpi_gpio_deferred_req_irqs_lock);
return 0;
}
/* We must use _sync so that this runs after the first deferred_probe run */
late_initcall_sync(acpi_gpio_handle_deferred_request_irqs);
static const struct dmi_system_id gpiolib_acpi_quirks[] __initconst = {
{
/*
* The Minix Neo Z83-4 has a micro-USB-B id-pin handler for
* a non existing micro-USB-B connector which puts the HDMI
* DDC pins in GPIO mode, breaking HDMI support.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "MINIX"),
DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.no_edge_events_on_boot = true,
},
},
{
/*
* The Terra Pad 1061 has a micro-USB-B id-pin handler, which
* instead of controlling the actual micro-USB-B turns the 5V
* boost for its USB-A connector off. The actual micro-USB-B
* connector is wired for charging only.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Wortmann_AG"),
DMI_MATCH(DMI_PRODUCT_NAME, "TERRA_PAD_1061"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.no_edge_events_on_boot = true,
},
},
{
/*
* The Dell Venue 10 Pro 5055, with Bay Trail SoC + TI PMIC uses an
* external embedded-controller connected via I2C + an ACPI GPIO
* event handler on INT33FFC:02 pin 12, causing spurious wakeups.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Venue 10 Pro 5055"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "INT33FC:02@12",
},
},
{
/*
* HP X2 10 models with Cherry Trail SoC + TI PMIC use an
* external embedded-controller connected via I2C + an ACPI GPIO
* event handler on INT33FF:01 pin 0, causing spurious wakeups.
* When suspending by closing the LID, the power to the USB
* keyboard is turned off, causing INT0002 ACPI events to
* trigger once the XHCI controller notices the keyboard is
* gone. So INT0002 events cause spurious wakeups too. Ignoring
* EC wakes breaks wakeup when opening the lid, the user needs
* to press the power-button to wakeup the system. The
* alternative is suspend simply not working, which is worse.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP x2 Detachable 10-p0XX"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "INT33FF:01@0,INT0002:00@2",
},
},
{
/*
* HP X2 10 models with Bay Trail SoC + AXP288 PMIC use an
* external embedded-controller connected via I2C + an ACPI GPIO
* event handler on INT33FC:02 pin 28, causing spurious wakeups.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"),
DMI_MATCH(DMI_BOARD_NAME, "815D"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "INT33FC:02@28",
},
},
{
/*
* HP X2 10 models with Cherry Trail SoC + AXP288 PMIC use an
* external embedded-controller connected via I2C + an ACPI GPIO
* event handler on INT33FF:01 pin 0, causing spurious wakeups.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"),
DMI_MATCH(DMI_BOARD_NAME, "813E"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "INT33FF:01@0",
},
},
{
/*
* Interrupt storm caused from edge triggered floating pin
* Found in BIOS UX325UAZ.300
* https://bugzilla.kernel.org/show_bug.cgi?id=216208
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX325UAZ_UM325UAZ"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_interrupt = "AMDI0030:00@18",
},
},
{
/*
* Spurious wakeups from TP_ATTN# pin
* Found in BIOS 1.7.8
* https://gitlab.freedesktop.org/drm/amd/-/issues/1722#note_1720627
*/
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "NL5xNU"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "ELAN0415:00@9",
},
},
{
/*
* Spurious wakeups from TP_ATTN# pin
* Found in BIOS 1.7.8
* https://gitlab.freedesktop.org/drm/amd/-/issues/1722#note_1720627
*/
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "NL5xRU"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "ELAN0415:00@9",
},
},
{
/*
* Spurious wakeups from TP_ATTN# pin
* Found in BIOS 1.7.7
*/
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "NH5xAx"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "SYNA1202:00@16",
},
},
{
/*
* On the Peaq C1010 2-in-1 INT33FC:00 pin 3 is connected to
* a "dolby" button. At the ACPI level an _AEI event-handler
* is connected which sets an ACPI variable to 1 on both
* edges. This variable can be polled + cleared to 0 using
* WMI. But since the variable is set on both edges the WMI
* interface is pretty useless even when polling.
* So instead the x86-android-tablets code instantiates
* a gpio-keys platform device for it.
* Ignore the _AEI handler for the pin, so that it is not busy.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "PEAQ"),
DMI_MATCH(DMI_PRODUCT_NAME, "PEAQ PMM C1010 MD99187"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_interrupt = "INT33FC:00@3",
},
},
{
/*
* Spurious wakeups from TP_ATTN# pin
* Found in BIOS 0.35
* https://gitlab.freedesktop.org/drm/amd/-/issues/3073
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "PNP0C50:00@8",
},
},
{
/*
* Spurious wakeups from GPIO 11
* Found in BIOS 1.04
* https://gitlab.freedesktop.org/drm/amd/-/issues/3954
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
DMI_MATCH(DMI_PRODUCT_FAMILY, "Acer Nitro V 14"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_interrupt = "AMDI0030:00@11",
},
},
{} /* Terminating entry */
};
static int __init acpi_gpio_setup_params(void)
{
const struct acpi_gpiolib_dmi_quirk *quirk = NULL;
const struct dmi_system_id *id;
id = dmi_first_match(gpiolib_acpi_quirks);
if (id)
quirk = id->driver_data;
if (run_edge_events_on_boot < 0) {
if (quirk && quirk->no_edge_events_on_boot)
run_edge_events_on_boot = 0;
else
run_edge_events_on_boot = 1;
}
if (ignore_wake == NULL && quirk && quirk->ignore_wake)
ignore_wake = quirk->ignore_wake;
if (ignore_interrupt == NULL && quirk && quirk->ignore_interrupt)
ignore_interrupt = quirk->ignore_interrupt;
return 0;
}
/* Directly after dmi_setup() which runs as core_initcall() */
postcore_initcall(acpi_gpio_setup_params);

View File

@@ -0,0 +1,363 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ACPI quirks for GPIO ACPI helpers
*
* Author: Hans de Goede <hdegoede@redhat.com>
*/
#include <linux/dmi.h>
#include <linux/kstrtox.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/printk.h>
#include <linux/string.h>
#include <linux/types.h>
#include "gpiolib-acpi.h"
static int run_edge_events_on_boot = -1;
module_param(run_edge_events_on_boot, int, 0444);
MODULE_PARM_DESC(run_edge_events_on_boot,
"Run edge _AEI event-handlers at boot: 0=no, 1=yes, -1=auto");
static char *ignore_wake;
module_param(ignore_wake, charp, 0444);
MODULE_PARM_DESC(ignore_wake,
"controller@pin combos on which to ignore the ACPI wake flag "
"ignore_wake=controller@pin[,controller@pin[,...]]");
static char *ignore_interrupt;
module_param(ignore_interrupt, charp, 0444);
MODULE_PARM_DESC(ignore_interrupt,
"controller@pin combos on which to ignore interrupt "
"ignore_interrupt=controller@pin[,controller@pin[,...]]");
/*
* For GPIO chips which call acpi_gpiochip_request_interrupts() before late_init
* (so builtin drivers) we register the ACPI GpioInt IRQ handlers from a
* late_initcall_sync() handler, so that other builtin drivers can register their
* OpRegions before the event handlers can run. This list contains GPIO chips
* for which the acpi_gpiochip_request_irqs() call has been deferred.
*/
static DEFINE_MUTEX(acpi_gpio_deferred_req_irqs_lock);
static LIST_HEAD(acpi_gpio_deferred_req_irqs_list);
static bool acpi_gpio_deferred_req_irqs_done;
bool acpi_gpio_add_to_deferred_list(struct list_head *list)
{
bool defer;
mutex_lock(&acpi_gpio_deferred_req_irqs_lock);
defer = !acpi_gpio_deferred_req_irqs_done;
if (defer)
list_add(list, &acpi_gpio_deferred_req_irqs_list);
mutex_unlock(&acpi_gpio_deferred_req_irqs_lock);
return defer;
}
void acpi_gpio_remove_from_deferred_list(struct list_head *list)
{
mutex_lock(&acpi_gpio_deferred_req_irqs_lock);
if (!list_empty(list))
list_del_init(list);
mutex_unlock(&acpi_gpio_deferred_req_irqs_lock);
}
int acpi_gpio_need_run_edge_events_on_boot(void)
{
return run_edge_events_on_boot;
}
bool acpi_gpio_in_ignore_list(enum acpi_gpio_ignore_list list,
const char *controller_in, unsigned int pin_in)
{
const char *ignore_list, *controller, *pin_str;
unsigned int pin;
char *endp;
int len;
switch (list) {
case ACPI_GPIO_IGNORE_WAKE:
ignore_list = ignore_wake;
break;
case ACPI_GPIO_IGNORE_INTERRUPT:
ignore_list = ignore_interrupt;
break;
default:
return false;
}
controller = ignore_list;
while (controller) {
pin_str = strchr(controller, '@');
if (!pin_str)
goto err;
len = pin_str - controller;
if (len == strlen(controller_in) &&
strncmp(controller, controller_in, len) == 0) {
pin = simple_strtoul(pin_str + 1, &endp, 10);
if (*endp != 0 && *endp != ',')
goto err;
if (pin == pin_in)
return true;
}
controller = strchr(controller, ',');
if (controller)
controller++;
}
return false;
err:
pr_err_once("Error: Invalid value for gpiolib_acpi.ignore_...: %s\n", ignore_list);
return false;
}
/* Run deferred acpi_gpiochip_request_irqs() */
static int __init acpi_gpio_handle_deferred_request_irqs(void)
{
mutex_lock(&acpi_gpio_deferred_req_irqs_lock);
acpi_gpio_process_deferred_list(&acpi_gpio_deferred_req_irqs_list);
acpi_gpio_deferred_req_irqs_done = true;
mutex_unlock(&acpi_gpio_deferred_req_irqs_lock);
return 0;
}
/* We must use _sync so that this runs after the first deferred_probe run */
late_initcall_sync(acpi_gpio_handle_deferred_request_irqs);
struct acpi_gpiolib_dmi_quirk {
bool no_edge_events_on_boot;
char *ignore_wake;
char *ignore_interrupt;
};
static const struct dmi_system_id gpiolib_acpi_quirks[] __initconst = {
{
/*
* The Minix Neo Z83-4 has a micro-USB-B id-pin handler for
* a non existing micro-USB-B connector which puts the HDMI
* DDC pins in GPIO mode, breaking HDMI support.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "MINIX"),
DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.no_edge_events_on_boot = true,
},
},
{
/*
* The Terra Pad 1061 has a micro-USB-B id-pin handler, which
* instead of controlling the actual micro-USB-B turns the 5V
* boost for its USB-A connector off. The actual micro-USB-B
* connector is wired for charging only.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Wortmann_AG"),
DMI_MATCH(DMI_PRODUCT_NAME, "TERRA_PAD_1061"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.no_edge_events_on_boot = true,
},
},
{
/*
* The Dell Venue 10 Pro 5055, with Bay Trail SoC + TI PMIC uses an
* external embedded-controller connected via I2C + an ACPI GPIO
* event handler on INT33FFC:02 pin 12, causing spurious wakeups.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "Venue 10 Pro 5055"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "INT33FC:02@12",
},
},
{
/*
* HP X2 10 models with Cherry Trail SoC + TI PMIC use an
* external embedded-controller connected via I2C + an ACPI GPIO
* event handler on INT33FF:01 pin 0, causing spurious wakeups.
* When suspending by closing the LID, the power to the USB
* keyboard is turned off, causing INT0002 ACPI events to
* trigger once the XHCI controller notices the keyboard is
* gone. So INT0002 events cause spurious wakeups too. Ignoring
* EC wakes breaks wakeup when opening the lid, the user needs
* to press the power-button to wakeup the system. The
* alternative is suspend simply not working, which is worse.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP x2 Detachable 10-p0XX"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "INT33FF:01@0,INT0002:00@2",
},
},
{
/*
* HP X2 10 models with Bay Trail SoC + AXP288 PMIC use an
* external embedded-controller connected via I2C + an ACPI GPIO
* event handler on INT33FC:02 pin 28, causing spurious wakeups.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"),
DMI_MATCH(DMI_BOARD_NAME, "815D"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "INT33FC:02@28",
},
},
{
/*
* HP X2 10 models with Cherry Trail SoC + AXP288 PMIC use an
* external embedded-controller connected via I2C + an ACPI GPIO
* event handler on INT33FF:01 pin 0, causing spurious wakeups.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"),
DMI_MATCH(DMI_BOARD_NAME, "813E"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "INT33FF:01@0",
},
},
{
/*
* Interrupt storm caused from edge triggered floating pin
* Found in BIOS UX325UAZ.300
* https://bugzilla.kernel.org/show_bug.cgi?id=216208
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX325UAZ_UM325UAZ"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_interrupt = "AMDI0030:00@18",
},
},
{
/*
* Spurious wakeups from TP_ATTN# pin
* Found in BIOS 1.7.8
* https://gitlab.freedesktop.org/drm/amd/-/issues/1722#note_1720627
*/
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "NL5xNU"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "ELAN0415:00@9",
},
},
{
/*
* Spurious wakeups from TP_ATTN# pin
* Found in BIOS 1.7.8
* https://gitlab.freedesktop.org/drm/amd/-/issues/1722#note_1720627
*/
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "NL5xRU"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "ELAN0415:00@9",
},
},
{
/*
* Spurious wakeups from TP_ATTN# pin
* Found in BIOS 1.7.7
*/
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "NH5xAx"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "SYNA1202:00@16",
},
},
{
/*
* On the Peaq C1010 2-in-1 INT33FC:00 pin 3 is connected to
* a "dolby" button. At the ACPI level an _AEI event-handler
* is connected which sets an ACPI variable to 1 on both
* edges. This variable can be polled + cleared to 0 using
* WMI. But since the variable is set on both edges the WMI
* interface is pretty useless even when polling.
* So instead the x86-android-tablets code instantiates
* a gpio-keys platform device for it.
* Ignore the _AEI handler for the pin, so that it is not busy.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "PEAQ"),
DMI_MATCH(DMI_PRODUCT_NAME, "PEAQ PMM C1010 MD99187"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_interrupt = "INT33FC:00@3",
},
},
{
/*
* Spurious wakeups from TP_ATTN# pin
* Found in BIOS 0.35
* https://gitlab.freedesktop.org/drm/amd/-/issues/3073
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "GPD"),
DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_wake = "PNP0C50:00@8",
},
},
{
/*
* Spurious wakeups from GPIO 11
* Found in BIOS 1.04
* https://gitlab.freedesktop.org/drm/amd/-/issues/3954
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
DMI_MATCH(DMI_PRODUCT_FAMILY, "Acer Nitro V 14"),
},
.driver_data = &(struct acpi_gpiolib_dmi_quirk) {
.ignore_interrupt = "AMDI0030:00@11",
},
},
{} /* Terminating entry */
};
static int __init acpi_gpio_setup_params(void)
{
const struct acpi_gpiolib_dmi_quirk *quirk = NULL;
const struct dmi_system_id *id;
id = dmi_first_match(gpiolib_acpi_quirks);
if (id)
quirk = id->driver_data;
if (run_edge_events_on_boot < 0) {
if (quirk && quirk->no_edge_events_on_boot)
run_edge_events_on_boot = 0;
else
run_edge_events_on_boot = 1;
}
if (ignore_wake == NULL && quirk && quirk->ignore_wake)
ignore_wake = quirk->ignore_wake;
if (ignore_interrupt == NULL && quirk && quirk->ignore_interrupt)
ignore_interrupt = quirk->ignore_interrupt;
return 0;
}
/* Directly after dmi_setup() which runs as core_initcall() */
postcore_initcall(acpi_gpio_setup_params);

View File

@@ -58,4 +58,19 @@ static inline int acpi_gpio_count(const struct fwnode_handle *fwnode,
}
#endif
void acpi_gpio_process_deferred_list(struct list_head *list);
bool acpi_gpio_add_to_deferred_list(struct list_head *list);
void acpi_gpio_remove_from_deferred_list(struct list_head *list);
int acpi_gpio_need_run_edge_events_on_boot(void);
enum acpi_gpio_ignore_list {
ACPI_GPIO_IGNORE_WAKE,
ACPI_GPIO_IGNORE_INTERRUPT,
};
bool acpi_gpio_in_ignore_list(enum acpi_gpio_ignore_list list,
const char *controller_in, unsigned int pin_in);
#endif /* GPIOLIB_ACPI_H */

View File

@@ -1366,9 +1366,6 @@ static long linereq_set_values(struct linereq *lr, void __user *ip)
/* scan requested lines to determine the subset to be set */
for (num_set = 0, i = 0; i < lr->num_lines; i++) {
if (lv.mask & BIT_ULL(i)) {
/* setting inputs is not allowed */
if (!test_bit(FLAG_IS_OUT, &lr->lines[i].desc->flags))
return -EPERM;
/* add to compacted values */
if (lv.bits & BIT_ULL(i))
__set_bit(num_set, vals);

View File

@@ -6,7 +6,7 @@
* Copyright (c) 2011 John Crispin <john@phrozen.org>
*/
#include <linux/device.h>
#include <linux/device/devres.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/gfp.h>
@@ -19,32 +19,14 @@
struct fwnode_handle;
struct lock_class_key;
static void devm_gpiod_release(struct device *dev, void *res)
static void devm_gpiod_release(void *desc)
{
struct gpio_desc **desc = res;
gpiod_put(*desc);
gpiod_put(desc);
}
static int devm_gpiod_match(struct device *dev, void *res, void *data)
static void devm_gpiod_release_array(void *descs)
{
struct gpio_desc **this = res, **gpio = data;
return *this == *gpio;
}
static void devm_gpiod_release_array(struct device *dev, void *res)
{
struct gpio_descs **descs = res;
gpiod_put_array(*descs);
}
static int devm_gpiod_match_array(struct device *dev, void *res, void *data)
{
struct gpio_descs **this = res, **gpios = data;
return *this == *gpios;
gpiod_put_array(descs);
}
/**
@@ -114,8 +96,8 @@ struct gpio_desc *__must_check devm_gpiod_get_index(struct device *dev,
unsigned int idx,
enum gpiod_flags flags)
{
struct gpio_desc **dr;
struct gpio_desc *desc;
int ret;
desc = gpiod_get_index(dev, con_id, idx, flags);
if (IS_ERR(desc))
@@ -126,23 +108,16 @@ struct gpio_desc *__must_check devm_gpiod_get_index(struct device *dev,
* already under resource management by this device.
*/
if (flags & GPIOD_FLAGS_BIT_NONEXCLUSIVE) {
struct devres *dres;
bool dres;
dres = devres_find(dev, devm_gpiod_release,
devm_gpiod_match, &desc);
dres = devm_is_action_added(dev, devm_gpiod_release, desc);
if (dres)
return desc;
}
dr = devres_alloc(devm_gpiod_release, sizeof(struct gpio_desc *),
GFP_KERNEL);
if (!dr) {
gpiod_put(desc);
return ERR_PTR(-ENOMEM);
}
*dr = desc;
devres_add(dev, dr);
ret = devm_add_action_or_reset(dev, devm_gpiod_release, desc);
if (ret)
return ERR_PTR(ret);
return desc;
}
@@ -171,22 +146,16 @@ struct gpio_desc *devm_fwnode_gpiod_get_index(struct device *dev,
enum gpiod_flags flags,
const char *label)
{
struct gpio_desc **dr;
struct gpio_desc *desc;
dr = devres_alloc(devm_gpiod_release, sizeof(struct gpio_desc *),
GFP_KERNEL);
if (!dr)
return ERR_PTR(-ENOMEM);
int ret;
desc = gpiod_find_and_request(dev, fwnode, con_id, index, flags, label, false);
if (IS_ERR(desc)) {
devres_free(dr);
if (IS_ERR(desc))
return desc;
}
*dr = desc;
devres_add(dev, dr);
ret = devm_add_action_or_reset(dev, devm_gpiod_release, desc);
if (ret)
return ERR_PTR(ret);
return desc;
}
@@ -244,22 +213,16 @@ struct gpio_descs *__must_check devm_gpiod_get_array(struct device *dev,
const char *con_id,
enum gpiod_flags flags)
{
struct gpio_descs **dr;
struct gpio_descs *descs;
dr = devres_alloc(devm_gpiod_release_array,
sizeof(struct gpio_descs *), GFP_KERNEL);
if (!dr)
return ERR_PTR(-ENOMEM);
int ret;
descs = gpiod_get_array(dev, con_id, flags);
if (IS_ERR(descs)) {
devres_free(dr);
if (IS_ERR(descs))
return descs;
}
*dr = descs;
devres_add(dev, dr);
ret = devm_add_action_or_reset(dev, devm_gpiod_release_array, descs);
if (ret)
return ERR_PTR(ret);
return descs;
}
@@ -307,8 +270,7 @@ EXPORT_SYMBOL_GPL(devm_gpiod_get_array_optional);
*/
void devm_gpiod_put(struct device *dev, struct gpio_desc *desc)
{
WARN_ON(devres_release(dev, devm_gpiod_release, devm_gpiod_match,
&desc));
devm_release_action(dev, devm_gpiod_release, desc);
}
EXPORT_SYMBOL_GPL(devm_gpiod_put);
@@ -332,13 +294,13 @@ void devm_gpiod_unhinge(struct device *dev, struct gpio_desc *desc)
if (IS_ERR_OR_NULL(desc))
return;
ret = devres_destroy(dev, devm_gpiod_release,
devm_gpiod_match, &desc);
/*
* If the GPIO descriptor is requested as nonexclusive, we
* may call this function several times on the same descriptor
* so it is OK if devres_destroy() returns -ENOENT.
*/
ret = devm_remove_action_nowarn(dev, devm_gpiod_release, desc);
if (ret == -ENOENT)
return;
/* Anything else we should warn about */
@@ -357,8 +319,7 @@ EXPORT_SYMBOL_GPL(devm_gpiod_unhinge);
*/
void devm_gpiod_put_array(struct device *dev, struct gpio_descs *descs)
{
WARN_ON(devres_release(dev, devm_gpiod_release_array,
devm_gpiod_match_array, &descs));
devm_remove_action(dev, devm_gpiod_release_array, descs);
}
EXPORT_SYMBOL_GPL(devm_gpiod_put_array);

View File

@@ -224,6 +224,15 @@ static void of_gpio_try_fixup_polarity(const struct device_node *np,
*/
{ "lantiq,pci-xway", "gpio-reset", false },
#endif
#if IS_ENABLED(CONFIG_REGULATOR_S5M8767)
/*
* According to S5M8767, the DVS and DS pin are
* active-high signals. However, exynos5250-spring.dts use
* active-low setting.
*/
{ "samsung,s5m8767-pmic", "s5m8767,pmic-buck-dvs-gpios", true },
{ "samsung,s5m8767-pmic", "s5m8767,pmic-buck-ds-gpios", true },
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_TSC2005)
/*
* DTS for Nokia N900 incorrectly specified "active high"
@@ -1278,3 +1287,11 @@ void of_gpiochip_remove(struct gpio_chip *chip)
{
of_node_put(dev_of_node(&chip->gpiodev->dev));
}
bool of_gpiochip_instance_match(struct gpio_chip *gc, unsigned int index)
{
if (gc->of_node_instance_match)
return gc->of_node_instance_match(gc, index);
return false;
}

View File

@@ -22,6 +22,7 @@ struct gpio_desc *of_find_gpio(struct device_node *np,
unsigned long *lookupflags);
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);
#else
static inline struct gpio_desc *of_find_gpio(struct device_node *np,
@@ -33,6 +34,11 @@ static inline struct gpio_desc *of_find_gpio(struct device_node *np,
}
static inline int of_gpiochip_add(struct gpio_chip *gc) { return 0; }
static inline void of_gpiochip_remove(struct gpio_chip *gc) { }
static inline bool of_gpiochip_instance_match(struct gpio_chip *gc,
unsigned int index)
{
return false;
}
static inline int of_gpio_count(const struct fwnode_handle *fwnode,
const char *con_id)
{

View File

@@ -134,16 +134,14 @@ static ssize_t value_store(struct device *dev,
long value;
status = kstrtol(buf, 0, &value);
guard(mutex)(&data->mutex);
if (!test_bit(FLAG_IS_OUT, &desc->flags))
return -EPERM;
if (status)
return status;
gpiod_set_value_cansleep(desc, value);
guard(mutex)(&data->mutex);
status = gpiod_set_value_cansleep(desc, value);
if (status)
return status;
return size;
}

View File

@@ -265,6 +265,20 @@ struct gpio_device *gpiod_to_gpio_device(struct gpio_desc *desc)
}
EXPORT_SYMBOL_GPL(gpiod_to_gpio_device);
/**
* gpiod_is_equal() - Check if two GPIO descriptors refer to the same pin.
* @desc: Descriptor to compare.
* @other: The second descriptor to compare against.
*
* Returns:
* True if the descriptors refer to the same physical pin. False otherwise.
*/
bool gpiod_is_equal(struct gpio_desc *desc, struct gpio_desc *other)
{
return desc == other;
}
EXPORT_SYMBOL_GPL(gpiod_is_equal);
/**
* gpio_device_get_base() - Get the base GPIO number allocated by this device
* @gdev: GPIO device
@@ -342,6 +356,37 @@ static int gpiochip_find_base_unlocked(u16 ngpio)
}
}
/*
* This descriptor validation needs to be inserted verbatim into each
* function taking a descriptor, so we need to use a preprocessor
* macro to avoid endless duplication. If the desc is NULL it is an
* optional GPIO and calls should just bail out.
*/
static int validate_desc(const struct gpio_desc *desc, const char *func)
{
if (!desc)
return 0;
if (IS_ERR(desc)) {
pr_warn("%s: invalid GPIO (errorpointer: %pe)\n", func, desc);
return PTR_ERR(desc);
}
return 1;
}
#define VALIDATE_DESC(desc) do { \
int __valid = validate_desc(desc, __func__); \
if (__valid <= 0) \
return __valid; \
} while (0)
#define VALIDATE_DESC_VOID(desc) do { \
int __valid = validate_desc(desc, __func__); \
if (__valid <= 0) \
return; \
} while (0)
static int gpiochip_get_direction(struct gpio_chip *gc, unsigned int offset)
{
int ret;
@@ -376,11 +421,8 @@ int gpiod_get_direction(struct gpio_desc *desc)
unsigned int offset;
int ret;
/*
* We cannot use VALIDATE_DESC() as we must not return 0 for a NULL
* descriptor like we usually do.
*/
if (IS_ERR_OR_NULL(desc))
ret = validate_desc(desc, __func__);
if (ret <= 0)
return -EINVAL;
CLASS(gpio_chip_guard, guard)(desc);
@@ -880,14 +922,12 @@ static void machine_gpiochip_add(struct gpio_chip *gc)
{
struct gpiod_hog *hog;
mutex_lock(&gpio_machine_hogs_mutex);
guard(mutex)(&gpio_machine_hogs_mutex);
list_for_each_entry(hog, &gpio_machine_hogs, list) {
if (!strcmp(gc->label, hog->chip_label))
gpiochip_machine_hog(gc, hog);
}
mutex_unlock(&gpio_machine_hogs_mutex);
}
static void gpiochip_setup_devs(void)
@@ -981,7 +1021,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
struct gpio_device *gdev;
unsigned int desc_index;
int base = 0;
int ret = 0;
int ret;
/* Only allow one set() and one set_multiple(). */
if ((gc->set && gc->set_rv) ||
@@ -1006,11 +1046,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
device_set_node(&gdev->dev, gpiochip_choose_fwnode(gc));
gdev->id = ida_alloc(&gpio_ida, GFP_KERNEL);
if (gdev->id < 0) {
ret = gdev->id;
ret = ida_alloc(&gpio_ida, GFP_KERNEL);
if (ret < 0)
goto err_free_gdev;
}
gdev->id = ret;
ret = dev_set_name(&gdev->dev, GPIOCHIP_NAME "%d", gdev->id);
if (ret)
@@ -1513,9 +1552,8 @@ static int gpiochip_hierarchy_irq_domain_translate(struct irq_domain *d,
unsigned int *type)
{
/* We support standard DT translation */
if (is_of_node(fwspec->fwnode) && fwspec->param_count == 2) {
return irq_domain_translate_twocell(d, fwspec, hwirq, type);
}
if (is_of_node(fwspec->fwnode))
return irq_domain_translate_twothreecell(d, fwspec, hwirq, type);
/* This is for board files and others not using DT */
if (is_fwnode_irqchip(fwspec->fwnode)) {
@@ -1817,11 +1855,26 @@ static void gpiochip_irq_unmap(struct irq_domain *d, unsigned int irq)
irq_set_chip_data(irq, NULL);
}
static int gpiochip_irq_select(struct irq_domain *d, struct irq_fwspec *fwspec,
enum irq_domain_bus_token bus_token)
{
struct fwnode_handle *fwnode = fwspec->fwnode;
struct gpio_chip *gc = d->host_data;
unsigned int index = fwspec->param[0];
if (fwspec->param_count == 3 && is_of_node(fwnode))
return of_gpiochip_instance_match(gc, index);
/* Fallback for twocells */
return (fwnode && (d->fwnode == fwnode) && (d->bus_token == bus_token));
}
static const struct irq_domain_ops gpiochip_domain_ops = {
.map = gpiochip_irq_map,
.unmap = gpiochip_irq_unmap,
.select = gpiochip_irq_select,
/* Virtually all GPIO irqchips are twocell:ed */
.xlate = irq_domain_xlate_twocell,
.xlate = irq_domain_xlate_twothreecell,
};
static struct irq_domain *gpiochip_simple_create_domain(struct gpio_chip *gc)
@@ -1841,7 +1894,6 @@ static int gpiochip_to_irq(struct gpio_chip *gc, unsigned int offset)
{
struct irq_domain *domain = gc->irq.domain;
#ifdef CONFIG_GPIOLIB_IRQCHIP
/*
* Avoid race condition with other code, which tries to lookup
* an IRQ before the irqchip has been properly registered,
@@ -1849,7 +1901,6 @@ static int gpiochip_to_irq(struct gpio_chip *gc, unsigned int offset)
*/
if (!gc->irq.initialized)
return -EPROBE_DEFER;
#endif
if (!gpiochip_irqchip_irq_valid(gc, offset))
return -ENXIO;
@@ -2411,37 +2462,6 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
return ret;
}
/*
* This descriptor validation needs to be inserted verbatim into each
* function taking a descriptor, so we need to use a preprocessor
* macro to avoid endless duplication. If the desc is NULL it is an
* optional GPIO and calls should just bail out.
*/
static int validate_desc(const struct gpio_desc *desc, const char *func)
{
if (!desc)
return 0;
if (IS_ERR(desc)) {
pr_warn("%s: invalid GPIO (errorpointer)\n", func);
return PTR_ERR(desc);
}
return 1;
}
#define VALIDATE_DESC(desc) do { \
int __valid = validate_desc(desc, __func__); \
if (__valid <= 0) \
return __valid; \
} while (0)
#define VALIDATE_DESC_VOID(desc) do { \
int __valid = validate_desc(desc, __func__); \
if (__valid <= 0) \
return; \
} while (0)
int gpiod_request(struct gpio_desc *desc, const char *label)
{
int ret = -EPROBE_DEFER;
@@ -3051,7 +3071,7 @@ int gpiod_direction_output_nonotify(struct gpio_desc *desc, int value)
*/
int gpiod_enable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags)
{
int ret = 0;
int ret;
VALIDATE_DESC(desc);
@@ -3084,7 +3104,7 @@ EXPORT_SYMBOL_GPL(gpiod_enable_hw_timestamp_ns);
*/
int gpiod_disable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags)
{
int ret = 0;
int ret;
VALIDATE_DESC(desc);
@@ -3599,6 +3619,9 @@ static int gpio_set_open_source_value_commit(struct gpio_desc *desc, bool value)
static int gpiod_set_raw_value_commit(struct gpio_desc *desc, bool value)
{
if (unlikely(!test_bit(FLAG_IS_OUT, &desc->flags)))
return -EPERM;
CLASS(gpio_chip_guard, guard)(desc);
if (!guard.gc)
return -ENODEV;
@@ -3670,6 +3693,12 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
if (!can_sleep)
WARN_ON(array_info->gdev->can_sleep);
for (i = 0; i < array_size; i++) {
if (unlikely(!test_bit(FLAG_IS_OUT,
&desc_array[i]->flags)))
return -EPERM;
}
guard(srcu)(&array_info->gdev->srcu);
gc = srcu_dereference(array_info->gdev->chip,
&array_info->gdev->srcu);
@@ -3729,6 +3758,9 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
int hwgpio = gpio_chip_hwgpio(desc);
int value = test_bit(i, value_bitmap);
if (unlikely(!test_bit(FLAG_IS_OUT, &desc->flags)))
return -EPERM;
/*
* Pins applicable for fast input but not for
* fast output processing may have been already
@@ -3950,13 +3982,10 @@ int gpiod_to_irq(const struct gpio_desc *desc)
struct gpio_device *gdev;
struct gpio_chip *gc;
int offset;
int ret;
/*
* Cannot VALIDATE_DESC() here as gpiod_to_irq() consumer semantics
* requires this function to not return zero on an invalid descriptor
* but rather a negative error number.
*/
if (IS_ERR_OR_NULL(desc))
ret = validate_desc(desc, __func__);
if (ret <= 0)
return -EINVAL;
gdev = desc->gdev;
@@ -3968,13 +3997,12 @@ int gpiod_to_irq(const struct gpio_desc *desc)
offset = gpio_chip_hwgpio(desc);
if (gc->to_irq) {
int retirq = gc->to_irq(gc, offset);
ret = gc->to_irq(gc, offset);
if (ret)
return ret;
/* Zero means NO_IRQ */
if (!retirq)
return -ENXIO;
return retirq;
return -ENXIO;
}
#ifdef CONFIG_GPIOLIB_IRQCHIP
if (gc->irq.chip) {
@@ -4329,12 +4357,10 @@ void gpiod_add_lookup_tables(struct gpiod_lookup_table **tables, size_t n)
{
unsigned int i;
mutex_lock(&gpio_lookup_lock);
guard(mutex)(&gpio_lookup_lock);
for (i = 0; i < n; i++)
list_add_tail(&tables[i]->list, &gpio_lookup_list);
mutex_unlock(&gpio_lookup_lock);
}
/**
@@ -4393,11 +4419,9 @@ void gpiod_remove_lookup_table(struct gpiod_lookup_table *table)
if (!table)
return;
mutex_lock(&gpio_lookup_lock);
guard(mutex)(&gpio_lookup_lock);
list_del(&table->list);
mutex_unlock(&gpio_lookup_lock);
}
EXPORT_SYMBOL_GPL(gpiod_remove_lookup_table);
@@ -4409,7 +4433,7 @@ void gpiod_add_hogs(struct gpiod_hog *hogs)
{
struct gpiod_hog *hog;
mutex_lock(&gpio_machine_hogs_mutex);
guard(mutex)(&gpio_machine_hogs_mutex);
for (hog = &hogs[0]; hog->chip_label; hog++) {
list_add_tail(&hog->list, &gpio_machine_hogs);
@@ -4423,8 +4447,6 @@ void gpiod_add_hogs(struct gpiod_hog *hogs)
if (gdev)
gpiochip_machine_hog(gpio_device_get_chip(gdev), hog);
}
mutex_unlock(&gpio_machine_hogs_mutex);
}
EXPORT_SYMBOL_GPL(gpiod_add_hogs);
@@ -4432,10 +4454,10 @@ void gpiod_remove_hogs(struct gpiod_hog *hogs)
{
struct gpiod_hog *hog;
mutex_lock(&gpio_machine_hogs_mutex);
guard(mutex)(&gpio_machine_hogs_mutex);
for (hog = &hogs[0]; hog->chip_label; hog++)
list_del(&hog->list);
mutex_unlock(&gpio_machine_hogs_mutex);
}
EXPORT_SYMBOL_GPL(gpiod_remove_hogs);
@@ -5114,8 +5136,7 @@ EXPORT_SYMBOL_GPL(gpiod_get_array_optional);
*/
void gpiod_put(struct gpio_desc *desc)
{
if (desc)
gpiod_free(desc);
gpiod_free(desc);
}
EXPORT_SYMBOL_GPL(gpiod_put);

View File

@@ -943,6 +943,26 @@ config MFD_MAX77714
drivers must be enabled in order to use each functionality of the
device.
config MFD_MAX77759
tristate "Maxim Integrated MAX77759 PMIC"
depends on I2C
depends on OF
select IRQ_DOMAIN
select MFD_CORE
select REGMAP_I2C
select REGMAP_IRQ
help
Say yes here to add support for Maxim Integrated MAX77759.
This is a companion Power Management IC for USB Type-C applications
with Battery Charger, Fuel Gauge, temperature sensors, USB Type-C
Port Controller (TCPC), NVMEM, and additional GPIO interfaces.
This driver provides common support for accessing the device;
additional drivers must be enabled in order to use the functionality
of the device.
To compile this driver as a module, choose M here: the module will be
called max77759.
config MFD_MAX77843
bool "Maxim Semiconductor MAX77843 PMIC Support"
depends on I2C=y

View File

@@ -169,6 +169,7 @@ obj-$(CONFIG_MFD_MAX77686) += max77686.o
obj-$(CONFIG_MFD_MAX77693) += max77693.o
obj-$(CONFIG_MFD_MAX77705) += max77705.o
obj-$(CONFIG_MFD_MAX77714) += max77714.o
obj-$(CONFIG_MFD_MAX77759) += max77759.o
obj-$(CONFIG_MFD_MAX77843) += max77843.o
obj-$(CONFIG_MFD_MAX8907) += max8907.o
max8925-objs := max8925-core.o max8925-i2c.o

690
drivers/mfd/max77759.c Normal file
View File

@@ -0,0 +1,690 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2020 Google Inc
* Copyright 2025 Linaro Ltd.
*
* Core driver for Maxim MAX77759 companion PMIC for USB Type-C
*/
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/dev_printk.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/jiffies.h>
#include <linux/mfd/core.h>
#include <linux/mfd/max77759.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/overflow.h>
#include <linux/regmap.h>
/* Chip ID as per MAX77759_PMIC_REG_PMIC_ID */
enum {
MAX77759_CHIP_ID = 59,
};
enum max77759_i2c_subdev_id {
/*
* These are arbitrary and simply used to match struct
* max77759_i2c_subdev entries to the regmap pointers in struct
* max77759 during probe().
*/
MAX77759_I2C_SUBDEV_ID_MAXQ,
MAX77759_I2C_SUBDEV_ID_CHARGER,
};
struct max77759_i2c_subdev {
enum max77759_i2c_subdev_id id;
const struct regmap_config *cfg;
u16 i2c_address;
};
static const struct regmap_range max77759_top_registers[] = {
regmap_reg_range(0x00, 0x02), /* PMIC_ID / PMIC_REVISION / OTP_REVISION */
regmap_reg_range(0x22, 0x24), /* INTSRC / INTSRCMASK / TOPSYS_INT */
regmap_reg_range(0x26, 0x26), /* TOPSYS_INT_MASK */
regmap_reg_range(0x40, 0x40), /* I2C_CNFG */
regmap_reg_range(0x50, 0x51), /* SWRESET / CONTROL_FG */
};
static const struct regmap_range max77759_top_ro_registers[] = {
regmap_reg_range(0x00, 0x02),
regmap_reg_range(0x22, 0x22),
};
static const struct regmap_range max77759_top_volatile_registers[] = {
regmap_reg_range(0x22, 0x22),
regmap_reg_range(0x24, 0x24),
};
static const struct regmap_access_table max77759_top_wr_table = {
.yes_ranges = max77759_top_registers,
.n_yes_ranges = ARRAY_SIZE(max77759_top_registers),
.no_ranges = max77759_top_ro_registers,
.n_no_ranges = ARRAY_SIZE(max77759_top_ro_registers),
};
static const struct regmap_access_table max77759_top_rd_table = {
.yes_ranges = max77759_top_registers,
.n_yes_ranges = ARRAY_SIZE(max77759_top_registers),
};
static const struct regmap_access_table max77759_top_volatile_table = {
.yes_ranges = max77759_top_volatile_registers,
.n_yes_ranges = ARRAY_SIZE(max77759_top_volatile_registers),
};
static const struct regmap_config max77759_regmap_config_top = {
.name = "top",
.reg_bits = 8,
.val_bits = 8,
.max_register = MAX77759_PMIC_REG_CONTROL_FG,
.wr_table = &max77759_top_wr_table,
.rd_table = &max77759_top_rd_table,
.volatile_table = &max77759_top_volatile_table,
.num_reg_defaults_raw = MAX77759_PMIC_REG_CONTROL_FG + 1,
.cache_type = REGCACHE_FLAT,
};
static const struct regmap_range max77759_maxq_registers[] = {
regmap_reg_range(0x60, 0x73), /* Device ID, Rev, INTx, STATUSx, MASKx */
regmap_reg_range(0x81, 0xa1), /* AP_DATAOUTx */
regmap_reg_range(0xb1, 0xd1), /* AP_DATAINx */
regmap_reg_range(0xe0, 0xe0), /* UIC_SWRST */
};
static const struct regmap_range max77759_maxq_ro_registers[] = {
regmap_reg_range(0x60, 0x63), /* Device ID, Rev */
regmap_reg_range(0x68, 0x6f), /* STATUSx */
regmap_reg_range(0xb1, 0xd1),
};
static const struct regmap_range max77759_maxq_volatile_registers[] = {
regmap_reg_range(0x64, 0x6f), /* INTx, STATUSx */
regmap_reg_range(0xb1, 0xd1),
regmap_reg_range(0xe0, 0xe0),
};
static const struct regmap_access_table max77759_maxq_wr_table = {
.yes_ranges = max77759_maxq_registers,
.n_yes_ranges = ARRAY_SIZE(max77759_maxq_registers),
.no_ranges = max77759_maxq_ro_registers,
.n_no_ranges = ARRAY_SIZE(max77759_maxq_ro_registers),
};
static const struct regmap_access_table max77759_maxq_rd_table = {
.yes_ranges = max77759_maxq_registers,
.n_yes_ranges = ARRAY_SIZE(max77759_maxq_registers),
};
static const struct regmap_access_table max77759_maxq_volatile_table = {
.yes_ranges = max77759_maxq_volatile_registers,
.n_yes_ranges = ARRAY_SIZE(max77759_maxq_volatile_registers),
};
static const struct regmap_config max77759_regmap_config_maxq = {
.name = "maxq",
.reg_bits = 8,
.val_bits = 8,
.max_register = MAX77759_MAXQ_REG_UIC_SWRST,
.wr_table = &max77759_maxq_wr_table,
.rd_table = &max77759_maxq_rd_table,
.volatile_table = &max77759_maxq_volatile_table,
.num_reg_defaults_raw = MAX77759_MAXQ_REG_UIC_SWRST + 1,
.cache_type = REGCACHE_FLAT,
};
static const struct regmap_range max77759_charger_registers[] = {
regmap_reg_range(0xb0, 0xcc),
};
static const struct regmap_range max77759_charger_ro_registers[] = {
regmap_reg_range(0xb4, 0xb8), /* INT_OK, DETAILS_0x */
};
static const struct regmap_range max77759_charger_volatile_registers[] = {
regmap_reg_range(0xb0, 0xb1), /* INTx */
regmap_reg_range(0xb4, 0xb8),
};
static const struct regmap_access_table max77759_charger_wr_table = {
.yes_ranges = max77759_charger_registers,
.n_yes_ranges = ARRAY_SIZE(max77759_charger_registers),
.no_ranges = max77759_charger_ro_registers,
.n_no_ranges = ARRAY_SIZE(max77759_charger_ro_registers),
};
static const struct regmap_access_table max77759_charger_rd_table = {
.yes_ranges = max77759_charger_registers,
.n_yes_ranges = ARRAY_SIZE(max77759_charger_registers),
};
static const struct regmap_access_table max77759_charger_volatile_table = {
.yes_ranges = max77759_charger_volatile_registers,
.n_yes_ranges = ARRAY_SIZE(max77759_charger_volatile_registers),
};
static const struct regmap_config max77759_regmap_config_charger = {
.name = "charger",
.reg_bits = 8,
.val_bits = 8,
.max_register = MAX77759_CHGR_REG_CHG_CNFG_19,
.wr_table = &max77759_charger_wr_table,
.rd_table = &max77759_charger_rd_table,
.volatile_table = &max77759_charger_volatile_table,
.num_reg_defaults_raw = MAX77759_CHGR_REG_CHG_CNFG_19 + 1,
.cache_type = REGCACHE_FLAT,
};
/*
* Interrupts - with the following interrupt hierarchy:
* pmic IRQs (INTSRC)
* - MAXQ_INT: MaxQ IRQs
* - UIC_INT1
* - APCmdResI
* - SysMsgI
* - GPIOxI
* - TOPSYS_INT: topsys
* - TOPSYS_INT
* - TSHDN_INT
* - SYSOVLO_INT
* - SYSUVLO_INT
* - FSHIP_NOT_RD
* - CHGR_INT: charger
* - CHG_INT
* - CHG_INT2
*/
enum {
MAX77759_INT_MAXQ,
MAX77759_INT_TOPSYS,
MAX77759_INT_CHGR,
};
enum {
MAX77759_TOPSYS_INT_TSHDN,
MAX77759_TOPSYS_INT_SYSOVLO,
MAX77759_TOPSYS_INT_SYSUVLO,
MAX77759_TOPSYS_INT_FSHIP_NOT_RD,
};
enum {
MAX77759_MAXQ_INT_APCMDRESI,
MAX77759_MAXQ_INT_SYSMSGI,
MAX77759_MAXQ_INT_GPIO,
MAX77759_MAXQ_INT_UIC1,
MAX77759_MAXQ_INT_UIC2,
MAX77759_MAXQ_INT_UIC3,
MAX77759_MAXQ_INT_UIC4,
};
enum {
MAX77759_CHARGER_INT_1,
MAX77759_CHARGER_INT_2,
};
static const struct regmap_irq max77759_pmic_irqs[] = {
REGMAP_IRQ_REG(MAX77759_INT_MAXQ, 0, MAX77759_PMIC_REG_INTSRC_MAXQ),
REGMAP_IRQ_REG(MAX77759_INT_TOPSYS, 0, MAX77759_PMIC_REG_INTSRC_TOPSYS),
REGMAP_IRQ_REG(MAX77759_INT_CHGR, 0, MAX77759_PMIC_REG_INTSRC_CHGR),
};
static const struct regmap_irq max77759_maxq_irqs[] = {
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_APCMDRESI, 0, MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI),
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_SYSMSGI, 0, MAX77759_MAXQ_REG_UIC_INT1_SYSMSGI),
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_GPIO, 0, GENMASK(1, 0)),
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC1, 0, GENMASK(5, 2)),
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC2, 1, GENMASK(7, 0)),
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC3, 2, GENMASK(7, 0)),
REGMAP_IRQ_REG(MAX77759_MAXQ_INT_UIC4, 3, GENMASK(7, 0)),
};
static const struct regmap_irq max77759_topsys_irqs[] = {
REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_TSHDN, 0, MAX77759_PMIC_REG_TOPSYS_INT_TSHDN),
REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_SYSOVLO, 0, MAX77759_PMIC_REG_TOPSYS_INT_SYSOVLO),
REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_SYSUVLO, 0, MAX77759_PMIC_REG_TOPSYS_INT_SYSUVLO),
REGMAP_IRQ_REG(MAX77759_TOPSYS_INT_FSHIP_NOT_RD, 0, MAX77759_PMIC_REG_TOPSYS_INT_FSHIP),
};
static const struct regmap_irq max77759_chgr_irqs[] = {
REGMAP_IRQ_REG(MAX77759_CHARGER_INT_1, 0, GENMASK(7, 0)),
REGMAP_IRQ_REG(MAX77759_CHARGER_INT_2, 1, GENMASK(7, 0)),
};
static const struct regmap_irq_chip max77759_pmic_irq_chip = {
.name = "max77759-pmic",
/* INTSRC is read-only and doesn't require clearing */
.status_base = MAX77759_PMIC_REG_INTSRC,
.mask_base = MAX77759_PMIC_REG_INTSRCMASK,
.num_regs = 1,
.irqs = max77759_pmic_irqs,
.num_irqs = ARRAY_SIZE(max77759_pmic_irqs),
};
/*
* We can let regmap-irq auto-ack the topsys interrupt bits as required, but
* for all others the individual drivers need to know which interrupt bit
* exactly is set inside their interrupt handlers, and therefore we can not set
* .ack_base for those.
*/
static const struct regmap_irq_chip max77759_maxq_irq_chip = {
.name = "max77759-maxq",
.domain_suffix = "MAXQ",
.status_base = MAX77759_MAXQ_REG_UIC_INT1,
.mask_base = MAX77759_MAXQ_REG_UIC_INT1_M,
.num_regs = 4,
.irqs = max77759_maxq_irqs,
.num_irqs = ARRAY_SIZE(max77759_maxq_irqs),
};
static const struct regmap_irq_chip max77759_topsys_irq_chip = {
.name = "max77759-topsys",
.domain_suffix = "TOPSYS",
.status_base = MAX77759_PMIC_REG_TOPSYS_INT,
.mask_base = MAX77759_PMIC_REG_TOPSYS_INT_MASK,
.ack_base = MAX77759_PMIC_REG_TOPSYS_INT,
.num_regs = 1,
.irqs = max77759_topsys_irqs,
.num_irqs = ARRAY_SIZE(max77759_topsys_irqs),
};
static const struct regmap_irq_chip max77759_chrg_irq_chip = {
.name = "max77759-chgr",
.domain_suffix = "CHGR",
.status_base = MAX77759_CHGR_REG_CHG_INT,
.mask_base = MAX77759_CHGR_REG_CHG_INT_MASK,
.num_regs = 2,
.irqs = max77759_chgr_irqs,
.num_irqs = ARRAY_SIZE(max77759_chgr_irqs),
};
static const struct max77759_i2c_subdev max77759_i2c_subdevs[] = {
{
.id = MAX77759_I2C_SUBDEV_ID_MAXQ,
.cfg = &max77759_regmap_config_maxq,
/* I2C address is same as for sub-block 'top' */
},
{
.id = MAX77759_I2C_SUBDEV_ID_CHARGER,
.cfg = &max77759_regmap_config_charger,
.i2c_address = 0x69,
},
};
static const struct resource max77759_gpio_resources[] = {
DEFINE_RES_IRQ_NAMED(MAX77759_MAXQ_INT_GPIO, "GPI"),
};
static const struct resource max77759_charger_resources[] = {
DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_1, "INT1"),
DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_2, "INT2"),
};
static const struct mfd_cell max77759_cells[] = {
MFD_CELL_OF("max77759-nvmem", NULL, NULL, 0, 0,
"maxim,max77759-nvmem"),
};
static const struct mfd_cell max77759_maxq_cells[] = {
MFD_CELL_OF("max77759-gpio", max77759_gpio_resources, NULL, 0, 0,
"maxim,max77759-gpio"),
};
static const struct mfd_cell max77759_charger_cells[] = {
MFD_CELL_RES("max77759-charger", max77759_charger_resources),
};
int max77759_maxq_command(struct max77759 *max77759,
const struct max77759_maxq_command *cmd,
struct max77759_maxq_response *rsp)
{
DEFINE_FLEX(struct max77759_maxq_response, _rsp, rsp, length, 1);
struct device *dev = regmap_get_device(max77759->regmap_maxq);
static const unsigned int timeout_ms = 200;
int ret;
if (cmd->length > MAX77759_MAXQ_OPCODE_MAXLENGTH)
return -EINVAL;
/*
* As a convenience for API users when issuing simple commands, rsp is
* allowed to be NULL. In that case we need a temporary here to write
* the response to, as we need to verify that the command was indeed
* completed correctly.
*/
if (!rsp)
rsp = _rsp;
if (!rsp->length || rsp->length > MAX77759_MAXQ_OPCODE_MAXLENGTH)
return -EINVAL;
guard(mutex)(&max77759->maxq_lock);
reinit_completion(&max77759->cmd_done);
/*
* MaxQ latches the message when the DATAOUT32 register is written. If
* cmd->length is shorter we still need to write 0 to it.
*/
ret = regmap_bulk_write(max77759->regmap_maxq,
MAX77759_MAXQ_REG_AP_DATAOUT0, cmd->cmd,
cmd->length);
if (!ret && cmd->length < MAX77759_MAXQ_OPCODE_MAXLENGTH)
ret = regmap_write(max77759->regmap_maxq,
MAX77759_MAXQ_REG_AP_DATAOUT32, 0);
if (ret) {
dev_err(dev, "writing command failed: %d\n", ret);
return ret;
}
/* Wait for response from MaxQ */
if (!wait_for_completion_timeout(&max77759->cmd_done,
msecs_to_jiffies(timeout_ms))) {
dev_err(dev, "timed out waiting for response\n");
return -ETIMEDOUT;
}
ret = regmap_bulk_read(max77759->regmap_maxq,
MAX77759_MAXQ_REG_AP_DATAIN0,
rsp->rsp, rsp->length);
if (ret) {
dev_err(dev, "reading response failed: %d\n", ret);
return ret;
}
/*
* As per the protocol, the first byte of the reply will match the
* request.
*/
if (cmd->cmd[0] != rsp->rsp[0]) {
dev_err(dev, "unexpected opcode response for %#.2x: %*ph\n",
cmd->cmd[0], (int)rsp->length, rsp->rsp);
return -EIO;
}
return 0;
}
EXPORT_SYMBOL_GPL(max77759_maxq_command);
static irqreturn_t apcmdres_irq_handler(int irq, void *irq_data)
{
struct max77759 *max77759 = irq_data;
regmap_write(max77759->regmap_maxq, MAX77759_MAXQ_REG_UIC_INT1,
MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI);
complete(&max77759->cmd_done);
return IRQ_HANDLED;
}
static int max77759_create_i2c_subdev(struct i2c_client *client,
struct max77759 *max77759,
const struct max77759_i2c_subdev *sd)
{
struct i2c_client *sub;
struct regmap *regmap;
int ret;
/*
* If 'sd' has an I2C address, 'sub' will be assigned a new 'dummy'
* device, otherwise use it as-is.
*/
sub = client;
if (sd->i2c_address) {
sub = devm_i2c_new_dummy_device(&client->dev,
client->adapter,
sd->i2c_address);
if (IS_ERR(sub))
return dev_err_probe(&client->dev, PTR_ERR(sub),
"failed to claim I2C device %s\n",
sd->cfg->name);
}
regmap = devm_regmap_init_i2c(sub, sd->cfg);
if (IS_ERR(regmap))
return dev_err_probe(&sub->dev, PTR_ERR(regmap),
"regmap init for '%s' failed\n",
sd->cfg->name);
ret = regmap_attach_dev(&client->dev, regmap, sd->cfg);
if (ret)
return dev_err_probe(&client->dev, ret,
"regmap attach of '%s' failed\n",
sd->cfg->name);
if (sd->id == MAX77759_I2C_SUBDEV_ID_MAXQ)
max77759->regmap_maxq = regmap;
else if (sd->id == MAX77759_I2C_SUBDEV_ID_CHARGER)
max77759->regmap_charger = regmap;
return 0;
}
static int max77759_add_chained_irq_chip(struct device *dev,
struct regmap *regmap,
int pirq,
struct regmap_irq_chip_data *parent,
const struct regmap_irq_chip *chip,
struct regmap_irq_chip_data **data)
{
int irq, ret;
irq = regmap_irq_get_virq(parent, pirq);
if (irq < 0)
return dev_err_probe(dev, irq,
"failed to get parent vIRQ(%d) for chip %s\n",
pirq, chip->name);
ret = devm_regmap_add_irq_chip(dev, regmap, irq,
IRQF_ONESHOT | IRQF_SHARED, 0, chip,
data);
if (ret)
return dev_err_probe(dev, ret, "failed to add %s IRQ chip\n",
chip->name);
return 0;
}
static int max77759_add_chained_maxq(struct i2c_client *client,
struct max77759 *max77759,
struct regmap_irq_chip_data *parent)
{
struct regmap_irq_chip_data *irq_chip_data;
int apcmdres_irq;
int ret;
ret = max77759_add_chained_irq_chip(&client->dev,
max77759->regmap_maxq,
MAX77759_INT_MAXQ,
parent,
&max77759_maxq_irq_chip,
&irq_chip_data);
if (ret)
return ret;
init_completion(&max77759->cmd_done);
apcmdres_irq = regmap_irq_get_virq(irq_chip_data,
MAX77759_MAXQ_INT_APCMDRESI);
ret = devm_request_threaded_irq(&client->dev, apcmdres_irq,
NULL, apcmdres_irq_handler,
IRQF_ONESHOT | IRQF_SHARED,
dev_name(&client->dev), max77759);
if (ret)
return dev_err_probe(&client->dev, ret,
"MAX77759_MAXQ_INT_APCMDRESI failed\n");
ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO,
max77759_maxq_cells,
ARRAY_SIZE(max77759_maxq_cells),
NULL, 0,
regmap_irq_get_domain(irq_chip_data));
if (ret)
return dev_err_probe(&client->dev, ret,
"failed to add child devices (MaxQ)\n");
return 0;
}
static int max77759_add_chained_topsys(struct i2c_client *client,
struct max77759 *max77759,
struct regmap_irq_chip_data *parent)
{
struct regmap_irq_chip_data *irq_chip_data;
int ret;
ret = max77759_add_chained_irq_chip(&client->dev,
max77759->regmap_top,
MAX77759_INT_TOPSYS,
parent,
&max77759_topsys_irq_chip,
&irq_chip_data);
if (ret)
return ret;
return 0;
}
static int max77759_add_chained_charger(struct i2c_client *client,
struct max77759 *max77759,
struct regmap_irq_chip_data *parent)
{
struct regmap_irq_chip_data *irq_chip_data;
int ret;
ret = max77759_add_chained_irq_chip(&client->dev,
max77759->regmap_charger,
MAX77759_INT_CHGR,
parent,
&max77759_chrg_irq_chip,
&irq_chip_data);
if (ret)
return ret;
ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO,
max77759_charger_cells,
ARRAY_SIZE(max77759_charger_cells),
NULL, 0,
regmap_irq_get_domain(irq_chip_data));
if (ret)
return dev_err_probe(&client->dev, ret,
"failed to add child devices (charger)\n");
return 0;
}
static int max77759_probe(struct i2c_client *client)
{
struct regmap_irq_chip_data *irq_chip_data_pmic;
struct irq_data *irq_data;
struct max77759 *max77759;
unsigned long irq_flags;
unsigned int pmic_id;
int ret;
max77759 = devm_kzalloc(&client->dev, sizeof(*max77759), GFP_KERNEL);
if (!max77759)
return -ENOMEM;
i2c_set_clientdata(client, max77759);
max77759->regmap_top = devm_regmap_init_i2c(client,
&max77759_regmap_config_top);
if (IS_ERR(max77759->regmap_top))
return dev_err_probe(&client->dev, PTR_ERR(max77759->regmap_top),
"regmap init for '%s' failed\n",
max77759_regmap_config_top.name);
ret = regmap_read(max77759->regmap_top,
MAX77759_PMIC_REG_PMIC_ID, &pmic_id);
if (ret)
return dev_err_probe(&client->dev, ret,
"unable to read device ID\n");
if (pmic_id != MAX77759_CHIP_ID)
return dev_err_probe(&client->dev, -ENODEV,
"unsupported device ID %#.2x (%d)\n",
pmic_id, pmic_id);
ret = devm_mutex_init(&client->dev, &max77759->maxq_lock);
if (ret)
return ret;
for (int i = 0; i < ARRAY_SIZE(max77759_i2c_subdevs); i++) {
ret = max77759_create_i2c_subdev(client, max77759,
&max77759_i2c_subdevs[i]);
if (ret)
return ret;
}
irq_data = irq_get_irq_data(client->irq);
if (!irq_data)
return dev_err_probe(&client->dev, -EINVAL,
"invalid IRQ: %d\n", client->irq);
irq_flags = IRQF_ONESHOT | IRQF_SHARED;
irq_flags |= irqd_get_trigger_type(irq_data);
ret = devm_regmap_add_irq_chip(&client->dev, max77759->regmap_top,
client->irq, irq_flags, 0,
&max77759_pmic_irq_chip,
&irq_chip_data_pmic);
if (ret)
return dev_err_probe(&client->dev, ret,
"failed to add IRQ chip '%s'\n",
max77759_pmic_irq_chip.name);
ret = max77759_add_chained_maxq(client, max77759, irq_chip_data_pmic);
if (ret)
return ret;
ret = max77759_add_chained_topsys(client, max77759, irq_chip_data_pmic);
if (ret)
return ret;
ret = max77759_add_chained_charger(client, max77759, irq_chip_data_pmic);
if (ret)
return ret;
return devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO,
max77759_cells, ARRAY_SIZE(max77759_cells),
NULL, 0,
regmap_irq_get_domain(irq_chip_data_pmic));
}
static const struct i2c_device_id max77759_i2c_id[] = {
{ "max77759" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max77759_i2c_id);
static const struct of_device_id max77759_of_id[] = {
{ .compatible = "maxim,max77759", },
{ }
};
MODULE_DEVICE_TABLE(of, max77759_of_id);
static struct i2c_driver max77759_i2c_driver = {
.driver = {
.name = "max77759",
.of_match_table = max77759_of_id,
},
.probe = max77759_probe,
.id_table = max77759_i2c_id,
};
module_i2c_driver(max77759_i2c_driver);
MODULE_AUTHOR("André Draszik <andre.draszik@linaro.org>");
MODULE_DESCRIPTION("Maxim MAX77759 core driver");
MODULE_LICENSE("GPL");

View File

@@ -154,6 +154,18 @@ config NVMEM_LPC18XX_OTP
To compile this driver as a module, choose M here: the module
will be called nvmem_lpc18xx_otp.
config NVMEM_MAX77759
tristate "Maxim Integrated MAX77759 NVMEM Support"
depends on MFD_MAX77759
default MFD_MAX77759
help
Say Y here to include support for the user-accessible storage found
in Maxim Integrated MAX77759 PMICs. This IC provides space for 30
bytes of storage.
This driver can also be built as a module. If so, the module
will be called nvmem-max77759.
config NVMEM_MESON_EFUSE
tristate "Amlogic Meson GX eFuse Support"
depends on (ARCH_MESON || COMPILE_TEST) && MESON_SM

View File

@@ -34,6 +34,8 @@ obj-$(CONFIG_NVMEM_LPC18XX_EEPROM) += nvmem_lpc18xx_eeprom.o
nvmem_lpc18xx_eeprom-y := lpc18xx_eeprom.o
obj-$(CONFIG_NVMEM_LPC18XX_OTP) += nvmem_lpc18xx_otp.o
nvmem_lpc18xx_otp-y := lpc18xx_otp.o
obj-$(CONFIG_NVMEM_MAX77759) += nvmem-max77759.o
nvmem-max77759-y := max77759-nvmem.o
obj-$(CONFIG_NVMEM_MESON_EFUSE) += nvmem_meson_efuse.o
nvmem_meson_efuse-y := meson-efuse.o
obj-$(CONFIG_NVMEM_MESON_MX_EFUSE) += nvmem_meson_mx_efuse.o

View File

@@ -0,0 +1,145 @@
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright 2020 Google Inc
// Copyright 2025 Linaro Ltd.
//
// NVMEM driver for Maxim MAX77759
#include <linux/dev_printk.h>
#include <linux/device.h>
#include <linux/device/driver.h>
#include <linux/err.h>
#include <linux/mfd/max77759.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/nvmem-provider.h>
#include <linux/overflow.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#define MAX77759_NVMEM_OPCODE_HEADER_LEN 3
/*
* NVMEM commands have a three byte header (which becomes part of the command),
* so we need to subtract that.
*/
#define MAX77759_NVMEM_SIZE (MAX77759_MAXQ_OPCODE_MAXLENGTH \
- MAX77759_NVMEM_OPCODE_HEADER_LEN)
struct max77759_nvmem {
struct device *dev;
struct max77759 *max77759;
};
static int max77759_nvmem_reg_read(void *priv, unsigned int offset,
void *val, size_t bytes)
{
struct max77759_nvmem *nvmem = priv;
DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length,
MAX77759_NVMEM_OPCODE_HEADER_LEN);
DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length,
MAX77759_MAXQ_OPCODE_MAXLENGTH);
int ret;
cmd->cmd[0] = MAX77759_MAXQ_OPCODE_USER_SPACE_READ;
cmd->cmd[1] = offset;
cmd->cmd[2] = bytes;
rsp->length = bytes + MAX77759_NVMEM_OPCODE_HEADER_LEN;
ret = max77759_maxq_command(nvmem->max77759, cmd, rsp);
if (ret < 0)
return ret;
if (memcmp(cmd->cmd, rsp->rsp, MAX77759_NVMEM_OPCODE_HEADER_LEN)) {
dev_warn(nvmem->dev, "protocol error (read)\n");
return -EIO;
}
memcpy(val, &rsp->rsp[MAX77759_NVMEM_OPCODE_HEADER_LEN], bytes);
return 0;
}
static int max77759_nvmem_reg_write(void *priv, unsigned int offset,
void *val, size_t bytes)
{
struct max77759_nvmem *nvmem = priv;
DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length,
MAX77759_MAXQ_OPCODE_MAXLENGTH);
DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length,
MAX77759_MAXQ_OPCODE_MAXLENGTH);
int ret;
cmd->cmd[0] = MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE;
cmd->cmd[1] = offset;
cmd->cmd[2] = bytes;
memcpy(&cmd->cmd[MAX77759_NVMEM_OPCODE_HEADER_LEN], val, bytes);
cmd->length = bytes + MAX77759_NVMEM_OPCODE_HEADER_LEN;
rsp->length = cmd->length;
ret = max77759_maxq_command(nvmem->max77759, cmd, rsp);
if (ret < 0)
return ret;
if (memcmp(cmd->cmd, rsp->rsp, cmd->length)) {
dev_warn(nvmem->dev, "protocol error (write)\n");
return -EIO;
}
return 0;
}
static int max77759_nvmem_probe(struct platform_device *pdev)
{
struct nvmem_config config = {
.dev = &pdev->dev,
.name = dev_name(&pdev->dev),
.id = NVMEM_DEVID_NONE,
.type = NVMEM_TYPE_EEPROM,
.ignore_wp = true,
.size = MAX77759_NVMEM_SIZE,
.word_size = sizeof(u8),
.stride = sizeof(u8),
.reg_read = max77759_nvmem_reg_read,
.reg_write = max77759_nvmem_reg_write,
};
struct max77759_nvmem *nvmem;
nvmem = devm_kzalloc(&pdev->dev, sizeof(*nvmem), GFP_KERNEL);
if (!nvmem)
return -ENOMEM;
nvmem->dev = &pdev->dev;
nvmem->max77759 = dev_get_drvdata(pdev->dev.parent);
config.priv = nvmem;
return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config));
}
static const struct of_device_id max77759_nvmem_of_id[] = {
{ .compatible = "maxim,max77759-nvmem", },
{ }
};
MODULE_DEVICE_TABLE(of, max77759_nvmem_of_id);
static const struct platform_device_id max77759_nvmem_platform_id[] = {
{ "max77759-nvmem", },
{ }
};
MODULE_DEVICE_TABLE(platform, max77759_nvmem_platform_id);
static struct platform_driver max77759_nvmem_driver = {
.driver = {
.name = "max77759-nvmem",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
.of_match_table = max77759_nvmem_of_id,
},
.probe = max77759_nvmem_probe,
.id_table = max77759_nvmem_platform_id,
};
module_platform_driver(max77759_nvmem_driver);
MODULE_AUTHOR("André Draszik <andre.draszik@linaro.org>");
MODULE_DESCRIPTION("NVMEM driver for Maxim MAX77759");
MODULE_LICENSE("GPL");

View File

@@ -23,7 +23,7 @@
* ACPI mechanisms, this is not a real GPIO at all.
*
* This driver will bind to the INT0002 device, and register as a GPIO
* controller, letting gpiolib-acpi.c call the _L02 handler as it would
* controller, letting gpiolib-acpi call the _L02 handler as it would
* for a real GPIO controller.
*/

View File

@@ -281,44 +281,6 @@ int __must_check device_create_bin_file(struct device *dev,
void device_remove_bin_file(struct device *dev,
const struct bin_attribute *attr);
/* allows to add/remove a custom action to devres stack */
int devm_remove_action_nowarn(struct device *dev, void (*action)(void *), void *data);
/**
* devm_remove_action() - removes previously added custom action
* @dev: Device that owns the action
* @action: Function implementing the action
* @data: Pointer to data passed to @action implementation
*
* Removes instance of @action previously added by devm_add_action().
* Both action and data should match one of the existing entries.
*/
static inline
void devm_remove_action(struct device *dev, void (*action)(void *), void *data)
{
WARN_ON(devm_remove_action_nowarn(dev, action, data));
}
void devm_release_action(struct device *dev, void (*action)(void *), void *data);
int __devm_add_action(struct device *dev, void (*action)(void *), void *data, const char *name);
#define devm_add_action(dev, action, data) \
__devm_add_action(dev, action, data, #action)
static inline int __devm_add_action_or_reset(struct device *dev, void (*action)(void *),
void *data, const char *name)
{
int ret;
ret = __devm_add_action(dev, action, data, name);
if (ret)
action(data);
return ret;
}
#define devm_add_action_or_reset(dev, action, data) \
__devm_add_action_or_reset(dev, action, data, #action)
/**
* devm_alloc_percpu - Resource-managed alloc_percpu
* @dev: Device to allocate per-cpu memory for

View File

@@ -8,6 +8,7 @@
#include <linux/overflow.h>
#include <linux/stdarg.h>
#include <linux/types.h>
#include <asm/bug.h>
struct device;
struct device_node;
@@ -126,4 +127,44 @@ void __iomem *devm_of_iomap(struct device *dev, struct device_node *node, int in
#endif
/* allows to add/remove a custom action to devres stack */
int devm_remove_action_nowarn(struct device *dev, void (*action)(void *), void *data);
/**
* devm_remove_action() - removes previously added custom action
* @dev: Device that owns the action
* @action: Function implementing the action
* @data: Pointer to data passed to @action implementation
*
* Removes instance of @action previously added by devm_add_action().
* Both action and data should match one of the existing entries.
*/
static inline
void devm_remove_action(struct device *dev, void (*action)(void *), void *data)
{
WARN_ON(devm_remove_action_nowarn(dev, action, data));
}
void devm_release_action(struct device *dev, void (*action)(void *), void *data);
int __devm_add_action(struct device *dev, void (*action)(void *), void *data, const char *name);
#define devm_add_action(dev, action, data) \
__devm_add_action(dev, action, data, #action)
static inline int __devm_add_action_or_reset(struct device *dev, void (*action)(void *),
void *data, const char *name)
{
int ret;
ret = __devm_add_action(dev, action, data, name);
if (ret)
action(data);
return ret;
}
#define devm_add_action_or_reset(dev, action, data) \
__devm_add_action_or_reset(dev, action, data, #action)
bool devm_is_action_added(struct device *dev, void (*action)(void *), void *data);
#endif /* _DEVICE_DEVRES_H_ */

View File

@@ -181,6 +181,8 @@ struct gpio_desc *devm_fwnode_gpiod_get_index(struct device *dev,
enum gpiod_flags flags,
const char *label);
bool gpiod_is_equal(struct gpio_desc *desc, struct gpio_desc *other);
#else /* CONFIG_GPIOLIB */
#include <linux/bug.h>
@@ -548,6 +550,13 @@ struct gpio_desc *devm_fwnode_gpiod_get_index(struct device *dev,
return ERR_PTR(-ENOSYS);
}
static inline bool
gpiod_is_equal(struct gpio_desc *desc, struct gpio_desc *other)
{
WARN_ON(desc || other);
return false;
}
#endif /* CONFIG_GPIOLIB */
#if IS_ENABLED(CONFIG_GPIOLIB) && IS_ENABLED(CONFIG_HTE)
@@ -588,7 +597,7 @@ struct gpio_desc *devm_fwnode_gpiod_get(struct device *dev,
struct acpi_gpio_params {
unsigned int crs_entry_index;
unsigned int line_index;
unsigned short line_index;
bool active_low;
};

View File

@@ -0,0 +1,165 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2020 Google Inc.
* Copyright 2025 Linaro Ltd.
*
* Maxim MAX77759 core driver
*/
#ifndef __LINUX_MFD_MAX77759_H
#define __LINUX_MFD_MAX77759_H
#include <linux/completion.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#define MAX77759_PMIC_REG_PMIC_ID 0x00
#define MAX77759_PMIC_REG_PMIC_REVISION 0x01
#define MAX77759_PMIC_REG_OTP_REVISION 0x02
#define MAX77759_PMIC_REG_INTSRC 0x22
#define MAX77759_PMIC_REG_INTSRCMASK 0x23
#define MAX77759_PMIC_REG_INTSRC_MAXQ BIT(3)
#define MAX77759_PMIC_REG_INTSRC_TOPSYS BIT(1)
#define MAX77759_PMIC_REG_INTSRC_CHGR BIT(0)
#define MAX77759_PMIC_REG_TOPSYS_INT 0x24
#define MAX77759_PMIC_REG_TOPSYS_INT_MASK 0x26
#define MAX77759_PMIC_REG_TOPSYS_INT_TSHDN BIT(6)
#define MAX77759_PMIC_REG_TOPSYS_INT_SYSOVLO BIT(5)
#define MAX77759_PMIC_REG_TOPSYS_INT_SYSUVLO BIT(4)
#define MAX77759_PMIC_REG_TOPSYS_INT_FSHIP BIT(0)
#define MAX77759_PMIC_REG_I2C_CNFG 0x40
#define MAX77759_PMIC_REG_SWRESET 0x50
#define MAX77759_PMIC_REG_CONTROL_FG 0x51
#define MAX77759_MAXQ_REG_UIC_INT1 0x64
#define MAX77759_MAXQ_REG_UIC_INT1_APCMDRESI BIT(7)
#define MAX77759_MAXQ_REG_UIC_INT1_SYSMSGI BIT(6)
#define MAX77759_MAXQ_REG_UIC_INT1_GPIO6I BIT(1)
#define MAX77759_MAXQ_REG_UIC_INT1_GPIO5I BIT(0)
#define MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(offs, en) (((en) & 1) << (offs))
#define MAX77759_MAXQ_REG_UIC_INT1_GPIOxI_MASK(offs) \
MAX77759_MAXQ_REG_UIC_INT1_GPIOxI(offs, ~0)
#define MAX77759_MAXQ_REG_UIC_INT2 0x65
#define MAX77759_MAXQ_REG_UIC_INT3 0x66
#define MAX77759_MAXQ_REG_UIC_INT4 0x67
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS1 0x68
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS2 0x69
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS3 0x6a
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS4 0x6b
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS5 0x6c
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS6 0x6d
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS7 0x6f
#define MAX77759_MAXQ_REG_UIC_UIC_STATUS8 0x6f
#define MAX77759_MAXQ_REG_UIC_INT1_M 0x70
#define MAX77759_MAXQ_REG_UIC_INT2_M 0x71
#define MAX77759_MAXQ_REG_UIC_INT3_M 0x72
#define MAX77759_MAXQ_REG_UIC_INT4_M 0x73
#define MAX77759_MAXQ_REG_AP_DATAOUT0 0x81
#define MAX77759_MAXQ_REG_AP_DATAOUT32 0xa1
#define MAX77759_MAXQ_REG_AP_DATAIN0 0xb1
#define MAX77759_MAXQ_REG_UIC_SWRST 0xe0
#define MAX77759_CHGR_REG_CHG_INT 0xb0
#define MAX77759_CHGR_REG_CHG_INT2 0xb1
#define MAX77759_CHGR_REG_CHG_INT_MASK 0xb2
#define MAX77759_CHGR_REG_CHG_INT2_MASK 0xb3
#define MAX77759_CHGR_REG_CHG_INT_OK 0xb4
#define MAX77759_CHGR_REG_CHG_DETAILS_00 0xb5
#define MAX77759_CHGR_REG_CHG_DETAILS_01 0xb6
#define MAX77759_CHGR_REG_CHG_DETAILS_02 0xb7
#define MAX77759_CHGR_REG_CHG_DETAILS_03 0xb8
#define MAX77759_CHGR_REG_CHG_CNFG_00 0xb9
#define MAX77759_CHGR_REG_CHG_CNFG_01 0xba
#define MAX77759_CHGR_REG_CHG_CNFG_02 0xbb
#define MAX77759_CHGR_REG_CHG_CNFG_03 0xbc
#define MAX77759_CHGR_REG_CHG_CNFG_04 0xbd
#define MAX77759_CHGR_REG_CHG_CNFG_05 0xbe
#define MAX77759_CHGR_REG_CHG_CNFG_06 0xbf
#define MAX77759_CHGR_REG_CHG_CNFG_07 0xc0
#define MAX77759_CHGR_REG_CHG_CNFG_08 0xc1
#define MAX77759_CHGR_REG_CHG_CNFG_09 0xc2
#define MAX77759_CHGR_REG_CHG_CNFG_10 0xc3
#define MAX77759_CHGR_REG_CHG_CNFG_11 0xc4
#define MAX77759_CHGR_REG_CHG_CNFG_12 0xc5
#define MAX77759_CHGR_REG_CHG_CNFG_13 0xc6
#define MAX77759_CHGR_REG_CHG_CNFG_14 0xc7
#define MAX77759_CHGR_REG_CHG_CNFG_15 0xc8
#define MAX77759_CHGR_REG_CHG_CNFG_16 0xc9
#define MAX77759_CHGR_REG_CHG_CNFG_17 0xca
#define MAX77759_CHGR_REG_CHG_CNFG_18 0xcb
#define MAX77759_CHGR_REG_CHG_CNFG_19 0xcc
/* MaxQ opcodes for max77759_maxq_command() */
#define MAX77759_MAXQ_OPCODE_MAXLENGTH (MAX77759_MAXQ_REG_AP_DATAOUT32 - \
MAX77759_MAXQ_REG_AP_DATAOUT0 + \
1)
#define MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_READ 0x21
#define MAX77759_MAXQ_OPCODE_GPIO_TRIGGER_WRITE 0x22
#define MAX77759_MAXQ_OPCODE_GPIO_CONTROL_READ 0x23
#define MAX77759_MAXQ_OPCODE_GPIO_CONTROL_WRITE 0x24
#define MAX77759_MAXQ_OPCODE_USER_SPACE_READ 0x81
#define MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE 0x82
/**
* struct max77759 - core max77759 internal data structure
*
* @regmap_top: Regmap for accessing TOP registers
* @maxq_lock: Lock for serializing access to MaxQ
* @regmap_maxq: Regmap for accessing MaxQ registers
* @cmd_done: Used to signal completion of a MaxQ command
* @regmap_charger: Regmap for accessing charger registers
*
* The MAX77759 comprises several sub-blocks, namely TOP, MaxQ, Charger,
* Fuel Gauge, and TCPCI.
*/
struct max77759 {
struct regmap *regmap_top;
/* This protects MaxQ commands - only one can be active */
struct mutex maxq_lock;
struct regmap *regmap_maxq;
struct completion cmd_done;
struct regmap *regmap_charger;
};
/**
* struct max77759_maxq_command - structure containing the MaxQ command to
* send
*
* @length: The number of bytes to send.
* @cmd: The data to send.
*/
struct max77759_maxq_command {
u8 length;
u8 cmd[] __counted_by(length);
};
/**
* struct max77759_maxq_response - structure containing the MaxQ response
*
* @length: The number of bytes to receive.
* @rsp: The data received. Must have at least @length bytes space.
*/
struct max77759_maxq_response {
u8 length;
u8 rsp[] __counted_by(length);
};
/**
* max77759_maxq_command() - issue a MaxQ command and wait for the response
* and associated data
*
* @max77759: The core max77759 device handle.
* @cmd: The command to be sent.
* @rsp: Any response data associated with the command will be copied here;
* can be %NULL if the command has no response (other than ACK).
*
* Return: 0 on success, a negative error number otherwise.
*/
int max77759_maxq_command(struct max77759 *max77759,
const struct max77759_maxq_command *cmd,
struct max77759_maxq_response *rsp);
#endif /* __LINUX_MFD_MAX77759_H */

View File

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

View File

@@ -2,3 +2,4 @@ CONFIG_GPIOLIB=y
CONFIG_GPIO_CDEV=y
CONFIG_GPIO_MOCKUP=m
CONFIG_GPIO_SIM=m
CONFIG_GPIO_AGGREGATOR=m

View File

@@ -0,0 +1,727 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2025 Bartosz Golaszewski <brgl@bgdev.pl>
# Copyright (C) 2025 Koichiro Den <koichiro.den@canonical.com>
BASE_DIR=$(dirname "$0")
CONFIGFS_SIM_DIR="/sys/kernel/config/gpio-sim"
CONFIGFS_AGG_DIR="/sys/kernel/config/gpio-aggregator"
SYSFS_AGG_DIR="/sys/bus/platform/drivers/gpio-aggregator"
MODULE="gpio-aggregator"
fail() {
echo "$*" >&2
echo "GPIO $MODULE test FAIL"
exit 1
}
skip() {
echo "$*" >&2
echo "GPIO $MODULE test SKIP"
exit 4
}
# gpio-sim
sim_enable_chip() {
local CHIP=$1
echo 1 > "$CONFIGFS_SIM_DIR/$CHIP/live" || fail "Unable to enable the chip"
}
sim_disable_chip() {
local CHIP=$1
echo 0 > "$CONFIGFS_SIM_DIR/$CHIP/live" || fail "Unable to disable the chip"
}
sim_configfs_cleanup() {
local NOCHECK=${1:-0}
for CHIP_DIR in "$CONFIGFS_SIM_DIR"/*; do
[ -d "$CHIP_DIR" ] || continue
echo 0 > "$CHIP_DIR/live"
find "$CHIP_DIR" -depth -type d -exec rmdir {} \;
done
[ "$NOCHECK" -eq 1 ] && return;
remaining=$(find "$CONFIGFS_SIM_DIR" -mindepth 1 -type d 2> /dev/null)
if [ -n "$remaining" ]; then
fail "Directories remain in $CONFIGFS_SIM_DIR: $remaining"
fi
}
sim_get_chip_label() {
local CHIP=$1
local BANK=$2
local CHIP_NAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/$BANK/chip_name" 2> /dev/null) || \
fail "Unable to read the chip name from configfs"
$BASE_DIR/gpio-chip-info "/dev/$CHIP_NAME" label || \
fail "Unable to read the chip label from the character device"
}
# gpio-aggregator
agg_create_chip() {
local CHIP=$1
mkdir "$CONFIGFS_AGG_DIR/$CHIP"
}
agg_remove_chip() {
local CHIP=$1
find "$CONFIGFS_AGG_DIR/$CHIP/" -depth -type d -exec rmdir {} \; || \
fail "Unable to remove $CONFIGFS_AGG_DIR/$CHIP"
}
agg_create_line() {
local CHIP=$1
local LINE=$2
mkdir "$CONFIGFS_AGG_DIR/$CHIP/$LINE"
}
agg_remove_line() {
local CHIP=$1
local LINE=$2
rmdir "$CONFIGFS_AGG_DIR/$CHIP/$LINE"
}
agg_set_key() {
local CHIP=$1
local LINE=$2
local KEY=$3
echo "$KEY" > "$CONFIGFS_AGG_DIR/$CHIP/$LINE/key" || fail "Unable to set the lookup key"
}
agg_set_offset() {
local CHIP=$1
local LINE=$2
local OFFSET=$3
echo "$OFFSET" > "$CONFIGFS_AGG_DIR/$CHIP/$LINE/offset" || \
fail "Unable to set the lookup offset"
}
agg_set_line_name() {
local CHIP=$1
local LINE=$2
local NAME=$3
echo "$NAME" > "$CONFIGFS_AGG_DIR/$CHIP/$LINE/name" || fail "Unable to set the line name"
}
agg_enable_chip() {
local CHIP=$1
echo 1 > "$CONFIGFS_AGG_DIR/$CHIP/live" || fail "Unable to enable the chip"
}
agg_disable_chip() {
local CHIP=$1
echo 0 > "$CONFIGFS_AGG_DIR/$CHIP/live" || fail "Unable to disable the chip"
}
agg_configfs_cleanup() {
local NOCHECK=${1:-0}
for CHIP_DIR in "$CONFIGFS_AGG_DIR"/*; do
[ -d "$CHIP_DIR" ] || continue
echo 0 > "$CHIP_DIR/live" 2> /dev/null
find "$CHIP_DIR" -depth -type d -exec rmdir {} \;
done
[ "$NOCHECK" -eq 1 ] && return;
remaining=$(find "$CONFIGFS_AGG_DIR" -mindepth 1 -type d 2> /dev/null)
if [ -n "$remaining" ]; then
fail "Directories remain in $CONFIGFS_AGG_DIR: $remaining"
fi
}
agg_configfs_dev_name() {
local CHIP=$1
cat "$CONFIGFS_AGG_DIR/$CHIP/dev_name" 2> /dev/null || \
fail "Unable to read the device name from configfs"
}
agg_configfs_chip_name() {
local CHIP=$1
local DEV_NAME=$(agg_configfs_dev_name "$CHIP")
local CHIP_LIST=$(find "/sys/devices/platform/$DEV_NAME" \
-maxdepth 1 -type d -name "gpiochip[0-9]*" 2> /dev/null)
local CHIP_COUNT=$(echo "$CHIP_LIST" | wc -l)
if [ -z "$CHIP_LIST" ]; then
fail "No gpiochip in /sys/devices/platform/$DEV_NAME/"
elif [ "$CHIP_COUNT" -ne 1 ]; then
fail "Multiple gpiochips unexpectedly found: $CHIP_LIST"
fi
basename "$CHIP_LIST"
}
agg_get_chip_num_lines() {
local CHIP=$1
local N_DIR=$(ls -d $CONFIGFS_AGG_DIR/$CHIP/line[0-9]* 2> /dev/null | wc -l)
local N_LINES
if [ "$(cat $CONFIGFS_AGG_DIR/$CHIP/live)" = 0 ]; then
echo "$N_DIR"
else
N_LINES=$(
$BASE_DIR/gpio-chip-info \
"/dev/$(agg_configfs_chip_name "$CHIP")" num-lines
) || fail "Unable to read the number of lines from the character device"
if [ $N_DIR != $N_LINES ]; then
fail "Discrepancy between two sources for the number of lines"
fi
echo "$N_LINES"
fi
}
agg_get_chip_label() {
local CHIP=$1
$BASE_DIR/gpio-chip-info "/dev/$(agg_configfs_chip_name "$CHIP")" label || \
fail "Unable to read the chip label from the character device"
}
agg_get_line_name() {
local CHIP=$1
local OFFSET=$2
local NAME_CONFIGFS=$(cat "$CONFIGFS_AGG_DIR/$CHIP/line${OFFSET}/name")
local NAME_CDEV
if [ "$(cat "$CONFIGFS_AGG_DIR/$CHIP/live")" = 0 ]; then
echo "$NAME_CONFIGFS"
else
NAME_CDEV=$(
$BASE_DIR/gpio-line-name \
"/dev/$(agg_configfs_chip_name "$CHIP")" "$OFFSET"
) || fail "Unable to read the line name from the character device"
if [ "$NAME_CONFIGFS" != "$NAME_CDEV" ]; then
fail "Discrepancy between two sources for the name of line"
fi
echo "$NAME_CDEV"
fi
}
# Load the modules. This will pull in configfs if needed too.
modprobe gpio-sim || skip "unable to load the gpio-sim module"
modprobe gpio-aggregator || skip "unable to load the gpio-aggregator module"
# Make sure configfs is mounted at /sys/kernel/config. Wait a bit if needed.
for IDX in $(seq 5); do
if [ "$IDX" -eq "5" ]; then
skip "configfs not mounted at /sys/kernel/config"
fi
mountpoint -q /sys/kernel/config && break
sleep 0.1
done
# If the module was already loaded: remove all previous chips
agg_configfs_cleanup
sim_configfs_cleanup
trap "exit 1" SIGTERM SIGINT
trap "agg_configfs_cleanup 1; sim_configfs_cleanup 1" EXIT
# Use gpio-sim chips as the test backend
for CHIP in $(seq -f "chip%g" 0 1); do
mkdir $CONFIGFS_SIM_DIR/$CHIP
for BANK in $(seq -f "bank%g" 0 1); do
mkdir -p "$CONFIGFS_SIM_DIR/$CHIP/$BANK"
echo "${CHIP}_${BANK}" > "$CONFIGFS_SIM_DIR/$CHIP/$BANK/label" || \
fail "unable to set the chip label"
echo 16 > "$CONFIGFS_SIM_DIR/$CHIP/$BANK/num_lines" || \
fail "unable to set the number of lines"
for IDX in $(seq 0 15); do
LINE_NAME="${CHIP}${BANK}_${IDX}"
LINE_DIR="$CONFIGFS_SIM_DIR/$CHIP/$BANK/line$IDX"
mkdir -p $LINE_DIR
echo "$LINE_NAME" > "$LINE_DIR/name" || fail "unable to set the line name"
done
done
sim_enable_chip "$CHIP"
done
echo "1. GPIO aggregator creation/deletion"
echo "1.1. Creation/deletion via configfs"
echo "1.1.1. Minimum creation/deletion"
agg_create_chip agg0
agg_create_line agg0 line0
agg_set_key agg0 line0 "$(sim_get_chip_label chip0 bank0)"
agg_set_offset agg0 line0 5
agg_set_line_name agg0 line0 test0
agg_enable_chip agg0
test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 1 || fail "chip unexpectedly dead"
test "$(agg_get_chip_label agg0)" = "$(agg_configfs_dev_name agg0)" || \
fail "label is inconsistent"
test "$(agg_get_chip_num_lines agg0)" = "1" || fail "number of lines is not 1"
test "$(agg_get_line_name agg0 0)" = "test0" || fail "line name is unset"
agg_disable_chip agg0
agg_remove_line agg0 line0
agg_remove_chip agg0
echo "1.1.2. Complex creation/deletion"
agg_create_chip agg0
agg_create_line agg0 line0
agg_create_line agg0 line1
agg_create_line agg0 line2
agg_create_line agg0 line3
agg_set_key agg0 line0 "$(sim_get_chip_label chip0 bank0)"
agg_set_key agg0 line1 "$(sim_get_chip_label chip0 bank1)"
agg_set_key agg0 line2 "$(sim_get_chip_label chip1 bank0)"
agg_set_key agg0 line3 "$(sim_get_chip_label chip1 bank1)"
agg_set_offset agg0 line0 1
agg_set_offset agg0 line1 3
agg_set_offset agg0 line2 5
agg_set_offset agg0 line3 7
agg_set_line_name agg0 line0 test0
agg_set_line_name agg0 line1 test1
agg_set_line_name agg0 line2 test2
agg_set_line_name agg0 line3 test3
agg_enable_chip agg0
test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 1 || fail "chip unexpectedly dead"
test "$(agg_get_chip_label agg0)" = "$(agg_configfs_dev_name agg0)" || \
fail "label is inconsistent"
test "$(agg_get_chip_num_lines agg0)" = "4" || fail "number of lines is not 1"
test "$(agg_get_line_name agg0 0)" = "test0" || fail "line name is unset"
test "$(agg_get_line_name agg0 1)" = "test1" || fail "line name is unset"
test "$(agg_get_line_name agg0 2)" = "test2" || fail "line name is unset"
test "$(agg_get_line_name agg0 3)" = "test3" || fail "line name is unset"
agg_disable_chip agg0
agg_remove_line agg0 line0
agg_remove_line agg0 line1
agg_remove_line agg0 line2
agg_remove_line agg0 line3
agg_remove_chip agg0
echo "1.1.3. Can't instantiate a chip without any line"
agg_create_chip agg0
echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled"
test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || fail "chip unexpectedly alive"
agg_remove_chip agg0
echo "1.1.4. Can't instantiate a chip with invalid configuration"
agg_create_chip agg0
agg_create_line agg0 line0
agg_set_key agg0 line0 "chipX_bankX"
agg_set_offset agg0 line0 99
agg_set_line_name agg0 line0 test0
echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled"
test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || fail "chip unexpectedly alive"
agg_remove_line agg0 line0
agg_remove_chip agg0
echo "1.1.5. Can't instantiate a chip asynchronously via deferred probe"
agg_create_chip agg0
agg_create_line agg0 line0
agg_set_key agg0 line0 "chip0_bank0"
agg_set_offset agg0 line0 5
agg_set_line_name agg0 line0 test0
sim_disable_chip chip0
echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled"
test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || fail "chip unexpectedly alive"
sim_enable_chip chip0
sleep 1
test "$(cat "$CONFIGFS_AGG_DIR/agg0/live")" = 0 || \
fail "chip unexpectedly transitioned to 'live' state"
agg_remove_line agg0 line0
agg_remove_chip agg0
echo "1.1.6. Can't instantiate a chip with _sysfs prefix"
mkdir "$CONFIGFS_AGG_DIR/_sysfs" 2> /dev/null && fail "chip _sysfs unexpectedly created"
mkdir "$CONFIGFS_AGG_DIR/_sysfs.foo" 2> /dev/null && fail "chip _sysfs.foo unexpectedly created"
echo "1.2. Creation/deletion via sysfs"
echo "1.2.1. Minimum creation/deletion"
echo "chip0_bank0 0" > "$SYSFS_AGG_DIR/new_device"
CHIPNAME=$(agg_configfs_chip_name _sysfs.0)
test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 1 || fail "chip unexpectedly dead"
test "$(agg_get_chip_label _sysfs.0)" = "$(agg_configfs_dev_name _sysfs.0)" || \
fail "label is inconsistent"
test "$(agg_get_chip_num_lines _sysfs.0)" = "1" || fail "number of lines is not 1"
test "$(agg_get_line_name _sysfs.0 0)" = "" || fail "line name is unset"
echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device"
test -d $CONFIGFS_AGG_DIR/_sysfs.0 && fail "_sysfs.0 unexpectedly remains"
test -d /dev/${CHIPNAME} && fail "/dev/${CHIPNAME} unexpectedly remains"
echo "1.2.2. Complex creation/deletion"
echo "chip0bank0_0 chip1_bank1 10-11" > "$SYSFS_AGG_DIR/new_device"
CHIPNAME=$(agg_configfs_chip_name _sysfs.0)
test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 1 || fail "chip unexpectedly dead"
test "$(agg_get_chip_label _sysfs.0)" = "$(agg_configfs_dev_name _sysfs.0)" || \
fail "label is inconsistent"
test "$(agg_get_chip_num_lines _sysfs.0)" = "3" || fail "number of lines is not 3"
test "$(agg_get_line_name _sysfs.0 0)" = "" || fail "line name is unset"
test "$(agg_get_line_name _sysfs.0 1)" = "" || fail "line name is unset"
test "$(agg_get_line_name _sysfs.0 2)" = "" || fail "line name is unset"
echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device"
test -d $CONFIGFS_AGG_DIR/_sysfs.0 && fail "_sysfs.0 unexpectedly remains"
test -d /dev/${CHIPNAME} && fail "/dev/${CHIPNAME} unexpectedly remains"
echo "1.2.3. Asynchronous creation with deferred probe"
sim_disable_chip chip0
echo 'chip0_bank0 0' > $SYSFS_AGG_DIR/new_device
sleep 1
test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 0 || fail "chip unexpectedly alive"
sim_enable_chip chip0
sleep 1
CHIPNAME=$(agg_configfs_chip_name _sysfs.0)
test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 1 || fail "chip unexpectedly remains dead"
test "$(agg_get_chip_label _sysfs.0)" = "$(agg_configfs_dev_name _sysfs.0)" || \
fail "label is inconsistent"
test "$(agg_get_chip_num_lines _sysfs.0)" = "1" || fail "number of lines is not 1"
test "$(agg_get_line_name _sysfs.0 0)" = "" || fail "line name unexpectedly set"
echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device"
test -d $CONFIGFS_AGG_DIR/_sysfs.0 && fail "_sysfs.0 unexpectedly remains"
test -d /dev/${CHIPNAME} && fail "/dev/${CHIPNAME} unexpectedly remains"
echo "1.2.4. Can't instantiate a chip with invalid configuration"
echo "xyz 0" > "$SYSFS_AGG_DIR/new_device"
test "$(cat $CONFIGFS_AGG_DIR/_sysfs.0/live)" = 0 || fail "chip unexpectedly alive"
echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device"
echo "2. GPIO aggregator configuration"
echo "2.1. Configuring aggregators instantiated via configfs"
setup_2_1() {
agg_create_chip agg0
agg_create_line agg0 line0
agg_create_line agg0 line1
agg_set_key agg0 line0 "$(sim_get_chip_label chip0 bank0)"
agg_set_key agg0 line1 "$(sim_get_chip_label chip1 bank0)"
agg_set_offset agg0 line0 1
agg_set_offset agg0 line1 3
agg_set_line_name agg0 line0 test0
agg_set_line_name agg0 line1 test1
agg_enable_chip agg0
}
teardown_2_1() {
agg_configfs_cleanup
}
echo "2.1.1. While offline"
echo "2.1.1.1. Line can be added/removed"
setup_2_1
agg_disable_chip agg0
agg_create_line agg0 line2
agg_set_key agg0 line2 "$(sim_get_chip_label chip0 bank1)"
agg_set_offset agg0 line2 5
agg_enable_chip agg0
test "$(agg_get_chip_num_lines agg0)" = "3" || fail "number of lines is not 1"
teardown_2_1
echo "2.1.1.2. Line key can be modified"
setup_2_1
agg_disable_chip agg0
agg_set_key agg0 line0 "$(sim_get_chip_label chip0 bank1)"
agg_set_key agg0 line1 "$(sim_get_chip_label chip1 bank1)"
agg_enable_chip agg0
teardown_2_1
echo "2.1.1.3. Line name can be modified"
setup_2_1
agg_disable_chip agg0
agg_set_line_name agg0 line0 new0
agg_set_line_name agg0 line1 new1
agg_enable_chip agg0
test "$(agg_get_line_name agg0 0)" = "new0" || fail "line name is unset"
test "$(agg_get_line_name agg0 1)" = "new1" || fail "line name is unset"
teardown_2_1
echo "2.1.1.4. Line offset can be modified"
setup_2_1
agg_disable_chip agg0
agg_set_offset agg0 line0 5
agg_set_offset agg0 line1 7
agg_enable_chip agg0
teardown_2_1
echo "2.1.1.5. Can re-enable a chip after valid reconfiguration"
setup_2_1
agg_disable_chip agg0
agg_set_key agg0 line0 "$(sim_get_chip_label chip1 bank1)"
agg_set_offset agg0 line0 15
agg_set_key agg0 line1 "$(sim_get_chip_label chip0 bank1)"
agg_set_offset agg0 line0 14
agg_create_line agg0 line2
agg_set_key agg0 line2 "$(sim_get_chip_label chip0 bank1)"
agg_set_offset agg0 line2 13
agg_enable_chip agg0
test "$(agg_get_chip_num_lines agg0)" = "3" || fail "number of lines is not 1"
teardown_2_1
echo "2.1.1.7. Can't re-enable a chip with invalid reconfiguration"
setup_2_1
agg_disable_chip agg0
agg_set_key agg0 line0 invalidkey
echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled"
teardown_2_1
setup_2_1
agg_disable_chip agg0
agg_set_offset agg0 line0 99
echo 1 > "$CONFIGFS_AGG_DIR/agg0/live" 2> /dev/null && fail "chip unexpectedly enabled"
teardown_2_1
echo "2.1.2. While online"
echo "2.1.2.1. Can't add/remove line"
setup_2_1
mkdir "$CONFIGFS_AGG_DIR/agg0/line2" 2> /dev/null && fail "line unexpectedly added"
rmdir "$CONFIGFS_AGG_DIR/agg0/line1" 2> /dev/null && fail "line unexpectedly removed"
teardown_2_1
echo "2.1.2.2. Can't modify line key"
setup_2_1
echo "chip1_bank1" > "$CONFIGFS_AGG_DIR/agg0/line0/key" 2> /dev/null && \
fail "lookup key unexpectedly updated"
teardown_2_1
echo "2.1.2.3. Can't modify line name"
setup_2_1
echo "new0" > "$CONFIGFS_AGG_DIR/agg0/line0/name" 2> /dev/null && \
fail "name unexpectedly updated"
teardown_2_1
echo "2.1.2.4. Can't modify line offset"
setup_2_1
echo "5" > "$CONFIGFS_AGG_DIR/agg0/line0/offset" 2> /dev/null && \
fail "offset unexpectedly updated"
teardown_2_1
echo "2.2. Configuring aggregators instantiated via sysfs"
setup_2_2() {
echo "chip0_bank0 1 chip1_bank0 3" > "$SYSFS_AGG_DIR/new_device"
}
teardown_2_2() {
echo "$(agg_configfs_dev_name _sysfs.0)" > "$SYSFS_AGG_DIR/delete_device"
}
echo "2.2.1. While online"
echo "2.2.1.1. Can toggle live"
setup_2_2
agg_disable_chip _sysfs.0
agg_enable_chip _sysfs.0
teardown_2_2
echo "2.2.1.2. Can't add/remove line"
setup_2_2
mkdir "$CONFIGFS_AGG_DIR/_sysfs.0/line2" 2> /dev/null && fail "line unexpectedly added"
rmdir "$CONFIGFS_AGG_DIR/_sysfs.0/line1" 2> /dev/null && fail "line unexpectedly removed"
teardown_2_2
echo "2.2.1.3. Can't modify line key"
setup_2_2
echo "chip1_bank1" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/key" 2> /dev/null && \
fail "lookup key unexpectedly updated"
teardown_2_2
echo "2.2.1.4. Can't modify line name"
setup_2_2
echo "new0" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/name" 2> /dev/null && \
fail "name unexpectedly updated"
teardown_2_2
echo "2.2.1.5. Can't modify line offset"
setup_2_2
echo "5" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/offset" 2> /dev/null && \
fail "offset unexpectedly updated"
teardown_2_2
echo "2.2.2. While waiting for deferred probe"
echo "2.2.2.1. Can't add/remove line despite live = 0"
sim_disable_chip chip0
setup_2_2
mkdir "$CONFIGFS_AGG_DIR/_sysfs.0/line2" 2> /dev/null && fail "line unexpectedly added"
rmdir "$CONFIGFS_AGG_DIR/_sysfs.0/line1" 2> /dev/null && fail "line unexpectedly removed"
teardown_2_2
sim_enable_chip chip0
echo "2.2.2.2. Can't modify line key"
sim_disable_chip chip0
setup_2_2
echo "chip1_bank1" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/key" 2> /dev/null && \
fail "lookup key unexpectedly updated"
teardown_2_2
sim_enable_chip chip0
echo "2.2.2.3. Can't modify line name"
sim_disable_chip chip0
setup_2_2
echo "new0" > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/name" 2> /dev/null && \
fail "name unexpectedly updated"
teardown_2_2
sim_enable_chip chip0
echo "2.2.2.4. Can't modify line offset"
sim_disable_chip chip0
setup_2_2
echo 5 > "$CONFIGFS_AGG_DIR/_sysfs.0/line0/offset" 2> /dev/null && \
fail "offset unexpectedly updated"
teardown_2_2
sim_enable_chip chip0
echo "2.2.2.5. Can't toggle live"
sim_disable_chip chip0
setup_2_2
test "$(cat "$CONFIGFS_AGG_DIR/_sysfs.0/live")" = 0 || fail "chip unexpectedly alive"
echo 1 > "$CONFIGFS_AGG_DIR/_sysfs.0/live" 2> /dev/null && fail "chip unexpectedly enabled"
teardown_2_2
sim_enable_chip chip0
echo "2.2.3. While offline"
echo "2.2.3.1. Can't add/remove line despite live = 0"
setup_2_2
agg_disable_chip _sysfs.0
mkdir "$CONFIGFS_AGG_DIR/_sysfs.0/line2" 2> /dev/null && fail "line unexpectedly added"
rmdir "$CONFIGFS_AGG_DIR/_sysfs.0/line1" 2> /dev/null && fail "line unexpectedly removed"
teardown_2_2
echo "2.2.3.2. Line key can be modified"
setup_2_2
agg_disable_chip _sysfs.0
agg_set_key _sysfs.0 line0 "$(sim_get_chip_label chip0 bank1)"
agg_set_key _sysfs.0 line1 "$(sim_get_chip_label chip1 bank1)"
agg_enable_chip _sysfs.0
teardown_2_2
echo "2.2.3.3. Line name can be modified"
setup_2_2
agg_disable_chip _sysfs.0
agg_set_line_name _sysfs.0 line0 new0
agg_set_line_name _sysfs.0 line1 new1
agg_enable_chip _sysfs.0
test "$(agg_get_line_name _sysfs.0 0)" = "new0" || fail "line name is unset"
test "$(agg_get_line_name _sysfs.0 1)" = "new1" || fail "line name is unset"
teardown_2_2
echo "2.2.3.4. Line offset can be modified"
setup_2_2
agg_disable_chip _sysfs.0
agg_set_offset _sysfs.0 line0 5
agg_set_offset _sysfs.0 line1 7
agg_enable_chip _sysfs.0
teardown_2_2
echo "2.2.3.5. Can re-enable a chip with valid reconfiguration"
setup_2_2
agg_disable_chip _sysfs.0
agg_set_key _sysfs.0 line0 "$(sim_get_chip_label chip1 bank1)"
agg_set_offset _sysfs.0 line0 15
agg_set_key _sysfs.0 line1 "$(sim_get_chip_label chip0 bank1)"
agg_set_offset _sysfs.0 line0 14
agg_enable_chip _sysfs.0
teardown_2_2
echo "2.2.3.6. Can't re-enable a chip with invalid reconfiguration"
setup_2_2
agg_disable_chip _sysfs.0
agg_set_key _sysfs.0 line0 invalidkey
echo 1 > "$CONFIGFS_AGG_DIR/_sysfs.0/live" 2> /dev/null && fail "chip unexpectedly enabled"
teardown_2_2
setup_2_2
agg_disable_chip _sysfs.0
agg_set_offset _sysfs.0 line0 99
echo 1 > "$CONFIGFS_AGG_DIR/_sysfs.0/live" 2> /dev/null && fail "chip unexpectedly enabled"
teardown_2_2
echo "3. Module unload"
echo "3.1. Can't unload module if there is at least one device created via configfs"
agg_create_chip agg0
modprobe -r gpio-aggregator 2> /dev/null
test -d /sys/module/gpio_aggregator || fail "module unexpectedly unloaded"
agg_remove_chip agg0
echo "3.2. Can unload module if there is no device created via configfs"
echo "chip0_bank0 1 chip1_bank0 3" > "$SYSFS_AGG_DIR/new_device"
modprobe -r gpio-aggregator 2> /dev/null
test -d /sys/module/gpio_aggregator && fail "module unexpectedly remains to be loaded"
modprobe gpio-aggregator 2> /dev/null
echo "4. GPIO forwarder functional"
SETTINGS="chip0:bank0:2 chip0:bank1:4 chip1:bank0:6 chip1:bank1:8"
setup_4() {
local OFFSET=0
agg_create_chip agg0
for SETTING in $SETTINGS; do
CHIP=$(echo "$SETTING" | cut -d: -f1)
BANK=$(echo "$SETTING" | cut -d: -f2)
LINE=$(echo "$SETTING" | cut -d: -f3)
agg_create_line agg0 "line${OFFSET}"
agg_set_key agg0 "line${OFFSET}" "$(sim_get_chip_label "$CHIP" "$BANK")"
agg_set_offset agg0 "line${OFFSET}" "$LINE"
OFFSET=$(expr $OFFSET + 1)
done
agg_enable_chip agg0
}
teardown_4() {
agg_configfs_cleanup
}
echo "4.1. Forwarding set values"
setup_4
OFFSET=0
for SETTING in $SETTINGS; do
CHIP=$(echo "$SETTING" | cut -d: -f1)
BANK=$(echo "$SETTING" | cut -d: -f2)
LINE=$(echo "$SETTING" | cut -d: -f3)
DEVNAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/dev_name")
CHIPNAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/$BANK/chip_name")
VAL_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio${LINE}/value"
test $(cat $VAL_PATH) = "0" || fail "incorrect value read from sysfs"
$BASE_DIR/gpio-mockup-cdev -s 1 "/dev/$(agg_configfs_chip_name agg0)" "$OFFSET" &
mock_pid=$!
sleep 0.1 # FIXME Any better way?
test "$(cat $VAL_PATH)" = "1" || fail "incorrect value read from sysfs"
kill "$mock_pid"
OFFSET=$(expr $OFFSET + 1)
done
teardown_4
echo "4.2. Forwarding set config"
setup_4
OFFSET=0
for SETTING in $SETTINGS; do
CHIP=$(echo "$SETTING" | cut -d: -f1)
BANK=$(echo "$SETTING" | cut -d: -f2)
LINE=$(echo "$SETTING" | cut -d: -f3)
DEVNAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/dev_name")
CHIPNAME=$(cat "$CONFIGFS_SIM_DIR/$CHIP/$BANK/chip_name")
VAL_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio${LINE}/value"
$BASE_DIR/gpio-mockup-cdev -b pull-up "/dev/$(agg_configfs_chip_name agg0)" "$OFFSET"
test $(cat "$VAL_PATH") = "1" || fail "incorrect value read from sysfs"
OFFSET=$(expr $OFFSET + 1)
done
teardown_4
echo "5. Race condition verification"
echo "5.1. Stress test of new_device/delete_device and module load/unload"
for _ in $(seq 1000); do
{
echo "dummy 0" > "$SYSFS_AGG_DIR/new_device"
cat "$CONFIGFS_AGG_DIR/_sysfs.0/dev_name" > "$SYSFS_AGG_DIR/delete_device"
} 2> /dev/null
done &
writer_pid=$!
while kill -0 "$writer_pid" 2> /dev/null; do
{
modprobe gpio-aggregator
modprobe -r gpio-aggregator
} 2> /dev/null
done
echo "GPIO $MODULE test PASS"