mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
refactor(dac): move DMA interrupt handling behind dac_priv_dma callbacks
This commit is contained in:
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]")
|
||||
|
||||
Reference in New Issue
Block a user