/* * Copyright 2025 NXP * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT zephyr_rtc_counter #include #include #include #include #include #include #include #include "rtc_utils.h" LOG_MODULE_REGISTER(rtc_counter, CONFIG_RTC_LOG_LEVEL); struct rtc_counter_config { const struct device *counter_dev; /* Number of alarm channels */ uint8_t alarms_count; }; struct rtc_counter_data { /* Offset in counter ticks between raw counter and Unix epoch */ int64_t epoch_offset; /* protects epoch_offset */ struct k_spinlock lock; #ifdef CONFIG_RTC_ALARM /* false if counter has no alarm channels */ bool alarm_capable; /* number of alarm channels exposed (<= underlying counter channels) */ uint8_t num_alarm_chans; const struct device *rtc_dev; /* Per-instance arrays provided by init macro */ rtc_alarm_callback *alarm_callback; void **alarm_user_data; uint16_t *alarm_mask; struct rtc_time *alarm_time; bool *alarm_pending; #endif /* CONFIG_RTC_ALARM */ }; /* * Generic RTC time to ticks conversion using time.h and counter API. * Returns 0 on success, -EINVAL on invalid time or overflow. */ static int rtc_counter_time_to_ticks(const struct rtc_time *timeptr, uint32_t tick_freq, uint64_t *ticks_out) { struct tm tm_val; int64_t seconds64; if (timeptr == NULL || ticks_out == NULL) { return -EINVAL; } /* Populate broken-down time structure */ memset(&tm_val, 0, sizeof(tm_val)); tm_val.tm_sec = timeptr->tm_sec; tm_val.tm_min = timeptr->tm_min; tm_val.tm_hour = timeptr->tm_hour; tm_val.tm_mday = timeptr->tm_mday; tm_val.tm_mon = timeptr->tm_mon; tm_val.tm_year = timeptr->tm_year; tm_val.tm_isdst = -1; /* UTC, 64-bit, no DST/timezone ambiguity */ seconds64 = timeutil_timegm64(&tm_val); if (seconds64 < 0) { return -EINVAL; } if (tick_freq == 0U) { return -ERANGE; } /* Guard overflow: ticks = seconds * freq must fit into 64-bit */ if ((uint64_t)seconds64 > (uint64_t)INT64_MAX / (uint64_t)tick_freq) { return -ERANGE; } *ticks_out = (uint64_t)seconds64 * (uint64_t)tick_freq; return 0; } /* Generic RTC ticks to time conversion using time.h and counter API */ static void rtc_counter_ticks_to_time(uint64_t ticks, uint32_t tick_freq, struct rtc_time *timeptr) { time_t seconds; struct tm tm_val; if (tick_freq == 0U) { memset(timeptr, 0, sizeof(struct rtc_time)); return; } seconds = (time_t)(ticks / (uint64_t)tick_freq); if (gmtime_r(&seconds, &tm_val) == NULL) { memset(timeptr, 0, sizeof(struct rtc_time)); return; } timeptr->tm_sec = tm_val.tm_sec; timeptr->tm_min = tm_val.tm_min; timeptr->tm_hour = tm_val.tm_hour; timeptr->tm_mday = tm_val.tm_mday; timeptr->tm_mon = tm_val.tm_mon; timeptr->tm_year = tm_val.tm_year; timeptr->tm_wday = tm_val.tm_wday; timeptr->tm_yday = tm_val.tm_yday; timeptr->tm_isdst = -1; timeptr->tm_nsec = 0; } #if defined(CONFIG_RTC_ALARM) static void rtc_counter_alarm_callback(const struct device *counter_dev, uint8_t chan_id, uint32_t ticks, void *user_data) { struct rtc_counter_data *data = (struct rtc_counter_data *)user_data; const struct device *rtc_dev = data->rtc_dev; if (chan_id < data->num_alarm_chans && data->alarm_callback[chan_id] != NULL) { data->alarm_callback[chan_id](rtc_dev, chan_id, data->alarm_user_data[chan_id]); data->alarm_pending[chan_id] = false; } else if (chan_id < data->num_alarm_chans) { data->alarm_pending[chan_id] = true; } else { LOG_DBG("Spurious alarm callback on channel %u (max %u)", chan_id, data->num_alarm_chans ? (data->num_alarm_chans - 1U) : 0U); } } static int rtc_counter_alarm_get_supported_fields(const struct device *dev, uint16_t id, uint16_t *mask) { struct rtc_counter_data *data = dev->data; if (!data->alarm_capable) { return -ENOTSUP; } if (id >= data->num_alarm_chans) { 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 | RTC_ALARM_TIME_MASK_WEEKDAY | RTC_ALARM_TIME_MASK_YEARDAY); return 0; } static int rtc_counter_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask, const struct rtc_time *timeptr) { const struct rtc_counter_config *config = dev->config; struct rtc_counter_data *data = dev->data; uint64_t desired_ticks; uint32_t freq; int ret; int64_t epoch; int64_t raw_alarm_ticks_64; uint32_t top; uint32_t alarm_ticks; uint32_t now_raw; struct counter_alarm_cfg alarm_cfg; if (!data->alarm_capable) { return -ENOTSUP; } if (id >= data->num_alarm_chans) { return -EINVAL; } if (mask == 0U) { /* Disable alarm on selected channel */ ret = counter_cancel_channel_alarm(config->counter_dev, (uint8_t)id); if (ret == 0) { K_SPINLOCK(&data->lock) { data->alarm_mask[id] = 0U; data->alarm_pending[id] = false; memset(&data->alarm_time[id], 0, sizeof(struct rtc_time)); } } return ret; } if (timeptr == NULL) { return -EINVAL; } if (!rtc_utils_validate_rtc_time(timeptr, mask)) { return -EINVAL; } freq = counter_get_frequency(config->counter_dev); ret = rtc_counter_time_to_ticks(timeptr, freq, &desired_ticks); /* -EINVAL on overflow/invalid time */ if (ret < 0) { return ret; } /* Convert desired absolute Unix time to a raw tick value for the counter */ K_SPINLOCK(&data->lock) { epoch = data->epoch_offset; } raw_alarm_ticks_64 = (int64_t)desired_ticks - epoch; /* Reject wraparound: must be within current window and not in the past */ top = counter_get_top_value(config->counter_dev); /* would require wrap */ if (raw_alarm_ticks_64 < 0 || raw_alarm_ticks_64 > (int64_t)top) { return -ERANGE; } alarm_ticks = (uint32_t)raw_alarm_ticks_64; ret = counter_get_value(config->counter_dev, &now_raw); if (ret < 0) { return ret; } /* target already passed in this window or equals 'now' */ if (alarm_ticks <= now_raw) { /* 1-tick guard, but cannot wrap */ if (now_raw == top) { return -ERANGE; } alarm_ticks = now_raw + 1U; } alarm_cfg.callback = rtc_counter_alarm_callback; alarm_cfg.ticks = alarm_ticks; alarm_cfg.user_data = data; alarm_cfg.flags = COUNTER_ALARM_CFG_ABSOLUTE | COUNTER_ALARM_CFG_EXPIRE_WHEN_LATE; /* Record configured mask and time for get_time; clear pending */ K_SPINLOCK(&data->lock) { data->alarm_mask[id] = mask; data->alarm_time[id] = *timeptr; data->alarm_pending[id] = false; } return counter_set_channel_alarm(config->counter_dev, (uint8_t)id, &alarm_cfg); } static int rtc_counter_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask, struct rtc_time *timeptr) { struct rtc_counter_data *data = dev->data; int ret = 0; if (!data->alarm_capable) { return -ENOTSUP; } if (id >= data->num_alarm_chans) { return -EINVAL; } if (mask == NULL || timeptr == NULL) { return -EINVAL; } K_SPINLOCK(&data->lock) { if (data->alarm_mask[id] == 0U) { ret = -EINVAL; } else { *mask = data->alarm_mask[id]; *timeptr = data->alarm_time[id]; } } return ret; } static int rtc_counter_alarm_is_pending(const struct device *dev, uint16_t id) { int ret = 0; struct rtc_counter_data *data = dev->data; if (!data->alarm_capable) { return -ENOTSUP; } if (id >= data->num_alarm_chans) { return -EINVAL; } K_SPINLOCK(&data->lock) { ret = data->alarm_pending[id] ? 1 : 0; data->alarm_pending[id] = false; } return ret; } static int rtc_counter_alarm_set_callback(const struct device *dev, uint16_t id, rtc_alarm_callback callback, void *user_data) { struct rtc_counter_data *data = dev->data; if (!data->alarm_capable) { return -ENOTSUP; } if (id >= data->num_alarm_chans) { return -EINVAL; } K_SPINLOCK(&data->lock) { data->alarm_callback[id] = callback; data->alarm_user_data[id] = user_data; } return 0; } /* Compute rearm tick within current counter window. * Returns true and writes the tick to out_ticks; returns false if it should be skipped. */ static inline bool rtc_counter_compute_rearm_ticks(int64_t raw_alarm_ticks_64, uint32_t now_raw, uint32_t top, uint32_t *out_ticks) { /* Out of range */ if (raw_alarm_ticks_64 > (int64_t)top) { return false; } /* In the past: arm to next tick if possible */ if (raw_alarm_ticks_64 < 0) { if (now_raw == top) { return false; } *out_ticks = now_raw + 1U; return true; } /* Within range */ *out_ticks = (uint32_t)raw_alarm_ticks_64; if (*out_ticks <= now_raw) { if (now_raw == top) { return false; } *out_ticks = now_raw + 1U; } return true; } /* Recompute and rearm all active alarms after a time base (epoch) change */ static void rtc_counter_reschedule_alarms(const struct device *dev) { const struct rtc_counter_config *config = dev->config; struct rtc_counter_data *data = dev->data; uint16_t configured_mask; struct rtc_time configured_time; uint64_t alarm_abs_ticks; uint32_t freq; int64_t epoch; int64_t raw_alarm_ticks_64; uint32_t top; uint32_t now_raw; uint32_t alarm_ticks; struct counter_alarm_cfg alarm_cfg; /* Time base changed: reschedule any active alarms to the new epoch */ if (!(data->alarm_capable && data->num_alarm_chans > 0U)) { return; } for (uint8_t id = 0U; id < data->num_alarm_chans; id++) { /* Snapshot configured alarm (if any) and clear pending */ K_SPINLOCK(&data->lock) { configured_mask = data->alarm_mask[id]; configured_time = data->alarm_time[id]; data->alarm_pending[id] = false; } if (configured_mask == 0U) { continue; } /* Cancel any in-flight alarm before reprogramming */ (void)counter_cancel_channel_alarm(config->counter_dev, (uint8_t)id); freq = counter_get_frequency(config->counter_dev); if (rtc_counter_time_to_ticks(&configured_time, freq, &alarm_abs_ticks) < 0) { /* Should not happen: skip this alarm */ continue; } K_SPINLOCK(&data->lock) { epoch = data->epoch_offset; } raw_alarm_ticks_64 = (int64_t)alarm_abs_ticks - epoch; top = counter_get_top_value(config->counter_dev); if (counter_get_value(config->counter_dev, &now_raw) < 0) { continue; } if (!rtc_counter_compute_rearm_ticks(raw_alarm_ticks_64, now_raw, top, &alarm_ticks)) { continue; } alarm_cfg.callback = rtc_counter_alarm_callback; alarm_cfg.ticks = alarm_ticks; alarm_cfg.user_data = data; alarm_cfg.flags = COUNTER_ALARM_CFG_ABSOLUTE | COUNTER_ALARM_CFG_EXPIRE_WHEN_LATE; (void)counter_set_channel_alarm(config->counter_dev, (uint8_t)id, &alarm_cfg); } } #endif /* CONFIG_RTC_ALARM */ static int rtc_counter_set_time(const struct device *dev, const struct rtc_time *timeptr) { const struct rtc_counter_config *config = dev->config; struct rtc_counter_data *data = dev->data; uint64_t desired_ticks = 0; uint32_t now_ticks = 0; int ret; uint32_t freq; if (timeptr == NULL) { return -EINVAL; } freq = counter_get_frequency(config->counter_dev); ret = rtc_counter_time_to_ticks(timeptr, freq, &desired_ticks); /* -EINVAL on overflow/invalid time */ if (ret < 0) { return ret; } /* Stop counter */ ret = counter_stop(config->counter_dev); if (ret < 0) { return ret; } ret = counter_get_value(config->counter_dev, &now_ticks); if (ret < 0) { return ret; } /* Update the software offset (in ticks): offset = desired_ticks - now_ticks */ K_SPINLOCK(&data->lock) { data->epoch_offset = (int64_t)desired_ticks - (int64_t)now_ticks; } #ifdef CONFIG_RTC_ALARM rtc_counter_reschedule_alarms(dev); #endif /* CONFIG_RTC_ALARM */ /* Restart counter */ ret = counter_start(config->counter_dev); if (ret < 0) { return ret; } return 0; } static int rtc_counter_get_time(const struct device *dev, struct rtc_time *timeptr) { const struct rtc_counter_config *config = dev->config; struct rtc_counter_data *data = dev->data; uint32_t now_ticks; int ret; int64_t epoch; uint64_t current_ticks_64; uint32_t freq; if (timeptr == NULL) { return -EINVAL; } ret = counter_get_value(config->counter_dev, &now_ticks); if (ret < 0) { return ret; } K_SPINLOCK(&data->lock) { epoch = data->epoch_offset; } /* Compute accumulated ticks (may be > 32-bit) */ int64_t sum_ticks = (int64_t)now_ticks + epoch; if (sum_ticks < 0) { return -ERANGE; } current_ticks_64 = (uint64_t)sum_ticks; freq = counter_get_frequency(config->counter_dev); /* Optional guard: derived seconds must fit 32-bit to mirror prior check */ if (freq == 0U) { return -ERANGE; } if ((current_ticks_64 / (uint64_t)freq) > (uint64_t)UINT32_MAX) { return -ERANGE; } memset(timeptr, 0, sizeof(struct rtc_time)); rtc_counter_ticks_to_time(current_ticks_64, freq, timeptr); return 0; } #if defined(CONFIG_RTC_UPDATE) static int rtc_counter_update_set_callback(const struct device *dev, rtc_update_callback callback, void *user_data) { ARG_UNUSED(dev); ARG_UNUSED(callback); ARG_UNUSED(user_data); return -ENOTSUP; } #endif /* CONFIG_RTC_UPDATE */ #if defined(CONFIG_RTC_CALIBRATION) static int rtc_counter_set_calibration(const struct device *dev, int32_t calibration) { ARG_UNUSED(dev); ARG_UNUSED(calibration); return -ENOTSUP; } static int rtc_counter_get_calibration(const struct device *dev, int32_t *calibration) { ARG_UNUSED(dev); ARG_UNUSED(calibration); return -ENOTSUP; } #endif /* CONFIG_RTC_CALIBRATION */ static int rtc_counter_init(const struct device *dev) { const struct rtc_counter_config *config = dev->config; struct rtc_counter_data *data = dev->data; uint32_t freq; if (!device_is_ready(config->counter_dev)) { LOG_ERR("Counter device %s not ready", config->counter_dev->name); return -ENODEV; } /* Validate counter frequency (must be non-zero) */ freq = counter_get_frequency(config->counter_dev); if (freq == 0U) { LOG_ERR("Unsupported counter frequency: %u Hz", freq); return -ENOTSUP; } /* Start with zero offset until rtc_set_time is called */ data->epoch_offset = 0; #ifdef CONFIG_RTC_ALARM data->rtc_dev = dev; if (config->alarms_count == 0U) { data->alarm_capable = false; } else { data->alarm_capable = true; data->num_alarm_chans = config->alarms_count; /* Clear per-channel state */ for (uint8_t i = 0; i < data->num_alarm_chans; i++) { data->alarm_callback[i] = NULL; data->alarm_user_data[i] = NULL; data->alarm_mask[i] = 0; memset(&data->alarm_time[i], 0, sizeof(struct rtc_time)); data->alarm_pending[i] = false; } } #endif /* CONFIG_RTC_ALARM */ return 0; } static DEVICE_API(rtc, rtc_counter_driver_api) = { .set_time = rtc_counter_set_time, .get_time = rtc_counter_get_time, #if defined(CONFIG_RTC_ALARM) .alarm_get_supported_fields = rtc_counter_alarm_get_supported_fields, .alarm_set_time = rtc_counter_alarm_set_time, .alarm_get_time = rtc_counter_alarm_get_time, .alarm_is_pending = rtc_counter_alarm_is_pending, .alarm_set_callback = rtc_counter_alarm_set_callback, #endif /* CONFIG_RTC_ALARM */ #if defined(CONFIG_RTC_UPDATE) .update_set_callback = rtc_counter_update_set_callback, #endif /* CONFIG_RTC_UPDATE */ #if defined(CONFIG_RTC_CALIBRATION) .set_calibration = rtc_counter_set_calibration, .get_calibration = rtc_counter_get_calibration, #endif /* CONFIG_RTC_CALIBRATION */ }; /* Ensure RTC init priority is bigger than counter */ BUILD_ASSERT(CONFIG_RTC_INIT_PRIORITY > CONFIG_COUNTER_INIT_PRIORITY, "RTC init priority must be bigger than counter"); #define RTC_COUNTER_ALARMS_COUNT(n) DT_PROP_OR(DT_DRV_INST(n), alarms_count, 0) #define RTC_COUNTER_ALARMS_SZ(n) MAX(RTC_COUNTER_ALARMS_COUNT(n), 1) #if defined(CONFIG_RTC_ALARM) /* Per-instance static storage for alarm context, sized from DT */ #define RTC_COUNTER_DECLARE_ALARM_STORAGE(n) \ static rtc_alarm_callback rtc_counter_alarm_callback_arr_##n[RTC_COUNTER_ALARMS_SZ(n)]; \ static void *rtc_counter_alarm_user_data_arr_##n[RTC_COUNTER_ALARMS_SZ(n)]; \ static uint16_t rtc_counter_alarm_mask_arr_##n[RTC_COUNTER_ALARMS_SZ(n)]; \ static struct rtc_time rtc_counter_alarm_time_arr_##n[RTC_COUNTER_ALARMS_SZ(n)]; \ static bool rtc_counter_alarm_pending_arr_##n[RTC_COUNTER_ALARMS_SZ(n)]; DT_INST_FOREACH_STATUS_OKAY(RTC_COUNTER_DECLARE_ALARM_STORAGE) #endif /* CONFIG_RTC_ALARM */ #define RTC_COUNTER_DEVICE_INIT(n) \ static const struct rtc_counter_config rtc_counter_config_##n = { \ .counter_dev = DEVICE_DT_GET(DT_INST_PARENT(n)), \ .alarms_count = DT_PROP_OR(DT_DRV_INST(n), alarms_count, 0), \ }; \ static struct rtc_counter_data rtc_counter_data_##n = { \ IF_ENABLED(CONFIG_RTC_ALARM, ( \ .alarm_callback = rtc_counter_alarm_callback_arr_##n, \ .alarm_user_data = rtc_counter_alarm_user_data_arr_##n, \ .alarm_mask = rtc_counter_alarm_mask_arr_##n, \ .alarm_time = rtc_counter_alarm_time_arr_##n, \ .alarm_pending = rtc_counter_alarm_pending_arr_##n, \ )) \ }; \ DEVICE_DT_INST_DEFINE(n, rtc_counter_init, NULL, &rtc_counter_data_##n, \ &rtc_counter_config_##n, POST_KERNEL, CONFIG_RTC_INIT_PRIORITY, \ &rtc_counter_driver_api); DT_INST_FOREACH_STATUS_OKAY(RTC_COUNTER_DEVICE_INIT)