diff --git a/soc/mediatek/mt8xxx/soc.c b/soc/mediatek/mt8xxx/soc.c index df98292af6b..128e7cad996 100644 --- a/soc/mediatek/mt8xxx/soc.c +++ b/soc/mediatek/mt8xxx/soc.c @@ -29,6 +29,113 @@ extern char _mtk_adsp_dram_end[]; #define LOG_LEN 0x100000 #endif +/* The MT8196 interrupt controller is very simple at runtime, with + * just an enable and status register needed, like its + * predecessors. But it has routing control which resets to "nothing + * enabled", so needs a driver. + * + * There are 64 interrupt inputs to the controller, controlled by + * pairs of words (the "intc64" type below). Each interrupt is + * associated with one[1] of 16 "groups", each of which directs to a + * different Xtensa architectural interrupt. So each Xtensa interrupt + * can be configured to handle any subset of interrupt inputs. + * + * The mapping of groups to Xtensa interrupts is given below. Note + * particularly that the final two groups are NMIs directed to an + * interrupt level higher than EXCM_LEVEL, so cannot be safely used + * for OS code (they'll interrupt spinlocks), but an app might exploit + * them for e.g. debug or watchdog hooks. + * + * GroupNum XtensaIRQ XtensaLevel + * 0-5 0-5 1 (L1 is shared w/exceptions, poor choice) + * 6-7 7-8 1 + * 8-10 9-11 2 + * 11-13 16-18 3 + * 14,15 20,21 4 (Unmaskable! Do not use w/Zephyr code!) + * + * Naming of the inputs looks like this, though obviously only a small + * fraction have been validated (or are even useful for an audio DSP): + * + * 0: CCU 20: USB1 40: WDT + * 1: SCP 21: SCPVOW 41: CONNSYS1 + * 2: SPM 22: CCIF3_C0 42: CONNSYS3 + * 3: PCIE 23: CCIF3_C1 43: CONNSYS4 + * 4: INFRA_HANG 24: PWR_CTRL 44: CONNSYS2 + * 5: PERI_TIMEOUT 25: DMA_C0 45: IPIC + * 6: MBOX_C0 26: DMA_C1 46: AXI_DMA2 + * 7: MBOX_C1 27: AXI_DMA0 47: AXI_DMA3 + * 8: TIMER0 28: AXI_DMA1 48: APSRC_DDREN + * 9: TIMER1 29: AUDIO_C0 49: LAT_MON_EMI + * 10: IPC_C0 30: AUDIO_C1 50: LAT_MON_INFRA + * 11: IPC_C1 31: HIFI5_WDT_C0 51: DEVAPC_VIO + * 12: IPC1_RSV 32: HIFI5_WDT_C1 52: AO_INFRA_HANG + * 13: C2C_SW_C0 33: APU_MBOX_C0 53: BUS_TRA_EMI + * 14: C2C_SW_C1 34: APU_MBOX_C1 54: BUS_TRA_INFRA + * 15: UART 35: TIMER2 55: L2SRAM_VIO + * 16: UART_BT 36: PWR_ON_C0_IRQ 56: L2SRAM_SETERR + * 17: LATENCY_MON 37: PWR_ON_C1_IRQ 57: PCIERC_GRP2 + * 18: BUS_TRACKER 38: WAKEUP_SRC_C0 58: PCIERC_GRP3 + * 19: USB0 39: WAKEUP_SRC_C1 59: IRQ_MAX_CHANNEL + * + * [1] It is legal and works as expected for an interrupt to be part + * of more than one group (more than one interrupt fires to handle + * it), though I don't understand why an application would want to + * do that. + */ + +struct intc64 { uint32_t lo, hi; }; + +struct intc_8196 { + struct intc64 input; /* Raw (?) input signal, normally high */ + struct intc64 status; /* Latched input, inverted (active == 1) */ + struct intc64 enable; /* Interrupt enable */ + struct intc64 polarity; /* 1 == active low */ + struct intc64 wake_enable; + struct intc64 _unused; + struct intc64 stage1_enable; + struct intc64 sw_trigger; + struct intc64 groups[16]; /* set bit == "member of group" */ + struct intc64 group_status[16]; /* status, but masked by group */ +}; + +#define INTC (*(volatile struct intc_8196 *)0x1a014000) + +static void set_group_bit(volatile struct intc64 *g, uint32_t bit, bool val) +{ + volatile uint32_t *p = bit < 32 ? &g->lo : &g->hi; + volatile uint32_t mask = BIT(bit & 0x1f); + + *p = val ? (*p | mask) : (*p & ~mask); +} + +static void mt8196_intc_set_irq_group(uint32_t irq, uint32_t group) +{ + for (int i = 0; i < 16; i++) { + set_group_bit(&INTC.groups[i], irq, i == group); + } +} + +void mt8196_intc_init(void) +{ + struct intc64 zero = { 0, 0 }; + + INTC.enable = zero; + INTC.polarity.lo = 0xffffffff; + INTC.polarity.hi = 0xffffffff; + INTC.wake_enable = zero; + INTC.stage1_enable = zero; + for (int i = 0; i < ARRAY_SIZE(INTC.groups); i++) { + INTC.groups[i] = zero; + } + + /* Now wire up known interrupts for existing drivers to their + * legacy settings + */ + mt8196_intc_set_irq_group(6, 2); /* mbox0 in group 2 */ + mt8196_intc_set_irq_group(7, 2); /* mbox1 in group 2 */ + mt8196_intc_set_irq_group(8, 1); /* ostimer in group 1 */ +} + /* This is the true boot vector. This device allows for direct * setting of the alternate reset vector, so we let it link wherever * it lands and extract its address in the loader. This represents @@ -209,6 +316,10 @@ void c_boot(void) /* Default console, a driver can override this later */ __stdout_hook_install(arch_printk_char_out); +#ifdef CONFIG_SOC_MT8196 + mt8196_intc_init(); +#endif + void z_prep_c(void); z_prep_c(); }