From 7397b3f75022e1902e34a542d0086bb5efd5e73b Mon Sep 17 00:00:00 2001 From: laokaiyao Date: Thu, 5 Jan 2023 20:14:43 +0800 Subject: [PATCH] i2s: support preload data Closes https://github.com/espressif/esp-idf/issues/8471 --- components/driver/i2s/i2s_common.c | 60 ++++++++++++++++++- .../driver/i2s/include/driver/i2s_common.h | 24 +++++++- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/components/driver/i2s/i2s_common.c b/components/driver/i2s/i2s_common.c index 6134fc8b39..705efcf664 100644 --- a/components/driver/i2s/i2s_common.c +++ b/components/driver/i2s/i2s_common.c @@ -285,6 +285,8 @@ 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.rw_pos = 0; + new_chan->dma.curr_ptr = NULL; new_chan->start = NULL; new_chan->stop = NULL; @@ -1014,8 +1016,6 @@ esp_err_t i2s_channel_enable(i2s_chan_handle_t handle) #if CONFIG_PM_ENABLE esp_pm_lock_acquire(handle->pm_lock); #endif - handle->dma.curr_ptr = NULL; - handle->dma.rw_pos = 0; handle->start(handle); handle->state = I2S_CHAN_STATE_RUNNING; /* Reset queue */ @@ -1043,6 +1043,8 @@ esp_err_t i2s_channel_disable(i2s_chan_handle_t handle) handle->state = I2S_CHAN_STATE_READY; /* Waiting for reading/wrinting operation quit */ xSemaphoreTake(handle->binary, portMAX_DELAY); + handle->dma.curr_ptr = NULL; + handle->dma.rw_pos = 0; handle->stop(handle); #if CONFIG_PM_ENABLE esp_pm_lock_release(handle->pm_lock); @@ -1056,6 +1058,60 @@ err: return ret; } +esp_err_t i2s_channel_preload_writing_data(i2s_chan_handle_t handle, const void *src, size_t size, size_t *bytes_loaded) +{ + I2S_NULL_POINTER_CHECK(TAG, handle); + ESP_RETURN_ON_FALSE(handle->dir == I2S_DIR_TX, ESP_ERR_INVALID_ARG, TAG, "this channel is not tx channel"); + ESP_RETURN_ON_FALSE(handle->state == I2S_CHAN_STATE_READY, ESP_ERR_INVALID_STATE, TAG, "data can only be preloaded when the channel is READY"); + + uint8_t *data_ptr = (uint8_t *)src; + size_t remain_bytes = size; + size_t total_loaded_bytes = 0; + + xSemaphoreTake(handle->mutex, portMAX_DELAY); + + /* The pre-load data will be loaded from the first descriptor */ + if (handle->dma.curr_ptr == NULL) { + handle->dma.curr_ptr = handle->dma.desc[0]; + handle->dma.rw_pos = 0; + } + lldesc_t *desc_ptr = (lldesc_t *)handle->dma.curr_ptr; + + /* Loop until no bytes in source buff remain or the descriptors are full */ + while (remain_bytes) { + size_t bytes_can_load = remain_bytes > (handle->dma.buf_size - handle->dma.rw_pos) ? + (handle->dma.buf_size - handle->dma.rw_pos) : remain_bytes; + /* When all the descriptors has loaded data, no more bytes can be loaded, break directly */ + if (bytes_can_load == 0) { + break; + } + /* Load the data from the last loaded position */ + memcpy((uint8_t *)(desc_ptr->buf + handle->dma.rw_pos), data_ptr, bytes_can_load); + 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 + handle->dma.rw_pos += bytes_can_load; // Move forward the dma buffer position + /* When the current position reach the end of the dma buffer */ + if (handle->dma.rw_pos == handle->dma.buf_size) { + /* If the next descriptor is not the first descriptor, keep load to the first descriptor + * otherwise all descriptor has been loaded, break directly, the dma buffer position + * will remain at the end of the last dma buffer */ + if (desc_ptr->empty != (uint32_t)handle->dma.desc[0]) { + desc_ptr = (lldesc_t *)desc_ptr->empty; + handle->dma.curr_ptr = (void *)desc_ptr; + handle->dma.rw_pos = 0; + } else { + break; + } + } + } + *bytes_loaded = total_loaded_bytes; + + xSemaphoreGive(handle->mutex); + + return ESP_OK; +} + esp_err_t i2s_channel_write(i2s_chan_handle_t handle, const void *src, size_t size, size_t *bytes_written, uint32_t timeout_ms) { I2S_NULL_POINTER_CHECK(TAG, handle); diff --git a/components/driver/i2s/include/driver/i2s_common.h b/components/driver/i2s/include/driver/i2s_common.h index d12e7763bf..31c1c4e1cd 100644 --- a/components/driver/i2s/include/driver/i2s_common.h +++ b/components/driver/i2s/include/driver/i2s_common.h @@ -142,7 +142,7 @@ esp_err_t i2s_channel_enable(i2s_chan_handle_t handle); /** * @brief Disable the i2s channel - * @note Only allowed to be called when the channel state is READY / RUNNING, (i.e., channel has been initialized) + * @note Only allowed to be called when the channel state is RUNNING, (i.e., channel has been started) * the channel will enter READY state once it is disabled successfully. * @note Disable the channel can stop the I2S communication on hardware. It will stop bclk and ws signal but not mclk signal * @@ -154,6 +154,28 @@ esp_err_t i2s_channel_enable(i2s_chan_handle_t handle); */ esp_err_t i2s_channel_disable(i2s_chan_handle_t handle); +/** + * @brief Preload the data into TX DMA buffer + * @note Only allowed to be called when the channel state is READY, (i.e., channel has been initialized, but not started) + * @note As the initial DMA buffer has no data inside, it will transmit the empty buffer after enabled the channel, + * this function is used to preload the data into the DMA buffer, so that the valid data can be transmit immediately + * when the channel is enabled. + * @note This function can be called multiple times before enabling the channel, the buffer that loaded later will be concatenated + * behind the former loaded buffer. But when all the DMA buffers have been loaded, no more data can be preload then, please + * check the `bytes_loaded` parameter to see how many bytes are loaded successfully, when the `bytes_loaded` is smaller than + * the `size`, it means the DMA buffers are full. + * + * @param[in] handle I2S TX channel handler + * @param[in] src The pointer of the source buffer to be loaded + * @param[in] size The source buffer size + * @param[out] bytes_loaded The bytes that successfully been loaded into the TX DMA buffer + * @return + * - ESP_OK Load data successful + * - ESP_ERR_INVALID_ARG NULL pointer or not TX direction + * - ESP_ERR_INVALID_STATE This channel has not stated + */ +esp_err_t i2s_channel_preload_writing_data(i2s_chan_handle_t handle, const void *src, size_t size, size_t *bytes_loaded); + /** * @brief I2S write data * @note Only allowed to be called when the channel state is RUNNING, (i.e., tx channel has been started and is not writing now)