drivers/irqchip/irq-renesas-rzg2l.c | 91 ++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-)
From: Biju Das <biju.das.jz@bp.renesas.com>
The RZ/G2L SoC has an NMI interrupt. Add support for the NMI interrupt.
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v1->v2:
* Added a comment in rzg2l_clear_nmi_int() and dropped the lock in the
caller as it is not an RMW operation.
* Added the macro NSCR_NSTAT to indicate the NMI status bit.
* Dropped the function parameter hwirq in rzg2l_clear_nmi_int().
* Updated the commit description from RZ/G3L to RZ/G2L to cover all SoCs.
---
drivers/irqchip/irq-renesas-rzg2l.c | 91 ++++++++++++++++++++++++++++-
1 file changed, 90 insertions(+), 1 deletion(-)
diff --git a/drivers/irqchip/irq-renesas-rzg2l.c b/drivers/irqchip/irq-renesas-rzg2l.c
index 199b3c6b02c8..b94949987ae3 100644
--- a/drivers/irqchip/irq-renesas-rzg2l.c
+++ b/drivers/irqchip/irq-renesas-rzg2l.c
@@ -20,11 +20,14 @@
#include <linux/spinlock.h>
#include <linux/syscore_ops.h>
+#define IRQC_NMI 0
#define IRQC_IRQ_START 1
#define IRQC_TINT_COUNT 32
#define IRQC_SHARED_IRQ_COUNT 8
#define IRQC_IRQ_SHARED_START (IRQC_IRQ_START + IRQC_SHARED_IRQ_COUNT)
+#define NSCR 0x0
+#define NITSR 0x4
#define ISCR 0x10
#define IITSR 0x14
#define TSCR 0x20
@@ -43,6 +46,10 @@
#define TSSR_OFFSET(n) ((n) % 4)
#define TSSR_INDEX(n) ((n) / 4)
+#define NSCR_NSTAT 0
+#define NITSR_NTSEL_EDGE_FALLING 0
+#define NITSR_NTSEL_EDGE_RISING 1
+
#define TITSR_TITSEL_EDGE_RISING 0
#define TITSR_TITSEL_EDGE_FALLING 1
#define TITSR_TITSEL_LEVEL_HIGH 2
@@ -63,11 +70,13 @@
/**
* struct rzg2l_irqc_reg_cache - registers cache (necessary for suspend/resume)
+ * @nitsr: NITSR register
* @iitsr: IITSR register
* @inttsel: INTTSEL register
* @titsr: TITSR registers
*/
struct rzg2l_irqc_reg_cache {
+ u32 nitsr;
u32 iitsr;
u32 inttsel;
u32 titsr[2];
@@ -116,6 +125,23 @@ static struct rzg2l_irqc_priv *irq_data_to_priv(struct irq_data *data)
return data->domain->host_data;
}
+static void rzg2l_clear_nmi_int(struct rzg2l_irqc_priv *priv)
+{
+ u32 bit = BIT(NSCR_NSTAT);
+ u32 reg;
+
+ /* Writing is allowed only when NSTAT is 1 */
+ reg = readl_relaxed(priv->base + NSCR);
+ if (reg & bit) {
+ writel_relaxed(reg & ~bit, priv->base + NSCR);
+ /*
+ * Enforce that the posted write is flushed to prevent that the
+ * just handled interrupt is raised again.
+ */
+ readl_relaxed(priv->base + NSCR);
+ }
+}
+
static void rzg2l_clear_irq_int(struct rzg2l_irqc_priv *priv, unsigned int hwirq)
{
unsigned int hw_irq = hwirq - IRQC_IRQ_START;
@@ -155,6 +181,14 @@ static void rzg2l_clear_tint_int(struct rzg2l_irqc_priv *priv, unsigned int hwir
}
}
+static void rzg2l_irqc_nmi_eoi(struct irq_data *d)
+{
+ struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
+
+ rzg2l_clear_nmi_int(priv);
+ irq_chip_eoi_parent(d);
+}
+
static void rzg2l_irqc_irq_eoi(struct irq_data *d)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
@@ -341,6 +375,29 @@ static void rzg2l_irqc_tint_enable(struct irq_data *d)
irq_chip_enable_parent(d);
}
+static int rzg2l_nmi_set_type(struct irq_data *d, unsigned int type)
+{
+ struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
+ u32 sense;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_FALLING:
+ sense = NITSR_NTSEL_EDGE_FALLING;
+ break;
+
+ case IRQ_TYPE_EDGE_RISING:
+ sense = NITSR_NTSEL_EDGE_RISING;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ writel_relaxed(sense, priv->base + NITSR);
+
+ return 0;
+}
+
static int rzg2l_irq_set_type(struct irq_data *d, unsigned int type)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
@@ -467,11 +524,23 @@ static int rzg2l_irqc_tint_set_type(struct irq_data *d, unsigned int type)
return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
}
+static int rzg2l_irqc_nmi_set_type(struct irq_data *d, unsigned int type)
+{
+ int ret;
+
+ ret = rzg2l_nmi_set_type(d, type);
+ if (ret)
+ return ret;
+
+ return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
+}
+
static int rzg2l_irqc_irq_suspend(void *data)
{
struct rzg2l_irqc_reg_cache *cache = &rzg2l_irqc_data->cache;
void __iomem *base = rzg2l_irqc_data->base;
+ cache->nitsr = readl_relaxed(base + NITSR);
cache->iitsr = readl_relaxed(base + IITSR);
if (rzg2l_irqc_data->info.shared_irq_cnt)
cache->inttsel = readl_relaxed(base + INTTSEL);
@@ -496,6 +565,7 @@ static void rzg2l_irqc_irq_resume(void *data)
if (rzg2l_irqc_data->info.shared_irq_cnt)
writel_relaxed(cache->inttsel, base + INTTSEL);
writel_relaxed(cache->iitsr, base + IITSR);
+ writel_relaxed(cache->nitsr, base + NITSR);
}
static const struct syscore_ops rzg2l_irqc_syscore_ops = {
@@ -507,6 +577,23 @@ static struct syscore rzg2l_irqc_syscore = {
.ops = &rzg2l_irqc_syscore_ops,
};
+static const struct irq_chip rzg2l_irqc_nmi_chip = {
+ .name = "rzg2l-irqc",
+ .irq_eoi = rzg2l_irqc_nmi_eoi,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_disable = irq_chip_disable_parent,
+ .irq_enable = irq_chip_enable_parent,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .irq_retrigger = irq_chip_retrigger_hierarchy,
+ .irq_set_type = rzg2l_irqc_nmi_set_type,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .flags = IRQCHIP_MASK_ON_SUSPEND |
+ IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE,
+};
+
static const struct irq_chip rzg2l_irqc_irq_chip = {
.name = "rzg2l-irqc",
.irq_eoi = rzg2l_irqc_irq_eoi,
@@ -662,7 +749,9 @@ static int rzg2l_irqc_alloc(struct irq_domain *domain, unsigned int virq,
* from 16-31 bits. TINT from the pinctrl driver needs to be programmed
* in IRQC registers to enable a given gpio pin as interrupt.
*/
- if (hwirq > priv->info.irq_count) {
+ if (hwirq == IRQC_NMI) {
+ chip = &rzg2l_irqc_nmi_chip;
+ } else if (hwirq > priv->info.irq_count) {
tint = TINT_EXTRACT_GPIOINT(hwirq);
hwirq = TINT_EXTRACT_HWIRQ(hwirq);
chip = priv->tint_chip;
--
2.43.0
The following commit has been merged into the irq/drivers branch of tip:
Commit-ID: 0e5988549dc73d985cc12c4cb438771ced74f522
Gitweb: https://git.kernel.org/tip/0e5988549dc73d985cc12c4cb438771ced74f522
Author: Biju Das <biju.das.jz@bp.renesas.com>
AuthorDate: Wed, 01 Apr 2026 12:45:00 +01:00
Committer: Thomas Gleixner <tglx@kernel.org>
CommitterDate: Wed, 01 Apr 2026 18:41:58 +02:00
irqchip/renesas-rzg2l: Add NMI support
The RZ/G2L SoC has an NMI interrupt. Add support for the NMI interrupt.
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
Signed-off-by: Thomas Gleixner <tglx@kernel.org>
Link: https://patch.msgid.link/20260401114504.332825-1-biju.das.jz@bp.renesas.com
---
drivers/irqchip/irq-renesas-rzg2l.c | 99 +++++++++++++++++++++++++--
1 file changed, 95 insertions(+), 4 deletions(-)
diff --git a/drivers/irqchip/irq-renesas-rzg2l.c b/drivers/irqchip/irq-renesas-rzg2l.c
index 199b3c6..f6b2e69 100644
--- a/drivers/irqchip/irq-renesas-rzg2l.c
+++ b/drivers/irqchip/irq-renesas-rzg2l.c
@@ -20,11 +20,14 @@
#include <linux/spinlock.h>
#include <linux/syscore_ops.h>
+#define IRQC_NMI 0
#define IRQC_IRQ_START 1
#define IRQC_TINT_COUNT 32
#define IRQC_SHARED_IRQ_COUNT 8
#define IRQC_IRQ_SHARED_START (IRQC_IRQ_START + IRQC_SHARED_IRQ_COUNT)
+#define NSCR 0x0
+#define NITSR 0x4
#define ISCR 0x10
#define IITSR 0x14
#define TSCR 0x20
@@ -43,6 +46,10 @@
#define TSSR_OFFSET(n) ((n) % 4)
#define TSSR_INDEX(n) ((n) / 4)
+#define NSCR_NSTAT 0
+#define NITSR_NTSEL_EDGE_FALLING 0
+#define NITSR_NTSEL_EDGE_RISING 1
+
#define TITSR_TITSEL_EDGE_RISING 0
#define TITSR_TITSEL_EDGE_FALLING 1
#define TITSR_TITSEL_LEVEL_HIGH 2
@@ -63,11 +70,13 @@
/**
* struct rzg2l_irqc_reg_cache - registers cache (necessary for suspend/resume)
- * @iitsr: IITSR register
- * @inttsel: INTTSEL register
- * @titsr: TITSR registers
+ * @nitsr: NITSR register
+ * @iitsr: IITSR register
+ * @inttsel: INTTSEL register
+ * @titsr: TITSR registers
*/
struct rzg2l_irqc_reg_cache {
+ u32 nitsr;
u32 iitsr;
u32 inttsel;
u32 titsr[2];
@@ -116,6 +125,28 @@ static struct rzg2l_irqc_priv *irq_data_to_priv(struct irq_data *data)
return data->domain->host_data;
}
+static void rzg2l_clear_nmi_int(struct rzg2l_irqc_priv *priv)
+{
+ u32 bit = BIT(NSCR_NSTAT);
+ u32 reg;
+
+ /*
+ * No locking required as the register is not shared
+ * with other interrupts.
+ *
+ * Writing is allowed only when NSTAT is 1
+ */
+ reg = readl_relaxed(priv->base + NSCR);
+ if (reg & bit) {
+ writel_relaxed(reg & ~bit, priv->base + NSCR);
+ /*
+ * Enforce that the posted write is flushed to prevent that the
+ * just handled interrupt is raised again.
+ */
+ readl_relaxed(priv->base + NSCR);
+ }
+}
+
static void rzg2l_clear_irq_int(struct rzg2l_irqc_priv *priv, unsigned int hwirq)
{
unsigned int hw_irq = hwirq - IRQC_IRQ_START;
@@ -155,6 +186,14 @@ static void rzg2l_clear_tint_int(struct rzg2l_irqc_priv *priv, unsigned int hwir
}
}
+static void rzg2l_irqc_nmi_eoi(struct irq_data *d)
+{
+ struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
+
+ rzg2l_clear_nmi_int(priv);
+ irq_chip_eoi_parent(d);
+}
+
static void rzg2l_irqc_irq_eoi(struct irq_data *d)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
@@ -341,6 +380,26 @@ static void rzg2l_irqc_tint_enable(struct irq_data *d)
irq_chip_enable_parent(d);
}
+static int rzg2l_nmi_set_type(struct irq_data *d, unsigned int type)
+{
+ struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
+ u32 sense;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_FALLING:
+ sense = NITSR_NTSEL_EDGE_FALLING;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ sense = NITSR_NTSEL_EDGE_RISING;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel_relaxed(sense, priv->base + NITSR);
+ return 0;
+}
+
static int rzg2l_irq_set_type(struct irq_data *d, unsigned int type)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
@@ -467,11 +526,23 @@ static int rzg2l_irqc_tint_set_type(struct irq_data *d, unsigned int type)
return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
}
+static int rzg2l_irqc_nmi_set_type(struct irq_data *d, unsigned int type)
+{
+ int ret;
+
+ ret = rzg2l_nmi_set_type(d, type);
+ if (ret)
+ return ret;
+
+ return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
+}
+
static int rzg2l_irqc_irq_suspend(void *data)
{
struct rzg2l_irqc_reg_cache *cache = &rzg2l_irqc_data->cache;
void __iomem *base = rzg2l_irqc_data->base;
+ cache->nitsr = readl_relaxed(base + NITSR);
cache->iitsr = readl_relaxed(base + IITSR);
if (rzg2l_irqc_data->info.shared_irq_cnt)
cache->inttsel = readl_relaxed(base + INTTSEL);
@@ -496,6 +567,7 @@ static void rzg2l_irqc_irq_resume(void *data)
if (rzg2l_irqc_data->info.shared_irq_cnt)
writel_relaxed(cache->inttsel, base + INTTSEL);
writel_relaxed(cache->iitsr, base + IITSR);
+ writel_relaxed(cache->nitsr, base + NITSR);
}
static const struct syscore_ops rzg2l_irqc_syscore_ops = {
@@ -507,6 +579,23 @@ static struct syscore rzg2l_irqc_syscore = {
.ops = &rzg2l_irqc_syscore_ops,
};
+static const struct irq_chip rzg2l_irqc_nmi_chip = {
+ .name = "rzg2l-irqc",
+ .irq_eoi = rzg2l_irqc_nmi_eoi,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_disable = irq_chip_disable_parent,
+ .irq_enable = irq_chip_enable_parent,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .irq_retrigger = irq_chip_retrigger_hierarchy,
+ .irq_set_type = rzg2l_irqc_nmi_set_type,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .flags = IRQCHIP_MASK_ON_SUSPEND |
+ IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE,
+};
+
static const struct irq_chip rzg2l_irqc_irq_chip = {
.name = "rzg2l-irqc",
.irq_eoi = rzg2l_irqc_irq_eoi,
@@ -662,7 +751,9 @@ static int rzg2l_irqc_alloc(struct irq_domain *domain, unsigned int virq,
* from 16-31 bits. TINT from the pinctrl driver needs to be programmed
* in IRQC registers to enable a given gpio pin as interrupt.
*/
- if (hwirq > priv->info.irq_count) {
+ if (hwirq == IRQC_NMI) {
+ chip = &rzg2l_irqc_nmi_chip;
+ } else if (hwirq > priv->info.irq_count) {
tint = TINT_EXTRACT_GPIOINT(hwirq);
hwirq = TINT_EXTRACT_HWIRQ(hwirq);
chip = priv->tint_chip;
© 2016 - 2026 Red Hat, Inc.