/**************************************************************************//** * * @copyright (C) 2020 Nuvoton Technology Corp. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2020-02-10 klcheng First version * ******************************************************************************/ #include #if defined (BSP_USING_RTC) #include #include #include /* Private define ---------------------------------------------------------------*/ /* convert the real year and month value to the format of struct tm. */ #define CONV_TO_TM_YEAR(year) ((year) - 1900) #define CONV_TO_TM_MON(mon) ((mon) - 1) /* convert the tm_year and tm_mon from struct tm to the real value. */ #define CONV_FROM_TM_YEAR(tm_year) ((tm_year) + 1900) #define CONV_FROM_TM_MON(tm_mon) ((tm_mon) + 1) /* rtc date upper bound reaches the year of 2099. */ #define RTC_TM_UPPER_BOUND \ { .tm_year = CONV_TO_TM_YEAR(2099), \ .tm_mon = CONV_TO_TM_MON(12), \ .tm_mday = 31, \ .tm_hour = 23, \ .tm_min = 59, \ .tm_sec = 59, \ } /* rtc date lower bound reaches the year of 2000. */ #define RTC_TM_LOWER_BOUND \ { .tm_year = CONV_TO_TM_YEAR(2000), \ .tm_mon = CONV_TO_TM_MON(1), \ .tm_mday = 1, \ .tm_hour = 0, \ .tm_min = 0, \ .tm_sec = 0, \ } /* Private typedef --------------------------------------------------------------*/ /* Private functions ------------------------------------------------------------*/ static rt_err_t nu_rtc_control(rt_device_t dev, int cmd, void *args); #if defined (NU_RTC_SUPPORT_IO_RW) static rt_size_t nu_rtc_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); static rt_size_t nu_rtc_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); #endif static rt_err_t nu_rtc_is_date_valid(const time_t *const t); static void nu_rtc_init(void); #if defined(RT_USING_ALARM) static void nu_rtc_alarm_reset(void); #endif /* Public functions -------------------------------------------------------------*/ #if defined (NU_RTC_SUPPORT_MSH_CMD) extern rt_err_t set_date(rt_uint32_t year, rt_uint32_t month, rt_uint32_t day); extern rt_err_t set_time(rt_uint32_t hour, rt_uint32_t minute, rt_uint32_t second); #endif /* Private variables ------------------------------------------------------------*/ static struct rt_device device_rtc; static void nu_rtc_init(void) { /* hw rtc initialise */ RTC_Open(NULL); RTC_DisableInt(RTC_INTEN_ALMIEN_Msk | RTC_INTEN_TICKIEN_Msk | RTC_INTEN_TAMP0IEN_Msk | RTC_INTEN_TAMP1IEN_Msk | RTC_INTEN_TAMP2IEN_Msk | RTC_INTEN_TAMP3IEN_Msk | RTC_INTEN_TAMP4IEN_Msk | RTC_INTEN_TAMP5IEN_Msk); #if defined(RT_USING_ALARM) nu_rtc_alarm_reset(); RTC_EnableInt(RTC_INTEN_ALMIEN_Msk); NVIC_EnableIRQ(RTC_IRQn); #endif } #if defined(RT_USING_ALARM) /* Reset alarm settings to avoid the unwanted values remain in rtc registers. */ static void nu_rtc_alarm_reset(void) { S_RTC_TIME_DATA_T alarm; /* Reset alarm time and calendar. */ alarm.u32Year = RTC_YEAR2000; alarm.u32Month = 0; alarm.u32Day = 0; alarm.u32Hour = 0; alarm.u32Minute = 0; alarm.u32Second = 0; alarm.u32TimeScale = RTC_CLOCK_24; RTC_SetAlarmDateAndTime(&alarm); /* Reset alarm time mask and calendar mask. */ RTC_SetAlarmDateMask(0, 0, 0, 0, 0, 0); RTC_SetAlarmTimeMask(0, 0, 0, 0, 0, 0); /* Clear alarm flag for safe */ RTC_CLEAR_ALARM_INT_FLAG(); } #endif /* rtc device driver initialise. */ int rt_hw_rtc_init(void) { rt_err_t ret; nu_rtc_init(); /* register rtc device IO operations */ device_rtc.type = RT_Device_Class_RTC; device_rtc.init = NULL; device_rtc.open = NULL; device_rtc.close = NULL; device_rtc.control = nu_rtc_control; #if defined (NU_RTC_SUPPORT_IO_RW) device_rtc.read = nu_rtc_read; device_rtc.write = nu_rtc_write; #else device_rtc.read = NULL; device_rtc.write = NULL; #endif device_rtc.user_data = RT_NULL; device_rtc.rx_indicate = RT_NULL; device_rtc.tx_complete = RT_NULL; ret = rt_device_register(&device_rtc, "rtc", RT_DEVICE_FLAG_RDWR); return (int)ret; } INIT_BOARD_EXPORT(rt_hw_rtc_init); #if defined (NU_RTC_SUPPORT_IO_RW) /* Register rt-thread device.read() entry. */ static rt_size_t nu_rtc_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) { (void) pos; nu_rtc_control(dev, RT_DEVICE_CTRL_RTC_GET_TIME, buffer); return size; } #endif #if defined (NU_RTC_SUPPORT_IO_RW) /* Register rt-thread device.write() entry. */ static rt_size_t nu_rtc_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) { (void) pos; nu_rtc_control(dev, RT_DEVICE_CTRL_RTC_SET_TIME, (void *)buffer); return size; } #endif static rt_err_t nu_rtc_is_date_valid(const time_t *const t) { static struct tm tm_upper = RTC_TM_UPPER_BOUND; static struct tm tm_lower = RTC_TM_LOWER_BOUND; static time_t t_upper, t_lower; static rt_bool_t initialised = RT_FALSE; if (!initialised) { t_upper = timegm((struct tm *)&tm_upper); t_lower = timegm((struct tm *)&tm_lower); initialised = RT_TRUE; } /* check the date is supported by rtc. */ if ((*t > t_upper) || (*t < t_lower)) return -(RT_EINVAL); return RT_EOK; } /* Register rt-thread device.control() entry. */ static rt_err_t nu_rtc_control(rt_device_t dev, int cmd, void *args) { struct tm tm_out, *tm_in; time_t *time; S_RTC_TIME_DATA_T hw_time; #if defined(RT_USING_ALARM) struct rt_rtc_wkalarm *wkalarm; S_RTC_TIME_DATA_T hw_alarm; #endif if ((dev == NULL) || (args == NULL)) return -(RT_EINVAL); switch (cmd) { case RT_DEVICE_CTRL_RTC_GET_TIME: time = (time_t *)args; RTC_GetDateAndTime(&hw_time); tm_out.tm_year = CONV_TO_TM_YEAR(hw_time.u32Year); tm_out.tm_mon = CONV_TO_TM_MON(hw_time.u32Month); tm_out.tm_mday = hw_time.u32Day; tm_out.tm_hour = hw_time.u32Hour; tm_out.tm_min = hw_time.u32Minute; tm_out.tm_sec = hw_time.u32Second; *time = timegm(&tm_out); break; case RT_DEVICE_CTRL_RTC_SET_TIME: time = (time_t *) args; tm_in = localtime(time); if (nu_rtc_is_date_valid(time) != RT_EOK) return RT_ERROR; hw_time.u32Year = CONV_FROM_TM_YEAR(tm_in->tm_year); hw_time.u32Month = CONV_FROM_TM_MON(tm_in->tm_mon); hw_time.u32Day = tm_in->tm_mday; hw_time.u32Hour = tm_in->tm_hour; hw_time.u32Minute = tm_in->tm_min; hw_time.u32Second = tm_in->tm_sec; hw_time.u32TimeScale = RTC_CLOCK_24; hw_time.u32AmPm = 0; RTC_SetDateAndTime(&hw_time); break; #if defined(RT_USING_ALARM) case RT_DEVICE_CTRL_RTC_GET_ALARM: wkalarm = (struct rt_rtc_wkalarm *) args; RTC_GetAlarmDateAndTime(&hw_alarm); wkalarm->tm_hour = hw_alarm.u32Hour; wkalarm->tm_min = hw_alarm.u32Minute; wkalarm->tm_sec = hw_alarm.u32Second; break; case RT_DEVICE_CTRL_RTC_SET_ALARM: wkalarm = (struct rt_rtc_wkalarm *) args; hw_alarm.u32Hour = wkalarm->tm_hour; hw_alarm.u32Minute = wkalarm->tm_min; hw_alarm.u32Second = wkalarm->tm_sec; RTC_SetAlarmDateMask(1, 1, 1, 1, 1, 1); RTC_SetAlarmDateAndTime(&hw_alarm); break; default: return -(RT_EINVAL); #endif } return RT_EOK; } #if defined (NU_RTC_SUPPORT_MSH_CMD) /* Support "rtc_det_date" command line in msh mode */ static rt_err_t msh_rtc_set_date(int argc, char **argv) { rt_uint32_t index, len, arg[3]; rt_memset(arg, 0, sizeof(arg)); len = (argc >= 4) ? 4 : argc; /* The date information stored in argv is represented by the following order : argv[0,1,2,3] = [cmd, year, month, day] */ for (index = 0; index < (len - 1); index ++) { arg[index] = atol(argv[index + 1]); } return set_date(arg[0], arg[1], arg[2]); } MSH_CMD_EXPORT_ALIAS(msh_rtc_set_date, rtc_set_date, e.g: rtc_set_date 2020 1 20); #endif #if defined (NU_RTC_SUPPORT_MSH_CMD) /* Support "rtc_det_time" command line in msh mode */ static rt_err_t msh_rtc_set_time(int argc, char **argv) { rt_uint32_t index, len, arg[3]; rt_memset(arg, 0, sizeof(arg)); len = (argc >= 4) ? 4 : argc; /* The time information stored in argv is represented by the following order : argv[0,1,2,3] = [cmd, hour, minute, second] */ for (index = 0; index < (len - 1); index ++) { arg[index] = atol(argv[index + 1]); } return set_time(arg[0], arg[1], arg[2]); } MSH_CMD_EXPORT_ALIAS(msh_rtc_set_time, rtc_set_time, e.g: rtc_set_time 18 30 00); #endif /* rtc interrupt entry */ void RTC_IRQHandler(void) { rt_interrupt_enter(); if (RTC_GET_TICK_INT_FLAG()) { RTC_CLEAR_TICK_INT_FLAG(); } #if defined(RT_USING_ALARM) if (RTC_GET_ALARM_INT_FLAG()) { RTC_CLEAR_ALARM_INT_FLAG(); /* Send an alarm event to notify rt-thread alarm service. */ rt_alarm_update(&device_rtc, NULL); } #endif rt_interrupt_leave(); } #endif /* BSP_USING_RTC */