/* * Copyright (c) 2024-2025 Gerson Fernando Budke * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT atmel_sam0_rtc /** @file * @brief RTC driver for Atmel SAM0 MCU family. */ #include #include #include #include "rtc_utils.h" #include LOG_MODULE_REGISTER(rtc_sam0, CONFIG_RTC_LOG_LEVEL); /* clang-format off */ #define RTC_SAM0_TIME_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 \ ) #define RTC_SAM0_CALIBRATE_PPB_MAX (127) #define RTC_SAM0_CALIBRATE_PPB_QUANTA (1000) enum rtc_sam0_counter_mode { COUNTER_MODE_0, COUNTER_MODE_1, COUNTER_MODE_2, }; struct rtc_sam0_config { Rtc *regs; enum rtc_sam0_counter_mode mode; uint16_t prescaler; volatile uint32_t *mclk; uint32_t mclk_mask; uint32_t gclk_gen; uint16_t gclk_id; bool has_gclk; bool has_osc32kctrl; uint8_t osc32_src; uint32_t evt_ctrl_msk; #ifdef CONFIG_RTC_ALARM uint8_t alarms_count; #endif /* CONFIG_RTC_ALARM */ #ifdef CONFIG_RTC_CALIBRATION int32_t cal_constant; #endif }; struct rtc_sam0_data_cb { rtc_alarm_callback cb; void *cb_data; }; struct rtc_sam0_data { struct k_spinlock lock; #ifdef CONFIG_RTC_ALARM struct rtc_sam0_data_cb *const alarms; #endif /* CONFIG_RTC_ALARM */ }; static inline void rtc_sam0_sync(Rtc *rtc) { /* Wait for synchronization */ #ifdef MCLK while (rtc->MODE0.SYNCBUSY.reg & RTC_MODE0_SYNCBUSY_MASK) { } #else while (rtc->MODE0.STATUS.reg & RTC_STATUS_SYNCBUSY) { } #endif } static int rtc_sam0_set_time(const struct device *dev, const struct rtc_time *timeptr) { const struct rtc_sam0_config *cfg = dev->config; struct rtc_sam0_data *data = dev->data; RtcMode2 *regs = &cfg->regs->MODE2; uint32_t datetime = 0; if (rtc_utils_validate_rtc_time(timeptr, RTC_SAM0_TIME_MASK) == false) { return -EINVAL; } datetime |= RTC_MODE2_CLOCK_SECOND(timeptr->tm_sec); datetime |= RTC_MODE2_CLOCK_MINUTE(timeptr->tm_min); datetime |= RTC_MODE2_CLOCK_HOUR(timeptr->tm_hour); datetime |= RTC_MODE2_CLOCK_DAY(timeptr->tm_mday); datetime |= RTC_MODE2_CLOCK_MONTH(timeptr->tm_mon + 1); datetime |= RTC_MODE2_CLOCK_YEAR(timeptr->tm_year - 99); k_spinlock_key_t key = k_spin_lock(&data->lock); #ifdef MCLK regs->CTRLA.reg &= ~RTC_MODE0_CTRLA_ENABLE; rtc_sam0_sync(cfg->regs); regs->CLOCK.reg = datetime; regs->CTRLA.reg |= RTC_MODE0_CTRLA_ENABLE; #else regs->CTRL.reg &= ~RTC_MODE0_CTRL_ENABLE; rtc_sam0_sync(cfg->regs); regs->CLOCK.reg = datetime; regs->CTRL.reg |= RTC_MODE0_CTRL_ENABLE; #endif k_spin_unlock(&data->lock, key); return 0; } static int rtc_sam0_get_time(const struct device *dev, struct rtc_time *timeptr) { const struct rtc_sam0_config *cfg = dev->config; RTC_MODE2_CLOCK_Type calendar = cfg->regs->MODE2.CLOCK; timeptr->tm_sec = calendar.bit.SECOND; timeptr->tm_min = calendar.bit.MINUTE; timeptr->tm_hour = calendar.bit.HOUR; timeptr->tm_mday = calendar.bit.DAY; timeptr->tm_mon = calendar.bit.MONTH - 1; timeptr->tm_year = calendar.bit.YEAR + 99; timeptr->tm_wday = -1; timeptr->tm_yday = -1; timeptr->tm_isdst = -1; timeptr->tm_nsec = 0; LOG_DBG("D/M/Y H:M:S %02d/%02d/%02d %02d:%02d:%02d", timeptr->tm_mday, timeptr->tm_mon + 1, timeptr->tm_year - 99, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec); return 0; } #ifdef CONFIG_RTC_ALARM static inline uint32_t rtc_sam0_datetime_from_tm(const struct rtc_time *timeptr, uint32_t mask) { uint32_t datetime = 0; if (mask & RTC_ALARM_TIME_MASK_SECOND) { datetime |= RTC_MODE2_CLOCK_SECOND(timeptr->tm_sec); } if (mask & RTC_ALARM_TIME_MASK_MINUTE) { datetime |= RTC_MODE2_CLOCK_MINUTE(timeptr->tm_min); } if (mask & RTC_ALARM_TIME_MASK_HOUR) { datetime |= RTC_MODE2_CLOCK_HOUR(timeptr->tm_hour); } if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) { datetime |= RTC_MODE2_CLOCK_DAY(timeptr->tm_mday); } if (mask & RTC_ALARM_TIME_MASK_MONTH) { datetime |= RTC_MODE2_CLOCK_MONTH(timeptr->tm_mon + 1); } if (mask & RTC_ALARM_TIME_MASK_YEAR) { datetime |= RTC_MODE2_CLOCK_YEAR(timeptr->tm_year - 99); } return datetime; } static inline void rtc_sam0_tm_from_datetime(struct rtc_time *timeptr, uint32_t mask, RTC_MODE2_ALARM_Type calendar) { memset(timeptr, 0x00, sizeof(struct rtc_time)); if (mask & RTC_ALARM_TIME_MASK_SECOND) { timeptr->tm_sec = calendar.bit.SECOND; } if (mask & RTC_ALARM_TIME_MASK_MINUTE) { timeptr->tm_min = calendar.bit.MINUTE; } if (mask & RTC_ALARM_TIME_MASK_HOUR) { timeptr->tm_hour = calendar.bit.HOUR; } if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) { timeptr->tm_mday = calendar.bit.DAY; } if (mask & RTC_ALARM_TIME_MASK_MONTH) { timeptr->tm_mon = calendar.bit.MONTH - 1; } if (mask & RTC_ALARM_TIME_MASK_YEAR) { timeptr->tm_year = calendar.bit.YEAR + 99; } timeptr->tm_wday = -1; timeptr->tm_yday = -1; timeptr->tm_isdst = -1; timeptr->tm_nsec = 0; } static inline uint32_t rtc_sam0_alarm_msk_from_mask(uint32_t mask) { uint32_t alarm_mask = 0; if (mask & RTC_ALARM_TIME_MASK_SECOND) { alarm_mask = RTC_MODE2_MASK_SEL_SS_Val; } if (mask & RTC_ALARM_TIME_MASK_MINUTE) { alarm_mask = RTC_MODE2_MASK_SEL_MMSS_Val; } if (mask & RTC_ALARM_TIME_MASK_HOUR) { alarm_mask = RTC_MODE2_MASK_SEL_HHMMSS_Val; } if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) { alarm_mask = RTC_MODE2_MASK_SEL_DDHHMMSS_Val; } if (mask & RTC_ALARM_TIME_MASK_MONTH) { alarm_mask = RTC_MODE2_MASK_SEL_MMDDHHMMSS_Val; } if (mask & RTC_ALARM_TIME_MASK_YEAR) { alarm_mask = RTC_MODE2_MASK_SEL_YYMMDDHHMMSS_Val; } return alarm_mask; } static inline uint32_t rtc_sam0_mask_from_alarm_msk(uint32_t alarm_mask) { uint32_t mask = 0; switch (alarm_mask) { case RTC_MODE2_MASK_SEL_YYMMDDHHMMSS_Val: mask |= RTC_ALARM_TIME_MASK_YEAR; __fallthrough; case RTC_MODE2_MASK_SEL_MMDDHHMMSS_Val: mask |= RTC_ALARM_TIME_MASK_MONTH; __fallthrough; case RTC_MODE2_MASK_SEL_DDHHMMSS_Val: mask |= RTC_ALARM_TIME_MASK_MONTHDAY; __fallthrough; case RTC_MODE2_MASK_SEL_HHMMSS_Val: mask |= RTC_ALARM_TIME_MASK_HOUR; __fallthrough; case RTC_MODE2_MASK_SEL_MMSS_Val: mask |= RTC_ALARM_TIME_MASK_MINUTE; __fallthrough; case RTC_MODE2_MASK_SEL_SS_Val: mask |= RTC_ALARM_TIME_MASK_SECOND; break; default: break; } return mask; } static int rtc_sam0_alarm_get_supported_fields(const struct device *dev, uint16_t id, uint16_t *mask) { ARG_UNUSED(dev); ARG_UNUSED(id); *mask = RTC_SAM0_TIME_MASK; return 0; } static int rtc_sam0_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask, const struct rtc_time *timeptr) { const struct rtc_sam0_config *cfg = dev->config; struct rtc_sam0_data *data = dev->data; RtcMode2 *regs = &cfg->regs->MODE2; uint32_t mask_supported = RTC_SAM0_TIME_MASK; uint32_t datetime; uint32_t alarm_msk; if (BIT(id) > RTC_MODE2_INTFLAG_ALARM_Msk) { return -EINVAL; } if ((mask > 0) && (timeptr == NULL)) { return -EINVAL; } if (mask & ~mask_supported) { return -EINVAL; } if (rtc_utils_validate_rtc_time(timeptr, mask) == false) { return -EINVAL; } datetime = rtc_sam0_datetime_from_tm(timeptr, mask); alarm_msk = rtc_sam0_alarm_msk_from_mask(mask); LOG_DBG("S: datetime: %d, mask: %d", datetime, alarm_msk); k_spinlock_key_t key = k_spin_lock(&data->lock); irq_disable(DT_INST_IRQN(0)); rtc_sam0_sync(cfg->regs); regs->Mode2Alarm[id].ALARM.reg = datetime; regs->Mode2Alarm[id].MASK.reg = RTC_MODE2_MASK_SEL(alarm_msk); regs->INTFLAG.reg = RTC_MODE2_INTFLAG_ALARM(BIT(id)); irq_enable(DT_INST_IRQN(0)); k_spin_unlock(&data->lock, key); return 0; } static int rtc_sam0_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask, struct rtc_time *timeptr) { const struct rtc_sam0_config *cfg = dev->config; struct rtc_sam0_data *data = dev->data; RtcMode2 *regs = &cfg->regs->MODE2; RTC_MODE2_ALARM_Type datetime; uint32_t alarm_msk; if (BIT(id) > RTC_MODE2_INTFLAG_ALARM_Msk) { return -EINVAL; } if ((mask == NULL) || (timeptr == NULL)) { return -EINVAL; } k_spinlock_key_t key = k_spin_lock(&data->lock); rtc_sam0_sync(cfg->regs); datetime = regs->Mode2Alarm[id].ALARM; alarm_msk = regs->Mode2Alarm[id].MASK.reg; LOG_DBG("G: datetime: %d, mask: %d", datetime.reg, alarm_msk); k_spin_unlock(&data->lock, key); *mask = rtc_sam0_mask_from_alarm_msk(alarm_msk); rtc_sam0_tm_from_datetime(timeptr, *mask, datetime); return 0; } static int rtc_sam0_alarm_is_pending(const struct device *dev, uint16_t id) { const struct rtc_sam0_config *cfg = dev->config; struct rtc_sam0_data *data = dev->data; RtcMode2 *regs = &cfg->regs->MODE2; if (BIT(id) > RTC_MODE2_INTFLAG_ALARM_Msk) { return -EINVAL; } k_spinlock_key_t key = k_spin_lock(&data->lock); if ((regs->INTFLAG.reg & RTC_MODE2_INTFLAG_ALARM(BIT(id))) == 0) { k_spin_unlock(&data->lock, key); return 0; } regs->INTFLAG.reg = RTC_MODE2_INTFLAG_ALARM(BIT(id)); k_spin_unlock(&data->lock, key); return 1; } static int rtc_sam0_alarm_set_callback(const struct device *dev, uint16_t id, rtc_alarm_callback callback, void *user_data) { const struct rtc_sam0_config *cfg = dev->config; struct rtc_sam0_data *data = dev->data; RtcMode2 *regs = &cfg->regs->MODE2; if (BIT(id) > RTC_MODE2_INTFLAG_ALARM_Msk) { return -EINVAL; } k_spinlock_key_t key = k_spin_lock(&data->lock); data->alarms[id].cb = callback; data->alarms[id].cb_data = user_data; if (callback) { regs->INTENSET.reg = RTC_MODE2_INTENSET_ALARM(BIT(id)); } else { regs->INTENCLR.reg = RTC_MODE2_INTENCLR_ALARM(BIT(id)); } k_spin_unlock(&data->lock, key); return 0; } static void rtc_sam0_isr(const struct device *dev) { const struct rtc_sam0_config *cfg = dev->config; struct rtc_sam0_data *data = dev->data; RtcMode2 *regs = &cfg->regs->MODE2; uint32_t int_flags = regs->INTFLAG.reg; for (int i = 0; i < cfg->alarms_count; ++i) { if (int_flags & RTC_MODE2_INTFLAG_ALARM(BIT(i))) { if (data->alarms[i].cb != NULL) { data->alarms[i].cb(dev, i, data->alarms[i].cb_data); } } } regs->INTFLAG.reg |= int_flags; } #endif /* CONFIG_RTC_ALARM */ #ifdef CONFIG_RTC_CALIBRATION static int rtc_sam0_set_calibration(const struct device *dev, int32_t calibration) { const struct rtc_sam0_config *cfg = dev->config; RtcMode2 *regs = &cfg->regs->MODE2; int32_t correction = calibration / (1000000000 / cfg->cal_constant); uint32_t abs_correction = abs(correction); LOG_DBG("Correction: %d, Absolute: %d, Calibration: %d", correction, abs_correction, calibration); if (abs_correction == 0) { regs->FREQCORR.reg = 0; return 0; } if (abs_correction > RTC_SAM0_CALIBRATE_PPB_MAX) { LOG_ERR("The calibration %d result in an out of range value %d", calibration, abs_correction); return -EINVAL; } rtc_sam0_sync(cfg->regs); regs->FREQCORR.reg = RTC_FREQCORR_VALUE(abs_correction) | (correction < 0 ? RTC_FREQCORR_SIGN : 0); LOG_DBG("W REG: 0x%02x", regs->FREQCORR.reg); return 0; } static int rtc_sam0_get_calibration(const struct device *dev, int32_t *calibration) { const struct rtc_sam0_config *cfg = dev->config; RtcMode2 *regs = &cfg->regs->MODE2; int32_t correction; if (calibration == NULL) { return -EINVAL; } correction = regs->FREQCORR.bit.VALUE; if (correction == 0) { *calibration = 0; } else { *calibration = (correction * 1000000000) / cfg->cal_constant; } if (regs->FREQCORR.bit.SIGN) { *calibration *= -1; } LOG_DBG("R REG: 0x%02x", regs->FREQCORR.reg); return 0; } #endif /* CONFIG_RTC_CALIBRATION */ static int rtc_sam0_init(const struct device *dev) { const struct rtc_sam0_config *cfg = dev->config; RtcMode0 *regs = &cfg->regs->MODE0; LOG_DBG("Counter Mode %d selected", cfg->mode); LOG_DBG("gclk_id: %d, gclk_gen: %d, prescaler: %d, osc32k: %d", cfg->gclk_id, cfg->gclk_gen, cfg->prescaler, cfg->osc32_src); *cfg->mclk |= cfg->mclk_mask; #ifdef MCLK if (cfg->has_gclk) { GCLK->PCHCTRL[cfg->gclk_id].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(cfg->gclk_gen); } #else GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(cfg->gclk_gen) | GCLK_CLKCTRL_ID(cfg->gclk_id); #endif rtc_sam0_sync(cfg->regs); #ifdef MCLK if (cfg->has_osc32kctrl) { OSC32KCTRL->RTCCTRL.reg = OSC32KCTRL_RTCCTRL_RTCSEL(cfg->osc32_src); } #endif rtc_sam0_sync(cfg->regs); regs->EVCTRL.reg = (cfg->evt_ctrl_msk & RTC_MODE0_EVCTRL_MASK); #ifdef MCLK regs->CTRLA.reg = RTC_MODE0_CTRLA_ENABLE | RTC_MODE0_CTRLA_COUNTSYNC | RTC_MODE0_CTRLA_MODE(cfg->mode) | RTC_MODE0_CTRLA_PRESCALER(cfg->prescaler + 1); #else regs->CTRL.reg = RTC_MODE0_CTRL_ENABLE | RTC_MODE0_CTRL_MODE(cfg->mode) | RTC_MODE0_CTRL_PRESCALER(cfg->prescaler); #endif regs->INTFLAG.reg = 0; #ifdef CONFIG_RTC_ALARM IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), rtc_sam0_isr, DEVICE_DT_INST_GET(0), 0); irq_enable(DT_INST_IRQN(0)); #endif return 0; } static DEVICE_API(rtc, rtc_sam0_driver_api) = { .set_time = rtc_sam0_set_time, .get_time = rtc_sam0_get_time, #ifdef CONFIG_RTC_ALARM .alarm_get_supported_fields = rtc_sam0_alarm_get_supported_fields, .alarm_set_time = rtc_sam0_alarm_set_time, .alarm_get_time = rtc_sam0_alarm_get_time, .alarm_is_pending = rtc_sam0_alarm_is_pending, .alarm_set_callback = rtc_sam0_alarm_set_callback, #endif /* CONFIG_RTC_ALARM */ #ifdef CONFIG_RTC_CALIBRATION .set_calibration = rtc_sam0_set_calibration, .get_calibration = rtc_sam0_get_calibration, #endif /* CONFIG_RTC_CALIBRATION */ }; #define ASSIGNED_CLOCKS_CELL_BY_NAME \ ATMEL_SAM0_DT_INST_ASSIGNED_CLOCKS_CELL_BY_NAME #define RTC_SAM0_GCLK(n) \ COND_CODE_1(DT_INST_CLOCKS_HAS_NAME(n, gclk), \ ( \ .has_gclk = true, \ .gclk_gen = ASSIGNED_CLOCKS_CELL_BY_NAME(n, gclk, gen), \ .gclk_id = DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, id) \ ), \ ( \ .has_gclk = false, \ .gclk_gen = 0, \ .gclk_id = 0 \ )) #define RTC_SAM0_OSC32KCTRL(n) \ COND_CODE_1(DT_INST_CLOCKS_HAS_NAME(n, osc32kctrl), \ ( \ .has_osc32kctrl = true, \ .osc32_src = ASSIGNED_CLOCKS_CELL_BY_NAME(n, osc32kctrl, src) \ ), \ ( \ .has_osc32kctrl = false, \ .osc32_src = 0 \ )) #define RTC_SAM0_DEVICE(n) \ BUILD_ASSERT(DT_INST_NODE_HAS_PROP(n, counter_mode), \ "sam0:rtc: Missing counter-mode devicetree property"); \ BUILD_ASSERT(DT_INST_NODE_HAS_PROP(n, prescaler), \ "sam0:rtc: Missing prescaler devicetree property"); \ \ static const struct rtc_sam0_config rtc_sam0_config_##n = { \ .regs = (Rtc *)DT_INST_REG_ADDR(n), \ .mode = DT_INST_ENUM_IDX(n, counter_mode), \ .prescaler = DT_INST_ENUM_IDX(n, prescaler), \ .evt_ctrl_msk = DT_INST_PROP(n, event_control_msk), \ RTC_SAM0_GCLK(n), \ RTC_SAM0_OSC32KCTRL(n), \ .mclk = ATMEL_SAM0_DT_INST_MCLK_PM_REG_ADDR_OFFSET(n), \ .mclk_mask = ATMEL_SAM0_DT_INST_MCLK_PM_PERIPH_MASK(n, bit), \ IF_ENABLED(CONFIG_RTC_ALARM, ( \ .alarms_count = DT_INST_PROP(n, alarms_count), \ )) \ IF_ENABLED(CONFIG_RTC_CALIBRATION, ( \ .cal_constant = DT_INST_PROP(n, cal_constant), \ )) \ }; \ \ IF_ENABLED(CONFIG_RTC_ALARM, ( \ static struct rtc_sam0_data_cb \ rtc_sam0_data_cb_##n[DT_INST_PROP(n, alarms_count)] = {}; \ )) \ \ static struct rtc_sam0_data rtc_sam0_data_##n = { \ IF_ENABLED(CONFIG_RTC_ALARM, ( \ .alarms = rtc_sam0_data_cb_##n, \ )) \ }; \ \ DEVICE_DT_INST_DEFINE(n, rtc_sam0_init, \ NULL, \ &rtc_sam0_data_##n, \ &rtc_sam0_config_##n, POST_KERNEL, \ CONFIG_RTC_INIT_PRIORITY, \ &rtc_sam0_driver_api); \ DT_INST_FOREACH_STATUS_OKAY(RTC_SAM0_DEVICE); /* clang-format on */