diff --git a/boards/infineon/cyw920829m2evk_02/cyw920829m2evk_02.yaml b/boards/infineon/cyw920829m2evk_02/cyw920829m2evk_02.yaml index a02da7d1937801..5292dd06cef51a 100644 --- a/boards/infineon/cyw920829m2evk_02/cyw920829m2evk_02.yaml +++ b/boards/infineon/cyw920829m2evk_02/cyw920829m2evk_02.yaml @@ -20,5 +20,6 @@ supported: - watchdog - spi - i2c + - rtc vendor: infineon diff --git a/drivers/rtc/CMakeLists.txt b/drivers/rtc/CMakeLists.txt index 0e9ca0bdc21af7..cd0c7a3ad5832e 100644 --- a/drivers/rtc/CMakeLists.txt +++ b/drivers/rtc/CMakeLists.txt @@ -13,6 +13,7 @@ zephyr_library_sources_ifdef(CONFIG_RTC_AMBIQ rtc_ambiq.c) zephyr_library_sources_ifdef(CONFIG_RTC_DS1307 rtc_ds1307.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE rtc_handlers.c) zephyr_library_sources_ifdef(CONFIG_RTC_EMUL rtc_emul.c) +zephyr_library_sources_ifdef(CONFIG_RTC_INFINEON_CAT1 rtc_ifx_cat1.c) zephyr_library_sources_ifdef(CONFIG_RTC_PCF8523 rtc_pcf8523.c) zephyr_library_sources_ifdef(CONFIG_RTC_PCF8563 rtc_pcf8563.c) zephyr_library_sources_ifdef(CONFIG_RTC_MOTOROLA_MC146818 rtc_mc146818.c) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index accb1ab10eee27..2b1d2d29a7a4e2 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -46,6 +46,7 @@ source "drivers/rtc/Kconfig.ambiq" source "drivers/rtc/Kconfig.ds1307" source "drivers/rtc/Kconfig.emul" source "drivers/rtc/Kconfig.fake" +source "drivers/rtc/Kconfig.ifx_cat1" source "drivers/rtc/Kconfig.mc146818" source "drivers/rtc/Kconfig.pcf8523" source "drivers/rtc/Kconfig.pcf8563" diff --git a/drivers/rtc/Kconfig.ifx_cat1 b/drivers/rtc/Kconfig.ifx_cat1 new file mode 100644 index 00000000000000..4a2a12f50c4289 --- /dev/null +++ b/drivers/rtc/Kconfig.ifx_cat1 @@ -0,0 +1,14 @@ +# Infineon CAT1 RTC configuration options + +# Copyright (c) 2024 Cypress Semiconductor Corporation (an Infineon company) or +# an affiliate of Cypress Semiconductor Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +config RTC_INFINEON_CAT1 + bool "Infineon CAT1 RTC driver" + default y + depends on DT_HAS_INFINEON_CAT1_RTC_ENABLED + select USE_INFINEON_RTC + help + This option enables the RTC driver for Infineon CAT1 family. diff --git a/drivers/rtc/rtc_ifx_cat1.c b/drivers/rtc/rtc_ifx_cat1.c new file mode 100644 index 00000000000000..24569ca3864bf1 --- /dev/null +++ b/drivers/rtc/rtc_ifx_cat1.c @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2024 Cypress Semiconductor Corporation (an Infineon company) or + * an affiliate of Cypress Semiconductor Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief RTC driver for Infineon CAT1 MCU family. + */ + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ifx_cat1_rtc, CONFIG_RTC_LOG_LEVEL); + +#define DT_DRV_COMPAT infineon_cat1_rtc + +#define _IFX_CAT1_RTC_STATE_UNINITIALIZED 0 +#define _IFX_CAT1_RTC_STATE_ENABLED 1 +#define _IFX_CAT1_RTC_STATE_TIME_SET 2 + +#define _IFX_CAT1_RTC_INIT_CENTURY 2000 +#define _IFX_CAT1_RTC_TM_YEAR_BASE 1900 + +#if defined(CONFIG_SOC_FAMILY_INFINEON_CAT1B) +#if defined(SRSS_BACKUP_NUM_BREG3) && (SRSS_BACKUP_NUM_BREG3 > 0) +#define _IFX_CAT1_RTC_BREG (BACKUP->BREG_SET3[SRSS_BACKUP_NUM_BREG3 - 1]) +#elif defined(SRSS_BACKUP_NUM_BREG2) && (SRSS_BACKUP_NUM_BREG2 > 0) +#define _IFX_CAT1_RTC_BREG (BACKUP->BREG_SET2[SRSS_BACKUP_NUM_BREG2 - 1]) +#elif defined(SRSS_BACKUP_NUM_BREG1) && (SRSS_BACKUP_NUM_BREG1 > 0) +#define _IFX_CAT1_RTC_BREG (BACKUP->BREG_SET1[SRSS_BACKUP_NUM_BREG1 - 1]) +#elif defined(SRSS_BACKUP_NUM_BREG0) && (SRSS_BACKUP_NUM_BREG0 > 0) +#define _IFX_CAT1_RTC_BREG (BACKUP->BREG_SET0[SRSS_BACKUP_NUM_BREG0 - 1]) +#endif +#endif + +#define _IFX_CAT1_RTC_BREG_CENTURY_Pos 0UL +#define _IFX_CAT1_RTC_BREG_CENTURY_Msk 0x0000FFFFUL +#define _IFX_CAT1_RTC_BREG_STATE_Pos 16UL +#define _IFX_CAT1_RTC_BREG_STATE_Msk 0xFFFF0000UL + +static const uint32_t _IFX_CAT1_RTC_MAX_RETRY = 10; +static const uint32_t _IFX_CAT1_RTC_RETRY_DELAY_MS = 1; + +static cy_stc_rtc_dst_t *_ifx_cat1_rtc_dst; + +#ifdef CONFIG_PM +static cy_en_syspm_status_t _ifx_cat1_rtc_syspm_callback(cy_stc_syspm_callback_params_t *params, + cy_en_syspm_callback_mode_t mode) +{ + return Cy_RTC_DeepSleepCallback(params, mode); +} + +static cy_stc_syspm_callback_params_t _ifx_cat1_rtc_pm_cb_params = {NULL, NULL}; +static cy_stc_syspm_callback_t _ifx_cat1_rtc_pm_cb = { + .callback = &_ifx_cat1_rtc_syspm_callback, + .type = CY_SYSPM_DEEPSLEEP, + .callbackParams = &_ifx_cat1_rtc_pm_cb_params, +}; +#endif /* CONFIG_PM */ + +#define _IFX_CAT1_RTC_WAIT_ONE_MS() Cy_SysLib_Delay(_IFX_CAT1_RTC_RETRY_DELAY_MS); + +/* Internal macro to validate RTC year parameter falls within 21st century */ +#define IFX_CAT1_RTC_VALID_CENTURY(year) ((year) >= _IFX_CAT1_RTC_INIT_CENTURY) + +#define MAX_IFX_CAT1_CAL (60) + +/* Convert parts per billion to groupings of 128 ticks added or removed from one hour of clock + * cycles at 32768 Hz. + * + * ROUND_DOWN(ppb * 32768Hz * 60min * 60sec / 1000000000, 128) / 128 + * ROUND_DOWN(ppb * 117964800 / 1000000000, 128) / 128 + * ROUND_DOWN(ppb * 9216 / 78125, 128) / 128 + */ +#define PPB_TO_WCO_PULSE_SETS(ppb) ((ROUND_DOWN((ppb * 9216 / 78125), 128)) / 128) + +/* Convert groupings of 128 ticks added or removed from one hour of clock cycles at + * 32768 Hz to parts per billion + * + * wps * 128 * 1000000000 / 32768Hz * 60min * 60sec + * wps * 128000000000 / 117964800 + * wps * 78125 / 72 + */ +#define WCO_PULSE_SETS_TO_PPB(wps) (wps * 78125 / 72) + +struct ifx_cat1_rtc_data { + struct k_spinlock lock; +}; + +static inline uint16_t _ifx_cat1_rtc_get_state(void) +{ + return _FLD2VAL(_IFX_CAT1_RTC_BREG_STATE, _IFX_CAT1_RTC_BREG); +} + +static inline void _ifx_cat1_rtc_set_state(uint16_t init) +{ + _IFX_CAT1_RTC_BREG &= _IFX_CAT1_RTC_BREG_CENTURY_Msk; + _IFX_CAT1_RTC_BREG |= _VAL2FLD(_IFX_CAT1_RTC_BREG_STATE, init); +} + +static inline uint16_t _ifx_cat1_rtc_get_century(void) +{ + return _FLD2VAL(_IFX_CAT1_RTC_BREG_CENTURY, _IFX_CAT1_RTC_BREG); +} + +static inline void _ifx_cat1_rtc_set_century(uint16_t century) +{ + _IFX_CAT1_RTC_BREG &= _IFX_CAT1_RTC_BREG_STATE_Msk; + _IFX_CAT1_RTC_BREG |= _VAL2FLD(_IFX_CAT1_RTC_BREG_CENTURY, century); +} + +static void _ifx_cat1_rtc_from_pdl_time(cy_stc_rtc_config_t *pdlTime, const int year, + struct rtc_time *z_time) +{ + CY_ASSERT(pdlTime != NULL); + CY_ASSERT(z_time != NULL); + + z_time->tm_sec = (int)pdlTime->sec; + z_time->tm_min = (int)pdlTime->min; + z_time->tm_hour = (int)pdlTime->hour; + z_time->tm_mday = (int)pdlTime->date; + z_time->tm_year = (int)(year - _IFX_CAT1_RTC_TM_YEAR_BASE); + + /* The subtraction of 1 here is to translate between internal ifx_cat1 code and the Zephyr + * driver. + */ + z_time->tm_mon = (int)(pdlTime->month - 1u); + z_time->tm_wday = (int)(pdlTime->dayOfWeek - 1u); + + /* year day not known in pdl RTC structure without conversion */ + z_time->tm_yday = -1; + + /* daylight savings currently marked as unknown */ + z_time->tm_isdst = -1; + + /* nanoseconds not tracked by ifx code. Set to value indicating unknown */ + z_time->tm_nsec = 0; +} + +static void _ifx_cat1_rtc_isr_handler(void) +{ + Cy_RTC_Interrupt(_ifx_cat1_rtc_dst, NULL != _ifx_cat1_rtc_dst); +} + +void _ifx_cat1_rtc_century_interrupt(void) +{ + /* The century is stored in its own register so when a "century interrupt" + * occurs at a rollover. The current century is retrieved and 100 is added + * to it and the register is reset to reflect the new century. + * i.e. 1999->2000 + */ + _ifx_cat1_rtc_set_century(_ifx_cat1_rtc_get_century() + 100); +} + +static int ifx_cat1_rtc_init(const struct device *dev) +{ + cy_rslt_t rslt = CY_RSLT_SUCCESS; + + Cy_SysClk_ClkBakSetSource(CY_SYSCLK_BAK_IN_CLKLF); + + if (_ifx_cat1_rtc_get_state() == _IFX_CAT1_RTC_STATE_UNINITIALIZED) { + if (Cy_RTC_IsExternalResetOccurred()) { + _ifx_cat1_rtc_set_century(_IFX_CAT1_RTC_INIT_CENTURY); + } + +#ifdef CONFIG_PM + rslt = Cy_SysPm_RegisterCallback(&_ifx_cat1_rtc_pm_cb) +#endif /* CONFIG_PM */ + + if (rslt == CY_RSLT_SUCCESS) { + _ifx_cat1_rtc_set_state(_IFX_CAT1_RTC_STATE_ENABLED); + } else { + rslt = -EINVAL; + } + + } else if (_ifx_cat1_rtc_get_state() == _IFX_CAT1_RTC_STATE_ENABLED || + _ifx_cat1_rtc_get_state() == _IFX_CAT1_RTC_STATE_TIME_SET) { + + if (Cy_RTC_GetInterruptStatus() & CY_RTC_INTR_CENTURY) { + _ifx_cat1_rtc_century_interrupt(); + } + } + + Cy_RTC_ClearInterrupt(CY_RTC_INTR_CENTURY); + Cy_RTC_SetInterruptMask(CY_RTC_INTR_CENTURY); + + _ifx_cat1_rtc_dst = NULL; + IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), _ifx_cat1_rtc_isr_handler, + DEVICE_DT_INST_GET(0), 0); + irq_enable(DT_INST_IRQN(0)); + + return rslt; +} + +static int ifx_cat1_rtc_set_time(const struct device *dev, const struct rtc_time *timeptr) +{ + struct ifx_cat1_rtc_data *data = dev->data; + + uint32_t sec = timeptr->tm_sec; + uint32_t min = timeptr->tm_min; + uint32_t hour = timeptr->tm_hour; + uint32_t day = timeptr->tm_mday; + /* The addition of 1 here is to translate between internal ifx_cat1 code and the Zephyr + * driver. + */ + uint32_t mon = timeptr->tm_mon + 1; + uint32_t year = timeptr->tm_year + _IFX_CAT1_RTC_TM_YEAR_BASE; + uint32_t year2digit = year % 100; + + cy_rslt_t rslt; + uint32_t retry = 0; + + if (!CY_RTC_IS_SEC_VALID(sec) || !CY_RTC_IS_MIN_VALID(min) || !CY_RTC_IS_HOUR_VALID(hour) || + !CY_RTC_IS_MONTH_VALID(mon) || !CY_RTC_IS_YEAR_SHORT_VALID(year2digit) || + !IFX_CAT1_RTC_VALID_CENTURY(year)) { + + return -EINVAL; + } + do { + if (retry != 0) { + _IFX_CAT1_RTC_WAIT_ONE_MS(); + } + + k_spinlock_key_t key = k_spin_lock(&data->lock); + + rslt = Cy_RTC_SetDateAndTimeDirect(sec, min, hour, day, mon, year2digit); + if (rslt == CY_RSLT_SUCCESS) { + _ifx_cat1_rtc_set_century((uint16_t)(year) - (uint16_t)(year2digit)); + } + + k_spin_unlock(&data->lock, key); + ++retry; + } while (rslt == CY_RTC_INVALID_STATE && retry < _IFX_CAT1_RTC_MAX_RETRY); + + retry = 0; + while (CY_RTC_BUSY == Cy_RTC_GetSyncStatus() && retry < _IFX_CAT1_RTC_MAX_RETRY) { + _IFX_CAT1_RTC_WAIT_ONE_MS(); + ++retry; + } + + if (rslt == CY_RSLT_SUCCESS) { + _ifx_cat1_rtc_set_state(_IFX_CAT1_RTC_STATE_TIME_SET); + return 0; + } else { + return -EINVAL; + } +} + +static int ifx_cat1_rtc_get_time(const struct device *dev, struct rtc_time *timeptr) +{ + struct ifx_cat1_rtc_data *data = dev->data; + + cy_stc_rtc_config_t dateTime = {.hrFormat = CY_RTC_24_HOURS}; + + if (_ifx_cat1_rtc_get_state() != _IFX_CAT1_RTC_STATE_TIME_SET) { + LOG_ERR("Valid time has not been set with rtc_set_time yet"); + return -ENODATA; + } + + k_spinlock_key_t key = k_spin_lock(&data->lock); + + Cy_RTC_GetDateAndTime(&dateTime); + const int year = (int)(dateTime.year + _ifx_cat1_rtc_get_century()); + + k_spin_unlock(&data->lock, key); + + _ifx_cat1_rtc_from_pdl_time(&dateTime, year, timeptr); + + return CY_RSLT_SUCCESS; +} + +#ifdef CONFIG_RTC_CALIBRATION +static int ifx_cat1_set_calibration(const struct device *dev, int32_t calibration) +{ + cy_rslt_t rslt; + + uint8_t uint_calibration; + cy_en_rtc_calib_sign_t calibration_sign; + + if (calibration >= 0) { + calibration_sign = CY_RTC_CALIB_SIGN_POSITIVE; + } else { + calibration = abs(calibration); + calibration_sign = CY_RTC_CALIB_SIGN_NEGATIVE; + } + + uint_calibration = PPB_TO_WCO_PULSE_SETS(calibration); + + /* Maximum calibration value on cat1b of 60 128 tick groupings */ + if (MAX_IFX_CAT1_CAL < uint_calibration) { + /* out of supported range */ + return -EINVAL; + } + + rslt = Cy_RTC_CalibrationControlEnable(uint_calibration, calibration_sign, + CY_RTC_CAL_SEL_CAL1); + if (rslt != CY_RSLT_SUCCESS) { + return -EINVAL; + } + + return 0; +} + +static int ifx_cat1_get_calibration(const struct device *dev, int32_t *calibration) +{ + ARG_UNUSED(dev); + + uint32_t hw_calibration = _FLD2VAL(BACKUP_CAL_CTL_CALIB_VAL, BACKUP_CAL_CTL); + cy_en_rtc_calib_sign_t hw_sign = + (cy_en_rtc_calib_sign_t)(_FLD2VAL(BACKUP_CAL_CTL_CALIB_SIGN, BACKUP_CAL_CTL)); + + if (CY_RTC_CALIB_SIGN_POSITIVE == hw_sign) { + *calibration = WCO_PULSE_SETS_TO_PPB(hw_calibration); + } else { + *calibration = WCO_PULSE_SETS_TO_PPB(hw_calibration) * -1; + } + + return 0; +} +#endif /* CONFIG_RTC_CALIBRATION */ + +static const struct rtc_driver_api ifx_cat1_rtc_driver_api = { + .set_time = ifx_cat1_rtc_set_time, + .get_time = ifx_cat1_rtc_get_time, +#ifdef CONFIG_RTC_CALIBRATION + .set_calibration = ifx_cat1_set_calibration, + .get_calibration = ifx_cat1_get_calibration, +#endif +}; + +#define INFINEON_CAT1_RTC_INIT(n) \ + static struct ifx_cat1_rtc_data ifx_cat1_rtc_data##n; \ + \ + DEVICE_DT_INST_DEFINE(n, ifx_cat1_rtc_init, NULL, &ifx_cat1_rtc_data##n, \ + NULL, PRE_KERNEL_1, CONFIG_RTC_INIT_PRIORITY, \ + &ifx_cat1_rtc_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(INFINEON_CAT1_RTC_INIT) diff --git a/dts/arm/infineon/cat1b/cyw20829/cyw20829.dtsi b/dts/arm/infineon/cat1b/cyw20829/cyw20829.dtsi index 1b90d91b578425..8f609c5309c1fa 100644 --- a/dts/arm/infineon/cat1b/cyw20829/cyw20829.dtsi +++ b/dts/arm/infineon/cat1b/cyw20829/cyw20829.dtsi @@ -138,6 +138,14 @@ status = "disabled"; }; + rtc0: rtc@40220000 { + compatible = "infineon,cat1-rtc"; + reg = <0x40220000 0x10000>; + interrupts = <10 6>; + alarms-count = <2>; + status = "disabled"; + }; + counter0_0: counter@404a0000 { compatible = "infineon,cat1-counter"; reg = <0x404a0000 0x80>; diff --git a/dts/bindings/rtc/infineon,cat1-rtc.yaml b/dts/bindings/rtc/infineon,cat1-rtc.yaml new file mode 100644 index 00000000000000..b70868e95bb5ab --- /dev/null +++ b/dts/bindings/rtc/infineon,cat1-rtc.yaml @@ -0,0 +1,17 @@ +# Copyright (c) 2024 Cypress Semiconductor Corporation (an Infineon company) or +# an affiliate of Cypress Semiconductor Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +description: Infineon CAT1 family RTC device + +compatible: "infineon,cat1-rtc" + +include: rtc-device.yaml + +properties: + reg: + required: true + + interrupts: + required: true diff --git a/tests/drivers/rtc/rtc_api/boards/cyw920829m2evk_02.conf b/tests/drivers/rtc/rtc_api/boards/cyw920829m2evk_02.conf new file mode 100644 index 00000000000000..088271dfac366e --- /dev/null +++ b/tests/drivers/rtc/rtc_api/boards/cyw920829m2evk_02.conf @@ -0,0 +1 @@ +CONFIG_RTC_CALIBRATION=y diff --git a/tests/drivers/rtc/rtc_api/boards/cyw920829m2evk_02.overlay b/tests/drivers/rtc/rtc_api/boards/cyw920829m2evk_02.overlay new file mode 100644 index 00000000000000..d73900529812ee --- /dev/null +++ b/tests/drivers/rtc/rtc_api/boards/cyw920829m2evk_02.overlay @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Cypress Semiconductor Corporation (an Infineon company) or + * an affiliate of Cypress Semiconductor Corporation + */ + +/ { + aliases { + rtc = &rtc0; + }; +}; + +&rtc0 { + status = "okay"; +};