From 4ffe418253c85eaaac145ef3dad78da21080d250 Mon Sep 17 00:00:00 2001 From: Andriy Gelman Date: Sun, 30 Jun 2024 15:54:51 -0400 Subject: [PATCH] drivers: rtc: Add RTC driver for Infineon XMC4xxx devices Adds support for settings/getting RTC time and using alarm/update feature. The alarm option needs all fields to be set due to a hardware limitation. RTC shares the same interrupt with the watchdog. Thus shared interrupts must be enabled when WDT and RTC both need to trigger the ISR. Signed-off-by: Andriy Gelman --- drivers/rtc/CMakeLists.txt | 1 + drivers/rtc/Kconfig | 1 + drivers/rtc/Kconfig.xmc4xxx | 9 + drivers/rtc/rtc_xmc4xxx.c | 270 +++++++++++++++++++++ dts/arm/infineon/cat3/xmc/xmc4xxx.dtsi | 10 +- dts/bindings/rtc/infineon,xmc4xxx-rtc.yaml | 15 ++ modules/Kconfig.infineon | 5 + soc/infineon/cat3/xmc4xxx/Kconfig | 1 + 8 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 drivers/rtc/Kconfig.xmc4xxx create mode 100644 drivers/rtc/rtc_xmc4xxx.c create mode 100644 dts/bindings/rtc/infineon,xmc4xxx-rtc.yaml diff --git a/drivers/rtc/CMakeLists.txt b/drivers/rtc/CMakeLists.txt index 107e608fbff..0e9ca0bdc21 100644 --- a/drivers/rtc/CMakeLists.txt +++ b/drivers/rtc/CMakeLists.txt @@ -24,3 +24,4 @@ zephyr_library_sources_ifdef(CONFIG_RTC_ATMEL_SAM rtc_sam.c) zephyr_library_sources_ifdef(CONFIG_RTC_RPI_PICO rtc_rpi_pico.c) zephyr_library_sources_ifdef(CONFIG_RTC_RV3028 rtc_rv3028.c) zephyr_library_sources_ifdef(CONFIG_RTC_NUMAKER rtc_numaker.c) +zephyr_library_sources_ifdef(CONFIG_RTC_XMC4XXX rtc_xmc4xxx.c) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 2ff140be1ad..accb1ab10ee 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -56,5 +56,6 @@ source "drivers/rtc/Kconfig.smartbond" source "drivers/rtc/Kconfig.stm32" source "drivers/rtc/Kconfig.numaker" source "drivers/rtc/Kconfig.rv8263" +source "drivers/rtc/Kconfig.xmc4xxx" endif # RTC diff --git a/drivers/rtc/Kconfig.xmc4xxx b/drivers/rtc/Kconfig.xmc4xxx new file mode 100644 index 00000000000..9d0ce539387 --- /dev/null +++ b/drivers/rtc/Kconfig.xmc4xxx @@ -0,0 +1,9 @@ +# Copyright 2023 Andriy Gelman +# SPDX-License-Identifier: Apache-2.0 + +config RTC_XMC4XXX + bool "XMC4xxx RTC driver" + default y + depends on DT_HAS_INFINEON_XMC4XXX_RTC_ENABLED + help + Build RTC driver for XMC4xxx SoCs. diff --git a/drivers/rtc/rtc_xmc4xxx.c b/drivers/rtc/rtc_xmc4xxx.c new file mode 100644 index 00000000000..c6efe8b3a4a --- /dev/null +++ b/drivers/rtc/rtc_xmc4xxx.c @@ -0,0 +1,270 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Andriy Gelman + * Author: Andriy Gelman + */ + +#include +#include +#include +#include + +#include +#include +#include + +#define DT_DRV_COMPAT infineon_xmc4xxx_rtc + +#define RTC_XMC4XXX_DEFAULT_PRESCALER 0x7fff + +#define RTC_XMC4XXX_SUPPORTED_ALARM_MASK \ + (RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE | RTC_ALARM_TIME_MASK_HOUR | \ + RTC_ALARM_TIME_MASK_MONTHDAY | RTC_ALARM_TIME_MASK_MONTH | RTC_ALARM_TIME_MASK_YEAR) + +struct rtc_xmc4xxx_data { +#if defined(CONFIG_RTC_ALARM) + rtc_alarm_callback alarm_callback; + void *alarm_user_data; +#endif +#if defined(CONFIG_RTC_UPDATE) + rtc_update_callback update_callback; + void *update_user_data; +#endif +}; + +static int rtc_xmc4xxx_set_time(const struct device *dev, const struct rtc_time *timeptr) +{ + const struct tm *stdtime; + + if (timeptr == NULL) { + return -EINVAL; + } + + XMC_RTC_Stop(); + + stdtime = rtc_time_to_tm((struct rtc_time *)timeptr); + XMC_RTC_SetTimeStdFormat(stdtime); + + XMC_RTC_Start(); + + return 0; +} + +static int rtc_xmc4xxx_get_time(const struct device *dev, struct rtc_time *timeptr) +{ + struct tm *stdtime = rtc_time_to_tm(timeptr); + + if (!XMC_RTC_IsRunning()) { + return -ENODATA; + } + + if (stdtime == NULL) { + return -EINVAL; + } + + XMC_RTC_GetTimeStdFormat(stdtime); + timeptr->tm_nsec = 0; + + return 0; +} + +#if defined(CONFIG_RTC_ALARM) || defined(CONFIG_RTC_UPDATE) +static void rtc_xmc4xxx_isr(const struct device *dev) +{ + struct rtc_xmc4xxx_data *dev_data = dev->data; + + uint32_t event = SCU_INTERRUPT->SRRAW; + +#if defined(CONFIG_RTC_ALARM) + if ((event & XMC_SCU_INTERRUPT_EVENT_RTC_ALARM) != 0) { + if (dev_data->alarm_callback != NULL) { + dev_data->alarm_callback(dev, 0, dev_data->alarm_user_data); + } + XMC_SCU_INTERRUPT_ClearEventStatus(XMC_SCU_INTERRUPT_EVENT_RTC_ALARM); + } +#endif + +#if defined(CONFIG_RTC_UPDATE) + if ((event & XMC_SCU_INTERRUPT_EVENT_RTC_PERIODIC) != 0) { + if (dev_data->update_callback != NULL) { + dev_data->update_callback(dev, dev_data->update_user_data); + } + XMC_SCU_INTERRUPT_ClearEventStatus(XMC_SCU_INTERRUPT_EVENT_RTC_PERIODIC); + } +#endif +} +#endif + +#ifdef CONFIG_RTC_ALARM +static int rtc_xmc4xxx_alarm_get_supported_fields(const struct device *dev, uint16_t id, + uint16_t *mask) +{ + ARG_UNUSED(dev); + ARG_UNUSED(id); + + *mask = RTC_XMC4XXX_SUPPORTED_ALARM_MASK; + return 0; +} + +static int rtc_xmc4xxx_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask, + const struct rtc_time *timeptr) +{ + const struct tm *stdtime = rtc_time_to_tm((struct rtc_time *)timeptr); + + if (id != 0 || (mask > 0 && timeptr == NULL)) { + return -EINVAL; + } + + if (mask == 0) { + XMC_RTC_DisableEvent(XMC_RTC_EVENT_ALARM); + XMC_SCU_INTERRUPT_ClearEventStatus(XMC_SCU_INTERRUPT_EVENT_RTC_ALARM); + return 0; + } + + if (mask != RTC_XMC4XXX_SUPPORTED_ALARM_MASK) { + return -EINVAL; + } + + XMC_RTC_SetAlarmStdFormat(stdtime); + XMC_RTC_EnableEvent(XMC_RTC_EVENT_ALARM); + + return 0; +} + +static int rtc_xmc4xxx_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask, + struct rtc_time *timeptr) +{ + ARG_UNUSED(dev); + struct tm *stdtime = rtc_time_to_tm(timeptr); + + if (id != 0 || mask == NULL || timeptr == NULL) { + return -EINVAL; + } + + *mask = RTC_XMC4XXX_SUPPORTED_ALARM_MASK; + + XMC_RTC_GetAlarmStdFormat(stdtime); + + return 0; +} + +static int rtc_xmc4xxx_alarm_is_pending(const struct device *dev, uint16_t id) +{ + ARG_UNUSED(dev); + unsigned int key; + int alarm = 0; + uint32_t event; + + if (id != 0) { + return -EINVAL; + } + + key = irq_lock(); + event = SCU_INTERRUPT->SRRAW; + + if ((event & XMC_SCU_INTERRUPT_EVENT_RTC_ALARM) != 0) { + alarm = 1; + XMC_SCU_INTERRUPT_ClearEventStatus(XMC_SCU_INTERRUPT_EVENT_RTC_ALARM); + } + irq_unlock(key); + + return alarm; +} + +static int rtc_xmc4xxx_alarm_set_callback(const struct device *dev, uint16_t id, + rtc_alarm_callback callback, void *user_data) +{ + struct rtc_xmc4xxx_data *dev_data = dev->data; + unsigned int key; + + if (id != 0) { + return -EINVAL; + } + + key = irq_lock(); + dev_data->alarm_callback = callback; + dev_data->alarm_user_data = user_data; + irq_unlock(key); + + if (dev_data->alarm_callback) { + XMC_SCU_INTERRUPT_EnableEvent(XMC_SCU_INTERRUPT_EVENT_RTC_ALARM); + } else { + XMC_SCU_INTERRUPT_DisableEvent(XMC_SCU_INTERRUPT_EVENT_RTC_ALARM); + } + + return 0; +} +#endif /* CONFIG_RTC_ALARM */ + +#ifdef CONFIG_RTC_UPDATE +static int rtc_xmc4xxx_update_set_callback(const struct device *dev, rtc_update_callback callback, + void *user_data) +{ + struct rtc_xmc4xxx_data *dev_data = dev->data; + unsigned int key; + + key = irq_lock(); + dev_data->update_callback = callback; + dev_data->update_user_data = user_data; + irq_unlock(key); + + if (dev_data->update_callback) { + XMC_RTC_EnableEvent(XMC_RTC_EVENT_PERIODIC_SECONDS); + XMC_SCU_INTERRUPT_EnableEvent(XMC_SCU_INTERRUPT_EVENT_RTC_PERIODIC); + } else { + XMC_SCU_INTERRUPT_DisableEvent(XMC_SCU_INTERRUPT_EVENT_RTC_PERIODIC); + XMC_RTC_DisableEvent(XMC_RTC_EVENT_PERIODIC_SECONDS); + } + + return 0; +} +#endif /* CONFIG_RTC_UPDATE */ + +static const struct rtc_driver_api rtc_xmc4xxx_driver_api = { + .set_time = rtc_xmc4xxx_set_time, + .get_time = rtc_xmc4xxx_get_time, +#ifdef CONFIG_RTC_ALARM + .alarm_get_supported_fields = rtc_xmc4xxx_alarm_get_supported_fields, + .alarm_set_time = rtc_xmc4xxx_alarm_set_time, + .alarm_get_time = rtc_xmc4xxx_alarm_get_time, + .alarm_is_pending = rtc_xmc4xxx_alarm_is_pending, + .alarm_set_callback = rtc_xmc4xxx_alarm_set_callback, +#endif +#ifdef CONFIG_RTC_UPDATE + .update_set_callback = rtc_xmc4xxx_update_set_callback, +#endif +}; + +#if defined(CONFIG_RTC_ALARM) || defined(CONFIG_RTC_UPDATE) +static void rtc_xmc4xxx_irq_config(void) +{ + /* RTC and watchdog share the same interrupt. Shared interrupts must */ + /* be enabled if WDT is enabled and RTC is using alarm or update feature */ + IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), rtc_xmc4xxx_isr, + DEVICE_DT_INST_GET(0), 0); + irq_enable(DT_INST_IRQN(0)); +} +#endif + +static int rtc_xmc4xxx_init(const struct device *dev) +{ + if (!XMC_RTC_IsRunning()) { + if (!XMC_SCU_HIB_IsHibernateDomainEnabled()) { + XMC_SCU_HIB_EnableHibernateDomain(); + } + XMC_RTC_SetPrescaler(RTC_XMC4XXX_DEFAULT_PRESCALER); + } + +#if defined(CONFIG_RTC_ALARM) || defined(CONFIG_RTC_UPDATE) + rtc_xmc4xxx_irq_config(); +#endif + + return 0; +} + + +static struct rtc_xmc4xxx_data rtc_xmc4xxx_data_0; + +DEVICE_DT_INST_DEFINE(0, rtc_xmc4xxx_init, NULL, &rtc_xmc4xxx_data_0, NULL, + POST_KERNEL, CONFIG_RTC_INIT_PRIORITY, &rtc_xmc4xxx_driver_api); diff --git a/dts/arm/infineon/cat3/xmc/xmc4xxx.dtsi b/dts/arm/infineon/cat3/xmc/xmc4xxx.dtsi index 98cdf56a8a5..d2ea0e1314e 100644 --- a/dts/arm/infineon/cat3/xmc/xmc4xxx.dtsi +++ b/dts/arm/infineon/cat3/xmc/xmc4xxx.dtsi @@ -233,7 +233,15 @@ wdt0: watchdog@50008000 { compatible = "infineon,xmc4xxx-watchdog"; reg = <0x50008000 0x4000>; - interrupts = <0 1>; + interrupts = <0 1>; // shared interrupt line with rtc + status = "disabled"; + }; + + rtc: rtc@50004a00 { + compatible = "infineon,xmc4xxx-rtc"; + reg = <0x50004a00 0x200>; + interrupts = <0 1>; // shared interrupt line with wdt0 + alarms-count = <1>; status = "disabled"; }; diff --git a/dts/bindings/rtc/infineon,xmc4xxx-rtc.yaml b/dts/bindings/rtc/infineon,xmc4xxx-rtc.yaml new file mode 100644 index 00000000000..23ab2799673 --- /dev/null +++ b/dts/bindings/rtc/infineon,xmc4xxx-rtc.yaml @@ -0,0 +1,15 @@ +# Copyright (c) 2023 Andriy Gelman +# SPDX-License-Identifier: Apache-2.0 + +description: Infineon XMC4xxx family RTC device + +compatible: "infineon,xmc4xxx-rtc" + +include: rtc-device.yaml + +properties: + reg: + required: true + + interrupts: + required: true diff --git a/modules/Kconfig.infineon b/modules/Kconfig.infineon index fc39318c8b2..a7d83de0c35 100644 --- a/modules/Kconfig.infineon +++ b/modules/Kconfig.infineon @@ -65,4 +65,9 @@ config HAS_XMCLIB_CAN help Enable XMCLIB CAN +config HAS_XMCLIB_RTC + bool + help + Enable XMCLIB RTC + endif # HAS_XMCLIB diff --git a/soc/infineon/cat3/xmc4xxx/Kconfig b/soc/infineon/cat3/xmc4xxx/Kconfig index 21f4c7b5cb3..a50ef22cc80 100644 --- a/soc/infineon/cat3/xmc4xxx/Kconfig +++ b/soc/infineon/cat3/xmc4xxx/Kconfig @@ -22,3 +22,4 @@ config SOC_SERIES_XMC4XXX select HAS_XMCLIB_WDT select HAS_XMCLIB_ETH select HAS_XMCLIB_CAN + select HAS_XMCLIB_RTC