diff --git a/samples/drivers/i2s/i2s_codec/CMakeLists.txt b/samples/drivers/i2s/i2s_codec/CMakeLists.txt new file mode 100644 index 00000000000..538833859a4 --- /dev/null +++ b/samples/drivers/i2s/i2s_codec/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(i2s_rx_tx) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/drivers/i2s/i2s_codec/Kconfig b/samples/drivers/i2s/i2s_codec/Kconfig new file mode 100644 index 00000000000..0396a87eb8b --- /dev/null +++ b/samples/drivers/i2s/i2s_codec/Kconfig @@ -0,0 +1,31 @@ +# Copyright (c) 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +source "Kconfig.zephyr" + +config I2S_INIT_BUFFERS + int "Initial count of audio data blocks" + default 2 + help + Controls the initial count of audio data blocks, which are (optionally) + filled by data from the DMIC peripheral and played back by the I2S + output perihperal. + +config SAMPLE_FREQ + int "Sample rate" + default 48000 + help + Sample frequency of the system. + +config USE_DMIC + bool "Use DMIC as an audio input" + +if USE_DMIC + +config DMIC_CHANNELS + int "Number of DMIC channels" + default 1 + help + Count of DMIC channels to capture and process. + +endif diff --git a/samples/drivers/i2s/i2s_codec/README.rst b/samples/drivers/i2s/i2s_codec/README.rst new file mode 100644 index 00000000000..d8fefa25867 --- /dev/null +++ b/samples/drivers/i2s/i2s_codec/README.rst @@ -0,0 +1,33 @@ +.. zephyr:code-sample:: i2s_codec + :name: I2S codec + :relevant-api: i2s_interface + + Process an audio stream to codec. + +Overview +******** + +This sample demonstrates how to use an I2S driver in a simple processing of +an audio stream. It configures and starts from memory buffer or from DMIC to +record i2s data and send to codec with DMA. + +Requirements +************ + +This sample has been tested on mimxrt595_evk/mimxrt595s/cm33 + +Building and Running +******************** + +The code can be found in :zephyr_file:`samples/drivers/i2s/i2s_codec`. + +To build and flash the application: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/i2s/i2s_codec + :board: mimxrt595_evk/mimxrt595s/cm33 + :goals: build flash + :compact: + +To run you can connect earphones to the lineout connect and hear the sound +from DMIC or from memory buffer. diff --git a/samples/drivers/i2s/i2s_codec/boards/mimxrt595_evk_mimxrt595s_cm33.conf b/samples/drivers/i2s/i2s_codec/boards/mimxrt595_evk_mimxrt595s_cm33.conf new file mode 100644 index 00000000000..7af9fce92a4 --- /dev/null +++ b/samples/drivers/i2s/i2s_codec/boards/mimxrt595_evk_mimxrt595s_cm33.conf @@ -0,0 +1,16 @@ +# +# Copyright 2024 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_DMA=y +CONFIG_I2S_MCUX_FLEXCOMM=y +CONFIG_I3C=y +CONFIG_HEAP_MEM_POOL_SIZE=81920 +CONFIG_AUDIO=y +CONFIG_AUDIO_CODEC=y +CONFIG_AUDIO_CODEC_WM8904=y +CONFIG_I2S_INIT_BUFFERS=4 +CONFIG_SAMPLE_FREQ=16000 +CONFIG_USE_DMIC=y diff --git a/samples/drivers/i2s/i2s_codec/boards/mimxrt595_evk_mimxrt595s_cm33.overlay b/samples/drivers/i2s/i2s_codec/boards/mimxrt595_evk_mimxrt595s_cm33.overlay new file mode 100644 index 00000000000..022fff64aaa --- /dev/null +++ b/samples/drivers/i2s/i2s_codec/boards/mimxrt595_evk_mimxrt595s_cm33.overlay @@ -0,0 +1,24 @@ +/ { + aliases { + i2s-codec-rx = &i2s0; + i2s-codec-tx = &i2s1; + }; +}; + +&i3c0 { + clk-divider = <20>; + + status = "okay"; +}; + +&i2s0 { + status = "okay"; +}; + +&i2s1 { + status = "okay"; +}; + +dmic_dev: &dmic0 { + status = "okay"; +}; diff --git a/samples/drivers/i2s/i2s_codec/boards/mimxrt685_evk_mimxrt685s_cm33.conf b/samples/drivers/i2s/i2s_codec/boards/mimxrt685_evk_mimxrt685s_cm33.conf new file mode 100644 index 00000000000..7af9fce92a4 --- /dev/null +++ b/samples/drivers/i2s/i2s_codec/boards/mimxrt685_evk_mimxrt685s_cm33.conf @@ -0,0 +1,16 @@ +# +# Copyright 2024 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_DMA=y +CONFIG_I2S_MCUX_FLEXCOMM=y +CONFIG_I3C=y +CONFIG_HEAP_MEM_POOL_SIZE=81920 +CONFIG_AUDIO=y +CONFIG_AUDIO_CODEC=y +CONFIG_AUDIO_CODEC_WM8904=y +CONFIG_I2S_INIT_BUFFERS=4 +CONFIG_SAMPLE_FREQ=16000 +CONFIG_USE_DMIC=y diff --git a/samples/drivers/i2s/i2s_codec/boards/mimxrt685_evk_mimxrt685s_cm33.overlay b/samples/drivers/i2s/i2s_codec/boards/mimxrt685_evk_mimxrt685s_cm33.overlay new file mode 100644 index 00000000000..46ca7223cfd --- /dev/null +++ b/samples/drivers/i2s/i2s_codec/boards/mimxrt685_evk_mimxrt685s_cm33.overlay @@ -0,0 +1,22 @@ +/ { + aliases { + i2s-codec-rx = &i2s0; + i2s-codec-tx = &i2s1; + }; +}; + +&i3c0 { + status = "okay"; +}; + +&i2s0 { + status = "okay"; +}; + +&i2s1 { + status = "okay"; +}; + +dmic_dev: &dmic0 { + status = "okay"; +}; diff --git a/samples/drivers/i2s/i2s_codec/prj.conf b/samples/drivers/i2s/i2s_codec/prj.conf new file mode 100644 index 00000000000..683caf4f587 --- /dev/null +++ b/samples/drivers/i2s/i2s_codec/prj.conf @@ -0,0 +1,3 @@ +CONFIG_I2S=y +CONFIG_AUDIO=y +CONFIG_AUDIO_DMIC=y diff --git a/samples/drivers/i2s/i2s_codec/sample.yaml b/samples/drivers/i2s/i2s_codec/sample.yaml new file mode 100644 index 00000000000..55926498e19 --- /dev/null +++ b/samples/drivers/i2s/i2s_codec/sample.yaml @@ -0,0 +1,12 @@ +sample: + name: codec sample +tests: + sample.drivers.i2s.codec: + tags: i2s + platform_allow: + - mimxrt595_evk/mimxrt595s/cm33 + harness: console + harness_config: + type: one_line + regex: + - "start streams" diff --git a/samples/drivers/i2s/i2s_codec/src/main.c b/samples/drivers/i2s/i2s_codec/src/main.c new file mode 100644 index 00000000000..734d9a9f27b --- /dev/null +++ b/samples/drivers/i2s/i2s_codec/src/main.c @@ -0,0 +1,219 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + + +#define I2S_CODEC_TX DT_ALIAS(i2s_codec_tx) + +#define SAMPLE_FREQUENCY CONFIG_SAMPLE_FREQ +#define SAMPLE_BIT_WIDTH (16U) +#define BYTES_PER_SAMPLE sizeof(int16_t) +#if CONFIG_USE_DMIC +#define NUMBER_OF_CHANNELS CONFIG_DMIC_CHANNELS +#else +#define NUMBER_OF_CHANNELS (2U) +#endif +/* Such block length provides an echo with the delay of 100 ms. */ +#define SAMPLES_PER_BLOCK ((SAMPLE_FREQUENCY / 10) * NUMBER_OF_CHANNELS) +#define INITIAL_BLOCKS CONFIG_I2S_INIT_BUFFERS +#define TIMEOUT (2000U) + +#define BLOCK_SIZE (BYTES_PER_SAMPLE * SAMPLES_PER_BLOCK) +#define BLOCK_COUNT (INITIAL_BLOCKS + 32) +K_MEM_SLAB_DEFINE_STATIC(mem_slab, BLOCK_SIZE, BLOCK_COUNT, 4); + +static bool configure_tx_streams(const struct device *i2s_dev, struct i2s_config *config) +{ + int ret; + + ret = i2s_configure(i2s_dev, I2S_DIR_TX, config); + if (ret < 0) { + printk("Failed to configure codec stream: %d\n", ret); + return false; + } + + return true; +} + +static bool trigger_command(const struct device *i2s_dev_codec, + enum i2s_trigger_cmd cmd) +{ + int ret; + + ret = i2s_trigger(i2s_dev_codec, I2S_DIR_TX, cmd); + if (ret < 0) { + printk("Failed to trigger command %d on TX: %d\n", cmd, ret); + return false; + } + + return true; +} + +int main(void) +{ + const struct device *const i2s_dev_codec = DEVICE_DT_GET(I2S_CODEC_TX); +#if CONFIG_USE_DMIC + const struct device *const dmic_dev = DEVICE_DT_GET(DT_NODELABEL(dmic_dev)); +#endif + const struct device *const codec_dev = DEVICE_DT_GET(DT_NODELABEL(audio_codec)); + struct i2s_config config; + struct audio_codec_cfg audio_cfg; + int ret; + +#if CONFIG_USE_DMIC + struct pcm_stream_cfg stream = { + .pcm_width = SAMPLE_BIT_WIDTH, + .mem_slab = &mem_slab, + }; + struct dmic_cfg 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 = &stream, + .channel = { + .req_num_streams = 1, + }, + }; +#endif + printk("codec sample\n"); + +#if CONFIG_USE_DMIC + if (!device_is_ready(dmic_dev)) { + printk("%s is not ready", dmic_dev->name); + return 0; + } +#endif + + if (!device_is_ready(i2s_dev_codec)) { + printk("%s is not ready\n", i2s_dev_codec->name); + return 0; + } + + + if (!device_is_ready(codec_dev)) { + printk("%s is not ready", codec_dev->name); + return 0; + } + audio_cfg.dai_route = AUDIO_ROUTE_PLAYBACK; + audio_cfg.dai_type = AUDIO_DAI_TYPE_I2S; + audio_cfg.dai_cfg.i2s.word_size = SAMPLE_BIT_WIDTH; + audio_cfg.dai_cfg.i2s.channels = 2; + audio_cfg.dai_cfg.i2s.format = I2S_FMT_DATA_FORMAT_I2S; + audio_cfg.dai_cfg.i2s.options = I2S_OPT_FRAME_CLK_MASTER; + audio_cfg.dai_cfg.i2s.frame_clk_freq = SAMPLE_FREQUENCY; + audio_cfg.dai_cfg.i2s.mem_slab = &mem_slab; + audio_cfg.dai_cfg.i2s.block_size = BLOCK_SIZE; + audio_codec_configure(codec_dev, &audio_cfg); + k_msleep(1000); + +#if CONFIG_USE_DMIC + cfg.channel.req_num_chan = 2; + cfg.channel.req_chan_map_lo = + dmic_build_channel_map(0, 0, PDM_CHAN_LEFT) | + dmic_build_channel_map(1, 0, PDM_CHAN_RIGHT); + cfg.streams[0].pcm_rate = SAMPLE_FREQUENCY; + cfg.streams[0].block_size = BLOCK_SIZE; + + printk("PCM output rate: %u, channels: %u\n", + cfg.streams[0].pcm_rate, cfg.channel.req_num_chan); + + ret = dmic_configure(dmic_dev, &cfg); + if (ret < 0) { + printk("Failed to configure the driver: %d", ret); + return ret; + } +#endif + + config.word_size = SAMPLE_BIT_WIDTH; + config.channels = NUMBER_OF_CHANNELS; + config.format = I2S_FMT_DATA_FORMAT_I2S; + config.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER; + config.frame_clk_freq = SAMPLE_FREQUENCY; + config.mem_slab = &mem_slab; + config.block_size = BLOCK_SIZE; + config.timeout = TIMEOUT; + if (!configure_tx_streams(i2s_dev_codec, &config)) { + printk("failure to config streams\n"); + return 0; + } + + printk("start streams\n"); + for (;;) { + bool started = false; +#if CONFIG_USE_DMIC + ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_START); + if (ret < 0) { + printk("START trigger failed: %d", ret); + return ret; + } +#endif + while (1) { + void *mem_block; + uint32_t block_size = BLOCK_SIZE; + int ret; + int i; + + for (i = 0; i < 2; i++) { +#if CONFIG_USE_DMIC + ret = dmic_read(dmic_dev, 0, + &mem_block, &block_size, TIMEOUT); + if (ret < 0) { + printk("read failed: %d", ret); + break; + } +#else + ret = k_mem_slab_alloc(&mem_slab, + &mem_block, Z_TIMEOUT_TICKS(TIMEOUT)); + if (ret < 0) { + printk("Failed to allocate TX block\n"); + return 0; + } +#endif + ret = i2s_write(i2s_dev_codec, mem_block, block_size); + if (ret < 0) { + printk("Failed to write data: %d\n", ret); + break; + } + } + if (ret < 0) { + printk("error %d\n", ret); + break; + } + if (!started) { + i2s_trigger(i2s_dev_codec, I2S_DIR_TX, I2S_TRIGGER_START); + started = true; + } + } + if (!trigger_command(i2s_dev_codec, + I2S_TRIGGER_DROP)) { + printk("Send I2S trigger DRAIN failed: %d", ret); + return 0; + } +#if CONFIG_USE_DMIC + ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_STOP); + if (ret < 0) { + printk("STOP trigger failed: %d", ret); + return 0; + } +#endif + printk("Streams stopped\n"); + return 0; + } +}