| /* |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Copyright (c) 2024 Andriy Gelman |
| * Author: Andriy Gelman <andriy.gelman@gmail.com> |
| */ |
| |
| #include <zephyr/drivers/rtc.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/kernel.h> |
| |
| #include <soc.h> |
| #include <xmc_rtc.h> |
| #include <xmc_scu.h> |
| |
| #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); |