diff --git a/drivers/interrupt_controller/CMakeLists.txt b/drivers/interrupt_controller/CMakeLists.txt index 7ef0a5e161e..989a75ab6dc 100644 --- a/drivers/interrupt_controller/CMakeLists.txt +++ b/drivers/interrupt_controller/CMakeLists.txt @@ -37,6 +37,7 @@ zephyr_library_sources_ifdef(CONFIG_NXP_S32_WKPU intc_wkpu_nxp_s32.c) zephyr_library_sources_ifdef(CONFIG_XMC4XXX_INTC intc_xmc4xxx.c) zephyr_library_sources_ifdef(CONFIG_NXP_PINT intc_nxp_pint.c) zephyr_library_sources_ifdef(CONFIG_RENESAS_RA_ICU intc_ra_icu.c) +zephyr_library_sources_ifdef(CONFIG_NXP_IRQSTEER intc_nxp_irqsteer.c) if(CONFIG_INTEL_VTD_ICTL) zephyr_library_include_directories(${ZEPHYR_BASE}/arch/x86/include) diff --git a/drivers/interrupt_controller/Kconfig b/drivers/interrupt_controller/Kconfig index 2a79a998c02..fca4b32e6a4 100644 --- a/drivers/interrupt_controller/Kconfig +++ b/drivers/interrupt_controller/Kconfig @@ -104,4 +104,6 @@ source "drivers/interrupt_controller/Kconfig.vim" source "drivers/interrupt_controller/Kconfig.ra" +source "drivers/interrupt_controller/Kconfig.nxp_irqsteer" + endmenu diff --git a/drivers/interrupt_controller/Kconfig.nxp_irqsteer b/drivers/interrupt_controller/Kconfig.nxp_irqsteer new file mode 100644 index 00000000000..b2dbc9b2afa --- /dev/null +++ b/drivers/interrupt_controller/Kconfig.nxp_irqsteer @@ -0,0 +1,14 @@ +# Copyright 2023 NXP +# SPDX-License-Identifier: Apache-2.0 + +config NXP_IRQSTEER + bool "IRQ_STEER interrupt controller for NXP chips" + default y + depends on DT_HAS_NXP_IRQSTEER_INTC_ENABLED + depends on MULTI_LEVEL_INTERRUPTS + depends on XTENSA + help + The IRQSTEER INTC provides support for MUX-ing + multiple interrupts from peripheral to one or + more CPU interrupt lines. This is used for CPUs + such as XTENSA DSPs. diff --git a/drivers/interrupt_controller/intc_nxp_irqsteer.c b/drivers/interrupt_controller/intc_nxp_irqsteer.c new file mode 100644 index 00000000000..a456c17545e --- /dev/null +++ b/drivers/interrupt_controller/intc_nxp_irqsteer.c @@ -0,0 +1,480 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Driver for NXP's IRQ_STEER IP. + * + * Below you may find some useful information that will help you better understand how the + * driver works. The ">" sign is used to mark ideas that are considered important and should + * be taken note of. + * + * 1) What is the IRQ_STEER IP? + * - in Zephyr terminology, the IRQ_STEER can be considered an interrupt aggregator. As such, + * its main goal is to multiplex multiple interrupt lines into a single/multiple ones. + * + * 2) How does the IRQ_STEER IP work? + * - below you may find a diagram meant to give you an intuition regarding the IP's structure + * and how it works (all of the information below is applicable to i.MX8MP but it can be + * extended to any NXP SoC using the IRQ_STEER IP): + * + * SYSTEM_INTID[0:159] + * | + * MASK[0:4]------ | + * | | + * +------+ + * | | + * |32 AND| + * | | + * +------+ + * | + * SET[0:4]------ | + * | | + * +------+ + * | | + * |32 OR | + * | | + * +------+ + * |__________ STATUS[0:4] + * | + * +------+ + * |GROUP | + * | BY | + * | 64 | + * +------+ + * | | | + * _____________| | |________________ + * | | | + * MASTER_IN[0] MASTER_IN[1] MASTER_IN[2] + * | | | + * | | | + * |_____________ | _______________| + * | | | + * +------+ + * | | + * | AND | ---------- MINTDIS[0:2] + * | | + * +------+ + * | | | + * _____________| | |________________ + * | | | + * MASTER_OUT[0] MASTER_OUT[1] MASTER_OUT[2] + * + * - initially, all SYSTEM_INTID are grouped by 32 => 5 groups. + * + * > each of these groups is controlled by a MASK, SET and STATUS index as follows: + * + * MASK/SET/STATUS[0] => SYSTEM_INTID[159:128] + * MASK/SET/STATUS[1] => SYSTEM_INTID[127:96] + * MASK/SET/STATUS[2] => SYSTEM_INTID[95:64] + * MASK/SET/STATUS[3] => SYSTEM_INTID[63:32] + * MASK/SET/STATUS[4] => SYSTEM_INTID[31:0] + * + * > after that, all SYSTEM_INTID are grouped by 64 as follows: + * + * SYSTEM_INTID[159:96] => MASTER_IN[2] + * SYSTEM_INTID[95:32] => MASTER_IN[1] + * SYSTEM_INTID[31:0] => MASTER_IN[0] + * + * note: MASTER_IN[0] is only responsible for 32 interrupts + * + * > the value of MASTER_IN[x] is obtained by OR'ing the input interrupt lines. + * + * > the value of MASTER_OUT[x] is obtained by AND'ing MASTER_IN[x] with !MINTDIS[x]. + * + * - whenever a SYSTEM_INTID is asserted, its corresponding MASTER_OUT signal will also + * be asserted, thus signaling the target processor. + * + * > please note the difference between an IRQ_STEER channel and an IRQ_STEER master output. + * An IRQ_STEER channel refers to an IRQ_STEER instance (e.g: the DSP uses IRQ_STEER channel + * 0 a.k.a instance 0). An IRQ_STEER channel has multiple master outputs. For example, in + * the case of i.MX8MP each IRQ_STEER channel has 3 master outputs since an IRQ_STEER channel + * routes 160 interrupts (32 for first master output, 64 for second master output, and 64 for + * the third master output). + * + * 3) Using Zephyr's multi-level interrupt support + * - since Zephyr supports organizing interrupts on multiple levels, we can use this to + * separate the interrupts in 2 levels: + * 1) LEVEL 1 INTERRUPTS + * - these are the interrupts that go directly to the processor (for example, + * on i.MX8MP the MU can directly assert the DSP's interrupt line 7) + * + * 2) LEVEL 2 INTERRUPTS + * - these interrupts go through IRQ_STEER and are signaled by a single + * processor interrupt line. + * - e.g: for i.MX8MP, INTID 34 (SDMA3) goes through IRQ_STEER and is signaled + * to the DSP by INTID 20 which is a direct interrupt (or LEVEL 1 interrupt). + * + * - the following diagram (1) shows the interrupt organization on i.MX8MP: + * +------------+ + * | | + * SYSTEM_INTID[31:0] ------ IRQ_STEER_MASTER_0 ---- | 19 | + * | | + * SYSTEM_INTID[95:32] ----- IRQ_STEER_MASTER_1 ---- | 20 DSP | + * | | + * SYSTEM_INTID[159:96] ---- IRQ_STEER_MASTER_2 ---- | 21 | + * | | + * +------------+ + * + * - as such, asserting a system interrupt will lead to asserting its corresponding DSP + * interrupt line (for example, if system interrupt 34 is asserted, that would lead to + * interrupt 20 being asserted) + * + * - in the above diagram, SYSTEM_INTID[x] are LEVEL 2 interrupts, while 19, 20, and 21 are + * LEVEL 1 interrupts. + * + * - INTID 19 is the parent of SYSTEM_INTID[31:0] and so on. + * + * > before going into how the INTIDs are encoded, we need to distinguish between 3 types of + * INTIDs: + * 1) System INTIDs + * - these are the values that can be found in NXP's TRMs for different + * SoCs (usually they have the same IDs as the GIC SPIs) + * - for example, INTID 34 is a system INTID for SDMA3 (i.MX8MP). + * + * 2) Zephyr INTIDs + * - these are the Zephyr-specific encodings of the system INTIDs. + * - these are used to encode multi-level interrupts (for more information + * please see [1]) + * > if you need to register an interrupt dynamically, you need to use this + * encoding when specifying the interrupt. + * + * 3) DTS INTIDs + * - these are the encodings of the system INTIDs used in the DTS. + * - all of these INTIDs are relative to IRQ_STEER's MASTER_OUTs. + * + * > encoding an INTID: + * 1) SYSTEM INTID => ZEPHYR INTID + * - the following steps need to be performed: + * + * a) Find out which IRQ_STEER MASTER + * is in charge of aggregating this interrupt. + * * for instance, SYSTEM_INTID 34 (SDMA3 on i.MX8MP) is + * aggregated by MASTER 1 as depicted in diagram (1). + * + * b) After finding the MASTER aggregator, you need + * to find the corresponding parent interrupt. + * * for example, SYSTEM_INTID 34 (SDMA3 on i.MX8MP) is + * aggregated by MASTER 1, which has the parent INTID 20 + * as depicted in diagram (1) => PARENT_INTID(34) = 20. + * + * c) Find the INTID relative to the MASTER aggregator. This is done + * by subtracting the number of interrupts each of the previous + * master aggregators is in charge of. If the master aggregator is + * MASTER 0 then RELATIVE_INTID=SYSTEM_INTID. + * * for example, SYSTEM_ID 34 is aggregated by MASTER 1. + * As such, we need to subtract 32 from 34 (because the + * previous master - MASTER 0 - is in charge of aggregating + * 32 interrupts) => RELATIVE_INTID(34) = 2. + * + * * generally speaking, RELATIVE_INTID can be computed using + * the following formula (assuming SYSTEM_INTID belongs to + * MASTER y): + * RELATIVE_INTID(x) = x - + * \sum{i=0}^{y - 1} GET_MASTER_INT_NUM(i) + * where: + * 1) GET_MASTER_INT_NUM(x) computes the number of + * interrupts master x aggregates + * 2) x is the system interrupt + * + * * to make sure your computation is correct use the + * following restriction: + * 0 <= RELATIVE_INTID(x) < GET_MASTER_INT_NUM(y) + * + * d) To the obtained RELATIVE_INTID you need to add the value of 1, + * left shift the result by the number of bits used to encode the + * level 1 interrupts (see [1] for details) and OR the parent ID. + * * for example, RELATIVE_INTID(34) = 2 (i.MX8MP), + * PARENT_INTID(34) = 20 => ZEPHYR_INTID = ((2 + 1) << 8) | 20 + * + * * generally speaking, ZEPHYR_INTID can be computed using + * the following formula: + * ZEPHYR_INTID(x) = ((RELATIVE_INTID(x) + 1) << + * NUM_LVL1_BITS) | PARENT_INTID(x) + * where: + * 1) RELATIVE_INTID(x) computes the relative INTID + * of system interrupt x (step c). + * + * 2) NUM_LVL1_BITS is the number of bits used to + * encode level 1 interrupts. + * + * 3) PARENT_INTID(x) computes the parent INTID of a + * system interrupt x (step b) + * + * - all of these steps are performed by to_zephyr_irq(). + * > for interrupts aggregated by MASTER 0 you may skip step c) as + * RELATIVE_INTID(x) = x. + * + * 2) SYSTEM INTID => DTS INTID + * - for this you just have to compute RELATIVE_INTID as described above in + * step c). + * - for example, if an IP uses INTID 34 you'd write its interrupts property + * as follows (i.MX8MP): + * interrupts = <&master1 2>; + * + * 4) Notes and comments + * > PLEASE DON'T MISTAKE THE ZEPHYR MULTI-LEVEL INTERRUPT ORGANIZATION WITH THE XTENSA ONE. + * THEY ARE DIFFERENT THINGS. + * + * [1]: https://docs.zephyrproject.org/latest/kernel/services/interrupts.html#multi-level-interrupt-handling + */ + +#include +#include +#include +#include + +#include "sw_isr_common.h" + +/* used for driver binding */ +#define DT_DRV_COMPAT nxp_irqsteer_intc + +/* macros used for DTS parsing */ +#define _IRQSTEER_REGISTER_DISPATCHER(node_id) \ + IRQ_CONNECT(DT_IRQN(node_id), \ + DT_IRQ(node_id, priority), \ + irqsteer_isr_dispatcher, \ + &dispatchers[DT_REG_ADDR(node_id)], \ + 0) + +#define _IRQSTEER_DECLARE_DISPATCHER(node_id) \ +{ \ + .dev = DEVICE_DT_GET(DT_PARENT(node_id)), \ + .master_index = DT_REG_ADDR(node_id), \ + .irq = DT_IRQN(node_id), \ +} + +#define IRQSTEER_DECLARE_DISPATCHERS(parent_id)\ + DT_FOREACH_CHILD_STATUS_OKAY_SEP(parent_id, _IRQSTEER_DECLARE_DISPATCHER, (,)) + +#define IRQSTEER_REGISTER_DISPATCHERS(parent_id)\ + DT_FOREACH_CHILD_STATUS_OKAY_SEP(parent_id, _IRQSTEER_REGISTER_DISPATCHER, (;)) + +/* utility macros */ +#define UINT_TO_IRQSTEER(x) ((IRQSTEER_Type *)(x)) + +struct irqsteer_config { + uint32_t regmap_phys; + uint32_t regmap_size; + struct irqsteer_dispatcher *dispatchers; +}; + +struct irqsteer_dispatcher { + const struct device *dev; + /* which set of interrupts is the dispatcher in charge of? */ + uint32_t master_index; + /* which interrupt line is the dispatcher tied to? */ + uint32_t irq; +}; + +static struct irqsteer_dispatcher dispatchers[] = { + IRQSTEER_DECLARE_DISPATCHERS(DT_NODELABEL(irqsteer)) +}; + +/* used to convert system INTID to zephyr INTID */ +static int to_zephyr_irq(uint32_t regmap, uint32_t irq, + struct irqsteer_dispatcher *dispatcher) +{ + int i, idx; + + idx = irq; + + for (i = dispatcher->master_index - 1; i >= 0; i--) { + idx -= IRQSTEER_GetMasterIrqCount(UINT_TO_IRQSTEER(regmap), i); + } + + return irq_to_level_2(idx) | dispatcher->irq; +} + +/* used to convert master-relative INTID to system INTID */ +static int to_system_irq(uint32_t regmap, int irq, int master_index) +{ + int i; + + for (i = master_index - 1; i >= 0; i--) { + irq += IRQSTEER_GetMasterIrqCount(UINT_TO_IRQSTEER(regmap), i); + } + + return irq; +} + +/* used to convert zephyr INTID to system INTID */ +static int from_zephyr_irq(uint32_t regmap, uint32_t irq, uint32_t master_index) +{ + int i, idx; + + idx = irq; + + for (i = 0; i < master_index; i++) { + idx += IRQSTEER_GetMasterIrqCount(UINT_TO_IRQSTEER(regmap), i); + } + + return idx; +} + +void z_soc_irq_enable_disable(uint32_t irq, bool enable) +{ + uint32_t parent_irq; + int i, system_irq, level2_irq; + const struct irqsteer_config *cfg; + + if (irq_get_level(irq) == 1) { + /* LEVEL 1 interrupts are DSP direct */ + if (enable) { + z_xtensa_irq_enable(XTENSA_IRQ_NUMBER(irq)); + } else { + z_xtensa_irq_disable(XTENSA_IRQ_NUMBER(irq)); + } + return; + } + + parent_irq = irq_parent_level_2(irq); + level2_irq = irq_from_level_2(irq); + + /* find dispatcher responsible for this interrupt */ + for (i = 0; i < ARRAY_SIZE(dispatchers); i++) { + if (dispatchers[i].irq != parent_irq) { + continue; + } + + cfg = dispatchers[i].dev->config; + + system_irq = from_zephyr_irq(cfg->regmap_phys, level2_irq, + dispatchers[i].master_index); + + if (enable) { + IRQSTEER_EnableInterrupt(UINT_TO_IRQSTEER(cfg->regmap_phys), + system_irq); + } else { + IRQSTEER_DisableInterrupt(UINT_TO_IRQSTEER(cfg->regmap_phys), + system_irq); + } + + return; + } +} + +void z_soc_irq_enable(uint32_t irq) +{ + z_soc_irq_enable_disable(irq, true); +} + +void z_soc_irq_disable(uint32_t irq) +{ + z_soc_irq_enable_disable(irq, false); +} + +int z_soc_irq_is_enabled(unsigned int irq) +{ + uint32_t parent_irq; + int i, system_irq, level2_irq; + const struct irqsteer_config *cfg; + + if (irq_get_level(irq) == 1) { + /* LEVEL 1 interrupts are DSP direct */ + return z_xtensa_irq_is_enabled(XTENSA_IRQ_NUMBER(irq)); + } + + parent_irq = irq_parent_level_2(irq); + level2_irq = irq_from_level_2(irq); + + /* find dispatcher responsible for this interrupt */ + for (i = 0; i < ARRAY_SIZE(dispatchers); i++) { + if (dispatchers[i].irq != parent_irq) { + continue; + } + + cfg = dispatchers[i].dev->config; + + system_irq = from_zephyr_irq(cfg->regmap_phys, level2_irq, + dispatchers[i].master_index); + + return IRQSTEER_InterruptIsEnabled(UINT_TO_IRQSTEER(cfg->regmap_phys), system_irq); + } + + return false; +} + + +static void irqsteer_isr_dispatcher(const void *data) +{ + struct irqsteer_dispatcher *dispatcher; + const struct irqsteer_config *cfg; + uint32_t table_idx; + int system_irq, zephyr_irq, i; + uint64_t status; + + dispatcher = (struct irqsteer_dispatcher *)data; + cfg = dispatcher->dev->config; + + /* fetch master interrupts status */ + status = IRQSTEER_GetMasterInterruptsStatus(UINT_TO_IRQSTEER(cfg->regmap_phys), + dispatcher->master_index); + + for (i = 0; status; i++) { + /* if bit 0 is set then that means relative INTID i is asserted */ + if (status & 1) { + /* convert master-relative INTID to a system INTID */ + system_irq = to_system_irq(cfg->regmap_phys, i, + dispatcher->master_index); + + /* convert system INTID to a Zephyr INTID */ + zephyr_irq = to_zephyr_irq(cfg->regmap_phys, system_irq, dispatcher); + + /* compute index in the SW ISR table */ + table_idx = z_get_sw_isr_table_idx(zephyr_irq); + + /* call child's ISR */ + _sw_isr_table[table_idx].isr(_sw_isr_table[table_idx].arg); + } + + status >>= 1; + } +} + +static void irqsteer_enable_dispatchers(const struct device *dev) +{ + int i; + struct irqsteer_dispatcher *dispatcher; + const struct irqsteer_config *cfg; + + cfg = dev->config; + + for (i = 0; i < ARRAY_SIZE(dispatchers); i++) { + dispatcher = &dispatchers[i]; + + IRQSTEER_EnableMasterInterrupt(UINT_TO_IRQSTEER(cfg->regmap_phys), + dispatcher->irq); + + z_xtensa_irq_enable(XTENSA_IRQ_NUMBER(dispatcher->irq)); + } +} + +static int irqsteer_init(const struct device *dev) +{ + IRQSTEER_REGISTER_DISPATCHERS(DT_NODELABEL(irqsteer)); + + /* enable all dispatchers */ + irqsteer_enable_dispatchers(dev); + + return 0; +} + + +/* TODO: do we need to add support for MMU-based SoCs? */ +static struct irqsteer_config irqsteer_config = { + .regmap_phys = DT_REG_ADDR(DT_NODELABEL(irqsteer)), + .regmap_size = DT_REG_SIZE(DT_NODELABEL(irqsteer)), + .dispatchers = dispatchers, +}; + +/* assumption: only 1 IRQ_STEER instance */ +DEVICE_DT_INST_DEFINE(0, + &irqsteer_init, + NULL, + NULL, &irqsteer_config, + PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, + NULL); diff --git a/dts/bindings/interrupt-controller/nxp,irqsteer-intc.yaml b/dts/bindings/interrupt-controller/nxp,irqsteer-intc.yaml index 03b5d3b39e0..f3300ae6821 100644 --- a/dts/bindings/interrupt-controller/nxp,irqsteer-intc.yaml +++ b/dts/bindings/interrupt-controller/nxp,irqsteer-intc.yaml @@ -2,12 +2,8 @@ description: i.MX DSP interrupt controller compatible: "nxp,irqsteer-intc" -include: [interrupt-controller.yaml, base.yaml] +include: [base.yaml] properties: - "#interrupt-cells": - const: 2 - -interrupt-cells: - - irq - - priority + reg: + required: true diff --git a/dts/bindings/interrupt-controller/nxp,irqsteer-master.yaml b/dts/bindings/interrupt-controller/nxp,irqsteer-master.yaml new file mode 100644 index 00000000000..18c3fac42db --- /dev/null +++ b/dts/bindings/interrupt-controller/nxp,irqsteer-master.yaml @@ -0,0 +1,15 @@ +description: i.MX IRQ_STEER master + +compatible: "nxp,irqsteer-master" + +include: [interrupt-controller.yaml, base.yaml] + +properties: + "#interrupt-cells": + const: 1 + + reg: + required: true + +interrupt-cells: + - irq diff --git a/dts/xtensa/nxp/nxp_imx8m.dtsi b/dts/xtensa/nxp/nxp_imx8m.dtsi index 21a094c0461..70bfd500798 100644 --- a/dts/xtensa/nxp/nxp_imx8m.dtsi +++ b/dts/xtensa/nxp/nxp_imx8m.dtsi @@ -17,6 +17,16 @@ device_type = "cpu"; compatible = "cdns,tensilica-xtensa-lx6"; reg = <0>; + + #address-cells = <1>; + #size-cells = <0>; + + clic: interrupt-controller@0 { + compatible = "cdns,xtensa-core-intc"; + reg = <0>; + interrupt-controller; + #interrupt-cells = <3>; + }; }; }; @@ -33,14 +43,6 @@ }; soc { - interrupt-parent = <&irqsteer>; - - irqsteer: interrupt-controller { - compatible = "nxp,irqsteer-intc"; - interrupt-controller; - #interrupt-cells = <2>; - }; - ccm: ccm@30380000 { compatible = "nxp,imx-ccm"; reg = <0x30380000 DT_SIZE_K(64)>; @@ -67,7 +69,8 @@ /* TODO: This INTID is just a dummy * until we can support UART interrupts */ - interrupts = <29 0>; + interrupt-parent = <&clic>; + interrupts = <29 0 0>; clocks = <&ccm IMX_CCM_UART4_CLK 0x6c 24>; status = "disabled"; }; @@ -75,7 +78,8 @@ mailbox0: mailbox@30e70000 { compatible = "nxp,imx-mu"; reg = <0x30e70000 0x10000>; - interrupts = <7 0>; + interrupt-parent = <&clic>; + interrupts = <7 0 0>; rdc = <0>; status = "disabled"; };