/* * 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 #include #include #include #include #include "sw_isr_common.h" LOG_MODULE_REGISTER(nxp_irqstr); /* 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)) #define DISPATCHER_REGMAP(disp) \ (((const struct irqsteer_config *)disp->dev->config)->regmap_phys) 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; /* reference count for all IRQs aggregated by dispatcher */ uint8_t irq_refcnt[CONFIG_MAX_IRQ_PER_AGGREGATOR]; /* dispatcher lock */ struct k_spinlock lock; /* reference count for dispatcher */ uint8_t refcnt; }; 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; } static void _irqstr_disp_enable_disable(struct irqsteer_dispatcher *disp, bool enable) { uint32_t regmap = DISPATCHER_REGMAP(disp); if (enable) { xtensa_irq_enable(XTENSA_IRQ_NUMBER(disp->irq)); IRQSTEER_EnableMasterInterrupt(UINT_TO_IRQSTEER(regmap), disp->irq); } else { IRQSTEER_DisableMasterInterrupt(UINT_TO_IRQSTEER(regmap), disp->irq); xtensa_irq_disable(XTENSA_IRQ_NUMBER(disp->irq)); } } static void _irqstr_disp_get_unlocked(struct irqsteer_dispatcher *disp) { int ret; if (disp->refcnt == UINT8_MAX) { LOG_WRN("disp for irq %d reference count reached limit", disp->irq); return; } if (!disp->refcnt) { ret = pm_device_runtime_get(disp->dev); if (ret < 0) { LOG_ERR("failed to enable PM resources: %d", ret); return; } _irqstr_disp_enable_disable(disp, true); } disp->refcnt++; LOG_DBG("get on disp for irq %d results in refcnt: %d", disp->irq, disp->refcnt); } static void _irqstr_disp_put_unlocked(struct irqsteer_dispatcher *disp) { int ret; if (!disp->refcnt) { LOG_WRN("disp for irq %d already put", disp->irq); return; } disp->refcnt--; if (!disp->refcnt) { _irqstr_disp_enable_disable(disp, false); ret = pm_device_runtime_put(disp->dev); if (ret < 0) { LOG_ERR("failed to disable PM resources: %d", ret); return; } } LOG_DBG("put on disp for irq %d results in refcnt: %d", disp->irq, disp->refcnt); } static void _irqstr_enable_disable_irq(struct irqsteer_dispatcher *disp, uint32_t system_irq, bool enable) { uint32_t regmap = DISPATCHER_REGMAP(disp); if (enable) { IRQSTEER_EnableInterrupt(UINT_TO_IRQSTEER(regmap), system_irq); } else { IRQSTEER_DisableInterrupt(UINT_TO_IRQSTEER(regmap), system_irq); } } static void irqstr_request_irq_unlocked(struct irqsteer_dispatcher *disp, uint32_t zephyr_irq) { uint32_t system_irq = from_zephyr_irq(DISPATCHER_REGMAP(disp), zephyr_irq, disp->master_index); #ifndef CONFIG_SHARED_INTERRUPTS if (disp->irq_refcnt[zephyr_irq]) { LOG_WRN("irq %d already requested", system_irq); return; } #endif /* CONFIG_SHARED_INTERRUPTS */ if (disp->irq_refcnt[zephyr_irq] == UINT8_MAX) { LOG_WRN("irq %d reference count reached limit", system_irq); return; } if (!disp->irq_refcnt[zephyr_irq]) { _irqstr_disp_get_unlocked(disp); _irqstr_enable_disable_irq(disp, system_irq, true); } disp->irq_refcnt[zephyr_irq]++; LOG_DBG("requested irq %d has refcount %d", system_irq, disp->irq_refcnt[zephyr_irq]); } static void irqstr_release_irq_unlocked(struct irqsteer_dispatcher *disp, uint32_t zephyr_irq) { uint32_t system_irq = from_zephyr_irq(DISPATCHER_REGMAP(disp), zephyr_irq, disp->master_index); if (!disp->irq_refcnt[zephyr_irq]) { LOG_WRN("irq %d already released", system_irq); return; } disp->irq_refcnt[zephyr_irq]--; if (!disp->irq_refcnt[zephyr_irq]) { _irqstr_enable_disable_irq(disp, system_irq, false); _irqstr_disp_put_unlocked(disp); } LOG_DBG("released irq %d has refcount %d", system_irq, disp->irq_refcnt[zephyr_irq]); } void z_soc_irq_enable_disable(uint32_t irq, bool enable) { uint32_t parent_irq; int i, level2_irq; 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; } K_SPINLOCK(&dispatchers[i].lock) { if (enable) { irqstr_request_irq_unlocked(&dispatchers[i], level2_irq); } else { irqstr_release_irq_unlocked(&dispatchers[i], level2_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; const struct irqsteer_config *cfg; bool enabled; 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); enabled = false; /* 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; K_SPINLOCK(&dispatchers[i].lock) { enabled = dispatchers[i].irq_refcnt[irq_from_level_2(irq)]; } return enabled; } 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; } } __maybe_unused static int irqstr_pm_action(const struct device *dev, enum pm_device_action action) { /* nothing to be done here */ return 0; } static int irqsteer_init(const struct device *dev) { IRQSTEER_REGISTER_DISPATCHERS(DT_NODELABEL(irqsteer)); return pm_device_runtime_enable(dev); } /* 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 */ PM_DEVICE_DT_INST_DEFINE(0, irqstr_pm_action); DEVICE_DT_INST_DEFINE(0, &irqsteer_init, PM_DEVICE_DT_INST_GET(0), 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);