From c4e48b66bd3643941712b3cfe4d64a078956460f Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 5 Dec 2023 22:37:10 +0000 Subject: [PATCH] tests: drivers: audio: add dmic API test Add DMIC API test, intended to verify DMIC drivers are functioning correctly within the DMIC API. The test verifies the following: - Mono channel audio - Stereo channel audio - Using maximum number of channels supported by DMIC IP - Pausing/restarting channels - Checks to make sure invalid channel maps are rejected by the DMIC. Signed-off-by: Daniel DeGrasse --- tests/drivers/audio/dmic_api/CMakeLists.txt | 8 + tests/drivers/audio/dmic_api/README.txt | 19 ++ .../boards/mimxrt595_evk_cm33.overlay | 39 +++ tests/drivers/audio/dmic_api/prj.conf | 9 + tests/drivers/audio/dmic_api/src/main.c | 291 ++++++++++++++++++ tests/drivers/audio/dmic_api/testcase.yaml | 8 + 6 files changed, 374 insertions(+) create mode 100644 tests/drivers/audio/dmic_api/CMakeLists.txt create mode 100644 tests/drivers/audio/dmic_api/README.txt create mode 100644 tests/drivers/audio/dmic_api/boards/mimxrt595_evk_cm33.overlay create mode 100644 tests/drivers/audio/dmic_api/prj.conf create mode 100644 tests/drivers/audio/dmic_api/src/main.c create mode 100644 tests/drivers/audio/dmic_api/testcase.yaml diff --git a/tests/drivers/audio/dmic_api/CMakeLists.txt b/tests/drivers/audio/dmic_api/CMakeLists.txt new file mode 100644 index 00000000000000..7d66184eab4c33 --- /dev/null +++ b/tests/drivers/audio/dmic_api/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(dmic_api) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/audio/dmic_api/README.txt b/tests/drivers/audio/dmic_api/README.txt new file mode 100644 index 00000000000000..66df40b33c56eb --- /dev/null +++ b/tests/drivers/audio/dmic_api/README.txt @@ -0,0 +1,19 @@ +DMIC API Test +################## + +This test is designed to verify that DMIC peripherals implement the API +correctly. It performs the following checks: + +* Verify the DMIC will not start sampling before it is configured + +* Verify the DMIC can sample from one left channel + +* Verify the DMIC can sample from a stereo L/R pair + +* Verify that the DMIC works with the maximum number of channels possible + (defined based on the DTS compatible present in the build) + +* Verify that the DMIC can restart sampling after being paused and resumed + +* Verify that invalid channel maps (R/R pair, non-adjacent channels) are + rejected by the DMIC driver. diff --git a/tests/drivers/audio/dmic_api/boards/mimxrt595_evk_cm33.overlay b/tests/drivers/audio/dmic_api/boards/mimxrt595_evk_cm33.overlay new file mode 100644 index 00000000000000..96cb58b52c1a0a --- /dev/null +++ b/tests/drivers/audio/dmic_api/boards/mimxrt595_evk_cm33.overlay @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2023 NXP + */ + +/* Enable PDM channels 0-3, + * Gain settings are configured for testing with a PDM generator, + * using a -20dbFS sine wave. + */ +&dmic0 { + dmic-channel@0 { + status = "okay"; + gainshift = <3>; + dc-cutoff = "155hz"; + dc-gain = <1>; + }; + + dmic-channel@1 { + status = "okay"; + gainshift = <3>; + dc-cutoff = "155hz"; + dc-gain = <1>; + }; + + dmic-channel@2 { + status = "okay"; + gainshift = <3>; + dc-cutoff = "155hz"; + dc-gain = <1>; + }; + + dmic-channel@3 { + status = "okay"; + gainshift = <3>; + dc-cutoff = "155hz"; + dc-gain = <1>; + }; +}; diff --git a/tests/drivers/audio/dmic_api/prj.conf b/tests/drivers/audio/dmic_api/prj.conf new file mode 100644 index 00000000000000..2b2ac5a5f9bc1e --- /dev/null +++ b/tests/drivers/audio/dmic_api/prj.conf @@ -0,0 +1,9 @@ +CONFIG_TEST=y +CONFIG_ZTEST=y +CONFIG_AUDIO=y +CONFIG_AUDIO_DMIC=y + +# Use deferred logging mode so the TC_PRINT calls while reading from DMIC +# do not block +CONFIG_LOG=y +CONFIG_LOG_MODE_DEFERRED=y diff --git a/tests/drivers/audio/dmic_api/src/main.c b/tests/drivers/audio/dmic_api/src/main.c new file mode 100644 index 00000000000000..488490e219ce5e --- /dev/null +++ b/tests/drivers/audio/dmic_api/src/main.c @@ -0,0 +1,291 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Based on DMIC driver sample, which is: + * Copyright (c) 2021 Nordic Semiconductor ASA + */ + +#include +#include +#include + +static const struct device *dmic_dev = DEVICE_DT_GET(DT_ALIAS(dmic_dev)); + +#if DT_HAS_COMPAT_STATUS_OKAY(nxp_dmic) +#define PDM_CHANNELS 4 /* Two L/R pairs of channels */ +#define SAMPLE_BIT_WIDTH 16 +#define BYTES_PER_SAMPLE sizeof(int16_t) +#define SLAB_ALIGN 4 +#define MAX_SAMPLE_RATE 48000 +/* Milliseconds to wait for a block to be read. */ +#define READ_TIMEOUT 1000 +/* Size of a block for 100 ms of audio data. */ +#define BLOCK_SIZE(_sample_rate, _number_of_channels) \ + (BYTES_PER_SAMPLE * (_sample_rate / 10) * _number_of_channels) +#else +#error "Unsupported DMIC device" +#endif + +/* Driver will allocate blocks from this slab to receive audio data into them. + * Application, after getting a given block from the driver and processing its + * data, needs to free that block. + */ +#define MAX_BLOCK_SIZE BLOCK_SIZE(MAX_SAMPLE_RATE, PDM_CHANNELS) +#define BLOCK_COUNT 8 +K_MEM_SLAB_DEFINE_STATIC(mem_slab, MAX_BLOCK_SIZE, BLOCK_COUNT, SLAB_ALIGN); + +static struct pcm_stream_cfg pcm_stream = { + .pcm_width = SAMPLE_BIT_WIDTH, + .mem_slab = &mem_slab, +}; +static struct dmic_cfg dmic_cfg = { + .io = { + /* These fields can be used to limit the PDM clock + * configurations that the driver is allowed to use + * to those supported by the microphone. + */ + .min_pdm_clk_freq = 1000000, + .max_pdm_clk_freq = 3500000, + .min_pdm_clk_dc = 40, + .max_pdm_clk_dc = 60, + }, + .streams = &pcm_stream, + .channel = { + .req_num_streams = 1, + }, +}; + +/* Verify that dmic_trigger fails when DMIC is not configured + * this test must run first, before DMIC has been configured + */ +ZTEST(dmic, test_0_start_fail) +{ + int ret; + + zassert_true(device_is_ready(dmic_dev), "DMIC device is not ready"); + ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_START); + zassert_not_equal(ret, 0, "DMIC trigger should fail when DMIC is not configured"); +} + +static int do_pdm_transfer(const struct device *dmic, + struct dmic_cfg *cfg) +{ + int ret; + void *buffer; + uint32_t size; + + TC_PRINT("PCM output rate: %u, channels: %u\n", + cfg->streams[0].pcm_rate, cfg->channel.req_num_chan); + + ret = dmic_configure(dmic, cfg); + if (ret < 0) { + TC_PRINT("DMIC configuration failed: %d\n", ret); + return ret; + } + + /* Check that the driver is properly populating the "act*" fields */ + zassert_equal(cfg->channel.act_num_chan, + cfg->channel.req_num_chan, + "DMIC configure should populate act_num_chan field"); + zassert_equal(cfg->channel.act_chan_map_lo, + cfg->channel.req_chan_map_lo, + "DMIC configure should populate act_chan_map_lo field"); + zassert_equal(cfg->channel.act_chan_map_hi, + cfg->channel.req_chan_map_hi, + "DMIC configure should populate act_chan_map_hi field"); + ret = dmic_trigger(dmic, DMIC_TRIGGER_START); + if (ret < 0) { + TC_PRINT("DMIC start trigger failed: %d\n", ret); + return ret; + } + + /* We read more than the total BLOCK_COUNT to insure the DMIC + * driver correctly reallocates memory slabs as it exhausts existing + * ones. + */ + for (int i = 0; i < (2 * BLOCK_COUNT); i++) { + ret = dmic_read(dmic, 0, &buffer, &size, READ_TIMEOUT); + if (ret < 0) { + TC_PRINT("DMIC read failed: %d\n", ret); + return ret; + } + + TC_PRINT("%d - got buffer %p of %u bytes\n", i, buffer, size); + k_mem_slab_free(&mem_slab, buffer); + } + + ret = dmic_trigger(dmic, DMIC_TRIGGER_STOP); + if (ret < 0) { + TC_PRINT("DMIC stop trigger failed: %d\n", ret); + return ret; + } + return 0; +} + + +/* Verify that the DMIC can transfer from a single channel */ +ZTEST(dmic, test_single_channel) +{ + dmic_cfg.channel.req_num_chan = 1; + dmic_cfg.channel.req_chan_map_lo = + dmic_build_channel_map(0, 0, PDM_CHAN_LEFT); + dmic_cfg.streams[0].pcm_rate = MAX_SAMPLE_RATE; + dmic_cfg.streams[0].block_size = + BLOCK_SIZE(dmic_cfg.streams[0].pcm_rate, + dmic_cfg.channel.req_num_chan); + zassert_equal(do_pdm_transfer(dmic_dev, &dmic_cfg), 0, + "Single channel transfer failed"); +} + +/* Verify that the DMIC can transfer from a L/R channel pair */ +ZTEST(dmic, test_stereo_channel) +{ + dmic_cfg.channel.req_num_chan = 2; + dmic_cfg.channel.req_chan_map_lo = + dmic_build_channel_map(0, 0, PDM_CHAN_LEFT) | + dmic_build_channel_map(1, 0, PDM_CHAN_RIGHT); + dmic_cfg.streams[0].pcm_rate = MAX_SAMPLE_RATE; + dmic_cfg.streams[0].block_size = + BLOCK_SIZE(dmic_cfg.streams[0].pcm_rate, + dmic_cfg.channel.req_num_chan); + zassert_equal(do_pdm_transfer(dmic_dev, &dmic_cfg), 0, + "L/R channel transfer failed"); + dmic_cfg.channel.req_chan_map_lo = + dmic_build_channel_map(0, 0, PDM_CHAN_RIGHT) | + dmic_build_channel_map(1, 0, PDM_CHAN_LEFT); + zassert_equal(do_pdm_transfer(dmic_dev, &dmic_cfg), 0, + "R/L channel transfer failed"); +} + +/* Test DMIC with maximum number of channels */ +ZTEST(dmic, test_max_channel) +{ + enum pdm_lr lr; + uint8_t pdm_hw_chan; + + dmic_cfg.channel.req_num_chan = PDM_CHANNELS; + dmic_cfg.channel.req_chan_map_lo = 0; + dmic_cfg.channel.req_chan_map_hi = 0; + for (uint8_t i = 0; i < PDM_CHANNELS; i++) { + lr = ((i % 2) == 0) ? PDM_CHAN_LEFT : PDM_CHAN_RIGHT; + pdm_hw_chan = i >> 1; + if (i < 4) { + dmic_cfg.channel.req_chan_map_lo |= + dmic_build_channel_map(i, pdm_hw_chan, lr); + } else { + dmic_cfg.channel.req_chan_map_hi |= + dmic_build_channel_map(i, pdm_hw_chan, lr); + } + } + + dmic_cfg.streams[0].pcm_rate = MAX_SAMPLE_RATE; + dmic_cfg.streams[0].block_size = + BLOCK_SIZE(dmic_cfg.streams[0].pcm_rate, + dmic_cfg.channel.req_num_chan); + zassert_equal(do_pdm_transfer(dmic_dev, &dmic_cfg), 0, + "Maximum channel transfer failed"); +} + +/* Test pausing and restarting a channel */ +ZTEST(dmic, test_pause_restart) +{ + int ret, i; + void *buffer; + uint32_t size; + + dmic_cfg.channel.req_num_chan = 1; + dmic_cfg.channel.req_chan_map_lo = + dmic_build_channel_map(0, 0, PDM_CHAN_LEFT); + dmic_cfg.streams[0].pcm_rate = MAX_SAMPLE_RATE; + dmic_cfg.streams[0].block_size = + BLOCK_SIZE(dmic_cfg.streams[0].pcm_rate, + dmic_cfg.channel.req_num_chan); + ret = dmic_configure(dmic_dev, &dmic_cfg); + zassert_equal(ret, 0, "DMIC configure failed"); + + /* Start the DMIC, and pause it immediately */ + ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_START); + zassert_equal(ret, 0, "DMIC start failed"); + ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_PAUSE); + zassert_equal(ret, 0, "DMIC pause failed"); + /* There may be some buffers in the DMIC queue, but a read + * should eventually time out while it is paused + */ + for (i = 0; i < (2 * BLOCK_COUNT); i++) { + ret = dmic_read(dmic_dev, 0, &buffer, &size, READ_TIMEOUT); + if (ret < 0) { + break; + } + k_mem_slab_free(&mem_slab, buffer); + } + zassert_not_equal(ret, 0, "DMIC is paused, reads should timeout"); + TC_PRINT("Queue drained after %d reads\n", i); + /* Unpause the DMIC */ + ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_RELEASE); + zassert_equal(ret, 0, "DMIC release failed"); + /* Reads should not timeout now */ + for (i = 0; i < (2 * BLOCK_COUNT); i++) { + ret = dmic_read(dmic_dev, 0, &buffer, &size, READ_TIMEOUT); + if (ret < 0) { + break; + } + k_mem_slab_free(&mem_slab, buffer); + } + zassert_equal(ret, 0, "DMIC is active, reads should succeed"); + TC_PRINT("%d reads completed\n", (2 * BLOCK_COUNT)); + /* Stop the DMIC, and repeat the same tests */ + ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_STOP); + zassert_equal(ret, 0, "DMIC stop failed"); + /* Versus a pause, DMIC reads should immediately stop once DMIC times + * out + */ + ret = dmic_read(dmic_dev, 0, &buffer, &size, READ_TIMEOUT); + zassert_not_equal(ret, 0, "DMIC read should timeout when DMIC is stopped"); + ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_START); + zassert_equal(ret, 0, "DMIC restart failed"); + /* Reads should not timeout now */ + for (i = 0; i < (2 * BLOCK_COUNT); i++) { + ret = dmic_read(dmic_dev, 0, &buffer, &size, READ_TIMEOUT); + if (ret < 0) { + break; + } + k_mem_slab_free(&mem_slab, buffer); + } + zassert_equal(ret, 0, "DMIC is active, reads should succeed"); + TC_PRINT("%d reads completed\n", (2 * BLOCK_COUNT)); + /* Test is over. Stop the DMIC */ + ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_STOP); + zassert_equal(ret, 0, "DMIC stop failed"); +} + +/* Verify that channel map without adjacent L/R pairs fails */ +ZTEST(dmic, test_bad_pair) +{ + int ret; + + dmic_cfg.channel.req_num_chan = 2; + dmic_cfg.channel.req_chan_map_lo = + dmic_build_channel_map(0, 0, PDM_CHAN_RIGHT) | + dmic_build_channel_map(1, 0, PDM_CHAN_RIGHT); + dmic_cfg.streams[0].pcm_rate = MAX_SAMPLE_RATE; + dmic_cfg.streams[0].block_size = + BLOCK_SIZE(dmic_cfg.streams[0].pcm_rate, + dmic_cfg.channel.req_num_chan); + ret = dmic_configure(dmic_dev, &dmic_cfg); + zassert_not_equal(ret, 0, "DMIC configure should fail with " + "two of same channel in map"); + + dmic_cfg.channel.req_num_chan = 2; + dmic_cfg.channel.req_chan_map_lo = + dmic_build_channel_map(0, 0, PDM_CHAN_RIGHT) | + dmic_build_channel_map(1, 1, PDM_CHAN_LEFT); + ret = dmic_configure(dmic_dev, &dmic_cfg); + zassert_not_equal(ret, 0, "DMIC configure should fail with " + "non adjacent channels in map"); +} + +ZTEST_SUITE(dmic, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/drivers/audio/dmic_api/testcase.yaml b/tests/drivers/audio/dmic_api/testcase.yaml new file mode 100644 index 00000000000000..960aad3323a809 --- /dev/null +++ b/tests/drivers/audio/dmic_api/testcase.yaml @@ -0,0 +1,8 @@ +tests: + drivers.audio.dmic_api: + depends_on: dmic + tags: dmic + harness: ztest + filter: dt_alias_exists("dmic-dev") + integration_platforms: + - mimxrt595_evk_cm33