Files
E3/e3_176_ref/drivers/source/rtc/sdrv_rtc.c
2025-10-21 19:40:27 +08:00

760 lines
18 KiB
C

/**
* @file sdrv_rtc.c
*
* Copyright (c) 2022 Semidrive Semiconductor.
* All rights reserved.
*
* Description: sec_rtc driver.
*
* Revision History:
* -----------------
*/
#include <debug.h>
#include <irq.h>
#include <irq_num.h>
#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;
}