blob: 49cfeb854fd88217292364dbb644bb656af54c2a [file] [edit]
/*
* Copyright (c) 2026 Realtek Semiconductor Corp.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT realtek_ameba_rtc
/* Include <soc.h> before <ameba_soc.h> to avoid redefining unlikely() macro */
#include <soc.h>
#include <ameba_soc.h>
#include <zephyr/drivers/rtc.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(rtc_ameba, CONFIG_RTC_LOG_LEVEL);
/* RTC start time: 1st, Jan, 1900 */
#define RTC_YEAR_REF RTC_BASE_YEAR
/* struct tm start time: 1st, Jan, 1900 */
#define TM_YEAR_REF 1900
struct rtc_ameba_config {
uint32_t async_prescaler;
uint32_t sync_prescaler;
const struct device *clock_dev;
clock_control_subsys_t clock_subsys;
#if defined(CONFIG_RTC_ALARM)
void (*irq_configure)(void);
#endif
};
struct rtc_ameba_data {
struct k_mutex lock;
#if defined(CONFIG_RTC_ALARM)
atomic_t alarm_pending;
rtc_alarm_callback alarm_cb;
void *alarm_cbdata;
#endif
};
static const uint8_t dim[12] = {31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
/**
* @brief Judge whether a year is a leap year or not.
* @param year: Actual year - 1900.
* @return Result.
* @retval 1: This year is a leap year.
* @retval 0: This year is not a leap year.
*/
static inline bool is_leap_year(uint32_t year)
{
uint32_t full_year = year + RTC_BASE_YEAR; /* start from 1900 */
return (!(full_year % 4) && (full_year % 100)) || !(full_year % 400);
}
/**
* @brief Calculate total days in a specified month of a specified year.
* @param year: Actual year - 1900.
* @param month: Specified month, which can be 0~11.
* @note 0 represents January.
* @return Number of days in the month of the year.
*/
static uint8_t days_in_month(uint8_t month, int year)
{
uint8_t ret = dim[month % 12];
if (ret == 0) {
ret = is_leap_year(year + month / 12) ? 29 : 28;
}
return ret;
}
/**
* @brief Calculate day of the year according to year, month, and day of the month.
* @param year Actual year - 1900.
* @param mon Month (0~11), where 0 represents January.
* @param mday Day of the month (1~31).
* @return The day of the year (0~364 or 0~365 for leap years).
*/
static int rtc_calculate_yday(int year, int mon, int mday)
{
int yday = 0;
for (int i = 0; i < mon; i++) {
yday += days_in_month(i, year);
}
yday += mday - 1;
return yday;
}
/**
* @brief Calculate month and day of the month according to year and day of the year.
* @param year: Actual year - 1900.
* @param yday: Day of the year.
* @param mon: Pointer to the variable that stores month, which can be 0~11.
* @note 0 represents January.
* @param mday: Pointer to the variable that stores day of month, which can be 1~31.
* @retval none
*/
static void rtc_calculate_mday(int year, int yday, int *mon, int *mday)
{
int t_mon = -1, t_yday = yday + 1;
while (t_yday > 0) {
t_mon++;
t_yday -= days_in_month(t_mon, year);
}
*mon = t_mon;
*mday = t_yday + days_in_month(t_mon, year);
}
/**
* @brief Calculate the day of a week according to date.
* @param year: Actual year - 1900.
* @param mon: Month of the year, which can be 0~11.
* @note 0 represents January.
* @param mday: Day of the month.
* @param wday: Pointer to the variable that stores day of a week, which can be 0~6.
* @note 0 represents Sunday.
* @retval none
*/
static void rtc_calculate_wday(int year, int mon, int mday, int *wday)
{
int t_year = year + 1900, t_mon = mon + 1;
if (t_mon == 1 || t_mon == 2) {
t_year--;
t_mon += 12;
}
int c = t_year / 100;
int y = t_year % 100;
int week = (c / 4) - 2 * c + (y + y / 4) + (26 * (t_mon + 1) / 10) + mday - 1;
while (week < 0) {
week += 7;
}
week %= 7;
*wday = week;
}
static int rtc_ameba_configure(const struct device *dev)
{
const struct rtc_ameba_config *cfg = dev->config;
int err = 0;
uint32_t initialized = 0;
RTC_InitTypeDef rtc_initstruct;
RTC_StructInit(&rtc_initstruct);
rtc_initstruct.RTC_AsynchPrediv = cfg->async_prescaler;
rtc_initstruct.RTC_SynchPrediv = cfg->sync_prescaler;
rtc_initstruct.RTC_HourFormat =
RTC_HourFormat_24; /* force this hour fmt for time_t reason */
initialized = RTC_Init(&rtc_initstruct);
if (!initialized) {
LOG_ERR("rtc initial fail.");
err = -EIO;
}
return err;
}
static int rtc_ameba_init(const struct device *dev)
{
const struct rtc_ameba_config *cfg = dev->config;
struct rtc_ameba_data *data = dev->data;
int err = 0;
if (!device_is_ready(cfg->clock_dev)) {
LOG_ERR("clock control device not ready");
return -ENODEV;
}
/* Enable RTC bus clock */
if (clock_control_on(cfg->clock_dev, cfg->clock_subsys)) {
LOG_ERR("clock op failed");
return -EIO;
}
RTC_Enable(ENABLE);
k_mutex_init(&data->lock);
err = rtc_ameba_configure(dev);
#if defined(CONFIG_RTC_ALARM)
if (cfg->irq_configure != NULL) {
cfg->irq_configure();
}
#endif
return err;
}
static int rtc_ameba_set_time(const struct device *dev, const struct rtc_time *timeptr)
{
struct rtc_ameba_data *data = dev->data;
int err = 0;
RTC_TimeTypeDef rtc_timestruct;
uint32_t real_year = timeptr->tm_year + TM_YEAR_REF;
if (real_year < RTC_YEAR_REF) {
/* RTC does not support years before 1900 */
return -EINVAL;
}
if (timeptr->tm_wday == -1) {
/* day of the week is expected */
return -EINVAL;
}
/* Do not check following two members.
* For getting function, need to set designated Unknown value
* if (timeptr->tm_isdst != -1)
* if (!(timeptr->tm_nsec))
*/
err = k_mutex_lock(&data->lock, K_NO_WAIT);
if (err != 0) {
LOG_ERR("%s lock fail !!!", __func__);
return err;
}
rtc_timestruct.RTC_Days =
rtc_calculate_yday(timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday);
rtc_timestruct.RTC_H12_PMAM = RTC_H12_AM; /* cautious in zsdk */
rtc_timestruct.RTC_Year = timeptr->tm_year + RTC_BASE_YEAR;
rtc_timestruct.RTC_Hours = timeptr->tm_hour;
rtc_timestruct.RTC_Minutes = timeptr->tm_min;
rtc_timestruct.RTC_Seconds = timeptr->tm_sec;
RTC_SetTime(RTC_Format_BIN, &rtc_timestruct);
k_mutex_unlock(&data->lock);
return err;
}
static int rtc_ameba_get_time(const struct device *dev, struct rtc_time *timeptr)
{
struct rtc_ameba_data *data = dev->data;
int err = 0;
uint32_t ydays_thr;
RTC_TimeTypeDef rtc_timestruct;
err = k_mutex_lock(&data->lock, K_NO_WAIT);
if (err) {
return err;
}
/* step1: get hour, min, sec from RTC */
RTC_GetTime(RTC_Format_BIN, &rtc_timestruct);
timeptr->tm_sec = rtc_timestruct.RTC_Seconds;
timeptr->tm_min = rtc_timestruct.RTC_Minutes;
timeptr->tm_hour = rtc_timestruct.RTC_Hours;
timeptr->tm_yday = rtc_timestruct.RTC_Days;
timeptr->tm_year = rtc_timestruct.RTC_Year - RTC_BASE_YEAR; /* struct tm start from 1900 */
/* step2: convert to mon, mday */
rtc_calculate_mday(timeptr->tm_year, timeptr->tm_yday, &timeptr->tm_mon, &timeptr->tm_mday);
/* step3: convert to wday */
rtc_calculate_wday(timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, &timeptr->tm_wday);
/* step4: check and update year or not */
ydays_thr = (is_leap_year(timeptr->tm_year)) ? 366 : 365;
if (rtc_timestruct.RTC_Days > (ydays_thr - 1)) {
rtc_timestruct.RTC_Days -= ydays_thr;
rtc_timestruct.RTC_Year++;
timeptr->tm_mon = 0; /* base 0 [0, 11] */
timeptr->tm_mday = 1; /* base 1 [1, 31] */
timeptr->tm_yday = rtc_timestruct.RTC_Days;
timeptr->tm_year = rtc_timestruct.RTC_Year - RTC_BASE_YEAR;
RTC_SetTime(RTC_Format_BIN, &rtc_timestruct);
}
k_mutex_unlock(&data->lock);
timeptr->tm_isdst = -1;
timeptr->tm_nsec = 0;
return 0;
}
#if defined(CONFIG_RTC_ALARM)
static void rtc_ameba_alarm_isr(const struct device *dev)
{
struct rtc_ameba_data *data = dev->data;
/* clear alarm flag */
RTC_AlarmClear();
if (data->alarm_cb) {
data->alarm_cb(dev, 0, data->alarm_cbdata);
atomic_set(&data->alarm_pending, 0);
} else {
atomic_set(&data->alarm_pending, 1);
}
}
static int rtc_ameba_alarm_get_supported_fields(const struct device *dev, uint16_t id,
uint16_t *mask)
{
ARG_UNUSED(dev);
if (id != 0) {
return -EINVAL;
}
(*mask) = (RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE |
RTC_ALARM_TIME_MASK_HOUR | RTC_ALARM_TIME_MASK_YEARDAY);
return 0;
}
static bool rtc_ameba_validate_alarm_time(const struct rtc_time *timeptr, uint16_t mask)
{
uint16_t sprt_mask = 0;
rtc_ameba_alarm_get_supported_fields(NULL, 0, &sprt_mask);
if (sprt_mask & mask) {
if ((mask & RTC_ALARM_TIME_MASK_SECOND) &&
(timeptr->tm_sec < 0 || timeptr->tm_sec > 59)) {
return false;
}
if ((mask & RTC_ALARM_TIME_MASK_MINUTE) &&
(timeptr->tm_min < 0 || timeptr->tm_min > 59)) {
return false;
}
if ((mask & RTC_ALARM_TIME_MASK_HOUR) &&
(timeptr->tm_hour < 0 || timeptr->tm_hour > 23)) {
return false;
}
if ((mask & RTC_ALARM_TIME_MASK_YEARDAY) &&
(timeptr->tm_yday < 0 || timeptr->tm_yday > 365)) {
return false;
}
} else {
LOG_ERR("Current mask 0x%x not supported", mask);
return false;
}
return true;
}
static int rtc_ameba_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask,
const struct rtc_time *timeptr)
{
struct rtc_ameba_data *data = dev->data;
int ret = 0;
RTC_AlarmTypeDef rtc_alarmstruct;
uint32_t alarm_mask = 0U;
if (id != 0) {
return -EINVAL;
}
if ((mask > 0) && (timeptr == NULL)) {
LOG_ERR("Invalid Alarm Set Mask and timeptr !!!");
return -EINVAL;
}
/* Check time valid */
if (mask > 0) {
if (!rtc_ameba_validate_alarm_time(timeptr, mask)) {
return -EINVAL;
}
} else {
/* If the mask parameter is 0, the alarm will be disabled. */
RTC_AlarmCmd(DISABLE);
return 0;
}
ret = k_mutex_lock(&data->lock, K_NO_WAIT);
if (ret) {
return ret;
}
/* step1: Init Ameba alarm struct */
RTC_AlarmStructInit(&rtc_alarmstruct);
rtc_alarmstruct.RTC_AlarmMask = RTC_AlarmMask_All; /* fix for mask seconds unset above */
rtc_alarmstruct.RTC_Alarm2Mask = RTC_Alarm2Mask_Days;
/* step2: Set Ameba RTC_AlarmMask */
if (mask & RTC_ALARM_TIME_MASK_SECOND) {
rtc_alarmstruct.RTC_AlarmTime.RTC_Seconds = timeptr->tm_sec;
alarm_mask |= RTC_AlarmMask_Seconds;
}
if (mask & RTC_ALARM_TIME_MASK_MINUTE) {
rtc_alarmstruct.RTC_AlarmTime.RTC_Minutes = timeptr->tm_min;
alarm_mask |= RTC_AlarmMask_Minutes;
}
if (mask & RTC_ALARM_TIME_MASK_HOUR) {
rtc_alarmstruct.RTC_AlarmTime.RTC_Hours = timeptr->tm_hour;
alarm_mask |= RTC_AlarmMask_Hours;
}
/* Note: In Ameba, if the mask parameter is 0, the alarm will be enabled, which is contrary
* to zephyr file declarations The RTC alarm will trigger when all enabled fields of
* the alarm time match the RTC time.
*/
if (alarm_mask != 0) {
rtc_alarmstruct.RTC_AlarmMask &= (~alarm_mask);
}
/* step3: Set Ameba RTC_Alarm2Mask */
alarm_mask = 0U;
if (mask & RTC_ALARM_TIME_MASK_YEARDAY) {
rtc_alarmstruct.RTC_AlarmTime.RTC_Days = timeptr->tm_yday;
alarm_mask |= RTC_Alarm2Mask_Days;
rtc_alarmstruct.RTC_Alarm2Mask &= (~alarm_mask);
}
RTC_SetAlarm(RTC_Format_BIN, &rtc_alarmstruct);
RTC_AlarmCmd(ENABLE);
k_mutex_unlock(&data->lock);
return 0;
}
static int rtc_ameba_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask,
struct rtc_time *timeptr)
{
struct rtc_ameba_data *data = dev->data;
int ret = 0;
RTC_AlarmTypeDef rtc_alarmstruct;
if (id != 0) {
return -EINVAL;
}
if (timeptr == NULL) {
LOG_ERR("Invalid Get Alarm timeptr");
return -EINVAL;
}
memset(timeptr, 0U, sizeof(*timeptr));
*mask = 0U;
ret = k_mutex_lock(&data->lock, K_NO_WAIT);
if (ret) {
return ret;
}
/* step1: read RTC reg */
RTC_GetAlarm(RTC_Format_BIN, &rtc_alarmstruct);
/* step2: parse RTC_AlarmMask */
if (!(rtc_alarmstruct.RTC_AlarmMask & RTC_AlarmMask_Seconds)) {
*mask |= RTC_ALARM_TIME_MASK_SECOND;
timeptr->tm_sec = rtc_alarmstruct.RTC_AlarmTime.RTC_Seconds;
}
if (!(rtc_alarmstruct.RTC_AlarmMask & RTC_AlarmMask_Minutes)) {
*mask |= RTC_ALARM_TIME_MASK_MINUTE;
timeptr->tm_min = rtc_alarmstruct.RTC_AlarmTime.RTC_Minutes;
}
if (!(rtc_alarmstruct.RTC_AlarmMask & RTC_AlarmMask_Hours)) {
*mask |= RTC_ALARM_TIME_MASK_HOUR;
timeptr->tm_hour = rtc_alarmstruct.RTC_AlarmTime.RTC_Hours;
}
/* step3: parse RTC_Alarm2Mask */
if (!(rtc_alarmstruct.RTC_Alarm2Mask & RTC_Alarm2Mask_Days)) {
*mask |= RTC_ALARM_TIME_MASK_YEARDAY;
timeptr->tm_yday = rtc_alarmstruct.RTC_AlarmTime.RTC_Days;
}
k_mutex_unlock(&data->lock);
return 0;
}
static int rtc_ameba_alarm_is_pending(const struct device *dev, uint16_t id)
{
struct rtc_ameba_data *data = dev->data;
if (id != 0) {
return -EINVAL;
}
int pending = atomic_set(&data->alarm_pending, 0);
return pending ? 1 : 0;
}
static int rtc_ameba_alarm_set_callback(const struct device *dev, uint16_t id,
rtc_alarm_callback callback, void *user_data)
{
struct rtc_ameba_data *data = dev->data;
int ret = 0;
if (id != 0) {
return -EINVAL;
}
ret = k_mutex_lock(&data->lock, K_NO_WAIT);
if (ret) {
return ret;
}
data->alarm_cb = callback;
data->alarm_cbdata = user_data;
/* Note: This enable alarm cb but not alarm, refer to doc.
* The alarm will remain enabled until manually disabled using rtc_alarm_set_time().
*/
k_mutex_unlock(&data->lock);
return 0;
}
#endif
static DEVICE_API(rtc, rtc_ameba_driver_api) = {
.set_time = rtc_ameba_set_time,
.get_time = rtc_ameba_get_time,
#if defined(CONFIG_RTC_ALARM) || defined(__DOXYGEN__)
.alarm_get_supported_fields = rtc_ameba_alarm_get_supported_fields,
.alarm_set_time = rtc_ameba_alarm_set_time,
.alarm_get_time = rtc_ameba_alarm_get_time,
.alarm_is_pending = rtc_ameba_alarm_is_pending,
.alarm_set_callback = rtc_ameba_alarm_set_callback,
#endif /* CONFIG_RTC_ALARM */
};
#if defined(CONFIG_RTC_ALARM)
static void rtc_ameba_irq_configure(void)
{
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), rtc_ameba_alarm_isr,
DEVICE_DT_INST_GET(0), 0);
irq_enable(DT_INST_IRQN(0));
}
#endif
static const struct rtc_ameba_config rtc_config = {
.async_prescaler = DT_INST_PROP(0, async_prescaler),
.sync_prescaler = DT_INST_PROP(0, sync_prescaler),
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(0)),
.clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(0, idx),
#ifdef CONFIG_RTC_ALARM
.irq_configure = rtc_ameba_irq_configure,
#endif
};
static struct rtc_ameba_data rtc_data;
DEVICE_DT_INST_DEFINE(0, &rtc_ameba_init, NULL, &rtc_data, &rtc_config, PRE_KERNEL_1,
CONFIG_RTC_INIT_PRIORITY, &rtc_ameba_driver_api);