diff --git a/CODEOWNERS b/CODEOWNERS index c86648cfa9d2..532c94411dfa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -717,6 +717,7 @@ /tests/bluetooth/iso/ @nrfconnect/ncs-audio @Frodevan /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/egpio_basic_api/ @nrfconnect/ncs-ll-ursus /tests/drivers/gpio/gpio_more_loops/ @nrfconnect/ncs-low-level-test /tests/drivers/flash/flash_rpc/ @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..60c86d093051 --- /dev/null +++ b/tests/drivers/audio/pdm_loopback/Kconfig @@ -0,0 +1,31 @@ +# +# 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 NRFX_DPPI + default $(dt_has_compat,$(DT_COMPAT_NORDIC_NRF_DPPIC)) 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..2686406e4bbd --- /dev/null +++ b/tests/drivers/audio/pdm_loopback/prj.conf @@ -0,0 +1,12 @@ +# +# 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 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..baa07c7b45e7 --- /dev/null +++ b/tests/drivers/audio/pdm_loopback/src/main.c @@ -0,0 +1,275 @@ +/* + * 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 defined(CONFIG_HAS_NORDIC_DMM) +static char __aligned(WB_UP(4)) _k_mem_slab_buf_mem_slab[(BLOCK_COUNT) * WB_UP(MAX_BLOCK_SIZE)] + DMM_MEMORY_SECTION(DT_NODELABEL(pdm_dev)); +static STRUCT_SECTION_ITERABLE(k_mem_slab, mem_slab) = Z_MEM_SLAB_INITIALIZER(mem_slab, + _k_mem_slab_buf_mem_slab, WB_UP(MAX_BLOCK_SIZE), BLOCK_COUNT); +#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); + + 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); + + for (int i = 0; i < block_count; ++i) { + 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); + } + + 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"); +} + +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..62d2fd24ede2 --- /dev/null +++ b/tests/drivers/audio/pdm_loopback/testcase.yaml @@ -0,0 +1,34 @@ +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 + 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 diff --git a/west.yml b/west.yml index 3ada816e93fa..56f29f478ec5 100644 --- a/west.yml +++ b/west.yml @@ -69,7 +69,7 @@ manifest: # https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/guides/modules.html - name: zephyr repo-path: sdk-zephyr - revision: 4f3ef0aa00b54dc19da82629c3402b2af149dfb5 + revision: pull/2280/head import: # In addition to the zephyr repository itself, NCS also # imports the contents of zephyr/west.yml at the above