/***************************************************************************
 *
 * Copyright 2015-2019 BES.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 *
 ****************************************************************************/
#include "plat_addr_map.h"
#include "cmsis_nvic.h"
#include "hal_chipid.h"
#include "hal_cmu.h"
#include "hal_dma.h"
#include "hal_location.h"
#include "hal_timer.h"
#include "hal_trace.h"
#include "reg_dma.h"

#if (defined(CHIP_BEST1000) || defined(CHIP_BEST2000) || defined(CHIP_BEST2300)) && !(defined(ROM_BUILD) || defined(PROGRAMMER))
#define DMA_REMAP
#endif

enum HAL_DMA_INST_T {
    HAL_DMA_INST_AUDMA = 0,
#if (CHIP_HAS_DMA == 1)
    HAL_DMA_INST_GPDMA = HAL_DMA_INST_AUDMA,
#else
    HAL_DMA_INST_GPDMA,
#endif

    HAL_DMA_INST_QTY
};

struct HAL_DMA_FIFO_ADDR_T {
    uint32_t count;
    const uint32_t *addr;
};

struct HAL_DMA_FIFO_PERIPH_T {
    uint32_t count;
    const enum HAL_DMA_PERIPH_T *periph;
#ifdef DMA_REMAP
    const enum HAL_DMA_PERIPH_T *periph_remap;
#endif
};

/*****************************************************************************
 * Private types/enumerations/variables
 ****************************************************************************/

#include CHIP_SPECIFIC_HDR(hal_dmacfg)

static struct DMA_T * const dma[HAL_DMA_INST_QTY] = {
    (struct DMA_T *)AUDMA_BASE,
#if (CHIP_HAS_DMA > 1)
    (struct DMA_T *)GPDMA_BASE,
#endif
};

static const IRQn_Type irq_type[HAL_DMA_INST_QTY] = {
    AUDMA_IRQn,
#if (CHIP_HAS_DMA > 1)
    GPDMA_IRQn,
#endif
};

static void hal_audma_irq_handler(void);
#if (CHIP_HAS_DMA > 1)
static void hal_gpdma_irq_handler(void);
#endif

static const uint32_t irq_entry[HAL_DMA_INST_QTY] = {
    (uint32_t)hal_audma_irq_handler,
#if (CHIP_HAS_DMA > 1)
    (uint32_t)hal_gpdma_irq_handler,
#endif
};

static const struct HAL_DMA_FIFO_ADDR_T fifo_addr[HAL_DMA_INST_QTY] = {
    {
        .count = ARRAY_SIZE(audma_fifo_addr),
        .addr = audma_fifo_addr,
    },
#if (CHIP_HAS_DMA > 1)
    {
        .count = ARRAY_SIZE(gpdma_fifo_addr),
        .addr = gpdma_fifo_addr,
    },
#endif
};

static const struct HAL_DMA_FIFO_PERIPH_T fifo_periph[HAL_DMA_INST_QTY] = {
    {
        .count = ARRAY_SIZE(audma_fifo_periph),
        .periph = audma_fifo_periph,
#ifdef DMA_REMAP
        .periph_remap = audma_fifo_periph_remap,
#endif
    },
#if (CHIP_HAS_DMA > 1)
    {
        .count = ARRAY_SIZE(gpdma_fifo_periph),
        .periph = gpdma_fifo_periph,
#ifdef DMA_REMAP
        .periph_remap = gpdma_fifo_periph_remap,
#endif
    },
#endif
};

static const uint8_t chan_start[HAL_DMA_INST_QTY] = {
    AUDMA_CHAN_START,
#if (CHIP_HAS_DMA > 1)
    GPDMA_CHAN_START,
#endif
};

static const uint8_t chan_num[HAL_DMA_INST_QTY] = {
    AUDMA_CHAN_NUM,
#if (CHIP_HAS_DMA > 1)
    GPDMA_CHAN_NUM,
#endif
};

/* Channel array to monitor free channel */
static bool chan_enabled[HAL_DMA_INST_QTY][DMA_NUMBER_CHANNELS];

static HAL_DMA_IRQ_HANDLER_T handler[HAL_DMA_INST_QTY][DMA_NUMBER_CHANNELS];

#ifdef DMA_REMAP
static uint32_t periph_remap_bitmap[HAL_DMA_INST_QTY];
#endif

#ifdef CORE_SLEEP_POWER_DOWN
static uint32_t saved_dma_regs[HAL_DMA_INST_QTY];
#endif

static const char * const err_invalid_inst = "Invalid DMA inst: %u";

static const char * const err_invalid_chan[HAL_DMA_INST_QTY] = {
    "Invalid AUDMA chan: %u",
#if (CHIP_HAS_DMA > 1)
    "Invalid GPDMA chan: %u",
#endif
};

static bool dma_opened = false;

static HAL_DMA_DELAY_FUNC dma_delay = NULL;


/*****************************************************************************
 * Public types/enumerations/variables
 ****************************************************************************/

/*****************************************************************************
 * Private functions
 ****************************************************************************/
static void hal_dma_delay(uint32_t ms)
{
    if (dma_delay && !in_isr()) {
        dma_delay(ms);
    } else {
        hal_sys_timer_delay(MS_TO_TICKS(ms));
    }
}

static inline uint8_t generate_chan(enum HAL_DMA_INST_T inst, uint8_t hwch)
{
    return ((inst << 4) | (hwch & 0xF));
}

static inline enum HAL_DMA_INST_T get_inst_from_chan(uint8_t ch)
{
    return (enum HAL_DMA_INST_T)(ch >> 4);
}

static inline uint8_t get_hwch_from_chan(uint8_t ch)
{
    return (ch & 0xF);
}

static inline int get_index_from_periph(enum HAL_DMA_PERIPH_T periph, enum HAL_DMA_INST_T *pinst, uint8_t *pidx)
{
    enum HAL_DMA_INST_T inst;
    int i;

    for (inst = 0; inst < HAL_DMA_INST_QTY; inst++) {
        for (i = 0; i < fifo_periph[inst].count; i++) {
            if (fifo_periph[inst].periph[i] == periph) {
                *pinst = inst;
                *pidx = i;
                return 0;
            }
        }
    }

    return 1;
}

#ifdef DMA_REMAP
static inline int get_remap_index_from_periph(enum HAL_DMA_INST_T inst, enum HAL_DMA_PERIPH_T periph, uint8_t *pidx)
{
    int i;

    if (fifo_periph[inst].periph_remap == NULL) {
        return 1;
    }

    for (i = 0; i < fifo_periph[inst].count; i++) {
        if (fifo_periph[inst].periph_remap[i] == periph) {
            *pidx = i;
            return 0;
        }
    }

    return 2;
}
#endif

static inline uint32_t hal_dma_get_periph_addr(enum HAL_DMA_PERIPH_T periph)
{
    int ret;
    enum HAL_DMA_INST_T inst;
    uint8_t index;

    ret = get_index_from_periph(periph, &inst, &index);
    if (ret) {
        return 0;
    }

    return fifo_addr[inst].addr[index];
}

/* Initialize the DMA */
static void hal_dma_open_inst(enum HAL_DMA_INST_T inst)
{
    uint8_t i;

    /* Reset all channel configuration register */
    for (i = 0; i < DMA_NUMBER_CHANNELS; i++) {
        dma[inst]->CH[i].CONFIG = 0;
    }

    /* Clear all DMA interrupt and error flag */
    dma[inst]->INTTCCLR = ~0UL;
    dma[inst]->INTERRCLR = ~0UL;

    dma[inst]->DMACONFIG = (dma[inst]->DMACONFIG & ~(DMA_DMACONFIG_AHB1_BIGENDIAN |
        DMA_DMACONFIG_AHB2_BIGENDIAN | DMA_DMACONFIG_CLK_EN_MASK)) |
        DMA_DMACONFIG_EN |
#ifdef CHIP_BEST3001
        DMA_DMACONFIG_TC_IRQ_EN_MASK |
#endif
        0;

#ifdef CHIP_BEST1400
    dma[inst]->DMACONFIG &= ~DMA_DMACONFIG_TC_IRQ_EN_MASK;
#endif

    /* Reset all channels are free */
    for (i = 0; i < DMA_NUMBER_CHANNELS; i++) {
        chan_enabled[inst][i] = false;
    }

    NVIC_SetVector(irq_type[inst], irq_entry[inst]);
    if (inst == HAL_DMA_INST_AUDMA) {
        NVIC_SetPriority(irq_type[inst], IRQ_PRIORITY_ABOVENORMAL);
    } else {
        NVIC_SetPriority(irq_type[inst], IRQ_PRIORITY_NORMAL);
    }
    NVIC_ClearPendingIRQ(irq_type[inst]);
    NVIC_EnableIRQ(irq_type[inst]);
}

/* Shutdown the DMA */
static void hal_dma_close_inst(enum HAL_DMA_INST_T inst)
{
    NVIC_DisableIRQ(irq_type[inst]);
    dma[inst]->DMACONFIG = 0;
}

static bool hal_dma_chan_busy_inst(enum HAL_DMA_INST_T inst, uint8_t hwch)
{
    return !!(dma[inst]->ENBLDCHNS & DMA_STAT_CHAN(hwch));
}

static void hal_dma_handle_chan_irq(enum HAL_DMA_INST_T inst, uint8_t hwch)
{
    uint32_t remains;
    struct HAL_DMA_DESC_T *lli;
    bool tcint, errint;

    /* Check counter terminal status */
    tcint = !!(dma[inst]->INTTCSTAT & DMA_STAT_CHAN(hwch));
    /* Check error terminal status */
    errint = !!(dma[inst]->INTERRSTAT & DMA_STAT_CHAN(hwch));

    if (tcint || errint) {
        if (tcint) {
            /* Clear terminate counter Interrupt pending */
            dma[inst]->INTTCCLR = DMA_STAT_CHAN(hwch);
        }
        if (errint) {
            /* Clear error counter Interrupt pending */
            dma[inst]->INTERRCLR = DMA_STAT_CHAN(hwch);
        }

        if (handler[inst][hwch]) {
            remains = GET_BITFIELD(dma[inst]->CH[hwch].CONTROL, DMA_CONTROL_TRANSFERSIZE);
            lli = (struct HAL_DMA_DESC_T *)dma[inst]->CH[hwch].LLI;
            handler[inst][hwch](generate_chan(inst, hwch), remains, errint, lli);
        }
    }
}

static void hal_dma_irq_handler(enum HAL_DMA_INST_T inst)
{
    uint8_t hwch;

    for (hwch = 0; hwch < DMA_NUMBER_CHANNELS; hwch++) {
        if ((dma[inst]->INTSTAT & DMA_STAT_CHAN(hwch)) == 0) {
            continue;
        }
        hal_dma_handle_chan_irq(inst, hwch);
    }
}

static void hal_audma_irq_handler(void)
{
    hal_dma_irq_handler(HAL_DMA_INST_AUDMA);
}

#if (CHIP_HAS_DMA > 1)
static void hal_gpdma_irq_handler(void)
{
    hal_dma_irq_handler(HAL_DMA_INST_GPDMA);
}
#endif

static enum HAL_DMA_RET_T hal_dma_init_control(uint32_t *ctrl,
                                               const struct HAL_DMA_CH_CFG_T *cfg,
                                               int tc_irq)
{
    uint32_t addr_inc;
    enum HAL_DMA_FLOW_CONTROL_T type;

    if (cfg->src_tsize > HAL_DMA_MAX_DESC_XFER_SIZE) {
        return HAL_DMA_ERR;
    }

#ifdef CHIP_BEST1000
    type = cfg->type;
#else
    type = cfg->type & ~HAL_DMA_FLOW_FLAG_MASK;
#endif

    switch (type) {
        case HAL_DMA_FLOW_M2M_DMA:
            addr_inc = DMA_CONTROL_SI | DMA_CONTROL_DI;
            break;
        case HAL_DMA_FLOW_M2P_DMA:
        case HAL_DMA_FLOW_M2P_PERIPH:
            addr_inc = DMA_CONTROL_SI;
            break;
        case HAL_DMA_FLOW_P2M_DMA:
        case HAL_DMA_FLOW_P2M_PERIPH:
            addr_inc = DMA_CONTROL_DI;
            break;
        case HAL_DMA_FLOW_P2P_DMA:
        case HAL_DMA_FLOW_P2P_DSTPERIPH:
        case HAL_DMA_FLOW_P2P_SRCPERIPH:
            addr_inc = 0;
            break;
        default:
            return HAL_DMA_ERR;
    }

#ifndef CHIP_BEST1000
    if (cfg->type & HAL_DMA_FLOW_FLAG_SI) {
        addr_inc |= DMA_CONTROL_SI;
    }
    if (cfg->type & HAL_DMA_FLOW_FLAG_DI) {
        addr_inc |= DMA_CONTROL_DI;
    }
#endif

    *ctrl = DMA_CONTROL_TRANSFERSIZE(cfg->src_tsize) |
            DMA_CONTROL_SBSIZE(cfg->src_bsize) |
            DMA_CONTROL_DBSIZE(cfg->dst_bsize) |
            DMA_CONTROL_SWIDTH(cfg->src_width) |
            DMA_CONTROL_DWIDTH(cfg->dst_width) |
            (tc_irq ? DMA_CONTROL_TC_IRQ : 0) |
            addr_inc;

    return HAL_DMA_OK;
}

/*****************************************************************************
 * Generic Public functions
 ****************************************************************************/

enum HAL_DMA_RET_T hal_dma_init_desc(struct HAL_DMA_DESC_T *desc,
                                     const struct HAL_DMA_CH_CFG_T *cfg,
                                     const struct HAL_DMA_DESC_T *next,
                                     int tc_irq)
{
    uint32_t ctrl;
    enum HAL_DMA_RET_T ret;
    enum HAL_DMA_FLOW_CONTROL_T type;

    ret = hal_dma_init_control(&ctrl, cfg, tc_irq);
    if (ret != HAL_DMA_OK) {
        return ret;
    }

#ifdef CHIP_BEST1000
    type = cfg->type;
#else
    type = cfg->type & ~HAL_DMA_FLOW_FLAG_MASK;
#endif

    if (type == HAL_DMA_FLOW_M2M_DMA || type == HAL_DMA_FLOW_M2P_DMA ||
            type == HAL_DMA_FLOW_M2P_PERIPH) {
        desc->src = cfg->src;
    } else {
        desc->src = hal_dma_get_periph_addr(cfg->src_periph);
    }
    if (type == HAL_DMA_FLOW_M2M_DMA || type == HAL_DMA_FLOW_P2M_DMA ||
            type == HAL_DMA_FLOW_P2M_PERIPH) {
        desc->dst = cfg->dst;
    } else {
        desc->dst = hal_dma_get_periph_addr(cfg->dst_periph);
    }
    desc->lli = (uint32_t)next;
    desc->ctrl = ctrl;

    return HAL_DMA_OK;
}

enum HAL_DMA_RET_T hal_dma_sg_2d_start(const struct HAL_DMA_DESC_T *desc,
                                       const struct HAL_DMA_CH_CFG_T *cfg,
                                       const struct HAL_DMA_2D_CFG_T *src_2d,
                                       const struct HAL_DMA_2D_CFG_T *dst_2d)
{
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;
    enum HAL_DMA_FLOW_CONTROL_T type;
    uint8_t src_periph, dst_periph;
    enum HAL_DMA_INST_T periph_inst;
    int ret;
    uint32_t irq_mask, try_burst;
    uint32_t lock;

    inst = get_inst_from_chan(cfg->ch);
    hwch = get_hwch_from_chan(cfg->ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);

#ifdef CHIP_BEST1000
    type = cfg->type;
#else
    type = cfg->type & ~HAL_DMA_FLOW_FLAG_MASK;
#endif

    if (type == HAL_DMA_FLOW_M2M_DMA || type == HAL_DMA_FLOW_M2P_DMA ||
            type == HAL_DMA_FLOW_M2P_PERIPH) {
        src_periph = 0;
    } else {
        ret = get_index_from_periph(cfg->src_periph, &periph_inst, &src_periph);
        ASSERT(ret == 0, "Failed to get src periph: %d", cfg->src_periph);
#ifdef DMA_REMAP
        if (periph_remap_bitmap[periph_inst] & (1 << src_periph)) {
            periph_inst ^= 1;
            ret = get_remap_index_from_periph(periph_inst, cfg->src_periph, &src_periph);
            ASSERT(ret == 0, "Failed to get remap src periph: %d", cfg->src_periph);
        }
#endif
        ASSERT(inst == periph_inst, "Mismatch inst in chan=0x%02x and src periph %d", cfg->ch, cfg->src_periph);
    }
    if (type == HAL_DMA_FLOW_M2M_DMA || type == HAL_DMA_FLOW_P2M_DMA ||
            type == HAL_DMA_FLOW_P2M_PERIPH) {
        dst_periph = 0;
    } else {
        ret = get_index_from_periph(cfg->dst_periph, &periph_inst, &dst_periph);
        ASSERT(ret == 0, "Failed to get dst periph: %d", cfg->dst_periph);
#ifdef DMA_REMAP
        if (periph_remap_bitmap[periph_inst] & (1 << dst_periph)) {
            periph_inst ^= 1;
            ret = get_remap_index_from_periph(periph_inst, cfg->dst_periph, &dst_periph);
            ASSERT(ret == 0, "Failed to get remap dst periph: %d", cfg->dst_periph);
        }
#endif
        ASSERT(inst == periph_inst, "Mismatch inst in chan=0x%02x and dst periph %d", cfg->ch, cfg->dst_periph);
    }

    if (!chan_enabled[inst][hwch]) {
        // Not acquired
        return HAL_DMA_ERR;
    }
    if (hal_dma_chan_busy_inst(inst, hwch)) {
        // Busy
        return HAL_DMA_ERR;
    }

    if (cfg->handler == NULL) {
        irq_mask = 0;
    } else {
        irq_mask = DMA_CONFIG_ERR_IRQMASK | DMA_CONFIG_TC_IRQMASK;
        handler[inst][hwch] = cfg->handler;
    }

    try_burst = cfg->try_burst ? DMA_CONFIG_TRY_BURST : 0;

    /* Reset the Interrupt status */
    dma[inst]->INTTCCLR = DMA_STAT_CHAN(hwch);
    dma[inst]->INTERRCLR = DMA_STAT_CHAN(hwch);

    dma[inst]->CH[hwch].SRCADDR = desc->src;
    dma[inst]->CH[hwch].DSTADDR = desc->dst;
    dma[inst]->CH[hwch].LLI = desc->lli;
    dma[inst]->CH[hwch].CONTROL = desc->ctrl;
    dma[inst]->CH[hwch].CONFIG = DMA_CONFIG_SRCPERIPH(src_periph) |
                                 DMA_CONFIG_DSTPERIPH(dst_periph) |
                                 DMA_CONFIG_TRANSFERTYPE(type) |
                                 irq_mask |
                                 try_burst;

#ifndef CHIP_BEST1000
    if (src_2d) {
        dma[inst]->_2D[hwch].SRCX = DMA_2D_MODIFY(src_2d->xmodify) | DMA_2D_COUNT(src_2d->xcount);
        dma[inst]->_2D[hwch].SRCY = DMA_2D_MODIFY(src_2d->ymodify) | DMA_2D_COUNT(src_2d->ycount);
        dma[inst]->_2D[hwch].CTRL |= DMA_2D_CTRL_SRC_EN;
    } else {
        dma[inst]->_2D[hwch].CTRL &= ~DMA_2D_CTRL_SRC_EN;
    }
    if (dst_2d) {
        dma[inst]->_2D[hwch].DSTX = DMA_2D_MODIFY(dst_2d->xmodify) | DMA_2D_COUNT(dst_2d->xcount);
        dma[inst]->_2D[hwch].DSTY = DMA_2D_MODIFY(dst_2d->ymodify) | DMA_2D_COUNT(dst_2d->ycount);
        dma[inst]->_2D[hwch].CTRL |= DMA_2D_CTRL_DST_EN;
    } else {
        dma[inst]->_2D[hwch].CTRL &= ~DMA_2D_CTRL_DST_EN;
    }
#endif

    lock = int_lock();
    if (cfg->start_cb) {
        cfg->start_cb(cfg->ch);
    }
    dma[inst]->CH[hwch].CONFIG |= DMA_CONFIG_EN;
    int_unlock(lock);

    return HAL_DMA_OK;
}

enum HAL_DMA_RET_T hal_dma_sg_start(const struct HAL_DMA_DESC_T *desc,
                                    const struct HAL_DMA_CH_CFG_T *cfg)
{
    return hal_dma_sg_2d_start(desc, cfg, NULL, NULL);
}

enum HAL_DMA_RET_T hal_dma_start(const struct HAL_DMA_CH_CFG_T *cfg)
{
    struct HAL_DMA_DESC_T desc;
    enum HAL_DMA_RET_T ret;

    ret = hal_dma_init_desc(&desc, cfg, NULL, 1);
    if (ret != HAL_DMA_OK) {
        return ret;
    }

    ret = hal_dma_sg_start(&desc, cfg);
    if (ret != HAL_DMA_OK) {
        return ret;
    }

    return HAL_DMA_OK;
}

uint32_t hal_dma_cancel(uint8_t ch)
{
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;
    uint32_t remains;

    inst = get_inst_from_chan(ch);
    hwch = get_hwch_from_chan(ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);

    dma[inst]->CH[hwch].CONFIG &= ~DMA_CONFIG_EN;
    dma[inst]->INTTCCLR = DMA_STAT_CHAN(hwch);
    dma[inst]->INTERRCLR = DMA_STAT_CHAN(hwch);

    remains = GET_BITFIELD(dma[inst]->CH[hwch].CONTROL, DMA_CONTROL_TRANSFERSIZE);

    return remains;
}

uint32_t hal_dma_stop(uint8_t ch)
{
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;
    uint8_t retry = 0;
    const uint8_t max_retry = 10;

    inst = get_inst_from_chan(ch);
    hwch = get_hwch_from_chan(ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);

    dma[inst]->CH[hwch].CONFIG |= DMA_CONFIG_HALT;
#if 1
    while ((dma[inst]->CH[hwch].CONFIG & DMA_CONFIG_ACTIVE) && (++retry < max_retry)) {
        if (retry <= 3) {
            hal_sys_timer_delay_us(10);
        } else {
            hal_dma_delay(1);
        }
    }
#else
    while (dma[inst]->CH[hwch].CONFIG & DMA_CONFIG_ACTIVE);
#endif
    return hal_dma_cancel(ch);
}

uint8_t hal_dma_get_chan(enum HAL_DMA_PERIPH_T periph, enum HAL_DMA_GET_CHAN_T policy)
{
    enum HAL_DMA_INST_T inst;
    int ret;
    uint8_t i, hwch;
    uint8_t got = HAL_DMA_CHAN_NONE;
    uint32_t lock;

    ASSERT(policy == HAL_DMA_HIGH_PRIO || policy == HAL_DMA_LOW_PRIO || policy == HAL_DMA_LOW_PRIO_ONLY,
        "Invalid DMA policy: %d", policy);

    if (periph == HAL_GPDMA_MEM) {
        inst = HAL_DMA_INST_GPDMA;
    } else if (periph == HAL_AUDMA_MEM) {
        inst = HAL_DMA_INST_AUDMA;
    } else {
        ret = get_index_from_periph(periph, &inst, &i);
        ASSERT(ret == 0, "Invalid DMA periph: %d", periph);
#ifdef DMA_REMAP
        if (periph_remap_bitmap[inst] & (1 << i)) {
            inst ^= 1;
        }
#endif
    }

    lock = int_lock();
    for (i = 0; i < DMA_NUMBER_CHANNELS; i++) {
        if (policy == HAL_DMA_HIGH_PRIO) {
            hwch = i;
        } else if (policy == HAL_DMA_LOW_PRIO) {
            hwch = DMA_NUMBER_CHANNELS - 1 - i;
        } else {
            hwch = DMA_NUMBER_CHANNELS - 1 - i;
            if (hwch < 6) {
                break;
            }
        }

        if (!chan_enabled[inst][hwch] && !hal_dma_chan_busy_inst(inst, hwch)) {
            chan_enabled[inst][hwch] = true;
            got = generate_chan(inst, hwch);
            dma[inst]->DMACONFIG |= DMA_DMACONFIG_CLK_EN(1 << hwch);
            break;
        }
    }
    int_unlock(lock);

    return got;
}

void hal_dma_free_chan(uint8_t ch)
{
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;
    uint32_t lock;

    inst = get_inst_from_chan(ch);
    hwch = get_hwch_from_chan(ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);
    ASSERT(chan_enabled[inst][hwch], "DMA chan not enabled: inst=%u hwch=%u", inst, hwch);

    hal_dma_cancel(ch);

    lock = int_lock();
    chan_enabled[inst][hwch] = false;
    dma[inst]->DMACONFIG &= ~DMA_DMACONFIG_CLK_EN(1 << hwch);
    int_unlock(lock);
}

uint32_t hal_dma_get_cur_src_addr(uint8_t ch)
{
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;

    inst = get_inst_from_chan(ch);
    hwch = get_hwch_from_chan(ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);

    return dma[inst]->CH[hwch].SRCADDR;
}

uint32_t hal_dma_get_cur_dst_addr(uint8_t ch)
{
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;

    inst = get_inst_from_chan(ch);
    hwch = get_hwch_from_chan(ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);

    return dma[inst]->CH[hwch].DSTADDR;
}

void SRAM_TEXT_LOC hal_dma_get_cur_src_remain_and_addr(uint8_t ch, uint32_t *remain, uint32_t *src)
{
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;
    uint32_t lock;
    uint32_t size[2];
    uint32_t srcaddr[2];

    inst = get_inst_from_chan(ch);
    hwch = get_hwch_from_chan(ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);

    lock = int_lock();
    size[0] = dma[inst]->CH[hwch].CONTROL;
    srcaddr[0] = dma[inst]->CH[hwch].SRCADDR;
    size[1] = dma[inst]->CH[hwch].CONTROL;
    srcaddr[1] = dma[inst]->CH[hwch].SRCADDR;
    int_unlock(lock);

    size[0] = GET_BITFIELD(size[0], DMA_CONTROL_TRANSFERSIZE);
    size[1] = GET_BITFIELD(size[1], DMA_CONTROL_TRANSFERSIZE);

    if (size[0] == size[1]) {
        *remain = size[0];
        *src = srcaddr[0];
    } else {
        *remain = size[1];
        *src = srcaddr[1];
    }

    return;
}

enum HAL_DMA_RET_T hal_dma_irq_run_chan(uint8_t ch)
{
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;

    inst = get_inst_from_chan(ch);
    hwch = get_hwch_from_chan(ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);

    if ((dma[inst]->INTSTAT & DMA_STAT_CHAN(hwch)) == 0) {
        return HAL_DMA_ERR;
    }

    hal_dma_handle_chan_irq(inst, hwch);

    return HAL_DMA_OK;
}

bool hal_dma_chan_busy(uint8_t ch)
{
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;

    inst = get_inst_from_chan(ch);
    hwch = get_hwch_from_chan(ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);

    return hal_dma_chan_busy_inst(inst, hwch);
}

bool hal_dma_busy(void)
{
    enum HAL_DMA_INST_T inst;
    int hwch;

    for (inst = HAL_DMA_INST_AUDMA; inst < HAL_DMA_INST_QTY; inst++) {
        for (hwch = chan_start[inst]; hwch < chan_num[inst]; hwch++) {
            if (hal_dma_chan_busy_inst(inst, hwch)) {
                return true;
            }
        }
    }

    return false;
}

uint32_t hal_dma_get_sg_remain_size(uint8_t ch)
{
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;
    uint32_t remains;
    const struct HAL_DMA_DESC_T *desc, *first;

    inst = get_inst_from_chan(ch);
    hwch = get_hwch_from_chan(ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);

    remains = GET_BITFIELD(dma[inst]->CH[hwch].CONTROL, DMA_CONTROL_TRANSFERSIZE);
    first = (const struct HAL_DMA_DESC_T *)dma[inst]->CH[hwch].LLI;
    desc = first;
    while (desc) {
        remains += GET_BITFIELD(desc->ctrl, DMA_CONTROL_TRANSFERSIZE);
        desc = (const struct HAL_DMA_DESC_T *)desc->lli;
        if (desc == first) {
            break;
        }
    }

    return remains;
}

void hal_dma_tc_irq_enable(uint8_t ch)
{
#if !(defined(CHIP_BEST1000) || defined(CHIP_BEST2000) || defined(CHIP_BEST3001) || defined(CHIP_BEST3005))
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;
    uint32_t lock;

    inst = get_inst_from_chan(ch);
    hwch = get_hwch_from_chan(ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);

    lock = int_lock();
#ifdef CHIP_BEST2300
    if (inst == HAL_DMA_INST_AUDMA) {
        hal_cmu_dma_tc_irq_set_chan(hwch);
    }
#else
    dma[inst]->DMACONFIG |= DMA_DMACONFIG_TC_IRQ_EN(1 << hwch);
#endif
    int_unlock(lock);
#endif
}

void hal_dma_tc_irq_disable(uint8_t ch)
{
#if !(defined(CHIP_BEST1000) || defined(CHIP_BEST2000) || defined(CHIP_BEST3001) || defined(CHIP_BEST3005))
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;
    uint32_t lock;

    inst = get_inst_from_chan(ch);
    hwch = get_hwch_from_chan(ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);

    lock = int_lock();
#ifdef CHIP_BEST2300
    if (inst == HAL_DMA_INST_AUDMA) {
        hal_cmu_dma_tc_irq_clear_chan(hwch);
    }
#else
    dma[inst]->DMACONFIG &= ~DMA_DMACONFIG_TC_IRQ_EN(1 << hwch);
#endif
    int_unlock(lock);
#endif
}

void hal_dma_set_burst_addr_inc(uint8_t ch, const struct HAL_DMA_BURST_ADDR_INC_T *inc)
{
#if !(defined(CHIP_BEST1000) || defined(CHIP_BEST2000) || defined(CHIP_BEST3001) || defined(CHIP_BEST3005))
    enum HAL_DMA_INST_T inst;
    uint8_t hwch;
    uint32_t lock;

    inst = get_inst_from_chan(ch);
    hwch = get_hwch_from_chan(ch);

    ASSERT(inst < HAL_DMA_INST_QTY, err_invalid_inst, inst);
    ASSERT(hwch < DMA_NUMBER_CHANNELS, err_invalid_chan[inst], hwch);

    lock = int_lock();
    if (inc && inc->src_inc_en) {
        dma[inst]->CH[hwch].CONTROL |= DMA_CONTROL_BURST_SI;
        dma[inst]->_2D[hwch].SRC_INC = SET_BITFIELD(dma[inst]->_2D[hwch].SRC_INC, DMA_BURST_SRC_INC_VAL, inc->src_inc_val);
    } else {
        dma[inst]->CH[hwch].CONTROL &= ~DMA_CONTROL_BURST_SI;
    }
    if (inc && inc->dst_inc_en) {
        dma[inst]->CH[hwch].CONTROL |= DMA_CONTROL_BURST_DI;
        dma[inst]->_2D[hwch].DST_INC = SET_BITFIELD(dma[inst]->_2D[hwch].DST_INC, DMA_BURST_DST_INC_VAL, inc->dst_inc_val);
    } else {
        dma[inst]->CH[hwch].CONTROL &= ~DMA_CONTROL_BURST_DI;
    }
    int_unlock(lock);
#endif
}

void hal_dma_clear_burst_addr_inc(uint8_t ch)
{
    hal_dma_set_burst_addr_inc(ch, NULL);
}

void hal_dma_set_desc_burst_addr_inc(struct HAL_DMA_DESC_T *desc, const struct HAL_DMA_BURST_ADDR_INC_T *inc)
{
#if !(defined(CHIP_BEST1000) || defined(CHIP_BEST2000) || defined(CHIP_BEST3001) || defined(CHIP_BEST3005))
    if (inc && inc->src_inc_en) {
        desc->ctrl |= DMA_CONTROL_BURST_SI;
    } else {
        desc->ctrl &= ~DMA_CONTROL_BURST_SI;
    }
    if (inc && inc->dst_inc_en) {
        desc->ctrl |= DMA_CONTROL_BURST_DI;
    } else {
        desc->ctrl &= ~DMA_CONTROL_BURST_DI;
    }
#endif
}

void hal_dma_clear_desc_burst_addr_inc(struct HAL_DMA_DESC_T *desc)
{
    hal_dma_set_desc_burst_addr_inc(desc, NULL);
}

#ifdef DMA_REMAP
void hal_dma_remap_periph(enum HAL_DMA_PERIPH_T periph, int enable)
{
    enum HAL_DMA_INST_T inst1, inst2;
    uint8_t index1, index2;
    int ret;
    uint32_t lock;

    // Tag the periph
    ret = get_index_from_periph(periph, &inst1, &index1);
    ASSERT(ret == 0, "Invalid DMA periph for remap1: %d", periph);

    // Tag the peer periph
    inst2 = inst1 ^ 1;
    ret = get_remap_index_from_periph(inst2, periph, &index2);
    ASSERT(ret == 0, "Invalid DMA periph for remap2: %d", periph);

    lock = int_lock();
    if (enable) {
        periph_remap_bitmap[inst1] |= (1 << index1);
        periph_remap_bitmap[inst2] |= (1 << index2);
#ifndef CHIP_BEST1000
        // For best2000, index1 == index2
        hal_cmu_dma_swap_enable(index1);
#endif
    } else {
        periph_remap_bitmap[inst1] &= ~(1 << index1);
        periph_remap_bitmap[inst2] &= ~(1 << index2);
#ifndef CHIP_BEST1000
        hal_cmu_dma_swap_disable(index1);
#endif
    }
    int_unlock(lock);
}
#endif

void hal_dma_open(void)
{
    enum HAL_DMA_INST_T inst;

    if (dma_opened) {
        return;
    }

    for (inst = 0; inst < HAL_DMA_INST_QTY; inst++) {
        hal_dma_open_inst(inst);
    }

#ifdef DMA_REMAP
#ifdef CHIP_BEST1000
    if (hal_get_chip_metal_id() >= HAL_CHIP_METAL_ID_4) {
        hal_dma_remap_periph(HAL_AUDMA_I2S0_RX, 1);
        hal_dma_remap_periph(HAL_AUDMA_I2S0_TX, 1);
        hal_dma_remap_periph(HAL_AUDMA_SPDIF0_RX, 1);
        hal_dma_remap_periph(HAL_AUDMA_SPDIF0_TX, 1);
    }
#endif
#endif

    dma_opened = true;
}

void hal_dma_close(void)
{
    enum HAL_DMA_INST_T inst;

    if (!dma_opened) {
        return;
    }

    for (inst = 0; inst < HAL_DMA_INST_QTY; inst++) {
        hal_dma_close_inst(inst);
    }

    dma_opened = false;
}

#ifdef CORE_SLEEP_POWER_DOWN
void hal_dma_sleep(void)
{
    enum HAL_DMA_INST_T inst;

    for (inst = 0; inst < HAL_DMA_INST_QTY; inst++) {
        saved_dma_regs[inst] = dma[inst]->DMACONFIG;
    }
}

void hal_dma_wakeup(void)
{
    enum HAL_DMA_INST_T inst;

    for (inst = 0; inst < HAL_DMA_INST_QTY; inst++) {
        dma[inst]->DMACONFIG = saved_dma_regs[inst];
    }
}
#endif

HAL_DMA_DELAY_FUNC hal_dma_set_delay_func(HAL_DMA_DELAY_FUNC new_func)
{
    HAL_DMA_DELAY_FUNC old_func = dma_delay;
    dma_delay = new_func;
    return old_func;
}

