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:
Andy Ross 2023-08-05 06:15:05 -07:00 committed by Anas Nashif
commit df8395e3d8
21 changed files with 1195 additions and 0 deletions

View 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

View 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"

View file

@ -0,0 +1,5 @@
boards:
- name: mt8195_adsp
vendor: mediatek
socs:
- name: mt8195_adsp

View 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 { };
};

View 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

View 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

View 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)

View 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

View 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

View 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

View 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);
}

View 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)

View 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);
}

View 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)

View 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

View 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 */

View 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 */

View 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
View 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();
}

View 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 */

View file

@ -0,0 +1,6 @@
family:
- name: mtk_adsp
series:
- name: mtk_adsp
socs:
- name: mt8195_adsp