/* * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #if CONFIG_DAC_ENABLE_DEBUG_LOG // The local log level must be defined before including esp_log.h // Set the maximum log level for this source file #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG #endif #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "freertos/idf_additions.h" #include "sdkconfig.h" #include "soc/soc_caps.h" #include "driver/dac_continuous.h" #include "esp_private/gdma_link.h" #include "esp_check.h" #include "dac_priv_common.h" #include "dac_priv_dma.h" #if CONFIG_PM_ENABLE #include "esp_pm.h" #endif #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 || CONFIG_DAC_CTRL_FUNC_IN_IRAM #define DAC_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) #else #define DAC_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT #endif #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 */ void *user_data; uint32_t cur_index; /* Index of the DMA descriptor that is currently being used by DMA. */ uint32_t used_desc_num; /* Number of used DMA descriptors. Determines cur_index wrap-around. */ QueueHandle_t free_desc_queue; /* Queue of free DMA descriptors indices. Only used in sync writing mode. */ SemaphoreHandle_t mutex; /* Serializes the public writing APIs (sync / cyclic) */ #if SOC_IS(ESP32) portMUX_TYPE dma_lock; /* Serializes the link/restart decision between the sync writing task and the ISR */ bool dma_running; /* Whether the DMA is running (guarded by 'dma_lock'). Only used in sync writing mode. */ #endif gdma_link_list_handle_t link; /* DMA descriptor link list */ uint8_t *bufs[]; /* Array of DMA buffers pointers */ }; typedef enum { DAC_CONT_FSM_IDLE, DAC_CONT_FSM_REGISTERED, DAC_CONT_FSM_ENABLED, // Ready and DMA is NOT running DAC_CONT_FSM_ASYNC, DAC_CONT_FSM_CYCLIC, DAC_CONT_FSM_SYNC, // Sync writing mode. DMA may or may not be running. DAC_CONT_FSM_SYNC_WAIT, // Transition state for sync writing mode DAC_CONT_FSM_WAIT, // Transition state } dac_continuous_fsm_t; static _Atomic dac_continuous_fsm_t s_dac_cont_fsm = DAC_CONT_FSM_IDLE; static const char *TAG = "dac_continuous"; static esp_err_t s_dac_continuous_stop_sync(dac_continuous_handle_t handle); static void s_dac_free_dma_desc(dac_continuous_handle_t handle) { if (handle->link != NULL) { gdma_del_link_list(handle->link); handle->link = NULL; } for (uint32_t i = 0; i < handle->cfg.desc_num; i++) { free(handle->bufs[i]); handle->bufs[i] = NULL; } } static esp_err_t s_dac_alloc_dma_desc(dac_continuous_handle_t handle) { esp_err_t ret = ESP_OK; const uint32_t desc_num = handle->cfg.desc_num; const size_t buf_size = handle->cfg.buf_size; /* Allocate DMA buffers */ for (uint32_t i = 0; i < desc_num; i++) { handle->bufs[i] = heap_caps_calloc(1, buf_size, DAC_DMA_ALLOC_CAPS); ESP_GOTO_ON_FALSE(handle->bufs[i], ESP_ERR_NO_MEM, err, TAG, "failed to allocate dma buffer"); } /** * Create the DMA descriptor link list. * The descriptor format of the link list item is binary-compatible with 'lldesc_t', * so the link list head address can be fed to the old DMA backend directly. */ gdma_link_list_config_t link_cfg = { .num_items = desc_num, .item_alignment = 4, .flags = { .items_in_ext_mem = false, .check_owner = false, }, }; ESP_GOTO_ON_ERROR(gdma_new_link_list(&link_cfg, &handle->link), err, TAG, "failed to create dma link list"); /** * Mount each DMA buffer to its own link list item once. * The buffer<->item binding stays fixed afterwards. */ for (uint32_t i = 0; i < desc_num; i++) { gdma_buffer_mount_config_t mount_cfg = { .buffer = handle->bufs[i], .length = buf_size, .buffer_alignment = 4, .flags = { .mark_final = GDMA_FINAL_LINK_TO_DEFAULT, }, }; ESP_GOTO_ON_ERROR(gdma_link_mount_buffers(handle->link, i, &mount_cfg, 1, NULL), err, TAG, "failed to mount dma buffer"); } return ESP_OK; err: s_dac_free_dma_desc(handle); return ret; } static void IRAM_ATTR s_dac_default_intr_handler(void *arg) { dac_continuous_handle_t handle = arg; BaseType_t need_awoke = pdFALSE; BaseType_t tmp = pdFALSE; 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 (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 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); } #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(); } } esp_err_t dac_continuous_new_channels(const dac_continuous_config_t *cont_cfg, dac_continuous_handle_t *ret_handle) { #if CONFIG_DAC_ENABLE_DEBUG_LOG esp_log_level_set(TAG, ESP_LOG_DEBUG); #endif /* Parameters validation */ 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->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"); esp_err_t ret = ESP_OK; /* FSM: IDLE -> WAIT */ dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_IDLE; if (!atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT)) { ESP_LOGE(TAG, "DAC continuous is already in use"); return ESP_ERR_INVALID_STATE; } /* 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); 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"); handle->cfg = *cont_cfg; #if SOC_IS(ESP32) handle->dma_lock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; #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"); handle->mutex = xSemaphoreCreateMutexWithCaps(DAC_MEM_ALLOC_CAPS); ESP_GOTO_ON_FALSE(handle->mutex, ESP_ERR_NO_MEM, err3, 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"); #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"); /* 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"); /* Connect DAC module to the DMA peripheral */ DAC_RTC_ENTER_CRITICAL(); dac_ll_digi_enable_dma(true); DAC_RTC_EXIT_CRITICAL(); /* FSM: WAIT -> REGISTERED */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_REGISTERED); *ret_handle = handle; return ret; err1: dac_dma_periph_deinit(); err2: s_dac_free_dma_desc(handle); err3: if (handle->free_desc_queue) { vQueueDeleteWithCaps(handle->free_desc_queue); } if (handle->mutex) { vSemaphoreDeleteWithCaps(handle->mutex); } #if CONFIG_PM_ENABLE if (handle->pm_lock) { esp_pm_lock_delete(handle->pm_lock); } #endif free(handle); err4: /* Deregister registered channels */ DAC_CHANNEL_MASK_FOREACH(chan, registered_chan_mask) { dac_priv_deregister_channel(chan); } /* FSM: WAIT -> IDLE */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_IDLE); return ret; } esp_err_t dac_continuous_del_channels(dac_continuous_handle_t handle) { DAC_NULL_POINTER_CHECK(handle); /* FSM: REGISTERED -> WAIT */ dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_REGISTERED; if (!atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT)) { ESP_LOGE(TAG, "DAC continuous is not registered / disabled"); 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"); /* Disconnect DAC module from the DMA peripheral */ DAC_RTC_ENTER_CRITICAL(); dac_ll_digi_enable_dma(false); DAC_RTC_EXIT_CRITICAL(); /* Free allocated resources */ s_dac_free_dma_desc(handle); if (handle->free_desc_queue) { vQueueDeleteWithCaps(handle->free_desc_queue); handle->free_desc_queue = NULL; } if (handle->mutex) { vSemaphoreDeleteWithCaps(handle->mutex); handle->mutex = NULL; } #if CONFIG_PM_ENABLE if (handle->pm_lock) { esp_pm_lock_delete(handle->pm_lock); handle->pm_lock = NULL; } #endif /* Deregister the channels */ DAC_CHANNEL_MASK_FOREACH(chan, handle->cfg.chan_mask) { dac_priv_deregister_channel(chan); } free(handle); /* FSM: WAIT -> IDLE */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_IDLE); return ESP_OK; } esp_err_t dac_continuous_register_event_callback(dac_continuous_handle_t handle, const dac_event_callbacks_t *callbacks, void *user_data) { DAC_NULL_POINTER_CHECK(handle); if (callbacks == NULL) { memset(&handle->cbs, 0, sizeof(dac_event_callbacks_t)); handle->user_data = NULL; return ESP_OK; } #if CONFIG_DAC_ISR_IRAM_SAFE if (callbacks->on_convert_done) { ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_convert_done), ESP_ERR_INVALID_ARG, TAG, "on_convert_done callback not in IRAM"); } if (callbacks->on_stop) { ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_stop), ESP_ERR_INVALID_ARG, TAG, "on_stop callback not in IRAM"); } if (user_data) { ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); } #endif handle->cbs = *callbacks; handle->user_data = user_data; return ESP_OK; } esp_err_t dac_continuous_enable(dac_continuous_handle_t handle) { DAC_NULL_POINTER_CHECK(handle); /* FSM: REGISTERED -> WAIT */ dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_REGISTERED; if (!atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT)) { ESP_LOGE(TAG, "DAC continuous is not registered / disabled"); return ESP_ERR_INVALID_STATE; } #ifdef CONFIG_PM_ENABLE esp_pm_lock_acquire(handle->pm_lock); #endif 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_RTC_ENTER_CRITICAL(); dac_ll_digi_enable_dma(true); DAC_RTC_EXIT_CRITICAL(); /* FSM: WAIT -> ENABLED */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ENABLED); return ESP_OK; } esp_err_t dac_continuous_disable(dac_continuous_handle_t handle) { DAC_NULL_POINTER_CHECK(handle); /* For backward compatibility, check if there is any ongoing cyclic conversion and stop it */ if (atomic_load(&s_dac_cont_fsm) == DAC_CONT_FSM_CYCLIC) { ESP_LOGW(TAG, "It is recommended to explicitly stop the cyclic conversion by calling dac_continuous_stop_cyclically() before performing other operations."); 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 */ 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"); } /* FSM: ENABLED -> WAIT */ dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_ENABLED; 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_RTC_ENTER_CRITICAL(); dac_ll_digi_enable_dma(false); DAC_RTC_EXIT_CRITICAL(); DAC_CHANNEL_MASK_FOREACH(chan, handle->cfg.chan_mask) { dac_priv_disable_channel(chan); } #ifdef CONFIG_PM_ENABLE esp_pm_lock_release(handle->pm_lock); #endif /* FSM: WAIT -> REGISTERED */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_REGISTERED); return ESP_OK; } //////////////////////////////////// Async writing //////////////////////////////////// esp_err_t dac_continuous_start_async_writing(dac_continuous_handle_t handle) { DAC_NULL_POINTER_CHECK(handle); ESP_RETURN_ON_FALSE(handle->cbs.on_convert_done, ESP_ERR_INVALID_STATE, TAG, "please register 'on_convert_done' callback before starting asynchronous writing"); /* For backward compatibility, check if there is any ongoing cyclic conversion and stop it */ if (atomic_load(&s_dac_cont_fsm) == DAC_CONT_FSM_CYCLIC) { ESP_LOGW(TAG, "It is recommended to explicitly stop the cyclic conversion by calling dac_continuous_stop_cyclically() before performing other operations."); 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 */ 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"); } /* FSM: ENABLED -> WAIT */ dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_ENABLED; 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"); /* Link all descriptors as a ring */ for (int i = 0; i < handle->cfg.desc_num; i++) { memset(handle->bufs[i], 0, handle->cfg.buf_size); gdma_link_set_length(handle->link, i, handle->cfg.buf_size); gdma_link_set_owner(handle->link, i, GDMA_LLI_OWNER_DMA); gdma_link_concat(handle->link, i, handle->link, (i < handle->cfg.desc_num - 1) ? i + 1 : 0); } 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)); /* FSM: WAIT -> ASYNC */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ASYNC); return ESP_OK; } esp_err_t dac_continuous_stop_async_writing(dac_continuous_handle_t handle) { DAC_NULL_POINTER_CHECK(handle); /* FSM: ASYNC -> WAIT */ dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_ASYNC; if (!atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT)) { ESP_LOGE(TAG, "DAC continuous is not in asynchronous writing mode"); return ESP_ERR_INVALID_STATE; } dac_dma_periph_trans_stop(); /* FSM: WAIT -> ENABLED */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ENABLED); return ESP_OK; } /* Buffer expanding coefficient, the input buffer will expand to twice length while enabled AUTO_16_BIT */ #if CONFIG_DAC_DMA_AUTO_16BIT_ALIGN #define DAC_16BIT_ALIGN_COEFF 2 #else #define DAC_16BIT_ALIGN_COEFF 1 #endif /** * @brief Load data into the DMA descriptor * * @param auto_balance Whether to balance the data between the last two descriptors. If disabled, we will load as much data as possible. * @return Loaded data length. The remaining data length is (data_len - return_value) * * @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) { /* Calculate the length of the data to be loaded */ size_t buf_size = handle->cfg.buf_size; // must be even size_t need_len = data_len * DAC_16BIT_ALIGN_COEFF; // must be even size_t load_len; // must be even if (need_len <= buf_size) { load_len = need_len; } else if (auto_balance && need_len < buf_size * 2) { /** * The remaining data can fit into two descriptors, so we load half in this round, * and the next round will naturally fall into the branch above. */ load_len = need_len / 2; load_len += load_len & 1U; // make it even } else { load_len = buf_size; } uint8_t *buf = handle->bufs[index]; #if CONFIG_DAC_DMA_AUTO_16BIT_ALIGN /* Load the data to the high 8 bit in the 16-bit width slot */ for (size_t i = 0; i < load_len; i += 2) { buf[i + 1] = data[i / 2] + handle->cfg.offset; } #else /* Load the data into the DMA buffer */ for (size_t i = 0; i < load_len; i++) { buf[i] = data[i] + handle->cfg.offset; } #endif gdma_link_set_length(handle->link, index, load_len); gdma_link_set_owner(handle->link, index, GDMA_LLI_OWNER_DMA); return load_len / DAC_16BIT_ALIGN_COEFF; } esp_err_t dac_continuous_write_asynchronously(dac_continuous_handle_t handle, uint8_t *dma_buf, size_t dma_buf_len, const uint8_t *data, size_t data_len, size_t *bytes_loaded) { DAC_NULL_POINTER_CHECK_ISR(handle); DAC_NULL_POINTER_CHECK_ISR(dma_buf); DAC_NULL_POINTER_CHECK_ISR(data); ESP_RETURN_ON_FALSE_ISR(data_len > 0, ESP_ERR_INVALID_ARG, TAG, "data_len must be > 0"); #if !CONFIG_DAC_DMA_AUTO_16BIT_ALIGN ESP_RETURN_ON_FALSE_ISR(data_len % 2 == 0, ESP_ERR_INVALID_ARG, TAG, "data_len must be even when AUTO_16BIT_ALIGN is disabled"); #endif /* FSM: ASYNC -> WAIT */ dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_ASYNC; if (!atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT)) { ESP_EARLY_LOGE(TAG, "DAC continuous is not in asynchronous writing mode"); return ESP_ERR_INVALID_STATE; } esp_err_t ret = ESP_OK; /** * Normally, dma_buf_len should always be equal to the buffer size of descriptors */ if (dma_buf_len != handle->cfg.buf_size) { ESP_EARLY_LOGW(TAG, "dma_buf_len != DMA buffer size. This parameter is ignored."); } /* Find the corresponding DMA descriptor index */ int index = 0; for (; index < handle->cfg.desc_num; index++) { if (dma_buf == handle->bufs[index]) { break; } } 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); if (bytes_loaded) { *bytes_loaded = loaded_len; } clean_up: /* FSM: WAIT -> ASYNC */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ASYNC); return ret; } //////////////////////////////////// Cyclic writing //////////////////////////////////// esp_err_t dac_continuous_write_cyclically(dac_continuous_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded) { DAC_NULL_POINTER_CHECK(handle); DAC_NULL_POINTER_CHECK(buf); ESP_RETURN_ON_FALSE(buf_size > 0, ESP_ERR_INVALID_ARG, TAG, "buf_size must be > 0"); #if !CONFIG_DAC_DMA_AUTO_16BIT_ALIGN ESP_RETURN_ON_FALSE(buf_size % 2 == 0, ESP_ERR_INVALID_ARG, TAG, "buf_size must be even when AUTO_16BIT_ALIGN is disabled"); #endif ESP_RETURN_ON_FALSE(buf_size * DAC_16BIT_ALIGN_COEFF <= handle->cfg.buf_size * handle->cfg.desc_num, ESP_ERR_INVALID_ARG, TAG, "Data size exceeds the total DMA buffer size"); esp_err_t ret = ESP_OK; /* Serialize with the other writing APIs */ ESP_RETURN_ON_FALSE(xSemaphoreTake(handle->mutex, portMAX_DELAY) == pdTRUE, ESP_ERR_TIMEOUT, TAG, "Take mutex timeout"); /* For backward compatibility, check if there is any ongoing cyclic conversion and stop it */ if (atomic_load(&s_dac_cont_fsm) == DAC_CONT_FSM_CYCLIC) { 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 */ 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"); } /* FSM: ENABLED -> WAIT */ dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_ENABLED; ESP_GOTO_ON_FALSE(atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT), ESP_ERR_INVALID_STATE, err, TAG, "DAC continuous is running/not enabled"); 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); remain_size -= loaded_len; buf += loaded_len; } /* All data should be loaded */ assert(remain_size == 0); /* Link the used descriptors as a ring: 0 -> 1 -> ... -> (index-1) -> 0 */ for (int k = 0; k < index - 1; k++) { gdma_link_concat(handle->link, k, handle->link, k + 1); } gdma_link_concat(handle->link, index - 1, handle->link, 0); handle->cur_index = 0; handle->used_desc_num = index; dac_dma_periph_trans_start(gdma_link_get_head_addr(handle->link)); /* FSM: WAIT -> CYCLIC */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_CYCLIC); if (bytes_loaded) { *bytes_loaded = buf_size; } err: xSemaphoreGive(handle->mutex); return ret; } esp_err_t dac_continuous_stop_cyclically(dac_continuous_handle_t handle) { DAC_NULL_POINTER_CHECK(handle); /* FSM: CYCLIC -> WAIT */ dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_CYCLIC; if (!atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_WAIT)) { ESP_LOGE(TAG, "DAC continuous is not in cyclic writing mode"); return ESP_ERR_INVALID_STATE; } dac_dma_periph_trans_stop(); /* FSM: WAIT -> ENABLED */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ENABLED); return ESP_OK; } //////////////////////////////////// Synchronous writing //////////////////////////////////// esp_err_t dac_continuous_write(dac_continuous_handle_t handle, uint8_t *buf, size_t buf_size, size_t *bytes_loaded, int timeout_ms) { DAC_NULL_POINTER_CHECK(handle); DAC_NULL_POINTER_CHECK(buf); ESP_RETURN_ON_FALSE(buf_size > 0, ESP_ERR_INVALID_ARG, TAG, "buf_size must be > 0"); #if !CONFIG_DAC_DMA_AUTO_16BIT_ALIGN ESP_RETURN_ON_FALSE(buf_size % 2 == 0, ESP_ERR_INVALID_ARG, TAG, "buf_size must be even when AUTO_16BIT_ALIGN is disabled"); #endif esp_err_t ret = ESP_OK; TickType_t timeout_tick = timeout_ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); size_t remain_size = buf_size; /* Serialize with the other writing APIs */ ESP_RETURN_ON_FALSE(xSemaphoreTake(handle->mutex, timeout_tick) == pdTRUE, ESP_ERR_TIMEOUT, TAG, "Take mutex timeout"); dac_continuous_fsm_t fsm = atomic_load(&s_dac_cont_fsm); switch (fsm) { case DAC_CONT_FSM_CYCLIC: /* For backward compatibility, check if there is any ongoing cyclic conversion and stop it */ ESP_LOGW(TAG, "It is recommended to explicitly stop the cyclic conversion by calling dac_continuous_stop_cyclically() before performing other operations."); ESP_GOTO_ON_ERROR(dac_continuous_stop_cyclically(handle), err, TAG, "Failed to stop cyclic conversion"); [[fallthrough]]; case DAC_CONT_FSM_ENABLED: /* FSM: ENABLED -> SYNC_WAIT */ dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_ENABLED; ESP_GOTO_ON_FALSE(atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_SYNC_WAIT), ESP_ERR_INVALID_STATE, err, TAG, "DAC continuous is running/not enabled"); /* Reset the free_desc_queue and the cur_index */ xQueueReset(handle->free_desc_queue); for (int i = 1; i < handle->cfg.desc_num; i++) { // skip 0 because we will use it right now xQueueSend(handle->free_desc_queue, &i, 0); } handle->cur_index = 0; 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); remain_size -= loaded_len; buf += loaded_len; gdma_link_concat(handle->link, 0, NULL, 0); #if SOC_IS(ESP32) /* 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)); goto skip_cas; case DAC_CONT_FSM_SYNC: /* FSM: SYNC -> SYNC_WAIT */ expected_fsm = DAC_CONT_FSM_SYNC; ESP_GOTO_ON_FALSE(atomic_compare_exchange_strong(&s_dac_cont_fsm, &expected_fsm, DAC_CONT_FSM_SYNC_WAIT), ESP_ERR_INVALID_STATE, err, TAG, "CAS failed: SYNC -> SYNC_WAIT"); skip_cas: while (remain_size > 0) { int index; if (xQueueReceive(handle->free_desc_queue, &index, timeout_tick) != pdTRUE) { ret = ESP_ERR_TIMEOUT; break; } size_t loaded_len = s_dac_load_data_into_desc(handle, index, buf, remain_size, true); remain_size -= loaded_len; buf += loaded_len; /** * link: (index-1) -> index -> NULL * NOTE: gdma_link_concat() can normalize the index to be between 0 and desc_num - 1. */ gdma_link_concat(handle->link, index, NULL, 0); #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. * 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)); } portEXIT_CRITICAL(&handle->dma_lock); #else gdma_link_concat(handle->link, index - 1, handle->link, index); dac_dma_periph_trans_append(); #endif } break; default: ESP_LOGE(TAG, "Unexpected FSM state: %u", fsm); ret = ESP_ERR_INVALID_STATE; goto err; } /* FSM: SYNC_WAIT -> SYNC */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_SYNC); err: xSemaphoreGive(handle->mutex); if (bytes_loaded) { *bytes_loaded = buf_size - remain_size; } return ret; } static esp_err_t s_dac_continuous_stop_sync(dac_continuous_handle_t handle) { /* FSM: SYNC -> WAIT */ dac_continuous_fsm_t expected_fsm = DAC_CONT_FSM_SYNC; 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 not in sync writing mode"); #if SOC_IS(ESP32) /** * Serialize with the TEOF ISR which may also call trans_start() on another core. * Both must be guarded by dma_lock to prevent concurrent hardware register access. */ portENTER_CRITICAL(&handle->dma_lock); dac_dma_periph_trans_stop(); handle->dma_running = false; portEXIT_CRITICAL(&handle->dma_lock); #else dac_dma_periph_trans_stop(); #endif /* FSM: WAIT -> ENABLED */ atomic_store(&s_dac_cont_fsm, DAC_CONT_FSM_ENABLED); return ESP_OK; }