| /* |
| * Copyright 2024 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nxp_irtc |
| |
| #include <zephyr/devicetree.h> |
| #include <zephyr/drivers/rtc.h> |
| #include <zephyr/irq.h> |
| #include "rtc_utils.h" |
| |
| struct nxp_irtc_config { |
| RTC_Type *base; |
| void (*irq_config_func)(const struct device *dev); |
| bool is_output_clock_enabled; |
| uint8_t clock_src; |
| uint8_t alarm_match_flag; |
| }; |
| |
| struct nxp_irtc_data { |
| bool is_dst_enabled; |
| #ifdef CONFIG_RTC_ALARM |
| rtc_alarm_callback alarm_callback; |
| void *alarm_user_data; |
| uint16_t alarm_mask; |
| #endif /* CONFIG_RTC_ALARM */ |
| }; |
| |
| /* The IRTC Offset is 2112 instead of 1900 [2112 - 1900] -> [212] */ |
| #define RTC_NXP_IRTC_YEAR_OFFSET 212 |
| |
| #define RTC_NXP_GET_REG_FIELD(_reg, _name, _field) \ |
| ((_reg->_name & RTC_##_name##_##_field##_MASK) >> \ |
| RTC_##_name##_##_field##_SHIFT) |
| |
| /* |
| * The runtime where this is accessed is unknown so we force a lock on the registers then force an |
| * unlock to guarantee 2 seconds of write time |
| */ |
| static void nxp_irtc_unlock_registers(RTC_Type *reg) |
| { |
| /* Lock the regsiters */ |
| while ((reg->STATUS & (uint16_t)RTC_STATUS_WRITE_PROT_EN_MASK) == 0) { |
| *(uint8_t *)(®->STATUS) |= RTC_STATUS_WE(0x2); |
| } |
| /* Unlock the registers */ |
| while ((reg->STATUS & (int16_t)RTC_STATUS_WRITE_PROT_EN_MASK) != 0) { |
| /* |
| * The casting is required for writing only a single Byte to the STATUS Register, |
| * the pattern here unlocks all RTC registers for writing. When unlocked if a 0x20 |
| * if written to the STATUS register the RTC registers will lock again and the next |
| * write will lead to a fault. |
| */ |
| *(volatile uint8_t *)(®->STATUS) = 0x00; |
| *(volatile uint8_t *)(®->STATUS) = 0x40; |
| *(volatile uint8_t *)(®->STATUS) = 0xC0; |
| *(volatile uint8_t *)(®->STATUS) = 0x80; |
| } |
| } |
| |
| static int nxp_irtc_set_time(const struct device *dev, const struct rtc_time *timeptr) |
| { |
| const struct nxp_irtc_config *config = dev->config; |
| struct nxp_irtc_data *data = dev->data; |
| RTC_Type *irtc_reg = config->base; |
| |
| if (!timeptr || !rtc_utils_validate_rtc_time(timeptr, 0)) { |
| return -EINVAL; |
| } |
| |
| int calc_year = timeptr->tm_year - RTC_NXP_IRTC_YEAR_OFFSET; |
| /* The IRTC Month Index starts at 1 instead of 0 */ |
| int calc_month = timeptr->tm_mon + 1; |
| |
| uint32_t key = irq_lock(); |
| |
| nxp_irtc_unlock_registers(irtc_reg); |
| irtc_reg->SECONDS = RTC_SECONDS_SEC_CNT(timeptr->tm_sec); |
| |
| irtc_reg->HOURMIN = RTC_HOURMIN_MIN_CNT(timeptr->tm_min) | |
| RTC_HOURMIN_HOUR_CNT(timeptr->tm_hour); |
| |
| /* 1 is valid for rtc_time.tm_wday property but is out of bounds for IRTC registers */ |
| irtc_reg->DAYS = RTC_DAYS_DAY_CNT(timeptr->tm_mday) | |
| (timeptr->tm_wday == -1 ? 0 : RTC_DAYS_DOW(timeptr->tm_wday)); |
| |
| irtc_reg->YEARMON = RTC_YEARMON_MON_CNT(calc_month) | RTC_YEARMON_YROFST(calc_year); |
| |
| if (timeptr->tm_isdst != -1) { |
| irtc_reg->CTRL |= RTC_CTRL_DST_EN(timeptr->tm_isdst); |
| data->is_dst_enabled = true; |
| } |
| |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static int nxp_irtc_get_time(const struct device *dev, struct rtc_time *timeptr) |
| { |
| const struct nxp_irtc_config *config = dev->config; |
| struct nxp_irtc_data *data = dev->data; |
| RTC_Type *irtc_reg = config->base; |
| |
| __ASSERT(timeptr != 0, "timeptr has not been set"); |
| |
| timeptr->tm_sec = RTC_NXP_GET_REG_FIELD(irtc_reg, SECONDS, SEC_CNT); |
| timeptr->tm_min = RTC_NXP_GET_REG_FIELD(irtc_reg, HOURMIN, MIN_CNT); |
| timeptr->tm_hour = RTC_NXP_GET_REG_FIELD(irtc_reg, HOURMIN, HOUR_CNT); |
| timeptr->tm_wday = RTC_NXP_GET_REG_FIELD(irtc_reg, DAYS, DOW); |
| timeptr->tm_mday = RTC_NXP_GET_REG_FIELD(irtc_reg, DAYS, DAY_CNT); |
| timeptr->tm_mon = RTC_NXP_GET_REG_FIELD(irtc_reg, YEARMON, MON_CNT) - 1; |
| timeptr->tm_year = (int8_t)RTC_NXP_GET_REG_FIELD(irtc_reg, YEARMON, YROFST) + |
| RTC_NXP_IRTC_YEAR_OFFSET; |
| if (data->is_dst_enabled) { |
| timeptr->tm_isdst = |
| ((irtc_reg->CTRL & RTC_CTRL_DST_EN_MASK) >> RTC_CTRL_DST_EN_SHIFT); |
| } |
| |
| /* There is no nano second support for IRTC */ |
| timeptr->tm_nsec = 0; |
| /* There is no day of the year support for IRTC */ |
| timeptr->tm_yday = -1; |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_RTC_ALARM) |
| static int nxp_irtc_alarm_get_supported_fields(const struct device *dev, uint16_t id, |
| uint16_t *mask) |
| { |
| ARG_UNUSED(dev); |
| |
| if (id != 0) { |
| return -EINVAL; |
| } |
| |
| *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); |
| |
| return 0; |
| } |
| |
| static int nxp_irtc_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask, |
| const struct rtc_time *timeptr) |
| { |
| const struct nxp_irtc_config *config = dev->config; |
| struct nxp_irtc_data *data = dev->data; |
| RTC_Type *irtc_reg = config->base; |
| |
| if (id != 0 || (mask && (timeptr == 0)) || |
| (timeptr && !rtc_utils_validate_rtc_time(timeptr, mask))) { |
| return -EINVAL; |
| } |
| |
| uint32_t key = irq_lock(); |
| |
| nxp_irtc_unlock_registers(irtc_reg); |
| |
| if (mask & RTC_ALARM_TIME_MASK_SECOND) { |
| irtc_reg->ALM_SECONDS = RTC_ALM_SECONDS_ALM_SEC(timeptr->tm_sec); |
| } |
| |
| if (mask & RTC_ALARM_TIME_MASK_MINUTE) { |
| irtc_reg->ALM_HOURMIN = RTC_ALM_HOURMIN_ALM_MIN(timeptr->tm_min); |
| } |
| |
| if (mask & RTC_ALARM_TIME_MASK_HOUR) { |
| irtc_reg->ALM_HOURMIN |= RTC_ALM_HOURMIN_ALM_HOUR(timeptr->tm_hour); |
| } |
| |
| if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) { |
| irtc_reg->ALM_DAYS = RTC_ALM_DAYS_ALM_DAY(timeptr->tm_mday); |
| } |
| |
| if (mask & RTC_ALARM_TIME_MASK_MONTH) { |
| irtc_reg->ALM_YEARMON = RTC_ALM_YEARMON_ALM_MON(timeptr->tm_mon + 1); |
| } |
| |
| if (mask & RTC_ALARM_TIME_MASK_YEAR) { |
| irtc_reg->ALM_YEARMON |= |
| RTC_ALM_YEARMON_ALM_YEAR(timeptr->tm_year - RTC_NXP_IRTC_YEAR_OFFSET); |
| } |
| |
| /* Clearing out the ALARM Flag Field then setting the correct value */ |
| irtc_reg->CTRL &= ~(0xC); |
| switch (mask) { |
| case 0x0F: |
| irtc_reg->CTRL |= RTC_CTRL_ALM_MATCH(0x4); |
| break; |
| case 0x1F: |
| irtc_reg->CTRL |= RTC_CTRL_ALM_MATCH(0x8); |
| break; |
| case 0x3F: |
| irtc_reg->CTRL |= RTC_CTRL_ALM_MATCH(0xC); |
| break; |
| default: |
| irtc_reg->CTRL |= RTC_CTRL_ALM_MATCH(0x0); |
| } |
| |
| /* Enabling Alarm Interrupts */ |
| irtc_reg->IER |= RTC_ISR_ALM_IS_MASK; |
| data->alarm_mask = mask; |
| |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static int nxp_irtc_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask, |
| struct rtc_time *timeptr) |
| { |
| const struct nxp_irtc_config *config = dev->config; |
| struct nxp_irtc_data *data = dev->data; |
| RTC_Type *irtc_reg = config->base; |
| uint16_t curr_alarm_mask = data->alarm_mask; |
| uint16_t return_mask = 0; |
| |
| if (id != 0 || !timeptr) { |
| return -EINVAL; |
| } |
| |
| if (curr_alarm_mask & RTC_ALARM_TIME_MASK_SECOND) { |
| timeptr->tm_sec = RTC_NXP_GET_REG_FIELD(irtc_reg, ALM_SECONDS, ALM_SEC); |
| return_mask |= RTC_ALARM_TIME_MASK_SECOND; |
| } |
| |
| if (curr_alarm_mask & RTC_ALARM_TIME_MASK_MINUTE) { |
| timeptr->tm_min = RTC_NXP_GET_REG_FIELD(irtc_reg, HOURMIN, MIN_CNT); |
| return_mask |= RTC_ALARM_TIME_MASK_MINUTE; |
| } |
| |
| if (curr_alarm_mask & RTC_ALARM_TIME_MASK_HOUR) { |
| timeptr->tm_hour = RTC_NXP_GET_REG_FIELD(irtc_reg, HOURMIN, HOUR_CNT); |
| return_mask |= RTC_ALARM_TIME_MASK_HOUR; |
| } |
| |
| if (curr_alarm_mask & RTC_ALARM_TIME_MASK_MONTHDAY) { |
| timeptr->tm_mday = RTC_NXP_GET_REG_FIELD(irtc_reg, DAYS, DAY_CNT); |
| return_mask |= RTC_ALARM_TIME_MASK_MONTHDAY; |
| } |
| |
| if (curr_alarm_mask & RTC_ALARM_TIME_MASK_MONTH) { |
| timeptr->tm_mon = RTC_NXP_GET_REG_FIELD(irtc_reg, YEARMON, MON_CNT) - 1; |
| return_mask |= RTC_ALARM_TIME_MASK_MONTH; |
| } |
| |
| if (curr_alarm_mask & RTC_ALARM_TIME_MASK_YEAR) { |
| timeptr->tm_year = (int8_t)RTC_NXP_GET_REG_FIELD(irtc_reg, YEARMON, YROFST) + |
| RTC_NXP_IRTC_YEAR_OFFSET; |
| return_mask |= RTC_ALARM_TIME_MASK_YEAR; |
| } |
| |
| *mask = return_mask; |
| |
| return 0; |
| } |
| |
| static int nxp_irtc_alarm_is_pending(const struct device *dev, uint16_t id) |
| { |
| struct nxp_irtc_data *data = dev->data; |
| RTC_Type *irtc_reg = config->base; |
| |
| if (id != 0) { |
| return -EINVAL; |
| } |
| |
| return RTC_ISR_ALM_IS(0x4); |
| } |
| |
| static int nxp_irtc_alarm_set_callback(const struct device *dev, uint16_t id, |
| rtc_alarm_callback callback, void *user_data) |
| { |
| struct nxp_irtc_data *data = dev->data; |
| |
| if (id != 0) { |
| return -EINVAL; |
| } |
| |
| uint32_t key = irq_lock(); |
| |
| data->alarm_callback = callback; |
| data->alarm_user_data = user_data; |
| |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| #endif /* CONFIG_RTC_ALARM */ |
| #if defined(CONFIG_RTC_UPDATE) |
| |
| static int nxp_irtc_update_set_callback(const struct device *dev, rtc_update_callback callback, |
| void *user_data) |
| { |
| ARG_UNUSED(dev); |
| ARG_UNUSED(calibration); |
| ARG_UNUSED(user_data); |
| return -ENOTSUP; |
| } |
| |
| #endif /* CONFIG_RTC_UPDATE */ |
| #if defined(CONFIG_RTC_CALIBRATION) |
| |
| static int nxp_irtc_set_calibration(const struct device *dev, int32_t calibration) |
| { |
| ARG_UNUSED(dev); |
| ARG_UNUSED(calibration); |
| return -ENOTSUP; |
| } |
| |
| static int nxp_irtc_get_calibration(const struct device *dev, int32_t *calibration) |
| { |
| ARG_UNUSED(dev); |
| ARG_UNUSED(calibration); |
| return -ENOTSUP; |
| } |
| |
| #endif /* CONFIG_RTC_CALIBRATION */ |
| |
| static int nxp_irtc_init(const struct device *dev) |
| { |
| const struct nxp_irtc_config *config = dev->config; |
| RTC_Type *irtc_reg = config->base; |
| |
| nxp_irtc_unlock_registers(irtc_reg); |
| |
| /* set the control register bits */ |
| irtc_reg->CTRL = RTC_CTRL_CLK_SEL(config->clock_src) | |
| RTC_CTRL_CLKO_DIS(!config->is_output_clock_enabled); |
| |
| config->irq_config_func(dev); |
| |
| return 0; |
| } |
| |
| static void nxp_irtc_isr(const struct device *dev) |
| { |
| #ifdef CONFIG_RTC_ALARM |
| const struct nxp_irtc_config *config = dev->config; |
| RTC_Type *irtc_reg = config->base; |
| struct nxp_irtc_data *data = dev->data; |
| uint32_t key = irq_lock(); |
| |
| nxp_irtc_unlock_registers(irtc_reg); |
| /* Clearing ISR Register since w1c */ |
| irtc_reg->ISR = irtc_reg->ISR; |
| |
| if (data->alarm_callback) { |
| data->alarm_callback(dev, 0, data->alarm_user_data); |
| } |
| irq_unlock(key); |
| #endif /* CONFIG_RTC_ALARM */ |
| } |
| |
| static const struct rtc_driver_api rtc_nxp_irtc_driver_api = { |
| .set_time = nxp_irtc_set_time, |
| .get_time = nxp_irtc_get_time, |
| #if defined(CONFIG_RTC_ALARM) |
| .alarm_get_supported_fields = nxp_irtc_alarm_get_supported_fields, |
| .alarm_set_time = nxp_irtc_alarm_set_time, |
| .alarm_get_time = nxp_irtc_alarm_get_time, |
| .alarm_is_pending = nxp_irtc_alarm_is_pending, |
| .alarm_set_callback = nxp_irtc_alarm_set_callback, |
| #endif /* CONFIG_RTC_ALARM */ |
| #if defined(CONFIG_RTC_UPDATE) |
| .update_set_callback = nxp_irtc_update_set_callback, |
| #endif /* CONFIG_RTC_UPDATE */ |
| #if defined(CONFIG_RTC_CALIBRATION) |
| .set_calibration = nxp_irtc_set_calibration, |
| .get_calibration = nxp_irtc_get_calibration, |
| #endif /* CONFIG_RTC_CALIBRATION */ |
| }; |
| |
| #define RTC_NXP_IRTC_DEVICE_INIT(n) \ |
| static void nxp_irtc_config_func_##n(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), nxp_irtc_isr, \ |
| DEVICE_DT_INST_GET(n), 0); \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| } \ |
| static const struct nxp_irtc_config nxp_irtc_config_##n = { \ |
| .base = (RTC_Type *)DT_INST_REG_ADDR(n), \ |
| .clock_src = DT_INST_PROP(n, clock_src), \ |
| .is_output_clock_enabled = DT_INST_PROP(n, output_clk_en), \ |
| .irq_config_func = nxp_irtc_config_func_##n, \ |
| }; \ |
| \ |
| static struct nxp_irtc_data nxp_irtc_data_##n; \ |
| \ |
| DEVICE_DT_INST_DEFINE(n, nxp_irtc_init, NULL, &nxp_irtc_data_##n, &nxp_irtc_config_##n, \ |
| PRE_KERNEL_1, CONFIG_RTC_INIT_PRIORITY, &rtc_nxp_irtc_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(RTC_NXP_IRTC_DEVICE_INIT) |