/* * 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(); }