* [PATCH v3 0/2] gpio: Add support for Axiado AX3005 SGPIO controller
@ 2026-09-03 7:11 Petar Stepanovic
2026-09-03 7:11 ` [PATCH v3 1/2] dt-bindings: gpio: add " Petar Stepanovic
2026-09-03 7:11 ` [PATCH v3 2/2] gpio: axiado: add AX3005 SGPIO controller support Petar Stepanovic
0 siblings, 2 replies; 6+ messages in thread
From: Petar Stepanovic @ 2026-09-03 7:11 UTC (permalink / raw)
To: Petar Stepanovic, Tzu-Hao Wei, Swark Yang, Prasad Bolisetty,
Linus Walleij, Bartosz Golaszewski, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Harshit Shah, SriNavmani A,
Karthikeyan Mitran, Michael Walle
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, openbmc
Each SGPIO position provides one input GPIO and one output GPIO with
fixed directions, along with interrupt support for input GPIOs.
This series adds support for the SGPIO controller found on the Axiado
AX3005 SoC.
The series includes:
- Devicetree binding documentation
- GPIO driver implementation
The driver uses gpio-regmap for GPIO operations and provides
controller-specific interrupt handling.
Tested on Axiado AX3005 platforms.
---
Patch 1: dt-bindings: gpio: add Axiado AX3005 SGPIO controller
Patch 2: gpio: axiado: add AX3005 SGPIO controller support
Signed-off-by: Petar Stepanovic <pstepanovic@axiado.com>
---
Changes in v3:
- Dropped AX3000 support.
- Renamed the series to reflect AX3005-only support.
- Removed AX3000-specific compatible, match data, and register-layout
handling.
- Reworked GPIO operations to use `gpio-regmap` with fixed GPIO
directions and custom register/mask translation.
- Changed `ngpios` to represent the total number of Linux GPIO lines,
with each SGPIO position contributing one input and one output line,
and allowed any even count up to the hardware limit.
- Reworked interrupt handling to use a dedicated managed IRQ domain and
the `gpio-regmap` IRQ resource helpers.
- Added IRQ lockdep classes and restricted IRQ mappings to input GPIOs.
- Reworked software edge detection using input shadow state and dynamic
bitmaps for rising, falling, and unmasked interrupts.
- Updated input shadow handling so transitions occurring while an IRQ is
masked are not replayed after it is unmasked.
- Added regmap caching for the write-only output shadow registers and
marked live and interrupt-status registers appropriately.
- Reworked SoC match data to explicitly describe bank count, input-bank
alignment.
- Reworked hardware initialization to check register-access errors, use
named register fields and masks, and correctly handle partial banks.
- Added managed hardware shutdown so the shift engine and interrupts are
disabled on probe failures and device removal.
- Simplified resource management and removed the custom GPIO data
shadowing and locking used by the previous implementation.
- Clarified the binding description of SGPIO positions, fixed
input/output directions, and interrupt capabilities.
- Link to v2: https://lore.kernel.org/r/20260729-axiado-ax3000-sgpio-controller-v2-0-976f4ed11438@axiado.com
Changes in v2:
- Split the series into two patches: the binding and `MAINTAINERS`
update, followed by the SGPIO driver.
- Added SoC-specific compatibles for AX3000 and AX3005 and selected
hardware limits and register layouts through match data.
- Reworked clock handling to use one APB clock and calculate the SGPIO
divider from `bus-frequency`.
- Removed the custom `design-variant`, `dout-init`, `apb-frequency`,
and clock-source selection properties from the previous design.
- Added validation for the GPIO count, clock rates, and SGPIO divider
calculation.
- Added fixed GPIO direction handling: even offsets are inputs and odd
offsets are outputs.
- Fixed child IRQ mapping for the interleaved input/output GPIO layout
and restricted interrupts to input GPIOs.
- Reworked interrupt handling to support rising, falling, and both-edge
detection through software filtering.
- Initialized the input cache before enabling interrupts to prevent
false edge events during probe.
- Serialized output cache updates and register writes with a mutex to
prevent lost concurrent updates.
- Updated GPIO IRQ handling to use GPIO IRQ resource helpers and
threaded nested interrupts.
- Added managed hardware cleanup for probe failures and device removal.
- Updated the Kconfig regmap dependency for the MMIO-backed variant.
- Simplified resource management and error handling and performed
general coding-style cleanup.
- Link to v1: https://lore.kernel.org/r/20260414-axiado-ax3000-sgpio-controller-v1-0-b5c7e4c2e69b@axiado.com
To: Petar Stepanovic <pstepanovic@axiado.com>
To: SriNavmani A <srinavmani@axiado.com>
To: Prasad Bolisetty <pbolisetty@axiado.com>
To: Linus Walleij <linusw@kernel.org>
To: Bartosz Golaszewski <brgl@kernel.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Tzu-Hao Wei <twei@axiado.com>
To: Karthikeyan Mitran <kmitran@axiado.com>
To: Michael Walle <mwalle@kernel.org>
Cc: linux-gpio@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
---
Petar Stepanovic (2):
dt-bindings: gpio: add Axiado AX3005 SGPIO controller
gpio: axiado: add AX3005 SGPIO controller support
.../bindings/gpio/axiado,ax3005-sgpio.yaml | 112 ++++
MAINTAINERS | 9 +
drivers/gpio/Kconfig | 21 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-axiado-sgpio.c | 737 +++++++++++++++++++++
5 files changed, 880 insertions(+)
---
base-commit: 818bebeb63dd6bf5f4e07e145f6cdbace520a34c
change-id: 20260320-axiado-ax3000-sgpio-controller-00f6e1db6ce9
Best regards,
--
Petar Stepanovic <pstepanovic@axiado.com>
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3 1/2] dt-bindings: gpio: add Axiado AX3005 SGPIO controller
2026-09-03 7:11 [PATCH v3 0/2] gpio: Add support for Axiado AX3005 SGPIO controller Petar Stepanovic
@ 2026-09-03 7:11 ` Petar Stepanovic
2026-09-03 17:36 ` Conor Dooley
2026-09-03 7:11 ` [PATCH v3 2/2] gpio: axiado: add AX3005 SGPIO controller support Petar Stepanovic
1 sibling, 1 reply; 6+ messages in thread
From: Petar Stepanovic @ 2026-09-03 7:11 UTC (permalink / raw)
To: Petar Stepanovic, Tzu-Hao Wei, Swark Yang, Prasad Bolisetty,
Linus Walleij, Bartosz Golaszewski, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Harshit Shah, SriNavmani A,
Karthikeyan Mitran, Michael Walle
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, openbmc
Add a Devicetree binding for the SGPIO controller found on the
Axiado AX3005 SoC.
Each SGPIO position provides one fixed-direction input GPIO and one
fixed-direction output GPIO. Input GPIOs support edge-triggered
interrupts.
The controller serializes output values and samples input values over
dedicated data, clock, and latch signals.
Signed-off-by: Petar Stepanovic <pstepanovic@axiado.com>
---
.../bindings/gpio/axiado,ax3005-sgpio.yaml | 112 +++++++++++++++++++++
MAINTAINERS | 8 ++
2 files changed, 120 insertions(+)
diff --git a/Documentation/devicetree/bindings/gpio/axiado,ax3005-sgpio.yaml b/Documentation/devicetree/bindings/gpio/axiado,ax3005-sgpio.yaml
new file mode 100644
index 000000000000..47ae1285d482
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/axiado,ax3005-sgpio.yaml
@@ -0,0 +1,112 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/gpio/axiado,ax3005-sgpio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Axiado AX3005 SGPIO Controller
+
+maintainers:
+ - Petar Stepanovic <pstepanovic@axiado.com>
+ - SriNavmani A <srinavmani@axiado.com>
+ - Prasad Bolisetty <pbolisetty@axiado.com>
+
+description: |
+ The Axiado SGPIO controller provides a serial GPIO expansion interface
+ that multiplexes multiple GPIO signals over a small set of physical lines.
+
+ The controller serializes output values on a data output line (DOUT) and
+ samples input values from a data input line (DIN) in synchronization with
+ a shift clock (SCLK). A latch signal (LATCH) updates the output states
+ after each transfer cycle.
+
+ SGPIO positions are organized in banks of 32, with registers controlling
+ output values, input sampling, and interrupt status. The number of SGPIO
+ positions supported depends on the controller configuration.
+
+ The controller detects changes on input GPIOs. Interrupt events are
+ aggregated and signaled through a single interrupt line to the parent
+ interrupt controller.
+
+ Register access is performed over the APB bus. The SGPIO shift clock is
+ derived from the APB clock using a programmable divider.
+
+ Each SGPIO position provides one input and one output, and both are
+ exposed as GPIO lines. Position N is described by two consecutive lines:
+ line 2 * N is the input and line 2 * N + 1 is the output. Directions are
+ fixed by the hardware, so even lines are always inputs and odd lines are
+ always outputs.
+
+ Only input lines can generate interrupts. Rising, falling, and both-edge
+ interrupts are supported.
+
+properties:
+ compatible:
+ const: axiado,ax3005-sgpio
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ description: APB interface clock
+ maxItems: 1
+
+ bus-frequency:
+ description: Desired SGPIO shift clock frequency in Hz
+ minimum: 1
+
+ gpio-controller: true
+
+ '#gpio-cells':
+ const: 2
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 2
+
+ interrupts:
+ maxItems: 1
+
+ ngpios:
+ description: |
+ Total number of GPIO lines provided by the controller. Each SGPIO
+ position wired up on the board contributes one input and one output
+ line, so this is twice the number of positions and must be even. It
+ may be lower than the maximum supported by the hardware.
+ minimum: 2
+ maximum: 1024
+ multipleOf: 2
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - bus-frequency
+ - gpio-controller
+ - '#gpio-cells'
+ - interrupts
+ - interrupt-controller
+ - '#interrupt-cells'
+ - ngpios
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ gpio@33201c00 {
+ compatible = "axiado,ax3005-sgpio";
+ reg = <0x33201c00 0x400>;
+ clocks = <&apb_clk>;
+ bus-frequency = <1000000>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 125 IRQ_TYPE_LEVEL_HIGH>;
+ ngpios = <256>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 77159f279621..5bec6ea50c70 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4406,6 +4406,14 @@ S: Orphan
F: Documentation/devicetree/bindings/sound/axentia,*
F: sound/soc/atmel/tse850-pcm5142.c
+AXIADO SGPIO DRIVER
+M: Petar Stepanovic <pstepanovic@axiado.com>
+M: SriNavmani A <srinavmani@axiado.com>
+M: Prasad Bolisetty <pbolisetty@axiado.com>
+L: linux-gpio@vger.kernel.org
+S: Supported
+F: Documentation/devicetree/bindings/gpio/axiado,ax3005-sgpio.yaml
+
AXIS ARTPEC ARM64 SoC SUPPORT
M: Jesper Nilsson <jesper.nilsson@axis.com>
M: Lars Persson <lars.persson@axis.com>
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v3 2/2] gpio: axiado: add AX3005 SGPIO controller support
2026-09-03 7:11 [PATCH v3 0/2] gpio: Add support for Axiado AX3005 SGPIO controller Petar Stepanovic
2026-09-03 7:11 ` [PATCH v3 1/2] dt-bindings: gpio: add " Petar Stepanovic
@ 2026-09-03 7:11 ` Petar Stepanovic
2026-09-03 7:22 ` sashiko-bot
2026-09-03 8:59 ` Bartosz Golaszewski
1 sibling, 2 replies; 6+ messages in thread
From: Petar Stepanovic @ 2026-09-03 7:11 UTC (permalink / raw)
To: Petar Stepanovic, Tzu-Hao Wei, Swark Yang, Prasad Bolisetty,
Linus Walleij, Bartosz Golaszewski, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Harshit Shah, SriNavmani A,
Karthikeyan Mitran, Michael Walle
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, openbmc
Add support for the Axiado AX3005 SGPIO controller.
Each SGPIO position provides one input GPIO and one output GPIO with
fixed directions. The driver registers the controller as a gpio_chip,
supports interrupts on input GPIOs, and uses regmap for register access.
Signed-off-by: Petar Stepanovic <pstepanovic@axiado.com>
---
MAINTAINERS | 1 +
drivers/gpio/Kconfig | 21 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-axiado-sgpio.c | 737 +++++++++++++++++++++++++++++++++++++++
4 files changed, 760 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 5bec6ea50c70..aeedf503d422 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4413,6 +4413,7 @@ M: Prasad Bolisetty <pbolisetty@axiado.com>
L: linux-gpio@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/gpio/axiado,ax3005-sgpio.yaml
+F: drivers/gpio/gpio-axiado-sgpio.c
AXIS ARTPEC ARM64 SoC SUPPORT
M: Jesper Nilsson <jesper.nilsson@axis.com>
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index a48586bb8edb..364472b3788f 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -204,6 +204,27 @@ config GPIO_ATH79
Select this option to enable GPIO driver for
Atheros AR71XX/AR724X/AR913X SoC devices.
+config GPIO_AXIADO_SGPIO
+ tristate "Axiado AX3005 SGPIO support"
+ depends on ARCH_AXIADO || COMPILE_TEST
+ select GPIOLIB_IRQCHIP
+ select GPIO_REGMAP
+ select REGMAP_MMIO
+ help
+ Enable support for the Axiado AX3005 Serial GPIO (SGPIO)
+ controller.
+
+ The SGPIO controller provides a serialized interface for
+ controlling multiple GPIO signals over a limited number of
+ physical lines. Each signal is exposed as one input and one
+ output line, and input lines can raise interrupts.
+
+ This driver integrates with the Linux GPIO subsystem and
+ exposes the controller as a standard GPIO provider.
+
+ To compile this driver as a module, choose M here: the module
+ will be called gpio-axiado-sgpio.
+
config GPIO_RASPBERRYPI_EXP
tristate "Raspberry Pi 3 GPIO Expander"
default RASPBERRYPI_FIRMWARE
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index dc9e6d643b5b..b2820e4cf98a 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o
obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o
obj-$(CONFIG_GPIO_ASPEED_SGPIO) += gpio-aspeed-sgpio.o
obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.o
+obj-$(CONFIG_GPIO_AXIADO_SGPIO) += gpio-axiado-sgpio.o
obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o
obj-$(CONFIG_GPIO_BCM_XGS_IPROC) += gpio-xgs-iproc.o
obj-$(CONFIG_GPIO_BD71815) += gpio-bd71815.o
diff --git a/drivers/gpio/gpio-axiado-sgpio.c b/drivers/gpio/gpio-axiado-sgpio.c
new file mode 100644
index 000000000000..a7c970217c59
--- /dev/null
+++ b/drivers/gpio/gpio-axiado-sgpio.c
@@ -0,0 +1,737 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022-2026 Axiado Corporation
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
+#include <linux/irqdomain.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+struct axiado_sgpio_reg_offsets {
+ u32 mux_0;
+ u32 preset_0;
+ u32 count_0;
+ u32 pos_0;
+
+ u32 mux_1;
+ u32 ld;
+ u32 ld_ss;
+
+ u32 preset_1;
+ u32 count_1;
+ u32 pos_1;
+
+ u32 mux_2;
+ u32 dout;
+ u32 dout_ss;
+
+ u32 preset_2;
+ u32 count_2;
+ u32 pos_2;
+
+ u32 mux_3;
+ u32 preset_3;
+ u32 count_3;
+ u32 pos_3;
+
+ u32 mux_4;
+ u32 oe;
+ u32 oe_ss;
+
+ u32 preset_4;
+ u32 count_4;
+ u32 pos_4;
+
+ u32 mask;
+ u32 ctrl_en;
+ u32 ctrl_en_pos;
+
+ u32 din_ss;
+ u32 status;
+};
+
+static const struct axiado_sgpio_reg_offsets axiado_sgpio_offsets = {
+ .mux_0 = 0x000,
+ .preset_0 = 0x1dc,
+ .count_0 = 0x1f0,
+ .pos_0 = 0x204,
+
+ .mux_1 = 0x004,
+ .ld = 0x014,
+ .ld_ss = 0x0d8,
+
+ .preset_1 = 0x1e0,
+ .count_1 = 0x1f4,
+ .pos_1 = 0x208,
+
+ .mux_2 = 0x008,
+ .dout = 0x054,
+ .dout_ss = 0x158,
+
+ .preset_2 = 0x1e4,
+ .count_2 = 0x1f8,
+ .pos_2 = 0x20c,
+
+ .mux_3 = 0x00c,
+ .preset_3 = 0x1e8,
+ .count_3 = 0x1fc,
+ .pos_3 = 0x210,
+
+ .mux_4 = 0x010,
+ .oe = 0x0d4,
+ .oe_ss = 0x1d8,
+
+ .preset_4 = 0x1ec,
+ .count_4 = 0x200,
+ .pos_4 = 0x214,
+
+ .mask = 0x224,
+ .ctrl_en = 0x218,
+ .ctrl_en_pos = 0x21c,
+
+ .din_ss = 0x198,
+ .status = 0x228,
+};
+
+/* Number of SGPIO positions held by one bank register. */
+#define SGPIO_BANK_SIZE 32
+#define SGPIO_MAX_SIGNALS 512
+#define SGPIO_NUM_BANKS 16
+
+/*
+ * Slice mux encodings taken from the AX3005 SGPIO programming sequence.
+ */
+#define SGPIO_MUX_CLK 0x306
+#define SGPIO_MUX_DATA 0x30c
+#define SGPIO_MUX_DIN 0x14c
+#define SGPIO_MUX_OE 0x10c
+
+/* A POS register holds the first and the last position driven by the slice. */
+#define SGPIO_POS_FIRST GENMASK(31, 16)
+#define SGPIO_POS_LAST GENMASK(15, 0)
+#define SGPIO_POS(pos) (FIELD_PREP(SGPIO_POS_FIRST, (pos)) | \
+ FIELD_PREP(SGPIO_POS_LAST, (pos)))
+
+/* The clock and output enable slices always cover a full bank. */
+#define SGPIO_POS_FULL_BANK SGPIO_POS(SGPIO_BANK_SIZE - 1)
+
+/* The enable and interrupt registers hold one bit per slice. */
+#define SGPIO_SLICE_MASK GENMASK(15, 0)
+
+/*
+ * Interrupt sources enabled by the programming sequence. Bit 13 is left
+ * masked, the remaining slice events are all reported.
+ */
+#define SGPIO_IRQ_ENABLE 0xdfff
+
+/* The upper half of the status register reports one bit per data bank. */
+#define SGPIO_STATUS_EXCHANGE GENMASK(31, 16)
+
+struct axiado_sgpio {
+ u32 preset_value;
+ u32 count_value;
+ u32 pos_reg;
+ /* Last sampled input word of each bank, for edge detection. */
+ u32 *din_shadow;
+ unsigned long *irq_unmasked;
+ unsigned long *irq_rising;
+ unsigned long *irq_falling;
+ u32 nsignals;
+ u32 din_bank_shift;
+ unsigned long *dir_out;
+ struct gpio_regmap *gpio;
+ struct irq_domain *domain;
+ struct regmap *regmap;
+ const struct axiado_sgpio_reg_offsets *regs;
+};
+
+/*
+ * Tell lockdep that these interrupts are in a different category than the
+ * parent they are handled from, so that the nested handling is not reported
+ * as recursion.
+ */
+static struct lock_class_key axiado_sgpio_irq_lock_class;
+static struct lock_class_key axiado_sgpio_irq_request_class;
+
+/*
+ * Each SGPIO signal is exposed as two GPIO lines: an even line for the serial
+ * input and the following odd line for the serial output. Signal n therefore
+ * lives in bit (n % 32) of bank (n / 32) of either the input or the output
+ * register.
+ */
+static int axiado_sgpio_reg_mask_xlate(struct gpio_regmap *gpio,
+ enum gpio_regmap_operation op,
+ unsigned int base, unsigned int offset,
+ unsigned int *reg, unsigned int *mask)
+{
+ struct axiado_sgpio *sgpio = gpio_regmap_get_drvdata(gpio);
+ unsigned int signal = offset / 2;
+ unsigned int bank = signal / SGPIO_BANK_SIZE;
+
+ /*
+ * An output line lives in the output register for both reads and
+ * writes, so the register follows from the line rather than from the
+ * operation. The AX3005 input banks are right aligned, hence the
+ * shift.
+ */
+ if (offset % 2)
+ base = sgpio->regs->dout_ss;
+ else
+ bank += sgpio->din_bank_shift;
+
+ *reg = base + bank * sizeof(u32);
+ *mask = BIT(signal % SGPIO_BANK_SIZE);
+
+ return 0;
+}
+
+static irqreturn_t axiado_sgpio_irq_handler(int irq, void *arg)
+{
+ struct axiado_sgpio *sgpio = arg;
+ unsigned int nbanks = DIV_ROUND_UP(sgpio->nsignals, SGPIO_BANK_SIZE);
+ u32 status, new_value, changed_value;
+ unsigned int bit, reg_ptr, i;
+ int ret;
+
+ /* Read-on-clear (ACK) parent cause */
+ ret = regmap_read(sgpio->regmap, sgpio->regs->status, &status);
+ if (ret)
+ return IRQ_NONE;
+
+ /* Nothing pending: the interrupt came from somebody else. */
+ if (!status)
+ return IRQ_NONE;
+
+ status = FIELD_GET(SGPIO_STATUS_EXCHANGE, status);
+
+ reg_ptr = sgpio->din_bank_shift;
+
+ for (i = 0; i < nbanks; i++, reg_ptr++) {
+ if (status & BIT(reg_ptr)) {
+ ret = regmap_read(sgpio->regmap,
+ sgpio->regs->din_ss + reg_ptr * sizeof(u32),
+ &new_value);
+ if (ret)
+ continue;
+
+ /*
+ * Track every signal so that the shadow always holds
+ * the last sampled level, then report only the ones
+ * whose interrupt is unmasked. Transitions of a masked
+ * signal are dropped rather than delivered later, the
+ * hardware has no per signal latch to replay them
+ * from. Only this handler touches the shadow and it is
+ * not reentrant.
+ */
+ changed_value = sgpio->din_shadow[i] ^ new_value;
+ sgpio->din_shadow[i] = new_value;
+ changed_value &= bitmap_read(sgpio->irq_unmasked,
+ i * SGPIO_BANK_SIZE,
+ SGPIO_BANK_SIZE);
+
+ while (changed_value) {
+ unsigned int child_irq;
+ irq_hw_number_t hwirq;
+ unsigned int position;
+ bool rising;
+
+ bit = __ffs(changed_value);
+ changed_value &= ~BIT(bit);
+
+ position = i * SGPIO_BANK_SIZE + bit;
+ hwirq = position * 2;
+
+ rising = !!(new_value & BIT(bit));
+
+ if (!test_bit(position, rising ? sgpio->irq_rising
+ : sgpio->irq_falling))
+ continue;
+
+ child_irq = irq_find_mapping(sgpio->domain, hwirq);
+ if (child_irq)
+ handle_nested_irq(child_irq);
+ }
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int axiado_sgpio_hw_init(struct axiado_sgpio *sgpio)
+{
+ const struct axiado_sgpio_reg_offsets *regs = sgpio->regs;
+ unsigned int nbanks = DIV_ROUND_UP(sgpio->nsignals, SGPIO_BANK_SIZE);
+ unsigned int last_bank = (sgpio->nsignals - 1) / SGPIO_BANK_SIZE;
+ unsigned int last_bit = (sgpio->nsignals - 1) % SGPIO_BANK_SIZE;
+ unsigned int i;
+ int ret;
+
+ /* Slice A0 is the shift clock, slice B1 the data load. */
+ const struct reg_sequence pre_load[] = {
+ { regs->mask, 0 },
+ { regs->mux_0, SGPIO_MUX_CLK },
+ { regs->preset_0, sgpio->preset_value },
+ { regs->count_0, sgpio->count_value },
+ { regs->pos_0, SGPIO_POS_FULL_BANK },
+ { regs->mux_1, SGPIO_MUX_DATA },
+ };
+ /* Slice C2 is the data output. */
+ const struct reg_sequence pre_dout[] = {
+ { regs->preset_1, sgpio->preset_value },
+ { regs->count_1, sgpio->count_value },
+ { regs->pos_1, sgpio->pos_reg },
+ { regs->mux_2, SGPIO_MUX_DATA },
+ };
+ /* Slice D3 is the data input, slice E4 the output enable. */
+ const struct reg_sequence post_dout[] = {
+ { regs->preset_2, sgpio->preset_value },
+ { regs->count_2, sgpio->count_value },
+ { regs->pos_2, sgpio->pos_reg },
+ { regs->mux_3, SGPIO_MUX_DIN },
+ { regs->preset_3, sgpio->preset_value },
+ { regs->count_3, sgpio->count_value },
+ { regs->pos_3, sgpio->pos_reg },
+ { regs->mux_4, SGPIO_MUX_OE },
+ { regs->oe, GENMASK(31, 0) },
+ { regs->oe_ss, GENMASK(31, 0) },
+ { regs->preset_4, sgpio->preset_value },
+ { regs->count_4, sgpio->count_value },
+ { regs->pos_4, SGPIO_POS_FULL_BANK },
+ { regs->ctrl_en, SGPIO_SLICE_MASK },
+ { regs->ctrl_en_pos, SGPIO_SLICE_MASK },
+ };
+
+ ret = regmap_multi_reg_write(sgpio->regmap, pre_load,
+ ARRAY_SIZE(pre_load));
+ if (ret)
+ return ret;
+
+ /*
+ * Latch the signals that are present. Only the last bank may be
+ * partially populated; a last bit of zero still describes one signal.
+ */
+ for (i = 0; i < nbanks; i++) {
+ u32 val = i < last_bank ? GENMASK(SGPIO_BANK_SIZE - 1, 0) :
+ GENMASK(last_bit, 0);
+
+ ret = regmap_write(sgpio->regmap, regs->ld + i * 4, val);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(sgpio->regmap, regs->ld_ss + i * 4, val);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_multi_reg_write(sgpio->regmap, pre_dout,
+ ARRAY_SIZE(pre_dout));
+ if (ret)
+ return ret;
+
+ /* All outputs start out driving zero. */
+ for (i = 0; i < nbanks; i++) {
+ ret = regmap_write(sgpio->regmap, regs->dout + i * 4, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(sgpio->regmap, regs->dout_ss + i * 4, 0);
+ if (ret)
+ return ret;
+ }
+
+ return regmap_multi_reg_write(sgpio->regmap, post_dout,
+ ARRAY_SIZE(post_dout));
+}
+
+static int axiado_sgpio_irq_enable(struct axiado_sgpio *sgpio)
+{
+ return regmap_write(sgpio->regmap, sgpio->regs->mask,
+ SGPIO_IRQ_ENABLE);
+}
+
+static void axiado_sgpio_mask_irqs(void *data)
+{
+ struct axiado_sgpio *sgpio = data;
+
+ regmap_write(sgpio->regmap, sgpio->regs->mask, 0);
+}
+
+static void axiado_sgpio_disable(void *data)
+{
+ struct axiado_sgpio *sgpio = data;
+
+ /* Mask the interrupts and stop the shift engine. */
+ regmap_write(sgpio->regmap, sgpio->regs->mask, 0);
+ regmap_write(sgpio->regmap, sgpio->regs->ctrl_en, 0);
+}
+
+static int axiado_sgpio_set_irq_type(struct irq_data *d, unsigned int type)
+{
+ struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+ unsigned int position;
+
+ /* Only even GPIO offsets represent SGPIO inputs. */
+ if (hwirq & 1)
+ return -EINVAL;
+
+ position = hwirq / 2;
+ if (position >= sgpio->nsignals)
+ return -EINVAL;
+
+ type &= IRQ_TYPE_SENSE_MASK;
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_BOTH:
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_EDGE_FALLING:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ assign_bit(position, sgpio->irq_rising, type & IRQ_TYPE_EDGE_RISING);
+ assign_bit(position, sgpio->irq_falling, type & IRQ_TYPE_EDGE_FALLING);
+
+ return 0;
+}
+
+static void axiado_sgpio_mask_irq(struct irq_data *d)
+{
+ struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ clear_bit(hwirq / 2, sgpio->irq_unmasked);
+ gpio_regmap_disable_irq(sgpio->gpio, hwirq);
+}
+
+static void axiado_sgpio_unmask_irq(struct irq_data *d)
+{
+ struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ gpio_regmap_enable_irq(sgpio->gpio, hwirq);
+ set_bit(hwirq / 2, sgpio->irq_unmasked);
+}
+
+static int axiado_sgpio_irq_request_resources(struct irq_data *d)
+{
+ struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d);
+
+ return gpio_regmap_reqres_irq(sgpio->gpio, d->hwirq);
+}
+
+static void axiado_sgpio_irq_release_resources(struct irq_data *d)
+{
+ struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d);
+
+ gpio_regmap_relres_irq(sgpio->gpio, d->hwirq);
+}
+
+static const struct irq_chip axiado_sgpio_irqchip = {
+ .name = "axiado-sgpio",
+ .irq_mask = axiado_sgpio_mask_irq,
+ .irq_unmask = axiado_sgpio_unmask_irq,
+ .irq_set_type = axiado_sgpio_set_irq_type,
+ .flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND,
+ .irq_request_resources = axiado_sgpio_irq_request_resources,
+ .irq_release_resources = axiado_sgpio_irq_release_resources,
+};
+
+static int axiado_sgpio_irq_map(struct irq_domain *domain, unsigned int virq,
+ irq_hw_number_t hwirq)
+{
+ struct axiado_sgpio *sgpio = domain->host_data;
+
+ /* Only even offsets represent input GPIOs. */
+ if (hwirq % 2)
+ return -EINVAL;
+
+ irq_set_chip_data(virq, sgpio);
+ irq_set_lockdep_class(virq, &axiado_sgpio_irq_lock_class,
+ &axiado_sgpio_irq_request_class);
+ irq_set_chip_and_handler(virq, &axiado_sgpio_irqchip, handle_simple_irq);
+ irq_set_nested_thread(virq, true);
+ irq_set_noprobe(virq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops axiado_sgpio_domain_ops = {
+ .map = axiado_sgpio_irq_map,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+static int axiado_sgpio_init_input_cache(struct axiado_sgpio *sgpio)
+{
+ unsigned int num_banks = DIV_ROUND_UP(sgpio->nsignals,
+ SGPIO_BANK_SIZE);
+ unsigned int reg_ptr = sgpio->din_bank_shift;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < num_banks; i++, reg_ptr++) {
+ ret = regmap_read(sgpio->regmap,
+ sgpio->regs->din_ss + reg_ptr * sizeof(u32),
+ &sgpio->din_shadow[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static bool axiado_sgpio_dout_reg(const struct axiado_sgpio *sgpio,
+ unsigned int reg)
+{
+ unsigned int span = SGPIO_NUM_BANKS * sizeof(u32);
+
+ return reg >= sgpio->regs->dout_ss &&
+ reg < sgpio->regs->dout_ss + span;
+}
+
+static bool axiado_sgpio_readable_reg(struct device *dev, unsigned int reg)
+{
+ const struct axiado_sgpio *sgpio = dev_get_drvdata(dev);
+
+ /* The status register is the last one of the block. */
+ if (reg > sgpio->regs->status)
+ return false;
+
+ /*
+ * The output shadow feeds the shift register and cannot be read back,
+ * the driven value is kept in the register cache instead.
+ */
+ return !axiado_sgpio_dout_reg(sgpio, reg);
+}
+
+static bool axiado_sgpio_volatile_reg(struct device *dev, unsigned int reg)
+{
+ const struct axiado_sgpio *sgpio = dev_get_drvdata(dev);
+
+ /*
+ * Everything but the output shadow is live hardware state: shifted in
+ * data, running counters and the interrupt status. Caching any of it
+ * would hand out stale values, so the cache holds the output banks
+ * alone.
+ */
+ return !axiado_sgpio_dout_reg(sgpio, reg);
+}
+
+static bool axiado_sgpio_precious_reg(struct device *dev, unsigned int reg)
+{
+ const struct axiado_sgpio *sgpio = dev_get_drvdata(dev);
+
+ /* Reading the status register acknowledges the interrupts. */
+ return reg == sgpio->regs->status;
+}
+
+static const struct regmap_config axiado_sgpio_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .readable_reg = axiado_sgpio_readable_reg,
+ .volatile_reg = axiado_sgpio_volatile_reg,
+ .precious_reg = axiado_sgpio_precious_reg,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int axiado_sgpio_probe(struct platform_device *pdev)
+{
+ struct gpio_regmap_config config = { };
+ struct irq_domain_info d_info = { };
+ struct axiado_sgpio *sgpio;
+ unsigned int ngpio, i;
+ unsigned long apb_freq;
+ struct clk *apb_clk;
+ void __iomem *base;
+ u32 sgpio_freq;
+ int irq, rc;
+
+ sgpio = devm_kzalloc(&pdev->dev, sizeof(*sgpio), GFP_KERNEL);
+ if (!sgpio)
+ return -ENOMEM;
+
+ /* The regmap callbacks below need both of these. */
+ platform_set_drvdata(pdev, sgpio);
+
+ sgpio->regs = &axiado_sgpio_offsets;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ sgpio->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &axiado_sgpio_regmap_config);
+ if (IS_ERR(sgpio->regmap))
+ return dev_err_probe(&pdev->dev, PTR_ERR(sgpio->regmap),
+ "Failed to init regmap\n");
+
+ rc = device_property_read_u32(&pdev->dev, "ngpios", &ngpio);
+ if (rc)
+ return dev_err_probe(&pdev->dev, rc,
+ "Failed to read ngpios property\n");
+
+ /*
+ * Each SGPIO signal is exposed as one input and one output line, so
+ * the number of lines is twice the number of signals.
+ */
+ if (!ngpio || ngpio % 2 || ngpio > SGPIO_MAX_SIGNALS * 2)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "Invalid ngpios value: %u (even, max %u)\n",
+ ngpio, SGPIO_MAX_SIGNALS * 2);
+
+ sgpio->nsignals = ngpio / 2;
+
+ sgpio->din_shadow = devm_kcalloc(&pdev->dev,
+ DIV_ROUND_UP(sgpio->nsignals,
+ SGPIO_BANK_SIZE),
+ sizeof(*sgpio->din_shadow), GFP_KERNEL);
+ sgpio->irq_unmasked = devm_bitmap_zalloc(&pdev->dev, sgpio->nsignals,
+ GFP_KERNEL);
+ sgpio->irq_rising = devm_bitmap_zalloc(&pdev->dev, sgpio->nsignals,
+ GFP_KERNEL);
+ sgpio->irq_falling = devm_bitmap_zalloc(&pdev->dev, sgpio->nsignals,
+ GFP_KERNEL);
+ if (!sgpio->din_shadow || !sgpio->irq_unmasked ||
+ !sgpio->irq_rising || !sgpio->irq_falling)
+ return -ENOMEM;
+
+ apb_clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(apb_clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(apb_clk),
+ "Failed to get and enable APB clock\n");
+
+ rc = device_property_read_u32(&pdev->dev, "bus-frequency",
+ &sgpio_freq);
+ if (rc)
+ return dev_err_probe(&pdev->dev, rc,
+ "Failed to read bus-frequency\n");
+
+ apb_freq = clk_get_rate(apb_clk);
+
+ if (!apb_freq || !sgpio_freq || sgpio_freq > apb_freq)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "Invalid SGPIO bus frequency\n");
+
+ sgpio->preset_value = (apb_freq / sgpio_freq) - 1;
+ sgpio->count_value = sgpio->preset_value;
+
+ sgpio->pos_reg = SGPIO_POS(sgpio->nsignals - 1);
+
+ sgpio->din_bank_shift = SGPIO_NUM_BANKS -
+ DIV_ROUND_UP(sgpio->nsignals, SGPIO_BANK_SIZE);
+
+ /*
+ * From here on the hardware may be running, so the shutdown action has
+ * to be in place before the first register is programmed. It only ever
+ * writes zeroes, which is harmless if initialisation never got that
+ * far, and it runs before the APB clock is disabled because that was
+ * requested earlier.
+ */
+ rc = devm_add_action_or_reset(&pdev->dev, axiado_sgpio_disable, sgpio);
+ if (rc)
+ return rc;
+
+ rc = axiado_sgpio_hw_init(sgpio);
+ if (rc)
+ return dev_err_probe(&pdev->dev, rc,
+ "Failed to initialize hardware\n");
+
+ rc = axiado_sgpio_init_input_cache(sgpio);
+ if (rc)
+ return dev_err_probe(&pdev->dev, rc,
+ "Failed to initialize input cache\n");
+
+ sgpio->dir_out = devm_bitmap_zalloc(&pdev->dev, ngpio, GFP_KERNEL);
+ if (!sgpio->dir_out)
+ return -ENOMEM;
+
+ /* Even lines are the serial inputs, odd lines the serial outputs. */
+ for (i = 1; i < ngpio; i += 2)
+ __set_bit(i, sgpio->dir_out);
+
+ d_info.fwnode = dev_fwnode(&pdev->dev);
+ d_info.size = ngpio;
+ d_info.hwirq_max = ngpio;
+ d_info.ops = &axiado_sgpio_domain_ops;
+ d_info.host_data = sgpio;
+
+ sgpio->domain = devm_irq_domain_instantiate(&pdev->dev, &d_info);
+ if (IS_ERR(sgpio->domain))
+ return PTR_ERR(sgpio->domain);
+
+ config.parent = &pdev->dev;
+ config.regmap = sgpio->regmap;
+ config.ngpio = ngpio;
+ config.reg_dat_base = GPIO_REGMAP_ADDR(sgpio->regs->din_ss);
+ config.reg_set_base = GPIO_REGMAP_ADDR(sgpio->regs->dout_ss);
+ config.reg_mask_xlate = axiado_sgpio_reg_mask_xlate;
+ config.fixed_direction_output = sgpio->dir_out;
+ config.irq_domain = sgpio->domain;
+ config.drvdata = sgpio;
+
+ sgpio->gpio = devm_gpio_regmap_register(&pdev->dev, &config);
+ if (IS_ERR(sgpio->gpio))
+ return dev_err_probe(&pdev->dev, PTR_ERR(sgpio->gpio),
+ "Could not register gpiochip\n");
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ rc = devm_request_threaded_irq(&pdev->dev, irq, NULL, axiado_sgpio_irq_handler,
+ IRQF_ONESHOT, dev_name(&pdev->dev), sgpio);
+ if (rc)
+ return dev_err_probe(&pdev->dev, rc, "Failed to request IRQ\n");
+
+ rc = axiado_sgpio_irq_enable(sgpio);
+ if (rc)
+ return dev_err_probe(&pdev->dev, rc,
+ "Failed to enable interrupts\n");
+
+ /*
+ * Registered last so that it runs first on teardown: the interrupts
+ * must be masked while the parent interrupt is still requested. The
+ * shift engine is stopped by axiado_sgpio_disable(), registered before
+ * the hardware was programmed so that it also covers failures during
+ * probe.
+ */
+ return devm_add_action_or_reset(&pdev->dev, axiado_sgpio_mask_irqs,
+ sgpio);
+}
+
+static const struct of_device_id axiado_sgpio_of_match[] = {
+ { .compatible = "axiado,ax3005-sgpio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, axiado_sgpio_of_match);
+
+static struct platform_driver axiado_sgpio_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = axiado_sgpio_of_match,
+ },
+ .probe = axiado_sgpio_probe,
+};
+module_platform_driver(axiado_sgpio_driver);
+
+MODULE_DESCRIPTION("Axiado AX3005 Serial GPIO Driver");
+MODULE_AUTHOR("Axiado Corporation");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v3 2/2] gpio: axiado: add AX3005 SGPIO controller support
2026-09-03 7:11 ` [PATCH v3 2/2] gpio: axiado: add AX3005 SGPIO controller support Petar Stepanovic
@ 2026-09-03 7:22 ` sashiko-bot
2026-09-03 8:59 ` Bartosz Golaszewski
1 sibling, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-09-03 7:22 UTC (permalink / raw)
To: Petar Stepanovic; +Cc: robh, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The IRQ domain is instantiated and published before the driver's internal state (`sgpio->gpio`) is fully initialized, allowing concurrent IRQ requests to cause a NULL pointer dereference.
--
commit 4babc207a066bfa307e830995ecabfdcf12404d1
Author: Petar Stepanovic <pstepanovic@axiado.com>
gpio: axiado: add AX3005 SGPIO controller support
Add support for the Axiado AX3005 SGPIO controller.
Each SGPIO position provides one input GPIO and one output GPIO with
fixed directions. The driver registers the controller as a gpio_chip,
supports interrupts on input GPIOs, and uses regmap for register access.
> diff --git a/drivers/gpio/gpio-axiado-sgpio.c b/drivers/gpio/gpio-axiado-sgpio.c
> new file mode 100644
> index 0000000000000..a7c970217c59e
> --- /dev/null
> +++ b/drivers/gpio/gpio-axiado-sgpio.c
[ ... ]
> +static int axiado_sgpio_irq_request_resources(struct irq_data *d)
> +{
> + struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d);
> +
> + return gpio_regmap_reqres_irq(sgpio->gpio, d->hwirq);
> +}
[ ... ]
> +static int axiado_sgpio_probe(struct platform_device *pdev)
> +{
[ ... ]
> + d_info.fwnode = dev_fwnode(&pdev->dev);
> + d_info.size = ngpio;
> + d_info.hwirq_max = ngpio;
> + d_info.ops = &axiado_sgpio_domain_ops;
> + d_info.host_data = sgpio;
> +
> + sgpio->domain = devm_irq_domain_instantiate(&pdev->dev, &d_info);
> + if (IS_ERR(sgpio->domain))
> + return PTR_ERR(sgpio->domain);
> +
> + config.parent = &pdev->dev;
> + config.regmap = sgpio->regmap;
> + config.ngpio = ngpio;
> + config.reg_dat_base = GPIO_REGMAP_ADDR(sgpio->regs->din_ss);
> + config.reg_set_base = GPIO_REGMAP_ADDR(sgpio->regs->dout_ss);
> + config.reg_mask_xlate = axiado_sgpio_reg_mask_xlate;
> + config.fixed_direction_output = sgpio->dir_out;
> + config.irq_domain = sgpio->domain;
> + config.drvdata = sgpio;
> +
> + sgpio->gpio = devm_gpio_regmap_register(&pdev->dev, &config);
[Severity: High]
Could concurrent IRQ mapping trigger a NULL pointer dereference here?
The IRQ domain is instantiated and published by devm_irq_domain_instantiate()
before sgpio->gpio is assigned by devm_gpio_regmap_register().
If a consumer driver probes concurrently and resolves the IRQ via the
device tree, it can call request_irq(), which invokes
axiado_sgpio_irq_request_resources().
That callback will dereference sgpio->gpio before it has been set:
return gpio_regmap_reqres_irq(sgpio->gpio, d->hwirq);
> + if (IS_ERR(sgpio->gpio))
> + return dev_err_probe(&pdev->dev, PTR_ERR(sgpio->gpio),
> + "Could not register gpiochip\n");
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260903-axiado-ax3000-sgpio-controller-v3-0-b30820e1b858@axiado.com?part=2
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v3 2/2] gpio: axiado: add AX3005 SGPIO controller support
2026-09-03 7:11 ` [PATCH v3 2/2] gpio: axiado: add AX3005 SGPIO controller support Petar Stepanovic
2026-09-03 7:22 ` sashiko-bot
@ 2026-09-03 8:59 ` Bartosz Golaszewski
1 sibling, 0 replies; 6+ messages in thread
From: Bartosz Golaszewski @ 2026-09-03 8:59 UTC (permalink / raw)
To: Petar Stepanovic
Cc: linux-gpio, devicetree, linux-arm-kernel, linux-kernel, openbmc,
Tzu-Hao Wei, Swark Yang, Prasad Bolisetty, Linus Walleij,
Bartosz Golaszewski, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Harshit Shah, SriNavmani A, Karthikeyan Mitran,
Michael Walle
On Thu, 3 Sep 2026 09:11:33 +0200, Petar Stepanovic
<pstepanovic@axiado.com> said:
> Add support for the Axiado AX3005 SGPIO controller.
>
> Each SGPIO position provides one input GPIO and one output GPIO with
> fixed directions. The driver registers the controller as a gpio_chip,
> supports interrupts on input GPIOs, and uses regmap for register access.
>
> Signed-off-by: Petar Stepanovic <pstepanovic@axiado.com>
> ---
...
> +
> +static int axiado_sgpio_probe(struct platform_device *pdev)
> +{
> + struct gpio_regmap_config config = { };
> + struct irq_domain_info d_info = { };
> + struct axiado_sgpio *sgpio;
> + unsigned int ngpio, i;
> + unsigned long apb_freq;
> + struct clk *apb_clk;
> + void __iomem *base;
> + u32 sgpio_freq;
> + int irq, rc;
> +
> + sgpio = devm_kzalloc(&pdev->dev, sizeof(*sgpio), GFP_KERNEL);
> + if (!sgpio)
> + return -ENOMEM;
> +
> + /* The regmap callbacks below need both of these. */
> + platform_set_drvdata(pdev, sgpio);
> +
> + sgpio->regs = &axiado_sgpio_offsets;
> +
> + base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(base))
> + return PTR_ERR(base);
> +
> + sgpio->regmap = devm_regmap_init_mmio(&pdev->dev, base,
> + &axiado_sgpio_regmap_config);
> + if (IS_ERR(sgpio->regmap))
> + return dev_err_probe(&pdev->dev, PTR_ERR(sgpio->regmap),
> + "Failed to init regmap\n");
> +
> + rc = device_property_read_u32(&pdev->dev, "ngpios", &ngpio);
> + if (rc)
> + return dev_err_probe(&pdev->dev, rc,
> + "Failed to read ngpios property\n");
> +
> + /*
> + * Each SGPIO signal is exposed as one input and one output line, so
> + * the number of lines is twice the number of signals.
> + */
> + if (!ngpio || ngpio % 2 || ngpio > SGPIO_MAX_SIGNALS * 2)
> + return dev_err_probe(&pdev->dev, -EINVAL,
> + "Invalid ngpios value: %u (even, max %u)\n",
> + ngpio, SGPIO_MAX_SIGNALS * 2);
> +
> + sgpio->nsignals = ngpio / 2;
> +
> + sgpio->din_shadow = devm_kcalloc(&pdev->dev,
> + DIV_ROUND_UP(sgpio->nsignals,
> + SGPIO_BANK_SIZE),
> + sizeof(*sgpio->din_shadow), GFP_KERNEL);
> + sgpio->irq_unmasked = devm_bitmap_zalloc(&pdev->dev, sgpio->nsignals,
> + GFP_KERNEL);
> + sgpio->irq_rising = devm_bitmap_zalloc(&pdev->dev, sgpio->nsignals,
> + GFP_KERNEL);
> + sgpio->irq_falling = devm_bitmap_zalloc(&pdev->dev, sgpio->nsignals,
> + GFP_KERNEL);
> + if (!sgpio->din_shadow || !sgpio->irq_unmasked ||
> + !sgpio->irq_rising || !sgpio->irq_falling)
> + return -ENOMEM;
> +
> + apb_clk = devm_clk_get_enabled(&pdev->dev, NULL);
> + if (IS_ERR(apb_clk))
> + return dev_err_probe(&pdev->dev, PTR_ERR(apb_clk),
> + "Failed to get and enable APB clock\n");
> +
> + rc = device_property_read_u32(&pdev->dev, "bus-frequency",
> + &sgpio_freq);
> + if (rc)
> + return dev_err_probe(&pdev->dev, rc,
> + "Failed to read bus-frequency\n");
> +
> + apb_freq = clk_get_rate(apb_clk);
> +
> + if (!apb_freq || !sgpio_freq || sgpio_freq > apb_freq)
> + return dev_err_probe(&pdev->dev, -EINVAL,
> + "Invalid SGPIO bus frequency\n");
> +
> + sgpio->preset_value = (apb_freq / sgpio_freq) - 1;
> + sgpio->count_value = sgpio->preset_value;
> +
> + sgpio->pos_reg = SGPIO_POS(sgpio->nsignals - 1);
> +
> + sgpio->din_bank_shift = SGPIO_NUM_BANKS -
> + DIV_ROUND_UP(sgpio->nsignals, SGPIO_BANK_SIZE);
> +
> + /*
> + * From here on the hardware may be running, so the shutdown action has
> + * to be in place before the first register is programmed. It only ever
> + * writes zeroes, which is harmless if initialisation never got that
> + * far, and it runs before the APB clock is disabled because that was
> + * requested earlier.
> + */
> + rc = devm_add_action_or_reset(&pdev->dev, axiado_sgpio_disable, sgpio);
> + if (rc)
> + return rc;
> +
> + rc = axiado_sgpio_hw_init(sgpio);
> + if (rc)
> + return dev_err_probe(&pdev->dev, rc,
> + "Failed to initialize hardware\n");
> +
> + rc = axiado_sgpio_init_input_cache(sgpio);
> + if (rc)
> + return dev_err_probe(&pdev->dev, rc,
> + "Failed to initialize input cache\n");
> +
> + sgpio->dir_out = devm_bitmap_zalloc(&pdev->dev, ngpio, GFP_KERNEL);
> + if (!sgpio->dir_out)
> + return -ENOMEM;
> +
> + /* Even lines are the serial inputs, odd lines the serial outputs. */
> + for (i = 1; i < ngpio; i += 2)
> + __set_bit(i, sgpio->dir_out);
> +
> + d_info.fwnode = dev_fwnode(&pdev->dev);
> + d_info.size = ngpio;
> + d_info.hwirq_max = ngpio;
> + d_info.ops = &axiado_sgpio_domain_ops;
> + d_info.host_data = sgpio;
> +
> + sgpio->domain = devm_irq_domain_instantiate(&pdev->dev, &d_info);
> + if (IS_ERR(sgpio->domain))
> + return PTR_ERR(sgpio->domain);
> +
> + config.parent = &pdev->dev;
> + config.regmap = sgpio->regmap;
> + config.ngpio = ngpio;
> + config.reg_dat_base = GPIO_REGMAP_ADDR(sgpio->regs->din_ss);
> + config.reg_set_base = GPIO_REGMAP_ADDR(sgpio->regs->dout_ss);
> + config.reg_mask_xlate = axiado_sgpio_reg_mask_xlate;
> + config.fixed_direction_output = sgpio->dir_out;
> + config.irq_domain = sgpio->domain;
> + config.drvdata = sgpio;
> +
> + sgpio->gpio = devm_gpio_regmap_register(&pdev->dev, &config);
> + if (IS_ERR(sgpio->gpio))
> + return dev_err_probe(&pdev->dev, PTR_ERR(sgpio->gpio),
> + "Could not register gpiochip\n");
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0)
> + return irq;
> +
> + rc = devm_request_threaded_irq(&pdev->dev, irq, NULL, axiado_sgpio_irq_handler,
> + IRQF_ONESHOT, dev_name(&pdev->dev), sgpio);
> + if (rc)
> + return dev_err_probe(&pdev->dev, rc, "Failed to request IRQ\n");
> +
> + rc = axiado_sgpio_irq_enable(sgpio);
> + if (rc)
> + return dev_err_probe(&pdev->dev, rc,
> + "Failed to enable interrupts\n");
Phew, that's a complex probe but I don't see anything inherently wrong. Linus
is a better expert in interrupts so may want to chime in.
> +
> + /*
> + * Registered last so that it runs first on teardown: the interrupts
> + * must be masked while the parent interrupt is still requested. The
> + * shift engine is stopped by axiado_sgpio_disable(), registered before
> + * the hardware was programmed so that it also covers failures during
> + * probe.
> + */
In this case it would actually be clearer to just have it in the .remove()
callback which runs before devres unwind. You don't get anything out of using
devres here wrt error handling as it's already the bottom of probe().
Fix it only if you'll need to send another version, otherwise LGTM.
Bart
> + return devm_add_action_or_reset(&pdev->dev, axiado_sgpio_mask_irqs,
> + sgpio);
> +}
> +
> +static const struct of_device_id axiado_sgpio_of_match[] = {
> + { .compatible = "axiado,ax3005-sgpio" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, axiado_sgpio_of_match);
> +
> +static struct platform_driver axiado_sgpio_driver = {
> + .driver = {
> + .name = KBUILD_MODNAME,
> + .of_match_table = axiado_sgpio_of_match,
> + },
> + .probe = axiado_sgpio_probe,
> +};
> +module_platform_driver(axiado_sgpio_driver);
> +
> +MODULE_DESCRIPTION("Axiado AX3005 Serial GPIO Driver");
> +MODULE_AUTHOR("Axiado Corporation");
> +MODULE_LICENSE("GPL");
>
> --
> 2.34.1
>
>
Bart
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: gpio: add Axiado AX3005 SGPIO controller
2026-09-03 7:11 ` [PATCH v3 1/2] dt-bindings: gpio: add " Petar Stepanovic
@ 2026-09-03 17:36 ` Conor Dooley
0 siblings, 0 replies; 6+ messages in thread
From: Conor Dooley @ 2026-09-03 17:36 UTC (permalink / raw)
To: Petar Stepanovic
Cc: Tzu-Hao Wei, Swark Yang, Prasad Bolisetty, Linus Walleij,
Bartosz Golaszewski, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Harshit Shah, SriNavmani A, Karthikeyan Mitran,
Michael Walle, linux-gpio, devicetree, linux-arm-kernel,
linux-kernel, openbmc
[-- Attachment #1: Type: text/plain, Size: 577 bytes --]
On Thu, Sep 03, 2026 at 12:11:32AM -0700, Petar Stepanovic wrote:
> Add a Devicetree binding for the SGPIO controller found on the
> Axiado AX3005 SoC.
>
> Each SGPIO position provides one fixed-direction input GPIO and one
> fixed-direction output GPIO. Input GPIOs support edge-triggered
> interrupts.
>
> The controller serializes output values and samples input values over
> dedicated data, clock, and latch signals.
>
> Signed-off-by: Petar Stepanovic <pstepanovic@axiado.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-03 17:36 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 7:11 [PATCH v3 0/2] gpio: Add support for Axiado AX3005 SGPIO controller Petar Stepanovic
2026-09-03 7:11 ` [PATCH v3 1/2] dt-bindings: gpio: add " Petar Stepanovic
2026-09-03 17:36 ` Conor Dooley
2026-09-03 7:11 ` [PATCH v3 2/2] gpio: axiado: add AX3005 SGPIO controller support Petar Stepanovic
2026-09-03 7:22 ` sashiko-bot
2026-09-03 8:59 ` Bartosz Golaszewski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox