drivers: counter: NXP SNVS rtc: Add support for NXP imx SNVS RTC

Adds a driver using the SNVS high power and optionally low power
RTC instances. A device specific function `mcux_snvs_rtc_set` is
provided to update the current counter value.

Signed-off-by: Pieter De Gendt <pieter.degendt@basalte.be>
This commit is contained in:
Pieter De Gendt 2021-11-15 11:00:07 +01:00 committed by Maureen Helm
commit 4d161a3b23
10 changed files with 445 additions and 1 deletions

View file

@ -16,6 +16,7 @@ zephyr_library_sources_ifdef(CONFIG_COUNTER_SAM_TC counter_sam_tc.c
zephyr_library_sources_ifdef(CONFIG_COUNTER_SAM0_TC32 counter_sam0_tc32.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_SAM0_TC32 counter_sam0_tc32.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_CMOS counter_cmos.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_CMOS counter_cmos.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_GPT counter_mcux_gpt.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_GPT counter_mcux_gpt.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_SNVS counter_mcux_snvs.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_XEC counter_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_XEC counter_mchp_xec.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_LPTMR counter_mcux_lptmr.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_LPTMR counter_mcux_lptmr.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_MAXIM_DS3231 maxim_ds3231.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MAXIM_DS3231 maxim_ds3231.c)

View file

@ -44,6 +44,8 @@ source "drivers/counter/Kconfig.cmos"
source "drivers/counter/Kconfig.mcux_gpt" source "drivers/counter/Kconfig.mcux_gpt"
source "drivers/counter/Kconfig.mcux_snvs"
source "drivers/counter/Kconfig.xec" source "drivers/counter/Kconfig.xec"
source "drivers/counter/Kconfig.mcux_lptmr" source "drivers/counter/Kconfig.mcux_lptmr"

View file

@ -0,0 +1,24 @@
# MCUXpresso SDK SNVS (Secure) RTC
# Copyright (c) 2021 Basalte bv
# SPDX-License-Identifier: Apache-2.0
config COUNTER_MCUX_SNVS
bool "IMX SNVS RTC driver"
depends on HAS_MCUX_SNVS
help
Enable support for the IMX SNVS High/Low Power clock.
config COUNTER_MCUX_SNVS_SRTC
bool "IMX SNVS SRTC low power support"
depends on COUNTER_MCUX_SNVS
default y
help
Enable the low power SRTC in SNVS to synchronise.
config COUNTER_MCUX_SNVS_SRTC_WAKE
bool "IMX SNVS wake-up on SRTC alarm"
depends on COUNTER_MCUX_SNVS_SRTC
default y
help
Assert Wake-Up Interrupt on SRTC alarm

View file

@ -0,0 +1,341 @@
/*
* Copyright (c) 2021 Basalte bv
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_imx_snvs_rtc
#include <logging/log.h>
LOG_MODULE_REGISTER(mcux_snvs, CONFIG_COUNTER_LOG_LEVEL);
#if CONFIG_COUNTER_MCUX_SNVS_SRTC
#define MCUX_SNVS_SRTC
#define MCUX_SNVS_NUM_CHANNELS 2
#else
#define MCUX_SNVS_NUM_CHANNELS 1
#endif
#include <drivers/counter.h>
#include <fsl_snvs_hp.h>
#ifdef MCUX_SNVS_SRTC
#include <fsl_snvs_lp.h>
#endif
struct mcux_snvs_config {
/* info must be first element */
struct counter_config_info info;
SNVS_Type *base;
void (*irq_config_func)(const struct device *dev);
};
struct mcux_snvs_data {
counter_alarm_callback_t alarm_hp_rtc_callback;
void *alarm_hp_rtc_user_data;
#ifdef MCUX_SNVS_SRTC
counter_alarm_callback_t alarm_lp_srtc_callback;
void *alarm_lp_srtc_user_data;
#endif
};
static int mcux_snvs_start(const struct device *dev)
{
ARG_UNUSED(dev);
return -EALREADY;
}
static int mcux_snvs_stop(const struct device *dev)
{
ARG_UNUSED(dev);
return -ENOTSUP;
}
static int mcux_snvs_get_value(const struct device *dev, uint32_t *ticks)
{
const struct mcux_snvs_config *config = dev->config;
uint32_t tmp = 0;
do {
*ticks = tmp;
tmp = (config->base->HPRTCMR << 17U);
tmp |= (config->base->HPRTCLR >> 15U);
} while (*ticks != tmp);
return 0;
}
static int mcux_snvs_set_alarm(const struct device *dev,
uint8_t chan_id,
const struct counter_alarm_cfg *alarm_cfg)
{
const struct mcux_snvs_config *config = dev->config;
struct mcux_snvs_data *data = dev->data;
uint32_t current, ticks;
mcux_snvs_get_value(dev, &current);
ticks = alarm_cfg->ticks;
if ((alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) == 0) {
ticks += current;
}
if (ticks < current) {
LOG_ERR("Invalid alarm ticks");
return -EINVAL;
}
if (chan_id == 0) {
if (data->alarm_hp_rtc_callback) {
return -EBUSY;
}
data->alarm_hp_rtc_callback = alarm_cfg->callback;
data->alarm_hp_rtc_user_data = alarm_cfg->user_data;
/* disable RTC alarm interrupt */
config->base->HPCR &= ~SNVS_HPCR_HPTA_EN_MASK;
while ((config->base->HPCR & SNVS_HPCR_HPTA_EN_MASK) != 0U) {
}
/* Set alarm in seconds*/
config->base->HPTAMR = (uint32_t)(ticks >> 17U);
config->base->HPTALR = (uint32_t)(ticks << 15U);
/* enable RTC alarm interrupt */
config->base->HPCR |= SNVS_HPCR_HPTA_EN_MASK;
#ifdef MCUX_SNVS_SRTC
} else if (chan_id == 1) {
if (data->alarm_lp_srtc_callback) {
return -EBUSY;
}
data->alarm_lp_srtc_callback = alarm_cfg->callback;
data->alarm_lp_srtc_user_data = alarm_cfg->user_data;
/* disable SRTC alarm interrupt */
config->base->LPCR &= ~SNVS_LPCR_LPTA_EN_MASK;
while ((config->base->LPCR & SNVS_LPCR_LPTA_EN_MASK) != 0U) {
}
/* Set alarm in seconds*/
config->base->LPTAR = ticks;
/* enable SRTC alarm interrupt */
config->base->LPCR |= SNVS_LPCR_LPTA_EN_MASK;
#endif
} else {
LOG_ERR("Invalid channel id");
return -EINVAL;
}
return 0;
}
static int mcux_snvs_cancel_alarm(const struct device *dev,
uint8_t chan_id)
{
const struct mcux_snvs_config *config = dev->config;
struct mcux_snvs_data *data = dev->data;
if (chan_id == 0) {
/* disable RTC alarm interrupt */
config->base->HPCR &= ~SNVS_HPCR_HPTA_EN_MASK;
while ((config->base->HPCR & SNVS_HPCR_HPTA_EN_MASK) != 0U) {
}
/* clear callback */
data->alarm_hp_rtc_callback = NULL;
#ifdef MCUX_SNVS_SRTC
} else if (chan_id == 1) {
/* disable SRTC alarm interrupt */
config->base->LPCR &= ~SNVS_LPCR_LPTA_EN_MASK;
while ((config->base->LPCR & SNVS_LPCR_LPTA_EN_MASK) != 0U) {
}
/* clear callback */
data->alarm_lp_srtc_callback = NULL;
#endif
} else {
LOG_ERR("Invalid channel id");
return -EINVAL;
}
return 0;
}
static int mcux_snvs_set_top_value(const struct device *dev,
const struct counter_top_cfg *cfg)
{
ARG_UNUSED(dev);
ARG_UNUSED(cfg);
return -ENOTSUP;
}
static uint32_t mcux_snvs_get_pending_int(const struct device *dev)
{
const struct mcux_snvs_config *config = dev->config;
uint32_t flags;
flags = SNVS_HP_RTC_GetStatusFlags(config->base) & kSNVS_RTC_AlarmInterruptFlag;
#ifdef MCUX_SNVS_SRTC
flags |= SNVS_LP_SRTC_GetStatusFlags(config->base) & kSNVS_SRTC_AlarmInterruptFlag;
#endif
return flags;
}
static uint32_t mcux_snvs_get_top_value(const struct device *dev)
{
ARG_UNUSED(dev);
return UINT32_MAX;
}
void mcux_snvs_isr(const struct device *dev)
{
const struct mcux_snvs_config *config = dev->config;
struct mcux_snvs_data *data = dev->data;
uint32_t current;
mcux_snvs_get_value(dev, &current);
if (SNVS_HP_RTC_GetStatusFlags(config->base) & kSNVS_RTC_AlarmInterruptFlag) {
/* Clear alarm flag */
SNVS_HP_RTC_ClearStatusFlags(config->base, kSNVS_RTC_AlarmInterruptFlag);
if (data->alarm_hp_rtc_callback) {
data->alarm_hp_rtc_callback(dev, 0, current, data->alarm_hp_rtc_user_data);
mcux_snvs_cancel_alarm(dev, 0);
}
}
#ifdef MCUX_SNVS_SRTC
if (SNVS_LP_SRTC_GetStatusFlags(config->base) & kSNVS_SRTC_AlarmInterruptFlag) {
/* Clear alarm flag */
SNVS_LP_SRTC_ClearStatusFlags(config->base, kSNVS_SRTC_AlarmInterruptFlag);
if (data->alarm_lp_srtc_callback) {
data->alarm_lp_srtc_callback(dev, 1, current,
data->alarm_lp_srtc_user_data);
mcux_snvs_cancel_alarm(dev, 1);
}
}
#endif
}
int mcux_snvs_rtc_set(const struct device *dev, uint32_t ticks)
{
const struct mcux_snvs_config *config = dev->config;
#ifdef MCUX_SNVS_SRTC
SNVS_LP_SRTC_StopTimer(config->base);
config->base->LPSRTCMR = (uint32_t)(ticks >> 17U);
config->base->LPSRTCLR = (uint32_t)(ticks << 15U);
SNVS_LP_SRTC_StartTimer(config->base);
/* Sync to our high power RTC */
SNVS_HP_RTC_TimeSynchronize(config->base);
#else
SNVS_HP_RTC_StopTimer(config->base);
config->base->HPRTCMR = (uint32_t)(ticks >> 17U);
config->base->HPRTCLR = (uint32_t)(ticks << 15U);
SNVS_HP_RTC_StartTimer(config->base);
#endif
return 0;
}
static int mcux_snvs_init(const struct device *dev)
{
const struct mcux_snvs_config *config = dev->config;
snvs_hp_rtc_config_t hp_rtc_config;
#ifdef MCUX_SNVS_SRTC
snvs_lp_srtc_config_t lp_srtc_config;
#endif
SNVS_HP_RTC_GetDefaultConfig(&hp_rtc_config);
SNVS_HP_RTC_Init(config->base, &hp_rtc_config);
#ifdef MCUX_SNVS_SRTC
/* Reset power glitch detector */
SNVS_LP_Init(config->base);
/* Init SRTC to default config */
SNVS_LP_SRTC_GetDefaultConfig(&lp_srtc_config);
SNVS_LP_SRTC_Init(config->base, &lp_srtc_config);
#if CONFIG_COUNTER_MCUX_SNVS_SRTC_WAKE
config->base->LPCR |= SNVS_LPCR_LPWUI_EN_MASK;
#endif
/* RTC should always run */
SNVS_LP_SRTC_StartTimer(config->base);
SNVS_HP_RTC_TimeSynchronize(config->base);
#endif
/* RTC should always run */
SNVS_HP_RTC_StartTimer(config->base);
config->irq_config_func(dev);
return 0;
}
static const struct counter_driver_api mcux_snvs_driver_api = {
.start = mcux_snvs_start,
.stop = mcux_snvs_stop,
.get_value = mcux_snvs_get_value,
.set_alarm = mcux_snvs_set_alarm,
.cancel_alarm = mcux_snvs_cancel_alarm,
.set_top_value = mcux_snvs_set_top_value,
.get_pending_int = mcux_snvs_get_pending_int,
.get_top_value = mcux_snvs_get_top_value,
};
/*
* This driver is single-instance. If the devicetree contains multiple
* instances, this will fail and the driver needs to be revisited.
*/
BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) <= 1,
"unsupported snvs instance");
#if DT_NODE_HAS_STATUS(DT_DRV_INST(0), okay)
static struct mcux_snvs_data mcux_snvs_data_0;
static void mcux_snvs_irq_config_0(const struct device *dev);
static struct mcux_snvs_config mcux_snvs_config_0 = {
.info = {
.max_top_value = 0,
.freq = 1,
.channels = MCUX_SNVS_NUM_CHANNELS,
.flags = COUNTER_CONFIG_INFO_COUNT_UP,
},
.base = (SNVS_Type *)DT_REG_ADDR(DT_PARENT(DT_DRV_INST(0))),
.irq_config_func = mcux_snvs_irq_config_0,
};
DEVICE_DT_INST_DEFINE(0, &mcux_snvs_init, NULL,
&mcux_snvs_data_0,
&mcux_snvs_config_0,
POST_KERNEL, CONFIG_COUNTER_INIT_PRIORITY,
&mcux_snvs_driver_api);
static void mcux_snvs_irq_config_0(const struct device *dev)
{
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority),
mcux_snvs_isr, DEVICE_DT_INST_GET(0), 0);
irq_enable(DT_INST_IRQN(0));
}
#endif /* DT_NODE_HAS_STATUS(DT_DRV_INST(0), okay) */

View file

@ -124,6 +124,18 @@
#clock-cells = <3>; #clock-cells = <3>;
}; };
snvs: snvs@400d4000 {
compatible = "nxp,imx-snvs";
reg = <0x400d4000 0x4000>;
label = "SNVS";
snvs_rtc: rtc {
compatible = "nxp,imx-snvs-rtc";
label = "SNVS_RTC";
interrupts = <46 0>;
};
};
gpio1: gpio@401b8000 { gpio1: gpio@401b8000 {
compatible = "nxp,imx-gpio"; compatible = "nxp,imx-gpio";
reg = <0x401b8000 0x4000>; reg = <0x401b8000 0x4000>;

View file

@ -0,0 +1,11 @@
#
# Copyright (c) 2021 Basalte bv
#
# SPDX-License-Identifier: Apache-2.0
#
description: NXP SNVS LP/HP RTC
compatible: "nxp,imx-snvs-rtc"
include: rtc.yaml

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2021 Basalte bv
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Real-time clock control based on the MCUX IMX SNVS counter API.
*
* The core Zephyr API to this device is as a counter.
*
* Additional implementation details a user should take into account:
* * an optional SRTC can be enabled (default) with configuration
* options
* * the high power channel (id 0) is always available, the low power
* channel (id 1) is optional
* * the low power alarm can be used to assert a wake-up
* * the counter has a fixed 1Hz period
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_RTC_MCUX_SNVS_H_
#define ZEPHYR_INCLUDE_DRIVERS_RTC_MCUX_SNVS_H_
#include <device.h>
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Set the current counter value
*
* As the counter advances at 1Hz this will usually be set to the
* current UNIX time stamp.
*
* @param dev the IMX SNVS RTC device pointer.
*
* @param ticks the new value of the internal counter
*
* @retval non-negative on success
*/
int mcux_snvs_rtc_set(const struct device *dev, uint32_t ticks);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_DRIVERS_RTC_MCUX_SNVS_H_ */

View file

@ -136,6 +136,11 @@ config HAS_MCUX_GPT
help help
Set if the general purpose timer (GPT) module is present in the SoC. Set if the general purpose timer (GPT) module is present in the SoC.
config HAS_MCUX_SNVS
bool
help
Set if the SNVS module is present on the SoC.
config HAS_MCUX_RNG config HAS_MCUX_RNG
bool bool
help help

View file

@ -247,6 +247,7 @@ config SOC_MIMXRT1064
select HAS_MCUX_LPUART select HAS_MCUX_LPUART
select HAS_MCUX_GPT select HAS_MCUX_GPT
select HAS_MCUX_SEMC select HAS_MCUX_SEMC
select HAS_MCUX_SNVS
select HAS_MCUX_SRC select HAS_MCUX_SRC
select HAS_MCUX_TRNG select HAS_MCUX_TRNG
select CPU_HAS_FPU_DOUBLE_PRECISION select CPU_HAS_FPU_DOUBLE_PRECISION

View file

@ -98,7 +98,7 @@ manifest:
groups: groups:
- hal - hal
- name: hal_nxp - name: hal_nxp
revision: fd8b79c40172e8aab808a3957978d49f46b01d87 revision: 47262f22f0f832d456d0ad8d0cb4cd4dde56be30
path: modules/hal/nxp path: modules/hal/nxp
groups: groups:
- hal - hal