mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
feat(i2s): supports DMA buffer in PSRAM
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,10 +27,6 @@
|
||||
#include "hal/i2s_types.h"
|
||||
#include "hal/hal_utils.h"
|
||||
#include "hal/dma_types.h"
|
||||
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
#include "hal/cache_hal.h"
|
||||
#include "hal/cache_ll.h"
|
||||
#endif
|
||||
|
||||
#if I2S_LL_GET(ADC_DAC_CAPABLE)
|
||||
#include "hal/adc_ll.h"
|
||||
@@ -62,17 +58,13 @@
|
||||
#include "esp_check.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_cache.h"
|
||||
#include "esp_private/esp_cache_private.h"
|
||||
#include "esp_private/mspi_mem_barrier.h"
|
||||
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "esp_memory_utils.h"
|
||||
|
||||
/* The actual max size of DMA buffer is 4095
|
||||
* Reserve several bytes for alignment, so that the position of the slot data in the buffer will be relatively fixed */
|
||||
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
#define I2S_DMA_BUFFER_MAX_SIZE DMA_DESCRIPTOR_BUFFER_MAX_SIZE_64B_ALIGNED
|
||||
#else
|
||||
#define I2S_DMA_BUFFER_MAX_SIZE DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED
|
||||
#endif // SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
#define I2S_DMA_BUFFER_MAX_SIZE DMA_DESCRIPTOR_BUFFER_MAX_SIZE
|
||||
|
||||
static const char *TAG = "i2s_common";
|
||||
|
||||
@@ -82,17 +74,43 @@ static void s_i2s_channel_update_tx_sync_callback(i2s_chan_handle_t tx_handle,
|
||||
void *user_data);
|
||||
#endif
|
||||
|
||||
__attribute__((always_inline))
|
||||
inline void *i2s_dma_calloc(i2s_chan_handle_t handle, size_t num, size_t size)
|
||||
static inline void *i2s_dma_calloc(i2s_chan_handle_t handle, size_t num, size_t size)
|
||||
{
|
||||
return heap_caps_aligned_calloc(4, num, size, I2S_DMA_ALLOC_CAPS);
|
||||
uint32_t caps = handle->dma.buffer_in_psram ?
|
||||
(MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT) : I2S_DMA_ALLOC_CAPS;
|
||||
return heap_caps_aligned_calloc(handle->dma.buf_alignment, num, size, caps);
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_dma_buf_msync(void *addr, size_t size, int flags)
|
||||
static inline void IRAM_ATTR i2s_dma_buf_sync_c2m(void *buf, size_t size)
|
||||
{
|
||||
if (esp_cache_get_line_size_by_addr(addr) > 0) {
|
||||
esp_cache_msync(addr, size, flags);
|
||||
if (esp_cache_get_line_size_by_addr(buf) > 0) {
|
||||
esp_cache_msync(buf, size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void IRAM_ATTR i2s_dma_buf_sync_m2c(i2s_chan_handle_t handle, void *buf, size_t size)
|
||||
{
|
||||
#if CONFIG_SPIRAM
|
||||
if (handle->dma.buffer_in_psram) {
|
||||
esp_psram_mspi_mb();
|
||||
}
|
||||
#endif
|
||||
if (esp_cache_get_line_size_by_addr(buf) > 0) {
|
||||
esp_cache_msync(buf, size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
|
||||
}
|
||||
}
|
||||
|
||||
static void i2s_dma_bufs_sync_for_start(i2s_chan_handle_t handle)
|
||||
{
|
||||
if (!handle->dma.bufs) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < handle->dma.desc_num; i++) {
|
||||
void *buf = handle->dma.bufs[i];
|
||||
if (esp_cache_get_line_size_by_addr(buf) > 0) {
|
||||
esp_cache_msync(buf, handle->dma.buf_size,
|
||||
ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,12 +278,7 @@ static void i2s_tx_channel_start(i2s_chan_handle_t handle)
|
||||
i2s_hal_tx_reset_fifo(&(handle->controller->hal));
|
||||
handle->dma.link_index = 0;
|
||||
/* Write back CPU updates (e.g. preload) then drop cache lines before DMA owns the buffers. */
|
||||
if (handle->dma.bufs) {
|
||||
for (int i = 0; i < handle->dma.desc_num; i++) {
|
||||
i2s_dma_buf_msync(handle->dma.bufs[i], handle->dma.buf_size,
|
||||
ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
|
||||
}
|
||||
}
|
||||
i2s_dma_bufs_sync_for_start(handle);
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
if (handle->dma.dma_chan) {
|
||||
gdma_start((handle->dma.dma_chan), gdma_link_get_head_addr(handle->dma.dma_link));
|
||||
@@ -295,12 +308,7 @@ static void i2s_rx_channel_start(i2s_chan_handle_t handle)
|
||||
i2s_hal_rx_reset_fifo(&(handle->controller->hal));
|
||||
handle->dma.link_index = 0;
|
||||
/* Flush dirty CPU lines then invalidate so DMA writes cannot be overwritten by later evictions. */
|
||||
if (handle->dma.bufs) {
|
||||
for (int i = 0; i < handle->dma.desc_num; i++) {
|
||||
i2s_dma_buf_msync(handle->dma.bufs[i], handle->dma.buf_size,
|
||||
ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
|
||||
}
|
||||
}
|
||||
i2s_dma_bufs_sync_for_start(handle);
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
if (handle->dma.dma_chan) {
|
||||
gdma_start(handle->dma.dma_chan, gdma_link_get_head_addr(handle->dma.dma_link));
|
||||
@@ -500,6 +508,7 @@ static esp_err_t i2s_register_channel(i2s_controller_t *i2s_obj, i2s_dir_t dir,
|
||||
new_chan->callbacks.on_recv_q_ovf = NULL;
|
||||
new_chan->callbacks.on_sent = NULL;
|
||||
new_chan->callbacks.on_send_q_ovf = NULL;
|
||||
new_chan->dma.buf_alignment = 4;
|
||||
new_chan->dma.rw_pos = 0;
|
||||
new_chan->dma.curr_ptr = NULL;
|
||||
new_chan->start = NULL;
|
||||
@@ -622,6 +631,11 @@ esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2
|
||||
#endif
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
#if CONFIG_I2S_ISR_IRAM_SAFE
|
||||
if (handle->dma.buffer_in_psram && (callbacks->on_recv || callbacks->on_sent)) {
|
||||
ESP_LOGW(TAG, "DMA buffers are in PSRAM; callbacks must not access them while the cache is disabled");
|
||||
}
|
||||
#endif
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
bool update_dma_cbs = dma_cb_supported && !(sync_only_update && handle->state == I2S_CHAN_STATE_RUNNING);
|
||||
ESP_GOTO_ON_FALSE(!update_dma_cbs || handle->state < I2S_CHAN_STATE_RUNNING,
|
||||
@@ -656,33 +670,28 @@ uint32_t i2s_get_buf_size(i2s_chan_handle_t handle, uint32_t data_bit_width, uin
|
||||
if (bytes_per_frame == 0) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t bufsize = dma_frame_num * bytes_per_frame;
|
||||
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
/* bufsize need to align with cache line size */
|
||||
uint32_t alignment = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
|
||||
size_t alignment = handle->dma.buf_alignment;
|
||||
uint32_t max_buf_size = I2S_DMA_BUFFER_MAX_SIZE & ~(alignment - 1);
|
||||
uint32_t aligned_frame_num = dma_frame_num;
|
||||
/* To make the buffer aligned with the cache line size, search for the ceil aligned size first,
|
||||
If the buffer size exceed the max DMA buffer size, toggle the sign to search for the floor aligned size */
|
||||
for (int sign = 1; bufsize % alignment != 0; aligned_frame_num += sign) {
|
||||
uint32_t bufsize = aligned_frame_num * bytes_per_frame;
|
||||
/* Prefer rounding the frame count up. If that would exceed one descriptor, round it down instead. */
|
||||
while (bufsize <= max_buf_size && bufsize % alignment != 0) {
|
||||
aligned_frame_num++;
|
||||
bufsize = aligned_frame_num * bytes_per_frame;
|
||||
/* If the buffer size exceed the max dma size */
|
||||
if (bufsize > I2S_DMA_BUFFER_MAX_SIZE && sign == 1) {
|
||||
sign = -1; // toggle the search sign
|
||||
aligned_frame_num = dma_frame_num; // Reset the frame num
|
||||
bufsize = aligned_frame_num * bytes_per_frame; // Reset the bufsize
|
||||
}
|
||||
if (bufsize > max_buf_size) {
|
||||
aligned_frame_num = dma_frame_num < max_buf_size / bytes_per_frame ?
|
||||
dma_frame_num : max_buf_size / bytes_per_frame;
|
||||
bufsize = aligned_frame_num * bytes_per_frame;
|
||||
while (aligned_frame_num > 0 && bufsize % alignment != 0) {
|
||||
aligned_frame_num--;
|
||||
bufsize = aligned_frame_num * bytes_per_frame;
|
||||
}
|
||||
}
|
||||
if (bufsize / bytes_per_frame != dma_frame_num) {
|
||||
ESP_LOGW(TAG, "dma frame num is adjusted to %"PRIu32" to align the dma buffer with %"PRIu32
|
||||
ESP_LOGW(TAG, "dma frame num is adjusted to %"PRIu32" to align the dma buffer with %zu"
|
||||
", bufsize = %"PRIu32, bufsize / bytes_per_frame, alignment, bufsize);
|
||||
}
|
||||
#endif
|
||||
/* Limit DMA buffer size if it is out of range */
|
||||
if (bufsize > I2S_DMA_BUFFER_MAX_SIZE) {
|
||||
uint32_t frame_num = I2S_DMA_BUFFER_MAX_SIZE / bytes_per_frame;
|
||||
bufsize = frame_num * bytes_per_frame;
|
||||
ESP_LOGW(TAG, "dma frame num is out of dma buffer size, limited to %"PRIu32, frame_num);
|
||||
}
|
||||
return bufsize;
|
||||
}
|
||||
|
||||
@@ -720,11 +729,12 @@ static esp_err_t i2s_mount_dma_link(i2s_chan_handle_t handle, uint32_t bufsize)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint32_t num = handle->dma.desc_num;
|
||||
size_t buffer_alignment = handle->dma.buf_alignment;
|
||||
gdma_link_list_config_t link_config = {
|
||||
.num_items = num,
|
||||
.item_alignment = 4,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(gdma_new_link_list(&link_config, &handle->dma.dma_link), err, TAG, "create I2S DMA link failed");
|
||||
ESP_RETURN_ON_ERROR(gdma_new_link_list(&link_config, &handle->dma.dma_link), TAG, "create I2S DMA link failed");
|
||||
|
||||
for (int i = 0; i < num; i++) {
|
||||
size_t buffer_alignment = 4;
|
||||
@@ -773,7 +783,10 @@ esp_err_t i2s_alloc_dma_resources(i2s_chan_handle_t handle, uint32_t bufsize)
|
||||
{
|
||||
I2S_NULL_POINTER_CHECK(TAG, handle);
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_RETURN_ON_FALSE(bufsize <= I2S_DMA_BUFFER_MAX_SIZE, ESP_ERR_INVALID_ARG, TAG, "dma buffer can't be bigger than %d", I2S_DMA_BUFFER_MAX_SIZE);
|
||||
size_t alignment = handle->dma.buf_alignment;
|
||||
uint32_t max_buf_size = I2S_DMA_BUFFER_MAX_SIZE & ~(alignment - 1);
|
||||
ESP_RETURN_ON_FALSE(bufsize <= max_buf_size && bufsize % alignment == 0, ESP_ERR_INVALID_ARG, TAG,
|
||||
"dma buffer must be aligned to %zu and no bigger than %"PRIu32, alignment, max_buf_size);
|
||||
uint32_t num = handle->dma.desc_num;
|
||||
handle->dma.buf_size = bufsize;
|
||||
|
||||
@@ -857,7 +870,7 @@ static bool i2s_dma_rx_callback(gdma_channel_handle_t dma_chan, gdma_event_data_
|
||||
(void)dma_chan;
|
||||
(void)event_data;
|
||||
handle->dma.link_index = (finish_index + 1) % handle->dma.desc_num;
|
||||
i2s_dma_buf_msync(finish_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
|
||||
i2s_dma_buf_sync_m2c(handle, finish_buf, handle->dma.buf_size);
|
||||
i2s_event_data_t evt = {
|
||||
.dma_buf = finish_buf,
|
||||
.size = handle->dma.buf_size,
|
||||
@@ -901,7 +914,7 @@ static bool i2s_dma_tx_callback(gdma_channel_handle_t dma_chan, gdma_event_data_
|
||||
}
|
||||
/* Sync buffer after the callback in case users update the buffer in the callback */
|
||||
if (handle->dma.auto_clear_before_cb || handle->callbacks.on_sent) {
|
||||
i2s_dma_buf_msync(curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
|
||||
i2s_dma_buf_sync_c2m(curr_buf, handle->dma.buf_size);
|
||||
}
|
||||
if (xQueueIsQueueFullFromISR(handle->msg_queue)) {
|
||||
xQueueReceiveFromISR(handle->msg_queue, &dummy, &need_yield1);
|
||||
@@ -912,7 +925,7 @@ static bool i2s_dma_tx_callback(gdma_channel_handle_t dma_chan, gdma_event_data_
|
||||
}
|
||||
if (handle->dma.auto_clear_after_cb) {
|
||||
memset(curr_buf, 0, handle->dma.buf_size);
|
||||
i2s_dma_buf_msync(curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
|
||||
i2s_dma_buf_sync_c2m(curr_buf, handle->dma.buf_size);
|
||||
}
|
||||
xQueueSendFromISR(handle->msg_queue, &curr_buf, &need_yield2);
|
||||
|
||||
@@ -940,9 +953,9 @@ static void i2s_dma_rx_callback(void *arg)
|
||||
uint32_t finish_index = handle->dma.link_index;
|
||||
void *finish_buf = gdma_link_get_buffer(handle->dma.dma_link, finish_index);
|
||||
handle->dma.link_index = (finish_index + 1) % handle->dma.desc_num;
|
||||
i2s_dma_buf_sync_m2c(handle, finish_buf, handle->dma.buf_size);
|
||||
evt.dma_buf = finish_buf;
|
||||
evt.size = handle->dma.buf_size;
|
||||
i2s_dma_buf_msync(finish_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
|
||||
if (handle->callbacks.on_recv) {
|
||||
user_need_yield |= handle->callbacks.on_recv(handle, &evt, handle->user_data);
|
||||
}
|
||||
@@ -991,7 +1004,7 @@ static void i2s_dma_tx_callback(void *arg)
|
||||
}
|
||||
/* Sync buffer after the callback in case users update the buffer in the callback */
|
||||
if (handle->dma.auto_clear_before_cb || handle->callbacks.on_sent) {
|
||||
i2s_dma_buf_msync(curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
|
||||
i2s_dma_buf_sync_c2m(curr_buf, handle->dma.buf_size);
|
||||
}
|
||||
if (xQueueIsQueueFullFromISR(handle->msg_queue)) {
|
||||
xQueueReceiveFromISR(handle->msg_queue, &dummy, &need_yield1);
|
||||
@@ -1002,7 +1015,7 @@ static void i2s_dma_tx_callback(void *arg)
|
||||
// Auto clear the dma buffer after data sent
|
||||
if (handle->dma.auto_clear_after_cb) {
|
||||
memset(curr_buf, 0, handle->dma.buf_size);
|
||||
i2s_dma_buf_msync(curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
|
||||
i2s_dma_buf_sync_c2m(curr_buf, handle->dma.buf_size);
|
||||
}
|
||||
xQueueSendFromISR(handle->msg_queue, &curr_buf, &need_yield2);
|
||||
}
|
||||
@@ -1013,6 +1026,68 @@ static void i2s_dma_tx_callback(void *arg)
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_err_t i2s_prepare_dma(i2s_chan_handle_t handle)
|
||||
{
|
||||
I2S_NULL_POINTER_CHECK(TAG, handle);
|
||||
if (handle->dma.dma_chan) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
handle->dma.buf_alignment = 4;
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
esp_err_t ret = ESP_OK;
|
||||
gdma_channel_alloc_config_t dma_cfg = {
|
||||
#if CONFIG_I2S_ISR_IRAM_SAFE
|
||||
.flags.isr_cache_safe = true,
|
||||
#endif // CONFIG_I2S_ISR_IRAM_SAFE
|
||||
};
|
||||
if (handle->dir == I2S_DIR_TX) {
|
||||
ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(&dma_cfg, &handle->dma.dma_chan, NULL),
|
||||
TAG, "register tx dma channel error");
|
||||
} else {
|
||||
ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(&dma_cfg, NULL, &handle->dma.dma_chan),
|
||||
TAG, "register rx dma channel error");
|
||||
}
|
||||
|
||||
gdma_transfer_config_t transfer_cfg = {
|
||||
.max_data_burst_size = 0,
|
||||
.access_ext_mem = handle->dma.buffer_in_psram,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(gdma_config_transfer(handle->dma.dma_chan, &transfer_cfg),
|
||||
err, TAG, "config dma transfer error");
|
||||
ESP_GOTO_ON_ERROR(gdma_get_alignment_constraints(handle->dma.dma_chan,
|
||||
handle->dma.buffer_in_psram ? NULL : &handle->dma.buf_alignment,
|
||||
handle->dma.buffer_in_psram ? &handle->dma.buf_alignment : NULL),
|
||||
err, TAG, "get dma alignment constraints failed");
|
||||
#else // I2S dedicated DMA (ESP32 / ESP32-S2)
|
||||
if (handle->dma.buffer_in_psram) {
|
||||
#if I2S_LL_SUPPORT(DMA_EXT_MEM)
|
||||
i2s_ll_dma_set_ext_mem_block_size(handle->controller->hal.dev, I2S_LL_DMA_EXT_MEM_ALIGNMENT);
|
||||
if (handle->dir == I2S_DIR_TX) {
|
||||
i2s_ll_dma_tx_enable_burst(handle->controller->hal.dev, true);
|
||||
} else {
|
||||
i2s_ll_dma_rx_enable_burst(handle->controller->hal.dev, true);
|
||||
}
|
||||
handle->dma.buf_alignment = I2S_LL_DMA_EXT_MEM_ALIGNMENT;
|
||||
#else // ESP32 cannot use external memory for DMA
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif // I2S_LL_SUPPORT(DMA_EXT_MEM)
|
||||
}
|
||||
#endif // SOC_GDMA_SUPPORTED
|
||||
|
||||
size_t cache_line_size = 0;
|
||||
esp_cache_get_alignment(handle->dma.buffer_in_psram ? MALLOC_CAP_SPIRAM : MALLOC_CAP_INTERNAL, &cache_line_size);
|
||||
handle->dma.buf_alignment = MAX(handle->dma.buf_alignment, cache_line_size);
|
||||
return ESP_OK;
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
err:
|
||||
gdma_del_channel(handle->dma.dma_chan);
|
||||
handle->dma.dma_chan = NULL;
|
||||
return ret;
|
||||
#endif // SOC_GDMA_SUPPORTED
|
||||
}
|
||||
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
/**
|
||||
* @brief I2S DMA interrupt initialization (implemented by I2S dedicated DMA)
|
||||
@@ -1065,54 +1140,24 @@ esp_err_t i2s_init_dma_intr(i2s_chan_handle_t handle, int intr_flag)
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/* Set GDMA config */
|
||||
gdma_channel_alloc_config_t dma_cfg = {
|
||||
#if CONFIG_I2S_ISR_IRAM_SAFE
|
||||
.flags.isr_cache_safe = true,
|
||||
#endif
|
||||
};
|
||||
if (handle->dir == I2S_DIR_TX) {
|
||||
/* Register a new GDMA tx channel */
|
||||
ESP_GOTO_ON_ERROR(gdma_new_ahb_channel(&dma_cfg, &handle->dma.dma_chan, NULL), err, TAG, "Register tx dma channel error");
|
||||
} else {
|
||||
/* Register a new GDMA rx channel */
|
||||
ESP_GOTO_ON_ERROR(gdma_new_ahb_channel(&dma_cfg, NULL, &handle->dma.dma_chan), err, TAG, "Register rx dma channel error");
|
||||
}
|
||||
|
||||
gdma_transfer_config_t transfer_cfg = {
|
||||
.max_data_burst_size = 0,
|
||||
.access_ext_mem = false,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(gdma_config_transfer(handle->dma.dma_chan, &transfer_cfg),
|
||||
err_channel, TAG, "Config dma transfer error");
|
||||
ESP_GOTO_ON_ERROR(i2s_mount_dma_link(handle, handle->dma.buf_size),
|
||||
err_channel, TAG, "Mount dma link error");
|
||||
ESP_GOTO_ON_ERROR(gdma_connect(handle->dma.dma_chan, trig), err_link, TAG, "Connect dma channel error");
|
||||
ESP_RETURN_ON_ERROR(gdma_connect(handle->dma.dma_chan, trig), TAG, "connect dma channel error");
|
||||
|
||||
if (handle->dir == I2S_DIR_TX) {
|
||||
gdma_tx_event_callbacks_t cb = {.on_trans_eof = i2s_dma_tx_callback};
|
||||
/* Set callback function for GDMA, the interrupt is triggered by GDMA, then the GDMA ISR will call the callback function */
|
||||
ESP_GOTO_ON_ERROR(gdma_register_tx_event_callbacks(handle->dma.dma_chan, &cb, handle),
|
||||
err_disconnect, TAG, "Register tx callback failed");
|
||||
err, TAG, "Register tx callback failed");
|
||||
} else {
|
||||
gdma_rx_event_callbacks_t cb = {.on_recv_eof = i2s_dma_rx_callback};
|
||||
/* Set callback function for GDMA, the interrupt is triggered by GDMA, then the GDMA ISR will call the callback function */
|
||||
ESP_GOTO_ON_ERROR(gdma_register_rx_event_callbacks(handle->dma.dma_chan, &cb, handle),
|
||||
err_disconnect, TAG, "Register rx callback failed");
|
||||
err, TAG, "Register rx callback failed");
|
||||
}
|
||||
handle->dma.connected = true;
|
||||
return ret;
|
||||
|
||||
err_disconnect:
|
||||
gdma_disconnect(handle->dma.dma_chan);
|
||||
err_link:
|
||||
if (handle->dma.dma_link) {
|
||||
gdma_del_link_list(handle->dma.dma_link);
|
||||
handle->dma.dma_link = NULL;
|
||||
}
|
||||
err_channel:
|
||||
gdma_del_channel(handle->dma.dma_chan);
|
||||
handle->dma.dma_chan = NULL;
|
||||
err:
|
||||
gdma_disconnect(handle->dma.dma_chan);
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
@@ -1265,6 +1310,35 @@ esp_err_t i2s_check_set_mclk(i2s_chan_handle_t handle, int id, int gpio_num, i2s
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t i2s_setup_channel(i2s_controller_t *i2s_obj, i2s_dir_t dir,
|
||||
const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t *ret_handle)
|
||||
{
|
||||
i2s_destination_t dest = (dir == I2S_DIR_TX) ? chan_cfg->tx_destination : chan_cfg->rx_destination;
|
||||
ESP_RETURN_ON_ERROR(i2s_register_channel(i2s_obj, dir, chan_cfg->dma_desc_num, dest == I2S_DESTINATION_DMA),
|
||||
TAG, "register I2S %s channel failed", dir == I2S_DIR_TX ? "tx" : "rx");
|
||||
|
||||
i2s_chan_handle_t chan = (dir == I2S_DIR_TX) ? i2s_obj->tx_chan : i2s_obj->rx_chan;
|
||||
chan->role = chan_cfg->role;
|
||||
chan->is_port_auto = chan_cfg->id == I2S_NUM_AUTO;
|
||||
chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED;
|
||||
chan->dma.buffer_in_psram = chan_cfg->dma_buffer_in_psram;
|
||||
chan->dma.desc_num = chan_cfg->dma_desc_num;
|
||||
chan->dma.frame_num = chan_cfg->dma_frame_num;
|
||||
chan->destination = dest;
|
||||
if (dir == I2S_DIR_TX) {
|
||||
chan->dma.auto_clear_after_cb = chan_cfg->auto_clear_after_cb;
|
||||
chan->dma.auto_clear_before_cb = chan_cfg->auto_clear_before_cb;
|
||||
chan->start = i2s_tx_channel_start;
|
||||
chan->stop = i2s_tx_channel_stop;
|
||||
} else {
|
||||
chan->start = i2s_rx_channel_start;
|
||||
chan->stop = i2s_rx_channel_stop;
|
||||
}
|
||||
*ret_handle = chan;
|
||||
ESP_LOGD(TAG, "%s channel is registered on I2S%d successfully", dir == I2S_DIR_TX ? "tx" : "rx", i2s_obj->id);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
I2S bus Public APIs
|
||||
----------------------------------------------------------------------------
|
||||
@@ -1281,6 +1355,16 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t *
|
||||
ESP_RETURN_ON_FALSE((chan_cfg->id >= 0 && chan_cfg->id < I2S_LL_GET(INST_NUM)) || chan_cfg->id == I2S_NUM_AUTO, ESP_ERR_INVALID_ARG, TAG, "invalid I2S port id");
|
||||
ESP_RETURN_ON_FALSE(chan_cfg->dma_desc_num >= 2, ESP_ERR_INVALID_ARG, TAG, "there should be at least 2 DMA buffers");
|
||||
ESP_RETURN_ON_FALSE(chan_cfg->intr_priority >= 0 && chan_cfg->intr_priority <= 7, ESP_ERR_INVALID_ARG, TAG, "intr_priority should be within 0~7");
|
||||
#if !(SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM)
|
||||
// Reject PSRAM DMA buffers when the I2S DMA cannot access PSRAM or PSRAM is not enabled.
|
||||
ESP_RETURN_ON_FALSE(!chan_cfg->dma_buffer_in_psram, ESP_ERR_NOT_SUPPORTED, TAG, "PSRAM DMA buffer is not supported");
|
||||
#endif
|
||||
#if CONFIG_I2S_ISR_IRAM_SAFE
|
||||
// Reject auto clear for PSRAM DMA buffers with IRAM-safe ISR
|
||||
ESP_RETURN_ON_FALSE(!(chan_cfg->dma_buffer_in_psram &&
|
||||
(chan_cfg->auto_clear_before_cb || chan_cfg->auto_clear_after_cb)),
|
||||
ESP_ERR_NOT_SUPPORTED, TAG, "auto clear is not supported for PSRAM DMA buffers with IRAM-safe ISR");
|
||||
#endif
|
||||
#if !SOC_HAS(PAU)
|
||||
ESP_RETURN_ON_FALSE(!chan_cfg->allow_pd, ESP_ERR_NOT_SUPPORTED, TAG, "register back up is not supported");
|
||||
#endif
|
||||
@@ -1333,39 +1417,13 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t *
|
||||
channel_found = i2s_take_available_channel(i2s_obj, chan_search_mask);
|
||||
}
|
||||
ESP_GOTO_ON_FALSE(channel_found, ESP_ERR_NOT_FOUND, err, TAG, "no available channel found");
|
||||
/* Register and specify the tx handle */
|
||||
if (tx_handle) {
|
||||
const bool tx_use_dma = chan_cfg->tx_destination == I2S_DESTINATION_DMA;
|
||||
ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_TX, chan_cfg->dma_desc_num, tx_use_dma),
|
||||
ESP_GOTO_ON_ERROR(i2s_setup_channel(i2s_obj, I2S_DIR_TX, chan_cfg, tx_handle),
|
||||
err, TAG, "register I2S tx channel failed");
|
||||
i2s_obj->tx_chan->role = chan_cfg->role;
|
||||
i2s_obj->tx_chan->is_port_auto = id == I2S_NUM_AUTO;
|
||||
i2s_obj->tx_chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED;
|
||||
i2s_obj->tx_chan->dma.auto_clear_after_cb = chan_cfg->auto_clear_after_cb;
|
||||
i2s_obj->tx_chan->dma.auto_clear_before_cb = chan_cfg->auto_clear_before_cb;
|
||||
i2s_obj->tx_chan->dma.desc_num = chan_cfg->dma_desc_num;
|
||||
i2s_obj->tx_chan->dma.frame_num = chan_cfg->dma_frame_num;
|
||||
i2s_obj->tx_chan->start = i2s_tx_channel_start;
|
||||
i2s_obj->tx_chan->stop = i2s_tx_channel_stop;
|
||||
i2s_obj->tx_chan->destination = chan_cfg->tx_destination;
|
||||
*tx_handle = i2s_obj->tx_chan;
|
||||
ESP_LOGD(TAG, "tx channel is registered on I2S%d successfully", i2s_obj->id);
|
||||
}
|
||||
/* Register and specify the rx handle */
|
||||
if (rx_handle) {
|
||||
const bool rx_use_dma = chan_cfg->rx_destination == I2S_DESTINATION_DMA;
|
||||
ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_RX, chan_cfg->dma_desc_num, rx_use_dma),
|
||||
ESP_GOTO_ON_ERROR(i2s_setup_channel(i2s_obj, I2S_DIR_RX, chan_cfg, rx_handle),
|
||||
err, TAG, "register I2S rx channel failed");
|
||||
i2s_obj->rx_chan->role = chan_cfg->role;
|
||||
i2s_obj->rx_chan->is_port_auto = id == I2S_NUM_AUTO;
|
||||
i2s_obj->rx_chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED;
|
||||
i2s_obj->rx_chan->dma.desc_num = chan_cfg->dma_desc_num;
|
||||
i2s_obj->rx_chan->dma.frame_num = chan_cfg->dma_frame_num;
|
||||
i2s_obj->rx_chan->start = i2s_rx_channel_start;
|
||||
i2s_obj->rx_chan->stop = i2s_rx_channel_stop;
|
||||
i2s_obj->rx_chan->destination = chan_cfg->rx_destination;
|
||||
*rx_handle = i2s_obj->rx_chan;
|
||||
ESP_LOGD(TAG, "rx channel is registered on I2S%d successfully", i2s_obj->id);
|
||||
}
|
||||
|
||||
if ((tx_handle != NULL) && (rx_handle != NULL)) {
|
||||
@@ -1481,7 +1539,10 @@ esp_err_t i2s_del_channel(i2s_chan_handle_t handle)
|
||||
#endif
|
||||
if (handle->dma.dma_chan) {
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
gdma_disconnect(handle->dma.dma_chan);
|
||||
if (handle->dma.connected) {
|
||||
gdma_disconnect(handle->dma.dma_chan);
|
||||
handle->dma.connected = false;
|
||||
}
|
||||
gdma_del_channel(handle->dma.dma_chan);
|
||||
#else
|
||||
esp_intr_free(handle->dma.dma_chan);
|
||||
@@ -1667,7 +1728,7 @@ esp_err_t i2s_channel_preload_data(i2s_chan_handle_t tx_handle, const void *src,
|
||||
}
|
||||
/* Load the data from the last loaded position */
|
||||
memcpy((uint8_t *)(tx_handle->dma.curr_ptr + tx_handle->dma.rw_pos), data_ptr, bytes_can_load);
|
||||
i2s_dma_buf_msync(tx_handle->dma.curr_ptr, tx_handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
|
||||
i2s_dma_buf_sync_c2m(tx_handle->dma.curr_ptr, tx_handle->dma.buf_size);
|
||||
data_ptr += bytes_can_load; // Move forward the data pointer
|
||||
total_loaded_bytes += bytes_can_load; // Add to the total loaded bytes
|
||||
remain_bytes -= bytes_can_load; // Update the remaining bytes to be loaded
|
||||
@@ -1719,7 +1780,7 @@ esp_err_t i2s_channel_write(i2s_chan_handle_t handle, const void *src, size_t si
|
||||
bytes_can_write = size;
|
||||
}
|
||||
memcpy(data_ptr, src_byte, bytes_can_write);
|
||||
i2s_dma_buf_msync(handle->dma.curr_ptr, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
|
||||
i2s_dma_buf_sync_c2m(handle->dma.curr_ptr, handle->dma.buf_size);
|
||||
size -= bytes_can_write;
|
||||
src_byte += bytes_can_write;
|
||||
handle->dma.rw_pos += bytes_can_write;
|
||||
|
||||
@@ -232,6 +232,9 @@ esp_err_t i2s_channel_init_pdm_tx_mode(i2s_chan_handle_t handle, const i2s_pdm_t
|
||||
}
|
||||
handle->mode_info = calloc(1, sizeof(i2s_pdm_tx_config_t));
|
||||
ESP_GOTO_ON_FALSE(handle->mode_info, ESP_ERR_NO_MEM, err, TAG, "no memory for storing the configurations");
|
||||
if (I2S_CHANNEL_USES_DMA(handle)) {
|
||||
ESP_GOTO_ON_ERROR(i2s_prepare_dma(handle), err, TAG, "prepare dma failed");
|
||||
}
|
||||
/* i2s_set_pdm_tx_slot should be called before i2s_set_pdm_tx_clock and i2s_pdm_tx_set_gpio
|
||||
* while initializing, because clock and gpio is relay on the slot */
|
||||
ESP_GOTO_ON_ERROR(i2s_pdm_tx_set_slot(handle, &pdm_tx_cfg->slot_cfg), err, TAG, "initialize channel failed while setting slot");
|
||||
@@ -256,6 +259,10 @@ esp_err_t i2s_channel_init_pdm_tx_mode(i2s_chan_handle_t handle, const i2s_pdm_t
|
||||
pm_type = ESP_PM_NO_LIGHT_SLEEP;
|
||||
}
|
||||
#endif // SOC_I2S_SUPPORTS_APLL
|
||||
if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) {
|
||||
// use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS
|
||||
pm_type = ESP_PM_CPU_FREQ_MAX;
|
||||
}
|
||||
ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed");
|
||||
#endif
|
||||
|
||||
@@ -307,6 +314,10 @@ esp_err_t i2s_channel_reconfig_pdm_tx_clock(i2s_chan_handle_t handle, const i2s_
|
||||
pm_type = ESP_PM_NO_LIGHT_SLEEP;
|
||||
}
|
||||
#endif // SOC_I2S_SUPPORTS_APLL
|
||||
if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) {
|
||||
// use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS
|
||||
pm_type = ESP_PM_CPU_FREQ_MAX;
|
||||
}
|
||||
ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed");
|
||||
}
|
||||
#endif //CONFIG_PM_ENABLE
|
||||
@@ -594,6 +605,9 @@ esp_err_t i2s_channel_init_pdm_rx_mode(i2s_chan_handle_t handle, const i2s_pdm_r
|
||||
}
|
||||
handle->mode_info = calloc(1, sizeof(i2s_pdm_rx_config_t));
|
||||
ESP_GOTO_ON_FALSE(handle->mode_info, ESP_ERR_NO_MEM, err, TAG, "no memory for storing the configurations");
|
||||
if (I2S_CHANNEL_USES_DMA(handle)) {
|
||||
ESP_GOTO_ON_ERROR(i2s_prepare_dma(handle), err, TAG, "prepare dma failed");
|
||||
}
|
||||
/* i2s_set_pdm_rx_slot should be called before i2s_set_pdm_rx_clock and i2s_pdm_rx_set_gpio while initializing, because clock is relay on the slot */
|
||||
ESP_GOTO_ON_ERROR(i2s_pdm_rx_set_slot(handle, &pdm_rx_cfg->slot_cfg), err, TAG, "initialize channel failed while setting slot");
|
||||
ESP_GOTO_ON_ERROR(i2s_pdm_rx_set_gpio(handle, &pdm_rx_cfg->gpio_cfg), err, TAG, "initialize channel failed while setting gpio pins");
|
||||
@@ -614,6 +628,10 @@ esp_err_t i2s_channel_init_pdm_rx_mode(i2s_chan_handle_t handle, const i2s_pdm_r
|
||||
pm_type = ESP_PM_NO_LIGHT_SLEEP;
|
||||
}
|
||||
#endif // SOC_I2S_SUPPORTS_APLL
|
||||
if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) {
|
||||
// use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS
|
||||
pm_type = ESP_PM_CPU_FREQ_MAX;
|
||||
}
|
||||
ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed");
|
||||
#endif
|
||||
|
||||
@@ -665,6 +683,10 @@ esp_err_t i2s_channel_reconfig_pdm_rx_clock(i2s_chan_handle_t handle, const i2s_
|
||||
pm_type = ESP_PM_NO_LIGHT_SLEEP;
|
||||
}
|
||||
#endif // SOC_I2S_SUPPORTS_APLL
|
||||
if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) {
|
||||
// use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS
|
||||
pm_type = ESP_PM_CPU_FREQ_MAX;
|
||||
}
|
||||
ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed");
|
||||
}
|
||||
#endif //CONFIG_PM_ENABLE
|
||||
|
||||
@@ -120,6 +120,11 @@ typedef struct {
|
||||
uint32_t desc_num; /*!< I2S DMA buffer number, it is also the number of DMA descriptor */
|
||||
uint32_t frame_num; /*!< I2S frame number in one DMA buffer. One frame means one-time sample data in all slots */
|
||||
uint32_t buf_size; /*!< dma buffer size */
|
||||
size_t buf_alignment; /*!< DMA buffer alignment required for address and size */
|
||||
bool buffer_in_psram; /*!< Whether the DMA buffers are allocated in PSRAM */
|
||||
#if SOC_GDMA_SUPPORTED
|
||||
bool connected; /*!< Whether the GDMA channel is connected to I2S */
|
||||
#endif
|
||||
bool auto_clear_after_cb; /*!< Set to auto clear DMA TX descriptor after callback, i2s will always send zero automatically if no data to send */
|
||||
bool auto_clear_before_cb; /*!< Set to auto clear DMA TX descriptor before callback, i2s will always send zero automatically if no data to send */
|
||||
uint32_t rw_pos; /*!< reading/writing pointer position */
|
||||
@@ -294,6 +299,17 @@ extern i2s_platform_t g_i2s; /*!< Global i2s instance for driver internal use *
|
||||
*/
|
||||
esp_err_t i2s_init_dma_intr(i2s_chan_handle_t handle, int intr_flag);
|
||||
|
||||
/**
|
||||
* @brief Prepare the DMA engine and query the memory alignment constraints
|
||||
*
|
||||
* @param handle I2S channel handle
|
||||
* @return
|
||||
* - ESP_OK DMA engine prepared
|
||||
* - ESP_ERR_NOT_SUPPORTED External-memory DMA is unsupported
|
||||
* - ESP_ERR_NOT_FOUND DMA channel not found
|
||||
*/
|
||||
esp_err_t i2s_prepare_dma(i2s_chan_handle_t handle);
|
||||
|
||||
#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
/**
|
||||
* @brief Initialize I2S peripheral interrupt
|
||||
|
||||
@@ -336,6 +336,10 @@ esp_err_t i2s_channel_init_std_mode(i2s_chan_handle_t handle, const i2s_std_conf
|
||||
#if SOC_I2S_HW_VERSION_1
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "Failed to constitute full-duplex mode");
|
||||
#endif
|
||||
/* DMA alignment must be known before allocating buffers in set_slot */
|
||||
if (I2S_CHANNEL_USES_DMA(handle)) {
|
||||
ESP_GOTO_ON_ERROR(i2s_prepare_dma(handle), err, TAG, "prepare dma failed");
|
||||
}
|
||||
/* i2s_set_std_slot should be called before i2s_set_std_clock while initializing, because clock is relay on the slot */
|
||||
ESP_GOTO_ON_ERROR(i2s_std_set_slot(handle, &std_cfg->slot_cfg), err, TAG, "initialize channel failed while setting slot");
|
||||
ESP_GOTO_ON_ERROR(i2s_std_set_clock(handle, &std_cfg->clk_cfg), err, TAG, "initialize channel failed while setting clock");
|
||||
@@ -368,6 +372,10 @@ esp_err_t i2s_channel_init_std_mode(i2s_chan_handle_t handle, const i2s_std_conf
|
||||
pm_type = ESP_PM_NO_LIGHT_SLEEP;
|
||||
}
|
||||
#endif // SOC_I2S_SUPPORTS_APLL
|
||||
// use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS
|
||||
if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) {
|
||||
pm_type = ESP_PM_CPU_FREQ_MAX;
|
||||
}
|
||||
ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed");
|
||||
#endif
|
||||
|
||||
@@ -421,6 +429,10 @@ esp_err_t i2s_channel_reconfig_std_clock(i2s_chan_handle_t handle, const i2s_std
|
||||
pm_type = ESP_PM_NO_LIGHT_SLEEP;
|
||||
}
|
||||
#endif // SOC_I2S_SUPPORTS_APLL
|
||||
if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) {
|
||||
// use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS
|
||||
pm_type = ESP_PM_CPU_FREQ_MAX;
|
||||
}
|
||||
ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed");
|
||||
}
|
||||
#endif //CONFIG_PM_ENABLE
|
||||
|
||||
@@ -75,7 +75,7 @@ static esp_err_t i2s_tdm_calculate_clock(i2s_chan_handle_t handle, const i2s_tdm
|
||||
clk_info->mclk_div = clk_info->sclk / clk_info->mclk;
|
||||
|
||||
/* Check if the configuration is correct. Use float for check in case the mclk division might be carried up in the fine division calculation */
|
||||
ESP_RETURN_ON_FALSE((float)clk_info->sclk > clk_info->mclk * min_mclk_div, ESP_ERR_INVALID_ARG, TAG, "sample rate is too large");
|
||||
ESP_RETURN_ON_FALSE((float)clk_info->sclk > clk_info->mclk * min_mclk_div, ESP_ERR_INVALID_ARG, TAG, "sample rate is too large, sclk: %"PRIu32" Hz, mclk: %"PRIu32" Hz", clk_info->sclk, clk_info->mclk);
|
||||
ESP_RETURN_ON_FALSE(clk_info->mclk_div < I2S_LL_CLK_FRAC_DIV_N_MAX, ESP_ERR_INVALID_ARG, TAG, "sample rate is too small");
|
||||
|
||||
return ESP_OK;
|
||||
@@ -316,6 +316,10 @@ esp_err_t i2s_channel_init_tdm_mode(i2s_chan_handle_t handle, const i2s_tdm_conf
|
||||
ESP_GOTO_ON_FALSE(handle->mode_info, ESP_ERR_NO_MEM, err, TAG, "no memory for storing the configurations");
|
||||
/* Try to constitute full-duplex mode if the TDM configuration is totally same as another channel */
|
||||
ESP_GOTO_ON_ERROR(s_i2s_channel_try_to_constitute_tdm_duplex(handle, tdm_cfg), err, TAG, "failed to constitute full-duplex mode");
|
||||
/* DMA alignment must be known before allocating buffers in set_slot */
|
||||
if (I2S_CHANNEL_USES_DMA(handle)) {
|
||||
ESP_GOTO_ON_ERROR(i2s_prepare_dma(handle), err, TAG, "prepare dma failed");
|
||||
}
|
||||
/* i2s_set_tdm_slot should be called before i2s_set_tdm_clock while initializing, because clock is relay on the slot */
|
||||
ESP_GOTO_ON_ERROR(i2s_tdm_set_slot(handle, &tdm_cfg->slot_cfg), err, TAG, "initialize channel failed while setting slot");
|
||||
ESP_GOTO_ON_ERROR(i2s_tdm_set_clock(handle, &tdm_cfg->clk_cfg), err, TAG, "initialize channel failed while setting clock");
|
||||
@@ -348,6 +352,10 @@ esp_err_t i2s_channel_init_tdm_mode(i2s_chan_handle_t handle, const i2s_tdm_conf
|
||||
pm_type = ESP_PM_NO_LIGHT_SLEEP;
|
||||
}
|
||||
#endif // SOC_I2S_SUPPORTS_APLL
|
||||
if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) {
|
||||
// use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS
|
||||
pm_type = ESP_PM_CPU_FREQ_MAX;
|
||||
}
|
||||
ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed");
|
||||
#endif
|
||||
|
||||
@@ -400,6 +408,10 @@ esp_err_t i2s_channel_reconfig_tdm_clock(i2s_chan_handle_t handle, const i2s_tdm
|
||||
pm_type = ESP_PM_NO_LIGHT_SLEEP;
|
||||
}
|
||||
#endif // SOC_I2S_SUPPORTS_APLL
|
||||
if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) {
|
||||
// use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS
|
||||
pm_type = ESP_PM_CPU_FREQ_MAX;
|
||||
}
|
||||
ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed");
|
||||
}
|
||||
#endif //CONFIG_PM_ENABLE
|
||||
|
||||
@@ -26,6 +26,7 @@ extern "C" {
|
||||
.dma_frame_num = 240, \
|
||||
.auto_clear_after_cb = false, \
|
||||
.auto_clear_before_cb = false, \
|
||||
.dma_buffer_in_psram = false, \
|
||||
.allow_pd = false, \
|
||||
.intr_priority = 0, \
|
||||
.tx_destination = I2S_DESTINATION_DMA, \
|
||||
@@ -39,6 +40,8 @@ extern "C" {
|
||||
* @note The callbacks are all running under ISR environment
|
||||
* @note When CONFIG_I2S_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
|
||||
* The variables used in the function should be in the SRAM as well.
|
||||
* @note When CONFIG_I2S_ISR_IRAM_SAFE is enabled and the DMA buffers are in PSRAM, the callback must not access
|
||||
* the DMA buffer while the cache is disabled.
|
||||
*/
|
||||
typedef struct {
|
||||
i2s_isr_callback_t on_recv; /**< Callback of data received event, only for RX channel
|
||||
@@ -86,6 +89,10 @@ typedef struct {
|
||||
int intr_priority; /*!< I2S interrupt priority, range [0, 7], if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3) */
|
||||
i2s_destination_t tx_destination; /*!< TX data path: DMA (memory) or Bluetooth (see `i2s_destination_t`). I2S0 only when set to `I2S_DESTINATION_BT`. Immutable after `i2s_new_channel`. */
|
||||
i2s_destination_t rx_destination; /*!< RX data path: DMA (memory) or Bluetooth. Same constraints as `tx_destination`. */
|
||||
bool dma_buffer_in_psram; /*!< Allocate the driver-owned DMA buffers in PSRAM.
|
||||
* The DMA descriptors are always allocated in internal RAM.
|
||||
* If PSRAM DMA is unsupported or allocation fails, channel initialization fails without falling back to internal RAM.
|
||||
*/
|
||||
} i2s_chan_config_t;
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
@@ -17,6 +19,7 @@
|
||||
#include "esp_private/gpio.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "unity.h"
|
||||
#include "math.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
@@ -45,12 +48,11 @@
|
||||
|
||||
#include "../../test_inc/test_i2s.h"
|
||||
|
||||
#if I2S_LL_GET(INST_NUM) > 1 && !CONFIG_ESP32P4_SELECTS_REV_LESS_V3
|
||||
#define I2S_TEST_MODE_SLAVE_TO_MASTER 0
|
||||
#define I2S_TEST_MODE_MASTER_TO_SLAVE 1
|
||||
#define I2S_TEST_MODE_LOOPBACK 2
|
||||
|
||||
// mode: 0, master rx, slave tx. mode: 1, master tx, slave rx. mode: 2, master tx rx loop-back
|
||||
// Since ESP32-S2 has only one I2S, only loop back test can be tested.
|
||||
// mode: 0, master rx, slave tx. mode: 1, master tx, slave rx.
|
||||
static void i2s_test_io_config(int mode)
|
||||
{
|
||||
// Connect internal signals using IO matrix.
|
||||
@@ -88,18 +90,13 @@ static void i2s_test_io_config(int mode)
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case I2S_TEST_MODE_LOOPBACK: {
|
||||
esp_rom_gpio_connect_out_signal(DATA_OUT_IO, i2s_periph_signal[0].data_out_sig, 0, 0);
|
||||
esp_rom_gpio_connect_in_signal(DATA_OUT_IO, i2s_periph_signal[0].data_in_sig, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
default: {
|
||||
TEST_FAIL_MESSAGE("error: mode not supported");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void i2s_read_write_test(i2s_chan_handle_t tx_chan, i2s_chan_handle_t rx_chan)
|
||||
{
|
||||
@@ -879,41 +876,270 @@ TEST_CASE("I2S_memory_leak_test", "[i2s]")
|
||||
printf("\r\nHeap size after: %"PRIu32"\n", esp_get_free_heap_size());
|
||||
}
|
||||
|
||||
TEST_CASE("I2S_loopback_test", "[i2s]")
|
||||
#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM
|
||||
static IRAM_ATTR bool i2s_record_external_ram_buf(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx)
|
||||
{
|
||||
*((volatile bool *)user_ctx) |= esp_ptr_external_ram(event->dma_buf);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Re-enable after disable checks that the circular DMA link restarts from its head. */
|
||||
static void i2s_std_master_loopback_test(bool dma_buffer_in_psram)
|
||||
{
|
||||
i2s_chan_handle_t tx_handle;
|
||||
i2s_chan_handle_t rx_handle;
|
||||
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||
chan_cfg.dma_buffer_in_psram = dma_buffer_in_psram;
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
|
||||
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(SAMPLE_BITS, I2S_SLOT_MODE_STEREO),
|
||||
.gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN,
|
||||
};
|
||||
std_cfg.gpio_cfg.din = std_cfg.gpio_cfg.dout;
|
||||
#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM
|
||||
volatile bool tx_in_ext = false;
|
||||
volatile bool rx_in_ext = false;
|
||||
#endif
|
||||
|
||||
TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle));
|
||||
TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
|
||||
TEST_ESP_OK(i2s_channel_init_std_mode(rx_handle, &std_cfg));
|
||||
i2s_test_io_config(I2S_TEST_MODE_LOOPBACK);
|
||||
#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM
|
||||
if (dma_buffer_in_psram) {
|
||||
i2s_event_callbacks_t tx_cbs = {
|
||||
.on_sent = i2s_record_external_ram_buf,
|
||||
};
|
||||
i2s_event_callbacks_t rx_cbs = {
|
||||
.on_recv = i2s_record_external_ram_buf,
|
||||
};
|
||||
TEST_ESP_OK(i2s_channel_register_event_callback(tx_handle, &tx_cbs, (void *)&tx_in_ext));
|
||||
TEST_ESP_OK(i2s_channel_register_event_callback(rx_handle, &rx_cbs, (void *)&rx_in_ext));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_ESP_OK(i2s_channel_enable(tx_handle));
|
||||
TEST_ESP_OK(i2s_channel_enable(rx_handle));
|
||||
|
||||
i2s_read_write_test(tx_handle, rx_handle);
|
||||
|
||||
TEST_ESP_OK(i2s_channel_disable(tx_handle));
|
||||
TEST_ESP_OK(i2s_channel_disable(rx_handle));
|
||||
|
||||
/* Verify the circular DMA link restarts from its head after both channels are re-enabled. */
|
||||
TEST_ESP_OK(i2s_channel_enable(tx_handle));
|
||||
TEST_ESP_OK(i2s_channel_enable(rx_handle));
|
||||
i2s_read_write_test(tx_handle, rx_handle);
|
||||
TEST_ESP_OK(i2s_channel_disable(tx_handle));
|
||||
TEST_ESP_OK(i2s_channel_disable(rx_handle));
|
||||
|
||||
TEST_ESP_OK(i2s_channel_enable(tx_handle));
|
||||
TEST_ESP_OK(i2s_channel_enable(rx_handle));
|
||||
i2s_read_write_test(tx_handle, rx_handle);
|
||||
TEST_ESP_OK(i2s_channel_disable(tx_handle));
|
||||
TEST_ESP_OK(i2s_channel_disable(rx_handle));
|
||||
|
||||
#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM
|
||||
if (dma_buffer_in_psram) {
|
||||
TEST_ASSERT_TRUE(tx_in_ext);
|
||||
TEST_ASSERT_TRUE(rx_in_ext);
|
||||
}
|
||||
#endif
|
||||
TEST_ESP_OK(i2s_del_channel(tx_handle));
|
||||
TEST_ESP_OK(i2s_del_channel(rx_handle));
|
||||
}
|
||||
|
||||
TEST_CASE("I2S_loopback_test", "[i2s]")
|
||||
{
|
||||
i2s_std_master_loopback_test(false);
|
||||
|
||||
#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM
|
||||
i2s_std_master_loopback_test(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !(SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM)
|
||||
TEST_CASE("I2S_psram_dma_buffer_rejected_when_unavailable", "[i2s]")
|
||||
{
|
||||
i2s_chan_handle_t tx_handle = NULL;
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
|
||||
chan_cfg.dma_buffer_in_psram = true;
|
||||
TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, i2s_new_channel(&chan_cfg, &tx_handle, NULL));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_I2S_ISR_IRAM_SAFE && SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM
|
||||
TEST_CASE("I2S_psram_auto_clear_rejected_with_iram_safe", "[i2s]")
|
||||
{
|
||||
i2s_chan_handle_t tx_handle = NULL;
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
|
||||
chan_cfg.dma_buffer_in_psram = true;
|
||||
|
||||
chan_cfg.auto_clear_before_cb = true;
|
||||
chan_cfg.auto_clear_after_cb = false;
|
||||
TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, i2s_new_channel(&chan_cfg, &tx_handle, NULL));
|
||||
|
||||
chan_cfg.auto_clear_before_cb = false;
|
||||
chan_cfg.auto_clear_after_cb = true;
|
||||
TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, i2s_new_channel(&chan_cfg, &tx_handle, NULL));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SOC_I2S_SUPPORTS_TDM
|
||||
#define I2S_TDM_INTEGRITY_MAGIC 0xA55A5AA5UL
|
||||
#define I2S_TDM_INTEGRITY_DESC_NUM 6
|
||||
#define I2S_TDM_INTEGRITY_FRAME_NUM 32
|
||||
#define I2S_TDM_INTEGRITY_VERIFY_FRAMES 1024
|
||||
#define I2S_TDM_INTEGRITY_PREAMBLE_FRAMES 200
|
||||
#define I2S_TDM_INTEGRITY_SEND_FRAMES (I2S_TDM_INTEGRITY_PREAMBLE_FRAMES + I2S_TDM_INTEGRITY_VERIFY_FRAMES)
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t seq;
|
||||
uint32_t seq_inv;
|
||||
uint32_t checksum;
|
||||
} i2s_tdm_integrity_frame_t;
|
||||
|
||||
_Static_assert(sizeof(i2s_tdm_integrity_frame_t) == 16, "TDM integrity frame must be 16 bytes");
|
||||
|
||||
static uint32_t i2s_tdm_integrity_checksum(uint32_t magic, uint32_t seq, uint32_t seq_inv)
|
||||
{
|
||||
/* Mix seq before combining with seq_inv so seq ^ ~seq cannot collapse to a constant. */
|
||||
uint32_t c = magic ^ (seq * 0x9E3779B1UL);
|
||||
c = (c << 7) | (c >> 25);
|
||||
return c ^ seq_inv ^ 0x13579BDFUL;
|
||||
}
|
||||
|
||||
static void i2s_tdm_integrity_fill_frame(i2s_tdm_integrity_frame_t *frame, uint32_t seq)
|
||||
{
|
||||
frame->magic = I2S_TDM_INTEGRITY_MAGIC;
|
||||
frame->seq = seq;
|
||||
frame->seq_inv = ~seq;
|
||||
frame->checksum = i2s_tdm_integrity_checksum(frame->magic, frame->seq, frame->seq_inv);
|
||||
}
|
||||
|
||||
static bool i2s_tdm_integrity_frame_valid(const i2s_tdm_integrity_frame_t *frame)
|
||||
{
|
||||
return frame->magic == I2S_TDM_INTEGRITY_MAGIC &&
|
||||
frame->seq_inv == ~frame->seq &&
|
||||
frame->checksum == i2s_tdm_integrity_checksum(frame->magic, frame->seq, frame->seq_inv);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
i2s_chan_handle_t tx_handle;
|
||||
const uint8_t *buf;
|
||||
size_t len;
|
||||
volatile esp_err_t ret;
|
||||
volatile bool done;
|
||||
} i2s_tdm_integrity_writer_ctx_t;
|
||||
|
||||
static void i2s_tdm_integrity_writer_task(void *args)
|
||||
{
|
||||
i2s_tdm_integrity_writer_ctx_t *ctx = (i2s_tdm_integrity_writer_ctx_t *)args;
|
||||
size_t bytes_written = 0;
|
||||
ctx->ret = i2s_channel_write(ctx->tx_handle, ctx->buf, ctx->len, &bytes_written, 2000);
|
||||
if (ctx->ret == ESP_OK && bytes_written != ctx->len) {
|
||||
ctx->ret = ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
ctx->done = true;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void i2s_tdm_integrity_loopback_test(bool dma_buffer_in_psram)
|
||||
{
|
||||
i2s_chan_handle_t tx_handle = NULL;
|
||||
i2s_chan_handle_t rx_handle = NULL;
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||
chan_cfg.dma_desc_num = I2S_TDM_INTEGRITY_DESC_NUM;
|
||||
chan_cfg.dma_frame_num = I2S_TDM_INTEGRITY_FRAME_NUM;
|
||||
chan_cfg.dma_buffer_in_psram = dma_buffer_in_psram;
|
||||
|
||||
i2s_tdm_config_t tdm_cfg = {
|
||||
.clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
|
||||
.slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO, 0x0F),
|
||||
.gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN,
|
||||
};
|
||||
tdm_cfg.gpio_cfg.din = tdm_cfg.gpio_cfg.dout;
|
||||
/* Full-duplex RX is forced to slave; default mclk/bclk=3 is below the driver's measured minimum of 4. */
|
||||
tdm_cfg.clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_512;
|
||||
#if CONFIG_IDF_TARGET_ESP32S31
|
||||
// S31 uses XTAL as default clock source, which cannot support high sample rate in this test
|
||||
tdm_cfg.clk_cfg.clk_src = I2S_CLK_SRC_APLL;
|
||||
#endif
|
||||
const size_t send_bytes = I2S_TDM_INTEGRITY_SEND_FRAMES * sizeof(i2s_tdm_integrity_frame_t);
|
||||
/* Read less than we write so TX is still feeding the bus while RX finishes. */
|
||||
const size_t recv_frame_budget = I2S_TDM_INTEGRITY_PREAMBLE_FRAMES + I2S_TDM_INTEGRITY_VERIFY_FRAMES;
|
||||
const size_t recv_bytes = recv_frame_budget * sizeof(i2s_tdm_integrity_frame_t);
|
||||
i2s_tdm_integrity_frame_t *send_frames = calloc(I2S_TDM_INTEGRITY_SEND_FRAMES, sizeof(i2s_tdm_integrity_frame_t));
|
||||
uint8_t *recv_buf = calloc(1, recv_bytes);
|
||||
TEST_ASSERT_NOT_NULL(send_frames);
|
||||
TEST_ASSERT_NOT_NULL(recv_buf);
|
||||
|
||||
for (uint32_t i = 0; i < I2S_TDM_INTEGRITY_SEND_FRAMES; i++) {
|
||||
i2s_tdm_integrity_fill_frame(&send_frames[i], i);
|
||||
}
|
||||
|
||||
TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle));
|
||||
TEST_ESP_OK(i2s_channel_init_tdm_mode(tx_handle, &tdm_cfg));
|
||||
TEST_ESP_OK(i2s_channel_init_tdm_mode(rx_handle, &tdm_cfg));
|
||||
|
||||
TEST_ESP_OK(i2s_channel_enable(tx_handle));
|
||||
TEST_ESP_OK(i2s_channel_enable(rx_handle));
|
||||
|
||||
i2s_tdm_integrity_writer_ctx_t writer_ctx = {
|
||||
.tx_handle = tx_handle,
|
||||
.buf = (const uint8_t *)send_frames,
|
||||
.len = send_bytes,
|
||||
.ret = ESP_FAIL,
|
||||
.done = false,
|
||||
};
|
||||
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(i2s_tdm_integrity_writer_task, "i2s_tdm_wr", 4096, &writer_ctx, 5, NULL));
|
||||
|
||||
size_t bytes_read = 0;
|
||||
TEST_ESP_OK(i2s_channel_read(rx_handle, recv_buf, recv_bytes, &bytes_read, 2000));
|
||||
TEST_ASSERT_EQUAL(recv_bytes, bytes_read);
|
||||
|
||||
while (!writer_ctx.done) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
/* Writer already called vTaskDelete; yield so idle can reclaim its TCB/stack. */
|
||||
vTaskDelay(1);
|
||||
TEST_ESP_OK(writer_ctx.ret);
|
||||
|
||||
TEST_ESP_OK(i2s_channel_disable(tx_handle));
|
||||
TEST_ESP_OK(i2s_channel_disable(rx_handle));
|
||||
TEST_ESP_OK(i2s_del_channel(tx_handle));
|
||||
TEST_ESP_OK(i2s_del_channel(rx_handle));
|
||||
|
||||
const i2s_tdm_integrity_frame_t *recv_frames = (const i2s_tdm_integrity_frame_t *)recv_buf;
|
||||
const size_t recv_frame_count = bytes_read / sizeof(i2s_tdm_integrity_frame_t);
|
||||
bool synced = false;
|
||||
uint32_t expected_seq = 0;
|
||||
uint32_t verified = 0;
|
||||
|
||||
for (size_t i = 0; i + 1 < recv_frame_count && verified < I2S_TDM_INTEGRITY_VERIFY_FRAMES; i++) {
|
||||
if (!synced) {
|
||||
if (i2s_tdm_integrity_frame_valid(&recv_frames[i])) {
|
||||
expected_seq = recv_frames[i].seq;
|
||||
synced = true;
|
||||
} else { // skip non-synced preamble frames
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(i2s_tdm_integrity_frame_valid(&recv_frames[i]),
|
||||
"corrupted TDM integrity frame after sync");
|
||||
TEST_ASSERT_EQUAL_UINT32(expected_seq, recv_frames[i].seq);
|
||||
expected_seq++;
|
||||
verified++;
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(synced, "failed to find TDM integrity sync frame\n");
|
||||
TEST_ASSERT_EQUAL_UINT32(I2S_TDM_INTEGRITY_VERIFY_FRAMES, verified);
|
||||
free(send_frames);
|
||||
free(recv_buf);
|
||||
}
|
||||
|
||||
TEST_CASE("I2S_tdm_integrity_loopback_test", "[i2s]")
|
||||
{
|
||||
i2s_tdm_integrity_loopback_test(false);
|
||||
#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM
|
||||
i2s_tdm_integrity_loopback_test(true);
|
||||
#endif
|
||||
}
|
||||
#endif // SOC_I2S_SUPPORTS_TDM
|
||||
|
||||
#if I2S_LL_GET(INST_NUM) > 1 && !CONFIG_ESP32P4_SELECTS_REV_LESS_V3
|
||||
TEST_CASE("I2S_master_write_slave_read_test", "[i2s]")
|
||||
{
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0
|
||||
@@ -27,6 +27,8 @@
|
||||
#define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat
|
||||
#define I2S_LL_BUS_WIDTH 24
|
||||
#define I2S_LL_INST_NUM 1
|
||||
#define I2S_LL_SUPPORT_DMA_EXT_MEM 1
|
||||
#define I2S_LL_DMA_EXT_MEM_ALIGNMENT 16
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -84,6 +86,43 @@ static inline void i2s_ll_dma_enable_eof_on_fifo_empty(i2s_dev_t *hw, bool en)
|
||||
hw->lc_conf.out_eof_mode = en;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the external-memory block size used by the I2S DMA
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address
|
||||
* @param block_size External-memory block size in bytes, valid values are 16, 32 and 64
|
||||
*/
|
||||
static inline void i2s_ll_dma_set_ext_mem_block_size(i2s_dev_t *hw, uint32_t block_size)
|
||||
{
|
||||
HAL_ASSERT(block_size == 16 || block_size == 32 || block_size == 64);
|
||||
hw->lc_conf.ext_mem_bk_size = __builtin_ctz(block_size) - 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable DMA burst access for TX data and descriptors
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address
|
||||
* @param en Whether to enable burst access
|
||||
*/
|
||||
static inline void i2s_ll_dma_tx_enable_burst(i2s_dev_t *hw, bool en)
|
||||
{
|
||||
hw->lc_conf.out_data_burst_en = en;
|
||||
hw->lc_conf.outdscr_burst_en = en;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable DMA burst access for RX descriptors
|
||||
*
|
||||
* @note ESP32-S2 does not provide a burst-enable bit for RX data.
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address
|
||||
* @param en Whether to enable descriptor burst access
|
||||
*/
|
||||
static inline void i2s_ll_dma_rx_enable_burst(i2s_dev_t *hw, bool en)
|
||||
{
|
||||
hw->lc_conf.indscr_burst_en = en;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable the bus clock for I2S module
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user