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..754089d87548 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -22,11 +22,42 @@ endif() find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) project(sample_sof) +if(NOT ZEPHYR_SOF_MODULE) + add_subdirectory(../zephyr sof_zephyr) +endif() + target_sources(app PRIVATE src/main.c ) +target_sources_ifdef(CONFIG_USB_DEVICE_STACK_NEXT app PRIVATE + src/usbd_init.c + ) + zephyr_library_include_directories(app PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/../src/arch/xtensa/include ${CMAKE_CURRENT_SOURCE_DIR}/../src/include ) + +if(CONFIG_XTENSA) + zephyr_library_include_directories(app PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../src/arch/xtensa/include + ) +endif() + +if(CONFIG_SOF_FULL_ZEPHYR_APPLICATION AND NOT CONFIG_XTENSA) + target_include_directories(app PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${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() +endif() + +if(COMMAND sof_append_relative_path_definitions) + sof_append_relative_path_definitions(app) +endif() diff --git a/app/Kconfig b/app/Kconfig index 339a16bed3e8..ef1f54ac7ef2 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -7,6 +7,11 @@ choice COMPILER_CODEGEN_VLIW endchoice source "Kconfig.zephyr" +osource "samples/subsys/usb/common/Kconfig.sample_usbd" + +if !ZEPHYR_SOF_MODULE +rsource "../zephyr/Kconfig" +endif if SOC_FAMILY_INTEL_ADSP rsource "boards/intel_adsp/Kconfig.defconfig" diff --git a/app/boards/teensy41.conf b/app/boards/teensy41.conf new file mode 100644 index 000000000000..7b88e55e304b --- /dev/null +++ b/app/boards/teensy41.conf @@ -0,0 +1,88 @@ +# Target Platform & SOF Core +CONFIG_PLATFORM_TEENSY41=y +CONFIG_SOF_FULL_ZEPHYR_APPLICATION=y +CONFIG_IPC_MAJOR_4=y +CONFIG_COMP_BASEFW_IPC4=n +CONFIG_CROSS_CORE_STREAM=n +CONFIG_STATIC_PIPELINE=y +CONFIG_ZEPHYR_NATIVE_DRIVERS=y +CONFIG_SYS_HEAP_BIG_ONLY=y +CONFIG_BUILD_OUTPUT_HEX=y +CONFIG_BUILD_OUTPUT_BIN=y + +# Zephyr DAI & DMA Drivers for NXP i.MX RT1062 +CONFIG_DAI=y +CONFIG_DAI_NXP_SAI=y +CONFIG_DAI_NXP_SPDIF=y +CONFIG_PINCTRL=y +CONFIG_DMA=y +CONFIG_DMA_MCUX_EDMA=y +CONFIG_DMA_TCD_QUEUE_SIZE=8 +CONFIG_DMA_MCUX_USE_DTCM_FOR_DMA_DESCRIPTORS=n + +# 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_TDFB=y +CONFIG_COMP_USB_AUDIO=y +CONFIG_COMP_LEVEL_MULTIPLIER=y +CONFIG_COMP_SEL=y +CONFIG_COMP_MUX=y +CONFIG_COMP_MIXIN_MIXOUT=y +CONFIG_MIXIN_MIXOUT_ARM_SIMD=y +CONFIG_FORMAT_CONVERT_ARM_SIMD=y + +# USB Device Stack Next & UAC2 Class +CONFIG_USB_DEVICE_STACK_NEXT=y +CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=n +CONFIG_USBD_AUDIO2_CLASS=y +CONFIG_UDC_ENABLE_SOF=y +CONFIG_UDC_NXP_EHCI=y +CONFIG_UDC_BUF_COUNT=64 +CONFIG_UDC_BUF_POOL_SIZE=16384 +CONFIG_UDC_WORKQUEUE_PRIORITY=-10 +CONFIG_UDC_NXP_EVENT_COUNT=16 +CONFIG_UDC_WORKQUEUE_STACK_SIZE=2048 +CONFIG_LL_THREAD_PRIORITY=0 + +# USB Device Identification +CONFIG_SAMPLE_USBD_VID=0x16C0 +CONFIG_SAMPLE_USBD_PID=0x04D2 +CONFIG_SAMPLE_USBD_MANUFACTURER="PJRC" +CONFIG_SAMPLE_USBD_PRODUCT="SOF Teensy 4.1 Audio A" + +# System Resources & Memory +CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 +CONFIG_USBD_THREAD_STACK_SIZE=4096 +CONFIG_ISR_STACK_SIZE=4096 +CONFIG_HW_STACK_PROTECTION=n +CONFIG_HEAP_MEM_POOL_SIZE=8192 +CONFIG_TICKLESS_KERNEL=n +CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000 + +# Logging +CONFIG_LOG=y +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_MAX_LEVEL=4 +CONFIG_SOF_LOG_LEVEL_INF=y +CONFIG_DAI_LOG_LEVEL_INF=y +CONFIG_USBD_UAC2_LOG_LEVEL_INF=y +CONFIG_UDC_DRIVER_LOG_LEVEL_INF=y +CONFIG_LOG_MODE_DEFERRED=y +CONFIG_LOG_BUFFER_SIZE=4096 +CONFIG_LOG_MODE_OVERFLOW=y +CONFIG_LOG_BACKEND_UART=n +CONFIG_LOG_PROCESS_THREAD=n +CONFIG_EARLY_CONSOLE=n +CONFIG_SCHEDULE_LL_STATS_LOG=n diff --git a/app/boards/teensy41.overlay b/app/boards/teensy41.overlay new file mode 100644 index 000000000000..54e8b949a90a --- /dev/null +++ b/app/boards/teensy41.overlay @@ -0,0 +1,99 @@ +/* + * 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"; + }; + + i2s_speaker: i2s_speaker { + compatible = "zephyr,uac2-output-terminal"; + data-source = <&i2s_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; + }; + + i2s_in_fu: i2s_in_fu { + compatible = "zephyr,uac2-feature-unit"; + data-source = <&i2s_mic>; + 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>; + }; + }; +}; + +&flexcan1 { + status = "disabled"; +}; + +&sai1_dai { + status = "okay"; +}; + +&spdif { + status = "okay"; +}; diff --git a/app/src/main.c b/app/src/main.c index aa9ee6960d8d..c8e1b48e0be0 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -8,6 +8,15 @@ #include #include +#if defined(CONFIG_PLATFORM_TEENSY41) +#include +#include +#include +#include +#include +#include +#endif + LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG); /* define qemu boot tests if any qemu target is defined, add targets to end */ @@ -41,6 +50,31 @@ static int sof_app_main(void) LOG_INF("SOF initialized"); +#if defined(CONFIG_PLATFORM_TEENSY41) + ret = sof_static_topology_init(&g_teensy41_static_topology); + if (ret < 0) { + LOG_ERR("Static topology initialization failed: %d", ret); + return ret; + } + + const struct device *uac2_dev = DEVICE_DT_GET_ONE(zephyr_uac2); + + if (device_is_ready(uac2_dev)) { + usbd_uac2_set_ops(uac2_dev, sof_static_uac2_get_ops(), NULL); + } else { + LOG_ERR("UAC2 device not ready"); + } + + struct usbd_context *sample_usbd = sample_usbd_init_device(NULL); + + if (sample_usbd) { + usbd_enable(sample_usbd); + LOG_INF("SOF USB UAC2 device and static pipelines started"); + } else { + LOG_ERR("Failed to initialize USB device context"); + } +#endif + #ifdef CONFIG_ARCH_POSIX_LIBFUZZER /* Workaround for an apparent timing bug in libfuzzer+asan. * If the initial/main thread is allowed to return, ASAN will diff --git a/app/src/sample_usbd.h b/app/src/sample_usbd.h new file mode 100644 index 000000000000..18355ce74a09 --- /dev/null +++ b/app/src/sample_usbd.h @@ -0,0 +1,16 @@ +/* + * 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 + +struct usbd_context *sample_usbd_init_device(usbd_msg_cb_t msg_cb); +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..d2c588519754 --- /dev/null +++ b/app/src/usbd_init.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(usbd_sample_config, LOG_LEVEL_INF); + +static const char *const blocklist[] = { + "dfu_dfu", + NULL, +}; + +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); + +#if defined(CONFIG_PLATFORM_TEENSY41) && defined(CONFIG_TEENSY41_BOARD_B) +USBD_DESC_PRODUCT_DEFINE(sample_product, "SOF Teensy 4.1 Audio B"); +#elif defined(CONFIG_PLATFORM_TEENSY41) && defined(CONFIG_TEENSY41_BOARD_A) +USBD_DESC_PRODUCT_DEFINE(sample_product, "SOF Teensy 4.1 Audio A"); +#else +USBD_DESC_PRODUCT_DEFINE(sample_product, CONFIG_SAMPLE_USBD_PRODUCT); +#endif + +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); + +#if defined(CONFIG_BOARD_TEENSY41) || defined(CONFIG_BOARD_TEENSY40) +static int teensy_reboot_to_dev(const struct usbd_context *const ctx, + const struct usb_setup_packet *const setup, + const struct net_buf *const buf) +{ + ARG_UNUSED(ctx); + ARG_UNUSED(setup); + ARG_UNUSED(buf); + + /* Trigger PJRC MKL02 bootloader companion to enter HalfKay HID mode */ + __asm__ volatile("bkpt #251"); + return 0; +} + +USBD_VREQUEST_DEFINE(teensy_reboot_vreq, 0xFB, NULL, teensy_reboot_to_dev); +#endif + +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; + + 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, &sample_product); + 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; + } + )) + + 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; + } + } + +#if defined(CONFIG_BOARD_TEENSY41) || defined(CONFIG_BOARD_TEENSY40) + err = usbd_device_register_vreq(&sample_usbd, &teensy_reboot_vreq); + if (err) { + LOG_ERR("Failed to register HalfKay reboot request (%d)", err); + } +#endif + + 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..0fe48bc851f3 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -155,6 +155,7 @@ endif() ### Common files sof_list_append_ifdef(CONFIG_COMP_BLOB base_files data_blob.c) +sof_list_append_ifdef(CONFIG_COMP_USB_AUDIO base_files usb_audio.c) ### (end of common files) diff --git a/src/audio/Kconfig b/src/audio/Kconfig index dbd12f1626b2..339d8a97d10b 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -451,4 +451,11 @@ config DAI_VERBOSE_GLITCH_WARNINGS always emit a warning. This option useful to debug issues with DAI DMA operation and issues with low-latency scheduling. +config COMP_USB_AUDIO + bool "USB Audio Class 2.0 host endpoint component" + default n + help + Select for USB Audio Class 2.0 (UAC2) streaming host endpoint + component in standalone Zephyr audio pipelines. + endmenu diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index 40d0a9f4902a..3d2d50f6df6f 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -64,6 +64,32 @@ static const struct comp_driver comp_dai; +#ifdef CONFIG_ZEPHYR_DOMAIN_AUDIO_TIMER +#include + +static struct comp_dev *s_active_sched_dai; + +bool dai_zephyr_has_active_audio_domain(void) +{ + return s_active_sched_dai != NULL; +} + +static void dai_zephyr_dma_period_cb(const struct device *dma_dev, void *user_data, + uint32_t channel, int status) +{ + struct comp_dev *dev = user_data; + + ARG_UNUSED(dma_dev); + ARG_UNUSED(channel); + ARG_UNUSED(status); + + if (dev != s_active_sched_dai) + return; + + zephyr_domain_audio_timer_cb(); +} +#endif + LOG_MODULE_REGISTER(dai_comp, CONFIG_SOF_LOG_LEVEL); SOF_DEFINE_REG_UUID(dai); @@ -158,7 +184,8 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, cfg.dai_index = common_config->dai_index; is_blob = common_config->is_config_blob; cfg.format = common_config->format; - cfg.options = is_blob ? 0 : sof_cfg->flags; + cfg.options = (is_blob || !spec_config || size < SOF_DAI_CONFIG_HW_SPEC_OFFSET) ? + 0 : sof_cfg->flags; cfg.rate = common_config->sampling_frequency; switch (common_config->type) { @@ -186,15 +213,23 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, break; case SOF_DAI_IMX_SAI: cfg.type = DAI_IMX_SAI; - cfg_params = &sof_cfg->sai; + cfg_params = (size >= sizeof(struct sof_ipc_dai_config)) ? + (is_blob ? spec_config : &sof_cfg->sai) : NULL; break; case SOF_DAI_IMX_ESAI: cfg.type = DAI_IMX_ESAI; - cfg_params = &sof_cfg->esai; + cfg_params = (size >= sizeof(struct sof_ipc_dai_config)) ? + (is_blob ? spec_config : &sof_cfg->esai) : NULL; break; case SOF_DAI_IMX_MICFIL: cfg.type = DAI_IMX_MICFIL; - cfg_params = &sof_cfg->micfil; + cfg_params = (size >= sizeof(struct sof_ipc_dai_config)) ? + (is_blob ? spec_config : &sof_cfg->micfil) : NULL; + break; + case SOF_DAI_IMX_SPDIF: + cfg.type = DAI_IMX_SPDIF; + cfg_params = (size >= sizeof(struct sof_ipc_dai_config)) ? + (is_blob ? spec_config : &sof_cfg->spdif) : NULL; break; case SOF_DAI_AMD_DMIC: cfg.type = DAI_AMD_DMIC; @@ -237,10 +272,23 @@ __cold int dai_set_config(struct dai *dai, struct ipc_config_dai *common_config, } if (!is_blob) { - if (size < SOF_DAI_CONFIG_HW_SPEC_OFFSET) - return -EINVAL; + switch (common_config->type) { + case SOF_DAI_IMX_SAI: + case SOF_DAI_IMX_SPDIF: + case SOF_DAI_IMX_ESAI: + case SOF_DAI_IMX_MICFIL: + if (size >= SOF_DAI_CONFIG_HW_SPEC_OFFSET) + dai_cfg_size -= SOF_DAI_CONFIG_HW_SPEC_OFFSET; + else + dai_cfg_size = size; + break; + default: + if (size < SOF_DAI_CONFIG_HW_SPEC_OFFSET) + return -EINVAL; - dai_cfg_size -= SOF_DAI_CONFIG_HW_SPEC_OFFSET; + dai_cfg_size -= SOF_DAI_CONFIG_HW_SPEC_OFFSET; + break; + } } return dai_config_set(dev, &cfg, cfg_params, dai_cfg_size); @@ -828,6 +876,10 @@ __cold static void dai_free(struct comp_dev *dev) /* heap is NULL to match what is passed in dai_new() */ sof_heap_free(NULL, dd); +#ifdef CONFIG_ZEPHYR_DOMAIN_AUDIO_TIMER + if (s_active_sched_dai == dev) + s_active_sched_dai = NULL; +#endif comp_free_device(dev); } @@ -929,7 +981,11 @@ static int dai_get_dma_slot(struct dai_data *dd, struct comp_dev *dev, uint32_t case DAI_IMX_SAI: case DAI_IMX_ESAI: case DAI_IMX_MICFIL: - *slot = (hs & GENMASK(15, 8)) >> 8; + case DAI_IMX_SPDIF: + if (hs & GENMASK(15, 8)) + *slot = (hs & GENMASK(15, 8)) >> 8; + else + *slot = hs; break; default: *slot = hs; @@ -1055,6 +1111,7 @@ static int dai_set_dma_config(struct dai_data *dd, struct comp_dev *dev) comp_err(dev, "dma_cfg allocation failed"); return -ENOMEM; } + memset(dma_cfg, 0, sizeof(*dma_cfg)); if (dev->direction == SOF_IPC_STREAM_PLAYBACK) dma_cfg->channel_direction = MEMORY_TO_PERIPHERAL; @@ -1064,15 +1121,24 @@ static int dai_set_dma_config(struct dai_data *dd, struct comp_dev *dev) dma_cfg->source_data_size = config->src_width; dma_cfg->dest_data_size = config->dest_width; - if (config->burst_elems) + if (dd->dai->type == SOF_DAI_IMX_SAI) + dma_cfg->source_burst_length = 16 * config->src_width; + else if (dd->dai->type == SOF_DAI_IMX_SPDIF) + dma_cfg->source_burst_length = 8; + else if (config->burst_elems) dma_cfg->source_burst_length = config->burst_elems; else dma_cfg->source_burst_length = 8; dma_cfg->dest_burst_length = dma_cfg->source_burst_length; dma_cfg->cyclic = config->cyclic; +#ifdef CONFIG_ZEPHYR_DOMAIN_AUDIO_TIMER + dma_cfg->user_data = dev; + dma_cfg->dma_callback = dai_zephyr_dma_period_cb; +#else dma_cfg->user_data = NULL; dma_cfg->dma_callback = NULL; +#endif dma_cfg->block_count = config->elem_array.count; if (dev->direction == SOF_IPC_STREAM_PLAYBACK) dma_cfg->dma_slot = config->dest_dev; @@ -1087,6 +1153,12 @@ static int dai_set_dma_config(struct dai_data *dd, struct comp_dev *dev) comp_err(dev, "dma_block_config allocation failed"); return -ENOMEM; } + memset(dma_block_cfg, 0, sizeof(struct dma_block_config) * dma_cfg->block_count); + + const bool is_imx_dai = (dd->dai->type == SOF_DAI_IMX_SAI || + dd->dai->type == SOF_DAI_IMX_SPDIF || + dd->dai->type == SOF_DAI_IMX_ESAI || + dd->dai->type == SOF_DAI_IMX_MICFIL); dma_cfg->head_block = dma_block_cfg; for (i = 0; i < dma_cfg->block_count; i++) { @@ -1097,15 +1169,29 @@ static int dai_set_dma_config(struct dai_data *dd, struct comp_dev *dev) local_to_host(config->elem_array.elems[i].src); dma_block_cfg->dest_address = config->elem_array.elems[i].dest; - dma_block_cfg->source_addr_adj = DMA_ADDR_ADJ_DECREMENT; - dma_block_cfg->dest_addr_adj = DMA_ADDR_ADJ_INCREMENT; + if (is_imx_dai) { + dma_block_cfg->source_addr_adj = DMA_ADDR_ADJ_INCREMENT; + dma_block_cfg->dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; + if (dma_cfg->block_count > 1) + dma_block_cfg->source_gather_en = 1; + } else { + dma_block_cfg->source_addr_adj = DMA_ADDR_ADJ_DECREMENT; + dma_block_cfg->dest_addr_adj = DMA_ADDR_ADJ_INCREMENT; + } } else { dma_block_cfg->source_address = config->elem_array.elems[i].src; dma_block_cfg->dest_address = local_to_host(config->elem_array.elems[i].dest); - dma_block_cfg->source_addr_adj = DMA_ADDR_ADJ_INCREMENT; - dma_block_cfg->dest_addr_adj = DMA_ADDR_ADJ_DECREMENT; + if (is_imx_dai) { + dma_block_cfg->source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; + dma_block_cfg->dest_addr_adj = DMA_ADDR_ADJ_INCREMENT; + if (dma_cfg->block_count > 1) + dma_block_cfg->dest_scatter_en = 1; + } else { + dma_block_cfg->source_addr_adj = DMA_ADDR_ADJ_INCREMENT; + dma_block_cfg->dest_addr_adj = DMA_ADDR_ADJ_DECREMENT; + } } prev = dma_block_cfg; prev->next_block = ++dma_block_cfg; @@ -1501,10 +1587,17 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, /* only start the DAI if we are not XRUN handling */ if (dd->xrun == 0) { + if (dd->z_config) + sof_dma_config(dd->dma, dd->chan_index, dd->z_config); ret = dai_dma_start(dd); if (ret < 0) return ret; +#ifdef CONFIG_ZEPHYR_DOMAIN_AUDIO_TIMER + if (!s_active_sched_dai) + s_active_sched_dai = dev; +#endif + /* start the DAI */ dai_trigger_op(dd->dai, cmd, dev->direction); } else { @@ -1544,6 +1637,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 CONFIG_STATIC_PIPELINE + dd->xrun = 0; +#endif /* only start the DAI if we are not XRUN handling */ if (dd->xrun == 0) { /* recover valid start position */ @@ -1560,6 +1656,11 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, if (ret < 0) return ret; +#ifdef CONFIG_ZEPHYR_DOMAIN_AUDIO_TIMER + if (!s_active_sched_dai) + s_active_sched_dai = dev; +#endif + /* start the DAI */ dai_trigger_op(dd->dai, cmd, dev->direction); } else { @@ -1576,6 +1677,10 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, COMPILER_FALLTHROUGH; case COMP_TRIGGER_STOP: comp_dbg(dev, "STOP"); +#ifdef CONFIG_ZEPHYR_DOMAIN_AUDIO_TIMER + if (s_active_sched_dai == dev) + s_active_sched_dai = NULL; +#endif /* * Some platforms cannot just simple disable * DMA channel during the transfer, @@ -1598,6 +1703,10 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, break; case COMP_TRIGGER_PAUSE: comp_dbg(dev, "PAUSE"); +#ifdef CONFIG_ZEPHYR_DOMAIN_AUDIO_TIMER + if (s_active_sched_dai == dev) + s_active_sched_dai = NULL; +#endif #if CONFIG_COMP_DAI_STOP_TRIGGER_ORDER_REVERSE ret = dai_dma_suspend(dd); dai_trigger_op(dd->dai, cmd, dev->direction); @@ -1616,7 +1725,7 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, break; } -#ifdef CONFIG_IPC_MAJOR_3 +#if defined(CONFIG_IPC_MAJOR_3) || defined(CONFIG_STATIC_PIPELINE) /* 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. @@ -1625,7 +1734,7 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, */ if (!ret) ret = comp_set_state(dev, cmd); -#endif /* CONFIG_IPC_MAJOR_3 */ +#endif /* CONFIG_IPC_MAJOR_3 || CONFIG_STATIC_PIPELINE */ if (!ret) telemetry_update_dai_hot(dd, dev); @@ -1862,14 +1971,15 @@ static void set_new_local_buffer(struct dai_data *dd, struct comp_dev *dev) uint32_t dma_fmt = audio_stream_get_frm_fmt(&dd->dma_buffer->stream); uint32_t local_fmt; - if (dev->direction == SOF_IPC_STREAM_PLAYBACK) + if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { dd->local_buffer = comp_dev_get_first_data_producer(dev); - else + local_fmt = audio_stream_get_frm_fmt(&dd->local_buffer->stream); + dd->process = pcm_get_conversion_function(local_fmt, dma_fmt); + } else { dd->local_buffer = comp_dev_get_first_data_consumer(dev); - - local_fmt = audio_stream_get_frm_fmt(&dd->local_buffer->stream); - - dd->process = pcm_get_conversion_function(local_fmt, dma_fmt); + local_fmt = audio_stream_get_frm_fmt(&dd->local_buffer->stream); + dd->process = pcm_get_conversion_function(dma_fmt, local_fmt); + } if (!dd->process) { comp_err(dev, "converter function NULL: local fmt %d dma fmt %d\n", diff --git a/src/audio/drc/drc_arm.c b/src/audio/drc/drc_arm.c index cb4795aefe08..0395ab3fddbf 100644 --- a/src/audio/drc/drc_arm.c +++ b/src/audio/drc/drc_arm.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include @@ -69,12 +71,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; + size_t div_start, i; + unsigned int ch; const int16_t *sample16_p; const int32_t *sample32_p; int32_t gain; @@ -82,7 +85,7 @@ void drc_update_detector_average(struct drc_state *state, int32_t db_per_frame; int32_t sat_release_rate; int32_t tmp; - int is_release; + bool is_release; if (state->pre_delay_write_index == 0) div_start = CONFIG_DRC_MAX_PRE_DELAY_FRAMES - DRC_DIVISION_FRAMES; @@ -169,8 +172,8 @@ void drc_update_envelope(struct drc_state *state, const struct sof_drc_params *p int32_t scaled_desired_gain; int32_t eff_atten_diff_db; int32_t lshift; - int is_releasing; - int is_bad_db; + bool is_releasing; + bool is_bad_db; scaled_desired_gain = drc_asin_fixed(state->detector_average); @@ -235,21 +238,22 @@ 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; + const size_t div_start = state->pre_delay_read_index; + const size_t count = DRC_DIVISION_FRAMES >> 2; int32_t x[4]; int32_t c, base, r, r2, r4; int32_t post_warp_compressor_gain; int32_t total_gain; int32_t tmp; - int i, j, ch, inc; + size_t i, j, inc; + unsigned int ch; int16_t *sample16_p; int32_t *sample32_p; int32_t lshift; - const int is_2byte = (nbyte == 2); + const bool is_2byte = (nbyte == 2); if (state->envelope_rate < ONE_Q30) { /* Attack mode */ diff --git a/src/audio/pipeline/pipeline-xrun.c b/src/audio/pipeline/pipeline-xrun.c index 09ad9be84457..bf2392cf8126 100644 --- a/src/audio/pipeline/pipeline-xrun.c +++ b/src/audio/pipeline/pipeline-xrun.c @@ -56,7 +56,14 @@ static int pipeline_comp_xrun(struct comp_dev *current, #endif /* CONFIG_IPC_MAJOR_3 */ -#if NO_XRUN_RECOVERY +#if CONFIG_STATIC_PIPELINE +/* Standalone static pipelines: clear xrun_bytes and continue streaming without host IPC reset */ +int pipeline_xrun_recover(struct pipeline *p) +{ + p->xrun_bytes = 0; + return 0; +} +#elif NO_XRUN_RECOVERY /* recover the pipeline from a XRUN condition */ int pipeline_xrun_recover(struct pipeline *p) { diff --git a/src/audio/pipeline/static_pipeline_uac2.c b/src/audio/pipeline/static_pipeline_uac2.c index 7b4506d9289c..980c296b1550 100644 --- a/src/audio/pipeline/static_pipeline_uac2.c +++ b/src/audio/pipeline/static_pipeline_uac2.c @@ -19,6 +19,12 @@ #include #include +#if defined(CONFIG_USBD_AUDIO2_CLASS) && defined(CONFIG_COMP_USB_AUDIO) +#include +#include +#include +#endif + LOG_MODULE_REGISTER(static_pipeline_uac2, CONFIG_SOF_LOG_LEVEL); /* Standard 0 dB gain constant for SOF volume representation (Q1.31 format) */ @@ -78,24 +84,28 @@ static struct uac2_control_cache *get_uac2_cache(uint8_t entity_id, bool is_volu * \brief Convert USB Audio Class 2.0 8.8 fixed-point dB volume to linear SOF volume. * * UAC2 expresses volume in 1/256 dB steps (e.g. 0x0000 = 0 dB, -90 dB = 0xa600). - * This helper translates UAC2 dB volume into SOF 32-bit linear volume using + * This helper translates UAC2 dB volume into SOF linear volume using * an integer 6 dB per bit shift approximation: - * - >= 0 dB maps to SOF_VOL_ZERO_DB (INT32_MAX, unity gain) + * - >= 0 dB maps to zero_db (unity gain, e.g. ctl->max or SOF_VOL_ZERO_DB) * - <= -90 dB maps to 0 (digital silence) * - Values between -90 dB and 0 dB are scaled proportionally. * * \param[in] volume USB Audio Class 2.0 8.8 fixed-point dB volume value. - * \return 32-bit linear volume value (0 to SOF_VOL_ZERO_DB). + * \param[in] zero_db Linear unity gain value for the target control. + * \return Linear volume value (0 to zero_db). */ -static int32_t uac2_to_sof_volume(int16_t volume) +static int32_t uac2_to_sof_volume(int16_t volume, int32_t zero_db) { + if (zero_db <= 0) + zero_db = SOF_VOL_ZERO_DB; + /* Treat volume below -90 dB as complete digital silence */ if (volume <= -90 * 256) return 0; /* Clip volume at or above 0 dB to unity gain */ if (volume >= 0) - return SOF_VOL_ZERO_DB; + return zero_db; /* Convert negative dB (scaled by 256) to tenths of dB: 1 dB = ~6 dB per bit */ int32_t db_x10 = (int32_t)(-volume) * 10 / 256; @@ -109,7 +119,7 @@ static int32_t uac2_to_sof_volume(int16_t volume) * next 6 dB boundary (v >> 1). We interpolate smoothly between v and v_next. */ int rem = db_x10 % 60; - uint64_t v = (uint64_t)SOF_VOL_ZERO_DB >> shift; + uint64_t v = (uint64_t)zero_db >> shift; uint64_t v_next = v >> 1; uint64_t val = v_next + (((v - v_next) * (60 - rem)) / 60); @@ -138,11 +148,13 @@ int sof_static_kcontrol_set_by_uac2(uint8_t entity_id, uint8_t channel, int32_t return -ENODEV; } - /* Search controls in active topology for matching UAC2 entity ID */ + /* Search controls in active topology for matching UAC2 entity ID and control type */ for (size_t i = 0; i < topo->num_controls; i++) { const struct sof_static_kcontrol *ctl = &topo->controls[i]; + bool type_match = (is_volume && ctl->type == SOF_STATIC_CTRL_VOLUME) || + (!is_volume && ctl->type == SOF_STATIC_CTRL_SWITCH); - if (ctl->uac2_entity_id == entity_id) { + if (ctl->uac2_entity_id == entity_id && type_match) { /* Validate channel bounds */ if (channel > ctl->channels && channel != 0) { LOG_ERR("sof_static_kcontrol_set_by_uac2: channel %u exceeds ctl channels %u", @@ -152,9 +164,10 @@ int sof_static_kcontrol_set_by_uac2(uint8_t entity_id, uint8_t channel, int32_t struct uac2_control_cache *cache = get_uac2_cache(entity_id, is_volume); - if (is_volume && ctl->type == SOF_STATIC_CTRL_VOLUME) { - /* Translate UAC2 8.8 dB volume to linear SOF Q1.31 volume */ - int32_t sof_vol = uac2_to_sof_volume((int16_t)val); + if (is_volume) { + /* Translate UAC2 8.8 dB volume to linear SOF volume */ + int32_t zero_db = (ctl->max > 0) ? ctl->max : SOF_VOL_ZERO_DB; + int32_t sof_vol = uac2_to_sof_volume((int16_t)val, zero_db); int ret; if (channel == 0) { @@ -181,7 +194,7 @@ int sof_static_kcontrol_set_by_uac2(uint8_t entity_id, uint8_t channel, int32_t cache->val[channel] = val; return 0; - } else if (!is_volume && ctl->type == SOF_STATIC_CTRL_SWITCH) { + } else { /* In UAC2: val=1 means MUTED, so enabled switch state is 0 */ int32_t sof_sw = val ? 0 : 1; int ret; @@ -236,11 +249,13 @@ int sof_static_kcontrol_get_by_uac2(uint8_t entity_id, uint8_t channel, int32_t return -EINVAL; } - /* Search controls in active topology for matching UAC2 entity ID */ + /* Search controls in active topology for matching UAC2 entity ID and control type */ for (size_t i = 0; i < topo->num_controls; i++) { const struct sof_static_kcontrol *ctl = &topo->controls[i]; + bool type_match = (is_volume && ctl->type == SOF_STATIC_CTRL_VOLUME) || + (!is_volume && ctl->type == SOF_STATIC_CTRL_SWITCH); - if (ctl->uac2_entity_id == entity_id) { + if (ctl->uac2_entity_id == entity_id && type_match) { /* Validate channel bounds */ if (channel > ctl->channels && channel != 0) { LOG_ERR("sof_static_kcontrol_get_by_uac2: channel %u exceeds ctl channels %u", @@ -307,3 +322,282 @@ int sof_static_pipeline_trigger_by_uac2_term(uint8_t terminal_id, bool start) LOG_ERR("sof_static_pipeline_trigger_by_uac2_term: no host component found for terminal %u", terminal_id); return -ENOENT; } + +#if defined(CONFIG_USBD_AUDIO2_CLASS) && defined(CONFIG_COMP_USB_AUDIO) + +#define UAC2_BUF_BLOCK_SIZE 256 + +#if defined(CONFIG_NOCACHE_MEMORY) +K_MEM_SLAB_DEFINE_IN_SECT_STATIC(uac2_rx_slab, __nocache, UAC2_BUF_BLOCK_SIZE, 32, 64); +K_MEM_SLAB_DEFINE_IN_SECT_STATIC(uac2_tx_slab, __nocache, UAC2_BUF_BLOCK_SIZE, 64, 64); +#else +K_MEM_SLAB_DEFINE_STATIC(uac2_rx_slab, UAC2_BUF_BLOCK_SIZE, 32, 64); +K_MEM_SLAB_DEFINE_STATIC(uac2_tx_slab, UAC2_BUF_BLOCK_SIZE, 64, 64); +#endif + +static uint32_t s_uac2_sample_rate = 48000; +static uint32_t s_uac2_cap_frame_sz = 4; +static uint8_t s_uac2_capture_term_id; +static bool s_uac2_capture_active; +static bool s_uac2_microframes; + +static void sof_uac2_send_capture_pending(const struct device *dev) +{ + uint32_t rate; + uint32_t frame_bytes; + + if (!s_uac2_capture_active || !s_uac2_capture_term_id || !dev) + return; + + rate = s_uac2_sample_rate ? s_uac2_sample_rate : 48000; + if (s_uac2_microframes) + frame_bytes = (rate / 8000) * s_uac2_cap_frame_sz; + else + frame_bytes = (rate / 1000) * s_uac2_cap_frame_sz; + + if (frame_bytes == 0 || frame_bytes > UAC2_BUF_BLOCK_SIZE) + frame_bytes = s_uac2_microframes ? (6 * s_uac2_cap_frame_sz) : + (48 * s_uac2_cap_frame_sz); + + for (int iter = 0; iter < 2; iter++) { + void *buf = NULL; + int ret; + + if (k_mem_slab_alloc(&uac2_tx_slab, &buf, K_NO_WAIT) != 0) + break; + + if (!usb_audio_peek_capture_data(buf, frame_bytes)) { + k_mem_slab_free(&uac2_tx_slab, buf); + break; + } + + ret = usbd_uac2_send(dev, s_uac2_capture_term_id, buf, (uint16_t)frame_bytes); + if (ret == 0) { + usb_audio_consume_capture_data(frame_bytes); + } else { + k_mem_slab_free(&uac2_tx_slab, buf); + break; + } + } +} + +static void sof_uac2_sof_cb(const struct device *dev, void *user_data) +{ + ARG_UNUSED(user_data); + + if (s_uac2_capture_active && dev) + sof_uac2_send_capture_pending(dev); +} + +static void sof_uac2_terminal_update_cb(const struct device *dev, uint8_t terminal, + bool enabled, bool microframes, void *user_data) +{ + const struct sof_static_topology *topo = sof_static_topology_get(); + + ARG_UNUSED(dev); + ARG_UNUSED(user_data); + + s_uac2_microframes = microframes; + + if (topo) { + for (size_t i = 0; i < topo->num_comps; i++) { + const struct sof_static_comp *cdesc = &topo->comps[i]; + + if (cdesc->type == SOF_STATIC_COMP_HOST && + cdesc->ep.usb.terminal_id == terminal && + cdesc->direction == SOF_IPC_STREAM_CAPTURE) { + uint32_t ch = cdesc->caps.max_channels ? + cdesc->caps.max_channels : 2; + uint32_t bps = (cdesc->caps.default_fmt == SOF_IPC_FRAME_S16_LE) ? + 2 : 4; + + s_uac2_cap_frame_sz = ch * bps; + s_uac2_capture_term_id = terminal; + s_uac2_capture_active = enabled; + break; + } + } + } + + sof_static_pipeline_trigger_by_uac2_term(terminal, enabled); +} + +static void *sof_uac2_get_recv_buf(const struct device *dev, uint8_t terminal, + uint16_t size, void *user_data) +{ + void *buf = NULL; + + ARG_UNUSED(dev); + ARG_UNUSED(terminal); + ARG_UNUSED(user_data); + + if (size > UAC2_BUF_BLOCK_SIZE) + return NULL; + + if (k_mem_slab_alloc(&uac2_rx_slab, &buf, K_NO_WAIT) != 0) + return NULL; + + return buf; +} + +static 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); + + if (buf && size > 0) + usb_audio_feed_playback_data(buf, size); + + if (buf) + k_mem_slab_free(&uac2_rx_slab, buf); +} + +static void sof_uac2_buf_release_cb(const struct device *dev, uint8_t terminal, + void *buf, void *user_data) +{ + ARG_UNUSED(user_data); + + if (buf) + k_mem_slab_free(&uac2_tx_slab, buf); + + if (s_uac2_capture_active && terminal == s_uac2_capture_term_id && dev) + sof_uac2_send_capture_pending(dev); +} + +static uint32_t sof_uac2_feedback_cb(const struct device *dev, uint8_t terminal, + void *user_data) +{ + uint32_t rate = s_uac2_sample_rate ? s_uac2_sample_rate : 48000; + + ARG_UNUSED(dev); + ARG_UNUSED(terminal); + ARG_UNUSED(user_data); + + if (s_uac2_microframes) + return (uint32_t)(((uint64_t)rate << 16) / 8000); + + return (uint32_t)(((uint64_t)rate << 14) / 1000); +} + +static 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); + + if (!s_uac2_sample_rate) { + uint32_t topo_rate = sof_static_get_sample_rate(); + + s_uac2_sample_rate = topo_rate ? topo_rate : 48000; + } + + return s_uac2_sample_rate; +} + +static int sof_uac2_set_sample_rate(const struct device *dev, uint8_t clock_id, + uint32_t rate, void *user_data) +{ + const struct sof_static_topology *topo = sof_static_topology_get(); + + ARG_UNUSED(dev); + ARG_UNUSED(clock_id); + ARG_UNUSED(user_data); + + s_uac2_sample_rate = rate; + + if (topo) { + for (size_t i = 0; i < topo->num_pipelines; i++) { + uint32_t pid = topo->pipelines[i].pipeline_id; + struct pipeline *pipe = sof_static_pipeline_get(pid); + + if (pipe && pipe->status != COMP_STATE_ACTIVE && + sof_static_pipeline_get_sample_rate(pid) != rate) + sof_static_pipeline_set_sample_rate(pid, rate); + } + } + + usb_audio_set_playback_rate(rate); + usb_audio_set_capture_rate(rate); + return 0; +} + +static 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(user_data); + + return sof_static_kcontrol_set_by_uac2(entity_id, channel, mute ? 1 : 0, false); +} + +static int sof_uac2_get_feature_mute(const struct device *dev, uint8_t entity_id, + uint8_t channel, bool *mute, void *user_data) +{ + int32_t val = 0; + int ret; + + ARG_UNUSED(dev); + ARG_UNUSED(user_data); + + if (!mute) + return -EINVAL; + + ret = sof_static_kcontrol_get_by_uac2(entity_id, channel, &val, false); + if (ret == 0) + *mute = (val != 0); + + return ret; +} + +static 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(user_data); + + return sof_static_kcontrol_set_by_uac2(entity_id, channel, volume, true); +} + +static int sof_uac2_get_feature_volume(const struct device *dev, uint8_t entity_id, + uint8_t channel, int16_t *volume, void *user_data) +{ + int32_t val = 0; + int ret; + + ARG_UNUSED(dev); + ARG_UNUSED(user_data); + + if (!volume) + return -EINVAL; + + ret = sof_static_kcontrol_get_by_uac2(entity_id, channel, &val, true); + if (ret == 0) + *volume = (int16_t)val; + + return ret; +} + +static const struct uac2_ops s_sof_static_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_cb, + .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_static_uac2_get_ops(void) +{ + return &s_sof_static_uac2_ops; +} + +#endif /* CONFIG_USBD_AUDIO2_CLASS && CONFIG_COMP_USB_AUDIO */ diff --git a/src/audio/usb_audio.c b/src/audio/usb_audio.c new file mode 100644 index 000000000000..67e91b5c35a9 --- /dev/null +++ b/src/audio/usb_audio.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright(c) 2026 Sound Open Firmware (SOF) Project. All rights reserved. + */ + +#include +#include +#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; + +static void ring_write_locked(struct usb_audio_ring_buffer *ring, + const uint8_t *src, size_t bytes) +{ + size_t first = MIN(bytes, (size_t)(USB_AUDIO_RING_BUFFER_SIZE - ring->head)); + + memcpy(&ring->buf[ring->head], src, first); + if (bytes > first) + memcpy(&ring->buf[0], src + first, bytes - first); + + ring->head = (ring->head + bytes) % USB_AUDIO_RING_BUFFER_SIZE; + ring->count += bytes; +} + +static void ring_read_locked(struct usb_audio_ring_buffer *ring, + uint8_t *dst, size_t bytes, bool consume) +{ + size_t first = MIN(bytes, (size_t)(USB_AUDIO_RING_BUFFER_SIZE - ring->tail)); + + memcpy(dst, &ring->buf[ring->tail], first); + if (bytes > first) + memcpy(dst + first, &ring->buf[0], bytes - first); + + if (consume) { + ring->tail = (ring->tail + bytes) % USB_AUDIO_RING_BUFFER_SIZE; + ring->count -= bytes; + } +} + +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; +} + +void usb_audio_feed_playback_data(const void *src, size_t bytes) +{ + if (!g_playback_data || !g_playback_data->active || !src || bytes == 0) + return; + + struct usb_audio_ring_buffer *ring = &g_playback_data->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + size_t avail = USB_AUDIO_RING_BUFFER_SIZE - ring->count; + + if (bytes > avail) + bytes = avail; + + ring_write_locked(ring, src, bytes); + k_spin_unlock(&ring->lock, key); +} + +bool usb_audio_has_capture_data(size_t min_bytes) +{ + if (!g_capture_data || !g_capture_data->active) + return false; + + struct usb_audio_ring_buffer *ring = &g_capture_data->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + bool ready; + + if (!g_capture_data->started) { + if (ring->count < USB_AUDIO_PREBUFFER_BYTES) { + k_spin_unlock(&ring->lock, key); + return false; + } + g_capture_data->started = true; + } + + ready = ring->count >= min_bytes; + k_spin_unlock(&ring->lock, key); + return ready; +} + +size_t usb_audio_fetch_capture_data(void *dst, size_t bytes) +{ + if (!dst || bytes == 0) + return 0; + + 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); + + 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; + } + + size_t to_read = MIN(bytes, (size_t)ring->count); + uint8_t *d = dst; + + ring_read_locked(ring, d, to_read, true); + if (to_read < bytes) + memset(d + to_read, 0, bytes - to_read); + + k_spin_unlock(&ring->lock, key); + return bytes; +} + +bool usb_audio_peek_capture_data(void *dst, size_t bytes) +{ + if (!dst || bytes == 0) + return false; + + 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); + + 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; + } + + size_t to_read = MIN(bytes, (size_t)ring->count); + uint8_t *d = dst; + + ring_read_locked(ring, d, to_read, false); + if (to_read < bytes) + 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 || bytes == 0) + return; + + struct usb_audio_ring_buffer *ring = &g_capture_data->ring; + k_spinlock_key_t key = k_spin_lock(&ring->lock); + size_t to_consume = MIN(bytes, (size_t)ring->count); + + 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) +{ + const struct ipc_config_host *hcfg = 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) { + comp_free_device(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 (hcfg && hcfg->direction == SOF_IPC_STREAM_CAPTURE) + g_capture_data = uad; + else + g_playback_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); + comp_free_device(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); + uint32_t frames_per_period; + + uad->sample_rate = params->rate; + uad->channels = params->channels; + uad->frame_bytes = params->sample_container_bytes * params->channels; + 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); + k_spinlock_key_t key; + + 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: + 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; + 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) { + 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) { + 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, 48U); + size_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 (!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; + } + + size_t to_copy = MIN(s16_bytes, (size_t)ring->count); + uint8_t *s16_raw = (uint8_t *)s16_buf; + + ring_read_locked(ring, s16_raw, to_copy, true); + 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 >= uad->period_bytes) { + uint8_t temp_buf[1024]; + size_t chunk = MIN((size_t)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 (!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; + } + + if (ring->count < chunk) { + memset(temp_buf, 0, chunk); + k_spin_unlock(&ring->lock, key); + } else { + ring_read_locked(ring, temp_buf, chunk, true); + 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) { + 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, 48U); + + 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 + s16_buf[i] = (int16_t)(val >= 0.0f ? (val + 0.5f) : (val - 0.5f)); + } + + size_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) { + size_t drop = (ring->count + s16_bytes) - USB_AUDIO_RING_BUFFER_SIZE; + + drop = (drop + 3) & ~((size_t)3); + if (drop > ring->count) + drop = ring->count; + ring->tail = (ring->tail + drop) % USB_AUDIO_RING_BUFFER_SIZE; + ring->count -= drop; + } + + size_t free_space = USB_AUDIO_RING_BUFFER_SIZE - ring->count; + size_t to_copy = MIN(s16_bytes, free_space); + + ring_write_locked(ring, (const uint8_t *)s16_buf, 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]; + size_t chunk = MIN((size_t)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) { + size_t drop = (ring->count + chunk) - USB_AUDIO_RING_BUFFER_SIZE; + + drop = (drop + 3) & ~((size_t)3); + if (drop > ring->count) + drop = ring->count; + ring->tail = (ring->tail + drop) % USB_AUDIO_RING_BUFFER_SIZE; + ring->count -= drop; + } + + size_t free_space = USB_AUDIO_RING_BUFFER_SIZE - ring->count; + size_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; + + ring_write_locked(ring, temp_buf, 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); + k_spinlock_key_t key = k_spin_lock(&uad->ring.lock); + + uad->active = false; + uad->started = false; + uad->ring.head = 0; + uad->ring.tail = 0; + uad->ring.count = 0; + k_spin_unlock(&uad->ring.lock, key); + 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/ipc/dai-imx.h b/src/include/ipc/dai-imx.h index 5f9fc4dce837..d16c9e021453 100644 --- a/src/include/ipc/dai-imx.h +++ b/src/include/ipc/dai-imx.h @@ -61,4 +61,10 @@ struct sof_ipc_dai_micfil_params { uint32_t pdm_ch; } __attribute__((packed, aligned(4))); +/* S/PDIF Configuration Request - SOF_IPC_DAI_SPDIF_CONFIG */ +struct sof_ipc_dai_spdif_params { + uint32_t sample_rate; + uint32_t reserved[3]; +} __attribute__((packed, aligned(4))); + #endif /* __IPC_DAI_IMX_H__ */ diff --git a/src/include/ipc/dai.h b/src/include/ipc/dai.h index 49ffa5d5fa94..f4b47db91baa 100644 --- a/src/include/ipc/dai.h +++ b/src/include/ipc/dai.h @@ -36,6 +36,7 @@ #define SOF_DAI_FMT_DSP_A 4 /**< L data MSB after FRM LRC */ #define SOF_DAI_FMT_DSP_B 5 /**< L data MSB during FRM LRC */ #define SOF_DAI_FMT_PDM 6 /**< Pulse density modulation */ +#define SOF_DAI_FMT_SPDIF 7 /**< SPDIF mode */ #define SOF_DAI_FMT_CONT (1 << 4) /**< continuous clock */ #define SOF_DAI_FMT_GATED (0 << 4) /**< clock is gated */ @@ -97,7 +98,8 @@ enum sof_ipc_dai_type { SOF_DAI_IMX_MICFIL, /**< i.MX MICFIL */ SOF_DAI_AMD_SDW, /**< Amd SDW */ SOF_DAI_INTEL_UAOL, /**< Intel UAOL */ - SOF_DAI_AMD_TDM /**< Amd TDM */ + SOF_DAI_AMD_TDM, /**< Amd TDM */ + SOF_DAI_IMX_SPDIF /**< i.MX S/PDIF */ }; #define SOF_DAI_CONFIG_HW_SPEC_OFFSET offsetof(struct sof_ipc_dai_config, ssp) @@ -131,6 +133,7 @@ struct sof_ipc_dai_config { struct sof_ipc_dai_afe_params afe; struct sof_ipc_dai_micfil_params micfil; struct sof_ipc_dai_acp_sdw_params acpsdw; + struct sof_ipc_dai_spdif_params spdif; }; } __attribute__((packed, aligned(4))); 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/module_adapter/module/generic.h b/src/include/sof/audio/module_adapter/module/generic.h index 52369bc7e92c..1234361cadf9 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 @@ -601,7 +601,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/static_pipeline.h b/src/include/sof/audio/pipeline/static_pipeline.h index 6c9a36f3c319..34f9f7c5a956 100644 --- a/src/include/sof/audio/pipeline/static_pipeline.h +++ b/src/include/sof/audio/pipeline/static_pipeline.h @@ -799,6 +799,17 @@ int sof_static_kcontrol_get_by_uac2(uint8_t entity_id, uint8_t channel, int32_t */ int sof_static_pipeline_trigger_by_uac2_term(uint8_t terminal_id, bool start); +#if defined(CONFIG_USBD_AUDIO2_CLASS) && defined(CONFIG_COMP_USB_AUDIO) +struct uac2_ops; + +/** + * \brief Retrieve standard Zephyr USB Audio Class 2.0 (UAC2) callback operations + * bridging USB streaming and Feature Unit requests to the active static topology. + * \return Pointer to static struct uac2_ops descriptor. + */ +const struct uac2_ops *sof_static_uac2_get_ops(void); +#endif + /** * \brief Start a static pipeline by pipeline ID. * diff --git a/src/include/sof/audio/usb_audio.h b/src/include/sof/audio/usb_audio.h new file mode 100644 index 000000000000..964151cbc186 --- /dev/null +++ b/src/include/sof/audio/usb_audio.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2026 Sound Open Firmware (SOF) Project. All rights reserved. + */ + +#ifndef __SOF_AUDIO_USB_AUDIO_H__ +#define __SOF_AUDIO_USB_AUDIO_H__ + +#include +#include +#include +#include +#include + +/* 16 KB (85 ms at 48 kHz stereo S16_LE) lock-protected ring buffer */ +#define USB_AUDIO_RING_BUFFER_SIZE 16384 +/* Prebuffer 10 ms (1920 bytes) at stream start to absorb USB/PLL jitter */ +#define USB_AUDIO_PREBUFFER_BYTES 1920 + +struct usb_audio_ring_buffer { + uint8_t buf[USB_AUDIO_RING_BUFFER_SIZE]; + uint32_t head; + uint32_t tail; + uint32_t count; + struct k_spinlock lock; +}; + +struct usb_audio_data { + struct usb_audio_ring_buffer ring; + bool active; + bool started; + uint32_t sample_rate; + uint32_t channels; + uint32_t frame_bytes; + uint32_t period_bytes; +}; + +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 min_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); + +#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 288caa3396ee..4b0080b9d078 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. @@ -100,6 +100,10 @@ __syscall void scheduler_dp_ll_tick(void); __syscall void scheduler_dp_check_deadline(uint16_t core, uint32_t deadline_clock_ticks); #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 static inline void scheduler_dp_check_deadline(uint16_t core, uint32_t deadline_clock_ticks) { (void)core; diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index 3860f5c96bf7..0bfbe1a32996 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -530,6 +530,11 @@ struct ll_schedule_domain *zephyr_domain_init(int clk); #define timer_domain_init(timer, clk) zephyr_domain_init(clk) +#ifdef CONFIG_ZEPHYR_DOMAIN_AUDIO_TIMER +void zephyr_domain_audio_timer_cb(void); +bool dai_zephyr_has_active_audio_domain(void); +#endif + #ifdef CONFIG_SOF_USERSPACE_LL /** * @brief Retrieve the Zephyr domain thread TID for the domain on the current core. @@ -557,8 +562,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/ipc/ipc4/dai.c b/src/ipc/ipc4/dai.c index b0737e3ac09b..6ce025864709 100644 --- a/src/ipc/ipc4/dai.c +++ b/src/ipc/ipc4/dai.c @@ -149,6 +149,12 @@ int dai_config_dma_channel(struct dai_data *dd, struct comp_dev *dev, const void channel = dai->host_dma_config[0]->dma_channel_id; break; #endif + case SOF_DAI_IMX_SAI: + case SOF_DAI_IMX_SPDIF: + case SOF_DAI_IMX_ESAI: + case SOF_DAI_IMX_MICFIL: + channel = 0; + break; default: /* other types of DAIs not handled for now */ comp_err(dev, "Unknown dai type %d", dai->type); @@ -216,6 +222,12 @@ int ipc_dai_data_config(struct dai_data *dd, struct comp_dev *dev) break; } #endif + case SOF_DAI_IMX_SAI: + case SOF_DAI_IMX_SPDIF: + case SOF_DAI_IMX_ESAI: + case SOF_DAI_IMX_MICFIL: + dd->config.burst_elems = 2; + break; default: /* other types of DAIs not handled for now */ comp_warn(dev, "Unknown dai type %d", dai->type); @@ -360,8 +372,12 @@ static int dai_init_llp_info(struct dai_data *dd, struct comp_dev *dev) copier_cfg = dd->dai_spec_config; node = copier_cfg->gtw_cfg.node_id; - /* HDA doesn't use llp slot */ - if (dd->ipc_config.type == SOF_DAI_INTEL_HDA) + /* HDA and i.MX DAIs don't use llp slot */ + if (dd->ipc_config.type == SOF_DAI_INTEL_HDA || + dd->ipc_config.type == SOF_DAI_IMX_SAI || + dd->ipc_config.type == SOF_DAI_IMX_SPDIF || + dd->ipc_config.type == SOF_DAI_IMX_ESAI || + dd->ipc_config.type == SOF_DAI_IMX_MICFIL) return 0; /* don't support more gateway like EVAD */ diff --git a/src/kernel/ipc/ipc3/dai.c b/src/kernel/ipc/ipc3/dai.c index 067aaf8579a6..fe5c9c365ec3 100644 --- a/src/kernel/ipc/ipc3/dai.c +++ b/src/kernel/ipc/ipc3/dai.c @@ -65,6 +65,8 @@ int dai_config_dma_channel(struct dai_data *dd, struct comp_dev *dev, const void case SOF_DAI_IMX_SAI: COMPILER_FALLTHROUGH; case SOF_DAI_IMX_ESAI: + COMPILER_FALLTHROUGH; + case SOF_DAI_IMX_SPDIF: handshake = dai_get_handshake(dd->dai, dai->direction, dd->stream_id); /* TODO: remove this when transition to native drivers is complete on all NXP platforms */ diff --git a/src/lib/dai.c b/src/lib/dai.c index 214dcf979711..03ddc9ac79db 100644 --- a/src/lib/dai.c +++ b/src/lib/dai.c @@ -187,6 +187,9 @@ const struct device *zephyr_dev[] = { #if CONFIG_DAI_NXP_MICFIL DT_FOREACH_STATUS_OKAY(nxp_dai_micfil, GET_DEVICE_LIST) #endif +#if CONFIG_DAI_NXP_SPDIF + DT_FOREACH_STATUS_OKAY(nxp_dai_spdif, GET_DEVICE_LIST) +#endif #if CONFIG_DAI_AMD_DMIC DT_FOREACH_STATUS_OKAY(amd_dmic_dai, GET_DEVICE_LIST) #endif @@ -230,6 +233,8 @@ static int sof_dai_type_to_zephyr(uint32_t type) return DAI_IMX_SAI; case SOF_DAI_IMX_ESAI: return DAI_IMX_ESAI; + case SOF_DAI_IMX_SPDIF: + return DAI_IMX_SPDIF; case SOF_DAI_AMD_DMIC: return DAI_AMD_DMIC; case SOF_DAI_MEDIATEK_AFE: @@ -334,6 +339,14 @@ static void dai_set_device_params(struct dai *d) case SOF_DAI_MEDIATEK_AFE: d->dma_dev = SOF_DMA_DEV_AFE_MEMIF; break; + case SOF_DAI_IMX_SAI: + d->dma_dev = SOF_DMA_DEV_SAI; + d->dma_caps = SOF_DMA_CAP_GP_LP | SOF_DMA_CAP_GP_HP; + break; + case SOF_DAI_IMX_SPDIF: + d->dma_dev = SOF_DMA_DEV_SPDIF; + d->dma_caps = SOF_DMA_CAP_GP_LP | SOF_DMA_CAP_GP_HP; + break; default: break; } diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index 57c10444a23e..d948dc317ceb 100644 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -2,4 +2,6 @@ if(CONFIG_LIBRARY) add_subdirectory(library) +elseif(CONFIG_PLATFORM_TEENSY41) + add_subdirectory(teensy41) endif() diff --git a/src/platform/Kconfig b/src/platform/Kconfig index bb3b4920a405..002af6b5092c 100644 --- a/src/platform/Kconfig +++ b/src/platform/Kconfig @@ -169,6 +169,15 @@ config IMX95 help Select if your target platform is imx95-compatible. +config PLATFORM_TEENSY41 + bool "Build for PJRC Teensy 4.1 (NXP i.MX RT1062 ARM Cortex-M7)" + select ZEPHYR_LOG + select ZEPHYR_NATIVE_DRIVERS + select ZEPHYR_DOMAIN_AUDIO_TIMER + select STATIC_PIPELINE + help + Select if your target platform is PJRC Teensy 4.1. + config REMBRANDT bool "Build for Rembrandt" select XT_INTERRUPT_LEVEL_5 @@ -217,6 +226,50 @@ config MTK endchoice +if PLATFORM_TEENSY41 + +choice + prompt "Teensy 4.1 Board Role" + default TEENSY41_BOARD_A + help + Select whether this binary is for Board A (Master Tx) or Board B (Slave Rx). + +config TEENSY41_BOARD_A + bool "Teensy 4.1 Board A (Master Clock Provider / Tx)" + help + Configure board as Board A (Master clock provider on SAI1, + generates BCLK and SYNC). Appears on host USB as + 'SOF Teensy 4.1 Audio A'. + +config TEENSY41_BOARD_B + bool "Teensy 4.1 Board B (Slave Clock Consumer / Rx)" + help + Configure board as Board B (Slave clock consumer on SAI1, + receives BCLK and SYNC). Appears on host USB as + 'SOF Teensy 4.1 Audio B'. + +endchoice + +choice + prompt "Teensy 4.1 Audio Hardware Interface" + default TEENSY41_INTERFACE_SPDIF + help + Select whether the static audio pipeline connects to SAI1 (I2S) or S/PDIF. + +config TEENSY41_INTERFACE_SAI_I2S + bool "SAI1 I2S Digital Audio Interface" + help + Route audio through NXP SAI1 (I2S mode). + +config TEENSY41_INTERFACE_SPDIF + bool "S/PDIF Hardware Transceiver" + help + Route audio through NXP on-chip S/PDIF transceiver. + +endchoice + +endif + config ESP32P4_PIE_SIMD bool "Enable ESP32-P4 PIE (Xespv2p2) 128-bit vector SIMD acceleration" default y diff --git a/src/platform/teensy41/CMakeLists.txt b/src/platform/teensy41/CMakeLists.txt new file mode 100644 index 000000000000..5ab8f3438310 --- /dev/null +++ b/src/platform/teensy41/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: BSD-3-Clause + +sof_add_include_directory(include) + +add_subdirectory(lib) + +sof_add_files( + platform.c + teensy41_pipeline_def.c +) diff --git a/src/platform/teensy41/include/platform/drivers/interrupt.h b/src/platform/teensy41/include/platform/drivers/interrupt.h new file mode 100644 index 000000000000..cf1ee35b2f08 --- /dev/null +++ b/src/platform/teensy41/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/teensy41/include/platform/lib/clk.h b/src/platform/teensy41/include/platform/lib/clk.h new file mode 100644 index 000000000000..3cce41d1b4a1 --- /dev/null +++ b/src/platform/teensy41/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/teensy41/include/platform/lib/dai.h b/src/platform/teensy41/include/platform/lib/dai.h new file mode 100644 index 000000000000..0f878cd3794e --- /dev/null +++ b/src/platform/teensy41/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/teensy41/include/platform/lib/dma.h b/src/platform/teensy41/include/platform/lib/dma.h new file mode 100644 index 000000000000..c20d3eb2e8bc --- /dev/null +++ b/src/platform/teensy41/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 1 + +#endif /* __PLATFORM_LIB_DMA_H__ */ diff --git a/src/platform/teensy41/include/platform/lib/mailbox.h b/src/platform/teensy41/include/platform/lib/mailbox.h new file mode 100644 index 000000000000..776d556483f2 --- /dev/null +++ b/src/platform/teensy41/include/platform/lib/mailbox.h @@ -0,0 +1,64 @@ +/* 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_teensy_mailbox[0x1000]; +#define MAILBOX_BASE ((uintptr_t)sof_teensy_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) +{ + (void)offset; + (void)src; +} + +static inline void mailbox_sw_regs_write(size_t offset, const void *src, size_t bytes) +{ + (void)offset; + (void)src; + (void)bytes; +} + +static inline uint32_t mailbox_sw_reg_read(size_t offset) +{ + (void)offset; + return 0; +} + +static inline uint64_t mailbox_sw_reg_read64(size_t offset) +{ + (void)offset; + return 0; +} + +#endif /* __PLATFORM_LIB_MAILBOX_H__ */ diff --git a/src/platform/teensy41/include/platform/lib/memory.h b/src/platform/teensy41/include/platform/lib/memory.h new file mode 100644 index 000000000000..b42ab6d4e7a2 --- /dev/null +++ b/src/platform/teensy41/include/platform/lib/memory.h @@ -0,0 +1,26 @@ +/* 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 +#define HEAP_SYSTEM_SIZE 0x4000 +#define HEAP_RUNTIME_SIZE 0x4000 +#define HEAP_BUFFER_SIZE 0x10000 + +#ifndef PLATFORM_DCACHE_ALIGN +#define PLATFORM_DCACHE_ALIGN 32 +#endif +#define PLATFORM_DCACHE_ALIGN_SHIFT 5 + +#define host_to_local(addr) (addr) +#define local_to_host(addr) (addr) + +#endif /* __PLATFORM_LIB_MEMORY_H__ */ diff --git a/src/platform/teensy41/include/platform/lib/pm_runtime.h b/src/platform/teensy41/include/platform/lib/pm_runtime.h new file mode 100644 index 000000000000..005fe1955d18 --- /dev/null +++ b/src/platform/teensy41/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/teensy41/include/platform/platform.h b/src/platform/teensy41/include/platform/platform.h new file mode 100644 index 000000000000..d393a80051df --- /dev/null +++ b/src/platform/teensy41/include/platform/platform.h @@ -0,0 +1,32 @@ +/* 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 PLATFORM_MAX_CHANNELS 8 +#define PLATFORM_MAX_STREAMS 8 + +#define DMA_TRACE_LOCAL_SIZE HOST_PAGE_SIZE + +struct sof_static_topology; +extern const struct sof_static_topology g_teensy41_static_topology; + +#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/teensy41/include/platform/trace/trace.h b/src/platform/teensy41/include/platform/trace/trace.h new file mode 100644 index 000000000000..f2b0e2b46301 --- /dev/null +++ b/src/platform/teensy41/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/teensy41/lib/CMakeLists.txt b/src/platform/teensy41/lib/CMakeLists.txt new file mode 100644 index 000000000000..a63b7cdf475c --- /dev/null +++ b/src/platform/teensy41/lib/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: BSD-3-Clause + +sof_add_files( + clk.c +) diff --git a/src/platform/teensy41/lib/clk.c b/src/platform/teensy41/lib/clk.c new file mode 100644 index 000000000000..e76571e56516 --- /dev/null +++ b/src/platform/teensy41/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/teensy41/platform.c b/src/platform/teensy41/platform.c new file mode 100644 index 000000000000..17382243b5bc --- /dev/null +++ b/src/platform/teensy41/platform.c @@ -0,0 +1,168 @@ +// 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 +#include +#include + +LOG_MODULE_REGISTER(teensy41_platform, CONFIG_SOF_LOG_LEVEL); + +SOF_DEFINE_REG_UUID(zipc_task); +extern struct task_ops ipc_task_ops; + +uint8_t sof_teensy_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; + + ARG_UNUSED(boot_message); + + 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("Teensy 4.1 platform boot complete"); + return 0; +} + +int platform_context_save(struct sof *sof) +{ + ARG_UNUSED(sof); + return 0; +} + +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; + + ARG_UNUSED(ipc); + + 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_complete_cmd(struct ipc *ipc) +{ + ARG_UNUSED(ipc); +} + +void ipc_platform_send_msg_direct(const struct ipc_msg *msg) +{ + ARG_UNUSED(msg); +} + +int platform_init(struct sof *sof) +{ + LOG_INF("Initializing Teensy 4.1 SOF Platform"); + + platform_clock_init(sof); + + scheduler_init_edf(); + + 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); + + dmac_init(sof); + dai_init(sof); + ipc_init(sof); + + LOG_INF("Teensy 4.1 SOF Platform initialized"); + return 0; +} diff --git a/src/platform/teensy41/teensy41_pipeline_def.c b/src/platform/teensy41/teensy41_pipeline_def.c new file mode 100644 index 000000000000..7bfcd223d8e3 --- /dev/null +++ b/src/platform/teensy41/teensy41_pipeline_def.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_TEENSY41_BOARD_B) +/* Board B: Slave Rx (consumes BCLK and SYNC generated by Board A) */ +#define TEENSY41_SAI_CLOCK_MODE SOF_DAI_FMT_CBP_CFP +#else +/* Board A: Master Tx (generates BCLK and SYNC on Pin 21 and Pin 20) */ +#define TEENSY41_SAI_CLOCK_MODE SOF_DAI_FMT_CBC_CFC +#endif + +extern const struct sof_uuid usb_audio_uuid; +extern const struct sof_uuid dai_uuid; + +/* UAC2 Device Tree Entity IDs */ +#define UAC2_STATIC_ENTITY_ID(node) (DT_NODE_CHILD_IDX(node) + 1) + +#define PLAYBACK_FU_ID UAC2_STATIC_ENTITY_ID(DT_NODELABEL(i2s_fu)) +#define CAPTURE_FU_ID UAC2_STATIC_ENTITY_ID(DT_NODELABEL(i2s_in_fu)) +#define PLAYBACK_TERM_ID UAC2_STATIC_ENTITY_ID(DT_NODELABEL(i2s_out_terminal)) +#define CAPTURE_TERM_ID UAC2_STATIC_ENTITY_ID(DT_NODELABEL(i2s_in_terminal)) + +/* 1. Component Declarations */ +static const struct sof_static_comp teensy41_comps[] = { + /* Playback Pipeline (Pipeline 1) */ + SOF_STATIC_COMP_HOST( + .id = 1, .pipeline_id = 1, .name = "USB_PB", + .uuid = &usb_audio_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2), + .ep.usb.terminal_id = PLAYBACK_TERM_ID + ), +#if defined(CONFIG_TEENSY41_INTERFACE_SPDIF) + SOF_STATIC_COMP_DAI( + .id = 5, .pipeline_id = 1, .name = "DAI_SPDIF_PB", + .uuid = &dai_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S24_4LE, 48000, 2), + .ep.dai.dai_type = SOF_DAI_IMX_SPDIF, + .ep.dai.dai_index = 0, + .ep.dai.format = SOF_DAI_FMT_SPDIF + ), + + /* Capture Pipeline (Pipeline 2) */ + SOF_STATIC_COMP_DAI( + .id = 6, .pipeline_id = 2, .name = "DAI_SPDIF_CAP", + .uuid = &dai_uuid, .direction = SOF_IPC_STREAM_CAPTURE, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S24_4LE, 48000, 2), + .ep.dai.dai_type = SOF_DAI_IMX_SPDIF, + .ep.dai.dai_index = 0, + .ep.dai.format = SOF_DAI_FMT_SPDIF + ), +#else + SOF_STATIC_COMP_DAI( + .id = 5, .pipeline_id = 1, .name = "DAI_I2S_PB", + .uuid = &dai_uuid, .direction = SOF_IPC_STREAM_PLAYBACK, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2), + .ep.dai.dai_type = SOF_DAI_IMX_SAI, + .ep.dai.dai_index = 1, + .ep.dai.format = (SOF_DAI_FMT_I2S | TEENSY41_SAI_CLOCK_MODE) + ), + + /* Capture Pipeline (Pipeline 2) */ + SOF_STATIC_COMP_DAI( + .id = 6, .pipeline_id = 2, .name = "DAI_I2S_CAP", + .uuid = &dai_uuid, .direction = SOF_IPC_STREAM_CAPTURE, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2), + .ep.dai.dai_type = SOF_DAI_IMX_SAI, + .ep.dai.dai_index = 1, + .ep.dai.format = (SOF_DAI_FMT_I2S | TEENSY41_SAI_CLOCK_MODE) + ), +#endif + SOF_STATIC_COMP_HOST( + .id = 10, .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 = CAPTURE_TERM_ID + ), +}; + +/* 2. Intermediate Audio Buffers */ +static const struct sof_static_buffer teensy41_buffers[] = { + /* Playback Buffer: USB_PB (1) -> [1] -> DAI (5) */ + SOF_STATIC_BUFFER(.id = 1, .size = 3072, .fmt = SOF_IPC_FRAME_S16_LE), + + /* Capture Buffer: DAI (6) -> [5] -> USB_CAP (10) */ + SOF_STATIC_BUFFER(.id = 5, .size = 3072, .fmt = SOF_IPC_FRAME_S16_LE), +}; + +/* 3. Pipeline Graph Routes */ +static const struct sof_static_route teensy41_routes[] = { + SOF_STATIC_ROUTE(.src_comp_id = 1, .buffer_id = 1, .sink_comp_id = 5), + SOF_STATIC_ROUTE(.src_comp_id = 6, .buffer_id = 5, .sink_comp_id = 10), +}; + +/* 4. Logical PCMs */ +static const struct sof_static_pcm teensy41_pcms[] = { + SOF_STATIC_PCM( + .pcm_id = 0, + .name = "Speaker Playback", + .direction = SOF_IPC_STREAM_PLAYBACK, + .pipeline_id = 1, + .host_comp_id = 1, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2) + ), + SOF_STATIC_PCM( + .pcm_id = 1, + .name = "Mic Capture", + .direction = SOF_IPC_STREAM_CAPTURE, + .pipeline_id = 2, + .host_comp_id = 10, + .caps = SOF_STATIC_CAPS(SOF_IPC_FRAME_S16_LE, 48000, 2) + ), +}; + +/* 5. UAC2 Feature Unit Controls */ +static const struct sof_static_kcontrol teensy41_controls[] = { + SOF_STATIC_KCONTROL_VOLUME( + .id = 1, .name = "Master Playback Volume", + .target_comp_id = 0, + .min = 0, .max = 65536, .def = 65536, .channels = 2, + .uac2_entity_id = PLAYBACK_FU_ID + ), + SOF_STATIC_KCONTROL_SWITCH( + .id = 2, .name = "Master Playback Switch", + .target_comp_id = 0, + .def = 1, .channels = 2, + .uac2_entity_id = PLAYBACK_FU_ID + ), + SOF_STATIC_KCONTROL_VOLUME( + .id = 3, .name = "Master Capture Volume", + .target_comp_id = 0, + .min = 0, .max = 65536, .def = 65536, .channels = 2, + .uac2_entity_id = CAPTURE_FU_ID + ), + SOF_STATIC_KCONTROL_SWITCH( + .id = 4, .name = "Master Capture Switch", + .target_comp_id = 0, + .def = 1, .channels = 2, + .uac2_entity_id = CAPTURE_FU_ID + ), +}; + +/* 6. Pipeline Descriptors */ +static const struct sof_static_pipeline_desc teensy41_pipelines[] = { + { + .pipeline_id = 1, + .name = "Playback Pipeline", + .direction = SOF_IPC_STREAM_PLAYBACK, + .priority = 0, + .core = 0, + .period = 1000, + .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 = "Capture Pipeline", + .direction = SOF_IPC_STREAM_CAPTURE, + .priority = 0, + .core = 0, + .period = 1000, + .frames_per_sched = 48, + .time_domain = SOF_TIME_DOMAIN_TIMER, + .sched_comp_id = 10, + .source_comp_id = 6, + .sink_comp_id = 10, + }, +}; + +const struct sof_static_topology g_teensy41_static_topology = { + .name = "Teensy 4.1 Playback & Capture Pipeline", + .num_pipelines = ARRAY_SIZE(teensy41_pipelines), + .pipelines = teensy41_pipelines, + .num_comps = ARRAY_SIZE(teensy41_comps), + .comps = teensy41_comps, + .num_buffers = ARRAY_SIZE(teensy41_buffers), + .buffers = teensy41_buffers, + .num_routes = ARRAY_SIZE(teensy41_routes), + .routes = teensy41_routes, + .num_pcms = ARRAY_SIZE(teensy41_pcms), + .pcms = teensy41_pcms, + .num_controls = ARRAY_SIZE(teensy41_controls), + .controls = teensy41_controls, +}; diff --git a/src/schedule/Kconfig b/src/schedule/Kconfig index f1383e70103f..8676ac9850eb 100644 --- a/src/schedule/Kconfig +++ b/src/schedule/Kconfig @@ -71,3 +71,12 @@ config TWB_IPC_TASK depends on ZEPHYR_TWB_SCHEDULER help Switch IPC task to TWB scheduler. + +config ZEPHYR_DOMAIN_AUDIO_TIMER + bool "Drive Zephyr LL timer domain from DAI DMA period interrupts" + default n + help + Select this on platforms where the system tick timer and the audio + PLL are derived from asynchronous clock sources. When an audio DAI + DMA stream is active, DMA period completion interrupts wake the LL + scheduler thread directly and k_timer ticks are suppressed. diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index 58d1f4b7c0fe..7677ecd5a207 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -175,6 +175,12 @@ static void zephyr_domain_timer_fn(struct k_timer *timer) return; } +#ifdef CONFIG_ZEPHYR_DOMAIN_AUDIO_TIMER + /* When audio stream is active, Audio PLL DMA interrupts drive the LL domain */ + if (dai_zephyr_has_active_audio_domain()) + return; +#endif + for (core = 0; core < CONFIG_CORE_COUNT; core++) { struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; @@ -183,6 +189,29 @@ static void zephyr_domain_timer_fn(struct k_timer *timer) } } +#ifdef CONFIG_ZEPHYR_DOMAIN_AUDIO_TIMER +void zephyr_domain_audio_timer_cb(void) +{ + struct ll_schedule_domain *domain = zephyr_ll_domain(); + struct zephyr_domain *zephyr_domain; + int core; + + if (!domain) + return; + + zephyr_domain = ll_sch_domain_get_pdata(domain); + if (!zephyr_domain) + return; + + for (core = 0; core < CONFIG_CORE_COUNT; core++) { + struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core; + + if (dt->handler) + k_sem_give(dt->sem); + } +} +#endif + /* The normal kernel-space implementation for register/unregister */ #ifndef CONFIG_SOF_USERSPACE_LL diff --git a/uuid-registry.txt b/uuid-registry.txt index 53b61626503b..793b1b13d865 100644 --- a/uuid-registry.txt +++ b/uuid-registry.txt @@ -177,6 +177,7 @@ 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 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..e3976c39a80f 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: 16a19aec1550a3f4ded4d825325e75f69d711cd0 + remote: lgirdwood # Import some projects listed in zephyr/west.yml@revision # diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 9101c662b382..10adc7f2e605 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_TEENSY41) + set(ARCH host) + set(PLATFORM "teensy41") + set(PLATFORM_HEADERS "teensy41") 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,41 @@ macro(sof_list_append_ifdef feature_toggle list) endif() endmacro() +# Setup SOF directories +set(SOF_ROOT_SOURCE_DIRECTORY ${sof_top_dir}) +set(SOF_ROOT_BINARY_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +# This generated/ directory is shared with Zephyr. +# PROJECT_BINARY_DIR is build/zephyr/ +set(GENERATED_DIRECTORY ${PROJECT_BINARY_DIR}/include/generated) +file(MAKE_DIRECTORY ${GENERATED_DIRECTORY}) + +set(VERSION_H_PATH ${GENERATED_DIRECTORY}/sof_versions.h) + +find_package(Python3 COMPONENTS Interpreter REQUIRED) +set(PYTHON3 "${Python3_EXECUTABLE}") +set(PYTHON_EXECUTABLE "${Python3_EXECUTABLE}") + +if (NOT CONFIG_COMPILER_INLINE_FUNCTION_OPTION) +target_compile_options(SOF INTERFACE -fno-inline-functions) +endif() + +# SOF needs `typeof`, `__VA_ARGS__` and maybe other GNU C99 +# extensions. TODO other flags required ? +target_compile_options(SOF INTERFACE $<$: -std=gnu99>) + +# Toolchain info +add_definitions(-DXCC_TOOLS_VERSION="${ZEPHYR_TOOLCHAIN_VARIANT}" -DCC_OPTIMIZE_FLAGS="${OPTIMIZATION_FLAG}") + +# create version information +include(../scripts/cmake/version.cmake) + +include(../scripts/cmake/uuid-registry.cmake) +add_dependencies(modules_sof uuid_reg_h) +if(TARGET app) + add_dependencies(app uuid_reg_h) +endif() + # directories with auxiliary modules need to be listed first # - llext dependencies need to added to linkage first add_subdirectory(../src/math/ math_unused_install/) @@ -415,6 +457,17 @@ if (CONFIG_SOC_MIMX9352_A55) set(PLATFORM "imx93_a55") endif() +if (CONFIG_PLATFORM_TEENSY41) + zephyr_library_sources( + ${SOF_PLATFORM_PATH}/teensy41/platform.c + ${SOF_PLATFORM_PATH}/teensy41/teensy41_pipeline_def.c + ${SOF_PLATFORM_PATH}/teensy41/lib/clk.c + lib/dma.c + ) + + set(PLATFORM "teensy41") + zephyr_include_directories(${SOF_PLATFORM_PATH}/teensy41/include) +endif() if (CONFIG_SOC_MIMX9596_M7) zephyr_library_sources( @@ -638,35 +691,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..a5ed2174d42b 100644 --- a/zephyr/include/rtos/cache.h +++ b/zephyr/include/rtos/cache.h @@ -36,6 +36,8 @@ #else #if defined(CONFIG_LIBRARY) || defined(CONFIG_ZEPHYR_POSIX) #define DCACHE_LINE_SIZE 64 +#elif defined(CONFIG_CPU_CORTEX_M) +#define DCACHE_LINE_SIZE 32 #else #error "Invalid cache configuration." #endif /* defined(CONFIG_LIBRARY) || defined(CONFIG_ZEPHYR_POSIX) */ diff --git a/zephyr/include/sof/lib/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/include/sof/lib/dma.h b/zephyr/include/sof/lib/dma.h index 261abe8cacea..ff209181ed8d 100644 --- a/zephyr/include/sof/lib/dma.h +++ b/zephyr/include/sof/lib/dma.h @@ -102,6 +102,7 @@ struct k_heap; #define SOF_DMA_DEV_MICFIL BIT(14) /**< connectable to MICFIL fifo */ #define SOF_DMA_DEV_SW BIT(15) /**< connectable to ACP SW */ #define SOF_DMA_DEV_TDM BIT(16) /**< connectable to ACP TDM I2S */ +#define SOF_DMA_DEV_SPDIF BIT(17) /**< connectable to S/PDIF */ /* DMA access privilege flag */ #define SOF_DMA_ACCESS_EXCLUSIVE 1 diff --git a/zephyr/lib/alloc.c b/zephyr/lib/alloc.c index dfaba93d8167..1b9e270552bb 100644 --- a/zephyr/lib/alloc.c +++ b/zephyr/lib/alloc.c @@ -143,6 +143,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_TEENSY41) + +#define HEAPMEM_SIZE (HEAP_SYSTEM_SIZE + HEAP_RUNTIME_SIZE + HEAP_BUFFER_SIZE) +static uint8_t __aligned(PLATFORM_DCACHE_ALIGN) heapmem[HEAPMEM_SIZE]; #else extern char _end[], _heap_sentry[]; diff --git a/zephyr/lib/dma.c b/zephyr/lib/dma.c index d9a9642e830b..9dd42187fcae 100644 --- a/zephyr/lib/dma.c +++ b/zephyr/lib/dma.c @@ -297,6 +297,18 @@ APP_SYSUSER_DATA struct sof_dma dma[] = { .z_dev = DEVICE_DT_GET(DT_NODELABEL(host_dma)), }, #endif +#if DT_NODE_HAS_STATUS(DT_NODELABEL(edma0), okay) && defined(CONFIG_PLATFORM_TEENSY41) +{ + .plat_data = { + .dir = SOF_DMA_DIR_MEM_TO_DEV | SOF_DMA_DIR_DEV_TO_MEM, + .caps = SOF_DMA_CAP_GP_LP | SOF_DMA_CAP_GP_HP, + .devs = SOF_DMA_DEV_SAI | SOF_DMA_DEV_SPDIF, + .channels = DT_PROP(DT_NODELABEL(edma0), dma_channels), + .period_count = 2, + }, + .z_dev = DEVICE_DT_GET(DT_NODELABEL(edma0)), +}, +#endif }; const struct dma_info lib_dma = {