| /* |
| * 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 <zephyr/device.h> |
| #include <zephyr/devicetree/interrupt_controller.h> |
| #include <zephyr/irq.h> |
| #include <fsl_irqsteer.h> |
| #include <zephyr/cache.h> |
| #include <zephyr/sw_isr_table.h> |
| |
| #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) { |
| xtensa_irq_enable(XTENSA_IRQ_NUMBER(irq)); |
| } else { |
| 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 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); |
| |
| 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); |
| |
| #define NXP_IRQSTEER_MASTER_IRQ_ENTRY_DEF(node_id) \ |
| IRQ_PARENT_ENTRY_DEFINE(CONCAT(nxp_irqsteer_master_, DT_NODE_CHILD_IDX(node_id)), NULL, \ |
| DT_IRQN(node_id), INTC_CHILD_ISR_TBL_OFFSET(node_id), \ |
| DT_INTC_GET_AGGREGATOR_LEVEL(node_id)); |
| |
| DT_INST_FOREACH_CHILD_STATUS_OKAY(0, NXP_IRQSTEER_MASTER_IRQ_ENTRY_DEF); |