|  | /* | 
|  | * Copyright (c) 2022 Intel Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT intel_ace_intc | 
|  |  | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/devicetree.h> | 
|  | #include <zephyr/devicetree/interrupt_controller.h> | 
|  | #include <zephyr/irq_nextlevel.h> | 
|  | #include <zephyr/arch/xtensa/irq.h> | 
|  | #include <zephyr/sw_isr_table.h> | 
|  | #include <zephyr/drivers/interrupt_controller/dw_ace.h> | 
|  | #include <soc.h> | 
|  | #include <adsp_interrupt.h> | 
|  | #include <zephyr/irq.h> | 
|  | #include "intc_dw.h" | 
|  |  | 
|  | /* ACE device interrupts are all packed into a single line on Xtensa's | 
|  | * architectural IRQ 4 (see below), run by a Designware interrupt | 
|  | * controller with 28 lines instantiated.  They get numbered | 
|  | * immediately after the Xtensa interrupt space in the numbering | 
|  | * (i.e. interrupts 0-31 are Xtensa IRQs, 32 represents DW input 0, | 
|  | * etc...). | 
|  | * | 
|  | * That IRQ 4 indeed has an interrupt type of "EXTERN_LEVEL" and an | 
|  | * interrupt level of 2.  The CPU has a level 1 external interrupt on | 
|  | * IRQ 1 and a level 3 on IRQ 6, but nothing seems wired there.  Note | 
|  | * that this level 2 ISR is also shared with the CCOUNT timer on IRQ3. | 
|  | * This interrupt is a very busy place! | 
|  | * | 
|  | * But, because there can never be a situation where all interrupts on | 
|  | * the Synopsys controller are disabled (such a system would halt | 
|  | * forever if it reached idle!), we at least can take advantage to | 
|  | * implement a simplified masking architecture.  Xtensa INTENABLE | 
|  | * always has the line active, and we do all masking of external | 
|  | * interrupts on the single controller. | 
|  | * | 
|  | * Finally: note that there is an extra layer of masking on ACE.  The | 
|  | * ACE_DINT registers provide separately maskable interrupt delivery | 
|  | * for each core, and with some devices for different internal | 
|  | * interrupt sources.  Responsibility for these mask bits is left with | 
|  | * the driver. | 
|  | * | 
|  | * Thus, the masking architecture picked here is: | 
|  | * | 
|  | * + Drivers manage ACE_DINT themselves, as there are device-specific | 
|  | *   mask indexes that only the driver can interpret.  If | 
|  | *   core-asymmetric interrupt routing needs to happen, it happens | 
|  | *   here. | 
|  | * | 
|  | * + The DW layer is en/disabled uniformly across all cores.  This is | 
|  | *   the layer toggled by arch_irq_en/disable(). | 
|  | * | 
|  | * + Index 4 in the INTENABLE SR is set at core startup and stays | 
|  | *   enabled always. | 
|  | */ | 
|  |  | 
|  | /* ACE also has per-core instantiations of a Synopsys interrupt | 
|  | * controller.  These inputs (with the same indices as ACE_INTL_* | 
|  | * above) are downstream of the DINT layer, and must be independently | 
|  | * masked/enabled.  The core Zephyr intc_dw driver unfortunately | 
|  | * doesn't understand this kind of MP implementation.  Note also that | 
|  | * as instantiated (there are only 28 sources), the high 32 bit | 
|  | * registers don't exist and aren't named here.  Access via e.g.: | 
|  | * | 
|  | *     ACE_INTC[core_id].irq_inten_l |= interrupt_bit; | 
|  | */ | 
|  |  | 
|  | #define ACE_INTC ((volatile struct dw_ictl_registers *)DT_INST_REG_ADDR(0)) | 
|  |  | 
|  | static inline bool is_dw_irq(uint32_t irq) | 
|  | { | 
|  | if (((irq & XTENSA_IRQ_NUM_MASK) == ACE_INTC_IRQ) | 
|  | && ((irq & ~XTENSA_IRQ_NUM_MASK) != 0)) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void dw_ace_irq_enable(const struct device *dev, uint32_t irq) | 
|  | { | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | if (is_dw_irq(irq)) { | 
|  | unsigned int num_cpus = arch_num_cpus(); | 
|  |  | 
|  | for (int i = 0; i < num_cpus; i++) { | 
|  | ACE_INTC[i].irq_inten_l |= BIT(ACE_IRQ_FROM_ZEPHYR(irq)); | 
|  | ACE_INTC[i].irq_intmask_l &= ~BIT(ACE_IRQ_FROM_ZEPHYR(irq)); | 
|  | } | 
|  | } else if ((irq & ~XTENSA_IRQ_NUM_MASK) == 0U) { | 
|  | xtensa_irq_enable(XTENSA_IRQ_NUMBER(irq)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void dw_ace_irq_disable(const struct device *dev, uint32_t irq) | 
|  | { | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | if (is_dw_irq(irq)) { | 
|  | unsigned int num_cpus = arch_num_cpus(); | 
|  |  | 
|  | for (int i = 0; i < num_cpus; i++) { | 
|  | ACE_INTC[i].irq_inten_l &= ~BIT(ACE_IRQ_FROM_ZEPHYR(irq)); | 
|  | ACE_INTC[i].irq_intmask_l |= BIT(ACE_IRQ_FROM_ZEPHYR(irq)); | 
|  | } | 
|  | } else if ((irq & ~XTENSA_IRQ_NUM_MASK) == 0U) { | 
|  | xtensa_irq_disable(XTENSA_IRQ_NUMBER(irq)); | 
|  | } | 
|  | } | 
|  |  | 
|  | int dw_ace_irq_is_enabled(const struct device *dev, unsigned int irq) | 
|  | { | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | if (is_dw_irq(irq)) { | 
|  | return ACE_INTC[0].irq_inten_l & BIT(ACE_IRQ_FROM_ZEPHYR(irq)); | 
|  | } else if ((irq & ~XTENSA_IRQ_NUM_MASK) == 0U) { | 
|  | return xtensa_irq_is_enabled(XTENSA_IRQ_NUMBER(irq)); | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_DYNAMIC_INTERRUPTS | 
|  | int dw_ace_irq_connect_dynamic(const struct device *dev, unsigned int irq, | 
|  | unsigned int priority, | 
|  | void (*routine)(const void *parameter), | 
|  | const void *parameter, uint32_t flags) | 
|  | { | 
|  | /* Simple architecture means that the Zephyr irq number and | 
|  | * the index into the ISR table are identical. | 
|  | */ | 
|  | ARG_UNUSED(dev); | 
|  | ARG_UNUSED(flags); | 
|  | ARG_UNUSED(priority); | 
|  | z_isr_install(irq, routine, parameter); | 
|  | return irq; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static void dwint_isr(const void *arg) | 
|  | { | 
|  | uint32_t fs = ACE_INTC[arch_proc_id()].irq_finalstatus_l; | 
|  |  | 
|  | while (fs) { | 
|  | uint32_t bit = find_lsb_set(fs) - 1; | 
|  | uint32_t offset = CONFIG_2ND_LVL_ISR_TBL_OFFSET + bit; | 
|  | const struct _isr_table_entry *ent = &_sw_isr_table[offset]; | 
|  |  | 
|  | fs &= ~BIT(bit); | 
|  | ent->isr(ent->arg); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int dw_ace_init(const struct device *dev) | 
|  | { | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | IRQ_CONNECT(ACE_INTC_IRQ, 0, dwint_isr, 0, 0); | 
|  | xtensa_irq_enable(ACE_INTC_IRQ); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct dw_ace_v1_ictl_driver_api dw_ictl_ace_v1x_apis = { | 
|  | .intr_enable = dw_ace_irq_enable, | 
|  | .intr_disable = dw_ace_irq_disable, | 
|  | .intr_is_enabled = dw_ace_irq_is_enabled, | 
|  | #ifdef CONFIG_DYNAMIC_INTERRUPTS | 
|  | .intr_connect_dynamic = dw_ace_irq_connect_dynamic, | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | DEVICE_DT_INST_DEFINE(0, dw_ace_init, NULL, NULL, NULL, | 
|  | PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, | 
|  | &dw_ictl_ace_v1x_apis); | 
|  |  | 
|  | IRQ_PARENT_ENTRY_DEFINE(ace_intc, DEVICE_DT_INST_GET(0), DT_INST_IRQN(0), | 
|  | INTC_BASE_ISR_TBL_OFFSET(DT_DRV_INST(0)), | 
|  | DT_INST_INTC_GET_AGGREGATOR_LEVEL(0)); |