Skip to content

Commit 13e7b33

Browse files
Demon000KAGA-KOKO
authored andcommitted
irqchip: Add RZ/{T2H,N2H} Interrupt Controller (ICU) driver
The Renesas RZ/T2H (R9A09G077) and Renesas RZ/N2H (R9A09G087) SoCs have an Interrupt Controller (ICU) that supports interrupts from external pins IRQ0 to IRQ15, and SEI, and software-triggered interrupts INTCPU0 to INTCPU15. INTCPU0 to INTCPU13, IRQ0 to IRQ13 are non-safety interrupts, while INTCPU14, INTCPU15, IRQ14, IRQ15 and SEI are safety interrupts, and are exposed via a separate register space. Signed-off-by: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Link: https://patch.msgid.link/20251201112933.488801-3-cosmin-gabriel.tanislav.xa@renesas.com
1 parent a6568d8 commit 13e7b33

5 files changed

Lines changed: 313 additions & 0 deletions

File tree

drivers/irqchip/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,14 @@ config RENESAS_RZG2L_IRQC
297297
Enable support for the Renesas RZ/G2L (and alike SoC) Interrupt Controller
298298
for external devices.
299299

300+
config RENESAS_RZT2H_ICU
301+
bool "Renesas RZ/{T2H,N2H} ICU support" if COMPILE_TEST
302+
select GENERIC_IRQ_CHIP
303+
select IRQ_DOMAIN_HIERARCHY
304+
help
305+
Enable support for the Renesas RZ/{T2H,N2H} Interrupt Controller
306+
(ICU).
307+
300308
config RENESAS_RZV2H_ICU
301309
bool "Renesas RZ/V2H(P) ICU support" if COMPILE_TEST
302310
select GENERIC_IRQ_CHIP

drivers/irqchip/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ obj-$(CONFIG_RENESAS_INTC_IRQPIN) += irq-renesas-intc-irqpin.o
5454
obj-$(CONFIG_RENESAS_IRQC) += irq-renesas-irqc.o
5555
obj-$(CONFIG_RENESAS_RZA1_IRQC) += irq-renesas-rza1.o
5656
obj-$(CONFIG_RENESAS_RZG2L_IRQC) += irq-renesas-rzg2l.o
57+
obj-$(CONFIG_RENESAS_RZT2H_ICU) += irq-renesas-rzt2h.o
5758
obj-$(CONFIG_RENESAS_RZV2H_ICU) += irq-renesas-rzv2h.o
5859
obj-$(CONFIG_VERSATILE_FPGA_IRQ) += irq-versatile-fpga.o
5960
obj-$(CONFIG_ARCH_NSPIRE) += irq-zevio.o
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
#include <linux/bitfield.h>
4+
#include <linux/err.h>
5+
#include <linux/io.h>
6+
#include <linux/irqchip.h>
7+
#include <linux/irqchip/irq-renesas-rzt2h.h>
8+
#include <linux/irqdomain.h>
9+
#include <linux/of_platform.h>
10+
#include <linux/pm_runtime.h>
11+
#include <linux/reset.h>
12+
#include <linux/spinlock.h>
13+
14+
#define RZT2H_ICU_INTCPU_NS_START 0
15+
#define RZT2H_ICU_INTCPU_NS_COUNT 14
16+
17+
#define RZT2H_ICU_INTCPU_S_START (RZT2H_ICU_INTCPU_NS_START + \
18+
RZT2H_ICU_INTCPU_NS_COUNT)
19+
#define RZT2H_ICU_INTCPU_S_COUNT 2
20+
21+
#define RZT2H_ICU_IRQ_NS_START (RZT2H_ICU_INTCPU_S_START + \
22+
RZT2H_ICU_INTCPU_S_COUNT)
23+
#define RZT2H_ICU_IRQ_NS_COUNT 14
24+
25+
#define RZT2H_ICU_IRQ_S_START (RZT2H_ICU_IRQ_NS_START + \
26+
RZT2H_ICU_IRQ_NS_COUNT)
27+
#define RZT2H_ICU_IRQ_S_COUNT 2
28+
29+
#define RZT2H_ICU_SEI_START (RZT2H_ICU_IRQ_S_START + \
30+
RZT2H_ICU_IRQ_S_COUNT)
31+
#define RZT2H_ICU_SEI_COUNT 1
32+
33+
#define RZT2H_ICU_NUM_IRQ (RZT2H_ICU_INTCPU_NS_COUNT + \
34+
RZT2H_ICU_INTCPU_S_COUNT + \
35+
RZT2H_ICU_IRQ_NS_COUNT + \
36+
RZT2H_ICU_IRQ_S_COUNT + \
37+
RZT2H_ICU_SEI_COUNT)
38+
39+
#define RZT2H_ICU_IRQ_IN_RANGE(n, type) \
40+
((n) >= RZT2H_ICU_##type##_START && \
41+
(n) < RZT2H_ICU_##type##_START + RZT2H_ICU_##type##_COUNT)
42+
43+
#define RZT2H_ICU_PORTNF_MD 0xc
44+
#define RZT2H_ICU_PORTNF_MDi_MASK(i) (GENMASK(1, 0) << ((i) * 2))
45+
#define RZT2H_ICU_PORTNF_MDi_PREP(i, val) (FIELD_PREP(GENMASK(1, 0), val) << ((i) * 2))
46+
47+
#define RZT2H_ICU_MD_LOW_LEVEL 0b00
48+
#define RZT2H_ICU_MD_FALLING_EDGE 0b01
49+
#define RZT2H_ICU_MD_RISING_EDGE 0b10
50+
#define RZT2H_ICU_MD_BOTH_EDGES 0b11
51+
52+
#define RZT2H_ICU_DMACn_RSSELi(n, i) (0x7d0 + 0x18 * (n) + 0x4 * (i))
53+
#define RZT2H_ICU_DMAC_REQ_SELx_MASK(x) (GENMASK(9, 0) << ((x) * 10))
54+
#define RZT2H_ICU_DMAC_REQ_SELx_PREP(x, val) (FIELD_PREP(GENMASK(9, 0), val) << ((x) * 10))
55+
56+
struct rzt2h_icu_priv {
57+
void __iomem *base_ns;
58+
void __iomem *base_s;
59+
struct irq_fwspec fwspec[RZT2H_ICU_NUM_IRQ];
60+
raw_spinlock_t lock;
61+
};
62+
63+
void rzt2h_icu_register_dma_req(struct platform_device *icu_dev, u8 dmac_index, u8 dmac_channel,
64+
u16 req_no)
65+
{
66+
struct rzt2h_icu_priv *priv = platform_get_drvdata(icu_dev);
67+
u8 y, upper;
68+
u32 val;
69+
70+
y = dmac_channel / 3;
71+
upper = dmac_channel % 3;
72+
73+
guard(raw_spinlock_irqsave)(&priv->lock);
74+
val = readl(priv->base_ns + RZT2H_ICU_DMACn_RSSELi(dmac_index, y));
75+
val &= ~RZT2H_ICU_DMAC_REQ_SELx_MASK(upper);
76+
val |= RZT2H_ICU_DMAC_REQ_SELx_PREP(upper, req_no);
77+
writel(val, priv->base_ns + RZT2H_ICU_DMACn_RSSELi(dmac_index, y));
78+
}
79+
EXPORT_SYMBOL_GPL(rzt2h_icu_register_dma_req);
80+
81+
static inline struct rzt2h_icu_priv *irq_data_to_priv(struct irq_data *data)
82+
{
83+
return data->domain->host_data;
84+
}
85+
86+
static inline int rzt2h_icu_irq_to_offset(struct irq_data *d, void __iomem **base,
87+
unsigned int *offset)
88+
{
89+
struct rzt2h_icu_priv *priv = irq_data_to_priv(d);
90+
unsigned int hwirq = irqd_to_hwirq(d);
91+
92+
/*
93+
* Safety IRQs and SEI use a separate register space from the non-safety IRQs.
94+
* SEI interrupt number follows immediately after the safety IRQs.
95+
*/
96+
if (RZT2H_ICU_IRQ_IN_RANGE(hwirq, IRQ_NS)) {
97+
*offset = hwirq - RZT2H_ICU_IRQ_NS_START;
98+
*base = priv->base_ns;
99+
} else if (RZT2H_ICU_IRQ_IN_RANGE(hwirq, IRQ_S) || RZT2H_ICU_IRQ_IN_RANGE(hwirq, SEI)) {
100+
*offset = hwirq - RZT2H_ICU_IRQ_S_START;
101+
*base = priv->base_s;
102+
} else {
103+
return -EINVAL;
104+
}
105+
return 0;
106+
}
107+
108+
static int rzt2h_icu_irq_set_type(struct irq_data *d, unsigned int type)
109+
{
110+
struct rzt2h_icu_priv *priv = irq_data_to_priv(d);
111+
unsigned int offset, parent_type;
112+
void __iomem *base;
113+
u32 val, md;
114+
int ret;
115+
116+
ret = rzt2h_icu_irq_to_offset(d, &base, &offset);
117+
if (ret)
118+
return ret;
119+
120+
switch (type & IRQ_TYPE_SENSE_MASK) {
121+
case IRQ_TYPE_LEVEL_LOW:
122+
md = RZT2H_ICU_MD_LOW_LEVEL;
123+
parent_type = IRQ_TYPE_LEVEL_HIGH;
124+
break;
125+
case IRQ_TYPE_EDGE_FALLING:
126+
md = RZT2H_ICU_MD_FALLING_EDGE;
127+
parent_type = IRQ_TYPE_EDGE_RISING;
128+
break;
129+
case IRQ_TYPE_EDGE_RISING:
130+
md = RZT2H_ICU_MD_RISING_EDGE;
131+
parent_type = IRQ_TYPE_EDGE_RISING;
132+
break;
133+
case IRQ_TYPE_EDGE_BOTH:
134+
md = RZT2H_ICU_MD_BOTH_EDGES;
135+
parent_type = IRQ_TYPE_EDGE_RISING;
136+
break;
137+
default:
138+
return -EINVAL;
139+
}
140+
141+
scoped_guard(raw_spinlock, &priv->lock) {
142+
val = readl_relaxed(base + RZT2H_ICU_PORTNF_MD);
143+
val &= ~RZT2H_ICU_PORTNF_MDi_MASK(offset);
144+
val |= RZT2H_ICU_PORTNF_MDi_PREP(offset, md);
145+
writel_relaxed(val, base + RZT2H_ICU_PORTNF_MD);
146+
}
147+
148+
return irq_chip_set_type_parent(d, parent_type);
149+
}
150+
151+
static int rzt2h_icu_set_type(struct irq_data *d, unsigned int type)
152+
{
153+
unsigned int hw_irq = irqd_to_hwirq(d);
154+
155+
/* IRQn and SEI are selectable, others are edge-only. */
156+
if (RZT2H_ICU_IRQ_IN_RANGE(hw_irq, IRQ_NS) ||
157+
RZT2H_ICU_IRQ_IN_RANGE(hw_irq, IRQ_S) ||
158+
RZT2H_ICU_IRQ_IN_RANGE(hw_irq, SEI))
159+
return rzt2h_icu_irq_set_type(d, type);
160+
161+
if ((type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_EDGE_RISING)
162+
return -EINVAL;
163+
164+
return irq_chip_set_type_parent(d, IRQ_TYPE_EDGE_RISING);
165+
}
166+
167+
static const struct irq_chip rzt2h_icu_chip = {
168+
.name = "rzt2h-icu",
169+
.irq_mask = irq_chip_mask_parent,
170+
.irq_unmask = irq_chip_unmask_parent,
171+
.irq_eoi = irq_chip_eoi_parent,
172+
.irq_set_type = rzt2h_icu_set_type,
173+
.irq_set_wake = irq_chip_set_wake_parent,
174+
.irq_set_affinity = irq_chip_set_affinity_parent,
175+
.irq_retrigger = irq_chip_retrigger_hierarchy,
176+
.irq_get_irqchip_state = irq_chip_get_parent_state,
177+
.irq_set_irqchip_state = irq_chip_set_parent_state,
178+
.flags = IRQCHIP_MASK_ON_SUSPEND |
179+
IRQCHIP_SET_TYPE_MASKED |
180+
IRQCHIP_SKIP_SET_WAKE,
181+
};
182+
183+
static int rzt2h_icu_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs,
184+
void *arg)
185+
{
186+
struct rzt2h_icu_priv *priv = domain->host_data;
187+
irq_hw_number_t hwirq;
188+
unsigned int type;
189+
int ret;
190+
191+
ret = irq_domain_translate_twocell(domain, arg, &hwirq, &type);
192+
if (ret)
193+
return ret;
194+
195+
ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &rzt2h_icu_chip, NULL);
196+
if (ret)
197+
return ret;
198+
199+
return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &priv->fwspec[hwirq]);
200+
}
201+
202+
static const struct irq_domain_ops rzt2h_icu_domain_ops = {
203+
.alloc = rzt2h_icu_alloc,
204+
.free = irq_domain_free_irqs_common,
205+
.translate = irq_domain_translate_twocell,
206+
};
207+
208+
static int rzt2h_icu_parse_interrupts(struct rzt2h_icu_priv *priv, struct device_node *np)
209+
{
210+
struct of_phandle_args map;
211+
unsigned int i;
212+
int ret;
213+
214+
for (i = 0; i < RZT2H_ICU_NUM_IRQ; i++) {
215+
ret = of_irq_parse_one(np, i, &map);
216+
if (ret)
217+
return ret;
218+
219+
of_phandle_args_to_fwspec(np, map.args, map.args_count, &priv->fwspec[i]);
220+
}
221+
222+
return 0;
223+
}
224+
225+
static int rzt2h_icu_init(struct platform_device *pdev, struct device_node *parent)
226+
{
227+
struct irq_domain *irq_domain, *parent_domain;
228+
struct device_node *node = pdev->dev.of_node;
229+
struct device *dev = &pdev->dev;
230+
struct rzt2h_icu_priv *priv;
231+
int ret;
232+
233+
parent_domain = irq_find_host(parent);
234+
if (!parent_domain)
235+
return dev_err_probe(dev, -ENODEV, "cannot find parent domain\n");
236+
237+
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
238+
if (!priv)
239+
return -ENOMEM;
240+
241+
raw_spin_lock_init(&priv->lock);
242+
243+
platform_set_drvdata(pdev, priv);
244+
245+
priv->base_ns = devm_of_iomap(dev, dev->of_node, 0, NULL);
246+
if (IS_ERR(priv->base_ns))
247+
return PTR_ERR(priv->base_ns);
248+
249+
priv->base_s = devm_of_iomap(dev, dev->of_node, 1, NULL);
250+
if (IS_ERR(priv->base_s))
251+
return PTR_ERR(priv->base_s);
252+
253+
ret = rzt2h_icu_parse_interrupts(priv, node);
254+
if (ret)
255+
return dev_err_probe(dev, ret, "cannot parse interrupts: %d\n", ret);
256+
257+
ret = devm_pm_runtime_enable(dev);
258+
if (ret)
259+
return dev_err_probe(dev, ret, "devm_pm_runtime_enable failed: %d\n", ret);
260+
261+
ret = pm_runtime_resume_and_get(dev);
262+
if (ret)
263+
return dev_err_probe(dev, ret, "pm_runtime_resume_and_get failed: %d\n", ret);
264+
265+
irq_domain = irq_domain_create_hierarchy(parent_domain, 0, RZT2H_ICU_NUM_IRQ,
266+
dev_fwnode(dev), &rzt2h_icu_domain_ops, priv);
267+
if (!irq_domain) {
268+
pm_runtime_put(dev);
269+
return -ENOMEM;
270+
}
271+
272+
return 0;
273+
}
274+
275+
IRQCHIP_PLATFORM_DRIVER_BEGIN(rzt2h_icu)
276+
IRQCHIP_MATCH("renesas,r9a09g077-icu", rzt2h_icu_init)
277+
IRQCHIP_PLATFORM_DRIVER_END(rzt2h_icu)
278+
MODULE_AUTHOR("Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>");
279+
MODULE_DESCRIPTION("Renesas RZ/T2H ICU Driver");
280+
MODULE_LICENSE("GPL");

drivers/soc/renesas/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ config ARCH_R9A09G057
423423
config ARCH_R9A09G077
424424
bool "ARM64 Platform support for R9A09G077 (RZ/T2H)"
425425
default y if ARCH_RENESAS
426+
select RENESAS_RZT2H_ICU
426427
help
427428
This enables support for the Renesas RZ/T2H SoC variants.
428429

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
/*
3+
* Renesas RZ/T2H Interrupt Control Unit (ICU)
4+
*
5+
* Copyright (C) 2025 Renesas Electronics Corporation.
6+
*/
7+
8+
#ifndef __LINUX_IRQ_RENESAS_RZT2H
9+
#define __LINUX_IRQ_RENESAS_RZT2H
10+
11+
#include <linux/platform_device.h>
12+
13+
#define RZT2H_ICU_DMAC_REQ_NO_DEFAULT 0x3ff
14+
15+
#ifdef CONFIG_RENESAS_RZT2H_ICU
16+
void rzt2h_icu_register_dma_req(struct platform_device *icu_dev, u8 dmac_index, u8 dmac_channel,
17+
u16 req_no);
18+
#else
19+
static inline void rzt2h_icu_register_dma_req(struct platform_device *icu_dev, u8 dmac_index,
20+
u8 dmac_channel, u16 req_no) { }
21+
#endif
22+
23+
#endif /* __LINUX_IRQ_RENESAS_RZT2H */

0 commit comments

Comments
 (0)