From 989cde884ed81a9db9e5ae5edbd2ab0a13f9e8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Stasiak?= Date: Tue, 19 Nov 2024 15:25:41 +0100 Subject: [PATCH] tests: drivers: audio: Add tests for PDM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added tests for PDM that check PDM_CLK output frequency and ensure that dmic API works as expected. Signed-off-by: MichaƂ Stasiak --- CODEOWNERS | 1 + .../drivers/audio/pdm_loopback/CMakeLists.txt | 12 + tests/drivers/audio/pdm_loopback/Kconfig | 36 +++ .../boards/nrf54h20dk_nrf54h20_cpuapp.overlay | 56 ++++ .../boards/nrf54l15dk_nrf54l15_cpuapp.overlay | 38 +++ tests/drivers/audio/pdm_loopback/prj.conf | 13 + tests/drivers/audio/pdm_loopback/src/main.c | 280 ++++++++++++++++++ .../drivers/audio/pdm_loopback/testcase.yaml | 42 +++ 8 files changed, 478 insertions(+) create mode 100644 tests/drivers/audio/pdm_loopback/CMakeLists.txt create mode 100644 tests/drivers/audio/pdm_loopback/Kconfig create mode 100644 tests/drivers/audio/pdm_loopback/boards/nrf54h20dk_nrf54h20_cpuapp.overlay create mode 100644 tests/drivers/audio/pdm_loopback/boards/nrf54l15dk_nrf54l15_cpuapp.overlay create mode 100644 tests/drivers/audio/pdm_loopback/prj.conf create mode 100644 tests/drivers/audio/pdm_loopback/src/main.c create mode 100644 tests/drivers/audio/pdm_loopback/testcase.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 9237b68d5f8f..92ed317d4245 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -786,6 +786,7 @@ /tests/bluetooth/bsim/nrf_auraconfig/ @nrfconnect/ncs-audio /tests/bluetooth/tester/ @carlescufi @nrfconnect/ncs-paladin /tests/crypto/ @stephen-nordic @magnev +/tests/drivers/audio/pdm_loopback/ @nrfconnect/ncs-low-level-test /tests/drivers/gpio/ @nrfconnect/ncs-low-level-test @nrfconnect/ncs-ll-ursus /tests/drivers/flash/flash_rpc/ @nrfconnect/ncs-pluto /tests/drivers/flash_patch/ @nrfconnect/ncs-pluto diff --git a/tests/drivers/audio/pdm_loopback/CMakeLists.txt b/tests/drivers/audio/pdm_loopback/CMakeLists.txt new file mode 100644 index 000000000000..1550200271e9 --- /dev/null +++ b/tests/drivers/audio/pdm_loopback/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(pdm_loopback) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/drivers/audio/pdm_loopback/Kconfig b/tests/drivers/audio/pdm_loopback/Kconfig new file mode 100644 index 000000000000..9679113c6fbb --- /dev/null +++ b/tests/drivers/audio/pdm_loopback/Kconfig @@ -0,0 +1,36 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +source "Kconfig.zephyr" + +config TEST_PDM_SAMPLING_RATE + int "PDM sample rate in Hz" + default 10000 + help + The test will use it to define frequency of PDM sampling and + calculate the size of the buffer. + +config TEST_PDM_EXPECTED_FREQUENCY + int "Expected PDM_CLK frequency in Hz" + default 1000000 + help + The test will use it to confirm that the captured PDM_CLK + frequency in correct. + +config TEST_PDM_SAMPLING_TIME + int "PDM sampling time for one block in ms" + default 100 + help + The test will use it to calculate the size of data block and + determine the period of capturing timer. + +config TEST_USE_DMM + bool "Use of DMM prealocation" + default n + help + The test will use it to determine whether to prealocate DMM + buffer or use regular mem slab and allocate dmm buffer inside + PDM driver. diff --git a/tests/drivers/audio/pdm_loopback/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/tests/drivers/audio/pdm_loopback/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 000000000000..c537da3a96d0 --- /dev/null +++ b/tests/drivers/audio/pdm_loopback/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + gpio_test { + compatible = "gpio-leds"; + pulse_counter: pulse_counter { + gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>; + }; + }; +}; + +&pinctrl { + pdm0_default_alt: pdm0_default_alt { + group1 { + psels = , + ; + }; + }; +}; + +pdm_dev: &pdm0 { + status = "okay"; + pinctrl-0 = <&pdm0_default_alt>; + pinctrl-names = "default"; + clock-source = "PCLK32M"; + memory-regions = <&cpuapp_dma_region>; +}; + +&gpio1 { + status = "okay"; +}; + +&gpiote130 { + owned-channels = <0>; + status = "okay"; +}; + +&timer130 { + status = "okay"; +}; + +&dppic130 { + owned-channels = <0>; + source-channels = <0>; + status = "okay"; +}; + +&dppic133 { + owned-channels = <0>; + sink-channels = <0>; + status = "okay"; +}; diff --git a/tests/drivers/audio/pdm_loopback/boards/nrf54l15dk_nrf54l15_cpuapp.overlay b/tests/drivers/audio/pdm_loopback/boards/nrf54l15dk_nrf54l15_cpuapp.overlay new file mode 100644 index 000000000000..be17f9683ee8 --- /dev/null +++ b/tests/drivers/audio/pdm_loopback/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + gpio_test { + compatible = "gpio-leds"; + pulse_counter: pulse_counter { + gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>; + }; + }; +}; + +&pinctrl { + pdm20_default_alt: pdm20_default_alt { + group1 { + psels = , + ; + }; + }; +}; + +pdm_dev: &pdm20 { + status = "okay"; + pinctrl-0 = <&pdm20_default_alt>; + pinctrl-names = "default"; + clock-source = "PCLK32M"; +}; + +&gpio1 { + status = "okay"; +}; + +&timer00 { + status = "okay"; +}; diff --git a/tests/drivers/audio/pdm_loopback/prj.conf b/tests/drivers/audio/pdm_loopback/prj.conf new file mode 100644 index 000000000000..582dd4a80925 --- /dev/null +++ b/tests/drivers/audio/pdm_loopback/prj.conf @@ -0,0 +1,13 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_ZTEST=y +CONFIG_TEST_USERSPACE=y + +CONFIG_AUDIO=y +CONFIG_AUDIO_DMIC=y +CONFIG_GPIO=y +CONFIG_NRFX_GPPI=y diff --git a/tests/drivers/audio/pdm_loopback/src/main.c b/tests/drivers/audio/pdm_loopback/src/main.c new file mode 100644 index 000000000000..acc37fe6f178 --- /dev/null +++ b/tests/drivers/audio/pdm_loopback/src/main.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_HAS_NORDIC_DMM) +#include +#endif + +#define PDM_SAMPLING_RATE CONFIG_TEST_PDM_SAMPLING_RATE +#define PDM_EXPECTED_FREQ CONFIG_TEST_PDM_EXPECTED_FREQUENCY +#define PDM_SAMPLING_TIME CONFIG_TEST_PDM_SAMPLING_TIME +#define PDM_READ_TIMEOUT 1000 + +#define SAMPLE_BIT_WIDTH 16 +#define BYTES_PER_SAMPLE sizeof(int16_t) + +#define BLOCK_SIZE(_sample_rate, _number_of_channels) \ + (BYTES_PER_SAMPLE * (_sample_rate * PDM_SAMPLING_TIME / 1000) * _number_of_channels) + +#define MAX_BLOCK_SIZE BLOCK_SIZE(PDM_SAMPLING_RATE, 1) +#define BLOCK_COUNT 4 +#define SAMPLING_RATIO PDM_SAMPLING_TIME / 1000 * BLOCK_COUNT / 2 + +#if CONFIG_TEST_USE_DMM +struct k_mem_slab mem_slab; +char __aligned(WB_UP(4)) mem_slab_buffer[BLOCK_COUNT * WB_UP(MAX_BLOCK_SIZE)] + DMM_MEMORY_SECTION(DT_NODELABEL(pdm_dev)); +#else +K_MEM_SLAB_DEFINE_STATIC(mem_slab, MAX_BLOCK_SIZE, BLOCK_COUNT, 4); +#endif + +#define CLOCK_INPUT_PIN NRF_DT_GPIOS_TO_PSEL(DT_NODELABEL(pulse_counter), gpios) + +static const struct device *const pdm_dev = DEVICE_DT_GET(DT_NODELABEL(pdm_dev)); +static const nrfx_gpiote_t gpiote_instance = NRFX_GPIOTE_INSTANCE( + NRF_DT_GPIOTE_INST( + DT_NODELABEL(pulse_counter), gpios)); +static struct pcm_stream_cfg stream_config; +static struct dmic_cfg pdm_cfg; + +#if CONFIG_NRFX_TIMER00 +static const nrfx_timer_t timer_instance = NRFX_TIMER_INSTANCE(00); +#elif CONFIG_NRFX_TIMER130 +static const nrfx_timer_t timer_instance = NRFX_TIMER_INSTANCE(130); +#else +#error "No timer instance found" +#endif + +void timer_handler(nrf_timer_event_t event, void *p_context) +{ + ARG_UNUSED(event); + ARG_UNUSED(p_context); +} + +static void *device_setup(void) +{ + int ret; + + ret = device_is_ready(pdm_dev); + zassert_true(ret, "PDM device is not ready, return code = %d", ret); + +#if CONFIG_TEST_USE_DMM + ret = k_mem_slab_init(&mem_slab, mem_slab_buffer, WB_UP(MAX_BLOCK_SIZE), BLOCK_COUNT); + zassert_true(ret >= 0, "Memory slab initialization failed, return code = %d", ret); +#endif + + return NULL; +} + +static void setup(void *unused) +{ + ARG_UNUSED(unused); + + stream_config.pcm_width = SAMPLE_BIT_WIDTH; + stream_config.pcm_rate = PDM_SAMPLING_RATE; + stream_config.mem_slab = &mem_slab; + stream_config.block_size = BLOCK_SIZE(PDM_SAMPLING_RATE, 1); + + pdm_cfg.io.min_pdm_clk_freq = 1000000; + pdm_cfg.io.max_pdm_clk_freq = 2000000; + pdm_cfg.io.min_pdm_clk_dc = 40; + pdm_cfg.io.max_pdm_clk_dc = 60; + pdm_cfg.streams = &stream_config, + pdm_cfg.channel.req_num_streams = 1; + pdm_cfg.channel.req_num_chan = 1; + pdm_cfg.channel.req_chan_map_lo = dmic_build_channel_map(0, 0, PDM_CHAN_LEFT); +} +static void teardown(void *unused) +{ + ARG_UNUSED(unused); + + int ret; + + ret = dmic_trigger(pdm_dev, DMIC_TRIGGER_STOP); + zassert_true(ret >= 0, "PDM start trigger failed, return code = %d", ret); +} + +static void capture_callback(struct k_timer *timer) +{ + ARG_UNUSED(timer); + nrfx_timer_capture(&timer_instance, NRF_TIMER_CC_CHANNEL0); +} + +K_TIMER_DEFINE(capture_timer, capture_callback, NULL); + +static void pdm_transfer(const struct device *pdm_dev, + struct dmic_cfg *pdm_cfg, + size_t block_count) +{ + int ret; + + ret = dmic_configure(pdm_dev, pdm_cfg); + zassert_true(ret >= 0, "PDM configuration failed, return code = %d", ret); + + ret = dmic_trigger(pdm_dev, DMIC_TRIGGER_START); + zassert_true(ret >= 0, "PDM start trigger failed, return code = %d", ret); + + /* Count clock cycles during 50% of transfer time. */ + k_timer_start(&capture_timer, K_MSEC(BLOCK_COUNT / 2 * PDM_SAMPLING_TIME), K_NO_WAIT); + + void *buffer; + uint32_t size; + + ret = dmic_read(pdm_dev, 0, &buffer, &size, PDM_READ_TIMEOUT); + zassert_true(ret >= 0, "PDM read failed, return code = %d", ret); + + k_mem_slab_free(&mem_slab, buffer); + + k_msleep(BLOCK_COUNT / 2 * PDM_SAMPLING_TIME); + + ret = dmic_trigger(pdm_dev, DMIC_TRIGGER_STOP); + zassert_true(ret >= 0, "PDM stop trigger failed, return code = %d", ret); +} + +ZTEST(pdm_loopback, test_pdm_configure) +{ + int ret; + + /* Not enough channels. */ + pdm_cfg.channel.req_num_chan = 0; + ret = dmic_configure(pdm_dev, &pdm_cfg); + zassert_true(ret == -EINVAL, "PDM configuration should fail, return code = %d", ret); + + /* Too many channels. */ + pdm_cfg.channel.req_num_chan = 3; + ret = dmic_configure(pdm_dev, &pdm_cfg); + zassert_true(ret == -EINVAL, "PDM configuration should fail, return code = %d", ret); + + /* Streams not equal to 1. */ + pdm_cfg.channel.req_num_chan = 1; + pdm_cfg.channel.req_num_streams = 0; + ret = dmic_configure(pdm_dev, &pdm_cfg); + zassert_true(ret == -EINVAL, "PDM configuration should fail, return code = %d", ret); + + /* Invalid sample bit width. */ + pdm_cfg.channel.req_num_streams = 1; + pdm_cfg.streams->pcm_width = 32; + ret = dmic_configure(pdm_dev, &pdm_cfg); + zassert_true(ret == -EINVAL, "PDM configuration should fail, return code = %d", ret); + + /* Invalid sample rate, unable to provide suitable clock. */ + pdm_cfg.streams->pcm_width = SAMPLE_BIT_WIDTH; + pdm_cfg.streams->pcm_rate = 100; + ret = dmic_configure(pdm_dev, &pdm_cfg); + zassert_true(ret == -EINVAL, "PDM configuration should fail, return code = %d", ret); + + /* Proper configuration. */ + pdm_cfg.streams->pcm_rate = PDM_SAMPLING_RATE; + ret = dmic_configure(pdm_dev, &pdm_cfg); + zassert_true(ret >= 0, "PDM configuration failed, return code = %d", ret); + + ret = dmic_trigger(pdm_dev, DMIC_TRIGGER_START); + zassert_true(ret >= 0, "PDM start trigger failed, return code = %d", ret); + + /* Device is running, unable to configure. */ + pdm_cfg.streams->pcm_rate = PDM_SAMPLING_RATE; + ret = dmic_configure(pdm_dev, &pdm_cfg); + zassert_true(ret == -EBUSY, "PDM configuration should fail, return code = %d", ret); +} + +ZTEST(pdm_loopback, test_start_trigger) +{ + int ret; + + /* Disable device. */ + pdm_cfg.streams->pcm_rate = 0; + ret = dmic_configure(pdm_dev, &pdm_cfg); + zassert_true(ret >= 0, "PDM configuration failed, return code = %d", ret); + + /* Device not configured. */ + ret = dmic_trigger(pdm_dev, DMIC_TRIGGER_START); + zassert_true(ret == -EIO, "PDM start trigger should fail, return code = %d", ret); + + pdm_cfg.streams->pcm_rate = PDM_SAMPLING_RATE; + ret = dmic_configure(pdm_dev, &pdm_cfg); + zassert_true(ret >= 0, "PDM configuration failed, return code = %d", ret); + + /* Proper start. */ + ret = dmic_trigger(pdm_dev, DMIC_TRIGGER_START); + zassert_true(ret >= 0, "PDM start trigger failed, return code = %d", ret); + + /* Multiple start triggers should return no error. */ + ret = dmic_trigger(pdm_dev, DMIC_TRIGGER_START); + zassert_true(ret >= 0, "PDM start trigger failed, return code = %d", ret); +} + +ZTEST(pdm_loopback, test_pdm_clk_frequency) +{ + int ret; + + uint8_t gpiote_channel; + + ret = nrfx_gpiote_channel_alloc(&gpiote_instance, &gpiote_channel); + zassert_true(ret == NRFX_SUCCESS, + "GPIOTE channel allocation failed, return code = 0x%08X", ret); + + nrfx_gpiote_trigger_config_t trigger_cfg = { + .p_in_channel = &gpiote_channel, + .trigger = NRFX_GPIOTE_TRIGGER_LOTOHI, + }; + + nrf_gpio_pin_pull_t pull_cfg = NRFX_GPIOTE_DEFAULT_PULL_CONFIG; + + nrfx_gpiote_input_pin_config_t gpiote_cfg = { + .p_pull_config = &pull_cfg, + .p_trigger_config = &trigger_cfg, + }; + + ret = nrfx_gpiote_input_configure(&gpiote_instance, CLOCK_INPUT_PIN, &gpiote_cfg); + zassert_true(ret == NRFX_SUCCESS, + "GPIOTE input configuration failed, return code = 0x%08X", ret); + + nrfx_gpiote_trigger_enable(&gpiote_instance, CLOCK_INPUT_PIN, false); + + nrfx_timer_config_t timer_config = NRFX_TIMER_DEFAULT_CONFIG( + NRFX_TIMER_BASE_FREQUENCY_GET(&timer_instance)); + timer_config.bit_width = NRF_TIMER_BIT_WIDTH_32; + timer_config.mode = NRF_TIMER_MODE_COUNTER; + + ret = nrfx_timer_init(&timer_instance, &timer_config, timer_handler); + zassert_true(ret == NRFX_SUCCESS, + "TIMER initialization failed, return code = 0x%08X", ret); + + nrfx_timer_enable(&timer_instance); + + uint8_t gppi_channel; + + ret = nrfx_gppi_channel_alloc(&gppi_channel); + + zassert_true(ret == NRFX_SUCCESS, + "GPPI channel allocation failed, return code = 0x%08X", ret); + nrfx_gppi_channel_endpoints_setup(gppi_channel, + nrfx_gpiote_in_event_address_get(&gpiote_instance, + CLOCK_INPUT_PIN), + nrfx_timer_task_address_get(&timer_instance, + NRF_TIMER_TASK_COUNT)); + nrfx_gppi_channels_enable(BIT(gppi_channel)); + + pdm_transfer(pdm_dev, &pdm_cfg, BLOCK_COUNT); + + uint32_t pulses = nrfx_timer_capture_get(&timer_instance, NRF_TIMER_CC_CHANNEL0); + + /* Assert that captured frequency is within 1% margin of expected one. */ + zassert_within(pulses, PDM_EXPECTED_FREQ * SAMPLING_RATIO, + PDM_EXPECTED_FREQ * SAMPLING_RATIO / 100, + "Captured incorrect frequency Hz. Captured pulses = %lu, expected = %lu", + pulses, PDM_EXPECTED_FREQ * SAMPLING_RATIO); +} + +ZTEST_SUITE(pdm_loopback, NULL, device_setup, setup, teardown, NULL); diff --git a/tests/drivers/audio/pdm_loopback/testcase.yaml b/tests/drivers/audio/pdm_loopback/testcase.yaml new file mode 100644 index 000000000000..ad4bb669c2cc --- /dev/null +++ b/tests/drivers/audio/pdm_loopback/testcase.yaml @@ -0,0 +1,42 @@ +tests: + drivers.audio.pdm_loopback.nrf54l15.1000khz: + platform_allow: + - nrf54l15dk/nrf54l15/cpuapp + extra_args: + CONFIG_NRFX_TIMER00=y + drivers.audio.pdm_loopback.nrf54l15.1280khz: + platform_allow: + - nrf54l15dk/nrf54l15/cpuapp + extra_args: + CONFIG_NRFX_TIMER00=y + CONFIG_TEST_PDM_SAMPLING_RATE=16000 + CONFIG_TEST_PDM_EXPECTED_FREQUENCY=1280000 + drivers.audio.pdm_loopback.nrf54l15.1600khz: + platform_allow: + - nrf54l15dk/nrf54l15/cpuapp + extra_args: + CONFIG_NRFX_TIMER00=y + CONFIG_TEST_PDM_SAMPLING_RATE=32000 + CONFIG_TEST_PDM_EXPECTED_FREQUENCY=1600000 + drivers.audio.pdm_loopback.nrf54h20.1000khz: + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + extra_args: + CONFIG_NRFX_TIMER130=y + CONFIG_TEST_PDM_SAMPLING_TIME=10 + CONFIG_TEST_USE_DMM=y + drivers.audio.pdm_loopback.nrf54h20.1600khz: + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + extra_args: + CONFIG_NRFX_TIMER130=y + CONFIG_TEST_PDM_SAMPLING_RATE=16000 + CONFIG_TEST_PDM_EXPECTED_FREQUENCY=1600000 + CONFIG_TEST_PDM_SAMPLING_TIME=10 + CONFIG_TEST_USE_DMM=y + drivers.audio.pdm_loopback.nrf54h20.no_dmm: + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + extra_args: + CONFIG_NRFX_TIMER130=y + CONFIG_TEST_PDM_SAMPLING_TIME=10