From df8395e3d8222c7ddc4eeb3d1eb9cf7490f803aa Mon Sep 17 00:00:00 2001 From: Andy Ross Date: Sat, 5 Aug 2023 06:15:05 -0700 Subject: [PATCH] soc: boards: Add MediaTek MT8195 Audio DSP This is a soc/board integration for the MediaTek Audio DSP device on the MT8195 SOC, along with a Zephyr mtk_adsp soc integration that will work to support similar 8186 and 8188 device shortly. A python loader (similar to cavsload.py) is included that will run in developer mode on current chromebooks (an HP x360 13b-ca000 was tested) with an unmodified kernel. Signed-off-by: Andy Ross --- boards/mediatek/mt8195_adsp/Kconfig.defconfig | 9 + .../mediatek/mt8195_adsp/Kconfig.mt8195_adsp | 5 + boards/mediatek/mt8195_adsp/board.yml | 5 + boards/mediatek/mt8195_adsp/mt8195_adsp.dts | 95 ++++++++++ .../mt8195_adsp/mt8195_adsp_defconfig | 5 + .../clock/mediatek,mt8195_cpuclk.yaml | 16 ++ soc/mediatek/mtk_adsp/CMakeLists.txt | 12 ++ soc/mediatek/mtk_adsp/Kconfig | 17 ++ soc/mediatek/mtk_adsp/Kconfig.defconfig | 75 ++++++++ soc/mediatek/mtk_adsp/Kconfig.soc | 8 + soc/mediatek/mtk_adsp/cpuclk.c | 163 ++++++++++++++++ soc/mediatek/mtk_adsp/gen_img.py | 71 +++++++ soc/mediatek/mtk_adsp/irq.c | 58 ++++++ soc/mediatek/mtk_adsp/mbox.c | 130 +++++++++++++ .../mtk_adsp/mt8195_adsp/Kconfig.defconfig | 19 ++ soc/mediatek/mtk_adsp/mt8195_adsp/linker.ld | 139 ++++++++++++++ soc/mediatek/mtk_adsp/mt8195_adsp/soc.h | 10 + soc/mediatek/mtk_adsp/mtk_adsp_load.py | 141 ++++++++++++++ soc/mediatek/mtk_adsp/soc.c | 175 ++++++++++++++++++ soc/mediatek/mtk_adsp/soc.h | 36 ++++ soc/mediatek/mtk_adsp/soc.yml | 6 + 21 files changed, 1195 insertions(+) create mode 100644 boards/mediatek/mt8195_adsp/Kconfig.defconfig create mode 100644 boards/mediatek/mt8195_adsp/Kconfig.mt8195_adsp create mode 100644 boards/mediatek/mt8195_adsp/board.yml create mode 100644 boards/mediatek/mt8195_adsp/mt8195_adsp.dts create mode 100644 boards/mediatek/mt8195_adsp/mt8195_adsp_defconfig create mode 100644 dts/bindings/clock/mediatek,mt8195_cpuclk.yaml create mode 100644 soc/mediatek/mtk_adsp/CMakeLists.txt create mode 100644 soc/mediatek/mtk_adsp/Kconfig create mode 100644 soc/mediatek/mtk_adsp/Kconfig.defconfig create mode 100644 soc/mediatek/mtk_adsp/Kconfig.soc create mode 100644 soc/mediatek/mtk_adsp/cpuclk.c create mode 100755 soc/mediatek/mtk_adsp/gen_img.py create mode 100644 soc/mediatek/mtk_adsp/irq.c create mode 100644 soc/mediatek/mtk_adsp/mbox.c create mode 100644 soc/mediatek/mtk_adsp/mt8195_adsp/Kconfig.defconfig create mode 100644 soc/mediatek/mtk_adsp/mt8195_adsp/linker.ld create mode 100644 soc/mediatek/mtk_adsp/mt8195_adsp/soc.h create mode 100755 soc/mediatek/mtk_adsp/mtk_adsp_load.py create mode 100644 soc/mediatek/mtk_adsp/soc.c create mode 100644 soc/mediatek/mtk_adsp/soc.h create mode 100644 soc/mediatek/mtk_adsp/soc.yml diff --git a/boards/mediatek/mt8195_adsp/Kconfig.defconfig b/boards/mediatek/mt8195_adsp/Kconfig.defconfig new file mode 100644 index 00000000000..31f557670b1 --- /dev/null +++ b/boards/mediatek/mt8195_adsp/Kconfig.defconfig @@ -0,0 +1,9 @@ +# Copyright 2023 The ChromiumOS Authors +# SPDX-License-Identifier: Apache-2.0 + +if BOARD_MT8195_ADSP + +config BOARD + default "mt8195_adsp" + +endif # BOARD_MT8195_ADSP diff --git a/boards/mediatek/mt8195_adsp/Kconfig.mt8195_adsp b/boards/mediatek/mt8195_adsp/Kconfig.mt8195_adsp new file mode 100644 index 00000000000..2cffa50962e --- /dev/null +++ b/boards/mediatek/mt8195_adsp/Kconfig.mt8195_adsp @@ -0,0 +1,5 @@ +# Copyright 2023 The ChromiumOS Authors +# SPDX-License-Identifier: Apache-2.0 + +config BOARD_MT8195_ADSP + bool "Mediatek MT8195 Audio DSP" diff --git a/boards/mediatek/mt8195_adsp/board.yml b/boards/mediatek/mt8195_adsp/board.yml new file mode 100644 index 00000000000..22c31deb1a0 --- /dev/null +++ b/boards/mediatek/mt8195_adsp/board.yml @@ -0,0 +1,5 @@ +boards: + - name: mt8195_adsp + vendor: mediatek + socs: + - name: mt8195_adsp diff --git a/boards/mediatek/mt8195_adsp/mt8195_adsp.dts b/boards/mediatek/mt8195_adsp/mt8195_adsp.dts new file mode 100644 index 00000000000..c26e2dd0f40 --- /dev/null +++ b/boards/mediatek/mt8195_adsp/mt8195_adsp.dts @@ -0,0 +1,95 @@ +/* Copyright 2023 The ChromiumOS Authors + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +/dts-v1/; +/ { + +#address-cells = <1>; +#size-cells = <1>; + +sram0: memory@40000000 { + device_type = "memory"; + compatible = "mmio-sram"; + reg = <0x40000000 DT_SIZE_K(256)>; +}; + +dram0: memory@60000000 { + device_type = "memory"; + compatible = "mmio-sram"; + reg = <0x60000000 DT_SIZE_M(17)>; +}; + +soc { + #address-cells = <1>; + #size-cells = <1>; + + cpuclk: cpuclk@10000000 { + compatible = "mediatek,mt8195_cpuclk"; + reg = <0x10000000 380>; + cg_reg = <0x10720180>; + pll_ctrl_reg = <0x1000c7e0>; + freqs_mhz = <26 370 540 720>; + }; + + core_intc: core_intc@0 { + compatible = "cdns,xtensa-core-intc"; + reg = <0 4>; + interrupt-controller; + #interrupt-cells = <3>; + }; + + intc1: intc@10680130 { + compatible = "mediatek,adsp_intc"; + interrupt-controller; + #interrupt-cells = <3>; + reg = <0x10680130 4>; + status-reg = <0x10680150>; + interrupts = <1 0 0>; + mask = <0x3ffffff0>; + interrupt-parent = <&core_intc>; + }; + + intc23: intc@108030f4 { + compatible = "mediatek,adsp_intc"; + interrupt-controller; + #interrupt-cells = <3>; + reg = <0x108030f4 4>; + status-reg = <0x108030fc>; + interrupts = <23 0 0>; + mask = <0xffff>; + interrupt-parent = <&core_intc>; + }; + + ostimer64: ostimer64@1080d080 { + compatible = "mediatek,ostimer64"; + reg = <0x1080d080 28>; + }; + + ostimer0: ostimer@1080d000 { + compatible = "mediatek,ostimer"; + reg = <0x1080d000 16>; + interrupt-parent = <&intc23>; + interrupts = <11 0 0>; + }; + + mbox0: mbox@10816000 { + compatible = "mediatek,mbox"; + reg = <0x10816000 56>; + interrupt-parent = <&intc23>; + interrupts = <0 0 0>; + }; + + mbox1: mbox@10817000 { + compatible = "mediatek,mbox"; + reg = <0x10817000 56>; + interrupt-parent = <&intc23>; + interrupts = <1 0 0>; + }; +}; /* soc */ + +chosen { }; +aliases { }; + +}; diff --git a/boards/mediatek/mt8195_adsp/mt8195_adsp_defconfig b/boards/mediatek/mt8195_adsp/mt8195_adsp_defconfig new file mode 100644 index 00000000000..15579f71ae8 --- /dev/null +++ b/boards/mediatek/mt8195_adsp/mt8195_adsp_defconfig @@ -0,0 +1,5 @@ +# Copyright 2023 The ChromiumOS Authors +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_SOC_SERIES_MT8195_ADSP=y +CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=13000000 diff --git a/dts/bindings/clock/mediatek,mt8195_cpuclk.yaml b/dts/bindings/clock/mediatek,mt8195_cpuclk.yaml new file mode 100644 index 00000000000..ea85d99580a --- /dev/null +++ b/dts/bindings/clock/mediatek,mt8195_cpuclk.yaml @@ -0,0 +1,16 @@ +# Copyright 2023 The ChromiumOS Authors +# SPDX-License-Identifier: Apache-2.0 + +description: MediaTek Audio DSP CPU Frequency Control +compatible: "mediatek,mt8195_cpuclk" +properties: + freqs_mhz: + type: array + description: Available frequencies in ascending order + required: true + reg: + type: array + cg_reg: + type: int + pll_ctrl_reg: + type: int diff --git a/soc/mediatek/mtk_adsp/CMakeLists.txt b/soc/mediatek/mtk_adsp/CMakeLists.txt new file mode 100644 index 00000000000..fb2d3b5933d --- /dev/null +++ b/soc/mediatek/mtk_adsp/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright 2023 The ChromiumOS Authors +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library_sources(soc.c irq.c cpuclk.c mbox.c) + +set(SOC_LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/${CONFIG_SOC}/linker.ld CACHE INTERNAL "") + +add_custom_target(dsp_img ALL + DEPENDS ${ZEPHYR_FINAL_EXECUTABLE} + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/gen_img.py + ${ZEPHYR_BINARY_DIR}/${KERNEL_ELF_NAME} + ${CMAKE_BINARY_DIR}/zephyr/zephyr.img) diff --git a/soc/mediatek/mtk_adsp/Kconfig b/soc/mediatek/mtk_adsp/Kconfig new file mode 100644 index 00000000000..55807f38da8 --- /dev/null +++ b/soc/mediatek/mtk_adsp/Kconfig @@ -0,0 +1,17 @@ +# Copyright 2024 The ChromiumOS Authors +# SPDX-License-Identifier: Apache-2.0 + +config SOC_FAMILY_MTK_ADSP + bool "Mediatek MT8xxx Series Audio DSPs" + select XTENSA + select XTENSA_GEN_HANDLERS + +config SOC_SERIES_MT8195_ADSP + bool "Mediatek 8195 Audio DSP" + select SOC_FAMILY_MTK_ADSP + help + Mediatek MT8195 Audio DSP + +config SOC_MT8195_ADSP + bool + select SOC_SERIES_MT8195_ADSP diff --git a/soc/mediatek/mtk_adsp/Kconfig.defconfig b/soc/mediatek/mtk_adsp/Kconfig.defconfig new file mode 100644 index 00000000000..84452a9be8d --- /dev/null +++ b/soc/mediatek/mtk_adsp/Kconfig.defconfig @@ -0,0 +1,75 @@ +# Copyright 2023 The ChromiumOS Authors +# SPDX-License-Identifier: Apache-2.0 + +orsource "*/Kconfig.defconfig" + +if SOC_FAMILY_MTK_ADSP + +config SOC_FAMILY + default "mtk_adsp" + +config INTC_MTK_ADSP + default y + +config XTENSA_SMALL_VECTOR_TABLE_ENTRY + default y + +config XTENSA_USE_CORE_CRT1 + default n + +config MULTI_LEVEL_INTERRUPTS + default y +config 2ND_LEVEL_INTERRUPTS + default y +config MAX_IRQ_PER_AGGREGATOR + default 32 +config 2ND_LVL_ISR_TBL_OFFSET + default 32 + +config MTK_ADSP_TIMER + default y +config XTENSA_TIMER + default n + +config MAIN_STACK_SIZE + default 2048 + +# This platform has a single big DRAM region where most linkage +# happens. The libc heap normally wants to steal all of it, when in +# fact SOF has its own heap. Just leave a little for stray malloc() +# calls to find. +config COMMON_LIBC_MALLOC_ARENA_SIZE + default 32768 + +# Don't build the HAL if the toolchain already includes it. Note that +# this is done in the SOC layer historically, really this belongs in +# arch/xtensa or the toolchain integration. +# +config XTENSA_HAL + default n if "$(ZEPHYR_TOOLCHAIN_VARIANT)" = "xcc" + default n if "$(ZEPHYR_TOOLCHAIN_VARIANT)" = "xt-clang" + default y + +config SOC_TOOLCHAIN_NAME + default "mtk_mt8195_adsp" + +config XTENSA_RESET_VECTOR + default n + +# This single-core device doesn't have S32C1I and so has no built-in +# atomics. Note we must disable _ARCH explicitly because +# CONFIG_XTENSA turns it on (due to an xcc lack of gcc builtins?) +# +config ATOMIC_OPERATIONS_C + default y +config ATOMIC_OPERATIONS_ARCH + default n + +config GEN_ISR_TABLES + default y +config GEN_SW_ISR_TABLE + default y +config GEN_IRQ_VECTOR_TABLE + default n + +endif # SOC_FAMILY_MTK_ADSP diff --git a/soc/mediatek/mtk_adsp/Kconfig.soc b/soc/mediatek/mtk_adsp/Kconfig.soc new file mode 100644 index 00000000000..8d0a6cd0ac2 --- /dev/null +++ b/soc/mediatek/mtk_adsp/Kconfig.soc @@ -0,0 +1,8 @@ +# Copyright 2024 The ChromiumOS Authors +# SPDX-License-Identifier: Apache-2.0 + +config SOC_MT8195_ADSP + bool + +config SOC + default "mt8195_adsp" if SOC_MT8195_ADSP diff --git a/soc/mediatek/mtk_adsp/cpuclk.c b/soc/mediatek/mtk_adsp/cpuclk.c new file mode 100644 index 00000000000..1c21358b7cc --- /dev/null +++ b/soc/mediatek/mtk_adsp/cpuclk.c @@ -0,0 +1,163 @@ +/* Copyright 2023 The ChromiumOS Authors + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +/* Be warned: the interface here is poorly understood. I did the best + * I could to transcribe it (with a little clarification and + * optimization) from the SOF mt8195 source, but without docs this + * needs to be treated with great care. + * + * Notes: + * * power-on default is 26Mhz, confirmed with a hacked SOF that + * loads but stubs out the clk code. + * * The original driver has a 13Mhz mode too, but it doesn't work (it + * hits all the same code and data paths as 26MHz and acts as a + * duplicate. + * * The magic numbers in the pll_con2 field are from the original + * source. No docs on the PLL register interface are provided. + */ + +struct mtk_pll_control { + uint32_t con0; + uint32_t con1; + uint32_t con2; + uint32_t con3; + uint32_t con4; +}; + +#define MTK_PLL_CTRL (*(volatile struct mtk_pll_control *) \ + DT_PROP(DT_NODELABEL(cpuclk), pll_ctrl_reg)) + +#define MTK_PLL_CON0_BASE_EN BIT(0) +#define MTK_PLL_CON0_EN BIT(9) +#define MTK_PLL_CON4_ISO_EN BIT(1) +#define MTK_PLL_CON4_PWR_ON BIT(0) + +struct mtk_clk_gen { + uint32_t mode; + uint32_t update[4]; + uint32_t _unused[3]; + struct { + uint32_t cur; + uint32_t set; + uint32_t clr; + } clk_cfg[29]; +}; + +#define MTK_CLK_GEN (*(volatile struct mtk_clk_gen *) \ + DT_REG_ADDR(DT_NODELABEL(cpuclk))) + +#define MTK_CLK22_SEL_PLL 8 +#define MTK_CLK22_SEL_26M 0 + +#define MTK_CLK28_SEL_PLL 7 +#define MTK_CLK28_SEL_26M 0 + +#define MTK_CK_CG (*(volatile uint32_t *) \ + DT_PROP(DT_NODELABEL(cpuclk), cg_reg)) + +#define MTK_CK_CG_SW 1 + +const struct { uint16_t mhz; bool pll; uint32_t pll_con2; } freqs[] = { + { 26, false, 0 }, + { 370, true, 0x831c7628 }, + { 540, true, 0x8214c4ed }, + { 720, true, 0x821bb13c }, +}; + +static int cur_idx; + +/* Can't use CPU-counted loops when changing CPU speed, and don't have + * an OS timer driver yet. Use the 13 MHz timer hardware directly. + * (The ostimer is always running AFAICT, there's not even an + * interface for a disable bit defined) + */ +#define TIMER (((volatile uint32_t *)DT_REG_ADDR(DT_NODELABEL(ostimer64)))[3]) +static inline void delay_us(int us) +{ + uint32_t t0 = TIMER; + + while (TIMER - t0 < (us * 13)) { + } +} + +static void set_pll_power(bool on) +{ + if (on) { + MTK_CK_CG &= ~MTK_CK_CG_SW; + MTK_PLL_CTRL.con4 |= MTK_PLL_CON4_PWR_ON; + delay_us(1); + MTK_PLL_CTRL.con4 &= ~MTK_PLL_CON4_ISO_EN; + delay_us(1); + MTK_PLL_CTRL.con0 |= MTK_PLL_CON0_EN; + delay_us(20); + } else { + MTK_PLL_CTRL.con0 &= ~MTK_PLL_CON0_EN; + delay_us(1); + MTK_PLL_CTRL.con4 |= MTK_PLL_CON4_ISO_EN; + delay_us(1); + MTK_PLL_CTRL.con4 &= ~MTK_PLL_CON4_PWR_ON; + } +} + +/* Oddball utility. There is a giant array of clocks (of which SOF + * only touches two), each with "clear" and "set" registers which are + * used to set 4-bit fields at a specific offset. After that, a + * particular bit in one of the "update" registers must be written, + * presumably to latch the input. + */ +static void setclk(int clk, int shift, int updreg, int ubit, int val) +{ + MTK_CLK_GEN.clk_cfg[clk].clr = (0xf << shift); + if (val) { + MTK_CLK_GEN.clk_cfg[clk].set = (val << shift); + } + MTK_CLK_GEN.update[updreg] = BIT(ubit); +} + +#define SETCLK22(val) setclk(22, 0, 2, 24, (val)) +#define SETCLK28(val) setclk(28, 16, 3, 18, (val)) + +void mtk_adsp_set_cpu_freq(int mhz) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(freqs); idx++) { + if (freqs[idx].mhz == mhz) { + break; + } + } + + if (idx == cur_idx || freqs[idx].mhz != mhz) { + return; + } + + if (freqs[idx].pll) { + /* Switch to PLL from 26Mhz */ + set_pll_power(true); + SETCLK22(MTK_CLK22_SEL_PLL); + SETCLK28(MTK_CLK28_SEL_PLL); + MTK_PLL_CTRL.con2 = freqs[idx].pll_con2; + } else { + /* Switch to 26Mhz from PLL */ + SETCLK28(MTK_CLK28_SEL_26M); + SETCLK22(MTK_CLK22_SEL_26M); + set_pll_power(false); + } + + cur_idx = idx; +} + +/* The CPU clock is not affected (!) by device reset, so we don't know + * the speed we're at (on MT8195, hardware powers up at 26 Mhz, but + * production SOF firmware sets it to 720 at load time and leaves it + * there). Set the lowest, then the highest speed unconditionally to + * force the transition. + */ +void mtk_adsp_cpu_freq_init(void) +{ + mtk_adsp_set_cpu_freq(freqs[0].mhz); + mtk_adsp_set_cpu_freq(freqs[ARRAY_SIZE(freqs) - 1].mhz); +} diff --git a/soc/mediatek/mtk_adsp/gen_img.py b/soc/mediatek/mtk_adsp/gen_img.py new file mode 100755 index 00000000000..5d14f5e9d13 --- /dev/null +++ b/soc/mediatek/mtk_adsp/gen_img.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# Copyright 2023 The ChromiumOS Authors +# SPDX-License-Identifier: Apache-2.0 +import sys +import struct +import elftools.elf.elffile +import elftools.elf.sections + +# Converts a zephyr.elf file into an extremely simple "image format" +# for device loading. Really we should just load the ELF file +# directly, but the python ChromeOS test image lacks elftools. Longer +# term we should probably just use rimage, but that's significantly +# harder to parse. +# +# Format: +# +# 1. Three LE 32 bit words: MagicNumber, SRAM len, BootAddress +# 2. Two byte arrays: SRAM (length specified), and DRAM (remainder of file) +# +# No padding or uninterpreted bytes. + +FILE_MAGIC = 0xe463be95 + +SRAM_START = 0x40000000 +SRAM_END = 0x40040000 +DRAM_START = 0x60000000 +DRAM_END = 0x61100000 + +elf_file = sys.argv[1] +out_file = sys.argv[2] + +ef = elftools.elf.elffile.ELFFile(open(elf_file, "rb")) + +sram = bytearray() +dram = bytearray() + +for seg in ef.iter_segments(): + h = seg.header + if h.p_type == "PT_LOAD": + if h.p_paddr in range(SRAM_START, SRAM_END): + buf = sram + off = h.p_paddr - SRAM_START + elif h.p_paddr in range(DRAM_START, DRAM_END): + buf = dram + off = h.p_paddr - DRAM_START + else: + print(f"Invalid PT_LOAD address {h.p_paddr:x}") + sys.exit(1) + + dat = seg.data() + end = off + len(dat) + if end > len(buf): + buf.extend(b'\x00' * (end - len(buf))) + + for i in range(len(dat)): + buf[i + off] = dat[i] + +for sec in ef.iter_sections(): + if isinstance(sec, elftools.elf.sections.SymbolTableSection): + for sym in sec.iter_symbols(): + if sym.name == "mtk_adsp_boot_entry": + boot_vector = sym.entry['st_value'] + +assert len(sram) < SRAM_END - SRAM_START +assert len(dram) < DRAM_END - DRAM_START +assert (SRAM_START <= boot_vector < SRAM_END) or (DRAM_START <= boot_vector < DRAM_END) + +of = open(out_file, "wb") +of.write(struct.pack(" +#include +#include + +bool intc_mtk_adsp_get_enable(const struct device *dev, int irq); +void intc_mtk_adsp_set_enable(const struct device *dev, int irq, bool val); + +/* Sort of annoying: assumes there are exactly two controller devices + * and that their instance IDs (i.e. the order in which they appear in + * the .dts file) match their order in the _sw_isr_table[]. A better + * scheme would be able to enumerate the tree at runtime. + */ +static const struct device *irq_dev(unsigned int *irq_inout) +{ + /* Controller 0 is on Xtensa vector 1, controller 1 on vector 23. */ + if ((*irq_inout & 0xff) == 1) { + *irq_inout >>= 8; + return DEVICE_DT_GET(DT_INST(0, mediatek_adsp_intc)); + } + __ASSERT_NO_MSG((*irq_inout & 0xff) == 23); + *irq_inout = (*irq_inout >> 8) - 1; + return DEVICE_DT_GET(DT_INST(1, mediatek_adsp_intc)); +} + +void z_soc_irq_enable(unsigned int irq) +{ + /* First 32 IRQs are the Xtensa architectural vectors, */ + if (irq < 32) { + xtensa_irq_enable(irq); + } else { + const struct device *dev = irq_dev(&irq); + + intc_mtk_adsp_set_enable(dev, irq, true); + } +} + +void z_soc_irq_disable(unsigned int irq) +{ + if (irq < 32) { + xtensa_irq_disable(irq); + } else { + const struct device *dev = irq_dev(&irq); + + intc_mtk_adsp_set_enable(dev, irq, false); + } +} + +int z_soc_irq_is_enabled(unsigned int irq) +{ + if (irq < 32) { + return xtensa_irq_is_enabled(irq); + } + return intc_mtk_adsp_get_enable(irq_dev(&irq), irq); +} diff --git a/soc/mediatek/mtk_adsp/mbox.c b/soc/mediatek/mtk_adsp/mbox.c new file mode 100644 index 00000000000..e2906b8fa29 --- /dev/null +++ b/soc/mediatek/mtk_adsp/mbox.c @@ -0,0 +1,130 @@ +/* Copyright 2023 The ChromiumOS Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define DT_DRV_COMPAT mediatek_mbox + +/* Mailbox: a simple interrupt source. Each direction has a 5-bit + * command register and will latch an interrupt if any of the bits are + * non-zero. The interrupt bits get cleared/acknowledged by writing + * ones to the corresponding bits of "cmd_clear". There are five + * scratch registers for use as message data in each direction. + * + * The same device is mapped at the same address by the host and DSP, + * and the naming is from the perspective of the DSP: the "in" + * registers control interrupts on the DSP, the "out" registers are + * for transmitting data to the host. + * + * There is an array of the devices. Linux's device-tree defines two. + * SOF uses those for IPC, but also implements platform_trace_point() + * using the third (no linux driver though?). The upstream headers + * list interrupts for FIVE, and indeed those all seem to be present + * and working. + * + * In practice: The first device (mbox0) is for IPC commands in both + * directions. The cmd register is written with a 1 ("IPI_OP_REQ") + * and the command is placed in shared DRAM. The message registers + * are ignored. The second device (mbox1) is for responses to IPC + * commands, writing a 2 (IPI_OP_RSP) to the command register. (Yes, + * this is redundant, and the actual value is ignored by the ISRs on + * both sides). + */ + +struct mtk_mbox { + uint32_t in_cmd; + uint32_t in_cmd_clr; + uint32_t in_msg[5]; + uint32_t out_cmd; + uint32_t out_cmd_clr; + uint32_t out_msg[5]; +}; + +struct mbox_cfg { + volatile struct mtk_mbox *mbox; + uint32_t irq; +}; + +struct mbox_data { + mtk_adsp_mbox_handler_t handlers[MTK_ADSP_MBOX_CHANNELS]; + void *handler_arg[MTK_ADSP_MBOX_CHANNELS]; +}; + +void mtk_adsp_mbox_set_handler(const struct device *mbox, uint32_t chan, + mtk_adsp_mbox_handler_t handler, void *arg) +{ + struct mbox_data *data = ((struct device *)mbox)->data; + + if (chan < MTK_ADSP_MBOX_CHANNELS) { + data->handlers[chan] = handler; + data->handler_arg[chan] = arg; + } +} + +void mtk_adsp_mbox_set_msg(const struct device *mbox, uint32_t idx, uint32_t val) +{ + const struct mbox_cfg *cfg = ((struct device *)mbox)->config; + + if (idx < MTK_ADSP_MBOX_MSG_WORDS) { + cfg->mbox->out_msg[idx] = val; + } +} + +uint32_t mtk_adsp_mbox_get_msg(const struct device *mbox, uint32_t idx) +{ + const struct mbox_cfg *cfg = ((struct device *)mbox)->config; + + if (idx < MTK_ADSP_MBOX_MSG_WORDS) { + return cfg->mbox->in_msg[idx]; + } + return 0; +} + +void mtk_adsp_mbox_signal(const struct device *mbox, uint32_t chan) +{ + const struct mbox_cfg *cfg = ((struct device *)mbox)->config; + + if (chan < MTK_ADSP_MBOX_CHANNELS) { + cfg->mbox->out_cmd |= BIT(chan); + } +} + +static void mbox_isr(const void *arg) +{ + const struct mbox_cfg *cfg = ((struct device *)arg)->config; + struct mbox_data *data = ((struct device *)arg)->data; + + for (int i = 0; i < MTK_ADSP_MBOX_CHANNELS; i++) { + if (cfg->mbox->in_cmd & BIT(i)) { + if (data->handlers[i] != NULL) { + data->handlers[i](arg, data->handler_arg[i]); + } + } + } + + cfg->mbox->in_cmd_clr = cfg->mbox->in_cmd; /* ACK */ +} + +#define DEF_IRQ(N) \ + IRQ_CONNECT(DT_INST_IRQN(N), 0, mbox_isr, DEVICE_DT_INST_GET(N), 0); + +static int mbox_init(void) +{ + DT_INST_FOREACH_STATUS_OKAY(DEF_IRQ); + return 0; +} + +SYS_INIT(mbox_init, POST_KERNEL, 0); + +#define DEF_DEV(N) \ + static struct mbox_data dev_data##N; \ + static const struct mbox_cfg dev_cfg##N = \ + { .irq = DT_INST_IRQN(N), .mbox = (void *)DT_INST_REG_ADDR(N), }; \ + DEVICE_DT_INST_DEFINE(N, NULL, NULL, &dev_data##N, &dev_cfg##N, \ + POST_KERNEL, 0, NULL); + +DT_INST_FOREACH_STATUS_OKAY(DEF_DEV) diff --git a/soc/mediatek/mtk_adsp/mt8195_adsp/Kconfig.defconfig b/soc/mediatek/mtk_adsp/mt8195_adsp/Kconfig.defconfig new file mode 100644 index 00000000000..6d834a20fb8 --- /dev/null +++ b/soc/mediatek/mtk_adsp/mt8195_adsp/Kconfig.defconfig @@ -0,0 +1,19 @@ +# Copyright 2023 The ChromiumOS Authors +# SPDX-License-Identifier: Apache-2.0 + +if SOC_SERIES_MT8195_ADSP + +config SOC + default "mt8195_adsp" + +config SOC_SERIES + default "mt8195_adsp" + +config NUM_2ND_LEVEL_AGGREGATORS + default 2 +config 2ND_LVL_INTR_00_OFFSET + default 1 +config 2ND_LVL_INTR_01_OFFSET + default 23 + +endif # SOC_SERIES_MT8195_ADSP diff --git a/soc/mediatek/mtk_adsp/mt8195_adsp/linker.ld b/soc/mediatek/mtk_adsp/mt8195_adsp/linker.ld new file mode 100644 index 00000000000..37e11375c3b --- /dev/null +++ b/soc/mediatek/mtk_adsp/mt8195_adsp/linker.ld @@ -0,0 +1,139 @@ +/* Copyright 2023 The ChromiumOS Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define SRAM_START DT_REG_ADDR(DT_NODELABEL(sram0)) +#define SRAM_SIZE DT_REG_SIZE(DT_NODELABEL(sram0)) +#define DRAM_START DT_REG_ADDR(DT_NODELABEL(dram0)) +#define DRAM_SIZE DT_REG_SIZE(DT_NODELABEL(dram0)) + +MEMORY { + sram (rwx) : ORIGIN = SRAM_START, LENGTH = SRAM_SIZE + dram (rwx) : ORIGIN = DRAM_START, LENGTH = DRAM_SIZE + + IDT_LIST (rwx) : ORIGIN = 0xfff00000, LENGTH = 0x00100000 /* see below */ +} + +/* Included files want this API defined */ +#define RAMABLE_REGION dram +#define ROMABLE_REGION dram + +ENTRY(mtk_adsp_boot_entry) + +SECTIONS { + +#include + > sram + + .iram : { + *(.iram0.*) + *(.literal.iram .iram.*) + } > sram + + _mtk_adsp_sram_end = .; + + .text : { + __text_region_start = .; + KEEP(*(.literal.init .init)) + *(.literal.init.* .init.*) + *(.literal .text .literal.* .text.*) + *(.gnu.linkonce.literal.* .gnu.linkone.t.*) + *(.fini.literal .fini .fini.literal.* .fini.*) + __text_region_end = .; + } > dram + + .rodata : { + __rodata_region_start = .; + *(.rodata) + *(.rodata.*) + *(.gnu.linkonce.r.*) + *(.rodata1) + +#include + + *(.gnu.linkonce.e.*) + *(.gnu.version_r) + KEEP (*(.eh_frame)) + *(.gnu.linkonce.h.*) + } > dram + +#include + + __rodata_region_end = .; + + . = ALIGN(4096); /* Switching MPU permissions, align by 4k */ + + _image_ram_start = .; + + .data : { + __data_start = .; + *(.data .data.*) + _trace_ctx_start = ABSOLUTE(.); + *(.trace_ctx) + _trace_ctx_end = ABSOLUTE(.); + __data_end = .; + } > dram + +#include + + .bss (NOLOAD) : + { + _bss_start = .; + *(.bss .bss.*) + *(.gnu.linkonce.b.*) + *(COMMON) + _bss_end = .; + } > dram + +#include +#include + + . = ALIGN(4096); + _end = .; + _mtk_adsp_dram_end = .; + + /* Non-runtime-loaded sections below */ + +#include + + /DISCARD/ : { *(.note.GNU-stack) } + + .xtensa.info 0 : { *(.xtensa.info) } + .xt.insn 0 : { + KEEP (*(.xt.insn)) + KEEP (*(.gnu.linkonce.x.*)) + } + .xt.prop 0 : { + KEEP (*(.xt.prop)) + KEEP (*(.xt.prop.*)) + KEEP (*(.gnu.linkonce.prop.*)) + } + .xt.lit 0 : { + KEEP (*(.xt.lit)) + KEEP (*(.xt.lit.*)) + KEEP (*(.gnu.linkonce.p.*)) + } + .xt.profile_range 0 : { + KEEP (*(.xt.profile_range)) + KEEP (*(.gnu.linkonce.profile_range.*)) + } + .xt.profile_ranges 0 : { + KEEP (*(.xt.profile_ranges)) + KEEP (*(.gnu.linkonce.xt.profile_ranges.*)) + } + .xt.profile_files 0 : { + KEEP (*(.xt.profile_files)) + KEEP (*(.gnu.linkonce.xt.profile_files.*)) + } + +/* Boilerplate. The Xtensa arch uses CONFIG_GEN_ISR_TABLES to + * generate the handler table, but doesn't actually use the (x86 + * specific) stuff that wants to go to a IDT_LIST memory area. We + * just dump it in an unreadable area at the top of memory. + */ +#include + +} /* SECTIONS */ diff --git a/soc/mediatek/mtk_adsp/mt8195_adsp/soc.h b/soc/mediatek/mtk_adsp/mt8195_adsp/soc.h new file mode 100644 index 00000000000..fc66e0e6582 --- /dev/null +++ b/soc/mediatek/mtk_adsp/mt8195_adsp/soc.h @@ -0,0 +1,10 @@ +/* Copyright 2023 The ChromiumOS Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SOC_MT8195_ADSP_SOC_H +#define ZEPHYR_SOC_MT8195_ADSP_SOC_H + +#include "../soc.h" + +#endif /* ZEPHYR_SOC_MT8195_ADSP_SOC_H */ diff --git a/soc/mediatek/mtk_adsp/mtk_adsp_load.py b/soc/mediatek/mtk_adsp/mtk_adsp_load.py new file mode 100755 index 00000000000..461b9061e65 --- /dev/null +++ b/soc/mediatek/mtk_adsp/mtk_adsp_load.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# Copyright 2023 The ChromiumOS Authors +# SPDX-License-Identifier: Apache-2.0 +import ctypes +import sys +import mmap +import time +import struct + +# MT8195 audio firmware load/debug gadget + +# Note that the hardware handling here is only partial: in practice +# the audio DSP depends on clock and power well devices whose drivers +# live elsewhere in the kernel. Those aren't duplicated here. Make +# sure the DSP has been started by a working kernel driver first. +# +# See gen_img.py for docs on the image format itself. The way this +# script works is to map the device memory regions and registers via +# /dev/mem and copy the two segments while resetting the DSP. +# +# In the kernel driver, the address/size values come from devicetree. +# But currently the MediaTek architecture is one kernel driver per SOC +# (i.e. the devicetree values in the kenrel source are tied to the +# specific SOC anyway), so it really doesn't matter and we hard-code +# the addresses for simplicity. +# +# (For future reference: in /proc/device-tree on current ChromeOS +# kernels, the host registers are a "cfg" platform resource on the +# "adsp@10803000" node. The sram is likewise the "sram" resource on +# that device node, and the two dram areas are "memory-region" +# phandles pointing to "adsp_mem_region" and "adsp_dma_mem_region" +# nodes under "/reserved-memory"). + +FILE_MAGIC = 0xe463be95 + +MAPPINGS = { "regs" : (0x10803000, 0xa000), + "sram" : (0x10840000, 0x40000), + "dram" : (0x60000000, 0x1000000) } + +# Runtime mmap objects for each MAPPINGS entry +maps = {} + +def stop(cfg): + cfg.RESET_SW |= 8 # Set RUNSTALL: halt CPU + cfg.RESET_SW |= 3 # Set low two bits: "BRESET|DRESET" + +def start(cfg, boot_vector): + stop(cfg) + + cfg.RESET_SW |= 0x10 # Enable "alternate reset" boot vector + cfg.ALTRESETVEC = boot_vector + + cfg.RESET_SW &= ~3 # Release reset bits + cfg.RESET_SW &= ~8 # Clear RUNSTALL: go! + +# Temporary logging protocol: watch the 1M null-terminated log +# stream at 0x60700000 -- the top of the linkable region of +# existing SOF firmware, before the heap. Nothing uses this +# currently. Will be replaced by winstream very soon. +def log(): + msg = b'' + dram = maps["dram"] + for i in range(0x700000, 0x800000): + x = dram[i] + if x == 0: + sys.stdout.buffer.write(msg) + sys.stdout.buffer.flush() + msg = b'' + while x == 0: + time.sleep(0.1) + x = dram[i] + msg += x.to_bytes(1, "little") + sys.stdout.buffer.write(msg) + sys.stdout.buffer.flush() + +def le4(bstr): + assert len(bstr) == 4 + return struct.unpack("") + +# (Cribbed from cavstool.py) +class Regs: + def __init__(self, base_addr): + vars(self)["base_addr"] = base_addr + vars(self)["ptrs"] = {} + vars(self)["frozen"] = False + def freeze(self): + vars(self)["frozen"] = True + def __setattr__(self, name, val): + if not self.frozen and name not in self.ptrs: + addr = self.base_addr + val + self.ptrs[name] = ctypes.c_uint32.from_address(addr) + else: + self.ptrs[name].value = val + def __getattr__(self, name): + return self.ptrs[name].value + +if __name__ == "__main__": + main() diff --git a/soc/mediatek/mtk_adsp/soc.c b/soc/mediatek/mtk_adsp/soc.c new file mode 100644 index 00000000000..529b235f5f8 --- /dev/null +++ b/soc/mediatek/mtk_adsp/soc.c @@ -0,0 +1,175 @@ +/* Copyright 2023 The ChromiumOS Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +extern char _mtk_adsp_sram_end[]; + +#define SRAM_START DT_REG_ADDR(DT_NODELABEL(sram0)) +#define SRAM_SIZE DT_REG_SIZE(DT_NODELABEL(sram0)) +#define SRAM_END (SRAM_START + SRAM_SIZE) + +extern char _mtk_adsp_dram_end[]; + +#define DRAM_START DT_REG_ADDR(DT_NODELABEL(dram0)) +#define DRAM_SIZE DT_REG_SIZE(DT_NODELABEL(dram0)) +#define DRAM_END (DRAM_START + DRAM_SIZE) + +/* 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 + * the minimum amount of effort required to successfully call a C + * function (and duplicates a few versions elsewhere in the tree: + * really this should move to the arch layer). + */ +__asm__(".align 4\n\t" + ".global mtk_adsp_boot_entry\n\t" + "mtk_adsp_boot_entry:\n\t" + " movi a0, 0x4002f\n\t" /* WOE|EXCM|INTLVL=15 */ + " wsr a0, PS\n\t" + " movi a0, 0\n\t" + " wsr a0, WINDOWBASE\n\t" + " movi a0, 1\n\t" + " wsr a0, WINDOWSTART\n\t" + " rsync\n\t" + " movi a1, 0x40040000\n\t" + " call4 c_boot\n\t"); + +/* Initial MPU configuration, needed to enable caching */ +static void enable_mpu(void) +{ + /* Note: we set the linked/in-use-by-zephyr regions of both + * SRAM and DRAM cached for performance. The remainder is + * left uncached, as it's likely to be shared with the host + * and/or DMA. This seems like a good default choice pending + * proper MPU integration + */ + static const uint32_t mpu[][2] = { + { 0x00000000, 0x06000 }, /* inaccessible null region */ + { 0x10000000, 0x06f00 }, /* MMIO registers */ + { 0x1d000000, 0x06000 }, /* inaccessible */ + { SRAM_START, 0xf7f00 }, /* cached SRAM */ + { (uint32_t)&_mtk_adsp_sram_end, 0x06f00 }, /* uncached SRAM */ + { SRAM_END, 0x06000 }, /* inaccessible */ + { DRAM_START, 0xf7f00 }, /* cached DRAM */ + { (uint32_t)&_mtk_adsp_dram_end, 0x06f00 }, /* uncached DRAM */ + { DRAM_END, 0x06000 }, /* inaccessible top of mem */ + }; + + /* Must write BACKWARDS FROM THE END to avoid introducing a + * non-monotonic segment at the current instruction fetch. The + * exception triggers even if all the segments involved are + * disabled! + */ + int32_t nseg = ARRAY_SIZE(mpu); + + for (int32_t i = 31; i >= 32 - nseg; i--) { + int32_t mpuidx = i - (32 - nseg); + uint32_t addren = mpu[mpuidx][0] | 1; + uint32_t segprot = (mpu[mpuidx][1]) | i; + + /* If an active pipelined instruction fetch is in the + * same segment, wptlb must be preceded by a memw in + * the same cache line. Jumping to an aligned-by-8 + * address ensures that the following two (3-byte) + * instructions are in the same 8 byte-aligned region. + */ + __asm__ volatile(" j 1f\n" + ".align 8\n" + "1:\n" + " memw\n" + " wptlb %1, %0" + :: "r"(addren), "r"(segprot)); + } +} + +/* Temporary console output, pending integration of a winstream + * backend. This simply appends a null-terminated string to an + * otherwise unused 1M region of shared DRAM (it's a hole in the SOF + * memory map before the DMA memory, so untouched by existing audio + * firmware), making early debugging much easier: it can be read + * directly out of /dev/mem (with e.g. dd | hexdump) and survives + * device resets/panics/etc. But it doesn't handle more than 1M of + * output, there's no way to detect a reset of the stream, and in fact + * it's actually racy with device startup as if you read too early + * you'll see the old run and not the new one. And it's wasteful, + * even if this device has a ton of usably-mapped DRAM + * + * Also note that the storage for the buffer and length value get + * reset by the DRAM clear near the end of c_boot(). If you want to + * use this for extremely early logging you'll need to stub out the + * dram clear and also set buf[0] to 0 manually (as it isn't affected + * by device reset). + */ +int arch_printk_char_out(int c) +{ + char volatile * const buf = (void *)0x60700000; + const size_t max = 0x100000 - 4; + int volatile * const len = (int *)&buf[max]; + + if (*len < max) { + buf[*len + 1] = 0; + buf[(*len)++] = c; + } + return 0; +} + +void c_boot(void) +{ + extern char _bss_start, _bss_end, z_xtensa_vecbase; /* Linker-emitted */ + uint32_t memctl = 0xffffff00; /* enable all caches */ + + /* Clear bss before doing anything else, device memory is + * persistent across resets (!) and we'd like our static + * variables to be actually zero. Do this without using + * memset() out of pedantry (because we don't know which libc is + * in use or whether it requires statics). + */ + for (char *p = &_bss_start; p < &_bss_end; p++) { + *p = 0; + } + + /* Set up MPU memory regions, both for protection and to + * enable caching (the hardware defaults is "uncached rwx + * memory everywhere"). + */ + enable_mpu(); + + /* But the CPU core won't actually use the cache without MEMCTL... */ + __asm__ volatile("wsr %0, MEMCTL; rsync" :: "r"(memctl)); + + /* Need the vector base set to receive exceptions and + * interrupts (including register window exceptions, meaning + * we can't make C function calls until this is done!) + */ + __asm__ volatile("wsr %0, VECBASE; rsync" :: "r"(&z_xtensa_vecbase)); + + mtk_adsp_cpu_freq_init(); + + /* Likewise, memory power is external to the device, and the + * kernel SOF loader doesn't zero it, so zero our unlinked + * memory to prevent possible pollution from previous runs. + * This region is uncached, no need to flush. + */ + memset(_mtk_adsp_sram_end, 0, SRAM_END - (uint32_t)&_mtk_adsp_sram_end); + memset(_mtk_adsp_dram_end, 0, DRAM_END - (uint32_t)&_mtk_adsp_dram_end); + + /* Clear pending interrupts. Note that this hardware has a + * habit of starting with all its timer interrupts flagged. + * These have to be cleared by writing to the equivalent + * CCOMPAREn register. Assumes XCHAL_NUM_TIMERS == 3... + */ + uint32_t val = 0; + + __asm__ volatile("wsr %0, CCOMPARE0" :: "r"(val)); + __asm__ volatile("wsr %0, CCOMPARE1" :: "r"(val)); + __asm__ volatile("wsr %0, CCOMPARE2" :: "r"(val)); + __ASSERT_NO_MSG(XCHAL_NUM_TIMERS == 3); + val = 0xffffffff; + __asm__ volatile("wsr %0, INTCLEAR" :: "r"(val)); + + z_cstart(); +} diff --git a/soc/mediatek/mtk_adsp/soc.h b/soc/mediatek/mtk_adsp/soc.h new file mode 100644 index 00000000000..8b6247b7910 --- /dev/null +++ b/soc/mediatek/mtk_adsp/soc.h @@ -0,0 +1,36 @@ +/* Copyright 2023 The ChromiumOS Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SOC_MTK_ADSP_SOC_H +#define ZEPHYR_SOC_MTK_ADSP_SOC_H + +#include + +void mtk_adsp_cpu_freq_init(void); +void mtk_adsp_set_cpu_freq(int mhz); + +/* Mailbox Driver: */ + +/* Hardware defines multiple "channel" bits that can be independently + * signaled and cleared. An interrupt is latched if any bits are + * set. + */ +#define MTK_ADSP_MBOX_CHANNELS 5 + +typedef void (*mtk_adsp_mbox_handler_t)(const struct device *mbox, void *arg); + +void mtk_adsp_mbox_set_handler(const struct device *mbox, uint32_t chan, + mtk_adsp_mbox_handler_t handler, void *arg); + +/* Mailbox hardware has an array of unstructured "message" data in + * each direction. Any value can be placed in the registers. + */ +#define MTK_ADSP_MBOX_MSG_WORDS 5 +void mtk_adsp_mbox_set_msg(const struct device *mbox, uint32_t idx, uint32_t val); +uint32_t mtk_adsp_mbox_get_msg(const struct device *mbox, uint32_t idx); + +/* Signal an interrupt on the specified channel for the other side */ +void mtk_adsp_mbox_signal(const struct device *mbox, uint32_t chan); + +#endif /* ZEPHYR_SOC_MTK_ADSP_SOC_H */ diff --git a/soc/mediatek/mtk_adsp/soc.yml b/soc/mediatek/mtk_adsp/soc.yml new file mode 100644 index 00000000000..c042b9bd2ae --- /dev/null +++ b/soc/mediatek/mtk_adsp/soc.yml @@ -0,0 +1,6 @@ +family: + - name: mtk_adsp + series: + - name: mtk_adsp + socs: + - name: mt8195_adsp