/******************************************************************************//**
 * @file     main.c
 * @version  V0.10
 * @brief    Simulate a HID mouse supporting USB suspend and remote wakeup.
 *
 * SPDX-License-Identifier: Apache-2.0
 * @copyright (C) 2020 Nuvoton Technology Corp. All rights reserved.
 ******************************************************************************/
#include <stdio.h>
#include "NuMicro.h"
#include "hid_mouse.h"

#define CRYSTAL_LESS    1 /* CRYSTAL_LESS must be 1 if USB clock source is HIRC */
#define TRIM_INIT           (SYS_BASE+0x118)

uint8_t volatile g_u8RemoteWakeup_gpio = 0;
uint8_t volatile g_u8Suspend = 0;

/*--------------------------------------------------------------------------*/
void SYS_Init(void)
{

    /*---------------------------------------------------------------------------------------------------------*/
    /* Init System Clock                                                                                       */
    /*---------------------------------------------------------------------------------------------------------*/

    /* Enable Internal HIRC 48 MHz clock */
    CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk);

    /* Waiting for Internal RC clock ready */
    CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk);

    /* Switch HCLK clock source to Internal HIRC and HCLK source divide 1 */
    CLK_SetHCLK(CLK_CLKSEL0_HCLKSEL_HIRC, CLK_CLKDIV0_HCLK(1));

    /* Enable module clock */
    CLK_EnableModuleClock(USBD_MODULE);
    CLK_EnableModuleClock(GPB_MODULE);

    /* Debug UART clock setting */
    UartDebugCLK();

    /*---------------------------------------------------------------------------------------------------------*/
    /* Init I/O Multi-function                                                                                 */
    /*---------------------------------------------------------------------------------------------------------*/
    UartDebugMFP();

}

void GPIO_Init(void)
{
    /* Enable PB0~5 interrupt for wakeup */
    GPIO_SetMode(PB, 0x3f, GPIO_MODE_QUASI); /* PC0~5 be Quasi mode */
    GPIO_CLR_INT_FLAG(PB, 0x3f);
    GPIO_EnableInt(PB, 0, GPIO_INT_BOTH_EDGE);
    GPIO_EnableInt(PB, 1, GPIO_INT_BOTH_EDGE);
    GPIO_EnableInt(PB, 2, GPIO_INT_BOTH_EDGE);
    GPIO_EnableInt(PB, 3, GPIO_INT_BOTH_EDGE);
    GPIO_EnableInt(PB, 4, GPIO_INT_BOTH_EDGE);
    GPIO_EnableInt(PB, 5, GPIO_INT_BOTH_EDGE);
    GPIO_ENABLE_DEBOUNCE(PB, 0x3f);      /* Enable key debounce */
    GPIO_SET_DEBOUNCE_TIME(GPIO_DBCTL_DBCLKSRC_LIRC, GPIO_DBCTL_DBCLKSEL_256); /* Debounce sampling cycle = 64 clocks */
    NVIC_EnableIRQ(GPB_IRQn);


}


void GPB_IRQHandler(void)
{
    GPIO_CLR_INT_FLAG(PB, 0x3f);
    g_u8RemoteWakeup_gpio = 1;
}

void PowerDown()
{
    printf("Enter power down ...\n");

    while (!IsDebugFifoEmpty());

    /* Wakeup Enable */
    USBD->INTEN |= USBD_INTEN_WKEN_Msk;

    CLK_PowerDown();

    /* Clear PWR_DOWN_EN if it is not clear by itself */
    if (CLK->PWRCTL & CLK_PWRCTL_PDEN_Msk)
        CLK->PWRCTL ^= CLK_PWRCTL_PDEN_Msk;

    /* Note HOST to resume USB tree if it is suspended and remote wakeup enabled */
    if (g_USBD_u8RemoteWakeupEn && g_u8RemoteWakeup_gpio)
    {
        /* Enable PHY before sending Resume('K') state */
        USBD->ATTR |= USBD_ATTR_PHYEN_Msk;

        /* Keep remote wakeup for 1 ms */
        USBD->ATTR |= USBD_ATTR_RWAKEUP_Msk;
        CLK_SysTickDelay(1000); /* Delay 1ms */
        USBD->ATTR ^= USBD_ATTR_RWAKEUP_Msk;
        g_u8RemoteWakeup_gpio = 0;
        printf("Remote Wakeup!!\n");
    }

    printf("device wakeup!\n");

}

/*---------------------------------------------------------------------------------------------------------*/
/*  Main Function                                                                                          */
/*---------------------------------------------------------------------------------------------------------*/
int32_t main(void)
{
#if CRYSTAL_LESS
    uint32_t u32TrimInit;
#endif
    /* Unlock protected registers */
    SYS_UnlockReg();

    SYS_Init();

    /* Configure UART for message printing */
    UartDebugInit();

    GPIO_Init();

    printf("\n");
    printf("+-----------------------------------------------------+\n");
    printf("|          NuMicro USB HID Mouse Sample Code          |\n");
    printf("+-----------------------------------------------------+\n");

    /* This sample code is used to simulate a mouse with suspend and remote wakeup supported.
       User can use PB0 ~ PB5 key to control the movement of mouse.
       PB4 is used as a LED to show working status.
    */

    USBD_Open(&gsInfo, HID_ClassRequest, NULL);


    /* Endpoint configuration */
    HID_Init();
    USBD_Start();

    NVIC_EnableIRQ(USBD_IRQn);

#if CRYSTAL_LESS
    /* Backup default trim value */
    u32TrimInit = M32(TRIM_INIT);

    /* Clear SOF */
    USBD_CLR_INT_FLAG(USBD_INTSTS_SOFIF_Msk);
#endif

    //    GPIO_SetMode(PB, BIT4, GPIO_MODE_OUTPUT);
    //    PB4 = 0; // LED to show system is on line

    while (1)
    {
#if CRYSTAL_LESS

        /* Start USB trim function if it is not enabled. */
        if ((SYS->HIRCTRIMCTL & SYS_HIRCTRIMCTL_FREQSEL_Msk) != 0x1)
        {
            /* Start USB trim only when USB signal arrived */
            if (USBD->INTSTS & USBD_INTSTS_SOFIF_Msk)
            {
                /* Clear SOF */
                USBD_CLR_INT_FLAG(USBD_INTSTS_SOFIF_Msk);

                /*
                    USB clock trim function:
                    HIRC Trimming with boundary function enhances robustility
                    and keeps HIRC in right frequency while receiving unstable USB signal
                */
                SYS->HIRCTRIMCTL = (0x1 << SYS_HIRCTRIMCTL_REFCKSEL_Pos)
                                   | (0x1 << SYS_HIRCTRIMCTL_FREQSEL_Pos)
                                   | (0x0 << SYS_HIRCTRIMCTL_LOOPSEL_Pos)
                                   | (0x1 << SYS_HIRCTRIMCTL_BOUNDEN_Pos)
                                   | (10  << SYS_HIRCTRIMCTL_BOUNDARY_Pos);
            }
        }

        /* Disable USB Trim when any error found */
        if (SYS->HIRCTRIMSTS & (SYS_HIRCTRIMSTS_CLKERIF_Msk | SYS_HIRCTRIMSTS_TFAILIF_Msk))
        {
            /* Init TRIM */
            M32(TRIM_INIT) = u32TrimInit;

            /* Disable USB clock trim function */
            SYS->HIRCTRIMCTL = 0;

            /* Clear trim error flags */
            SYS->HIRCTRIMSTS = SYS_HIRCTRIMSTS_CLKERIF_Msk | SYS_HIRCTRIMSTS_TFAILIF_Msk;

            /* Clear SOF */
            USBD_CLR_INT_FLAG(USBD_INTSTS_SOFIF_Msk);
        }

#endif

        /* Enter power down when USB suspend */
        if (g_u8Suspend)
        {
            PowerDown();

            /* Waiting for key release */
            while ((PB->PIN & 0x3F) != 0x3F);
        }

        HID_UpdateMouseData();
    }
}
