/**************************************************************************//**
  * \file     RTC.c
  * \brief    Real time clock driver file
  * \details
  * \author   Zhang Xuan
  * \version  V1.0.0
  * \date     21-Nov-2018
  * \par      History:
  *           V1.0.0 Initial release
  * \par      Copyright:
  *           (c) Heilongjiang TYW Electronics co., LTD
******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "RTC.h"

/* Private typedef -----------------------------------------------------------*/

typedef struct
{
    uint16_t  Year;
    uint8_t   Month;
    uint8_t   Date;
} RTC_Date_st_t;
volatile uint16_t RTCRollingCounter;
/* Private define ------------------------------------------------------------*/
/*=============================================================================
Set the start year of RTC, MUST be a leap year.

Since 2100 is not a leap year, but 2100 can be divisible by 4, setting the
starting year to 1920, making the RTC year counted in the range of 1920 - 2099.
In this range, all years that can be divisible by 4 are leap years, simplifying
leap year calculation.

Limitations of the current leap year algorithm must be considered when
modifying RTC_YEAR_BASE.
=============================================================================*/
#define   RTC_YEAR_BASE                     1920U
#define   RTC_YEAR_TOP                      (RTC_YEAR_BASE + 179U)

/* Private macro -------------------------------------------------------------*/
#if ((RTC_DEFAULT_YEAR < RTC_YEAR_BASE) || (RTC_DEFAULT_YEAR >=RTC_YEAR_TOP))
#error RTC default year out of range.
#endif

#if (RTC_DEFAULT_MONTH < 1U) || (RTC_DEFAULT_MONTH > 12U)
#error Bad RTC default month setting.
#endif

#if (RTC_DEFAULT_DATE < 1U) || (RTC_DEFAULT_DATE > 31U)
#error Bad RTC default date setting.
#endif

#if (RTC_DEFAULT_HOUR > 23U)
#error Bad RTC default hour setting.
#endif

#if (RTC_DEFAULT_MINUTE > 59U)
#error Bad RTC default minute setting.
#endif

#if (RTC_DEFAULT_SECOND > 59U)
#error Bad RTC default second setting.
#endif

/* Private variables ---------------------------------------------------------*/
static const uint8_t  RTCMonthCalcTable[2][12] =
{
    {31, 28, 31, 30, 31, 30, 31, 31, 30 , 31, 30, 31},
    {31, 29, 31, 30, 31, 30, 31, 31, 30 , 31, 30, 31},
};

volatile RTC_Time_st_t  RTCTime;
uint16_t                RTCDaysBackup;

/* Private function prototypes -----------------------------------------------*/
static void     RTC_Days_To_Date_Conv ( uint16_t Days, RTC_Date_st_t *pDate );
static uint16_t RTC_Date_To_Days_Conv ( RTC_Date_st_t *pDate );
static uint8_t  RTC_Determine_Leap_Year ( uint16_t Year );

/* Private functions ---------------------------------------------------------*/

/**************************************************************************//**
  * \brief      Real time clock pre-initialization
  * \details    Configure and start real time clock. Write default time into
  *             real time clock. Call this function only once when power is
  *             first turned on.
  * \retval     None
******************************************************************************/
void RTC_Pre_Init(void)
{
    uint16_t  Days;

    un_rtc_wrt_t  RTCTimeReg;
    RTC_Date_st_t RTCDefaultDate;

    /* Disable RTC Interrupts */
    RTC_WINE = 0U;

    /* Reset internal counters before starting the RTC */
    RTC_WTCR_ST = 1U;
    while (RTC_WTSR_RUN == 0U){}
    RTC_WTCR_ST = 0U;

    /* Write default time settings */
    RTCDefaultDate.Year  = RTC_DEFAULT_YEAR;
    RTCDefaultDate.Month = RTC_DEFAULT_MONTH;
    RTCDefaultDate.Date  = RTC_DEFAULT_DATE;

    RTCTimeReg.stcField.u6WTSR = RTC_DEFAULT_SECOND;
    RTCTimeReg.stcField.u6WTMR = RTC_DEFAULT_MINUTE;
    RTCTimeReg.stcField.u5WTHR = RTC_DEFAULT_HOUR;
    Days = RTC_Date_To_Days_Conv (&RTCDefaultDate);
    
    while (RTC_WTSR_RUN){}

    RTC_RTR1_WTDR = Days;
    RTC_WRT       = RTCTimeReg.u32Register;

    /* Select source clock */
    RTC_WTCR_RCKSEL = 0U;      /* Main clock (CLK_MAIN/2) is selected as CLKRTC */

    /* Sub-Second Register */
    RTC_WTBR = (CLK_MAIN_OSC_FREQ >> 2U) - 1U+(143u);/*校正92+46+23*/

    /* Clear interrupts */
    RTC_WINC = 0x0000007FUL;

    /* Debug mode */
#ifdef __DEBUG
    RTC_DEBUG_DBGEN = 1U;
#else
    RTC_DEBUG_DBGEN = 0U;
#endif

    /* Restart RTC */
    RTC_WTCR_ST = 1U;
}

/**************************************************************************//**
  * \brief      Initialization real time clock
  * \details    Load time into registers. Call this function once when MCU
  *             starts running.
  * \retval     None
******************************************************************************/
void RTC_Init(void)
{
    uint16_t  Days;
    uint16_t  DaysComp;

    un_rtc_wrt_t  RTCTimeReg;
    RTC_Date_st_t RTCCurrentDate;

    do
    {
        Days = RTC_RTR1_WTDR;
        RTCTimeReg.u32Register = RTC_WRT;
        DaysComp = RTC_RTR1_WTDR;
    }
    while (Days != DaysComp);

    RTCDaysBackup = Days;
    RTC_Days_To_Date_Conv(Days, &RTCCurrentDate);

    RTCTime.Year     = RTCCurrentDate.Year;
    RTCTime.Month    = RTCCurrentDate.Month;
    RTCTime.Date     = RTCCurrentDate.Date;
    RTCTime.Hour     = (uint8_t)RTCTimeReg.stcField.u5WTHR;
    RTCTime.Minute   = (uint8_t)RTCTimeReg.stcField.u6WTMR;
    RTCTime.Second   = (uint8_t)RTCTimeReg.stcField.u6WTSR;
    RTCTime.LeapYear = RTC_Determine_Leap_Year ( RTCTime.Year );
}

/**************************************************************************//**
  * \brief      Set new time to real time clock
  * \param      pTime: Pointer to a time setting structure
  * \retval     None
******************************************************************************/
void RTC_Set_Time(RTC_Time_Setting_st_t *pTime)
{
    uint8_t   IsLeapYear;
    uint16_t  Days;

    un_rtc_wrt_t  RTCTimeReg;
    RTC_Date_st_t RTCCurrentDate;

    if (pTime != NULL)
    {
        if ((pTime -> Year >= RTC_YEAR_BASE) && (pTime -> Year < RTC_YEAR_TOP) && \
            (pTime -> Month >  0U)           && (pTime -> Month < 13U)         && \
            (pTime -> Hour  < 24U) && (pTime -> Minute < 60U) && (pTime -> Second < 60U))
        {
            IsLeapYear = RTC_Determine_Leap_Year (pTime -> Year);
            if ((pTime -> Date > 0U) && (pTime -> Date <= RTCMonthCalcTable[IsLeapYear][pTime -> Month - 1U]))
            {
                RTC_WTCR_ST = 0U;

                RTCCurrentDate.Year  = pTime -> Year;
                RTCCurrentDate.Month = pTime -> Month;
                RTCCurrentDate.Date  = pTime -> Date;

                RTCTimeReg.stcField.u6WTSR = pTime -> Second;
                RTCTimeReg.stcField.u6WTMR = pTime -> Minute;
                RTCTimeReg.stcField.u5WTHR = pTime -> Hour;

                Days = RTC_Date_To_Days_Conv ( &RTCCurrentDate );

                RTCTime.Year     = pTime -> Year;
                RTCTime.Month    = pTime -> Month;
                RTCTime.Date     = pTime -> Date;
                RTCTime.Hour     = pTime -> Hour;
                RTCTime.Minute   = pTime -> Minute;
                RTCTime.Second   = pTime -> Second;
                RTCTime.LeapYear = IsLeapYear;
                RTCDaysBackup    = Days;

                while (RTC_WTSR_RUN){}
                RTC_RTR1_WTDR = Days;
                RTC_WRT       = RTCTimeReg.u32Register;

                RTC_WTCR_ST = 1U;
            }
        }
    }
}

/**************************************************************************//**
  * \brief      Rreal time clock timing control
  * \attention  Call this function every 100 ms.
  * \retval     None
******************************************************************************/
void RTC_Timing_Service(void)
{
    uint16_t  Days;
    uint16_t  DaysComp;
    un_rtc_wrt_t  RTCTimeReg;
    RTC_Date_st_t RTCCurrentDate;
    do
    {
        Days = RTC_RTR1_WTDR;
        RTCTimeReg.u32Register = RTC_WRT;
        DaysComp = RTC_RTR1_WTDR;
    }
    while (Days != DaysComp);

    if(Days < 65380U)
    {
        RTCTime.Hour       = (uint8_t)RTCTimeReg.stcField.u5WTHR;
        RTCTime.Minute     = (uint8_t)RTCTimeReg.stcField.u6WTMR;
        RTCTime.Second     = (uint8_t)RTCTimeReg.stcField.u6WTSR;

        if (Days != RTCDaysBackup)
        {
            RTC_Days_To_Date_Conv(Days, &RTCCurrentDate);

            RTCTime.Year     = RTCCurrentDate.Year;
            RTCTime.Month    = RTCCurrentDate.Month;
            RTCTime.Date     = RTCCurrentDate.Date;
            RTCTime.LeapYear = RTC_Determine_Leap_Year(RTCTime.Year);
            RTCDaysBackup    = Days;
        }
    }
    else
    {
        RTC_WTCR_ST   = 0U;
        while (RTC_WTSR_RUN){}
        RTC_RTR1_WTDR = 0x0000U;
        RTC_WRT       = 0x00000000UL;
        RTC_WTCR_ST   = 1U;

        RTCTime.Hour     = 0U;
        RTCTime.Minute   = 0U;
        RTCTime.Second   = 0U;
        RTCTime.Year     = RTC_YEAR_BASE;
        RTCTime.Month    = 1U;
        RTCTime.Date     = 1U;
        RTCTime.LeapYear = 1U;
        RTCDaysBackup    = 0U;
    }
}

/**************************************************************************//**
  * \brief      Convert days to date
  * \param      Days : Total days since Jan 1, RTC_YEAR_BASE
  * \param      pDate: Pointer to a date structure
  * \retval     None
******************************************************************************/
static void RTC_Days_To_Date_Conv(uint16_t Days, RTC_Date_st_t *pDate)
{
    uint8_t   i;
    uint8_t   IsLeapYear;

    pDate -> Year  = RTC_YEAR_BASE;
    pDate -> Year += ((Days / 1461U) << 2U);   /* (Days / (366 + 365 * 3)) * 4 */
    Days %= 1461U;

    if (Days > 365U)
    {
        IsLeapYear = 0U;
        pDate -> Year += (Days - 1U) / 365U;
        Days           = (Days - 1U) % 365U;
    }
    else
    {
        IsLeapYear = 1U;
    }

    i = 1U;
    while (Days >= RTCMonthCalcTable[IsLeapYear][i - 1U])
    {
        Days -= RTCMonthCalcTable[IsLeapYear][i - 1U];
        i++;
    }

    pDate -> Month = i;
    pDate -> Date  = (uint8_t)Days + 1U;
}

/**************************************************************************//**
  * \brief      Convert date to days
  * \param      pDate: Pointer to a date structure
  * \retval     Total days since Jan 1, RTC_YEAR_BASE
******************************************************************************/
static uint16_t RTC_Date_To_Days_Conv ( RTC_Date_st_t *pDate )
{
    uint8_t   i;
    uint8_t   IsLeapYear;
    uint16_t  Years;
    uint16_t  TotalDays;

    TotalDays = 0U;
    Years  = pDate -> Year;
    Years -= RTC_YEAR_BASE;

    TotalDays += Years * 365U;
    TotalDays += (Years + 3U) >> 2U; /* One day per leap year */

    IsLeapYear = RTC_Determine_Leap_Year(pDate -> Year);

    for (i = 1U; i < pDate -> Month; i++)
    {
        TotalDays += RTCMonthCalcTable[IsLeapYear][i - 1U];
    }

    TotalDays += (uint16_t)pDate -> Date - 1U;

    return TotalDays;
}

/**************************************************************************//**
  * \brief      Determine if a year is leap year
  * \param      Year: the year to be determined
  * \retval     \arg 0: Not leap year
  *             \arg 1: Leap year
******************************************************************************/
static uint8_t RTC_Determine_Leap_Year(uint16_t Year)
{
    uint8_t IsLeapYear;
    
    if (Year & 0x0003U)
    {
        IsLeapYear = 0U;
    }
    else
    {
        IsLeapYear = 1U;
    }
    
    return IsLeapYear;
}
