/*
 * Copyright 2019 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_common.h"
#include "board.h"
#include "pin_mux.h"
#include "fsl_debug_console.h"

#include "fsl_smc.h"
#include "fsl_llwu.h"
#include "fsl_rcm.h"
#include "fsl_lptmr.h"
#include "fsl_port.h"
#include "fsl_lpuart.h"
#include "fsl_pmc.h"
#include "fsl_rtc.h"

#include "LCD_S401M16KR.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define APP_DEBUG_UART_BAUDRATE 9600 /* Debug console baud rate. */

/* Default debug console clock source. */
#define APP_DEBUG_UART_DEFAULT_CLKSRC_NAME kCLOCK_McgPeriphClk /* MCGPCLK */
#define APP_DEBUG_UART_DEFAULT_CLKSRC 0x01                     /* MCGPCLK */

/* Debug console clock source in VLPR mode. */
#define APP_DEBUG_UART_VLPR_CLKSRC_NAME kCLOCK_McgInternalRefClk /* MCGIRCLK */
#define APP_DEBUG_UART_VLPR_CLKSRC 0x03                          /* MCGIRCCLK */

//#define LLWU_LPTMR_IDX 0U      /* LLWU_M0IF */
#define LLWU_WAKEUP_PIN_IDX 7U /* LLWU_P7 */
#define LLWU_WAKEUP_PIN_TYPE kLLWU_ExternalPinFallingEdge

#define APP_WAKEUP_BUTTON_GPIO BOARD_SW3_GPIO
#define APP_WAKEUP_BUTTON_PORT BOARD_SW3_PORT
#define APP_WAKEUP_BUTTON_GPIO_PIN BOARD_SW3_GPIO_PIN
#define APP_WAKEUP_BUTTON_IRQ BOARD_SW3_IRQ
#define APP_WAKEUP_BUTTON_IRQ_HANDLER BOARD_SW3_IRQ_HANDLER
#define APP_WAKEUP_BUTTON_NAME BOARD_SW3_NAME
#define APP_WAKEUP_BUTTON_IRQ_TYPE kPORT_InterruptFallingEdge

/* Debug console RX pin: PORTA1 MUX: 2 */
#define DEBUG_CONSOLE_RX_PORT PORTA
#define DEBUG_CONSOLE_RX_GPIO GPIOA
#define DEBUG_CONSOLE_RX_PIN 1
#define DEBUG_CONSOLE_RX_PINMUX kPORT_MuxAlt2
/* Debug console TX pin: PORTA2 MUX: 2 */
#define DEBUG_CONSOLE_TX_PORT PORTA
#define DEBUG_CONSOLE_TX_GPIO GPIOA
#define DEBUG_CONSOLE_TX_PIN 2
#define DEBUG_CONSOLE_TX_PINMUX kPORT_MuxAlt2
#define CORE_CLK_FREQ CLOCK_GetFreq(kCLOCK_CoreSysClk)

#define APP_LOW_POWER_MEM_TOKEN 0x55555555

/* Power mode definition used in application. */
typedef enum _app_power_mode
{
    kAPP_PowerModeRun,   /* Normal RUN mode */
    kAPP_PowerModeWait,  /* WAIT mode. */
    kAPP_PowerModeStop,  /* STOP mode. */
    kAPP_PowerModeVlpr,  /* VLPR mode. */
    kAPP_PowerModeVlpw,  /* VLPW mode. */
    kAPP_PowerModeVlps,  /* VLPS mode. */
    kAPP_PowerModeLls,   /* LLS mode. */
    kAPP_PowerModeVlls0, /* VLLS0 mode. */
    kAPP_PowerModeVlls1, /* VLLS1 mode. */
    kAPP_PowerModeVlls3, /* VLLS3 mode. */
    kAPP_PowerModeMax
} app_power_mode_t;

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

extern void APP_SetClockRunFromVlpr(void);
extern void APP_SetClockVlpr(void);

static app_power_mode_t APP_GetTargetPowerMode(void);
static uint32_t APP_CheckPowerMode(smc_power_state_t curPowerState, app_power_mode_t targetPowerMode);
static void APP_SetWakeupConfig(app_power_mode_t targetMode);

static void APP_PowerPreSwitchHook(smc_power_state_t originPowerState, app_power_mode_t targetMode);
static void APP_PowerModeSwitch(smc_power_state_t curPowerState, app_power_mode_t targetPowerMode);
static void APP_PowerPostSwitchHook(smc_power_state_t originPowerState, app_power_mode_t targetMode);

static void APP_InitDefaultDebugConsole(void);
static void APP_InitVlprDebugConsole(void);

static void APP_InitRtcClock(void);
static void osc32_clkout_to_pte0(void);

/*******************************************************************************
 * Variables
 ******************************************************************************/
volatile uint32_t app_always_keep_value;   /* keep the token value during in low power modes. */

smc_power_state_t curPowerState;
app_power_mode_t  targetPowerMode;
volatile uint32_t sw1_irq_counter;
volatile bool     sw3_irq_done;

/*******************************************************************************
 * Code
 ******************************************************************************/

/*!
 * @brief main demo function.
 */
int main(void)
{
    uint32_t freq = 0;
    
    BOARD_InitPins(); /* Must configure pins before PMC_ClearPeriphIOIsolationFlag */
    BOARD_BootClockRUN();
    APP_InitDefaultDebugConsole();

    osc32_clkout_to_pte0();

    /* setup slcd. */
    APP_InitRtcClock();
    slcd_init();
    slcd_start();
    slcd_set_number(0, SLCD_ON_SHOW_NUMBER_NONE, false);
    slcd_set_number(1, SLCD_ON_SHOW_NUMBER_NONE, false);
    slcd_set_number(2, SLCD_ON_SHOW_NUMBER_NONE, false);
    slcd_set_number(3, SLCD_ON_SHOW_NUMBER_NONE, false);

    /* Unlock all the power modes of chip. */
    SMC_SetPowerModeProtection(SMC, kSMC_AllowPowerModeAll);

    /* Clear the low power lock bit. */
    if (kRCM_SourceWakeup & RCM_GetPreviousResetSources(RCM)) /* Wakeup from VLLS. */
    {
        PMC_ClearPeriphIOIsolationFlag(PMC);
        NVIC_ClearPendingIRQ(LLWU_IRQn);
        PRINTF("\r\nMCU wakeup from VLLS modes...\r\n");

        if (APP_LOW_POWER_MEM_TOKEN == app_always_keep_value)
        {
            slcd_set_number(2, 1, false);
            PRINTF("token value is kept.\r\n");
        }
        else
        {
            slcd_set_number(2, 0, false);
            PRINTF("token value is missed.\r\n");
        }
    }
    else
    {
        PRINTF("\r\npower mode switch example.\r\n");
    }

    NVIC_EnableIRQ(LLWU_IRQn);
    NVIC_EnableIRQ(APP_WAKEUP_BUTTON_IRQ); /* PORTC_PORTD_IRQn. */
    NVIC_EnableIRQ(PORTA_IRQn);

    while (1)
    {
        curPowerState = SMC_GetPowerModeState(SMC);

        freq = CLOCK_GetFreq(kCLOCK_CoreSysClk);

        PRINTF("\r\n####################  Power Mode Switch Demo ####################\n\r\n");
        PRINTF("    Core Clock = %dHz \r\n", freq);

        //APP_ShowPowerMode(curPowerState);
        if (curPowerState == kSMC_PowerStateRun)
        {
            PRINTF("    Power mode: RUN\r\n");
            sw1_irq_counter = kAPP_PowerModeRun;
        }
        else if ( curPowerState ==  kSMC_PowerStateVlpr)
        {
            PRINTF("    Power mode: VLPR\r\n");
            sw1_irq_counter = kAPP_PowerModeVlpr;
        }

        //slcd_stop();
        slcd_set_number(0, (uint8_t)sw1_irq_counter, false);
        //slcd_start();

        PRINTF("\r\nSelect the desired operation \n\r\n");
        PRINTF("Press  %d for enter: RUN      - Normal RUN mode\r\n", kAPP_PowerModeRun);
        PRINTF("Press  %d for enter: WAIT     - Wait mode\r\n", kAPP_PowerModeWait);
        PRINTF("Press  %d for enter: STOP     - Stop mode\r\n", kAPP_PowerModeStop);
        PRINTF("Press  %d for enter: VLPR     - Very Low Power Run mode\r\n", kAPP_PowerModeVlpr);
        PRINTF("Press  %d for enter: VLPW     - Very Low Power Wait mode\r\n", kAPP_PowerModeVlpw);
        PRINTF("Press  %d for enter: VLPS     - Very Low Power Stop mode\r\n", kAPP_PowerModeVlps);
        PRINTF("Press  %d for enter: LLS/LLS3 - Low Leakage Stop mode\r\n", kAPP_PowerModeLls);
        PRINTF("Press  %d for enter: VLLS0    - Very Low Leakage Stop 0 mode\r\n", kAPP_PowerModeVlls0);
        PRINTF("Press  %d for enter: VLLS1    - Very Low Leakage Stop 1 mode\r\n", kAPP_PowerModeVlls1);
        PRINTF("Press  %d for enter: VLLS3    - Very Low Leakage Stop 3 mode\r\n", kAPP_PowerModeVlls3);
        PRINTF("\r\nWaiting for power mode select..\r\n\r\n");

        /* Wait for user response */
        targetPowerMode = APP_GetTargetPowerMode();

        /* only go next with avaiable and right input. */
        if (1u == APP_CheckPowerMode(curPowerState, targetPowerMode))
        {
            APP_PowerPreSwitchHook(curPowerState, targetPowerMode);

            /* setup wakeup source. */
            if (   (kAPP_PowerModeRun  != targetPowerMode)
                && (kAPP_PowerModeVlpr != targetPowerMode) )
            {
                APP_SetWakeupConfig(targetPowerMode);
            }

            APP_PowerModeSwitch(curPowerState, targetPowerMode);
            APP_PowerPostSwitchHook(curPowerState, targetPowerMode);

            //PRINTF("app_always_keep_value: 0x%x\r\n", app_always_keep_value);
            PRINTF("\r\nNext loop\r\n");
        }
    }
}

void LLWU_IRQHandler(void)
{
    /* If wakeup by external pin. */
    if (LLWU_GetExternalWakeupPinFlag(LLWU, LLWU_WAKEUP_PIN_IDX))
    {
        PORT_SetPinInterruptConfig(APP_WAKEUP_BUTTON_PORT, APP_WAKEUP_BUTTON_GPIO_PIN, kPORT_InterruptOrDMADisabled);
        PORT_ClearPinsInterruptFlags(APP_WAKEUP_BUTTON_PORT, (1U << APP_WAKEUP_BUTTON_GPIO_PIN));
        LLWU_ClearExternalWakeupPinFlag(LLWU, LLWU_WAKEUP_PIN_IDX);
    }
}

/* PTC3. */
void APP_WAKEUP_BUTTON_IRQ_HANDLER(void)
{
    if ((1U << APP_WAKEUP_BUTTON_GPIO_PIN) & PORT_GetPinsInterruptFlags(APP_WAKEUP_BUTTON_PORT))
    {
        /* Disable interrupt. */
        PORT_SetPinInterruptConfig(APP_WAKEUP_BUTTON_PORT, APP_WAKEUP_BUTTON_GPIO_PIN, kPORT_InterruptOrDMADisabled);
        PORT_ClearPinsInterruptFlags(APP_WAKEUP_BUTTON_PORT, (1U << APP_WAKEUP_BUTTON_GPIO_PIN));
        sw3_irq_done = true;
    }
}

/* PTA4. */
void PORTA_IRQHandler(void)
{
    uint32_t flags = PORT_GetPinsInterruptFlags(PORTA);
    PORT_ClearPinsInterruptFlags(PORTA, flags); /* clear flags. */

    /* PTA4. */
    if ( 0u != ((1u << 4u) & flags) )
    {
        sw1_irq_counter = (sw1_irq_counter+1u)% 10u;

        slcd_set_number(0u, sw1_irq_counter, false);
        PRINTF(".");
    }
}

static void APP_InitDefaultDebugConsole(void)
{
    uint32_t uartDefaultClkSrcFreq;

    CLOCK_SetLpuart0Clock(APP_DEBUG_UART_DEFAULT_CLKSRC);
    uartDefaultClkSrcFreq = CLOCK_GetFreq(APP_DEBUG_UART_DEFAULT_CLKSRC_NAME);
    DbgConsole_Init(BOARD_DEBUG_UART_INSTANCE, APP_DEBUG_UART_BAUDRATE, BOARD_DEBUG_UART_TYPE, uartDefaultClkSrcFreq);
}

static void APP_InitVlprDebugConsole(void)
{
    uint32_t uartVlprClkSrcFreq;
    CLOCK_SetLpuart0Clock(APP_DEBUG_UART_VLPR_CLKSRC);
    uartVlprClkSrcFreq = CLOCK_GetFreq(APP_DEBUG_UART_VLPR_CLKSRC_NAME);
    DbgConsole_Init(BOARD_DEBUG_UART_INSTANCE, APP_DEBUG_UART_BAUDRATE, BOARD_DEBUG_UART_TYPE, uartVlprClkSrcFreq);
}

/*
* 1. disable all the unnecessary peripherals.
* 2. setup all pins to analog function or output low(grounded inside the chip).
*/
static void APP_PowerPreSwitchHook(smc_power_state_t originPowerState, app_power_mode_t targetMode)
{
    /* setup a token in memory. */
    app_always_keep_value = APP_LOW_POWER_MEM_TOKEN;
    slcd_set_number(2, SLCD_ON_SHOW_NUMBER_NONE, false);
    PRINTF("Set a token in memory\r\n");

    /* setup a flag to show in low power mode. */
    slcd_set_number(3, SLCD_ON_SHOW_NUMBER_NONE, true);

    /* Wait for debug console output finished. */
    while (!(kLPUART_TransmissionCompleteFlag & LPUART_GetStatusFlags((LPUART_Type *)BOARD_DEBUG_UART_BASEADDR)))
    {
    }
    DbgConsole_Deinit();

    if ((kAPP_PowerModeRun != targetMode) && (kAPP_PowerModeVlpr != targetMode))
    {
        /*
         * Set pin for current leakage.
         * Debug console RX pin: Set to pinmux to disable.
         * Debug console TX pin: Don't need to change.
         */
        PORT_SetPinMux(DEBUG_CONSOLE_RX_PORT, DEBUG_CONSOLE_RX_PIN, kPORT_PinDisabledOrAnalog);
    }
}

static void APP_PowerPostSwitchHook(smc_power_state_t originPowerState, app_power_mode_t targetMode)
{
    smc_power_state_t powerState = SMC_GetPowerModeState(SMC);

    /* clear the low power flag. */
    slcd_set_number(3, SLCD_ON_SHOW_NUMBER_NONE, false);

    /* check the token. */
    if (APP_LOW_POWER_MEM_TOKEN == app_always_keep_value)
    {
        slcd_set_number(2, 1, false);
        PRINTF("token value is kept.\r\n");
    }
    else
    {
        slcd_set_number(2, 0, false);
        PRINTF("token value is missed.\r\n");
    }
    /*
     * For some other platforms, if enter LLS mode from VLPR mode, when wakeup, the
     * power mode is VLPR. But for some platforms, if enter LLS mode from VLPR mode,
     * when wakeup, the power mode is RUN. In this case, the clock setting is still
     * VLPR mode setting, so change to RUN mode setting here.
     */
    if ((kSMC_PowerStateVlpr == originPowerState) && (kSMC_PowerStateRun == powerState))
    {
        APP_SetClockRunFromVlpr();
    }

    if ((kAPP_PowerModeRun != targetMode) && (kAPP_PowerModeVlpr != targetMode))
    {
        /*
         * Debug console RX pin is set to disable for current leakage, nee to re-configure pinmux.
         * Debug console TX pin: Don't need to change.
         */
        PORT_SetPinMux(DEBUG_CONSOLE_RX_PORT, DEBUG_CONSOLE_RX_PIN, DEBUG_CONSOLE_RX_PINMUX);
    }

    /* Set debug console clock source. */
    if (kSMC_PowerStateVlpr == powerState)
    {
        APP_InitVlprDebugConsole();
    }
    else
    {
        APP_InitDefaultDebugConsole();
    }
}

/* setup the wakeup source. */
static void APP_SetWakeupConfig(app_power_mode_t targetMode)
{
    /* only use pin as wakeup source. */
    PORT_SetPinInterruptConfig(APP_WAKEUP_BUTTON_PORT, APP_WAKEUP_BUTTON_GPIO_PIN, APP_WAKEUP_BUTTON_IRQ_TYPE);

    /* If the targetMode is WAIT/STOP/VLPW/VLPS, just enable the NVIC interrupt by always-default.
     * If targetMode is VLLSn/LLS, setup LLWU additionally. */
    if (     (kAPP_PowerModeWait != targetMode)
          && (kAPP_PowerModeVlpw != targetMode)
          && (kAPP_PowerModeVlps != targetMode)
          && (kAPP_PowerModeStop != targetMode) )
    {
        LLWU_SetExternalWakeupPinMode(LLWU, LLWU_WAKEUP_PIN_IDX, LLWU_WAKEUP_PIN_TYPE);
        NVIC_EnableIRQ(LLWU_IRQn);
    }
}

/*
 * Check whether could switch to target power mode from current mode.
 * Return true if could switch, return false if could not switch.
 * Return the error code:
 * 0 - can not switch
 * 1 - can switch
 * 2 - no need to switch, already here.
 */
static uint32_t APP_CheckPowerMode(smc_power_state_t curPowerState, app_power_mode_t targetPowerMode)
{
    uint32_t err_code = 1u;

    if (targetPowerMode >= kAPP_PowerModeMax)
    {
        err_code = 0u; /* no available input. */
    }
    else if (  ( (kSMC_PowerStateRun  == curPowerState) && (kAPP_PowerModeVlpw == targetPowerMode) )
            || ( (kSMC_PowerStateVlpr == curPowerState) && (kAPP_PowerModeWait == targetPowerMode) )
            || ( (kSMC_PowerStateVlpr == curPowerState) && (kAPP_PowerModeStop == targetPowerMode) )
            )
    {
        err_code = 0u; /* can not jump. */
    }
    else if (  ( (kSMC_PowerStateRun  == curPowerState) && (kAPP_PowerModeRun  == targetPowerMode) )
            || ( (kSMC_PowerStateVlpr == curPowerState) && (kAPP_PowerModeVlpr == targetPowerMode) )
        )
    {
        err_code = 2u; /* no need to switch, already in the target mode. */
    }

    return err_code;
}

static void APP_PowerModeSwitch(smc_power_state_t curPowerState, app_power_mode_t targetPowerMode)
{
    smc_power_mode_vlls_config_t vlls_config;
    vlls_config.enablePorDetectInVlls0 = true;

    switch (targetPowerMode)
    {
        case kAPP_PowerModeVlpr:
            APP_SetClockVlpr();
            SMC_SetPowerModeVlpr(SMC);
            while (kSMC_PowerStateVlpr != SMC_GetPowerModeState(SMC))
            {
            }
            break;

        case kAPP_PowerModeRun:

            /* Power mode change. */
            SMC_SetPowerModeRun(SMC);
            while (kSMC_PowerStateRun != SMC_GetPowerModeState(SMC))
            {
            }

            /* If enter RUN from VLPR, change clock after the power mode change. */
            if (kSMC_PowerStateVlpr == curPowerState)
            {
                APP_SetClockRunFromVlpr();
            }
            break;

        case kAPP_PowerModeWait:
            SMC_PreEnterWaitModes();
            SMC_SetPowerModeWait(SMC);
            SMC_PostExitWaitModes();
            break;

        case kAPP_PowerModeStop:
            SMC_PreEnterStopModes();
            SMC_SetPowerModeStop(SMC, kSMC_PartialStop);
            SMC_PostExitStopModes();
            break;

        case kAPP_PowerModeVlpw:
            SMC_PreEnterWaitModes();
            SMC_SetPowerModeVlpw(SMC);
            SMC_PostExitWaitModes();
            break;

        case kAPP_PowerModeVlps:
            SMC_PreEnterStopModes();
            SMC_SetPowerModeVlps(SMC);
            SMC_PostExitStopModes();
            break;

        case kAPP_PowerModeLls:
            SMC_PreEnterStopModes();
            SMC_SetPowerModeLls(SMC);
            SMC_PostExitStopModes();
            break;

        case kAPP_PowerModeVlls0:
            vlls_config.subMode = kSMC_StopSub0;
            SMC_PreEnterStopModes();
            SMC_SetPowerModeVlls(SMC, &vlls_config);
            SMC_PostExitStopModes();
            break;

        case kAPP_PowerModeVlls1:
            vlls_config.subMode = kSMC_StopSub1;
            SMC_PreEnterStopModes();
            SMC_SetPowerModeVlls(SMC, &vlls_config);
            SMC_PostExitStopModes();
            break;

        case kAPP_PowerModeVlls3:
            vlls_config.subMode = kSMC_StopSub3;
            SMC_PreEnterStopModes();
            SMC_SetPowerModeVlls(SMC, &vlls_config);
            SMC_PostExitStopModes();
            break;

        default:
            break;
    }
}

static app_power_mode_t APP_GetTargetPowerMode(void)
{
    //sw1_irq_counter = 0u;
    sw3_irq_done = false;

    /* only use pin as wakeup source. */
    PORT_SetPinInterruptConfig(PORTA, 4u, kPORT_InterruptFallingEdge); /* enable the pin detection. */

    /* wait for a new sw3_irq. */
    PORT_SetPinInterruptConfig(PORTC, 3u, kPORT_InterruptFallingEdge);
    while (!sw3_irq_done)
    {}

    PORT_SetPinInterruptConfig(PORTA, 4u, kPORT_InterruptOrDMADisabled); /* disable the pin detection. */

    PRINTF("%d\r\n", sw1_irq_counter);

    return (app_power_mode_t)(sw1_irq_counter);
}

static void rtc_wait_osc_ready(uint32_t delay_ms)
{
    uint32_t ticks = 0UL;
    uint32_t count = delay_ms;

    /* Make a 1 milliseconds counter. */
    ticks = SystemCoreClock / 1000UL;
    assert((ticks - 1UL) <= SysTick_LOAD_RELOAD_Msk);

    /* Enable the SysTick for counting. */
    SysTick->LOAD = (uint32_t)(ticks - 1UL);
    SysTick->VAL  = 0UL;
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;

    for (; count > 0U; count--)
    {
        /* Wait for the SysTick counter reach 0. */
        while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk))
        {
        }
    }

    /* Disable SysTick. */
    SysTick->CTRL &= ~(SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk);
    SysTick->LOAD = 0UL;
    SysTick->VAL  = 0UL;
}

static void APP_InitRtcClock(void)
{
    rtc_config_t rtcConfig;
    RTC_GetDefaultConfig(&rtcConfig);
    RTC_Init(RTC, &rtcConfig); /* including the operation to enable clock. */
    RTC_SetClockSource(RTC);
    rtc_wait_osc_ready(1000u);
}

static void osc32_clkout_to_pte0(void)
{
    SIM->SCGC5 |= SIM_SCGC5_PORTE_MASK;

    PORTE->PCR[0] = PORT_PCR_MUX(1); /* clkout32k. */

    SIM->SOPT1 |= SIM_SOPT1_OSC32KOUT(1); /* use pte0. */
}


/* EOF. */

