diff --git a/cpu/qn908x/Kconfig b/cpu/qn908x/Kconfig index 9fbf36d8cb..5eaee65f09 100644 --- a/cpu/qn908x/Kconfig +++ b/cpu/qn908x/Kconfig @@ -13,6 +13,7 @@ config CPU_FAM_QN908X select HAS_PERIPH_CPUID select HAS_PERIPH_GPIO select HAS_PERIPH_GPIO_IRQ + select HAS_PERIPH_RTC select HAS_PERIPH_WDT select HAS_PERIPH_WDT_CB diff --git a/cpu/qn908x/Makefile.features b/cpu/qn908x/Makefile.features index 9bc70e8433..8a1419cdfe 100644 --- a/cpu/qn908x/Makefile.features +++ b/cpu/qn908x/Makefile.features @@ -4,6 +4,7 @@ CPU_FAM = qn908x FEATURES_PROVIDED += cortexm_mpu FEATURES_PROVIDED += periph_cpuid FEATURES_PROVIDED += periph_gpio periph_gpio_irq +FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_wdt periph_wdt_cb include $(RIOTCPU)/cortexm_common/Makefile.features diff --git a/cpu/qn908x/doc.txt b/cpu/qn908x/doc.txt index e574b6736b..192230bdea 100644 --- a/cpu/qn908x/doc.txt +++ b/cpu/qn908x/doc.txt @@ -50,6 +50,27 @@ SCT blocks between pwm and timer functions. #define TIMER_NUMOF 4 +@defgroup cpu_qn908x_rtc NXP QN908x Real-Time-Clock (RTC) +@ingroup cpu_qn908x +@brief NXP QN908x RTC driver + +The RTC block in the QN908x can be driven by the external 32.768 kHz crystal or +by the internal 32 kHz RCO oscillator clock, whichever is selected as the +`CLK_32K` clock source. The RTC has an internal "second counter" calibrated +depending on the frequency of the clock source selected at the time the RTC +clock is initialized by calling @ref rtc_init. + +The RTC function in this cpu doesn't have a match against a target value to +generate an interrupt like the timer peripheral, instead, the alarm function in +the rtc.h interface is implemented by an interrupt generated every second which +checks the target value in software. Keep in mind that while the RTC can operate +while the cpu is the power-down 0 mode, using the alarm functionality during +that time means that the cpu will wake up every second for a brief moment, +potentially impacting the power consumption. + +No RTC-specific configuration is necessary. + + @defgroup cpu_qn908x_uart NXP QN908x UART @ingroup cpu_qn908x @brief NXP QN908x UART driver diff --git a/cpu/qn908x/periph/rtc.c b/cpu/qn908x/periph/rtc.c new file mode 100644 index 0000000000..11b948e3b6 --- /dev/null +++ b/cpu/qn908x/periph/rtc.c @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2020 iosabi + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_qn908x + * @ingroup drivers_periph_rtc + * + * @{ + * + * @file + * @brief Low-level Real-Time Clock (RTC) driver implementation + * + * @author iosabi + * + * @} + */ + + +#include + +#include "cpu.h" +#include "board.h" +#include "periph_conf.h" +#include "periph/rtc.h" + +#include "vendor/drivers/fsl_clock.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + + +/* Callback context. */ +typedef struct { + rtc_alarm_cb_t cb; + void *arg; + uint32_t alarm; +} rtc_ctx_t; + +static rtc_ctx_t rtc_ctx = { NULL, NULL, 0 }; + +void rtc_init(void) +{ + DEBUG("rtc_init(), rtc=%" PRIu32 "\n", RTC->SEC); + /* The RTC is really meant to be using the RCO32K at 32KHz. However, if the + * 32K clock source is set to the external XTAL at 32.768 KHz we can use a + * calibration parameter to correct for this: + * ppm = (32000 / 32768 - 1) * (1 << 20) = -24576 + */ +#if CONFIG_CPU_CLK_32K_RCO + /* 32000 Hz, no need to use the calibration. */ + RTC->CTRL &= ~RTC_CTRL_CAL_EN_MASK; +#elif CONFIG_CPU_CLK_32K_XTAL + /* 32768 Hz, use -24576 ppm correction. */ + /* Positive ppm values would have the RTC_CAL_DIR_MASK set, but this is + * negative. */ + RTC->CAL = RTC_CAL_PPM(24576); + RTC->CTRL |= RTC_CTRL_CAL_EN_MASK; +#else +#error "One of the CONFIG_CPU_CLK_32K_* must be set." +#endif + /* RTC clock (BIV) starts enabled after reset anyway. */ + CLOCK_EnableClock(kCLOCK_Biv); + + RTC->CTRL &= ~RTC_CTRL_SEC_INT_EN_MASK; + + /* We only use the RTC_SEC_IRQ which triggers every second if enabled by the + * alarm. */ + NVIC_EnableIRQ(RTC_SEC_IRQn); +} + +int rtc_set_time(struct tm *time) +{ + uint32_t ts = rtc_mktime(time); + + DEBUG("rtc_set_time(%" PRIu32 ")\n", ts); + /* Writing 1 to CFG in CTRL register sets the CNT 0 timer to 0, resetting + * the fractional part of the second, meaning that "SEC" is a round number + * of second when this instruction executes. */ + RTC->CTRL |= RTC_CTRL_CFG_MASK; + RTC->SEC = ts; + while (RTC->STATUS & + (RTC_STATUS_SEC_SYNC_MASK | RTC_STATUS_CTRL_SYNC_MASK)) {} + return 0; +} + +int rtc_get_time(struct tm *time) +{ + uint32_t ts = RTC->SEC; + + DEBUG("rtc_get_time() -> %" PRIu32 "\n", ts); + rtc_localtime(ts, time); + return 0; +} + +int rtc_set_alarm(struct tm *time, rtc_alarm_cb_t cb, void *arg) +{ + uint32_t ts = rtc_mktime(time); + + DEBUG("rtc_set_alarm(%" PRIu32 ", %p, %p)\n", ts, cb, arg); + + if (ts <= RTC->SEC) { + /* The requested time is in the past at the time of executing this + * instruction, so we return invalid time. */ + return -2; + } + + /* If the requested time arrives (SEC_INT should have fired) before we get + * to set the RTC_CTRL_SEC_INT_EN_MASK mask a few instruction below, the + * alarm will be 1 second late. */ + rtc_ctx.cb = cb; + rtc_ctx.arg = arg; + rtc_ctx.alarm = ts; + + RTC->CTRL |= RTC_CTRL_SEC_INT_EN_MASK; + /* Wait until the CTRL_SEC is synced. */ + while (RTC->STATUS & RTC_STATUS_CTRL_SYNC_MASK) {} + + return 0; +} + +int rtc_get_alarm(struct tm *time) +{ + DEBUG("rtc_clear_alarm() -> %" PRIu32 "\n", rtc_ctx.alarm); + rtc_localtime(rtc_ctx.alarm, time); + return 0; +} + +void rtc_clear_alarm(void) +{ + DEBUG("rtc_clear_alarm()\n"); + /* Disable the alarm flag before clearing out the callback. */ + RTC->CTRL &= ~RTC_CTRL_SEC_INT_EN_MASK; + rtc_ctx.cb = NULL; + rtc_ctx.alarm = 0; +} + +void rtc_poweron(void) +{ + /* We don't power on/off the RTC since it is shared with the timer module. + * Besides the CLK_32K clock source for every peripheral that might use it, + * there isn't much to turn off here. */ +} + +void rtc_poweroff(void) +{ + /* TODO: Coordinate with the RTT module to turn off the RTC clock when + * neither one is in use. */ +} + +/** + * @brief Interrupt service declared in vectors_qn908x.h + * + * We can only generate an interrupt every second, so we check the alarm value + * every time. For a hardware based comparison use the timer instead. + */ +void isr_rtc_sec(void) +{ + if (RTC_STATUS_SEC_INT_MASK & RTC->STATUS) { + DEBUG("isr_rtc_sec at %" PRIu32 "\n", RTC->SEC); + /* Write 1 to clear the STATUS flag. */ + RTC->STATUS = RTC_STATUS_SEC_INT_MASK; + if (rtc_ctx.cb != NULL && rtc_ctx.alarm <= RTC->SEC) { + rtc_alarm_cb_t cb = rtc_ctx.cb; + rtc_ctx.cb = NULL; + /* Disable the interrupt. The cb may call rtc_set_alarm() again, + * but otherwise we don't need the interrupt anymore. */ + RTC->CTRL &= ~RTC_CTRL_SEC_INT_EN_MASK; + + cb(rtc_ctx.arg); + } + } + cortexm_isr_end(); +}