Skip to content

Commit 0632873

Browse files
andyrosskartben
authored andcommitted
soc/mt8196: Add interrupt routing support
The MT8196 device has a newer interrupt controller that acts like the legacy ones once initialized (see intc_mtk_adsp.c). But it has some (only slightly) more complicated routing control that must be initialized on reset, as the default is "don't deliver any interrupts at all". Previous versions of the device integration worked becuase they relied on a SOF binary to be loaded at boot, but obviously that doesn't work for a Zephyr-based SOF firmware image. Signed-off-by: Andy Ross <[email protected]>
1 parent 6309c1b commit 0632873

File tree

1 file changed

+111
-0
lines changed

1 file changed

+111
-0
lines changed

soc/mediatek/mt8xxx/soc.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,113 @@ extern char _mtk_adsp_dram_end[];
2929
#define LOG_LEN 0x100000
3030
#endif
3131

32+
/* The MT8196 interrupt controller is very simple at runtime, with
33+
* just an enable and status register needed, like its
34+
* predecessors. But it has routing control which resets to "nothing
35+
* enabled", so needs a driver.
36+
*
37+
* There are 64 interrupt inputs to the controller, controlled by
38+
* pairs of words (the "intc64" type below). Each interrupt is
39+
* associated with one[1] of 16 "groups", each of which directs to a
40+
* different Xtensa architectural interrupt. So each Xtensa interrupt
41+
* can be configured to handle any subset of interrupt inputs.
42+
*
43+
* The mapping of groups to Xtensa interrupts is given below. Note
44+
* particularly that the final two groups are NMIs directed to an
45+
* interrupt level higher than EXCM_LEVEL, so cannot be safely used
46+
* for OS code (they'll interrupt spinlocks), but an app might exploit
47+
* them for e.g. debug or watchdog hooks.
48+
*
49+
* GroupNum XtensaIRQ XtensaLevel
50+
* 0-5 0-5 1 (L1 is shared w/exceptions, poor choice)
51+
* 6-7 7-8 1
52+
* 8-10 9-11 2
53+
* 11-13 16-18 3
54+
* 14,15 20,21 4 (Unmaskable! Do not use w/Zephyr code!)
55+
*
56+
* Naming of the inputs looks like this, though obviously only a small
57+
* fraction have been validated (or are even useful for an audio DSP):
58+
*
59+
* 0: CCU 20: USB1 40: WDT
60+
* 1: SCP 21: SCPVOW 41: CONNSYS1
61+
* 2: SPM 22: CCIF3_C0 42: CONNSYS3
62+
* 3: PCIE 23: CCIF3_C1 43: CONNSYS4
63+
* 4: INFRA_HANG 24: PWR_CTRL 44: CONNSYS2
64+
* 5: PERI_TIMEOUT 25: DMA_C0 45: IPIC
65+
* 6: MBOX_C0 26: DMA_C1 46: AXI_DMA2
66+
* 7: MBOX_C1 27: AXI_DMA0 47: AXI_DMA3
67+
* 8: TIMER0 28: AXI_DMA1 48: APSRC_DDREN
68+
* 9: TIMER1 29: AUDIO_C0 49: LAT_MON_EMI
69+
* 10: IPC_C0 30: AUDIO_C1 50: LAT_MON_INFRA
70+
* 11: IPC_C1 31: HIFI5_WDT_C0 51: DEVAPC_VIO
71+
* 12: IPC1_RSV 32: HIFI5_WDT_C1 52: AO_INFRA_HANG
72+
* 13: C2C_SW_C0 33: APU_MBOX_C0 53: BUS_TRA_EMI
73+
* 14: C2C_SW_C1 34: APU_MBOX_C1 54: BUS_TRA_INFRA
74+
* 15: UART 35: TIMER2 55: L2SRAM_VIO
75+
* 16: UART_BT 36: PWR_ON_C0_IRQ 56: L2SRAM_SETERR
76+
* 17: LATENCY_MON 37: PWR_ON_C1_IRQ 57: PCIERC_GRP2
77+
* 18: BUS_TRACKER 38: WAKEUP_SRC_C0 58: PCIERC_GRP3
78+
* 19: USB0 39: WAKEUP_SRC_C1 59: IRQ_MAX_CHANNEL
79+
*
80+
* [1] It is legal and works as expected for an interrupt to be part
81+
* of more than one group (more than one interrupt fires to handle
82+
* it), though I don't understand why an application would want to
83+
* do that.
84+
*/
85+
86+
struct intc64 { uint32_t lo, hi; };
87+
88+
struct intc_8196 {
89+
struct intc64 input; /* Raw (?) input signal, normally high */
90+
struct intc64 status; /* Latched input, inverted (active == 1) */
91+
struct intc64 enable; /* Interrupt enable */
92+
struct intc64 polarity; /* 1 == active low */
93+
struct intc64 wake_enable;
94+
struct intc64 _unused;
95+
struct intc64 stage1_enable;
96+
struct intc64 sw_trigger;
97+
struct intc64 groups[16]; /* set bit == "member of group" */
98+
struct intc64 group_status[16]; /* status, but masked by group */
99+
};
100+
101+
#define INTC (*(volatile struct intc_8196 *)0x1a014000)
102+
103+
static void set_group_bit(volatile struct intc64 *g, uint32_t bit, bool val)
104+
{
105+
volatile uint32_t *p = bit < 32 ? &g->lo : &g->hi;
106+
volatile uint32_t mask = BIT(bit & 0x1f);
107+
108+
*p = val ? (*p | mask) : (*p & ~mask);
109+
}
110+
111+
static void mt8196_intc_set_irq_group(uint32_t irq, uint32_t group)
112+
{
113+
for (int i = 0; i < 16; i++) {
114+
set_group_bit(&INTC.groups[i], irq, i == group);
115+
}
116+
}
117+
118+
void mt8196_intc_init(void)
119+
{
120+
struct intc64 zero = { 0, 0 };
121+
122+
INTC.enable = zero;
123+
INTC.polarity.lo = 0xffffffff;
124+
INTC.polarity.hi = 0xffffffff;
125+
INTC.wake_enable = zero;
126+
INTC.stage1_enable = zero;
127+
for (int i = 0; i < ARRAY_SIZE(INTC.groups); i++) {
128+
INTC.groups[i] = zero;
129+
}
130+
131+
/* Now wire up known interrupts for existing drivers to their
132+
* legacy settings
133+
*/
134+
mt8196_intc_set_irq_group(6, 2); /* mbox0 in group 2 */
135+
mt8196_intc_set_irq_group(7, 2); /* mbox1 in group 2 */
136+
mt8196_intc_set_irq_group(8, 1); /* ostimer in group 1 */
137+
}
138+
32139
/* This is the true boot vector. This device allows for direct
33140
* setting of the alternate reset vector, so we let it link wherever
34141
* it lands and extract its address in the loader. This represents
@@ -209,6 +316,10 @@ void c_boot(void)
209316
/* Default console, a driver can override this later */
210317
__stdout_hook_install(arch_printk_char_out);
211318

319+
#ifdef CONFIG_SOC_MT8196
320+
mt8196_intc_init();
321+
#endif
322+
212323
void z_prep_c(void);
213324
z_prep_c();
214325
}

0 commit comments

Comments
 (0)