/* * MIT License * * Copyright (c) 2020 Joey Castillo * Copyright (c) 2025 Alessandro Genova * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include "watch_rtc.h" #include "watch_private.h" #include "watch_utility.h" static const uint32_t RTC_OSC_DIV = 10; static const uint32_t RTC_OSC_HZ = 1 << RTC_OSC_DIV; // 2^10 = 1024 static const uint32_t RTC_PRESCALER_DIV = 3; static const uint32_t RTC_CNT_HZ = RTC_OSC_HZ >> RTC_PRESCALER_DIV; // 1024 / 2^3 = 128 static const uint32_t RTC_CNT_DIV = RTC_OSC_DIV - RTC_PRESCALER_DIV; // 7 static const uint32_t RTC_CNT_TICKS_PER_MINUTE = RTC_CNT_HZ * 60; static const uint32_t RTC_CNT_TICKS_PER_HOUR = RTC_CNT_TICKS_PER_MINUTE * 60; static const int TB_BKUP_REG = 7; #define WATCH_RTC_N_COMP_CB 8 typedef struct { volatile uint32_t counter; volatile watch_cb_t callback; volatile bool enabled; } comp_cb_t; watch_cb_t tick_callbacks[8]; comp_cb_t comp_callbacks[WATCH_RTC_N_COMP_CB]; watch_cb_t alarm_callback; watch_cb_t btn_alarm_callback; watch_cb_t a2_callback; watch_cb_t a4_callback; void watch_rtc_callback(uint16_t interrupt_status); bool _watch_rtc_is_enabled(void) { return rtc_is_enabled(); } void _watch_rtc_init(void) { rtc_init(); #ifdef STATIC_FREQCORR watch_rtc_freqcorr_write(STATIC_FREQCORR, 0); #endif rtc_enable(); rtc_configure_callback(watch_rtc_callback); for (uint8_t index = 0; index < WATCH_RTC_N_COMP_CB; ++index) { comp_callbacks[index].counter = 0; comp_callbacks[index].callback = NULL; comp_callbacks[index].enabled = false; } NVIC_ClearPendingIRQ(RTC_IRQn); NVIC_EnableIRQ(RTC_IRQn); } void watch_rtc_set_date_time(rtc_date_time_t date_time) { watch_rtc_set_unix_time(watch_utility_date_time_to_unix_time(date_time, 0)); } rtc_date_time_t watch_rtc_get_date_time(void) { return watch_utility_date_time_from_unix_time(watch_rtc_get_unix_time(), 0); } void watch_rtc_set_unix_time(unix_timestamp_t unix_time) { // time_backup + counter / RTC_CNT_HZ = unix_time rtc_counter_t counter = rtc_get_counter(); unix_timestamp_t tb = unix_time - (counter >> RTC_CNT_DIV); watch_store_backup_data(tb, TB_BKUP_REG); } unix_timestamp_t watch_rtc_get_unix_time(void) { // time_backup + counter / RTC_CNT_HZ = unix_time rtc_counter_t counter = rtc_get_counter(); unix_timestamp_t tb = watch_get_backup_data(TB_BKUP_REG); return tb + (counter >> RTC_CNT_DIV); } rtc_counter_t watch_rtc_get_counter(void) { return rtc_get_counter(); } uint32_t watch_rtc_get_frequency(void) { return RTC_CNT_HZ; } uint32_t watch_rtc_get_ticks_per_minute(void) { return RTC_CNT_TICKS_PER_MINUTE; } rtc_date_time_t watch_get_init_date_time(void) { rtc_date_time_t date_time; #ifdef BUILD_YEAR date_time.unit.year = BUILD_YEAR; #else date_time.unit.year = 5; #endif #ifdef BUILD_MONTH date_time.unit.month = BUILD_MONTH; #else date_time.unit.month = 1; #endif #ifdef BUILD_DAY date_time.unit.day = BUILD_DAY; #else date_time.unit.day = 1; #endif #ifdef BUILD_HOUR date_time.unit.hour = BUILD_HOUR; #endif #ifdef BUILD_MINUTE date_time.unit.minute = BUILD_MINUTE; #endif return date_time; } void watch_rtc_register_tick_callback(watch_cb_t callback) { watch_rtc_register_periodic_callback(callback, 1); } void watch_rtc_disable_tick_callback(void) { watch_rtc_disable_periodic_callback(1); } void watch_rtc_register_periodic_callback(watch_cb_t callback, uint8_t frequency) { // we told them, it has to be a power of 2. if (__builtin_popcount(frequency) != 1) return; // this left-justifies the period in a 32-bit integer. uint32_t tmp = (frequency & 0xFF) << 24; // now we can count the leading zeroes to get the value we need. // 0x01 (1 Hz) will have 7 leading zeros for PER7. 0x80 (128 Hz) will have no leading zeroes for PER0. uint8_t per_n = __builtin_clz(tmp); // this also maps nicely to an index for our list of tick callbacks. tick_callbacks[per_n] = callback; // NVIC_ClearPendingIRQ(RTC_IRQn); // NVIC_EnableIRQ(RTC_IRQn); RTC->MODE0.INTENSET.reg = 1 << per_n; } void watch_rtc_disable_periodic_callback(uint8_t frequency) { if (__builtin_popcount(frequency) != 1) return; uint8_t per_n = __builtin_clz((frequency & 0xFF) << 24); RTC->MODE0.INTENCLR.reg = 1 << per_n; } void watch_rtc_disable_matching_periodic_callbacks(uint8_t mask) { RTC->MODE0.INTENCLR.reg = mask; } void watch_rtc_disable_all_periodic_callbacks(void) { watch_rtc_disable_matching_periodic_callbacks(0xFF); } static void _watch_rtc_schedule_next_comp(void) { rtc_disable_compare_interrupt(); // The soonest we can schedule is the next tick rtc_counter_t curr_counter = watch_rtc_get_counter() + 1; bool schedule_any = false; rtc_counter_t comp_counter; rtc_counter_t min_diff = UINT_MAX; for (uint8_t index = 0; index < WATCH_RTC_N_COMP_CB; ++index) { if (comp_callbacks[index].enabled) { rtc_counter_t diff = comp_callbacks[index].counter - curr_counter; if (diff <= min_diff) { min_diff = diff; comp_counter = comp_callbacks[index].counter; schedule_any = true; } } } if (schedule_any) { rtc_enable_compare_interrupt(comp_counter); } } void watch_rtc_register_comp_callback(watch_cb_t callback, rtc_counter_t counter, uint8_t index) { if (index >= WATCH_RTC_N_COMP_CB) { return; } rtc_disable_compare_interrupt(); comp_callbacks[index].counter = counter; comp_callbacks[index].callback = callback; comp_callbacks[index].enabled = true; _watch_rtc_schedule_next_comp(); } void watch_rtc_disable_comp_callback(uint8_t index) { if (index >= WATCH_RTC_N_COMP_CB) { return; } rtc_disable_compare_interrupt(); comp_callbacks[index].enabled = false; _watch_rtc_schedule_next_comp(); } void watch_rtc_callback(uint16_t interrupt_cause) { // First read all relevant registers, to ensure no changes occurr during the callbacks uint16_t interrupt_enabled = RTC->MODE0.INTENSET.reg; rtc_counter_t comp_counter = RTC->MODE0.COMP[0].reg; if ((interrupt_cause & interrupt_enabled) & RTC_MODE0_INTFLAG_PER_Msk) { // handle the tick callback first, it's what we do the most. // start from PER7, the 1 Hz tick. for(int8_t i = 7; i >= 0; i--) { if ((interrupt_cause & interrupt_enabled) & (1 << i)) { if (tick_callbacks[i] != NULL) { tick_callbacks[i](); } } } } if ((interrupt_cause & interrupt_enabled) & RTC_MODE0_INTFLAG_TAMPER) { // handle the extwake interrupts next. uint8_t reason = RTC->MODE0.TAMPID.reg; if (reason & RTC_TAMPID_TAMPID2) { if (btn_alarm_callback != NULL) btn_alarm_callback(); } else if (reason & RTC_TAMPID_TAMPID1) { if (a2_callback != NULL) a2_callback(); } else if (reason & RTC_TAMPID_TAMPID0) { if (a4_callback != NULL) a4_callback(); } RTC->MODE0.TAMPID.reg = reason; } if ((interrupt_cause & interrupt_enabled) & RTC_MODE0_INTFLAG_CMP0) { // The comp interrupt is generated one tick after the matched counter // rtc_counter_t comp_counter = watch_rtc_get_counter() - 1; for (uint8_t index = 0; index < WATCH_RTC_N_COMP_CB; ++index) { if (comp_callbacks[index].enabled && comp_counter == comp_callbacks[index].counter) { comp_callbacks[index].enabled = false; comp_callbacks[index].callback(); } } _watch_rtc_schedule_next_comp(); } if ((interrupt_cause & interrupt_enabled) & RTC_MODE0_INTFLAG_OVF) { // Handle the overflow of the counter. All we need to do is reset the reference time. unix_timestamp_t tb = watch_get_backup_data(TB_BKUP_REG); watch_store_backup_data(tb + (UINT_MAX >> RTC_CNT_DIV), TB_BKUP_REG); } } void watch_rtc_enable(bool en) { // Writing it twice - as it's quite dangerous operation. // If write fails - we might hang with RTC off, which means no recovery possible while (RTC->MODE0.SYNCBUSY.reg); RTC->MODE0.CTRLA.bit.ENABLE = en ? 1 : 0; while (RTC->MODE0.SYNCBUSY.reg); RTC->MODE0.CTRLA.bit.ENABLE = en ? 1 : 0; while (RTC->MODE0.SYNCBUSY.reg); } void watch_rtc_freqcorr_write(int16_t value, int16_t sign) { RTC_FREQCORR_Type data; data.bit.VALUE = value; data.bit.SIGN = sign; RTC->MODE0.FREQCORR.reg = data.reg; // Setting correction in single write operation // We do not sycnronize. We are not in a hurry }