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 <andyross@google.com>
This commit is contained in:
parent
e0c56f1998
commit
df8395e3d8
21 changed files with 1195 additions and 0 deletions
9
boards/mediatek/mt8195_adsp/Kconfig.defconfig
Normal file
9
boards/mediatek/mt8195_adsp/Kconfig.defconfig
Normal file
|
@ -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
|
5
boards/mediatek/mt8195_adsp/Kconfig.mt8195_adsp
Normal file
5
boards/mediatek/mt8195_adsp/Kconfig.mt8195_adsp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Copyright 2023 The ChromiumOS Authors
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
config BOARD_MT8195_ADSP
|
||||||
|
bool "Mediatek MT8195 Audio DSP"
|
5
boards/mediatek/mt8195_adsp/board.yml
Normal file
5
boards/mediatek/mt8195_adsp/board.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
boards:
|
||||||
|
- name: mt8195_adsp
|
||||||
|
vendor: mediatek
|
||||||
|
socs:
|
||||||
|
- name: mt8195_adsp
|
95
boards/mediatek/mt8195_adsp/mt8195_adsp.dts
Normal file
95
boards/mediatek/mt8195_adsp/mt8195_adsp.dts
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
/* Copyright 2023 The ChromiumOS Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#include <mem.h>
|
||||||
|
|
||||||
|
/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 { };
|
||||||
|
|
||||||
|
};
|
5
boards/mediatek/mt8195_adsp/mt8195_adsp_defconfig
Normal file
5
boards/mediatek/mt8195_adsp/mt8195_adsp_defconfig
Normal file
|
@ -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
|
16
dts/bindings/clock/mediatek,mt8195_cpuclk.yaml
Normal file
16
dts/bindings/clock/mediatek,mt8195_cpuclk.yaml
Normal file
|
@ -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
|
12
soc/mediatek/mtk_adsp/CMakeLists.txt
Normal file
12
soc/mediatek/mtk_adsp/CMakeLists.txt
Normal file
|
@ -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)
|
17
soc/mediatek/mtk_adsp/Kconfig
Normal file
17
soc/mediatek/mtk_adsp/Kconfig
Normal file
|
@ -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
|
75
soc/mediatek/mtk_adsp/Kconfig.defconfig
Normal file
75
soc/mediatek/mtk_adsp/Kconfig.defconfig
Normal file
|
@ -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
|
8
soc/mediatek/mtk_adsp/Kconfig.soc
Normal file
8
soc/mediatek/mtk_adsp/Kconfig.soc
Normal file
|
@ -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
|
163
soc/mediatek/mtk_adsp/cpuclk.c
Normal file
163
soc/mediatek/mtk_adsp/cpuclk.c
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
/* Copyright 2023 The ChromiumOS Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
#include <zephyr/devicetree.h>
|
||||||
|
#include <zephyr/sys/util.h>
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
71
soc/mediatek/mtk_adsp/gen_img.py
Executable file
71
soc/mediatek/mtk_adsp/gen_img.py
Executable file
|
@ -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("<III", FILE_MAGIC, len(sram), boot_vector))
|
||||||
|
of.write(sram)
|
||||||
|
of.write(dram)
|
58
soc/mediatek/mtk_adsp/irq.c
Normal file
58
soc/mediatek/mtk_adsp/irq.c
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/* Copyright 2023 The ChromiumOS Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/irq.h>
|
||||||
|
#include <zephyr/devicetree.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
130
soc/mediatek/mtk_adsp/mbox.c
Normal file
130
soc/mediatek/mtk_adsp/mbox.c
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
/* Copyright 2023 The ChromiumOS Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/irq.h>
|
||||||
|
#include <zephyr/devicetree.h>
|
||||||
|
#include <soc.h>
|
||||||
|
|
||||||
|
#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)
|
19
soc/mediatek/mtk_adsp/mt8195_adsp/Kconfig.defconfig
Normal file
19
soc/mediatek/mtk_adsp/mt8195_adsp/Kconfig.defconfig
Normal file
|
@ -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
|
139
soc/mediatek/mtk_adsp/mt8195_adsp/linker.ld
Normal file
139
soc/mediatek/mtk_adsp/mt8195_adsp/linker.ld
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
/* Copyright 2023 The ChromiumOS Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/linker/linker-defs.h>
|
||||||
|
#include <zephyr/linker/linker-tool.h>
|
||||||
|
|
||||||
|
#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 <xtensa_vectors.ld>
|
||||||
|
> 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 <snippets-rodata.ld>
|
||||||
|
|
||||||
|
*(.gnu.linkonce.e.*)
|
||||||
|
*(.gnu.version_r)
|
||||||
|
KEEP (*(.eh_frame))
|
||||||
|
*(.gnu.linkonce.h.*)
|
||||||
|
} > dram
|
||||||
|
|
||||||
|
#include <zephyr/linker/common-rom.ld>
|
||||||
|
|
||||||
|
__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 <zephyr/linker/common-ram.ld>
|
||||||
|
|
||||||
|
.bss (NOLOAD) :
|
||||||
|
{
|
||||||
|
_bss_start = .;
|
||||||
|
*(.bss .bss.*)
|
||||||
|
*(.gnu.linkonce.b.*)
|
||||||
|
*(COMMON)
|
||||||
|
_bss_end = .;
|
||||||
|
} > dram
|
||||||
|
|
||||||
|
#include <zephyr/linker/common-noinit.ld>
|
||||||
|
#include <snippets-sections.ld>
|
||||||
|
|
||||||
|
. = ALIGN(4096);
|
||||||
|
_end = .;
|
||||||
|
_mtk_adsp_dram_end = .;
|
||||||
|
|
||||||
|
/* Non-runtime-loaded sections below */
|
||||||
|
|
||||||
|
#include <zephyr/linker/debug-sections.ld>
|
||||||
|
|
||||||
|
/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 <zephyr/linker/intlist.ld>
|
||||||
|
|
||||||
|
} /* SECTIONS */
|
10
soc/mediatek/mtk_adsp/mt8195_adsp/soc.h
Normal file
10
soc/mediatek/mtk_adsp/mt8195_adsp/soc.h
Normal file
|
@ -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 */
|
141
soc/mediatek/mtk_adsp/mtk_adsp_load.py
Executable file
141
soc/mediatek/mtk_adsp/mtk_adsp_load.py
Executable file
|
@ -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("<I", bstr)[0]
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Open device and establish mappings
|
||||||
|
devmem_fd = open("/dev/mem", "wb+")
|
||||||
|
for mp in MAPPINGS:
|
||||||
|
paddr = MAPPINGS[mp][0]
|
||||||
|
mapsz = MAPPINGS[mp][1]
|
||||||
|
maps[mp] = mmap.mmap(devmem_fd.fileno(), mapsz, offset=paddr,
|
||||||
|
flags=mmap.MAP_SHARED, prot=mmap.PROT_WRITE|mmap.PROT_READ)
|
||||||
|
|
||||||
|
# Create a Regs object for the registers
|
||||||
|
cfg = Regs(ctypes.addressof(ctypes.c_int.from_buffer(maps["regs"])))
|
||||||
|
cfg.ALTRESETVEC = 0x0004 # Xtensa boot address
|
||||||
|
cfg.RESET_SW = 0x0024 # Xtensa halt/reset/boot control
|
||||||
|
cfg.PDEBUGBUS0 = 0x000c # Unclear, enabled by host, unused by SOF?
|
||||||
|
cfg.SRAM_POOL_CON = 0x0930 # SRAM power control: low 4 bits (banks?) enable
|
||||||
|
cfg.EMI_MAP_ADDR = 0x981c # == host SRAM mapping - 0x40000000 (controls MMIO map?)
|
||||||
|
cfg.freeze()
|
||||||
|
|
||||||
|
if sys.argv[1] == "load":
|
||||||
|
dat = open(sys.argv[2], "rb").read()
|
||||||
|
assert le4(dat[0:4])== FILE_MAGIC
|
||||||
|
sram_len = le4(dat[4:8])
|
||||||
|
boot_vector = le4(dat[8:12])
|
||||||
|
sram = dat[12:12+sram_len]
|
||||||
|
dram = dat[12 + sram_len:]
|
||||||
|
assert len(sram) <= MAPPINGS["sram"][1]
|
||||||
|
assert len(dram) <= MAPPINGS["dram"][1]
|
||||||
|
for i in range(sram_len):
|
||||||
|
maps["sram"][i] = sram[i]
|
||||||
|
for i in range(sram_len, MAPPINGS["sram"][1]):
|
||||||
|
maps["sram"][i] = 0
|
||||||
|
for i in range(len(dram)):
|
||||||
|
maps["dram"][i] = dram[i]
|
||||||
|
for i in range(len(dram), MAPPINGS["dram"][1]):
|
||||||
|
maps["dram"][i] = 0
|
||||||
|
start(cfg, boot_vector)
|
||||||
|
|
||||||
|
elif sys.argv[1] == "log":
|
||||||
|
log()
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"Usage: {sys.argv[0]} log | load <file>")
|
||||||
|
|
||||||
|
# (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()
|
175
soc/mediatek/mtk_adsp/soc.c
Normal file
175
soc/mediatek/mtk_adsp/soc.c
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
/* Copyright 2023 The ChromiumOS Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/devicetree.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <kernel_internal.h>
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
36
soc/mediatek/mtk_adsp/soc.h
Normal file
36
soc/mediatek/mtk_adsp/soc.h
Normal file
|
@ -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 <zephyr/device.h>
|
||||||
|
|
||||||
|
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 */
|
6
soc/mediatek/mtk_adsp/soc.yml
Normal file
6
soc/mediatek/mtk_adsp/soc.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
family:
|
||||||
|
- name: mtk_adsp
|
||||||
|
series:
|
||||||
|
- name: mtk_adsp
|
||||||
|
socs:
|
||||||
|
- name: mt8195_adsp
|
Loading…
Add table
Add a link
Reference in a new issue