/*
 * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdatomic.h>
#include <string.h>
#include <sys/queue.h>
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/idf_additions.h"
#include "sdkconfig.h"

#include "rom/lldesc.h"
#include "soc/soc_caps.h"
#include "driver/dac_continuous.h"

#include "dac_priv_common.h"
#include "dac_priv_dma.h"

#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 "esp_check.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)

#define DAC_STAILQ_REMOVE(head, elm, type, field) do {          \
    if ((head)->stqh_first == (elm)) {                          \
        STAILQ_REMOVE_HEAD((head), field);                      \
    } else {                                                    \
        struct type *curelm = (head)->stqh_first;               \
        while (curelm->field.stqe_next != (elm) &&              \
               curelm->field.stqe_next != NULL)                 \
            curelm = curelm->field.stqe_next;                   \
        if (curelm->field.stqe_next && (curelm->field.stqe_next = \
            curelm->field.stqe_next->field.stqe_next) == NULL)  \
                (head)->stqh_last = &(curelm)->field.stqe_next; \
    }                                                           \
} while (/*CONSTCOND*/0)

struct dac_continuous_s {
    uint32_t                chan_cnt;
    dac_continuous_config_t cfg;
    atomic_bool             is_enabled;
    atomic_bool             is_cyclic;
    atomic_bool             is_running;
    atomic_bool             is_async;
    intr_handle_t           intr_handle;                /* Interrupt handle */
#if CONFIG_PM_ENABLE
    esp_pm_lock_handle_t    pm_lock;
#endif
    SemaphoreHandle_t       mutex;
    QueueHandle_t           desc_pool;                  /* The pool of available descriptors
                                                         * The descriptors in the pool are not linked in to pending chain */

    lldesc_t                **desc;
    uint8_t                 **bufs;
    STAILQ_HEAD(desc_chain_s, lldesc_s) head;           /* Head of the descriptor chain
                                                         * The descriptors in the chain are pending to be sent or sending now */
    dac_event_callbacks_t   cbs;                        /* Interrupt callbacks */
    void                    *user_data;
};

static const char *TAG = "dac_continuous";

static bool s_dma_in_use = false;
static portMUX_TYPE desc_spinlock = portMUX_INITIALIZER_UNLOCKED;

#define DESC_ENTER_CRITICAL()        portENTER_CRITICAL(&desc_spinlock)
#define DESC_EXIT_CRITICAL()         portEXIT_CRITICAL(&desc_spinlock)

#define DESC_ENTER_CRITICAL_ISR()    portENTER_CRITICAL_ISR(&desc_spinlock)
#define DESC_EXIT_CRITICAL_ISR()     portEXIT_CRITICAL_ISR(&desc_spinlock)

static void s_dac_free_dma_desc(dac_continuous_handle_t handle)
{
    STAILQ_INIT(&handle->head);
    if (handle->desc != NULL) {
        if (handle->desc[0]) {
            free(handle->desc[0]);
        }
        free(handle->desc);
        handle->desc = NULL;
    }

    if (handle->bufs != NULL) {
        for (int i = 0; i < handle->cfg.desc_num; i++) {
            if (handle->bufs[i]) {
                free(handle->bufs[i]);
                handle->bufs[i] = NULL;
            }
        }
        free(handle->bufs);
        handle->bufs = NULL;
    }
}

static esp_err_t s_dac_alloc_dma_desc(dac_continuous_handle_t handle)
{
    esp_err_t ret = ESP_OK;

    STAILQ_INIT(&handle->head);
    handle->desc = (lldesc_t **) heap_caps_calloc(handle->cfg.desc_num, sizeof(lldesc_t *), DAC_DMA_ALLOC_CAPS);
    ESP_RETURN_ON_FALSE(handle->desc, ESP_ERR_NO_MEM, TAG, "failed to allocate dma descriptor array");
    handle->bufs = (uint8_t **) heap_caps_calloc(handle->cfg.desc_num, sizeof(uint8_t *), DAC_DMA_ALLOC_CAPS);
    ESP_RETURN_ON_FALSE(handle->bufs, ESP_ERR_NO_MEM, TAG, "failed to allocate dma buffer array");
    lldesc_t *descs = (lldesc_t *)heap_caps_calloc(handle->cfg.desc_num, sizeof(lldesc_t), DAC_DMA_ALLOC_CAPS);
    ESP_RETURN_ON_FALSE(descs, ESP_ERR_NO_MEM, TAG, "failed to allocate dma descriptors");
    for (int cnt = 0; cnt < handle->cfg.desc_num; cnt++) {
        /* Allocate DMA descriptor */
        handle->desc[cnt] = &descs[cnt];
        ESP_GOTO_ON_FALSE(handle->desc[cnt], ESP_ERR_NO_MEM, err, TAG,  "failed to allocate dma descriptor");
        ESP_LOGD(TAG, "desc[%d] %p", cnt, handle->desc[cnt]);
        /* Allocate DMA buffer */
        handle->bufs[cnt] = (uint8_t *) heap_caps_calloc(1, handle->cfg.buf_size, DAC_DMA_ALLOC_CAPS);
        ESP_GOTO_ON_FALSE(handle->bufs[cnt], ESP_ERR_NO_MEM, err, TAG,  "failed to allocate dma buffer");
        /* Assign initial value */
        lldesc_config(handle->desc[cnt], LLDESC_SW_OWNED, 1, 0, handle->cfg.buf_size);
        handle->desc[cnt]->size = handle->cfg.buf_size;
        handle->desc[cnt]->buf = handle->bufs[cnt];
        handle->desc[cnt]->offset = 0;
    }

    return ESP_OK;
err:
    /* Free DMA buffer if failed to allocate memory */
    s_dac_free_dma_desc(handle);
    return ret;
}

static void IRAM_ATTR s_dac_default_intr_handler(void *arg)
{
    dac_continuous_handle_t handle = (dac_continuous_handle_t)arg;
    uint32_t dummy;
    BaseType_t need_awoke = pdFALSE;
    BaseType_t tmp = pdFALSE;
    uint32_t intr_mask = dac_dma_periph_intr_is_triggered();
    if (intr_mask & DAC_DMA_EOF_INTR) {
        lldesc_t *fdesc = (lldesc_t *)dac_dma_periph_intr_get_eof_desc();
        if (!atomic_load(&handle->is_cyclic)) {
            /* Remove the descriptor in the chain that finished sent */
            DESC_ENTER_CRITICAL_ISR();
            if (STAILQ_FIRST(&handle->head) != NULL) {
                DAC_STAILQ_REMOVE(&handle->head, fdesc, lldesc_s, qe);
            }
            DESC_EXIT_CRITICAL_ISR();

            if (xQueueIsQueueFullFromISR(handle->desc_pool) == pdTRUE) {
                xQueueReceiveFromISR(handle->desc_pool, &dummy, &tmp);
                need_awoke |= tmp;
            }
            xQueueSendFromISR(handle->desc_pool, &fdesc, &tmp);
            need_awoke |= tmp;
        }
        if (handle->cbs.on_convert_done) {
            dac_event_data_t evt_data = {
                .buf = (void *)fdesc->buf,
                .buf_size = handle->cfg.buf_size,
                .write_bytes = fdesc->length,
            };
            need_awoke |= handle->cbs.on_convert_done(handle, &evt_data, handle->user_data);
        }
    }
    if (intr_mask & DAC_DMA_TEOF_INTR) {
        /* Total end of frame interrupt received, DMA stopped */
        atomic_store(&handle->is_running, false);
        if (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(cont_cfg->chan_mask <= DAC_CHANNEL_MASK_ALL, ESP_ERR_INVALID_ARG, TAG, "invalid dac channel id");
    ESP_RETURN_ON_FALSE(cont_cfg->desc_num > 1, ESP_ERR_INVALID_STATE, TAG, "at least two DMA descriptor needed");
    ESP_RETURN_ON_FALSE(!s_dma_in_use, ESP_ERR_INVALID_STATE, TAG, "DMA already in use");

    esp_err_t ret = ESP_OK;

    /* Register the channels */
    for (uint32_t i = 0, mask = cont_cfg->chan_mask; mask; mask >>= 1, i++) {
        if (mask & 0x01) {
            ESP_GOTO_ON_ERROR(dac_priv_register_channel(i, "dac continuous"),
                              err4, TAG, "register dac channel %"PRIu32" failed", i);
        }
    }

    /* Allocate continuous mode struct */
    dac_continuous_handle_t handle = heap_caps_calloc(1, sizeof(struct dac_continuous_s), DAC_MEM_ALLOC_CAPS);
    ESP_RETURN_ON_FALSE(handle, ESP_ERR_NO_MEM, TAG, "no memory for the dac continuous mode structure");

    /* Allocate queue and mutex*/
    handle->desc_pool = xQueueCreateWithCaps(cont_cfg->desc_num, sizeof(lldesc_t *), DAC_MEM_ALLOC_CAPS);
    handle->mutex = xSemaphoreCreateMutexWithCaps(DAC_MEM_ALLOC_CAPS);
    ESP_GOTO_ON_FALSE(handle->desc_pool, ESP_ERR_NO_MEM, err3, TAG, "no memory for message queue");
    ESP_GOTO_ON_FALSE(handle->mutex, ESP_ERR_NO_MEM, err3, TAG, "no memory for channels 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
    handle->chan_cnt = __builtin_popcount(cont_cfg->chan_mask);
    memcpy(&(handle->cfg), cont_cfg, sizeof(dac_continuous_config_t));

    atomic_init(&handle->is_enabled, false);
    atomic_init(&handle->is_cyclic, false);
    atomic_init(&handle->is_running, false);
    atomic_init(&handle->is_async, false);

    /* Allocate DMA buffer */
    ESP_GOTO_ON_ERROR(s_dac_alloc_dma_desc(handle), err2, TAG, "Failed to allocate memory for DMA 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();
    s_dma_in_use = true;
    *ret_handle = handle;
    return ret;

err1:
    dac_dma_periph_deinit();
err2:
    s_dac_free_dma_desc(handle);
err3:
    if (handle->desc_pool) {
        vQueueDeleteWithCaps(handle->desc_pool);
    }
    if (handle->mutex) {
        vSemaphoreDeleteWithCaps(handle->mutex);
    }
    free(handle);
err4:
    /* Deregister the channels */
    for (uint32_t i = 0, mask = cont_cfg->chan_mask; mask; mask >>= 1, i++) {
        if (mask & 0x01) {
            dac_priv_deregister_channel(i);
        }
    }
    return ret;
}

esp_err_t dac_continuous_del_channels(dac_continuous_handle_t handle)
{
    DAC_NULL_POINTER_CHECK(handle);
    ESP_RETURN_ON_FALSE(!atomic_load(&handle->is_enabled), ESP_ERR_INVALID_STATE, TAG, "dac continuous output not disabled yet");

    /* 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->desc_pool) {
        vQueueDeleteWithCaps(handle->desc_pool);
        handle->desc_pool = 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 */
    for (uint32_t i = 0, mask = handle->cfg.chan_mask; mask; mask >>= 1, i++) {
        if (mask & 0x01) {
            dac_priv_deregister_channel(i);
        }
    }
    free(handle);
    s_dma_in_use = false;

    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) {
        memset(&handle->cbs, 0, sizeof(dac_event_callbacks_t));
        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
    memcpy(&handle->cbs, callbacks, sizeof(dac_event_callbacks_t));
    handle->user_data = user_data;

    return ESP_OK;
}

esp_err_t dac_continuous_enable(dac_continuous_handle_t handle)
{
    DAC_NULL_POINTER_CHECK(handle);
    ESP_RETURN_ON_FALSE(!atomic_load(&handle->is_enabled), ESP_ERR_INVALID_STATE, TAG, "dac continuous has already enabled");
    esp_err_t ret = ESP_OK;
    /* Reset the descriptor pool */
    xQueueReset(handle->desc_pool);
    for (int i = 0; i < handle->cfg.desc_num; i++) {
        ESP_GOTO_ON_FALSE(xQueueSend(handle->desc_pool, &handle->desc[i], 0) == pdTRUE,
                          ESP_ERR_INVALID_STATE, err, TAG, "the descriptor pool is not cleared");
    }
#ifdef CONFIG_PM_ENABLE
    esp_pm_lock_acquire(handle->pm_lock);
#endif
    for (uint32_t i = 0, mask = handle->cfg.chan_mask; mask; mask >>= 1, i++) {
        if (mask & 0x01) {
            dac_priv_enable_channel(i);
        }
    }
    dac_dma_periph_enable();
    esp_intr_enable(handle->intr_handle);
    DAC_RTC_ENTER_CRITICAL();
    dac_ll_digi_enable_dma(true);
    DAC_RTC_EXIT_CRITICAL();
    atomic_store(&handle->is_enabled, true);
err:
    return ret;
}

esp_err_t dac_continuous_disable(dac_continuous_handle_t handle)
{
    DAC_NULL_POINTER_CHECK(handle);
    ESP_RETURN_ON_FALSE(atomic_load(&handle->is_enabled), ESP_ERR_INVALID_STATE, TAG, "dac continuous has already disabled");
    atomic_store(&handle->is_enabled, false);
    dac_dma_periph_disable();
    esp_intr_disable(handle->intr_handle);
    DAC_RTC_ENTER_CRITICAL();
    dac_ll_digi_enable_dma(false);
    DAC_RTC_EXIT_CRITICAL();
    atomic_store(&handle->is_running, false);
    for (uint32_t i = 0, mask = handle->cfg.chan_mask; mask; mask >>= 1, i++) {
        if (mask & 0x01) {
            dac_priv_disable_channel(i);
        }
    }
#ifdef CONFIG_PM_ENABLE
    esp_pm_lock_release(handle->pm_lock);
#endif
    return ESP_OK;
}

esp_err_t dac_continuous_start_async_writing(dac_continuous_handle_t handle)
{
    DAC_NULL_POINTER_CHECK(handle);
    ESP_RETURN_ON_FALSE(atomic_load(&handle->is_enabled), ESP_ERR_INVALID_STATE, TAG, "dac continuous has not been enabled");
    ESP_RETURN_ON_FALSE(handle->cbs.on_convert_done, ESP_ERR_INVALID_STATE, TAG,
                        "please register 'on_convert_done' callback before starting asynchronous writing");

    atomic_store(&handle->is_async, true);

    if (atomic_load(&handle->is_cyclic)) {
        /* Break the DMA descriptor chain to stop the DMA first */
        for (int i = 0; i < handle->cfg.desc_num; i++) {
            STAILQ_NEXT(handle->desc[i], qe) = NULL;
        }
    }
    /* Wait for the previous DMA stop */
    while (atomic_load(&handle->is_running)) {}

    /* 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);
        STAILQ_NEXT(handle->desc[i], qe) = (i < handle->cfg.desc_num - 1) ? handle->desc[i + 1] : handle->desc[0];
    }
    dac_dma_periph_dma_trans_start((uint32_t)handle->desc[0]);
    atomic_store(&handle->is_running, true);

    return ESP_OK;
}

esp_err_t dac_continuous_stop_async_writing(dac_continuous_handle_t handle)
{
    DAC_NULL_POINTER_CHECK(handle);
    ESP_RETURN_ON_FALSE(atomic_load(&handle->is_async), ESP_ERR_INVALID_STATE, TAG, "dac asynchronous writing has not been started");

    /* Break the DMA descriptor chain to stop the DMA first */
    for (int i = 0; i < handle->cfg.desc_num; i++) {
        STAILQ_NEXT(handle->desc[i], qe) = NULL;
    }
    /* Wait for the previous DMA stop */
    while (atomic_load(&handle->is_running)) {}
    atomic_store(&handle->is_async, false);

    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

static size_t s_dac_load_data_into_buf(dac_continuous_handle_t handle, uint8_t *dest, size_t dest_len, const uint8_t *src, size_t src_len)
{
    size_t load_bytes = 0;
#if CONFIG_DAC_DMA_AUTO_16BIT_ALIGN
    /* Load the data to the high 8 bit in the 16-bit width slot */
    load_bytes = (src_len * 2 > dest_len) ? dest_len : src_len * 2;
    for (int i = 0; i < load_bytes; i += 2) {
        dest[i + 1] = src[i / 2] + handle->cfg.offset;
    }
#else
    /* Load the data into the DMA buffer */
    load_bytes = (src_len > dest_len) ? dest_len : src_len;
    for (int i = 0; i < load_bytes; i++) {
        dest[i] = src[i] + handle->cfg.offset;
    }
#endif
    return load_bytes;
}

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(atomic_load(&handle->is_async), ESP_ERR_INVALID_STATE, TAG, "The asynchronous writing has not started");
    int i;
    for (i = 0; i < handle->cfg.desc_num; i++) {
        if (dma_buf == handle->bufs[i]) {
            break;
        }
    }
    /* Fail to find the DMA buffer address */
    ESP_RETURN_ON_FALSE_ISR(i < handle->cfg.desc_num, ESP_ERR_NOT_FOUND, TAG, "Not found the corresponding DMA buffer");
    size_t load_bytes = s_dac_load_data_into_buf(handle, dma_buf, dma_buf_len, data, data_len);
    lldesc_config(handle->desc[i], LLDESC_HW_OWNED, 1, 0, load_bytes);
    if (bytes_loaded) {
        *bytes_loaded = load_bytes / DAC_16BIT_ALIGN_COEFF;
    }
    return ESP_OK;
}

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);
    ESP_RETURN_ON_FALSE(atomic_load(&handle->is_enabled), ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has not been enabled");
    ESP_RETURN_ON_FALSE(!atomic_load(&handle->is_async), ESP_ERR_INVALID_STATE, TAG, "Asynchronous writing is running, can't write cyclically");
    ESP_RETURN_ON_FALSE(buf_size <= handle->cfg.buf_size * handle->cfg.desc_num, ESP_ERR_INVALID_ARG, TAG,
                        "The cyclic buffer size exceeds the total DMA buffer size: %"PRIu32"(desc_num) * %d(buf_size) = %"PRIu32,
                        handle->cfg.desc_num, handle->cfg.buf_size, handle->cfg.buf_size * handle->cfg.desc_num);

    esp_err_t ret = ESP_OK;
    xSemaphoreTake(handle->mutex,  portMAX_DELAY);
    if (atomic_load(&handle->is_cyclic)) {
        /* Break the DMA descriptor chain to stop the DMA first */
        for (int i = 0; i < handle->cfg.desc_num; i++) {
            STAILQ_NEXT(handle->desc[i], qe) = NULL;
        }
    }
    /* Wait for the previous DMA stop */
    while (atomic_load(&handle->is_running)) {}
    atomic_store(&handle->is_cyclic, true);

    size_t src_buf_size = buf_size;
    uint32_t split = 1;
    int i;
    for (i = 0; i < handle->cfg.desc_num && buf_size > 0; i++) {
        /* To spread data more averagely, average the last two descriptors */
        split = (buf_size * DAC_16BIT_ALIGN_COEFF < handle->cfg.buf_size * 2) ? 3 - split : 1;
        size_t load_bytes = s_dac_load_data_into_buf(handle, handle->bufs[i], handle->cfg.buf_size, buf, buf_size / split);
        lldesc_config(handle->desc[i], LLDESC_HW_OWNED, 1, 0, load_bytes);
        /* Link to the next descriptor */
        STAILQ_NEXT(handle->desc[i], qe) = (i < handle->cfg.desc_num - 1) ? handle->desc[i + 1] : NULL;
        buf_size -= load_bytes / DAC_16BIT_ALIGN_COEFF;
        buf += load_bytes / DAC_16BIT_ALIGN_COEFF;
    }
    /* Link the tail to the head as a ring */
    STAILQ_NEXT(handle->desc[i - 1], qe) = handle->desc[0];

    dac_dma_periph_dma_trans_start((uint32_t)handle->desc[0]);
    atomic_store(&handle->is_running, true);
    if (bytes_loaded) {
        *bytes_loaded = src_buf_size - buf_size;
    }

    xSemaphoreGive(handle->mutex);
    return ret;
}

static esp_err_t s_dac_wait_to_load_dma_data(dac_continuous_handle_t handle, uint8_t *buf, size_t buf_size, size_t *w_size, TickType_t timeout_tick)
{
    lldesc_t *desc;
    /* Try to get the descriptor from the pool */
    ESP_RETURN_ON_FALSE(xQueueReceive(handle->desc_pool, &desc, timeout_tick) == pdTRUE,
                        ESP_ERR_TIMEOUT, TAG, "Get available descriptor timeout");
    /* To ensure it is not in the pending desc chain */
    if (STAILQ_FIRST(&handle->head) != NULL) {
        DAC_STAILQ_REMOVE(&handle->head, desc, lldesc_s, qe);
    }

    static bool split_flag = false;
    uint8_t *dma_buf = (uint8_t *)desc->buf;
    if (buf_size * DAC_16BIT_ALIGN_COEFF < 2 * handle->cfg.buf_size) {
        if (!split_flag) {
            buf_size >>= 1;
            split_flag = true;
        } else {
            split_flag = false;
        }
    }
    size_t load_bytes = s_dac_load_data_into_buf(handle, dma_buf, handle->cfg.buf_size, buf, buf_size);
    lldesc_config(desc, LLDESC_HW_OWNED, 1, 0, load_bytes);
    desc->size = load_bytes;
    *w_size = load_bytes / DAC_16BIT_ALIGN_COEFF;
    /* Insert the loaded descriptor to the end of the chain, waiting to be sent */
    DESC_ENTER_CRITICAL();
    STAILQ_INSERT_TAIL(&handle->head, desc, qe);
    DESC_EXIT_CRITICAL();

    return ESP_OK;
}

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(atomic_load(&handle->is_enabled), ESP_ERR_INVALID_STATE, TAG, "This set of DAC channels has not been enabled");
    ESP_RETURN_ON_FALSE(!atomic_load(&handle->is_async), ESP_ERR_INVALID_STATE, TAG, "Asynchronous writing is running, can't write synchronously");

    esp_err_t ret = ESP_OK;
    TickType_t timeout_tick = timeout_ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
    ESP_RETURN_ON_FALSE(xSemaphoreTake(handle->mutex,  timeout_tick) == pdTRUE, ESP_ERR_TIMEOUT, TAG, "Take semaphore timeout");

    size_t w_size = 0;
    size_t src_buf_size = buf_size;
    /* Reset the desc_pool and chain if called cyclic function last time  */
    if (atomic_load(&handle->is_cyclic)) {
        xQueueReset(handle->desc_pool);
        /* Break the chain if DMA still running */
        for (int i = 0; i < handle->cfg.desc_num; i++) {
            STAILQ_NEXT(handle->desc[i], qe) = NULL;
            xQueueSend(handle->desc_pool, &handle->desc[i], 0);
        }
        STAILQ_INIT(&handle->head);
        atomic_store(&handle->is_cyclic, false);
    }
    /* When there is no descriptor in the chain, DMA has stopped, load data and start the DMA link */
    if (STAILQ_FIRST(&handle->head) == NULL) {
        /* Wait for the previous DMA stop */
        while (atomic_load(&handle->is_running)) {}
        for (int i = 0;
                i < handle->cfg.desc_num && buf_size > 0;
                i++, buf += w_size, buf_size -= w_size) {
            ESP_GOTO_ON_ERROR(s_dac_wait_to_load_dma_data(handle, buf, buf_size, &w_size, timeout_tick), err, TAG, "Load data failed");
        }
        dac_dma_periph_dma_trans_start((uint32_t)(STAILQ_FIRST(&handle->head)));
        atomic_store(&handle->is_running, true);
    }
    /* If the source buffer is not totally loaded, keep loading the rest data */
    while (buf_size > 0) {
        ESP_GOTO_ON_ERROR(s_dac_wait_to_load_dma_data(handle, buf, buf_size, &w_size, timeout_tick), err, TAG, "Load data failed");
        /* If the DMA stopped but there are still some descriptors not sent, start the DMA again */
        DESC_ENTER_CRITICAL();
        if (STAILQ_FIRST(&handle->head) && !atomic_load(&handle->is_running)) {
            dac_dma_periph_dma_trans_start((uint32_t)(STAILQ_FIRST(&handle->head)));
            atomic_store(&handle->is_running, true);
        }
        DESC_EXIT_CRITICAL();
        buf += w_size;
        buf_size -= w_size;
    }
err:
    /* The bytes number that has been loaded */
    if (bytes_loaded) {
        *bytes_loaded = src_buf_size - buf_size;
    }
    xSemaphoreGive(handle->mutex);
    return ret;
}