mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +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
|
||||
*
|
||||
|
||||
@@ -291,6 +291,10 @@ The data transport of the I2S peripheral, including sending and receiving, is re
|
||||
|
||||
Both :cpp:func:`i2s_channel_write` and :cpp:func:`i2s_channel_read` are blocking functions. They keeps waiting until the whole source buffer is sent or the whole destination buffer is loaded, unless they exceed the max blocking time, where the error code ``ESP_ERR_TIMEOUT`` returns. To send or receive data asynchronously, callbacks can be registered by :cpp:func:`i2s_channel_register_event_callback`. Users are able to access the DMA buffer directly in the callback function instead of transmitting or receiving by the two blocking functions. However, please be aware that it is an interrupt callback, so do not add complex logic, run floating operation, or call non-reentrant functions in the callback.
|
||||
|
||||
.. only:: SOC_PSRAM_DMA_CAPABLE
|
||||
|
||||
To reduce internal RAM usage, set :cpp:member:`i2s_chan_config_t::dma_buffer_in_psram` to allocate the driver-owned DMA buffers in PSRAM. DMA descriptors remain in internal RAM. The driver returns an error if PSRAM DMA is unavailable or the allocation fails; it does not fall back to internal RAM. When :ref:`CONFIG_I2S_ISR_IRAM_SAFE` is enabled, TX auto-clear cannot be used with PSRAM DMA buffers, and callbacks must not access the DMA buffer while the cache is disabled.
|
||||
|
||||
.. only:: SOC_I2S_SUPPORTS_BT_DEST
|
||||
|
||||
On {IDF_TARGET_NAME}, when calling :cpp:func:`i2s_new_channel`, you can select the data path for TX and RX separately via :cpp:member:`i2s_chan_config_t::tx_destination` and :cpp:member:`i2s_chan_config_t::rx_destination`. For each direction you can choose either **DMA** or **Bluetooth**:
|
||||
|
||||
@@ -291,6 +291,12 @@ I2S 的数据传输(包括数据发送和接收)由 DMA 实现。在传输
|
||||
|
||||
:cpp:func:`i2s_channel_write` 和 :cpp:func:`i2s_channel_read` 都是阻塞函数,在源缓冲区的数据发送完毕前,或是整个目标缓冲区都被加载数据占用时,它们会一直保持等待状态。在等待时间达到最大阻塞时间时,返回 ``ESP_ERR_TIMEOUT`` 错误。要实现异步发送或接收数据,可以通过 :cpp:func:`i2s_channel_register_event_callback` 注册回调,随即便可在回调函数中直接访问 DMA 缓冲区,无需通过这两个阻塞函数来发送或接收数据。但请注意,该回调是一个中断回调,不要在该回调中添加复杂的逻辑、进行浮点运算或调用不可重入函数。
|
||||
|
||||
:cpp:member:`i2s_chan_config_t::dma_burst_size` 用于设置 DMA 突发传输大小(字节)。设为 ``0`` (:c:macro:`I2S_CHANNEL_DEFAULT_CONFIG` 的默认值)时,驱动使用默认值 32 字节。非 0 值必须是芯片 GDMA 支持的 2 的幂。在不支持配置 DMA 突发大小的芯片上,该字段会被忽略。
|
||||
|
||||
.. only:: SOC_PSRAM_DMA_CAPABLE
|
||||
|
||||
为减少内部 RAM 占用,可设置 :cpp:member:`i2s_chan_config_t::dma_buffer_in_psram`,将驱动管理的 DMA 缓冲区分配到 PSRAM 中。DMA 描述符仍分配在内部 RAM 中。如果 PSRAM DMA 不可用或分配失败,驱动将返回错误,而不会回退到内部 RAM。启用 :ref:`CONFIG_I2S_ISR_IRAM_SAFE` 时,TX 自动清零功能不能与 PSRAM DMA 缓冲区同时使用,且在 cache 被禁用期间,回调函数不得访问 DMA 缓冲区。
|
||||
|
||||
.. only:: SOC_I2S_SUPPORTS_BT_DEST
|
||||
|
||||
在 {IDF_TARGET_NAME} 上,可在调用 :cpp:func:`i2s_new_channel` 时通过 :cpp:member:`i2s_chan_config_t::tx_destination` 与 :cpp:member:`i2s_chan_config_t::rx_destination` 分别为 TX 与 RX 方向选择数据路径;每个方向可在 **DMA** 与 **Bluetooth** 二者中择一:
|
||||
|
||||
Reference in New Issue
Block a user