diff --git a/drivers/dai/CMakeLists.txt b/drivers/dai/CMakeLists.txt index 2196c11485f68..432bb4dccd7a6 100644 --- a/drivers/dai/CMakeLists.txt +++ b/drivers/dai/CMakeLists.txt @@ -7,3 +7,4 @@ add_subdirectory_ifdef(CONFIG_DAI_INTEL_HDA intel/hda) add_subdirectory_ifdef(CONFIG_DAI_NXP_SAI nxp/sai) add_subdirectory_ifdef(CONFIG_DAI_NXP_ESAI nxp/esai) add_subdirectory_ifdef(CONFIG_DAI_NXP_MICFIL nxp/micfil) +add_subdirectory_ifdef(CONFIG_DAI_VIRTUAL virtual) diff --git a/drivers/dai/Kconfig b/drivers/dai/Kconfig index fc3972a989666..471f79f2354fd 100644 --- a/drivers/dai/Kconfig +++ b/drivers/dai/Kconfig @@ -32,5 +32,6 @@ source "drivers/dai/intel/hda/Kconfig.hda" source "drivers/dai/nxp/sai/Kconfig.sai" source "drivers/dai/nxp/esai/Kconfig.esai" source "drivers/dai/nxp/micfil/Kconfig.micfil" +source "drivers/dai/virtual/Kconfig.virtual" endif # DAI diff --git a/drivers/dai/virtual/CMakeLists.txt b/drivers/dai/virtual/CMakeLists.txt new file mode 100644 index 0000000000000..4c850527964b5 --- /dev/null +++ b/drivers/dai/virtual/CMakeLists.txt @@ -0,0 +1,5 @@ +# Copyright (c) 2025 Suraj Sonawane +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_sources_ifdef(CONFIG_DAI_VIRTUAL virtual_dai.c) diff --git a/drivers/dai/virtual/Kconfig.virtual b/drivers/dai/virtual/Kconfig.virtual new file mode 100644 index 0000000000000..3599df6d24df0 --- /dev/null +++ b/drivers/dai/virtual/Kconfig.virtual @@ -0,0 +1,11 @@ +# Copyright (c) 2025, Suraj Sonawane +# SPDX-License-Identifier: Apache-2.0 + +config DAI_VIRTUAL + bool "Virtual DAI driver" + depends on DT_HAS_ZEPHYR_VIRTUAL_DAI_ENABLED + help + Select this to enable support for a Virtual DAI driver. + This driver is useful for debugging and rapid prototyping without + real hardware. It simulates audio input/output behavior in software + and is useful in memory-to-memory pipelines. diff --git a/drivers/dai/virtual/virtual_dai.c b/drivers/dai/virtual/virtual_dai.c new file mode 100644 index 0000000000000..b021f431c9016 --- /dev/null +++ b/drivers/dai/virtual/virtual_dai.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025 Suraj Sonawane + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#define DT_DRV_COMPAT zephyr_virtual_dai +LOG_MODULE_REGISTER(virtual_dai, CONFIG_DAI_LOG_LEVEL); + +struct virtual_dai_data { + struct dai_config cfg; +}; + +static int virtual_dai_probe(const struct device *dev) +{ + return 0; +} + +static int virtual_dai_remove(const struct device *dev) +{ + return 0; +} + +static int virtual_dai_config_set(const struct device *dev, + const struct dai_config *cfg, + const void *bespoke_data) +{ + if (cfg->type != DAI_VIRTUAL) { + LOG_ERR("wrong DAI type: %d", cfg->type); + return -EINVAL; + } + + return 0; +} + +static int virtual_dai_config_get(const struct device *dev, + struct dai_config *cfg, + enum dai_dir dir) +{ + struct virtual_dai_data *data = dev->data; + + /* dump content of the DAI configuration */ + memcpy(cfg, &data->cfg, sizeof(*cfg)); + + return 0; +} + +static const struct dai_properties * +virtual_dai_get_properties(const struct device *dev, enum dai_dir dir, int stream_id) +{ + return NULL; +} + +static int virtual_dai_trigger(const struct device *dev, + enum dai_dir dir, + enum dai_trigger_cmd cmd) +{ + switch (cmd) { + case DAI_TRIGGER_START: + LOG_DBG("virtual_dai: START (dir=%d)", dir); + return 0; + case DAI_TRIGGER_STOP: + LOG_DBG("virtual_dai: STOP (dir=%d)", dir); + return 0; + case DAI_TRIGGER_PAUSE: + LOG_DBG("virtual_dai: PAUSE (dir=%d)", dir); + return 0; + case DAI_TRIGGER_PRE_START: + case DAI_TRIGGER_COPY: + LOG_DBG("virtual_dai: DAI_TRIGGER_COPY"); + return 0; + default: + LOG_WRN("virtual_dai: Unknown trigger %d", cmd); + return -EINVAL; + } + + CODE_UNREACHABLE; +} + +static DEVICE_API(dai, virtual_dai_api) = { + .probe = virtual_dai_probe, + .remove = virtual_dai_remove, + .config_set = virtual_dai_config_set, + .config_get = virtual_dai_config_get, + .get_properties = virtual_dai_get_properties, + .trigger = virtual_dai_trigger, +}; + +static int virtual_dai_init(const struct device *dev) +{ + return 0; +} + +#define VIRTUAL_DAI_INIT(inst) \ +static struct virtual_dai_data virtual_dai_data_##inst = { \ + .cfg.type = DAI_VIRTUAL, \ + .cfg.dai_index = DT_INST_PROP_OR(inst, dai_index, 0), \ +}; \ + \ +DEVICE_DT_INST_DEFINE(inst, \ + &virtual_dai_init, NULL, \ + &virtual_dai_data_##inst, NULL, \ + POST_KERNEL, CONFIG_DAI_INIT_PRIORITY, \ + &virtual_dai_api); + +DT_INST_FOREACH_STATUS_OKAY(VIRTUAL_DAI_INIT); diff --git a/dts/bindings/dai/zephyr,virtual-dai.yaml b/dts/bindings/dai/zephyr,virtual-dai.yaml new file mode 100644 index 0000000000000..47135475b0c8a --- /dev/null +++ b/dts/bindings/dai/zephyr,virtual-dai.yaml @@ -0,0 +1,18 @@ +# Copyright (c) 2025 Suraj Sonawane +# SPDX-License-Identifier: Apache-2.0 + +title: Zephyr binding for a Virtual DAI (Digital Audio Interface) + +description: | + This DAI is not connected to real hardware and is used for software-only + audio routing or testing within SOF + Zephyr systems. + +compatible: "zephyr,virtual-dai" + +include: base.yaml + +properties: + dai-index: + type: int + description: | + DAI index used to identify this virtual DAI in IPC and topology. diff --git a/include/zephyr/drivers/dai.h b/include/zephyr/drivers/dai.h index b70e5bb057248..1f1b1955e3b07 100644 --- a/include/zephyr/drivers/dai.h +++ b/include/zephyr/drivers/dai.h @@ -116,6 +116,7 @@ enum dai_type { DAI_INTEL_HDA_NHLT, /**< nhlt Intel HD/A */ DAI_INTEL_ALH_NHLT, /**< nhlt Intel ALH */ DAI_IMX_MICFIL, /**< i.MX PDM MICFIL */ + DAI_VIRTUAL, /**< Virtual DAI*/ }; /** diff --git a/tests/drivers/dai/virtual_dai_api/CMakeLists.txt b/tests/drivers/dai/virtual_dai_api/CMakeLists.txt new file mode 100644 index 0000000000000..f3e33da7bbef0 --- /dev/null +++ b/tests/drivers/dai/virtual_dai_api/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Suraj Sonawane +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(virtual_dai_api) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/dai/virtual_dai_api/boards/native_sim.conf b/tests/drivers/dai/virtual_dai_api/boards/native_sim.conf new file mode 100644 index 0000000000000..2f221909e05d3 --- /dev/null +++ b/tests/drivers/dai/virtual_dai_api/boards/native_sim.conf @@ -0,0 +1 @@ +CONFIG_DAI_VIRTUAL=y diff --git a/tests/drivers/dai/virtual_dai_api/boards/native_sim.overlay b/tests/drivers/dai/virtual_dai_api/boards/native_sim.overlay new file mode 100644 index 0000000000000..84d15701f757e --- /dev/null +++ b/tests/drivers/dai/virtual_dai_api/boards/native_sim.overlay @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025, Suraj Sonawane + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + virtual_dai: virtual_dai { + compatible = "zephyr,virtual-dai"; + dai-index = <7>; + status = "okay"; + }; +}; diff --git a/tests/drivers/dai/virtual_dai_api/prj.conf b/tests/drivers/dai/virtual_dai_api/prj.conf new file mode 100644 index 0000000000000..d0df91499ad90 --- /dev/null +++ b/tests/drivers/dai/virtual_dai_api/prj.conf @@ -0,0 +1,3 @@ +CONFIG_ZTEST=y +CONFIG_DAI=y +CONFIG_LOG=y diff --git a/tests/drivers/dai/virtual_dai_api/src/test_virtual_dai.c b/tests/drivers/dai/virtual_dai_api/src/test_virtual_dai.c new file mode 100644 index 0000000000000..68f71d7e08335 --- /dev/null +++ b/tests/drivers/dai/virtual_dai_api/src/test_virtual_dai.c @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025 Suraj Sonawane + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(test_virtual_dai, CONFIG_DAI_LOG_LEVEL); + +/* Get the virtual DAI device */ +static const struct device *const virtual_dai_dev = DEVICE_DT_GET(DT_NODELABEL(virtual_dai)); + +ZTEST_SUITE(virtual_dai, NULL, NULL, NULL, NULL, NULL); + +/* Test 1: Verify device exists and is ready */ +ZTEST(virtual_dai, test_device_exists) +{ + zassert_not_null(virtual_dai_dev, "Virtual DAI device should exist"); + zassert_true(device_is_ready(virtual_dai_dev), "Device should be ready"); +} + +/* Test 2: Test dai_config_set using values from dai_config_get */ +ZTEST(virtual_dai, test_dai_config_set_using_retrieved_config) +{ + struct dai_config config; + int ret; + + /* First get the configuration */ + ret = dai_config_get(virtual_dai_dev, &config, 0); + zassert_ok(ret, "dai_config_get should succeed"); + + /* Log the configuration */ + LOG_INF("Config: type=%d, dai_index=%d, rate=%d, channels=%d", + config.type, config.dai_index); + + /* Set the configuration */ + ret = dai_config_set(virtual_dai_dev, &config, NULL); + zassert_ok(ret, "dai_config_set should return success (0)"); +} + +/* Test 3: Test dai_config_set with invalid type (should fail) */ +ZTEST(virtual_dai, test_dai_config_set_invalid_type) +{ + struct dai_config config; + struct dai_config invalid_config; + int ret; + + /* Get the current configuration to see what type is valid */ + ret = dai_config_get(virtual_dai_dev, &config, 0); + zassert_ok(ret, "dai_config_get should succeed"); + + /* Create an invalid configuration (use a different type) */ + invalid_config.type = config.type + 100; /* Invalid type */ + invalid_config.dai_index = config.dai_index; + + /* This should fail with -EINVAL */ + ret = dai_config_set(virtual_dai_dev, &invalid_config, NULL); + zassert_equal(ret, -EINVAL, "dai_config_set should return -EINVAL for invalid type"); +} + +/* Test 4: Test dai_trigger commands */ +ZTEST(virtual_dai, test_dai_trigger_commands) +{ + int ret; + + /* Test START trigger*/ + ret = dai_trigger(virtual_dai_dev, 0, DAI_TRIGGER_START); /* dir = 0 (TX) */ + zassert_ok(ret, "START trigger should return success (0)"); + + /* Test STOP trigger*/ + ret = dai_trigger(virtual_dai_dev, 0, DAI_TRIGGER_STOP); + zassert_ok(ret, "STOP trigger should return success (0)"); + + /* Test PAUSE trigger*/ + ret = dai_trigger(virtual_dai_dev, 0, DAI_TRIGGER_PAUSE); + zassert_ok(ret, "PAUSE trigger should return success (0)"); + + /* Test COPY trigger*/ + ret = dai_trigger(virtual_dai_dev, 0, DAI_TRIGGER_COPY); + zassert_ok(ret, "COPY trigger should return success (0)"); + + /* Test invalid trigger command */ + ret = dai_trigger(virtual_dai_dev, 0, 99); /* Invalid command */ + zassert_equal(ret, -EINVAL, "Should return -EINVAL for invalid trigger"); +} + +/* Test 5: Test dai_get_properties */ +ZTEST(virtual_dai, test_dai_get_properties) +{ + const struct dai_properties *props; + + props = dai_get_properties(virtual_dai_dev, 0, 0); /* dir = 0 (TX), stream_id = 0 */ + zassert_is_null(props, "dai_get_properties should return NULL"); +} + +/* Test 6: Test dai_probe and dai_remove functions */ +ZTEST(virtual_dai, test_probe_remove) +{ + int ret; + + ret = dai_probe(virtual_dai_dev); + zassert_ok(ret, "Probe should succeed"); + + ret = dai_remove(virtual_dai_dev); + zassert_ok(ret, "Remove should succeed"); +} diff --git a/tests/drivers/dai/virtual_dai_api/testcase.yaml b/tests/drivers/dai/virtual_dai_api/testcase.yaml new file mode 100644 index 0000000000000..0c1b9fbdc382c --- /dev/null +++ b/tests/drivers/dai/virtual_dai_api/testcase.yaml @@ -0,0 +1,7 @@ +common: + tags: + - dai + - drivers +tests: + drivers.dai.virtual_dai_api: + depends_on: dai