diff --git a/drivers/rtc/CMakeLists.txt b/drivers/rtc/CMakeLists.txt index 26ea1a5656..85ff98320d 100644 --- a/drivers/rtc/CMakeLists.txt +++ b/drivers/rtc/CMakeLists.txt @@ -20,3 +20,4 @@ zephyr_library_sources_ifdef(CONFIG_RTC_FAKE rtc_fake.c) zephyr_library_sources_ifdef(CONFIG_RTC_SMARTBOND rtc_smartbond.c) zephyr_library_sources_ifdef(CONFIG_RTC_ATMEL_SAM rtc_sam.c) zephyr_library_sources_ifdef(CONFIG_RTC_RPI_PICO rtc_rpi_pico.c) +zephyr_library_sources_ifdef(CONFIG_RTC_NUMAKER rtc_numaker.c) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 3b44235b75..46bd2562dd 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -52,5 +52,6 @@ source "drivers/rtc/Kconfig.rpi_pico" source "drivers/rtc/Kconfig.sam" source "drivers/rtc/Kconfig.smartbond" source "drivers/rtc/Kconfig.stm32" +source "drivers/rtc/Kconfig.numaker" endif # RTC diff --git a/drivers/rtc/Kconfig.numaker b/drivers/rtc/Kconfig.numaker new file mode 100644 index 0000000000..5e55f6a108 --- /dev/null +++ b/drivers/rtc/Kconfig.numaker @@ -0,0 +1,14 @@ +# NUMAKER RTC Driver configuration options + +# Copyright (c) 2024 Nuvoton Technology Corporation. +# SPDX-License-Identifier: Apache-2.0 + +config RTC_NUMAKER + bool "Nuvoton NuMaker MCU RTC driver" + default y + select HAS_NUMAKER_RTC + depends on DT_HAS_NUVOTON_NUMAKER_RTC_ENABLED + help + This option enables the RTC driver for Nuvoton NuMaker family of + processors. + Say y if you wish to enable NuMaker RTC. diff --git a/drivers/rtc/rtc_numaker.c b/drivers/rtc/rtc_numaker.c new file mode 100644 index 0000000000..4356a76eef --- /dev/null +++ b/drivers/rtc/rtc_numaker.c @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2024 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nuvoton_numaker_rtc + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); diff --git a/dts/arm/nuvoton/m46x.dtsi b/dts/arm/nuvoton/m46x.dtsi index 1d0e15a61d..f89523c6a8 100644 --- a/dts/arm/nuvoton/m46x.dtsi +++ b/dts/arm/nuvoton/m46x.dtsi @@ -18,6 +18,10 @@ zephyr,flash-controller = &fmc; }; + aliases { + rtc = &rtc; + }; + cpus { #address-cells = <1>; #size-cells = <0>; @@ -46,7 +50,7 @@ reg = <0x40000200 0x100>; #clock-cells = <0>; /* hxt = "enable"; */ - /* lxt = "enable"; */ + lxt = "enable"; clk-pclkdiv = <(NUMAKER_CLK_PCLKDIV_APB0DIV_DIV2 | NUMAKER_CLK_PCLKDIV_APB1DIV_DIV2)>; core-clock = <200000000>; @@ -616,6 +620,15 @@ clocks = <&pcc NUMAKER_WWDT_MODULE NUMAKER_CLK_CLKSEL1_WWDTSEL_LIRC 0>; status = "disabled"; }; + + rtc: rtc@40041000 { + compatible = "nuvoton,numaker-rtc"; + reg = <0x40041000 0x138>; + interrupts = <6 0>; + oscillator = "lxt"; + clocks = <&pcc NUMAKER_RTC_MODULE 0 0>; + alarms-count = <1>; + }; }; }; diff --git a/dts/bindings/rtc/nuvoton,numaker-rtc.yaml b/dts/bindings/rtc/nuvoton,numaker-rtc.yaml new file mode 100644 index 0000000000..720cff1ab5 --- /dev/null +++ b/dts/bindings/rtc/nuvoton,numaker-rtc.yaml @@ -0,0 +1,27 @@ +# Copyright (c) 2024 Nuvoton Technology Corporation. +# SPDX-License-Identifier: Apache-2.0 + +description: Nuvoton, NuMaker RTC controller + +compatible: "nuvoton,numaker-rtc" + +include: rtc-device.yaml + +properties: + reg: + required: true + + interrupts: + required: true + + clocks: + required: true + + oscillator: + type: string + description: | + Specify RTC oscillator source + enum: + - "lxt" + - "lirc" + - "lirc32k" diff --git a/modules/Kconfig.nuvoton b/modules/Kconfig.nuvoton index 33dabc67f8..b976536035 100644 --- a/modules/Kconfig.nuvoton +++ b/modules/Kconfig.nuvoton @@ -75,4 +75,8 @@ menu "Nuvoton NuMaker drivers" bool "NuMaker RMC" help Enable Nuvoton RMC HAL module driver + config HAS_NUMAKER_RTC + bool "NuMaker RTC" + help + Enable Nuvoton RTC HAL module driver endmenu diff --git a/west.yml b/west.yml index 5f51067481..30e2269aae 100644 --- a/west.yml +++ b/west.yml @@ -188,7 +188,7 @@ manifest: groups: - hal - name: hal_nuvoton - revision: 34efb92e37bd07043a2cab7fff847d8443d930f9 + revision: ab342e6915bf7bff2203a20985754f6dbdb5343d path: modules/hal/nuvoton groups: - hal