| /* |
| * Copyright (c) 2024 Nuvoton Technology Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nuvoton_numaker_rtc |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/device.h> |
| #include <zephyr/irq.h> |
| #include <zephyr/drivers/rtc.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/spinlock.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/clock_control/clock_control_numaker.h> |
| #include "rtc_utils.h" |
| |
| LOG_MODULE_REGISTER(rtc_numaker, CONFIG_RTC_LOG_LEVEL); |
| |
| /* RTC support 2000 ~ 2099 */ |
| #define NVT_RTC_YEAR_MIN 2000U |
| #define NVT_RTC_YEAR_MAX 2099U |
| /* struct tm start time: 1st, Jan, 1900 */ |
| #define TM_YEAR_REF 1900U |
| |
| #define NVT_TIME_SCALE RTC_CLOCK_24 |
| #define NVT_ALARM_MSK 0x3fU |
| #define NVT_ALARM_UNIT_MSK 0x03U |
| |
| struct rtc_numaker_config { |
| RTC_T *rtc_base; |
| uint32_t clk_modidx; |
| const struct device *clk_dev; |
| uint32_t oscillator; |
| }; |
| |
| struct rtc_numaker_data { |
| struct k_spinlock lock; |
| #ifdef CONFIG_RTC_ALARM |
| rtc_alarm_callback alarm_callback; |
| void *alarm_user_data; |
| bool alarm_pending; |
| #endif /* CONFIG_RTC_ALARM */ |
| }; |
| |
| struct rtc_numaker_time { |
| uint32_t year; /* Year value */ |
| uint32_t month; /* Month value */ |
| uint32_t day; /* Day value */ |
| uint32_t day_of_week; /* Day of week value */ |
| uint32_t hour; /* Hour value */ |
| uint32_t minute; /* Minute value */ |
| uint32_t second; /* Second value */ |
| uint32_t time_scale; /* 12-Hour, 24-Hour */ |
| uint32_t am_pm; /* Only Time Scale select 12-hr used */ |
| }; |
| |
| static int rtc_numaker_set_time(const struct device *dev, const struct rtc_time *timeptr) |
| { |
| struct rtc_numaker_time curr_time; |
| struct rtc_numaker_data *data = dev->data; |
| uint32_t real_year = timeptr->tm_year + TM_YEAR_REF; |
| #ifdef CONFIG_RTC_ALARM |
| const struct rtc_numaker_config *config = dev->config; |
| RTC_T *rtc_base = config->rtc_base; |
| #endif |
| |
| if (real_year < NVT_RTC_YEAR_MIN || real_year > NVT_RTC_YEAR_MAX) { |
| /* RTC can't support years out of 2000 ~ 2099 */ |
| return -EINVAL; |
| } |
| |
| if (timeptr->tm_wday == -1) { |
| return -EINVAL; |
| } |
| |
| curr_time.year = real_year; |
| curr_time.month = timeptr->tm_mon + 1; |
| curr_time.day = timeptr->tm_mday; |
| curr_time.hour = timeptr->tm_hour; |
| curr_time.minute = timeptr->tm_min; |
| curr_time.second = timeptr->tm_sec; |
| curr_time.day_of_week = timeptr->tm_wday; |
| curr_time.time_scale = NVT_TIME_SCALE; |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| RTC_SetDateAndTime((S_RTC_TIME_DATA_T *)&curr_time); |
| |
| #ifdef CONFIG_RTC_ALARM |
| /* Restore RTC alarm mask */ |
| rtc_base->CAMSK = rtc_base->SPR[1]; |
| rtc_base->TAMSK = rtc_base->SPR[2]; |
| #endif |
| k_spin_unlock(&data->lock, key); |
| |
| return 0; |
| } |
| |
| static int rtc_numaker_get_time(const struct device *dev, struct rtc_time *timeptr) |
| { |
| struct rtc_numaker_data *data = dev->data; |
| struct rtc_numaker_time curr_time; |
| |
| curr_time.time_scale = NVT_TIME_SCALE; |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| RTC_GetDateAndTime((S_RTC_TIME_DATA_T *)&curr_time); |
| k_spin_unlock(&data->lock, key); |
| |
| timeptr->tm_year = curr_time.year - TM_YEAR_REF; |
| timeptr->tm_mon = curr_time.month - 1; |
| timeptr->tm_mday = curr_time.day; |
| timeptr->tm_wday = curr_time.day_of_week; |
| |
| timeptr->tm_hour = curr_time.hour; |
| timeptr->tm_min = curr_time.minute; |
| timeptr->tm_sec = curr_time.second; |
| timeptr->tm_nsec = 0; |
| |
| /* unknown values */ |
| timeptr->tm_yday = -1; |
| timeptr->tm_isdst = -1; |
| |
| return 0; |
| } |
| |
| static void rtc_numaker_isr(const struct device *dev) |
| { |
| const struct rtc_numaker_config *config = dev->config; |
| RTC_T *rtc_base = config->rtc_base; |
| uint32_t int_status; |
| #ifdef CONFIG_RTC_ALARM |
| struct rtc_numaker_data *data = dev->data; |
| #endif |
| |
| int_status = rtc_base->INTSTS; |
| if (int_status & RTC_INTSTS_TICKIF_Msk) { |
| /* Clear RTC Tick interrupt flag */ |
| rtc_base->INTSTS = RTC_INTSTS_TICKIF_Msk; |
| } |
| |
| #ifdef CONFIG_RTC_ALARM |
| if (int_status & RTC_INTSTS_ALMIF_Msk) { |
| rtc_alarm_callback callback; |
| void *user_data; |
| |
| /* Clear RTC Alarm interrupt flag */ |
| rtc_base->INTSTS = RTC_INTSTS_ALMIF_Msk; |
| rtc_base->CAMSK = 0x00; |
| rtc_base->TAMSK = 0x00; |
| callback = data->alarm_callback; |
| user_data = data->alarm_user_data; |
| data->alarm_pending = callback ? false : true; |
| |
| if (callback != NULL) { |
| callback(dev, 0, user_data); |
| } |
| } |
| #endif /* CONFIG_RTC_ALARM */ |
| } |
| |
| #ifdef CONFIG_RTC_ALARM |
| static int rtc_numaker_alarm_get_supported_fields(const struct device *dev, uint16_t id, |
| uint16_t *mask) |
| { |
| ARG_UNUSED(dev); |
| ARG_UNUSED(id); |
| |
| *mask = RTC_ALARM_TIME_MASK_SECOND |
| | RTC_ALARM_TIME_MASK_MINUTE |
| | RTC_ALARM_TIME_MASK_HOUR |
| | RTC_ALARM_TIME_MASK_MONTHDAY |
| | RTC_ALARM_TIME_MASK_MONTH |
| | RTC_ALARM_TIME_MASK_YEAR; |
| |
| return 0; |
| } |
| |
| static int rtc_numaker_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask, |
| const struct rtc_time *timeptr) |
| { |
| struct rtc_numaker_data *data = dev->data; |
| const struct rtc_numaker_config *config = dev->config; |
| RTC_T *rtc_base = config->rtc_base; |
| uint16_t mask_capable; |
| struct rtc_numaker_time alarm_time; |
| |
| rtc_numaker_alarm_get_supported_fields(dev, 0, &mask_capable); |
| |
| if ((id != 0)) { |
| return -EINVAL; |
| } |
| |
| if ((mask != 0) && (timeptr == NULL)) { |
| return -EINVAL; |
| } |
| |
| if (mask & ~mask_capable) { |
| return -EINVAL; |
| } |
| |
| if (rtc_utils_validate_rtc_time(timeptr, mask) == false) { |
| return -EINVAL; |
| } |
| |
| k_spinlock_key_t key = k_spin_lock(&data->lock); |
| |
| irq_disable(DT_INST_IRQN(0)); |
| if ((mask == 0) || (timeptr == NULL)) { |
| /* Disable the alarm */ |
| rtc_base->SPR[0] = mask; |
| rtc_base->SPR[1] = 0x00; |
| rtc_base->SPR[2] = 0x00; |
| irq_enable(DT_INST_IRQN(0)); |
| k_spin_unlock(&data->lock, key); |
| rtc_base->CAMSK = rtc_base->SPR[1]; |
| rtc_base->TAMSK = rtc_base->SPR[2]; |
| /* Disable RTC Alarm Interrupt */ |
| RTC_DisableInt(RTC_INTEN_ALMIEN_Msk); |
| return 0; |
| } |
| |
| alarm_time.time_scale = NVT_TIME_SCALE; |
| RTC_GetDateAndTime((S_RTC_TIME_DATA_T *)&alarm_time); |
| |
| /* Reset RTC alarm mask of camsk & tamsk */ |
| uint32_t camsk = NVT_ALARM_MSK; |
| uint32_t tamsk = NVT_ALARM_MSK; |
| |
| /* Set H/W care to match bits */ |
| if (mask & RTC_ALARM_TIME_MASK_YEAR) { |
| alarm_time.year = timeptr->tm_year + TM_YEAR_REF; |
| camsk &= ~(NVT_ALARM_UNIT_MSK << RTC_CAMSK_MYEAR_Pos); |
| } |
| if (mask & RTC_ALARM_TIME_MASK_MONTH) { |
| alarm_time.month = timeptr->tm_mon + 1; |
| camsk &= ~(NVT_ALARM_UNIT_MSK << RTC_CAMSK_MMON_Pos); |
| } |
| if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) { |
| alarm_time.day = timeptr->tm_mday; |
| camsk &= ~(NVT_ALARM_UNIT_MSK << RTC_CAMSK_MDAY_Pos); |
| } |
| if (mask & RTC_ALARM_TIME_MASK_HOUR) { |
| alarm_time.hour = timeptr->tm_hour; |
| tamsk &= ~(NVT_ALARM_UNIT_MSK << RTC_TAMSK_MHR_Pos); |
| } |
| if (mask & RTC_ALARM_TIME_MASK_MINUTE) { |
| alarm_time.minute = timeptr->tm_min; |
| tamsk &= ~(NVT_ALARM_UNIT_MSK << RTC_TAMSK_MMIN_Pos); |
| } |
| if (mask & RTC_ALARM_TIME_MASK_SECOND) { |
| alarm_time.second = timeptr->tm_sec; |
| tamsk &= ~(NVT_ALARM_UNIT_MSK << RTC_TAMSK_MSEC_Pos); |
| } |
| |
| /* Disable RTC Alarm Interrupt */ |
| RTC_DisableInt(RTC_INTEN_ALMIEN_Msk); |
| |
| /* Set the alarm time */ |
| RTC_SetAlarmDateAndTime((S_RTC_TIME_DATA_T *)&alarm_time); |
| |
| /* Clear RTC alarm interrupt flag */ |
| RTC_CLEAR_ALARM_INT_FLAG(); |
| |
| rtc_base->SPR[0] = mask; |
| rtc_base->SPR[1] = camsk; |
| rtc_base->SPR[2] = tamsk; |
| |
| rtc_base->CAMSK = rtc_base->SPR[1]; |
| rtc_base->TAMSK = rtc_base->SPR[2]; |
| |
| k_spin_unlock(&data->lock, key); |
| irq_enable(DT_INST_IRQN(0)); |
| |
| /* Enable RTC Alarm Interrupt */ |
| RTC_EnableInt(RTC_INTEN_ALMIEN_Msk); |
| |
| return 0; |
| } |
| |
| static int rtc_numaker_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask, |
| struct rtc_time *timeptr) |
| { |
| struct rtc_numaker_data *data = dev->data; |
| const struct rtc_numaker_config *config = dev->config; |
| RTC_T *rtc_base = config->rtc_base; |
| struct rtc_numaker_time alarm_time; |
| |
| if ((id != 0) || (mask == NULL) || (timeptr == NULL)) { |
| return -EINVAL; |
| } |
| |
| alarm_time.time_scale = NVT_TIME_SCALE; |
| |
| K_SPINLOCK(&data->lock) { |
| RTC_GetAlarmDateAndTime((S_RTC_TIME_DATA_T *)&alarm_time); |
| } |
| |
| *mask = rtc_base->SPR[0]; |
| if (*mask & RTC_ALARM_TIME_MASK_YEAR) { |
| timeptr->tm_year = alarm_time.year - TM_YEAR_REF; |
| } |
| if (*mask & RTC_ALARM_TIME_MASK_MONTH) { |
| timeptr->tm_mon = alarm_time.month - 1; |
| } |
| if (*mask & RTC_ALARM_TIME_MASK_MONTHDAY) { |
| timeptr->tm_mday = alarm_time.day; |
| } |
| if (*mask & RTC_ALARM_TIME_MASK_HOUR) { |
| timeptr->tm_hour = alarm_time.hour; |
| } |
| if (*mask & RTC_ALARM_TIME_MASK_MINUTE) { |
| timeptr->tm_min = alarm_time.minute; |
| } |
| if (*mask & RTC_ALARM_TIME_MASK_SECOND) { |
| timeptr->tm_sec = alarm_time.second; |
| } |
| |
| return 0; |
| } |
| |
| static int rtc_numaker_alarm_is_pending(const struct device *dev, uint16_t id) |
| { |
| struct rtc_numaker_data *data = dev->data; |
| int ret; |
| |
| if (id != 0) { |
| return -EINVAL; |
| } |
| |
| K_SPINLOCK(&data->lock) { |
| ret = data->alarm_pending ? 1 : 0; |
| data->alarm_pending = false; |
| } |
| |
| return ret; |
| } |
| |
| static int rtc_numaker_alarm_set_callback(const struct device *dev, uint16_t id, |
| rtc_alarm_callback callback, void *user_data) |
| { |
| struct rtc_numaker_data *data = dev->data; |
| |
| if (id != 0) { |
| return -EINVAL; |
| } |
| |
| K_SPINLOCK(&data->lock) { |
| irq_disable(DT_INST_IRQN(0)); |
| data->alarm_callback = callback; |
| data->alarm_user_data = user_data; |
| if ((callback == NULL) && (user_data == NULL)) { |
| /* Disable RTC Alarm Interrupt */ |
| RTC_DisableInt(RTC_INTEN_ALMIEN_Msk); |
| } |
| irq_enable(DT_INST_IRQN(0)); |
| } |
| |
| return 0; |
| } |
| #endif /* CONFIG_RTC_ALARM */ |
| |
| static const struct rtc_driver_api rtc_numaker_driver_api = { |
| .set_time = rtc_numaker_set_time, |
| .get_time = rtc_numaker_get_time, |
| #ifdef CONFIG_RTC_ALARM |
| .alarm_get_supported_fields = rtc_numaker_alarm_get_supported_fields, |
| .alarm_set_time = rtc_numaker_alarm_set_time, |
| .alarm_get_time = rtc_numaker_alarm_get_time, |
| .alarm_is_pending = rtc_numaker_alarm_is_pending, |
| .alarm_set_callback = rtc_numaker_alarm_set_callback, |
| #endif /* CONFIG_RTC_ALARM */ |
| }; |
| |
| static int rtc_numaker_init(const struct device *dev) |
| { |
| const struct rtc_numaker_config *cfg = dev->config; |
| struct numaker_scc_subsys scc_subsys; |
| RTC_T *rtc_base = cfg->rtc_base; |
| int err; |
| |
| /* CLK controller */ |
| memset(&scc_subsys, 0x00, sizeof(scc_subsys)); |
| scc_subsys.subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC; |
| scc_subsys.pcc.clk_modidx = cfg->clk_modidx; |
| |
| SYS_UnlockReg(); |
| |
| /* CLK_EnableModuleClock */ |
| err = clock_control_on(cfg->clk_dev, (clock_control_subsys_t)&scc_subsys); |
| if (err != 0) { |
| goto done; |
| } |
| |
| RTC_SetClockSource(cfg->oscillator); |
| /* Enable spare registers */ |
| rtc_base->SPRCTL = RTC_SPRCTL_SPRRWEN_Msk; |
| |
| irq_disable(DT_INST_IRQN(0)); |
| |
| IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), rtc_numaker_isr, |
| DEVICE_DT_INST_GET(0), 0); |
| |
| irq_enable(DT_INST_IRQN(0)); |
| err = RTC_Open(0); |
| |
| done: |
| SYS_LockReg(); |
| return err; |
| } |
| |
| static struct rtc_numaker_data rtc_data; |
| |
| /* Set config based on DTS */ |
| static const struct rtc_numaker_config rtc_config = { |
| .rtc_base = (RTC_T *)DT_INST_REG_ADDR(0), |
| .clk_modidx = DT_INST_CLOCKS_CELL(0, clock_module_index), |
| .clk_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(0))), |
| .oscillator = DT_ENUM_IDX(DT_NODELABEL(rtc), oscillator), |
| }; |
| |
| DEVICE_DT_INST_DEFINE(0, &rtc_numaker_init, NULL, &rtc_data, &rtc_config, PRE_KERNEL_1, |
| CONFIG_RTC_INIT_PRIORITY, &rtc_numaker_driver_api); |