#include "n32l40x.h"
#include "n32l40x_adc.h"




/**
 * @brief Initialize ADC peripheral.
 * @details Initialize ADC peripheral and configure ADC interrupt for left &
 *          right earbud current channels and charging case voltage channel.
 * @param callback the callback function on power manager
 * @return None
 */
void powerAdcInit(ADCDONECALLBACK callback)
{
    assert_param(callback);

    GPIO_InitType stGpioInit;
    NVIC_InitType stNvicInit;

    /* Initialize stGpioInit */
    GPIO_InitStruct(&stGpioInit);
    /* Configure PA.01, PA.03, PA.05, PA.06 and PA.07 as analog input --------*/
    stGpioInit.Pin = ADC_PIN_GROUP_A;
    stGpioInit.GPIO_Mode = GPIO_Mode_Analog;
    GPIO_InitPeripheral(ADC_GPIO_PORT_A, &stGpioInit);

    /* Enable ADC clocks */
    RCC_EnableAHBPeriphClk(ADC_PERIPH_CLK, ENABLE);

    /* RCC_ADCHCLK_DIV16 */
    ADC_ConfigClk(ADC_CTRL3_CKMOD_AHB, RCC_ADCHCLK_DIV16);
    /* selsect HSE as RCC ADC1M CLK Source */
#ifdef XTALLESS
    RCC_ConfigAdc1mClk(RCC_ADC1MCLK_SRC_HSI, RCC_ADC1MCLK_DIV8);
#else
    RCC_ConfigAdc1mClk(RCC_ADC1MCLK_SRC_HSE, RCC_ADC1MCLK_DIV8);
#endif /* end of XTALLESS */

    /* Configure ADC interrupt */
    stNvicInit.NVIC_IRQChannel = ADC_IRQ;
    stNvicInit.NVIC_IRQChannelPreemptionPriority = ADC_IRQ_PRIORITY;
    stNvicInit.NVIC_IRQChannelSubPriority = ADC_IRQ_SUB_PRIORITY;
    stNvicInit.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&stNvicInit);

    stAdcContext.enState = ADC_STATE_OFF;
    stAdcContext.enChannel = ADC_CHANNEL_NULL;
    for (uint8_t ucIndex = 0; ucIndex < ADC_QUEUE_SIZE; ucIndex++)
    {
        stAdcContext.stQueue.enItem[ucIndex] = ADC_CHANNEL_NULL;
    }
    stAdcContext.stQueue.scFrontIndex = ADC_NULL;
    stAdcContext.stQueue.scRearIndex = ADC_NULL;
    stAdcContext.convertDoneCallback = callback;
    stAdcContext.blFactoryTestModeEnable = false;
    timerCreate(&stAdcContext.stCoolDownTimer);
    timerCreate(&stAdcContext.stReadDelayTimer);
    timerCreate(&stAdcContext.stDestroyCaseVoltageReadTimer);

    powerAdcPinGpioInit();
    powerAdcStart();
}




/**
 * @brief Start ADC function.
 * @details Start ADC function, enable VS_SENSE pin for current monitor and
 *          enable VBAT_EN for voltage detector.
 * @param
 * @return None
 */
void powerAdcStart(void)
{
    if (stAdcContext.enState == ADC_STATE_OFF)
    {
        powerMcuPowerSetModuleBusy(MCU_POWER_MODULE_ADC, true);

        ADC_DeInit(ADC_INSTANCE);
        ADC_InitType ADC_InitStructure;
        /* ADC configuration ------------------------------------------------*/
        ADC_InitStructure.MultiChEn = DISABLE;
        ADC_InitStructure.ContinueConvEn = DISABLE;
        ADC_InitStructure.ExtTrigSelect = ADC_EXT_TRIGCONV_NONE;
        ADC_InitStructure.DatAlign = ADC_DAT_ALIGN_R;
        ADC_InitStructure.ChsNumber = ADC_CHANNEL_SIZE;
        ADC_Init(ADC_INSTANCE, &ADC_InitStructure);

        /* Enable ENDC interrupt */
        ADC_ConfigInt(ADC_INSTANCE, ADC_INT_ENDC, ENABLE);
        /* Enable ADC */
        ADC_Enable(ADC_INSTANCE, ENABLE);

        /* clang-format off */
        /* Check ADC Ready */
        while (ADC_GetFlagStatusNew(ADC_INSTANCE, ADC_FLAG_RDY) == RESET);
        /* Start ADC calibration */
        ADC_StartCalibration(ADC_INSTANCE);
        /* Check the end of ADC calibration */
        while (ADC_GetCalibrationStatus(ADC_INSTANCE));
        /* clang-format on */

        /* Disable VS_SENSE Pin */
        powerAdcSetVsSensePin(Bit_RESET);
        /* Disable VBAT_EN Pin */
        powerAdcSetVBatEnablePin(Bit_RESET);
#ifdef NTC_TEST_ENABLE
        /* Enable Qi_NTC_VDD Pin */
        powerAdcSetQiVddPin(Bit_SET);
        /* Enable BAT_NTC_VDD Pin */
        powerAdcSetBatNTCVddPin(Bit_SET);
#else
        /* Disable Qi_NTC_VDD Pin */
        powerAdcSetQiVddPin(Bit_RESET);
        /* Disable BAT_NTC_VDD Pin */
        powerAdcSetBatNTCVddPin(Bit_RESET);
#endif
        stAdcContext.enState = ADC_STATE_IDLE;
    }
}




/**
 * @brief Stop ADC function.
 * @details Stop ADC function and timer, disable VS_SENSE pin for current
 *          monitor, disable VBAT_EN for voltage detector.
 * @param
 * @return None
 */
void powerAdcStop(void)
{
    if (stAdcContext.enState != ADC_STATE_OFF)
    {
        stAdcContext.enState = ADC_STATE_OFF;
        /* Disable ADC */
        ADC_Enable(ADC_INSTANCE, DISABLE);
        /* clang-format off */
        /* Check ADC Power Down Ready */
        while(ADC_GetFlagStatusNew(ADC_INSTANCE, ADC_FLAG_PD_RDY) == RESET);
        /* clang-format on */
        /* Stop ADC timer */
        timerCancel(&stAdcContext.stCoolDownTimer);
        timerCancel(&stAdcContext.stReadDelayTimer);
        /* Disable VS_SENSE Pin */
        powerAdcSetVsSensePin(Bit_RESET);
        /* Disable VBAT_EN Pin */
        powerAdcSetVBatEnablePin(Bit_RESET);
#ifdef NTC_TEST_ENABLE
        /* Enable Qi_NTC_VDD Pin */
        powerAdcSetQiVddPin(Bit_SET);
        /* Ensable BAT_NTC_VDD Pin */
        powerAdcSetBatNTCVddPin(Bit_SET);
#else
        /* Disable Qi_NTC_VDD Pin */
        powerAdcSetQiVddPin(Bit_RESET);
        /* Disable BAT_NTC_VDD Pin */
        powerAdcSetBatNTCVddPin(Bit_RESET);
#endif
        powerMcuPowerSetModuleBusy(MCU_POWER_MODULE_ADC, false);
    }
}

/**
 * @brief Initialize MCU power mode.
 * @details Initialize MCU power mode to configure system clock and enable all
 *          GPIO clock.
 * @param
 * @return None
 */
void powerMcuPowerInit(void)
{
    powerMcuPowerConfigureSystemClock();
    /* Enable PWR Clock */
    RCC_EnableAPB1PeriphClk(MCUPOWER_PERIPH_PWR_CLK, ENABLE);
    /* Enable GPIO clock */
    RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_AFIO, ENABLE);
    RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE);
    RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB, ENABLE);

    stMcuPowerContext.enState = MCU_POWER_STATE_NORMAL_RUN;
    stMcuPowerContext.enRunState = MCU_POWER_STATE_LOW_POWER_RUN;
    stMcuPowerContext.enLowestState = MCU_POWER_STATE_SLEEP;
    stMcuPowerContext.blPowerStateRecompute = true;

    for (uint8_t i = 0; i < MCU_POWER_MODULE_SIZE; i++)
    {
        stMcuPowerContext.blModuleBusy[i] = false;
    }
}




/**
 * @brief Set MCU power module to busy.
 * @details Set MCU power module to busy and update MCU power run state & lowest
 *          state based on different modules power state requirement table.
 * @param enModule specified MCU power module
 *        @arg MCU_POWER_MODULE_ADC module ADC
 *        @arg MCU_POWER_MODULE_VBUS module VBUS
 *        @arg MCU_POWER_MODULE_I2C module I2C
 *        @arg MCU_POWER_MODULE_ONE_WIRE module One Wire
 *        @arg MCU_POWER_MODULE_LED module LED
 *        @arg MCU_POWER_MODULE_APPLICATION module Application
 * @param blSetBusy Set module to busy or idle
 *        @arg true set module to busy state
 *        @arg false reset module to idle state
 * @return None
 */
void powerMcuPowerSetModuleBusy(MCU_POWER_MODULE enModule, bool blSetBusy)
{
    if (stMcuPowerContext.blModuleBusy[enModule] == blSetBusy)
    {
        return;
    }

    stMcuPowerContext.blModuleBusy[enModule] = blSetBusy;

    if (blSetBusy)
    {
        if (stMcuPowerContext.enLowestState >
            enMcuLowestPowerState[enModule][MCUPOWER_LOWEST_INDEX])
        {
            stMcuPowerContext.enLowestState =
                enMcuLowestPowerState[enModule][MCUPOWER_LOWEST_INDEX];
        }
        if (stMcuPowerContext.enRunState >
            enMcuLowestPowerState[enModule][MCUPOWER_RUN_INDEX])
        {
            stMcuPowerContext.enRunState =
                enMcuLowestPowerState[enModule][MCUPOWER_RUN_INDEX];

            // change state immediately
            stMcuPowerContext.enState = powerMcuPowerStateTransition(
                stMcuPowerContext.enState,
                stMcuPowerContext.enRunState);
        }
    }
    else
    {
        if (stMcuPowerContext.enLowestState ==
            enMcuLowestPowerState[enModule][MCUPOWER_LOWEST_INDEX])
        {
            stMcuPowerContext.blPowerStateRecompute = true;
        }
        else if (stMcuPowerContext.enRunState ==
                 enMcuLowestPowerState[enModule][MCUPOWER_RUN_INDEX])
        {
            stMcuPowerContext.blPowerStateRecompute = true;
        }
    }
}



/**
 * @brief MCU power state transition.
 * @details Process and transit the MCU power mode from current state to next
 *          state.
 * @param enState the current MCU power state
 * @param enNextState the next MCU power state
 *        The following of values for enState and enNextState:
 *        @arg MCU_POWER_STATE_NORMAL_RUN MCU power normal run mode
 *        @arg MCU_POWER_STATE_LOW_POWER_RUN MCU power low power run mode
 *        @arg MCU_POWER_STATE_SLEEP MCU power sleep mode
 *        @arg MCU_POWER_STATE_LOW_POWER_SLEEP MCU power low power sleep mode
 *        @arg MCU_POWER_STATE_STOP2 MCU power stop2 mode
 *        @arg MCU_POWER_STATE_STANDBY MCU power standby mode
 * @return Specific the current MCU power run state.
 * @retval The enumeration of MCU power run state:
 *         - MCU_POWER_STATE_NORMAL_RUN MCU power normal run mode
 *         - MCU_POWER_STATE_LOW_POWER_RUN MCU power low power run mode
 */
static MCU_POWER_STATE powerMcuPowerStateTransition(MCU_POWER_STATE enState,
                                                    MCU_POWER_STATE enNextState)
{
    MCU_POWER_STATE enEndState = enNextState;
    bool blIsContinueTransition;

    do
    {
        blIsContinueTransition = false;
        switch (enState)
        {
            case MCU_POWER_STATE_NORMAL_RUN:
            {
                switch (enNextState)
                {
                    case MCU_POWER_STATE_STOP2:
                    {
                        // printf("Stop2FromRun \n\r");
                        powerMcuPowerEnterStop2Mode();
                        // After wake up from stop2, reinit LED timer
                        ledPWMTimerReinit();
                        enEndState = MCU_POWER_STATE_NORMAL_RUN;
                        break;
                    }
                    case MCU_POWER_STATE_SLEEP:
                    {
                        // printf("EnterSleep \n\r");
                        powerMcuPowerEnterSleepMode();
                        enEndState = MCU_POWER_STATE_NORMAL_RUN;
                        break;
                    }
                    case MCU_POWER_STATE_LOW_POWER_RUN:
                    {
                        // printf("LPRunFromRun \n\r");
                        powerMcuPowerEnterLowPowerRunMode();
                        enState = MCU_POWER_STATE_LOW_POWER_RUN;
                        blIsContinueTransition = true;
                        break;
                    }
                    default:
                        break;
                }
                break;
            }
            case MCU_POWER_STATE_LOW_POWER_RUN:
            {
                switch (enNextState)
                {
                    case MCU_POWER_STATE_STOP2:
                    {
                        // printf("Stop2FromLPRun \n\r");
                        powerMcuPowerEnterStop2Mode();
                        // After wake up from stop2, reinit LED timer
                        ledPWMTimerReinit();
                        enEndState = MCU_POWER_STATE_LOW_POWER_RUN;
                        break;
                    }
                    case MCU_POWER_STATE_LOW_POWER_SLEEP:
                    {
                        // printf("EnterLowPowerSleep \n\r");
                        powerMcuPowerEnterSleepMode();
                        enEndState = MCU_POWER_STATE_LOW_POWER_RUN;
                        break;
                    }
                    case MCU_POWER_STATE_NORMAL_RUN:
                    {
                        powerMcuPowerExitLowPowerRunMode();
                        // printf("RunFromLPRun\n\r");
                        enState = MCU_POWER_STATE_NORMAL_RUN;
                        blIsContinueTransition = true;
                        break;
                    }
                    default:
                        break;
                }
                break;
            }
            default:
                enEndState = enState;
                break;
        }
    }
    while (blIsContinueTransition);

    return enEndState;
}
/**
 * @brief Configure System Clock.
 * @details Configure System Clock as 48M Hz.
 * @param
 * @return None
 */
static void powerMcuPowerConfigureSystemClock(void)
{
    FlagStatus enMsiReadyFlag = RESET;

    /* clang-format off */
#ifdef XTALLESS
    /* Enable HSI */
    RCC_ConfigHsi(RCC_HSI_ENABLE);
    /* Wait till HSI is ready */
    while (SUCCESS != RCC_WaitHsiStable());
#else
    /* Enable HSE */
    RCC_ConfigHse(RCC_HSE_ENABLE);
    /* Wait till HSE is ready */
    while (SUCCESS != RCC_WaitHseStable());
#endif /* end of XTALLESS */
    /* clang-format on */

    /* Cheak if MSI is Ready */
    if (RCC_GetFlagStatus(RCC_CTRLSTS_FLAG_MSIRD) == RESET)
    {
        RCC_ConfigMsi(RCC_MSI_ENABLE, RCC_MSI_RANGE_4M);
        /* clang-format off */
        while (SUCCESS != RCC_WaitMsiStable());
        /* clang-format on */
        enMsiReadyFlag = SET;
    }
    /* Select MSI as system clock source */
    RCC_ConfigSysclk(RCC_SYSCLK_SRC_MSI);
    FLASH_SetLatency(FLASH_LATENCY_1);
    RCC_ConfigHclk(RCC_SYSCLK_DIV1);
    RCC_ConfigPclk2(RCC_HCLK_DIV2);
    RCC_ConfigPclk1(RCC_HCLK_DIV2);
    RCC_EnablePll(DISABLE);
#ifdef XTALLESS
    RCC_ConfigPll(RCC_PLL_HSI_PRE_DIV1, RCC_PLL_MUL_3, RCC_PLLDIVCLK_DISABLE);
#else
    RCC_ConfigPll(RCC_PLL_SRC_HSE_DIV1, RCC_PLL_MUL_6, RCC_PLLDIVCLK_DISABLE);
#endif /* end of XTALLESS */
    RCC_EnablePll(ENABLE);
    /* clang-format off */
    while (RCC_GetFlagStatus(RCC_CTRL_FLAG_PLLRDF) == RESET);
    /* Select PLL as system clock source */
    RCC_ConfigSysclk(RCC_SYSCLK_SRC_PLLCLK);
    while (RCC_GetSysclkSrc() != 0x0C);
    /* clang-format on */
    if (enMsiReadyFlag == SET)
    {
        RCC_ConfigMsi(RCC_MSI_DISABLE, RCC_MSI_RANGE_4M);
    }
}



/**
 * @brief Enter MCU power low power run mode.
 * @details Enter MCU power low power run mode.
 * @param
 * @return None
 */
static void powerMcuPowerEnterLowPowerRunMode(void)
{
    CRITICAL_ENTER();
#if defined DEBUG_USART_NON_BLOCKING
    // Used to flush out previous debug printing
    volatile uint16_t uiDelayCount = 1000;
    while (uiDelayCount--)
    {
        ;
    }
#endif /* end of DEBUG_USART_NON_BLOCKING */
    PWR_EnterLowPowerRunMode();
    /* System clock is automatically set as 4M Hz when enter Low Power Run */
    powerMcuPowerConfigureUsartBaudRate();
    CRITICAL_EXIT();
}




/**
 * @brief Exit MCU power low power run mode.
 * @details Exit MCU power low power run mode and reconfigure system clock.
 * @param
 * @return None
 */
static void powerMcuPowerExitLowPowerRunMode(void)
{
    CRITICAL_ENTER();
    PWR_ExitLowPowerRunMode();
    /* System clock is set as normal 48M Hz when exit Low Power Run */
    powerMcuPowerConfigureSystemClock();
    powerMcuPowerConfigureUsartBaudRate();
    CRITICAL_EXIT();
}




/**
 * @brief Configure the USART baud rate.
 * @details Reconfigure the USART baud rate for debug print when system clock
 *          is changed at entering and exiting MCU power low power run mode.
 * @param
 * @return None
 */
static void powerMcuPowerConfigureUsartBaudRate(void)
{
    uint32_t ulRegister = 0x00;
    uint32_t ulIntDivider = 0x00;
    uint32_t ulFractDivider = 0x00;
    uint32_t ulUsartBaudRate = 115200;
    RCC_ClocksType stRccClocksStatus;

    /* Obtain system RCC clock */
    RCC_GetClocksFreqValue(&stRccClocksStatus);

    /* Determine the integer part */
    ulIntDivider = (25 * stRccClocksStatus.Pclk2Freq) / (4 * ulUsartBaudRate);
    ulRegister = (ulIntDivider / 100) << 4;
    /* Determine the fractional part */
    ulFractDivider = ulIntDivider - (100 * (ulRegister >> 4));
    /* Implement the fractional part in the register */
    ulRegister |= ((((ulFractDivider * 16) + 50) / 100)) & ((uint8_t)0x0F);
    /* Write to USART 1 PBC for debug print */
    USART1->BRCF = (uint16_t)ulRegister;
    // WARNING: 16-bit unsigned short int assumed.

    /* Determine the integer part */
    ulIntDivider = (25 * stRccClocksStatus.Pclk1Freq) / (4 * ulUsartBaudRate);
    ulRegister = (ulIntDivider / 100) << 4;
    /* Determine the fractional part */
    ulFractDivider = ulIntDivider - (100 * (ulRegister >> 4));
    /* Implement the fractional part in the register */
    ulRegister |= ((((ulFractDivider * 16) + 50) / 100)) & ((uint8_t)0x0F);
    /* Write to USART 3 PBC for onewire */
    USART3->BRCF = (uint16_t)ulRegister;
    // WARNING: 16-bit unsigned short int assumed.
}


