rt-thread-official/bsp/cvitek/drivers/drv_rtc.c

410 lines
10 KiB
C

/*
* Copyright (c) 2006-2024, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-03-04 ShichengChu the first version
*/
#include <rtthread.h>
#include <rtdevice.h>
#ifdef BSP_USING_RTC
#define DBG_TAG "DRV.RTC"
#define DBG_LVL DBG_WARNING
#include <rtdbg.h>
#include "pinctrl.h"
#include "mmio.h"
#define CVI_RTC_BASE 0x05026000U
#define RTC_ALARM_O 17
#define CVI_RTC_CTRL_BASE 0x05025000U
#define CLK_EN_0 0x03002000U
#define CLK_RTC_25M_BIT (1 << 8)
/* CVITEK RTC registers */
#define CVI_RTC_ANA_CALIB 0x0
#define CVI_RTC_SEC_PULSE_GEN 0x4
#define CVI_RTC_ALARM_TIME 0x8
#define CVI_RTC_ALARM_ENABLE 0xC
#define CVI_RTC_SET_SEC_CNTR_VALUE 0x10
#define CVI_RTC_SET_SEC_CNTR_TRIG 0x14
#define CVI_RTC_SEC_CNTR_VALUE 0x18
#define CVI_RTC_APB_RDATA_SEL 0x3C
#define CVI_RTC_POR_DB_MAGIC_KEY 0x68
#define CVI_RTC_EN_PWR_WAKEUP 0xBC
#define CVI_RTC_PWR_DET_SEL 0x140
/* CVITEK RTC MACRO registers */
#define RTC_MACRO_DA_CLEAR_ALL 0x480
#define RTC_MACRO_DA_SOC_READY 0x48C
#define RTC_MACRO_RO_T 0x4A8
#define RTC_MACRO_RG_SET_T 0x498
/* CVITEK RTC CTRL registers */
#define CVI_RTC_FC_COARSE_EN 0x40
#define CVI_RTC_FC_COARSE_CAL 0x44
#define CVI_RTC_FC_FINE_EN 0x48
#define CVI_RTC_FC_FINE_CAL 0x50
#define RTC_SEC_MAX_VAL 0xFFFFFFFF
#define RTC_OFFSET_SN 0x5201800
#define RTC_ALARM_IRQ_NUM 0x11
struct rtc_device_object
{
rt_rtc_dev_t rtc_dev;
};
static struct rtc_device_object rtc_device;
#define LEAPS_THRU_END_OF(y) ((y)/4 - (y)/100 + (y)/400)
typedef struct {
int tm_sec; ///< Second. [0-59]
int tm_min; ///< Minute. [0-59]
int tm_hour; ///< Hour. [0-23]
int tm_mday; ///< Day. [1-31]
int tm_mon; ///< Month. [0-11]
int tm_year; ///< Year-1900. [70- ] !NOTE:Set 100 mean 2000
int tm_wday; ///< Day of week. [0-6 ] !NOTE:Set 0 mean Sunday
int tm_yday; ///< Days in year.[0-365] !NOTE:Set 0 mean January 1st
} cvi_rtc_time_t;
static const unsigned char cvi_rtc_days_in_month[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
static inline int is_leap_year(unsigned int year)
{
return (!(year % 4) && (year % 100)) || !(year % 400);
}
static int rtc_month_days(unsigned int month, unsigned int year)
{
return cvi_rtc_days_in_month[month] + (is_leap_year(year) && month == 1);
}
static void hal_cvi_rtc_clk_set(int enable)
{
uint32_t clk_state;
clk_state = mmio_read_32((long unsigned int)CLK_EN_0);
if(enable)
clk_state |= CLK_RTC_25M_BIT;
else
clk_state &= ~(CLK_RTC_25M_BIT);
mmio_write_32((long unsigned int)CLK_EN_0, clk_state);
}
static void hal_cvi_rtc_enable_sec_counter(uintptr_t rtc_base)
{
uint32_t value = 0;
value = mmio_read_32(rtc_base + CVI_RTC_SEC_PULSE_GEN) & ~(1 << 31);
mmio_write_32(rtc_base + CVI_RTC_SEC_PULSE_GEN, value);
value = mmio_read_32(rtc_base + CVI_RTC_ANA_CALIB) & ~(1 << 31);
mmio_write_32(rtc_base + CVI_RTC_ANA_CALIB, value);
mmio_read_32(rtc_base + CVI_RTC_SEC_CNTR_VALUE);
mmio_write_32(rtc_base + CVI_RTC_ALARM_ENABLE, 0x0);
}
static void hal_cvi_rtc_set_time(uintptr_t rtc_base, unsigned long sec)
{
mmio_write_32(rtc_base + CVI_RTC_SET_SEC_CNTR_VALUE, sec);
mmio_write_32(rtc_base + CVI_RTC_SET_SEC_CNTR_TRIG, 1);
mmio_write_32(rtc_base + RTC_MACRO_RG_SET_T, sec);
mmio_write_32(rtc_base + RTC_MACRO_DA_CLEAR_ALL, 1);
mmio_write_32(rtc_base + RTC_MACRO_DA_SOC_READY, 1);
mmio_write_32(rtc_base + RTC_MACRO_DA_CLEAR_ALL, 0);
mmio_write_32(rtc_base + RTC_MACRO_RG_SET_T, 0);
mmio_write_32(rtc_base + RTC_MACRO_DA_SOC_READY, 0);
}
static int hal_cvi_rtc_get_time_sec(uintptr_t rtc_base,unsigned long *ret_sec)
{
int ret = 0;
unsigned long sec;
unsigned long sec_ro_t;
sec = mmio_read_32(rtc_base + CVI_RTC_SEC_CNTR_VALUE);
sec_ro_t = mmio_read_32(rtc_base + RTC_MACRO_RO_T);
LOG_D("sec=%lx, sec_ro_t=%lx\n", sec, sec_ro_t);
if (sec_ro_t > 0x30000000) {
sec = sec_ro_t;
// Writeback to SEC CVI_RTC_SEC_CNTR_VALUE
mmio_write_32(rtc_base + CVI_RTC_SET_SEC_CNTR_VALUE, sec);
mmio_write_32(rtc_base + CVI_RTC_SET_SEC_CNTR_TRIG, 1);
} else if (sec < 0x30000000) {
LOG_D("RTC invalid time\n");
ret = -EINVAL;
}
*ret_sec = sec;
return ret;
}
static inline int64_t div_u64_rem(uint64_t dividend, uint32_t divisor, uint32_t *remainder)
{
*remainder = dividend % divisor;
return dividend / divisor;
}
/*
* rtc_time_to_tm64 - Converts time64_t to rtc_time.
* Convert seconds since 01-01-1970 00:00:00 to Gregorian date.
*/
static void rtc_time64_to_tm(int64_t time, cvi_rtc_time_t *cvi_tm)
{
unsigned int month, year, secs;
int days;
/* time must be positive */
days = div_u64_rem(time, 86400, &secs);
/* day of the week, 1970-01-01 was a Thursday */
cvi_tm->tm_wday = (days + 4) % 7;
year = 1970 + days / 365;
days -= (year - 1970) * 365
+ LEAPS_THRU_END_OF(year - 1)
- LEAPS_THRU_END_OF(1970 - 1);
while (days < 0) {
year -= 1;
days += 365 + is_leap_year(year);
}
cvi_tm->tm_year = year - 1900;
cvi_tm->tm_yday = days + 1;
for (month = 0; month < 11; month++) {
int newdays;
newdays = days - rtc_month_days(month, year);
if (newdays < 0)
break;
days = newdays;
}
cvi_tm->tm_mon = month;
cvi_tm->tm_mday = days + 1;
cvi_tm->tm_hour = secs / 3600;
secs -= cvi_tm->tm_hour * 3600;
cvi_tm->tm_min = secs / 60;
cvi_tm->tm_sec = secs - cvi_tm->tm_min * 60;
}
static int64_t mktime64(const unsigned int year0, const unsigned int mon0,
const unsigned int day, const unsigned int hour,
const unsigned int min, const unsigned int sec)
{
unsigned int mon = mon0, year = year0;
/* 1..12 -> 11,12,1..10 */
if (0 >= (int) (mon -= 2)) {
mon += 12; /* Puts Feb last since it has leap day */
year -= 1;
}
return ((((int64_t)
(year/4 - year/100 + year/400 + 367*mon/12 + day) +
year*365 - 719499
)*24 + hour /* now have hours - midnight tomorrow handled here */
)*60 + min /* now have minutes */
)*60 + sec; /* finally seconds */
}
/*
* rtc_tm_to_time64 - Converts rtc_time to time64_t.
* Convert Gregorian date to seconds since 01-01-1970 00:00:00.
*/
static int64_t rtc_tm_to_time64(const cvi_rtc_time_t *cvi_tm)
{
return mktime64(cvi_tm->tm_year + 1900, cvi_tm->tm_mon + 1, cvi_tm->tm_mday,
cvi_tm->tm_hour, cvi_tm->tm_min, cvi_tm->tm_sec);
}
static rt_err_t _rtc_get_timeval(struct timeval *tv)
{
unsigned long sec;
cvi_rtc_time_t t = {0};
struct tm tm_new = {0};
hal_cvi_rtc_get_time_sec(CVI_RTC_BASE, &sec);
rtc_time64_to_tm(sec, &t);
tm_new.tm_sec = t.tm_sec;
tm_new.tm_min = t.tm_min;
tm_new.tm_hour = t.tm_hour;
tm_new.tm_wday = t.tm_wday;
tm_new.tm_mday = t.tm_mday;
tm_new.tm_mon = t.tm_mon;
tm_new.tm_year = t.tm_year;
tv->tv_sec = timegm(&tm_new);
return RT_EOK;
}
static rt_err_t _rtc_init(void)
{
hal_cvi_rtc_clk_set(1);
hal_cvi_rtc_enable_sec_counter(CVI_RTC_BASE);
return RT_EOK;
}
static rt_err_t _rtc_get_secs(time_t *sec)
{
struct timeval tv;
_rtc_get_timeval(&tv);
*(time_t *) sec = tv.tv_sec;
LOG_D("RTC: get rtc_time %d", *sec);
return RT_EOK;
}
static rt_err_t _rtc_set_secs(time_t *sec)
{
rt_err_t result = RT_EOK;
cvi_rtc_time_t t = {0};
struct tm tm = {0};
unsigned long set_sec;
gmtime_r(sec, &tm);
t.tm_sec = tm.tm_sec;
t.tm_min = tm.tm_min;
t.tm_hour = tm.tm_hour;
t.tm_mday = tm.tm_mday;
t.tm_mon = tm.tm_mon;
t.tm_year = tm.tm_year;
t.tm_wday = tm.tm_wday;
set_sec = rtc_tm_to_time64(&t);
hal_cvi_rtc_set_time(CVI_RTC_BASE, set_sec);
return result;
}
#ifdef RT_USING_ALARM
static void rtc_alarm_enable(rt_bool_t enable)
{
mmio_write_32(CVI_RTC_BASE + CVI_RTC_ALARM_ENABLE, enable);
}
static void rt_hw_rtc_isr(int irqno, void *param)
{
rt_interrupt_enter();
/* send event to alarm */
rt_alarm_update(&rtc_device.rtc_dev.parent, 1);
/* clear alarm */
rtc_alarm_enable(0);
rt_interrupt_leave();
}
#endif
static rt_err_t _rtc_get_alarm(struct rt_rtc_wkalarm *alarm)
{
if (alarm == RT_NULL)
return -RT_ERROR;
unsigned long int sec;
cvi_rtc_time_t t = {0};
sec = mmio_read_32(CVI_RTC_BASE + CVI_RTC_ALARM_TIME);
rtc_time64_to_tm(sec, &t);
alarm->tm_sec = t.tm_sec;
alarm->tm_min = t.tm_min;
alarm->tm_hour = t.tm_hour;
alarm->tm_mday = t.tm_mday;
alarm->tm_mon = t.tm_mon;
alarm->tm_year = t.tm_year;
LOG_D("GET_ALARM %d:%d:%d", alarm->tm_hour, alarm->tm_min, alarm->tm_sec);
return RT_EOK;
}
static rt_err_t _rtc_set_alarm(struct rt_rtc_wkalarm *alarm)
{
if (alarm == RT_NULL)
return -RT_ERROR;
cvi_rtc_time_t t = {0};
unsigned long int set_sec;
if (alarm->enable){
t.tm_sec = alarm->tm_sec;
t.tm_min = alarm->tm_min;
t.tm_hour = alarm->tm_hour;
t.tm_mday = alarm->tm_mday;
t.tm_mon = alarm->tm_mon;
t.tm_year = alarm->tm_year;
set_sec = rtc_tm_to_time64(&t);
mmio_write_32(CVI_RTC_BASE + CVI_RTC_ALARM_TIME, set_sec);
LOG_D("GET_ALARM %d:%d:%d", alarm->tm_hour, alarm->tm_min, alarm->tm_sec);
}
rtc_alarm_enable(alarm->enable);
return RT_EOK;
}
static const struct rt_rtc_ops _rtc_ops =
{
_rtc_init,
_rtc_get_secs,
_rtc_set_secs,
#ifdef RT_USING_ALARM
_rtc_get_alarm,
_rtc_set_alarm,
#else
RT_NULL,
RT_NULL,
#endif
_rtc_get_timeval,
RT_NULL,
};
static int rt_hw_rtc_init(void)
{
rt_err_t result;
rtc_device.rtc_dev.ops = &_rtc_ops;
result = rt_hw_rtc_register(&rtc_device.rtc_dev, "rtc", RT_DEVICE_FLAG_RDWR, RT_NULL);
if (result != RT_EOK)
{
LOG_E("rtc register err code: %d", result);
return result;
}
#ifdef RT_USING_ALARM
rt_hw_interrupt_install(RTC_ALARM_IRQ_NUM, rt_hw_rtc_isr, RT_NULL, "rtc");
rt_hw_interrupt_umask(RTC_ALARM_IRQ_NUM);
#endif
LOG_D("rtc init success");
return RT_EOK;
}
INIT_DEVICE_EXPORT(rt_hw_rtc_init);
#endif /* BSP_USING_RTC */