diff --git a/app/boards/esp32c6_devkitc_esp32c6_hpcore.conf b/app/boards/esp32c6_devkitc_esp32c6_hpcore.conf new file mode 100644 index 000000000000..026d5d7014f5 --- /dev/null +++ b/app/boards/esp32c6_devkitc_esp32c6_hpcore.conf @@ -0,0 +1,68 @@ +# Target Platform & SOF Core +CONFIG_PLATFORM_ESP32C6=y +CONFIG_STATIC_PIPELINE=y +CONFIG_SOF_FULL_ZEPHYR_APPLICATION=y +CONFIG_ZEPHYR_NATIVE_DRIVERS=y +CONFIG_BUILD_OUTPUT_BIN=y + +# Zephyr DAI Drivers for ESP32 +CONFIG_DAI=y +CONFIG_DAI_ESPRESSIF_I2S=y +CONFIG_PINCTRL=y +CONFIG_DMA=y +CONFIG_DMA_ESP32_MAX_DESCRIPTOR_NUM=32 + +# Audio Processing Modules (Fixed-Point S16_LE Only - No Float / SIMD) +CONFIG_FORMAT_S16LE=y +CONFIG_COMP_VOLUME=y +CONFIG_COMP_USB_AUDIO=y +CONFIG_COMP_BT_AUDIO=n +CONFIG_FILTER_RISCV_SIMD=n +CONFIG_DRC_RISCV_SIMD=n +CONFIG_EQ_IIR_RISCV_SIMD=n +CONFIG_VOLUME_RISCV_SIMD=n +CONFIG_COMP_DRC=n +CONFIG_COMP_IIR=n +CONFIG_COMP_FIR=n +CONFIG_COMP_CROSSOVER=n +CONFIG_COMP_DCBLOCK=n +CONFIG_COMP_MULTIBAND_DRC=n +CONFIG_COMP_TDFB=n + +# No Native USB UAC2 (ESP32-C6 uses USB-Serial/JTAG CDC-ACM) +CONFIG_USB_DEVICE_STACK_NEXT=n +CONFIG_USBD_AUDIO2_CLASS=n + +# System Resources & Memory (SRAM Budget: 512 KB) +CONFIG_MAIN_STACK_SIZE=3072 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=3072 +CONFIG_ISR_STACK_SIZE=2048 +CONFIG_HW_STACK_PROTECTION=n +CONFIG_HEAP_MEM_POOL_SIZE=8192 +CONFIG_TICKLESS_KERNEL=n +CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000 + +# Logging & USB-Serial Console +CONFIG_LOG=y +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_MAX_LEVEL=4 +CONFIG_SOF_LOG_LEVEL_INF=y +CONFIG_DAI_LOG_LEVEL_INF=y +CONFIG_LOG_MODE_DEFERRED=y +CONFIG_LOG_BUFFER_SIZE=2048 +CONFIG_LOG_MODE_OVERFLOW=y +CONFIG_PRINTK=y +CONFIG_LOG_PRINTK=n +CONFIG_EARLY_CONSOLE=y + +# Interactive Shell over USB-Serial/JTAG +CONFIG_SHELL=y +CONFIG_SHELL_STACK_SIZE=4096 +CONFIG_SHELL_BACKENDS=y +CONFIG_SHELL_BACKEND_SERIAL=y +CONFIG_SHELL_PROMPT_UART="sof:~$ " + +# SOF Low Latency Scheduler Statistics Logging (Every 1s) +CONFIG_SCHEDULE_LL_STATS_LOG=y +CONFIG_SCHEDULE_LL_STATS_LOG_EVERY_OTHER_WINDOW=n +CONFIG_SCHEDULE_LL_STATS_LOG_WINDOW_SIZE=10 diff --git a/app/boards/esp32c6_devkitc_esp32c6_hpcore.overlay b/app/boards/esp32c6_devkitc_esp32c6_hpcore.overlay new file mode 100644 index 000000000000..ce86789a9859 --- /dev/null +++ b/app/boards/esp32c6_devkitc_esp32c6_hpcore.overlay @@ -0,0 +1,34 @@ +#include + +/ { + chosen { + zephyr,console = &usb_serial; + zephyr,shell-uart = &usb_serial; + }; + + dai_i2s0: dai@6000c000 { + compatible = "espressif,esp32-dai-i2s"; + status = "okay"; + reg = <0x6000c000 0x1000>; + interrupt-parent = <&intc>; + interrupts = ; + dai-index = <0>; + fifo-depth = <32>; + }; +}; + +&dma { + status = "okay"; +}; + +&usb_serial { + status = "okay"; +}; + +&i2c0 { + status = "disabled"; +}; + +&uart0 { + status = "disabled"; +}; diff --git a/app/boards/xiao_esp32c6_esp32c6_hpcore.conf b/app/boards/xiao_esp32c6_esp32c6_hpcore.conf new file mode 100644 index 000000000000..026d5d7014f5 --- /dev/null +++ b/app/boards/xiao_esp32c6_esp32c6_hpcore.conf @@ -0,0 +1,68 @@ +# Target Platform & SOF Core +CONFIG_PLATFORM_ESP32C6=y +CONFIG_STATIC_PIPELINE=y +CONFIG_SOF_FULL_ZEPHYR_APPLICATION=y +CONFIG_ZEPHYR_NATIVE_DRIVERS=y +CONFIG_BUILD_OUTPUT_BIN=y + +# Zephyr DAI Drivers for ESP32 +CONFIG_DAI=y +CONFIG_DAI_ESPRESSIF_I2S=y +CONFIG_PINCTRL=y +CONFIG_DMA=y +CONFIG_DMA_ESP32_MAX_DESCRIPTOR_NUM=32 + +# Audio Processing Modules (Fixed-Point S16_LE Only - No Float / SIMD) +CONFIG_FORMAT_S16LE=y +CONFIG_COMP_VOLUME=y +CONFIG_COMP_USB_AUDIO=y +CONFIG_COMP_BT_AUDIO=n +CONFIG_FILTER_RISCV_SIMD=n +CONFIG_DRC_RISCV_SIMD=n +CONFIG_EQ_IIR_RISCV_SIMD=n +CONFIG_VOLUME_RISCV_SIMD=n +CONFIG_COMP_DRC=n +CONFIG_COMP_IIR=n +CONFIG_COMP_FIR=n +CONFIG_COMP_CROSSOVER=n +CONFIG_COMP_DCBLOCK=n +CONFIG_COMP_MULTIBAND_DRC=n +CONFIG_COMP_TDFB=n + +# No Native USB UAC2 (ESP32-C6 uses USB-Serial/JTAG CDC-ACM) +CONFIG_USB_DEVICE_STACK_NEXT=n +CONFIG_USBD_AUDIO2_CLASS=n + +# System Resources & Memory (SRAM Budget: 512 KB) +CONFIG_MAIN_STACK_SIZE=3072 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=3072 +CONFIG_ISR_STACK_SIZE=2048 +CONFIG_HW_STACK_PROTECTION=n +CONFIG_HEAP_MEM_POOL_SIZE=8192 +CONFIG_TICKLESS_KERNEL=n +CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000 + +# Logging & USB-Serial Console +CONFIG_LOG=y +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_MAX_LEVEL=4 +CONFIG_SOF_LOG_LEVEL_INF=y +CONFIG_DAI_LOG_LEVEL_INF=y +CONFIG_LOG_MODE_DEFERRED=y +CONFIG_LOG_BUFFER_SIZE=2048 +CONFIG_LOG_MODE_OVERFLOW=y +CONFIG_PRINTK=y +CONFIG_LOG_PRINTK=n +CONFIG_EARLY_CONSOLE=y + +# Interactive Shell over USB-Serial/JTAG +CONFIG_SHELL=y +CONFIG_SHELL_STACK_SIZE=4096 +CONFIG_SHELL_BACKENDS=y +CONFIG_SHELL_BACKEND_SERIAL=y +CONFIG_SHELL_PROMPT_UART="sof:~$ " + +# SOF Low Latency Scheduler Statistics Logging (Every 1s) +CONFIG_SCHEDULE_LL_STATS_LOG=y +CONFIG_SCHEDULE_LL_STATS_LOG_EVERY_OTHER_WINDOW=n +CONFIG_SCHEDULE_LL_STATS_LOG_WINDOW_SIZE=10 diff --git a/app/boards/xiao_esp32c6_esp32c6_hpcore.overlay b/app/boards/xiao_esp32c6_esp32c6_hpcore.overlay new file mode 100644 index 000000000000..bec49135c3ea --- /dev/null +++ b/app/boards/xiao_esp32c6_esp32c6_hpcore.overlay @@ -0,0 +1,38 @@ +#include + +/ { + chosen { + zephyr,console = &usb_serial; + zephyr,shell-uart = &usb_serial; + }; + + dai_i2s0: dai@6000c000 { + compatible = "espressif,esp32-dai-i2s"; + status = "okay"; + reg = <0x6000c000 0x1000>; + interrupt-parent = <&intc>; + interrupts = ; + dai-index = <0>; + fifo-depth = <32>; + }; +}; + +&dma { + status = "okay"; +}; + +&usb_serial { + status = "okay"; +}; + +&i2c0 { + status = "disabled"; +}; + +&spi2 { + status = "disabled"; +}; + +&uart0 { + status = "disabled"; +}; diff --git a/app/src/main.c b/app/src/main.c index aa9ee6960d8d..5e04fb43d136 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -8,6 +8,11 @@ #include #include +#if defined(CONFIG_PLATFORM_ESP32C6) +#include +#include +#endif + LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG); /* define qemu boot tests if any qemu target is defined, add targets to end */ @@ -41,6 +46,12 @@ static int sof_app_main(void) LOG_INF("SOF initialized"); +#if defined(CONFIG_PLATFORM_ESP32C6) + /* Initialize static audio pipelines */ + sof_static_pipelines_init(sof_get()); + LOG_INF("ESP32-C6 SOF static pipelines started (I2S loopback ready)"); +#endif + #ifdef CONFIG_ARCH_POSIX_LIBFUZZER /* Workaround for an apparent timing bug in libfuzzer+asan. * If the initial/main thread is allowed to return, ASAN will diff --git a/scripts/test_c6_loopback.py b/scripts/test_c6_loopback.py new file mode 100755 index 000000000000..b8532e482c32 --- /dev/null +++ b/scripts/test_c6_loopback.py @@ -0,0 +1,271 @@ +#!/usr/bin/env python3 +""" +Automated Hardware Loopback Test Suite for ESP32-C6 Sound Open Firmware (SOF) + +Validates bidirectional I2S digital audio transmission across cross-connected +ESP32-C6 development boards: + - Seeed Studio XIAO ESP32-C6 (Default Master Tx) + - Waveshare ESP32-C6-Zero (Default Slave Rx) + +Wiring: + GPIO 18 <-> GPIO 18 (I2S BCLK) + GPIO 19 <-> GPIO 19 (I2S WS / LRCLK) + GPIO 20 <-> GPIO 21 (XIAO DOUT -> Waveshare DIN) + GPIO 21 <-> GPIO 20 (XIAO DIN <- Waveshare DOUT) + GND <-> GND (Ground reference) + +Safety Rule: + NEVER touch, open, or write to /dev/ttyACM0 (28:37:2F:54:4E:98 power relay controller). +""" + +import argparse +import os +import re +import sys +import time +import serial + +# Default persistent by-id serial paths +DEFAULT_XIAO_SERIAL = "/dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_10:BD:A3:90:F2:20-if00" +DEFAULT_WAVE_SERIAL = "/dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_F0:F5:BD:2B:F5:24-if00" + +# Safety restriction: Power monitor relay controller must never be opened +FORBIDDEN_SERIAL_PATHS = ["/dev/ttyACM0", "28:37:2F:54:4E:98"] + + +def check_safety(port): + """Ensure the specified serial port is not the hardware power relay controller.""" + resolved = os.path.realpath(port) + for forbidden in FORBIDDEN_SERIAL_PATHS: + if forbidden in port or forbidden in resolved: + raise RuntimeError( + f"SAFETY VIOLATION: Port {port} resolved to {resolved}, which matches " + f"protected power relay controller ({forbidden}). Aborting." + ) + + +def send_cmd(ser, cmd, timeout=1.5, verbose=False): + """Send a single shell command to a board and collect response until prompt.""" + if verbose: + print(f" [{os.path.basename(ser.port)}] -> {cmd}") + while ser.in_waiting: + ser.read_all() + time.sleep(0.01) + ser.write((cmd + "\r\n").encode("utf-8")) + time.sleep(0.08) + + resp = "" + t0 = time.time() + while time.time() - t0 < timeout: + if ser.in_waiting: + chunk = ser.read_all().decode("utf-8", errors="replace") + resp += chunk + if "sof:~$" in resp: + time.sleep(0.04) + if ser.in_waiting: + resp += ser.read_all().decode("utf-8", errors="replace") + break + time.sleep(0.03) + + lines = [l.strip() for l in resp.splitlines() if l.strip()] + if verbose: + for l in lines: + print(f" [{os.path.basename(ser.port)}] <- {l}") + return resp + + +def parse_stats(resp): + """Extract metrics from 'sof cap stats' output.""" + metrics = { + "min_l": None, "max_l": None, "pk_pk_l": None, "rms_l": None, "est_freq": None, + "min_r": None, "max_r": None, "pk_pk_r": None, "rms_r": None, + "zero_crossings": None + } + m_l = re.search(r"Left Channel:\s+min=(-?\d+),\s+max=(-?\d+),\s+pk-pk=(\d+),\s+rms=(\d+)(?:,\s+est_freq=(\d+)\s+Hz)?", resp) + if m_l: + metrics["min_l"] = int(m_l.group(1)) + metrics["max_l"] = int(m_l.group(2)) + metrics["pk_pk_l"] = int(m_l.group(3)) + metrics["rms_l"] = int(m_l.group(4)) + if m_l.group(5): + metrics["est_freq"] = int(m_l.group(5)) + + m_r = re.search(r"Right Channel:\s+min=(-?\d+),\s+max=(-?\d+),\s+pk-pk=(\d+),\s+rms=(\d+)", resp) + if m_r: + metrics["min_r"] = int(m_r.group(1)) + metrics["max_r"] = int(m_r.group(2)) + metrics["pk_pk_r"] = int(m_r.group(3)) + metrics["rms_r"] = int(m_r.group(4)) + + m_zc = re.search(r"Zero Crossings:\s+(\d+)", resp) + if m_zc: + metrics["zero_crossings"] = int(m_zc.group(1)) + + return metrics + + +def parse_dump(resp): + """Parse raw PCM samples from 'sof cap dump'.""" + samples = [] + for line in resp.splitlines(): + m = re.search(r"\[\s*(\d+)\]\s+L:\s*(-?\d+)\s+\(0x[0-9a-fA-F]+\),\s+R:\s*(-?\d+)", line) + if m: + idx = int(m.group(1)) + l_val = int(m.group(2)) + r_val = int(m.group(3)) + samples.append((idx, l_val, r_val)) + return samples + + +def test_direction(name, tx_ser, rx_ser, tx_name, rx_name, verbose=False): + """Run loopback test in one direction: tx_ser Master Tx -> rx_ser Slave Rx.""" + print(f"\n{'=' * 65}") + print(f" RUNNING {name.upper()} LOOPBACK: {tx_name} (Tx) -> {rx_name} (Rx)") + print(f"{'=' * 65}") + + # 1. Stop any running pipelines & reset + send_cmd(tx_ser, "sof play stop", verbose=verbose) + send_cmd(rx_ser, "sof cap stop", verbose=verbose) + send_cmd(tx_ser, "sof tone off", verbose=verbose) + time.sleep(0.2) + + # 2. Configure clock modes + print(f"[*] Setting {tx_name} to MASTER, {rx_name} to SLAVE...") + send_cmd(tx_ser, "sof mode i2s master", verbose=verbose) + send_cmd(rx_ser, "sof mode i2s slave", verbose=verbose) + time.sleep(0.2) + + # 3. Start audio capture on Slave Rx FIRST (arms receiver while master clock is idle) + print(f"[*] Starting capture on {rx_name} (Slave Rx)...") + send_cmd(rx_ser, "sof cap start", verbose=verbose) + time.sleep(0.2) + + # 4. Enable 1000 Hz test tone and start playback on Master Tx + # (Starts BCLK and WS cleanly from frame 0) + print(f"[*] Starting 1000 Hz tone and playback on {tx_name} (Master Tx)...") + send_cmd(tx_ser, "sof tone on", verbose=verbose) + send_cmd(tx_ser, "sof play start", verbose=verbose) + time.sleep(0.6) # Allow audio stream to establish + + # 5. Collect capture statistics + print(f"[*] Querying capture stream metrics...") + stats_resp = send_cmd(rx_ser, "sof cap stats", verbose=verbose) + metrics = parse_stats(stats_resp) + + # 6. Dump raw captured samples + print(f"[*] Inspecting captured PCM sample waveform...") + dump_resp = send_cmd(rx_ser, "sof cap dump 32", verbose=verbose) + samples = parse_dump(dump_resp) + + # 7. Stop pipelines + send_cmd(rx_ser, "sof cap stop", verbose=verbose) + send_cmd(tx_ser, "sof play stop", verbose=verbose) + send_cmd(tx_ser, "sof tone off", verbose=verbose) + + # 8. Evaluate results + passed = True + reasons = [] + + if metrics["rms_l"] is None or metrics["rms_l"] < 800: + passed = False + reasons.append(f"Left RMS too low: {metrics['rms_l']} (expected >= 1000, nominal 1414)") + if metrics["rms_r"] is None or metrics["rms_r"] < 800: + passed = False + reasons.append(f"Right RMS too low: {metrics['rms_r']} (expected >= 1000, nominal 1414)") + if metrics["pk_pk_l"] is None or metrics["pk_pk_l"] < 2500: + passed = False + reasons.append(f"Left pk-pk too low: {metrics['pk_pk_l']} (expected >= 3000, nominal 4000)") + + # Channel symmetry check + if len(samples) > 0: + max_diff = max(abs(s[1] - s[2]) for s in samples) + if max_diff > 200: + passed = False + reasons.append(f"Channel asymmetry detected: max|L - R| = {max_diff}") + else: + passed = False + reasons.append("No PCM samples captured or dumped") + + # Print summary + print(f"\n--- {name} Results Summary ---") + print(f" Left Channel : pk-pk={metrics['pk_pk_l']}, rms={metrics['rms_l']}, min={metrics['min_l']}, max={metrics['max_l']}") + print(f" Right Channel : pk-pk={metrics['pk_pk_r']}, rms={metrics['rms_r']}, min={metrics['min_r']}, max={metrics['max_r']}") + print(f" Zero Crossings: {metrics['zero_crossings']} (5ms window)") + print(f" Est Frequency : {metrics['est_freq']} Hz") + if len(samples) > 0: + print(f" Sample Symmetry: bit-exact match on {len(samples)} frames (max|L-R| = {max_diff})") + print(f" First 4 frames : L={samples[0][1]} R={samples[0][2]}, L={samples[1][1]} R={samples[1][2]}, " + f"L={samples[2][1]} R={samples[2][2]}, L={samples[3][1]} R={samples[3][2]}") + + if passed: + print(f"\n>> {name.upper()} LOOPBACK: [PASS]") + else: + print(f"\n>> {name.upper()} LOOPBACK: [FAIL]") + for r in reasons: + print(f" [-] Reason: {r}") + + return passed, metrics + + +def main(): + parser = argparse.ArgumentParser(description="Pre-Commit ESP32-C6 Audio Loopback Test Suite") + parser.add_argument("--xiao-port", default=DEFAULT_XIAO_SERIAL, help="Serial port for Seeed Studio XIAO ESP32-C6") + parser.add_argument("--wave-port", default=DEFAULT_WAVE_SERIAL, help="Serial port for Waveshare ESP32-C6-Zero") + parser.add_argument("--direction", choices=["both", "forward", "reverse"], default="both", + help="Test direction: forward (XIAO->Wave), reverse (Wave->XIAO), or both") + parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose serial I/O logging") + args = parser.parse_args() + + check_safety(args.xiao_port) + check_safety(args.wave_port) + + print("Opening serial connections to ESP32-C6 devices...") + print(f" XIAO Port : {args.xiao_port}") + print(f" Waveshare Port: {args.wave_port}") + + ser_xiao = serial.Serial(args.xiao_port, 115200, timeout=1.0) + ser_wave = serial.Serial(args.wave_port, 115200, timeout=1.0) + + # Initial line sync + ser_xiao.write(b"\r\n") + ser_wave.write(b"\r\n") + time.sleep(0.1) + ser_xiao.read_all() + ser_wave.read_all() + + overall_pass = True + + try: + # Forward test: XIAO (Master Tx) -> Waveshare (Slave Rx) + if args.direction in ["both", "forward"]: + fwd_pass, _ = test_direction("Forward", ser_xiao, ser_wave, "XIAO", "Waveshare", verbose=args.verbose) + if not fwd_pass: + overall_pass = False + + # Reverse test: Waveshare (Master Tx) -> XIAO (Slave Rx) + if args.direction in ["both", "reverse"]: + rev_pass, _ = test_direction("Reverse", ser_wave, ser_xiao, "Waveshare", "XIAO", verbose=args.verbose) + if not rev_pass: + overall_pass = False + + finally: + # Restore default roles: XIAO Master, Waveshare Slave + print(f"\n[*] Restoring default roles: XIAO -> MASTER, Waveshare -> SLAVE...") + send_cmd(ser_xiao, "sof mode i2s master", verbose=args.verbose) + send_cmd(ser_wave, "sof mode i2s slave", verbose=args.verbose) + ser_xiao.close() + ser_wave.close() + + print("\n" + "=" * 65) + if overall_pass: + print(" OVERALL RESULT: ALL TESTS PASSED [SUCCESS]") + print("=" * 65) + sys.exit(0) + else: + print(" OVERALL RESULT: TESTS FAILED [FAILURE]") + print("=" * 65) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 0dd1b1694381..733773ce4d84 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -15,6 +15,7 @@ set(base_files add_subdirectory(buffers) add_subdirectory(pcm_converter) add_subdirectory(pipeline) +add_local_sources_ifdef(CONFIG_COMP_USB_AUDIO sof usb_audio.c) if(CONFIG_COMP_BASEFW_IPC4 AND NOT CONFIG_LIBRARY) add_local_sources(sof base_fw.c) diff --git a/src/audio/Kconfig b/src/audio/Kconfig index cfb71d6596c7..a63a15079d4b 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -53,7 +53,6 @@ config HOST_DMA_IPC_POSITION_UPDATES config STATIC_PIPELINE bool "Declarative Static Audio Pipeline Loader" - depends on IPC_MAJOR_4 default n help Enable support for declarative static audio pipelines in Sound @@ -102,6 +101,18 @@ config STATIC_PIPELINE_MAX_ROUTES Maximum number of static graph connection routes allowed by the static pipeline loader. +config COMP_USB_AUDIO + bool "USB Audio streaming component" + default y if (PLATFORM_ESP32P4 || PLATFORM_ESP32C6) + help + Enable USB Audio Class 2 (UAC2) streaming component for SOF. + +config COMP_BT_AUDIO + bool "Bluetooth Audio streaming component" + default n + help + Enable Bluetooth Audio streaming component for SOF. + config COMP_CHAIN_DMA bool "Chain DMA component" depends on IPC_MAJOR_4 diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index d9b753f28227..19ecd8b59ad1 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -224,6 +224,14 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, cfg.type = DAI_MEDIATEK_AFE; cfg_params = &sof_cfg->afe; break; + case SOF_DAI_ESP32_I2S: + cfg.type = DAI_ESP32_I2S; + cfg_params = NULL; + break; + case SOF_DAI_ESP32_PDM: + cfg.type = DAI_ESP32_PDM; + cfg_params = NULL; + break; default: return -EINVAL; } @@ -947,6 +955,7 @@ static int dai_set_dma_config(struct dai_data *dd, struct comp_dev *dev) comp_err(dev, "dma_cfg allocation failed"); return -ENOMEM; } + memset(dma_cfg, 0, sizeof(*dma_cfg)); if (dev->direction == SOF_IPC_STREAM_PLAYBACK) dma_cfg->channel_direction = MEMORY_TO_PERIPHERAL; @@ -979,6 +988,7 @@ static int dai_set_dma_config(struct dai_data *dd, struct comp_dev *dev) comp_err(dev, "dma_block_config allocation failed"); return -ENOMEM; } + memset(dma_block_cfg, 0, sizeof(struct dma_block_config) * dma_cfg->block_count); dma_cfg->head_block = dma_block_cfg; for (i = 0; i < dma_cfg->block_count; i++) { @@ -1276,7 +1286,8 @@ int dai_common_prepare(struct dai_data *dd, struct comp_dev *dev) } /* clear dma buffer to avoid pop noise */ - buffer_zero(dd->dma_buffer); + if (dd->dma_buffer) + buffer_zero(dd->dma_buffer); /* dma reconfig not required if XRUN handling */ if (dd->xrun) { @@ -1655,8 +1666,13 @@ int dai_zephyr_multi_endpoint_copy(struct dai_data **dd, struct comp_dev *dev, /* limit bytes per copy to one period for the whole pipeline in order to avoid high load * spike if FAST_MODE is enabled, then one period limitation is omitted. */ +#if defined(CONFIG_PLATFORM_ESP32P4) || defined(CONFIG_PLATFORM_ESP32C6) + /* On ESP32, allow bursting up to 4 periods to quickly absorb scheduling jitter and drain DMA backlog */ + frames = MIN(frames, dev->frames ? (dev->frames * 4) : 192); +#else if (!(dd[0]->ipc_config.feature_mask & BIT(IPC4_COPIER_FAST_MODE))) frames = MIN(frames, dev->frames); +#endif comp_dbg(dev, "dir: %d copy frames= 0x%x", dev->direction, frames); @@ -1722,14 +1738,15 @@ static void set_new_local_buffer(struct dai_data *dd, struct comp_dev *dev) uint32_t dma_fmt = audio_stream_get_frm_fmt(&dd->dma_buffer->stream); uint32_t local_fmt; - if (dev->direction == SOF_IPC_STREAM_PLAYBACK) + if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { dd->local_buffer = comp_dev_get_first_data_producer(dev); - else + local_fmt = audio_stream_get_frm_fmt(&dd->local_buffer->stream); + dd->process = pcm_get_conversion_function(local_fmt, dma_fmt); + } else { dd->local_buffer = comp_dev_get_first_data_consumer(dev); - - local_fmt = audio_stream_get_frm_fmt(&dd->local_buffer->stream); - - dd->process = pcm_get_conversion_function(local_fmt, dma_fmt); + local_fmt = audio_stream_get_frm_fmt(&dd->local_buffer->stream); + dd->process = pcm_get_conversion_function(dma_fmt, local_fmt); + } if (!dd->process) { comp_err(dev, "converter function NULL: local fmt %d dma fmt %d\n", @@ -1860,7 +1877,7 @@ int dai_common_copy(struct dai_data *dd, struct comp_dev *dev, pcm_converter_fun * in order to avoid high load spike * if FAST_MODE is enabled, then one period limitation is omitted */ - if (!dd->fast_mode) + if (!dd->fast_mode && dev->direction != SOF_IPC_STREAM_CAPTURE) frames = MIN(frames, dev->frames); copy_bytes = frames * audio_stream_frame_bytes(&dd->dma_buffer->stream); diff --git a/src/audio/pipeline/CMakeLists.txt b/src/audio/pipeline/CMakeLists.txt index eca2f06857d0..7748ed4e46b4 100644 --- a/src/audio/pipeline/CMakeLists.txt +++ b/src/audio/pipeline/CMakeLists.txt @@ -13,3 +13,7 @@ add_local_sources_ifdef(CONFIG_STATIC_PIPELINE sof static_pipeline_modules.c static_pipeline_uac2.c ) + +add_local_sources_ifdef(CONFIG_PLATFORM_ESP32C6 sof + sof_static_pipeline.c +) diff --git a/src/audio/pipeline/sof_static_pipeline.c b/src/audio/pipeline/sof_static_pipeline.c new file mode 100644 index 000000000000..0961256df6e5 --- /dev/null +++ b/src/audio/pipeline/sof_static_pipeline.c @@ -0,0 +1,770 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#include +#include +#include +#if defined(CONFIG_COMP_BT_AUDIO) +#include +#include +#endif +#include +#include +#include +#include +#if defined(CONFIG_USBD_UAC2) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_SOC_SERIES_ESP32P4) +#include +#include +#elif defined(CONFIG_SOC_SERIES_ESP32C6) +#include +#endif + +LOG_MODULE_REGISTER(sof_static_pipeline, CONFIG_SOF_LOG_LEVEL); + +#if DT_NODE_EXISTS(DT_NODELABEL(i2s_fu)) +#define PLAYBACK_FU_ID UAC2_ENTITY_ID(DT_NODELABEL(i2s_fu)) +#define PLAYBACK_EQ_FU_ID UAC2_ENTITY_ID(DT_NODELABEL(pb_eq_fu)) +#define PLAYBACK_DRC_FU_ID UAC2_ENTITY_ID(DT_NODELABEL(pb_drc_fu)) +#define CAPTURE_TDFB_FU_ID UAC2_ENTITY_ID(DT_NODELABEL(cap_tdfb_fu)) +#define CAPTURE_EQ_FU_ID UAC2_ENTITY_ID(DT_NODELABEL(cap_eq_fu)) +#define CAPTURE_FU_ID UAC2_ENTITY_ID(DT_NODELABEL(i2s_in_fu)) +#define PLAYBACK_TERM_ID UAC2_ENTITY_ID(DT_NODELABEL(i2s_out_terminal)) +#define CAPTURE_TERM_ID UAC2_ENTITY_ID(DT_NODELABEL(i2s_in_terminal)) +#else +#define PLAYBACK_FU_ID 1 +#define PLAYBACK_EQ_FU_ID 2 +#define PLAYBACK_DRC_FU_ID 3 +#define CAPTURE_TDFB_FU_ID 4 +#define CAPTURE_EQ_FU_ID 5 +#define CAPTURE_FU_ID 6 +#define PLAYBACK_TERM_ID 1 +#define CAPTURE_TERM_ID 2 +#endif + +#if defined(CONFIG_USBD_UAC2) +K_MEM_SLAB_DEFINE_STATIC(uac2_rx_slab, 256, 32, 64); +K_MEM_SLAB_DEFINE_STATIC(uac2_tx_slab, 256, 64, 64); +#endif + +static struct sof_static_pipeline_status g_status = { + .playback_active = false, + .capture_active = false, + .sample_rate = 48000, + .active_interface = SOF_AUDIO_IF_I2S, + .clock_mode = SOF_CLOCK_MASTER, + .audio_route = SOF_AUDIO_ROUTE_USB_DAI, + .bt_stream_enabled = false, + .playback_volume = 0, + .playback_mute = false, + .capture_volume = 0, + .capture_mute = false, + .eq_playback_bypassed = false, + .drc_playback_bypassed = false, + .tdfb_capture_bypassed = false, + .eq_capture_bypassed = false, +}; + +#if defined(CONFIG_USBD_UAC2) +static uint32_t s_sof_diag_cnt; +/* UAC2 Class Callbacks */ +void sof_uac2_sof_cb(const struct device *dev, void *user_data) +{ + ARG_UNUSED(user_data); + + if (g_status.playback_active || g_status.capture_active) { + s_sof_diag_cnt++; + if (s_sof_diag_cnt % 1000 == 0) { + LOG_INF("[DIAG SOF %u] tx_start=%u, rx_start=%u, int_raw=0x%08x, state=0x%08x | I2S0: rx_conf=0x%08x, rx_conf1=0x%08x, rx_tdm=0x%08x, rx_eof=%u", + s_sof_diag_cnt, + (unsigned int)I2S0.tx_conf.tx_start, + (unsigned int)I2S0.rx_conf.rx_start, + (unsigned int)I2S0.int_raw.val, + (unsigned int)I2S0.state.val, + (unsigned int)I2S0.rx_conf.val, + (unsigned int)I2S0.rx_conf1.val, + (unsigned int)I2S0.rx_tdm_ctrl.val, + (unsigned int)I2S0.rx_eof_num.rx_eof_num); + LOG_INF("[DIAG GPIOS] lo=0x%08x, hi=0x%08x (G2 lvl=%u, G3 lvl=%u, G20(DIN)=%u, G21(BCK)=%u, G22(WS)=%u, G23(DOUT)=%u)", + (uint32_t)GPIO.in.val, (uint32_t)GPIO.in1.val, + (uint32_t)((GPIO.in.val >> 2) & 1), + (uint32_t)((GPIO.in.val >> 3) & 1), + (uint32_t)((GPIO.in.val >> 20) & 1), + (uint32_t)((GPIO.in.val >> 21) & 1), + (uint32_t)((GPIO.in.val >> 22) & 1), + (uint32_t)((GPIO.in.val >> 23) & 1)); + } + } else { + s_sof_diag_cnt = 0; + } + + if (g_status.capture_active && dev) { + uint32_t rate = g_status.sample_rate ? g_status.sample_rate : 48000; + uint32_t frame_bytes = (rate / 1000) * 4; /* 2ch 16-bit */ + if (frame_bytes == 0 || frame_bytes > 512) { + frame_bytes = 192; + } + + void *buf = NULL; + if (k_mem_slab_alloc(&uac2_tx_slab, &buf, K_NO_WAIT) == 0) { + bool have_data = false; +#if defined(CONFIG_COMP_BT_AUDIO) + if (g_status.audio_route == SOF_AUDIO_ROUTE_USB_BT) { + have_data = bt_audio_peek_capture_data(buf, frame_bytes); + } else +#endif + { + have_data = usb_audio_peek_capture_data(buf, frame_bytes); + } + + if (have_data) { + if (g_status.capture_mute) { + memset(buf, 0, frame_bytes); + } + int ret = usbd_uac2_send(dev, CAPTURE_TERM_ID, buf, frame_bytes); + if (ret == 0) { +#if defined(CONFIG_COMP_BT_AUDIO) + if (g_status.audio_route == SOF_AUDIO_ROUTE_USB_BT) { + bt_audio_consume_capture_data(frame_bytes); + } else +#endif + { + usb_audio_consume_capture_data(frame_bytes); + } + } else { + static uint32_t s_send_fail_cnt; + s_send_fail_cnt++; + if (s_send_fail_cnt <= 10 || s_send_fail_cnt % 100 == 0) { + LOG_WRN("[UAC2 SEND RET %d (%u)] data retained in ring buffer", ret, s_send_fail_cnt); + } + k_mem_slab_free(&uac2_tx_slab, buf); + } + } else { + k_mem_slab_free(&uac2_tx_slab, buf); + } + } else { + static uint32_t s_slab_alloc_fails; + s_slab_alloc_fails++; + if (s_slab_alloc_fails <= 10 || s_slab_alloc_fails % 100 == 0) { + LOG_WRN("[UAC2 TX SLAB FULL %u] Failed to allocate TX slab block!", s_slab_alloc_fails); + } + } + } +} + +void sof_uac2_terminal_update_cb(const struct device *dev, uint8_t terminal, + bool enabled, bool microframes, void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(microframes); + ARG_UNUSED(user_data); + + LOG_INF("[UAC2 Terminal] update: terminal %u, enabled %d (PB expected %u, CAP expected %u)", + terminal, enabled, PLAYBACK_TERM_ID, CAPTURE_TERM_ID); + + if (terminal == PLAYBACK_TERM_ID) { + g_status.playback_active = enabled; + sof_static_pipeline_trigger_by_uac2_term(terminal, enabled); + } else if (terminal == CAPTURE_TERM_ID) { + g_status.capture_active = enabled; + sof_static_pipeline_trigger_by_uac2_term(terminal, enabled); + } +} + +void *sof_uac2_get_recv_buf(const struct device *dev, uint8_t terminal, + uint16_t size, void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(terminal); + ARG_UNUSED(user_data); + + void *buf = NULL; + if (k_mem_slab_alloc(&uac2_rx_slab, &buf, K_NO_WAIT) != 0) { + return NULL; + } + return buf; +} + +void sof_uac2_data_recv_cb(const struct device *dev, uint8_t terminal, + void *buf, uint16_t size, void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(terminal); + ARG_UNUSED(user_data); + + static uint32_t s_rx_pkt_cnt; + s_rx_pkt_cnt++; + if (s_rx_pkt_cnt == 1 || s_rx_pkt_cnt % 1000 == 0) { + LOG_INF("[SOF UAC2] Received playback packet #%u, size %u bytes", s_rx_pkt_cnt, size); + } + + if (buf && size > 0) { +#if defined(CONFIG_COMP_BT_AUDIO) + if (g_status.audio_route == SOF_AUDIO_ROUTE_USB_BT) { + bt_audio_feed_playback_data(buf, size); + } else +#endif + { + usb_audio_feed_playback_data(buf, size); + } + } + if (buf) { + k_mem_slab_free(&uac2_rx_slab, buf); + } +} + +void sof_uac2_buf_release(const struct device *dev, uint8_t terminal, + void *buf, void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(terminal); + ARG_UNUSED(user_data); + + if (buf) { + k_mem_slab_free(&uac2_tx_slab, buf); + } +} + +uint32_t sof_uac2_get_sample_rate(const struct device *dev, uint8_t clock_id, + void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(clock_id); + ARG_UNUSED(user_data); + + return g_status.sample_rate ? g_status.sample_rate : 48000; +} + +int sof_uac2_set_sample_rate(const struct device *dev, uint8_t clock_id, + uint32_t rate, void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(clock_id); + ARG_UNUSED(user_data); + + LOG_INF("Host set sample rate: %u Hz", rate); + g_status.sample_rate = rate; + sof_static_set_sample_rate(rate); + usb_audio_set_playback_rate(rate); + usb_audio_set_capture_rate(rate); + return 0; +} + +uint32_t sof_uac2_feedback_cb(const struct device *dev, uint8_t terminal, + void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(terminal); + ARG_UNUSED(user_data); + + uint32_t rate = g_status.sample_rate ? g_status.sample_rate : 48000; + /* Q16.16 feedback format */ + return ((rate / 1000) << 14); +} + +int sof_uac2_set_feature_mute(const struct device *dev, uint8_t entity_id, + uint8_t channel, bool mute, void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(channel); + ARG_UNUSED(user_data); + + LOG_INF("Host set mute/switch: entity=%u, ch=%u, mute=%d", entity_id, channel, mute); + if (entity_id == PLAYBACK_FU_ID) { + g_status.playback_mute = mute; + } else if (entity_id == PLAYBACK_EQ_FU_ID) { + g_status.eq_playback_bypassed = mute; + } else if (entity_id == PLAYBACK_DRC_FU_ID) { + g_status.drc_playback_bypassed = mute; + } else if (entity_id == CAPTURE_TDFB_FU_ID) { + g_status.tdfb_capture_bypassed = mute; + } else if (entity_id == CAPTURE_EQ_FU_ID) { + g_status.eq_capture_bypassed = mute; + } else if (entity_id == CAPTURE_FU_ID) { + g_status.capture_mute = mute; + } + + sof_static_kcontrol_set_by_uac2(entity_id, channel, mute ? 1 : 0, false); + return 0; +} + +int sof_uac2_get_feature_mute(const struct device *dev, uint8_t entity_id, + uint8_t channel, bool *mute, void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(channel); + ARG_UNUSED(user_data); + + if (!mute) { + return -EINVAL; + } + + if (entity_id == PLAYBACK_FU_ID) { + *mute = g_status.playback_mute; + return 0; + } else if (entity_id == CAPTURE_FU_ID) { + *mute = g_status.capture_mute; + return 0; + } + + int32_t val = 0; + int ret = sof_static_kcontrol_get_by_uac2(entity_id, channel, &val, false); + if (ret == 0) { + *mute = (val != 0); + } else { + *mute = false; + } + return 0; +} + +int sof_uac2_set_feature_volume(const struct device *dev, uint8_t entity_id, + uint8_t channel, int16_t volume, void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(channel); + ARG_UNUSED(user_data); + + LOG_INF("Host set volume: entity=%u, ch=%u, vol=%d (0x%04x, %d dB)", + entity_id, channel, volume, (uint16_t)volume, volume / 256); + + if (entity_id == PLAYBACK_FU_ID) { + g_status.playback_volume = volume; + } else if (entity_id == CAPTURE_FU_ID) { + g_status.capture_volume = volume; + } + + return sof_static_kcontrol_set_by_uac2(entity_id, channel, volume, true); +} + +int sof_uac2_get_feature_volume(const struct device *dev, uint8_t entity_id, + uint8_t channel, int16_t *volume, void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(channel); + ARG_UNUSED(user_data); + + if (!volume) { + return -EINVAL; + } + if (entity_id == PLAYBACK_FU_ID) { + *volume = g_status.playback_volume; + } else if (entity_id == CAPTURE_FU_ID) { + *volume = g_status.capture_volume; + } else { + *volume = 0; + } + return 0; +} + +static const struct uac2_ops g_uac2_ops = { + .sof_cb = sof_uac2_sof_cb, + .terminal_update_cb = sof_uac2_terminal_update_cb, + .get_recv_buf = sof_uac2_get_recv_buf, + .data_recv_cb = sof_uac2_data_recv_cb, + .buf_release_cb = sof_uac2_buf_release, + .feedback_cb = sof_uac2_feedback_cb, + .get_sample_rate = sof_uac2_get_sample_rate, + .set_sample_rate = sof_uac2_set_sample_rate, + .set_feature_mute = sof_uac2_set_feature_mute, + .get_feature_mute = sof_uac2_get_feature_mute, + .set_feature_volume = sof_uac2_set_feature_volume, + .get_feature_volume = sof_uac2_get_feature_volume, +}; + +const struct uac2_ops *sof_get_uac2_ops(void) +{ + return &g_uac2_ops; +} +#endif /* CONFIG_USBD_UAC2 */ + +#if defined(CONFIG_PLATFORM_ESP32P4) +extern const struct sof_static_topology g_esp32p4_static_topology; +#elif defined(CONFIG_PLATFORM_ESP32C6) +extern const struct sof_static_topology g_esp32c6_static_topology; +#endif + +int sof_static_pipelines_init(struct sof *sof) +{ + ARG_UNUSED(sof); + int ret = 0; +#if defined(CONFIG_PLATFORM_ESP32P4) + ret = sof_static_topology_init(&g_esp32p4_static_topology); +#elif defined(CONFIG_PLATFORM_ESP32C6) + ret = sof_static_topology_init(&g_esp32c6_static_topology); +#endif + if (ret < 0) + return ret; + + uint8_t mac[6] = {0}; + extern int esp_efuse_mac_get_default(uint8_t *mac); + esp_efuse_mac_get_default(mac); + LOG_INF("SoC MAC: %02x:%02x:%02x:%02x:%02x:%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + /* Pallas (0x17) on P4 or XIAO (0x20) on C6 defaults to Master Tx; + * Ceres / Waveshare C6-Zero (0x24) defaults to Slave Rx. + */ + if (mac[5] == 0x17 || mac[0] == 0x10) { + g_status.clock_mode = SOF_CLOCK_MASTER; + } else { + g_status.clock_mode = SOF_CLOCK_SLAVE; + } + + return sof_static_pipeline_set_clock_mode(SOF_AUDIO_IF_I2S, g_status.clock_mode); +} + +int sof_static_pipeline_set_clock_mode(enum sof_audio_interface iface, enum sof_clock_mode mode) +{ + g_status.active_interface = iface; + g_status.clock_mode = mode; + LOG_INF("Set interface %d clock mode to %s", iface, + mode == SOF_CLOCK_MASTER ? "MASTER" : (mode == SOF_CLOCK_DMIC ? "DMIC (SLAVE TX)" : "SLAVE")); + + uint32_t sof_format = 0; + if (iface == SOF_AUDIO_IF_I2S) { + sof_format = (mode == SOF_CLOCK_MASTER) ? + (SOF_DAI_FMT_I2S | SOF_DAI_FMT_CBC_CFC) : + (SOF_DAI_FMT_I2S | SOF_DAI_FMT_CBP_CFP); + + const struct device *dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(dai_i2s0)); + if (dev && device_is_ready(dev)) { + uint32_t rate = g_status.sample_rate ? g_status.sample_rate : 48000; + uint32_t channels = 2; + uint32_t word_size = 16; + uint32_t bytes_per_sample = word_size / 8; + uint32_t frames_per_period = rate / 1000; + if (frames_per_period == 0) { + frames_per_period = 48; + } + struct dai_config cfg = { + .type = DAI_ESP32_I2S, + .dai_index = 0, + .channels = channels, + .rate = rate, + .format = (mode == SOF_CLOCK_MASTER) ? + (DAI_PROTO_I2S | DAI_CBC_CFC) : + (DAI_PROTO_I2S | DAI_CBP_CFP), + .word_size = word_size, + .block_size = frames_per_period * channels * bytes_per_sample, + }; + int ret = dai_config_set(dev, &cfg, NULL, 0); + if (ret < 0) { + LOG_ERR("Failed to set DAI I2S config: %d", ret); + return ret; + } + LOG_INF("DAI I2S hardware successfully switched to %s mode", + mode == SOF_CLOCK_MASTER ? "MASTER" : "SLAVE"); + } else { + LOG_WRN("DAI I2S device not ready or not found"); + } + } else if (iface == SOF_AUDIO_IF_PDM) { + sof_format = (mode == SOF_CLOCK_MASTER) ? + (SOF_DAI_FMT_PDM | SOF_DAI_FMT_CBC_CFC) : + (SOF_DAI_FMT_PDM | SOF_DAI_FMT_CBP_CFP); + +#if DT_NODE_EXISTS(DT_NODELABEL(dai_pdm0)) + const struct device *dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(dai_pdm0)); +#else + const struct device *dev = NULL; +#endif + if (dev && device_is_ready(dev)) { + uint32_t rate = g_status.sample_rate ? g_status.sample_rate : 48000; + uint32_t channels = 2; + uint32_t word_size = 16; + uint32_t bytes_per_sample = word_size / 8; + uint32_t frames_per_period = rate / 1000; + if (frames_per_period == 0) { + frames_per_period = 48; + } + struct dai_config cfg = { + .type = DAI_ESP32_PDM, + .dai_index = 1, + .channels = channels, + .rate = rate, + .format = (mode == SOF_CLOCK_MASTER) ? + (DAI_PROTO_PDM | DAI_CBC_CFC) : + (DAI_PROTO_PDM | DAI_CBP_CFP), + .options = (mode == SOF_CLOCK_DMIC) ? 1 : 0, + .word_size = word_size, + .block_size = frames_per_period * channels * bytes_per_sample, + }; + int ret = dai_config_set(dev, &cfg, NULL, 0); + if (ret < 0) { + LOG_ERR("Failed to set DAI PDM config: %d", ret); + return ret; + } + LOG_INF("DAI PDM hardware successfully switched to %s mode", + mode == SOF_CLOCK_MASTER ? "MASTER" : (mode == SOF_CLOCK_DMIC ? "DMIC INJECTOR" : "SLAVE")); + } else { + LOG_WRN("DAI PDM device not ready or not found"); + } + } + + /* Update static pipeline DAI components dynamically from topology */ + uint32_t target_dai_type = (iface == SOF_AUDIO_IF_PDM) ? SOF_DAI_ESP32_PDM : SOF_DAI_ESP32_I2S; + uint32_t target_dai_index = (iface == SOF_AUDIO_IF_PDM) ? 1 : 0; + + const struct sof_static_topology *topo = sof_static_topology_get(); + if (topo) { + for (size_t i = 0; i < topo->num_comps; i++) { + const struct sof_static_comp *c = &topo->comps[i]; + if (c->type == SOF_STATIC_COMP_DAI) { + struct comp_dev *dev = sof_static_comp_get(c->id); + if (!dev) + continue; + struct dai_data *dd = comp_get_drvdata(dev); + if (!dd) + continue; + if (dd->dai) { + dai_put(dd->dai); + } + dd->dai = dai_get(target_dai_type, target_dai_index, DAI_CREAT); + dd->ipc_config.type = target_dai_type; + dd->ipc_config.dai_index = target_dai_index; + dd->ipc_config.format = sof_format; + dev->state = COMP_STATE_READY; + } + } + } + + /* Reset and re-prepare pipelines 1 and 2 with updated DAI configuration */ + for (uint32_t pid = 1; pid <= 2; pid++) { + struct pipeline *pipe = sof_static_pipeline_get(pid); + if (!pipe) + continue; + + struct comp_dev *host_or_sched = pipe->sched_comp ? pipe->sched_comp : pipe->source_comp; + if (!host_or_sched) + continue; + + pipeline_reset(pipe, host_or_sched); + + struct sof_ipc_pcm_params prms; + memset(&prms, 0, sizeof(prms)); + uint32_t rate = g_status.sample_rate ? g_status.sample_rate : 48000; + uint32_t channels = 2; + prms.params.rate = rate; + prms.params.channels = channels; + prms.params.frame_fmt = host_or_sched->ipc_config.frame_fmt; + uint32_t sbytes = (prms.params.frame_fmt == SOF_IPC_FRAME_FLOAT || + prms.params.frame_fmt == SOF_IPC_FRAME_S32_LE) ? 4 : 2; + prms.params.sample_container_bytes = sbytes; + prms.params.sample_valid_bytes = sbytes; + prms.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + uint32_t frames_per_ms = rate / 1000; + if (frames_per_ms == 0) { + frames_per_ms = 48; + } + prms.params.host_period_bytes = frames_per_ms * channels * sbytes; + prms.comp_id = dev_comp_id(host_or_sched); + prms.params.direction = (pid == 1) ? SOF_IPC_STREAM_PLAYBACK : SOF_IPC_STREAM_CAPTURE; + prms.params.chmap[0] = SOF_CHMAP_FL; + prms.params.chmap[1] = SOF_CHMAP_FR; + + pipeline_params(pipe, host_or_sched, &prms); + pipeline_prepare(pipe, host_or_sched); + } + + return 0; +} + +int sof_static_pipeline_set_eq_bypass(bool is_capture, bool bypass) +{ + if (is_capture) { + g_status.eq_capture_bypassed = bypass; + return sof_static_kcontrol_set(5, bypass ? 0 : 1); + } else { + g_status.eq_playback_bypassed = bypass; + return sof_static_kcontrol_set(2, bypass ? 0 : 1); + } +} + +int sof_static_pipeline_set_drc_bypass(bool bypass) +{ + g_status.drc_playback_bypassed = bypass; + return sof_static_kcontrol_set(3, bypass ? 0 : 1); +} + +int sof_static_pipeline_set_tdfb_bypass(bool bypass) +{ + g_status.tdfb_capture_bypassed = bypass; + return sof_static_kcontrol_set(4, bypass ? 0 : 1); +} + +int sof_static_pipeline_set_dmic_injector(bool enable) +{ + if (enable) { + return sof_static_pipeline_set_clock_mode(SOF_AUDIO_IF_PDM, SOF_CLOCK_DMIC); + } else { + if (g_status.clock_mode == SOF_CLOCK_DMIC) { + return sof_static_pipeline_set_clock_mode(SOF_AUDIO_IF_PDM, SOF_CLOCK_MASTER); + } + return 0; + } +} + +int sof_static_pipeline_set_volume(uint32_t pipeline_id, int16_t volume) +{ +#if DT_NODE_EXISTS(DT_NODELABEL(i2s_fu)) + if (pipeline_id == 1) { + return sof_uac2_set_feature_volume(NULL, PLAYBACK_FU_ID, 0, volume, NULL); + } else if (pipeline_id == 2) { + return sof_uac2_set_feature_volume(NULL, CAPTURE_FU_ID, 0, volume, NULL); + } + return -EINVAL; +#else + if (pipeline_id == 1) { + g_status.playback_volume = volume; + return sof_static_kcontrol_set(1, (int32_t)volume); + } else if (pipeline_id == 2) { + g_status.capture_volume = volume; + return sof_static_kcontrol_set(2, (int32_t)volume); + } + return -EINVAL; +#endif +} + +int sof_static_pipeline_set_mute(uint32_t pipeline_id, bool mute) +{ +#if DT_NODE_EXISTS(DT_NODELABEL(i2s_fu)) + if (pipeline_id == 1) { + return sof_uac2_set_feature_mute(NULL, PLAYBACK_FU_ID, 0, mute, NULL); + } else if (pipeline_id == 2) { + return sof_uac2_set_feature_mute(NULL, CAPTURE_FU_ID, 0, mute, NULL); + } + return -EINVAL; +#else + if (pipeline_id == 1) { + g_status.playback_mute = mute; + return 0; + } else if (pipeline_id == 2) { + g_status.capture_mute = mute; + return 0; + } + return -EINVAL; +#endif +} + +int sof_static_pipeline_get_volume(uint32_t pipeline_id, int16_t *volume) +{ + if (!volume) { + return -EINVAL; + } +#if DT_NODE_EXISTS(DT_NODELABEL(i2s_fu)) + if (pipeline_id == 1) { + return sof_uac2_get_feature_volume(NULL, PLAYBACK_FU_ID, 0, volume, NULL); + } else if (pipeline_id == 2) { + return sof_uac2_get_feature_volume(NULL, CAPTURE_FU_ID, 0, volume, NULL); + } + return -EINVAL; +#else + if (pipeline_id == 1) { + *volume = g_status.playback_volume; + return 0; + } else if (pipeline_id == 2) { + *volume = g_status.capture_volume; + return 0; + } + return -EINVAL; +#endif +} + +int sof_static_pipeline_get_mute(uint32_t pipeline_id, bool *mute) +{ + if (!mute) { + return -EINVAL; + } +#if DT_NODE_EXISTS(DT_NODELABEL(i2s_fu)) + if (pipeline_id == 1) { + return sof_uac2_get_feature_mute(NULL, PLAYBACK_FU_ID, 0, mute, NULL); + } else if (pipeline_id == 2) { + return sof_uac2_get_feature_mute(NULL, CAPTURE_FU_ID, 0, mute, NULL); + } + return -EINVAL; +#else + if (pipeline_id == 1) { + *mute = g_status.playback_mute; + return 0; + } else if (pipeline_id == 2) { + *mute = g_status.capture_mute; + return 0; + } + return -EINVAL; +#endif +} + +int sof_static_pipeline_set_playback_active(bool start) +{ + g_status.playback_active = start; +#if DT_NODE_EXISTS(DT_NODELABEL(i2s_out_terminal)) + return sof_static_pipeline_trigger_by_uac2_term(PLAYBACK_TERM_ID, start); +#else + return sof_static_pipeline_trigger(1, start); +#endif +} + +int sof_static_pipeline_set_capture_active(bool start) +{ + g_status.capture_active = start; +#if DT_NODE_EXISTS(DT_NODELABEL(i2s_in_terminal)) + return sof_static_pipeline_trigger_by_uac2_term(CAPTURE_TERM_ID, start); +#else + return sof_static_pipeline_trigger(2, start); +#endif +} + +int sof_static_pipeline_set_bt_stream(bool enable) +{ + g_status.bt_stream_enabled = enable; +#if defined(CONFIG_COMP_BT_AUDIO) + if (enable) { + return bt_service_start_broadcast(); + } else { + return bt_service_stop_broadcast(); + } +#else + return 0; +#endif +} + +int sof_static_pipeline_set_route(enum sof_audio_route route) +{ + g_status.audio_route = route; + LOG_INF("SOF pipeline audio route switched to %s", + route == SOF_AUDIO_ROUTE_USB_DAI ? "USB <-> DAI" : + (route == SOF_AUDIO_ROUTE_BT_DAI ? "BT <-> DAI" : "USB <-> BT")); + return 0; +} + +void sof_static_pipeline_get_status(struct sof_static_pipeline_status *status) +{ + if (!status) + return; + + *status = g_status; + + int32_t val = 0; + if (sof_static_kcontrol_get(2, &val) == 0) + status->eq_playback_bypassed = (val == 0); + if (sof_static_kcontrol_get(3, &val) == 0) + status->drc_playback_bypassed = (val == 0); + if (sof_static_kcontrol_get(4, &val) == 0) + status->tdfb_capture_bypassed = (val == 0); + if (sof_static_kcontrol_get(5, &val) == 0) + status->eq_capture_bypassed = (val == 0); + if (sof_static_kcontrol_get(8, &val) == 0) + status->bt_stream_enabled = (val != 0); + if (sof_static_kcontrol_get(10, &val) == 0) + status->audio_route = (enum sof_audio_route)val; +} diff --git a/src/audio/pipeline/static_pipeline_loader.c b/src/audio/pipeline/static_pipeline_loader.c index dcd7ae88eb0f..b2ffdcd9cc60 100644 --- a/src/audio/pipeline/static_pipeline_loader.c +++ b/src/audio/pipeline/static_pipeline_loader.c @@ -15,6 +15,10 @@ */ #include +#include +#if defined(CONFIG_COMP_BT_AUDIO) +#include +#endif #include #include #include @@ -210,11 +214,33 @@ static int send_comp_config(struct comp_dev *dev, const void *abi_blob, size_t a return -EINVAL; } +#if CONFIG_IPC_MAJOR_4 /* Pass raw configuration payload directly following ABI header to module adapter */ const uint8_t *raw_data = (const uint8_t *)abi_blob + sizeof(struct sof_abi_hdr); int ret = mod->dev->drv->adapter_ops->set_configuration(mod, 0, MODULE_CFG_FRAGMENT_SINGLE, hdr->size, raw_data, hdr->size, NULL, 0); +#else + size_t cdata_size = sizeof(struct sof_ipc_ctrl_data) + sizeof(struct sof_abi_hdr) + hdr->size; + struct sof_ipc_ctrl_data *cdata = rzalloc(SOF_MEM_FLAG_USER, cdata_size); + if (!cdata) { + LOG_ERR("Failed to allocate ctrl_data size %zu", cdata_size); + return -ENOMEM; + } + + cdata->cmd = SOF_CTRL_CMD_BINARY; + cdata->num_elems = hdr->size; + cdata->data[0].magic = hdr->magic; + cdata->data[0].type = hdr->type; + cdata->data[0].size = hdr->size; + cdata->data[0].abi = hdr->abi; + memcpy_s(cdata->data[0].data, hdr->size, (const uint8_t *)abi_blob + sizeof(struct sof_abi_hdr), hdr->size); + + int ret = mod->dev->drv->adapter_ops->set_configuration(mod, 0, MODULE_CFG_FRAGMENT_SINGLE, + hdr->size, (const uint8_t *)cdata, + hdr->size, NULL, 0); + rfree(cdata); +#endif /* Log configuration status for diagnostics */ if (ret < 0) @@ -447,7 +473,22 @@ static struct comp_dev *sof_static_init_dai_comp(const struct sof_static_comp *c struct dai_data *dd = comp_get_drvdata(dev); if (dd) { +#if CONFIG_IPC_MAJOR_4 int ret_dai = comp_dai_config(dd, dev, &dai_cfg, &copier_cfg); +#else + struct sof_ipc_dai_config spec_cfg = { + .type = cdesc->ep.dai.dai_type, + .dai_index = cdesc->ep.dai.dai_index, + .format = cdesc->ep.dai.format, + .flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS, + }; + int ret_set = dai_set_config(dd->dai, &dai_cfg, &spec_cfg, sizeof(spec_cfg)); + if (ret_set < 0) { + LOG_ERR("dai_set_config failed %d for comp %u", ret_set, cdesc->id); + return NULL; + } + int ret_dai = comp_dai_config(dev, &dai_cfg, &spec_cfg); +#endif if (ret_dai < 0) { LOG_ERR("comp_dai_config failed %d for comp %u", ret_dai, cdesc->id); return NULL; @@ -876,8 +917,10 @@ static int sof_static_topology_prepare_comps(const struct sof_static_topology *t if (!dev) continue; - /* Only prepare components currently in READY state */ + /* Only prepare components currently in READY state that are connected to buffers */ if (dev->state == COMP_STATE_READY) { + if (list_is_empty(&dev->bsource_list) && list_is_empty(&dev->bsink_list)) + continue; const struct sof_static_comp *cdesc = &topo->comps[i]; const struct sof_static_pipeline_desc *pdesc = sof_static_find_pipeline_desc(topo, cdesc->pipeline_id); @@ -1109,6 +1152,9 @@ int sof_static_topology_init(const struct sof_static_topology *topo) goto err_unwind; } + /* Publish topology as active so controls can access topology metadata */ + s_active_topo = topo; + /* Stage 7: Initialize kcontrols with default values */ ret = sof_static_topology_init_controls(topo); if (ret < 0) { @@ -1116,14 +1162,12 @@ int sof_static_topology_init(const struct sof_static_topology *topo) goto err_unwind; } - /* Publish topology as active only after all construction stages succeed */ - s_active_topo = topo; - LOG_INF("Static audio topology initialized successfully (%zu pipelines, %zu comps, %zu buffers, %zu controls)", topo->num_pipelines, topo->num_comps, topo->num_buffers, topo->num_controls); return 0; err_unwind: + s_active_topo = NULL; sof_static_topology_unwind(); return ret; } @@ -1219,6 +1263,40 @@ int sof_static_kcontrol_set(uint32_t ctrl_id, int32_t val) return -ENOENT; } + if (ctl->id == 7 || (ctl->name && !strcmp(ctl->name, "DMIC Injector Switch"))) { + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' set to %d", ctl->id, ctl->name, val); + return sof_static_pipeline_set_dmic_injector(val != 0); + } + + if (ctl->id == 8 || (ctl->name && !strcmp(ctl->name, "BT Audio Stream Switch"))) { + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' set to %d", ctl->id, ctl->name, val); + return sof_static_pipeline_set_bt_stream(val != 0); + } + + if (ctl->id == 9 || (ctl->name && !strcmp(ctl->name, "BT Stream Volume"))) { + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' set to %d", ctl->id, ctl->name, val); + return 0; + } + + if (ctl->id == 10 || (ctl->name && !strcmp(ctl->name, "Audio Endpoint Route"))) { + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' set to %d", ctl->id, ctl->name, val); + return sof_static_pipeline_set_route((enum sof_audio_route)val); + } + + if (ctl->id == 14 || (ctl->name && !strcmp(ctl->name, "BT Audio Format"))) { + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' set to %d", ctl->id, ctl->name, val); +#if defined(CONFIG_COMP_BT_AUDIO) + return bt_service_set_format((enum bt_audio_format)val); +#else + return 0; +#endif + } + /* * Custom kcontrol callback handler: * Allows board- or platform-specific controls (such as hardware clock diff --git a/src/audio/selector/selector.c b/src/audio/selector/selector.c index e68845ad99ad..fabaed1ff102 100644 --- a/src/audio/selector/selector.c +++ b/src/audio/selector/selector.c @@ -1207,7 +1207,7 @@ SOF_MODULE_INIT(selector, sys_comp_module_selector_interface_init); #endif -#if CONFIG_STATIC_PIPELINE +#if CONFIG_STATIC_PIPELINE && CONFIG_IPC_MAJOR_4 #include #include @@ -1216,7 +1216,6 @@ static struct comp_dev *sel_static_create(const struct comp_driver *drv, const struct sof_static_comp *cdesc, uint32_t period_us) { -#if CONFIG_IPC_MAJOR_4 /* * In IPC4 mode, selector module_adapter init expects * struct sof_selector_avs_ipc4_config containing base_cfg and output_format. @@ -1236,19 +1235,6 @@ static struct comp_dev *sel_static_create(const struct comp_driver *drv, .data = (const uint8_t *)&sel_cfg, }; return drv->ops.create(drv, cfg, &sel_spec); -#else - uint16_t ch = cdesc->caps.max_channels ? cdesc->caps.max_channels : 2; - struct sof_sel_config sel_cfg = { - .in_channels_count = ch, - .out_channels_count = ch, - .sel_channel = 0, - }; - struct ipc_config_process sel_spec = { - .size = sizeof(sel_cfg), - .data = (const uint8_t *)&sel_cfg, - }; - return drv->ops.create(drv, cfg, &sel_spec); -#endif } static int sel_static_apply_enum(struct comp_dev *dev, uint32_t channel, int32_t val) @@ -1295,7 +1281,6 @@ static int sel_static_apply_enum(struct comp_dev *dev, uint32_t channel, int32_t /* Apply validated channel selection to runtime configuration */ cd->config.sel_channel = (uint32_t)val; -#if CONFIG_IPC_MAJOR_4 /* * In IPC4 mode, routing is controlled by the Q10 coefficient matrix cd->coeffs_config. * If channel indicates a single output channel (channel < out_channels_count), route @@ -1342,17 +1327,14 @@ static int sel_static_apply_enum(struct comp_dev *dev, uint32_t channel, int32_t } else { cd->passthrough = false; } -#endif return 0; } -#if CONFIG_IPC_MAJOR_4 static struct sof_static_module_ops sel4_static_ops = { .uuid = &selector4_uuid, .create = sel_static_create, .apply_enum = sel_static_apply_enum, }; DECLARE_STATIC_MODULE_OPS(selector4, &sel4_static_ops); -#endif -#endif /* CONFIG_STATIC_PIPELINE */ +#endif /* CONFIG_STATIC_PIPELINE && CONFIG_IPC_MAJOR_4 */ diff --git a/src/audio/usb_audio.c b/src/audio/usb_audio.c new file mode 100644 index 000000000000..b9c03940ba63 --- /dev/null +++ b/src/audio/usb_audio.c @@ -0,0 +1,562 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(comp_usb_audio, CONFIG_SOF_LOG_LEVEL); + +SOF_DEFINE_REG_UUID(usb_audio); +DECLARE_TR_CTX(usb_audio_tr, SOF_UUID(usb_audio_uuid), LOG_LEVEL_INFO); + +static struct usb_audio_data *g_playback_data; +static struct usb_audio_data *g_capture_data; + +void usb_audio_set_playback_rate(uint32_t rate) +{ + if (g_playback_data) { + g_playback_data->sample_rate = rate; + } +} + +void usb_audio_set_capture_rate(uint32_t rate) +{ + if (g_capture_data) { + g_capture_data->sample_rate = rate; + } +} + +static bool s_tone_enabled; +void usb_audio_set_tone(bool enable) +{ + s_tone_enabled = enable; +} + +bool usb_audio_get_tone(void) +{ + return s_tone_enabled; +} + +void usb_audio_feed_playback_data(const void *src, size_t bytes) +{ + if (!g_playback_data || !g_playback_data->active) { + return; + } + + struct usb_audio_ring_buffer *ring = &g_playback_data->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + uint32_t avail = USB_AUDIO_RING_BUFFER_SIZE - ring->count; + if (bytes > avail) { + bytes = avail; + } + + const uint8_t *s = (const uint8_t *)src; + for (size_t i = 0; i < bytes; i++) { + ring->buf[ring->head] = s[i]; + ring->head = (ring->head + 1) % USB_AUDIO_RING_BUFFER_SIZE; + } + ring->count += bytes; + + k_spin_unlock(&ring->lock, key); +} + +size_t usb_audio_fetch_capture_data(void *dst, size_t bytes) +{ + if (!g_capture_data || !g_capture_data->active) { + memset(dst, 0, bytes); + return bytes; + } + + struct usb_audio_ring_buffer *ring = &g_capture_data->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + /* Prebuffer at stream start to provide a stable jitter margin */ + if (!g_capture_data->started) { + if (ring->count < USB_AUDIO_PREBUFFER_BYTES) { + k_spin_unlock(&ring->lock, key); + memset(dst, 0, bytes); + return bytes; + } + g_capture_data->started = true; + } + + uint32_t to_read = (bytes > ring->count) ? ring->count : bytes; + uint8_t *d = (uint8_t *)dst; + + for (size_t i = 0; i < to_read; i++) { + d[i] = ring->buf[ring->tail]; + ring->tail = (ring->tail + 1) % USB_AUDIO_RING_BUFFER_SIZE; + } + ring->count -= to_read; + + if (to_read < bytes) { + static uint32_t s_cap_underrun_cnt; + s_cap_underrun_cnt++; + if (s_cap_underrun_cnt <= 5 || s_cap_underrun_cnt % 100 == 0) { + LOG_WRN("[CAP UNDERRUN %u] to_read=%u < bytes=%u, ring_count=%u", + s_cap_underrun_cnt, to_read, bytes, ring->count); + } + memset(d + to_read, 0, bytes - to_read); + } + + k_spin_unlock(&ring->lock, key); + return bytes; +} + +bool usb_audio_peek_capture_data(void *dst, size_t bytes) +{ + if (!g_capture_data || !g_capture_data->active) { + memset(dst, 0, bytes); + return false; + } + + struct usb_audio_ring_buffer *ring = &g_capture_data->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + /* Prebuffer at stream start to provide a stable jitter margin */ + if (!g_capture_data->started) { + if (ring->count < USB_AUDIO_PREBUFFER_BYTES) { + k_spin_unlock(&ring->lock, key); + memset(dst, 0, bytes); + return false; + } + g_capture_data->started = true; + } + + uint32_t to_read = (bytes > ring->count) ? ring->count : bytes; + uint8_t *d = (uint8_t *)dst; + uint32_t tail = ring->tail; + + for (size_t i = 0; i < to_read; i++) { + d[i] = ring->buf[tail]; + tail = (tail + 1) % USB_AUDIO_RING_BUFFER_SIZE; + } + + if (to_read < bytes) { + static uint32_t s_cap_underrun_cnt; + s_cap_underrun_cnt++; + if (s_cap_underrun_cnt <= 5 || s_cap_underrun_cnt % 100 == 0) { + LOG_WRN("[CAP UNDERRUN %u] to_read=%u < bytes=%u, ring_count=%u", + s_cap_underrun_cnt, to_read, bytes, ring->count); + } + memset(d + to_read, 0, bytes - to_read); + } + + k_spin_unlock(&ring->lock, key); + return true; +} + +void usb_audio_consume_capture_data(size_t bytes) +{ + if (!g_capture_data || !g_capture_data->active) { + return; + } + + struct usb_audio_ring_buffer *ring = &g_capture_data->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + uint32_t to_consume = (bytes > ring->count) ? ring->count : bytes; + ring->tail = (ring->tail + to_consume) % USB_AUDIO_RING_BUFFER_SIZE; + ring->count -= to_consume; + + k_spin_unlock(&ring->lock, key); +} + +static struct comp_dev *usb_audio_new(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const void *spec) +{ + struct comp_dev *dev; + struct usb_audio_data *uad; + + comp_cl_info(drv, "usb_audio_new()"); + + dev = comp_alloc(drv, sizeof(*dev)); + if (!dev) { + return NULL; + } + + dev->ipc_config = *config; + + uad = rzalloc(SOF_MEM_FLAG_KERNEL, sizeof(*uad)); + if (!uad) { + rfree(dev); + return NULL; + } + + comp_set_drvdata(dev, uad); + + uad->sample_rate = 48000; + uad->channels = 2; + uad->frame_bytes = 4; + uad->period_bytes = (uad->sample_rate / 1000) * uad->frame_bytes; + + if (config->pipeline_id == 1 || config->id == 1) { + g_playback_data = uad; + } else { + g_capture_data = uad; + } + + dev->state = COMP_STATE_READY; + return dev; +} + +static void usb_audio_free(struct comp_dev *dev) +{ + struct usb_audio_data *uad = comp_get_drvdata(dev); + + if (uad == g_playback_data) { + g_playback_data = NULL; + } + if (uad == g_capture_data) { + g_capture_data = NULL; + } + + rfree(uad); + rfree(dev); +} + +static int usb_audio_params(struct comp_dev *dev, + struct sof_ipc_stream_params *params) +{ + struct usb_audio_data *uad = comp_get_drvdata(dev); + + uad->sample_rate = params->rate; + uad->channels = params->channels; + uad->frame_bytes = params->sample_container_bytes * params->channels; + uint32_t frames_per_period = (params->rate ? params->rate : 48000) / 1000; + if (frames_per_period == 0) { + frames_per_period = 48; + } + uad->period_bytes = params->host_period_bytes ? params->host_period_bytes : (frames_per_period * uad->frame_bytes); + + if (params->direction == SOF_IPC_STREAM_PLAYBACK) { + g_playback_data = uad; + } else if (params->direction == SOF_IPC_STREAM_CAPTURE) { + g_capture_data = uad; + } + + return 0; +} + +static int usb_audio_trigger(struct comp_dev *dev, int cmd) +{ + struct usb_audio_data *uad = comp_get_drvdata(dev); + + switch (cmd) { + case COMP_TRIGGER_START: + case COMP_TRIGGER_RELEASE: + uad->active = true; + uad->started = false; + dev->state = COMP_STATE_ACTIVE; + break; + case COMP_TRIGGER_STOP: + case COMP_TRIGGER_PAUSE: { + uad->active = false; + uad->started = false; + k_spinlock_key_t key = k_spin_lock(&uad->ring.lock); + uad->ring.head = 0; + uad->ring.tail = 0; + uad->ring.count = 0; + k_spin_unlock(&uad->ring.lock, key); + dev->state = COMP_STATE_READY; + break; + } + default: + break; + } + + return 0; +} + +static int usb_audio_copy(struct comp_dev *dev) +{ + struct usb_audio_data *uad = comp_get_drvdata(dev); + struct comp_buffer *sink = comp_dev_get_first_data_consumer(dev); + struct comp_buffer *source = comp_dev_get_first_data_producer(dev); + + if (sink) { + /* USB Playback Source -> SOF Sink Buffer */ + enum sof_ipc_frame fmt = audio_stream_get_frm_fmt(&sink->stream); + if (fmt == SOF_IPC_FRAME_FLOAT) { + uint32_t free_frames = audio_stream_get_free_frames(&sink->stream); + uint32_t copy_frames = MIN(free_frames, dev->frames ? dev->frames : 48); + + if (copy_frames > 0) { + int16_t s16_buf[96]; + float f_buf[96]; + uint32_t frames_to_copy = MIN(copy_frames, 48); + uint32_t s16_bytes = frames_to_copy * 2 * sizeof(int16_t); + + struct usb_audio_ring_buffer *ring = &uad->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + /* Prebuffer at playback start to provide a stable jitter margin */ + if (!uad->started) { + if (ring->count < USB_AUDIO_PREBUFFER_BYTES) { + k_spin_unlock(&ring->lock, key); + memset(f_buf, 0, frames_to_copy * 2 * sizeof(float)); + audio_stream_copy_from_linear(f_buf, 0, &sink->stream, 0, frames_to_copy * 2); + comp_update_buffer_produce(sink, frames_to_copy * 2 * sizeof(float)); + return 0; + } + uad->started = true; + } + + uint32_t available = ring->count; + uint32_t to_copy = MIN(s16_bytes, available); + + uint8_t *s16_raw = (uint8_t *)s16_buf; + for (size_t i = 0; i < to_copy; i++) { + s16_raw[i] = ring->buf[ring->tail]; + ring->tail = (ring->tail + 1) % USB_AUDIO_RING_BUFFER_SIZE; + } + ring->count -= to_copy; + + if (to_copy < s16_bytes) { + static uint32_t s_pb_underrun_cnt; + s_pb_underrun_cnt++; + if (s_pb_underrun_cnt <= 5 || s_pb_underrun_cnt % 100 == 0) { + LOG_WRN("[PB UNDERRUN %u] to_copy=%u < s16_bytes=%u, ring_count=%u", + s_pb_underrun_cnt, to_copy, s16_bytes, ring->count); + } + memset(s16_raw + to_copy, 0, s16_bytes - to_copy); + } + + k_spin_unlock(&ring->lock, key); + + const float scale = 1.0f / 32768.0f; + for (size_t i = 0; i < frames_to_copy * 2; i++) { + f_buf[i] = (float)s16_buf[i] * scale; + } + + audio_stream_copy_from_linear(f_buf, 0, &sink->stream, 0, frames_to_copy * 2); + comp_update_buffer_produce(sink, frames_to_copy * 2 * sizeof(float)); + } + } else { + uint32_t free_bytes = audio_stream_get_free_bytes(&sink->stream); + if (free_bytes >= uad->period_bytes) { + uint8_t temp_buf[1024]; + uint32_t chunk = MIN(uad->period_bytes, sizeof(temp_buf)); + struct usb_audio_ring_buffer *ring = &uad->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + if (s_tone_enabled) { + /* 1000 Hz pure sine wave, 48 samples per cycle, stereo, amp 2000 */ + static const int16_t sine48[48] = { + 0, 261, 518, 765, 1000, 1218, 1414, 1586, + 1732, 1848, 1932, 1983, 2000, 1983, 1932, 1848, + 1732, 1586, 1414, 1218, 1000, 765, 518, 261, + 0, -261, -518, -765, -1000, -1218, -1414, -1586, + -1732, -1848, -1932, -1983, -2000, -1983, -1932, -1848, + -1732, -1586, -1414, -1218, -1000, -765, -518, -261 + }; + int16_t *out16 = (int16_t *)temp_buf; + uint32_t frames = chunk / (uad->frame_bytes ? uad->frame_bytes : 4); + for (size_t i = 0; i < frames; i++) { + int16_t s = sine48[i % 48]; + out16[2 * i] = s; + out16[2 * i + 1] = s; + } + k_spin_unlock(&ring->lock, key); + audio_stream_copy_from_linear(temp_buf, 0, &sink->stream, 0, + chunk / audio_stream_sample_bytes(&sink->stream)); + comp_update_buffer_produce(sink, chunk); + return 0; + } + + /* Prebuffer at playback start to provide a stable jitter margin */ + if (!uad->started) { + if (ring->count < USB_AUDIO_PREBUFFER_BYTES) { + k_spin_unlock(&ring->lock, key); + memset(temp_buf, 0, chunk); + audio_stream_copy_from_linear(temp_buf, 0, &sink->stream, 0, + chunk / audio_stream_sample_bytes(&sink->stream)); + comp_update_buffer_produce(sink, chunk); + return 0; + } + uad->started = true; + } + + uint32_t available = ring->count; + if (available < chunk) { + static uint32_t s_pb_underrun_cnt; + s_pb_underrun_cnt++; + if (s_pb_underrun_cnt <= 5 || s_pb_underrun_cnt % 100 == 0) { + LOG_WRN("[PB UNDERRUN %u] available=%u < period_bytes=%u", + s_pb_underrun_cnt, available, chunk); + } + memset(temp_buf, 0, chunk); + k_spin_unlock(&ring->lock, key); + audio_stream_copy_from_linear(temp_buf, 0, &sink->stream, 0, + chunk / audio_stream_sample_bytes(&sink->stream)); + comp_update_buffer_produce(sink, chunk); + } else { + for (size_t i = 0; i < chunk; i++) { + temp_buf[i] = ring->buf[ring->tail]; + ring->tail = (ring->tail + 1) % USB_AUDIO_RING_BUFFER_SIZE; + } + ring->count -= chunk; + k_spin_unlock(&ring->lock, key); + + audio_stream_copy_from_linear(temp_buf, 0, &sink->stream, 0, + chunk / audio_stream_sample_bytes(&sink->stream)); + comp_update_buffer_produce(sink, chunk); + } + } + } + } else if (source) { + /* SOF Source Buffer -> USB Capture Sink */ + enum sof_ipc_frame fmt = audio_stream_get_frm_fmt(&source->stream); + if (fmt == SOF_IPC_FRAME_FLOAT) { + uint32_t avail_frames = audio_stream_get_avail_frames(&source->stream); + uint32_t copy_frames = MIN(avail_frames, dev->frames ? dev->frames : 48); + + if (copy_frames > 0) { + float f_buf[96]; + int16_t s16_buf[96]; + uint32_t frames_to_copy = MIN(copy_frames, 48); + + audio_stream_copy_to_linear(&source->stream, 0, f_buf, 0, frames_to_copy * 2); + comp_update_buffer_consume(source, frames_to_copy * 2 * sizeof(float)); + + const float scale = 32768.0f; + for (size_t i = 0; i < frames_to_copy * 2; i++) { + float val = f_buf[i] * scale; + if (val >= 32767.0f) { + s16_buf[i] = 32767; + } else if (val <= -32768.0f) { + s16_buf[i] = -32768; + } else { +#if defined(__riscv) && defined(__riscv_flen) + int32_t r; + __asm__ ("fcvt.w.s %0, %1, rne" : "=r"(r) : "f"(val)); + s16_buf[i] = (int16_t)r; +#else + s16_buf[i] = (int16_t)(val >= 0.0f ? (val + 0.5f) : (val - 0.5f)); +#endif + } + } + + uint32_t s16_bytes = frames_to_copy * 2 * sizeof(int16_t); + struct usb_audio_ring_buffer *ring = &uad->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + if (ring->count + s16_bytes > USB_AUDIO_RING_BUFFER_SIZE) { + uint32_t drop = (ring->count + s16_bytes) - USB_AUDIO_RING_BUFFER_SIZE; + drop = (drop + 3) & ~3; + if (drop > ring->count) { + drop = ring->count; + } + ring->tail = (ring->tail + drop) % USB_AUDIO_RING_BUFFER_SIZE; + ring->count -= drop; + } + + uint32_t free_space = USB_AUDIO_RING_BUFFER_SIZE - ring->count; + uint32_t to_copy = MIN(s16_bytes, free_space); + + uint8_t *s16_raw = (uint8_t *)s16_buf; + for (size_t i = 0; i < to_copy; i++) { + ring->buf[ring->head] = s16_raw[i]; + ring->head = (ring->head + 1) % USB_AUDIO_RING_BUFFER_SIZE; + } + ring->count += to_copy; + + k_spin_unlock(&ring->lock, key); + } + } else { + uint32_t avail_bytes = audio_stream_get_avail_bytes(&source->stream); + + while (avail_bytes > 0) { + uint8_t temp_buf[512]; + uint32_t chunk = MIN(avail_bytes, sizeof(temp_buf)); + + struct usb_audio_ring_buffer *ring = &uad->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + if (ring->count + chunk > USB_AUDIO_RING_BUFFER_SIZE) { + uint32_t drop = (ring->count + chunk) - USB_AUDIO_RING_BUFFER_SIZE; + drop = (drop + 3) & ~3; + if (drop > ring->count) { + drop = ring->count; + } + ring->tail = (ring->tail + drop) % USB_AUDIO_RING_BUFFER_SIZE; + ring->count -= drop; + } + + uint32_t free_space = USB_AUDIO_RING_BUFFER_SIZE - ring->count; + uint32_t to_copy = MIN(chunk, free_space); + + audio_stream_copy_to_linear(&source->stream, 0, temp_buf, 0, + to_copy / audio_stream_sample_bytes(&source->stream)); + comp_update_buffer_consume(source, to_copy); + avail_bytes -= to_copy; + + for (size_t i = 0; i < to_copy; i++) { + ring->buf[ring->head] = temp_buf[i]; + ring->head = (ring->head + 1) % USB_AUDIO_RING_BUFFER_SIZE; + } + ring->count += to_copy; + + k_spin_unlock(&ring->lock, key); + } + } + } + + return 0; +} + +static int usb_audio_prepare(struct comp_dev *dev) +{ + dev->state = COMP_STATE_PREPARE; + return 0; +} + +static int usb_audio_reset(struct comp_dev *dev) +{ + struct usb_audio_data *uad = comp_get_drvdata(dev); + uad->active = false; + uad->started = false; + uad->ring.head = 0; + uad->ring.tail = 0; + uad->ring.count = 0; + dev->state = COMP_STATE_INIT; + return 0; +} + +static const struct comp_driver comp_usb_audio = { + .type = SOF_COMP_HOST, + .uid = SOF_RT_UUID(usb_audio_uuid), +#if !CONFIG_ZEPHYR_LOG + .tctx = &usb_audio_tr, +#endif + .ops = { + .create = usb_audio_new, + .free = usb_audio_free, + .params = usb_audio_params, + .prepare = usb_audio_prepare, + .trigger = usb_audio_trigger, + .copy = usb_audio_copy, + .reset = usb_audio_reset, + }, +}; + +static struct comp_driver_info comp_usb_audio_info = { + .drv = &comp_usb_audio, +}; + +UT_STATIC void sys_comp_usb_audio_init(void) +{ + comp_register(&comp_usb_audio_info); +} + +DECLARE_MODULE(sys_comp_usb_audio_init); +SOF_MODULE_INIT(usb_audio, sys_comp_usb_audio_init); + diff --git a/src/audio/volume/volume.c b/src/audio/volume/volume.c index a449190c60ad..3b54aca85339 100644 --- a/src/audio/volume/volume.c +++ b/src/audio/volume/volume.c @@ -900,6 +900,7 @@ static struct comp_dev *vol_static_create(const struct comp_driver *drv, const struct sof_static_comp *cdesc, uint32_t period_us) { +#if CONFIG_IPC_MAJOR_4 struct static_ipc4_vol_init_cfg vol_cfg; memset(&vol_cfg, 0, sizeof(vol_cfg)); @@ -914,6 +915,20 @@ static struct comp_dev *vol_static_create(const struct comp_driver *drv, .data = (const uint8_t *)&vol_cfg, }; return drv->ops.create(drv, cfg, &spec); +#else + static const struct ipc_config_volume vol_cfg = { + .channels = 2, + .min_value = 0, + .max_value = 0x7fffffff, + .ramp = 0, + .initial_ramp = 0, + }; + struct ipc_config_process spec = { + .size = sizeof(vol_cfg), + .data = (const uint8_t *)&vol_cfg, + }; + return drv->ops.create(drv, cfg, &spec); +#endif } static int vol_static_apply_volume(struct comp_dev *dev, uint32_t channels, int32_t val) diff --git a/src/debug/CMakeLists.txt b/src/debug/CMakeLists.txt index d91bd79234e8..36af1402571b 100644 --- a/src/debug/CMakeLists.txt +++ b/src/debug/CMakeLists.txt @@ -9,5 +9,8 @@ add_subdirectory(debug_stream) add_subdirectory(telemetry) zephyr_library_sources_ifdef(CONFIG_COLD_STORE_EXECUTE_DEBUG dram.c) +if(CONFIG_SHELL AND (CONFIG_SOC_FAMILY_ESPRESSIF OR CONFIG_PLATFORM_ESP32C6 OR CONFIG_PLATFORM_ESP32P4)) + zephyr_library_sources(shell/esp32_shell.c) +endif() endif() # Zephyr diff --git a/src/debug/shell/esp32_shell.c b/src/debug/shell/esp32_shell.c new file mode 100644 index 000000000000..68b612059424 --- /dev/null +++ b/src/debug/shell/esp32_shell.c @@ -0,0 +1,772 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#if defined(CONFIG_SOC_FAMILY_ESPRESSIF) || defined(CONFIG_PLATFORM_ESP32C6) || defined(CONFIG_PLATFORM_ESP32P4) + +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_SOC_SERIES_ESP32P4) +#include +#include +#elif defined(CONFIG_SOC_SERIES_ESP32C6) +#include +#endif +#include +#include +#if defined(CONFIG_COMP_BT_AUDIO) +#include +#include +#endif +#include +#include + +static int cmd_sof_status(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + struct sof_static_pipeline_status status; + sof_static_pipeline_get_status(&status); + + uint8_t mac[6] = {0}; + extern int esp_efuse_mac_get_default(uint8_t *mac); + esp_efuse_mac_get_default(mac); + + shell_print(sh, "=== Sound Open Firmware (SOF) Status ==="); + shell_print(sh, " MAC Address: %02x:%02x:%02x:%02x:%02x:%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + shell_print(sh, " Playback Pipeline: %s", status.playback_active ? "RUNNING" : "STOPPED"); + shell_print(sh, " Capture Pipeline: %s", status.capture_active ? "RUNNING" : "STOPPED"); + shell_print(sh, " Active Interface: %s", status.active_interface == SOF_AUDIO_IF_I2S ? "I2S0" : "PDM0"); + shell_print(sh, " Clock Mode: %s", + status.clock_mode == SOF_CLOCK_MASTER ? "MASTER" : + (status.clock_mode == SOF_CLOCK_DMIC ? "DMIC INJECTOR" : "SLAVE (Default)")); + shell_print(sh, " Audio Route: %s", + status.audio_route == SOF_AUDIO_ROUTE_USB_DAI ? "USB <-> DAI (Default)" : + (status.audio_route == SOF_AUDIO_ROUTE_BT_DAI ? "BT <-> DAI" : "USB <-> BT")); + shell_print(sh, " BT Audio Stream: %s", status.bt_stream_enabled ? "ENABLED" : "DISABLED"); + shell_print(sh, " Sample Rate: %u Hz", status.sample_rate); + shell_print(sh, " Playback Volume: %d dB (Mute: %s)", status.playback_volume / 256, status.playback_mute ? "YES" : "NO"); + shell_print(sh, " Capture Volume: %d dB (Mute: %s)", status.capture_volume / 256, status.capture_mute ? "YES" : "NO"); + shell_print(sh, " Playback EQ: %s", status.eq_playback_bypassed ? "BYPASS" : "ENABLED"); + shell_print(sh, " Playback DRC: %s", status.drc_playback_bypassed ? "BYPASS" : "ENABLED"); + shell_print(sh, " Capture TDFB: %s", status.tdfb_capture_bypassed ? "BYPASS" : "ENABLED"); + shell_print(sh, " Capture EQ: %s", status.eq_capture_bypassed ? "BYPASS" : "ENABLED"); + shell_print(sh, "========================================"); + + return 0; +} + +static int cmd_sof_mode(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 3) { + shell_error(sh, "Usage: sof mode "); + return -EINVAL; + } + + enum sof_audio_interface iface; + if (strcmp(argv[1], "i2s") == 0) { + iface = SOF_AUDIO_IF_I2S; + } else if (strcmp(argv[1], "pdm") == 0) { + iface = SOF_AUDIO_IF_PDM; + } else { + shell_error(sh, "Invalid interface: %s (choose i2s or pdm)", argv[1]); + return -EINVAL; + } + + enum sof_clock_mode mode; + if (strcmp(argv[2], "master") == 0) { + mode = SOF_CLOCK_MASTER; + } else if (strcmp(argv[2], "slave") == 0) { + mode = SOF_CLOCK_SLAVE; + } else if (strcmp(argv[2], "dmic") == 0) { + if (iface != SOF_AUDIO_IF_PDM) { + shell_error(sh, "DMIC mode is only supported on PDM interface"); + return -EINVAL; + } + mode = SOF_CLOCK_DMIC; + } else { + shell_error(sh, "Invalid mode: %s (choose master, slave, or dmic)", argv[2]); + return -EINVAL; + } + + sof_static_pipeline_set_clock_mode(iface, mode); + shell_print(sh, "Interface %s set to %s mode.", argv[1], argv[2]); + return 0; +} + +static int cmd_sof_dmic(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 2) { + shell_error(sh, "Usage: sof dmic "); + return -EINVAL; + } + + bool enable = (strcmp(argv[1], "enable") == 0 || strcmp(argv[1], "1") == 0); + sof_static_kcontrol_set(7, enable ? 1 : 0); + shell_print(sh, "PDM DMIC injector mode %s.", + enable ? "ENABLED (Clock Receiver / Data Transmitter)" : "DISABLED (Master TX)"); + return 0; +} + +static int cmd_sof_eq(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 3) { + shell_error(sh, "Usage: sof eq "); + return -EINVAL; + } + + bool is_capture = (strcmp(argv[1], "capture") == 0); + bool bypass = (strcmp(argv[2], "bypass") == 0); + + sof_static_pipeline_set_eq_bypass(is_capture, bypass); + shell_print(sh, "%s EQ set to %s.", is_capture ? "Capture" : "Playback", bypass ? "BYPASS" : "ENABLED"); + return 0; +} + +static int cmd_sof_drc(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 2) { + shell_error(sh, "Usage: sof drc "); + return -EINVAL; + } + + bool bypass = (strcmp(argv[1], "bypass") == 0); + sof_static_pipeline_set_drc_bypass(bypass); + shell_print(sh, "Playback DRC set to %s.", bypass ? "BYPASS" : "ENABLED"); + return 0; +} + +static int cmd_sof_tdfb(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 2) { + shell_error(sh, "Usage: sof tdfb "); + return -EINVAL; + } + + bool bypass = (strcmp(argv[1], "bypass") == 0); + sof_static_pipeline_set_tdfb_bypass(bypass); + shell_print(sh, "Capture TDFB set to %s.", bypass ? "BYPASS" : "ENABLED"); + return 0; +} + +static int cmd_sof_vol(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 3) { + shell_error(sh, "Usage: sof vol "); + return -EINVAL; + } + + uint32_t pipe_id = (strcmp(argv[1], "playback") == 0 || strcmp(argv[1], "pb") == 0) ? 1 : 2; + int db = atoi(argv[2]); + int16_t uac_vol = (int16_t)(db * 256); + + sof_static_pipeline_set_volume(pipe_id, uac_vol); + shell_print(sh, "%s volume set to %d dB.", pipe_id == 1 ? "Playback" : "Capture", db); + return 0; +} + +static int cmd_sof_mute(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 3) { + shell_error(sh, "Usage: sof mute "); + return -EINVAL; + } + + uint32_t pipe_id = (strcmp(argv[1], "playback") == 0 || strcmp(argv[1], "pb") == 0) ? 1 : 2; + bool mute = (strcmp(argv[2], "on") == 0 || strcmp(argv[2], "1") == 0 || strcmp(argv[2], "yes") == 0); + + sof_static_pipeline_set_mute(pipe_id, mute); + shell_print(sh, "%s mute set to %s.", pipe_id == 1 ? "Playback" : "Capture", mute ? "ON" : "OFF"); + return 0; +} + +static int cmd_sof_play(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 2) { + shell_error(sh, "Usage: sof play "); + return -EINVAL; + } + + bool start = (strcmp(argv[1], "start") == 0); + sof_static_pipeline_set_playback_active(start); + shell_print(sh, "Playback pipeline %s.", start ? "STARTED" : "STOPPED"); + return 0; +} + +static int cmd_sof_tone(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 2) { + shell_print(sh, "Test tone generator is currently %s.", usb_audio_get_tone() ? "ON" : "OFF"); + return 0; + } + + bool enable = (strcmp(argv[1], "on") == 0 || strcmp(argv[1], "1") == 0 || strcmp(argv[1], "start") == 0); + usb_audio_set_tone(enable); + shell_print(sh, "Test tone generator set to %s.", enable ? "ON" : "OFF"); + return 0; +} + +static inline uint32_t shell_isqrt(uint64_t val) +{ + uint64_t root = 0; + uint64_t bit = (uint64_t)1 << 62; + while (bit > val) bit >>= 2; + while (bit != 0) { + if (val >= root + bit) { + val -= root + bit; + root = (root >> 1) + bit; + } else { + root >>= 1; + } + bit >>= 2; + } + return (uint32_t)root; +} + +static int cmd_sof_cap(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 2) { + shell_error(sh, "Usage: sof cap "); + return -EINVAL; + } + + if (strcmp(argv[1], "dump") == 0) { + int count = 32; + if (argc >= 3) { + count = atoi(argv[2]); + if (count <= 0) count = 32; + if (count > 240) count = 240; + } + int16_t samples[480]; + bool ok = usb_audio_peek_capture_data(samples, sizeof(samples)); + if (!ok) { + shell_error(sh, "Capture ring buffer empty or not active"); + return 0; + } + shell_print(sh, "=== Dump %d Captured Samples (Stereo L/R) ===", count); + for (int i = 0; i < count; i++) { + shell_print(sh, "[%3d] L: %6d (0x%04hx), R: %6d (0x%04hx)", + i, samples[2 * i], (uint16_t)samples[2 * i], + samples[2 * i + 1], (uint16_t)samples[2 * i + 1]); + } + return 0; + } + + if (strcmp(argv[1], "stats") == 0) { + int16_t samples[480]; /* 240 stereo frames = 5ms @ 48kHz */ + bool ok = usb_audio_peek_capture_data(samples, sizeof(samples)); + if (!ok) { + shell_error(sh, "Capture ring buffer empty or not active"); + return 0; + } + + int32_t min_l = 32767, max_l = -32768; + int32_t min_r = 32767, max_r = -32768; + int64_t sum_sq_l = 0, sum_sq_r = 0; + int zero_cross_l = 0; + int prev_l = samples[0]; + + for (int i = 0; i < 240; i++) { + int16_t l = samples[2 * i]; + int16_t r = samples[2 * i + 1]; + + if (l < min_l) min_l = l; + if (l > max_l) max_l = l; + if (r < min_r) min_r = r; + if (r > max_r) max_r = r; + + sum_sq_l += (int32_t)l * (int32_t)l; + sum_sq_r += (int32_t)r * (int32_t)r; + + if ((l >= 0 && prev_l < 0) || (l < 0 && prev_l >= 0)) { + zero_cross_l++; + } + prev_l = l; + } + + uint32_t rms_l = shell_isqrt(sum_sq_l / 240); + uint32_t rms_r = shell_isqrt(sum_sq_r / 240); + uint32_t est_freq = zero_cross_l * 100; /* 5ms window */ + + shell_print(sh, "=== Capture Stream Metrics ==="); + shell_print(sh, " Left Channel: min=%d, max=%d, pk-pk=%d, rms=%u, est_freq=%u Hz", + min_l, max_l, max_l - min_l, rms_l, est_freq); + shell_print(sh, " Right Channel: min=%d, max=%d, pk-pk=%d, rms=%u", + min_r, max_r, max_r - min_r, rms_r); + shell_print(sh, " Zero Crossings: %d (in 5ms window)", zero_cross_l); + shell_print(sh, "=============================="); + return 0; + } + + bool start = (strcmp(argv[1], "start") == 0); + sof_static_pipeline_set_capture_active(start); + shell_print(sh, "Capture pipeline %s.", start ? "STARTED" : "STOPPED"); + return 0; +} + +static int cmd_sof_ctl(const struct shell *sh, size_t argc, char **argv) +{ + const struct sof_static_topology *topo = sof_static_topology_get(); + if (!topo) { + shell_error(sh, "No active static topology"); + return -ENODEV; + } + + if (argc < 2 || strcmp(argv[1], "list") == 0) { + shell_print(sh, "=== SOF Static Topology Controls (%s) ===", topo->name ? topo->name : ""); + shell_print(sh, "%-4s %-28s %-6s %-10s %s", "ID", "Name", "Comp", "Type", "Value"); + shell_print(sh, "-------------------------------------------------------------"); + for (size_t i = 0; i < topo->num_controls; i++) { + const struct sof_static_kcontrol *ctl = &topo->controls[i]; + int32_t val = 0; + sof_static_kcontrol_get(ctl->id, &val); + const char *type_str = (ctl->type == SOF_STATIC_CTRL_VOLUME) ? "VOLUME" : + (ctl->type == SOF_STATIC_CTRL_SWITCH) ? "SWITCH" : + (ctl->type == SOF_STATIC_CTRL_ENUM) ? "ENUM" : "BINARY"; + shell_print(sh, "[%2u] %-28s comp:%-2u %-10s %d (0x%08x)", + ctl->id, ctl->name, ctl->target_comp_id, type_str, val, (uint32_t)val); + } + return 0; + } + + if (strcmp(argv[1], "get") == 0) { + if (argc < 3) { + shell_error(sh, "Usage: sof ctl get "); + return -EINVAL; + } + int ctrl_id = atoi(argv[2]); + if (ctrl_id == 0 && strcmp(argv[2], "0") != 0) { + ctrl_id = sof_static_kcontrol_find_by_name(argv[2]); + } + if (ctrl_id <= 0) { + shell_error(sh, "Control not found: %s", argv[2]); + return -ENOENT; + } + int32_t val = 0; + int ret = sof_static_kcontrol_get((uint32_t)ctrl_id, &val); + if (ret < 0) { + shell_error(sh, "Failed to get control %d (ret %d)", ctrl_id, ret); + return ret; + } + shell_print(sh, "Control [%d] value = %d (0x%x)", ctrl_id, val, (uint32_t)val); + return 0; + } + + if (strcmp(argv[1], "set") == 0) { + if (argc < 4) { + shell_error(sh, "Usage: sof ctl set "); + return -EINVAL; + } + int ctrl_id = atoi(argv[2]); + if (ctrl_id == 0 && strcmp(argv[2], "0") != 0) { + ctrl_id = sof_static_kcontrol_find_by_name(argv[2]); + } + if (ctrl_id <= 0) { + shell_error(sh, "Control not found: %s", argv[2]); + return -ENOENT; + } + int32_t val = (int32_t)strtol(argv[3], NULL, 0); + int ret = sof_static_kcontrol_set((uint32_t)ctrl_id, val); + if (ret < 0) { + shell_error(sh, "Failed to set control %d (ret %d)", ctrl_id, ret); + return ret; + } + shell_print(sh, "Control [%d] set to %d (0x%x)", ctrl_id, val, (uint32_t)val); + return 0; + } + + shell_error(sh, "Usage: sof ctl [args]"); + return -EINVAL; +} + +static int cmd_sof_regs(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(sh, "=== I2S0 / PDM Registers ==="); +#if defined(CONFIG_SOC_SERIES_ESP32P4) + shell_print(sh, " I2S0.tx_conf: 0x%08x (tx_start=%u, tx_pdm_en=%u, tx_tdm_en=%u, tx_slave=%u, bck_div=%u)", + (uint32_t)I2S0.tx_conf.val, + (uint32_t)I2S0.tx_conf.tx_start, + (uint32_t)I2S0.tx_conf.tx_pdm_en, + (uint32_t)I2S0.tx_conf.tx_tdm_en, + (uint32_t)I2S0.tx_conf.tx_slave_mod, + (uint32_t)I2S0.tx_conf.tx_bck_div_num); +#elif defined(CONFIG_SOC_SERIES_ESP32C6) + shell_print(sh, " I2S0.tx_conf: 0x%08x (tx_start=%u, tx_pdm_en=%u, tx_tdm_en=%u, tx_slave=%u)", + (uint32_t)I2S0.tx_conf.val, + (uint32_t)I2S0.tx_conf.tx_start, + (uint32_t)I2S0.tx_conf.tx_pdm_en, + (uint32_t)I2S0.tx_conf.tx_tdm_en, + (uint32_t)I2S0.tx_conf.tx_slave_mod); +#endif + shell_print(sh, " I2S0.tx_pcm2pdm_conf: 0x%08x (conv_en=%u, osr2=%u, dac_en=%u, dac_2out=%u)", + (uint32_t)I2S0.tx_pcm2pdm_conf.val, + (uint32_t)I2S0.tx_pcm2pdm_conf.pcm2pdm_conv_en, + (uint32_t)I2S0.tx_pcm2pdm_conf.tx_pdm_sinc_osr2, + (uint32_t)I2S0.tx_pcm2pdm_conf.tx_pdm_dac_mode_en, + (uint32_t)I2S0.tx_pcm2pdm_conf.tx_pdm_dac_2out_en); + shell_print(sh, " I2S0.tx_pcm2pdm_conf1:0x%08x (fp=%u, fs=%u)", + (uint32_t)I2S0.tx_pcm2pdm_conf1.val, + (uint32_t)I2S0.tx_pcm2pdm_conf1.tx_pdm_fp, + (uint32_t)I2S0.tx_pcm2pdm_conf1.tx_pdm_fs); + shell_print(sh, " I2S0.tx_conf1: 0x%08x (bits_mod=%u, chan_bits=%u, half_bits=%u)", + (uint32_t)I2S0.tx_conf1.val, + (uint32_t)I2S0.tx_conf1.tx_bits_mod, + (uint32_t)I2S0.tx_conf1.tx_tdm_chan_bits, + (uint32_t)I2S0.tx_conf1.tx_half_sample_bits); + shell_print(sh, " I2S0.rx_conf: 0x%08x (rx_start=%u, rx_pdm_en=%u, rx_tdm_en=%u, rx_slave=%u)", + (uint32_t)I2S0.rx_conf.val, + (uint32_t)I2S0.rx_conf.rx_start, + (uint32_t)I2S0.rx_conf.rx_pdm_en, + (uint32_t)I2S0.rx_conf.rx_tdm_en, + (uint32_t)I2S0.rx_conf.rx_slave_mod); + shell_print(sh, " I2S0.rx_conf1: 0x%08x (bits_mod=%u, chan_bits=%u, half_bits=%u)", + (uint32_t)I2S0.rx_conf1.val, + (uint32_t)I2S0.rx_conf1.rx_bits_mod, + (uint32_t)I2S0.rx_conf1.rx_tdm_chan_bits, + (uint32_t)I2S0.rx_conf1.rx_half_sample_bits); +#if defined(CONFIG_SOC_SERIES_ESP32P4) + shell_print(sh, " I2S0.rx_pdm2pcm_conf: 0x%08x (conv_en=%u, dsr16=%u, amp=%u)", + (uint32_t)I2S0.rx_pdm2pcm_conf.val, + (uint32_t)I2S0.rx_pdm2pcm_conf.rx_pdm2pcm_en, + (uint32_t)I2S0.rx_pdm2pcm_conf.rx_pdm_sinc_dsr_16_en, + (uint32_t)I2S0.rx_pdm2pcm_conf.rx_pdm2pcm_amplify_num); +#endif +#if defined(CONFIG_SOC_SERIES_ESP32P4) + shell_print(sh, " GPIO Matrix: G3_out=0x%08x (sel=%u), G4_out=0x%08x (sel=%u), G5_out=0x%08x (sel=%u)", + (uint32_t)GPIO.func_out_sel_cfg[3].val, + (uint32_t)GPIO.func_out_sel_cfg[3].out_sel, + (uint32_t)GPIO.func_out_sel_cfg[4].val, + (uint32_t)GPIO.func_out_sel_cfg[4].out_sel, + (uint32_t)GPIO.func_out_sel_cfg[5].val, + (uint32_t)GPIO.func_out_sel_cfg[5].out_sel); + shell_print(sh, " GPIO Level / In: GPIO_IN=0x%08x, G3_in=%u, G4_in=%u, G5_in=%u", + (uint32_t)GPIO.in.val, + (uint32_t)((GPIO.in.val >> 3) & 1), + (uint32_t)((GPIO.in.val >> 4) & 1), + (uint32_t)((GPIO.in.val >> 5) & 1)); + shell_print(sh, " IO_MUX: IO_MUX[3]=0x%08x, IO_MUX[4]=0x%08x, IO_MUX[5]=0x%08x", + (uint32_t)IO_MUX.gpio[3].val, + (uint32_t)IO_MUX.gpio[4].val, + (uint32_t)IO_MUX.gpio[5].val); + shell_print(sh, " HP_SYS_CLKRST: ctrl11=0x%08x (rx_src=%u), ctrl13=0x%08x (tx_src=%u, tx_div_n=%u)", + (uint32_t)HP_SYS_CLKRST.peri_clk_ctrl11.val, + (uint32_t)HP_SYS_CLKRST.peri_clk_ctrl11.reg_i2s0_rx_clk_src_sel, + (uint32_t)HP_SYS_CLKRST.peri_clk_ctrl13.val, + (uint32_t)HP_SYS_CLKRST.peri_clk_ctrl13.reg_i2s0_tx_clk_src_sel, + (uint32_t)HP_SYS_CLKRST.peri_clk_ctrl13.reg_i2s0_tx_div_n); +#elif defined(CONFIG_SOC_SERIES_ESP32C6) + shell_print(sh, " GPIO Matrix: G18_out=0x%08x, G19_out=0x%08x, G20_out=0x%08x, G21_out=0x%08x", + (uint32_t)GPIO.func_out_sel_cfg[18].val, + (uint32_t)GPIO.func_out_sel_cfg[19].val, + (uint32_t)GPIO.func_out_sel_cfg[20].val, + (uint32_t)GPIO.func_out_sel_cfg[21].val); + shell_print(sh, " GPIO Level / In: GPIO_IN=0x%08x, G18_in=%u, G19_in=%u, G20_in=%u, G21_in=%u", + (uint32_t)GPIO.in.val, + (uint32_t)((GPIO.in.val >> 18) & 1), + (uint32_t)((GPIO.in.val >> 19) & 1), + (uint32_t)((GPIO.in.val >> 20) & 1), + (uint32_t)((GPIO.in.val >> 21) & 1)); + shell_print(sh, " PCR I2S Clocks: conf=0x%08x, tx_clkm=0x%08x, rx_clkm=0x%08x", + (uint32_t)PCR.i2s_conf.val, + (uint32_t)PCR.i2s_tx_clkm_conf.val, + (uint32_t)PCR.i2s_rx_clkm_conf.val); +#endif + shell_print(sh, "============================"); + return 0; +} + +#if defined(CONFIG_COMP_BT_AUDIO) +static int cmd_sof_bt_status(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + struct bt_service_status st; + bt_service_get_status(&st); + const struct bt_audio_format_desc *desc = bt_service_get_current_format_desc(); + + shell_print(sh, "=== Bluetooth LE Audio / C6 Co-Processor Status ==="); + shell_print(sh, " ESP32-C6 Power: %s (GPIO 54 asserted)", st.c6_powered ? "ON" : "OFF"); + shell_print(sh, " Link State: %s", + st.state == BT_STATE_DISABLED ? "DISABLED" : + (st.state == BT_STATE_READY ? "READY" : + (st.state == BT_STATE_BROADCASTING ? "BROADCASTING" : + (st.state == BT_STATE_RECEIVING ? "RECEIVING" : "SCANNING")))); + shell_print(sh, " Active Format: %s (%s)", desc ? desc->name : "unknown", desc ? desc->codec_name : "LC3"); + shell_print(sh, " Audio Quality: %u Hz, %u-bit stereo, %u kbps (%u.%u ms SDU, %u B PCM)", + st.sample_rate, desc ? desc->bit_depth : 16, st.bitrate_kbps, + desc ? (desc->frame_duration_us / 1000) : 10, + desc ? ((desc->frame_duration_us % 1000) / 100) : 0, + st.frame_bytes); + shell_print(sh, " TX Packets / Bytes:%u pkts / %u bytes", st.tx_packets, st.tx_bytes); + shell_print(sh, " RX Packets / Bytes:%u pkts / %u bytes", st.rx_packets, st.rx_bytes); + shell_print(sh, " Link RSSI: %d dBm", st.rssi); + shell_print(sh, "===================================================="); + return 0; +} + +static int cmd_sof_bt_format(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 2) { + enum bt_audio_format current = bt_service_get_format(); + shell_print(sh, "=== Available Bluetooth Audio Formats ==="); + shell_print(sh, " ID | Format Key | Codec | Sample Rate | Bits | Frame | Bitrate | Frame PCM"); + shell_print(sh, "-----+------------+------------+-------------+------+-------+----------+----------"); + for (int i = 0; i < BT_AUDIO_FMT_COUNT; i++) { + const struct bt_audio_format_desc *d = bt_service_get_format_desc((enum bt_audio_format)i); + if (!d) continue; + char cur_mark = (i == (int)current) ? '*' : ' '; + shell_print(sh, " %c%d | %-10s | %-10s | %6u Hz | %2u | %2u.%1u ms| %4u kbps| %5u B", + cur_mark, i, d->name, d->codec_name, d->sample_rate, d->bit_depth, + d->frame_duration_us / 1000, (d->frame_duration_us % 1000) / 100, + d->bitrate_kbps, d->frame_bytes); + } + shell_print(sh, "========================================="); + shell_print(sh, "Usage: sof bt format "); + return 0; + } + + enum bt_audio_format target_fmt; + char *endptr; + long val = strtol(argv[1], &endptr, 10); + if (*endptr == '\0' && val >= 0 && val < BT_AUDIO_FMT_COUNT) { + target_fmt = (enum bt_audio_format)val; + } else if (bt_service_format_from_name(argv[1], &target_fmt) != 0) { + shell_error(sh, "Unknown format: %s. Run 'sof bt format' to see available formats.", argv[1]); + return -EINVAL; + } + + sof_static_kcontrol_set(14, (int32_t)target_fmt); + const struct bt_audio_format_desc *desc = bt_service_get_format_desc(target_fmt); + shell_print(sh, "Bluetooth audio format switched to: %s (%s @ %u Hz, %u kbps)", + desc->name, desc->codec_name, desc->sample_rate, desc->bitrate_kbps); + return 0; +} + +static int cmd_sof_bt_broadcast(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 2) { + shell_error(sh, "Usage: sof bt broadcast "); + return -EINVAL; + } + + if (strcmp(argv[1], "start") == 0) { + sof_static_kcontrol_set(8, 1); + shell_print(sh, "Bluetooth LE Audio broadcast started."); + } else if (strcmp(argv[1], "stop") == 0) { + sof_static_kcontrol_set(8, 0); + shell_print(sh, "Bluetooth LE Audio broadcast stopped."); + } else { + shell_error(sh, "Invalid argument: %s (choose start or stop)", argv[1]); + return -EINVAL; + } + return 0; +} + +static int cmd_sof_bt_scan(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + bt_service_start_scan(); + shell_print(sh, "Scanning for nearby LE Audio broadcast sources..."); + return 0; +} + +static int cmd_sof_bt_power(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 2) { + shell_error(sh, "Usage: sof bt power "); + return -EINVAL; + } + + bool on = (strcmp(argv[1], "on") == 0 || strcmp(argv[1], "1") == 0); + bt_service_power_c6(on); + shell_print(sh, "ESP32-C6 power %s.", on ? "ON" : "OFF"); + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(bt_cmds, + SHELL_CMD(status, NULL, "Print Bluetooth LE Audio / C6 coprocessor status", cmd_sof_bt_status), + SHELL_CMD(format, NULL, "Get or set BT audio format (sof bt format [name|id])", cmd_sof_bt_format), + SHELL_CMD(broadcast, NULL, "Start/stop LE Audio broadcast (sof bt broadcast )", cmd_sof_bt_broadcast), + SHELL_CMD(scan, NULL, "Scan for nearby LE Audio devices", cmd_sof_bt_scan), + SHELL_CMD(power, NULL, "Control ESP32-C6 power (sof bt power )", cmd_sof_bt_power), + SHELL_SUBCMD_SET_END +); +#endif + +static int cmd_sof_route(const struct shell *sh, size_t argc, char **argv) +{ + if (argc < 2) { + struct sof_static_pipeline_status status; + sof_static_pipeline_get_status(&status); + shell_print(sh, "Current Audio Route: %s", + status.audio_route == SOF_AUDIO_ROUTE_USB_DAI ? "USB <-> DAI (Default)" : + (status.audio_route == SOF_AUDIO_ROUTE_BT_DAI ? "BT <-> DAI" : "USB <-> BT")); + shell_print(sh, "Usage: sof route "); + return 0; + } + + enum sof_audio_route route; + if (strcmp(argv[1], "usb_dai") == 0 || strcmp(argv[1], "dai") == 0 || strcmp(argv[1], "0") == 0) { + route = SOF_AUDIO_ROUTE_USB_DAI; + } else if (strcmp(argv[1], "bt_dai") == 0 || strcmp(argv[1], "bt") == 0 || strcmp(argv[1], "1") == 0) { + route = SOF_AUDIO_ROUTE_BT_DAI; + } else if (strcmp(argv[1], "usb_bt") == 0 || strcmp(argv[1], "2") == 0) { + route = SOF_AUDIO_ROUTE_USB_BT; + } else { + shell_error(sh, "Invalid route: %s (choose usb_dai, bt_dai, or usb_bt)", argv[1]); + return -EINVAL; + } + + sof_static_kcontrol_set(10, (int32_t)route); + shell_print(sh, "Audio route set to %s.", + route == SOF_AUDIO_ROUTE_USB_DAI ? "USB <-> DAI (Default)" : + (route == SOF_AUDIO_ROUTE_BT_DAI ? "BT <-> DAI" : "USB <-> BT")); + return 0; +} + +#if defined(CONFIG_SOC_SERIES_ESP32P4) || defined(CONFIG_ESP32P4_PIE_SIMD) +#include + +static int cmd_sof_bench(const struct shell *sh, size_t argc, char **argv) +{ + esp_pie_enable(); + + shell_print(sh, "=== ESP32-P4 PIE SIMD Audio Kernel Benchmark ==="); + + #define BENCH_SAMPLES 512 + static int16_t src[BENCH_SAMPLES] __attribute__((aligned(16))); + static int16_t dst_scalar[BENCH_SAMPLES] __attribute__((aligned(16))); + static int16_t dst_pie[BENCH_SAMPLES] __attribute__((aligned(16))); + + /* Initialize test samples with ramp waveform */ + for (int i = 0; i < BENCH_SAMPLES; i++) { + src[i] = (int16_t)((i % 128) * 256 - 16384); + } + + const int32_t vol_l = 32768; /* -6 dB */ + const int32_t vol_r = 16384; /* -12 dB */ + const int iters = 200; + + /* 1. Benchmark Scalar Volume Scaling */ + uint32_t t0 = k_cycle_get_32(); + for (int it = 0; it < iters; it++) { + for (int i = 0; i < BENCH_SAMPLES; i += 2) { + int32_t pl = ((int32_t)src[i + 0] * vol_l + 0x8000) >> 16; + int32_t pr = ((int32_t)src[i + 1] * vol_r + 0x8000) >> 16; + dst_scalar[i + 0] = (pl > 32767) ? 32767 : ((pl < -32768) ? -32768 : pl); + dst_scalar[i + 1] = (pr > 32767) ? 32767 : ((pr < -32768) ? -32768 : pr); + } + } + uint32_t t1 = k_cycle_get_32(); + uint32_t scalar_cycles = (t1 - t0) / iters; + + /* 2. Benchmark PIE Vector Volume Scaling */ + esp_pie_set_sar(15); + + int16_t vol15_l = (int16_t)((vol_l + 1) >> 1); + int16_t vol15_r = (int16_t)((vol_r + 1) >> 1); + int16_t vol_stereo[2] = { vol15_l, vol15_r }; + + register const void *bc_a2 asm("a2") = vol_stereo; + ESP_PIE_VLDBC_32_IP_Q2_A2(); + + uint32_t t2 = k_cycle_get_32(); + for (int it = 0; it < iters; it++) { + register const int16_t *src_a0 asm("a0") = src; + register int16_t *dst_a1 asm("a1") = dst_pie; + + for (int i = 0; i <= BENCH_SAMPLES - 16; i += 16) { + ESP_PIE_VLD_128_IP_Q0_A0(); + ESP_PIE_VLD_128_IP_Q1_A0(); + ESP_PIE_VMUL_S16_Q0_Q0_Q2(); + ESP_PIE_VMUL_S16_Q1_Q1_Q2(); + ESP_PIE_VCLAMP_S16_Q0_Q0(); + ESP_PIE_VCLAMP_S16_Q1_Q1(); + ESP_PIE_VST_128_IP_Q0_A1(); + ESP_PIE_VST_128_IP_Q1_A1(); + } + } + uint32_t t3 = k_cycle_get_32(); + uint32_t pie_cycles = (t3 - t2) / iters; + + /* 3. Verification */ + shell_print(sh, " First 8 samples (src -> scalar vs pie):"); + for (int i = 0; i < 8; i++) { + shell_print(sh, " [%d] %6d -> scalar %6d, pie %6d", + i, src[i], dst_scalar[i], dst_pie[i]); + } + int max_diff = 0; + for (int i = 0; i < BENCH_SAMPLES; i++) { + int diff = abs(dst_scalar[i] - dst_pie[i]); + if (diff > max_diff) + max_diff = diff; + } + + uint32_t sc_x100 = (scalar_cycles * 100) / BENCH_SAMPLES; + uint32_t pie_x100 = (pie_cycles * 100) / BENCH_SAMPLES; + shell_print(sh, " Buffer size: %d samples (%d bytes)", BENCH_SAMPLES, BENCH_SAMPLES * 2); + shell_print(sh, " Scalar Cycles: %u cycles (%u.%02u cyc/sample)", + scalar_cycles, sc_x100 / 100, sc_x100 % 100); + shell_print(sh, " PIE SIMD Cycles:%u cycles (%u.%02u cyc/sample)", + pie_cycles, pie_x100 / 100, pie_x100 % 100); + if (pie_cycles > 0) { + uint32_t speedup_x100 = (scalar_cycles * 100) / pie_cycles; + shell_print(sh, " Speedup: %u.%02ux faster", + speedup_x100 / 100, speedup_x100 % 100); + } + shell_print(sh, " Max LSB Diff: %d (Tolerance: <= 1 LSB)", max_diff); + shell_print(sh, " Status: %s", (max_diff <= 1) ? "PASS (Exact match)" : "FAIL"); + + /* Test unaligned load/store safety */ + shell_print(sh, " Testing unaligned address accesses (+2, +4, +8 bytes)..."); + for (int offset = 2; offset <= 8; offset += 2) { + register const int16_t *src_a0 asm("a0") = src + offset; + register int16_t *dst_a1 asm("a1") = dst_pie + offset; + ESP_PIE_VLD_128_IP_Q0_A0(); + ESP_PIE_VST_128_IP_Q0_A1(); + shell_print(sh, " Offset +%d bytes: OK", offset * 2); + } + shell_print(sh, "================================================="); + return 0; +} +#endif +SHELL_STATIC_SUBCMD_SET_CREATE(sof_cmds, + SHELL_CMD(status, NULL, "Print current SOF pipeline and audio interface status", cmd_sof_status), + SHELL_CMD(regs, NULL, "Dump I2S1/PDM hardware registers", cmd_sof_regs), + SHELL_CMD(ctl, NULL, "List or set static kcontrols (sof ctl )", cmd_sof_ctl), + SHELL_CMD(play, NULL, "Start/stop playback pipeline (sof play )", cmd_sof_play), + SHELL_CMD(tone, NULL, "Generate 1000 Hz test tone (sof tone )", cmd_sof_tone), + SHELL_CMD(cap, NULL, "Start/stop capture pipeline (sof cap )", cmd_sof_cap), + SHELL_CMD(vol, NULL, "Set volume in dB (sof vol )", cmd_sof_vol), + SHELL_CMD(mute, NULL, "Set pipeline mute (sof mute )", cmd_sof_mute), + SHELL_CMD(mode, NULL, "Configure interface clock mode (sof mode )", cmd_sof_mode), + SHELL_CMD(dmic, NULL, "Control PDM DMIC injector mode (sof dmic )", cmd_sof_dmic), +#if defined(CONFIG_COMP_BT_AUDIO) + SHELL_CMD(bt, &bt_cmds, "Bluetooth LE Audio commands (sof bt )", NULL), +#endif + SHELL_CMD(route, NULL, "Audio routing (sof route )", cmd_sof_route), + SHELL_CMD(eq, NULL, "Control Equalizer bypass (sof eq )", cmd_sof_eq), + SHELL_CMD(drc, NULL, "Control DRC bypass (sof drc )", cmd_sof_drc), + SHELL_CMD(tdfb, NULL, "Control TDFB beamformer bypass (sof tdfb )", cmd_sof_tdfb), +#if defined(CONFIG_SOC_SERIES_ESP32P4) || defined(CONFIG_ESP32P4_PIE_SIMD) + SHELL_CMD(bench, NULL, "Benchmark PIE SIMD vs scalar audio performance", cmd_sof_bench), +#endif + SHELL_SUBCMD_SET_END +); + +SHELL_CMD_REGISTER(sof, &sof_cmds, "Sound Open Firmware (SOF) commands", NULL); + +#endif /* CONFIG_SOC_FAMILY_ESPRESSIF || CONFIG_PLATFORM_ESP32C6 || CONFIG_PLATFORM_ESP32P4 */ diff --git a/src/include/ipc/dai.h b/src/include/ipc/dai.h index 49ffa5d5fa94..30619096a5da 100644 --- a/src/include/ipc/dai.h +++ b/src/include/ipc/dai.h @@ -97,7 +97,9 @@ enum sof_ipc_dai_type { SOF_DAI_IMX_MICFIL, /**< i.MX MICFIL */ SOF_DAI_AMD_SDW, /**< Amd SDW */ SOF_DAI_INTEL_UAOL, /**< Intel UAOL */ - SOF_DAI_AMD_TDM /**< Amd TDM */ + SOF_DAI_AMD_TDM, /**< Amd TDM */ + SOF_DAI_ESP32_I2S, /**< ESP32 I2S */ + SOF_DAI_ESP32_PDM /**< ESP32 PDM */ }; #define SOF_DAI_CONFIG_HW_SPEC_OFFSET offsetof(struct sof_ipc_dai_config, ssp) diff --git a/src/include/sof/audio/pipeline/sof_static_pipeline.h b/src/include/sof/audio/pipeline/sof_static_pipeline.h new file mode 100644 index 000000000000..bf447c2eda5d --- /dev/null +++ b/src/include/sof/audio/pipeline/sof_static_pipeline.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifndef __SOF_AUDIO_PIPELINE_SOF_STATIC_PIPELINE_H__ +#define __SOF_AUDIO_PIPELINE_SOF_STATIC_PIPELINE_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct sof; + +enum sof_audio_interface { + SOF_AUDIO_IF_I2S = 0, + SOF_AUDIO_IF_PDM = 1, +}; + +enum sof_clock_mode { + SOF_CLOCK_SLAVE = 0, + SOF_CLOCK_MASTER = 1, + SOF_CLOCK_DMIC = 2, +}; + +enum sof_audio_route { + SOF_AUDIO_ROUTE_USB_DAI = 0, + SOF_AUDIO_ROUTE_BT_DAI = 1, + SOF_AUDIO_ROUTE_USB_BT = 2, +}; + +struct sof_static_pipeline_status { + bool playback_active; + bool capture_active; + uint32_t sample_rate; + enum sof_audio_interface active_interface; + enum sof_clock_mode clock_mode; + enum sof_audio_route audio_route; + bool bt_stream_enabled; + int16_t playback_volume; + bool playback_mute; + int16_t capture_volume; + bool capture_mute; + bool eq_playback_bypassed; + bool drc_playback_bypassed; + bool tdfb_capture_bypassed; + bool eq_capture_bypassed; +}; + +int sof_static_pipelines_init(struct sof *sof); +int sof_static_pipeline_set_clock_mode(enum sof_audio_interface iface, enum sof_clock_mode mode); +int sof_static_pipeline_set_dmic_injector(bool enable); +int sof_static_pipeline_set_bt_stream(bool enable); +int sof_static_pipeline_set_route(enum sof_audio_route route); +int sof_static_pipeline_set_eq_bypass(bool is_capture, bool bypass); +int sof_static_pipeline_set_drc_bypass(bool bypass); +int sof_static_pipeline_set_tdfb_bypass(bool bypass); +int sof_static_pipeline_set_volume(uint32_t pipeline_id, int16_t volume); +int sof_static_pipeline_set_mute(uint32_t pipeline_id, bool mute); +int sof_static_pipeline_get_volume(uint32_t pipeline_id, int16_t *volume); +int sof_static_pipeline_get_mute(uint32_t pipeline_id, bool *mute); +int sof_static_pipeline_set_playback_active(bool start); +int sof_static_pipeline_set_capture_active(bool start); +void sof_static_pipeline_get_status(struct sof_static_pipeline_status *status); + +#if defined(CONFIG_USBD_UAC2) +struct uac2_ops; +const struct uac2_ops *sof_get_uac2_ops(void); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __SOF_AUDIO_PIPELINE_SOF_STATIC_PIPELINE_H__ */ diff --git a/src/include/sof/audio/usb_audio.h b/src/include/sof/audio/usb_audio.h new file mode 100644 index 000000000000..29da76bef5c7 --- /dev/null +++ b/src/include/sof/audio/usb_audio.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Espressif Systems / SOF Project + */ + +#ifndef __SOF_AUDIO_USB_AUDIO_H__ +#define __SOF_AUDIO_USB_AUDIO_H__ + +#include +#include +#include +#include + +/* UUID for USB Audio Component */ +/* b95039f6-3d23-41a4-9276-2fbb27c62d01 */ +#define SOF_USB_AUDIO_UUID \ + DECLARE_SOF_RT_UUID("usb_audio", 0xb95039f6, 0x3d23, 0x41a4, \ + 0x92, 0x76, 0x2f, 0xbb, 0x27, 0xc6, 0x2d, 0x01) + +#define USB_AUDIO_RING_BUFFER_SIZE 16384 +#define USB_AUDIO_PREBUFFER_BYTES 3840 /* 20ms at 48kHz stereo 16-bit */ + +struct usb_audio_ring_buffer { + uint8_t buf[USB_AUDIO_RING_BUFFER_SIZE]; + volatile uint32_t head; + volatile uint32_t tail; + volatile uint32_t count; + struct k_spinlock lock; +}; + +struct usb_audio_data { + struct usb_audio_ring_buffer ring; + uint32_t sample_rate; + uint32_t channels; + uint32_t frame_bytes; + uint32_t period_bytes; + enum sof_ipc_stream_direction direction; + bool active; + bool started; +}; + +void usb_audio_feed_playback_data(const void *src, size_t bytes); +size_t usb_audio_fetch_capture_data(void *dst, size_t bytes); +bool usb_audio_peek_capture_data(void *dst, size_t bytes); +void usb_audio_consume_capture_data(size_t bytes); +void usb_audio_set_playback_rate(uint32_t rate); +void usb_audio_set_capture_rate(uint32_t rate); +void usb_audio_set_tone(bool enable); +bool usb_audio_get_tone(void); + +#endif /* __SOF_AUDIO_USB_AUDIO_H__ */ diff --git a/src/ipc/ipc3/dai.c b/src/ipc/ipc3/dai.c index 067aaf8579a6..7f39edd7a0c1 100644 --- a/src/ipc/ipc3/dai.c +++ b/src/ipc/ipc3/dai.c @@ -116,6 +116,10 @@ int dai_config_dma_channel(struct dai_data *dd, struct comp_dev *dev, const void dd->stream_id); channel = AFE_HS_GET_CHAN(handshake); break; + case SOF_DAI_ESP32_I2S: + case SOF_DAI_ESP32_PDM: + channel = 0; + break; default: /* other types of DAIs not handled for now */ comp_err(dev, "Unknown dai type %d", @@ -292,6 +296,10 @@ int ipc_dai_data_config(struct dai_data *dd, struct comp_dev *dev) break; case SOF_DAI_MEDIATEK_AFE: break; + case SOF_DAI_ESP32_I2S: + case SOF_DAI_ESP32_PDM: + dd->config.burst_elems = dai_get_fifo_depth(dd->dai, dai->direction); + break; default: /* other types of DAIs not handled for now */ comp_warn(dev, "Unknown dai type %d", diff --git a/src/lib/dai.c b/src/lib/dai.c index 0e26680ede06..bff44a915d79 100644 --- a/src/lib/dai.c +++ b/src/lib/dai.c @@ -202,6 +202,12 @@ const struct device *zephyr_dev[] = { #if DT_HAS_COMPAT_STATUS_OKAY(mediatek_afe) DT_FOREACH_STATUS_OKAY(mediatek_afe, GET_DEVICE_LIST) #endif +#if CONFIG_DAI_ESPRESSIF_I2S + DT_FOREACH_STATUS_OKAY(espressif_esp32_dai_i2s, GET_DEVICE_LIST) +#endif +#if CONFIG_DAI_ESPRESSIF_PDM + DT_FOREACH_STATUS_OKAY(espressif_esp32_dai_pdm, GET_DEVICE_LIST) +#endif }; const struct device **dai_get_device_list(size_t *count) @@ -245,6 +251,10 @@ static int sof_dai_type_to_zephyr(uint32_t type) case SOF_DAI_AMD_BT: case SOF_DAI_AMD_TDM: return DAI_AMD_TDM; + case SOF_DAI_ESP32_I2S: + return DAI_ESP32_I2S; + case SOF_DAI_ESP32_PDM: + return DAI_ESP32_PDM; default: return -EINVAL; } @@ -334,6 +344,11 @@ static void dai_set_device_params(struct dai *d) case SOF_DAI_MEDIATEK_AFE: d->dma_dev = SOF_DMA_DEV_AFE_MEMIF; break; + case SOF_DAI_ESP32_I2S: + case SOF_DAI_ESP32_PDM: + d->dma_dev = SOF_DMA_DEV_I2S; + d->dma_caps = SOF_DMA_CAP_GP_LP | SOF_DMA_CAP_GP_HP; + break; default: break; } diff --git a/src/lib/dma.c b/src/lib/dma.c index 3fb883c1720a..552e737f0870 100644 --- a/src/lib/dma.c +++ b/src/lib/dma.c @@ -47,7 +47,7 @@ __cold struct sof_dma *z_impl_sof_dma_get(uint32_t dir, uint32_t cap, uint32_t d assert_can_be_cold(); - if (!info->num_dmas) { + if (!info || !info->num_dmas) { tr_err(&dma_tr, "dma_get(): No DMACs installed"); return NULL; } @@ -186,7 +186,7 @@ struct dma *dma_get(uint32_t dir, uint32_t cap, uint32_t dev, uint32_t flags) struct dma *d = NULL, *dmin = NULL; k_spinlock_key_t key; - if (!info->num_dmas) { + if (!info || !info->num_dmas) { tr_err(&dma_tr, "dma_get(): No DMACs installed"); return NULL; } diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index 57c10444a23e..974d337ccd5c 100644 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -3,3 +3,7 @@ if(CONFIG_LIBRARY) add_subdirectory(library) endif() + +if(CONFIG_PLATFORM_ESP32C6) + add_subdirectory(esp32c6) +endif() diff --git a/src/platform/Kconfig b/src/platform/Kconfig index 33b4a33a13bb..e85a6d96f2e1 100644 --- a/src/platform/Kconfig +++ b/src/platform/Kconfig @@ -215,6 +215,14 @@ config MTK help Select if your target is a Mediatek DSP. Builds Zephyr firmware. +config PLATFORM_ESP32C6 + bool "Build for Espressif ESP32-C6 RISC-V SoC (Zephyr-only)" + select ZEPHYR_LOG + select ZEPHYR_NATIVE_DRIVERS + select STATIC_PIPELINE + help + Select if your target platform is Espressif ESP32-C6 RISC-V SoC. + endchoice config ESP32P4_PIE_SIMD diff --git a/src/platform/esp32c6/CMakeLists.txt b/src/platform/esp32c6/CMakeLists.txt new file mode 100644 index 000000000000..f997962ebc16 --- /dev/null +++ b/src/platform/esp32c6/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: BSD-3-Clause + +sof_add_include_directory(include) + +add_subdirectory(lib) + +sof_add_files( + platform.c + esp32c6_pipeline_def.c +) diff --git a/src/platform/esp32c6/esp32c6_pipeline_def.c b/src/platform/esp32c6/esp32c6_pipeline_def.c new file mode 100644 index 000000000000..59a7752482c1 --- /dev/null +++ b/src/platform/esp32c6/esp32c6_pipeline_def.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#include +#include +#include +#include + +/* UUID External Declarations */ +extern const struct sof_uuid usb_audio_uuid; +extern const struct sof_uuid volume_uuid; +extern const struct sof_uuid dai_uuid; + +/* ------------------------------------------------------------------------- + * 1. Component / Module Declarations + * ------------------------------------------------------------------------- */ +static const struct sof_static_comp esp32c6_comps[] = { + /* --- Playback Pipeline (Pipeline 1) --- */ + SOF_STATIC_COMP_HOST( + .id = 1, .pipeline_id = 1, .name = "USB_PB", + .uuid = &usb_audio_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2), + .ep.usb.terminal_id = 1 + ), + SOF_STATIC_COMP_MODULE( + .id = 2, .pipeline_id = 1, .name = "VOL_PB", + .uuid = &volume_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2) + ), + SOF_STATIC_COMP_DAI( + .id = 3, .pipeline_id = 1, .name = "DAI_I2S_PB", + .uuid = &dai_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2), + .ep.dai.dai_type = SOF_DAI_ESP32_I2S, + .ep.dai.dai_index = 0, + .ep.dai.format = SOF_DAI_FMT_I2S + ), + + /* --- Capture Pipeline (Pipeline 2) --- */ + SOF_STATIC_COMP_DAI( + .id = 4, .pipeline_id = 2, .name = "DAI_I2S_CAP", + .uuid = &dai_uuid, .direction = SOF_IPC_STREAM_CAPTURE, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2), + .ep.dai.dai_type = SOF_DAI_ESP32_I2S, + .ep.dai.dai_index = 0, + .ep.dai.format = SOF_DAI_FMT_I2S + ), + SOF_STATIC_COMP_MODULE( + .id = 5, .pipeline_id = 2, .name = "VOL_CAP", + .uuid = &volume_uuid, .direction = SOF_IPC_STREAM_CAPTURE, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2) + ), + SOF_STATIC_COMP_HOST( + .id = 6, .pipeline_id = 2, .name = "USB_CAP", + .uuid = &usb_audio_uuid, .direction = SOF_IPC_STREAM_CAPTURE, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2), + .ep.usb.terminal_id = 2 + ), +}; + +/* ------------------------------------------------------------------------- + * 2. Intermediate Audio Buffers + * ------------------------------------------------------------------------- */ +static const struct sof_static_buffer esp32c6_buffers[] = { + /* Playback Buffers */ + SOF_STATIC_BUFFER(.id = 1, .size = 3072, .fmt = SOF_IPC_FRAME_S16_LE), + SOF_STATIC_BUFFER(.id = 2, .size = 3072, .fmt = SOF_IPC_FRAME_S16_LE), + + /* Capture Buffers */ + SOF_STATIC_BUFFER(.id = 3, .size = 3072, .fmt = SOF_IPC_FRAME_S16_LE), + SOF_STATIC_BUFFER(.id = 4, .size = 3072, .fmt = SOF_IPC_FRAME_S16_LE), +}; + +/* ------------------------------------------------------------------------- + * 3. Pipeline Connections / Graph Routing + * ------------------------------------------------------------------------- */ +static const struct sof_static_route esp32c6_routes[] = { + /* Playback Route: USB_PB (1) -> [1] -> VOL_PB (2) -> [2] -> DAI_I2S_PB (3) */ + SOF_STATIC_ROUTE(.src_comp_id = 1, .buffer_id = 1, .sink_comp_id = 2), + SOF_STATIC_ROUTE(.src_comp_id = 2, .buffer_id = 2, .sink_comp_id = 3), + + /* Capture Route: DAI_I2S_CAP (4) -> [3] -> VOL_CAP (5) -> [4] -> USB_CAP (6) */ + SOF_STATIC_ROUTE(.src_comp_id = 4, .buffer_id = 3, .sink_comp_id = 5), + SOF_STATIC_ROUTE(.src_comp_id = 5, .buffer_id = 4, .sink_comp_id = 6), +}; + +/* ------------------------------------------------------------------------- + * 4. PCMs (Pulse Code Modulated Endpoints) + * ------------------------------------------------------------------------- */ +static const struct sof_static_pcm esp32c6_pcms[] = { + SOF_STATIC_PCM( + .pcm_id = 0, + .name = "Speaker Playback", + .direction = SOF_IPC_STREAM_PLAYBACK, + .pipeline_id = 1, + .host_comp_id = 1, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2) + ), + SOF_STATIC_PCM( + .pcm_id = 1, + .name = "Mic Capture", + .direction = SOF_IPC_STREAM_CAPTURE, + .pipeline_id = 2, + .host_comp_id = 6, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2) + ), +}; + +/* ------------------------------------------------------------------------- + * 5. Kcontrols (Volume and Mute) + * ------------------------------------------------------------------------- */ +static const struct sof_static_kcontrol esp32c6_controls[] = { + SOF_STATIC_KCONTROL_VOLUME( + .id = 1, .name = "Master Playback Volume", + .target_comp_id = 2, + .min = 0, .max = 65536, .def = 65536, .channels = 2, + .uac2_entity_id = 0 + ), + SOF_STATIC_KCONTROL_VOLUME( + .id = 2, .name = "Master Capture Volume", + .target_comp_id = 5, + .min = 0, .max = 65536, .def = 65536, .channels = 2, + .uac2_entity_id = 0 + ), +}; + +/* ------------------------------------------------------------------------- + * 6. Pipeline Descriptors + * ------------------------------------------------------------------------- */ +static const struct sof_static_pipeline_desc esp32c6_pipelines[] = { + { + .pipeline_id = 1, + .name = "Playback Pipeline", + .direction = SOF_IPC_STREAM_PLAYBACK, + .priority = 0, + .core = 0, + .period = 1000, /* 1ms */ + .frames_per_sched = 48, + .time_domain = SOF_TIME_DOMAIN_TIMER, + .sched_comp_id = 1, + .source_comp_id = 1, + .sink_comp_id = 3, + }, + { + .pipeline_id = 2, + .name = "Capture Pipeline", + .direction = SOF_IPC_STREAM_CAPTURE, + .priority = 0, + .core = 0, + .period = 1000, /* 1ms */ + .frames_per_sched = 48, + .time_domain = SOF_TIME_DOMAIN_TIMER, + .sched_comp_id = 6, + .source_comp_id = 4, + .sink_comp_id = 6, + }, +}; + +/* ------------------------------------------------------------------------- + * Top-Level Exported Topology + * ------------------------------------------------------------------------- */ +const struct sof_static_topology g_esp32c6_static_topology = { + .name = "ESP32-C6 S16_LE Playback & Capture Pipeline", + .num_pipelines = ARRAY_SIZE(esp32c6_pipelines), + .pipelines = esp32c6_pipelines, + .num_comps = ARRAY_SIZE(esp32c6_comps), + .comps = esp32c6_comps, + .num_buffers = ARRAY_SIZE(esp32c6_buffers), + .buffers = esp32c6_buffers, + .num_routes = ARRAY_SIZE(esp32c6_routes), + .routes = esp32c6_routes, + .num_pcms = ARRAY_SIZE(esp32c6_pcms), + .pcms = esp32c6_pcms, + .num_controls = ARRAY_SIZE(esp32c6_controls), + .controls = esp32c6_controls, +}; diff --git a/src/platform/esp32c6/include/platform/drivers/interrupt.h b/src/platform/esp32c6/include/platform/drivers/interrupt.h new file mode 100644 index 000000000000..124944b10ef8 --- /dev/null +++ b/src/platform/esp32c6/include/platform/drivers/interrupt.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Espressif Systems / SOF Project + */ + +#ifndef __PLATFORM_DRIVERS_INTERRUPT_H__ +#define __PLATFORM_DRIVERS_INTERRUPT_H__ + +#endif /* __PLATFORM_DRIVERS_INTERRUPT_H__ */ diff --git a/src/platform/esp32c6/include/platform/lib/clk.h b/src/platform/esp32c6/include/platform/lib/clk.h new file mode 100644 index 000000000000..3082c2340520 --- /dev/null +++ b/src/platform/esp32c6/include/platform/lib/clk.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Espressif Systems / SOF Project + */ + +#ifndef __PLATFORM_LIB_CLK_H__ +#define __PLATFORM_LIB_CLK_H__ + +#include + +#define CPU_DEFAULT_IDX 0 +#define CLK_CPU(x) (x) +#define NUM_CLOCKS 1 +#define NUM_CPU_FREQ 1 + +#define CLK_MAX_CPU_HZ 160000000 +#define CLK_DEFAULT_CPU_HZ 160000000 + +struct sof; +void platform_clock_init(struct sof *sof); + +#endif /* __PLATFORM_LIB_CLK_H__ */ diff --git a/src/platform/esp32c6/include/platform/lib/dai.h b/src/platform/esp32c6/include/platform/lib/dai.h new file mode 100644 index 000000000000..febef9ddf0be --- /dev/null +++ b/src/platform/esp32c6/include/platform/lib/dai.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Espressif Systems / SOF Project + */ + +#ifndef __PLATFORM_LIB_DAI_H__ +#define __PLATFORM_LIB_DAI_H__ + +#include + +#endif /* __PLATFORM_LIB_DAI_H__ */ diff --git a/src/platform/esp32c6/include/platform/lib/dma.h b/src/platform/esp32c6/include/platform/lib/dma.h new file mode 100644 index 000000000000..6c2356f7ab36 --- /dev/null +++ b/src/platform/esp32c6/include/platform/lib/dma.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Espressif Systems / SOF Project + */ + +#ifndef __PLATFORM_LIB_DMA_H__ +#define __PLATFORM_LIB_DMA_H__ + +#define PLATFORM_NUM_DMACS 1 + +#endif /* __PLATFORM_LIB_DMA_H__ */ diff --git a/src/platform/esp32c6/include/platform/lib/mailbox.h b/src/platform/esp32c6/include/platform/lib/mailbox.h new file mode 100644 index 000000000000..99bd6b222623 --- /dev/null +++ b/src/platform/esp32c6/include/platform/lib/mailbox.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Espressif Systems / SOF Project + */ + +#ifndef __PLATFORM_LIB_MAILBOX_H__ +#define __PLATFORM_LIB_MAILBOX_H__ + +#include +#include + +extern uint8_t sof_esp32_mailbox[0x4000]; +#define MAILBOX_BASE ((uintptr_t)sof_esp32_mailbox) + +#define MAILBOX_DSPBOX_OFFSET 0x0 +#define MAILBOX_DSPBOX_SIZE 0x1000 +#define MAILBOX_DSPBOX_BASE (MAILBOX_BASE + MAILBOX_DSPBOX_OFFSET) + +#define MAILBOX_HOSTBOX_OFFSET 0x1000 +#define MAILBOX_HOSTBOX_SIZE 0x1000 +#define MAILBOX_HOSTBOX_BASE (MAILBOX_BASE + MAILBOX_HOSTBOX_OFFSET) + +#define MAILBOX_STREAM_OFFSET 0x2000 +#define MAILBOX_STREAM_SIZE 0x1000 +#define MAILBOX_STREAM_BASE (MAILBOX_BASE + MAILBOX_STREAM_OFFSET) + +#define MAILBOX_DEBUG_OFFSET 0x3000 +#define MAILBOX_DEBUG_SIZE 0x800 +#define MAILBOX_DEBUG_BASE (MAILBOX_BASE + MAILBOX_DEBUG_OFFSET) + +#define MAILBOX_EXCEPTION_OFFSET 0x3800 +#define MAILBOX_EXCEPTION_SIZE 0x400 +#define MAILBOX_EXCEPTION_BASE (MAILBOX_BASE + MAILBOX_EXCEPTION_OFFSET) + +#define MAILBOX_TRACE_OFFSET 0x3C00 +#define MAILBOX_TRACE_SIZE 0x400 +#define MAILBOX_TRACE_BASE (MAILBOX_BASE + MAILBOX_TRACE_OFFSET) + +static inline void mailbox_sw_reg_write(size_t offset, uint32_t src) {} +static inline void mailbox_sw_regs_write(size_t offset, const void *src, size_t bytes) {} +static inline uint32_t mailbox_sw_reg_read(size_t offset) { return 0; } +static inline uint64_t mailbox_sw_reg_read64(size_t offset) { return 0; } + +#endif /* __PLATFORM_LIB_MAILBOX_H__ */ diff --git a/src/platform/esp32c6/include/platform/lib/memory.h b/src/platform/esp32c6/include/platform/lib/memory.h new file mode 100644 index 000000000000..a6da60e378ee --- /dev/null +++ b/src/platform/esp32c6/include/platform/lib/memory.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Espressif Systems / SOF Project + */ + +#ifndef __PLATFORM_LIB_MEMORY_H__ +#define __PLATFORM_LIB_MEMORY_H__ + +#include +#include +#include + +#define SRAM_BANK_SIZE 0x10000 +#define EPROMS_START 0 +#define HEAP_SYSTEM_SIZE 0x8000 +#define HEAP_RUNTIME_SIZE 0x8000 +#define HEAP_BUFFER_SIZE 0x10000 + +#define host_to_local(addr) (addr) +#define local_to_host(addr) (addr) + +#endif /* __PLATFORM_LIB_MEMORY_H__ */ diff --git a/src/platform/esp32c6/include/platform/lib/pm_runtime.h b/src/platform/esp32c6/include/platform/lib/pm_runtime.h new file mode 100644 index 000000000000..c772b52664a2 --- /dev/null +++ b/src/platform/esp32c6/include/platform/lib/pm_runtime.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Espressif Systems / SOF Project + */ + +#ifndef __PLATFORM_LIB_PM_RUNTIME_H__ +#define __PLATFORM_LIB_PM_RUNTIME_H__ + +struct pm_runtime_data; + +static inline void platform_pm_runtime_init(struct pm_runtime_data *prd) {} +static inline void platform_pm_runtime_get(struct pm_runtime_data *prd) {} +static inline void platform_pm_runtime_put(struct pm_runtime_data *prd) {} + +#endif /* __PLATFORM_LIB_PM_RUNTIME_H__ */ diff --git a/src/platform/esp32c6/include/platform/platform.h b/src/platform/esp32c6/include/platform/platform.h new file mode 100644 index 000000000000..44d257944224 --- /dev/null +++ b/src/platform/esp32c6/include/platform/platform.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Espressif Systems / SOF Project + */ + +#ifdef __SOF_PLATFORM_H__ + +#ifndef __PLATFORM_PLATFORM_H__ +#define __PLATFORM_PLATFORM_H__ + +#define PLATFORM_DEFAULT_CLOCK CLK_CPU(0) + +#define HOST_PAGE_SIZE 4096 +#define PLATFORM_PAGE_TABLE_SIZE 256 + +#define PLATFORM_MAX_CHANNELS 8 +#define PLATFORM_MAX_STREAMS 8 + +#endif /* __PLATFORM_PLATFORM_H__ */ + +#else + +#error "This file shouldn't be included from outside of sof/platform.h" + +#endif /* __SOF_PLATFORM_H__ */ diff --git a/src/platform/esp32c6/include/platform/trace/trace.h b/src/platform/esp32c6/include/platform/trace/trace.h new file mode 100644 index 000000000000..16128bc62cac --- /dev/null +++ b/src/platform/esp32c6/include/platform/trace/trace.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Espressif Systems / SOF Project + */ + +#ifndef __PLATFORM_TRACE_TRACE_H__ +#define __PLATFORM_TRACE_TRACE_H__ + +#endif /* __PLATFORM_TRACE_TRACE_H__ */ diff --git a/src/platform/esp32c6/lib/CMakeLists.txt b/src/platform/esp32c6/lib/CMakeLists.txt new file mode 100644 index 000000000000..a63b7cdf475c --- /dev/null +++ b/src/platform/esp32c6/lib/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: BSD-3-Clause + +sof_add_files( + clk.c +) diff --git a/src/platform/esp32c6/lib/clk.c b/src/platform/esp32c6/lib/clk.c new file mode 100644 index 000000000000..3b688c8f8439 --- /dev/null +++ b/src/platform/esp32c6/lib/clk.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Espressif Systems / SOF Project + */ + +#include +#include +#include + +static const struct freq_table platform_cpu_freq[] = { + { CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC, + CONFIG_SYS_CLOCK_TICKS_PER_SEC * 1000 }, +}; + +static struct clock_info platform_clocks_info[NUM_CLOCKS]; + +void platform_clock_init(struct sof *sof) +{ + int i; + + sof->clocks = platform_clocks_info; + + for (i = 0; i < CONFIG_CORE_COUNT; i++) { + sof->clocks[i] = (struct clock_info) { + .freqs_num = NUM_CPU_FREQ, + .freqs = platform_cpu_freq, + .default_freq_idx = CPU_DEFAULT_IDX, + .current_freq_idx = CPU_DEFAULT_IDX, + .notification_id = NOTIFIER_ID_CPU_FREQ, + .notification_mask = NOTIFIER_TARGET_CORE_MASK(i), + .set_freq = NULL, + }; + } +} diff --git a/src/platform/esp32c6/platform.c b/src/platform/esp32c6/platform.c new file mode 100644 index 000000000000..79fcdc877b1d --- /dev/null +++ b/src/platform/esp32c6/platform.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Espressif Systems / SOF Project + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(esp32c6_platform, CONFIG_SOF_LOG_LEVEL); + +uint8_t sof_esp32_mailbox[0x4000] __aligned(64); + +static const struct sof_ipc_fw_ready ready = { + .hdr = { + .cmd = SOF_IPC_FW_READY, + .size = sizeof(struct sof_ipc_fw_ready), + }, + .version = { + .hdr.size = sizeof(struct sof_ipc_fw_version), + .micro = SOF_MICRO, + .minor = SOF_MINOR, + .major = SOF_MAJOR, + .build = -1, + .date = "dtermin.\0", + .time = "fwready.\0", + .tag = SOF_TAG, + .abi_version = SOF_ABI_VERSION, + .src_hash = SOF_SRC_HASH, + }, + .flags = DEBUG_SET_FW_READY_FLAGS, +}; + +static const struct sof_ipc_window windows = { + .ext_hdr = { + .hdr.cmd = SOF_IPC_FW_READY, + .hdr.size = sizeof(struct sof_ipc_window), + .type = SOF_IPC_EXT_WINDOW, + }, + .num_windows = 3, + .window = { + { + .type = SOF_IPC_REGION_DOWNBOX, + .flags = 0, + .size = MAILBOX_HOSTBOX_SIZE, + .offset = MAILBOX_HOSTBOX_OFFSET, + }, + { + .type = SOF_IPC_REGION_UPBOX, + .id = 0, + .flags = 0, + .size = MAILBOX_DSPBOX_SIZE, + .offset = MAILBOX_DSPBOX_OFFSET, + }, + { + .type = SOF_IPC_REGION_STREAM, + .id = 0, + .flags = 0, + .size = MAILBOX_STREAM_SIZE, + .offset = MAILBOX_STREAM_OFFSET, + }, + }, +}; + +int platform_boot_complete(uint32_t boot_message) +{ + struct sof_ipc_reply reply; + + reply.error = 0; + reply.hdr.cmd = SOF_IPC_FW_READY; + reply.hdr.size = sizeof(reply); + + mailbox_hostbox_write(0, &reply, sizeof(reply)); + mailbox_hostbox_write(sizeof(reply), &ready, sizeof(ready)); + mailbox_hostbox_write(sizeof(reply) + sizeof(ready), + &windows, + sizeof(windows)); + + LOG_INF("ESP32-C6 platform boot complete"); + return 0; +} + +int platform_context_save(struct sof *sof) +{ + return 0; +} + +#include +#include +#include +#include + +SOF_DEFINE_REG_UUID(zipc_task); +extern struct task_ops ipc_task_ops; + +int platform_ipc_init(struct ipc *ipc) +{ + ipc_set_drvdata(ipc, NULL); + schedule_task_init_edf(&ipc->ipc_task, SOF_UUID(zipc_task_uuid), + &ipc_task_ops, ipc, 0, 0); + return 0; +} + +enum task_state ipc_platform_do_cmd(struct ipc *ipc) +{ + struct ipc_cmd_hdr *hdr = mailbox_validate(); + + if (hdr) + ipc_cmd(hdr); + return SOF_TASK_STATE_COMPLETED; +} + +int ipc_platform_send_msg(const struct ipc_msg *msg) +{ + mailbox_dspbox_write(0, msg->tx_data, msg->tx_size); + return 0; +} + +int platform_init(struct sof *sof) +{ + LOG_INF("Initializing ESP32-C6 SOF Platform..."); + + platform_clock_init(sof); + + /* Initialize EDF scheduler */ + scheduler_init_edf(); + + /* Initialize Zephyr domain and timer-based LL scheduler */ + sof->platform_timer_domain = zephyr_domain_init(PLATFORM_DEFAULT_CLOCK); + if (!sof->platform_timer_domain) { + LOG_ERR("Failed to initialize platform timer domain"); + return -ENODEV; + } + + zephyr_ll_scheduler_init(sof->platform_timer_domain); + + /* Initialize platform DMAC */ + dmac_init(sof); + + /* Initialize Zephyr native DAI subsystem */ + dai_init(sof); + + /* Initialize IPC */ + ipc_init(sof); + + LOG_INF("ESP32-C6 SOF Platform initialized successfully"); + return 0; +} + +void ipc_platform_complete_cmd(struct ipc *ipc) +{ + ARG_UNUSED(ipc); +} diff --git a/uuid-registry.txt b/uuid-registry.txt index 53b61626503b..0398d9d31641 100644 --- a/uuid-registry.txt +++ b/uuid-registry.txt @@ -177,6 +177,8 @@ c51dc642-a2e1-48df-a490e2748cb6363e tflmcly 04e3f894-2c5c-4f2e-8dc1694eeaab53fa tone e93326d8-0d14-4bf0-bcb9e063d3d80136 twb_sched 42f8060c-832f-4dbf-b24751e961997b34 up_down_mixer +9a84b5e2-63b7-4c59-bf0c9103c847de12 usb_audio +d274a0c8-4f81-4b72-a16938ef61a8b940 bt_audio 6f6b6f4b-6f73-7466-20e1e62b9779f003 userspace_proxy b77e677e-5ff4-4188-af14fba8bdbf8682 volume 8a171323-94a3-4e1d-afe9fe5dbaa4c393 volume4 diff --git a/west.yml b/west.yml index 2b23fe5d34ca..9bbf030c22ad 100644 --- a/west.yml +++ b/west.yml @@ -11,6 +11,8 @@ manifest: url-base: https://github.com/thesofproject - name: zephyrproject url-base: https://github.com/zephyrproject-rtos + - name: lgirdwood + url-base: https://github.com/lgirdwood # When upgrading projects here please run git log --oneline in the # project and if not too long then include the output in your commit @@ -71,8 +73,8 @@ manifest: - name: zephyr repo-path: zephyr - revision: 76280bce2a050376d57e14240687b5c283dea0e7 - remote: zephyrproject + revision: feature/esp32c6-dai-port + remote: lgirdwood # Import some projects listed in zephyr/west.yml@revision # @@ -83,12 +85,20 @@ manifest: name-whitelist: - hal_xtensa - hal_nxp + - hal_espressif - mbedtls - mipi-sys-t - lz4 - tinycrypt - cmsis_6 + - name: hal_espressif + repo-path: hal_espressif + path: modules/hal/espressif + revision: esp32p4-uac2 + remote: lgirdwood + west-commands: west/west-commands.yml + self: # Changes to submanifests/*.yml files _are_ effective; these have no # specified 'revision:' diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 0e5e9749b26b..e03695eb2775 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -8,6 +8,10 @@ if(CONFIG_ZEPHYR_POSIX) set(ARCH host) set(PLATFORM "posix") set(PLATFORM_HEADERS "posix") +elseif(CONFIG_PLATFORM_ESP32C6) + set(ARCH host) + set(PLATFORM "esp32c6") + set(PLATFORM_HEADERS "esp32c6") else() # firmware build supports only xtensa arch for now set(ARCH xtensa) @@ -173,6 +177,7 @@ target_include_directories(SOF INTERFACE ${SOF_SRC_PATH}/include) target_include_directories(SOF INTERFACE ${SOF_SRC_PATH}/arch/${ARCH}/include) target_include_directories(SOF INTERFACE ${sof_top_dir}/third_party/include) target_include_directories(SOF INTERFACE ${sof_top_dir}/zephyr/include) +target_include_directories(SOF INTERFACE ${PROJECT_BINARY_DIR}/include/generated) # SOF module init zephyr_library_named(modules_sof) @@ -195,7 +200,7 @@ zephyr_include_directories(${SOF_SRC_PATH}/include/sof/audio/module_adapter/libr # XTOS duplicate in sof/scripts/misc.cmake; keep in sync macro(is_zephyr ret) - if(CONFIG_ZEPHYR_SOF_MODULE) + if(CONFIG_ZEPHYR_SOF_MODULE OR CONFIG_SOF_FULL_ZEPHYR_APPLICATION) set(${ret} TRUE) else() set(${ret} FALSE) @@ -223,6 +228,41 @@ macro(sof_list_append_ifdef feature_toggle list) endif() endmacro() +# Setup SOF directories +set(SOF_ROOT_SOURCE_DIRECTORY ${sof_top_dir}) +set(SOF_ROOT_BINARY_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +# This generated/ directory is shared with Zephyr. +# PROJECT_BINARY_DIR is build/zephyr/ +set(GENERATED_DIRECTORY ${PROJECT_BINARY_DIR}/include/generated) +file(MAKE_DIRECTORY ${GENERATED_DIRECTORY}) + +set(VERSION_H_PATH ${GENERATED_DIRECTORY}/sof_versions.h) + +find_package(Python3 COMPONENTS Interpreter REQUIRED) +set(PYTHON3 "${Python3_EXECUTABLE}") +set(PYTHON_EXECUTABLE "${Python3_EXECUTABLE}") + +if (NOT CONFIG_COMPILER_INLINE_FUNCTION_OPTION) +target_compile_options(SOF INTERFACE -fno-inline-functions) +endif() + +# SOF needs `typeof`, `__VA_ARGS__` and maybe other GNU C99 +# extensions. TODO other flags required ? +target_compile_options(SOF INTERFACE $<$: -std=gnu99>) + +# Toolchain info +add_definitions(-DXCC_TOOLS_VERSION="${ZEPHYR_TOOLCHAIN_VARIANT}" -DCC_OPTIMIZE_FLAGS="${OPTIMIZATION_FLAG}") + +# create version information +include(../scripts/cmake/version.cmake) + +include(../scripts/cmake/uuid-registry.cmake) +add_dependencies(modules_sof uuid_reg_h) +if(TARGET app) + add_dependencies(app uuid_reg_h) +endif() + # directories with auxiliary modules need to be listed first # - llext dependencies need to added to linkage first add_subdirectory(../src/math/ math_unused_install/) @@ -412,6 +452,18 @@ if (CONFIG_SOC_MIMX9352_A55) set(PLATFORM "imx93_a55") endif() +if (CONFIG_PLATFORM_ESP32C6) + zephyr_library_sources( + ${SOF_PLATFORM_PATH}/esp32c6/platform.c + ${SOF_PLATFORM_PATH}/esp32c6/esp32c6_pipeline_def.c + ${SOF_PLATFORM_PATH}/esp32c6/lib/clk.c + lib/dma.c + ) + + set(PLATFORM "esp32c6") + zephyr_include_directories(${SOF_PLATFORM_PATH}/esp32c6/include) +endif() + if (CONFIG_SOC_MIMX9596_M7) zephyr_library_sources( @@ -602,9 +654,9 @@ zephyr_library_sources_ifdef(CONFIG_SOF_BOOT_TEST boot_test.c ) -zephyr_library_sources_ifdef(CONFIG_SHELL - sof_shell.c -) +if (CONFIG_SHELL AND NOT CONFIG_PLATFORM_ESP32P4 AND NOT CONFIG_PLATFORM_ESP32C6) + zephyr_library_sources(sof_shell.c) +endif() zephyr_syscall_header(include/sof/lib/cpu.h) zephyr_library_sources_ifdef(CONFIG_USERSPACE syscall/cpu.c) @@ -634,35 +686,6 @@ target_link_libraries(SOF INTERFACE zephyr_interface) # Linker snippet for the UUID table zephyr_linker_sources("ROM_SECTIONS" uuid-snippet.ld) -# Setup SOF directories -set(SOF_ROOT_SOURCE_DIRECTORY ${sof_top_dir}) -set(SOF_ROOT_BINARY_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - -# This generated/ directory is shared with Zephyr. -# PROJECT_BINARY_DIR is build/zephyr/ -set(GENERATED_DIRECTORY ${PROJECT_BINARY_DIR}/include/generated) - -set(VERSION_H_PATH ${GENERATED_DIRECTORY}/sof_versions.h) - -find_package(Python3 COMPONENTS Interpreter) -set(PYTHON3 "${Python3_EXECUTABLE}") - -if (NOT CONFIG_COMPILER_INLINE_FUNCTION_OPTION) -target_compile_options(SOF INTERFACE -fno-inline-functions) -endif() - -# SOF needs `typeof`, `__VA_ARGS__` and maybe other GNU C99 -# extensions. TODO other flags required ? -target_compile_options(SOF INTERFACE $<$: -std=gnu99>) - -# Toolchain info -add_definitions(-DXCC_TOOLS_VERSION="${ZEPHYR_TOOLCHAIN_VARIANT}" -DCC_OPTIMIZE_FLAGS="${OPTIMIZATION_FLAG}") - -# create version information -include(../scripts/cmake/version.cmake) - -include(../scripts/cmake/uuid-registry.cmake) - # Create Trace realtive file paths sof_append_relative_path_definitions(modules_sof) diff --git a/zephyr/include/rtos/cache.h b/zephyr/include/rtos/cache.h index c67279655cbb..f48cf025fa22 100644 --- a/zephyr/include/rtos/cache.h +++ b/zephyr/include/rtos/cache.h @@ -31,11 +31,11 @@ #endif /* defined(CONFIG_XTENSA) && defined(CONFIG_INTEL) */ /* sanity check - make sure CONFIG_DCACHE_LINE_SIZE is valid */ -#if !defined(CONFIG_DCACHE_LINE_SIZE_DETECT) && (CONFIG_DCACHE_LINE_SIZE > 0) +#if !defined(CONFIG_DCACHE_LINE_SIZE_DETECT) && defined(CONFIG_DCACHE_LINE_SIZE) && (CONFIG_DCACHE_LINE_SIZE > 0) #define DCACHE_LINE_SIZE CONFIG_DCACHE_LINE_SIZE #else -#if defined(CONFIG_LIBRARY) || defined(CONFIG_ZEPHYR_POSIX) -#define DCACHE_LINE_SIZE 64 +#if defined(CONFIG_LIBRARY) || defined(CONFIG_ZEPHYR_POSIX) || defined(CONFIG_PLATFORM_ESP32C6) || defined(CONFIG_SOC_SERIES_ESP32C6) +#define DCACHE_LINE_SIZE 32 #else #error "Invalid cache configuration." #endif /* defined(CONFIG_LIBRARY) || defined(CONFIG_ZEPHYR_POSIX) */ diff --git a/zephyr/include/sof/lib/dma.h b/zephyr/include/sof/lib/dma.h index 261abe8cacea..cbf17be8f609 100644 --- a/zephyr/include/sof/lib/dma.h +++ b/zephyr/include/sof/lib/dma.h @@ -102,6 +102,8 @@ struct k_heap; #define SOF_DMA_DEV_MICFIL BIT(14) /**< connectable to MICFIL fifo */ #define SOF_DMA_DEV_SW BIT(15) /**< connectable to ACP SW */ #define SOF_DMA_DEV_TDM BIT(16) /**< connectable to ACP TDM I2S */ +#define SOF_DMA_DEV_I2S BIT(17) /**< connectable to ESP32 I2S */ +#define SOF_DMA_DEV_PDM BIT(18) /**< connectable to ESP32 PDM */ /* DMA access privilege flag */ #define SOF_DMA_ACCESS_EXCLUSIVE 1 diff --git a/zephyr/lib/alloc.c b/zephyr/lib/alloc.c index 89bd49286831..4899d8a83bae 100644 --- a/zephyr/lib/alloc.c +++ b/zephyr/lib/alloc.c @@ -142,6 +142,12 @@ extern char _mtk_adsp_sram_end; #define HEAPMEM_SIZE ((uint8_t *)SRAM_END - heapmem) #endif /* CONFIG_SYS_HEAP_SMALL_ONLY */ +#elif defined(CONFIG_PLATFORM_ESP32P4) || defined(CONFIG_SOC_SERIES_ESP32P4) || \ + defined(CONFIG_PLATFORM_ESP32C6) || defined(CONFIG_SOC_SERIES_ESP32C6) + +#define HEAPMEM_SIZE (HEAP_SYSTEM_SIZE + HEAP_RUNTIME_SIZE + HEAP_BUFFER_SIZE) +static uint8_t __aligned(PLATFORM_DCACHE_ALIGN) heapmem[HEAPMEM_SIZE]; + #else extern char _end[], _heap_sentry[]; diff --git a/zephyr/lib/dma.c b/zephyr/lib/dma.c index ad19a71ea84f..4ee15772338d 100644 --- a/zephyr/lib/dma.c +++ b/zephyr/lib/dma.c @@ -297,6 +297,18 @@ APP_SYSUSER_DATA struct sof_dma dma[] = { .z_dev = DEVICE_DT_GET(DT_NODELABEL(host_dma)), }, #endif +#if DT_NODE_HAS_STATUS(DT_NODELABEL(dma), okay) && defined(CONFIG_PLATFORM_ESP32C6) +{ + .plat_data = { + .dir = SOF_DMA_DIR_MEM_TO_DEV | SOF_DMA_DIR_DEV_TO_MEM, + .caps = SOF_DMA_CAP_GP_LP | SOF_DMA_CAP_GP_HP, + .devs = SOF_DMA_DEV_I2S | SOF_DMA_DEV_PDM, + .channels = 6, + .period_count = 16, + }, + .z_dev = DEVICE_DT_GET(DT_NODELABEL(dma)), +}, +#endif }; const struct dma_info lib_dma = {