/* mbed Microcontroller Library
 * Copyright (c) 2017-2018 Nuvoton
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "rtc_api.h"

#if DEVICE_RTC

#include "mbed_wait_api.h"
#include "mbed_error.h"
#include "nu_modutil.h"
#include "nu_miscutil.h"
#include "mbed_mktime.h"
#include "partition_M2351.h"
#include "hal_secure.h"

/* NOTE: BSP RTC driver judges secure/non-secure RTC by PC. This implementation cannot support non-secure RTC
 *       controlled by secure executable. A better way would be that secure/non-secure RTC base is passed
 *       to RTC API as an argument like most other APIs. With BSP RTC driver unchanged, we must enforce 
 *       secure RTC. */
#if defined(SCU_INIT_PNSSET2_VAL) && (SCU_INIT_PNSSET2_VAL & (1 << 1))
#error("Limited by BSP/RTC, we can only support secure RTC.")
#endif

void rtc_init(void)
{
    rtc_init_s();
}

void rtc_free(void)
{
    rtc_free_s();
}

int rtc_isenabled(void)
{
    return rtc_isenabled_s();
}

time_t rtc_read(void)
{
    return rtc_read_s();
}

void rtc_write(time_t t)
{
    rtc_write_s(t);
}

#if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)

/* Micro seconds per second */
#define NU_US_PER_SEC               1000000
/* Timer clock per second
 *
 * NOTE: This dependents on real hardware.
 */
#define NU_RTCCLK_PER_SEC           ((CLK->CLKSEL3 & CLK_CLKSEL3_SC0SEL_Msk) ? __LIRC : __LXT)

/* Strategy for implementation of RTC HAL
 *
 * H/W RTC just supports year range 2000~2099, which cannot fully cover POSIX time (starting since 2970)
 * and date time of struct TM (starting since 1900).
 *
 * To conquer the difficulty, we don't use H/W RTC to keep real date time. Instead, we use it to keep
 * elapsed time in seconds since one reference time point. The strategy would be:
 *
 * 1. Choose DATETIME_HWRTC_ORIGIN (00:00:00 UTC, Saturday, 1 January 2000) as reference time point of H/W RTC.
 * 2. t_hwrtc_origin = DATETIME_HWRTC_ORIGIN in POSIX time
 * 3. t_hwrtc_elapsed = t_hwrtc_origin + elapsed time since t_hwrtc_origin
 * 4. t_write = POSIX time set by rtc_write().
 * 5. t_present = rtc_read() = t_write + (t_hwrtc_elapsed - t_hwrtc_origin)
 *
 * 1900
 * |---------------------------------------------------------------------------------|
 *           1970    t_write           t_present
 * |---------|-------|-----------------|---------------------------------------------|
 * 
 * 2000 
 * |-----------------|---------------------------------------------------------------|
 * t_hwrtc_origin    t_hwrtc_elapsed
 *
 */
/* Start year of struct TM*/
#define NU_TM_YEAR0         1900
/* Start year of POSIX time (set_time()/time()) */
#define NU_POSIX_YEAR0      1970
/* Start year of H/W RTC */
#define NU_HWRTC_YEAR0      2000

/* RTC H/W origin time: 00:00:00 UTC, Saturday, 1 January 2000 */
static const S_RTC_TIME_DATA_T DATETIME_HWRTC_ORIGIN = {
    2000,           /* Year value, range between 2000 ~ 2099 */
    1,              /* Month value, range between 1 ~ 12 */
    1,              /* Day value, range between 1 ~ 31 */
    RTC_SATURDAY,   /* Day of the week */
    0,              /* Hour value, range between 0 ~ 23 */
    0,              /* Minute value, range between 0 ~ 59 */
    0,              /* Second value, range between 0 ~ 59 */
    RTC_CLOCK_24,   /* 12-Hour (RTC_CLOCK_12) / 24-Hour (RTC_CLOCK_24) */
    0               /* RTC_AM / RTC_PM (used only for 12-Hour) */
};
/* t_hwrtc_origin initialized or not? */
static bool t_hwrtc_origin_inited = 0;
/* POSIX time of DATETIME_HWRTC_ORIGIN (since 00:00:00 UTC, Thursday, 1 January 1970) */
static time_t t_hwrtc_origin = 0;
/* POSIX time set by rtc_write() */
static time_t t_write = 0;
/* Convert date time from H/W RTC to struct TM */
static void rtc_convert_datetime_hwrtc_to_tm(struct tm *datetime_tm, const S_RTC_TIME_DATA_T *datetime_hwrtc);

static const struct nu_modinit_s rtc_modinit = {RTC_0, RTC_MODULE, 0, 0, 0, RTC_IRQn, NULL};

__NONSECURE_ENTRY
void rtc_init_s(void)
{
    if (rtc_isenabled()) {
        return;
    }

    RTC_Open(NULL);

    /* POSIX time origin (00:00:00 UTC, Thursday, 1 January 1970) */
    rtc_write(0);
}

__NONSECURE_ENTRY
void rtc_free_s(void)
{
    CLK_DisableModuleClock_S(rtc_modinit.clkidx);
}

__NONSECURE_ENTRY
int32_t rtc_isenabled_s(void)
{
    // NOTE: To access (RTC) registers, clock must be enabled first.
    if (! (CLK->APBCLK0 & CLK_APBCLK0_RTCCKEN_Msk)) {
        // Enable IP clock
        CLK_EnableModuleClock_S(rtc_modinit.clkidx);
    }

    RTC_T *rtc_base = (RTC_T *) NU_MODBASE(rtc_modinit.modname);
    
    // NOTE: Check RTC Init Active flag to support crossing reset cycle.
    return !! (rtc_base->INIT & RTC_INIT_ACTIVE_Msk);
}

__NONSECURE_ENTRY
int64_t rtc_read_s(void)
{
    /* NOTE: After boot, RTC time registers are not synced immediately, about 1 sec latency.
     *       RTC time got (through RTC_GetDateAndTime()) in this sec would be last-synced and incorrect.
     *       NUC472/M453: Known issue
     *       M487: Fixed
     */
    if (! rtc_isenabled()) {
        rtc_init();
    }

    /* Used for intermediary between date time of H/W RTC and POSIX time */
    struct tm datetime_tm;

    if (! t_hwrtc_origin_inited) {
        t_hwrtc_origin_inited = 1;
        
        /* Convert date time from H/W RTC to struct TM */     
        rtc_convert_datetime_hwrtc_to_tm(&datetime_tm, &DATETIME_HWRTC_ORIGIN);
        /* Convert date time of struct TM to POSIX time */
        if (! _rtc_maketime(&datetime_tm, &t_hwrtc_origin, RTC_FULL_LEAP_YEAR_SUPPORT)) {
            return 0;
        }

        /* Load t_write from RTC spare register to cross reset cycle */
        RTC_T *rtc_base = (RTC_T *) NU_MODBASE(rtc_modinit.modname);
        RTC_WaitAccessEnable();
        RTC_EnableSpareAccess();
        RTC_WaitAccessEnable();
        t_write = RTC_READ_SPARE_REGISTER(rtc_base, 0);
    }

    S_RTC_TIME_DATA_T hwrtc_datetime_2K_present;
    RTC_WaitAccessEnable();
    RTC_GetDateAndTime(&hwrtc_datetime_2K_present);
    /* Convert date time from H/W RTC to struct TM */
    rtc_convert_datetime_hwrtc_to_tm(&datetime_tm, &hwrtc_datetime_2K_present);
    /* Convert date time of struct TM to POSIX time */
    time_t t_hwrtc_elapsed;
    if (! _rtc_maketime(&datetime_tm, &t_hwrtc_elapsed, RTC_FULL_LEAP_YEAR_SUPPORT)) {
        return 0;
    }

    /* Present time in POSIX time */
    time_t t_present = t_write + (t_hwrtc_elapsed - t_hwrtc_origin);
    return t_present;
}

__NONSECURE_ENTRY
void rtc_write_s(int64_t t)
{
    if (! rtc_isenabled()) {
        rtc_init();
    }

    t_write = t;

    /* Store t_write to RTC spare register to cross reset cycle */
    RTC_T *rtc_base = (RTC_T *) NU_MODBASE(rtc_modinit.modname);
    RTC_WaitAccessEnable();
    RTC_EnableSpareAccess();
    RTC_WaitAccessEnable();
    RTC_WRITE_SPARE_REGISTER(rtc_base, 0, t_write);

    RTC_WaitAccessEnable();
    RTC_SetDateAndTime((S_RTC_TIME_DATA_T *) &DATETIME_HWRTC_ORIGIN);
    /* NOTE: When engine is clocked by low power clock source (LXT/LIRC), we need to wait for 3 engine clocks. */
    wait_us((NU_US_PER_SEC / NU_RTCCLK_PER_SEC) * 3);
}

/*
 struct tm
   tm_sec      seconds after the minute 0-61
   tm_min      minutes after the hour 0-59
   tm_hour     hours since midnight 0-23
   tm_mday     day of the month 1-31
   tm_mon      months since January 0-11
   tm_year     years since 1900
   tm_wday     days since Sunday 0-6
   tm_yday     days since January 1 0-365
   tm_isdst    Daylight Saving Time flag
*/
static void rtc_convert_datetime_hwrtc_to_tm(struct tm *datetime_tm, const S_RTC_TIME_DATA_T *datetime_hwrtc)
{
    datetime_tm->tm_year = datetime_hwrtc->u32Year - NU_TM_YEAR0;
    datetime_tm->tm_mon  = datetime_hwrtc->u32Month - 1;
    datetime_tm->tm_mday = datetime_hwrtc->u32Day;
    datetime_tm->tm_wday = datetime_hwrtc->u32DayOfWeek;
    datetime_tm->tm_hour = datetime_hwrtc->u32Hour;
    if (datetime_hwrtc->u32TimeScale == RTC_CLOCK_12 && datetime_hwrtc->u32AmPm == RTC_PM) {
        datetime_tm->tm_hour += 12;
    }
    datetime_tm->tm_min  = datetime_hwrtc->u32Minute;
    datetime_tm->tm_sec  = datetime_hwrtc->u32Second;
}

#endif
#endif
