-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]>
- Loading branch information
1 parent
ff4143a
commit c4e48b6
Showing
6 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
39 changes: 39 additions & 0 deletions
39
tests/drivers/audio/dmic_api/boards/mimxrt595_evk_cm33.overlay
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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>; | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <zephyr/kernel.h> | ||
#include <zephyr/audio/dmic.h> | ||
#include <zephyr/ztest.h> | ||
|
||
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |