blob: 5ed4004670e57a3a5e05e519dad275e491158d1d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include "os/mynewt.h"
#include <hal/hal_os_tick.h>
#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.
* This makes it almost impossible to use with SEGGER SystemView, therefore when OS_SYSVIEW
* is defined __WFI will become a loop waiting for pending interrupts.
*/
#if MYNEWT_VAL(OS_SYSVIEW)
#undef __WFI
#define __WFI() do { } while ((SCB->ICSR & (SCB_ICSR_ISRPENDING_Msk | SCB_ICSR_PENDSTSET_Msk)) == 0)
#else
/*
* Errata for STM32F405, STM32F407, STM32F415, STM32F417.
* When WFI instruction is placed at address like 0x080xxxx4
* (also seen for addresses ending with xxx2). System may
* crash.
* __WFI function places WFI instruction at address ending with x0 or x8
* for affected MCUs.
*/
#if defined(STM32F405xx) || defined(STM32F407xx) || \
defined(STM32F415xx) || defined(STM32F417xx)
#undef __WFI
__attribute__((aligned(8), naked)) void static
__WFI(void)
{
__ASM volatile("wfi\n"
"bx lr");
}
#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
os_tick_idle(os_time_t ticks)
{
OS_ASSERT_CRITICAL();
__DSB();
__WFI();
}
void
os_tick_init(uint32_t os_ticks_per_sec, int prio)
{
uint32_t reload_val;
reload_val = ((uint64_t)SystemCoreClock / os_ticks_per_sec) - 1;
/* Set the system time ticker up */
SysTick->LOAD = reload_val;
SysTick->VAL = 0;
SysTick->CTRL = 0x0007;
/* Set the system tick priority */
NVIC_SetPriority(SysTick_IRQn, prio);
/*
* Keep clocking debug even when CPU is sleeping, stopped or in standby.
*/
#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
}
#endif
#endif