refactor(dac): move DMA interrupt handling behind dac_priv_dma callbacks

This commit is contained in:
Hu Rui
2026-08-18 18:02:38 +08:00
parent b29d8694b3
commit f7dc7a638d
9 changed files with 332 additions and 263 deletions

View File

@@ -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 */

View File

@@ -6,71 +6,72 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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]")