387 lines
9.8 KiB
C
387 lines
9.8 KiB
C
/*
|
|
* Kneron RTC (Real Time Clock) driver using SCU
|
|
*
|
|
* Copyright (C) 2019 Kneron, Inc. All rights reserved.
|
|
*
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include "cmsis_os2.h"
|
|
#include "kmdw_power_manager.h"
|
|
#include "kdrv_power.h"
|
|
#include "rtc.h"
|
|
#include "kmdw_console.h"
|
|
#include "io.h"
|
|
|
|
#define RTC_PA_BASE (SCU_FTSCU100_PA_BASE + 0x200)
|
|
|
|
/* SCU RTC Registers */
|
|
#define RTC_REG_TIME1 (RTC_PA_BASE + 0x00)
|
|
#define RTC_REG_TIME2 (RTC_PA_BASE + 0x04)
|
|
#define RTC_REG_ALM1 (RTC_PA_BASE + 0x08)
|
|
#define RTC_REG_ALM2 (RTC_PA_BASE + 0x0C)
|
|
#define RTC_REG_CTRL (RTC_PA_BASE + 0x10)
|
|
|
|
/* RTC_REG_TIME1 & RTC_REG_ALM1 */
|
|
#define RTC_TIME1_WEEKDAY 0x07000000
|
|
#define RTC_TIME1_HOUR 0x003F0000
|
|
#define RTC_TIME1_MIN 0x00007F00
|
|
#define RTC_TIME1_SEC 0x0000007F
|
|
|
|
#define RTC_TIME1_WEEKDAY_SHIFT 24
|
|
#define RTC_TIME1_HOUR_SHIFT 16
|
|
#define RTC_TIME1_MIN_SHIFT 8
|
|
#define RTC_TIME1_SEC_SHIFT 0
|
|
|
|
/* RTC_REG_TIME2 & RTC_REG_ALM2 */
|
|
#define RTC_TIME2_CENTURY 0xFF000000
|
|
#define RTC_TIME2_YEAR 0x00FF0000
|
|
#define RTC_TIME2_MONTH 0x00001F00
|
|
#define RTC_TIME2_DATE 0x0000003F
|
|
|
|
#define RTC_TIME2_CENTURY_SHIFT 24
|
|
#define RTC_TIME2_YEAR_SHIFT 16
|
|
#define RTC_TIME2_MONTH_SHIFT 8
|
|
#define RTC_TIME2_DATE_SHIFT 0
|
|
|
|
/* RTC_REG_CTRL */
|
|
#define RTC_CTRL_PWREN_IE_OUT BIT30
|
|
#define RTC_CTRL_CLK_READY BIT15
|
|
#define RTC_CTRL_ALMEN_STS BIT9
|
|
#define RTC_CTRL_RTCEN_STS BIT8
|
|
#define RTC_CTRL_SECOUT_EN BIT7
|
|
#define RTC_CTRL_PERIODIC_SEL (BIT6 | BIT5 | BIT4)
|
|
#define RTC_CTRL_LOCK_EN BIT2
|
|
#define RTC_CTRL_ALM_EN BIT1
|
|
#define RTC_CTRL_EN BIT0
|
|
|
|
#define RTC_CTRL_PERIODIC_SEL_SHIFT 4
|
|
|
|
#define RTC_CTRL_PERIODIC_SEC 0x70
|
|
#define RTC_CTRL_PERIODIC_MIN 0x60
|
|
#define RTC_CTRL_PERIODIC_HOUR 0x50
|
|
#define RTC_CTRL_PERIODIC_DAY 0x40
|
|
#define RTC_CTRL_PERIODIC_MONTH 0x30
|
|
|
|
union rtc_date_u {
|
|
struct rtc_date_s date_s;
|
|
uint32_t date_raw;
|
|
};
|
|
|
|
union rtc_time_u {
|
|
struct rtc_time_s time_s;
|
|
uint32_t time_raw;
|
|
};
|
|
|
|
static const uint32_t per_int_table[] = {
|
|
[PERIODIC_MONTH_INT] = RTC_CTRL_PERIODIC_MONTH,
|
|
[PERIODIC_DAY_INT] = RTC_CTRL_PERIODIC_DAY,
|
|
[PERIODIC_HOUR_INT] = RTC_CTRL_PERIODIC_HOUR,
|
|
[PERIODIC_MIN_INT] = RTC_CTRL_PERIODIC_MIN,
|
|
[PERIODIC_SEC_INT] = RTC_CTRL_PERIODIC_SEC,
|
|
};
|
|
|
|
static const int days_of_month[] = {
|
|
0, // invalid
|
|
31, // Jan
|
|
28, // Feb
|
|
31, // Mar
|
|
30, // Apr
|
|
31, // May
|
|
30, // Jun
|
|
31, // Jul
|
|
31, // Aug
|
|
30, // Spt
|
|
31, // Oct
|
|
30, // Nov
|
|
31, // Dec
|
|
};
|
|
|
|
static const int days_to_month[] = {
|
|
0, // Jan
|
|
31, // Feb
|
|
28+31, // Mar
|
|
31+28+31, // Apr
|
|
30+31+28+31, // May
|
|
31+30+31+28+31, // Jun
|
|
30+31+30+31+28+31, // Jul
|
|
31+30+31+30+31+28+31, // Aug
|
|
31+31+30+31+30+31+28+31, // Spt
|
|
30+31+31+30+31+30+31+28+31, // Oct
|
|
31+30+31+31+30+31+30+31+28+31, // Nov
|
|
30+31+30+31+31+30+31+30+31+28+31, // Dec
|
|
31+30+31+30+31+31+30+31+30+31+28+31, // Year 365
|
|
};
|
|
|
|
static const char *weekdays[] = {
|
|
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
|
|
};
|
|
|
|
static struct rtc_date_s init_date = {
|
|
/* 11/07/2019 */
|
|
11,
|
|
7,
|
|
19,
|
|
20,
|
|
};
|
|
|
|
static struct rtc_time_s init_time = {
|
|
/* 07:11:00 Thu(4) */
|
|
0,
|
|
11,
|
|
7,
|
|
4,
|
|
};
|
|
|
|
static void rtc_rectify_date(struct rtc_date_s *date)
|
|
{
|
|
date->century %= CENTURY_PER_100;
|
|
date->year %= YEARS_PER_CENTURY;
|
|
|
|
if (date->month) // RTC valid month: 1-12
|
|
date->month--;
|
|
date->month %= MONTH_PER_YEAR;
|
|
date->month++;
|
|
|
|
if (date->date) // RTC valid date: 1-31
|
|
date->date--;
|
|
date->date %= MAX_DAYS_PER_MONTH;
|
|
date->date++;
|
|
}
|
|
|
|
static void rtc_rectify_time(struct rtc_time_s *time)
|
|
{
|
|
time->weekday %= DAYS_PER_WEEK;
|
|
time->hour %= HOURS_PER_DAY;
|
|
time->min %= MINS_PER_HOUR;
|
|
time->sec %= SECS_PER_MIN;
|
|
}
|
|
|
|
static void rtc_set_date(struct rtc_date_s *date)
|
|
{
|
|
union rtc_date_u *date_p;
|
|
|
|
rtc_rectify_date(date);
|
|
|
|
date_p = (union rtc_date_u *)date;
|
|
outw(RTC_REG_TIME2, date_p->date_raw);
|
|
dbg_msg("rtc_set_date: 0x%8.8x\n", date_p->date_raw);
|
|
}
|
|
|
|
static void rtc_get_date(struct rtc_date_s *date)
|
|
{
|
|
union rtc_date_u *date_p;
|
|
|
|
date_p = (union rtc_date_u *)date;
|
|
date_p->date_raw = inw(RTC_REG_TIME2);
|
|
}
|
|
|
|
static void rtc_set_time(struct rtc_time_s *time)
|
|
{
|
|
union rtc_time_u *time_p;
|
|
|
|
rtc_rectify_time(time);
|
|
|
|
time_p = (union rtc_time_u *)time;
|
|
outw(RTC_REG_TIME1, time_p->time_raw);
|
|
dbg_msg("rtc_set_time: 0x%8.8x\n", time_p->time_raw);
|
|
}
|
|
|
|
static void rtc_get_time(struct rtc_time_s *time)
|
|
{
|
|
union rtc_time_u *time_p;
|
|
uint32_t first_read, second_read;
|
|
|
|
time_p = (union rtc_time_u *)time;
|
|
|
|
/* Read twice to get a good/same reading */
|
|
do {
|
|
first_read = inw(RTC_REG_TIME1);
|
|
second_read = inw(RTC_REG_TIME1);
|
|
} while (first_read != second_read);
|
|
|
|
time_p->time_raw = second_read;
|
|
}
|
|
|
|
static void rtc_enable(void)
|
|
{
|
|
masked_outw(RTC_REG_CTRL, RTC_CTRL_EN, RTC_CTRL_EN);
|
|
do {
|
|
} while (!(inw(RTC_REG_CTRL) & RTC_CTRL_RTCEN_STS));
|
|
}
|
|
|
|
static void rtc_disable(void)
|
|
{
|
|
outw(RTC_REG_CTRL, 0); // clear all
|
|
do {
|
|
} while (inw(RTC_REG_CTRL) & RTC_CTRL_RTCEN_STS);
|
|
}
|
|
|
|
static void rtc_alm_enable(void)
|
|
{
|
|
masked_outw(RTC_REG_CTRL, RTC_CTRL_ALM_EN, RTC_CTRL_ALM_EN);
|
|
do {
|
|
} while (!(inw(RTC_REG_CTRL) & RTC_CTRL_ALMEN_STS));
|
|
}
|
|
|
|
static void rtc_alm_disable(void)
|
|
{
|
|
masked_outw(RTC_REG_CTRL, 0, RTC_CTRL_ALM_EN);
|
|
do {
|
|
} while (inw(RTC_REG_CTRL) & RTC_CTRL_ALMEN_STS);
|
|
}
|
|
|
|
void rtc_alarm_enable(enum alarm_type alm_type, void *param1, void *param2)
|
|
{
|
|
union rtc_time_u *time_p;
|
|
union rtc_date_u *date_p;
|
|
uint32_t tmp;
|
|
|
|
// disable hw first
|
|
rtc_alm_disable();
|
|
|
|
if (alm_type == ALARM_IN_SECS) {
|
|
struct rtc_time_s time;
|
|
struct rtc_date_s date;
|
|
uint32_t time_in_secs, carry_on;
|
|
|
|
rtc_get_date(&date);
|
|
rtc_get_time(&time);
|
|
time_in_secs = *(uint32_t *)param1;
|
|
|
|
// update seconds
|
|
tmp = time.sec + time_in_secs; // use u32 tmp to avoid overflow
|
|
time.sec = tmp % SECS_PER_MIN;
|
|
carry_on = tmp / SECS_PER_MIN;
|
|
if (carry_on) {
|
|
// update minutes
|
|
tmp = time.min + carry_on;
|
|
time.min = tmp % MINS_PER_HOUR;
|
|
carry_on = tmp / MINS_PER_HOUR;
|
|
if (carry_on) {
|
|
// update hours
|
|
tmp = time.hour + carry_on;
|
|
time.hour = tmp % HOURS_PER_DAY;
|
|
carry_on = tmp / HOURS_PER_DAY;
|
|
if (carry_on) {
|
|
// update weekday
|
|
tmp = time.weekday + carry_on;
|
|
time.weekday = tmp % DAYS_PER_WEEK;
|
|
|
|
// Now update date
|
|
tmp = date.date + carry_on;
|
|
date.date = ((tmp - 1) % days_of_month[date.month]) + 1;
|
|
if (tmp > days_of_month[date.month]) {
|
|
// update month: no more than 1 month in future
|
|
date.month = (date.month % MONTH_PER_YEAR) + 1;
|
|
if (date.month == 1)
|
|
// update year
|
|
date.year++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
date_p = (union rtc_date_u *)&date;
|
|
outw(RTC_REG_ALM2, date_p->date_raw);
|
|
|
|
time_p = (union rtc_time_u *)&time;
|
|
outw(RTC_REG_ALM1, time_p->time_raw);
|
|
|
|
// enable now
|
|
rtc_alm_enable();
|
|
} else if (alm_type == ALARM_IN_DATE_TIME) {
|
|
struct rtc_time_s *time;
|
|
struct rtc_date_s *date;
|
|
|
|
date = (struct rtc_date_s *)param1;
|
|
rtc_rectify_date(date);
|
|
date_p = (union rtc_date_u *)date;
|
|
outw(RTC_REG_ALM2, date_p->date_raw);
|
|
|
|
time = (struct rtc_time_s *)param2;
|
|
rtc_rectify_time(time);
|
|
time_p = (union rtc_time_u *)time;
|
|
outw(RTC_REG_ALM1, time_p->time_raw);
|
|
|
|
// enable now
|
|
rtc_alm_enable();
|
|
}
|
|
}
|
|
|
|
void rtc_alarm_disable(enum alarm_type alm_type)
|
|
{
|
|
rtc_alm_disable();
|
|
}
|
|
|
|
void rtc_periodic_enable(enum periodic_interrupt per_int_type)
|
|
{
|
|
uint32_t ctrl;
|
|
|
|
ctrl = per_int_table[per_int_type];
|
|
masked_outw(RTC_REG_CTRL, ctrl, RTC_CTRL_PERIODIC_SEL);
|
|
}
|
|
|
|
void rtc_current_time_info(void)
|
|
{
|
|
struct rtc_time_s time;
|
|
struct rtc_date_s date;
|
|
|
|
rtc_get_date(&date);
|
|
rtc_get_time(&time);
|
|
|
|
info_msg("RTC: (%s) %2.2d%2.2d/%2.2d/%2.2d - %2.2d:%2.2d:%2.2d\n",
|
|
weekdays[time.weekday],
|
|
date.century, date.year, date.month, date.date,
|
|
time.hour, time.min, time.sec);
|
|
}
|
|
|
|
void rtc_get_date_time_in_secs(uint32_t *date_time_in_secs)
|
|
{
|
|
struct rtc_time_s time;
|
|
struct rtc_date_s date;
|
|
uint32_t long_time;
|
|
|
|
rtc_get_date(&date);
|
|
rtc_get_time(&time);
|
|
|
|
if (0) {
|
|
// simple test
|
|
long_time = time.sec + time.min * SECS_PER_MIN + time.hour * SECS_PER_HOUR;
|
|
} else {
|
|
// TODO: leap year
|
|
long_time = time.sec + time.min * SECS_PER_MIN + time.hour * SECS_PER_HOUR
|
|
+ (date.date - 1) * SECS_PER_DAY + days_to_month[date.month - 1] * SECS_PER_DAY
|
|
+ date.year * (DAYS_PER_YEAR * SECS_PER_DAY);
|
|
}
|
|
|
|
if (date_time_in_secs != NULL)
|
|
*date_time_in_secs = long_time;
|
|
else
|
|
info_msg("Flat time: %d\n", long_time);
|
|
}
|
|
|
|
void rtc_get_date_time(struct rtc_date_s *date, struct rtc_time_s *time)
|
|
{
|
|
if (date != NULL)
|
|
rtc_get_date(date);
|
|
if (time != NULL)
|
|
rtc_get_time(time);
|
|
}
|
|
|
|
void rtc_init(struct rtc_time_s *time, struct rtc_date_s *date)
|
|
{
|
|
rtc_disable();
|
|
|
|
if (time == NULL)
|
|
rtc_set_time(&init_time);
|
|
else
|
|
rtc_set_time(time);
|
|
|
|
if (date == NULL)
|
|
rtc_set_date(&init_date);
|
|
else
|
|
rtc_set_date(date);
|
|
|
|
rtc_enable();
|
|
}
|