diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index 0d597434fc5d..39138cc9e6e6 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -607,7 +607,7 @@ __cold void dai_common_free(struct dai_data *dd) dai_group_put(dd->group); if (dd->chan) { - dma_release_channel(dd->dma->z_dev, dd->chan->index); + sof_dma_release_channel(dd->dma, dd->chan->index); dd->chan->dev_data = NULL; } @@ -798,7 +798,7 @@ static int dai_set_sg_config(struct dai_data *dd, struct comp_dev *dev, uint32_t comp_dbg(dev, "fifo 0x%x", fifo); - err = dma_get_attribute(dd->dma->z_dev, DMA_ATTR_MAX_BLOCK_COUNT, &max_block_count); + err = sof_dma_get_attribute(dd->dma, DMA_ATTR_MAX_BLOCK_COUNT, &max_block_count); if (err < 0) { comp_err(dev, "can't get max block count, err = %d", err); @@ -954,14 +954,14 @@ static int dai_set_dma_buffer(struct dai_data *dd, struct comp_dev *dev, return -EINVAL; } - err = dma_get_attribute(dd->dma->z_dev, DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT, &addr_align); + err = sof_dma_get_attribute(dd->dma, DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT, &addr_align); if (err < 0) { comp_err(dev, "can't get dma buffer addr align, err = %d", err); return err; } - err = dma_get_attribute(dd->dma->z_dev, DMA_ATTR_BUFFER_SIZE_ALIGNMENT, &align); + err = sof_dma_get_attribute(dd->dma, DMA_ATTR_BUFFER_SIZE_ALIGNMENT, &align); if (err < 0 || !align) { comp_err(dev, "no valid dma align, err = %d, align = %u", err, align); @@ -1155,7 +1155,7 @@ int dai_common_config_prepare(struct dai_data *dd, struct comp_dev *dev) } /* get DMA channel */ - channel = dma_request_channel(dd->dma->z_dev, &channel); + channel = sof_dma_request_channel(dd->dma, channel); if (channel < 0) { comp_err(dev, "dma_request_channel() failed"); dd->chan = NULL; @@ -1199,7 +1199,7 @@ int dai_common_prepare(struct dai_data *dd, struct comp_dev *dev) return 0; } - ret = dma_config(dd->chan->dma->z_dev, dd->chan->index, dd->z_config); + ret = sof_dma_config(dd->chan->dma, dd->chan->index, dd->z_config); if (ret < 0) comp_set_state(dev, COMP_TRIGGER_RESET); @@ -1286,7 +1286,7 @@ 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) { - ret = dma_start(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_start(dd->chan->dma, dd->chan->index); if (ret < 0) return ret; @@ -1324,16 +1324,16 @@ 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) { /* recover valid start position */ - ret = dma_stop(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_stop(dd->chan->dma, dd->chan->index); if (ret < 0) return ret; /* dma_config needed after stop */ - ret = dma_config(dd->chan->dma->z_dev, dd->chan->index, dd->z_config); + ret = sof_dma_config(dd->chan->dma, dd->chan->index, dd->z_config); if (ret < 0) return ret; - ret = dma_start(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_start(dd->chan->dma, dd->chan->index); if (ret < 0) return ret; @@ -1361,11 +1361,11 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, * as soon as possible. */ #if CONFIG_COMP_DAI_STOP_TRIGGER_ORDER_REVERSE - ret = dma_stop(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_stop(dd->chan->dma, dd->chan->index); dai_trigger_op(dd->dai, cmd, dev->direction); #else dai_trigger_op(dd->dai, cmd, dev->direction); - ret = dma_stop(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_stop(dd->chan->dma, dd->chan->index); if (ret) { comp_warn(dev, "dma was stopped earlier"); ret = 0; @@ -1375,11 +1375,11 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, case COMP_TRIGGER_PAUSE: comp_dbg(dev, "PAUSE"); #if CONFIG_COMP_DAI_STOP_TRIGGER_ORDER_REVERSE - ret = dma_suspend(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_suspend(dd->chan->dma, dd->chan->index); dai_trigger_op(dd->dai, cmd, dev->direction); #else dai_trigger_op(dd->dai, cmd, dev->direction); - ret = dma_suspend(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_suspend(dd->chan->dma, dd->chan->index); #endif break; case COMP_TRIGGER_PRE_START: @@ -1471,7 +1471,7 @@ static int dai_comp_trigger(struct comp_dev *dev, int cmd) /* get status from dma and check for xrun */ static int dai_get_status(struct comp_dev *dev, struct dai_data *dd, struct dma_status *stat) { - int ret = dma_get_status(dd->chan->dma->z_dev, dd->chan->index, stat); + int ret = sof_dma_get_status(dd->chan->dma, dd->chan->index, stat); #if CONFIG_XRUN_NOTIFICATIONS_ENABLE if (ret == -EPIPE && !dd->xrun_notification_sent) { struct ipc_msg *notify = ipc_notification_pool_get(IPC4_RESOURCE_EVENT_SIZE); @@ -1582,7 +1582,7 @@ int dai_zephyr_multi_endpoint_copy(struct dai_data **dd, struct comp_dev *dev, #endif for (i = 0; i < num_endpoints; i++) { - ret = dma_reload(dd[i]->chan->dma->z_dev, dd[i]->chan->index, 0, 0, 0); + ret = sof_dma_reload(dd[i]->chan->dma, dd[i]->chan->index, 0); if (ret < 0) { dai_report_xrun(dd[i], dev, 0); return ret; @@ -1608,10 +1608,10 @@ int dai_zephyr_multi_endpoint_copy(struct dai_data **dd, struct comp_dev *dev, status = dai_dma_multi_endpoint_cb(dd[i], dev, frames, multi_endpoint_buffer); if (status == SOF_DMA_CB_STATUS_END) - dma_stop(dd[i]->chan->dma->z_dev, dd[i]->chan->index); + sof_dma_stop(dd[i]->chan->dma, dd[i]->chan->index); copy_bytes = frames * audio_stream_frame_bytes(&dd[i]->dma_buffer->stream); - ret = dma_reload(dd[i]->chan->dma->z_dev, dd[i]->chan->index, 0, 0, copy_bytes); + ret = sof_dma_reload(dd[i]->chan->dma, dd[i]->chan->index, copy_bytes); if (ret < 0) { dai_report_xrun(dd[i], dev, copy_bytes); return ret; @@ -1800,7 +1800,7 @@ int dai_common_copy(struct dai_data *dd, struct comp_dev *dev, pcm_converter_fun comp_warn(dev, "nothing to copy, src_frames: %u, sink_frames: %u", src_frames, sink_frames); #endif - dma_reload(dd->chan->dma->z_dev, dd->chan->index, 0, 0, 0); + sof_dma_reload(dd->chan->dma, dd->chan->index, 0); return 0; } @@ -1810,9 +1810,9 @@ int dai_common_copy(struct dai_data *dd, struct comp_dev *dev, pcm_converter_fun comp_warn(dev, "dai trigger copy failed"); if (dai_dma_cb(dd, dev, copy_bytes, converter) == SOF_DMA_CB_STATUS_END) - dma_stop(dd->chan->dma->z_dev, dd->chan->index); + sof_dma_stop(dd->chan->dma, dd->chan->index); - ret = dma_reload(dd->chan->dma->z_dev, dd->chan->index, 0, 0, copy_bytes); + ret = sof_dma_reload(dd->chan->dma, dd->chan->index, copy_bytes); if (ret < 0) { dai_report_xrun(dd, dev, copy_bytes); return ret; diff --git a/src/include/ipc4/gateway.h b/src/include/ipc4/gateway.h index f78c21caf979..9398bb44be0e 100644 --- a/src/include/ipc4/gateway.h +++ b/src/include/ipc4/gateway.h @@ -162,8 +162,8 @@ struct ipc4_ipc_gateway_config_blob { uint32_t buffer_size; /**< Flags */ - union flags { - struct bits { + union { + struct { /**< Activates high threshold notification */ /*! * Indicates whether notification should be sent to the host diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 534b30300ed5..b1af82ae856b 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -490,11 +490,10 @@ if(NOT DEFINED PLATFORM) endif() zephyr_include_directories(${SOF_PLATFORM_PATH}/${PLATFORM}/include) -zephyr_library_sources_ifdef(CONFIG_USERSPACE - syscall/sof_dma.c -) - -zephyr_syscall_header(include/sof/lib/sof_dma.h) +if(CONFIG_SOF_USERSPACE_INTERFACE_DMA) + zephyr_library_sources(syscall/sof_dma.c) + zephyr_syscall_header(include/sof/lib/sof_dma.h) +endif() # Mandatory Files used on all platforms. # Commented files will be added/removed as integration dictates. diff --git a/zephyr/include/sof/lib/sof_dma.h b/zephyr/include/sof/lib/sof_dma.h index 74b7d4289083..bdd64aeac9fa 100644 --- a/zephyr/include/sof/lib/sof_dma.h +++ b/zephyr/include/sof/lib/sof_dma.h @@ -51,6 +51,10 @@ __syscall int sof_dma_get_status(struct sof_dma *dma, uint32_t channel, struct d __syscall int sof_dma_reload(struct sof_dma *dma, uint32_t channel, size_t size); +__syscall int sof_dma_suspend(struct sof_dma *dma, uint32_t channel); + +__syscall int sof_dma_resume(struct sof_dma *dma, uint32_t channel); + static inline int z_impl_sof_dma_get_attribute(struct sof_dma *dma, uint32_t type, uint32_t *value) { return dma_get_attribute(dma->z_dev, type, value); @@ -95,6 +99,16 @@ static inline int z_impl_sof_dma_reload(struct sof_dma *dma, uint32_t channel, s return dma_reload(dma->z_dev, channel, 0, 0, size); } +static inline int z_impl_sof_dma_suspend(struct sof_dma *dma, uint32_t channel) +{ + return dma_suspend(dma->z_dev, channel); +} + +static inline int z_impl_sof_dma_resume(struct sof_dma *dma, uint32_t channel) +{ + return dma_resume(dma->z_dev, channel); +} + #ifdef CONFIG_SOF_USERSPACE_INTERFACE_DMA /* include definitions from generated file */ @@ -164,6 +178,16 @@ static inline int sof_dma_reload(struct sof_dma *dma, uint32_t channel, size_t s return z_impl_sof_dma_reload(dma, channel, size); } +static inline int sof_dma_suspend(struct sof_dma *dma, uint32_t channel) +{ + return z_impl_sof_dma_suspend(dma, channel); +} + +static inline int sof_dma_resume(struct sof_dma *dma, uint32_t channel) +{ + return z_impl_sof_dma_resume(dma, channel); +} + #endif /* CONFIG_SOF_USERSPACE_INTERFACE_DMA */ #endif diff --git a/zephyr/syscall/sof_dma.c b/zephyr/syscall/sof_dma.c index 84fd2f24aee4..ed69ffc78423 100644 --- a/zephyr/syscall/sof_dma.c +++ b/zephyr/syscall/sof_dma.c @@ -235,4 +235,20 @@ static inline int z_vrfy_sof_dma_reload(struct sof_dma *dma, uint32_t channel, s } #include +static inline int z_vrfy_sof_dma_suspend(struct sof_dma *dma, uint32_t channel) +{ + K_OOPS(!sof_dma_is_valid(dma)); + + return z_impl_sof_dma_suspend(dma, channel); +} +#include + +static inline int z_vrfy_sof_dma_resume(struct sof_dma *dma, uint32_t channel) +{ + K_OOPS(!sof_dma_is_valid(dma)); + + return z_impl_sof_dma_resume(dma, channel); +} +#include + #endif /* CONFIG_SOF_USERSPACE_INTERFACE_DMA */ diff --git a/zephyr/test/CMakeLists.txt b/zephyr/test/CMakeLists.txt index 767d0984d1c5..d7a2bbece5bd 100644 --- a/zephyr/test/CMakeLists.txt +++ b/zephyr/test/CMakeLists.txt @@ -4,8 +4,11 @@ if (CONFIG_SOF_BOOT_TEST) ) endif() -if (CONFIG_SOF_BOOT_TEST_STANDALONE) - if (CONFIG_DT_HAS_INTEL_ADSP_HDA_HOST_IN_ENABLED AND CONFIG_SOF_USERSPACE_INTERFACE_DMA) +if (CONFIG_SOF_BOOT_TEST_STANDALONE AND CONFIG_SOF_USERSPACE_INTERFACE_DMA) + if (CONFIG_DT_HAS_INTEL_ADSP_HDA_HOST_IN_ENABLED) zephyr_library_sources(userspace/test_intel_hda_dma.c) endif() + if (CONFIG_DT_HAS_INTEL_ADSP_HDA_SSP_CAP_ENABLED) + zephyr_library_sources(userspace/test_intel_ssp_dai.c) + endif() endif() diff --git a/zephyr/test/userspace/README.md b/zephyr/test/userspace/README.md index 55807eecb329..46119f3753db 100644 --- a/zephyr/test/userspace/README.md +++ b/zephyr/test/userspace/README.md @@ -1,10 +1,19 @@ -intel_hda_dma test ------------------- +User-space interface tests for Intel ADSP +----------------------------------------- -This is a standalone test to exercise the Intel HDA DMA host interface -from a userspace Zephyr thread. -Build with ("ptl" example): +This folder contains multiple tests to exercise Intel DSP device interfaces +from a user-space Zephyr thread. +Available tests: +- test_intel_hda_dma.c + - Test Intel HDA DMA host interface from a userspace + Zephyr thread. Use cavstool.py as host runner. +- test_intel_ssp_dai.c + - Test Zephyr DAI interface, together with SOF DMA + wrapper from a user thread. Mimics the call flows done in + sof/src/audio/dai-zephyr.c. Use cavstool.py as host runner. + +Building for Intel Panther Lake: ./scripts/xtensa-build-zephyr.py --cmake-args=-DCONFIG_SOF_BOOT_TEST_STANDALONE=y \ --cmake-args=-DCONFIG_SOF_USERSPACE_INTERFACE_DMA=y \ -o app/overlays/ptl/userspace_overlay.conf -o app/winconsole_overlay.conf ptl diff --git a/zephyr/test/userspace/test_intel_hda_dma.c b/zephyr/test/userspace/test_intel_hda_dma.c index 4b5a3e80cdf2..dd54e6d85f2a 100644 --- a/zephyr/test/userspace/test_intel_hda_dma.c +++ b/zephyr/test/userspace/test_intel_hda_dma.c @@ -62,6 +62,7 @@ static void intel_hda_dma_user(void *p1, void *p2, void *p3) LOG_INF("configure DMA channel"); channel = sof_dma_request_channel(dma, TEST_CHANNEL); + zassert_equal(channel, TEST_CHANNEL); LOG_INF("sof_dma_request_channel: ret %d", channel); err = sof_dma_get_attribute(dma, DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT, @@ -234,7 +235,7 @@ ZTEST_SUITE(userspace_intel_hda_dma, NULL, NULL, NULL, NULL, NULL); */ static int run_tests(void) { - ztest_run_all(NULL, false, 1, 1); + ztest_run_test_suite(userspace_intel_hda_dma, false, 1, 1, NULL); return 0; } diff --git a/zephyr/test/userspace/test_intel_ssp_dai.c b/zephyr/test/userspace/test_intel_ssp_dai.c new file mode 100644 index 000000000000..6c700c3839bb --- /dev/null +++ b/zephyr/test/userspace/test_intel_ssp_dai.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright(c) 2025 Intel Corporation. + */ + +/* + * Test case for user-space use of the SOF DMA interface. The tests + * covers all key interfaces of DMA and DAI, testing their use from + * a user-space threads. Due to hardware constraints, the actual DMA + * transfers cannot be tested as this would require cooperation with a + * host entity that would manage the HDA link DMA in sync with the DP + * test case. Test does check all programming can be done and no + * errors are raised from the drivers. Valid configuration blobs are + * passed, to fully exercise the drivers interfaces. + + * Requirements for host side test execution environment: + * - I2S offload must be enabled on host side (HDAMLI2S) to allow + * the DAI driver to access hardware registers. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../../../src/audio/copier/dai_copier.h" +#include "../../../../zephyr/drivers/dai/intel/ssp/ssp.h" + +LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); + +#define USER_STACKSIZE 8192 +#define HD_DMA_BUF_ALIGN 128 +#define TEST_BUF_SIZE (2*HD_DMA_BUF_ALIGN) +#define TEST_CHANNEL_OUT 3 +#define TEST_CHANNEL_IN 4 +#define SSP_DEVICE ssp00 + +static struct k_thread user_thread; +static K_THREAD_STACK_DEFINE(user_stack, USER_STACKSIZE); + +static K_SEM_DEFINE(ipc_sem_wake_user, 0, 1); +static K_SEM_DEFINE(ipc_sem_wake_kernel, 0, 1); + +static int call_dai_set_ssp_v3_config_48k_2ch_32bit(const struct device *dai_dev) +{ + union hdalink_cfg link_cfg; + const uint8_t stream_id = 0; + + link_cfg.full = 0; + link_cfg.part.dir = DAI_DIR_TX; + link_cfg.part.stream = stream_id; + + struct dai_config common_config = { + .type = DAI_INTEL_SSP_NHLT, + .dai_index = 0, + .channels = 2, + .rate = 48000, + .format = DAI_CBC_CFC | DAI_PROTO_I2S | DAI_INVERSION_NB_NF, + .options = 0, + .word_size = 32, + .block_size = 0, + .link_config = link_cfg.full, + .tdm_slot_group = 0 + }; + + /* + * There are no suitable struct definitions to create these + * config objects, so we have to define a custom type that + * includes the common header, a single MDIV entry, one TLV + * entry and the link_ctl struct. These are normally part of + * ACPI NHLT and can be alternatively created with alsa-utils + * nhlt plugin. + */ + struct { + struct dai_intel_ipc4_ssp_configuration_blob_ver_3_0 b; + uint32_t mdivr0; + uint32_t type; + uint32_t size; + struct ssp_intel_link_ctl link_ctl; + } __packed blob30; + + memset(&blob30, 0, sizeof(blob30)); + /* DAI config blob header for SSP v3 */ + blob30.b.version = SSP_BLOB_VER_3_0; + blob30.b.size = sizeof(blob30); + /* I2S config matching sof-ptl-nocodec.tplg (32bit/48kHz/2ch) */ + blob30.b.i2s_ssp_config.ssc0 = 0x81d0077f; + blob30.b.i2s_ssp_config.ssc1 = 0xd0400004; + blob30.b.i2s_ssp_config.sscto = 0; + blob30.b.i2s_ssp_config.sspsp = 0x02200000; + blob30.b.i2s_ssp_config.ssc2 = 0x00004002; + blob30.b.i2s_ssp_config.sspsp2 = 0; + blob30.b.i2s_ssp_config.ssc3 = 0; + blob30.b.i2s_ssp_config.ssioc = 0x00000020; + /* clock control settings matching sof-ptl-nocodec.tplg */ + blob30.b.i2s_mclk_control.mdivctlr = 0x00010001; + blob30.b.i2s_mclk_control.mdivrcnt = 1; + /* variable-size section of clock control, one entry for mdivr */ + blob30.mdivr0 = 0xfff; + /* aux-data with one TLV entry for link-clk-source */ + blob30.type = SSP_LINK_CLK_SOURCE; + blob30.size = sizeof(struct ssp_intel_link_ctl); + blob30.link_ctl.clock_source = 1; + + return dai_config_set(dai_dev, &common_config, &blob30, sizeof(blob30)); +} + +static void intel_ssp_dai_user(void *p1, void *p2, void *p3) +{ + struct dma_block_config dma_block_cfg; + struct sof_dma *dma_in, *dma_out; + uint8_t data_buf_out[TEST_BUF_SIZE] __aligned(HD_DMA_BUF_ALIGN); + uint8_t data_buf_in[TEST_BUF_SIZE] __aligned(HD_DMA_BUF_ALIGN); + uint32_t addr_align = 0; + const struct device *dai_dev; + struct dai_properties dai_props; + struct dma_config config; + struct dma_status stat; + int err, channel_out, channel_in; + + zassert_true(k_is_user_context()); + + /* + * note: this gets a pointer to kernel memory this thread + * cannot access + */ + dma_in = sof_dma_get(SOF_DMA_DIR_DEV_TO_MEM, 0, SOF_DMA_DEV_SSP, SOF_DMA_ACCESS_SHARED); + dma_out = sof_dma_get(SOF_DMA_DIR_MEM_TO_DEV, 0, SOF_DMA_DEV_SSP, SOF_DMA_ACCESS_SHARED); + + k_sem_take(&ipc_sem_wake_user, K_FOREVER); + + LOG_INF("create a DAI device for %s", STRINGIFY(SSP_DEVICE)); + + dai_dev = DEVICE_DT_GET(DT_NODELABEL(SSP_DEVICE)); + err = dai_probe(dai_dev); + zassert_equal(err, 0); + + channel_out = sof_dma_request_channel(dma_out, TEST_CHANNEL_OUT); + zassert_equal(channel_out, TEST_CHANNEL_OUT); + LOG_INF("sof_dma_request_channel (out): ret ch %d", channel_out); + channel_in = sof_dma_request_channel(dma_in, TEST_CHANNEL_IN); + zassert_equal(channel_in, TEST_CHANNEL_IN); + LOG_INF("sof_dma_request_channel (in): ret ch %d", channel_in); + + err = sof_dma_get_attribute(dma_out, DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT, + &addr_align); + zassert_equal(err, 0); + zassert_true(addr_align == HD_DMA_BUF_ALIGN); + + /* set up a DMA transfer */ + memset(&dma_block_cfg, 0, sizeof(dma_block_cfg)); + + err = dai_get_properties_copy(dai_dev, DAI_DIR_TX, 0, &dai_props); + zassert_equal(err, 0); + + LOG_INF("dai_get_properties_copy (TX), ret %d, fifo %u", err, dai_props.fifo_address); + + dma_block_cfg.dest_address = dai_props.fifo_address; /* dai fifo */ + dma_block_cfg.source_address = (uintptr_t)data_buf_out; + dma_block_cfg.block_size = sizeof(data_buf_out); + + memset(&config, 0, sizeof(config)); + config.channel_direction = MEMORY_TO_PERIPHERAL; + config.block_count = 1; + config.head_block = &dma_block_cfg; + config.source_data_size = 4; + config.dest_data_size = 4; + + err = sof_dma_config(dma_out, channel_out, &config); + zassert_equal(err, 0); + + err = dai_get_properties_copy(dai_dev, DAI_DIR_RX, 0, &dai_props); + zassert_equal(err, 0); + LOG_INF("dai_get_properties_copy (RX), ret %d, fifo %u", err, dai_props.fifo_address); + + dma_block_cfg.dest_address = (uintptr_t)data_buf_in; + dma_block_cfg.source_address = dai_props.fifo_address; /* dai fifo */ + dma_block_cfg.block_size = sizeof(data_buf_in); + + config.channel_direction = PERIPHERAL_TO_MEMORY; + config.block_count = 1; + + err = sof_dma_config(dma_in, channel_in, &config); + zassert_equal(err, 0, "dma-config error"); + + err = call_dai_set_ssp_v3_config_48k_2ch_32bit(dai_dev); + zassert_equal(err, 0); + LOG_INF("DAI configuration ready, sync with kernel on start"); + + k_sem_give(&ipc_sem_wake_kernel); + k_sem_take(&ipc_sem_wake_user, K_FOREVER); + LOG_INF("start DMA test and transfer data"); + + err = dai_trigger(dai_dev, DAI_DIR_RX, DAI_TRIGGER_PRE_START); + zassert_equal(err, 0); + + err = dai_trigger(dai_dev, DAI_DIR_TX, DAI_TRIGGER_PRE_START); + zassert_equal(err, 0); + LOG_INF("dai_trigger RX+TX PRE_START done"); + + err = sof_dma_get_status(dma_in, channel_in, &stat); + zassert_equal(err, 0); + LOG_INF("sof_dma_get_status ( dma_in/start):\tpend %3u free %3u", + stat.pending_length, stat.free); + + err = sof_dma_get_status(dma_out, channel_out, &stat); + zassert_equal(err, 0); + LOG_INF("sof_dma_get_status (dma_out/start):\tpend %3u free %3u", + stat.pending_length, stat.free); + + err = sof_dma_start(dma_in, channel_in); + zassert_equal(err, 0); + + err = sof_dma_start(dma_out, channel_out); + zassert_equal(err, 0); + + err = dai_trigger(dai_dev, DAI_DIR_RX, DAI_TRIGGER_START); + zassert_equal(err, 0); + + err = dai_trigger(dai_dev, DAI_DIR_TX, DAI_TRIGGER_START); + zassert_equal(err, 0); + LOG_INF("DMAs and DAIs started."); + + k_sleep(K_USEC(10)); + + err = sof_dma_get_status(dma_in, channel_in, &stat); + zassert_equal(err, 0); + /* after start, there should be at least some free data */ + zassert_true(stat.free > 0); + zassert_true(stat.pending_length < TEST_BUF_SIZE); + LOG_INF("sof_dma_get_status ( dma_in/run):\tpend %3u free %3u", + stat.pending_length, stat.free); + + err = sof_dma_reload(dma_in, channel_in, sizeof(data_buf_in)); + zassert_equal(err, 0); + + err = sof_dma_get_status(dma_in, channel_in, &stat); + zassert_equal(err, 0); + /* after reload, there should be at least some data pending */ + zassert_true(stat.free < TEST_BUF_SIZE); + zassert_true(stat.pending_length > 0); + + err = sof_dma_get_status(dma_out, channel_out, &stat); + zassert_equal(err, 0); + LOG_INF("sof_dma_get_status (dma_out/run):\tpend %3u free %3u", + stat.pending_length, stat.free); + zassert_true(stat.free < TEST_BUF_SIZE); + zassert_true(stat.pending_length > 0); + + LOG_INF("DMA setup done, asking host to clean up "); + k_sem_give(&ipc_sem_wake_kernel); + k_sem_take(&ipc_sem_wake_user, K_FOREVER); + LOG_INF("Cleaning up resources"); + + err = sof_dma_stop(dma_out, channel_out); + zassert_equal(err, 0); + + err = sof_dma_stop(dma_in, channel_in); + zassert_equal(err, 0); + + err = dai_trigger(dai_dev, DAI_DIR_TX, DAI_TRIGGER_STOP); + zassert_equal(err, 0); + + err = dai_trigger(dai_dev, DAI_DIR_RX, DAI_TRIGGER_STOP); + zassert_equal(err, 0); + + sof_dma_release_channel(dma_out, channel_out); + + sof_dma_release_channel(dma_in, channel_in); + + err = dai_remove(dai_dev); + zassert_equal(err, 0); + + sof_dma_put(dma_in); + sof_dma_put(dma_out); + + LOG_INF("Cleanup successful, terminating user thread."); + + k_sem_give(&ipc_sem_wake_kernel); +} + +static void intel_ssp_dai_kernel(void) +{ + const struct device *dma_out, *dma_in; + const struct device *dai_dev; + + k_thread_create(&user_thread, user_stack, USER_STACKSIZE, + intel_ssp_dai_user, NULL, NULL, NULL, + -1, K_USER, K_FOREVER); + + k_thread_access_grant(&user_thread, &ipc_sem_wake_user); + k_thread_access_grant(&user_thread, &ipc_sem_wake_kernel); + + dma_out = DEVICE_DT_GET(DT_NODELABEL(hda_link_out)); + dma_in = DEVICE_DT_GET(DT_NODELABEL(hda_link_in)); + dai_dev = DEVICE_DT_GET(DT_NODELABEL(SSP_DEVICE)); + + k_thread_access_grant(&user_thread, dma_out); + k_thread_access_grant(&user_thread, dma_in); + k_thread_access_grant(&user_thread, dai_dev); + + k_thread_start(&user_thread); + + LOG_INF("user started, waiting for it to be ready"); + + k_sem_give(&ipc_sem_wake_user); + k_sem_take(&ipc_sem_wake_kernel, K_FOREVER); + + LOG_INF("user ready, starting HDA test"); + + k_sem_give(&ipc_sem_wake_user); + k_sem_take(&ipc_sem_wake_kernel, K_FOREVER); + + LOG_INF("transfer done, grant permission to clean up"); + + k_sem_give(&ipc_sem_wake_user); + k_sem_take(&ipc_sem_wake_kernel, K_FOREVER); + + LOG_INF("test done, terminate user thread"); + + k_thread_join(&user_thread, K_FOREVER); +} + +ZTEST(userspace_intel_dai_ssp, dai_ssp_loopback_setup) +{ + intel_ssp_dai_kernel(); + + ztest_test_pass(); +} + +ZTEST_SUITE(userspace_intel_dai_ssp, NULL, NULL, NULL, NULL, NULL); + +/** + * SOF main has booted up and IPC handling is stopped. + * Run test suites with ztest_run_all. + */ +static int run_tests(void) +{ + ztest_run_test_suite(userspace_intel_dai_ssp, false, 1, 1, NULL); + return 0; +} + +SYS_INIT(run_tests, APPLICATION, 99);