Skip to contentSkip to conversation

gpio: renesas: Add support for GPIO and related interrupts in RZ/N1 SoC

PATCH Superseded Herve Codina (Schneider Electric) 5 participants 20 messages Oct 20, 2025 – Oct 26, 2025

Subsystems (5)

v5 of 8 Superseded Latest is v8 →

lore
Share & tools
lore
#0 lore raw

v5 cover gpio: renesas: Add support for GPIO and related interrupts in RZ/N1 SoC

Hi,

This series adds support for GPIO and GPIO IRQ mux available in the RZ/N1 SoCs.

The first patches in this series are related to a new helper introduced to parse an interrupt-map property.
- patch 1: Introduce the helper (for_each_of_imap_item)
- patch 2: Add a unittest for the new helper
- patch 3 and 4: convert existing drivers to use this new helper

Patch 5 adds support for GPIO (device-tree description)

The last patches (6, 7 and 8) of the series are related to GPIO interrupts and GPIO IRQ multiplexer.

In the RZ/N1 SoCs, GPIO interrupts are wired to a GPIO IRQ multiplexer.

This multiplexer does nothing but select 8 GPIO IRQ lines out of the 96 available to wire them to the GIC input lines.

One upstreaming attempt have been done previously by Phil Edworthy [1] but the series has never been applied.

Based on my understanding, I have fully reworked the driver proposed by Phil and removed the IRQ domain. Indeed, the device doesn't handle interrupts. It just routes signals.

Also, as an interrupt-map property is used, the driver cannot be involved as an interrupt controller itself. It is a nexus node.

With that in mind,
- Patch 6 is related to the irq-mux binding.

- Patch 7 introduces the irq-mux driver.
This driver uses the 'for_each_of_imap_item' helper introduced previously. Indeed, the lines routing is defined by the interrupt-map property and the driver needs to set registers to apply this routing.

- Patch 8 is the RZ/N1 device-tree description update to have the support for the GPIO interrupts.

[1] https://lore.kernel.org/all/[email protected]/

Best regards,
Hervé

Changes v4 -> v5
v4: https://lore.kernel.org/lkml/[email protected]/

Rebase on top of v6.18-rc1

Patches 1 to 3:
No changes

Patch 4:
Fix conflict.
Add 'Tested-by: Wolfram Sang'.

Patch 5:
Add 'Reviewed-by: Bartosz Golaszewski'.

Patch 6:
Add 'Reviewed-by: Wolfram Sang'.

Patch 7:
Add a missing 'const' qualifier.
Remove an unneeded dev_err_probe() call.
Add a check to avoid setting the same output line multiple times.

Patch 8:
Update comment
Add 'Reviewed-by: Bartosz Golaszewski'

Changes v3 -> v4
v3: https://lore.kernel.org/lkml/[email protected]/

Patch 1:
- Add 'Tested-by: Wolfram Sang'

Patch 2..5:
- No changes

Patch 6:
- Add minItems and maxItems
- Update the 'interrup-map' description

Patch 7:
- Use rzn1_irqmux prefix instead of irqmux.
- Introduce rzn1_irqmux_output_lines[] to give the mapping between the interrupt output line index and the GIC controller interrupt number.
- Remove of_irq_count() call and related checks

Patch 8:
- Describe the irq mux node using a reduced (one item) interrupt-map property.

Changes v2 -> v3
v2: https://lore.kernel.org/lkml/[email protected]/

Reordered patches as suggested by Thomas Gleixner.

Patch 1: (3 in v2)
- Replace a wrong 'extern' by 'static inline' in of_irq.h (detected by test robots)

Patch 2: (4 in v2)
Patch 3: (5 in v2)
Patch 4: (6 in v2)
- No changes

Patch 5: (1 in v2)
- Add 'Reviewed-by: Wolfram Sang'
- Add 'Tested-by: Wolfram Sang'

Patch 6: (2 in v2)
- Add '#address-cells = <0>;' in the interrupt-controller node present in the example.

Patch 7:
Patch 8:
- No changes

Changes v1 -> v2
v1: https://lore.kernel.org/lkml/[email protected]/

Rebase on top of v6.17-rc5

Patch 1 in v1
- Removed in v2 (no need for RZ/N1 compatible strings).

Patch 1 (2 in v1)
- Fix node names (issue reported by Rob's bot)
- Fix compatible RZ/N1 compatible strings
- Removed undocumented and unused 'bank-name' properties

Patch 2 (3 in v1)
- Remove 'interrupts' property
- Update 'interrupt-map' description

Patch 3 (4 in v1)
- Rework of_irq_foreach_imap() to provide the for_each_of_imap_item iterator (similar to for_each_of_range)

Patch 4 (new in v2)
- Add a unittest for for_each_of_imap_item

Patch 5 (new in v2)
- Convert irqchip/ls-extirq to use for_each_of_imap_item

Patch 6 (new in v2)
- Convert irqchip/renesas-rza1 to use for_each_of_imap_item

Patch 7 (5 in v1)
- Use for_each_of_imap_item
- Remove 'interrupts' property usage

Patch 8 (6 in v1)
- Remove 'interrupts' property

Herve Codina (Schneider Electric) (8):
of/irq: Introduce for_each_of_imap_item
of: unittest: Add a test case for for_each_of_imap_item iterator irqchip/ls-extirq: Use for_each_of_imap_item iterator irqchip/renesas-rza1: Use for_each_of_imap_item iterator
ARM: dts: r9a06g032: Add GPIO controllers
dt-bindings: soc: renesas: Add the Renesas RZ/N1 GPIO Interrupt
Multiplexer
soc: renesas: Add support for Renesas RZ/N1 GPIO Interrupt Multiplexer
ARM: dts: r9a06g032: Add support for GPIO interrupts

 .../soc/renesas/renesas,rzn1-gpioirqmux.yaml  |  87 ++++++++++
 arch/arm/boot/dts/renesas/r9a06g032.dtsi      | 162 ++++++++++++++++++
 drivers/irqchip/irq-ls-extirq.c               |  47 ++---
 drivers/irqchip/irq-renesas-rza1.c            |  43 ++---
 drivers/of/irq.c                              |  70 ++++++++
 .../of/unittest-data/tests-interrupts.dtsi    |   9 +
 drivers/of/unittest.c                         | 116 +++++++++++++
 drivers/soc/renesas/Kconfig                   |   4 +
 drivers/soc/renesas/Makefile                  |   1 +
 drivers/soc/renesas/rzn1_irqmux.c             | 150 ++++++++++++++++
 include/linux/of_irq.h                        |  41 ++++-
 11 files changed, 672 insertions(+), 58 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/soc/renesas/renesas,rzn1-gpioirqmux.yaml
 create mode 100644 drivers/soc/renesas/rzn1_irqmux.c
Signature · 1 line
2.51.0
#1 lore raw

PATCH v5 1/8 of/irq: Introduce for_each_of_imap_item

for_each_of_imap_item is an iterator designed to help a driver to parse an interrupt-map property.

Indeed some drivers need to know details about the interrupt mapping described in the device-tree in order to set internal registers accordingly.

---
 drivers/of/irq.c       | 70 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/of_irq.h | 41 ++++++++++++++++++++++++-
 2 files changed, 110 insertions(+), 1 deletion(-)
drivers/of/irq.c +70 -0
index 65c3c23255b7..ca7d51d07047 100644
@@ -157,6 +157,76 @@ const __be32 *of_irq_parse_imap_parent(const __be32 *imap, int len, struct of_ph
 	return imap;
 }
 
+int of_imap_parser_init(struct of_imap_parser *parser, struct device_node *node,
+			struct of_imap_item *item)
+{
+	int imaplen;
+	u32 tmp;
+	int ret;
+
+	/*
+	 * parent_offset is the offset where the parent part is starting.
+	 * In other words, the offset where the parent interrupt controller
+	 * phandle is present.
+	 *
+	 * Compute this offset (child #interrupt-cells + child #address-cells)
+	 */
+	parser->parent_offset = of_bus_n_addr_cells(node);
+
+	ret = of_property_read_u32(node, "#interrupt-cells", &tmp);
+	if (ret)
+		return ret;
+
+	parser->parent_offset += tmp;
+
+	if (WARN(parser->parent_offset > ARRAY_SIZE(item->child_imap),
+		 "child part size = %u, cannot fit in array of %zu items",
+		 parser->parent_offset, ARRAY_SIZE(item->child_imap)))
+		return -EINVAL;
+
+	parser->imap = of_get_property(node, "interrupt-map", &imaplen);
+	if (!parser->imap)
+		return -ENOENT;
+
+	imaplen /= sizeof(*parser->imap);
+	parser->imap_end = parser->imap + imaplen;
+
+	memset(item, 0, sizeof(*item));
+	item->child_imap_count = parser->parent_offset;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(of_imap_parser_init);
+
+struct of_imap_item *of_imap_parser_one(struct of_imap_parser *parser,
+					struct of_imap_item *item)
+{
+	const __be32 *imap_parent, *imap_next;
+	int i;
+
+	/* Release previously get parent node */
+	of_node_put(item->parent_args.np);
+
+	if (parser->imap + parser->parent_offset + 1 >= parser->imap_end)
+		return NULL;
+
+	imap_parent = parser->imap + parser->parent_offset;
+
+	imap_next = of_irq_parse_imap_parent(imap_parent,
+					     parser->imap_end - imap_parent,
+					     &item->parent_args);
+	if (!imap_next)
+		return NULL;
+
+	for (i = 0; i < parser->parent_offset; i++)
+		item->child_imap[i] = be32_to_cpu(*(parser->imap + i));
+
+	parser->imap = imap_next;
+
+	return item;
+}
+EXPORT_SYMBOL_GPL(of_imap_parser_one);
+
 /**
  * of_irq_parse_raw - Low level interrupt tree parsing
  * @addr:	address specifier (start of "reg" property of the device) in be32 format
include/linux/of_irq.h +40 -1

59 lines not on this page · raw message

Signature · 1 line
2.51.0
maintainer Reviewed-by on v5 1/8
#2 lore raw

Re: [PATCH v5 1/8] of/irq: Introduce for_each_of_imap_item

Quoted 7 lines · Herve Codina (Schneider Electric)

for_each_of_imap_item is an iterator designed to help a driver to parse an interrupt-map property.

Indeed some drivers need to know details about the interrupt mapping described in the device-tree in order to set internal registers accordingly.

Quoted 7 lines
---
 drivers/of/irq.c       | 70 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/of_irq.h | 41 ++++++++++++++++++++++++-
 2 files changed, 110 insertions(+), 1 deletion(-)
#3 lore raw

PATCH v5 2/8 of: unittest: Add a test case for for_each_of_imap_item iterator

In reply to Herve Codina (Schneider Electric)

Recently for_each_of_imap_item iterator has been introduce to help drivers in parsing the interrupt-map property.

Add a test case for this iterator.

---
 .../of/unittest-data/tests-interrupts.dtsi    |   9 ++
 drivers/of/unittest.c                         | 116 ++++++++++++++++++
 2 files changed, 125 insertions(+)
drivers/of/unittest-data/tests-interrupts.dtsi +9 -0
index 4ccb54f91c30..974f888c9b15 100644
@@ -50,6 +50,15 @@ test_intmap1: intmap1 {
 				interrupt-map = <0x5000 1 2 &test_intc0 15>;
 			};
 
+			intmap2 {
+				#interrupt-cells = <2>;
+				#address-cells = <0>;
+				interrupt-map = <1 11 &test_intc0 100>,
+						<2 22 &test_intc1 200 201 202>,
+						<3 33 &test_intc2 300 301>,
+						<4 44 &test_intc2 400 401>;
+			};
+
 			test_intc_intmap0: intc-intmap0 {
 				#interrupt-cells = <1>;
 				#address-cells = <1>;
drivers/of/unittest.c +116 -0

128 lines not on this page · raw message

Signature · 1 line
2.51.0
maintainer Reviewed-by on v5 2/8
#4 lore raw

Re: [PATCH v5 2/8] of: unittest: Add a test case for for_each_of_imap_item iterator

Quoted 11 lines · Herve Codina (Schneider Electric)

Recently for_each_of_imap_item iterator has been introduce to help drivers in parsing the interrupt-map property.

Add a test case for this iterator.

---
 .../of/unittest-data/tests-interrupts.dtsi    |   9 ++
 drivers/of/unittest.c                         | 116 ++++++++++++++++++
 2 files changed, 125 insertions(+)
#5 lore raw

PATCH v5 3/8 irqchip/ls-extirq: Use for_each_of_imap_item iterator

In reply to Herve Codina (Schneider Electric)

The ls-extirq driver parses the interrupt-map property. It does it using open code.

Recently for_each_of_imap_item iterator has been introduce to help drivers in this parsing.

Convert the ls-extirq driver to use the for_each_of_imap_item iterator instead of open code.

---
 drivers/irqchip/irq-ls-extirq.c | 47 ++++++++++++---------------------
 1 file changed, 17 insertions(+), 30 deletions(-)
drivers/irqchip/irq-ls-extirq.c +17 -30
index 50a7b38381b9..ed8755777349 100644
@@ -125,45 +125,32 @@ static const struct irq_domain_ops extirq_domain_ops = {
 static int
 ls_extirq_parse_map(struct ls_extirq_data *priv, struct device_node *node)
 {
-	const __be32 *map;
-	u32 mapsize;
+	struct of_imap_parser imap_parser;
+	struct of_imap_item imap_item;
 	int ret;
 
-	map = of_get_property(node, "interrupt-map", &mapsize);
-	if (!map)
-		return -ENOENT;
-	if (mapsize % sizeof(*map))
-		return -EINVAL;
-	mapsize /= sizeof(*map);
+	ret = of_imap_parser_init(&imap_parser, node, &imap_item);
+	if (ret)
+		return ret;
 
-	while (mapsize) {
+	for_each_of_imap_item(&imap_parser, &imap_item) {
 		struct device_node *ipar;
-		u32 hwirq, intsize, j;
+		u32 hwirq;
+		int i;
 
-		if (mapsize < 3)
-			return -EINVAL;
-		hwirq = be32_to_cpup(map);
-		if (hwirq >= MAXIRQ)
+		hwirq = imap_item.child_imap[0];
+		if (hwirq >= MAXIRQ) {
+			of_node_put(imap_item.parent_args.np);
 			return -EINVAL;
+		}
 		priv->nirq = max(priv->nirq, hwirq + 1);
 
-		ipar = of_find_node_by_phandle(be32_to_cpup(map + 2));
-		map += 3;
-		mapsize -= 3;
-		if (!ipar)
-			return -EINVAL;
-		priv->map[hwirq].fwnode = &ipar->fwnode;
-		ret = of_property_read_u32(ipar, "#interrupt-cells", &intsize);
-		if (ret)
-			return ret;
-
-		if (intsize > mapsize)
-			return -EINVAL;
+		ipar = of_node_get(imap_item.parent_args.np);
+		priv->map[hwirq].fwnode = of_fwnode_handle(ipar);
 
-		priv->map[hwirq].param_count = intsize;
-		for (j = 0; j < intsize; ++j)
-			priv->map[hwirq].param[j] = be32_to_cpup(map++);
-		mapsize -= intsize;
+		priv->map[hwirq].param_count = imap_item.parent_args.args_count;
+		for (i = 0; i < priv->map[hwirq].param_count; i++)
+			priv->map[hwirq].param[i] = imap_item.parent_args.args[i];
 	}
 	return 0;
 }
Signature · 1 line
2.51.0
#6 lore raw

PATCH v5 4/8 irqchip/renesas-rza1: Use for_each_of_imap_item iterator

In reply to Herve Codina (Schneider Electric)

The renesas-rza1 driver parses the interrupt-map property. It does it using open code.

Recently for_each_of_imap_item iterator has been introduce to help drivers in this parsing.

Convert the renesas-rza1 driver to use the for_each_of_imap_item iterator instead of open code.

---
 drivers/irqchip/irq-renesas-rza1.c | 43 +++++++++++-------------------
 1 file changed, 16 insertions(+), 27 deletions(-)
drivers/irqchip/irq-renesas-rza1.c +16 -27
index 6047a524ac77..370d968b2398 100644
@@ -142,47 +142,36 @@ static const struct irq_domain_ops rza1_irqc_domain_ops = {
 static int rza1_irqc_parse_map(struct rza1_irqc_priv *priv,
 			       struct device_node *gic_node)
 {
+	struct of_imap_parser imap_parser;
 	struct device *dev = priv->dev;
-	unsigned int imaplen, i, j;
+	struct of_imap_item imap_item;
 	struct device_node *ipar;
-	const __be32 *imap;
-	u32 intsize;
+	unsigned int j;
+	u32 i = 0;
 	int ret;
 
-	imap = of_get_property(dev->of_node, "interrupt-map", &imaplen);
-	if (!imap)
-		return -EINVAL;
-
-	for (i = 0; i < IRQC_NUM_IRQ; i++) {
-		if (imaplen < 3)
-			return -EINVAL;
+	ret = of_imap_parser_init(&imap_parser, dev->of_node, &imap_item);
+	if (ret)
+		return ret;
 
+	for_each_of_imap_item(&imap_parser, &imap_item) {
 		/* Check interrupt number, ignore sense */
-		if (be32_to_cpup(imap) != i)
+		if (imap_item.child_imap[0] != i) {
+			of_node_put(imap_item.parent_args.np);
 			return -EINVAL;
+		}
 
-		ipar = of_find_node_by_phandle(be32_to_cpup(imap + 2));
+		ipar  = imap_item.parent_args.np;
 		if (ipar != gic_node) {
 			of_node_put(ipar);
 			return -EINVAL;
 		}
 
-		imap += 3;
-		imaplen -= 3;
-
-		ret = of_property_read_u32(ipar, "#interrupt-cells", &intsize);
-		of_node_put(ipar);
-		if (ret)
-			return ret;
-
-		if (imaplen < intsize)
-			return -EINVAL;
-
-		priv->map[i].args_count = intsize;
-		for (j = 0; j < intsize; j++)
-			priv->map[i].args[j] = be32_to_cpup(imap++);
+		priv->map[i].args_count = imap_item.parent_args.args_count;
+		for (j = 0; j < priv->map[i].args_count; j++)
+			priv->map[i].args[j] = imap_item.parent_args.args[j];
 
-		imaplen -= intsize;
+		i++;
 	}
 
 	return 0;
Signature · 1 line
2.51.0
#7 lore raw

PATCH v5 5/8 ARM: dts: r9a06g032: Add GPIO controllers

In reply to Herve Codina (Schneider Electric)

Add GPIO controllers (Synosys DesignWare IPs) available in the r9a06g032 (RZ/N1D) SoC.

---
 arch/arm/boot/dts/renesas/r9a06g032.dtsi | 121 +++++++++++++++++++++++
 1 file changed, 121 insertions(+)
arch/arm/boot/dts/renesas/r9a06g032.dtsi +121 -0
index 13a60656b044..da977cdd8487 100644
@@ -499,6 +499,127 @@ gic: interrupt-controller@44101000 {
 				<GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_HIGH)>;
 		};
 
+		/*
+		 * The GPIO mapping to the corresponding pins is not obvious.
+		 * See the hardware documentation for details.
+		 */
+		gpio0: gpio@5000b000 {
+			compatible = "snps,dw-apb-gpio";
+			reg = <0x5000b000 0x80>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			clocks = <&sysctrl R9A06G032_HCLK_GPIO0>;
+			clock-names = "bus";
+			status = "disabled";
+
+			/* GPIO0a[0]      connected to pin  GPIO0      */
+			/* GPIO0a[1..2]   connected to pins GPIO3..4   */
+			/* GPIO0a[3..4]   connected to pins GPIO9..10  */
+			/* GPIO0a[5]      connected to pin  GPIO12     */
+			/* GPIO0a[6..7]   connected to pins GPIO15..16 */
+			/* GPIO0a[8..9]   connected to pins GPIO21..22 */
+			/* GPIO0a[10]     connected to pin  GPIO24     */
+			/* GPIO0a[11..12] connected to pins GPIO27..28 */
+			/* GPIO0a[13..14] connected to pins GPIO33..34 */
+			/* GPIO0a[15]     connected to pin  GPIO36     */
+			/* GPIO0a[16..17] connected to pins GPIO39..40 */
+			/* GPIO0a[18..19] connected to pins GPIO45..46 */
+			/* GPIO0a[20]     connected to pin  GPIO48     */
+			/* GPIO0a[21..22] connected to pins GPIO51..52 */
+			/* GPIO0a[23..24] connected to pins GPIO57..58 */
+			/* GPIO0a[25..31] connected to pins GPIO62..68 */
+			gpio0a: gpio-port@0 {
+				compatible = "snps,dw-apb-gpio-port";
+				gpio-controller;
+				#gpio-cells = <2>;
+				snps,nr-gpios = <32>;
+				reg = <0>;
+			};
+
+			/* GPIO0b[0..1]   connected to pins GPIO1..2   */
+			/* GPIO0b[2..5]   connected to pins GPIO5..8   */
+			/* GPIO0b[6]      connected to pin  GPIO11     */
+			/* GPIO0b[7..8]   connected to pins GPIO13..14 */
+			/* GPIO0b[9..12]  connected to pins GPIO17..20 */
+			/* GPIO0b[13]     connected to pin  GPIO23     */
+			/* GPIO0b[14..15] connected to pins GPIO25..26 */
+			/* GPIO0b[16..19] connected to pins GPIO29..32 */
+			/* GPIO0b[20]     connected to pin  GPIO35     */
+			/* GPIO0b[21..22] connected to pins GPIO37..38 */
+			/* GPIO0b[23..26] connected to pins GPIO41..44 */
+			/* GPIO0b[27]     connected to pin  GPIO47     */
+			/* GPIO0b[28..29] connected to pins GPIO49..50 */
+			/* GPIO0b[30..31] connected to pins GPIO53..54 */
+			gpio0b: gpio-port@1 {
+				compatible = "snps,dw-apb-gpio-port";
+				gpio-controller;
+				#gpio-cells = <2>;
+				snps,nr-gpios = <32>;
+				reg = <1>;
+			};
+		};
+
+		gpio1: gpio@5000c000 {
+			compatible = "snps,dw-apb-gpio";
+			reg = <0x5000c000 0x80>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			clocks = <&sysctrl R9A06G032_HCLK_GPIO1>;
+			clock-names = "bus";
+			status = "disabled";
+
+			/* GPIO1a[0..4]  connected to pins GPIO69..73 */
+			/* GPIO1a[5..31] connected to pins GPIO95..121 */
+			gpio1a: gpio-port@0 {
+				compatible = "snps,dw-apb-gpio-port";
+				gpio-controller;
+				#gpio-cells = <2>;
+				snps,nr-gpios = <32>;
+				reg = <0>;
+			};
+
+			/* GPIO1b[0..1]   connected to pins GPIO55..56 */
+			/* GPIO1b[2..4]   connected to pins GPIO59..61 */
+			/* GPIO1b[5..25]  connected to pins GPIO74..94 */
+			/* GPIO1b[26..31] connected to pins GPIO150..155 */
+			gpio1b: gpio-port@1 {
+				compatible = "snps,dw-apb-gpio-port";
+				gpio-controller;
+				#gpio-cells = <2>;
+				snps,nr-gpios = <32>;
+				reg = <1>;
+			};
+		};
+
+		gpio2: gpio@5000d000 {
+			compatible = "snps,dw-apb-gpio";
+			reg = <0x5000d000 0x80>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			clocks = <&sysctrl R9A06G032_HCLK_GPIO2>;
+			clock-names = "bus";
+			status = "disabled";
+
+			/* GPIO2a[0..27]  connected to pins GPIO122..149 */
+			/* GPIO2a[28..31] connected to pins GPIO156..159 */
+			gpio2a: gpio-port@0 {
+				compatible = "snps,dw-apb-gpio-port";
+				gpio-controller;
+				#gpio-cells = <2>;
+				snps,nr-gpios = <32>;
+				reg = <0>;
+			};
+
+			/* GPIO2b[0..9] connected to pins GPIO160..169 */
+			gpio2b: gpio-port@1 {
+				compatible = "snps,dw-apb-gpio-port";
+				gpio-controller;
+				#gpio-cells = <2>;
+				snps,nr-gpios = <10>;
+				reg = <1>;
+			};
+		};
+
 		can0: can@52104000 {
 			compatible = "renesas,r9a06g032-sja1000", "renesas,rzn1-sja1000";
 			reg = <0x52104000 0x800>;
Signature · 1 line
2.51.0
#8 lore raw

PATCH v5 6/8 dt-bindings: soc: renesas: Add the Renesas RZ/N1 GPIO Interrupt Multiplexer

In reply to Herve Codina (Schneider Electric)

On the Renesas RZ/N1 SoC, GPIOs can generate interruptions. Those interruption lines are multiplexed by the GPIO Interrupt Multiplexer in order to map 32 * 3 GPIO interrupt lines to 8 GIC interrupt lines.

The GPIO interrupt multiplexer IP does nothing but select 8 GPIO IRQ lines out of the 96 available to wire them to the GIC input lines.

---
 .../soc/renesas/renesas,rzn1-gpioirqmux.yaml  | 87 +++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/soc/renesas/renesas,rzn1-gpioirqmux.yaml
Documentation/devicetree/bindings/soc/renesas/renesas,rzn1-gpioirqmux.yaml new file +87 -0
new file mode 100644
index 000000000000..1a31c11bc3b4
@@ -0,0 +1,87 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/renesas/renesas,rzn1-gpioirqmux.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Renesas RZ/N1 SoCs GPIO Interrupt Multiplexer
+
+description: |
+   The Renesas RZ/N1 GPIO Interrupt Multiplexer multiplexes GPIO interrupt
+   lines to the interrupt controller available in the SoC.
+
+   It selects up to 8 of the 96 GPIO interrupt lines available and connect them
+   to 8 output interrupt lines.
+
+maintainers:
+  - Herve Codina <[email protected]>
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - renesas,r9a06g032-gpioirqmux
+      - const: renesas,rzn1-gpioirqmux
+
+  reg:
+    maxItems: 1
+
+  "#address-cells":
+    const: 0
+
+  "#interrupt-cells":
+    const: 1
+
+  interrupt-map-mask:
+    items:
+      - const: 0x7f
+
+  interrupt-map:
+    description: |
+      Specifies the mapping from external GPIO interrupt lines to the output
+      interrupts. The array has up to 8 items defining the mapping related to
+      the output line 0 (GIC 103) up to the output line 7 (GIC 110).
+
+      The child interrupt number set in arrays items is computed using the
+      following formula:
+          gpio_bank * 32 + gpio_number
+      with:
+        - gpio_bank: The GPIO bank number
+            - 0 for GPIO0A,
+            - 1 for GPIO1A,
+            - 2 for GPIO2A
+        - gpio_number: Number of the gpio in the bank (0..31)
+    minItems: 1
+    maxItems: 8
+
+required:
+  - compatible
+  - reg
+  - "#address-cells"
+  - "#interrupt-cells"
+  - interrupt-map-mask
+  - interrupt-map
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    gic: interrupt-controller {
+        interrupt-controller;
+        #address-cells = <0>;
+        #interrupt-cells = <3>;
+    };
+
+    interrupt-controller@51000480 {
+        compatible = "renesas,r9a06g032-gpioirqmux", "renesas,rzn1-gpioirqmux";
+        reg = <0x51000480 0x20>;
+        #address-cells = <0>;
+        #interrupt-cells = <1>;
+        interrupt-map-mask = <0x7f>;
+        interrupt-map =
+            <32 &gic GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>, /* line 0, GPIO1A.0 */
+            <89 &gic GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>, /* line 1, GPIO2A.25 */
+            <9 &gic GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>; /* line 3, GPIO0A.9 */
+    };
Signature · 1 line
2.51.0
maintainer Reviewed-by on v5 6/8
#9 lore raw

Re: [PATCH v5 6/8] dt-bindings: soc: renesas: Add the Renesas RZ/N1 GPIO Interrupt Multiplexer

Quoted 7 lines · Herve Codina (Schneider Electric)

On the Renesas RZ/N1 SoC, GPIOs can generate interruptions. Those interruption lines are multiplexed by the GPIO Interrupt Multiplexer in order to map 32 * 3 GPIO interrupt lines to 8 GIC interrupt lines.

The GPIO interrupt multiplexer IP does nothing but select 8 GPIO IRQ lines out of the 96 available to wire them to the GIC input lines.

Quoted 7 lines
---
 .../soc/renesas/renesas,rzn1-gpioirqmux.yaml  | 87 +++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/soc/renesas/renesas,rzn1-gpioirqmux.yaml
#10 lore raw

PATCH v5 7/8 soc: renesas: Add support for Renesas RZ/N1 GPIO Interrupt Multiplexer

In reply to Herve Codina (Schneider Electric)

On the Renesas RZ/N1 SoC, GPIOs can generate interruptions. Those interruption lines are multiplexed by the GPIO Interrupt Multiplexer in order to map 32 * 3 GPIO interrupt lines to 8 GIC interrupt lines.

The GPIO interrupt multiplexer IP does nothing but select 8 GPIO IRQ lines out of the 96 available to wire them to the GIC input lines.

---
 drivers/soc/renesas/Kconfig       |   4 +
 drivers/soc/renesas/Makefile      |   1 +
 drivers/soc/renesas/rzn1_irqmux.c | 150 ++++++++++++++++++++++++++++++
 3 files changed, 155 insertions(+)
 create mode 100644 drivers/soc/renesas/rzn1_irqmux.c
drivers/soc/renesas/Kconfig +4 -0
index 340a1ff7e92b..71865778b058 100644
@@ -62,6 +62,7 @@ config ARCH_RZN1
 	select PM
 	select PM_GENERIC_DOMAINS
 	select ARM_AMBA
+	select RZN1_IRQMUX
 
 if ARM && ARCH_RENESAS
 
@@ -459,6 +460,9 @@ config PWC_RZV2M
 config RST_RCAR
 	bool "Reset Controller support for R-Car" if COMPILE_TEST
 
+config RZN1_IRQMUX
+	bool "Renesas RZ/N1 GPIO IRQ multiplexer support" if COMPILE_TEST
+
 config SYSC_RZ
 	bool "System controller for RZ SoCs" if COMPILE_TEST
 	select MFD_SYSCON
drivers/soc/renesas/Makefile +1 -0
index 3bdcc6a395d5..daa932c7698d 100644
@@ -14,4 +14,5 @@ obj-$(CONFIG_SYS_R9A09G057)	+= r9a09g057-sys.o
 # Family
 obj-$(CONFIG_PWC_RZV2M)		+= pwc-rzv2m.o
 obj-$(CONFIG_RST_RCAR)		+= rcar-rst.o
+obj-$(CONFIG_RZN1_IRQMUX)		+= rzn1_irqmux.o
 obj-$(CONFIG_SYSC_RZ)		+= rz-sysc.o
drivers/soc/renesas/rzn1_irqmux.c new file +150 -0

150 lines not on this page · raw message

Signature · 1 line
2.51.0
#11 lore raw

Re: [PATCH v5 7/8] soc: renesas: Add support for Renesas RZ/N1 GPIO Interrupt Multiplexer

Hi Hervé,

Quoted 8 lines · Herve Codina (Schneider Electric)

On the Renesas RZ/N1 SoC, GPIOs can generate interruptions. Those interruption lines are multiplexed by the GPIO Interrupt Multiplexer in order to map 32 * 3 GPIO interrupt lines to 8 GIC interrupt lines.

The GPIO interrupt multiplexer IP does nothing but select 8 GPIO IRQ lines out of the 96 available to wire them to the GIC input lines.

Thanks for your patch!

drivers/soc/renesas/Makefile obj-$(CONFIG_SYS_R9A09G057) += r9a09g057-sys.o · line 17

Quoted 7 lines
drivers/soc/renesas/Makefile +1 -0
@@ -14,4 +14,5 @@ obj-$(CONFIG_SYS_R9A09G057)   += r9a09g057-sys.o
 # Family
 obj-$(CONFIG_PWC_RZV2M)                += pwc-rzv2m.o
 obj-$(CONFIG_RST_RCAR)         += rcar-rst.o
+obj-$(CONFIG_RZN1_IRQMUX)              += rzn1_irqmux.o

One TAB too much.

Quoted 17 lines
drivers/soc/renesas/rzn1_irqmux.c new file +14 -0
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RZ/N1 GPIO Interrupt Multiplexer
+ *
+ * Copyright 2025 Schneider Electric
+ * Author: Herve Codina <[email protected]>
+ */
+
+#include <linux/bitops.h>
+#include <linux/build_bug.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>

drivers/soc/renesas/rzn1_irqmux.c line 24

Quoted 10 lines
drivers/soc/renesas/rzn1_irqmux.c new file +10 -0
+#include <linux/platform_device.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+/*
+ * The array index is the output line index, the value at the index is the
+ * GIC SPI interrupt number the output line is connected to.
+ */
+static const u32 rzn1_irqmux_output_lines[] = {
+       103, 104, 105, 106, 107, 108, 109, 110
+};

I did read the discussion with Wolfram, but the flexibility (and overhead) provided by this array sounds a bit overkill to me.

What about just defining:

    #define RZN1_IRQMUX_SPI_BASE    103
    #define RZN1_IRQMUX_NUM_IRQS    8

?

If/when a new SoC with a similar setup ever arrives, you can probably just replace the constants above by variables inside SoC-specific match data. And if the new mapping would be non-contiguous, you can still revive this array ;-)

More about this below...

Quoted 19 lines
drivers/soc/renesas/rzn1_irqmux.c new file +19 -0
+
+static int rzn1_irqmux_parent_args_to_line_index(struct device *dev,
+                                                const struct of_phandle_args *parent_args,
+                                                const u32 output_lines[],
+                                                unsigned int output_lines_count)
+{
+       unsigned int i;
+
+       /*
+        * The parent interrupt should be one of the GIC controller.
+        * Three arguments must be provided.
+        *  - args[0]: GIC_SPI
+        *  - args[1]: The GIC interrupt number
+        *  - args[2]: The interrupt flags
+        *
+        * We retrieve the line index based on the GIC interrupt number
+        * provided and rzn1_irqmux_output_line[] mapping array.
+        */
+

drivers/soc/renesas/rzn1_irqmux.c line 53

Quoted 10 lines
drivers/soc/renesas/rzn1_irqmux.c new file +10 -0
+       if (parent_args->args_count != 3 ||
+           parent_args->args[0] != GIC_SPI) {
+               dev_err(dev, "Invalid interrupt-map item\n");
+               return -EINVAL;
+       }
+
+       for (i = 0; i < output_lines_count; i++) {
+               if (parent_args->args[1] == output_lines[i])
+                       return i;
+       }

... then this loop can be replaced by two simple comparisons.

drivers/soc/renesas/rzn1_irqmux.c line 63

Quoted 10 lines
drivers/soc/renesas/rzn1_irqmux.c new file +10 -0
+
+       dev_err(dev, "Invalid GIC interrupt %u\n", parent_args->args[1]);
+       return -EINVAL;
+}
+
+static int rzn1_irqmux_setup(struct device *dev, struct device_node *np, u32 __iomem *regs)
+{
+       struct of_imap_parser imap_parser;
+       struct of_imap_item imap_item;
+       unsigned long index_done = 0;

Perhaps "DECLARE_BITMAP(index_done, RZN1_IRQMUX_NUM_IRQS)", so the BITS_PER_LONG limit can be lifted, without any cost?

Quoted 13 lines
drivers/soc/renesas/rzn1_irqmux.c new file +13 -0
+       int index;
+       int ret;
+       u32 tmp;
+
+       /* We support only #interrupt-cells = <1> and #address-cells = <0> */
+       ret = of_property_read_u32(np, "#interrupt-cells", &tmp);
+       if (ret)
+               return ret;
+       if (tmp != 1)
+               return -EINVAL;
+
+       ret = of_property_read_u32(np, "#address-cells", &tmp);
+       if (ret)

drivers/soc/renesas/rzn1_irqmux.c line 86

Quoted 10 lines
drivers/soc/renesas/rzn1_irqmux.c new file +10 -0
+               return ret;
+       if (tmp != 0)
+               return -EINVAL;
+
+       ret = of_imap_parser_init(&imap_parser, np, &imap_item);
+       if (ret)
+               return ret;
+
+       /* 8 output lines are available */
+       BUILD_BUG_ON(ARRAY_SIZE(rzn1_irqmux_output_lines) != 8);

... then this check can be removed, too.

drivers/soc/renesas/rzn1_irqmux.c line 92

Quoted 6 lines
drivers/soc/renesas/rzn1_irqmux.c new file +6 -0
+
+       /*
+        * index_done is an unsigned long integer. Be sure that no buffer
+        * overflow can occur.
+        */
+       BUILD_BUG_ON(ARRAY_SIZE(rzn1_irqmux_output_lines) > BITS_PER_LONG);

Currently this is less strict than the check above, so a bit useless?

Quoted 16 lines
drivers/soc/renesas/rzn1_irqmux.c new file +16 -0
+
+       for_each_of_imap_item(&imap_parser, &imap_item) {
+               index = rzn1_irqmux_parent_args_to_line_index(dev,
+                                                             &imap_item.parent_args,
+                                                             rzn1_irqmux_output_lines,
+                                                             ARRAY_SIZE(rzn1_irqmux_output_lines));
+               if (index < 0) {
+                       of_node_put(imap_item.parent_args.np);
+                       return index;
+               }
+
+               if (test_and_set_bit(index, &index_done)) {
+                       of_node_put(imap_item.parent_args.np);
+                       dev_err(dev, "Mux output line already defined\n");
+                       return -EINVAL;
+               }

drivers/soc/renesas/rzn1_irqmux.c line 118

Quoted 10 lines
drivers/soc/renesas/rzn1_irqmux.c new file +10 -0
+
+               /*
+                * The child #address-cells is 0 (already checked). The first
+                * value in imap item is the src hwirq.
+                */
+               writel(imap_item.child_imap[0], regs + index);
+       }
+
+       return 0;
+}

Gr{oetje,eeting}s,

Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds

#12 lore raw

Re: [PATCH v5 7/8] soc: renesas: Add support for Renesas RZ/N1 GPIO Interrupt Multiplexer

Hi Geert,

Quoted 15 lines · On Tue, 21 Oct 2025 15:05:35 +0200 Geert Uytterhoeven <[email protected]>…

Hi Hervé,

Quoted 8 lines · Herve Codina (Schneider Electric)

On the Renesas RZ/N1 SoC, GPIOs can generate interruptions. Those interruption lines are multiplexed by the GPIO Interrupt Multiplexer in order to map 32 * 3 GPIO interrupt lines to 8 GIC interrupt lines.

The GPIO interrupt multiplexer IP does nothing but select 8 GPIO IRQ lines out of the 96 available to wire them to the GIC input lines.

Thanks for your patch!

Quoted 9 lines

drivers/soc/renesas/Makefile obj-$(CONFIG_SYS_R9A09G057) += r9a09g057-sys.o · line 17

Quoted 7 lines
drivers/soc/renesas/Makefile +1 -0
@@ -14,4 +14,5 @@ obj-$(CONFIG_SYS_R9A09G057)   += r9a09g057-sys.o
 # Family
 obj-$(CONFIG_PWC_RZV2M)                += pwc-rzv2m.o
 obj-$(CONFIG_RST_RCAR)         += rcar-rst.o
+obj-$(CONFIG_RZN1_IRQMUX)              += rzn1_irqmux.o  

One TAB too much.

Yes indeed, will be removed.

Quoted 37 lines
Quoted 18 lines
drivers/soc/renesas/rzn1_irqmux.c new file +14 -0
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RZ/N1 GPIO Interrupt Multiplexer
+ *
+ * Copyright 2025 Schneider Electric
+ * Author: Herve Codina <[email protected]>
+ */
+
+#include <linux/bitops.h>
+#include <linux/build_bug.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>

drivers/soc/renesas/rzn1_irqmux.c line 24

Quoted 10 lines
drivers/soc/renesas/rzn1_irqmux.c new file +10 -0
+#include <linux/platform_device.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+/*
+ * The array index is the output line index, the value at the index is the
+ * GIC SPI interrupt number the output line is connected to.
+ */
+static const u32 rzn1_irqmux_output_lines[] = {
+       103, 104, 105, 106, 107, 108, 109, 110
+};  

I did read the discussion with Wolfram, but the flexibility (and overhead) provided by this array sounds a bit overkill to me.

What about just defining:

    #define RZN1_IRQMUX_SPI_BASE    103
    #define RZN1_IRQMUX_NUM_IRQS    8
Quoted 6 lines

?

If/when a new SoC with a similar setup ever arrives, you can probably just replace the constants above by variables inside SoC-specific match data. And if the new mapping would be non-contiguous, you can still revive this array ;-)

I have in mind a use case that can lead to a non-contiguous mapping.

The RZ/N1 SoC embeds a Cortex-M3 CPU. This CPU can use GPIOs and some of them for interrupt purpose. In that case, those GPIOs have to be routed to the interrupt line expected by the Cortex-M3.

And so, we have some interrupts reserved for CPUs running Linux and some others for the Cortex-M3.

Among those reserved interrupts may some are not used.

for instance:
Interrupt 103, 102: Reserved and used by Linux
Interrupt 103: Reserved for Linux but not used -> Hole in the mapping Interrupt 104: Reserved and used my Cortex-M3 (need to be routed by Linux)

I don't know if this use case is relevant but I think we should be too restrictive on the mapping and so accept holes.

With that in mind, I let you confirm that you still prefer to have a mapping without any holes. A future patch to support that is always possible.

Quoted 35 lines

More about this below...

Quoted 19 lines
drivers/soc/renesas/rzn1_irqmux.c new file +19 -0
+
+static int rzn1_irqmux_parent_args_to_line_index(struct device *dev,
+                                                const struct of_phandle_args *parent_args,
+                                                const u32 output_lines[],
+                                                unsigned int output_lines_count)
+{
+       unsigned int i;
+
+       /*
+        * The parent interrupt should be one of the GIC controller.
+        * Three arguments must be provided.
+        *  - args[0]: GIC_SPI
+        *  - args[1]: The GIC interrupt number
+        *  - args[2]: The interrupt flags
+        *
+        * We retrieve the line index based on the GIC interrupt number
+        * provided and rzn1_irqmux_output_line[] mapping array.
+        */
+

drivers/soc/renesas/rzn1_irqmux.c line 53

Quoted 10 lines
drivers/soc/renesas/rzn1_irqmux.c new file +10 -0
+       if (parent_args->args_count != 3 ||
+           parent_args->args[0] != GIC_SPI) {
+               dev_err(dev, "Invalid interrupt-map item\n");
+               return -EINVAL;
+       }
+
+       for (i = 0; i < output_lines_count; i++) {
+               if (parent_args->args[1] == output_lines[i])
+                       return i;
+       }  

... then this loop can be replaced by two simple comparisons.

Quoted 13 lines

drivers/soc/renesas/rzn1_irqmux.c line 63

Quoted 10 lines
drivers/soc/renesas/rzn1_irqmux.c new file +10 -0
+
+       dev_err(dev, "Invalid GIC interrupt %u\n", parent_args->args[1]);
+       return -EINVAL;
+}
+
+static int rzn1_irqmux_setup(struct device *dev, struct device_node *np, u32 __iomem *regs)
+{
+       struct of_imap_parser imap_parser;
+       struct of_imap_item imap_item;
+       unsigned long index_done = 0;  

Perhaps "DECLARE_BITMAP(index_done, RZN1_IRQMUX_NUM_IRQS)", so the BITS_PER_LONG limit can be lifted, without any cost?

Yes.

Quoted 27 lines
Quoted 14 lines
drivers/soc/renesas/rzn1_irqmux.c new file +13 -0
+       int index;
+       int ret;
+       u32 tmp;
+
+       /* We support only #interrupt-cells = <1> and #address-cells = <0> */
+       ret = of_property_read_u32(np, "#interrupt-cells", &tmp);
+       if (ret)
+               return ret;
+       if (tmp != 1)
+               return -EINVAL;
+
+       ret = of_property_read_u32(np, "#address-cells", &tmp);
+       if (ret)

drivers/soc/renesas/rzn1_irqmux.c line 86

Quoted 10 lines
drivers/soc/renesas/rzn1_irqmux.c new file +10 -0
+               return ret;
+       if (tmp != 0)
+               return -EINVAL;
+
+       ret = of_imap_parser_init(&imap_parser, np, &imap_item);
+       if (ret)
+               return ret;
+
+       /* 8 output lines are available */
+       BUILD_BUG_ON(ARRAY_SIZE(rzn1_irqmux_output_lines) != 8);  

... then this check can be removed, too.

Quoted 8 lines

drivers/soc/renesas/rzn1_irqmux.c line 92

Quoted 6 lines
drivers/soc/renesas/rzn1_irqmux.c new file +6 -0
+
+       /*
+        * index_done is an unsigned long integer. Be sure that no buffer
+        * overflow can occur.
+        */
+       BUILD_BUG_ON(ARRAY_SIZE(rzn1_irqmux_output_lines) > BITS_PER_LONG);  

Currently this is less strict than the check above, so a bit useless?

Yes. My first intention was to explicitly check both constraints:
- The number of output lines which lead to the maximum index value used to index register addresses (i.e. writel(imap_item.child_imap[0], regs + index) below).

- The number of output lines to the maximum index value used to index register addresses which which lead to the maximum index value used for testing and setting the "index_done" flags.

But yes, I can keep the stricter one.

Also, if RZN1_IRQMUX_NUM_IRQS is introduced and related DECLARE_BITMAP(index_done, RZN1_IRQMUX_NUM_IRQS), a single check against RZN1_IRQMUX_NUM_IRQS will be enough for both cases

Quoted 28 lines
Quoted 27 lines
drivers/soc/renesas/rzn1_irqmux.c new file +26 -0
+
+       for_each_of_imap_item(&imap_parser, &imap_item) {
+               index = rzn1_irqmux_parent_args_to_line_index(dev,
+                                                             &imap_item.parent_args,
+                                                             rzn1_irqmux_output_lines,
+                                                             ARRAY_SIZE(rzn1_irqmux_output_lines));
+               if (index < 0) {
+                       of_node_put(imap_item.parent_args.np);
+                       return index;
+               }
+
+               if (test_and_set_bit(index, &index_done)) {
+                       of_node_put(imap_item.parent_args.np);
+                       dev_err(dev, "Mux output line already defined\n");
+                       return -EINVAL;
+               }
+
+               /*
+                * The child #address-cells is 0 (already checked). The first
+                * value in imap item is the src hwirq.
+                */
+               writel(imap_item.child_imap[0], regs + index);
+       }
+
+       return 0;
+}  
#13 lore raw

Re: [PATCH v5 7/8] soc: renesas: Add support for Renesas RZ/N1 GPIO Interrupt Multiplexer

Hi Hervé,

Quoted 65 lines · Herve Codina
Quoted 47 lines · On Tue, 21 Oct 2025 15:05:35 +0200 Geert Uytterhoeven <[email protected]>…
Quoted 26 lines · Herve Codina (Schneider Electric)

On the Renesas RZ/N1 SoC, GPIOs can generate interruptions. Those interruption lines are multiplexed by the GPIO Interrupt Multiplexer in order to map 32 * 3 GPIO interrupt lines to 8 GIC interrupt lines.

The GPIO interrupt multiplexer IP does nothing but select 8 GPIO IRQ lines out of the 96 available to wire them to the GIC input lines.

drivers/soc/renesas/rzn1_irqmux.c new file +14 -0
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RZ/N1 GPIO Interrupt Multiplexer
+ *
+ * Copyright 2025 Schneider Electric
+ * Author: Herve Codina <[email protected]>
+ */
+
+#include <linux/bitops.h>
+#include <linux/build_bug.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>

drivers/soc/renesas/rzn1_irqmux.c line 24

Quoted 10 lines
drivers/soc/renesas/rzn1_irqmux.c new file +10 -0
+#include <linux/platform_device.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+/*
+ * The array index is the output line index, the value at the index is the
+ * GIC SPI interrupt number the output line is connected to.
+ */
+static const u32 rzn1_irqmux_output_lines[] = {
+       103, 104, 105, 106, 107, 108, 109, 110
+};

I did read the discussion with Wolfram, but the flexibility (and overhead) provided by this array sounds a bit overkill to me.

What about just defining:

    #define RZN1_IRQMUX_SPI_BASE    103
    #define RZN1_IRQMUX_NUM_IRQS    8
Quoted 6 lines

?

If/when a new SoC with a similar setup ever arrives, you can probably just replace the constants above by variables inside SoC-specific match data. And if the new mapping would be non-contiguous, you can still revive this array ;-)

I have in mind a use case that can lead to a non-contiguous mapping.

The RZ/N1 SoC embeds a Cortex-M3 CPU. This CPU can use GPIOs and some of them for interrupt purpose. In that case, those GPIOs have to be routed to the interrupt line expected by the Cortex-M3.

And so, we have some interrupts reserved for CPUs running Linux and some others for the Cortex-M3.

Quoted 6 lines

Among those reserved interrupts may some are not used.

for instance:
Interrupt 103, 102: Reserved and used by Linux
Interrupt 103: Reserved for Linux but not used -> Hole in the mapping Interrupt 104: Reserved and used my Cortex-M3 (need to be routed by Linux)

102 does not seem to be correct?

Quoted 5 lines

I don't know if this use case is relevant but I think we should be too restrictive on the mapping and so accept holes.

With that in mind, I let you confirm that you still prefer to have a mapping without any holes. A future patch to support that is always possible.

While that would indeed be a non-discontiguous mapping, I do not see how it is related to rzn1_irqmux_output_lines[] in the driver. That array would still contain the same contiguous values 103..110, right?

Sorry, I haven't been following the development of this driver that closely (RZ/N1 is completely different from e.g. R-Car, and I never had access to an RZ/N1 platform), so perhaps I am missing something. Why does the user have to specify an interrupt-map in DT? Can't the driver create the mapping dynamically, based actual usage of the GPIOs? I.e. the first 8 GPIOs that ask for interrupt functionality receive it, and are mapped to an available GIC interrupt?
I believe this is how rzg2l-irqc works, mapping up to 32 GPIO interrupts to 32 GIC (TINT) interrupts.

Thanks!

Gr{oetje,eeting}s,

Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds

#14 lore raw

Re: [PATCH v5 7/8] soc: renesas: Add support for Renesas RZ/N1 GPIO Interrupt Multiplexer

Hi Geert,

Quoted 10 lines · On Thu, 23 Oct 2025 13:30:53 +0200 Geert Uytterhoeven <[email protected]>…
Quoted 10 lines

I have in mind a use case that can lead to a non-contiguous mapping.

The RZ/N1 SoC embeds a Cortex-M3 CPU. This CPU can use GPIOs and some of them for interrupt purpose. In that case, those GPIOs have to be routed to the interrupt line expected by the Cortex-M3.

And so, we have some interrupts reserved for CPUs running Linux and some others for the Cortex-M3.

Quoted 8 lines
Quoted 6 lines

Among those reserved interrupts may some are not used.

for instance:
Interrupt 103, 102: Reserved and used by Linux
Interrupt 103: Reserved for Linux but not used -> Hole in the mapping Interrupt 104: Reserved and used my Cortex-M3 (need to be routed by Linux)

102 does not seem to be correct?

My bad, my example was wrong.
Interrupt 103, 104: Reserved and used by Linux
Interrupt 105: Reserved for Linux but not used -> Hole in the mapping Interrupt 106: Reserved and used my Cortex-M3 (need to be routed by Linux)

Quoted 10 lines
Quoted 6 lines

I don't know if this use case is relevant but I think we should be too restrictive on the mapping and so accept holes.

With that in mind, I let you confirm that you still prefer to have a mapping without any holes. A future patch to support that is always possible.

While that would indeed be a non-discontiguous mapping, I do not see how it is related to rzn1_irqmux_output_lines[] in the driver. That array would still contain the same contiguous values 103..110, right?

The array rzn1_irqmux_output_lines is still contiguous yes but the mapping defined in irq-map property no.

Looking back again at your proposal, indeed I can remove the following loop:

	for (i = 0; i < output_lines_count; i++) {
               if (parent_args->args[1] == output_lines[i])
                       return i;
	} 

With just

	if (parent_args->args[1] >= RZN1_IRQMUX_SPI_BASE &&
            parent_args->args[1] < RZN1_IRQMUX_SPI_BASE + RZN1_IRQMUX_NUM_IRQS) {
		return parent_args->args[1] - RZN1_IRQMUX_SPI_BASE;

	dev_err(dev, "Invalid GIC interrupt %u\n", parent_args->args[1]);
	return -EINVAL;
Quoted 10 lines

Sorry, I haven't been following the development of this driver that closely (RZ/N1 is completely different from e.g. R-Car, and I never had access to an RZ/N1 platform), so perhaps I am missing something. Why does the user have to specify an interrupt-map in DT? Can't the driver create the mapping dynamically, based actual usage of the GPIOs? I.e. the first 8 GPIOs that ask for interrupt functionality receive it, and are mapped to an available GIC interrupt?
I believe this is how rzg2l-irqc works, mapping up to 32 GPIO interrupts to 32 GIC (TINT) interrupts.

I think the main difference with rzg2l-irqc is that the RZ/N1 irq mux is clearly not an interrupt controller.

It is just a mux with 96 inputs (GPIO lines coming from several GPIO controller) and 8 outputs (connected to the GIC).

It is represented as an interrupt nexus node and has an interrupt-map property. to describe the routing.

The interrupt-map property cannot be dynamically created.

Also, the routing is necessary even if the related GPIO is not used by Linux. This GPIO can be used as a GPIO input interrupt line by the Cortex M3.

If the irq mux driver performs the routing only on Linux GPIO usage, it will not route GPIOs depending on Cortex M3 internal usage.

Best regards,
Hervé

#15 lore raw

Re: [PATCH v5 7/8] soc: renesas: Add support for Renesas RZ/N1 GPIO Interrupt Multiplexer

Hi Hervé,

Quoted 18 lines · Herve Codina
Quoted 16 lines · On Thu, 23 Oct 2025 13:30:53 +0200 Geert Uytterhoeven <[email protected]>…
Quoted 15 lines

I have in mind a use case that can lead to a non-contiguous mapping.

The RZ/N1 SoC embeds a Cortex-M3 CPU. This CPU can use GPIOs and some of them for interrupt purpose. In that case, those GPIOs have to be routed to the interrupt line expected by the Cortex-M3.

And so, we have some interrupts reserved for CPUs running Linux and some others for the Cortex-M3.

Among those reserved interrupts may some are not used.

for instance:
Interrupt 103, 102: Reserved and used by Linux
Interrupt 103: Reserved for Linux but not used -> Hole in the mapping Interrupt 104: Reserved and used my Cortex-M3 (need to be routed by Linux)

Quoted 6 lines
Quoted 1 line

102 does not seem to be correct?

My bad, my example was wrong.
Interrupt 103, 104: Reserved and used by Linux
Interrupt 105: Reserved for Linux but not used -> Hole in the mapping Interrupt 106: Reserved and used my Cortex-M3 (need to be routed by Linux)

OK, much better!

Quoted 19 lines
Quoted 9 lines
Quoted 5 lines

I don't know if this use case is relevant but I think we should be too restrictive on the mapping and so accept holes.

With that in mind, I let you confirm that you still prefer to have a mapping without any holes. A future patch to support that is always possible.

While that would indeed be a non-discontiguous mapping, I do not see how it is related to rzn1_irqmux_output_lines[] in the driver. That array would still contain the same contiguous values 103..110, right?

The array rzn1_irqmux_output_lines is still contiguous yes but the mapping defined in irq-map property no.

Looking back again at your proposal, indeed I can remove the following loop:

        for (i = 0; i < output_lines_count; i++) {
               if (parent_args->args[1] == output_lines[i])
                       return i;
        }
Quoted 7 lines

With just

        if (parent_args->args[1] >= RZN1_IRQMUX_SPI_BASE &&
            parent_args->args[1] < RZN1_IRQMUX_SPI_BASE + RZN1_IRQMUX_NUM_IRQS) {
                return parent_args->args[1] - RZN1_IRQMUX_SPI_BASE;

        dev_err(dev, "Invalid GIC interrupt %u\n", parent_args->args[1]);
        return -EINVAL;

Good. I like simpler code ;-)

BTW, please invert the logic, i.e. bail out early in case of error.

Quoted 19 lines
Quoted 9 lines

Sorry, I haven't been following the development of this driver that closely (RZ/N1 is completely different from e.g. R-Car, and I never had access to an RZ/N1 platform), so perhaps I am missing something. Why does the user have to specify an interrupt-map in DT? Can't the driver create the mapping dynamically, based actual usage of the GPIOs? I.e. the first 8 GPIOs that ask for interrupt functionality receive it, and are mapped to an available GIC interrupt?
I believe this is how rzg2l-irqc works, mapping up to 32 GPIO interrupts to 32 GIC (TINT) interrupts.

I think the main difference with rzg2l-irqc is that the RZ/N1 irq mux is clearly not an interrupt controller.

It is just a mux with 96 inputs (GPIO lines coming from several GPIO controller) and 8 outputs (connected to the GIC).

It is represented as an interrupt nexus node and has an interrupt-map property. to describe the routing.

Quoted 7 lines

The interrupt-map property cannot be dynamically created.

Also, the routing is necessary even if the related GPIO is not used by Linux. This GPIO can be used as a GPIO input interrupt line by the Cortex M3.

If the irq mux driver performs the routing only on Linux GPIO usage, it will not route GPIOs depending on Cortex M3 internal usage.

Thanks, makes sense!

Gr{oetje,eeting}s,

Geert

Signature · 5 lines
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
#16 lore raw

PATCH v5 8/8 ARM: dts: r9a06g032: Add support for GPIO interrupts

In reply to Herve Codina (Schneider Electric)

In the RZ/N1 SoC, the GPIO interrupts are multiplexed using the GPIO Interrupt Multiplexer.

Add the multiplexer node and connect GPIO interrupt lines to the multiplexer.

The interrupt-map available in the multiplexer node has to be updated in dts files depending on the GPIO usage. Indeed, the usage of an interrupt for a GPIO is board dependent.

Up to 8 GPIOs can be used as an interrupt line (one per multiplexer output interrupt).

---
 arch/arm/boot/dts/renesas/r9a06g032.dtsi | 41 ++++++++++++++++++++++++
 1 file changed, 41 insertions(+)
arch/arm/boot/dts/renesas/r9a06g032.dtsi +41 -0
index da977cdd8487..c7196e720c6c 100644
@@ -534,6 +534,14 @@ gpio0a: gpio-port@0 {
 				#gpio-cells = <2>;
 				snps,nr-gpios = <32>;
 				reg = <0>;
+
+				interrupt-controller;
+				interrupt-parent = <&gpioirqmux>;
+				interrupts = < 0  1  2  3  4  5  6  7
+					       8  9 10 11 12 13 14 15
+					      16 17 18 19 20 21 22 23
+					      24 25 26 27 28 29 30 31 >;
+				#interrupt-cells = <2>;
 			};
 
 			/* GPIO0b[0..1]   connected to pins GPIO1..2   */
@@ -576,6 +584,14 @@ gpio1a: gpio-port@0 {
 				#gpio-cells = <2>;
 				snps,nr-gpios = <32>;
 				reg = <0>;
+
+				interrupt-controller;
+				interrupt-parent = <&gpioirqmux>;
+				interrupts = < 32 33 34 35 36 37 38 39
+					       40 41 42 43 44 45 46 47
+					       48 49 50 51 52 53 54 55
+					       56 57 58 59 60 61 62 63 >;
+				#interrupt-cells = <2>;
 			};
 
 			/* GPIO1b[0..1]   connected to pins GPIO55..56 */
@@ -608,6 +624,14 @@ gpio2a: gpio-port@0 {
 				#gpio-cells = <2>;
 				snps,nr-gpios = <32>;
 				reg = <0>;
+
+				interrupt-controller;
+				interrupt-parent = <&gpioirqmux>;
+				interrupts = < 64 65 66 67 68 69 70 71
+					       72 73 74 75 76 77 78 79
+					       80 81 82 83 84 85 86 87
+					       88 89 90 91 92 93 94 95 >;
+				#interrupt-cells = <2>;
 			};
 
 			/* GPIO2b[0..9] connected to pins GPIO160..169 */
@@ -620,6 +644,23 @@ gpio2b: gpio-port@1 {
 			};
 		};
 
+		gpioirqmux: interrupt-controller@51000480 {
+			compatible = "renesas,r9a06g032-gpioirqmux", "renesas,rzn1-gpioirqmux";
+			reg = <0x51000480 0x20>;
+			#interrupt-cells = <1>;
+			#address-cells = <0>;
+			interrupt-map-mask = <0x7f>;
+
+			/*
+			 * Example mapping entry. Board DTs need to overwrite
+			 * 'interrupt-map' with their specific mapping. Check
+			 * the irqmux binding documentation for details.
+			 */
+			interrupt-map = <0 &gic GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
+
+			status = "disabled";
+		};
+
 		can0: can@52104000 {
 			compatible = "renesas,r9a06g032-sja1000", "renesas,rzn1-sja1000";
 			reg = <0x52104000 0x800>;
Signature · 1 line
2.51.0
Reviewed-by on the v5 series
#17 lore raw
In reply to Herve Codina (Schneider Electric)

Hi Herve,

Quoted 8 lines · Herve Codina (Schneider Electric)

The first patches in this series are related to a new helper introduced to parse an interrupt-map property.
- patch 1: Introduce the helper (for_each_of_imap_item)
- patch 2: Add a unittest for the new helper
- patch 3 and 4: convert existing drivers to use this new helper

Patch 5 adds support for GPIO (device-tree description)

Quoted 7 lines

The last patches (6, 7 and 8) of the series are related to GPIO interrupts and GPIO IRQ multiplexer.

In the RZ/N1 SoCs, GPIO interrupts are wired to a GPIO IRQ multiplexer.

This multiplexer does nothing but select 8 GPIO IRQ lines out of the 96 available to wire them to the GIC input lines.

I had my worries about the multiplexer but seeing the whole picture and the nice refactoring with for_each_of_imap_item() I have to say the patch series looks very nice.

Yours,
Linus Walleij

#18 lore raw
In reply to Herve Codina (Schneider Electric)
Quoted 2 lines · Herve Codina (Schneider Electric)

This series adds support for GPIO and GPIO IRQ mux available in the RZ/N1 SoCs.

I definitely want to review and test this series. But I can do this on Friday earliest, more likely next Monday.

#19 lore raw
Quoted 6 lines
Quoted 3 lines

This series adds support for GPIO and GPIO IRQ mux available in the RZ/N1 SoCs.

I definitely want to review and test this series. But I can do this on Friday earliest, more likely next Monday.

Since your discussion with Geert lead to changes, I will wait for v6 and review and test that. I had a glimpse on v5 and have nothing to add now.