/** * @file sdrv_rtc.c * * Copyright (c) 2022 Semidrive Semiconductor. * All rights reserved. * * Description: sec_rtc driver. * * Revision History: * ----------------- */ #include #include #include #include "sdrv_rtc.h" #include "sdrv_rtc_reg.h" #include "udelay/udelay.h" #define RTC_CLK_HZ (32 * 1024ul) #define RTC_CLK_SHIFT __builtin_ctz(RTC_CLK_HZ) #define RTC_TICK_TO_SECOND(t) ((t) >> RTC_CLK_SHIFT) #define RTC_SECOND_TO_TICK(s) ((s) << RTC_CLK_SHIFT) #define RTC_TICK_TO_MILLISECOND(t) ((uint64_t)(t) * 1000 / 32768) #define RTC_MILLISECOND_TO_TICK(s) ((uint64_t)(s) * 32768 / 1000) #define DELAY_US 240 #define SECONDS_PER_MINUTE (60ul) #define MINUTES_PER_HOUR (60ul) #define HOURS_PER_DAY (24ul) #define SECONDS_PER_HOUR (SECONDS_PER_MINUTE * MINUTES_PER_HOUR) #define SECONDS_PER_DAY (SECONDS_PER_HOUR * HOURS_PER_DAY) #define LEAP_YEAR_DAYS (366ul) #define COMMON_YEAR_DAYS (365ul) #define TM_START_YEAR (1900ul) #define EPOCH_START_YEAR (1970ul) static const uint32_t month_days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; /** * @brief check RTC time is valid. * * @param [in] tm RTC time. * @return 0 success, otherwise failed. */ static status_t sdrv_check_rtc_time(struct rtc_time *tm) { if (tm->tm_subsec > 999u) { return SDRV_RTC_STATUS_INVALID_RTC_TIME; } if (tm->tm_sec > 61u) { return SDRV_RTC_STATUS_INVALID_RTC_TIME; } if (tm->tm_min > 59u) { return SDRV_RTC_STATUS_INVALID_RTC_TIME; } if (tm->tm_hour > 23u) { return SDRV_RTC_STATUS_INVALID_RTC_TIME; } if ((tm->tm_mday < 1u) || (tm->tm_mday > 31u)) { return SDRV_RTC_STATUS_INVALID_RTC_TIME; } if (tm->tm_mon > 11u) { return SDRV_RTC_STATUS_INVALID_RTC_TIME; } if (tm->tm_wday > 6u) { return SDRV_RTC_STATUS_INVALID_RTC_TIME; } if (tm->tm_year > 365u) { return SDRV_RTC_STATUS_INVALID_RTC_TIME; } return SDRV_STATUS_OK; } /** * @brief Simple algorithm to convert RTC time to Epoch seconds. * * Epoch seconds starts from 1970-1-1, 00:00:01 * * @param [in] tm RTC time. * @return uint64_t Number of seconds since Epoch. */ uint64_t rtc_to_epoch(struct rtc_time *tm) { uint64_t epoch_sec = 0; uint32_t year = tm->tm_year + TM_START_YEAR; if (sdrv_check_rtc_time(tm) < 0) { ssdk_printf(SSDK_WARNING, "%s invalid rtc time!\n", __func__); return 0; } for (uint32_t num = EPOCH_START_YEAR; num < year; num++) { epoch_sec += SECONDS_PER_DAY * (is_leap_year(num) ? LEAP_YEAR_DAYS : COMMON_YEAR_DAYS); } for (uint32_t num = 0; num < tm->tm_mon; num++) { epoch_sec += month_days[num] * SECONDS_PER_DAY; if (is_leap_year(year) && (num == 1)) { epoch_sec += SECONDS_PER_DAY; } } epoch_sec += (tm->tm_mday - 1) * SECONDS_PER_DAY; epoch_sec += tm->tm_hour * SECONDS_PER_HOUR; epoch_sec += tm->tm_min * SECONDS_PER_MINUTE; epoch_sec += tm->tm_sec; return epoch_sec; } /** * @brief Simple algorithm to convert epoch seconds to RTC time. * * Epoch seconds starts from 1970-1-1, 00:00:01 * * @param [out] tm * @param [in] epoch_sec */ status_t epoch_to_rtc(struct rtc_time *tm, uint64_t epoch_sec) { uint32_t year = EPOCH_START_YEAR; uint32_t month = 0; uint32_t days = epoch_sec / SECONDS_PER_DAY; uint32_t seconds; tm->tm_wday = (4 + days) % 7; while (days >= COMMON_YEAR_DAYS) { if (is_leap_year(year)) { if (days >= LEAP_YEAR_DAYS) { days -= LEAP_YEAR_DAYS; } else { break; } } else { days -= COMMON_YEAR_DAYS; } year++; } tm->tm_year = year - TM_START_YEAR; while (days >= 28) { if (is_leap_year(year) && (month == 1)) { if (days >= 29) { days -= 29; } else { break; } } else { if (days >= month_days[month]) { days -= month_days[month]; } else { break; } } month++; } tm->tm_mon = month; tm->tm_mday = days + 1; seconds = epoch_sec % SECONDS_PER_DAY; tm->tm_hour = seconds / 3600; tm->tm_min = (seconds % 3600) / 60; tm->tm_sec = (seconds % 3600) % 60; return sdrv_check_rtc_time(tm); } /** * @brief Simple algorithm to convert RTC time to Epoch milliseconds. * * Epoch seconds starts from 1970-1-1, 00:00:00.001 * * @param [in] tm RTC time. * @return uint64_t number of milliseconds since epoch. */ uint64_t rtc_to_epoch_ms(struct rtc_time *tm) { uint64_t epoch_millisec = 0; uint32_t year = tm->tm_year + TM_START_YEAR; if (sdrv_check_rtc_time(tm) < 0) { ssdk_printf(SSDK_WARNING, "%s invalid rtc time!\n", __func__); return 0; } for (uint32_t num = EPOCH_START_YEAR; num < year; num++) { epoch_millisec += (uint64_t)SECONDS_PER_DAY * 1000 * (is_leap_year(num) ? LEAP_YEAR_DAYS : COMMON_YEAR_DAYS); } for (uint32_t num = 0; num < tm->tm_mon; num++) { epoch_millisec += (uint64_t)month_days[num] * SECONDS_PER_DAY * 1000; if (is_leap_year(year) && (num == 1)) { epoch_millisec += SECONDS_PER_DAY * 1000; } } epoch_millisec += (uint64_t)(tm->tm_mday - 1) * SECONDS_PER_DAY * 1000; epoch_millisec += (uint64_t)tm->tm_hour * SECONDS_PER_HOUR * 1000; epoch_millisec += (uint64_t)tm->tm_min * SECONDS_PER_MINUTE * 1000; epoch_millisec += (uint64_t)tm->tm_sec * 1000; epoch_millisec += (uint64_t)tm->tm_subsec; return epoch_millisec; } /** * @brief Simple algorithm to convert epoch milliseconds to RTC time. * * Epoch milliseconds starts from 1970-1-1, 00:00:00.001 * * @param [out] tm * @param [in] epoch_sec */ status_t epoch_ms_to_rtc(struct rtc_time *tm, uint64_t epoch_millisec) { uint32_t year = EPOCH_START_YEAR; uint32_t month = 0; uint32_t days = epoch_millisec / SECONDS_PER_DAY / 1000; uint32_t seconds; tm->tm_wday = (4 + days) % 7; while (days >= COMMON_YEAR_DAYS) { if (is_leap_year(year)) { if (days >= LEAP_YEAR_DAYS) { days -= LEAP_YEAR_DAYS; } else { break; } } else { days -= COMMON_YEAR_DAYS; } year++; } tm->tm_year = year - TM_START_YEAR; while (days >= 28) { if (is_leap_year(year) && (month == 1)) { if (days >= 29) { days -= 29; } else { break; } } else { if (days >= month_days[month]) { days -= month_days[month]; } else { break; } } month++; } tm->tm_mon = month; tm->tm_mday = days + 1; seconds = (epoch_millisec % (SECONDS_PER_DAY * 1000)) / 1000; tm->tm_hour = seconds / 3600; tm->tm_min = (seconds % 3600) / 60; tm->tm_sec = (seconds % 3600) % 60; tm->tm_subsec = epoch_millisec % 1000; return sdrv_check_rtc_time(tm); } static status_t _rtc_handler(uint32_t source, void *arg) { sdrv_rtc_t *dev = arg; Rtc *rtc = (Rtc *)dev->base; sdrv_rtc_violation_e vio_status; /* disable overflow/disable violation intr */ if (source == dev->violation_intr) { vio_status = sdrv_rtc_get_violation_status(dev); if (dev->vio_mask & vio_status) { sdrv_rtc_violation_intr_enable(dev, (sdrv_rtc_violation_e)(dev->vio_mask & vio_status), false); } } else if (source == dev->wakeup_intr) { sec_rtc_wakeup_enable(rtc, BM_WAKEUP_CTRL_ENABLE | BM_WAKEUP_CTRL_IRQ_ENABLE | BM_WAKEUP_CTRL_REQ_ENABLE, false); } if (dev->cb) dev->cb(source, arg); /* overflow/disable violation intr */ if (source == dev->violation_intr) { sdrv_rtc_clear_violation_status(dev, vio_status); if (dev->vio_mask & vio_status) { sdrv_rtc_violation_intr_enable(dev, (sdrv_rtc_violation_e)(dev->vio_mask & vio_status), true); } } return SDRV_STATUS_OK; } /** * @brief sdrv rtc enable. * * @param [in] dev sdrv rtc controller * @param [in] enable enable or disable * @return 0 success, otherwise failed. */ status_t sdrv_rtc_enable(sdrv_rtc_t *dev, bool enable) { if (dev->base == 0) { return SDRV_STATUS_INVALID_PARAM; } int ret; Rtc *rtc = (Rtc *)dev->base; ret = sec_rtc_local_enable(rtc, enable); if (ret < 0) { ssdk_printf(SSDK_ERR, "%s sec rtc enable faild!\n", __func__); return SDRV_RTC_STATUS_LOCK; } udelay(DELAY_US); return SDRV_STATUS_OK; } /** * @brief sdrv enable rtc wake up. * * @param [in] dev sdrv rtc controller * @param [in] type sdrv rtc wake up type * @param [in] enable enable or disable * @return 0 success, otherwise failed. */ status_t sdrv_rtc_wakeup_enable(sdrv_rtc_t *dev, sdrv_rtc_wakeup_enable_type_e type, bool enable) { if (dev->base == 0) { return SDRV_STATUS_INVALID_PARAM; } Rtc *rtc = (Rtc *)dev->base; uint8_t vtype = 0; switch (type) { case SDRV_WKUP_ENABLE: vtype = BM_WAKEUP_CTRL_ENABLE; break; case SDRV_WKUP_ENABLE_IRQ: vtype = BM_WAKEUP_CTRL_ENABLE | BM_WAKEUP_CTRL_IRQ_ENABLE; break; case SDRV_WKUP_ENABLE_REQ: vtype = BM_WAKEUP_CTRL_ENABLE | BM_WAKEUP_CTRL_REQ_ENABLE; break; case SDRV_WKUP_ENABLE_ALL: vtype = BM_WAKEUP_CTRL_ENABLE | BM_WAKEUP_CTRL_IRQ_ENABLE | BM_WAKEUP_CTRL_REQ_ENABLE; break; default: vtype = BM_WAKEUP_CTRL_ENABLE | BM_WAKEUP_CTRL_IRQ_ENABLE | BM_WAKEUP_CTRL_REQ_ENABLE; break; } sec_rtc_wakeup_enable(rtc, vtype, enable); return SDRV_STATUS_OK; } /** * @brief sdrv get rtc wake up status. * * @param [in] dev sdrv rtc controller * @return 1 wakeup, 0 not wakeup. */ bool sdrv_rtc_get_wakeup_status(sdrv_rtc_t *dev) { Rtc *rtc = (Rtc *)dev->base; return sec_rtc_get_wakeup_status(rtc); } /** * @brief sdrv clear rtc wake up status. * * @param [in] dev sdrv rtc controller * @return 0 success, otherwise failed. */ status_t sdrv_rtc_clear_wakeup_status(sdrv_rtc_t *dev) { if (dev->base == 0) { return SDRV_STATUS_INVALID_PARAM; } /* rtc wakeup status has been cleared */ if (!sdrv_rtc_get_wakeup_status(dev)) { return SDRV_STATUS_OK; } Rtc *rtc = (Rtc *)dev->base; if (sec_rtc_is_alarm_enable(rtc)) { sec_rtc_wakeup_enable(rtc, BM_WAKEUP_CTRL_ENABLE | BM_WAKEUP_CTRL_IRQ_ENABLE | BM_WAKEUP_CTRL_REQ_ENABLE, false); } sec_rtc_clear_wakeup_status(rtc); return SDRV_STATUS_OK; } /** * @brief sdrv get rtc violation status. * * @param [in] dev sdrv rtc controller * @return 0 no violation, 1 overflow violation, 2 disable violation, 3 overflow & disable */ sdrv_rtc_violation_e sdrv_rtc_get_violation_status(sdrv_rtc_t *dev) { Rtc *rtc = (Rtc *)dev->base; return (sdrv_rtc_violation_e)sec_rtc_get_violation_status(rtc); } /** * @brief sdrv clear rtc violation status. * * @param [in] dev sdrv rtc controller * @return 0 success, otherwise failed. */ status_t sdrv_rtc_clear_violation_status(sdrv_rtc_t *dev, sdrv_rtc_violation_e vio) { if (dev->base == 0) { return SDRV_STATUS_INVALID_PARAM; } Rtc *rtc = (Rtc *)dev->base; sec_rtc_clear_violation_status(rtc, vio); if (vio & SDRV_RTC_VIO_OVERFLOW) { sec_rtc_set_cross_clk_en(rtc); } return SDRV_STATUS_OK; } /** * @brief sdrv rtc violation enable. * * @param [in] dev sdrv rtc controller * @param [in] vio_mask sdrv rtc violation type * @param [in] en violation enable or disable * @return 0 success, otherwise failed. */ status_t sdrv_rtc_violation_intr_enable(sdrv_rtc_t *dev, sdrv_rtc_violation_e vio, bool en) { if (dev->base == 0) { return SDRV_STATUS_INVALID_PARAM; } Rtc *rtc = (Rtc *)dev->base; if (en) { sec_rtc_set_violation_enable(rtc, vio); } else { sec_rtc_set_violation_disable(rtc, vio); } return SDRV_STATUS_OK; } /** * @brief Enable rtc interrupt. * @param [in] source rtc wakeup/periodical/violation interrupt num. * @param [in] arg pointer to interrupt arg(rtc device structure). * @param [in] cb callback function. */ status_t sdrv_rtc_enable_it(uint32_t source, void *arg, rtc_cb_t cb) { sdrv_rtc_t *dev = arg; dev->cb = cb; if (source > 0) { irq_attach(source, (irq_handler)_rtc_handler, dev); irq_enable(source); return SDRV_STATUS_OK; } return SDRV_STATUS_INVALID_PARAM; } /** * @brief disable rtc interrput. * @param source rtc wakeup/periodical/violation interrupt num. * @return 0 success, otherwise failed. */ status_t sdrv_rtc_disable_it(uint32_t source) { if (source > 0) { irq_detach(source); irq_disable(source); return SDRV_STATUS_OK; } return SDRV_STATUS_INVALID_PARAM; } /** * @brief rtc get time * * Get current rtc time. * * @param [in] dev sdrv rtc controller * @param [in] tm * @return 0 success, otherwise failed. */ status_t sdrv_rtc_get_time(sdrv_rtc_t *dev, struct rtc_time *tm) { if (dev->base == 0) { return SDRV_STATUS_INVALID_PARAM; } Rtc *rtc = (Rtc *)dev->base; uint64_t millisecs = RTC_TICK_TO_MILLISECOND(sec_rtc_get_tick(rtc)); return epoch_ms_to_rtc(tm, millisecs); } /** * @brief rtc set time * * Set current rtc time. * * @param [in] dev * @param [in] tm * @return 0 success, otherwise failed. */ status_t sdrv_rtc_set_time(sdrv_rtc_t *dev, struct rtc_time *tm) { uint64_t millisecs = rtc_to_epoch_ms(tm); int ret; if (millisecs == 0) { ssdk_printf(SSDK_WARNING, "%s default set rtc time 1970-1-1 00:00:00.000\n", __func__); } Rtc *rtc = (Rtc *)dev->base; ret = sec_rtc_local_enable(rtc, 0); if (ret < 0) { ssdk_printf(SSDK_ERR, "%s sec rtc disable failed!\n", __func__); return SDRV_RTC_STATUS_LOCK; } /* writing RTC_L/H, extra waitting 2*240us */ udelay(DELAY_US); sec_rtc_set_tick(rtc, RTC_MILLISECOND_TO_TICK(millisecs)); sec_rtc_set_cross_clk_en(rtc); ret = sec_rtc_local_enable(rtc, 1); if (ret < 0) { ssdk_printf(SSDK_ERR, "%s sec rtc enable failed!\n", __func__); return SDRV_RTC_STATUS_LOCK; } udelay(DELAY_US); return SDRV_STATUS_OK; } /** * @brief rtc get alarm * * Get rtc alarm time, rtc will trigger interrupt when rtc time arrives at alarm time. * * @param [in] dev * @param [in] alrm * @return 0 */ status_t sdrv_rtc_get_alarm(sdrv_rtc_t *dev, struct rtc_wkalrm *alrm) { status_t ret = SDRV_STATUS_FAIL; if (dev->base == 0) { return SDRV_STATUS_INVALID_PARAM; } Rtc *rtc = (Rtc *)dev->base; uint64_t millisecs = RTC_TICK_TO_MILLISECOND(sec_rtc_get_alarm_tick(rtc)); ret = epoch_ms_to_rtc(&alrm->tm, millisecs); alrm->enable = sec_rtc_is_alarm_enable(rtc); alrm->pending = sec_rtc_get_wakeup_status(rtc); return ret; } /** * @brief rtc set alarm * * Set rtc alarm time, rtc will trigger interrupt when rtc time arrives at alarm time. * * @param [in] dev sdrv rtc controller * @param [in] alrm * @return 0 success, otherwise failed. */ status_t sdrv_rtc_set_alarm(sdrv_rtc_t *dev, struct rtc_wkalrm *alrm) { if (dev->base == 0) { return SDRV_STATUS_INVALID_PARAM; } uint64_t millisecs = rtc_to_epoch_ms(&alrm->tm); if (millisecs == 0) { ssdk_printf(SSDK_ERR, "%s sec rtc set sec failed!\n", __func__); return SDRV_RTC_STATUS_INVALID_RTC_TIME; } Rtc *rtc = (Rtc *)dev->base; sec_rtc_set_alarm_tick(rtc, RTC_MILLISECOND_TO_TICK(millisecs)); sec_rtc_set_cross_clk_en(rtc); return SDRV_STATUS_OK; } /** * @brief Enable rtc alarm. * * @param [in] dev sdrv rtc controller * @param [in] enable enable or disable * @return 0 success, otherwise failed. */ status_t sdrv_rtc_enable_alarm(sdrv_rtc_t *dev, bool enable) { if (dev->base == 0) { return SDRV_STATUS_INVALID_PARAM; } Rtc *rtc = (Rtc *)dev->base; sec_rtc_wakeup_enable(rtc, BM_WAKEUP_CTRL_ENABLE | BM_WAKEUP_CTRL_IRQ_ENABLE | BM_WAKEUP_CTRL_REQ_ENABLE, enable); if (!enable) { sec_rtc_clear_wakeup_status(rtc); } return SDRV_STATUS_OK; } /** * @brief Read rtc general purpose register. * * Data in general purpose register will not be lost until RTC power down. * * @param [in] dev sdrv rtc controller * @param [in] gp_num sdrv general purpose register num * @return general purpose register value */ uint32_t sdrv_rtc_read_general_purpose_reg(sdrv_rtc_t *dev, sdrv_general_purpose_e gp_num) { if (dev->base == 0) { return SDRV_STATUS_INVALID_PARAM; } Rtc *rtc = (Rtc *)dev->base; return sec_rtc_read_general_purpose_reg(rtc, gp_num); } /** * @brief Write rtc general purpose register. * * @param [in] dev sdrv rtc controller * @param [in] gp_num sdrv general purpose register num * @param [in] value general purpose register value * @return 0 success, otherwise failed. */ status_t sdrv_rtc_write_general_purpose_reg(sdrv_rtc_t *dev, sdrv_general_purpose_e gp_num, uint32_t value) { if (dev->base == 0) { return SDRV_STATUS_INVALID_PARAM; } Rtc *rtc = (Rtc *)dev->base; sec_rtc_write_general_purpose_reg(rtc, gp_num, value); return SDRV_STATUS_OK; } /** * @brief rtc lock. * * Once set rtc lock, rtc enable can not be changed until next power on reset. * * @param [in] dev sdrv rtc controller * @return 0 success, otherwise failed. */ status_t sdrv_rtc_lock(sdrv_rtc_t *dev) { if (dev->base == 0) { return SDRV_STATUS_INVALID_PARAM; } Rtc *rtc = (Rtc *)dev->base; sec_rtc_lock(rtc); return SDRV_STATUS_OK; }