/* * SPDX-License-Identifier: Apache-2.0 * * Copyright (c) 2024 Ambiq Micro */ #include #include #include #include #include "rtc_utils.h" #define DT_DRV_COMPAT ambiq_rtc LOG_MODULE_REGISTER(ambiq_rtc, CONFIG_RTC_LOG_LEVEL); #include #define AMBIQ_RTC_ALARM_TIME_MASK \ (RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE | RTC_ALARM_TIME_MASK_HOUR | \ RTC_ALARM_TIME_MASK_WEEKDAY | RTC_ALARM_TIME_MASK_MONTH | RTC_ALARM_TIME_MASK_MONTHDAY) /* struct tm start time: 1st, Jan, 1900 */ #define TM_YEAR_REF 1900 #define AMBIQ_RTC_YEAR_MAX 2199 struct ambiq_rtc_config { uint8_t clk_src; }; struct ambiq_rtc_data { struct k_spinlock lock; #ifdef CONFIG_RTC_ALARM struct rtc_time alarm_time; uint16_t alarm_set_mask; rtc_alarm_callback alarm_user_callback; void *alarm_user_data; bool alarm_pending; #endif }; static void rtc_time_to_ambiq_time_set(const struct rtc_time *tm, am_hal_rtc_time_t *atm) { #if defined(CONFIG_SOC_SERIES_APOLLO3X) atm->ui32Century = ((tm->tm_year <= 99) || (tm->tm_year >= 200)); #else atm->ui32CenturyBit = ((tm->tm_year > 99) && (tm->tm_year < 200)); #endif atm->ui32Year = tm->tm_year; if (tm->tm_year > 99) { atm->ui32Year = tm->tm_year % 100; } atm->ui32Weekday = tm->tm_wday; atm->ui32Month = tm->tm_mon + 1; atm->ui32DayOfMonth = tm->tm_mday; atm->ui32Hour = tm->tm_hour; atm->ui32Minute = tm->tm_min; atm->ui32Second = tm->tm_sec; /* Nanoseconds times 10mil is hundredths */ atm->ui32Hundredths = tm->tm_nsec / 10000000; if (atm->ui32Hundredths > 99) { uint16_t value = atm->ui32Hundredths / 100; atm->ui32Second += value; atm->ui32Hundredths -= value*100; } } /* To set the timer registers */ static void ambiq_time_to_rtc_time_set(const am_hal_rtc_time_t *atm, struct rtc_time *tm) { tm->tm_year = atm->ui32Year; #if defined(CONFIG_SOC_SERIES_APOLLO3X) if (atm->ui32Century == 0) { tm->tm_year += 100; } else { tm->tm_year += 200; } #else if (atm->ui32CenturyBit == 0) { tm->tm_year += 200; } else { tm->tm_year += 100; } #endif tm->tm_wday = atm->ui32Weekday; tm->tm_mon = atm->ui32Month - 1; tm->tm_mday = atm->ui32DayOfMonth; tm->tm_hour = atm->ui32Hour; tm->tm_min = atm->ui32Minute; tm->tm_sec = atm->ui32Second; /* Nanoseconds times 10mil is hundredths */ tm->tm_nsec = atm->ui32Hundredths * 10000000; } static int test_for_rollover(am_hal_rtc_time_t *atm) { if ((atm->ui32Year == 99) && (atm->ui32Month == 12) && (atm->ui32DayOfMonth == 31)) { return -EINVAL; } return 0; } /* To set the timer registers */ static int ambiq_rtc_set_time(const struct device *dev, const struct rtc_time *timeptr) { int err = 0; am_hal_rtc_time_t ambiq_time = {0}; struct ambiq_rtc_data *data = dev->data; if (timeptr->tm_year + TM_YEAR_REF > AMBIQ_RTC_YEAR_MAX) { return -EINVAL; } k_spinlock_key_t key = k_spin_lock(&data->lock); LOG_DBG("set time: year = %d, mon = %d, mday = %d, wday = %d, hour = %d, " "min = %d, sec = %d", timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec); /* Convertn to Ambiq Time */ rtc_time_to_ambiq_time_set(timeptr, &ambiq_time); if (test_for_rollover(&ambiq_time)) { return -EINVAL; } err = am_hal_rtc_time_set(&ambiq_time); if (err) { LOG_WRN("Set Timer returned an error - %d!", err); } k_spin_unlock(&data->lock, key); return err; } /* To get from the timer registers */ static int ambiq_rtc_get_time(const struct device *dev, struct rtc_time *timeptr) { int err = 0; am_hal_rtc_time_t ambiq_time = {0}; struct ambiq_rtc_data *data = dev->data; k_spinlock_key_t key = k_spin_lock(&data->lock); err = am_hal_rtc_time_get(&ambiq_time); if (err != 0) { LOG_WRN("Get Timer returned an error - %d!", err); goto unlock; } ambiq_time_to_rtc_time_set(&ambiq_time, timeptr); LOG_DBG("get time: year = %d, mon = %d, mday = %d, wday = %d, hour = %d, " "min = %d, sec = %d", timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec); unlock: k_spin_unlock(&data->lock, key); return err; } #ifdef CONFIG_RTC_ALARM static int ambiq_rtc_alarm_get_supported_fields(const struct device *dev, uint16_t id, uint16_t *mask) { ARG_UNUSED(dev); if (id != 0U) { LOG_ERR("Invalid ID %d", id); return -EINVAL; } *mask = AMBIQ_RTC_ALARM_TIME_MASK; return 0; } /* To get from the alarm registers */ static int ambiq_rtc_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask, struct rtc_time *timeptr) { am_hal_rtc_time_t ambiq_time = {0}; struct ambiq_rtc_data *data = dev->data; if (id != 0U) { LOG_ERR("Invalid ID %d", id); return -EINVAL; } k_spinlock_key_t key = k_spin_lock(&data->lock); #if defined(CONFIG_SOC_SERIES_APOLLO3X) am_hal_rtc_alarm_get(&ambiq_time); #else am_hal_rtc_alarm_get(&ambiq_time, NULL); #endif ambiq_time_to_rtc_time_set(&ambiq_time, timeptr); *mask = data->alarm_set_mask; LOG_DBG("get alarm: wday = %d, mon = %d, mday = %d, hour = %d, min = %d, sec = %d, " "mask = 0x%04x", timeptr->tm_wday, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, *mask); k_spin_unlock(&data->lock, key); return 0; } static int ambiq_rtc_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask, const struct rtc_time *timeptr) { struct ambiq_rtc_data *data = dev->data; am_hal_rtc_time_t ambiq_time = {0}; uint16_t mask_available; if (id != 0U) { LOG_ERR("Invalid ID %d", id); return -EINVAL; } if (rtc_utils_validate_rtc_time(timeptr, mask) == false) { LOG_DBG("Invalid Input Value"); return -EINVAL; } (void)ambiq_rtc_alarm_get_supported_fields(NULL, 0, &mask_available); if (mask & ~mask_available) { return -EINVAL; } data->alarm_set_mask = mask; k_spinlock_key_t key = k_spin_lock(&data->lock); /* Disable and clear the alarm */ #if defined(CONFIG_SOC_SERIES_APOLLO3X) am_hal_rtc_int_disable(AM_HAL_RTC_INT_ALM); am_hal_rtc_int_clear(AM_HAL_RTC_INT_ALM); #else am_hal_rtc_interrupt_disable(AM_HAL_RTC_INT_ALM); am_hal_rtc_interrupt_clear(AM_HAL_RTC_INT_ALM); #endif /* When mask is 0 */ if (mask == 0) { LOG_DBG("The alarm is disabled"); goto unlock; } LOG_DBG("set alarm: second = %d, min = %d, hour = %d, mday = %d, month = %d," "wday = %d, mask = 0x%04x", timeptr->tm_sec, timeptr->tm_min, timeptr->tm_hour, timeptr->tm_mday, timeptr->tm_mon, timeptr->tm_wday, mask); /* Convertn to Ambiq Time */ rtc_time_to_ambiq_time_set(timeptr, &ambiq_time); /* Set RTC ALARM, Ambiq must have interval != AM_HAL_RTC_ALM_RPT_DIS */ am_hal_rtc_alarm_set(&ambiq_time, AM_HAL_RTC_ALM_RPT_YR); #if defined(CONFIG_SOC_SERIES_APOLLO3X) am_hal_rtc_int_enable(AM_HAL_RTC_INT_ALM); #else am_hal_rtc_interrupt_enable(AM_HAL_RTC_INT_ALM); #endif unlock: k_spin_unlock(&data->lock, key); return 0; } static int ambiq_rtc_alarm_is_pending(const struct device *dev, uint16_t id) { struct ambiq_rtc_data *data = dev->data; int ret = 0; if (id != 0) { return -EINVAL; } K_SPINLOCK(&data->lock) { ret = data->alarm_pending ? 1 : 0; data->alarm_pending = false; } return ret; } static void ambiq_rtc_isr(const struct device *dev) { /* Clear the RTC alarm interrupt. 8*/ #if defined(CONFIG_SOC_SERIES_APOLLO3X) am_hal_rtc_int_clear(AM_HAL_RTC_INT_ALM); #else am_hal_rtc_interrupt_clear(AM_HAL_RTC_INT_ALM); #endif #if defined(CONFIG_RTC_ALARM) struct ambiq_rtc_data *data = dev->data; if (data->alarm_user_callback) { data->alarm_user_callback(dev, 0, data->alarm_user_data); data->alarm_pending = false; } else { data->alarm_pending = true; } #endif } static int ambiq_rtc_alarm_set_callback(const struct device *dev, uint16_t id, rtc_alarm_callback callback, void *user_data) { struct ambiq_rtc_data *data = dev->data; if (id != 0U) { LOG_ERR("Invalid ID %d", id); return -EINVAL; } K_SPINLOCK(&data->lock) { data->alarm_user_callback = callback; data->alarm_user_data = user_data; if ((callback == NULL) && (user_data == NULL)) { #if defined(CONFIG_SOC_SERIES_APOLLO3X) am_hal_rtc_int_disable(AM_HAL_RTC_INT_ALM); #else am_hal_rtc_interrupt_disable(AM_HAL_RTC_INT_ALM); #endif } } return 0; } #endif static int ambiq_rtc_init(const struct device *dev) { const struct ambiq_rtc_config *config = dev->config; #ifdef CONFIG_RTC_ALARM struct ambiq_rtc_data *data = dev->data; #endif /* Enable the clock for RTC. */ #if defined(CONFIG_SOC_SERIES_APOLLO3X) am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_XTAL_START + config->clk_src, NULL); #endif am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_RTC_SEL_XTAL + config->clk_src, NULL); /* Enable the RTC. */ am_hal_rtc_osc_enable(); #ifdef CONFIG_RTC_ALARM data->alarm_user_callback = NULL; data->alarm_pending = false; IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), ambiq_rtc_isr, DEVICE_DT_INST_GET(0), 0); irq_enable(DT_INST_IRQN(0)); #endif return 0; } static DEVICE_API(rtc, ambiq_rtc_driver_api) = { .set_time = ambiq_rtc_set_time, .get_time = ambiq_rtc_get_time, /* RTC_UPDATE not supported */ #ifdef CONFIG_RTC_ALARM .alarm_get_supported_fields = ambiq_rtc_alarm_get_supported_fields, .alarm_set_time = ambiq_rtc_alarm_set_time, .alarm_get_time = ambiq_rtc_alarm_get_time, .alarm_is_pending = ambiq_rtc_alarm_is_pending, .alarm_set_callback = ambiq_rtc_alarm_set_callback, #endif }; #define AMBIQ_RTC_INIT(inst) \ static const struct ambiq_rtc_config ambiq_rtc_config_##inst = { \ .clk_src = DT_INST_ENUM_IDX(inst, clock)}; \ \ static struct ambiq_rtc_data ambiq_rtc_data##inst; \ \ DEVICE_DT_INST_DEFINE(inst, &ambiq_rtc_init, NULL, &ambiq_rtc_data##inst, \ &ambiq_rtc_config_##inst, POST_KERNEL, \ CONFIG_RTC_INIT_PRIORITY, &ambiq_rtc_driver_api); DT_INST_FOREACH_STATUS_OKAY(AMBIQ_RTC_INIT)