From 3890091ced8955bb5d0cf0bda41da57fe92cfef1 Mon Sep 17 00:00:00 2001 From: iosabi Date: Sat, 5 Dec 2020 17:24:45 +0000 Subject: [PATCH] cpu/qn908x: Add the RTC module. This patch implements the real time clock module for the QN908X cpus. This module is very straightforward with only the one notable drawback that it doesn't have a match register like the CTIMER block to implement the alarm function. Instead, this driver can only use the interrupt generated ever 1 second to implement the alarm match comparison in software. --- cpu/qn908x/Kconfig | 1 + cpu/qn908x/Makefile.features | 1 + cpu/qn908x/doc.txt | 21 +++++ cpu/qn908x/periph/rtc.c | 178 +++++++++++++++++++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 cpu/qn908x/periph/rtc.c diff --git a/cpu/qn908x/Kconfig b/cpu/qn908x/Kconfig index 9fbf36d8cb59..5eaee65f094c 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 9bc70e84334a..8a1419cdfe8d 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 e574b6736b10..192230bdeab7 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 000000000000..11b948e3b63f --- /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(); +}