/***************************************************************************
 *
 * 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.
 *
 ****************************************************************************/
#ifdef CHIP_HAS_UART

#include "plat_addr_map.h"
#include "hal_uart.h"
#include "reg_uart.h"
#include "cmsis_nvic.h"
#include "hal_bootmode.h"
#include "hal_cmu.h"
#include "hal_dma.h"
#include "hal_iomux.h"
#include "hal_timer.h"
#include "hal_trace.h"
#include "string.h"

#define UART_FLUSH_DELAY_DEFAULT        2

enum UART_RX_DMA_MODE_T {
    UART_RX_DMA_MODE_NORMAL,
    UART_RX_DMA_MODE_PINGPANG,
    UART_RX_DMA_MODE_STREAM,
    UART_RX_DMA_MODE_BUF_LIST,
};

struct HAL_UART_HW_DESC_T {
    struct UART_T *base;
    IRQn_Type irq;
    enum HAL_CMU_MOD_ID_T mod;
    enum HAL_CMU_MOD_ID_T apb;
    enum HAL_DMA_PERIPH_T rx_periph;
    enum HAL_DMA_PERIPH_T tx_periph;
};

static const struct HAL_UART_HW_DESC_T uart[HAL_UART_ID_QTY] = {
    {
        .base = (struct UART_T *)UART0_BASE,
        .irq = UART0_IRQn,
        .mod = HAL_CMU_MOD_O_UART0,
        .apb = HAL_CMU_MOD_P_UART0,
        .rx_periph = HAL_GPDMA_UART0_RX,
        .tx_periph = HAL_GPDMA_UART0_TX,
    },
#if (CHIP_HAS_UART > 1)
    {
        .base = (struct UART_T *)UART1_BASE,
        .irq = UART1_IRQn,
        .mod = HAL_CMU_MOD_O_UART1,
        .apb = HAL_CMU_MOD_P_UART1,
        .rx_periph = HAL_GPDMA_UART1_RX,
        .tx_periph = HAL_GPDMA_UART1_TX,
    },
#endif
#if (CHIP_HAS_UART > 2)
    {
        .base = (struct UART_T *)UART2_BASE,
        .irq = UART2_IRQn,
        .mod = HAL_CMU_MOD_O_UART2,
        .apb = HAL_CMU_MOD_P_UART2,
        .rx_periph = HAL_GPDMA_UART2_RX,
        .tx_periph = HAL_GPDMA_UART2_TX,
    },
#endif
#ifdef BT_UART
    {
        .base = (struct UART_T *)BT_UART_BASE,
        .irq = INVALID_IRQn,
        .mod = HAL_CMU_MOD_QTY,
        .apb = HAL_CMU_MOD_QTY,
        .rx_periph = HAL_DMA_PERIPH_NULL,
        .tx_periph = HAL_DMA_PERIPH_NULL,
    },
#endif
};

static bool init_done = false;

static HAL_UART_IRQ_HANDLER_T irq_handler[HAL_UART_ID_QTY] = { NULL };

static HAL_UART_IRQ_RXDMA_HANDLER_T rxdma_handler[HAL_UART_ID_QTY] = { NULL };
static HAL_UART_IRQ_TXDMA_HANDLER_T txdma_handler[HAL_UART_ID_QTY] = { NULL };

static uint8_t recv_dma_chan[HAL_UART_ID_QTY];
static uint8_t send_dma_chan[HAL_UART_ID_QTY];

static uint32_t recv_dma_size[HAL_UART_ID_QTY];
static uint32_t send_dma_size[HAL_UART_ID_QTY];

static union HAL_UART_IRQ_T recv_mask[HAL_UART_ID_QTY];

static enum UART_RX_DMA_MODE_T recv_dma_mode[HAL_UART_ID_QTY];

static const char * const err_invalid_id = "Invalid UART ID: %d";
static const char * const err_recv_dma_api = "%s: Set RT irq in hal_uart_dma_recv_mask... to avoid data lost";

static const struct HAL_UART_CFG_T default_cfg = {
    .parity = HAL_UART_PARITY_NONE,
    .stop = HAL_UART_STOP_BITS_1,
    .data = HAL_UART_DATA_BITS_8,
    .flow = HAL_UART_FLOW_CONTROL_RTSCTS,
    .tx_level = HAL_UART_FIFO_LEVEL_1_2,
    .rx_level = HAL_UART_FIFO_LEVEL_1_4,
    .baud = 921600,
    .dma_rx = false,
    .dma_tx = false,
    .dma_rx_stop_on_err = false,
};

static void hal_uart_irq_handler(void);

static void set_baud_rate(enum HAL_UART_ID_T id, uint32_t rate)
{
    uint32_t mod_clk;
    uint32_t ibrd, fbrd;
    uint32_t div;

    mod_clk = 0;
#ifdef PERIPH_PLL_FREQ
    if (PERIPH_PLL_FREQ / 2 > 2 * hal_cmu_get_crystal_freq()) {
        // Init to OSC_X2
        mod_clk = 2 * hal_cmu_get_crystal_freq() / 16;
        if (cfg->baud > mod_clk) {
            mod_clk = PERIPH_PLL_FREQ / 2 / 16;

            if (id == HAL_UART_ID_0) {
                hal_cmu_uart0_set_div(2);
#if (CHIP_HAS_UART > 1)
            } else if (id == HAL_UART_ID_1) {
                hal_cmu_uart1_set_div(2);
#endif
#if (CHIP_HAS_UART > 2)
            } else if (id == HAL_UART_ID_2) {
                hal_cmu_uart2_set_div(2);
#endif
            }
        } else {
            mod_clk = 0;
        }
    }
#endif
    if (mod_clk == 0) {
        enum HAL_CMU_PERIPH_FREQ_T periph_freq;

        // Init to OSC
        mod_clk = hal_cmu_get_crystal_freq() / 16;
        if (rate > mod_clk) {
            mod_clk *= 2;
            periph_freq = HAL_CMU_PERIPH_FREQ_52M;
        } else {
            periph_freq = HAL_CMU_PERIPH_FREQ_26M;
        }

        if (id == HAL_UART_ID_0) {
            hal_cmu_uart0_set_freq(periph_freq);
#if (CHIP_HAS_UART > 1)
        } else if (id == HAL_UART_ID_1) {
            hal_cmu_uart1_set_freq(periph_freq);
#endif
#if (CHIP_HAS_UART > 2)
        } else if (id == HAL_UART_ID_2) {
            hal_cmu_uart2_set_freq(periph_freq);
#endif
        }
    }

    div = (mod_clk * 64 + rate / 2) / rate;
    ibrd = div / 64;
    fbrd = div % 64;
    if (ibrd == 0 || ibrd >= 65535) {
        ASSERT(false, "Invalid baud param: %d", rate);
    }

    uart[id].base->UARTIBRD = ibrd;
    uart[id].base->UARTFBRD = fbrd;

    return;
}

int hal_uart_open(enum HAL_UART_ID_T id, const struct HAL_UART_CFG_T *cfg)
{
    uint32_t cr, lcr, dmacr;
    int i;

    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);

    if (!init_done) {
        init_done = true;
        for (i = HAL_UART_ID_0; i < HAL_UART_ID_QTY; i++) {
            recv_dma_chan[i] = HAL_DMA_CHAN_NONE;
            send_dma_chan[i] = HAL_DMA_CHAN_NONE;
        }
    }

    if (hal_uart_opened(id)) {
        hal_uart_close(id);
    }

    if (cfg == NULL) {
        cfg = &default_cfg;
    }

    hal_cmu_clock_enable(uart[id].mod);
    hal_cmu_clock_enable(uart[id].apb);
    hal_cmu_reset_clear(uart[id].mod);
    hal_cmu_reset_clear(uart[id].apb);

    cr = lcr = 0;

    switch (cfg->parity) {
        case HAL_UART_PARITY_NONE:
            break;
        case HAL_UART_PARITY_ODD:
            lcr |= UARTLCR_H_PEN;
            break;
        case HAL_UART_PARITY_EVEN:
            lcr |= UARTLCR_H_PEN
                |  UARTLCR_H_EPS;
            break;
        case HAL_UART_PARITY_FORCE1:
            lcr |= UARTLCR_H_PEN
                |  UARTLCR_H_SPS;
            break;
        case HAL_UART_PARITY_FORCE0:
            lcr |= UARTLCR_H_PEN
                |  UARTLCR_H_EPS
                |  UARTLCR_H_SPS;
            break;
        default:
            ASSERT(false, "Invalid parity param: %d", cfg->parity);
            break;
    }

    if (cfg->stop == HAL_UART_STOP_BITS_2) {
        lcr |= UARTLCR_H_STP2;
    } else if (cfg->stop != HAL_UART_STOP_BITS_1) {
        ASSERT(false, "Invalid stop bits param: %d", cfg->stop);
    }

    switch (cfg->data) {
        case HAL_UART_DATA_BITS_5:
            lcr |= UARTLCR_H_WLEN_5;
            break;
        case HAL_UART_DATA_BITS_6:
            lcr |= UARTLCR_H_WLEN_6;
            break;
        case HAL_UART_DATA_BITS_7:
            lcr |= UARTLCR_H_WLEN_7;
            break;
        case HAL_UART_DATA_BITS_8:
            lcr |= UARTLCR_H_WLEN_8;
            break;
        default:
            ASSERT(false, "Invalid data bits param: %d", cfg->data);
            break;
    }

    switch (cfg->flow) {
        case HAL_UART_FLOW_CONTROL_NONE:
            break;
        case HAL_UART_FLOW_CONTROL_RTS:
            cr |= UARTCR_RTSEN;
            break;
        case HAL_UART_FLOW_CONTROL_CTS:
            cr |= UARTCR_CTSEN;
            break;
        case HAL_UART_FLOW_CONTROL_RTSCTS:
            cr |= UARTCR_RTSEN
               |  UARTCR_CTSEN;
            break;
        default:
            ASSERT(false, "Invalid flow control param: %d", cfg->flow);
            break;
    }

    lcr |= UARTLCR_H_FEN | UARTLCR_H_DMA_RT_CNT(9);
    cr |= UARTCR_UARTEN | UARTCR_TXE | UARTCR_RXE;

    dmacr = 0;
    if (cfg->dma_rx) {
        dmacr |= UARTDMACR_RXDMAE;
    }
    if (cfg->dma_tx) {
        dmacr |= UARTDMACR_TXDMAE;
    }
    if (cfg->dma_rx_stop_on_err) {
        dmacr |= UARTDMACR_DMAONERR;
    }

    // Disable UART
    uart[id].base->UARTCR &= ~UARTCR_UARTEN;
    // Empty FIFO
    uart[id].base->UARTLCR_H &= ~UARTLCR_H_FEN;
    // Wait until UART becomes idle
    while (((uart[id].base->UARTFR) & UARTFR_BUSY) != 0);
    // Clear previous errors
    uart[id].base->UARTECR = 1;
    // Clear previous IRQs
    uart[id].base->UARTIMSC = 0;
    uart[id].base->UARTICR = ~0UL;
    // Configure UART
    set_baud_rate(id, cfg->baud);
    uart[id].base->UARTLCR_H = lcr;
    uart[id].base->UARTDMACR = dmacr;
    uart[id].base->UARTIFLS = UARTIFLS_TXFIFO_LEVEL(cfg->tx_level) |
                         UARTIFLS_RXFIFO_LEVEL(cfg->rx_level);
    uart[id].base->UARTCR = cr;

    if (uart[id].irq != INVALID_IRQn) {
        NVIC_SetVector(uart[id].irq, (uint32_t)hal_uart_irq_handler);
        // The priority should be the same as DMA's
        NVIC_SetPriority(uart[id].irq, IRQ_PRIORITY_NORMAL);
        NVIC_ClearPendingIRQ(uart[id].irq);
        NVIC_EnableIRQ(uart[id].irq);
    }

    return 0;
}

int hal_uart_reopen(enum HAL_UART_ID_T id, const struct HAL_UART_CFG_T *cfg)
{
    uint32_t cr, dmacr;
    int i;

    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);

    if (!init_done) {
        init_done = true;
        for (i = HAL_UART_ID_0; i < HAL_UART_ID_QTY; i++) {
            recv_dma_chan[i] = HAL_DMA_CHAN_NONE;
            send_dma_chan[i] = HAL_DMA_CHAN_NONE;
        }
    }

    cr = 0;
    switch (cfg->flow) {
        case HAL_UART_FLOW_CONTROL_NONE:
            break;
        case HAL_UART_FLOW_CONTROL_RTS:
            cr |= UARTCR_RTSEN;
            break;
        case HAL_UART_FLOW_CONTROL_CTS:
            cr |= UARTCR_CTSEN;
            break;
        case HAL_UART_FLOW_CONTROL_RTSCTS:
            cr |= UARTCR_RTSEN
               |  UARTCR_CTSEN;
            break;
        default:
            ASSERT(false, "Invalid flow control param: %d", cfg->flow);
            break;
    }

    dmacr = 0;
    if (cfg->dma_rx) {
        dmacr |= UARTDMACR_RXDMAE;
    }
    if (cfg->dma_tx) {
        dmacr |= UARTDMACR_TXDMAE;
    }
    if (cfg->dma_rx_stop_on_err) {
        dmacr |= UARTDMACR_DMAONERR;
    }

    // Configure UART
    uart[id].base->UARTDMACR = dmacr;
    uart[id].base->UARTCR = (uart[id].base->UARTCR & ~(UARTCR_RTSEN | UARTCR_CTSEN)) | cr;

    if (uart[id].irq != INVALID_IRQn) {
        NVIC_SetVector(uart[id].irq, (uint32_t)hal_uart_irq_handler);
        // The priority should be the same as DMA's
        NVIC_SetPriority(uart[id].irq, IRQ_PRIORITY_NORMAL);
        NVIC_ClearPendingIRQ(uart[id].irq);
        NVIC_EnableIRQ(uart[id].irq);
    }

    return 0;
}

int hal_uart_close(enum HAL_UART_ID_T id)
{
    uint32_t lock;

    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);

    if (uart[id].irq != INVALID_IRQn) {
        NVIC_DisableIRQ(uart[id].irq);
    }

    lock = int_lock();
    if (recv_dma_chan[id] != HAL_DMA_CHAN_NONE) {
        hal_gpdma_cancel(recv_dma_chan[id]);
        hal_gpdma_free_chan(recv_dma_chan[id]);
        recv_dma_chan[id] = HAL_DMA_CHAN_NONE;
    }
    if (send_dma_chan[id] != HAL_DMA_CHAN_NONE) {
        hal_gpdma_cancel(send_dma_chan[id]);
        hal_gpdma_free_chan(send_dma_chan[id]);
        send_dma_chan[id] = HAL_DMA_CHAN_NONE;
    }
    int_unlock(lock);

    // Disable UART
    uart[id].base->UARTCR &= ~UARTCR_UARTEN;
    // Empty FIFO
    uart[id].base->UARTLCR_H &= ~UARTLCR_H_FEN;

    hal_cmu_reset_set(uart[id].apb);
    hal_cmu_reset_set(uart[id].mod);
    hal_cmu_clock_disable(uart[id].apb);
    hal_cmu_clock_disable(uart[id].mod);

    return 0;
}

int hal_uart_opened(enum HAL_UART_ID_T id)
{
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    if (uart[id].apb < HAL_CMU_MOD_QTY &&
            hal_cmu_clock_get_status(uart[id].apb) != HAL_CMU_CLK_ENABLED) {
        return 0;
    }
    if (uart[id].base->UARTCR & UARTCR_UARTEN) {
        return 1;
    }
    return 0;
}

int hal_uart_change_baud_rate(enum HAL_UART_ID_T id, uint32_t rate)
{
    union HAL_UART_FLAG_T flag;

    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);

    if (!hal_uart_opened(id)) {
        return 1;
    }

    flag.reg = uart[id].base->UARTFR;
    if (flag.BUSY) {
        return 2;
    }

    uart[id].base->UARTCR &= ~UARTCR_UARTEN;

    set_baud_rate(id, rate);

    uart[id].base->UARTLCR_H = uart[id].base->UARTLCR_H;
    uart[id].base->UARTCR |= UARTCR_UARTEN;

    return 0;
}

int hal_uart_pause(enum HAL_UART_ID_T id)
{
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    if (hal_uart_opened(id)) {
        uart[id].base->UARTCR &= ~(UARTCR_TXE | UARTCR_RXE);
        return 0;
    }
    return 1;
}

int hal_uart_continue(enum HAL_UART_ID_T id)
{
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    if (hal_uart_opened(id)) {
        uart[id].base->UARTCR |= (UARTCR_TXE | UARTCR_RXE);
        return 0;
    }
    return 1;
}

int hal_uart_readable(enum HAL_UART_ID_T id)
{
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    return (uart[id].base->UARTFR & UARTFR_RXFE) == 0;
}

int hal_uart_writable(enum HAL_UART_ID_T id)
{
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    return (uart[id].base->UARTFR & UARTFR_TXFF) == 0;
}

uint8_t hal_uart_getc(enum HAL_UART_ID_T id)
{
    uint32_t c;
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    ASSERT((uart[id].base->UARTDMACR & UARTDMACR_RXDMAE) == 0, "RX-DMA configured on UART %d", id);
    c = uart[id].base->UARTDR;
    return (c & 0xFF);
}

int hal_uart_putc(enum HAL_UART_ID_T id, uint8_t c)
{
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    ASSERT((uart[id].base->UARTDMACR & UARTDMACR_TXDMAE) == 0, "TX-DMA configured on UART %d", id);
    uart[id].base->UARTDR = c;
    return 0;
}

uint8_t hal_uart_blocked_getc(enum HAL_UART_ID_T id)
{
    while (hal_uart_readable(id) == 0);
    return hal_uart_getc(id);
}

int hal_uart_blocked_putc(enum HAL_UART_ID_T id, uint8_t c)
{
    while (hal_uart_writable(id) == 0);
    return hal_uart_putc(id, c);
}

union HAL_UART_FLAG_T hal_uart_get_flag(enum HAL_UART_ID_T id)
{
    union HAL_UART_FLAG_T flag;
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    flag.reg = uart[id].base->UARTFR;
    return flag;
}

union HAL_UART_STATUS_T hal_uart_get_status(enum HAL_UART_ID_T id)
{
    union HAL_UART_STATUS_T status;
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    status.reg = uart[id].base->UARTRSR;
    return status;
}

void hal_uart_clear_status(enum HAL_UART_ID_T id)
{
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    uart[id].base->UARTECR = 0;
}

void hal_uart_break_set(enum HAL_UART_ID_T id)
{
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    uart[id].base->UARTLCR_H |= UARTLCR_H_BRK;
}

void hal_uart_break_clear(enum HAL_UART_ID_T id)
{
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    uart[id].base->UARTLCR_H &= ~UARTLCR_H_BRK;
}

void hal_uart_flush(enum HAL_UART_ID_T id, uint32_t ticks)
{
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);

    if (!hal_uart_opened(id)) {
        return;
    }
    if ((uart[id].base->UARTLCR_H & UARTLCR_H_FEN) == 0) {
        return;
    }

    // Disable the UART
    uart[id].base->UARTCR &= ~UARTCR_UARTEN;
    // Wait for the end of transmission or reception of the current character
    hal_sys_timer_delay((ticks > 0) ? ticks : UART_FLUSH_DELAY_DEFAULT);

    // Flush FIFO
    uart[id].base->UARTLCR_H &= ~UARTLCR_H_FEN;
    uart[id].base->UARTLCR_H |= UARTLCR_H_FEN;

    // Enable the UART
    uart[id].base->UARTCR |= UARTCR_UARTEN;
}

union HAL_UART_IRQ_T hal_uart_get_raw_irq(enum HAL_UART_ID_T id)
{
    union HAL_UART_IRQ_T irq;

    irq.reg = uart[id].base->UARTRIS;

    return irq;
}

void hal_uart_clear_irq(enum HAL_UART_ID_T id, union HAL_UART_IRQ_T irq)
{
    uart[id].base->UARTICR = irq.reg;
}

union HAL_UART_IRQ_T hal_uart_irq_get_mask(enum HAL_UART_ID_T id)
{
    union HAL_UART_IRQ_T mask;

    mask.reg = uart[id].base->UARTIMSC;

    return mask;
}

union HAL_UART_IRQ_T hal_uart_irq_set_mask(enum HAL_UART_ID_T id, union HAL_UART_IRQ_T mask)
{
    union HAL_UART_IRQ_T old_mask;

    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    if (mask.RT) {
        ASSERT(recv_dma_chan[id] == HAL_DMA_CHAN_NONE, err_recv_dma_api, __FUNCTION__);
    }
    old_mask.reg = uart[id].base->UARTIMSC;
    uart[id].base->UARTIMSC = mask.reg;

    return old_mask;
}

HAL_UART_IRQ_HANDLER_T hal_uart_irq_set_handler(enum HAL_UART_ID_T id, HAL_UART_IRQ_HANDLER_T handler)
{
    HAL_UART_IRQ_HANDLER_T old_handler;

    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    old_handler = irq_handler[id];
    irq_handler[id] = handler;

    return old_handler;
}

static void dma_mode_uart_irq_handler(enum HAL_UART_ID_T id, union HAL_UART_IRQ_T status)
{
    uint32_t xfer = 0;
    uint32_t lock;

    if (status.RT || status.FE || status.OE || status.PE || status.BE) {
        if (recv_dma_mode[id] == UART_RX_DMA_MODE_NORMAL) {
            // Restore the traditional RT behaviour
            lock = int_lock();
            uart[id].base->UARTLCR_H &= ~UARTLCR_H_DMA_RT_EN;
            int_unlock(lock);
        }

        if (rxdma_handler[id]) {
            if (recv_dma_chan[id] != HAL_DMA_CHAN_NONE) {
                if (recv_dma_mode[id] == UART_RX_DMA_MODE_NORMAL) {
                    xfer = hal_uart_stop_dma_recv(id);
                    if (recv_dma_size[id] > xfer) {
                        xfer = recv_dma_size[id] - xfer;
                    } else {
                        xfer = 0;
                    }
                }
            }
            rxdma_handler[id](xfer, 0, status);
        }
    }
}

void hal_uart_irq_set_dma_handler(enum HAL_UART_ID_T id, HAL_UART_IRQ_RXDMA_HANDLER_T rxdma, HAL_UART_IRQ_TXDMA_HANDLER_T txdma)
{
    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    rxdma_handler[id] = rxdma;
    txdma_handler[id] = txdma;
    irq_handler[id] = dma_mode_uart_irq_handler;
}

static void recv_dma_irq_handler(uint8_t chan, uint32_t remain_tsize, uint32_t error, struct HAL_DMA_DESC_T *lli)
{
    enum HAL_UART_ID_T id;
    uint32_t xfer;
    uint32_t lock;
    union HAL_UART_IRQ_T status;

    lock = int_lock();
    for (id = HAL_UART_ID_0; id < HAL_UART_ID_QTY; id++) {
        if (recv_dma_chan[id] == chan) {
            if (recv_dma_mode[id] == UART_RX_DMA_MODE_NORMAL) {
                recv_dma_chan[id] = HAL_DMA_CHAN_NONE;
            }
            break;
        }
    }
    int_unlock(lock);

    if (id == HAL_UART_ID_QTY) {
        return;
    }

    if (recv_dma_mode[id] == UART_RX_DMA_MODE_NORMAL) {
        // Get remain xfer size
        xfer = hal_gpdma_get_sg_remain_size(chan);

        hal_gpdma_free_chan(chan);
    } else {
        xfer = 0;
        status.reg = 0;
    }

    if (rxdma_handler[id]) {
        if (recv_dma_mode[id] == UART_RX_DMA_MODE_NORMAL) {
            // Already get xfer size
            if (recv_dma_size[id] > xfer) {
                xfer = recv_dma_size[id] - xfer;
            } else {
                xfer = 0;
            }
        } else if (recv_dma_mode[id] == UART_RX_DMA_MODE_PINGPANG) {
            xfer = recv_dma_size[id] / 2;
        }
        rxdma_handler[id](xfer, error, status);
    }
}

static void recv_dma_start_callback(uint8_t chan)
{
    enum HAL_UART_ID_T id;

    for (id = HAL_UART_ID_0; id < HAL_UART_ID_QTY; id++) {
        if (recv_dma_chan[id] == chan) {
            break;
        }
    }

    if (id == HAL_UART_ID_QTY) {
        return;
    }

    uart[id].base->UARTIMSC = recv_mask[id].reg;
}

static int fill_buf_list_dma_desc(struct HAL_DMA_DESC_T *desc, uint32_t cnt, struct HAL_DMA_CH_CFG_T *cfg,
                                  const struct HAL_UART_BUF_T *ubuf, uint32_t ucnt, uint32_t step)
{
    enum HAL_DMA_RET_T ret;
    struct HAL_DMA_DESC_T *last_desc;
    int tc_irq;
    int i;
    int u;
    uint32_t remain;
    uint32_t dlen;

    last_desc = NULL;

    u = 0;
    remain = ubuf[0].len;

    for (i = 0, u = 0; i < cnt - 1; i++) {
        if (ubuf[u].loop_hdr && last_desc == NULL) {
            last_desc = &desc[i];
        }
        if (remain <= step) {
            dlen = remain;
            tc_irq = ubuf[u].irq;
        } else {
            dlen = step;
            tc_irq = 0;
        }
        cfg->src_tsize = dlen;
        ret = hal_gpdma_init_desc(&desc[i], cfg, &desc[i + 1], tc_irq);
        if (ret != HAL_DMA_OK) {
            return 1;
        }
        remain -= dlen;
        if (remain) {
            cfg->dst += dlen;
        } else {
            u++;
            if (u >= ucnt) {
                return 2;
            }
            cfg->dst = (uint32_t)ubuf[u].buf;
            remain = ubuf[u].len;
        }
    }

    cfg->src_tsize = remain;
    ret = hal_gpdma_init_desc(&desc[i], cfg, last_desc, ubuf[u].irq);
    if (ret != HAL_DMA_OK) {
        return 1;
    }

    return 0;
}

static int fill_dma_desc(struct HAL_DMA_DESC_T *desc, uint32_t cnt, struct HAL_DMA_CH_CFG_T *cfg,
                         uint32_t len, enum UART_RX_DMA_MODE_T mode, uint32_t step)
{
    enum HAL_DMA_RET_T ret;
    struct HAL_DMA_DESC_T *last_desc;
    int tc_irq;
    int i;

    for (i = 0; i < cnt - 1; i++) {
        cfg->src_tsize = step;
        tc_irq = 0;
        if (mode == UART_RX_DMA_MODE_PINGPANG) {
            tc_irq = (i == cnt / 2 - 1) ? 1 : 0;
        } else if (mode == UART_RX_DMA_MODE_STREAM) {
            tc_irq = 1;
        }
        ret = hal_gpdma_init_desc(&desc[i], cfg, &desc[i + 1], tc_irq);
        if (ret != HAL_DMA_OK) {
            return 1;
        }
        cfg->dst += step;
    }

    cfg->src_tsize = len - (step * i);
    last_desc = NULL;
    if (mode == UART_RX_DMA_MODE_PINGPANG || mode == UART_RX_DMA_MODE_STREAM) {
        last_desc = &desc[0];
    }
    ret = hal_gpdma_init_desc(&desc[i], cfg, last_desc, 1);
    if (ret != HAL_DMA_OK) {
        return 1;
    }

    return 0;
}

static int start_recv_dma_with_mask(enum HAL_UART_ID_T id, const struct HAL_UART_BUF_T *ubuf, uint32_t ucnt,
                                    struct HAL_DMA_DESC_T *desc, uint32_t *desc_cnt,
                                    const union HAL_UART_IRQ_T *mask,
                                    enum UART_RX_DMA_MODE_T mode, uint32_t step)
{
    uint8_t *buf;
    uint32_t len;
    struct HAL_DMA_CH_CFG_T dma_cfg;
    enum HAL_DMA_RET_T ret;
    uint32_t lock;
    uint32_t cnt;
    uint32_t i;
    enum HAL_DMA_PERIPH_T periph;

    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    ASSERT(uart[id].irq != INVALID_IRQn, "DMA not supported on UART %d", id);
    ASSERT((uart[id].base->UARTDMACR & UARTDMACR_RXDMAE), "DMA not configured on UART %d", id);

    if (ucnt == 0) {
        return -21;
    }
    if (step > HAL_UART_DMA_TRANSFER_STEP || step == 0) {
        return -22;
    }

    buf = ubuf[0].buf;
    len = ubuf[0].len;

    if (buf == NULL) {
        return -23;
    }
    if (len == 0) {
        return -24;
    }

    if (0) {
    } else if (mode == UART_RX_DMA_MODE_NORMAL) {
        cnt = (len + step - 1) / step;
    } else if (mode == UART_RX_DMA_MODE_PINGPANG) {
        cnt = ((len / 2 + step - 1) / step) * 2;
        step = len / cnt;
        if (len % cnt != 0) {
            return -11;
        }
        if (step == 0) {
            return -12;
        }
    } else if (mode == UART_RX_DMA_MODE_STREAM) {
        cnt = (len + step - 1) / step;
        if (cnt == 1) {
            // cnt should >= 2
            cnt++;
        }
        step = (len + cnt - 1) / cnt;
        if (step == 0) {
            return -13;
        }
    } else if (mode == UART_RX_DMA_MODE_BUF_LIST) {
        cnt = 0;
        for (i = 0; i < ucnt; i++) {
            if (ubuf->buf == NULL) {
                return -14;
            }
            if (ubuf->len == 0) {
                return -15;
            }
            cnt += (ubuf->len + step - 1) / step;
        }
    } else {
        return -10;
    }

    // Return the required DMA descriptor count
    if (desc == NULL && desc_cnt) {
        *desc_cnt = (cnt == 1) ? 0 : cnt;
        return 0;
    }

    if (cnt > 1) {
        if (desc == NULL || desc_cnt == NULL) {
            return -1;
        }
        if (*desc_cnt < cnt) {
            return -2;
        }
    }
    if (desc_cnt) {
        *desc_cnt = (cnt == 1) ? 0 : cnt;
    }

    periph = uart[id].rx_periph;

    lock = int_lock();
    if (recv_dma_chan[id] != HAL_DMA_CHAN_NONE) {
        int_unlock(lock);
        return 1;
    }
    recv_dma_chan[id] = hal_gpdma_get_chan(periph, HAL_DMA_HIGH_PRIO);
    if (recv_dma_chan[id] == HAL_DMA_CHAN_NONE) {
        int_unlock(lock);
        return 2;
    }
    int_unlock(lock);

    recv_dma_mode[id] = mode;
    recv_dma_size[id] = len;

    memset(&dma_cfg, 0, sizeof(dma_cfg));
    dma_cfg.ch = recv_dma_chan[id];
    dma_cfg.dst = (uint32_t)buf;
    dma_cfg.dst_bsize = HAL_DMA_BSIZE_32;
    dma_cfg.dst_periph = 0; // useless
    dma_cfg.dst_width = HAL_DMA_WIDTH_BYTE;
    dma_cfg.handler = recv_dma_irq_handler;
    dma_cfg.src = 0; // useless
    dma_cfg.src_bsize = HAL_DMA_BSIZE_8;
    dma_cfg.src_periph = periph;
    dma_cfg.src_tsize = len;
    dma_cfg.src_width = HAL_DMA_WIDTH_BYTE;
    dma_cfg.type = HAL_DMA_FLOW_P2M_DMA;
    dma_cfg.try_burst = 0;

    if (mask) {
        recv_mask[id] = *mask;
        dma_cfg.start_cb = recv_dma_start_callback;
    } else {
        union HAL_UART_IRQ_T irq_mask;

        irq_mask.reg = uart[id].base->UARTIMSC;
        ASSERT(irq_mask.RT == 0, err_recv_dma_api, __FUNCTION__);
    }

    // Activate DMA RT behaviour
    lock = int_lock();
    uart[id].base->UARTLCR_H |= UARTLCR_H_DMA_RT_EN;
    int_unlock(lock);

    if (cnt == 1) {
        ret = hal_gpdma_start(&dma_cfg);
    } else {
        if (mode == UART_RX_DMA_MODE_BUF_LIST) {
            ret = fill_buf_list_dma_desc(desc, cnt, &dma_cfg, ubuf, ucnt, step);
        } else {
            ret = fill_dma_desc(desc, cnt, &dma_cfg, len, mode, step);
        }
        if (ret) {
            goto _err_exit;
        }
        ret = hal_gpdma_sg_start(desc, &dma_cfg);
    }

    if (ret != HAL_DMA_OK) {
_err_exit:
        // Restore the traditional RT behaviour
        lock = int_lock();
        uart[id].base->UARTLCR_H &= ~UARTLCR_H_DMA_RT_EN;
        int_unlock(lock);
        hal_gpdma_free_chan(recv_dma_chan[id]);
        recv_dma_chan[id] = HAL_DMA_CHAN_NONE;
        return 3;
    }

    return 0;
}

// Safe API to trigger receive timeout IRQ and DMA IRQ
int hal_uart_dma_recv_mask(enum HAL_UART_ID_T id, uint8_t *buf, uint32_t len,
                           struct HAL_DMA_DESC_T *desc, uint32_t *desc_cnt,
                           const union HAL_UART_IRQ_T *mask)
{
    struct HAL_UART_BUF_T uart_buf;

    uart_buf.buf = buf;
    uart_buf.len = len;
    uart_buf.irq = false;
    uart_buf.loop_hdr = false;
    return start_recv_dma_with_mask(id, &uart_buf, 1, desc, desc_cnt, mask, UART_RX_DMA_MODE_NORMAL, HAL_UART_DMA_TRANSFER_STEP);
}

// Safe API to trigger receive timeout IRQ and DMA IRQ
int hal_uart_dma_recv_mask_pingpang(enum HAL_UART_ID_T id, uint8_t *buf, uint32_t len,
                                    struct HAL_DMA_DESC_T *desc, uint32_t *desc_cnt,
                                    const union HAL_UART_IRQ_T *mask, uint32_t step)
{
    struct HAL_UART_BUF_T uart_buf;

    uart_buf.buf = buf;
    uart_buf.len = len;
    uart_buf.irq = false;
    uart_buf.loop_hdr = false;
    return start_recv_dma_with_mask(id, &uart_buf, 1, desc, desc_cnt, mask, UART_RX_DMA_MODE_PINGPANG, step);
}

// Safe API to trigger receive timeout IRQ and DMA IRQ
int hal_uart_dma_recv_mask_stream(enum HAL_UART_ID_T id, uint8_t *buf, uint32_t len,
                                  struct HAL_DMA_DESC_T *desc, uint32_t *desc_cnt,
                                  const union HAL_UART_IRQ_T *mask, uint32_t step)
{
    struct HAL_UART_BUF_T uart_buf;

    uart_buf.buf = buf;
    uart_buf.len = len;
    uart_buf.irq = false;
    uart_buf.loop_hdr = false;
    return start_recv_dma_with_mask(id, &uart_buf, 1, desc, desc_cnt, mask, UART_RX_DMA_MODE_STREAM, step);
}

// Safe API to trigger receive timeout IRQ and DMA IRQ
int hal_uart_dma_recv_mask_buf_list(enum HAL_UART_ID_T id, const struct HAL_UART_BUF_T *ubuf, uint32_t ucnt,
                                    struct HAL_DMA_DESC_T *desc, uint32_t *desc_cnt, const union HAL_UART_IRQ_T *mask)
{
    return start_recv_dma_with_mask(id, ubuf, ucnt, desc, desc_cnt, mask, UART_RX_DMA_MODE_BUF_LIST, HAL_UART_DMA_TRANSFER_STEP);
}

int hal_uart_dma_recv(enum HAL_UART_ID_T id, uint8_t *buf, uint32_t len,
                      struct HAL_DMA_DESC_T *desc, uint32_t *desc_cnt)
{
    struct HAL_UART_BUF_T uart_buf;

    uart_buf.buf = buf;
    uart_buf.len = len;
    uart_buf.irq = false;
    uart_buf.loop_hdr = false;
    return start_recv_dma_with_mask(id, &uart_buf, 1, desc, desc_cnt, NULL, UART_RX_DMA_MODE_NORMAL, HAL_UART_DMA_TRANSFER_STEP);
}

int hal_uart_dma_recv_pingpang(enum HAL_UART_ID_T id, uint8_t *buf, uint32_t len,
                               struct HAL_DMA_DESC_T *desc, uint32_t *desc_cnt)
{
    struct HAL_UART_BUF_T uart_buf;

    uart_buf.buf = buf;
    uart_buf.len = len;
    uart_buf.irq = false;
    uart_buf.loop_hdr = false;
    return start_recv_dma_with_mask(id, &uart_buf, 1, desc, desc_cnt, NULL, UART_RX_DMA_MODE_PINGPANG, HAL_UART_DMA_TRANSFER_STEP_PINGPANG);
}

uint32_t hal_uart_get_dma_recv_addr(enum HAL_UART_ID_T id)
{
    uint32_t lock;
    uint32_t addr;
    int i;

    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);

    addr = 0;

    lock = int_lock();
    if (recv_dma_chan[id] != HAL_DMA_CHAN_NONE) {
        for (i = 0; i < 2; i++) {
            addr = hal_dma_get_cur_dst_addr(recv_dma_chan[id]);
            if (addr) {
                break;
            }
        }
    }
    int_unlock(lock);

    return addr;
}

uint32_t hal_uart_stop_dma_recv(enum HAL_UART_ID_T id)
{
    uint32_t remains;
    uint32_t lock;
    uint8_t chan;

    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);

    lock = int_lock();
    chan = recv_dma_chan[id];
    recv_dma_chan[id] = HAL_DMA_CHAN_NONE;
    // Restore the traditional RT behaviour
    uart[id].base->UARTLCR_H &= ~UARTLCR_H_DMA_RT_EN;
    int_unlock(lock);

    if (chan == HAL_DMA_CHAN_NONE) {
        return 0;
    }

    // Save the data in DMA FIFO
    hal_gpdma_stop(chan);
    remains = hal_gpdma_get_sg_remain_size(chan);
    hal_gpdma_free_chan(chan);

    return remains;
}

static void send_dma_irq_handler(uint8_t chan, uint32_t remain_tsize, uint32_t error, struct HAL_DMA_DESC_T *lli)
{
    enum HAL_UART_ID_T id;
    uint32_t xfer;
    uint32_t lock;

    lock = int_lock();
    for (id = HAL_UART_ID_0; id < HAL_UART_ID_QTY; id++) {
        if (send_dma_chan[id] == chan) {
            send_dma_chan[id] = HAL_DMA_CHAN_NONE;
            break;
        }
    }
    int_unlock(lock);

    if (id == HAL_UART_ID_QTY) {
        return;
    }

    // Get remain xfer size
    xfer = hal_gpdma_get_sg_remain_size(chan);

    hal_gpdma_free_chan(chan);

    if (txdma_handler[id]) {
        // Get already xfer size
        if (send_dma_size[id] > xfer) {
            xfer = send_dma_size[id] - xfer;
        } else {
            xfer = 0;
        }
        txdma_handler[id](xfer, error);
    }
}

int hal_uart_dma_send(enum HAL_UART_ID_T id, const uint8_t *buf, uint32_t len,
                      struct HAL_DMA_DESC_T *desc, uint32_t *desc_cnt)
{
    struct HAL_DMA_CH_CFG_T dma_cfg;
    enum HAL_DMA_RET_T ret;
    uint32_t lock;
    uint32_t cnt;
    uint32_t i;
    enum HAL_DMA_PERIPH_T periph;

    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);
    ASSERT(uart[id].irq != INVALID_IRQn, "DMA not supported on UART %d", id);
    ASSERT((uart[id].base->UARTDMACR & UARTDMACR_TXDMAE), "DMA not configured on UART %d", id);

    cnt = (len + HAL_UART_DMA_TRANSFER_STEP - 1) / HAL_UART_DMA_TRANSFER_STEP;

    // Return the required DMA descriptor count
    if (desc == NULL && desc_cnt) {
        *desc_cnt = (cnt == 1) ? 0 : cnt;
        return 0;
    }

    if (cnt == 0) {
        return 0;
    }
    if (cnt > 1) {
        if (desc == NULL || desc_cnt == NULL) {
            return -1;
        }
        if (*desc_cnt < cnt) {
            return -2;
        }
    }
    if (desc_cnt) {
        *desc_cnt = (cnt == 1) ? 0 : cnt;
    }

    periph = uart[id].tx_periph;

    lock = int_lock();
    if (send_dma_chan[id] != HAL_DMA_CHAN_NONE) {
        int_unlock(lock);
        return 1;
    }
    send_dma_chan[id] = hal_gpdma_get_chan(periph, HAL_DMA_HIGH_PRIO);
    if (send_dma_chan[id] == HAL_DMA_CHAN_NONE) {
        int_unlock(lock);
        return 2;
    }
    int_unlock(lock);

    send_dma_size[id] = len;

    memset(&dma_cfg, 0, sizeof(dma_cfg));
    dma_cfg.ch = send_dma_chan[id];
    dma_cfg.dst = 0; // useless
    dma_cfg.dst_bsize = HAL_DMA_BSIZE_8;
    dma_cfg.dst_periph = periph;
    dma_cfg.dst_width = HAL_DMA_WIDTH_BYTE;
    dma_cfg.handler = send_dma_irq_handler;
    dma_cfg.src = (uint32_t)buf;
    dma_cfg.src_bsize = HAL_DMA_BSIZE_32;
    dma_cfg.src_periph = 0; // useless
    dma_cfg.src_tsize = len;
    dma_cfg.src_width = HAL_DMA_WIDTH_BYTE;
    dma_cfg.type = HAL_DMA_FLOW_M2P_DMA;
    dma_cfg.try_burst = 0;

    if (cnt == 1) {
        ret = hal_gpdma_start(&dma_cfg);
    } else {
        for (i = 0; i < cnt - 1; i++) {
            dma_cfg.src_tsize = HAL_UART_DMA_TRANSFER_STEP;
            ret = hal_gpdma_init_desc(&desc[i], &dma_cfg, &desc[i + 1], 0);
            if (ret != HAL_DMA_OK) {
                goto _err_exit;
            }
            dma_cfg.src += HAL_UART_DMA_TRANSFER_STEP;
        }
        dma_cfg.src_tsize = len - (HAL_UART_DMA_TRANSFER_STEP * i);
        ret = hal_gpdma_init_desc(&desc[i], &dma_cfg, NULL, 1);
        if (ret != HAL_DMA_OK) {
            goto _err_exit;
        }
        ret = hal_gpdma_sg_start(desc, &dma_cfg);
    }

    if (ret != HAL_DMA_OK) {
_err_exit:
        hal_gpdma_free_chan(send_dma_chan[id]);
        send_dma_chan[id] = HAL_DMA_CHAN_NONE;
        return 3;
    }

    return 0;
}

uint32_t hal_uart_stop_dma_send(enum HAL_UART_ID_T id)
{
    uint32_t remains;
    uint32_t lock;
    uint8_t chan;

    ASSERT(id < HAL_UART_ID_QTY, err_invalid_id, id);

    lock = int_lock();
    chan = send_dma_chan[id];
    send_dma_chan[id] = HAL_DMA_CHAN_NONE;
    int_unlock(lock);

    if (chan == HAL_DMA_CHAN_NONE) {
        return 0;
    }

    // Not to keep the data in DMA FIFO
    hal_gpdma_cancel(chan);
    remains = hal_gpdma_get_sg_remain_size(chan);
    hal_gpdma_free_chan(chan);

    return remains;
}

static void hal_uart_irq_handler(void)
{
    enum HAL_UART_ID_T id;
    union HAL_UART_IRQ_T state;

    for (id = HAL_UART_ID_0; id < HAL_UART_ID_QTY; id++) {
        state.reg = uart[id].base->UARTMIS;

        if (state.reg) {
            uart[id].base->UARTICR = state.reg;

            if (irq_handler[id] != NULL) {
                irq_handler[id](id, state);
            }
        }
    }
}

// ========================================================================
// Test function

#include "stdarg.h"
#include "stdio.h"

#if !defined(DEBUG_PORT) || (DEBUG_PORT == 1)
#define UART_PRINTF_ID                  HAL_UART_ID_0
#else
#define UART_PRINTF_ID                  HAL_UART_ID_1
#endif

#ifndef TRACE_BAUD_RATE
#define TRACE_BAUD_RATE                  (921600)
#endif

int hal_uart_printf_init(void)
{
    static const struct HAL_UART_CFG_T uart_cfg = {
        .parity = HAL_UART_PARITY_NONE,
        .stop = HAL_UART_STOP_BITS_1,
        .data = HAL_UART_DATA_BITS_8,
        .flow = HAL_UART_FLOW_CONTROL_NONE,//HAL_UART_FLOW_CONTROL_RTSCTS,
        .tx_level = HAL_UART_FIFO_LEVEL_1_2,
        .rx_level = HAL_UART_FIFO_LEVEL_1_4,
        .baud = TRACE_BAUD_RATE,
        .dma_rx = false,
        .dma_tx = false,
        .dma_rx_stop_on_err = false,
    };

    if (UART_PRINTF_ID == HAL_UART_ID_0) {
        hal_iomux_set_uart0();
    } else {
        hal_iomux_set_uart1();
    }

    return hal_uart_open(UART_PRINTF_ID, &uart_cfg);
}

void hal_uart_printf(const char *fmt, ...)
{
    char buf[200];
    int ret;
    int i;
    va_list ap;

    va_start(ap, fmt);
    ret = vsnprintf(buf, sizeof(buf), fmt, ap);
#ifdef TRACE_CRLF
    if (ret + 2 < sizeof(buf)) {
        buf[ret++] = '\r';
    }
#endif
    if (ret + 1 < sizeof(buf)) {
        buf[ret++] = '\n';
    }
    //buf[ret] = 0;
    va_end(ap);

    if (ret > 0) {
        for (i = 0; i < ret; i++) {
            hal_uart_blocked_putc(UART_PRINTF_ID, buf[i]);
        }
    }
}

#endif // CHIP_HAS_UART
