stm32: Add tickless sleep based on RTC

RTC is available with very similar functionality
across STM32F0, STM32F4, STM32F7, STM32L0, STM32L1, STM32L4 STM32WB55.

STM32F1 had RTC base tickless sleep (where RTC is completely different).

This adds possibility to have tickless sleep based on RTC instead of
ARM tick clock.

OS ticks based on RTC are limited to 128, 256, 512, 1024 ticks per second.
If RTC is powered by battery RTC also propagated hardware kept time up
to OS at start.

RTC Alarm A interrupt is used as tick source.
diff --git a/hw/mcu/stm/stm32_common/src/hal_os_tick.c b/hw/mcu/stm/stm32_common/src/hal_os_tick.c
index 9d42763..5ed4004 100644
--- a/hw/mcu/stm/stm32_common/src/hal_os_tick.c
+++ b/hw/mcu/stm/stm32_common/src/hal_os_tick.c
@@ -21,9 +21,10 @@
 #include "os/mynewt.h"
 #include <hal/hal_os_tick.h>
 
-/*
- * XXX implement tickless mode for MCU other then STM32F1.
- */
+#if MYNEWT_VAL(OS_TICKS_USE_RTC)
+#include <stm32_common/stm32_hal.h>
+#include <datetime/datetime.h>
+#endif
 
 /*
  * ST's MCUs seems to have problem with accessing AHB interface from SWD during SLEEP.
@@ -54,6 +55,317 @@
 #endif
 #endif
 
+#if MYNEWT_VAL(OS_TICKS_USE_RTC)
+
+#if MYNEWT_VAL(STM32_CLOCK_LSE) && (((32768 / OS_TICKS_PER_SEC) * OS_TICKS_PER_SEC) != 32768)
+#error OS_TICKS_PER_SEC should be divisible by power of 2 like 128, 256, 512, 1024 when OS_TICKS_USE_RTC is enabled.
+#endif
+
+#ifndef HAL_RTC_MODULE_ENABLED
+#error Define HAL_RTC_MODULE_ENABLED in stm32[fl][0-4]xx_hal_conf.h in your bsp
+#endif
+
+#define ASYNCH_PREDIV       7
+#define SYNCH_PREDIV        (32768 / (ASYNCH_PREDIV + 1) - 1)
+#define SUB_SECONDS_BITS    12
+
+#if defined(STM32L0) || defined(STM32F0)
+#define RTC_IRQ RTC_IRQn
+#else
+#define RTC_IRQ RTC_Alarm_IRQn
+#endif
+
+#if defined(STM32L0) || defined(STM32L1)
+#define IS_RTC_ENABLED (RCC->CSR & RCC_CSR_RTCEN)
+#else
+#define IS_RTC_ENABLED (RCC->BDCR & RCC_BDCR_RTCEN)
+#endif
+
+_Static_assert(SUB_SECONDS_BITS == __builtin_popcount(SYNCH_PREDIV),
+    "SUB_SECONDS_BITS should be number of 1s in SYNCH_PREDIV");
+
+/* RTC time of the last tick. */
+static uint32_t last_rtc_time;
+static uint32_t sub_seconds_per_tick;
+static uint8_t sub_seconds_tick_bits;
+
+/* RTC holds UTC time. */
+static RTC_HandleTypeDef rtc = {
+    .Instance = RTC,
+    .Init = {
+        .HourFormat = RTC_HOURFORMAT_24,
+        .OutPut = RTC_OUTPUT_DISABLE,
+        .OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH,
+        .OutPutType = RTC_OUTPUT_TYPE_PUSHPULL,
+    }
+};
+
+/* RTC AlarmA used for OS ticks. */
+static RTC_AlarmTypeDef alarm = {
+    .AlarmTime = {
+        .Hours = 0,
+        .Minutes = 0,
+        .Seconds = 0,
+        .SubSeconds = 0,
+        .TimeFormat = RTC_HOURFORMAT12_AM,
+        .SecondFraction = 0,
+        .DayLightSaving = RTC_DAYLIGHTSAVING_NONE,
+        .StoreOperation = RTC_STOREOPERATION_RESET,
+    },
+    .AlarmMask = RTC_ALARMMASK_ALL,
+    .AlarmSubSecondMask = 0,
+    .Alarm = RTC_ALARM_A,
+};
+
+static uint32_t
+rtc_time_to_sub_seconds(const RTC_TimeTypeDef *time)
+{
+    uint32_t sub_seconds;
+
+    /* Convert sub second filed to running up sub seconds. */
+    sub_seconds = time->SecondFraction - time->SubSeconds;
+
+    sub_seconds += (((time->Hours * 60 + time->Minutes) * 60) + time->Seconds) << SUB_SECONDS_BITS;
+
+    /* Round down to ticks. */
+    sub_seconds &= ~((1 << sub_seconds_tick_bits) - 1);
+
+    return sub_seconds;
+}
+
+static void
+sub_seconds_to_rtc(uint32_t sub_seconds, RTC_TimeTypeDef *time)
+{
+    time->SubSeconds = time->SecondFraction - (sub_seconds & time->SecondFraction);
+    sub_seconds >>= SUB_SECONDS_BITS;
+    time->Seconds = sub_seconds % 60;
+    sub_seconds /= 60;
+    time->Minutes = sub_seconds % 60;
+    sub_seconds /= 60;
+    time->Hours = sub_seconds % 24;
+}
+
+static void
+rtc_update_time(void)
+{
+    int32_t delta;
+    uint32_t now;
+
+    HAL_RTC_GetTime(&rtc, &alarm.AlarmTime, RTC_FORMAT_BIN);
+    /* Get sub seconds rounded down to tick. */
+    now = rtc_time_to_sub_seconds(&alarm.AlarmTime);
+    delta = now - last_rtc_time;
+    if (delta < 0) {
+        delta += 3600 << SUB_SECONDS_BITS;
+    }
+    alarm.AlarmTime.SubSeconds = alarm.AlarmTime.SecondFraction - (now & alarm.AlarmTime.SecondFraction);
+    alarm.AlarmTime.SubSeconds -= sub_seconds_per_tick;
+    if ((int32_t)alarm.AlarmTime.SubSeconds < 0) {
+        alarm.AlarmTime.SubSeconds += alarm.AlarmTime.SecondFraction + 1;
+        alarm.AlarmTime.Seconds++;
+        if (alarm.AlarmTime.Seconds >= 60) {
+            alarm.AlarmTime.Seconds = 0;
+            alarm.AlarmTime.Minutes++;
+            if (alarm.AlarmTime.Minutes >= 60) {
+                alarm.AlarmTime.Minutes = 0;
+                if (alarm.AlarmTime.Hours >= 24) {
+                    alarm.AlarmTime.Hours = 0;
+                }
+            }
+        }
+    }
+    /* Switch to tick timer interrupt by unmasking only sub second alarm bits. */
+    alarm.AlarmMask = RTC_ALARMMASK_ALL;
+    alarm.AlarmSubSecondMask = sub_seconds_tick_bits << RTC_ALRMASSR_MASKSS_Pos;
+    HAL_RTC_SetAlarm_IT(&rtc, &alarm, RTC_FORMAT_BIN);
+
+    last_rtc_time = now;
+    os_time_advance(delta >> sub_seconds_tick_bits);
+}
+
+void
+os_tick_idle(os_time_t ticks)
+{
+    uint32_t sub_seconds;
+    OS_ASSERT_CRITICAL();
+
+    if (ticks > 0) {
+        /* Get current time directly to alarm. */
+        HAL_RTC_GetTime(&rtc, &alarm.AlarmTime, RTC_FORMAT_BIN);
+
+        alarm.AlarmSubSecondMask = SUB_SECONDS_BITS << RTC_ALRMASSR_MASKSS_Pos;
+        if (ticks < OS_TICKS_PER_SEC) {
+            /* Convert sub-seconds to up-counting value. */
+            sub_seconds = alarm.AlarmTime.SecondFraction - alarm.AlarmTime.SubSeconds;
+            /* Round down to tick. */
+            sub_seconds &= ~((1 << sub_seconds_tick_bits) - 1);
+            /* Add requested number of ticks. */
+            sub_seconds += (ticks & (OS_TICKS_PER_SEC - 1)) << sub_seconds_tick_bits;
+            /* Remove any overflow which is OK since we are sticking to 1 second limit. */
+            sub_seconds &= (1 << SUB_SECONDS_BITS) - 1;
+            /* Convert back to down-counting sub-seconds for alarm to use. */
+            alarm.AlarmTime.SubSeconds = alarm.AlarmTime.SecondFraction - sub_seconds;
+            alarm.AlarmMask = RTC_ALARMMASK_ALL;
+        } else {
+            sub_seconds = rtc_time_to_sub_seconds(&alarm.AlarmTime);
+            sub_seconds += ticks << sub_seconds_tick_bits;
+            sub_seconds_to_rtc(sub_seconds, &alarm.AlarmTime);
+            alarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY;
+        }
+        HAL_RTC_SetAlarm_IT(&rtc, &alarm, RTC_FORMAT_BIN);
+    }
+
+    __DSB();
+    __WFI();
+
+    if (ticks > 0) {
+        rtc_update_time();
+    }
+}
+
+/* ST HAL interrupt handler calls this function. */
+void
+HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
+{
+    (void)hrtc;
+    rtc_update_time();
+}
+
+void
+RTC_Alarm_IRQHandler(void)
+{
+    int sr;
+
+    os_trace_isr_enter();
+
+    OS_ENTER_CRITICAL(sr);
+    HAL_RTC_AlarmIRQHandler(&rtc);
+    OS_EXIT_CRITICAL(sr);
+
+    os_trace_isr_exit();
+}
+
+static void
+stm32_rtc_os_time_change(const struct os_time_change_info *info, void *arg)
+{
+    struct clocktime ct;
+    RTC_DateTypeDef date;
+    uint32_t sub_seconds;
+    int sr;
+
+    timeval_to_clocktime(info->tci_cur_tv, NULL, &ct);
+    date.Year = ct.year - 2000;
+    date.Month = ct.mon;
+    date.Date = ct.day;
+    /* ST HAL Sunday is 7. */
+    date.WeekDay = ct.dow ? ct.dow : 7;
+    sub_seconds = ct.usec * 4096U / 1000000U;
+
+    OS_ENTER_CRITICAL(sr);
+
+    alarm.AlarmTime.Hours = ct.hour;
+    alarm.AlarmTime.Minutes = ct.min;
+    alarm.AlarmTime.Seconds = ct.sec;
+    alarm.AlarmTime.SubSeconds = alarm.AlarmTime.SecondFraction;
+
+    HAL_RTC_SetTime(&rtc, &alarm.AlarmTime, RTC_FORMAT_BIN);
+    HAL_RTC_SetDate(&rtc, &date, RTC_FORMAT_BIN);
+    if (sub_seconds) {
+        RTC->SHIFTR = RTC_SHIFTR_ADD1S | sub_seconds;
+    }
+    last_rtc_time = rtc_time_to_sub_seconds(&alarm.AlarmTime);
+    alarm.AlarmTime.SubSeconds -= sub_seconds_per_tick;
+    alarm.AlarmMask = RTC_ALARMMASK_ALL;
+    HAL_RTC_SetAlarm_IT(&rtc, &alarm, RTC_FORMAT_BIN);
+
+    OS_EXIT_CRITICAL(sr);
+}
+
+static struct os_time_change_listener rtc_setter = {
+    .tcl_fn = stm32_rtc_os_time_change,
+};
+
+static void
+set_os_datetime_from_rtc(const RTC_TimeTypeDef *time, const RTC_DateTypeDef *date)
+{
+    struct os_timeval utc;
+    struct clocktime ct;
+    ct.year = 2000 + date->Year;
+    ct.mon = date->Month;
+    ct.day = date->Date;
+    ct.dow = date->WeekDay == 7 ? 0 : date->WeekDay;
+    ct.hour = time->Hours;
+    ct.min = time->Minutes;
+    ct.sec = time->Seconds;
+    ct.usec = ((time->SecondFraction - time->SubSeconds) * 1000000) >> SUB_SECONDS_BITS;
+    clocktime_to_timeval(&ct, NULL, &utc);
+    os_settimeofday(&utc, NULL);
+}
+
+void
+os_tick_init(uint32_t os_ticks_per_sec, int prio)
+{
+    uint32_t sr;
+    RTC_DateTypeDef date = { .Year = 20, .Month = 1, .Date = 1, .WeekDay = RTC_WEEKDAY_WEDNESDAY };
+    RTC_TimeTypeDef rtc_time = { 0 };
+
+    RCC_PeriphCLKInitTypeDef clock_init = {
+        .PeriphClockSelection = RCC_PERIPHCLK_RTC,
+        .RTCClockSelection = RCC_RTCCLKSOURCE_LSE,
+    };
+    HAL_RCCEx_PeriphCLKConfig(&clock_init);
+
+    /* Set the system tick priority. */
+    NVIC_SetPriority(RTC_IRQ, prio);
+    NVIC_SetVector(RTC_IRQ, (uint32_t)RTC_Alarm_IRQHandler);
+
+    /* If RTC is already on get time and date before reinit. */
+    if (IS_RTC_ENABLED) {
+        HAL_RTC_GetTime(&rtc, &rtc_time, RTC_FORMAT_BIN);
+        HAL_RTC_GetDate(&rtc, &date, RTC_FORMAT_BIN);
+    } else {
+        bzero(&rtc_time, sizeof(rtc_time));
+        __HAL_RCC_RTC_ENABLE();
+    }
+
+    __HAL_DBGMCU_FREEZE_RTC();
+
+#if !MYNEWT_VAL(MCU_STM32F0)
+    DBGMCU->CR |= (DBGMCU_CR_DBG_SLEEP | DBGMCU_CR_DBG_STOP | DBGMCU_CR_DBG_STANDBY);
+#else
+    DBGMCU->CR |= (DBGMCU_CR_DBG_STOP | DBGMCU_CR_DBG_STANDBY);
+#endif
+
+    OS_ENTER_CRITICAL(sr);
+
+    /* RTCCLK 32768 Hz, ck_apre = 4096 Hz, ck_spre = 1Hz. */
+    rtc.Init.AsynchPrediv = ASYNCH_PREDIV;
+    rtc.Init.SynchPrediv = SYNCH_PREDIV;
+    alarm.AlarmSubSecondMask = SUB_SECONDS_BITS << RTC_ALRMASSR_MASKSS_Pos;
+    sub_seconds_per_tick = 32768 / 8 / os_ticks_per_sec;
+    sub_seconds_tick_bits = __builtin_popcount(sub_seconds_per_tick - 1);
+
+    HAL_RTC_Init(&rtc);
+    HAL_RTCEx_EnableBypassShadow(&rtc);
+    HAL_RTC_SetTime(&rtc, &rtc_time, RTC_FORMAT_BIN);
+    HAL_RTC_SetDate(&rtc, &date, RTC_FORMAT_BIN);
+    HAL_RTC_GetTime(&rtc, &rtc_time, RTC_FORMAT_BIN);
+    last_rtc_time = rtc_time_to_sub_seconds(&rtc_time);
+
+    alarm.AlarmTime.SubSeconds = rtc.Init.SynchPrediv - sub_seconds_per_tick;
+
+    HAL_RTC_SetAlarm_IT(&rtc, &alarm, RTC_FORMAT_BIN);
+
+    OS_EXIT_CRITICAL(sr);
+
+    /* Set OS date time, then subscribe to changes so initial change will not trigger RTC update. */
+    set_os_datetime_from_rtc(&rtc_time, &date);
+    os_time_change_listen(&rtc_setter);
+
+    NVIC_EnableIRQ(RTC_IRQ);
+}
+
+#else
 #if MYNEWT_VAL(STM32_CLOCK_LSE) == 0 || (((32768 / OS_TICKS_PER_SEC) * OS_TICKS_PER_SEC) != 32768) || !defined(STM32F1)
 
 void
@@ -90,3 +402,4 @@
 }
 
 #endif
+#endif
diff --git a/hw/mcu/stm/stm32_common/syscfg.yml b/hw/mcu/stm/stm32_common/syscfg.yml
index 604955a..3ecb73e 100644
--- a/hw/mcu/stm/stm32_common/syscfg.yml
+++ b/hw/mcu/stm/stm32_common/syscfg.yml
@@ -433,5 +433,11 @@
         description: "ADC_2"
         value:  0
 
+    OS_TICKS_USE_RTC:
+        description: >
+            Use RTC as source of system ticks.
+            When enabled, OS_TICKS_PER_SEC should be one of 128, 256, 512, 1024.
+        value: 0
+
 syscfg.vals:
     OS_TICKS_PER_SEC: 1000