Updates for the interrupt chip driver subsystem:

- A large refactoring for the Renesas RZV2H driver to add new interrupt
     types cleanly.
 
   - A large refactoring for the Renesas RZG2L driver to add support the new
     RZ/G3L variant.
 
   - Add support for the new NXP S32N79 chip in the IMX irq-steer driver.
 
   - Add support for the Apple AICv3 variant
 
   - Enhance the Loongson PCH LPC driver so it can be used on MIPS with
     device tree firmware
 
   - Allow the PIC32 EVIC driver to be built independent of MIPS in compile
     tests.
 
   - The usual small fixes and enhancements all over the place
 -----BEGIN PGP SIGNATURE-----
 
 iQJEBAABCgAuFiEEQp8+kY+LLUocC4bMphj1TA10mKEFAmnbuaAQHHRnbHhAa2Vy
 bmVsLm9yZwAKCRCmGPVMDXSYoQc6D/9i6QTUNw4ZqpZSjJvrxX2mv49ej3FkWsQf
 1P59ej9A0/w1+0A8SyAFd6ExuvO3am9k64nhpsFlwv0lMTu57va3Oj14E28fjN+M
 H1XWJYCLX3p7MWGsGFCmbHIeJJQnwCqyiZcs3DG0BMA5iYglJEcOijB+h+FCAwCN
 jjiBW4bbqPxPcpT90uQZhwwa3eUvkrkwzZEedKkbtvgPy3jdmKvrNCAte9GJSQu3
 CvLgAB1Zraq1yIDFQN/km5NByan7pVmbGuv10K/jqirqmjplM2H0X+fwXlWz4M0Q
 uo33xDtJvtOegnySiJ6EimY+GTHDiloZpn2nqqUnHRIR0hxYjYudukSRorxbjXWv
 c93CGk1/Mq9WHqCsWvX5xe75Ttuf5xsfej8DETYvP+cTkGV/Kk9EUNmDUoYnGkiv
 UZyZBUMWejJLjnNmMpouUwW9Vc/08OLAtmqJgQukl2cbh/ujcmGC0TSA2SpBKlJT
 FpGU6ZsHWxpm1UHVv+9Uhx5dArRVNeTvjNgLFtu+ZkGM+C2oBs/MKHpu44mBfAf8
 Ex4sFzCslKXbYo6TwO48w9VIMQJ6DDPrwza1YN6hRn4IwBkRXl3SQsT3pJxG1WMg
 NcP4dMRfe68o9y4ywxnoomH+qlux4j/tq3J7PqRY5s3WzWe8jXqfiQGNE1f10rop
 gGZe5f+/Mw==
 =kvZ2
 -----END PGP SIGNATURE-----

Merge tag 'irq-drivers-2026-04-12' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull interrupt chip driver updates from Thomas Gleixner:

 - A large refactoring for the Renesas RZV2H driver to add new interrupt
   types cleanly

 - A large refactoring for the Renesas RZG2L driver to add support the
   new RZ/G3L variant

 - Add support for the new NXP S32N79 chip in the IMX irq-steer driver

 - Add support for the Apple AICv3 variant

 - Enhance the Loongson PCH LPC driver so it can be used on MIPS with
   device tree firmware

 - Allow the PIC32 EVIC driver to be built independent of MIPS in
   compile tests

 - The usual small fixes and enhancements all over the place

* tag 'irq-drivers-2026-04-12' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (46 commits)
  irqchip/irq-pic32-evic: Add __maybe_unused for board_bind_eic_interrupt in COMPILE_TEST
  irqchip/renesas-rzv2h: Kill icu_err string
  irqchip/renesas-rzv2h: Kill swint_names[]
  irqchip/renesas-rzv2h: Kill swint_idx[]
  irqchip/renesas-rzg2l: Add NMI support
  irqchip/renesas-rzg2l: Clear the shared interrupt bit in rzg2l_irqc_free()
  irqchip/renesas-rzg2l: Replace raw_spin_{lock,unlock} with guard() in rzg2l_irq_set_type()
  irqchip/gic-v3: Print a warning for out-of-range interrupt numbers
  irqchip/renesas-rzg2l: Add shared interrupt support
  irqchip/renesas-rzg2l: Add RZ/G3L support
  irqchip/renesas-rzg2l: Drop IRQC_IRQ_COUNT macro
  irqchip/renesas-rzg2l: Drop IRQC_TINT_START macro
  irqchip/renesas-rzg2l: Drop IRQC_NUM_IRQ macro
  irqchip/renesas-rzg2l: Dynamically allocate fwspec array
  irqchip/renesas-rzg2l: Split rzfive_irqc_{mask,unmask} into separate IRQ and TINT handlers
  irqchip/renesas-rzg2l: Split rzfive_tint_irq_endisable() into separate IRQ and TINT helpers
  irqchip/renesas-rzg2l: Replace rzg2l_irqc_irq_{enable,disable} with TINT-specific handlers
  irqchip/renesas-rzg2l: Split set_type handler into separate IRQ and TINT functions
  irqchip/renesas-rzg2l: Split EOI handler into separate IRQ and TINT functions
  irqchip/renesas-rzg2l: Replace single irq_chip with per-region irq_chip instances
  ...
master
Linus Torvalds 2026-04-14 10:18:10 -07:00
commit c0ecb2a9ee
15 changed files with 1135 additions and 375 deletions

View File

@ -4,10 +4,10 @@
$id: http://devicetree.org/schemas/interrupt-controller/apple,aic2.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Apple Interrupt Controller 2
title: Apple Interrupt Controller 2 and 3
maintainers:
- Hector Martin <marcan@marcan.st>
- Janne Grunau <j@jannau.net>
description: |
The Apple Interrupt Controller 2 is a simple interrupt controller present on
@ -28,14 +28,24 @@ description: |
which do not go through a discrete interrupt controller. It also handles
FIQ-based Fast IPIs.
The Apple Interrupt Controller 3 is in its base functionality very similar to
the Apple Interrupt Controller 2 and uses the same device tree bindings. It is
found on Apple ARM SoCs platforms starting with t8122 (M3).
properties:
compatible:
items:
- enum:
- apple,t8112-aic
- apple,t6000-aic
- apple,t6020-aic
- const: apple,aic2
oneOf:
- items:
- enum:
- apple,t6000-aic
- apple,t6020-aic
- apple,t8112-aic
- const: apple,aic2
- items:
- enum:
- apple,t6030-aic3
- const: apple,t8122-aic3
- const: apple,t8122-aic3
interrupt-controller: true
@ -117,7 +127,9 @@ allOf:
properties:
compatible:
contains:
const: apple,t8112-aic
enum:
- apple,t8112-aic
- apple,t8122-aic3
then:
properties:
'#interrupt-cells':

View File

@ -0,0 +1,52 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/interrupt-controller/loongson,pch-lpc.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Loongson PCH LPC Controller
maintainers:
- Jiaxun Yang <jiaxun.yang@flygoat.com>
description:
This interrupt controller is found in the Loongson LS7A family of PCH for
accepting interrupts sent by LPC-connected peripherals and signalling PIC
via a single interrupt line when interrupts are available.
properties:
compatible:
const: loongson,ls7a-lpc
reg:
maxItems: 1
interrupt-controller: true
interrupts:
maxItems: 1
'#interrupt-cells':
const: 2
required:
- compatible
- reg
- interrupt-controller
- interrupts
- '#interrupt-cells'
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
lpc: interrupt-controller@10002000 {
compatible = "loongson,ls7a-lpc";
reg = <0x10002000 0x400>;
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&pic>;
interrupts = <19 IRQ_TYPE_LEVEL_HIGH>;
};
...

View File

@ -30,7 +30,9 @@ properties:
- renesas,r9a08g045-irqc # RZ/G3S
- const: renesas,rzg2l-irqc
- const: renesas,r9a07g043f-irqc # RZ/Five
- enum:
- renesas,r9a07g043f-irqc # RZ/Five
- renesas,r9a08g046-irqc # RZ/G3L
'#interrupt-cells':
description: The first cell should contain a macro RZG2L_{NMI,IRQX} included in the
@ -48,107 +50,35 @@ properties:
interrupts:
minItems: 45
items:
- description: NMI interrupt
- description: IRQ0 interrupt
- description: IRQ1 interrupt
- description: IRQ2 interrupt
- description: IRQ3 interrupt
- description: IRQ4 interrupt
- description: IRQ5 interrupt
- description: IRQ6 interrupt
- description: IRQ7 interrupt
- description: GPIO interrupt, TINT0
- description: GPIO interrupt, TINT1
- description: GPIO interrupt, TINT2
- description: GPIO interrupt, TINT3
- description: GPIO interrupt, TINT4
- description: GPIO interrupt, TINT5
- description: GPIO interrupt, TINT6
- description: GPIO interrupt, TINT7
- description: GPIO interrupt, TINT8
- description: GPIO interrupt, TINT9
- description: GPIO interrupt, TINT10
- description: GPIO interrupt, TINT11
- description: GPIO interrupt, TINT12
- description: GPIO interrupt, TINT13
- description: GPIO interrupt, TINT14
- description: GPIO interrupt, TINT15
- description: GPIO interrupt, TINT16
- description: GPIO interrupt, TINT17
- description: GPIO interrupt, TINT18
- description: GPIO interrupt, TINT19
- description: GPIO interrupt, TINT20
- description: GPIO interrupt, TINT21
- description: GPIO interrupt, TINT22
- description: GPIO interrupt, TINT23
- description: GPIO interrupt, TINT24
- description: GPIO interrupt, TINT25
- description: GPIO interrupt, TINT26
- description: GPIO interrupt, TINT27
- description: GPIO interrupt, TINT28
- description: GPIO interrupt, TINT29
- description: GPIO interrupt, TINT30
- description: GPIO interrupt, TINT31
- description: Bus error interrupt
- description: ECCRAM0 or combined ECCRAM0/1 1bit error interrupt
- description: ECCRAM0 or combined ECCRAM0/1 2bit error interrupt
- description: ECCRAM0 or combined ECCRAM0/1 error overflow interrupt
- description: ECCRAM1 1bit error interrupt
- description: ECCRAM1 2bit error interrupt
- description: ECCRAM1 error overflow interrupt
maxItems: 61
interrupt-names:
minItems: 45
maxItems: 61
items:
- const: nmi
- const: irq0
- const: irq1
- const: irq2
- const: irq3
- const: irq4
- const: irq5
- const: irq6
- const: irq7
- const: tint0
- const: tint1
- const: tint2
- const: tint3
- const: tint4
- const: tint5
- const: tint6
- const: tint7
- const: tint8
- const: tint9
- const: tint10
- const: tint11
- const: tint12
- const: tint13
- const: tint14
- const: tint15
- const: tint16
- const: tint17
- const: tint18
- const: tint19
- const: tint20
- const: tint21
- const: tint22
- const: tint23
- const: tint24
- const: tint25
- const: tint26
- const: tint27
- const: tint28
- const: tint29
- const: tint30
- const: tint31
- const: bus-err
- const: ec7tie1-0
- const: ec7tie2-0
- const: ec7tiovf-0
- const: ec7tie1-1
- const: ec7tie2-1
- const: ec7tiovf-1
oneOf:
- description: NMI interrupt
const: nmi
- description: External IRQ interrupt
pattern: '^irq([0-9]|1[0-5])$'
- description: GPIO interrupt
pattern: '^tint([0-9]|1[0-9]|2[0-9]|3[0-1])$'
- description: Bus error interrupt
const: bus-err
- description: ECCRAM0 or combined ECCRAM0/1 1bit error interrupt
const: ec7tie1-0
- description: ECCRAM0 or combined ECCRAM0/1 2bit error interrupt
const: ec7tie2-0
- description: ECCRAM0 or combined ECCRAM0/1 error overflow interrupt
const: ec7tiovf-0
- description: ECCRAM1 1bit error interrupt
const: ec7tie1-1
- description: ECCRAM1 2bit error interrupt
const: ec7tie2-1
- description: ECCRAM1 error overflow interrupt
const: ec7tiovf-1
- description: Integrated GPT Error interrupt
pattern: '^ovfunf([0-7])$'
clocks:
maxItems: 2
@ -180,6 +110,24 @@ required:
allOf:
- $ref: /schemas/interrupt-controller.yaml#
- if:
properties:
compatible:
contains:
enum:
- renesas,r9a07g043f-irqc
- renesas,r9a07g043u-irqc
- renesas,r9a07g044-irqc
- renesas,r9a07g054-irqc
then:
properties:
interrupts:
minItems: 48
maxItems: 48
interrupt-names:
minItems: 48
maxItems: 48
- if:
properties:
compatible:
@ -192,12 +140,19 @@ allOf:
maxItems: 45
interrupt-names:
maxItems: 45
else:
- if:
properties:
compatible:
contains:
enum:
- renesas,r9a08g046-irqc
then:
properties:
interrupts:
minItems: 48
minItems: 61
interrupt-names:
minItems: 48
minItems: 61
unevaluatedProperties: false

View File

@ -11,6 +11,7 @@
#include <linux/irqchip.h>
#include <linux/kernel_stat.h>
#include <linux/proc_fs.h>
#include <linux/minmax.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
@ -99,6 +100,11 @@ int __init arch_probe_nr_irqs(void)
return NR_IRQS_LEGACY;
}
unsigned int arch_dynirq_lower_bound(unsigned int from)
{
return MAX(from, NR_IRQS_LEGACY);
}
void __init init_IRQ(void)
{
int i;

View File

@ -7,6 +7,7 @@
#include <linux/irqchip.h>
#include <linux/logic_pio.h>
#include <linux/memblock.h>
#include <linux/minmax.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <asm/bootinfo.h>
@ -227,3 +228,8 @@ void __init arch_init_irq(void)
reserve_pio_range();
irqchip_init();
}
unsigned int arch_dynirq_lower_bound(unsigned int from)
{
return MAX(from, NR_IRQS_LEGACY);
}

View File

@ -20,7 +20,6 @@ config PIC32MZDA
select LIBFDT
select USE_OF
select PINCTRL
select PIC32_EVIC
help
Support for the Microchip PIC32MZDA microcontroller.

View File

@ -252,9 +252,12 @@ config ORION_IRQCHIP
select IRQ_DOMAIN
config PIC32_EVIC
bool
def_bool MACH_PIC32 || COMPILE_TEST
select GENERIC_IRQ_CHIP
select IRQ_DOMAIN
help
Enable support for the interrupt controller on the Microchip PIC32
family of platforms.
config JCORE_AIC
bool "J-Core integrated AIC" if COMPILE_TEST
@ -541,11 +544,11 @@ config CSKY_APB_INTC
config IMX_IRQSTEER
bool "i.MX IRQSTEER support"
depends on ARCH_MXC || COMPILE_TEST
default ARCH_MXC
depends on ARCH_MXC || ARCH_S32 || COMPILE_TEST
default y if ARCH_MXC || ARCH_S32
select IRQ_DOMAIN
help
Support for the i.MX IRQSTEER interrupt multiplexer/remapper.
Support for the i.MX and S32 IRQSTEER interrupt multiplexer/remapper.
config IMX_INTMUX
bool "i.MX INTMUX support" if COMPILE_TEST
@ -761,7 +764,6 @@ config LOONGSON_PCH_MSI
config LOONGSON_PCH_LPC
bool "Loongson PCH LPC Controller"
depends on LOONGARCH
depends on MACH_LOONGSON64 || LOONGARCH
default MACH_LOONGSON64
select IRQ_DOMAIN_HIERARCHY

View File

@ -134,8 +134,12 @@
#define AIC2_IRQ_CFG 0x2000
/* AIC v3 registers (MMIO) */
#define AIC3_IRQ_CFG 0x10000
/*
* AIC2 registers are laid out like this, starting at AIC2_IRQ_CFG:
* AIC3 registers use the same layout but start at AIC3_IRQ_CFG:
*
* Repeat for each die:
* IRQ_CFG: u32 * MAX_IRQS
@ -293,6 +297,15 @@ static const struct aic_info aic2_info __initconst = {
.local_fast_ipi = true,
};
static const struct aic_info aic3_info __initconst = {
.version = 3,
.irq_cfg = AIC3_IRQ_CFG,
.fast_ipi = true,
.local_fast_ipi = true,
};
static const struct of_device_id aic_info_match[] = {
{
.compatible = "apple,t8103-aic",
@ -310,6 +323,10 @@ static const struct of_device_id aic_info_match[] = {
.compatible = "apple,aic2",
.data = &aic2_info,
},
{
.compatible = "apple,t8122-aic3",
.data = &aic3_info,
},
{}
};
@ -620,7 +637,7 @@ static int aic_irq_domain_map(struct irq_domain *id, unsigned int irq,
u32 type = FIELD_GET(AIC_EVENT_TYPE, hw);
struct irq_chip *chip = &aic_chip;
if (ic->info.version == 2)
if (ic->info.version == 2 || ic->info.version == 3)
chip = &aic2_chip;
if (type == AIC_EVENT_TYPE_IRQ) {
@ -991,7 +1008,7 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p
break;
}
case 2: {
case 2 ... 3: {
u32 info1, info3;
info1 = aic_ic_read(irqc, AIC2_INFO1);
@ -1065,7 +1082,7 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p
off += irqc->info.die_stride;
}
if (irqc->info.version == 2) {
if (irqc->info.version == 2 || irqc->info.version == 3) {
u32 config = aic_ic_read(irqc, AIC2_CONFIG);
config |= AIC2_CONFIG_ENABLE;
@ -1116,3 +1133,4 @@ err_unmap:
IRQCHIP_DECLARE(apple_aic, "apple,aic", aic_of_ic_init);
IRQCHIP_DECLARE(apple_aic2, "apple,aic2", aic_of_ic_init);
IRQCHIP_DECLARE(apple_aic3, "apple,t8122-aic3", aic_of_ic_init);

View File

@ -1603,15 +1603,23 @@ static int gic_irq_domain_translate(struct irq_domain *d,
switch (fwspec->param[0]) {
case 0: /* SPI */
if (fwspec->param[1] > 987)
pr_warn_once("SPI %u out of range (use ESPI?)\n", fwspec->param[1]);
*hwirq = fwspec->param[1] + 32;
break;
case 1: /* PPI */
if (fwspec->param[1] > 15)
pr_warn_once("PPI %u out of range (use EPPI?)\n", fwspec->param[1]);
*hwirq = fwspec->param[1] + 16;
break;
case 2: /* ESPI */
if (fwspec->param[1] > 1023)
pr_warn_once("ESPI %u out of range\n", fwspec->param[1]);
*hwirq = fwspec->param[1] + ESPI_BASE_INTID;
break;
case 3: /* EPPI */
if (fwspec->param[1] > 63)
pr_warn_once("EPPI %u out of range\n", fwspec->param[1]);
*hwirq = fwspec->param[1] + EPPI_BASE_INTID;
break;
case GIC_IRQ_TYPE_LPI: /* LPI */
@ -2252,7 +2260,7 @@ static int __init gic_of_init(struct device_node *node, struct device_node *pare
out_unmap_rdist:
for (i = 0; i < nr_redist_regions; i++)
if (rdist_regs[i].redist_base && !IS_ERR(rdist_regs[i].redist_base))
if (!IS_ERR_OR_NULL(rdist_regs[i].redist_base))
iounmap(rdist_regs[i].redist_base);
kfree(rdist_regs);
out_unmap_dist:

View File

@ -26,19 +26,38 @@
#define CHAN_MAX_OUTPUT_INT 0xF
struct irqsteer_data {
void __iomem *regs;
struct clk *ipg_clk;
int irq[CHAN_MAX_OUTPUT_INT];
int irq_count;
raw_spinlock_t lock;
int reg_num;
int channel;
struct irq_domain *domain;
u32 *saved_reg;
struct device *dev;
/* SoC does not implement the CHANCTRL register */
#define IRQSTEER_QUIRK_NO_CHANCTRL BIT(0)
struct irqsteer_devtype_data {
u32 quirks;
};
struct irqsteer_data {
void __iomem *regs;
struct clk *ipg_clk;
int irq[CHAN_MAX_OUTPUT_INT];
int irq_count;
raw_spinlock_t lock;
int reg_num;
int channel;
struct irq_domain *domain;
u32 *saved_reg;
struct device *dev;
const struct irqsteer_devtype_data *devtype_data;
};
static const struct irqsteer_devtype_data imx_data = { };
static const struct irqsteer_devtype_data s32n79_data = {
.quirks = IRQSTEER_QUIRK_NO_CHANCTRL,
};
static bool irqsteer_has_chanctrl(const struct irqsteer_devtype_data *data)
{
return !(data->quirks & IRQSTEER_QUIRK_NO_CHANCTRL);
}
static int imx_irqsteer_get_reg_index(struct irqsteer_data *data,
unsigned long irqnum)
{
@ -188,6 +207,10 @@ static int imx_irqsteer_probe(struct platform_device *pdev)
if (ret)
return ret;
data->devtype_data = device_get_match_data(&pdev->dev);
if (!data->devtype_data)
return dev_err_probe(&pdev->dev, -ENODEV, "failed to match device data\n");
/*
* There is one output irq for each group of 64 inputs.
* One register bit map can represent 32 input interrupts.
@ -210,7 +233,8 @@ static int imx_irqsteer_probe(struct platform_device *pdev)
}
/* steer all IRQs into configured channel */
writel_relaxed(BIT(data->channel), data->regs + CHANCTRL);
if (irqsteer_has_chanctrl(data->devtype_data))
writel_relaxed(BIT(data->channel), data->regs + CHANCTRL);
data->domain = irq_domain_create_linear(dev_fwnode(&pdev->dev), data->reg_num * 32,
&imx_irqsteer_domain_ops, data);
@ -279,7 +303,9 @@ static void imx_irqsteer_restore_regs(struct irqsteer_data *data)
{
int i;
writel_relaxed(BIT(data->channel), data->regs + CHANCTRL);
if (irqsteer_has_chanctrl(data->devtype_data))
writel_relaxed(BIT(data->channel), data->regs + CHANCTRL);
for (i = 0; i < data->reg_num; i++)
writel_relaxed(data->saved_reg[i],
data->regs + CHANMASK(i, data->reg_num));
@ -319,7 +345,8 @@ static const struct dev_pm_ops imx_irqsteer_pm_ops = {
};
static const struct of_device_id imx_irqsteer_dt_ids[] = {
{ .compatible = "fsl,imx-irqsteer", },
{ .compatible = "fsl,imx-irqsteer", .data = &imx_data },
{ .compatible = "nxp,s32n79-irqsteer", .data = &s32n79_data },
{},
};

View File

@ -13,6 +13,8 @@
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/syscore_ops.h>
#include "irq-loongson.h"
@ -175,13 +177,10 @@ static struct syscore pch_lpc_syscore = {
.ops = &pch_lpc_syscore_ops,
};
int __init pch_lpc_acpi_init(struct irq_domain *parent,
struct acpi_madt_lpc_pic *acpi_pchlpc)
static int __init pch_lpc_init(phys_addr_t addr, unsigned long size,
struct fwnode_handle *irq_handle, int parent_irq)
{
int parent_irq;
struct pch_lpc *priv;
struct irq_fwspec fwspec;
struct fwnode_handle *irq_handle;
priv = kzalloc_obj(*priv);
if (!priv)
@ -189,7 +188,7 @@ int __init pch_lpc_acpi_init(struct irq_domain *parent,
raw_spin_lock_init(&priv->lpc_lock);
priv->base = ioremap(acpi_pchlpc->address, acpi_pchlpc->size);
priv->base = ioremap(addr, size);
if (!priv->base)
goto free_priv;
@ -198,12 +197,6 @@ int __init pch_lpc_acpi_init(struct irq_domain *parent,
goto iounmap_base;
}
irq_handle = irq_domain_alloc_named_fwnode("lpcintc");
if (!irq_handle) {
pr_err("Unable to allocate domain handle\n");
goto iounmap_base;
}
/*
* The LPC interrupt controller is a legacy i8259-compatible device,
* which requires a static 1:1 mapping for IRQs 0-15.
@ -213,15 +206,10 @@ int __init pch_lpc_acpi_init(struct irq_domain *parent,
&pch_lpc_domain_ops, priv);
if (!priv->lpc_domain) {
pr_err("Failed to create IRQ domain\n");
goto free_irq_handle;
goto iounmap_base;
}
pch_lpc_reset(priv);
fwspec.fwnode = parent->fwnode;
fwspec.param[0] = acpi_pchlpc->cascade + GSI_MIN_PCH_IRQ;
fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH;
fwspec.param_count = 2;
parent_irq = irq_create_fwspec_mapping(&fwspec);
irq_set_chained_handler_and_data(parent_irq, lpc_irq_dispatch, priv);
pch_lpc_priv = priv;
@ -230,8 +218,6 @@ int __init pch_lpc_acpi_init(struct irq_domain *parent,
return 0;
free_irq_handle:
irq_domain_free_fwnode(irq_handle);
iounmap_base:
iounmap(priv->base);
free_priv:
@ -239,3 +225,69 @@ free_priv:
return -ENOMEM;
}
#ifdef CONFIG_ACPI
int __init pch_lpc_acpi_init(struct irq_domain *parent, struct acpi_madt_lpc_pic *acpi_pchlpc)
{
struct fwnode_handle *irq_handle;
struct irq_fwspec fwspec;
int parent_irq, ret;
irq_handle = irq_domain_alloc_named_fwnode("lpcintc");
if (!irq_handle) {
pr_err("Unable to allocate domain handle\n");
return -ENOMEM;
}
fwspec.fwnode = parent->fwnode;
fwspec.param[0] = acpi_pchlpc->cascade + GSI_MIN_PCH_IRQ;
fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH;
fwspec.param_count = 2;
parent_irq = irq_create_fwspec_mapping(&fwspec);
if (parent_irq <= 0) {
pr_err("Unable to map LPC parent interrupt\n");
irq_domain_free_fwnode(irq_handle);
return -ENOMEM;
}
ret = pch_lpc_init(acpi_pchlpc->address, acpi_pchlpc->size, irq_handle, parent_irq);
if (ret) {
irq_dispose_mapping(parent_irq);
irq_domain_free_fwnode(irq_handle);
return ret;
}
return 0;
}
#endif /* CONFIG_ACPI */
#ifdef CONFIG_OF
static int __init pch_lpc_of_init(struct device_node *node, struct device_node *parent)
{
struct fwnode_handle *irq_handle;
struct resource res;
int parent_irq, ret;
if (of_address_to_resource(node, 0, &res))
return -EINVAL;
parent_irq = irq_of_parse_and_map(node, 0);
if (!parent_irq) {
pr_err("Failed to get the parent IRQ for LPC IRQs\n");
return -EINVAL;
}
irq_handle = of_fwnode_handle(node);
ret = pch_lpc_init(res.start, resource_size(&res), irq_handle,
parent_irq);
if (ret) {
irq_dispose_mapping(parent_irq);
return ret;
}
return 0;
}
IRQCHIP_DECLARE(pch_lpc, "loongson,ls7a-lpc", pch_lpc_of_init);
#endif /* CONFIG_OF */

View File

@ -217,7 +217,7 @@ err_unmap:
for (i = 0; i < odmis_count; i++) {
struct odmi_data *odmi = &odmis[i];
if (odmi->base && !IS_ERR(odmi->base))
if (!IS_ERR_OR_NULL(odmi->base))
iounmap(odmis[i].base);
}
bitmap_free(odmis_bm);

View File

@ -15,8 +15,10 @@
#include <linux/irq.h>
#include <linux/platform_data/pic32.h>
#ifdef CONFIG_MIPS
#include <asm/irq.h>
#include <asm/traps.h>
#endif
#define REG_INTCON 0x0000
#define REG_INTSTAT 0x0020
@ -40,6 +42,7 @@ struct evic_chip_data {
static struct irq_domain *evic_irq_domain;
static void __iomem *evic_base;
#ifdef CONFIG_MIPS
asmlinkage void __weak plat_irq_dispatch(void)
{
unsigned int hwirq;
@ -47,6 +50,9 @@ asmlinkage void __weak plat_irq_dispatch(void)
hwirq = readl(evic_base + REG_INTSTAT) & 0xFF;
do_domain_IRQ(evic_irq_domain, hwirq);
}
#else
static __maybe_unused void (*board_bind_eic_interrupt)(int irq, int regset);
#endif
static struct evic_chip_data *irqd_to_priv(struct irq_data *data)
{
@ -196,7 +202,7 @@ static void __init pic32_ext_irq_of_init(struct irq_domain *domain)
of_property_for_each_u32(node, pname, hwirq) {
if (i >= ARRAY_SIZE(priv->ext_irqs)) {
pr_warn("More than %d external irq, skip rest\n",
pr_warn("More than %zu external irq, skip rest\n",
ARRAY_SIZE(priv->ext_irqs));
break;
}

View File

@ -20,18 +20,21 @@
#include <linux/spinlock.h>
#include <linux/syscore_ops.h>
#define IRQC_NMI 0
#define IRQC_IRQ_START 1
#define IRQC_IRQ_COUNT 8
#define IRQC_TINT_START (IRQC_IRQ_START + IRQC_IRQ_COUNT)
#define IRQC_TINT_COUNT 32
#define IRQC_NUM_IRQ (IRQC_TINT_START + IRQC_TINT_COUNT)
#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
#define TITSR(n) (0x24 + (n) * 4)
#define TITSR0_MAX_INT 16
#define TITSEL_WIDTH 0x2
#define INTTSEL 0x2c
#define TSSR(n) (0x30 + ((n) * 4))
#define TIEN BIT(7)
#define TSSEL_SHIFT(n) (8 * (n))
@ -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
@ -55,33 +62,62 @@
#define IITSR_IITSEL_EDGE_BOTH 3
#define IITSR_IITSEL_MASK(n) IITSR_IITSEL((n), 3)
#define INTTSEL_TINTSEL(n) BIT(n)
#define INTTSEL_TINTSEL_START 24
#define TINT_EXTRACT_HWIRQ(x) FIELD_GET(GENMASK(15, 0), (x))
#define TINT_EXTRACT_GPIOINT(x) FIELD_GET(GENMASK(31, 16), (x))
/**
* struct rzg2l_irqc_reg_cache - registers cache (necessary for suspend/resume)
* @iitsr: IITSR 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];
};
/**
* struct rzg2l_hw_info - Interrupt Control Unit controller hardware info structure.
* @tssel_lut: TINT lookup table
* @irq_count: Number of IRQC interrupts
* @tint_start: Start of TINT interrupts
* @num_irq: Total Number of interrupts
* @shared_irq_cnt: Number of shared interrupts
*/
struct rzg2l_hw_info {
const u8 *tssel_lut;
unsigned int irq_count;
unsigned int tint_start;
unsigned int num_irq;
unsigned int shared_irq_cnt;
};
/**
* struct rzg2l_irqc_priv - IRQ controller private data structure
* @base: Controller's base address
* @irqchip: Pointer to struct irq_chip
* @irq_chip: Pointer to struct irq_chip for irq
* @tint_chip: Pointer to struct irq_chip for tint
* @fwspec: IRQ firmware specific data
* @lock: Lock to serialize access to hardware registers
* @info: Hardware specific data
* @cache: Registers cache for suspend/resume
* @used_irqs: Bitmap to manage the shared interrupts
*/
static struct rzg2l_irqc_priv {
void __iomem *base;
const struct irq_chip *irqchip;
struct irq_fwspec fwspec[IRQC_NUM_IRQ];
const struct irq_chip *irq_chip;
const struct irq_chip *tint_chip;
struct irq_fwspec *fwspec;
raw_spinlock_t lock;
struct rzg2l_hw_info info;
struct rzg2l_irqc_reg_cache cache;
DECLARE_BITMAP(used_irqs, IRQC_SHARED_IRQ_COUNT);
} *rzg2l_irqc_data;
static struct rzg2l_irqc_priv *irq_data_to_priv(struct irq_data *data)
@ -89,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;
@ -114,7 +172,7 @@ static void rzg2l_clear_irq_int(struct rzg2l_irqc_priv *priv, unsigned int hwirq
static void rzg2l_clear_tint_int(struct rzg2l_irqc_priv *priv, unsigned int hwirq)
{
u32 bit = BIT(hwirq - IRQC_TINT_START);
u32 bit = BIT(hwirq - priv->info.tint_start);
u32 reg;
reg = readl_relaxed(priv->base + TSCR);
@ -128,17 +186,33 @@ static void rzg2l_clear_tint_int(struct rzg2l_irqc_priv *priv, unsigned int hwir
}
}
static void rzg2l_irqc_eoi(struct irq_data *d)
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);
unsigned int hw_irq = irqd_to_hwirq(d);
raw_spin_lock(&priv->lock);
if (hw_irq >= IRQC_IRQ_START && hw_irq <= IRQC_IRQ_COUNT)
scoped_guard(raw_spinlock, &priv->lock)
rzg2l_clear_irq_int(priv, hw_irq);
else if (hw_irq >= IRQC_TINT_START && hw_irq < IRQC_NUM_IRQ)
irq_chip_eoi_parent(d);
}
static void rzg2l_irqc_tint_eoi(struct irq_data *d)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
unsigned int hw_irq = irqd_to_hwirq(d);
scoped_guard(raw_spinlock, &priv->lock)
rzg2l_clear_tint_int(priv, hw_irq);
raw_spin_unlock(&priv->lock);
irq_chip_eoi_parent(d);
}
@ -161,7 +235,7 @@ static void rzfive_irqc_unmask_irq_interrupt(struct rzg2l_irqc_priv *priv,
static void rzfive_irqc_mask_tint_interrupt(struct rzg2l_irqc_priv *priv,
unsigned int hwirq)
{
u32 bit = BIT(hwirq - IRQC_TINT_START);
u32 bit = BIT(hwirq - priv->info.tint_start);
writel_relaxed(readl_relaxed(priv->base + TMSK) | bit, priv->base + TMSK);
}
@ -169,125 +243,170 @@ static void rzfive_irqc_mask_tint_interrupt(struct rzg2l_irqc_priv *priv,
static void rzfive_irqc_unmask_tint_interrupt(struct rzg2l_irqc_priv *priv,
unsigned int hwirq)
{
u32 bit = BIT(hwirq - IRQC_TINT_START);
u32 bit = BIT(hwirq - priv->info.tint_start);
writel_relaxed(readl_relaxed(priv->base + TMSK) & ~bit, priv->base + TMSK);
}
static void rzfive_irqc_mask(struct irq_data *d)
static void rzfive_irqc_irq_mask(struct irq_data *d)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
unsigned int hwirq = irqd_to_hwirq(d);
raw_spin_lock(&priv->lock);
if (hwirq >= IRQC_IRQ_START && hwirq <= IRQC_IRQ_COUNT)
scoped_guard(raw_spinlock, &priv->lock)
rzfive_irqc_mask_irq_interrupt(priv, hwirq);
else if (hwirq >= IRQC_TINT_START && hwirq < IRQC_NUM_IRQ)
rzfive_irqc_mask_tint_interrupt(priv, hwirq);
raw_spin_unlock(&priv->lock);
irq_chip_mask_parent(d);
}
static void rzfive_irqc_unmask(struct irq_data *d)
static void rzfive_irqc_tint_mask(struct irq_data *d)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
unsigned int hwirq = irqd_to_hwirq(d);
raw_spin_lock(&priv->lock);
if (hwirq >= IRQC_IRQ_START && hwirq <= IRQC_IRQ_COUNT)
scoped_guard(raw_spinlock, &priv->lock)
rzfive_irqc_mask_tint_interrupt(priv, hwirq);
irq_chip_mask_parent(d);
}
static void rzfive_irqc_irq_unmask(struct irq_data *d)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
unsigned int hwirq = irqd_to_hwirq(d);
scoped_guard(raw_spinlock, &priv->lock)
rzfive_irqc_unmask_irq_interrupt(priv, hwirq);
else if (hwirq >= IRQC_TINT_START && hwirq < IRQC_NUM_IRQ)
rzfive_irqc_unmask_tint_interrupt(priv, hwirq);
raw_spin_unlock(&priv->lock);
irq_chip_unmask_parent(d);
}
static void rzfive_tint_irq_endisable(struct irq_data *d, bool enable)
static void rzfive_irqc_tint_unmask(struct irq_data *d)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
unsigned int hwirq = irqd_to_hwirq(d);
if (hwirq >= IRQC_TINT_START && hwirq < IRQC_NUM_IRQ) {
u32 offset = hwirq - IRQC_TINT_START;
u32 tssr_offset = TSSR_OFFSET(offset);
u8 tssr_index = TSSR_INDEX(offset);
u32 reg;
scoped_guard(raw_spinlock, &priv->lock)
rzfive_irqc_unmask_tint_interrupt(priv, hwirq);
raw_spin_lock(&priv->lock);
if (enable)
rzfive_irqc_unmask_tint_interrupt(priv, hwirq);
else
rzfive_irqc_mask_tint_interrupt(priv, hwirq);
reg = readl_relaxed(priv->base + TSSR(tssr_index));
if (enable)
reg |= TIEN << TSSEL_SHIFT(tssr_offset);
else
reg &= ~(TIEN << TSSEL_SHIFT(tssr_offset));
writel_relaxed(reg, priv->base + TSSR(tssr_index));
raw_spin_unlock(&priv->lock);
} else {
raw_spin_lock(&priv->lock);
if (enable)
rzfive_irqc_unmask_irq_interrupt(priv, hwirq);
else
rzfive_irqc_mask_irq_interrupt(priv, hwirq);
raw_spin_unlock(&priv->lock);
}
irq_chip_unmask_parent(d);
}
static void rzfive_irq_endisable(struct irq_data *d, bool enable)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
unsigned int hwirq = irqd_to_hwirq(d);
guard(raw_spinlock)(&priv->lock);
if (enable)
rzfive_irqc_unmask_irq_interrupt(priv, hwirq);
else
rzfive_irqc_mask_irq_interrupt(priv, hwirq);
}
static void rzfive_tint_endisable(struct irq_data *d, bool enable)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
unsigned int hwirq = irqd_to_hwirq(d);
unsigned int offset = hwirq - priv->info.tint_start;
unsigned int tssr_offset = TSSR_OFFSET(offset);
unsigned int tssr_index = TSSR_INDEX(offset);
u32 reg;
guard(raw_spinlock)(&priv->lock);
if (enable)
rzfive_irqc_unmask_tint_interrupt(priv, hwirq);
else
rzfive_irqc_mask_tint_interrupt(priv, hwirq);
reg = readl_relaxed(priv->base + TSSR(tssr_index));
if (enable)
reg |= TIEN << TSSEL_SHIFT(tssr_offset);
else
reg &= ~(TIEN << TSSEL_SHIFT(tssr_offset));
writel_relaxed(reg, priv->base + TSSR(tssr_index));
}
static void rzfive_irqc_irq_disable(struct irq_data *d)
{
irq_chip_disable_parent(d);
rzfive_tint_irq_endisable(d, false);
rzfive_irq_endisable(d, false);
}
static void rzfive_irqc_irq_enable(struct irq_data *d)
{
rzfive_tint_irq_endisable(d, true);
rzfive_irq_endisable(d, true);
irq_chip_enable_parent(d);
}
static void rzfive_irqc_tint_disable(struct irq_data *d)
{
irq_chip_disable_parent(d);
rzfive_tint_endisable(d, false);
}
static void rzfive_irqc_tint_enable(struct irq_data *d)
{
rzfive_tint_endisable(d, true);
irq_chip_enable_parent(d);
}
static void rzg2l_tint_irq_endisable(struct irq_data *d, bool enable)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
unsigned int hw_irq = irqd_to_hwirq(d);
unsigned int offset = hw_irq - priv->info.tint_start;
unsigned int tssr_offset = TSSR_OFFSET(offset);
unsigned int tssr_index = TSSR_INDEX(offset);
u32 reg;
if (hw_irq >= IRQC_TINT_START && hw_irq < IRQC_NUM_IRQ) {
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
u32 offset = hw_irq - IRQC_TINT_START;
u32 tssr_offset = TSSR_OFFSET(offset);
u8 tssr_index = TSSR_INDEX(offset);
u32 reg;
raw_spin_lock(&priv->lock);
reg = readl_relaxed(priv->base + TSSR(tssr_index));
if (enable)
reg |= TIEN << TSSEL_SHIFT(tssr_offset);
else
reg &= ~(TIEN << TSSEL_SHIFT(tssr_offset));
writel_relaxed(reg, priv->base + TSSR(tssr_index));
raw_spin_unlock(&priv->lock);
}
guard(raw_spinlock)(&priv->lock);
reg = readl_relaxed(priv->base + TSSR(tssr_index));
if (enable)
reg |= TIEN << TSSEL_SHIFT(tssr_offset);
else
reg &= ~(TIEN << TSSEL_SHIFT(tssr_offset));
writel_relaxed(reg, priv->base + TSSR(tssr_index));
}
static void rzg2l_irqc_irq_disable(struct irq_data *d)
static void rzg2l_irqc_tint_disable(struct irq_data *d)
{
irq_chip_disable_parent(d);
rzg2l_tint_irq_endisable(d, false);
}
static void rzg2l_irqc_irq_enable(struct irq_data *d)
static void rzg2l_irqc_tint_enable(struct irq_data *d)
{
rzg2l_tint_irq_endisable(d, true);
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);
unsigned int hwirq = irqd_to_hwirq(d);
u32 iitseln = hwirq - IRQC_IRQ_START;
unsigned int iitseln = hwirq - IRQC_IRQ_START;
bool clear_irq_int = false;
u16 sense, tmp;
unsigned int sense, tmp;
switch (type & IRQ_TYPE_SENSE_MASK) {
case IRQ_TYPE_LEVEL_LOW:
@ -313,14 +432,13 @@ static int rzg2l_irq_set_type(struct irq_data *d, unsigned int type)
return -EINVAL;
}
raw_spin_lock(&priv->lock);
guard(raw_spinlock)(&priv->lock);
tmp = readl_relaxed(priv->base + IITSR);
tmp &= ~IITSR_IITSEL_MASK(iitseln);
tmp |= IITSR_IITSEL(iitseln, sense);
if (clear_irq_int)
rzg2l_clear_irq_int(priv, hwirq);
writel_relaxed(tmp, priv->base + IITSR);
raw_spin_unlock(&priv->lock);
return 0;
}
@ -331,6 +449,11 @@ static u32 rzg2l_disable_tint_and_set_tint_source(struct irq_data *d, struct rzg
u32 tint = (u32)(uintptr_t)irq_data_get_irq_chip_data(d);
u32 tien = reg & (TIEN << TSSEL_SHIFT(tssr_offset));
if (priv->info.tssel_lut)
tint = priv->info.tssel_lut[tint];
else
tint = (u32)(uintptr_t)irq_data_get_irq_chip_data(d);
/* Clear the relevant byte in reg */
reg &= ~(TSSEL_MASK << TSSEL_SHIFT(tssr_offset));
/* Set TINT and leave TIEN clear */
@ -344,10 +467,10 @@ static int rzg2l_tint_set_edge(struct irq_data *d, unsigned int type)
{
struct rzg2l_irqc_priv *priv = irq_data_to_priv(d);
unsigned int hwirq = irqd_to_hwirq(d);
u32 titseln = hwirq - IRQC_TINT_START;
u32 tssr_offset = TSSR_OFFSET(titseln);
u8 tssr_index = TSSR_INDEX(titseln);
u8 index, sense;
unsigned int titseln = hwirq - priv->info.tint_start;
unsigned int tssr_offset = TSSR_OFFSET(titseln);
unsigned int tssr_index = TSSR_INDEX(titseln);
unsigned int index, sense;
u32 reg, tssr;
switch (type & IRQ_TYPE_SENSE_MASK) {
@ -383,15 +506,31 @@ static int rzg2l_tint_set_edge(struct irq_data *d, unsigned int type)
return 0;
}
static int rzg2l_irqc_set_type(struct irq_data *d, unsigned int type)
static int rzg2l_irqc_irq_set_type(struct irq_data *d, unsigned int type)
{
unsigned int hw_irq = irqd_to_hwirq(d);
int ret = -EINVAL;
int ret = rzg2l_irq_set_type(d, type);
if (hw_irq >= IRQC_IRQ_START && hw_irq <= IRQC_IRQ_COUNT)
ret = rzg2l_irq_set_type(d, type);
else if (hw_irq >= IRQC_TINT_START && hw_irq < IRQC_NUM_IRQ)
ret = rzg2l_tint_set_edge(d, type);
if (ret)
return ret;
return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
}
static int rzg2l_irqc_tint_set_type(struct irq_data *d, unsigned int type)
{
int ret = rzg2l_tint_set_edge(d, type);
if (ret)
return ret;
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;
@ -403,7 +542,10 @@ 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);
for (u8 i = 0; i < 2; i++)
cache->titsr[i] = readl_relaxed(base + TITSR(i));
@ -422,7 +564,10 @@ static void rzg2l_irqc_irq_resume(void *data)
*/
for (u8 i = 0; i < 2; i++)
writel_relaxed(cache->titsr[i], base + TITSR(i));
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 = {
@ -434,44 +579,162 @@ static struct syscore rzg2l_irqc_syscore = {
.ops = &rzg2l_irqc_syscore_ops,
};
static const struct irq_chip rzg2l_irqc_chip = {
static const struct irq_chip rzg2l_irqc_nmi_chip = {
.name = "rzg2l-irqc",
.irq_eoi = rzg2l_irqc_eoi,
.irq_eoi = rzg2l_irqc_nmi_eoi,
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_disable = rzg2l_irqc_irq_disable,
.irq_enable = rzg2l_irqc_irq_enable,
.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_set_type,
.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 rzfive_irqc_chip = {
static const struct irq_chip rzg2l_irqc_irq_chip = {
.name = "rzg2l-irqc",
.irq_eoi = rzg2l_irqc_irq_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_irq_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_tint_chip = {
.name = "rzg2l-irqc",
.irq_eoi = rzg2l_irqc_tint_eoi,
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_disable = rzg2l_irqc_tint_disable,
.irq_enable = rzg2l_irqc_tint_enable,
.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_tint_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 rzfive_irqc_irq_chip = {
.name = "rzfive-irqc",
.irq_eoi = rzg2l_irqc_eoi,
.irq_mask = rzfive_irqc_mask,
.irq_unmask = rzfive_irqc_unmask,
.irq_eoi = rzg2l_irqc_irq_eoi,
.irq_mask = rzfive_irqc_irq_mask,
.irq_unmask = rzfive_irqc_irq_unmask,
.irq_disable = rzfive_irqc_irq_disable,
.irq_enable = rzfive_irqc_irq_enable,
.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_set_type,
.irq_set_type = rzg2l_irqc_irq_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 rzfive_irqc_tint_chip = {
.name = "rzfive-irqc",
.irq_eoi = rzg2l_irqc_tint_eoi,
.irq_mask = rzfive_irqc_tint_mask,
.irq_unmask = rzfive_irqc_tint_unmask,
.irq_disable = rzfive_irqc_tint_disable,
.irq_enable = rzfive_irqc_tint_enable,
.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_tint_set_type,
.irq_set_affinity = irq_chip_set_affinity_parent,
.flags = IRQCHIP_MASK_ON_SUSPEND |
IRQCHIP_SET_TYPE_MASKED |
IRQCHIP_SKIP_SET_WAKE,
};
static bool rzg2l_irqc_is_shared_irqc(const struct rzg2l_hw_info info, unsigned int hw_irq)
{
return ((hw_irq >= (info.tint_start - info.shared_irq_cnt)) && hw_irq < info.tint_start);
}
static bool rzg2l_irqc_is_shared_tint(const struct rzg2l_hw_info info, unsigned int hw_irq)
{
return ((hw_irq >= (info.num_irq - info.shared_irq_cnt)) && hw_irq < info.num_irq);
}
static bool rzg2l_irqc_is_shared_and_get_irq_num(struct rzg2l_irqc_priv *priv,
irq_hw_number_t hwirq, unsigned int *irq_num)
{
bool is_shared = false;
if (rzg2l_irqc_is_shared_irqc(priv->info, hwirq)) {
*irq_num = hwirq - IRQC_IRQ_SHARED_START;
is_shared = true;
} else if (rzg2l_irqc_is_shared_tint(priv->info, hwirq)) {
*irq_num = hwirq - IRQC_TINT_COUNT - IRQC_IRQ_SHARED_START;
is_shared = true;
}
return is_shared;
}
static void rzg2l_irqc_set_inttsel(struct rzg2l_irqc_priv *priv, unsigned int offset,
unsigned int select_irq)
{
u32 reg;
guard(raw_spinlock_irqsave)(&priv->lock);
reg = readl_relaxed(priv->base + INTTSEL);
if (select_irq)
reg |= INTTSEL_TINTSEL(offset);
else
reg &= ~INTTSEL_TINTSEL(offset);
writel_relaxed(reg, priv->base + INTTSEL);
}
static int rzg2l_irqc_shared_irq_alloc(struct rzg2l_irqc_priv *priv, irq_hw_number_t hwirq)
{
unsigned int irq_num;
if (rzg2l_irqc_is_shared_and_get_irq_num(priv, hwirq, &irq_num)) {
if (test_and_set_bit(irq_num, priv->used_irqs))
return -EBUSY;
if (hwirq < priv->info.tint_start)
rzg2l_irqc_set_inttsel(priv, INTTSEL_TINTSEL_START + irq_num, 1);
else
rzg2l_irqc_set_inttsel(priv, INTTSEL_TINTSEL_START + irq_num, 0);
}
return 0;
}
static void rzg2l_irqc_shared_irq_free(struct rzg2l_irqc_priv *priv, irq_hw_number_t hwirq)
{
unsigned int irq_num;
if (rzg2l_irqc_is_shared_and_get_irq_num(priv, hwirq, &irq_num) &&
test_and_clear_bit(irq_num, priv->used_irqs))
rzg2l_irqc_set_inttsel(priv, INTTSEL_TINTSEL_START + irq_num, 0);
}
static int rzg2l_irqc_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *arg)
{
struct rzg2l_irqc_priv *priv = domain->host_data;
const struct irq_chip *chip;
unsigned long tint = 0;
irq_hw_number_t hwirq;
unsigned int type;
@ -488,28 +751,57 @@ 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 > IRQC_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);
if (hwirq < IRQC_TINT_START)
return -EINVAL;
chip = priv->tint_chip;
} else {
chip = priv->irq_chip;
}
if (hwirq > (IRQC_NUM_IRQ - 1))
if (hwirq >= priv->info.num_irq)
return -EINVAL;
ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, priv->irqchip,
(void *)(uintptr_t)tint);
if (ret)
return ret;
if (priv->info.shared_irq_cnt) {
ret = rzg2l_irqc_shared_irq_alloc(priv, hwirq);
if (ret)
return ret;
}
return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &priv->fwspec[hwirq]);
ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, (void *)(uintptr_t)tint);
if (ret)
goto shared_irq_free;
ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &priv->fwspec[hwirq]);
if (ret)
goto shared_irq_free;
return 0;
shared_irq_free:
if (priv->info.shared_irq_cnt)
rzg2l_irqc_shared_irq_free(priv, hwirq);
return ret;
}
static void rzg2l_irqc_free(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs)
{
struct irq_data *d = irq_domain_get_irq_data(domain, virq);
struct rzg2l_irqc_priv *priv = domain->host_data;
irq_hw_number_t hwirq = irqd_to_hwirq(d);
irq_domain_free_irqs_common(domain, virq, nr_irqs);
if (priv->info.shared_irq_cnt)
rzg2l_irqc_shared_irq_free(priv, hwirq);
}
static const struct irq_domain_ops rzg2l_irqc_domain_ops = {
.alloc = rzg2l_irqc_alloc,
.free = irq_domain_free_irqs_common,
.free = rzg2l_irqc_free,
.translate = irq_domain_translate_twocell,
};
@ -520,7 +812,7 @@ static int rzg2l_irqc_parse_interrupts(struct rzg2l_irqc_priv *priv,
unsigned int i;
int ret;
for (i = 0; i < IRQC_NUM_IRQ; i++) {
for (i = 0; i < priv->info.num_irq; i++) {
ret = of_irq_parse_one(np, i, &map);
if (ret)
return ret;
@ -532,7 +824,9 @@ static int rzg2l_irqc_parse_interrupts(struct rzg2l_irqc_priv *priv,
}
static int rzg2l_irqc_common_probe(struct platform_device *pdev, struct device_node *parent,
const struct irq_chip *irq_chip)
const struct irq_chip *irq_chip,
const struct irq_chip *tint_chip,
const struct rzg2l_hw_info info)
{
struct irq_domain *irq_domain, *parent_domain;
struct device_node *node = pdev->dev.of_node;
@ -548,12 +842,20 @@ static int rzg2l_irqc_common_probe(struct platform_device *pdev, struct device_n
if (!rzg2l_irqc_data)
return -ENOMEM;
rzg2l_irqc_data->irqchip = irq_chip;
rzg2l_irqc_data->irq_chip = irq_chip;
rzg2l_irqc_data->tint_chip = tint_chip;
rzg2l_irqc_data->base = devm_of_iomap(dev, dev->of_node, 0, NULL);
if (IS_ERR(rzg2l_irqc_data->base))
return PTR_ERR(rzg2l_irqc_data->base);
rzg2l_irqc_data->info = info;
rzg2l_irqc_data->fwspec = devm_kcalloc(&pdev->dev, info.num_irq,
sizeof(*rzg2l_irqc_data->fwspec), GFP_KERNEL);
if (!rzg2l_irqc_data->fwspec)
return -ENOMEM;
ret = rzg2l_irqc_parse_interrupts(rzg2l_irqc_data, node);
if (ret)
return dev_err_probe(dev, ret, "cannot parse interrupts: %d\n", ret);
@ -574,10 +876,10 @@ static int rzg2l_irqc_common_probe(struct platform_device *pdev, struct device_n
raw_spin_lock_init(&rzg2l_irqc_data->lock);
irq_domain = irq_domain_create_hierarchy(parent_domain, 0, IRQC_NUM_IRQ, dev_fwnode(dev),
irq_domain = irq_domain_create_hierarchy(parent_domain, 0, info.num_irq, dev_fwnode(dev),
&rzg2l_irqc_domain_ops, rzg2l_irqc_data);
if (!irq_domain) {
pm_runtime_put(dev);
pm_runtime_put_sync(dev);
return -ENOMEM;
}
@ -586,18 +888,64 @@ static int rzg2l_irqc_common_probe(struct platform_device *pdev, struct device_n
return 0;
}
/* Mapping based on port index on Table 4.2-1 and GPIOINT on Table 4.6-7 */
static const u8 rzg3l_tssel_lut[] = {
83, 84, /* P20-P21 */
7, 8, 9, 10, 11, 12, 13, /* P30-P36 */
85, 86, 87, 88, 89, 90, 91, /* P50-P56 */
92, 93, 94, 95, 96, 97, 98, /* P60-P66 */
99, 100, 101, 102, 103, 104, 105, 106, /* P70-P77 */
107, 108, 109, 110, 111, 112, /* P80-P85 */
45, 46, 47, 48, 49, 50, 51, 52, /* PA0-PA7 */
53, 54, 55, 56, 57, 58, 59, 60, /* PB0-PB7 */
61, 62, 63, /* PC0-PC2 */
64, 65, 66, 67, 68, 69, 70, 71, /* PD0-PD7 */
72, 73, 74, 75, 76, 77, 78, 79, /* PE0-PE7 */
80, 81, 82, /* PF0-PF2 */
27, 28, 29, 30, 31, 32, 33, 34, /* PG0-PG7 */
35, 36, 37, 38, 39, 40, /* PH0-PH5 */
2, 3, 4, 5, 6, /* PJ0-PJ4 */
41, 42, 43, 44, /* PK0-PK3 */
14, 15, 16, 17, 26, /* PL0-PL4 */
18, 19, 20, 21, 22, 23, 24, 25, /* PM0-PM7 */
0, 1 /* PS0-PS1 */
};
static const struct rzg2l_hw_info rzg3l_hw_params = {
.tssel_lut = rzg3l_tssel_lut,
.irq_count = 16,
.tint_start = IRQC_IRQ_START + 16,
.num_irq = IRQC_IRQ_START + 16 + IRQC_TINT_COUNT,
.shared_irq_cnt = IRQC_SHARED_IRQ_COUNT,
};
static const struct rzg2l_hw_info rzg2l_hw_params = {
.irq_count = 8,
.tint_start = IRQC_IRQ_START + 8,
.num_irq = IRQC_IRQ_START + 8 + IRQC_TINT_COUNT,
};
static int rzg2l_irqc_probe(struct platform_device *pdev, struct device_node *parent)
{
return rzg2l_irqc_common_probe(pdev, parent, &rzg2l_irqc_chip);
return rzg2l_irqc_common_probe(pdev, parent, &rzg2l_irqc_irq_chip, &rzg2l_irqc_tint_chip,
rzg2l_hw_params);
}
static int rzg3l_irqc_probe(struct platform_device *pdev, struct device_node *parent)
{
return rzg2l_irqc_common_probe(pdev, parent, &rzg2l_irqc_irq_chip, &rzg2l_irqc_tint_chip,
rzg3l_hw_params);
}
static int rzfive_irqc_probe(struct platform_device *pdev, struct device_node *parent)
{
return rzg2l_irqc_common_probe(pdev, parent, &rzfive_irqc_chip);
return rzg2l_irqc_common_probe(pdev, parent, &rzfive_irqc_irq_chip, &rzfive_irqc_tint_chip,
rzg2l_hw_params);
}
IRQCHIP_PLATFORM_DRIVER_BEGIN(rzg2l_irqc)
IRQCHIP_MATCH("renesas,rzg2l-irqc", rzg2l_irqc_probe)
IRQCHIP_MATCH("renesas,r9a08g046-irqc", rzg3l_irqc_probe)
IRQCHIP_MATCH("renesas,r9a07g043f-irqc", rzfive_irqc_probe)
IRQCHIP_PLATFORM_DRIVER_END(rzg2l_irqc)
MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>");

View File

@ -12,6 +12,7 @@
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irqchip.h>
#include <linux/irqchip/irq-renesas-rzv2h.h>
@ -25,9 +26,17 @@
/* DT "interrupts" indexes */
#define ICU_IRQ_START 1
#define ICU_IRQ_COUNT 16
#define ICU_TINT_START (ICU_IRQ_START + ICU_IRQ_COUNT)
#define ICU_IRQ_LAST (ICU_IRQ_START + ICU_IRQ_COUNT - 1)
#define ICU_TINT_START (ICU_IRQ_LAST + 1)
#define ICU_TINT_COUNT 32
#define ICU_NUM_IRQ (ICU_TINT_START + ICU_TINT_COUNT)
#define ICU_TINT_LAST (ICU_TINT_START + ICU_TINT_COUNT - 1)
#define ICU_CA55_INT_START (ICU_TINT_LAST + 1)
#define ICU_CA55_INT_COUNT 4
#define ICU_CA55_INT_LAST (ICU_CA55_INT_START + ICU_CA55_INT_COUNT - 1)
#define ICU_ERR_INT_START (ICU_CA55_INT_LAST + 1)
#define ICU_ERR_INT_COUNT 1
#define ICU_ERR_INT_LAST (ICU_ERR_INT_START + ICU_ERR_INT_COUNT - 1)
#define ICU_NUM_IRQ (ICU_ERR_INT_LAST + 1)
/* Registers */
#define ICU_NSCNT 0x00
@ -40,6 +49,15 @@
#define ICU_TSCLR 0x24
#define ICU_TITSR(k) (0x28 + (k) * 4)
#define ICU_TSSR(k) (0x30 + (k) * 4)
#define ICU_BEISR(k) (0x70 + (k) * 4)
#define ICU_BECLR(k) (0x80 + (k) * 4)
#define ICU_EREISR(k) (0x90 + (k) * 4)
#define ICU_ERCLR(k) (0xE0 + (k) * 4)
#define ICU_SWINT 0x130
#define ICU_ERINTA55CTL(k) (0x338 + (k) * 4)
#define ICU_ERINTA55CRL(k) (0x348 + (k) * 4)
#define ICU_ERINTA55MSK(k) (0x358 + (k) * 4)
#define ICU_SWPE 0x370
#define ICU_DMkSELy(k, y) (0x420 + (k) * 0x20 + (y) * 4)
#define ICU_DMACKSELk(k) (0x500 + (k) * 4)
@ -90,6 +108,10 @@
#define ICU_RZG3E_TSSEL_MAX_VAL 0x8c
#define ICU_RZV2H_TSSEL_MAX_VAL 0x55
#define ICU_SWPE_NUM 16
#define ICU_NUM_BE 4
#define ICU_NUM_A55ERR 4
/**
* struct rzv2h_irqc_reg_cache - registers cache (necessary for suspend/resume)
* @nitsr: ICU_NITSR register
@ -108,12 +130,16 @@ struct rzv2h_irqc_reg_cache {
* @t_offs: TINT offset
* @max_tssel: TSSEL max value
* @field_width: TSSR field width
* @ecc_start: Start index of ECC RAM interrupts
* @ecc_end: End index of ECC RAM interrupts
*/
struct rzv2h_hw_info {
const u8 *tssel_lut;
u16 t_offs;
u8 max_tssel;
u8 field_width;
u8 ecc_start;
u8 ecc_end;
};
/* DMAC */
@ -167,32 +193,47 @@ static inline struct rzv2h_icu_priv *irq_data_to_priv(struct irq_data *data)
return data->domain->host_data;
}
static void rzv2h_icu_eoi(struct irq_data *d)
static void rzv2h_icu_tint_eoi(struct irq_data *d)
{
struct rzv2h_icu_priv *priv = irq_data_to_priv(d);
unsigned int hw_irq = irqd_to_hwirq(d);
unsigned int tintirq_nr;
u32 bit;
scoped_guard(raw_spinlock, &priv->lock) {
if (hw_irq >= ICU_TINT_START) {
tintirq_nr = hw_irq - ICU_TINT_START;
bit = BIT(tintirq_nr);
if (!irqd_is_level_type(d))
writel_relaxed(bit, priv->base + priv->info->t_offs + ICU_TSCLR);
} else if (hw_irq >= ICU_IRQ_START) {
tintirq_nr = hw_irq - ICU_IRQ_START;
bit = BIT(tintirq_nr);
if (!irqd_is_level_type(d))
writel_relaxed(bit, priv->base + ICU_ISCLR);
} else {
writel_relaxed(ICU_NSCLR_NCLR, priv->base + ICU_NSCLR);
}
if (!irqd_is_level_type(d)) {
tintirq_nr = hw_irq - ICU_TINT_START;
bit = BIT(tintirq_nr);
writel_relaxed(bit, priv->base + priv->info->t_offs + ICU_TSCLR);
}
irq_chip_eoi_parent(d);
}
static void rzv2h_icu_irq_eoi(struct irq_data *d)
{
struct rzv2h_icu_priv *priv = irq_data_to_priv(d);
unsigned int hw_irq = irqd_to_hwirq(d);
unsigned int tintirq_nr;
u32 bit;
if (!irqd_is_level_type(d)) {
tintirq_nr = hw_irq - ICU_IRQ_START;
bit = BIT(tintirq_nr);
writel_relaxed(bit, priv->base + ICU_ISCLR);
}
irq_chip_eoi_parent(d);
}
static void rzv2h_icu_nmi_eoi(struct irq_data *d)
{
struct rzv2h_icu_priv *priv = irq_data_to_priv(d);
writel_relaxed(ICU_NSCLR_NCLR, priv->base + ICU_NSCLR);
irq_chip_eoi_parent(d);
}
static void rzv2h_tint_irq_endisable(struct irq_data *d, bool enable)
{
struct rzv2h_icu_priv *priv = irq_data_to_priv(d);
@ -200,9 +241,6 @@ static void rzv2h_tint_irq_endisable(struct irq_data *d, bool enable)
u32 tint_nr, tssel_n, k, tssr;
u8 nr_tint;
if (hw_irq < ICU_TINT_START)
return;
tint_nr = hw_irq - ICU_TINT_START;
nr_tint = 32 / priv->info->field_width;
k = tint_nr / nr_tint;
@ -225,13 +263,13 @@ static void rzv2h_tint_irq_endisable(struct irq_data *d, bool enable)
writel_relaxed(BIT(tint_nr), priv->base + priv->info->t_offs + ICU_TSCLR);
}
static void rzv2h_icu_irq_disable(struct irq_data *d)
static void rzv2h_icu_tint_disable(struct irq_data *d)
{
irq_chip_disable_parent(d);
rzv2h_tint_irq_endisable(d, false);
}
static void rzv2h_icu_irq_enable(struct irq_data *d)
static void rzv2h_icu_tint_enable(struct irq_data *d)
{
rzv2h_tint_irq_endisable(d, true);
irq_chip_enable_parent(d);
@ -257,7 +295,7 @@ static int rzv2h_nmi_set_type(struct irq_data *d, unsigned int type)
writel_relaxed(sense, priv->base + ICU_NITSR);
return 0;
return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
}
static void rzv2h_clear_irq_int(struct rzv2h_icu_priv *priv, unsigned int hwirq)
@ -307,14 +345,15 @@ static int rzv2h_irq_set_type(struct irq_data *d, unsigned int type)
return -EINVAL;
}
guard(raw_spinlock)(&priv->lock);
iitsr = readl_relaxed(priv->base + ICU_IITSR);
iitsr &= ~ICU_IITSR_IITSEL_MASK(irq_nr);
iitsr |= ICU_IITSR_IITSEL_PREP(sense, irq_nr);
rzv2h_clear_irq_int(priv, hwirq);
writel_relaxed(iitsr, priv->base + ICU_IITSR);
scoped_guard(raw_spinlock, &priv->lock) {
iitsr = readl_relaxed(priv->base + ICU_IITSR);
iitsr &= ~ICU_IITSR_IITSEL_MASK(irq_nr);
iitsr |= ICU_IITSR_IITSEL_PREP(sense, irq_nr);
rzv2h_clear_irq_int(priv, hwirq);
writel_relaxed(iitsr, priv->base + ICU_IITSR);
}
return 0;
return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
}
static void rzv2h_clear_tint_int(struct rzv2h_icu_priv *priv, unsigned int hwirq)
@ -389,49 +428,82 @@ static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type)
titsr_k = ICU_TITSR_K(tint_nr);
titsel_n = ICU_TITSR_TITSEL_N(tint_nr);
guard(raw_spinlock)(&priv->lock);
scoped_guard(raw_spinlock, &priv->lock) {
tssr = readl_relaxed(priv->base + priv->info->t_offs + ICU_TSSR(tssr_k));
titsr = readl_relaxed(priv->base + priv->info->t_offs + ICU_TITSR(titsr_k));
tssr = readl_relaxed(priv->base + priv->info->t_offs + ICU_TSSR(tssr_k));
titsr = readl_relaxed(priv->base + priv->info->t_offs + ICU_TITSR(titsr_k));
tssr_cur = field_get(ICU_TSSR_TSSEL_MASK(tssel_n, priv->info->field_width), tssr);
titsr_cur = field_get(ICU_TITSR_TITSEL_MASK(titsel_n), titsr);
if (tssr_cur == tint && titsr_cur == sense)
goto set_parent_type;
tssr_cur = field_get(ICU_TSSR_TSSEL_MASK(tssel_n, priv->info->field_width), tssr);
titsr_cur = field_get(ICU_TITSR_TITSEL_MASK(titsel_n), titsr);
if (tssr_cur == tint && titsr_cur == sense)
tssr &= ~(ICU_TSSR_TSSEL_MASK(tssel_n, priv->info->field_width) | tien);
tssr |= ICU_TSSR_TSSEL_PREP(tint, tssel_n, priv->info->field_width);
writel_relaxed(tssr, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k));
titsr &= ~ICU_TITSR_TITSEL_MASK(titsel_n);
titsr |= ICU_TITSR_TITSEL_PREP(sense, titsel_n);
writel_relaxed(titsr, priv->base + priv->info->t_offs + ICU_TITSR(titsr_k));
rzv2h_clear_tint_int(priv, hwirq);
writel_relaxed(tssr | tien, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k));
}
set_parent_type:
return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
}
static int rzv2h_icu_swint_set_irqchip_state(struct irq_data *d, enum irqchip_irq_state which,
bool state)
{
unsigned int hwirq = irqd_to_hwirq(d);
struct rzv2h_icu_priv *priv;
unsigned int bit;
if (which != IRQCHIP_STATE_PENDING)
return irq_chip_set_parent_state(d, which, state);
if (!state)
return 0;
tssr &= ~(ICU_TSSR_TSSEL_MASK(tssel_n, priv->info->field_width) | tien);
tssr |= ICU_TSSR_TSSEL_PREP(tint, tssel_n, priv->info->field_width);
priv = irq_data_to_priv(d);
bit = BIT(hwirq - ICU_CA55_INT_START);
writel_relaxed(tssr, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k));
titsr &= ~ICU_TITSR_TITSEL_MASK(titsel_n);
titsr |= ICU_TITSR_TITSEL_PREP(sense, titsel_n);
writel_relaxed(titsr, priv->base + priv->info->t_offs + ICU_TITSR(titsr_k));
rzv2h_clear_tint_int(priv, hwirq);
writel_relaxed(tssr | tien, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k));
/* Trigger the software interrupt */
writel_relaxed(bit, priv->base + ICU_SWINT);
return 0;
}
static int rzv2h_icu_set_type(struct irq_data *d, unsigned int type)
static int rzv2h_icu_swpe_set_irqchip_state(struct irq_data *d, enum irqchip_irq_state which,
bool state)
{
unsigned int hw_irq = irqd_to_hwirq(d);
int ret;
struct rzv2h_icu_priv *priv;
unsigned int bit;
static u8 swpe;
if (hw_irq >= ICU_TINT_START)
ret = rzv2h_tint_set_type(d, type);
else if (hw_irq >= ICU_IRQ_START)
ret = rzv2h_irq_set_type(d, type);
else
ret = rzv2h_nmi_set_type(d, type);
if (which != IRQCHIP_STATE_PENDING)
return irq_chip_set_parent_state(d, which, state);
if (ret)
return ret;
if (!state)
return 0;
return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
priv = irq_data_to_priv(d);
bit = BIT(swpe);
/*
* SWPE has 16 bits; the bit position is rotated on each trigger
* and wraps around once all bits have been used.
*/
if (++swpe >= ICU_SWPE_NUM)
swpe = 0;
/* Trigger the pseudo error interrupt */
writel_relaxed(bit, priv->base + ICU_SWPE);
return 0;
}
static int rzv2h_irqc_irq_suspend(void *data)
@ -472,27 +544,98 @@ static struct syscore rzv2h_irqc_syscore = {
.ops = &rzv2h_irqc_syscore_ops,
};
static const struct irq_chip rzv2h_icu_chip = {
static const struct irq_chip rzv2h_icu_tint_chip = {
.name = "rzv2h-icu",
.irq_eoi = rzv2h_icu_eoi,
.irq_eoi = rzv2h_icu_tint_eoi,
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_disable = rzv2h_icu_irq_disable,
.irq_enable = rzv2h_icu_irq_enable,
.irq_disable = rzv2h_icu_tint_disable,
.irq_enable = rzv2h_icu_tint_enable,
.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 = rzv2h_icu_set_type,
.irq_set_type = rzv2h_tint_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 rzv2h_icu_irq_chip = {
.name = "rzv2h-icu",
.irq_eoi = rzv2h_icu_irq_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 = rzv2h_irq_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 rzv2h_icu_nmi_chip = {
.name = "rzv2h-icu",
.irq_eoi = rzv2h_icu_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 = rzv2h_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 rzv2h_icu_swint_chip = {
.name = "rzv2h-icu",
.irq_eoi = irq_chip_eoi_parent,
.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 = rzv2h_icu_swint_set_irqchip_state,
.irq_retrigger = irq_chip_retrigger_hierarchy,
.irq_set_type = irq_chip_set_type_parent,
.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 rzv2h_icu_swpe_err_chip = {
.name = "rzv2h-icu",
.irq_eoi = irq_chip_eoi_parent,
.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 = rzv2h_icu_swpe_set_irqchip_state,
.irq_retrigger = irq_chip_retrigger_hierarchy,
.irq_set_type = irq_chip_set_type_parent,
.irq_set_affinity = irq_chip_set_affinity_parent,
.flags = IRQCHIP_MASK_ON_SUSPEND |
IRQCHIP_SET_TYPE_MASKED |
IRQCHIP_SKIP_SET_WAKE,
};
#define hwirq_within(hwirq, which) ((hwirq) >= which##_START && (hwirq) <= which##_LAST)
static int rzv2h_icu_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs,
void *arg)
{
struct rzv2h_icu_priv *priv = domain->host_data;
const struct irq_chip *chip;
unsigned long tint = 0;
irq_hw_number_t hwirq;
unsigned int type;
@ -508,19 +651,27 @@ static int rzv2h_icu_alloc(struct irq_domain *domain, unsigned int virq, unsigne
* hwirq is embedded in bits 0-15.
* TINT is embedded in bits 16-31.
*/
if (hwirq >= ICU_TINT_START) {
tint = ICU_TINT_EXTRACT_GPIOINT(hwirq);
tint = ICU_TINT_EXTRACT_GPIOINT(hwirq);
if (tint || hwirq_within(hwirq, ICU_TINT)) {
hwirq = ICU_TINT_EXTRACT_HWIRQ(hwirq);
if (hwirq < ICU_TINT_START)
if (!hwirq_within(hwirq, ICU_TINT))
return -EINVAL;
chip = &rzv2h_icu_tint_chip;
} else if (hwirq_within(hwirq, ICU_IRQ)) {
chip = &rzv2h_icu_irq_chip;
} else if (hwirq_within(hwirq, ICU_CA55_INT)) {
chip = &rzv2h_icu_swint_chip;
} else if (hwirq_within(hwirq, ICU_ERR_INT)) {
chip = &rzv2h_icu_swpe_err_chip;
} else {
chip = &rzv2h_icu_nmi_chip;
}
if (hwirq > (ICU_NUM_IRQ - 1))
return -EINVAL;
ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &rzv2h_icu_chip,
(void *)(uintptr_t)tint);
ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, (void *)(uintptr_t)tint);
if (ret)
return ret;
@ -550,62 +701,160 @@ static int rzv2h_icu_parse_interrupts(struct rzv2h_icu_priv *priv, struct device
return 0;
}
static irqreturn_t rzv2h_icu_error_irq(int irq, void *data)
{
struct rzv2h_icu_priv *priv = data;
const struct rzv2h_hw_info *hw_info = priv->info;
void __iomem *base = priv->base;
unsigned int k;
u32 st;
/* 1) Bus errors (BEISR0..3) */
for (k = 0; k < ICU_NUM_BE; k++) {
st = readl(base + ICU_BEISR(k));
if (!st)
continue;
writel_relaxed(st, base + ICU_BECLR(k));
pr_warn("rzv2h-icu: BUS error k=%u status=0x%08x\n", k, st);
}
/* 2) ECC RAM errors (EREISR0..X) */
for (k = hw_info->ecc_start; k <= hw_info->ecc_end; k++) {
st = readl(base + ICU_EREISR(k));
if (!st)
continue;
writel_relaxed(st, base + ICU_ERCLR(k));
pr_warn("rzv2h-icu: ECC error k=%u status=0x%08x\n", k, st);
}
/* 3) IP/CA55 error interrupt status (ERINTA55CTL0..3) */
for (k = 0; k < ICU_NUM_A55ERR; k++) {
st = readl(base + ICU_ERINTA55CTL(k));
if (!st)
continue;
/* there is no relation with status bits so clear all the interrupts */
writel_relaxed(0xffffffff, base + ICU_ERINTA55CRL(k));
pr_warn("rzv2h-icu: IP/CA55 error k=%u status=0x%08x\n", k, st);
}
return IRQ_HANDLED;
}
static irqreturn_t rzv2h_icu_swint_irq(int irq, void *data)
{
unsigned int cpu = (uintptr_t)data;
pr_info("SWINT interrupt for CA55 core %u\n", cpu);
return IRQ_HANDLED;
}
static int rzv2h_icu_setup_irqs(struct platform_device *pdev, struct irq_domain *irq_domain)
{
const struct rzv2h_hw_info *hw_info = rzv2h_icu_data->info;
bool irq_inject = IS_ENABLED(CONFIG_GENERIC_IRQ_INJECTION);
void __iomem *base = rzv2h_icu_data->base;
struct device *dev = &pdev->dev;
struct irq_fwspec fwspec;
unsigned int i, virq;
int ret;
for (i = 0; i < ICU_CA55_INT_COUNT && irq_inject; i++) {
fwspec.fwnode = irq_domain->fwnode;
fwspec.param_count = 2;
fwspec.param[0] = ICU_CA55_INT_START + i;
fwspec.param[1] = IRQ_TYPE_EDGE_RISING;
virq = irq_create_fwspec_mapping(&fwspec);
if (!virq) {
return dev_err_probe(dev, -EINVAL,
"failed to create int-ca55-%u IRQ mapping\n", i);
}
ret = devm_request_irq(dev, virq, rzv2h_icu_swint_irq, 0, dev_name(dev),
(void *)(uintptr_t)i);
if (ret)
return dev_err_probe(dev, ret, "Failed to request int-ca55-%u IRQ\n", i);
}
/* Unmask and clear all IP/CA55 error interrupts */
for (i = 0; i < ICU_NUM_A55ERR; i++) {
writel_relaxed(0xffffff, base + ICU_ERINTA55CRL(i));
writel_relaxed(0x0, base + ICU_ERINTA55MSK(i));
}
/* Clear all Bus errors */
for (i = 0; i < ICU_NUM_BE; i++)
writel_relaxed(0xffffffff, base + ICU_BECLR(i));
/* Clear all ECCRAM errors */
for (i = hw_info->ecc_start; i <= hw_info->ecc_end; i++)
writel_relaxed(0xffffffff, base + ICU_ERCLR(i));
fwspec.fwnode = irq_domain->fwnode;
fwspec.param_count = 2;
fwspec.param[0] = ICU_ERR_INT_START;
fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH;
virq = irq_create_fwspec_mapping(&fwspec);
if (!virq)
return dev_err_probe(dev, -EINVAL, "failed to create icu-error-ca55 IRQ mapping\n");
ret = devm_request_irq(dev, virq, rzv2h_icu_error_irq, 0, dev_name(dev), rzv2h_icu_data);
if (ret)
return dev_err_probe(dev, ret, "Failed to request icu-error-ca55 IRQ\n");
return 0;
}
static int rzv2h_icu_probe_common(struct platform_device *pdev, struct device_node *parent,
const struct rzv2h_hw_info *hw_info)
{
struct irq_domain *irq_domain, *parent_domain;
struct device_node *node = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct reset_control *resetn;
int ret;
parent_domain = irq_find_host(parent);
if (!parent_domain) {
dev_err(&pdev->dev, "cannot find parent domain\n");
return -ENODEV;
}
if (!parent_domain)
return dev_err_probe(dev, -ENODEV, "cannot find parent domain\n");
rzv2h_icu_data = devm_kzalloc(&pdev->dev, sizeof(*rzv2h_icu_data), GFP_KERNEL);
rzv2h_icu_data = devm_kzalloc(dev, sizeof(*rzv2h_icu_data), GFP_KERNEL);
if (!rzv2h_icu_data)
return -ENOMEM;
platform_set_drvdata(pdev, rzv2h_icu_data);
rzv2h_icu_data->base = devm_of_iomap(&pdev->dev, pdev->dev.of_node, 0, NULL);
rzv2h_icu_data->base = devm_of_iomap(dev, node, 0, NULL);
if (IS_ERR(rzv2h_icu_data->base))
return PTR_ERR(rzv2h_icu_data->base);
ret = rzv2h_icu_parse_interrupts(rzv2h_icu_data, node);
if (ret) {
dev_err(&pdev->dev, "cannot parse interrupts: %d\n", ret);
return ret;
}
if (ret)
return dev_err_probe(dev, ret, "cannot parse interrupts\n");
resetn = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL);
if (IS_ERR(resetn)) {
ret = PTR_ERR(resetn);
dev_err(&pdev->dev, "failed to acquire deasserted reset: %d\n", ret);
return ret;
}
resetn = devm_reset_control_get_exclusive_deasserted(dev, NULL);
if (IS_ERR(resetn))
return dev_err_probe(dev, PTR_ERR(resetn), "failed to acquire deasserted reset\n");
ret = devm_pm_runtime_enable(&pdev->dev);
if (ret < 0) {
dev_err(&pdev->dev, "devm_pm_runtime_enable failed, %d\n", ret);
return ret;
}
ret = devm_pm_runtime_enable(dev);
if (ret < 0)
return dev_err_probe(dev, ret, "devm_pm_runtime_enable failed\n");
ret = pm_runtime_resume_and_get(&pdev->dev);
if (ret < 0) {
dev_err(&pdev->dev, "pm_runtime_resume_and_get failed: %d\n", ret);
return ret;
}
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
return dev_err_probe(dev, ret, "pm_runtime_resume_and_get failed\n");
raw_spin_lock_init(&rzv2h_icu_data->lock);
irq_domain = irq_domain_create_hierarchy(parent_domain, 0, ICU_NUM_IRQ,
dev_fwnode(&pdev->dev), &rzv2h_icu_domain_ops,
dev_fwnode(dev), &rzv2h_icu_domain_ops,
rzv2h_icu_data);
if (!irq_domain) {
dev_err(&pdev->dev, "failed to add irq domain\n");
dev_err(dev, "failed to add irq domain\n");
ret = -ENOMEM;
goto pm_put;
}
@ -614,15 +863,18 @@ static int rzv2h_icu_probe_common(struct platform_device *pdev, struct device_no
register_syscore(&rzv2h_irqc_syscore);
ret = rzv2h_icu_setup_irqs(pdev, irq_domain);
if (ret)
goto pm_put;
/*
* coccicheck complains about a missing put_device call before returning, but it's a false
* positive. We still need &pdev->dev after successfully returning from this function.
* positive. We still need dev after successfully returning from this function.
*/
return 0;
pm_put:
pm_runtime_put_sync(&pdev->dev);
pm_runtime_put_sync(dev);
return ret;
}
@ -657,12 +909,24 @@ static const struct rzv2h_hw_info rzg3e_hw_params = {
.t_offs = ICU_RZG3E_TINT_OFFSET,
.max_tssel = ICU_RZG3E_TSSEL_MAX_VAL,
.field_width = 16,
.ecc_start = 1,
.ecc_end = 4,
};
static const struct rzv2h_hw_info rzv2n_hw_params = {
.t_offs = 0,
.max_tssel = ICU_RZV2H_TSSEL_MAX_VAL,
.field_width = 8,
.ecc_start = 0,
.ecc_end = 2,
};
static const struct rzv2h_hw_info rzv2h_hw_params = {
.t_offs = 0,
.max_tssel = ICU_RZV2H_TSSEL_MAX_VAL,
.field_width = 8,
.ecc_start = 0,
.ecc_end = 11,
};
static int rzg3e_icu_probe(struct platform_device *pdev, struct device_node *parent)
@ -670,6 +934,11 @@ static int rzg3e_icu_probe(struct platform_device *pdev, struct device_node *par
return rzv2h_icu_probe_common(pdev, parent, &rzg3e_hw_params);
}
static int rzv2n_icu_probe(struct platform_device *pdev, struct device_node *parent)
{
return rzv2h_icu_probe_common(pdev, parent, &rzv2n_hw_params);
}
static int rzv2h_icu_probe(struct platform_device *pdev, struct device_node *parent)
{
return rzv2h_icu_probe_common(pdev, parent, &rzv2h_hw_params);
@ -677,7 +946,7 @@ static int rzv2h_icu_probe(struct platform_device *pdev, struct device_node *par
IRQCHIP_PLATFORM_DRIVER_BEGIN(rzv2h_icu)
IRQCHIP_MATCH("renesas,r9a09g047-icu", rzg3e_icu_probe)
IRQCHIP_MATCH("renesas,r9a09g056-icu", rzv2h_icu_probe)
IRQCHIP_MATCH("renesas,r9a09g056-icu", rzv2n_icu_probe)
IRQCHIP_MATCH("renesas,r9a09g057-icu", rzv2h_icu_probe)
IRQCHIP_PLATFORM_DRIVER_END(rzv2h_icu)
MODULE_AUTHOR("Fabrizio Castro <fabrizio.castro.jz@renesas.com>");