zephyr/soc/mediatek/mt8xxx/mtk_adsp_load.py
Andy Ross 5364783ba1 soc: boards: Add Mediatek MT8186 and MT8188 audio DSPs
These are very similar devices to mt8195, minimal changes needed
beyond boilerplate configuration.

In the process, this reworks the board/soc layout to be HWMv2
compliant, with "adsp" becoming a CPU cluster beneath the SOC.  So the
name of the boards to west become e.g. "mt8195/mt8195/adsp" (which can
be shortened to "mt8195//adsp" if desired).

Note that the cpuclk driver is not yet ported, it works only with 8195
(the clocking/power architecture seems similar between the parts, but
the graph of wells and clocks is different and historically these have
been three separate drivers in SOF).  The biggest changes are in the
image/loader scripts, which needed some rework for cross-device
portability.

Signed-off-by: Andy Ross <andyross@google.com>
2024-11-28 20:51:50 +01:00

227 lines
8.2 KiB
Python
Executable file

#!/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
from glob import glob
import re
# 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
# Runtime mmap objects for each MAPPINGS entry
maps = {}
# Returns a string (e.g. "mt8195", "mt8186", "mt8188") if a supported
# adsp is detected, or None if not
def detect():
compat = readfile(glob("/proc/device-tree/**/adsp@*/compatible",
recursive=True)[0], "r")
m = re.match(r'.*(mt\d{4})-dsp', compat)
if m:
return m.group(1)
# Parse devicetree to find the MMIO mappings: there is an "adsp" node
# (in various locations) with an array of named "reg" mappings. It
# also refers by reference to reserved-memory regions of system
# DRAM. Those don't have names, call them dram0/1 (dram1 is the main
# region to which code is linked, dram0 is presumably a dma pool but
# unused by current firmware). Returns a dict mapping name to a
# (addr, size) tuple.
def mappings():
path = glob("/proc/device-tree/**/adsp@*/", recursive=True)[0]
rnames = readfile(path + "reg-names", "r").split('\0')[:-1]
regs = struct.unpack(f">{2 * len(rnames)}Q", readfile(path + "reg"))
maps = { n : (regs[2*i], regs[2*i+1]) for i, n in enumerate(rnames) }
for i, ph in enumerate(struct.unpack(">II", readfile(path + "memory-region"))):
for rmem in glob("/proc/device-tree/reserved-memory/*/"):
if struct.unpack(">I", readfile(rmem + "phandle"))[0] == ph:
(addr, sz) = struct.unpack(">QQ", readfile(rmem + "reg"))
maps[f"dram{i}"] = (addr, sz)
break
return maps
# Register API for 8195
class MT8195():
def __init__(self, maps):
# Create a Regs object for the registers
r = Regs(ctypes.addressof(ctypes.c_int.from_buffer(maps["cfg"])))
r.ALTRESETVEC = 0x0004 # Xtensa boot address
r.RESET_SW = 0x0024 # Xtensa halt/reset/boot control
r.PDEBUGBUS0 = 0x000c # Unclear, enabled by host, unused by SOF?
r.SRAM_POOL_CON = 0x0930 # SRAM power control: low 4 bits (banks?) enable
r.EMI_MAP_ADDR = 0x981c # == host SRAM mapping - 0x40000000 (controls MMIO map?)
r.freeze()
self.cfg = r
def stop(self):
self.cfg.RESET_SW |= 8 # Set RUNSTALL: halt CPU
self.cfg.RESET_SW |= 3 # Set low two bits: "BRESET|DRESET"
def start(self, boot_vector):
self.stop()
self.cfg.RESET_SW |= 0x10 # Enable "alternate reset" boot vector
self.cfg.ALTRESETVEC = boot_vector
self.cfg.RESET_SW &= ~3 # Release reset bits
self.cfg.RESET_SW &= ~8 # Clear RUNSTALL: go!
# Register API for 8186/8188
class MT818x():
def __init__(self, maps):
# These have registers spread across two blocks
cfg_base = ctypes.addressof(ctypes.c_int.from_buffer(maps["cfg"]))
sec_base = ctypes.addressof(ctypes.c_int.from_buffer(maps["sec"]))
self.cfg = Regs(cfg_base)
self.cfg.SW_RSTN = 0x00
self.cfg.IO_CONFIG = 0x0c
self.cfg.freeze()
self.sec = Regs(sec_base)
self.sec.ALTVEC_C0 = 0x04
self.sec.ALTVECSEL = 0x0c
self.sec.freeze()
def stop(self):
self.cfg.IO_CONFIG |= (1<<31) # Set RUNSTALL to stop core
time.sleep(0.1)
self.cfg.SW_RSTN |= 0x11 # Assert reset: SW_RSTN_C0|SW_DBG_RSTN_C0
# Note: 8186 and 8188 use different bits in ALTVECSEC, but
# it's safe to write both to enable the alternate boot vector
def start(self, boot_vector):
self.cfg.IO_CONFIG |= (1<<31) # Set RUNSTALL
self.sec.ALTVEC_C0 = boot_vector
self.sec.ALTVECSEL = 0x03 # Enable alternate vector
self.cfg.SW_RSTN |= 0x00000011 # Assert reset
self.cfg.SW_RSTN &= 0xffffffee # Release reset
self.cfg.IO_CONFIG &= 0x7fffffff # Clear RUNSTALL
# 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["dram1"]
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()
# (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
def readfile(f, mode="rb"):
return open(f, mode).read()
def le4(bstr):
assert len(bstr) == 4
return struct.unpack("<I", bstr)[0]
def main():
dsp = detect()
assert dsp
# Probe devicetree for mappable memory regions
mmio = mappings()
# Open device and establish mappings
devmem_fd = open("/dev/mem", "wb+")
for mp in mmio:
paddr = mmio[mp][0]
mapsz = mmio[mp][1]
mapsz = int((mapsz + 4095) / 4096) * 4096
maps[mp] = mmap.mmap(devmem_fd.fileno(), mapsz, offset=paddr,
flags=mmap.MAP_SHARED, prot=mmap.PROT_WRITE|mmap.PROT_READ)
if dsp == "mt8195":
dev = MT8195(maps)
elif dsp in ("mt8186", "mt8188"):
dev = MT818x(maps)
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) <= mmio["sram"][1]
assert len(dram) <= mmio["dram1"][1]
# Stop the device and write the regions. Note that we don't
# zero-fill SRAM, as that's been observed to reboot the host
# (!!) on mt8186 when the writes near the end of the 512k
# region.
# pylint: disable=consider-using-enumerate
for i in range(sram_len):
maps["sram"][i] = sram[i]
#for i in range(sram_len, mmio["sram"][1]):
# maps["sram"][i] = 0
for i in range(len(dram)):
maps["dram1"][i] = dram[i]
for i in range(len(dram), mmio["dram1"][1]):
maps["dram1"][i] = 0
dev.start(boot_vector)
log()
elif sys.argv[1] == "log":
log()
elif sys.argv[1] == "dump":
sz = mmio[sys.argv[2]][1]
mm = maps[sys.argv[2]]
sys.stdout.buffer.write(mm[0:sz])
else:
print(f"Usage: {sys.argv[0]} log | load <file>")
if __name__ == "__main__":
main()