/*
 * Copyright (c) 2006-2022, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author            Notes
 * 2021-02-25     iysheng           first version
 * 2022-05-03     BruceOu           optimization adc
 */

#include "drv_adc.h"

#define DBG_TAG             "drv.adc"
#define DBG_LVL             DBG_INFO

#define USING_DMA

#if defined SOC_SERIES_GD32H7xx
     #if defined USING_DMA
            #define BUFFER_SIZE           4
            __attribute__((aligned(32))) uint32_t adc_value[1];
     #endif
#endif



#include <rtdbg.h>

#ifdef RT_USING_ADC

#if defined(BSP_USING_ADC0)
struct rt_adc_device adc0;
#endif

#if defined(BSP_USING_ADC1)
struct rt_adc_device adc1;
#endif

#if defined(BSP_USING_ADC2)
struct rt_adc_device adc2;
#endif

#define MAX_EXTERN_ADC_CHANNEL    20

static const struct gd32_adc adc_obj[] = {
#ifdef BSP_USING_ADC0
    {
        ADC0,
        RCU_ADC0,
        {
        #if defined SOC_SERIES_GD32F4xx
            GET_PIN(A, 0), GET_PIN(A, 1), GET_PIN(A, 2), GET_PIN(A, 3),
            GET_PIN(A, 4), GET_PIN(A, 5), GET_PIN(A, 6), GET_PIN(A, 7),
            GET_PIN(B, 0), GET_PIN(B, 1), GET_PIN(C, 0), GET_PIN(C, 1),
            GET_PIN(C, 2), GET_PIN(C, 3), GET_PIN(C, 4), GET_PIN(C, 5),
        #elif defined SOC_SERIES_GD32H7xx
            0xFFFF0000, 0xFFFF0000, GET_PIN(F, 11), GET_PIN(A, 6),
            GET_PIN(C, 4), GET_PIN(B, 1), GET_PIN(F, 12), GET_PIN(A, 7),
            GET_PIN(C, 5), GET_PIN(B, 0), GET_PIN(C, 0), GET_PIN(C, 1),
            GET_PIN(C, 2), GET_PIN(C, 3), GET_PIN(A, 2), GET_PIN(A, 3),
            GET_PIN(A, 0), GET_PIN(A, 1), GET_PIN(A, 4), GET_PIN(A, 5),
        #endif
            
        },
        &adc0,
        "adc0",
    },
#endif

#ifdef BSP_USING_ADC1
    {
        ADC1,
        RCU_ADC1,
        {
        #if defined SOC_SERIES_GD32F4xx
            GET_PIN(A, 0), GET_PIN(A, 1), GET_PIN(A, 2), GET_PIN(A, 3),
            GET_PIN(A, 4), GET_PIN(A, 5), GET_PIN(A, 6), GET_PIN(A, 7),
            GET_PIN(B, 0), GET_PIN(B, 1), GET_PIN(C, 0), GET_PIN(C, 1),
            GET_PIN(C, 2), GET_PIN(C, 3), GET_PIN(C, 4), GET_PIN(C, 5),
        #elif defined SOC_SERIES_GD32H7xx
            0xFFFF0000, 0xFFFF0000, GET_PIN(F, 13), GET_PIN(A, 6),
            GET_PIN(C, 4), GET_PIN(B, 1), GET_PIN(F, 14), GET_PIN(A, 7),
            GET_PIN(C, 5), GET_PIN(B, 0), GET_PIN(C, 0), GET_PIN(C, 1),
            GET_PIN(C, 2), GET_PIN(C, 3), GET_PIN(A, 2), GET_PIN(A, 3),
            0xFFFF0000, 0xFFFF0000, GET_PIN(A, 4), GET_PIN(A, 5),
        #endif
        },
        &adc1,
        "adc1",
    },
#endif
#ifdef BSP_USING_ADC2
    {
        ADC2,
        RCU_ADC2,
        {
        #if defined SOC_SERIES_GD32F4xx
            GET_PIN(A, 0), GET_PIN(A, 1), GET_PIN(A, 2), GET_PIN(A, 3),
            GET_PIN(A, 4), GET_PIN(A, 5), GET_PIN(A, 6), GET_PIN(A, 7),
            GET_PIN(B, 0), GET_PIN(B, 1), GET_PIN(C, 0), GET_PIN(C, 1),
            GET_PIN(C, 2), GET_PIN(C, 3), GET_PIN(C, 4), GET_PIN(C, 5),
        #elif defined SOC_SERIES_GD32H7xx
            0xFFFF0000, 0xFFFF0000, GET_PIN(F, 9), GET_PIN(F, 7),
            GET_PIN(F, 5), GET_PIN(F, 3), GET_PIN(F, 10), GET_PIN(F, 8),
            GET_PIN(F, 6), GET_PIN(F, 4), GET_PIN(C, 0), GET_PIN(C, 1),
            GET_PIN(C, 2), GET_PIN(H, 2), GET_PIN(H, 3), GET_PIN(H, 4),
            GET_PIN(H, 5), 0xFFFF0000, 0xFFFF0000, 0xFFFF0000,
        #endif
        },
        &adc2,
        "adc2",
    },
#endif
};

/**
* @brief ADC MSP Initialization
*        This function configures the hardware resources.
* @param adc_clk, pin
* @retval None
*/
static void gd32_adc_gpio_init(struct rt_adc_device *device, rt_base_t pin)
{
    uint32_t adc_periph;
    struct gd32_adc * adc = (struct gd32_adc *)device->parent.user_data;
    /* enable ADC clock */

    adc_periph = (uint32_t )(adc->adc_periph);
    rcu_periph_clock_enable(adc->adc_clk);
#if defined SOC_SERIES_GD32H7xx
    if(adc->adc_clk == RCU_ADC1){
        rcu_periph_clock_enable(RCU_ADC0);
        adc_deinit(adc_periph);
        adc_clock_config(ADC0, ADC_CLK_SYNC_HCLK_DIV6);
    }else{
        adc_deinit(adc_periph);
        adc_clock_config(adc_periph, ADC_CLK_SYNC_HCLK_DIV6);
    }

#endif

#if defined SOC_SERIES_GD32F4xx
    /* configure adc pin */
    gpio_mode_set(PIN_GDPORT(pin), GPIO_MODE_ANALOG, GPIO_PUPD_NONE, PIN_GDPIN(pin));
#elif defined SOC_SERIES_GD32H7xx
    if(pin != 0xFFFF0000){
        /* configure adc pin */
        gpio_mode_set(PIN_GDPORT(pin), GPIO_MODE_ANALOG, GPIO_PUPD_NONE, PIN_GDPIN(pin));
     }else{
     }
#else
    /* configure adc pin */
    gpio_init(PIN_GDPORT(pin), GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, PIN_GDPIN(pin));

#endif
     
#if defined SOC_SERIES_GD32H7xx
     #if defined USING_DMA
         /* enable DMA clock */
        rcu_periph_clock_enable(RCU_DMA0);
        rcu_periph_clock_enable(RCU_DMAMUX);
     #endif
#endif
}

#if defined SOC_SERIES_GD32H7xx
    #if defined USING_DMA
/**
* @brief DMA configure
*        This function configuring DMA.
* @param None
* @retval None
*/
void dma_config(struct rt_adc_device *device)
{
    uint32_t adc_periph;
    struct gd32_adc * adc = (struct gd32_adc *)device->parent.user_data;
        /* clean the cache line */
    SCB_CleanDCache_by_Addr((uint32_t *)adc_value, BUFFER_SIZE);
    /* ADC_DMA_channel configuration */
    dma_single_data_parameter_struct dma_single_data_parameter;
    adc_periph = (uint32_t )(adc->adc_periph);

    /* ADC DMA_channel configuration */
    dma_deinit(DMA0, DMA_CH0);

    /* initialize DMA single data mode */
    if(ADC0 == adc_periph) {
        dma_single_data_parameter.request = DMA_REQUEST_ADC0;
    }else if(ADC1 == adc_periph){
        dma_single_data_parameter.request = DMA_REQUEST_ADC1;
    }else{
        dma_single_data_parameter.request = DMA_REQUEST_ADC2;
    }
    //dma_single_data_parameter.request = DMA_REQUEST_ADC0;
    dma_single_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA(adc_periph));
    dma_single_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_single_data_parameter.memory0_addr = (uint32_t)(&adc_value);
    dma_single_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_single_data_parameter.periph_memory_width = DMA_PERIPH_WIDTH_32BIT;
    dma_single_data_parameter.direction = DMA_PERIPH_TO_MEMORY;
    dma_single_data_parameter.number = 1;
    dma_single_data_parameter.priority = DMA_PRIORITY_HIGH;
    dma_single_data_mode_init(DMA0, DMA_CH0, &dma_single_data_parameter);

    /* enable DMA circulation mode */
    dma_circulation_enable(DMA0, DMA_CH0);
    /* enable DMA transfer complete interrupt */
    dma_interrupt_enable(DMA0, DMA_CH0, DMA_INT_FTF);
    /* enable DMA channel */
    dma_channel_enable(DMA0, DMA_CH0);
}
    #endif
#endif

/**
* @brief ADC enable
*        This function enable adc.
* @param device, channel, enabled
* @retval None
*/
static rt_err_t gd32_adc_enabled(struct rt_adc_device *device, rt_int8_t channel, rt_bool_t enabled)
{
    uint32_t adc_periph;
    struct gd32_adc * adc = (struct gd32_adc *)device->parent.user_data;

    if (channel >= MAX_EXTERN_ADC_CHANNEL)
    {
        LOG_E("invalid channel");
        return -RT_EINVAL;
    }

    adc_periph = (uint32_t )(adc->adc_periph);

    if (enabled == ENABLE)
    {
        gd32_adc_gpio_init(device, adc->adc_pins[channel]);

        adc_channel_length_config(adc_periph, ADC_REGULAR_CHANNEL, 1);
        adc_data_alignment_config(adc_periph, ADC_DATAALIGN_RIGHT);

#if defined SOC_SERIES_GD32F4xx
        adc_external_trigger_source_config(adc_periph, ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_EXTI_11);
        adc_external_trigger_config(adc_periph, ADC_REGULAR_CHANNEL, ENABLE);
#elif defined SOC_SERIES_GD32H7xx
         adc_external_trigger_config(adc_periph, ADC_REGULAR_CHANNEL, EXTERNAL_TRIGGER_DISABLE);
#else
        adc_external_trigger_source_config(adc_periph, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);
        adc_external_trigger_config(adc_periph, ADC_REGULAR_CHANNEL, ENABLE);
#endif


#if defined SOC_SERIES_GD32F4xx
        adc_regular_channel_config(adc_periph, 0, channel, ADC_SAMPLETIME_480);
#elif defined SOC_SERIES_GD32H7xx
        adc_regular_channel_config(adc_periph, 0, channel, 24);
#else
        adc_regular_channel_config(adc_periph, 0, channel, ADC_SAMPLETIME_13POINT5);
#endif

#if defined SOC_SERIES_GD32H7xx
    #if defined USING_DMA
        /* ADC DMA function enable */
        adc_dma_request_after_last_enable(adc_periph);
        adc_dma_mode_enable(adc_periph);
    #endif
#endif


        adc_enable(adc_periph);

        /* ADC calibration and reset calibration */
        adc_calibration_enable(adc_periph);
        
#if defined SOC_SERIES_GD32H7xx
    #if defined USING_DMA
        /* configuring DMA */
        dma_config(device);
    #endif
#endif
    }
    else
    {
        adc_disable(adc_periph);
    }
    return 0;
}

/**
* @brief convert adc.
*        This function get adc value.
* @param device, channel, value
* @retval None
*/
static rt_err_t gd32_adc_convert(struct rt_adc_device *device, rt_int8_t channel, rt_uint32_t *value)
{
    uint32_t adc_periph;
    struct gd32_adc * adc = (struct gd32_adc *)(device->parent.user_data);

    if (!value)
    {
        LOG_E("invalid param");
        return -RT_EINVAL;
    }

    adc_periph = (uint32_t )(adc->adc_periph);
    adc_software_trigger_enable(adc_periph, ADC_REGULAR_CHANNEL);

#if defined SOC_SERIES_GD32H7xx
    #if defined USING_DMA
        while(!dma_interrupt_flag_get(DMA0, DMA_CH0, DMA_INT_FLAG_FTF)){};
        // clear flag
        dma_interrupt_flag_clear(DMA0, DMA_CH0, DMA_INT_FLAG_FTF);
        /* D-Cache Invalidate by address */
        SCB_InvalidateDCache_by_Addr((uint32_t *)adc_value, BUFFER_SIZE);
        /* get adc value */
        *value = adc_value[0];
            
    #else
        while(!adc_flag_get(adc_periph, ADC_FLAG_EOC)){};
        // clear flag
        adc_flag_clear(adc_periph, ADC_FLAG_EOC);
        *value = adc_regular_data_read(adc_periph);
    #endif
#endif
    

    return 0;
}

static struct rt_adc_ops gd32_adc_ops = {
    .enabled = gd32_adc_enabled,
    .convert = gd32_adc_convert,
};

static int rt_hw_adc_init(void)
{
    int ret, i = 0;

    for (; i < sizeof(adc_obj) / sizeof(adc_obj[0]); i++)
    {
        ret = rt_hw_adc_register(adc_obj[i].adc, \
            (const char *)adc_obj[i].device_name, \
            &gd32_adc_ops, &adc_obj[i]);
        if (ret != RT_EOK)
        {
            /* TODO err handler */
            LOG_E("failed register %s, err=%d", adc_obj[i].device_name, ret);
        }
    }

    return ret;
}
INIT_BOARD_EXPORT(rt_hw_adc_init);
#endif
