Skip to content

Commit

Permalink
Merge pull request #15567 from iosabi/qn908x_rtc
Browse files Browse the repository at this point in the history
cpu/qn908x: Add the RTC module.
  • Loading branch information
benpicco committed Dec 6, 2020
2 parents 0fcecde + 3890091 commit e3558a4
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 0 deletions.
1 change: 1 addition & 0 deletions cpu/qn908x/Kconfig
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions cpu/qn908x/Makefile.features
Expand Up @@ -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
21 changes: 21 additions & 0 deletions cpu/qn908x/doc.txt
Expand Up @@ -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
Expand Down
178 changes: 178 additions & 0 deletions 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 <iosabi@protonmail.com>
*
* @}
*/


#include <stdlib.h>

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

0 comments on commit e3558a4

Please sign in to comment.