diff --git a/components/esp_driver_dac/dac_continuous.c b/components/esp_driver_dac/dac_continuous.c index 464e989f807..e242afb944d 100644 --- a/components/esp_driver_dac/dac_continuous.c +++ b/components/esp_driver_dac/dac_continuous.c @@ -28,22 +28,15 @@ #define DAC_DMA_MAX_BUF_SIZE 4092 // Max DMA buffer size is 4095 but better to align with 4 bytes, so set 4092 here -#if CONFIG_DAC_ISR_IRAM_SAFE -#define DAC_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED) -#else -#define DAC_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED) -#endif - #define DAC_DMA_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA) struct dac_continuous_s { dac_continuous_config_t cfg; - intr_handle_t intr_handle; /* Interrupt handle */ #if CONFIG_PM_ENABLE esp_pm_lock_handle_t pm_lock; #endif - dac_event_callbacks_t cbs; /* Interrupt callbacks */ + dac_event_callbacks_t cbs; /* User event callbacks */ void *user_data; uint32_t cur_index; /* Index of the DMA descriptor that is currently being used by DMA. */ @@ -139,62 +132,67 @@ err: return ret; } -static void IRAM_ATTR s_dac_default_intr_handler(void *arg) +bool dac_dma_done_callback(void *ctx) { - dac_continuous_handle_t handle = arg; - BaseType_t need_awoke = pdFALSE; - BaseType_t tmp = pdFALSE; + dac_continuous_handle_t handle = ctx; + bool need_awoke = false; - uint32_t intr_mask = dac_dma_periph_intr_get_mask(); dac_continuous_fsm_t fsm = atomic_load(&s_dac_cont_fsm); - - if (intr_mask & DAC_DMA_DONE_INTR) { - if (fsm == DAC_CONT_FSM_SYNC || fsm == DAC_CONT_FSM_SYNC_WAIT) { - /* Sync writing mode: Recycle the descriptor */ - xQueueSendFromISR(handle->free_desc_queue, &handle->cur_index, &tmp); - need_awoke |= tmp; - } - - if (handle->cbs.on_convert_done) { - dac_event_data_t evt_data = { - .buf = handle->bufs[handle->cur_index], - .buf_size = handle->cfg.buf_size, - .write_bytes = gdma_link_get_length(handle->link, handle->cur_index), - }; - need_awoke |= handle->cbs.on_convert_done(handle, &evt_data, handle->user_data); - } - - handle->cur_index = (handle->cur_index + 1) % handle->used_desc_num; + if (fsm == DAC_CONT_FSM_SYNC || fsm == DAC_CONT_FSM_SYNC_WAIT) { + /* Sync writing mode: Recycle the descriptor */ + BaseType_t tmp = pdFALSE; + xQueueSendFromISR(handle->free_desc_queue, &handle->cur_index, &tmp); + need_awoke |= (tmp == pdTRUE); } - if (intr_mask & DAC_DMA_TEOF_INTR) { - /** - * Total EOF interrupt: DMA has reached the end of a descriptor chain (NULL next pointer). - * This only occurs naturally in sync writing mode when all queued data has been transmitted. - */ - bool dma_restart = false; + + if (handle->cbs.on_convert_done) { + dac_event_data_t evt_data = { + .buf = handle->bufs[handle->cur_index], + .buf_size = handle->cfg.buf_size, + .write_bytes = gdma_link_get_length(handle->link, handle->cur_index), + }; + need_awoke |= handle->cbs.on_convert_done(handle, &evt_data, handle->user_data); + } + + handle->cur_index = (handle->cur_index + 1) % handle->used_desc_num; + return need_awoke; +} + +bool dac_dma_teof_callback(void *ctx) +{ + dac_continuous_handle_t handle = ctx; + bool need_awoke = false; + + /** + * Total EOF interrupt: DMA has reached the end of a descriptor chain (NULL next pointer). + * This only occurs naturally in sync writing mode when all queued data has been transmitted. + */ + bool dma_restart = false; #if SOC_IS(ESP32) - if (fsm == DAC_CONT_FSM_SYNC || fsm == DAC_CONT_FSM_SYNC_WAIT) { - /* Check for any remaining descriptors (ignored due to prefetching), and restart the DMA */ - portENTER_CRITICAL_ISR(&handle->dma_lock); - if (!handle->dma_running) { - /* Stop already in progress, do not restart */ - } else if (gdma_link_check_end(handle->link, (int)handle->cur_index - 1) == false) { - dac_dma_periph_trans_start(gdma_link_get_item_addr(handle->link, handle->cur_index)); - dma_restart = true; - } else { - handle->dma_running = false; - } - portEXIT_CRITICAL_ISR(&handle->dma_lock); + /** + * Due to a hardware limitation affecting the ESP32 I2S DMA append() operation, dac_continuous_write() + * uses start() to chain subsequent transfers. As a result, descriptor prefetching can cause issues. + */ + dac_continuous_fsm_t fsm = atomic_load(&s_dac_cont_fsm); + if (fsm == DAC_CONT_FSM_SYNC || fsm == DAC_CONT_FSM_SYNC_WAIT) { + /* Check for any remaining descriptors (ignored due to prefetching), and restart the DMA */ + portENTER_CRITICAL_ISR(&handle->dma_lock); + if (!handle->dma_running) { + /* Stop already in progress, do not restart */ + } else if (gdma_link_check_end(handle->link, (int)handle->cur_index - 1) == false) { + dac_priv_dma_trans_start(gdma_link_get_item_addr(handle->link, handle->cur_index)); + dma_restart = true; + } else { + handle->dma_running = false; } + portEXIT_CRITICAL_ISR(&handle->dma_lock); + } #endif - if (!dma_restart && handle->cbs.on_stop) { - need_awoke |= handle->cbs.on_stop(handle, NULL, handle->user_data); - } - } - if (need_awoke == pdTRUE) { - portYIELD_FROM_ISR(); + if (!dma_restart && handle->cbs.on_stop) { + need_awoke |= handle->cbs.on_stop(handle, NULL, handle->user_data); } + return need_awoke; } esp_err_t dac_continuous_new_channels(const dac_continuous_config_t *cont_cfg, dac_continuous_handle_t *ret_handle) @@ -203,6 +201,8 @@ esp_err_t dac_continuous_new_channels(const dac_continuous_config_t *cont_cfg, d DAC_NULL_POINTER_CHECK(cont_cfg); DAC_NULL_POINTER_CHECK(ret_handle); ESP_RETURN_ON_FALSE(IS_VALID_DAC_CHANNEL_MASK(cont_cfg->chan_mask) && cont_cfg->chan_mask, ESP_ERR_INVALID_ARG, TAG, "invalid dac channel mask"); + ESP_RETURN_ON_FALSE(cont_cfg->chan_mode != DAC_CHANNEL_MODE_ALTER || cont_cfg->chan_mask == DAC_CHANNEL_MASK_ALL, + ESP_ERR_INVALID_ARG, TAG, "alternate mode requires both DAC channels enabled"); ESP_RETURN_ON_FALSE(cont_cfg->desc_num > 1, ESP_ERR_INVALID_ARG, TAG, "at least two DMA descriptor needed"); ESP_RETURN_ON_FALSE(cont_cfg->buf_size > 0 && cont_cfg->buf_size % 2 == 0, ESP_ERR_INVALID_ARG, TAG, "buf_size must be a positive even number"); ESP_RETURN_ON_FALSE(cont_cfg->buf_size <= DAC_DMA_MAX_BUF_SIZE, ESP_ERR_INVALID_ARG, TAG, "buf_size exceeds the maximum limit"); @@ -219,14 +219,13 @@ esp_err_t dac_continuous_new_channels(const dac_continuous_config_t *cont_cfg, d /* Register the channels */ dac_channel_mask_t registered_chan_mask = 0; DAC_CHANNEL_MASK_FOREACH(chan, cont_cfg->chan_mask) { - ESP_GOTO_ON_ERROR(dac_priv_register_channel(chan), - err4, TAG, "register dac channel %"PRIu32" failed", chan); + ESP_GOTO_ON_ERROR(dac_priv_register_channel(chan), err_dereg, TAG, "register dac channel %"PRIu32" failed", chan); registered_chan_mask |= BIT(chan); } /* Allocate continuous mode struct */ dac_continuous_handle_t handle = heap_caps_calloc(1, sizeof(struct dac_continuous_s) + cont_cfg->desc_num * sizeof(uint8_t *), DAC_MEM_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(handle, ESP_ERR_NO_MEM, err4, TAG, "no memory for the dac continuous mode structure"); + ESP_GOTO_ON_FALSE(handle, ESP_ERR_NO_MEM, err_dereg, TAG, "no memory for the dac continuous mode structure"); handle->cfg = *cont_cfg; @@ -235,29 +234,26 @@ esp_err_t dac_continuous_new_channels(const dac_continuous_config_t *cont_cfg, d #endif handle->free_desc_queue = xQueueCreateWithCaps(cont_cfg->desc_num, sizeof(int), DAC_MEM_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(handle->free_desc_queue, ESP_ERR_NO_MEM, err3, TAG, "Failed to create free descriptor queue"); + ESP_GOTO_ON_FALSE(handle->free_desc_queue, ESP_ERR_NO_MEM, err_free, TAG, "Failed to create free descriptor queue"); handle->mutex = xSemaphoreCreateMutexWithCaps(DAC_MEM_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(handle->mutex, ESP_ERR_NO_MEM, err3, TAG, "Failed to create mutex"); + ESP_GOTO_ON_FALSE(handle->mutex, ESP_ERR_NO_MEM, err_free, TAG, "Failed to create mutex"); /* Create PM lock */ #if CONFIG_PM_ENABLE esp_pm_lock_type_t pm_lock_type = cont_cfg->clk_src == DAC_DIGI_CLK_SRC_APLL ? ESP_PM_NO_LIGHT_SLEEP : ESP_PM_APB_FREQ_MAX; - ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_lock_type, 0, "dac_driver", &handle->pm_lock), err3, TAG, "Failed to create DAC pm lock"); + ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_lock_type, 0, "dac_driver", &handle->pm_lock), err_free, TAG, "Failed to create DAC pm lock"); #endif /* Create DMA descriptors and buffers */ - ESP_GOTO_ON_ERROR(s_dac_alloc_dma_desc(handle), err3, TAG, "Failed to create DMA descriptors and buffers"); + ESP_GOTO_ON_ERROR(s_dac_alloc_dma_desc(handle), err_free, TAG, "Failed to create DMA descriptors and buffers"); /* Initialize DAC DMA peripheral */ - ESP_GOTO_ON_ERROR(dac_dma_periph_init(cont_cfg->freq_hz, - cont_cfg->chan_mode == DAC_CHANNEL_MODE_ALTER, - cont_cfg->clk_src == DAC_DIGI_CLK_SRC_APLL), - err2, TAG, "Failed to initialize DAC DMA peripheral"); - - /* Register DMA interrupt */ - ESP_GOTO_ON_ERROR(esp_intr_alloc(dac_dma_periph_get_intr_signal(), DAC_INTR_ALLOC_FLAGS, - s_dac_default_intr_handler, handle, &(handle->intr_handle)), - err1, TAG, "Failed to register DAC DMA interrupt"); + dac_dma_event_callbacks_t cbs = { + .on_done = dac_dma_done_callback, + .on_teof = dac_dma_teof_callback, + }; + ESP_GOTO_ON_ERROR(dac_priv_dma_init(cont_cfg->clk_src, cont_cfg->freq_hz, cont_cfg->chan_mode == DAC_CHANNEL_MODE_ALTER, &cbs, handle), + err_desc, TAG, "Failed to initialize DAC DMA peripheral"); /* Connect DAC module to the DMA peripheral */ DAC_ENTER_CRITICAL(); @@ -270,11 +266,9 @@ esp_err_t dac_continuous_new_channels(const dac_continuous_config_t *cont_cfg, d *ret_handle = handle; return ret; -err1: - dac_dma_periph_deinit(); -err2: +err_desc: s_dac_free_dma_desc(handle); -err3: +err_free: if (handle->free_desc_queue) { vQueueDeleteWithCaps(handle->free_desc_queue); } @@ -287,7 +281,7 @@ err3: } #endif free(handle); -err4: +err_dereg: /* Deregister registered channels */ DAC_CHANNEL_MASK_FOREACH(chan, registered_chan_mask) { dac_priv_deregister_channel(chan); @@ -308,14 +302,8 @@ esp_err_t dac_continuous_del_channels(dac_continuous_handle_t handle) return ESP_ERR_INVALID_STATE; } - /* Deregister DMA interrupt */ - if (handle->intr_handle) { - ESP_RETURN_ON_ERROR(esp_intr_free(handle->intr_handle), TAG, "Failed to deregister DMA interrupt"); - handle->intr_handle = NULL; - } - /* Deinitialize DMA peripheral */ - ESP_RETURN_ON_ERROR(dac_dma_periph_deinit(), TAG, "Failed to deinitialize DAC DMA peripheral"); + ESP_RETURN_ON_ERROR(dac_priv_dma_deinit(), TAG, "Failed to deinitialize DAC DMA peripheral"); /* Disconnect DAC module from the DMA peripheral */ DAC_ENTER_CRITICAL(); @@ -397,8 +385,7 @@ esp_err_t dac_continuous_enable(dac_continuous_handle_t handle) DAC_CHANNEL_MASK_FOREACH(chan, handle->cfg.chan_mask) { dac_priv_enable_channel(chan); } - dac_dma_periph_enable(); - esp_intr_enable(handle->intr_handle); + dac_priv_dma_enable(); DAC_ENTER_CRITICAL(); dac_ll_digi_enable_dma(true); @@ -419,7 +406,7 @@ esp_err_t dac_continuous_disable(dac_continuous_handle_t handle) ESP_RETURN_ON_ERROR(dac_continuous_stop_cyclically(handle), TAG, "Failed to stop cyclic conversion"); } - /* Check if there is any ongoing SYNC writing and wait for it to stop */ + /* Check if there is any ongoing SYNC writing and stop it */ if (atomic_load(&s_dac_cont_fsm) == DAC_CONT_FSM_SYNC) { ESP_RETURN_ON_ERROR(s_dac_continuous_stop_sync(handle), TAG, "Failed to stop sync writing"); } @@ -429,8 +416,7 @@ esp_err_t dac_continuous_disable(dac_continuous_handle_t handle) ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT), ESP_ERR_INVALID_STATE, TAG, "DAC continuous is running/not enabled"); - dac_dma_periph_disable(); - esp_intr_disable(handle->intr_handle); + dac_priv_dma_disable(); DAC_ENTER_CRITICAL(); dac_ll_digi_enable_dma(false); @@ -462,7 +448,7 @@ esp_err_t dac_continuous_start_async_writing(dac_continuous_handle_t handle) ESP_RETURN_ON_ERROR(dac_continuous_stop_cyclically(handle), TAG, "Failed to stop cyclic conversion"); } - /* Check if there is any ongoing SYNC writing and wait for it to stop */ + /* Check if there is any ongoing SYNC writing and stop it */ if (atomic_load(&s_dac_cont_fsm) == DAC_CONT_FSM_SYNC) { ESP_RETURN_ON_ERROR(s_dac_continuous_stop_sync(handle), TAG, "Failed to stop sync writing"); } @@ -483,7 +469,7 @@ esp_err_t dac_continuous_start_async_writing(dac_continuous_handle_t handle) handle->cur_index = 0; handle->used_desc_num = handle->cfg.desc_num; /* Start with an all-zero buffer. User will be notified by the 'on_convert_done' callback, then load the data into the buffer. */ - dac_dma_periph_trans_start(gdma_link_get_head_addr(handle->link)); + dac_priv_dma_trans_start(gdma_link_get_head_addr(handle->link)); /* FSM: WAIT -> ASYNC */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ASYNC); @@ -502,7 +488,7 @@ esp_err_t dac_continuous_stop_async_writing(dac_continuous_handle_t handle) return ESP_ERR_INVALID_STATE; } - dac_dma_periph_trans_stop(); + dac_priv_dma_trans_stop(); /* FSM: WAIT -> ENABLED */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ENABLED); @@ -525,7 +511,7 @@ esp_err_t dac_continuous_stop_async_writing(dac_continuous_handle_t handle) * * @note if CONFIG_DAC_DMA_AUTO_16BIT_ALIGN is enabled, data_len can be odd, otherwise it must be even */ -static size_t s_dac_load_data_into_desc(dac_continuous_handle_t handle, int index, const uint8_t *data, size_t data_len, bool auto_balance) +size_t dac_load_data_into_desc(dac_continuous_handle_t handle, int index, const uint8_t *data, size_t data_len, bool auto_balance) { /* Calculate the length of the data to be loaded */ size_t buf_size = handle->cfg.buf_size; // must be even @@ -600,7 +586,7 @@ esp_err_t dac_continuous_write_asynchronously(dac_continuous_handle_t handle, ui ESP_GOTO_ON_FALSE_ISR(index < handle->cfg.desc_num, ESP_ERR_NOT_FOUND, clean_up, TAG, "Corresponding DMA descriptor not found"); /* Load data into DMA buffer. We disable the auto balance here because the total length is actually uncertain. */ - size_t loaded_len = s_dac_load_data_into_desc(handle, index, data, data_len, false); + size_t loaded_len = dac_load_data_into_desc(handle, index, data, data_len, false); if (bytes_loaded) { *bytes_loaded = loaded_len; } @@ -635,7 +621,7 @@ esp_err_t dac_continuous_write_cyclically(dac_continuous_handle_t handle, uint8_ ESP_GOTO_ON_ERROR(dac_continuous_stop_cyclically(handle), err, TAG, "Failed to stop cyclic conversion"); } - /* Check if there is any ongoing SYNC writing and wait for it to stop */ + /* Check if there is any ongoing SYNC writing and stop it */ if (atomic_load(&s_dac_cont_fsm) == DAC_CONT_FSM_SYNC) { ESP_GOTO_ON_ERROR(s_dac_continuous_stop_sync(handle), err, TAG, "Failed to stop sync writing"); } @@ -648,7 +634,7 @@ esp_err_t dac_continuous_write_cyclically(dac_continuous_handle_t handle, uint8_ size_t remain_size = buf_size; uint32_t index = 0; for (; index < handle->cfg.desc_num && remain_size > 0; index++) { - size_t loaded_len = s_dac_load_data_into_desc(handle, index, buf, remain_size, true); + size_t loaded_len = dac_load_data_into_desc(handle, index, buf, remain_size, true); remain_size -= loaded_len; buf += loaded_len; } @@ -663,7 +649,7 @@ esp_err_t dac_continuous_write_cyclically(dac_continuous_handle_t handle, uint8_ handle->cur_index = 0; handle->used_desc_num = index; - dac_dma_periph_trans_start(gdma_link_get_head_addr(handle->link)); + dac_priv_dma_trans_start(gdma_link_get_head_addr(handle->link)); /* FSM: WAIT -> CYCLIC */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_CYCLIC); @@ -688,7 +674,7 @@ esp_err_t dac_continuous_stop_cyclically(dac_continuous_handle_t handle) return ESP_ERR_INVALID_STATE; } - dac_dma_periph_trans_stop(); + dac_priv_dma_trans_stop(); /* FSM: WAIT -> ENABLED */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ENABLED); @@ -739,7 +725,7 @@ esp_err_t dac_continuous_write(dac_continuous_handle_t handle, uint8_t *buf, siz handle->used_desc_num = handle->cfg.desc_num; /* Load one descriptor and start the DMA */ - size_t loaded_len = s_dac_load_data_into_desc(handle, 0, buf, remain_size, true); + size_t loaded_len = dac_load_data_into_desc(handle, 0, buf, remain_size, true); remain_size -= loaded_len; buf += loaded_len; gdma_link_concat(handle->link, 0, NULL, 0); @@ -747,7 +733,7 @@ esp_err_t dac_continuous_write(dac_continuous_handle_t handle, uint8_t *buf, siz /* It is safe to operate without the lock here because the DMA is not running yet. */ handle->dma_running = true; #endif - dac_dma_periph_trans_start(gdma_link_get_head_addr(handle->link)); + dac_priv_dma_trans_start(gdma_link_get_head_addr(handle->link)); goto skip_cas; @@ -764,7 +750,7 @@ skip_cas: ret = ESP_ERR_TIMEOUT; break; } - size_t loaded_len = s_dac_load_data_into_desc(handle, index, buf, remain_size, true); + size_t loaded_len = dac_load_data_into_desc(handle, index, buf, remain_size, true); remain_size -= loaded_len; buf += loaded_len; /** @@ -775,19 +761,19 @@ skip_cas: #if SOC_IS(ESP32) /** - * The ESP32 I2S DMA append() (restart) is buggy, so we re-issue start() when the DMA has stopped. See IDF-15791. + * The ESP32 I2S DMA append() (restart) has a hardware limitation, so we re-issue start() when the DMA has stopped. See IDF-15791. * Synchronize with the TEOF handler via dma_lock to prevent duplicate or missed starts. */ portENTER_CRITICAL(&handle->dma_lock); gdma_link_concat(handle->link, index - 1, handle->link, index); if (!handle->dma_running) { handle->dma_running = true; - dac_dma_periph_trans_start(gdma_link_get_item_addr(handle->link, index)); + dac_priv_dma_trans_start(gdma_link_get_item_addr(handle->link, index)); } portEXIT_CRITICAL(&handle->dma_lock); #else gdma_link_concat(handle->link, index - 1, handle->link, index); - dac_dma_periph_trans_append(); + dac_priv_dma_trans_append(); #endif } break; @@ -821,11 +807,11 @@ static esp_err_t s_dac_continuous_stop_sync(dac_continuous_handle_t handle) * Both must be guarded by dma_lock to prevent concurrent hardware register access. */ portENTER_CRITICAL(&handle->dma_lock); - dac_dma_periph_trans_stop(); + dac_priv_dma_trans_stop(); handle->dma_running = false; portEXIT_CRITICAL(&handle->dma_lock); #else - dac_dma_periph_trans_stop(); + dac_priv_dma_trans_stop(); #endif /* FSM: WAIT -> ENABLED */ diff --git a/components/esp_driver_dac/dac_priv_dma.h b/components/esp_driver_dac/dac_priv_dma.h index fb03619cfdd..07ab4f32e5f 100644 --- a/components/esp_driver_dac/dac_priv_dma.h +++ b/components/esp_driver_dac/dac_priv_dma.h @@ -6,71 +6,72 @@ #pragma once +#include +#include #include "esp_err.h" #include "soc/soc_caps.h" -#include "esp_bit_defs.h" -#include "esp_intr_alloc.h" +#include "soc/clk_tree_defs.h" #ifdef __cplusplus extern "C" { #endif -// one node in the descriptor chain is finished -#define DAC_DMA_DONE_INTR BIT(0) -// all nodes in the descriptor chain are finished -#define DAC_DMA_TEOF_INTR BIT(1) +/** + * @brief DAC DMA event callback + * @note Invoked from the DMA ISR + * + * @return Whether a high-priority task has been woken up by this callback + */ +typedef bool (*dac_dma_event_callback_t)(void *ctx); + +/** + * @brief Group of DAC DMA event callbacks + * @note The callbacks run in ISR context + * @note When CONFIG_DAC_ISR_IRAM_SAFE is enabled, the callbacks and the functions they call + * must be placed in IRAM, and the variables they use must be in internal RAM + */ +typedef struct { + dac_dma_event_callback_t on_done; /*!< Invoked when one DMA descriptor is finished */ + dac_dma_event_callback_t on_teof; /*!< Invoked when the DMA descriptor chain reaches total EOF */ +} dac_dma_event_callbacks_t; /** * @brief Initialize DAC DMA peripheral * - * @param[in] freq_hz DAC data frequency per channel + * @param[in] clk_src DAC digital controller clock source + * @param[in] freq_hz Requested DAC data frequency per channel * @param[in] is_alternate Transmit data alternate between two channels or simultaneously - * @param[in] is_apll Whether use APLL as DAC digital controller clock source + * @param[in] cbs Group of event callback functions, must not be NULL + * @param[in] ctx Driver context passed to the callback functions * @return + * - ESP_OK Initialize DAC DMA peripheral success + * - ESP_ERR_INVALID_ARG Invalid clock source, frequency, or `cbs` is NULL * - ESP_ERR_NOT_FOUND The DMA peripheral has been occupied * - ESP_ERR_NO_MEM No memory for the DMA peripheral struct - * - ESP_ERR_INVALID_ARG The frequency is out of range - * - ESP_OK Initialize DAC DMA peripheral success */ -esp_err_t dac_dma_periph_init(uint32_t freq_hz, bool is_alternate, bool is_apll); +esp_err_t dac_priv_dma_init(soc_periph_dac_digi_clk_src_t clk_src, uint32_t freq_hz, bool is_alternate, + const dac_dma_event_callbacks_t *cbs, void *ctx); /** * @brief Deinitialize DAC DMA peripheral * * @return - * - ESP_ERR_INVALID_STATE The DAC DMA has been de-initialized already - * or the interrupt has not been de-registered * - ESP_OK Deinitialize DAC DMA peripheral success + * - Others Failed to release interrupt, clock, or DMA peripheral */ -esp_err_t dac_dma_periph_deinit(void); - -/** - * @brief Get the DMA interrupt signal id - * - * @return - * - int DMA interrupt signal id - */ -int dac_dma_periph_get_intr_signal(void); +esp_err_t dac_priv_dma_deinit(void); /** * @brief Enable the DMA and interrupt of the DAC DMA peripheral * */ -void dac_dma_periph_enable(void); +void dac_priv_dma_enable(void); /** * @brief Disable the DMA and interrupt of the DAC DMA peripheral * */ -void dac_dma_periph_disable(void); - -/** - * @brief Get the mask of the triggered interrupt - * - * @return - * - uint32_t Mask of the triggered interrupt: DAC_DMA_DONE_INTR, DAC_DMA_TEOF_INTR - */ -uint32_t dac_dma_periph_intr_get_mask(void); +void dac_priv_dma_disable(void); /** * @brief Start a DMA transaction @@ -78,19 +79,19 @@ uint32_t dac_dma_periph_intr_get_mask(void); * * @param[in] desc_addr Descriptor address */ -void dac_dma_periph_trans_start(uintptr_t desc_addr); +void dac_priv_dma_trans_start(uintptr_t desc_addr); /** * @brief Stop the current DMA transaction immediately */ -void dac_dma_periph_trans_stop(void); +void dac_priv_dma_trans_stop(void); #if !SOC_IS(ESP32) /** * @brief Append the newly linked DMA descriptors to the current transaction * @note The caller should link new descriptors to the current tail before calling this function. */ -void dac_dma_periph_trans_append(void); +void dac_priv_dma_trans_append(void); #endif #ifdef __cplusplus diff --git a/components/esp_driver_dac/esp32/dac_dma.c b/components/esp_driver_dac/esp32/dac_dma.c index 357a97deddc..b6e7fc6a8bb 100644 --- a/components/esp_driver_dac/esp32/dac_dma.c +++ b/components/esp_driver_dac/esp32/dac_dma.c @@ -5,11 +5,11 @@ */ /** - * This file is a target specific for DAC DMA peripheral + * Target-specific DAC DMA backend implementation * Target: ESP32 * DAC DMA peripheral (data source): I2S0 (i.e. use I2S DMA to transmit data) * DAC DMA interrupt source: I2S0 - * DAC digital controller clock source: I2S ws signal (root clock: D2PLL or APLL) + * DAC digital controller clock source: I2S ws signal (root clock: PLL_F160M or APLL) */ #include "dac_priv_common.h" @@ -22,23 +22,52 @@ #include "hal/i2s_periph.h" #include "dac_priv_dma.h" #include "esp_private/i2s_platform.h" -#include "esp_private/esp_clk.h" #include "esp_clk_tree.h" #include "esp_log.h" #include "esp_check.h" -#include "esp_attr.h" #define DAC_DMA_PERIPH_I2S_NUM 0 #define DAC_DMA_PERIPH_I2S_BIT_WIDTH 16 // Fixed bit width, only the high 8 bits take effect +#if CONFIG_DAC_ISR_IRAM_SAFE +#define DAC_DMA_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) +#else +#define DAC_DMA_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_INTRDISABLED) +#endif + typedef struct { void *periph_dev; /* DMA peripheral device address */ intr_handle_t intr_handle; /* Interrupt handle */ - bool use_apll; /* Whether use APLL as clock source */ + soc_periph_dac_digi_clk_src_t clk_src; /* Acquired clock source; 0 means not enabled yet */ + dac_dma_event_callbacks_t cbs; /* Event callbacks */ + void *ctx; /* Driver context for callbacks */ } dac_dma_periph_i2s_t; static dac_dma_periph_i2s_t *s_ddp = NULL; // Static DAC DMA peripheral structure pointer +void dac_priv_dma_intr_handler(void *arg) +{ + dac_dma_periph_i2s_t *ddp = arg; + bool need_yield = false; + + uint32_t status = i2s_ll_get_intr_status(ddp->periph_dev); + if (status == 0) { + // Avoid spurious interrupt + return; + } + i2s_ll_clear_intr_status(ddp->periph_dev, status); + + if ((status & I2S_LL_EVENT_TX_DONE) && ddp->cbs.on_done) { + need_yield |= ddp->cbs.on_done(ddp->ctx); + } + if ((status & I2S_LL_EVENT_TX_TEOF) && ddp->cbs.on_teof) { + need_yield |= ddp->cbs.on_teof(ddp->ctx); + } + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + static uint32_t s_dac_set_apll_freq(uint32_t mclk) { /* Calculate the expected APLL */ @@ -64,26 +93,26 @@ static uint32_t s_dac_set_apll_freq(uint32_t mclk) /** * @brief Calculate and set DAC data frequency - * @note DAC frequency is decided by I2S WS frequency, the clock source of I2S is D2PLL or APLL on ESP32 + * @note DAC frequency is decided by I2S WS frequency, the clock source of I2S is PLL_F160M or APLL on ESP32 * freq_hz = ws = bclk / I2S_LL_AD_BCK_FACTOR + * @param clk_src DAC digital controller clock source * @param freq_hz DAC byte transmit frequency * @return * - ESP_OK config success * - ESP_ERR_INVALID_ARG invalid frequency */ -static esp_err_t s_dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll) +static esp_err_t s_dac_priv_dma_set_clock(soc_periph_dac_digi_clk_src_t clk_src, uint32_t freq_hz) { /* Calculate clock coefficients */ uint32_t bclk = freq_hz * I2S_LL_AD_BCK_FACTOR; uint32_t bclk_div = DAC_DMA_PERIPH_I2S_BIT_WIDTH; uint32_t mclk = bclk * bclk_div; uint32_t sclk; // use 160M PLL clock as default, minimum support freq: 19.6 KHz maximum support freq: 2.5 MHz - if (is_apll) { + if (clk_src == DAC_DIGI_CLK_SRC_APLL) { sclk = s_dac_set_apll_freq(mclk); ESP_RETURN_ON_FALSE(sclk, ESP_ERR_INVALID_ARG, TAG, "set APLL coefficients failed"); } else { - // [clk_tree] TODO: replace the following clock by clk_tree API - sclk = esp_clk_apb_freq() * 2; // D2PLL + ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &sclk), TAG, "get clock source frequency failed"); } uint32_t mclk_div = sclk / mclk; @@ -92,7 +121,7 @@ static esp_err_t s_dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll) ESP_RETURN_ON_FALSE(mclk_div < 256, ESP_ERR_INVALID_ARG, TAG, "Frequency is too small, the mclk division exceed the maximum value 255"); ESP_LOGD(TAG, "[sclk] %"PRIu32" [mclk] %"PRIu32" [mclk_div] %"PRIu32" [bclk] %"PRIu32" [bclk_div] %"PRIu32, sclk, mclk, mclk_div, bclk, bclk_div); - i2s_ll_tx_clk_set_src(s_ddp->periph_dev, is_apll ? I2S_CLK_SRC_APLL : I2S_CLK_SRC_DEFAULT); + i2s_ll_tx_clk_set_src(s_ddp->periph_dev, (i2s_clock_src_t)clk_src); hal_utils_clk_div_t mclk_div_coeff = {}; i2s_hal_calc_mclk_precise_division(sclk, mclk, &mclk_div_coeff); i2s_ll_tx_set_mclk(s_ddp->periph_dev, &mclk_div_coeff); @@ -101,21 +130,25 @@ static esp_err_t s_dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll) return ESP_OK; } -esp_err_t dac_dma_periph_init(uint32_t freq_hz, bool is_alternate, bool is_apll) +esp_err_t dac_priv_dma_init(soc_periph_dac_digi_clk_src_t clk_src, uint32_t freq_hz, bool is_alternate, + const dac_dma_event_callbacks_t *cbs, void *ctx) { + ESP_RETURN_ON_FALSE(clk_src == DAC_DIGI_CLK_SRC_PLL_160M || clk_src == DAC_DIGI_CLK_SRC_APLL, ESP_ERR_INVALID_ARG, TAG, "invalid DAC digital clock source"); + DAC_NULL_POINTER_CHECK(cbs); + esp_err_t ret = ESP_OK; - /* Acquire DMA peripheral */ - ESP_RETURN_ON_ERROR(i2s_platform_acquire_occupation(I2S_CTLR_HP, DAC_DMA_PERIPH_I2S_NUM, "dac_dma"), TAG, "Failed to acquire DAC DMA peripheral"); + /* Allocate DAC DMA peripheral object */ s_ddp = (dac_dma_periph_i2s_t *)heap_caps_calloc(1, sizeof(dac_dma_periph_i2s_t), DAC_MEM_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(s_ddp, ESP_ERR_NO_MEM, err, TAG, "No memory for DAC DMA object"); + ESP_RETURN_ON_FALSE(s_ddp, ESP_ERR_NO_MEM, TAG, "No memory for DAC DMA object"); + + /* Acquire DMA peripheral */ + ESP_GOTO_ON_ERROR(i2s_platform_acquire_occupation(I2S_CTLR_HP, DAC_DMA_PERIPH_I2S_NUM, "dac_dma"), err, TAG, "Failed to acquire DAC DMA peripheral"); s_ddp->periph_dev = (void *)I2S_LL_GET_HW(DAC_DMA_PERIPH_I2S_NUM); - if (is_apll) { - ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src(SOC_MOD_CLK_APLL, true), err, TAG, "APLL enable failed"); - s_ddp->use_apll = true; - } - ESP_GOTO_ON_ERROR(s_dac_dma_periph_set_clock(freq_hz, is_apll), err, TAG, "Failed to set clock of DMA peripheral"); + ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true), err, TAG, "enable DAC digital clock source failed"); + s_ddp->clk_src = clk_src; + ESP_GOTO_ON_ERROR(s_dac_priv_dma_set_clock(clk_src, freq_hz), err, TAG, "Failed to set clock of DMA peripheral"); i2s_ll_enable_builtin_adc_dac(s_ddp->periph_dev, true); i2s_ll_tx_reset(s_ddp->periph_dev); @@ -131,43 +164,53 @@ esp_err_t dac_dma_periph_init(uint32_t freq_hz, bool is_alternate, bool is_apll) i2s_ll_tx_force_enable_fifo_mod(s_ddp->periph_dev, true); i2s_ll_dma_enable_auto_write_back(s_ddp->periph_dev, true); + s_ddp->cbs = *cbs; + s_ddp->ctx = ctx; + ESP_GOTO_ON_ERROR(esp_intr_alloc(i2s_periph_signal[DAC_DMA_PERIPH_I2S_NUM].irq, DAC_DMA_INTR_ALLOC_FLAGS, dac_priv_dma_intr_handler, s_ddp, &s_ddp->intr_handle), + err, TAG, "Failed to register DAC DMA interrupt"); + return ret; err: - dac_dma_periph_deinit(); + dac_priv_dma_deinit(); return ret; } -esp_err_t dac_dma_periph_deinit(void) +esp_err_t dac_priv_dma_deinit(void) { if (!s_ddp) { return ESP_OK; } - ESP_RETURN_ON_FALSE(s_ddp->intr_handle == NULL, ESP_ERR_INVALID_STATE, TAG, "The interrupt is not deregistered yet"); - ESP_RETURN_ON_ERROR(i2s_platform_release_occupation(I2S_CTLR_HP, DAC_DMA_PERIPH_I2S_NUM), TAG, "Failed to release DAC DMA peripheral"); - if (s_ddp->use_apll) { - ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src(SOC_MOD_CLK_APLL, false), TAG, "APLL disable failed"); - s_ddp->use_apll = false; + if (s_ddp->intr_handle) { + ESP_RETURN_ON_ERROR(esp_intr_disable(s_ddp->intr_handle), TAG, "Failed to disable DAC DMA interrupt"); + ESP_RETURN_ON_ERROR(esp_intr_free(s_ddp->intr_handle), TAG, "Failed to deregister DAC DMA interrupt"); + s_ddp->intr_handle = NULL; } + + if (s_ddp->clk_src) { + ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)s_ddp->clk_src, false), TAG, "disable DAC digital clock source failed"); + s_ddp->clk_src = 0; + } + + if (s_ddp->periph_dev) { + ESP_RETURN_ON_ERROR(i2s_platform_release_occupation(I2S_CTLR_HP, DAC_DMA_PERIPH_I2S_NUM), TAG, "Failed to release DAC DMA peripheral"); + s_ddp->periph_dev = NULL; + } + free(s_ddp); s_ddp = NULL; return ESP_OK; } -int dac_dma_periph_get_intr_signal(void) -{ - return i2s_periph_signal[DAC_DMA_PERIPH_I2S_NUM].irq; -} - -static void s_dac_dma_periph_reset(void) +static void s_dac_priv_dma_reset(void) { i2s_ll_tx_reset(s_ddp->periph_dev); i2s_ll_tx_reset_dma(s_ddp->periph_dev); i2s_ll_tx_reset_fifo(s_ddp->periph_dev); } -static void s_dac_dma_periph_start(void) +static void s_dac_priv_dma_start(void) { i2s_ll_enable_dma(s_ddp->periph_dev, true); i2s_ll_enable_intr(s_ddp->periph_dev, I2S_LL_EVENT_TX_DONE | I2S_LL_EVENT_TX_TEOF, true); @@ -176,7 +219,7 @@ static void s_dac_dma_periph_start(void) i2s_ll_dma_enable_auto_write_back(s_ddp->periph_dev, true); } -static void s_dac_dma_periph_stop(void) +static void s_dac_priv_dma_stop(void) { i2s_ll_tx_stop(s_ddp->periph_dev); i2s_ll_tx_stop_link(s_ddp->periph_dev); @@ -186,42 +229,30 @@ static void s_dac_dma_periph_stop(void) i2s_ll_dma_enable_auto_write_back(s_ddp->periph_dev, false); } -void dac_dma_periph_enable(void) +void dac_priv_dma_enable(void) { /* Reset */ - s_dac_dma_periph_reset(); + s_dac_priv_dma_reset(); /* Start */ - s_dac_dma_periph_start(); + s_dac_priv_dma_start(); + esp_intr_enable(s_ddp->intr_handle); } -void dac_dma_periph_disable(void) +void dac_priv_dma_disable(void) { /* Reset */ - s_dac_dma_periph_reset(); + s_dac_priv_dma_reset(); /* Stop */ - s_dac_dma_periph_stop(); + s_dac_priv_dma_stop(); + esp_intr_disable(s_ddp->intr_handle); } -uint32_t IRAM_ATTR dac_dma_periph_intr_get_mask(void) -{ - uint32_t status = i2s_ll_get_intr_status(s_ddp->periph_dev); - if (status == 0) { - // Avoid spurious interrupt - return 0UL; - } - i2s_ll_clear_intr_status(s_ddp->periph_dev, status); - uint32_t ret = 0; - ret |= (status & I2S_LL_EVENT_TX_DONE) ? DAC_DMA_DONE_INTR : 0; - ret |= (status & I2S_LL_EVENT_TX_TEOF) ? DAC_DMA_TEOF_INTR : 0; - return ret; -} - -void IRAM_ATTR dac_dma_periph_trans_start(uintptr_t desc_addr) +void dac_priv_dma_trans_start(uintptr_t desc_addr) { i2s_ll_tx_start_link(s_ddp->periph_dev, desc_addr); } -void dac_dma_periph_trans_stop(void) +void dac_priv_dma_trans_stop(void) { i2s_ll_tx_stop_link(s_ddp->periph_dev); } diff --git a/components/esp_driver_dac/esp32s2/dac_dma.c b/components/esp_driver_dac/esp32s2/dac_dma.c index f566eb79fe9..b0feb10a0c4 100644 --- a/components/esp_driver_dac/esp32s2/dac_dma.c +++ b/components/esp_driver_dac/esp32s2/dac_dma.c @@ -5,7 +5,7 @@ */ /** - * This file is a target specific for DAC DMA peripheral + * Target-specific DAC DMA backend implementation * Target: ESP32-S2 * DAC DMA peripheral (data source): SPI3 (i.e. use SPI DMA to transmit data) * DAC DMA interrupt source: SPI3 @@ -29,20 +29,47 @@ #include "esp_clk_tree.h" #include "esp_log.h" #include "esp_check.h" -#include "esp_attr.h" -#include "esp_heap_caps.h" #define DAC_DMA_PERIPH_SPI_HOST SPI3_HOST +#if CONFIG_DAC_ISR_IRAM_SAFE +#define DAC_DMA_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) +#else +#define DAC_DMA_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_INTRDISABLED) +#endif + typedef struct { void *periph_dev; /* DMA peripheral device address */ uint32_t dma_chan; intr_handle_t intr_handle; /* Interrupt handle */ - bool use_apll; /* Whether use APLL as digital controller clock source */ + soc_periph_dac_digi_clk_src_t clk_src; /* Acquired clock source; 0 means not enabled yet */ + dac_dma_event_callbacks_t cbs; /* Event callbacks */ + void *ctx; /* Driver context for callbacks */ } dac_dma_periph_spi_t; static dac_dma_periph_spi_t *s_ddp = NULL; // Static DAC DMA peripheral structure pointer +void dac_priv_dma_intr_handler(void *arg) +{ + dac_dma_periph_spi_t *ddp = arg; + bool need_yield = false; + + bool done = spi_ll_get_intr(ddp->periph_dev, SPI_LL_INTR_OUT_DONE); + bool teof = spi_ll_get_intr(ddp->periph_dev, SPI_LL_INTR_OUT_TOTAL_EOF); + spi_ll_clear_intr(ddp->periph_dev, SPI_LL_INTR_OUT_DONE); + spi_ll_clear_intr(ddp->periph_dev, SPI_LL_INTR_OUT_TOTAL_EOF); + + if (done && ddp->cbs.on_done) { + need_yield |= ddp->cbs.on_done(ddp->ctx); + } + if (teof && ddp->cbs.on_teof) { + need_yield |= ddp->cbs.on_teof(ddp->ctx); + } + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + static uint32_t s_dac_set_apll_freq(uint32_t expt_freq) { /* Set APLL coefficients to the given frequency */ @@ -63,23 +90,24 @@ static uint32_t s_dac_set_apll_freq(uint32_t expt_freq) * @note DAC clock shares clock divider with ADC, the clock source is APB or APLL on ESP32-S2 * freq_hz = (source_clk / (clk_div + (b / a) + 1)) / interval * interval range: 1~4095 + * @param clk_src DAC digital controller clock source * @param freq_hz DAC byte transmit frequency * @return * - ESP_OK config success * - ESP_ERR_INVALID_ARG invalid frequency */ -static esp_err_t s_dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll) +static esp_err_t s_dac_priv_dma_set_clock(soc_periph_dac_digi_clk_src_t clk_src, uint32_t freq_hz) { /* Step 1: Determine the digital clock source frequency */ uint32_t digi_ctrl_freq; // Digital controller clock - if (is_apll) { + if (clk_src == DAC_DIGI_CLK_SRC_APLL) { /* Theoretical frequency range (due to the limitation of DAC, the maximum frequency may not reach): * CLK_LL_APLL_MAX_HZ: 119.24 Hz ~ 67.5 MHz * CLK_LL_APLL_MIN_HZ: 5.06 Hz ~ 2.65 MHz */ digi_ctrl_freq = s_dac_set_apll_freq(freq_hz < 120 ? CLK_LL_APLL_MIN_HZ : CLK_LL_APLL_MAX_HZ); ESP_RETURN_ON_FALSE(digi_ctrl_freq, ESP_ERR_INVALID_ARG, TAG, "set APLL coefficients failed"); } else { - digi_ctrl_freq = APB_CLK_FREQ; + ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &digi_ctrl_freq), TAG, "get clock source frequency failed"); } /* Step 2: Determine the interval */ @@ -114,107 +142,118 @@ static esp_err_t s_dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll) dac_ll_digi_clk_inv(true); dac_ll_digi_set_trigger_interval(interval); // secondary clock division adc_ll_digi_controller_clk_div(adc_clk_div.integer - 1, adc_clk_div.denominator, adc_clk_div.numerator); - adc_ll_digi_clk_sel(is_apll ? ADC_DIGI_CLK_SRC_APLL : ADC_DIGI_CLK_SRC_DEFAULT); + adc_ll_digi_clk_sel((adc_continuous_clk_src_t)clk_src); return ESP_OK; } -esp_err_t dac_dma_periph_init(uint32_t freq_hz, bool is_alternate, bool is_apll) +esp_err_t dac_priv_dma_init(soc_periph_dac_digi_clk_src_t clk_src, uint32_t freq_hz, bool is_alternate, + const dac_dma_event_callbacks_t *cbs, void *ctx) { + ESP_RETURN_ON_FALSE(clk_src == DAC_DIGI_CLK_SRC_APB || clk_src == DAC_DIGI_CLK_SRC_APLL, ESP_ERR_INVALID_ARG, TAG, "invalid DAC digital clock source"); + DAC_NULL_POINTER_CHECK(cbs); + esp_err_t ret = ESP_OK; - /* Acquire DMA peripheral */ - ESP_RETURN_ON_FALSE(spicommon_periph_claim(DAC_DMA_PERIPH_SPI_HOST, "dac_dma"), ESP_ERR_NOT_FOUND, TAG, "Failed to acquire DAC DMA peripheral"); - adc_apb_periph_claim(); + /* Allocate DAC DMA peripheral object */ s_ddp = (dac_dma_periph_spi_t *)heap_caps_calloc(1, sizeof(dac_dma_periph_spi_t), DAC_MEM_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(s_ddp, ESP_ERR_NO_MEM, err, TAG, "No memory for DAC DMA object"); + ESP_RETURN_ON_FALSE(s_ddp, ESP_ERR_NO_MEM, TAG, "No memory for DAC DMA object"); + + /* Acquire DMA peripheral */ + ESP_GOTO_ON_FALSE(spicommon_periph_claim(DAC_DMA_PERIPH_SPI_HOST, "dac_dma"), ESP_ERR_NOT_FOUND, err, TAG, "Failed to acquire DAC DMA peripheral"); + adc_apb_periph_claim(); s_ddp->periph_dev = (void *)SPI_LL_GET_HW(DAC_DMA_PERIPH_SPI_HOST); - if (is_apll) { - ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src(SOC_MOD_CLK_APLL, true), err, TAG, "APLL enable failed"); - s_ddp->use_apll = true; - } + /* Configure clock source and frequency */ + ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true), err, TAG, "enable DAC digital clock source failed"); + s_ddp->clk_src = clk_src; /* When transmit alternately, twice frequency is needed to guarantee the convert frequency in one channel */ uint32_t trans_freq_hz = freq_hz * (is_alternate ? 2 : 1); - ESP_GOTO_ON_ERROR(s_dac_dma_periph_set_clock(trans_freq_hz, is_apll), err, TAG, "Failed to set clock of DMA peripheral"); + ESP_GOTO_ON_ERROR(s_dac_priv_dma_set_clock(clk_src, trans_freq_hz), err, TAG, "Failed to set clock of DMA peripheral"); + ESP_GOTO_ON_ERROR(spicommon_dma_chan_alloc(DAC_DMA_PERIPH_SPI_HOST, SPI_DMA_CH_AUTO, 0), err, TAG, "Failed to allocate dma peripheral channel"); s_ddp->dma_chan = spi_bus_get_dma_ctx(DAC_DMA_PERIPH_SPI_HOST)->rx_dma_chan.chan_id; spi_ll_enable_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_DONE | SPI_LL_INTR_OUT_TOTAL_EOF); dac_ll_digi_set_convert_mode(is_alternate); + + s_ddp->cbs = *cbs; + s_ddp->ctx = ctx; + ESP_GOTO_ON_ERROR(esp_intr_alloc(spicommon_irqdma_source_for_host(DAC_DMA_PERIPH_SPI_HOST), DAC_DMA_INTR_ALLOC_FLAGS, dac_priv_dma_intr_handler, s_ddp, &s_ddp->intr_handle), + err, TAG, "Failed to register DAC DMA interrupt"); return ret; err: - dac_dma_periph_deinit(); + dac_priv_dma_deinit(); return ret; } -esp_err_t dac_dma_periph_deinit(void) +esp_err_t dac_priv_dma_deinit(void) { - ESP_RETURN_ON_FALSE(s_ddp != NULL, ESP_ERR_INVALID_STATE, TAG, "DAC DMA peripheral is not initialized"); - ESP_RETURN_ON_FALSE(s_ddp->intr_handle == NULL, ESP_ERR_INVALID_STATE, TAG, "The interrupt is not deregistered yet"); + if (!s_ddp) { + return ESP_OK; + } + + if (s_ddp->intr_handle) { + ESP_RETURN_ON_ERROR(esp_intr_disable(s_ddp->intr_handle), TAG, "Failed to disable DAC DMA interrupt"); + ESP_RETURN_ON_ERROR(esp_intr_free(s_ddp->intr_handle), TAG, "Failed to deregister DAC DMA interrupt"); + s_ddp->intr_handle = NULL; + } + if (s_ddp->dma_chan) { ESP_RETURN_ON_ERROR(spicommon_dma_chan_free(DAC_DMA_PERIPH_SPI_HOST), TAG, "Failed to free dma peripheral channel"); + s_ddp->dma_chan = 0; } - ESP_RETURN_ON_FALSE(spicommon_periph_free(DAC_DMA_PERIPH_SPI_HOST), ESP_FAIL, TAG, "Failed to release DAC DMA peripheral"); - spi_ll_disable_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_DONE | SPI_LL_INTR_OUT_TOTAL_EOF); - adc_apb_periph_free(); - if (s_ddp) { - if (s_ddp->use_apll) { - ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src(SOC_MOD_CLK_APLL, false), TAG, "APLL disable failed"); - s_ddp->use_apll = false; - } - free(s_ddp); - s_ddp = NULL; + + if (s_ddp->periph_dev) { + spi_ll_disable_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_DONE | SPI_LL_INTR_OUT_TOTAL_EOF); + adc_apb_periph_free(); + ESP_RETURN_ON_FALSE(spicommon_periph_free(DAC_DMA_PERIPH_SPI_HOST), ESP_FAIL, TAG, "Failed to release DAC DMA peripheral"); + s_ddp->periph_dev = NULL; } + + if (s_ddp->clk_src) { + ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)s_ddp->clk_src, false), TAG, "disable DAC digital clock source failed"); + s_ddp->clk_src = 0; + } + + free(s_ddp); + s_ddp = NULL; return ESP_OK; } -int dac_dma_periph_get_intr_signal(void) -{ - return spicommon_irqdma_source_for_host(DAC_DMA_PERIPH_SPI_HOST); -} - -static void s_dac_dma_periph_reset(void) +static void s_dac_priv_dma_reset(void) { spi_dma_ll_tx_reset(s_ddp->periph_dev, s_ddp->dma_chan); spi_ll_dma_tx_fifo_reset(s_ddp->periph_dev); } -void dac_dma_periph_enable(void) +void dac_priv_dma_enable(void) { - s_dac_dma_periph_reset(); + s_dac_priv_dma_reset(); dac_ll_digi_trigger_output(true); + esp_intr_enable(s_ddp->intr_handle); } -void dac_dma_periph_disable(void) +void dac_priv_dma_disable(void) { - s_dac_dma_periph_reset(); + s_dac_priv_dma_reset(); spi_dma_ll_tx_stop(s_ddp->periph_dev, s_ddp->dma_chan); dac_ll_digi_trigger_output(false); + esp_intr_disable(s_ddp->intr_handle); } -uint32_t IRAM_ATTR dac_dma_periph_intr_get_mask(void) -{ - uint32_t ret = 0; - ret |= spi_ll_get_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_DONE) ? DAC_DMA_DONE_INTR : 0; - ret |= spi_ll_get_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_TOTAL_EOF) ? DAC_DMA_TEOF_INTR : 0; - spi_ll_clear_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_DONE); - spi_ll_clear_intr(s_ddp->periph_dev, SPI_LL_INTR_OUT_TOTAL_EOF); - return ret; -} - -void IRAM_ATTR dac_dma_periph_trans_start(uintptr_t desc_addr) +void dac_priv_dma_trans_start(uintptr_t desc_addr) { spi_dma_ll_tx_reset(s_ddp->periph_dev, s_ddp->dma_chan); spi_ll_dma_tx_fifo_reset(s_ddp->periph_dev); spi_dma_ll_tx_start(s_ddp->periph_dev, s_ddp->dma_chan, (lldesc_t *)desc_addr); } -void dac_dma_periph_trans_stop(void) +void dac_priv_dma_trans_stop(void) { spi_dma_ll_tx_stop(s_ddp->periph_dev, s_ddp->dma_chan); } -void dac_dma_periph_trans_append(void) +void dac_priv_dma_trans_append(void) { spi_dma_ll_tx_restart(s_ddp->periph_dev, s_ddp->dma_chan); } diff --git a/components/esp_driver_dac/linker.lf b/components/esp_driver_dac/linker.lf index 5a8dc0355af..46cb76f8176 100644 --- a/components/esp_driver_dac/linker.lf +++ b/components/esp_driver_dac/linker.lf @@ -1,21 +1,31 @@ [mapping:dac_driver] archive: libesp_driver_dac.a entries: + if DAC_ISR_IRAM_SAFE = y: + dac_continuous: dac_dma_done_callback (noflash) + dac_continuous: dac_dma_teof_callback (noflash) + + if IDF_TARGET_ESP32 = y || IDF_TARGET_ESP32S2 = y: + dac_dma: dac_priv_dma_intr_handler (noflash) + + if IDF_TARGET_ESP32 = y: + dac_dma: dac_priv_dma_trans_start (noflash) + if DAC_CTRL_FUNC_IN_IRAM = y: dac_oneshot: dac_oneshot_output_voltage (noflash) + dac_continuous: dac_load_data_into_desc (noflash) dac_continuous: dac_continuous_write_asynchronously (noflash) - dac_continuous: s_dac_load_data_into_desc (noflash) [mapping:dac_driver_gdma_link] archive: libesp_driver_dma.a entries: - # Reached from the dac_continuous ISR + # Reached from the dac_continuous ISR callback if DAC_ISR_IRAM_SAFE = y: gdma_link: gdma_link_get_length (noflash) gdma_link: gdma_link_get_item_addr (noflash) gdma_link: gdma_link_check_end (noflash) - # Reached from 'dac_continuous_write_asynchronously' (via 's_dac_load_data_into_desc') + # Reached from 'dac_continuous_write_asynchronously' (via 'dac_load_data_into_desc') if DAC_CTRL_FUNC_IN_IRAM = y: gdma_link: gdma_link_set_length (noflash) gdma_link: gdma_link_set_owner (noflash) diff --git a/components/esp_driver_dac/test_apps/dac/main/test_dac.c b/components/esp_driver_dac/test_apps/dac/main/test_dac.c index 930c2100a59..6968691c700 100644 --- a/components/esp_driver_dac/test_apps/dac/main/test_dac.c +++ b/components/esp_driver_dac/test_apps/dac/main/test_dac.c @@ -233,7 +233,7 @@ TEST_CASE("DAC_dma_write_test", "[dac]") * With small DMA buffers and a deliberate long delay between writes, the DMA drains all of * its descriptors and stops (raises TEOF) before the next write happens. Every write except * the first one therefore has to re-link a descriptor and resume the transfer through - * dac_dma_periph_trans_append(). If that resume path is broken, the descriptors are never + * dac_priv_dma_trans_append(). If that resume path is broken, the descriptors are never * recycled, so dac_continuous_write() will return ESP_ERR_TIMEOUT, and dac_continuous_disable() * (which waits for the ongoing synchronous transfer to stop) will block forever. */ TEST_CASE("DAC_dma_sync_write_resume_test", "[dac]") diff --git a/components/soc/esp32/include/soc/clk_tree_defs.h b/components/soc/esp32/include/soc/clk_tree_defs.h index e0f67b8552c..9d0a1ac7895 100644 --- a/components/soc/esp32/include/soc/clk_tree_defs.h +++ b/components/soc/esp32/include/soc/clk_tree_defs.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 OR MIT */ @@ -347,16 +347,18 @@ typedef enum { /** * @brief Array initializer for all supported clock sources of DAC digital controller */ -#define SOC_DAC_DIGI_CLKS {SOC_MOD_CLK_PLL_D2, SOC_MOD_CLK_APLL} +#define SOC_DAC_DIGI_CLKS {SOC_MOD_CLK_PLL_F160M, SOC_MOD_CLK_APLL} /** * @brief DAC digital controller clock source * + * @note The ESP32 DAC DMA is based on I2S and uses the same clock source as I2S. */ typedef enum { - DAC_DIGI_CLK_SRC_PLLD2 = SOC_MOD_CLK_PLL_D2, /*!< Select PLL_D2 as the source clock */ - DAC_DIGI_CLK_SRC_APLL = SOC_MOD_CLK_APLL, /*!< Select APLL as the source clock */ - DAC_DIGI_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_D2, /*!< Select PLL_D2 as the default source clock */ + DAC_DIGI_CLK_SRC_PLLD2 [[deprecated("use DAC_DIGI_CLK_SRC_PLL_160M instead")]] = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the source clock */ + DAC_DIGI_CLK_SRC_PLL_160M = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the source clock */ + DAC_DIGI_CLK_SRC_APLL = SOC_MOD_CLK_APLL, /*!< Select APLL as the source clock */ + DAC_DIGI_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default source clock */ } soc_periph_dac_digi_clk_src_t; /** diff --git a/docs/en/api-reference/peripherals/dac.rst b/docs/en/api-reference/peripherals/dac.rst index 4c76d822cbc..dda117027ca 100644 --- a/docs/en/api-reference/peripherals/dac.rst +++ b/docs/en/api-reference/peripherals/dac.rst @@ -87,7 +87,7 @@ The following diagram illustrates the life cycle of the DAC continuous driver an The clock of the DAC digital controller comes from I2S0 as well, so there are two clock sources for selection: - - :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_PLL_D2` supports frequency between 19.6 KHz to several MHz. It is the default clock which can also be selected by :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT`. + - :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_PLL_160M` supports frequency between 19.6 KHz to several MHz. It is the default clock which can also be selected by :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT`. - :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_APLL` supports frequency between 648 Hz to several MHz. However, it might be occupied by other peripherals, thus not providing the required frequency. In such case, this clock source is available only if APLL still can be correctly divided into the target DAC DMA frequency. .. only:: esp32s2 diff --git a/docs/zh_CN/api-reference/peripherals/dac.rst b/docs/zh_CN/api-reference/peripherals/dac.rst index 007f3bf62fc..6dd190d4bb1 100644 --- a/docs/zh_CN/api-reference/peripherals/dac.rst +++ b/docs/zh_CN/api-reference/peripherals/dac.rst @@ -87,7 +87,7 @@ DAC 通道可以通过 DMA 连续转换数字信号,这种模式下有三种 DAC 的数字控制器的时钟也来自 I2S0,有以下两种时钟源可选: - - :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_PLL_D2` 支持 19.6 KHz 到若干 MHz 之间的频率。该时钟源为默认时钟源,也可通过选择 :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT` 来启用该时钟源。 + - :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_PLL_160M` 支持 19.6 KHz 到若干 MHz 之间的频率。该时钟源为默认时钟源,也可通过选择 :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT` 来启用该时钟源。 - :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_APLL` 支持 648 Hz 到若干 MHz 之间的频率。该时钟源可能会被其他外设占用而导致频率无法更改,此时除非 APLL 仍能准确分频得到 DAC DMA 的目标频率,否则将无法使用该时钟源。 .. only:: esp32s2