diff --git a/Kconfig.sof b/Kconfig.sof index 3c8d8af05db9..8647ab29cab8 100644 --- a/Kconfig.sof +++ b/Kconfig.sof @@ -60,9 +60,7 @@ rsource "Kconfig.xtos" rsource "src/Kconfig" -if ZEPHYR_SOF_MODULE - rsource "Kconfig.zephyr-log" -endif +rsource "Kconfig.zephyr-log" menu "Debug" diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index fe9b208fe99f..b37a654047a3 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -19,6 +19,8 @@ if(DEFINED EXTRA_CONF_FILE AND endif() endif() +list(APPEND EXTRA_ZEPHYR_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/..") + find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) project(sample_sof) @@ -26,7 +28,43 @@ target_sources(app PRIVATE src/main.c ) -zephyr_library_include_directories(app PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/../src/arch/xtensa/include + +target_sources_ifdef(CONFIG_USB_DEVICE_STACK_NEXT app PRIVATE + src/usbd_init.c + ) + +target_include_directories(app PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/../src/include + ${CMAKE_CURRENT_SOURCE_DIR}/../zephyr/include + ${CMAKE_CURRENT_SOURCE_DIR}/../src/platform/${PLATFORM}/include + ${PROJECT_BINARY_DIR}/include/generated ) + +target_link_libraries(app PRIVATE SOF -Wl,--start-group modules_sof sof_kernel -Wl,--end-group) +if(TARGET uuid_reg_h) + add_dependencies(app uuid_reg_h modules_sof sof_kernel) +endif() + +if(CONFIG_XTENSA) + target_include_directories(app PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../src/arch/xtensa/include + ) +endif() + +function(sof_append_relative_path_definitions target) + get_target_property(sources ${target} SOURCES) + foreach(src ${sources}) + get_filename_component(ABS_PATH ${src} ABSOLUTE) + file(RELATIVE_PATH rel ${PROJECT_SOURCE_DIR} ${ABS_PATH}) + set_property( + SOURCE ${src} + APPEND + PROPERTY COMPILE_DEFINITIONS + RELATIVE_FILE="${rel}") + endforeach() +endfunction() + +sof_append_relative_path_definitions(app) + + diff --git a/app/Kconfig b/app/Kconfig index 339a16bed3e8..bb4b8d736fd6 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -7,6 +7,7 @@ choice COMPILER_CODEGEN_VLIW endchoice source "Kconfig.zephyr" +source "samples/subsys/usb/common/Kconfig.sample_usbd" if SOC_FAMILY_INTEL_ADSP rsource "boards/intel_adsp/Kconfig.defconfig" diff --git a/app/boards/nrf54l15dk_nrf54l15_cpuapp.conf b/app/boards/nrf54l15dk_nrf54l15_cpuapp.conf new file mode 100644 index 000000000000..a3c98cf41de0 --- /dev/null +++ b/app/boards/nrf54l15dk_nrf54l15_cpuapp.conf @@ -0,0 +1,64 @@ +# Target Platform & SOF Core +CONFIG_PLATFORM_NORDIC=y +CONFIG_PLATFORM_NRF54L15=y +CONFIG_SOF_FULL_ZEPHYR_APPLICATION=y +CONFIG_ZEPHYR_NATIVE_DRIVERS=y +CONFIG_IPC_MAJOR_4=y +CONFIG_COMP_BASEFW_IPC4=n +CONFIG_STATIC_PIPELINE=y +CONFIG_SCHED_CPU_MASK=n +CONFIG_SYS_HEAP_BIG_ONLY=y +CONFIG_BUILD_OUTPUT_HEX=y +CONFIG_BUILD_OUTPUT_BIN=y + +# Audio Processing Modules & ARM SIMD Optimizations +CONFIG_COMP_VOLUME=y +CONFIG_VOLUME_ARM_SIMD=y +CONFIG_COMP_IIR=y +CONFIG_EQ_IIR_ARM_SIMD=y +CONFIG_FILTER_ARM_SIMD=y +CONFIG_COMP_FIR=y +CONFIG_COMP_DRC=y +CONFIG_DRC_ARM_SIMD=y +CONFIG_COMP_CROSSOVER=y +CONFIG_COMP_DCBLOCK=y +CONFIG_DCBLOCK_ARM_SIMD=y +CONFIG_COMP_MULTIBAND_DRC=y +CONFIG_COMP_TONE=y +CONFIG_COMP_LEVEL_MULTIPLIER=y +CONFIG_COMP_SEL=y +CONFIG_COMP_MUX=y +CONFIG_FORMAT_CONVERT_ARM_SIMD=y +CONFIG_COMP_USB_AUDIO=y +CONFIG_COMP_BT_AUDIO=n +CONFIG_COMP_I2S_AUDIO=y + +# I2S Hardware Audio Interface (I2S20 Slave) +CONFIG_I2S=y +CONFIG_I2S_NRFX=y + +# Bluetooth Stack Disabled for Direct I2S Cable Loopback +CONFIG_BT=n + +# Hardware FPU & ARM DSP +CONFIG_FPU=y + +# System Resources & Memory (Lean footprint for 256KB RAM) +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 +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 & UART Console +CONFIG_LOG=y +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_MODE_DEFERRED=y +CONFIG_LOG_BUFFER_SIZE=4096 +CONFIG_LOG_BACKEND_UART=y +CONFIG_PRINTK=y +CONFIG_LOG_PRINTK=y +CONFIG_EARLY_CONSOLE=y + diff --git a/app/boards/nrf54l15dk_nrf54l15_cpuapp.overlay b/app/boards/nrf54l15dk_nrf54l15_cpuapp.overlay new file mode 100644 index 000000000000..5ac92c12321a --- /dev/null +++ b/app/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + i2s-node0 = &i2s20; + }; +}; + +&pinctrl { + i2s20_default: i2s20_default { + group1 { + psels = , + , + , + ; + }; + }; + + i2s20_sleep: i2s20_sleep { + group1 { + psels = , + , + , + ; + low-power-enable; + }; + }; +}; + +&i2s20 { + status = "okay"; + pinctrl-0 = <&i2s20_default>; + pinctrl-1 = <&i2s20_sleep>; + pinctrl-names = "default", "sleep"; +}; diff --git a/app/boards/nrf54lm20dk_nrf54lm20b_cpuapp.conf b/app/boards/nrf54lm20dk_nrf54lm20b_cpuapp.conf new file mode 100644 index 000000000000..c53129b42be9 --- /dev/null +++ b/app/boards/nrf54lm20dk_nrf54lm20b_cpuapp.conf @@ -0,0 +1,81 @@ +# Target Platform & SOF Core +CONFIG_PLATFORM_NORDIC=y +CONFIG_PLATFORM_NRF54LM20=y +CONFIG_SOF_FULL_ZEPHYR_APPLICATION=y +CONFIG_ZEPHYR_NATIVE_DRIVERS=y +CONFIG_IPC_MAJOR_4=y +CONFIG_COMP_BASEFW_IPC4=n +CONFIG_STATIC_PIPELINE=y +CONFIG_SCHED_CPU_MASK=n +CONFIG_SYS_HEAP_BIG_ONLY=y +CONFIG_BUILD_OUTPUT_HEX=y +CONFIG_BUILD_OUTPUT_BIN=y + +# Audio Processing Modules & ARM SIMD Optimizations +CONFIG_COMP_VOLUME=y +CONFIG_VOLUME_ARM_SIMD=y +CONFIG_COMP_IIR=y +CONFIG_EQ_IIR_ARM_SIMD=y +CONFIG_FILTER_ARM_SIMD=y +CONFIG_COMP_FIR=y +CONFIG_COMP_DRC=y +CONFIG_DRC_ARM_SIMD=y +CONFIG_COMP_CROSSOVER=y +CONFIG_COMP_DCBLOCK=y +CONFIG_DCBLOCK_ARM_SIMD=y +CONFIG_COMP_MULTIBAND_DRC=y +CONFIG_COMP_TONE=y +CONFIG_COMP_LEVEL_MULTIPLIER=y +CONFIG_COMP_SEL=y +CONFIG_COMP_MUX=y +CONFIG_FORMAT_CONVERT_ARM_SIMD=y +CONFIG_COMP_USB_AUDIO=y +CONFIG_COMP_BT_AUDIO=n +CONFIG_COMP_I2S_AUDIO=y + +# I2S Hardware Audio Interface (TDM Master) +CONFIG_I2S=y +CONFIG_I2S_NRF_TDM=y +CONFIG_I2S_NRF_TDM_TX_BLOCK_COUNT=32 +CONFIG_I2S_NRF_TDM_RX_BLOCK_COUNT=32 + +# Asynchronous deferred logging for audio real-time performance +CONFIG_LOG_MODE_DEFERRED=y +CONFIG_LOG_BUFFER_SIZE=8192 + +# USB Device & UAC2 Audio Class +CONFIG_USB_DEVICE_STACK_NEXT=y +CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=n +CONFIG_USBD_AUDIO2_CLASS=y +CONFIG_UDC_DWC2_PTI=y +CONFIG_SAMPLE_USBD_VID=0x1915 +CONFIG_SAMPLE_USBD_PID=0x5420 +CONFIG_SAMPLE_USBD_PRODUCT="Nordic nRF54LM20 SOF UAC2" + +# Bluetooth Stack Disabled for Direct I2S Cable Loopback +CONFIG_BT=n + +# Hardware FPU & ARM DSP +CONFIG_FPU=y + +# MPU Null-Pointer Detection override (0x0-0x400 unmapped on LM20) +CONFIG_NULL_POINTER_EXCEPTION_DETECTION_NONE=y + +# System Resources & Memory (512KB RAM) +CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 +CONFIG_ISR_STACK_SIZE=4096 +CONFIG_HW_STACK_PROTECTION=n +CONFIG_HEAP_MEM_POOL_SIZE=16384 +CONFIG_TICKLESS_KERNEL=n +CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000 + + +# Logging & UART Console +CONFIG_LOG=y +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_MODE_IMMEDIATE=y +CONFIG_LOG_BACKEND_UART=y +CONFIG_PRINTK=y +CONFIG_LOG_PRINTK=y +CONFIG_EARLY_CONSOLE=y diff --git a/app/boards/nrf54lm20dk_nrf54lm20b_cpuapp.overlay b/app/boards/nrf54lm20dk_nrf54lm20b_cpuapp.overlay new file mode 100644 index 000000000000..0ef7a4aa3a76 --- /dev/null +++ b/app/boards/nrf54lm20dk_nrf54lm20b_cpuapp.overlay @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + uac2_card: usb_audio2 { + compatible = "zephyr,uac2"; + status = "okay"; + full-speed; + high-speed; + audio-function = ; + + uac_aclk: aclk { + compatible = "zephyr,uac2-clock-source"; + clock-type = "internal-programmable"; + frequency-control = "host-programmable"; + sampling-frequencies = <48000>; + sof-synchronized; + }; + + /* ===== Playback from Host (ISO OUT) ===== */ + i2s_out_terminal: i2s_out_terminal { + compatible = "zephyr,uac2-input-terminal"; + clock-source = <&uac_aclk>; + terminal-type = ; + front-left; + front-right; + }; + + i2s_fu: i2s_fu { + compatible = "zephyr,uac2-feature-unit"; + data-source = <&i2s_out_terminal>; + mute-control = "host-programmable"; + volume-control = "host-programmable"; + }; + + pb_eq_fu: pb_eq_fu { + compatible = "zephyr,uac2-feature-unit"; + data-source = <&i2s_fu>; + mute-control = "host-programmable"; + }; + + pb_drc_fu: pb_drc_fu { + compatible = "zephyr,uac2-feature-unit"; + data-source = <&pb_eq_fu>; + mute-control = "host-programmable"; + }; + + i2s_speaker: i2s_speaker { + compatible = "zephyr,uac2-output-terminal"; + data-source = <&pb_drc_fu>; + clock-source = <&uac_aclk>; + terminal-type = ; + assoc-terminal = <&i2s_mic>; + }; + + /* ===== Capture to Host (ISO IN) ===== */ + i2s_mic: i2s_mic { + compatible = "zephyr,uac2-input-terminal"; + clock-source = <&uac_aclk>; + terminal-type = ; + front-left; + front-right; + }; + + cap_eq_fu: cap_eq_fu { + compatible = "zephyr,uac2-feature-unit"; + data-source = <&i2s_mic>; + mute-control = "host-programmable"; + }; + + i2s_in_fu: i2s_in_fu { + compatible = "zephyr,uac2-feature-unit"; + data-source = <&cap_eq_fu>; + mute-control = "host-programmable"; + volume-control = "host-programmable"; + }; + + i2s_in_terminal: i2s_in_terminal { + compatible = "zephyr,uac2-output-terminal"; + data-source = <&i2s_in_fu>; + clock-source = <&uac_aclk>; + terminal-type = ; + }; + + /* Audio Streaming Interfaces */ + as_i2s_out: as_i2s_out { + compatible = "zephyr,uac2-audio-streaming"; + linked-terminal = <&i2s_out_terminal>; + subslot-size = <2>; + bit-resolution = <16>; + }; + + as_i2s_in: as_i2s_in { + compatible = "zephyr,uac2-audio-streaming"; + linked-terminal = <&i2s_in_terminal>; + subslot-size = <2>; + bit-resolution = <16>; + }; + }; + + aliases { + i2s-node0 = &tdm; + }; +}; + +&pinctrl { + tdm_default: tdm_default { + group1 { + psels = , + , + , + ; + }; + }; + + tdm_sleep: tdm_sleep { + group1 { + psels = , + , + , + ; + low-power-enable; + }; + }; +}; + +&tdm { + status = "okay"; + pinctrl-0 = <&tdm_default>; + pinctrl-1 = <&tdm_sleep>; + pinctrl-names = "default", "sleep"; +}; diff --git a/app/src/main.c b/app/src/main.c index aa9ee6960d8d..c9e8ff4f3c5a 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -8,6 +8,19 @@ #include #include +#if defined(CONFIG_PLATFORM_NORDIC) +#if defined(CONFIG_USB_DEVICE_STACK_NEXT) +#include +#include +#include +#endif +#include +#include +#include +#if defined(CONFIG_COMP_BT_AUDIO) +#include +#endif +#endif LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG); /* define qemu boot tests if any qemu target is defined, add targets to end */ @@ -41,6 +54,35 @@ static int sof_app_main(void) LOG_INF("SOF initialized"); +#if defined(CONFIG_PLATFORM_NORDIC) +#if defined(CONFIG_USB_DEVICE_STACK_NEXT) + /* Register UAC2 class callbacks before initializing USB stack */ + const struct device *uac2_dev = DEVICE_DT_GET_ONE(zephyr_uac2); + if (device_is_ready(uac2_dev)) { + usbd_uac2_set_ops(uac2_dev, sof_get_uac2_ops(), NULL); + } else { + LOG_ERR("UAC2 device not ready"); + } + + /* Initialize USB device stack and UAC2 class */ + struct usbd_context *sample_usbd = sample_usbd_init_device(NULL); + if (sample_usbd) { + usbd_enable(sample_usbd); + LOG_INF("SOF USB UAC2 device stack enabled"); + } else { + LOG_ERR("Failed to initialize USB device context"); + } +#endif + +#if defined(CONFIG_COMP_BT_AUDIO) + /* Initialize Bluetooth Audio Service */ + bt_service_init(); +#endif + + /* Initialize static audio pipelines (EQ+DRC Playback & EQ Capture / Synth) */ + sof_static_pipelines_init(sof_get()); + LOG_INF("SOF static audio pipelines started"); +#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 @@ -53,6 +95,11 @@ static int sof_app_main(void) * and easy. */ k_thread_suspend(k_current_get()); +#endif +#if defined(CONFIG_PLATFORM_NORDIC) + while (1) { + k_sleep(K_FOREVER); + } #endif return 0; } diff --git a/app/src/sample_usbd.h b/app/src/sample_usbd.h new file mode 100644 index 000000000000..b12a62323d70 --- /dev/null +++ b/app/src/sample_usbd.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SAMPLES_SUBSYS_USB_COMMON_SAMPLE_USBD_H +#define ZEPHYR_SAMPLES_SUBSYS_USB_COMMON_SAMPLE_USBD_H + +#include +#include + +/* + * The scope of this header is limited to use in USB samples together with the + * new experimental USB device stack, you should not use it in your own + * application. However, you can use the code as a template. + */ + +/* + * This function uses Kconfig.sample_usbd options to configure and initialize a + * USB device. It configures sample's device context, default string descriptors, + * USB device configuration, registers any available class instances, and + * finally initializes USB device. It is limited to a single device with a + * single configuration instantiated in sample_usbd_init.c, which should be + * enough for a simple USB device sample. + * + * It returns the configured and initialized USB device context on success, + * otherwise it returns NULL. + */ +struct usbd_context *sample_usbd_init_device(usbd_msg_cb_t msg_cb); + +/* + * This function is similar to sample_usbd_init_device(), but does not + * initialize the device. It allows the application to set additional features, + * such as additional descriptors. + */ +struct usbd_context *sample_usbd_setup_device(usbd_msg_cb_t msg_cb); + +#endif /* ZEPHYR_SAMPLES_SUBSYS_USB_COMMON_SAMPLE_USBD_H */ diff --git a/app/src/usbd_init.c b/app/src/usbd_init.c new file mode 100644 index 000000000000..9d8c61bfc402 --- /dev/null +++ b/app/src/usbd_init.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2026 + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(usbd_sample_config, LOG_LEVEL_INF); + +/* By default, do not register the USB DFU class DFU mode instance. */ +static const char *const blocklist[] = { + "dfu_dfu", + NULL, +}; + +/* + * Instantiate a context named sample_usbd using the default USB device + * controller, the Zephyr project vendor ID, and the sample product ID. + */ +USBD_DEVICE_DEFINE(sample_usbd, + DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), + CONFIG_SAMPLE_USBD_VID, CONFIG_SAMPLE_USBD_PID); + +USBD_DESC_LANG_DEFINE(sample_lang); +USBD_DESC_MANUFACTURER_DEFINE(sample_mfr, CONFIG_SAMPLE_USBD_MANUFACTURER); + +USBD_DESC_PRODUCT_DEFINE(default_product, "Nordic nRF54LM20 SOF UAC2"); + +USBD_DESC_STRING_DEFINE(clock_master_str, "Clock Master", USBD_DUT_STRING_INTERFACE); + +IF_ENABLED(CONFIG_HWINFO, (USBD_DESC_SERIAL_NUMBER_DEFINE(sample_sn))); + +USBD_DESC_CONFIG_DEFINE(fs_cfg_desc, "FS Configuration"); +USBD_DESC_CONFIG_DEFINE(hs_cfg_desc, "HS Configuration"); + +static const uint8_t attributes = (IS_ENABLED(CONFIG_SAMPLE_USBD_SELF_POWERED) ? + USB_SCD_SELF_POWERED : 0) | + (IS_ENABLED(CONFIG_SAMPLE_USBD_REMOTE_WAKEUP) ? + USB_SCD_REMOTE_WAKEUP : 0); + +USBD_CONFIGURATION_DEFINE(sample_fs_config, + attributes, + CONFIG_SAMPLE_USBD_MAX_POWER, &fs_cfg_desc); + +USBD_CONFIGURATION_DEFINE(sample_hs_config, + attributes, + CONFIG_SAMPLE_USBD_MAX_POWER, &hs_cfg_desc); + +enum usbd_speed sample_usbd_get_speed(void) +{ + return usbd_bus_speed(&sample_usbd); +} + +static void sample_fix_code_triple(struct usbd_context *uds_ctx, + const enum usbd_speed speed) +{ + if (IS_ENABLED(CONFIG_USBD_CDC_ACM_CLASS) || + IS_ENABLED(CONFIG_USBD_CDC_ECM_CLASS) || + IS_ENABLED(CONFIG_USBD_CDC_NCM_CLASS) || + IS_ENABLED(CONFIG_USBD_MIDI2_CLASS) || + IS_ENABLED(CONFIG_USBD_AUDIO2_CLASS) || + IS_ENABLED(CONFIG_USBD_VIDEO_CLASS)) { + usbd_device_set_code_triple(uds_ctx, speed, + USB_BCC_MISCELLANEOUS, 0x02, 0x01); + } else { + usbd_device_set_code_triple(uds_ctx, speed, 0, 0, 0); + } +} + +struct usbd_context *sample_usbd_setup_device(usbd_msg_cb_t msg_cb) +{ + int err; + struct usbd_desc_node *product_desc = &default_product; + + err = usbd_add_descriptor(&sample_usbd, &sample_lang); + if (err) { + LOG_ERR("Failed to initialize language descriptor (%d)", err); + return NULL; + } + + err = usbd_add_descriptor(&sample_usbd, &sample_mfr); + if (err) { + LOG_ERR("Failed to initialize manufacturer descriptor (%d)", err); + return NULL; + } + + err = usbd_add_descriptor(&sample_usbd, product_desc); + if (err) { + LOG_ERR("Failed to initialize product descriptor (%d)", err); + return NULL; + } + + IF_ENABLED(CONFIG_HWINFO, ( + err = usbd_add_descriptor(&sample_usbd, &sample_sn); + if (err) { + LOG_ERR("Failed to initialize SN descriptor (%d)", err); + return NULL; + } + )) + + err = usbd_add_descriptor(&sample_usbd, &clock_master_str); + if (err) { + LOG_ERR("Failed to initialize Clock Master string descriptor (%d)", err); + return NULL; + } + LOG_INF("Registered Clock Master string descriptor at index %u", clock_master_str.str.idx); + + if (USBD_SUPPORTS_HIGH_SPEED && + usbd_caps_speed(&sample_usbd) == USBD_SPEED_HS) { + err = usbd_add_configuration(&sample_usbd, USBD_SPEED_HS, + &sample_hs_config); + if (err) { + LOG_ERR("Failed to add High-Speed configuration"); + return NULL; + } + + err = usbd_register_all_classes(&sample_usbd, USBD_SPEED_HS, 1, + blocklist); + if (err) { + LOG_ERR("Failed to register classes"); + return NULL; + } + + sample_fix_code_triple(&sample_usbd, USBD_SPEED_HS); + } + + err = usbd_add_configuration(&sample_usbd, USBD_SPEED_FS, + &sample_fs_config); + if (err) { + LOG_ERR("Failed to add Full-Speed configuration"); + return NULL; + } + + err = usbd_register_all_classes(&sample_usbd, USBD_SPEED_FS, 1, blocklist); + if (err) { + LOG_ERR("Failed to register classes"); + return NULL; + } + + sample_fix_code_triple(&sample_usbd, USBD_SPEED_FS); + usbd_self_powered(&sample_usbd, attributes & USB_SCD_SELF_POWERED); + + if (msg_cb != NULL) { + err = usbd_msg_register_cb(&sample_usbd, msg_cb); + if (err) { + LOG_ERR("Failed to register message callback"); + return NULL; + } + } + + return &sample_usbd; +} + +struct usbd_context *sample_usbd_init_device(usbd_msg_cb_t msg_cb) +{ + int err; + + if (sample_usbd_setup_device(msg_cb) == NULL) { + return NULL; + } + + err = usbd_init(&sample_usbd); + if (err) { + LOG_ERR("Failed to initialize device support"); + return NULL; + } + + return &sample_usbd; +} diff --git a/scripts/cmake/misc.cmake b/scripts/cmake/misc.cmake index 647b69c8c478..f652e40ca410 100644 --- a/scripts/cmake/misc.cmake +++ b/scripts/cmake/misc.cmake @@ -42,7 +42,7 @@ endfunction() # Zephyr duplicate in sof/zephyr/CMakeLists.txt; 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) diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 0ca532950f5c..fe98927c5a56 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -15,6 +15,9 @@ 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) +add_local_sources_ifdef(CONFIG_COMP_BT_AUDIO sof bt_audio.c bt_service.c) +add_local_sources_ifdef(CONFIG_COMP_I2S_AUDIO sof i2s_audio.c) # directories and files included conditionally (alphabetical order) if(CONFIG_COMP_ARIA) diff --git a/src/audio/Kconfig b/src/audio/Kconfig index dbd12f1626b2..f0edb08f55af 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -102,6 +102,22 @@ 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" + help + Enable USB Audio Class 2 (UAC2) streaming component for SOF. + +config COMP_BT_AUDIO + bool "Bluetooth Audio streaming component" + help + Enable Bluetooth Audio streaming component for SOF. + +config COMP_I2S_AUDIO + bool "I2S Hardware Audio streaming component" + default y if PLATFORM_NORDIC + help + Enable physical I2S/TDM Hardware Audio streaming component for SOF. + config COMP_CHAIN_DMA bool "Chain DMA component" depends on IPC_MAJOR_4 diff --git a/src/audio/bt_audio.c b/src/audio/bt_audio.c new file mode 100644 index 000000000000..a945fc62529a --- /dev/null +++ b/src/audio/bt_audio.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(comp_bt_audio, CONFIG_SOF_LOG_LEVEL); + +SOF_DEFINE_UUID("bt_audio", bt_audio_uuid, 0xd274a0c8, 0x4f81, 0x4b72, + 0xa1, 0x69, 0x38, 0xef, 0x61, 0xa8, 0xb9, 0x40); +DECLARE_TR_CTX(bt_audio_tr, SOF_UUID(bt_audio_uuid), LOG_LEVEL_INFO); + +static struct bt_audio_data s_bt_playback_inst; +static struct bt_audio_data s_bt_capture_inst; +static struct bt_audio_data *g_bt_playback_data = &s_bt_playback_inst; +static struct bt_audio_data *g_bt_capture_data = &s_bt_capture_inst; + +void bt_audio_init(void) +{ + g_bt_playback_data->sample_rate = 48000; + g_bt_playback_data->channels = 2; + g_bt_playback_data->frame_bytes = 4; + g_bt_playback_data->period_bytes = 192; + g_bt_playback_data->active = true; + g_bt_playback_data->started = false; + + g_bt_capture_data->sample_rate = 48000; + g_bt_capture_data->channels = 2; + g_bt_capture_data->frame_bytes = 4; + g_bt_capture_data->period_bytes = 192; + g_bt_capture_data->active = true; + g_bt_capture_data->started = false; +} + +void bt_audio_set_playback_rate(uint32_t rate) +{ + if (g_bt_playback_data) { + g_bt_playback_data->sample_rate = rate; + } +} + +void bt_audio_set_capture_rate(uint32_t rate) +{ + if (g_bt_capture_data) { + g_bt_capture_data->sample_rate = rate; + } +} + +bool bt_audio_is_playback_active(void) +{ + return g_bt_playback_data && g_bt_playback_data->active; +} + +bool bt_audio_is_capture_active(void) +{ + return g_bt_capture_data && g_bt_capture_data->active; +} + +void bt_audio_feed_playback_data(const void *src, size_t bytes) +{ + struct bt_audio_data *bad = g_bt_playback_data; + if (!bad || !bad->active) { + if (g_bt_capture_data && g_bt_capture_data->active) { + bad = g_bt_capture_data; + } + } + if (!bad || !bad->active) { + return; + } + + struct bt_audio_ring_buffer *ring = &bad->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + uint32_t avail = BT_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) % BT_AUDIO_RING_BUFFER_SIZE; + } + ring->count += bytes; + + k_spin_unlock(&ring->lock, key); +} + +size_t bt_audio_fetch_capture_data(void *dst, size_t bytes) +{ + struct bt_audio_data *bad = g_bt_capture_data; + if (!bad || !bad->active || bad->ring.count == 0) { + if (g_bt_playback_data && g_bt_playback_data->active && g_bt_playback_data->ring.count > 0) { + bad = g_bt_playback_data; + } + } + if (!bad || !bad->active) { + memset(dst, 0, bytes); + return bytes; + } + + struct bt_audio_ring_buffer *ring = &bad->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + /* Prebuffer at stream start to provide a stable jitter margin */ + if (!bad->started) { + if (ring->count < BT_AUDIO_PREBUFFER_BYTES) { + k_spin_unlock(&ring->lock, key); + memset(dst, 0, bytes); + return bytes; + } + bad->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) % BT_AUDIO_RING_BUFFER_SIZE; + } + ring->count -= to_read; + + if (to_read < bytes) { + memset(d + to_read, 0, bytes - to_read); + } + + k_spin_unlock(&ring->lock, key); + return bytes; +} + +bool bt_audio_peek_capture_data(void *dst, size_t bytes) +{ + if (!g_bt_capture_data || !g_bt_capture_data->active) { + return false; + } + + struct bt_audio_ring_buffer *ring = &g_bt_capture_data->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + if (ring->count < BT_AUDIO_PREBUFFER_BYTES) { + k_spin_unlock(&ring->lock, key); + return false; + } + + if (ring->count < bytes) { + k_spin_unlock(&ring->lock, key); + return false; + } + + uint8_t *d = (uint8_t *)dst; + uint32_t tail = ring->tail; + + for (size_t i = 0; i < bytes; i++) { + d[i] = ring->buf[tail]; + tail = (tail + 1) % BT_AUDIO_RING_BUFFER_SIZE; + } + + k_spin_unlock(&ring->lock, key); + return true; +} + +void bt_audio_consume_capture_data(size_t bytes) +{ + if (!g_bt_capture_data || !g_bt_capture_data->active) { + return; + } + + struct bt_audio_ring_buffer *ring = &g_bt_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) % BT_AUDIO_RING_BUFFER_SIZE; + ring->count -= to_consume; + + k_spin_unlock(&ring->lock, key); +} + +static struct comp_dev *bt_audio_new(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const void *spec) +{ + struct comp_dev *dev; + struct bt_audio_data *bad; + + comp_cl_info(drv, "bt_audio_new()"); + + dev = comp_alloc(drv, sizeof(*dev)); + if (!dev) { + return NULL; + } + + dev->ipc_config = *config; + + /* Use static instances to avoid dynamic heap allocations */ + if (config->pipeline_id == 1 || config->id == 15) { + bad = &s_bt_playback_inst; + g_bt_playback_data = bad; + } else { + bad = &s_bt_capture_inst; + g_bt_capture_data = bad; + } + + comp_set_drvdata(dev, bad); + + bad->sample_rate = 48000; + bad->channels = 2; + bad->frame_bytes = 4; + bad->period_bytes = (bad->sample_rate / 1000) * bad->frame_bytes; + + dev->state = COMP_STATE_READY; + return dev; +} + +static void bt_audio_free(struct comp_dev *dev) +{ + struct bt_audio_data *bad = comp_get_drvdata(dev); + + if (bad == g_bt_playback_data) { + g_bt_playback_data = NULL; + } + if (bad == g_bt_capture_data) { + g_bt_capture_data = NULL; + } + + /* bad points to static BSS instance, do not rfree */ + rfree(dev); +} + +static int bt_audio_params(struct comp_dev *dev, + struct sof_ipc_stream_params *params) +{ + struct bt_audio_data *bad = comp_get_drvdata(dev); + + bad->sample_rate = params->rate; + bad->channels = params->channels; + bad->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; + } + bad->period_bytes = params->host_period_bytes ? params->host_period_bytes : (frames_per_period * bad->frame_bytes); + + if (params->direction == SOF_IPC_STREAM_PLAYBACK) { + g_bt_playback_data = bad; + } else if (params->direction == SOF_IPC_STREAM_CAPTURE) { + g_bt_capture_data = bad; + } + + return 0; +} + +static int bt_audio_trigger(struct comp_dev *dev, int cmd) +{ + struct bt_audio_data *bad = comp_get_drvdata(dev); + + switch (cmd) { + case COMP_TRIGGER_START: + case COMP_TRIGGER_RELEASE: + bad->active = true; + bad->started = false; + dev->state = COMP_STATE_ACTIVE; + break; + case COMP_TRIGGER_STOP: + case COMP_TRIGGER_PAUSE: { + bad->active = false; + bad->started = false; + k_spinlock_key_t key = k_spin_lock(&bad->ring.lock); + bad->ring.head = 0; + bad->ring.tail = 0; + bad->ring.count = 0; + k_spin_unlock(&bad->ring.lock, key); + dev->state = COMP_STATE_READY; + break; + } + default: + break; + } + + return 0; +} + +static int bt_audio_copy(struct comp_dev *dev) +{ + struct bt_audio_data *bad = 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) { + /* BT Audio Source -> SOF Pipeline 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 bt_audio_ring_buffer *ring = &bad->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + if (!bad->started) { + if (ring->count < BT_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; + } + bad->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) % BT_AUDIO_RING_BUFFER_SIZE; + } + ring->count -= to_copy; + + if (to_copy < s16_bytes) { + 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 >= bad->period_bytes) { + uint8_t temp_buf[1024]; + uint32_t chunk = MIN(bad->period_bytes, sizeof(temp_buf)); + struct bt_audio_ring_buffer *ring = &bad->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + if (!bad->started) { + if (ring->count < BT_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; + } + bad->started = true; + } + + uint32_t available = ring->count; + uint32_t to_copy = MIN(chunk, available); + + for (size_t i = 0; i < to_copy; i++) { + temp_buf[i] = ring->buf[ring->tail]; + ring->tail = (ring->tail + 1) % BT_AUDIO_RING_BUFFER_SIZE; + } + ring->count -= to_copy; + + if (to_copy < chunk) { + memset(temp_buf + to_copy, 0, chunk - to_copy); + } + + 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); + } + } + } + + if (source) { + /* Pipeline Buffer -> BT Audio Sink Buffer */ + 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; +#elif defined(__ARM_ARCH) && defined(__VFP_FP__) && !defined(__SOFTFP__) + int32_t r; + __asm__ ("vcvt.s32.f32 %0, %1" : "=t"(r) : "t"(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 bt_audio_ring_buffer *ring = &bad->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + if (ring->count + s16_bytes > BT_AUDIO_RING_BUFFER_SIZE) { + uint32_t drop = (ring->count + s16_bytes) - BT_AUDIO_RING_BUFFER_SIZE; + drop = (drop + 3) & ~3; + if (drop > ring->count) { + drop = ring->count; + } + ring->tail = (ring->tail + drop) % BT_AUDIO_RING_BUFFER_SIZE; + ring->count -= drop; + } + + uint32_t free_space = BT_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) % BT_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 bt_audio_ring_buffer *ring = &bad->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + + if (ring->count + chunk > BT_AUDIO_RING_BUFFER_SIZE) { + uint32_t drop = (ring->count + chunk) - BT_AUDIO_RING_BUFFER_SIZE; + drop = (drop + 3) & ~3; + if (drop > ring->count) { + drop = ring->count; + } + ring->tail = (ring->tail + drop) % BT_AUDIO_RING_BUFFER_SIZE; + ring->count -= drop; + } + + uint32_t free_space = BT_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) % BT_AUDIO_RING_BUFFER_SIZE; + } + ring->count += to_copy; + + k_spin_unlock(&ring->lock, key); + } + } + } + + return 0; +} + +static int bt_audio_prepare(struct comp_dev *dev) +{ + dev->state = COMP_STATE_PREPARE; + return 0; +} + +static int bt_audio_reset(struct comp_dev *dev) +{ + struct bt_audio_data *bad = comp_get_drvdata(dev); + bad->active = false; + bad->started = false; + bad->ring.head = 0; + bad->ring.tail = 0; + bad->ring.count = 0; + dev->state = COMP_STATE_INIT; + return 0; +} + +static const struct comp_driver comp_bt_audio = { + .type = SOF_COMP_HOST, + .uid = SOF_RT_UUID(bt_audio_uuid), +#if !CONFIG_ZEPHYR_LOG + .tctx = &bt_audio_tr, +#endif + .ops = { + .create = bt_audio_new, + .free = bt_audio_free, + .params = bt_audio_params, + .prepare = bt_audio_prepare, + .trigger = bt_audio_trigger, + .copy = bt_audio_copy, + .reset = bt_audio_reset, + }, +}; + +static struct comp_driver_info comp_bt_audio_info = { + .drv = &comp_bt_audio, +}; + +UT_STATIC void sys_comp_bt_audio_init(void) +{ + comp_register(&comp_bt_audio_info); +} + +DECLARE_MODULE(sys_comp_bt_audio_init); +SOF_MODULE_INIT(bt_audio, sys_comp_bt_audio_init); diff --git a/src/audio/bt_service.c b/src/audio/bt_service.c new file mode 100644 index 000000000000..c58633e7e944 --- /dev/null +++ b/src/audio/bt_service.c @@ -0,0 +1,709 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_PLATFORM_ESP32P4) +#include +#include +#endif + +#if defined(CONFIG_BT) +#include +#include +#include +#include +#include +#endif + +LOG_MODULE_REGISTER(bt_service, CONFIG_SOF_LOG_LEVEL); + +#define ESP32P4_GPIO_C6_EN_BIT (1U << 22) /* GPIO 54 = 32 + 22 */ + +static const struct bt_audio_format_desc s_formats[BT_AUDIO_FMT_COUNT] = { + [BT_AUDIO_FMT_48K_STD] = { + .format = BT_AUDIO_FMT_48K_STD, + .name = "48k_std", + .codec_name = "LC3", + .sample_rate = 48000, + .bit_depth = 16, + .frame_duration_us = 10000, + .frame_duration_ms = 10, + .octets_per_codec_frame = 100, + .bitrate_kbps = 160, + .frame_samples = 480, + .frame_bytes = 1920, + }, + [BT_AUDIO_FMT_48K_HQ] = { + .format = BT_AUDIO_FMT_48K_HQ, + .name = "48k_hq", + .codec_name = "LC3", + .sample_rate = 48000, + .bit_depth = 16, + .frame_duration_us = 10000, + .frame_duration_ms = 10, + .octets_per_codec_frame = 120, + .bitrate_kbps = 192, + .frame_samples = 480, + .frame_bytes = 1920, + }, + [BT_AUDIO_FMT_48K_MAX] = { + .format = BT_AUDIO_FMT_48K_MAX, + .name = "48k_max", + .codec_name = "LC3", + .sample_rate = 48000, + .bit_depth = 24, + .frame_duration_us = 10000, + .frame_duration_ms = 10, + .octets_per_codec_frame = 240, + .bitrate_kbps = 384, + .frame_samples = 480, + .frame_bytes = 1920, + }, + [BT_AUDIO_FMT_44K] = { + .format = BT_AUDIO_FMT_44K, + .name = "44k", + .codec_name = "LC3", + .sample_rate = 44100, + .bit_depth = 16, + .frame_duration_us = 10000, + .frame_duration_ms = 10, + .octets_per_codec_frame = 130, + .bitrate_kbps = 208, + .frame_samples = 441, + .frame_bytes = 1764, + }, + [BT_AUDIO_FMT_48K_LL] = { + .format = BT_AUDIO_FMT_48K_LL, + .name = "48k_ll", + .codec_name = "LC3", + .sample_rate = 48000, + .bit_depth = 16, + .frame_duration_us = 7500, + .frame_duration_ms = 8, + .octets_per_codec_frame = 75, + .bitrate_kbps = 160, + .frame_samples = 360, + .frame_bytes = 1440, + }, + [BT_AUDIO_FMT_96K_HR] = { + .format = BT_AUDIO_FMT_96K_HR, + .name = "96k_hr", + .codec_name = "LC3plus-HR", + .sample_rate = 96000, + .bit_depth = 24, + .frame_duration_us = 5000, + .frame_duration_ms = 5, + .octets_per_codec_frame = 160, + .bitrate_kbps = 512, + .frame_samples = 480, + .frame_bytes = 1920, + }, + [BT_AUDIO_FMT_LPCM] = { + .format = BT_AUDIO_FMT_LPCM, + .name = "lpcm", + .codec_name = "LPCM (Raw)", + .sample_rate = 32000, + .bit_depth = 16, + .frame_duration_us = 10000, + .frame_duration_ms = 10, + .octets_per_codec_frame = 640, + .bitrate_kbps = 1024, + .frame_samples = 320, + .frame_bytes = 1280, + }, +}; + +static struct bt_service_status s_status = { + .c6_powered = false, + .state = BT_STATE_DISABLED, + .route = SOF_AUDIO_ROUTE_USB_DAI, + .format = BT_AUDIO_FMT_48K_STD, + .sample_rate = 48000, + .bitrate_kbps = 160, + .frame_bytes = 1920, + .codec_name = "LC3", + .tx_packets = 0, + .rx_packets = 0, + .tx_bytes = 0, + .rx_bytes = 0, + .rssi = -42, +}; + +static K_THREAD_STACK_DEFINE(s_bt_stack, 3072); +static struct k_thread s_bt_thread; +static volatile bool s_thread_running = true; + +#if defined(CONFIG_BT) +#define BT_UUID_SOF_AUDIO_VAL \ + BT_UUID_128_ENCODE(0x4c524700, 0x534f, 0x462d, 0x4175, 0x64696f537663) +#define BT_UUID_SOF_AUDIO_STREAM_VAL \ + BT_UUID_128_ENCODE(0x4c524701, 0x534f, 0x462d, 0x4175, 0x64696f537663) + +#define BT_UUID_SOF_AUDIO BT_UUID_DECLARE_128(BT_UUID_SOF_AUDIO_VAL) +#define BT_UUID_SOF_AUDIO_STREAM BT_UUID_DECLARE_128(BT_UUID_SOF_AUDIO_STREAM_VAL) +#endif + +#if defined(CONFIG_PLATFORM_NRF54LM20) && defined(CONFIG_BT) +static struct bt_conn *s_active_conn; +static volatile bool s_audio_notif_enabled; + +static void sof_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + ARG_UNUSED(attr); + s_audio_notif_enabled = (value == BT_GATT_CCC_NOTIFY); + LOG_INF("SOF Audio stream notifications %s by receiver", + s_audio_notif_enabled ? "ENABLED" : "DISABLED"); +} + +BT_GATT_SERVICE_DEFINE(sof_audio_svc, + BT_GATT_PRIMARY_SERVICE(BT_UUID_SOF_AUDIO), + BT_GATT_CHARACTERISTIC(BT_UUID_SOF_AUDIO_STREAM, + BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + NULL, NULL, NULL), + BT_GATT_CCC(sof_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), +); + +static void lm20_connected(struct bt_conn *conn, uint8_t err) +{ + if (err) { + LOG_WRN("Connection failed (err 0x%02x)", err); + return; + } + s_active_conn = bt_conn_ref(conn); + s_status.state = BT_STATE_BROADCASTING; + LOG_INF("Bluetooth receiver connected! Requesting 2M PHY & low-latency interval..."); + +#if defined(CONFIG_BT_USER_PHY_UPDATE) + struct bt_conn_le_phy_param phy = { + .options = BT_CONN_LE_PHY_OPT_NONE, + .pref_tx_phy = BT_GAP_LE_PHY_2M, + .pref_rx_phy = BT_GAP_LE_PHY_2M, + }; + bt_conn_le_phy_update(conn, &phy); +#endif + + struct bt_le_conn_param conn_param = { + .interval_min = 6, /* 7.5 ms */ + .interval_max = 8, /* 10.0 ms */ + .latency = 0, + .timeout = 400, /* 4 s */ + }; + bt_conn_le_param_update(conn, &conn_param); +} + +static struct k_work_delayable s_adv_work; + +static void adv_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + bt_service_start_broadcast(); +} + +static void lm20_disconnected(struct bt_conn *conn, uint8_t reason) +{ + LOG_INF("Bluetooth receiver disconnected (reason 0x%02x: %s)", + reason, bt_hci_err_to_str(reason)); + s_audio_notif_enabled = false; + if (s_active_conn) { + bt_conn_unref(s_active_conn); + s_active_conn = NULL; + } + s_status.state = BT_STATE_READY; + k_work_schedule(&s_adv_work, K_MSEC(100)); +} + +BT_CONN_CB_DEFINE(lm20_conn_cbs) = { + .connected = lm20_connected, + .disconnected = lm20_disconnected, +}; + +static const struct bt_data lm20_ad[] = { + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1), +}; + +static const struct bt_data lm20_sd[] = { + BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_SOF_AUDIO_VAL), +}; +#endif /* CONFIG_PLATFORM_NRF54LM20 && CONFIG_BT */ + +#if defined(CONFIG_PLATFORM_NRF54L15) && defined(CONFIG_BT) +static struct bt_conn *s_receiver_conn; +static struct bt_gatt_discover_params s_discover_params; +static struct bt_gatt_subscribe_params s_subscribe_params; +static struct bt_gatt_exchange_params s_mtu_exchange_params; +static struct bt_uuid_128 s_discover_uuid = BT_UUID_INIT_128(BT_UUID_SOF_AUDIO_STREAM_VAL); + +static void mtu_exchange_cb(struct bt_conn *conn, uint8_t err, + struct bt_gatt_exchange_params *params) +{ + LOG_INF("[BT RX] MTU exchange %s, negotiated MTU: %u bytes", + err == 0 ? "successful" : "failed", + bt_gatt_get_mtu(conn)); +} + +static uint8_t sof_audio_notify_cb(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + ARG_UNUSED(conn); + ARG_UNUSED(params); + + if (!data || length == 0) { + return BT_GATT_ITER_STOP; + } + + bt_audio_feed_playback_data(data, length); + s_status.rx_packets++; + s_status.rx_bytes += length; + + if (s_status.rx_packets == 1 || s_status.rx_packets % 500 == 0) { + LOG_INF("[BT RX] Received packet #%u (%u bytes, total %u KB)", + s_status.rx_packets, length, (uint32_t)(s_status.rx_bytes / 1024)); + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t gatt_discover_cb(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + if (!attr) { + LOG_DBG("GATT discovery completed"); + (void)memset(params, 0, sizeof(*params)); + return BT_GATT_ITER_STOP; + } + + LOG_INF("[BT RX] Found characteristic attribute handle: %u", attr->handle); + + if (!bt_uuid_cmp(params->uuid, BT_UUID_SOF_AUDIO_STREAM)) { + memcpy(&s_discover_uuid, BT_UUID_GATT_CCC, sizeof(s_discover_uuid)); + params->uuid = &s_discover_uuid.uuid; + params->start_handle = attr->handle + 1; + params->type = BT_GATT_DISCOVER_DESCRIPTOR; + s_subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); + + int err = bt_gatt_discover(conn, params); + if (err) { + LOG_ERR("GATT CCC discovery failed: %d", err); + } + } else { + s_subscribe_params.notify = sof_audio_notify_cb; + s_subscribe_params.value = BT_GATT_CCC_NOTIFY; + s_subscribe_params.ccc_handle = attr->handle; + + int err = bt_gatt_subscribe(conn, &s_subscribe_params); + if (err && err != -EALREADY) { + LOG_ERR("Failed to subscribe to audio notifications: %d", err); + } else { + s_status.state = BT_STATE_RECEIVING; + LOG_INF("[BT RX] Subscribed to SOF Audio Stream notifications! Audio reception active."); + } + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_STOP; +} + +static void l15_connected(struct bt_conn *conn, uint8_t err) +{ + if (err) { + LOG_WRN("Connection to SOF Transmitter failed (err 0x%02x)", err); + s_receiver_conn = NULL; + bt_service_start_scan(); + return; + } + + s_receiver_conn = bt_conn_ref(conn); + LOG_INF("[BT RX] Connected to SOF Audio Transmitter! Discovering audio service..."); + + s_mtu_exchange_params.func = mtu_exchange_cb; + int err_mtu = bt_gatt_exchange_mtu(conn, &s_mtu_exchange_params); + if (err_mtu) { + LOG_WRN("[BT RX] Failed to initiate MTU exchange: %d", err_mtu); + } + + memcpy(&s_discover_uuid, BT_UUID_SOF_AUDIO_STREAM, sizeof(s_discover_uuid)); + s_discover_params.uuid = &s_discover_uuid.uuid; + s_discover_params.func = gatt_discover_cb; + s_discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + s_discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + s_discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + + err = bt_gatt_discover(conn, &s_discover_params); + if (err) { + LOG_ERR("GATT discover failed: %d", err); + } +} + +static struct k_work_delayable s_scan_work; + +static void scan_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + bt_service_start_scan(); +} + +static void l15_disconnected(struct bt_conn *conn, uint8_t reason) +{ + LOG_INF("[BT RX] Disconnected from transmitter (reason 0x%02x: %s)", + reason, bt_hci_err_to_str(reason)); + if (s_receiver_conn) { + bt_conn_unref(s_receiver_conn); + s_receiver_conn = NULL; + } + s_status.state = BT_STATE_SCANNING; + k_work_schedule(&s_scan_work, K_MSEC(100)); +} + +BT_CONN_CB_DEFINE(l15_conn_cbs) = { + .connected = l15_connected, + .disconnected = l15_disconnected, +}; + +static bool parse_adv_name(struct bt_data *data, void *user_data) +{ + char *name = user_data; + switch (data->type) { + case BT_DATA_NAME_SHORTENED: + case BT_DATA_NAME_COMPLETE: { + uint8_t len = MIN(data->data_len, 31); + memcpy(name, data->data, len); + name[len] = '\0'; + return false; + } + default: + return true; + } +} + +static void scan_device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, + struct net_buf_simple *ad) +{ + ARG_UNUSED(type); + if (s_receiver_conn) { + return; + } + + char dev_name[32] = {0}; + bt_data_parse(ad, parse_adv_name, dev_name); + + if (strcmp(dev_name, "SOF-Audio-LM20") == 0) { + LOG_INF("[BT RX] Discovered '%s' (RSSI %d dBm), initiating connection...", + dev_name, rssi); + s_status.rssi = rssi; + + int err = bt_le_scan_stop(); + if (err) { + LOG_WRN("Failed to stop scan: %d", err); + } + + struct bt_conn *conn = NULL; + err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, + BT_LE_CONN_PARAM_DEFAULT, &conn); + if (err) { + LOG_ERR("Failed to create connection: %d", err); + k_work_schedule(&s_scan_work, K_MSEC(100)); + } else { + bt_conn_unref(conn); + } + } +} +#endif /* CONFIG_PLATFORM_NRF54L15 && CONFIG_BT */ + +/* Hardware control for ESP32-C6 co-processor via GPIO 54 */ +void bt_service_power_c6(bool enable) +{ +#if defined(CONFIG_PLATFORM_ESP32P4) + /* Ensure GPIO 54 is enabled as output */ + sys_write32(ESP32P4_GPIO_C6_EN_BIT, GPIO_ENABLE1_W1TS_REG); + + if (enable) { + sys_write32(ESP32P4_GPIO_C6_EN_BIT, GPIO_OUT1_W1TS_REG); + s_status.c6_powered = true; + s_status.state = BT_STATE_READY; + LOG_INF("ESP32-C6 co-processor powered ON (GPIO 54 asserted HIGH)"); + } else { + sys_write32(ESP32P4_GPIO_C6_EN_BIT, GPIO_OUT1_W1TC_REG); + s_status.c6_powered = false; + s_status.state = BT_STATE_DISABLED; + LOG_INF("ESP32-C6 co-processor powered OFF (GPIO 54 driven LOW in reset)"); + } +#else + s_status.c6_powered = enable; + s_status.state = enable ? BT_STATE_READY : BT_STATE_DISABLED; + LOG_INF("Nordic 2.4GHz on-chip Bluetooth radio %s", enable ? "READY" : "DISABLED"); +#endif +} + +int bt_service_start_broadcast(void) +{ +#if defined(CONFIG_PLATFORM_NRF54LM20) && defined(CONFIG_BT) + int err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, lm20_ad, ARRAY_SIZE(lm20_ad), + lm20_sd, ARRAY_SIZE(lm20_sd)); + if (err && err != -EALREADY) { + LOG_ERR("Failed to start advertising: %d", err); + return err; + } + s_status.state = BT_STATE_BROADCASTING; + LOG_INF("Bluetooth LE Audio advertising started as '%s'", CONFIG_BT_DEVICE_NAME); + return 0; +#else + if (!s_status.c6_powered) { + bt_service_power_c6(true); + k_msleep(150); + } + + const struct bt_audio_format_desc *desc = &s_formats[s_status.format]; + s_status.state = BT_STATE_BROADCASTING; + LOG_INF("Bluetooth LE Audio Broadcast started: '%s' (%s @ %u Hz, %u kbps, %u ms SDU)", + desc->name, desc->codec_name, desc->sample_rate, desc->bitrate_kbps, desc->frame_duration_ms); + return 0; +#endif +} + +int bt_service_stop_broadcast(void) +{ +#if defined(CONFIG_BT_BROADCASTER) + bt_le_adv_stop(); +#endif + if (s_status.state == BT_STATE_BROADCASTING) { + s_status.state = BT_STATE_READY; + LOG_INF("Bluetooth LE Audio Broadcast stopped"); + } + return 0; +} + +int bt_service_start_scan(void) +{ +#if defined(CONFIG_PLATFORM_NRF54L15) && defined(CONFIG_BT) + struct bt_le_scan_param scan_param = { + .type = BT_LE_SCAN_TYPE_ACTIVE, + .options = BT_LE_SCAN_OPT_NONE, + .interval = BT_GAP_SCAN_FAST_INTERVAL, + .window = BT_GAP_SCAN_FAST_WINDOW, + }; + + int err = bt_le_scan_start(&scan_param, scan_device_found); + if (err && err != -EALREADY) { + LOG_ERR("Failed to start BLE scan: %d", err); + return err; + } + s_status.state = BT_STATE_SCANNING; + LOG_INF("[BT RX] Scanning for SOF Audio Transmitter ('SOF-Audio-LM20')..."); + return 0; +#else + if (!s_status.c6_powered) { + bt_service_power_c6(true); + k_msleep(150); + } + + s_status.state = BT_STATE_SCANNING; + LOG_INF("Bluetooth LE Audio scan started, listening for nearby BAP broadcast sources..."); + return 0; +#endif +} + +int bt_service_set_route(enum sof_audio_route route) +{ + s_status.route = route; + sof_static_pipeline_set_route(route); + LOG_INF("Audio routing updated: %s", + route == SOF_AUDIO_ROUTE_USB_DAI ? "USB <-> DAI (Default)" : + (route == SOF_AUDIO_ROUTE_BT_DAI ? "BT <-> DAI" : "USB <-> BT")); + return 0; +} + +enum sof_audio_route bt_service_get_route(void) +{ + return s_status.route; +} + +int bt_service_set_format(enum bt_audio_format fmt) +{ + if (fmt >= BT_AUDIO_FMT_COUNT) { + LOG_ERR("Invalid Bluetooth audio format: %d", fmt); + return -EINVAL; + } + + const struct bt_audio_format_desc *desc = &s_formats[fmt]; + s_status.format = fmt; + s_status.sample_rate = desc->sample_rate; + s_status.bitrate_kbps = desc->bitrate_kbps; + s_status.frame_bytes = desc->frame_bytes; + s_status.codec_name = desc->codec_name; + + bt_audio_set_playback_rate(desc->sample_rate); + bt_audio_set_capture_rate(desc->sample_rate); + + LOG_INF("Bluetooth audio format set to '%s' (%s @ %u Hz %u-bit, %u kbps, %u ms SDU, %u B PCM)", + desc->name, desc->codec_name, desc->sample_rate, desc->bit_depth, + desc->bitrate_kbps, desc->frame_duration_ms, desc->frame_bytes); + return 0; +} + +enum bt_audio_format bt_service_get_format(void) +{ + return s_status.format; +} + +const struct bt_audio_format_desc *bt_service_get_format_desc(enum bt_audio_format fmt) +{ + if (fmt >= BT_AUDIO_FMT_COUNT) { + return NULL; + } + return &s_formats[fmt]; +} + +const struct bt_audio_format_desc *bt_service_get_current_format_desc(void) +{ + return &s_formats[s_status.format]; +} + +int bt_service_format_from_name(const char *name, enum bt_audio_format *fmt) +{ + if (!name || !fmt) { + return -EINVAL; + } + + for (int i = 0; i < BT_AUDIO_FMT_COUNT; i++) { + if (strcmp(name, s_formats[i].name) == 0) { + *fmt = (enum bt_audio_format)i; + return 0; + } + } + return -ENOENT; +} + +void bt_service_get_status(struct bt_service_status *status) +{ + if (status) { + *status = s_status; + } +} + +/* Background worker thread for ISO audio streaming and loopback */ +static void bt_audio_stream_task(void *p1, void *p2, void *p3) +{ + ARG_UNUSED(p1); + ARG_UNUSED(p2); + ARG_UNUSED(p3); + + static uint8_t s_audio_buf[1920] __aligned(4); + + while (s_thread_running) { + const struct bt_audio_format_desc *desc = &s_formats[s_status.format]; + uint32_t frame_bytes = desc->frame_bytes ? desc->frame_bytes : 192; + uint32_t sleep_ms = desc->frame_duration_ms ? desc->frame_duration_ms : 10; + +#if defined(CONFIG_PLATFORM_NRF54LM20) && defined(CONFIG_BT) + /* Transmitter logic on nRF54LM20: fetch from pipeline and stream via GATT notify */ + if (s_status.state == BT_STATE_BROADCASTING || s_audio_notif_enabled) { + size_t fetched = bt_audio_fetch_capture_data(s_audio_buf, frame_bytes); + if (fetched > 0) { + s_status.tx_packets++; + s_status.tx_bytes += fetched; + + if (s_active_conn && s_audio_notif_enabled) { + /* Stream over GATT notifications in chunks up to negotiated ATT MTU */ + uint16_t mtu = bt_gatt_get_mtu(s_active_conn); + uint16_t max_chunk = (mtu > 3) ? (mtu - 3) : 20; + uint16_t offset = 0; + while (offset < fetched) { + uint16_t chunk = MIN((uint16_t)(fetched - offset), max_chunk); + int err = bt_gatt_notify(s_active_conn, &sof_audio_svc.attrs[1], + &s_audio_buf[offset], chunk); + if (err) { + break; + } + offset += chunk; + } + } + + if (s_status.tx_packets == 1 || s_status.tx_packets % 500 == 0) { + LOG_INF("[BT TX] Streamed packet #%u (%u bytes, total %u KB, peer=%s)", + s_status.tx_packets, (uint32_t)fetched, + (uint32_t)(s_status.tx_bytes / 1024), + s_audio_notif_enabled ? "STREAMING" : "IDLE"); + } + } + } +#elif defined(CONFIG_PLATFORM_NRF54L15) && defined(CONFIG_BT) + /* Receiver telemetry on nRF54L15 */ + ARG_UNUSED(s_audio_buf); + ARG_UNUSED(frame_bytes); +#else + /* ESP32-P4 loopback logic */ + if (s_status.state == BT_STATE_BROADCASTING) { + size_t fetched = bt_audio_fetch_capture_data(s_audio_buf, frame_bytes); + if (fetched > 0) { + s_status.tx_packets++; + s_status.tx_bytes += fetched; + + /* In USB <-> BT or BT loopback mode, mirror back to playback sink */ + if (s_status.route == SOF_AUDIO_ROUTE_USB_BT || s_status.route == SOF_AUDIO_ROUTE_BT_DAI) { + bt_audio_feed_playback_data(s_audio_buf, fetched); + s_status.rx_packets++; + s_status.rx_bytes += fetched; + } + } + } else if (s_status.state == BT_STATE_RECEIVING) { + s_status.rx_packets++; + s_status.rx_bytes += frame_bytes; + } +#endif + + k_msleep(sleep_ms); + } +} + +int bt_service_init(void) +{ + LOG_INF("Initializing Bluetooth Audio Service..."); + + /* Initialize static BT audio ring buffers */ + bt_audio_init(); + +#if defined(CONFIG_PLATFORM_ESP32P4) + /* Power up onboard ESP32-C6 coprocessor */ + bt_service_power_c6(true); + k_msleep(100); +#elif defined(CONFIG_BT) + /* Initialize native Zephyr Bluetooth Subsystem on Nordic SoC */ + int err = bt_enable(NULL); + if (err) { + LOG_ERR("Zephyr Bluetooth init failed: %d", err); + return err; + } + s_status.c6_powered = true; + s_status.state = BT_STATE_READY; + LOG_INF("Zephyr native Bluetooth subsystem initialized successfully"); + +#if defined(CONFIG_PLATFORM_NRF54LM20) + k_work_init_delayable(&s_adv_work, adv_work_handler); + bt_service_start_broadcast(); +#elif defined(CONFIG_PLATFORM_NRF54L15) + k_work_init_delayable(&s_scan_work, scan_work_handler); + bt_service_start_scan(); +#endif +#endif + + /* Spawn background streaming task */ + k_thread_create(&s_bt_thread, s_bt_stack, K_THREAD_STACK_SIZEOF(s_bt_stack), + bt_audio_stream_task, NULL, NULL, NULL, + K_PRIO_PREEMPT(5), 0, K_NO_WAIT); + k_thread_name_set(&s_bt_thread, "bt_audio_svc"); + + LOG_INF("Bluetooth Audio Service initialized successfully"); + return 0; +} diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index f6a0b66d2956..41a2a422cf09 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -1428,6 +1428,7 @@ static int dai_prepare(struct comp_dev *dev) void dai_common_reset(struct dai_data *dd, struct comp_dev *dev) { +#if !defined(CONFIG_PLATFORM_NORDIC) struct dma_sg_config *config = &dd->config; /* @@ -1448,6 +1449,7 @@ void dai_common_reset(struct dai_data *dd, struct comp_dev *dev) buffer_free(dd->dma_buffer); dd->dma_buffer = NULL; } +#endif #ifdef CONFIG_UAOL_INTEL_ADSP uaol_free(dd); @@ -1532,6 +1534,9 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, if (!(dd->dai->dma_caps & SOF_DMA_CAP_HDA)) audio_stream_reset(&dd->dma_buffer->stream); +#if defined(CONFIG_PLATFORM_NORDIC) + dd->xrun = 0; +#endif /* only start the DAI if we are not XRUN handling */ if (dd->xrun == 0) { /* recover valid start position */ @@ -1596,6 +1601,9 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, case COMP_TRIGGER_PRE_START: case COMP_TRIGGER_PRE_RELEASE: /* only start the DAI if we are not XRUN handling */ +#if defined(CONFIG_PLATFORM_NORDIC) + dd->xrun = 0; +#endif if (dd->xrun) dd->xrun = 0; else @@ -1603,16 +1611,11 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, break; } -#ifdef CONFIG_IPC_MAJOR_3 - /* TODO: investigate why making this IPC version-agnostic - * breaks some Intel tests and check if doing so would be - * possible (or even make sense) on Intel platforms. - * - * See issue #8920 for details. - */ +#if defined(CONFIG_IPC_MAJOR_3) || defined(CONFIG_PLATFORM_NORDIC) + /* On static topology platforms (Nordic), DAI state must transition cleanly */ if (!ret) return comp_set_state(dev, cmd); -#endif /* CONFIG_IPC_MAJOR_3 */ +#endif /* CONFIG_IPC_MAJOR_3 || CONFIG_PLATFORM_NORDIC */ return ret; } diff --git a/src/audio/drc/drc_arm.c b/src/audio/drc/drc_arm.c index cb4795aefe08..5a1ba0fc5ccb 100644 --- a/src/audio/drc/drc_arm.c +++ b/src/audio/drc/drc_arm.c @@ -69,12 +69,13 @@ static int32_t volume_gain(const struct sof_drc_params *p, int32_t x) /* Update detector average across 32 frames */ void drc_update_detector_average(struct drc_state *state, const struct sof_drc_params *p, - int nbyte, - int nch) + size_t nbyte, + unsigned int nch) { int32_t detector_average = state->detector_average; int32_t abs_input_array[DRC_DIVISION_FRAMES]; - int div_start, i, ch; + int div_start, i; + unsigned int ch; const int16_t *sample16_p; const int32_t *sample32_p; int32_t gain; @@ -235,8 +236,8 @@ void drc_update_envelope(struct drc_state *state, const struct sof_drc_params *p /* Compress output across 4-frame unrolled iterations */ void drc_compress_output(struct drc_state *state, const struct sof_drc_params *p, - int nbyte, - int nch) + size_t nbyte, + unsigned int nch) { const int div_start = state->pre_delay_read_index; const int count = DRC_DIVISION_FRAMES >> 2; @@ -245,7 +246,8 @@ void drc_compress_output(struct drc_state *state, int32_t post_warp_compressor_gain; int32_t total_gain; int32_t tmp; - int i, j, ch, inc; + int i, j, inc; + unsigned int ch; int16_t *sample16_p; int32_t *sample32_p; int32_t lshift; diff --git a/src/audio/drc/drc_riscv.c b/src/audio/drc/drc_riscv.c index a939e4e09c12..529100de7bb3 100644 --- a/src/audio/drc/drc_riscv.c +++ b/src/audio/drc/drc_riscv.c @@ -74,12 +74,13 @@ static int32_t volume_gain(const struct sof_drc_params *p, int32_t x) /* Update detector average across 32 frames */ void drc_update_detector_average(struct drc_state *state, const struct sof_drc_params *p, - int nbyte, - int nch) + size_t nbyte, + unsigned int nch) { int32_t detector_average = state->detector_average; int32_t abs_input_array[DRC_DIVISION_FRAMES]; - int div_start, i, ch; + int div_start, i; + unsigned int ch; const int16_t *sample16_p; const int32_t *sample32_p; int32_t gain; @@ -240,8 +241,8 @@ void drc_update_envelope(struct drc_state *state, const struct sof_drc_params *p /* Compress output across 4-frame unrolled iterations */ void drc_compress_output(struct drc_state *state, const struct sof_drc_params *p, - int nbyte, - int nch) + size_t nbyte, + unsigned int nch) { const int div_start = state->pre_delay_read_index; const int count = DRC_DIVISION_FRAMES >> 2; @@ -250,7 +251,8 @@ void drc_compress_output(struct drc_state *state, int32_t post_warp_compressor_gain; int32_t total_gain; int32_t tmp; - int i, j, ch, inc; + int i, j, inc; + unsigned int ch; int16_t *sample16_p; int32_t *sample32_p; int32_t lshift; diff --git a/src/audio/i2s_audio.c b/src/audio/i2s_audio.c new file mode 100644 index 000000000000..f1014c6a20d7 --- /dev/null +++ b/src/audio/i2s_audio.c @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(comp_i2s_audio, CONFIG_SOF_LOG_LEVEL); + +SOF_DEFINE_UUID("i2s_audio", i2s_audio_uuid, 0x3d6b7c52, 0x19a0, 0x4e61, + 0x98, 0x22, 0x8a, 0x3f, 0x47, 0x10, 0xd5, 0x6e); +DECLARE_TR_CTX(i2s_audio_tr, SOF_UUID(i2s_audio_uuid), LOG_LEVEL_INFO); + +#define I2S_NODE DT_ALIAS(i2s_node0) +#if DT_NODE_HAS_STATUS_OKAY(I2S_NODE) +static const struct device *const s_i2s_dev = DEVICE_DT_GET(I2S_NODE); +#else +static const struct device *const s_i2s_dev = NULL; +#endif + +#define I2S_BLOCK_SIZE 192 /* 48 frames * 2 channels * 2 bytes */ +#define I2S_BLOCK_COUNT 32 + +K_MEM_SLAB_DEFINE_STATIC(i2s_tx_slab, I2S_BLOCK_SIZE, I2S_BLOCK_COUNT, 4); +K_MEM_SLAB_DEFINE_STATIC(i2s_rx_slab, I2S_BLOCK_SIZE, I2S_BLOCK_COUNT, 4); + +struct i2s_audio_data { + enum sof_ipc_stream_direction direction; + uint32_t sample_rate; + uint32_t channels; + uint32_t frame_bytes; + uint32_t period_bytes; + bool active; + bool configured; +}; + +static struct i2s_audio_data *g_i2s_playback_data = NULL; +static struct i2s_audio_data *g_i2s_capture_data = NULL; + +static struct i2s_audio_stats s_i2s_stats; +static bool s_hw_started = false; +static bool s_tx_started = false; +static bool s_rx_started = false; + +void i2s_audio_get_stats(struct i2s_audio_stats *stats) +{ + if (stats) { + *stats = s_i2s_stats; + } +} + +static int i2s_audio_configure_hw(struct i2s_audio_data *iad) +{ + if (!s_i2s_dev || !device_is_ready(s_i2s_dev)) { + LOG_ERR("I2S device '%s' not ready", s_i2s_dev ? s_i2s_dev->name : "NULL"); + return -ENODEV; + } + + enum i2s_dir dir = (iad->direction == SOF_IPC_STREAM_PLAYBACK) ? I2S_DIR_TX : I2S_DIR_RX; + struct i2s_config cfg = { + .word_size = 16, + .channels = 2, + .format = I2S_FMT_DATA_FORMAT_I2S | I2S_FMT_CLK_NF_NB, +#if defined(CONFIG_PLATFORM_NRF54LM20) + .options = I2S_OPT_BIT_CLK_CONTROLLER | I2S_OPT_FRAME_CLK_CONTROLLER, +#else + .options = I2S_OPT_BIT_CLK_TARGET | I2S_OPT_FRAME_CLK_TARGET, +#endif + .frame_clk_freq = 48000, + .mem_slab = (dir == I2S_DIR_TX) ? &i2s_tx_slab : &i2s_rx_slab, + .block_size = I2S_BLOCK_SIZE, + .timeout = 0, + }; + + int ret = i2s_configure(s_i2s_dev, dir, &cfg); + if (ret < 0) { + LOG_ERR("i2s_configure(%s) failed: %d", dir == I2S_DIR_TX ? "TX" : "RX", ret); + return ret; + } + + iad->configured = true; + LOG_INF("I2S hardware configured for %s (%s mode, 48kHz stereo)", + dir == I2S_DIR_TX ? "TX" : "RX", +#if defined(CONFIG_PLATFORM_NRF54LM20) + "MASTER" +#else + "SLAVE" +#endif + ); + return 0; +} + +static struct comp_dev *i2s_audio_new(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const void *spec) +{ + struct comp_dev *dev; + struct i2s_audio_data *iad; + + comp_cl_info(drv, "i2s_audio_new()"); + + dev = comp_alloc(drv, sizeof(*dev)); + if (!dev) { + return NULL; + } + + dev->ipc_config = *config; + + iad = rzalloc(SOF_MEM_FLAG_KERNEL, sizeof(*iad)); + if (!iad) { + rfree(dev); + return NULL; + } + + comp_set_drvdata(dev, iad); + + iad->sample_rate = 48000; + iad->channels = 2; + iad->frame_bytes = 4; + iad->period_bytes = 192; + iad->active = false; + iad->configured = false; + + /* Component ID 5 is I2S_TX (playback); Component ID 1 or 6 is I2S_RX (capture) */ + if (config->id == 5) { + iad->direction = SOF_IPC_STREAM_PLAYBACK; + g_i2s_playback_data = iad; + } else { + iad->direction = SOF_IPC_STREAM_CAPTURE; + g_i2s_capture_data = iad; + } + + dev->state = COMP_STATE_READY; + return dev; +} + +static void i2s_audio_free(struct comp_dev *dev) +{ + struct i2s_audio_data *iad = comp_get_drvdata(dev); + + if (iad == g_i2s_playback_data) { + g_i2s_playback_data = NULL; + } + if (iad == g_i2s_capture_data) { + g_i2s_capture_data = NULL; + } + + rfree(iad); + rfree(dev); +} + +static int i2s_audio_params(struct comp_dev *dev, + struct sof_ipc_stream_params *params) +{ + struct i2s_audio_data *iad = comp_get_drvdata(dev); + + iad->sample_rate = params->rate ? params->rate : 48000; + iad->channels = params->channels ? params->channels : 2; + iad->frame_bytes = 4; + iad->period_bytes = 192; + + return 0; +} + +static int i2s_audio_prepare(struct comp_dev *dev) +{ + struct i2s_audio_data *iad = comp_get_drvdata(dev); + + if (!iad->configured) { + int ret = i2s_audio_configure_hw(iad); + if (ret < 0) { + return ret; + } + } + + dev->state = COMP_STATE_PREPARE; + return 0; +} + +static int i2s_audio_trigger(struct comp_dev *dev, int cmd) +{ + struct i2s_audio_data *iad = comp_get_drvdata(dev); + enum i2s_dir dir = (iad->direction == SOF_IPC_STREAM_PLAYBACK) ? I2S_DIR_TX : I2S_DIR_RX; + bool has_both = (g_i2s_playback_data && g_i2s_capture_data); + + switch (cmd) { + case COMP_TRIGGER_PRE_START: + case COMP_TRIGGER_START: + case COMP_TRIGGER_RELEASE: { + if (!iad->configured) { + int ret = i2s_audio_configure_hw(iad); + if (ret < 0) { + return ret; + } + } + + iad->active = true; + dev->state = COMP_STATE_ACTIVE; + + /* Check if both TX and RX are active */ + bool both_active = (g_i2s_playback_data && g_i2s_playback_data->active && + g_i2s_capture_data && g_i2s_capture_data->active); + + if (has_both) { + if (both_active && !s_hw_started) { + void *block; + for (int i = 0; i < 8; i++) { + if (k_mem_slab_alloc(&i2s_tx_slab, &block, K_NO_WAIT) == 0) { + memset(block, 0, I2S_BLOCK_SIZE); + int wret = i2s_write(s_i2s_dev, block, I2S_BLOCK_SIZE); + if (wret < 0) { + LOG_ERR("i2s_write priming failed: %d", wret); + k_mem_slab_free(&i2s_tx_slab, block); + break; + } + } + } + int ret = i2s_trigger(s_i2s_dev, I2S_DIR_BOTH, I2S_TRIGGER_START); + if (ret < 0) { + LOG_ERR("i2s_trigger BOTH failed: %d", ret); + return ret; + } + s_hw_started = true; + s_tx_started = true; + s_rx_started = true; + LOG_INF("I2S hardware started in full-duplex (BOTH) mode"); + } + } else { + if (dir == I2S_DIR_TX && !s_tx_started) { + void *block; + for (int i = 0; i < 8; i++) { + if (k_mem_slab_alloc(&i2s_tx_slab, &block, K_NO_WAIT) == 0) { + memset(block, 0, I2S_BLOCK_SIZE); + int wret = i2s_write(s_i2s_dev, block, I2S_BLOCK_SIZE); + if (wret < 0) { + LOG_ERR("i2s_write priming failed: %d", wret); + k_mem_slab_free(&i2s_tx_slab, block); + break; + } + } + } + int ret = i2s_trigger(s_i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START); + if (ret < 0) { + LOG_ERR("i2s_trigger TX failed: %d", ret); + return ret; + } + s_tx_started = true; + LOG_INF("I2S hardware started in TX mode"); + } else if (dir == I2S_DIR_RX && !s_rx_started) { + int ret = i2s_trigger(s_i2s_dev, I2S_DIR_RX, I2S_TRIGGER_START); + if (ret < 0) { + LOG_ERR("i2s_trigger RX failed: %d", ret); + return ret; + } + s_rx_started = true; + LOG_INF("I2S hardware started in RX mode"); + } + } + break; + } + + case COMP_TRIGGER_STOP: + case COMP_TRIGGER_PAUSE: { + iad->active = false; + dev->state = COMP_STATE_READY; + if (has_both) { + bool any_active = (g_i2s_playback_data && g_i2s_playback_data->active) || + (g_i2s_capture_data && g_i2s_capture_data->active); + if (s_i2s_dev && s_hw_started && !any_active) { + i2s_trigger(s_i2s_dev, I2S_DIR_BOTH, I2S_TRIGGER_STOP); + s_hw_started = false; + s_tx_started = false; + s_rx_started = false; + LOG_INF("I2S hardware stopped (both streams inactive)"); + } + } else { + if (dir == I2S_DIR_TX && s_tx_started) { + i2s_trigger(s_i2s_dev, I2S_DIR_TX, I2S_TRIGGER_STOP); + s_tx_started = false; + } else if (dir == I2S_DIR_RX && s_rx_started) { + i2s_trigger(s_i2s_dev, I2S_DIR_RX, I2S_TRIGGER_STOP); + s_rx_started = false; + } + } + LOG_INF("I2S %s stream stopped", dir == I2S_DIR_TX ? "TX" : "RX"); + break; + } + + default: + break; + } + + return 0; +} + +static int i2s_audio_copy(struct comp_dev *dev) +{ + struct i2s_audio_data *iad = 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 (!iad || !iad->active || !s_hw_started) { + return 0; + } + + if (source && iad->direction == SOF_IPC_STREAM_PLAYBACK) { + enum sof_ipc_frame fmt = audio_stream_get_frm_fmt(&source->stream); + uint32_t avail_frames = audio_stream_get_avail_frames(&source->stream); + uint32_t copy_frames = MIN(avail_frames, dev->frames ? dev->frames : 48); + + void *tx_block = NULL; + if (k_mem_slab_alloc(&i2s_tx_slab, &tx_block, K_NO_WAIT) == 0) { + int16_t *s16_dst = (int16_t *)tx_block; + if (copy_frames > 0) { + uint32_t frames_to_copy = MIN(copy_frames, 48); + if (fmt == SOF_IPC_FRAME_FLOAT) { + float f_buf[96]; + 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_dst[i] = 32767; + } else if (val <= -32768.0f) { + s16_dst[i] = -32768; + } else { +#if defined(__ARM_ARCH) && defined(__VFP_FP__) && !defined(__SOFTFP__) + int32_t r; + __asm__ ("vcvt.s32.f32 %0, %1" : "=t"(r) : "t"(val)); + s16_dst[i] = (int16_t)r; +#else + s16_dst[i] = (int16_t)(val >= 0.0f ? (val + 0.5f) : (val - 0.5f)); +#endif + } + } + if (frames_to_copy < 48) { + memset(&s16_dst[frames_to_copy * 2], 0, (48 - frames_to_copy) * 2 * sizeof(int16_t)); + } + } else { + audio_stream_copy_to_linear(&source->stream, 0, s16_dst, 0, frames_to_copy * 2); + comp_update_buffer_consume(source, frames_to_copy * 2 * sizeof(int16_t)); + if (frames_to_copy < 48) { + memset(&s16_dst[frames_to_copy * 2], 0, (48 - frames_to_copy) * 2 * sizeof(int16_t)); + } + } + } else { + /* Send silence so EasyDMA never starves */ + memset(tx_block, 0, I2S_BLOCK_SIZE); + } + + int ret = i2s_write(s_i2s_dev, tx_block, I2S_BLOCK_SIZE); + if (ret < 0) { + k_mem_slab_free(&i2s_tx_slab, tx_block); + s_i2s_stats.tx_errors++; + if (ret == -EIO) { + /* Recover from driver state error: drop and restart with primed buffers */ + static uint32_t s_last_recover_ktime = 0; + uint32_t now = k_uptime_get_32(); + if (now - s_last_recover_ktime >= 100) { + s_last_recover_ktime = now; + enum i2s_dir r_dir = (g_i2s_playback_data && g_i2s_capture_data) ? + I2S_DIR_BOTH : I2S_DIR_TX; + i2s_trigger(s_i2s_dev, r_dir, I2S_TRIGGER_DROP); + void *b; + for (int i = 0; i < 8; i++) { + if (k_mem_slab_alloc(&i2s_tx_slab, &b, K_NO_WAIT) == 0) { + memset(b, 0, I2S_BLOCK_SIZE); + i2s_write(s_i2s_dev, b, I2S_BLOCK_SIZE); + } + } + i2s_trigger(s_i2s_dev, r_dir, I2S_TRIGGER_START); + } + } + } else { + s_i2s_stats.tx_frames += (copy_frames > 0) ? MIN(copy_frames, 48) : 48; + } + } else { + s_i2s_stats.tx_errors++; + if (copy_frames > 0) { + comp_update_buffer_consume(source, copy_frames * 2 * + (fmt == SOF_IPC_FRAME_FLOAT ? sizeof(float) : sizeof(int16_t))); + } + } + } + + if (sink && iad->direction == SOF_IPC_STREAM_CAPTURE) { + enum sof_ipc_frame fmt = audio_stream_get_frm_fmt(&sink->stream); + 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) { + uint32_t frames_to_copy = MIN(copy_frames, 48); + void *rx_block = NULL; + size_t rx_size = 0; + + int ret = i2s_read(s_i2s_dev, &rx_block, &rx_size); + if (ret == 0 && rx_block && rx_size >= I2S_BLOCK_SIZE) { + int16_t *s16_src = (int16_t *)rx_block; + if (fmt == SOF_IPC_FRAME_FLOAT) { + float f_buf[96]; + const float scale = 1.0f / 32768.0f; + for (size_t i = 0; i < frames_to_copy * 2; i++) { + f_buf[i] = (float)s16_src[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 { + audio_stream_copy_from_linear(s16_src, 0, &sink->stream, 0, frames_to_copy * 2); + comp_update_buffer_produce(sink, frames_to_copy * 2 * sizeof(int16_t)); + } + k_mem_slab_free(&i2s_rx_slab, rx_block); + s_i2s_stats.rx_frames += frames_to_copy; + } else { + if (rx_block) { + k_mem_slab_free(&i2s_rx_slab, rx_block); + } + if (fmt == SOF_IPC_FRAME_FLOAT) { + float f_silence[96] = {0}; + audio_stream_copy_from_linear(f_silence, 0, &sink->stream, 0, frames_to_copy * 2); + comp_update_buffer_produce(sink, frames_to_copy * 2 * sizeof(float)); + } else { + int16_t s_silence[96] = {0}; + audio_stream_copy_from_linear(s_silence, 0, &sink->stream, 0, frames_to_copy * 2); + comp_update_buffer_produce(sink, frames_to_copy * 2 * sizeof(int16_t)); + } + s_i2s_stats.rx_errors++; + } + } + } + + static uint32_t s_last_log_tx = 0; + static uint32_t s_last_log_rx = 0; + if (s_i2s_stats.tx_frames - s_last_log_tx >= 48000) { + s_last_log_tx = s_i2s_stats.tx_frames; + LOG_INF("I2S TX Streaming: %u frames transferred (+48,000 frames/s)", + s_i2s_stats.tx_frames); + } + if (s_i2s_stats.rx_frames - s_last_log_rx >= 48000) { + s_last_log_rx = s_i2s_stats.rx_frames; + LOG_INF("I2S RX Streaming: %u frames received (+48,000 frames/s)", + s_i2s_stats.rx_frames); + } + + return 0; +} + +static int i2s_audio_reset(struct comp_dev *dev) +{ + struct i2s_audio_data *iad = comp_get_drvdata(dev); + iad->active = false; + dev->state = COMP_STATE_INIT; + return 0; +} + +static const struct comp_driver comp_i2s_audio = { + .type = SOF_COMP_HOST, + .uid = SOF_RT_UUID(i2s_audio_uuid), +#if !CONFIG_ZEPHYR_LOG + .tctx = &i2s_audio_tr, +#endif + .ops = { + .create = i2s_audio_new, + .free = i2s_audio_free, + .params = i2s_audio_params, + .prepare = i2s_audio_prepare, + .trigger = i2s_audio_trigger, + .copy = i2s_audio_copy, + .reset = i2s_audio_reset, + }, +}; + +static struct comp_driver_info comp_i2s_audio_info = { + .drv = &comp_i2s_audio, +}; + +UT_STATIC void sys_comp_i2s_audio_init(void) +{ + comp_register(&comp_i2s_audio_info); +} + +DECLARE_MODULE(sys_comp_i2s_audio_init); +SOF_MODULE_INIT(i2s_audio, sys_comp_i2s_audio_init); diff --git a/src/audio/module_adapter/module_adapter_ipc4.c b/src/audio/module_adapter/module_adapter_ipc4.c index 6b66c89a8257..56d81eadf97f 100644 --- a/src/audio/module_adapter/module_adapter_ipc4.c +++ b/src/audio/module_adapter/module_adapter_ipc4.c @@ -523,7 +523,11 @@ int module_adapter_sink_src_prepare(struct comp_dev *dev) { struct processing_module *mod = comp_mod(dev); + if (!mod->num_of_sources && !mod->num_of_sinks) + module_adapter_update_sinks_sources(dev); + /* Prepare module */ return module_prepare(mod, mod->sources, mod->num_of_sources, mod->sinks, mod->num_of_sinks); } + diff --git a/src/audio/pipeline/CMakeLists.txt b/src/audio/pipeline/CMakeLists.txt index eca2f06857d0..8e6b9e34cf29 100644 --- a/src/audio/pipeline/CMakeLists.txt +++ b/src/audio/pipeline/CMakeLists.txt @@ -13,3 +13,6 @@ add_local_sources_ifdef(CONFIG_STATIC_PIPELINE sof static_pipeline_modules.c static_pipeline_uac2.c ) +add_local_sources_ifdef(CONFIG_PLATFORM_NORDIC 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..d2daea1ed514 --- /dev/null +++ b/src/audio/pipeline/sof_static_pipeline.c @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(sof_static_pipeline, CONFIG_SOF_LOG_LEVEL); + +#if defined(CONFIG_USBD_AUDIO2_CLASS) +#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_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_EQ_FU_ID 4 +#define CAPTURE_FU_ID 5 +#define PLAYBACK_TERM_ID 6 +#define CAPTURE_TERM_ID 7 +#endif + +#if defined(CONFIG_NOCACHE_MEMORY) +K_MEM_SLAB_DEFINE_IN_SECT_STATIC(uac2_rx_slab, __nocache, 256, 32, 64); +K_MEM_SLAB_DEFINE_IN_SECT_STATIC(uac2_tx_slab, __nocache, 256, 64, 64); +#else +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, +}; + +static uint32_t s_sof_diag_cnt; +uint32_t g_rx_pkt_cnt = 0; +uint32_t g_tx_pkt_cnt = 0; + +static void sof_uac2_send_capture_pending(const struct device *dev) +{ + if (!g_status.capture_active || !dev) { + return; + } + + uint32_t rate = g_status.sample_rate ? g_status.sample_rate : 48000; + uint32_t frame_bytes; + if (g_status.microframes) { + frame_bytes = (rate / 8000) * 4; /* 6 samples * 4 bytes = 24 bytes */ + } else { + frame_bytes = (rate / 1000) * 4; /* 48 samples * 4 bytes = 192 bytes */ + } + if (frame_bytes == 0 || frame_bytes > 512) { + frame_bytes = g_status.microframes ? 24 : 192; + } + +#if CONFIG_COMP_BT_AUDIO + if (g_status.audio_route != SOF_AUDIO_ROUTE_USB_BT && !usb_audio_has_capture_data(frame_bytes)) { + return; + } +#else + if (!usb_audio_has_capture_data(frame_bytes)) { + return; + } +#endif + + for (int iter = 0; iter < 2; iter++) { + void *buf = NULL; + if (k_mem_slab_alloc(&uac2_tx_slab, &buf, K_NO_WAIT) != 0) { + static uint32_t s_slab_alloc_fails; + s_slab_alloc_fails++; + if (s_slab_alloc_fails <= 5 || s_slab_alloc_fails % 100 == 0) { + LOG_WRN("[UAC2 TX SLAB FULL %u] Failed to allocate TX slab block!", s_slab_alloc_fails); + } + break; + } + + bool have_data = false; +#if 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) { + k_mem_slab_free(&uac2_tx_slab, buf); + break; + } + + 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) { + g_tx_pkt_cnt++; + if (!g_status.capture_mute) { +#if 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 { + k_mem_slab_free(&uac2_tx_slab, buf); + break; + } + } +} + +/* 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_DBG("[DIAG SOF %u] Active streaming", s_sof_diag_cnt); + } + } else { + s_sof_diag_cnt = 0; + } + + if (g_status.capture_active && dev) { + sof_uac2_send_capture_pending(dev); + } +} + +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(user_data); + + LOG_INF("[UAC2 Terminal] update: terminal %u, enabled %d, microframes %d (PB expected %u, CAP expected %u)", + terminal, enabled, microframes, PLAYBACK_TERM_ID, CAPTURE_TERM_ID); + + g_status.microframes = microframes; + + 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++; + g_rx_pkt_cnt++; + if (s_rx_pkt_cnt == 1 || s_rx_pkt_cnt % 1000 == 0) { + LOG_DBG("[SOF UAC2] Received playback packet #%u, size %u bytes", s_rx_pkt_cnt, size); + } + + if (buf && size > 0) { + if (g_status.playback_mute) { + memset(buf, 0, size); + } +#if CONFIG_COMP_BT_AUDIO + if (g_status.audio_route == SOF_AUDIO_ROUTE_BT_DAI) { + bt_audio_write_playback(buf, size); + } else +#endif + { + usb_audio_feed_playback_data(buf, size); + } + } +} + +void sof_uac2_buf_release(const struct device *dev, uint8_t terminal, + void *buf, void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(user_data); + + if (buf) { + k_mem_slab_free(&uac2_tx_slab, buf); + } + + if (terminal == CAPTURE_TERM_ID && dev) { + sof_uac2_send_capture_pending(dev); + } +} + +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; +} + +enum usbd_speed sample_usbd_get_speed(void); + +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; + + if (sample_usbd_get_speed() == USBD_SPEED_HS) { + /* High-Speed UAC2 explicit feedback: Q16.16 format in samples per microframe (8000/s) */ + return (uint32_t)(((uint64_t)rate << 16) / 8000); + } else { + /* Full-Speed UAC2 explicit feedback: Q10.14 format in samples per frame (1000/s) */ + return (uint32_t)(((uint64_t)rate << 14) / 1000); + } +} + +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_DBG("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 == CAPTURE_FU_ID) { + g_status.capture_mute = mute; + } + + return sof_static_kcontrol_set_by_uac2(entity_id, channel, mute ? 1 : 0, false); +} + +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_DBG("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; + return 0; + } else if (entity_id == CAPTURE_FU_ID) { + *volume = g_status.capture_volume; + return 0; + } + + int32_t val = 0; + int ret = sof_static_kcontrol_get_by_uac2(entity_id, channel, &val, true); + if (ret == 0) { + *volume = (int16_t)val; + } 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; +} + +#if defined(CONFIG_PLATFORM_NRF54L15) +extern const struct sof_static_topology g_nrf54l15_static_topology; +#elif defined(CONFIG_PLATFORM_NRF54LM20) +extern const struct sof_static_topology g_nrf54lm20_static_topology; +#endif + +int sof_static_pipelines_init(struct sof *sof) +{ + ARG_UNUSED(sof); +#if defined(CONFIG_PLATFORM_NRF54L15) + int ret = sof_static_topology_init(&g_nrf54l15_static_topology); + if (ret < 0) + return ret; + + g_status.clock_mode = SOF_CLOCK_SLAVE; + sof_static_pipeline_trigger(1, true); + return 0; +#elif defined(CONFIG_PLATFORM_NRF54LM20) + int ret = sof_static_topology_init(&g_nrf54lm20_static_topology); + if (ret < 0) + return ret; + + g_status.clock_mode = SOF_CLOCK_MASTER; + sof_static_pipeline_trigger(1, true); + sof_static_pipeline_trigger(2, true); + return 0; +#else + return -ENODEV; +#endif +} + +int sof_static_pipeline_set_route(enum sof_audio_route route) +{ + g_status.audio_route = route; + return 0; +} + +void sof_static_pipeline_get_status(struct sof_static_pipeline_status *status) +{ + if (status) { + *status = g_status; + } +} diff --git a/src/audio/usb_audio.c b/src/audio/usb_audio.c new file mode 100644 index 000000000000..140ffed90170 --- /dev/null +++ b/src/audio/usb_audio.c @@ -0,0 +1,603 @@ +#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_DBG("[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_has_capture_data(size_t bytes) +{ + if (!g_capture_data || !g_capture_data->active || !g_capture_data->started) { + return false; + } + + struct usb_audio_ring_buffer *ring = &g_capture_data->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + bool has_data = ring->count >= bytes; + k_spin_unlock(&ring->lock, key); + + return has_data; +} + +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_DBG("[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 (to_read > 0); +} + +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_PRE_START: + case COMP_TRIGGER_PRE_RELEASE: + dev->state = COMP_STATE_PRE_ACTIVE; + break; + case COMP_TRIGGER_START: + case COMP_TRIGGER_RELEASE: { + 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); + 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_PREPARE; + 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 (!uad || !uad->active) { + if (source) { + /* Capture inactive: consume data so source buffer doesn't overflow */ + uint32_t avail_bytes = audio_stream_get_avail_bytes(&source->stream); + if (avail_bytes > 0) { + comp_update_buffer_consume(source, avail_bytes); + } + } + return 0; + } + + 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_DBG("[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_DBG("[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) { + static uint32_t s_cap_overflow_cnt; + s_cap_overflow_cnt++; + if (s_cap_overflow_cnt <= 5 || s_cap_overflow_cnt % 100 == 0) { + LOG_DBG("[CAP OVERFLOW %u] ring_count=%u, chunk=%u", + s_cap_overflow_cnt, ring->count, chunk); + } + 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/include/ipc4/handler.h b/src/include/ipc4/handler.h index 3335c108fa29..78c323640abc 100644 --- a/src/include/ipc4/handler.h +++ b/src/include/ipc4/handler.h @@ -95,7 +95,7 @@ int ipc4_comp_connect(struct ipc *ipc, const struct ipc4_module_bind_unbind *bu) */ int ipc4_comp_disconnect(struct ipc *ipc, const struct ipc4_module_bind_unbind *bu); -#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) /** * \brief Increment the IPC compound message pre-start barrier counter. * \ingroup ipc4_envelope_api diff --git a/src/include/ipc4/notification.h b/src/include/ipc4/notification.h index c2a55dd32284..361cfa439d62 100644 --- a/src/include/ipc4/notification.h +++ b/src/include/ipc4/notification.h @@ -299,7 +299,7 @@ void send_mixer_underrun_notif_msg(uint32_t resource_id, uint32_t eos_flag, uint */ void ipc4_update_notification_mask(uint32_t ntfy_mask, uint32_t enabled_mask); -#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) /** * \brief Send generic resource event notification. diff --git a/src/include/sof/audio/bt_audio.h b/src/include/sof/audio/bt_audio.h new file mode 100644 index 000000000000..0d46956e139a --- /dev/null +++ b/src/include/sof/audio/bt_audio.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifndef __SOF_AUDIO_BT_AUDIO_H__ +#define __SOF_AUDIO_BT_AUDIO_H__ + +#include +#include +#include +#include + +/* UUID for Bluetooth Audio Component */ +/* d274a0c8-4f81-4b72-a169-38ef61a8b940 */ +#define SOF_BT_AUDIO_UUID \ + DECLARE_SOF_RT_UUID("bt_audio", 0xd274a0c8, 0x4f81, 0x4b72, \ + 0xa1, 0x69, 0x38, 0xef, 0x61, 0xa8, 0xb9, 0x40) + +#define BT_AUDIO_RING_BUFFER_SIZE 1920 +#define BT_AUDIO_PREBUFFER_BYTES 960 /* 5ms at 48kHz stereo 16-bit */ + +struct bt_audio_ring_buffer { + uint8_t buf[BT_AUDIO_RING_BUFFER_SIZE]; + volatile uint32_t head; + volatile uint32_t tail; + volatile uint32_t count; + struct k_spinlock lock; +}; + +struct bt_audio_data { + struct bt_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; +}; + +/* Bridge functions for feeding and fetching audio between BT stack and SOF pipeline */ +void bt_audio_init(void); +void bt_audio_feed_playback_data(const void *src, size_t bytes); +size_t bt_audio_fetch_capture_data(void *dst, size_t bytes); +bool bt_audio_peek_capture_data(void *dst, size_t bytes); +void bt_audio_consume_capture_data(size_t bytes); +void bt_audio_set_playback_rate(uint32_t rate); +void bt_audio_set_capture_rate(uint32_t rate); +bool bt_audio_is_playback_active(void); +bool bt_audio_is_capture_active(void); + +#endif /* __SOF_AUDIO_BT_AUDIO_H__ */ diff --git a/src/include/sof/audio/bt_service.h b/src/include/sof/audio/bt_service.h new file mode 100644 index 000000000000..41a4a43b2ac8 --- /dev/null +++ b/src/include/sof/audio/bt_service.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifndef __SOF_AUDIO_BT_SERVICE_H__ +#define __SOF_AUDIO_BT_SERVICE_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum bt_service_state { + BT_STATE_DISABLED = 0, + BT_STATE_READY = 1, + BT_STATE_BROADCASTING = 2, + BT_STATE_RECEIVING = 3, + BT_STATE_SCANNING = 4, +}; + +enum bt_audio_format { + BT_AUDIO_FMT_48K_STD = 0, /* Standard BAP (48_2_1): 48 kHz / 16b @ 160 kbps, 10ms, 100 octets/ch */ + BT_AUDIO_FMT_48K_HQ = 1, /* High-Quality BAP (48_4_1): 48 kHz / 16b @ 192 kbps, 10ms, 120 octets/ch */ + BT_AUDIO_FMT_48K_MAX = 2, /* High-Bitrate Studio (48_MAX): 48 kHz / 24b @ 384 kbps, 10ms, 240 octets/ch */ + BT_AUDIO_FMT_44K = 3, /* CD Audio BAP (441_2_1): 44.1 kHz / 16b @ 208 kbps, 10ms, 130 octets/ch */ + BT_AUDIO_FMT_48K_LL = 4, /* Low-Latency BAP (48_1_1): 48 kHz / 16b @ 160 kbps, 7.5ms, 75 octets/ch */ + BT_AUDIO_FMT_96K_HR = 5, /* LC3plus High-Resolution: 96 kHz / 24b @ 512 kbps, 5.0ms, 160 octets/ch */ + BT_AUDIO_FMT_LPCM = 6, /* Direct Linear PCM: 32 kHz / 16b @ 1024 kbps, 10ms, 640 octets/ch */ + BT_AUDIO_FMT_COUNT +}; + +struct bt_audio_format_desc { + enum bt_audio_format format; + const char *name; + const char *codec_name; + uint32_t sample_rate; + uint8_t bit_depth; + uint32_t frame_duration_us; + uint32_t frame_duration_ms; + uint16_t octets_per_codec_frame; + uint16_t bitrate_kbps; + uint16_t frame_samples; + uint16_t frame_bytes; +}; + +struct bt_service_status { + bool c6_powered; + enum bt_service_state state; + enum sof_audio_route route; + enum bt_audio_format format; + uint32_t sample_rate; + uint16_t bitrate_kbps; + uint16_t frame_bytes; + const char *codec_name; + uint32_t tx_packets; + uint32_t rx_packets; + uint32_t tx_bytes; + uint32_t rx_bytes; + int8_t rssi; +}; + +int bt_service_init(void); +void bt_service_power_c6(bool enable); +int bt_service_start_broadcast(void); +int bt_service_stop_broadcast(void); +int bt_service_start_scan(void); +int bt_service_set_route(enum sof_audio_route route); +enum sof_audio_route bt_service_get_route(void); +int bt_service_set_format(enum bt_audio_format fmt); +enum bt_audio_format bt_service_get_format(void); +const struct bt_audio_format_desc *bt_service_get_format_desc(enum bt_audio_format fmt); +const struct bt_audio_format_desc *bt_service_get_current_format_desc(void); +int bt_service_format_from_name(const char *name, enum bt_audio_format *fmt); +void bt_service_get_status(struct bt_service_status *status); + +#ifdef __cplusplus +} +#endif + +#endif /* __SOF_AUDIO_BT_SERVICE_H__ */ diff --git a/src/include/sof/audio/i2s_audio.h b/src/include/sof/audio/i2s_audio.h new file mode 100644 index 000000000000..8a46b610bd71 --- /dev/null +++ b/src/include/sof/audio/i2s_audio.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifndef __SOF_AUDIO_I2S_AUDIO_H__ +#define __SOF_AUDIO_I2S_AUDIO_H__ + +#include +#include +#include +#include + +/* UUID for I2S Hardware Audio Component */ +/* 3d6b7c52-19a0-4e61-9822-8a3f4710d56e */ +#define SOF_I2S_AUDIO_UUID \ + DECLARE_SOF_RT_UUID("i2s_audio", 0x3d6b7c52, 0x19a0, 0x4e61, \ + 0x98, 0x22, 0x8a, 0x3f, 0x47, 0x10, 0xd5, 0x6e) + +struct i2s_audio_stats { + uint32_t tx_frames; + uint32_t rx_frames; + uint32_t tx_errors; + uint32_t rx_errors; +}; + +void i2s_audio_get_stats(struct i2s_audio_stats *stats); + +#endif /* __SOF_AUDIO_I2S_AUDIO_H__ */ diff --git a/src/include/sof/audio/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index 78d29f793e38..cc631361d98b 100644 --- a/src/include/sof/audio/module_adapter/module/generic.h +++ b/src/include/sof/audio/module_adapter/module/generic.h @@ -195,7 +195,7 @@ int module_init(struct processing_module *mod); * @param alignment Required alignment in bytes. * @return Pointer to allocated buffer, or NULL on failure. */ -#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) __syscall void *mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment); #else void *z_impl_mod_balloc_align(struct processing_module *mod, size_t size, size_t alignment); @@ -228,7 +228,7 @@ void mod_heap_info(struct processing_module *mod, size_t *size, uintptr_t *start * @param alignment Required alignment in bytes. * @return Pointer to allocated memory, or NULL on failure. */ -#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) __syscall void *mod_alloc_ext(struct processing_module *mod, uint32_t flags, size_t size, size_t alignment); /** @@ -371,7 +371,7 @@ static inline void mod_ipc_msg_free(struct processing_module *mod, } #if CONFIG_COMP_BLOB -#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) /** * \brief Module-managed wrapper for comp_data_blob_handler_new_ext(). */ @@ -395,7 +395,7 @@ int z_impl_mod_data_blob_set(struct processing_module *mod, void mod_data_blob_handler_free(struct processing_module *mod, struct comp_data_blob_handler *dbh); #endif #if CONFIG_FAST_GET -#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) __syscall const void *mod_fast_get(struct processing_module *mod, const void * const dram_ptr, size_t size); #else @@ -598,7 +598,7 @@ static inline uint32_t module_get_lpt(struct processing_module *mod) return mod->dev->period; } -#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) #include #endif 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..dd30286deaa2 --- /dev/null +++ b/src/include/sof/audio/pipeline/sof_static_pipeline.h @@ -0,0 +1,65 @@ +/* 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 +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +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; + bool microframes; + 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_route(enum sof_audio_route route); +void sof_static_pipeline_get_status(struct sof_static_pipeline_status *status); + +struct uac2_ops; +const struct uac2_ops *sof_get_uac2_ops(void); + +#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..4a88cf0181ea --- /dev/null +++ b/src/include/sof/audio/usb_audio.h @@ -0,0 +1,52 @@ +/* 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_has_capture_data(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/include/sof/ipc/ipc_reply.h b/src/include/sof/ipc/ipc_reply.h index 915b6261c6bd..ef6249de42d0 100644 --- a/src/include/sof/ipc/ipc_reply.h +++ b/src/include/sof/ipc/ipc_reply.h @@ -14,7 +14,7 @@ struct sof_ipc_reply; * \brief reply to an IPC message. * @param[in] reply pointer to the reply structure. */ -#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) __syscall void ipc_msg_reply(struct sof_ipc_reply *reply); #include diff --git a/src/include/sof/ipc/msg.h b/src/include/sof/ipc/msg.h index 943faf6df813..2f1304f860f1 100644 --- a/src/include/sof/ipc/msg.h +++ b/src/include/sof/ipc/msg.h @@ -70,7 +70,7 @@ static inline struct ipc_msg *ipc_msg_init(struct k_heap *heap, * * @param msg The IPC message to remove from the queue. */ -#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) __syscall void ipc_msg_list_remove(struct ipc_msg *msg); #include #else @@ -103,7 +103,7 @@ void ipc_send_queued_msg(void); * @param data The message data. * @param high_priority True if a high priority message. */ -#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) __syscall void ipc_msg_send(struct ipc_msg *msg, void *data, bool high_priority); #include diff --git a/src/include/sof/lib/dai-zephyr.h b/src/include/sof/lib/dai-zephyr.h index defab09317f9..79d1129190cb 100644 --- a/src/include/sof/lib/dai-zephyr.h +++ b/src/include/sof/lib/dai-zephyr.h @@ -374,9 +374,7 @@ void dai_dma_position_update(struct dai_data *dd, struct comp_dev *dev); */ void dai_release_llp_slot(struct dai_data *dd); -/** - * \brief Retrieve a pointer to the Zephyr device structure for a DAI of a given type and index. - */ +#if defined(CONFIG_USERSPACE) __syscall const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32_t index); /** @@ -385,8 +383,14 @@ __syscall const struct device *dai_get_device(enum sof_ipc_dai_type type, uint32 * \return Pointer to the array of device pointers. */ const struct device **dai_get_device_list(size_t *count); -/** @}*/ #include +#else +const struct device *z_impl_dai_get_device(enum sof_ipc_dai_type type, uint32_t index); +#define dai_get_device z_impl_dai_get_device +const struct device **dai_get_device_list(size_t *count); +#endif + +/** @}*/ #endif /* __SOF_LIB_DAI_ZEPHYR_H__ */ diff --git a/src/include/sof/lib/vregion.h b/src/include/sof/lib/vregion.h index eab7e42a36ff..7a2a27516546 100644 --- a/src/include/sof/lib/vregion.h +++ b/src/include/sof/lib/vregion.h @@ -213,7 +213,9 @@ bool vregion_pinned_get(struct vregion *vr); */ int vregion_pinned_put(struct vregion *vr); +#if defined(CONFIG_USERSPACE) #include +#endif #else diff --git a/src/include/sof/lib_manager.h b/src/include/sof/lib_manager.h index 70ca5f97102c..06f47e1f2d77 100644 --- a/src/include/sof/lib_manager.h +++ b/src/include/sof/lib_manager.h @@ -230,7 +230,7 @@ int lib_manager_mod_create_priv(const struct comp_driver *drv, const void *spec, void **adapter_priv, struct userspace_context **userspace); -#if defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(__ZEPHYR__) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) __syscall int lib_manager_free_module(const uint32_t component_id); #include diff --git a/src/include/sof/schedule/dp_schedule.h b/src/include/sof/schedule/dp_schedule.h index 1dac74b600ef..70fd43fd2587 100644 --- a/src/include/sof/schedule/dp_schedule.h +++ b/src/include/sof/schedule/dp_schedule.h @@ -76,7 +76,7 @@ int scheduler_dp_task_init(struct task **task, size_t stack_size, uint32_t options); -#if defined(__ZEPHYR__) && CONFIG_SOF_FULL_ZEPHYR_APPLICATION +#if defined(CONFIG_USERSPACE) && defined(__ZEPHYR__) && CONFIG_SOF_FULL_ZEPHYR_APPLICATION /** * @brief Free kernel-side internal resources associated with a DP task. * @param[in,out] task Task being freed. @@ -91,6 +91,11 @@ __syscall void scheduler_dp_internal_free(struct task *task); */ __syscall void scheduler_dp_ll_tick(void); #include +#else +void z_impl_scheduler_dp_internal_free(struct task *task); +void z_impl_scheduler_dp_ll_tick(void); +#define scheduler_dp_internal_free z_impl_scheduler_dp_internal_free +#define scheduler_dp_ll_tick z_impl_scheduler_dp_ll_tick #endif /** diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index c369242640af..9ca47fdf0de0 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -555,8 +555,7 @@ struct k_mem_domain *zephyr_ll_mem_domain(void); */ struct k_thread *zephyr_ll_domain_thread(void); #endif /* CONFIG_SOF_USERSPACE_LL */ - -#ifdef CONFIG_SOF_FULL_ZEPHYR_APPLICATION +#if defined(CONFIG_USERSPACE) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) /** * @brief Allocate a completion semaphore for a Low-Latency task. * @param[in,out] task Target task. diff --git a/src/include/user/debug_stream_slot.h b/src/include/user/debug_stream_slot.h index 5eaae22115b5..de0081a49dfb 100644 --- a/src/include/user/debug_stream_slot.h +++ b/src/include/user/debug_stream_slot.h @@ -136,8 +136,13 @@ struct debug_stream_record; * @param[in] rec Pointer to abstract debug stream record to transmit. * @return 0 on success, -ENODEV if debug slot is unconfigured, or -ENOMEM if record exceeds capacity. */ +#if defined(CONFIG_USERSPACE) __syscall int debug_stream_slot_send_record(struct debug_stream_record *rec); #include +#else +int z_impl_debug_stream_slot_send_record(struct debug_stream_record *rec); +#define debug_stream_slot_send_record z_impl_debug_stream_slot_send_record +#endif #endif /* __SOC_DEBUG_WINDOW_SLOT_H__ */ diff --git a/src/platform/Kconfig b/src/platform/Kconfig index bb3b4920a405..4d649124537d 100644 --- a/src/platform/Kconfig +++ b/src/platform/Kconfig @@ -169,6 +169,12 @@ config IMX95 help Select if your target platform is imx95-compatible. +config PLATFORM_NORDIC + bool "Build for Nordic Semiconductor nRF54L Series SoC (Zephyr-only)" + select ZEPHYR_LOG + select ZEPHYR_NATIVE_DRIVERS + help + Select if your target platform is Nordic Semiconductor nRF54L Series SoC. config REMBRANDT bool "Build for Rembrandt" select XT_INTERRUPT_LEVEL_5 @@ -225,6 +231,26 @@ config ESP32P4_PIE_SIMD Enable ESP32-P4 custom Processor Instruction Extensions (PIE / Xespv2p2) 128-bit vector SIMD engine for audio processing kernels (volume, mixer, etc.). +if PLATFORM_NORDIC + +choice + prompt "Nordic Target SoC" + default PLATFORM_NRF54L15 + +config PLATFORM_NRF54L15 + bool "Nordic nRF54L15 (ARM Cortex-M33 @ 128 MHz, 256KB RAM)" + help + Select for nRF54L15-DK board. + +config PLATFORM_NRF54LM20 + bool "Nordic nRF54LM20 (ARM Cortex-M33 @ 128 MHz, 512KB RAM, High-Speed USB)" + help + Select for nRF54LM20-DK board. + +endchoice + +endif + # # For non-Zephyr builds like testbench and cmocka, # set core count separately. diff --git a/src/platform/nordic/include/platform/drivers/interrupt.h b/src/platform/nordic/include/platform/drivers/interrupt.h new file mode 100644 index 000000000000..cf1ee35b2f08 --- /dev/null +++ b/src/platform/nordic/include/platform/drivers/interrupt.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifndef __PLATFORM_DRIVERS_INTERRUPT_H__ +#define __PLATFORM_DRIVERS_INTERRUPT_H__ + +#endif /* __PLATFORM_DRIVERS_INTERRUPT_H__ */ diff --git a/src/platform/nordic/include/platform/lib/clk.h b/src/platform/nordic/include/platform/lib/clk.h new file mode 100644 index 000000000000..3cce41d1b4a1 --- /dev/null +++ b/src/platform/nordic/include/platform/lib/clk.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifndef __PLATFORM_LIB_CLK_H__ +#define __PLATFORM_LIB_CLK_H__ + +#include + +#define CPU_DEFAULT_IDX 0 +#define NUM_CPU_FREQ 1 +#define NUM_CLOCKS 1 + +struct sof; +void platform_clock_init(struct sof *sof); + +#endif /* __PLATFORM_LIB_CLK_H__ */ diff --git a/src/platform/nordic/include/platform/lib/dai.h b/src/platform/nordic/include/platform/lib/dai.h new file mode 100644 index 000000000000..0f878cd3794e --- /dev/null +++ b/src/platform/nordic/include/platform/lib/dai.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifndef __PLATFORM_LIB_DAI_H__ +#define __PLATFORM_LIB_DAI_H__ + +#include + +#endif /* __PLATFORM_LIB_DAI_H__ */ diff --git a/src/platform/nordic/include/platform/lib/dma.h b/src/platform/nordic/include/platform/lib/dma.h new file mode 100644 index 000000000000..5119f391d8d6 --- /dev/null +++ b/src/platform/nordic/include/platform/lib/dma.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifndef __PLATFORM_LIB_DMA_H__ +#define __PLATFORM_LIB_DMA_H__ + +#define PLATFORM_NUM_DMACS 0 + +#endif /* __PLATFORM_LIB_DMA_H__ */ diff --git a/src/platform/nordic/include/platform/lib/mailbox.h b/src/platform/nordic/include/platform/lib/mailbox.h new file mode 100644 index 000000000000..ec1c784206b9 --- /dev/null +++ b/src/platform/nordic/include/platform/lib/mailbox.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifndef __PLATFORM_LIB_MAILBOX_H__ +#define __PLATFORM_LIB_MAILBOX_H__ + +#include +#include + +extern uint8_t sof_nordic_mailbox[0x1000]; +#define MAILBOX_BASE ((uintptr_t)sof_nordic_mailbox) + +#define MAILBOX_DSPBOX_OFFSET 0x0 +#define MAILBOX_DSPBOX_SIZE 0x400 +#define MAILBOX_DSPBOX_BASE (MAILBOX_BASE + MAILBOX_DSPBOX_OFFSET) + +#define MAILBOX_HOSTBOX_OFFSET 0x400 +#define MAILBOX_HOSTBOX_SIZE 0x400 +#define MAILBOX_HOSTBOX_BASE (MAILBOX_BASE + MAILBOX_HOSTBOX_OFFSET) + +#define MAILBOX_STREAM_OFFSET 0x800 +#define MAILBOX_STREAM_SIZE 0x400 +#define MAILBOX_STREAM_BASE (MAILBOX_BASE + MAILBOX_STREAM_OFFSET) + +#define MAILBOX_DEBUG_OFFSET 0xC00 +#define MAILBOX_DEBUG_SIZE 0x200 +#define MAILBOX_DEBUG_BASE (MAILBOX_BASE + MAILBOX_DEBUG_OFFSET) + +#define MAILBOX_EXCEPTION_OFFSET 0xE00 +#define MAILBOX_EXCEPTION_SIZE 0x100 +#define MAILBOX_EXCEPTION_BASE (MAILBOX_BASE + MAILBOX_EXCEPTION_OFFSET) + +#define MAILBOX_TRACE_OFFSET 0xF00 +#define MAILBOX_TRACE_SIZE 0x100 +#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/nordic/include/platform/lib/memory.h b/src/platform/nordic/include/platform/lib/memory.h new file mode 100644 index 000000000000..f1aa94b014ad --- /dev/null +++ b/src/platform/nordic/include/platform/lib/memory.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifndef __PLATFORM_LIB_MEMORY_H__ +#define __PLATFORM_LIB_MEMORY_H__ + +#include +#include + +#define SRAM_BANK_SIZE 0x10000 +#define EPROMS_START 0 + +#if defined(CONFIG_PLATFORM_NRF54L15) +#define HEAP_SYSTEM_SIZE 0x3000 +#define HEAP_RUNTIME_SIZE 0x3000 +#define HEAP_BUFFER_SIZE 0x10000 +#else +#define HEAP_SYSTEM_SIZE 0x4000 +#define HEAP_RUNTIME_SIZE 0x4000 +#define HEAP_BUFFER_SIZE 0x20000 +#endif + +#ifndef PLATFORM_DCACHE_ALIGN +#define PLATFORM_DCACHE_ALIGN 32 +#endif +#ifndef PLATFORM_DCACHE_ALIGN_SHIFT +#define PLATFORM_DCACHE_ALIGN_SHIFT 5 +#endif + +#ifndef uncache_to_cache +#define uncache_to_cache(address) (address) +#endif +#ifndef cache_to_uncache +#define cache_to_uncache(address) (address) +#endif +#ifndef is_uncached +#define is_uncached(address) 0 +#endif +#define host_to_local(addr) (addr) +#define local_to_host(addr) (addr) + +#endif /* __PLATFORM_LIB_MEMORY_H__ */ diff --git a/src/platform/nordic/include/platform/lib/pm_runtime.h b/src/platform/nordic/include/platform/lib/pm_runtime.h new file mode 100644 index 000000000000..005fe1955d18 --- /dev/null +++ b/src/platform/nordic/include/platform/lib/pm_runtime.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifndef __PLATFORM_LIB_PM_RUNTIME_H__ +#define __PLATFORM_LIB_PM_RUNTIME_H__ + +#endif /* __PLATFORM_LIB_PM_RUNTIME_H__ */ diff --git a/src/platform/nordic/include/platform/platform.h b/src/platform/nordic/include/platform/platform.h new file mode 100644 index 000000000000..dd31b2639921 --- /dev/null +++ b/src/platform/nordic/include/platform/platform.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifdef __SOF_PLATFORM_H__ + +#ifndef __PLATFORM_PLATFORM_H__ +#define __PLATFORM_PLATFORM_H__ + +#include +#include + +#define PLATFORM_DEFAULT_CLOCK 0 +#define HOST_PAGE_SIZE 4096 +#define PLATFORM_PAGE_TABLE_SIZE 256 +#define DMA_TRACE_LOCAL_SIZE HOST_PAGE_SIZE + +#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/nordic/include/platform/trace/trace.h b/src/platform/nordic/include/platform/trace/trace.h new file mode 100644 index 000000000000..f2b0e2b46301 --- /dev/null +++ b/src/platform/nordic/include/platform/trace/trace.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#ifndef __PLATFORM_TRACE_TRACE_H__ +#define __PLATFORM_TRACE_TRACE_H__ + +#endif /* __PLATFORM_TRACE_TRACE_H__ */ diff --git a/src/platform/nordic/lib/clk.c b/src/platform/nordic/lib/clk.c new file mode 100644 index 000000000000..e76571e56516 --- /dev/null +++ b/src/platform/nordic/lib/clk.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#include +#include + +static const struct freq_table platform_cpu_freq[] = { + { + .freq = CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC, + .ticks_per_msec = 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/nordic/nrf54l15_pipeline_def.c b/src/platform/nordic/nrf54l15_pipeline_def.c new file mode 100644 index 000000000000..4a7135c21f35 --- /dev/null +++ b/src/platform/nordic/nrf54l15_pipeline_def.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#include +#include +#include +#include +#include + +/* UUID External Declarations */ +extern const struct sof_uuid volume4_uuid; +#define volume_uuid volume4_uuid +extern const struct sof_uuid eq_iir_uuid; +extern const struct sof_uuid drc_uuid; +extern const struct sof_uuid i2s_audio_uuid; + +/* Default 2-channel 4-band Parametric IIR EQ Coefficients */ +static const uint32_t nrf54l_default_iir_coef_2ch[51] = { + 0x00464f53, 0x00000000, 0x000000ac, 0x03013000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x000000ac, 0x00000002, 0x00000001, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000004, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xc12c82bd, + 0x7ed0b52e, 0x1fc7cc0c, 0xc07067e9, 0x1fc7cc0c, + 0x00000000, 0x00004000, 0xcad0cdef, 0x742e8c5d, + 0x0cdc9086, 0xe2f11723, 0x10b2f932, 0x00000000, + 0x00004000, 0xcf45334a, 0x68260de9, 0x0a54e176, + 0xe5d6cb75, 0x11fc1f3d, 0x00000000, 0x00004000, + 0xf2940609, 0xe25f3930, 0x0d69ba64, 0x1ad374c8, + 0x0d69ba64, 0xfffffffb, 0x000045bf +}; + +/* Default Speaker DRC Compressor Profile */ +static const uint32_t nrf54l_default_drc_coef[35] = { + 0x00464f53, 0x00000000, 0x0000006c, 0x0301a000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x0000006c, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0xe2000000, 0x14000000, + 0x0a000000, 0x00624dd3, 0x02061b8a, 0x06666666, + 0x00ba972f, 0x001e0c18, 0xffe04220, 0x0050f44e, + 0x08349f9a, 0x04d82cd3, 0x0071c71c, 0xff777777, + 0x001f77d8, 0x00000005, 0x00438000, 0x00047dd7, + 0x0025cea0, 0x00097dd7, 0x0000b5b1 +}; + +/* ------------------------------------------------------------------------- + * 1. Component / Module Declarations + * ------------------------------------------------------------------------- */ +static const struct sof_static_comp nrf54l15_comps[] = { + /* --- Audio Processing Pipeline (Pipeline 1) --- */ + SOF_STATIC_COMP_HOST( + .id = 1, .pipeline_id = 1, .name = "I2S_RX", + .uuid = &i2s_audio_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2) + ), + 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_MODULE( + .id = 3, .pipeline_id = 1, .name = "EQ_PB", + .uuid = &eq_iir_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2), + .init_blob = nrf54l_default_iir_coef_2ch, + .init_blob_size = sizeof(nrf54l_default_iir_coef_2ch) + ), + SOF_STATIC_COMP_MODULE( + .id = 4, .pipeline_id = 1, .name = "DRC_PB", + .uuid = &drc_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2), + .init_blob = nrf54l_default_drc_coef, + .init_blob_size = sizeof(nrf54l_default_drc_coef) + ), + SOF_STATIC_COMP_HOST( + .id = 5, .pipeline_id = 1, .name = "I2S_TX", + .uuid = &i2s_audio_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2) + ), +}; + +/* ------------------------------------------------------------------------- + * 2. Intermediate Audio Buffers + * ------------------------------------------------------------------------- */ +static const struct sof_static_buffer nrf54l15_buffers[] = { + /* Pipeline 1 Buffers */ + SOF_STATIC_BUFFER(.id = 1, .size = 1536, .fmt = SOF_IPC_FRAME_S16_LE), + SOF_STATIC_BUFFER(.id = 2, .size = 1536, .fmt = SOF_IPC_FRAME_S16_LE), + SOF_STATIC_BUFFER(.id = 3, .size = 1536, .fmt = SOF_IPC_FRAME_S16_LE), + SOF_STATIC_BUFFER(.id = 4, .size = 1536, .fmt = SOF_IPC_FRAME_S16_LE), +}; + +/* ------------------------------------------------------------------------- + * 3. Static Audio Routes + * ------------------------------------------------------------------------- */ +static const struct sof_static_route nrf54l15_routes[] = { + /* Pipeline 1: I2S_RX (1) -> [1] -> Vol (2) -> [2] -> EQ (3) -> [3] -> DRC (4) -> [4] -> I2S_TX (5) */ + 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), + SOF_STATIC_ROUTE(.src_comp_id = 3, .buffer_id = 3, .sink_comp_id = 4), + SOF_STATIC_ROUTE(.src_comp_id = 4, .buffer_id = 4, .sink_comp_id = 5), +}; + +/* ------------------------------------------------------------------------- + * 4. Controls (Kcontrols) + * ------------------------------------------------------------------------- */ +static const struct sof_static_kcontrol nrf54l15_controls[] = { + SOF_STATIC_KCONTROL_VOLUME( + .id = 1, + .name = "Master Playback Volume", + .target_comp_id = 2, + .channels = 2, + .min = 0, + .max = 65536, + .def = 65536 + ), + SOF_STATIC_KCONTROL_SWITCH( + .id = 2, + .name = "Master Playback Switch", + .target_comp_id = 2, + .channels = 2, + .min = 0, + .max = 1, + .def = 1 + ), +}; + +/* ------------------------------------------------------------------------- + * 5. Pipelines + * ------------------------------------------------------------------------- */ +static const struct sof_static_pipeline_desc nrf54l15_pipelines[] = { + { + .pipeline_id = 1, + .name = "Nordic I2S Echo 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 = 5, + }, +}; + +/* ------------------------------------------------------------------------- + * Top-Level Exported Topology + * ------------------------------------------------------------------------- */ +const struct sof_static_topology g_nrf54l15_static_topology = { + .name = "nRF54L15 Static Audio Pipeline", + .num_pipelines = ARRAY_SIZE(nrf54l15_pipelines), + .pipelines = nrf54l15_pipelines, + .num_comps = ARRAY_SIZE(nrf54l15_comps), + .comps = nrf54l15_comps, + .num_buffers = ARRAY_SIZE(nrf54l15_buffers), + .buffers = nrf54l15_buffers, + .num_routes = ARRAY_SIZE(nrf54l15_routes), + .routes = nrf54l15_routes, + .num_pcms = 0, + .pcms = NULL, + .num_controls = ARRAY_SIZE(nrf54l15_controls), + .controls = nrf54l15_controls, +}; diff --git a/src/platform/nordic/nrf54lm20_pipeline_def.c b/src/platform/nordic/nrf54lm20_pipeline_def.c new file mode 100644 index 000000000000..81b07f03f7af --- /dev/null +++ b/src/platform/nordic/nrf54lm20_pipeline_def.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#include +#include +#include +#include +#include + +/* UUID External Declarations */ +extern const struct sof_uuid volume4_uuid; +#define volume_uuid volume4_uuid +extern const struct sof_uuid eq_iir_uuid; +extern const struct sof_uuid drc_uuid; +extern const struct sof_uuid level_multiplier_uuid; +extern const struct sof_uuid usb_audio_uuid; +extern const struct sof_uuid i2s_audio_uuid; + +/* Default 2-channel 4-band Parametric IIR EQ Coefficients */ +static const uint32_t nrf54lm20_default_iir_coef_2ch[51] = { + 0x00464f53, 0x00000000, 0x000000ac, 0x03013000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x000000ac, 0x00000002, 0x00000001, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000004, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xc12c82bd, + 0x7ed0b52e, 0x1fc7cc0c, 0xc07067e9, 0x1fc7cc0c, + 0x00000000, 0x00004000, 0xcad0cdef, 0x742e8c5d, + 0x0cdc9086, 0xe2f11723, 0x10b2f932, 0x00000000, + 0x00004000, 0xcf45334a, 0x68260de9, 0x0a54e176, + 0xe5d6cb75, 0x11fc1f3d, 0x00000000, 0x00004000, + 0xf2940609, 0xe25f3930, 0x0d69ba64, 0x1ad374c8, + 0x0d69ba64, 0xfffffffb, 0x000045bf +}; + +/* Default Speaker DRC Compressor Profile */ +static const uint32_t nrf54lm20_default_drc_coef[35] = { + 0x00464f53, 0x00000000, 0x0000006c, 0x0301a000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x0000006c, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000001, 0xe2000000, 0x14000000, + 0x0a000000, 0x00624dd3, 0x02061b8a, 0x06666666, + 0x00ba972f, 0x001e0c18, 0xffe04220, 0x0050f44e, + 0x08349f9a, 0x04d82cd3, 0x0071c71c, 0xff777777, + 0x001f77d8, 0x00000005, 0x00438000, 0x00047dd7, + 0x0025cea0, 0x00097dd7, 0x0000b5b1 +}; + +/* ------------------------------------------------------------------------- + * 1. Component / Module Declarations + * ------------------------------------------------------------------------- */ +static const struct sof_static_comp nrf54lm20_comps[] = { + /* --- Audio Processing 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_MODULE( + .id = 3, .pipeline_id = 1, .name = "EQ_PB", + .uuid = &eq_iir_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2), + .init_blob = nrf54lm20_default_iir_coef_2ch, + .init_blob_size = sizeof(nrf54lm20_default_iir_coef_2ch) + ), + SOF_STATIC_COMP_MODULE( + .id = 4, .pipeline_id = 1, .name = "DRC_PB", + .uuid = &drc_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2), + .init_blob = nrf54lm20_default_drc_coef, + .init_blob_size = sizeof(nrf54lm20_default_drc_coef) + ), + SOF_STATIC_COMP_HOST( + .id = 5, .pipeline_id = 1, .name = "I2S_TX", + .uuid = &i2s_audio_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2) + ), + + /* --- Physical Loopback Capture Pipeline (Pipeline 2) --- */ + SOF_STATIC_COMP_HOST( + .id = 6, .pipeline_id = 2, .name = "I2S_RX", + .uuid = &i2s_audio_uuid, .direction = SOF_IPC_STREAM_CAPTURE, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2) + ), + SOF_STATIC_COMP_MODULE( + .id = 7, .pipeline_id = 2, .name = "LEVEL_RX", + .uuid = &level_multiplier_uuid, .direction = SOF_IPC_STREAM_CAPTURE, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2) + ), + SOF_STATIC_COMP_HOST( + .id = 8, .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 nrf54lm20_buffers[] = { + /* Pipeline 1 Buffers */ + SOF_STATIC_BUFFER(.id = 1, .size = 1536, .fmt = SOF_IPC_FRAME_S16_LE), + SOF_STATIC_BUFFER(.id = 2, .size = 1536, .fmt = SOF_IPC_FRAME_S16_LE), + SOF_STATIC_BUFFER(.id = 3, .size = 1536, .fmt = SOF_IPC_FRAME_S16_LE), + SOF_STATIC_BUFFER(.id = 4, .size = 1536, .fmt = SOF_IPC_FRAME_S16_LE), + + /* Pipeline 2 Buffers */ + SOF_STATIC_BUFFER(.id = 5, .size = 1536, .fmt = SOF_IPC_FRAME_S16_LE), + SOF_STATIC_BUFFER(.id = 6, .size = 1536, .fmt = SOF_IPC_FRAME_S16_LE), +}; + +/* ------------------------------------------------------------------------- + * 3. Static Audio Routes + * ------------------------------------------------------------------------- */ +static const struct sof_static_route nrf54lm20_routes[] = { + /* Pipeline 1: USB_PB (1) -> [1] -> Vol (2) -> [2] -> EQ (3) -> [3] -> DRC (4) -> [4] -> I2S_TX (5) */ + 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), + SOF_STATIC_ROUTE(.src_comp_id = 3, .buffer_id = 3, .sink_comp_id = 4), + SOF_STATIC_ROUTE(.src_comp_id = 4, .buffer_id = 4, .sink_comp_id = 5), + + /* Pipeline 2: I2S_RX (6) -> [5] -> Level (7) -> [6] -> USB_CAP (8) */ + SOF_STATIC_ROUTE(.src_comp_id = 6, .buffer_id = 5, .sink_comp_id = 7), + SOF_STATIC_ROUTE(.src_comp_id = 7, .buffer_id = 6, .sink_comp_id = 8), +}; + +/* ------------------------------------------------------------------------- + * 4. Controls (Kcontrols) + * ------------------------------------------------------------------------- */ +static const struct sof_static_kcontrol nrf54lm20_controls[] = { + SOF_STATIC_KCONTROL_VOLUME( + .id = 1, + .name = "Master Playback Volume", + .target_comp_id = 2, + .channels = 2, + .min = 0, + .max = 65536, + .def = 65536 + ), + SOF_STATIC_KCONTROL_SWITCH( + .id = 2, + .name = "Master Playback Switch", + .target_comp_id = 2, + .channels = 2, + .min = 0, + .max = 1, + .def = 1 + ), + SOF_STATIC_KCONTROL_VOLUME( + .id = 3, + .name = "Loopback Level Multiplier", + .target_comp_id = 7, + .channels = 2, + .min = 0, + .max = 65536, + .def = 32768 + ), + SOF_STATIC_KCONTROL_SWITCH( + .id = 4, + .name = "Loopback Switch", + .target_comp_id = 7, + .channels = 2, + .min = 0, + .max = 1, + .def = 1 + ), +}; + +/* ------------------------------------------------------------------------- + * 5. Pipelines + * ------------------------------------------------------------------------- */ +static const struct sof_static_pipeline_desc nrf54lm20_pipelines[] = { + { + .pipeline_id = 1, + .name = "Nordic I2S Transmit 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 = 5, + }, + { + .pipeline_id = 2, + .name = "Nordic I2S Loopback 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 = 8, + .source_comp_id = 6, + .sink_comp_id = 8, + }, +}; + +/* ------------------------------------------------------------------------- + * Top-Level Exported Topology + * ------------------------------------------------------------------------- */ +const struct sof_static_topology g_nrf54lm20_static_topology = { + .name = "nRF54LM20 Static Audio Pipeline", + .num_pipelines = ARRAY_SIZE(nrf54lm20_pipelines), + .pipelines = nrf54lm20_pipelines, + .num_comps = ARRAY_SIZE(nrf54lm20_comps), + .comps = nrf54lm20_comps, + .num_buffers = ARRAY_SIZE(nrf54lm20_buffers), + .buffers = nrf54lm20_buffers, + .num_routes = ARRAY_SIZE(nrf54lm20_routes), + .routes = nrf54lm20_routes, + .num_pcms = 0, + .pcms = NULL, + .num_controls = ARRAY_SIZE(nrf54lm20_controls), + .controls = nrf54lm20_controls, +}; diff --git a/src/platform/nordic/platform.c b/src/platform/nordic/platform.c new file mode 100644 index 000000000000..43bdcdfa481c --- /dev/null +++ b/src/platform/nordic/platform.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(nordic_platform, CONFIG_SOF_LOG_LEVEL); + +uint8_t sof_nordic_mailbox[0x1000] __aligned(32); + +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("Nordic 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; +} + +void ipc_platform_send_msg_direct(const struct ipc_msg *msg) +{ + mailbox_dspbox_write(0, msg->tx_data, msg->tx_size); +} + +void ipc_platform_complete_cmd(struct ipc *ipc) +{ + ARG_UNUSED(ipc); +} + +int platform_init(struct sof *sof) +{ + LOG_INF("Initializing Nordic 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("Nordic SOF Platform initialized successfully"); + return 0; +} 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 3200fc166e1c..3319d73af357 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 @@ -87,8 +89,8 @@ manifest: - name: zephyr repo-path: zephyr - revision: 76f604468505b30dfafa2c5a80d8b1c665ed6633 - remote: zephyrproject + revision: fcf26c58845d3cf24348d273e119879a699e4fc9 + remote: lgirdwood # Import some projects listed in zephyr/west.yml@revision # diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 9101c662b382..66c5261de131 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_NORDIC) + set(ARCH host) + set(PLATFORM "nordic") + set(PLATFORM_HEADERS "nordic") 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) # Code that only runs in kernel mode, from src/kernel. It is built as a # library of its own so that it can later be given kernel-only include @@ -186,6 +191,8 @@ zephyr_library_named(modules_sof) # Zephyr C++ code requires 14 or newer standard set_property(TARGET modules_sof PROPERTY CXX_STANDARD 17) +target_link_libraries(modules_sof PUBLIC kernel) + zephyr_include_directories(include) zephyr_include_directories(${ZEPHYR_BASE}/kernel/include) @@ -202,7 +209,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) @@ -230,6 +237,62 @@ 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() + +# Wrappers for compatibility and re-use of existing, XTOS CMake files. +# Do NOT use these macros in this file or in any other Zephyr-specific +# CMake code. +macro(add_local_sources target) + if (NOT "${target}" STREQUAL "sof") + message(FATAL_ERROR "add_local_sources() target is not 'sof'") + endif() + zephyr_library_sources(${ARGN}) +endmacro() +macro(add_local_sources_ifdef condition target) + if (NOT "${target}" STREQUAL "sof") + message(FATAL_ERROR "add_local_sources_ifdef() target is not 'sof'") + endif() + zephyr_library_sources_ifdef(${condition} ${ARGN}) +endmacro() +macro(sof_list_append_ifdef feature_toggle list) + if(${${feature_toggle}}) + list(APPEND ${list} ${ARGN}) + endif() +endmacro() + # directories with auxiliary modules need to be listed first # - llext dependencies need to added to linkage first add_subdirectory(../src/math/ math_unused_install/) @@ -415,6 +478,26 @@ if (CONFIG_SOC_MIMX9352_A55) set(PLATFORM "imx93_a55") endif() +if (CONFIG_PLATFORM_NORDIC) + zephyr_library_sources( + ${SOF_PLATFORM_PATH}/nordic/platform.c + ${SOF_PLATFORM_PATH}/nordic/lib/clk.c + lib/dma.c + ) + + if (CONFIG_PLATFORM_NRF54L15) + zephyr_library_sources( + ${SOF_PLATFORM_PATH}/nordic/nrf54l15_pipeline_def.c + ) + elseif (CONFIG_PLATFORM_NRF54LM20) + zephyr_library_sources( + ${SOF_PLATFORM_PATH}/nordic/nrf54lm20_pipeline_def.c + ) + endif() + + set(PLATFORM "nordic") + zephyr_include_directories(${SOF_PLATFORM_PATH}/nordic/include) +endif() if (CONFIG_SOC_MIMX9596_M7) zephyr_library_sources( @@ -638,35 +721,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) sof_append_relative_path_definitions(sof_kernel) diff --git a/zephyr/include/rtos/cache.h b/zephyr/include/rtos/cache.h index c67279655cbb..fb31597cd16f 100644 --- a/zephyr/include/rtos/cache.h +++ b/zephyr/include/rtos/cache.h @@ -34,11 +34,11 @@ #if !defined(CONFIG_DCACHE_LINE_SIZE_DETECT) && (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_NORDIC) +#define DCACHE_LINE_SIZE 32 #else #error "Invalid cache configuration." -#endif /* defined(CONFIG_LIBRARY) || defined(CONFIG_ZEPHYR_POSIX) */ +#endif /* defined(CONFIG_LIBRARY) || defined(CONFIG_ZEPHYR_POSIX) || defined(CONFIG_PLATFORM_NORDIC) */ #endif /* !defined(CONFIG_DCACHE_LINE_SIZE_DETECT) && (CONFIG_DCACHE_LINE_SIZE > 0) */ /** Alignment that keeps a buffer on data cache lines of its own */ diff --git a/zephyr/include/sof/lib/cpu.h b/zephyr/include/sof/lib/cpu.h index a47b93898973..1c87848611a4 100644 --- a/zephyr/include/sof/lib/cpu.h +++ b/zephyr/include/sof/lib/cpu.h @@ -48,14 +48,14 @@ void cpu_notify_state_exit(enum pm_state state); * from user mode. In supervisor context the generated wrapper inlines * the z_impl_cpu_get_id() body, so there is no overhead there. */ -#if defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) __syscall int cpu_get_id(void); #endif /* let the compiler optimise when in single core mode */ #if CONFIG_MULTICORE && CONFIG_SMP -#if defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) static inline int z_impl_cpu_get_id(void) { return arch_proc_id(); @@ -92,7 +92,7 @@ int cpu_restore_secondary_cores(void); int cpu_secondary_cores_prepare_d0ix(void); #else -#if defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) static inline int z_impl_cpu_get_id(void) { return 0; }; #else static inline int cpu_get_id(void) { return 0; }; @@ -116,7 +116,7 @@ static inline int cpu_secondary_cores_prepare_d0ix(void) { return 0; }; #endif /* CONFIG_MULTICORE && CONFIG_SMP */ -#if defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) +#if defined(CONFIG_USERSPACE) && defined(CONFIG_SOF_FULL_ZEPHYR_APPLICATION) #include #endif diff --git a/zephyr/lib/alloc.c b/zephyr/lib/alloc.c index 89bd49286831..5bfe69b4b3c3 100644 --- a/zephyr/lib/alloc.c +++ b/zephyr/lib/alloc.c @@ -142,6 +142,10 @@ extern char _mtk_adsp_sram_end; #define HEAPMEM_SIZE ((uint8_t *)SRAM_END - heapmem) #endif /* CONFIG_SYS_HEAP_SMALL_ONLY */ +#elif defined(CONFIG_PLATFORM_NORDIC) + +#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[];