From 4d161a3b23599fb42e2f33a33dbf13c44ab2931e Mon Sep 17 00:00:00 2001 From: Pieter De Gendt Date: Mon, 15 Nov 2021 11:00:07 +0100 Subject: [PATCH] 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 --- drivers/counter/CMakeLists.txt | 1 + drivers/counter/Kconfig | 2 + drivers/counter/Kconfig.mcux_snvs | 24 ++ drivers/counter/counter_mcux_snvs.c | 341 +++++++++++++++++++++++++ dts/arm/nxp/nxp_rt.dtsi | 12 + dts/bindings/rtc/nxp,imx-snvs-rtc.yaml | 11 + include/drivers/rtc/mcux_snvs_rtc.h | 47 ++++ modules/Kconfig.mcux | 5 + soc/arm/nxp_imx/rt/Kconfig.soc | 1 + west.yml | 2 +- 10 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 drivers/counter/Kconfig.mcux_snvs create mode 100644 drivers/counter/counter_mcux_snvs.c create mode 100644 dts/bindings/rtc/nxp,imx-snvs-rtc.yaml create mode 100644 include/drivers/rtc/mcux_snvs_rtc.h diff --git a/drivers/counter/CMakeLists.txt b/drivers/counter/CMakeLists.txt index 90ed11a1be4..286e9b876d1 100644 --- a/drivers/counter/CMakeLists.txt +++ b/drivers/counter/CMakeLists.txt @@ -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_CMOS counter_cmos.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_MCUX_LPTMR counter_mcux_lptmr.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MAXIM_DS3231 maxim_ds3231.c) diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig index d99bd0729b4..cfdf376b8a9 100644 --- a/drivers/counter/Kconfig +++ b/drivers/counter/Kconfig @@ -44,6 +44,8 @@ source "drivers/counter/Kconfig.cmos" source "drivers/counter/Kconfig.mcux_gpt" +source "drivers/counter/Kconfig.mcux_snvs" + source "drivers/counter/Kconfig.xec" source "drivers/counter/Kconfig.mcux_lptmr" diff --git a/drivers/counter/Kconfig.mcux_snvs b/drivers/counter/Kconfig.mcux_snvs new file mode 100644 index 00000000000..b0acb3fb910 --- /dev/null +++ b/drivers/counter/Kconfig.mcux_snvs @@ -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 diff --git a/drivers/counter/counter_mcux_snvs.c b/drivers/counter/counter_mcux_snvs.c new file mode 100644 index 00000000000..9bdfec607ee --- /dev/null +++ b/drivers/counter/counter_mcux_snvs.c @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2021 Basalte bv + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_imx_snvs_rtc +#include + +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 +#include + +#ifdef MCUX_SNVS_SRTC +#include +#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, ¤t); + 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, ¤t); + + 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) */ diff --git a/dts/arm/nxp/nxp_rt.dtsi b/dts/arm/nxp/nxp_rt.dtsi index 8783512f9e6..174e78b6018 100644 --- a/dts/arm/nxp/nxp_rt.dtsi +++ b/dts/arm/nxp/nxp_rt.dtsi @@ -124,6 +124,18 @@ #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 { compatible = "nxp,imx-gpio"; reg = <0x401b8000 0x4000>; diff --git a/dts/bindings/rtc/nxp,imx-snvs-rtc.yaml b/dts/bindings/rtc/nxp,imx-snvs-rtc.yaml new file mode 100644 index 00000000000..7e0b24c7231 --- /dev/null +++ b/dts/bindings/rtc/nxp,imx-snvs-rtc.yaml @@ -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 diff --git a/include/drivers/rtc/mcux_snvs_rtc.h b/include/drivers/rtc/mcux_snvs_rtc.h new file mode 100644 index 00000000000..fc60266af11 --- /dev/null +++ b/include/drivers/rtc/mcux_snvs_rtc.h @@ -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 + +#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_ */ diff --git a/modules/Kconfig.mcux b/modules/Kconfig.mcux index 887890476a1..04a397c72bd 100644 --- a/modules/Kconfig.mcux +++ b/modules/Kconfig.mcux @@ -136,6 +136,11 @@ config HAS_MCUX_GPT help 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 bool help diff --git a/soc/arm/nxp_imx/rt/Kconfig.soc b/soc/arm/nxp_imx/rt/Kconfig.soc index a9d5f781509..94de9f8f2f8 100644 --- a/soc/arm/nxp_imx/rt/Kconfig.soc +++ b/soc/arm/nxp_imx/rt/Kconfig.soc @@ -247,6 +247,7 @@ config SOC_MIMXRT1064 select HAS_MCUX_LPUART select HAS_MCUX_GPT select HAS_MCUX_SEMC + select HAS_MCUX_SNVS select HAS_MCUX_SRC select HAS_MCUX_TRNG select CPU_HAS_FPU_DOUBLE_PRECISION diff --git a/west.yml b/west.yml index 877681ef4c6..6416383749c 100644 --- a/west.yml +++ b/west.yml @@ -98,7 +98,7 @@ manifest: groups: - hal - name: hal_nxp - revision: fd8b79c40172e8aab808a3957978d49f46b01d87 + revision: 47262f22f0f832d456d0ad8d0cb4cd4dde56be30 path: modules/hal/nxp groups: - hal