|
@@ -28,26 +28,73 @@
|
|
|
#include <stdio.h>
|
|
|
#include <string.h>
|
|
|
|
|
|
+//! The type of a raw reading from the RTC (masked to 0xFFFFFF).
|
|
|
+typedef uint32_t RtcIntervalTicks;
|
|
|
+
|
|
|
+//! How frequently we save the time state to the backup registers in ticks.
|
|
|
+#define SAVE_TIME_FREQUENCY (30 * RTC_TICKS_HZ)
|
|
|
+static RtcIntervalTicks prv_get_last_save_time_ticks(void);
|
|
|
+static void prv_save_rtc_time_state(RtcIntervalTicks current_rtc_ticks);
|
|
|
+
|
|
|
+#define TICKS_IN_AN_INTERVAL (RTC_COUNTER_COUNTER_Msk + 1)
|
|
|
+
|
|
|
+static RtcIntervalTicks prv_elapsed_ticks(RtcIntervalTicks before, RtcIntervalTicks after) {
|
|
|
+ int32_t result = after - before;
|
|
|
+ if (result < 0) {
|
|
|
+ result = (TICKS_IN_AN_INTERVAL - before) + after;
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+static RtcIntervalTicks prv_get_rtc_interval_ticks(void) {
|
|
|
+ return nrf_rtc_counter_get(NRF_RTC1);
|
|
|
+}
|
|
|
+
|
|
|
+/***
|
|
|
+ * Logic associated with keeping raw coarse / fine RTC ticks -- the
|
|
|
+ * monotonic RtcTicks counter.
|
|
|
+ */
|
|
|
+
|
|
|
//! The value of the RTC registers last time we checked them.
|
|
|
static uint32_t s_last_ticks = 0;
|
|
|
//! This value is added to the current value of the RTC ticks to get the number
|
|
|
//! of ticks since system start. Incremented whenever we detect a rollover.
|
|
|
static RtcTicks s_coarse_ticks = 1;
|
|
|
|
|
|
-void rtc_set_time(time_t time) {
|
|
|
-#ifdef PBL_LOG_ENABLED
|
|
|
- char buffer[TIME_STRING_BUFFER_SIZE];
|
|
|
- PBL_LOG(LOG_LEVEL_INFO, "Setting time to %lu <%s>", time, time_t_to_string(buffer, time));
|
|
|
-#endif
|
|
|
-}
|
|
|
-
|
|
|
-time_t rtc_get_time(void) {
|
|
|
- return 0;
|
|
|
-}
|
|
|
+static bool s_did_init_rtc = false;
|
|
|
+
|
|
|
+//! did we boot with a full reset that brought RTC ticks to 0?
|
|
|
+static bool s_had_amnesia_on_boot = false;
|
|
|
+
|
|
|
+static void prv_check_and_handle_rollover(RtcIntervalTicks rtc_ticks) {
|
|
|
+ bool save_needed = false;
|
|
|
+
|
|
|
+ const RtcIntervalTicks last_ticks = s_last_ticks;
|
|
|
+ s_last_ticks = rtc_ticks;
|
|
|
+
|
|
|
+ if (rtc_ticks < last_ticks) {
|
|
|
+ // We've wrapped. Add on the RTC wrap length to the base number. On
|
|
|
+ // nRF5, this is 0xFFFFFF; that's only 4.5 hours (at 1.024 kHz),
|
|
|
+ // compared to STM32's available SECONDS_IN_A_DAY. Sucks for us; oh
|
|
|
+ // well.
|
|
|
+
|
|
|
+ s_coarse_ticks += TICKS_IN_AN_INTERVAL;
|
|
|
+
|
|
|
+ save_needed = true;
|
|
|
+ } else if (prv_elapsed_ticks(prv_get_last_save_time_ticks(), rtc_ticks) > SAVE_TIME_FREQUENCY) {
|
|
|
+ // If we didn't do this, we would have an edge case where if the watch reset
|
|
|
+ // immediately before rollover and then rolled over before we booted again,
|
|
|
+ // we wouldn't be able to detect the rollover and we'd think the saved state
|
|
|
+ // is very fresh, when really it's over an interval old. By saving multiple
|
|
|
+ // times an interval this is still possible to happen, but it's much less likely.
|
|
|
+ // We would need to be shutdown for (RTC_COUNTER_COUNTER_Msk - SAVE_TIME_FREQUENCY) ticks
|
|
|
+ // for this to happen.
|
|
|
+ save_needed = true;
|
|
|
+ }
|
|
|
|
|
|
-void rtc_get_time_ms(time_t* out_seconds, uint16_t* out_ms) {
|
|
|
- *out_seconds = 0;
|
|
|
- *out_ms = 0;
|
|
|
+ if (save_needed) {
|
|
|
+ prv_save_rtc_time_state(rtc_ticks);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
RtcTicks rtc_get_ticks(void) {
|
|
@@ -57,11 +104,8 @@ RtcTicks rtc_get_ticks(void) {
|
|
|
__disable_irq();
|
|
|
}
|
|
|
|
|
|
- RtcTicks rtc_interval_ticks = nrf_rtc_counter_get(NRF_RTC1);
|
|
|
- if (rtc_interval_ticks < s_last_ticks) {
|
|
|
- s_coarse_ticks += RTC_COUNTER_COUNTER_Msk;
|
|
|
- }
|
|
|
- s_last_ticks = rtc_interval_ticks;
|
|
|
+ RtcTicks rtc_interval_ticks = prv_get_rtc_interval_ticks();
|
|
|
+ prv_check_and_handle_rollover(rtc_interval_ticks);
|
|
|
|
|
|
if (ints_enabled) {
|
|
|
__enable_irq();
|
|
@@ -70,20 +114,100 @@ RtcTicks rtc_get_ticks(void) {
|
|
|
return s_coarse_ticks + rtc_interval_ticks;
|
|
|
}
|
|
|
|
|
|
-void rtc_alarm_init(void) {
|
|
|
+/***
|
|
|
+ * Logic associated with converting extended RTC ticks to wall clock time.
|
|
|
+ */
|
|
|
+
|
|
|
+//! This variable is a UNIX timestamp of what the current wall clock time was at tick s_time_tick_base.
|
|
|
+static time_t s_time_base = 0;
|
|
|
+//! This variable is the tick where the wall clock time was equal to s_time_base. If you subtract this variable
|
|
|
+//! from the current tick count, you'll get the number of ticks that have elapsed since s_time_base, which will
|
|
|
+//! allow you to calculate the current wall clock time. Note that this value may be negative on startup, see
|
|
|
+//! prv_restore_rtc_time_state
|
|
|
+static int64_t s_time_tick_base = 0;
|
|
|
+
|
|
|
+static time_t prv_ticks_to_time(RtcTicks ticks) {
|
|
|
+ return s_time_base + ((ticks - s_time_tick_base) / RTC_TICKS_HZ);
|
|
|
}
|
|
|
|
|
|
-void rtc_alarm_set(RtcTicks num_ticks) {
|
|
|
+void rtc_set_time(time_t time) {
|
|
|
+#ifdef PBL_LOG_ENABLED
|
|
|
+ char buffer[TIME_STRING_BUFFER_SIZE];
|
|
|
+ PBL_LOG(LOG_LEVEL_INFO, "Setting time to %lu <%s>", time, time_t_to_string(buffer, time));
|
|
|
+#endif
|
|
|
+
|
|
|
+ s_time_base = time;
|
|
|
+ s_time_tick_base = rtc_get_ticks();
|
|
|
+
|
|
|
+ prv_save_rtc_time_state(s_time_tick_base - s_coarse_ticks);
|
|
|
}
|
|
|
|
|
|
-RtcTicks rtc_alarm_get_elapsed_ticks(void) {
|
|
|
- return 0;
|
|
|
+time_t rtc_get_time(void) {
|
|
|
+ return prv_ticks_to_time(rtc_get_ticks());
|
|
|
}
|
|
|
|
|
|
-bool rtc_alarm_is_initialized(void) {
|
|
|
- return 0;
|
|
|
+void rtc_get_time_ms(time_t* out_seconds, uint16_t* out_ms) {
|
|
|
+ RtcTicks ticks = rtc_get_ticks();
|
|
|
+
|
|
|
+ RtcTicks ticks_since_time_base = (ticks - s_time_tick_base);
|
|
|
+ *out_seconds = s_time_base + (ticks_since_time_base / RTC_TICKS_HZ);
|
|
|
+
|
|
|
+ RtcTicks ticks_this_second = ticks_since_time_base % RTC_TICKS_HZ;
|
|
|
+ *out_ms = (ticks_this_second * 1000) / RTC_TICKS_HZ;
|
|
|
+}
|
|
|
+
|
|
|
+/***
|
|
|
+ * Logic associated with saving the RTC-tick-to-wallclock conversion factor
|
|
|
+ * to retained-RAM.
|
|
|
+ */
|
|
|
+
|
|
|
+static void prv_restore_rtc_time_state(void) {
|
|
|
+ // Recover the previously set time from the RTC backup registers.
|
|
|
+ RtcIntervalTicks last_save_time_ticks = retained_read(CURRENT_INTERVAL_TICKS_REGISTER);
|
|
|
+ time_t last_save_time = retained_read(CURRENT_TIME_REGISTER);
|
|
|
+
|
|
|
+ if (s_had_amnesia_on_boot) {
|
|
|
+ /* We have no idea what time it might be. The closest we got is the
|
|
|
+ * last time we saved. */
|
|
|
+ s_time_base = last_save_time;
|
|
|
+ s_time_tick_base = 0;
|
|
|
+ PBL_LOG(LOG_LEVEL_INFO, "Restore RTC: we are on our way up with amnesia");
|
|
|
+ } else {
|
|
|
+ RtcIntervalTicks current_ticks = prv_get_rtc_interval_ticks();
|
|
|
+ const int32_t ticks_since_last_save = prv_elapsed_ticks(last_save_time_ticks * RTC_TICKS_HZ, current_ticks);
|
|
|
+ s_time_base = last_save_time + (ticks_since_last_save / RTC_TICKS_HZ);
|
|
|
+ s_time_tick_base = -(((int64_t)current_ticks) % RTC_TICKS_HZ);
|
|
|
+ PBL_LOG(LOG_LEVEL_INFO, "Restore RTC: we are on our way up with interval_ticks = %"PRIu32, current_ticks);
|
|
|
+ PBL_LOG(LOG_LEVEL_INFO, "Restore RTC: saved: %"PRIu32" diff: %"PRIu32, last_save_time_ticks, ticks_since_last_save);
|
|
|
+ }
|
|
|
+
|
|
|
+ char buffer[TIME_STRING_BUFFER_SIZE];
|
|
|
+ PBL_LOG(LOG_LEVEL_INFO, "Restore RTC: saved_time: %s raw: %lu", time_t_to_string(buffer, last_save_time), last_save_time);
|
|
|
+ PBL_LOG(LOG_LEVEL_INFO, "Restore RTC: current time: %s", time_t_to_string(buffer, s_time_base));
|
|
|
+}
|
|
|
+
|
|
|
+static RtcIntervalTicks prv_get_last_save_time_ticks(void) {
|
|
|
+ return retained_read(CURRENT_INTERVAL_TICKS_REGISTER);
|
|
|
+}
|
|
|
+
|
|
|
+static void prv_save_rtc_time_state_exact(RtcIntervalTicks current_rtc_ticks, time_t time) {
|
|
|
+ retained_write(CURRENT_TIME_REGISTER, time);
|
|
|
+ retained_write(CURRENT_INTERVAL_TICKS_REGISTER, current_rtc_ticks);
|
|
|
+}
|
|
|
+
|
|
|
+static void prv_save_rtc_time_state(RtcIntervalTicks current_rtc_ticks) {
|
|
|
+ if (!s_did_init_rtc) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Floor it to the latest second
|
|
|
+ const RtcIntervalTicks current_rtc_ticks_at_second = (current_rtc_ticks / RTC_TICKS_HZ) * RTC_TICKS_HZ;
|
|
|
+
|
|
|
+ prv_save_rtc_time_state_exact(current_rtc_ticks_at_second, prv_ticks_to_time(s_coarse_ticks + current_rtc_ticks));
|
|
|
}
|
|
|
|
|
|
+/*** Logic that ought be refactored into rtc_common, were it not stm32-only. ***/
|
|
|
+
|
|
|
bool rtc_sanitize_struct_tm(struct tm *t) {
|
|
|
// These values come from time_t (which suffers from the 2038 problem) and our hardware which
|
|
|
// only stores a 2 digit year, so we only represent values after 2000.
|
|
@@ -115,11 +239,11 @@ void rtc_get_time_tm(struct tm* time_tm) {
|
|
|
localtime_r(&t, time_tm);
|
|
|
}
|
|
|
|
|
|
-const char* rtc_get_time_string(char* buffer) {
|
|
|
+const char *rtc_get_time_string(char *buffer) {
|
|
|
return time_t_to_string(buffer, rtc_get_time());
|
|
|
}
|
|
|
|
|
|
-const char* time_t_to_string(char* buffer, time_t t) {
|
|
|
+const char *time_t_to_string(char *buffer, time_t t) {
|
|
|
struct tm time;
|
|
|
localtime_r(&t, &time);
|
|
|
|
|
@@ -143,7 +267,6 @@ void rtc_set_timezone(TimezoneInfo *tzinfo) {
|
|
|
retained_write(RTC_TIMEZONE_DST_END, raw[4]);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
void rtc_get_timezone(TimezoneInfo *tzinfo) {
|
|
|
uint32_t *raw = (uint32_t*)tzinfo;
|
|
|
|
|
@@ -176,15 +299,49 @@ void rtc_enable_backup_regs(void) {
|
|
|
}
|
|
|
|
|
|
void rtc_calibrate_frequency(uint32_t frequency) {
|
|
|
+ /* On nRF5, there is no way to calibrate the RTC. That crystal had better
|
|
|
+ * be accurate!
|
|
|
+ */
|
|
|
}
|
|
|
|
|
|
void rtc_init(void) {
|
|
|
#ifndef NRF_RTC_FREQ_TO_PRESCALER
|
|
|
#define NRF_RTC_FREQ_TO_PRESCALER RTC_FREQ_TO_PRESCALER
|
|
|
#endif
|
|
|
+ if (prv_get_rtc_interval_ticks() == 0) {
|
|
|
+ s_had_amnesia_on_boot = true;
|
|
|
+ PBL_LOG(LOG_LEVEL_INFO, "RTC appears to have been reset :( hope you have your phone connected");
|
|
|
+ }
|
|
|
+
|
|
|
nrf_rtc_prescaler_set(NRF_RTC1, NRF_RTC_FREQ_TO_PRESCALER(RTC_TICKS_HZ));
|
|
|
nrf_rtc_task_trigger(NRF_RTC1, NRF_RTC_TASK_START);
|
|
|
+
|
|
|
+ prv_restore_rtc_time_state();
|
|
|
+ s_did_init_rtc = true;
|
|
|
+}
|
|
|
+
|
|
|
+//! Our RTC tick counter can overflow if nobody asks about it. This
|
|
|
+//! repeating callback allows us to make sure this doesn't happen.
|
|
|
+static void prv_rtc_resync_timer_callback() {
|
|
|
+ rtc_get_ticks();
|
|
|
}
|
|
|
|
|
|
void rtc_init_timers(void) {
|
|
|
+ static RegularTimerInfo rtc_sync_timer = { .list_node = { 0, 0 }, .cb = prv_rtc_resync_timer_callback};
|
|
|
+ regular_timer_add_minutes_callback(&rtc_sync_timer);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void rtc_alarm_init(void) {
|
|
|
+}
|
|
|
+
|
|
|
+void rtc_alarm_set(RtcTicks num_ticks) {
|
|
|
+}
|
|
|
+
|
|
|
+RtcTicks rtc_alarm_get_elapsed_ticks(void) {
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+bool rtc_alarm_is_initialized(void) {
|
|
|
+ return 0;
|
|
|
}
|