mbed-os/targets/TARGET_NUVOTON/TARGET_M2351/rtc_api.c

386 lines
11 KiB
C

/*
* Copyright (c) 2017-2018, Nuvoton Technology Corporation
*
* SPDX-License-Identifier: Apache-2.0
*
* 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"
#if defined(DOMAIN_NS) && (DOMAIN_NS == 1L) && (TFM_LVL > 0)
#include "tfm_ns_lock.h"
#endif
/* Secure attribution of RTC
*
* We need RTC to be secure for security concern.
*
* On M2351, configured to secure
* On M2354, hard-wired to secure
*/
#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 (__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, CLK_CLKSEL3_RTCSEL_LXT, 0, 0, RTC_IRQn, NULL};
static void rtc_init_impl(void);
static void rtc_free_impl(void);
static int32_t rtc_isenabled_impl(void);
static int64_t rtc_read_impl(void);
static void rtc_write_impl(int64_t t);
static void rtc_init_impl(void)
{
if (rtc_isenabled_impl()) {
return;
}
RTC_Open(NULL);
/* POSIX time origin (00:00:00 UTC, Thursday, 1 January 1970) */
rtc_write_impl(0);
}
static void rtc_free_impl(void)
{
CLK_DisableModuleClock_S(rtc_modinit.clkidx);
}
static int32_t rtc_isenabled_impl(void)
{
// To access (RTC) registers, clock must be enabled first.
// For TZ, with RTC being secure, we needn't call the secure gateway versions.
CLK_EnableModuleClock(rtc_modinit.clkidx);
CLK_SetModuleClock(rtc_modinit.clkidx, rtc_modinit.clksrc, rtc_modinit.clkdiv);
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);
}
static int64_t rtc_read_impl(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_impl()) {
rtc_init_impl();
}
/* 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;
}
static void rtc_write_impl(int64_t t)
{
if (! rtc_isenabled_impl()) {
rtc_init_impl();
}
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;
}
#if (TFM_LVL > 0)
__NONSECURE_ENTRY
int32_t rtc_init_veneer(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3)
{
rtc_init_impl();
return 0;
}
__NONSECURE_ENTRY
int32_t rtc_free_veneer(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3)
{
rtc_free_impl();
return 0;
}
__NONSECURE_ENTRY
int32_t rtc_isenabled_veneer(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3)
{
int32_t *enabled = (int32_t *) arg0;
*enabled = rtc_isenabled_impl();
return 0;
}
__NONSECURE_ENTRY
int32_t rtc_read_veneer(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3)
{
int64_t *t = (int64_t *) arg0;
*t = rtc_read_impl();
return 0;
}
__NONSECURE_ENTRY
int32_t rtc_write_veneer(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3)
{
int64_t *t = (int64_t *) arg0;
rtc_write_impl(*t);
return 0;
}
#endif
#endif
#if defined(DOMAIN_NS) && (DOMAIN_NS == 1) && (TFM_LVL > 0)
void rtc_init_s(void)
{
tfm_ns_lock_dispatch(rtc_init_veneer, 0, 0, 0, 0);
}
void rtc_free_s(void)
{
tfm_ns_lock_dispatch(rtc_free_veneer, 0, 0, 0, 0);
}
int32_t rtc_isenabled_s(void)
{
int32_t enabled = 0;
tfm_ns_lock_dispatch(rtc_isenabled_veneer, (uint32_t) &enabled, 0, 0, 0);
return enabled;
}
int64_t rtc_read_s(void)
{
int64_t t = 0;
tfm_ns_lock_dispatch(rtc_read_veneer, (uint32_t) &t, 0, 0, 0);
return t;
}
void rtc_write_s(int64_t t)
{
tfm_ns_lock_dispatch(rtc_write_veneer, (uint32_t) &t, 0, 0, 0);
}
#elif defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
#if (TFM_LVL == 0)
__NONSECURE_ENTRY
#endif
void rtc_init_s(void)
{
rtc_init_impl();
}
#if (TFM_LVL == 0)
__NONSECURE_ENTRY
#endif
void rtc_free_s(void)
{
rtc_free_impl();
}
#if (TFM_LVL == 0)
__NONSECURE_ENTRY
#endif
int32_t rtc_isenabled_s(void)
{
return rtc_isenabled_impl();
}
#if (TFM_LVL == 0)
__NONSECURE_ENTRY
#endif
int64_t rtc_read_s(void)
{
return rtc_read_impl();
}
#if (TFM_LVL == 0)
__NONSECURE_ENTRY
#endif
void rtc_write_s(int64_t t)
{
rtc_write_impl(t);
}
#endif
#endif