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 <andyross@google.com>
This commit is contained in:
parent
6309c1b1a5
commit
0632873fb3
1 changed files with 111 additions and 0 deletions
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue