/***************************************************************************//**
* @file
* @brief SLEEPTIMER API implementation.
*******************************************************************************
* # License
* Copyright 2019 Silicon Laboratories Inc. www.silabs.com
*******************************************************************************
*
* SPDX-License-Identifier: Zlib
*
* The licensor of this software is Silicon Laboratories Inc.
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
******************************************************************************/
#include
#include
#include "em_device.h"
#include "sl_core.h"
#include "sl_sleeptimer.h"
#include "sli_sleeptimer_hal.h"
#include "sl_atomic.h"
#include "sl_sleeptimer_config.h"
#if defined(SL_COMPONENT_CATALOG_PRESENT)
#include "sl_component_catalog.h"
#endif
#if defined(SL_CATALOG_POWER_MANAGER_PRESENT)
#include "sl_power_manager.h"
#include "sli_power_manager.h"
#endif
#define TIME_UNIX_EPOCH (1970u)
#define TIME_NTP_EPOCH (1900u)
#define TIME_ZIGBEE_EPOCH (2000u)
#define TIME_64_EPOCH TIME_NTP_EPOCH
#define TIME_NTP_UNIX_EPOCH_DIFF (TIME_UNIX_EPOCH - TIME_NTP_EPOCH)
#define TIME_ZIGBEE_UNIX_EPOCH_DIFF (TIME_ZIGBEE_EPOCH - TIME_UNIX_EPOCH)
#define TIME_DAY_COUNT_NTP_TO_UNIX_EPOCH (TIME_NTP_UNIX_EPOCH_DIFF * 365u + 17u) ///< 70 years and 17 leap days
#define TIME_DAY_COUNT_ZIGBEE_TO_UNIX_EPOCH (TIME_ZIGBEE_UNIX_EPOCH_DIFF * 365u + 7u) ///< 30 years and 7 leap days
#define TIME_SEC_PER_DAY (60u * 60u * 24u)
#define TIME_NTP_EPOCH_OFFSET_SEC (TIME_DAY_COUNT_NTP_TO_UNIX_EPOCH * TIME_SEC_PER_DAY)
#define TIME_ZIGBEE_EPOCH_OFFSET_SEC (TIME_DAY_COUNT_ZIGBEE_TO_UNIX_EPOCH * TIME_SEC_PER_DAY)
#define TIME_DAY_PER_YEAR (365u)
#define TIME_SEC_PER_YEAR (TIME_SEC_PER_DAY * TIME_DAY_PER_YEAR)
#define TIME_UNIX_TIMESTAMP_MAX (0x7FFFFFFF)
#define TIME_64_BIT_UNIX_TIMESTAMP_MAX (0x497968BD7F) /// Max 64 bit timestamp supported is 11:59:59 PM 12/31/11899
#define TIME_UNIX_YEAR_MAX (2038u - TIME_NTP_EPOCH) ///< Max UNIX year based from a 1900 epoch
#define TIME_64_BIT_YEAR_MAX (11899u - TIME_NTP_EPOCH) ///< Max 64 bit format year based from a 1900 epoch
#define TIME_64_TO_32_EPOCH_OFFSET_SEC TIME_NTP_EPOCH_OFFSET_SEC
#define TIME_UNIX_TO_NTP_MAX (0xFFFFFFFF - TIME_NTP_EPOCH_OFFSET_SEC)
// Minimum count difference used when evaluating if a timer expired or not after an interrupt
// by comparing the current count value and the expected expiration count value.
// The difference should be null or of few ticks since the counter never stop.
#define MIN_DIFF_BETWEEN_COUNT_AND_EXPIRATION 2
/// @brief Time Format.
SLEEPTIMER_ENUM(sl_sleeptimer_time_format_t) {
TIME_FORMAT_UNIX = 0, ///< Number of seconds since January 1, 1970, 00:00. Type is signed, so represented on 31 bit.
TIME_FORMAT_NTP = 1, ///< Number of seconds since January 1, 1900, 00:00. Type is unsigned, so represented on 32 bit.
TIME_FORMAT_ZIGBEE_CLUSTER = 2, ///< Number of seconds since January 1, 2000, 00:00. Type is unsigned, so represented on 32 bit.
TIME_FORMAT_UNIX_64_BIT = 3, ///< Number of seconds since January 1, 1900, 00:00. Type is unsigned, so represented on 64 bit.
};
// tick_count, it can wrap around.
typedef uint32_t sl_sleeptimer_tick_count_t;
// Overflow counter used to provide 64-bits tick count.
static volatile uint32_t overflow_counter;
#if SL_SLEEPTIMER_WALLCLOCK_CONFIG
// Current time count.
static volatile sl_sleeptimer_timestamp_64_t second_count;
// Tick rest when the frequency is not a divider of the timer width.
static volatile uint32_t overflow_tick_rest = 0;
// Current time zone offset.
static sl_sleeptimer_time_zone_offset_t tz_offset = 0;
// Precalculated tick rest in case of overflow.
static uint32_t calculated_tick_rest = 0;
// Precalculated timer overflow duration in seconds.
static uint32_t calculated_sec_count = 0;
#endif
// Timer frequency in Hz.
static uint32_t timer_frequency;
// Head of timer list.
static sl_sleeptimer_timer_handle_t *timer_head;
// Count at last update of delta of first timer.
static volatile sl_sleeptimer_tick_count_t last_delta_update_count;
// Initialization flag.
static bool is_sleeptimer_initialized = false;
// Flag that indicates if power manager's timer will expire at next compare match.
static volatile bool next_timer_to_expire_is_power_manager = false;
// Precalculated value to avoid millisecond to tick conversion overflow.
static uint32_t max_millisecond_conversion;
// Sleep on ISR exit flag.
static volatile bool sleep_on_isr_exit = false;
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_SLEEPTIMER, SL_CODE_CLASS_TIME_CRITICAL)
static void delta_list_insert_timer(sl_sleeptimer_timer_handle_t *handle,
sl_sleeptimer_tick_count_t timeout);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_SLEEPTIMER, SL_CODE_CLASS_TIME_CRITICAL)
static sl_status_t delta_list_remove_timer(sl_sleeptimer_timer_handle_t *handle);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_SLEEPTIMER, SL_CODE_CLASS_TIME_CRITICAL)
static sl_status_t set_comparator_for_next_timer(void);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_SLEEPTIMER, SL_CODE_CLASS_TIME_CRITICAL)
static void update_delta_list(void);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_SLEEPTIMER, SL_CODE_CLASS_TIME_CRITICAL)
__STATIC_INLINE uint32_t div_to_log2(uint32_t div);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_SLEEPTIMER, SL_CODE_CLASS_TIME_CRITICAL)
__STATIC_INLINE bool is_power_of_2(uint32_t nbr);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_SLEEPTIMER, SL_CODE_CLASS_TIME_CRITICAL)
static sl_status_t create_timer(sl_sleeptimer_timer_handle_t *handle,
sl_sleeptimer_tick_count_t timeout_initial,
sl_sleeptimer_tick_count_t timeout_periodic,
sl_sleeptimer_timer_callback_t callback,
void *callback_data,
uint8_t priority,
uint16_t option_flags);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_SLEEPTIMER, SL_CODE_CLASS_TIME_CRITICAL)
static void process_expired_timer(sl_sleeptimer_timer_handle_t *timer);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_SLEEPTIMER, SL_CODE_CLASS_TIME_CRITICAL)
static void update_next_timer_to_expire_is_power_manager(void);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_SLEEPTIMER, SL_CODE_CLASS_TIME_CRITICAL)
static void delay_callback(sl_sleeptimer_timer_handle_t *handle,
void *data);
#if SL_SLEEPTIMER_WALLCLOCK_CONFIG
static bool is_leap_year(uint16_t year);
static uint16_t number_of_leap_days(uint32_t base_year, uint32_t current_year);
static sl_sleeptimer_weekDay_t compute_day_of_week(uint32_t day);
static sl_sleeptimer_weekDay_t compute_day_of_week_64(uint64_t day);
static uint16_t compute_day_of_year(sl_sleeptimer_month_t month, uint8_t day, bool isLeapYear);
static bool is_valid_time(sl_sleeptimer_timestamp_t time,
sl_sleeptimer_time_format_t format,
sl_sleeptimer_time_zone_offset_t time_zone);
static bool is_valid_time_64(sl_sleeptimer_timestamp_64_t time,
sl_sleeptimer_time_format_t format,
sl_sleeptimer_time_zone_offset_t time_zone);
static bool is_valid_date(sl_sleeptimer_date_t *date);
static bool is_valid_date_64(sl_sleeptimer_date_t *date);
static const uint8_t days_in_month[2u][12] = {
/* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */
{ 31u, 28u, 31u, 30u, 31u, 30u, 31u, 31u, 30u, 31u, 30u, 31u },
{ 31u, 29u, 31u, 30u, 31u, 30u, 31u, 31u, 30u, 31u, 30u, 31u }
};
#endif
/**************************************************************************//**
* Initializes sleep timer.
*****************************************************************************/
sl_status_t sl_sleeptimer_init(void)
{
CORE_DECLARE_IRQ_STATE;
CORE_ENTER_ATOMIC();
if (!is_sleeptimer_initialized) {
timer_head = NULL;
last_delta_update_count = 0u;
overflow_counter = 0u;
sleeptimer_hal_init_timer();
sleeptimer_hal_enable_int(SLEEPTIMER_EVENT_OF);
timer_frequency = sleeptimer_hal_get_timer_frequency();
if (timer_frequency == 0) {
CORE_EXIT_ATOMIC();
return SL_STATUS_INVALID_CONFIGURATION;
}
#if SL_SLEEPTIMER_WALLCLOCK_CONFIG
second_count = 0;
calculated_tick_rest = ((uint64_t)UINT32_MAX + 1) % (uint64_t)timer_frequency;
calculated_sec_count = (((uint64_t)UINT32_MAX + 1) / (uint64_t)timer_frequency);
#endif
max_millisecond_conversion = (uint32_t)(((uint64_t)UINT32_MAX * (uint64_t)1000u) / timer_frequency);
is_sleeptimer_initialized = true;
}
CORE_EXIT_ATOMIC();
return SL_STATUS_OK;
}
/**************************************************************************//**
* Starts a 32 bits timer.
*****************************************************************************/
sl_status_t sl_sleeptimer_start_timer(sl_sleeptimer_timer_handle_t *handle,
uint32_t timeout,
sl_sleeptimer_timer_callback_t callback,
void *callback_data,
uint8_t priority,
uint16_t option_flags)
{
bool is_running = false;
if (handle == NULL) {
return SL_STATUS_NULL_POINTER;
}
handle->conversion_error = 0;
handle->accumulated_error = 0;
sl_sleeptimer_is_timer_running(handle, &is_running);
if (is_running == true) {
return SL_STATUS_NOT_READY;
}
return create_timer(handle,
timeout,
0,
callback,
callback_data,
priority,
option_flags);
}
/**************************************************************************//**
* Restarts a 32 bits timer.
*****************************************************************************/
sl_status_t sl_sleeptimer_restart_timer(sl_sleeptimer_timer_handle_t *handle,
uint32_t timeout,
sl_sleeptimer_timer_callback_t callback,
void *callback_data,
uint8_t priority,
uint16_t option_flags)
{
if (handle == NULL) {
return SL_STATUS_NULL_POINTER;
}
handle->conversion_error = 0;
handle->accumulated_error = 0;
//Trying to stop the Timer. Failing to do so implies the timer is not running.
sl_sleeptimer_stop_timer(handle);
//Creates the timer in any case.
return create_timer(handle,
timeout,
0,
callback,
callback_data,
priority,
option_flags);
}
/**************************************************************************//**
* Starts a 32 bits periodic timer.
*****************************************************************************/
sl_status_t sl_sleeptimer_start_periodic_timer(sl_sleeptimer_timer_handle_t *handle,
uint32_t timeout,
sl_sleeptimer_timer_callback_t callback,
void *callback_data,
uint8_t priority,
uint16_t option_flags)
{
bool is_running = false;
if (handle == NULL) {
return SL_STATUS_NULL_POINTER;
}
handle->conversion_error = 0;
handle->accumulated_error = 0;
sl_sleeptimer_is_timer_running(handle, &is_running);
if (is_running == true) {
return SL_STATUS_INVALID_STATE;
}
return create_timer(handle,
timeout,
timeout,
callback,
callback_data,
priority,
option_flags);
}
/**************************************************************************//**
* Starts a 32 bits periodic timer using milliseconds as the timebase.
*****************************************************************************/
sl_status_t sl_sleeptimer_start_periodic_timer_ms(sl_sleeptimer_timer_handle_t *handle,
uint32_t timeout_ms,
sl_sleeptimer_timer_callback_t callback,
void *callback_data,
uint8_t priority,
uint16_t option_flags)
{
bool is_running = false;
sl_status_t status;
uint32_t timeout_tick;
if (handle == NULL) {
return SL_STATUS_NULL_POINTER;
}
sl_sleeptimer_is_timer_running(handle, &is_running);
if (is_running == true) {
return SL_STATUS_INVALID_STATE;
}
status = sl_sleeptimer_ms32_to_tick(timeout_ms, &timeout_tick);
if (status != SL_STATUS_OK) {
return status;
}
// Calculate ms to ticks conversion error
handle->conversion_error = 1000
- ((uint64_t)(timeout_ms * sl_sleeptimer_get_timer_frequency())
% 1000);
if (handle->conversion_error == 1000) {
handle->conversion_error = 0;
}
// Initialize accumulated error to 0. The calculated conversion error will
// be added to this variable each time a timer in the series of periodic timers
// expires.
handle->accumulated_error = 0;
return create_timer(handle,
timeout_tick,
timeout_tick,
callback,
callback_data,
priority,
option_flags);
}
/**************************************************************************//**
* Restarts a 32 bits periodic timer.
*****************************************************************************/
sl_status_t sl_sleeptimer_restart_periodic_timer(sl_sleeptimer_timer_handle_t *handle,
uint32_t timeout,
sl_sleeptimer_timer_callback_t callback,
void *callback_data,
uint8_t priority,
uint16_t option_flags)
{
if (handle == NULL) {
return SL_STATUS_NULL_POINTER;
}
handle->conversion_error = 0;
handle->accumulated_error = 0;
//Trying to stop the Timer. Failing to do so implies the timer has already been stopped.
sl_sleeptimer_stop_timer(handle);
//Creates the timer in any case.
return create_timer(handle,
timeout,
timeout,
callback,
callback_data,
priority,
option_flags);
}
/**************************************************************************//**
* Restarts a 32 bits periodic timer using milliseconds as the timebase.
*****************************************************************************/
sl_status_t sl_sleeptimer_restart_periodic_timer_ms(sl_sleeptimer_timer_handle_t *handle,
uint32_t timeout_ms,
sl_sleeptimer_timer_callback_t callback,
void *callback_data,
uint8_t priority,
uint16_t option_flags)
{
sl_status_t status;
uint32_t timeout_tick;
if (handle == NULL) {
return SL_STATUS_NULL_POINTER;
}
status = sl_sleeptimer_ms32_to_tick(timeout_ms, &timeout_tick);
if (status != SL_STATUS_OK) {
return status;
}
// Calculate ms to ticks conversion error
handle->conversion_error = 1000
- ((uint64_t)(timeout_ms * sl_sleeptimer_get_timer_frequency())
% 1000);
if (handle->conversion_error == 1000) {
handle->conversion_error = 0;
}
// Initialize accumulated error to 0. The calculated conversion error will
// be added to this variable each time a timer in the series of periodic timers
// expires.
handle->accumulated_error = 0;
//Trying to stop the Timer. Failing to do so implies the timer has already been stopped.
sl_sleeptimer_stop_timer(handle);
//Creates the timer in any case.
return create_timer(handle,
timeout_tick,
timeout_tick,
callback,
callback_data,
priority,
option_flags);
}
/**************************************************************************//**
* Stops a 32 bits timer.
*****************************************************************************/
sl_status_t sl_sleeptimer_stop_timer(sl_sleeptimer_timer_handle_t *handle)
{
CORE_DECLARE_IRQ_STATE;
sl_status_t error;
bool set_comparator = false;
// Disable PRS compare and capture channel, if configured for early wakeup
#if ((SL_SLEEPTIMER_PERIPHERAL == SL_SLEEPTIMER_PERIPHERAL_SYSRTC) \
&& defined(SL_CATALOG_POWER_MANAGER_PRESENT) \
&& !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT))
if (handle->option_flags == (SLI_SLEEPTIMER_POWER_MANAGER_EARLY_WAKEUP_TIMER_FLAG | SLI_SLEEPTIMER_POWER_MANAGER_HF_ACCURACY_CLK_FLAG)) {
sleeptimer_hal_disable_prs_compare_and_capture_channel();
}
#endif
if (handle == NULL) {
return SL_STATUS_NULL_POINTER;
}
CORE_ENTER_CRITICAL();
update_delta_list();
// If first timer in list, update timer comparator.
if (timer_head == handle) {
set_comparator = true;
}
error = delta_list_remove_timer(handle);
if (error != SL_STATUS_OK) {
CORE_EXIT_CRITICAL();
return error;
}
if (set_comparator) {
error = set_comparator_for_next_timer();
if (error == SL_STATUS_NULL_POINTER) {
sleeptimer_hal_disable_int(SLEEPTIMER_EVENT_COMP);
}
}
CORE_EXIT_CRITICAL();
return SL_STATUS_OK;
}
/**************************************************************************//**
* Gets the status of a timer.
*****************************************************************************/
sl_status_t sl_sleeptimer_is_timer_running(const sl_sleeptimer_timer_handle_t *handle,
bool *running)
{
CORE_DECLARE_IRQ_STATE;
sl_sleeptimer_timer_handle_t *current;
if (handle == NULL || running == NULL) {
return SL_STATUS_NULL_POINTER;
} else {
*running = false;
CORE_ENTER_ATOMIC();
current = timer_head;
while (current != NULL && !*running) {
if (current == handle) {
*running = true;
} else {
current = current->next;
}
}
CORE_EXIT_ATOMIC();
}
return SL_STATUS_OK;
}
/**************************************************************************//**
* Gets a 32 bits timer's time remaining.
*****************************************************************************/
sl_status_t sl_sleeptimer_get_timer_time_remaining(const sl_sleeptimer_timer_handle_t *handle,
uint32_t *time)
{
CORE_DECLARE_IRQ_STATE;
sl_sleeptimer_timer_handle_t *current;
if (handle == NULL || time == NULL) {
return SL_STATUS_NULL_POINTER;
}
CORE_ENTER_ATOMIC();
update_delta_list();
*time = handle->delta;
// Retrieve timer in list and add the deltas.
current = timer_head;
while (current != handle && current != NULL) {
*time += current->delta;
current = current->next;
}
if (current != handle) {
CORE_EXIT_ATOMIC();
return SL_STATUS_NOT_READY;
}
// Substract time since last compare match.
if (*time > sleeptimer_hal_get_counter() - last_delta_update_count) {
*time -= sleeptimer_hal_get_counter() - last_delta_update_count;
} else {
*time = 0;
}
CORE_EXIT_ATOMIC();
return SL_STATUS_OK;
}
/**************************************************************************//**
* Gets the time remaining until the first timer with the matching set of flags
* expires.
*****************************************************************************/
sl_status_t sl_sleeptimer_get_remaining_time_of_first_timer(uint16_t option_flags,
uint32_t *time_remaining)
{
CORE_DECLARE_IRQ_STATE;
sl_sleeptimer_timer_handle_t *current;
uint32_t time = 0;
CORE_ENTER_ATOMIC();
// parse list and retrieve first timer with option flags requirement.
current = timer_head;
while (current != NULL) {
// save time remaining for timer.
time += current->delta;
// Check if the current timer has the flags requested
if (current->option_flags == option_flags
|| option_flags == SL_SLEEPTIMER_ANY_FLAG) {
// Substract time since last compare match.
if (time > (sleeptimer_hal_get_counter() - last_delta_update_count)) {
time -= (sleeptimer_hal_get_counter() - last_delta_update_count);
} else {
time = 0;
}
*time_remaining = time;
CORE_EXIT_ATOMIC();
return SL_STATUS_OK;
}
current = current->next;
}
CORE_EXIT_ATOMIC();
return SL_STATUS_EMPTY;
}
/**************************************************************************//**
* Determines if next timer to expire has the option flag
* "SL_SLEEPTIMER_POWER_MANAGER_EARLY_WAKEUP_TIMER_FLAG".
*
* @note This function is for internal use only.
*
* @note A check to validate that the Power Manager Sleeptimer is expired on
* top of being the next timer was added. This is because
* this function is called when coming back from EM2 sleep to validate
* that the system woke up because of this precise timer expiration.
* Some race conditions, seen with FreeRTOS, could create invalid RTC
* interrupt leading to believe that the power manager timer was expired
* when it was not.
*****************************************************************************/
bool sli_sleeptimer_is_power_manager_timer_next_to_expire(void)
{
bool next_timer_is_power_manager;
sl_atomic_load(next_timer_is_power_manager, next_timer_to_expire_is_power_manager);
// Make sure that the Power Manager Sleeptimer is actually expired in addition
// to being the next timer.
if (next_timer_is_power_manager
&& ((sl_sleeptimer_get_tick_count() - timer_head->timeout_expected_tc) > MIN_DIFF_BETWEEN_COUNT_AND_EXPIRATION)) {
next_timer_is_power_manager = false;
}
return next_timer_is_power_manager;
}
/***************************************************************************//**
* Gets current 32 bits tick count.
*******************************************************************************/
uint32_t sl_sleeptimer_get_tick_count(void)
{
uint32_t cnt;
CORE_DECLARE_IRQ_STATE;
CORE_ENTER_ATOMIC();
cnt = sleeptimer_hal_get_counter();
CORE_EXIT_ATOMIC();
return cnt;
}
/***************************************************************************//**
* Gets current 64 bits tick count.
*******************************************************************************/
uint64_t sl_sleeptimer_get_tick_count64(void)
{
uint32_t tick_cnt;
uint32_t of_cnt;
CORE_DECLARE_IRQ_STATE;
CORE_ENTER_ATOMIC();
tick_cnt = sleeptimer_hal_get_counter();
of_cnt = overflow_counter;
if (sli_sleeptimer_hal_is_int_status_set(SLEEPTIMER_EVENT_OF)) {
tick_cnt = sleeptimer_hal_get_counter();
of_cnt++;
}
CORE_EXIT_ATOMIC();
return (((uint64_t) of_cnt) << 32) | tick_cnt;
}
/***************************************************************************//**
* Get timer frequency.
******************************************************************************/
uint32_t sl_sleeptimer_get_timer_frequency(void)
{
return timer_frequency;
}
#if SL_SLEEPTIMER_WALLCLOCK_CONFIG
/***************************************************************************//**
* Retrieves current 32 bit time.
******************************************************************************/
sl_sleeptimer_timestamp_t sl_sleeptimer_get_time(void)
{
uint64_t temp_time = sl_sleeptimer_get_time_64();
// Add offset for 64 to 32 bit time
if (temp_time >= TIME_64_TO_32_EPOCH_OFFSET_SEC) {
temp_time -= TIME_64_TO_32_EPOCH_OFFSET_SEC;
}
// Return lower 32 bits of 64 bit time
uint32_t time = (temp_time & 0xFFFFFFFF);
return time;
}
/***************************************************************************//**
* Retrieves current 64 bit time.
******************************************************************************/
sl_sleeptimer_timestamp_64_t sl_sleeptimer_get_time_64(void)
{
uint32_t cnt = 0u;
uint32_t freq = 0u;
sl_sleeptimer_timestamp_64_t time;
CORE_DECLARE_IRQ_STATE;
cnt = sleeptimer_hal_get_counter();
freq = sl_sleeptimer_get_timer_frequency();
CORE_ENTER_ATOMIC();
time = second_count + cnt / freq;
if (cnt % freq + overflow_tick_rest >= freq) {
time++;
}
CORE_EXIT_ATOMIC();
return time;
}
/***************************************************************************//**
* Sets current time from 32 bit variable.
******************************************************************************/
sl_status_t sl_sleeptimer_set_time(sl_sleeptimer_timestamp_t time)
{
// convert 32 bit time to 64 bit time
uint64_t temp_time = time + (uint64_t)TIME_64_TO_32_EPOCH_OFFSET_SEC;
sl_status_t err_code = sl_sleeptimer_set_time_64(temp_time);
return err_code;
}
/***************************************************************************//**
* Sets current time from 64 bit variable.
******************************************************************************/
sl_status_t sl_sleeptimer_set_time_64(sl_sleeptimer_timestamp_64_t time)
{
uint32_t freq = 0u;
uint32_t counter_sec = 0u;
uint32_t cnt = 0;
CORE_DECLARE_IRQ_STATE;
// convert 64 bit time to 32 bit time
if (!is_valid_time_64(time, TIME_FORMAT_UNIX_64_BIT, 0u)) {
return SL_STATUS_INVALID_PARAMETER;
}
freq = sl_sleeptimer_get_timer_frequency();
cnt = sleeptimer_hal_get_counter();
CORE_ENTER_ATOMIC();
// store 64 bit time as 64 bits's
second_count = time;
// Convert 64 bit time to 32 bit time in order to check for overflow
// i.e. if 32 bit time is >=counter_sec
uint64_t temp_time = second_count - TIME_64_TO_32_EPOCH_OFFSET_SEC;
uint32_t second_time_32 = (temp_time & 0xFFFFFFFF);
overflow_tick_rest = 0;
counter_sec = cnt / freq;
if (second_time_32 >= counter_sec) {
second_count -= counter_sec;
} else {
CORE_EXIT_ATOMIC();
return SL_STATUS_INVALID_PARAMETER;
}
CORE_EXIT_ATOMIC();
return SL_STATUS_OK;
}
/***************************************************************************//**
* Gets current date.
******************************************************************************/
sl_status_t sl_sleeptimer_get_datetime(sl_sleeptimer_date_t *date)
{
sl_sleeptimer_timestamp_64_t time = 0u;
sl_sleeptimer_time_zone_offset_t tz;
sl_status_t err_code = SL_STATUS_OK;
// Fetch 64 bit timestamp
time = sl_sleeptimer_get_time_64();
tz = sl_sleeptimer_get_tz();
err_code = sl_sleeptimer_convert_time_to_date_64(time, tz, date);
return err_code;
}
/***************************************************************************//**
* Sets current time, in date format.
******************************************************************************/
sl_status_t sl_sleeptimer_set_datetime(sl_sleeptimer_date_t *date)
{
sl_sleeptimer_timestamp_64_t time = 0u;
sl_status_t err_code = SL_STATUS_OK;
CORE_DECLARE_IRQ_STATE;
if (!is_valid_date_64(date)) {
return SL_STATUS_INVALID_PARAMETER;
}
err_code = sl_sleeptimer_convert_date_to_time_64(date, &time);
if (err_code != SL_STATUS_OK) {
return err_code;
}
CORE_ENTER_ATOMIC();
// sets the 64 bit second_time value
err_code = sl_sleeptimer_set_time_64(time);
if (err_code == SL_STATUS_OK) {
sl_sleeptimer_set_tz(date->time_zone);
}
CORE_EXIT_ATOMIC();
return err_code;
}
/***************************************************************************//**
* Builds a date time structure based on the provided parameters.
******************************************************************************/
sl_status_t sl_sleeptimer_build_datetime(sl_sleeptimer_date_t *date,
uint16_t year,
sl_sleeptimer_month_t month,
uint8_t month_day,
uint8_t hour,
uint8_t min,
uint8_t sec,
sl_sleeptimer_time_zone_offset_t tz_offset)
{
if (date == NULL) {
return SL_STATUS_NULL_POINTER;
}
// If year is smaller than 1900, assume NTP Epoch is used.
date->year = ((year < TIME_NTP_EPOCH) ? year : (year - TIME_NTP_EPOCH));
date->month = month;
date->month_day = month_day;
date->hour = hour;
date->min = min;
date->sec = sec;
date->time_zone = tz_offset;
// Validate that input parameters are correct before filing the missing fields
if (!is_valid_date(date)) {
return SL_STATUS_INVALID_PARAMETER;
}
date->day_of_year = compute_day_of_year(date->month, date->month_day, is_leap_year(date->year));
date->day_of_week = compute_day_of_week(((date->year - TIME_NTP_UNIX_EPOCH_DIFF) * TIME_DAY_PER_YEAR)
+ number_of_leap_days(TIME_UNIX_EPOCH, (date->year + TIME_NTP_EPOCH))
+ date->day_of_year - 1);
return SL_STATUS_OK;
}
/***************************************************************************//**
* Builds a date time structure based on the provided parameters.
******************************************************************************/
sl_status_t sl_sleeptimer_build_datetime_64(sl_sleeptimer_date_t *date,
uint16_t year,
sl_sleeptimer_month_t month,
uint8_t month_day,
uint8_t hour,
uint8_t min,
uint8_t sec,
sl_sleeptimer_time_zone_offset_t tz_offset)
{
if (date == NULL) {
return SL_STATUS_NULL_POINTER;
}
// Ensure that year is greater than 1900 and based on 0 epoch
if (year < TIME_NTP_EPOCH) {
return SL_STATUS_INVALID_PARAMETER;
}
// Convert year based on 0 epoch to a valid date->year based on 1900 epoch
date->year = (year - TIME_NTP_EPOCH);
date->month = month;
date->month_day = month_day;
date->hour = hour;
date->min = min;
date->sec = sec;
date->time_zone = tz_offset;
// Validate that input parameters are correct before filing the missing fields
if (!is_valid_date_64(date)) {
return SL_STATUS_INVALID_PARAMETER;
}
date->day_of_year = compute_day_of_year(date->month, date->month_day, is_leap_year(date->year));
date->day_of_week = compute_day_of_week_64((date->year * TIME_DAY_PER_YEAR)
+ number_of_leap_days(TIME_NTP_EPOCH, (date->year + TIME_NTP_EPOCH))
+ date->day_of_year - 1);
return SL_STATUS_OK;
}
/*******************************************************************************
* Convert a 32 bit time stamp into a date structure.
******************************************************************************/
sl_status_t sl_sleeptimer_convert_time_to_date(sl_sleeptimer_timestamp_t time,
sl_sleeptimer_time_zone_offset_t time_zone,
sl_sleeptimer_date_t *date)
{
// convert 32 bit timestamp to 64 bit
sl_sleeptimer_timestamp_64_t temp_time = (uint64_t)time + TIME_64_TO_32_EPOCH_OFFSET_SEC;
sl_status_t err_code = sl_sleeptimer_convert_time_to_date_64(temp_time, time_zone, date);
return err_code;
}
/*******************************************************************************
* Convert a 64 bit time stamp into a date structure.
******************************************************************************/
sl_status_t sl_sleeptimer_convert_time_to_date_64(sl_sleeptimer_timestamp_64_t time,
sl_sleeptimer_time_zone_offset_t time_zone,
sl_sleeptimer_date_t *date)
{
uint16_t full_year = 0;
uint16_t leap_day = 0;
uint8_t leap_year_flag = 0;
uint8_t current_month = 0;
if (!is_valid_time_64(time, TIME_FORMAT_UNIX_64_BIT, time_zone)) {
return SL_STATUS_INVALID_PARAMETER;
}
time += time_zone; // add UTC offset to convert to Standard Time
date->sec = time % 60;
time /= 60;
date->min = time % 60;
time /= 60;
date->hour = time % 24;
time /= 24; // time is now the number of days since 1900
date->day_of_week = (sl_sleeptimer_weekDay_t)compute_day_of_week_64(time);
full_year = time / TIME_DAY_PER_YEAR; // Approximates the number of full years
uint32_t base_year = 1900u;
uint32_t current_year = full_year + base_year;
if (full_year > 4) { // 1904 is the first leap year since 1900
leap_day = number_of_leap_days(base_year, current_year); // Approximates the number of leap days.
full_year = (time - leap_day) / TIME_DAY_PER_YEAR; // Computes the number of year integrating the leap days.
current_year = full_year + base_year;
leap_day = number_of_leap_days(base_year, current_year); // Computes the actual number of leap days of the previous years.
}
date->year = full_year; // Year in date struct must be based on a 1900 epoch.
if (is_leap_year(date->year)) {
leap_year_flag = 1;
}
time = (time - leap_day) - (TIME_DAY_PER_YEAR * full_year); // Subtracts days of previous year.
date->day_of_year = time + 1;
while (time >= days_in_month[leap_year_flag][current_month]) {
time -= days_in_month[leap_year_flag][current_month]; // Subtracts the number of days of the passed month.
current_month++;
}
date->month = (sl_sleeptimer_month_t)current_month;
date->month_day = time + 1;
date->time_zone = time_zone;
return SL_STATUS_OK;
}
/*******************************************************************************
* Convert a date structure into a 32 bit time stamp.
******************************************************************************/
sl_status_t sl_sleeptimer_convert_date_to_time(sl_sleeptimer_date_t *date,
sl_sleeptimer_timestamp_t *time)
{
// Create a 64 bit time stamp
sl_sleeptimer_timestamp_64_t temp_time = 0;
sl_status_t err_code = sl_sleeptimer_convert_date_to_time_64(date, &temp_time);
if (err_code != SL_STATUS_OK) {
return err_code;
}
// Convert 64 bit time to 32 bit time
sl_sleeptimer_timestamp_64_t time_32 = temp_time;
time_32 -= TIME_64_TO_32_EPOCH_OFFSET_SEC;
*time = (time_32 & 0xFFFFFFFF);
return err_code;
}
/*******************************************************************************
* Convert a date structure into a 64 bit time stamp.
******************************************************************************/
sl_status_t sl_sleeptimer_convert_date_to_time_64(sl_sleeptimer_date_t *date,
sl_sleeptimer_timestamp_64_t *time)
{
uint16_t month_days = 0;
uint8_t month;
uint16_t full_year = 0;
uint8_t leap_year_flag = 0;
uint16_t leap_days = 0;
if (!is_valid_date_64(date)) {
return SL_STATUS_INVALID_PARAMETER;
}
full_year = (date->year); // base year for 64 bits its 1900 not 1970
month = date->month; // offset to get months value from 1 to 12.
uint32_t base_year = 1900u;
uint32_t current_year = full_year + base_year;
*time = (full_year * (uint64_t)TIME_SEC_PER_YEAR);
if (full_year > 4) { // 1904 is the first leap year since 1900
leap_days = number_of_leap_days(base_year, current_year);
month_days = leap_days;
}
if (is_leap_year(date->year)) {
leap_year_flag = 1;
}
for (int i = 0; i < month; i++) {
month_days += days_in_month[leap_year_flag][i]; // Add the number of days of the month of the year.
}
month_days += (date->month_day - 1); // Add full days of the current month.
*time += month_days * TIME_SEC_PER_DAY;
*time += (3600 * date->hour) + (60 * date->min) + date->sec;
*time -= date->time_zone;
return SL_STATUS_OK;
}
/*******************************************************************************
* Convert a date structure to string.
******************************************************************************/
uint32_t sl_sleeptimer_convert_date_to_str(char *str,
size_t size,
const uint8_t *format,
sl_sleeptimer_date_t *date)
{
uint32_t return_size = 0u;
if (is_valid_date(date)) {
struct tm date_struct;
date_struct.tm_hour = date->hour;
date_struct.tm_mday = date->month_day;
date_struct.tm_min = date->min;
date_struct.tm_mon = date->month;
date_struct.tm_sec = date->sec;
date_struct.tm_wday = date->day_of_week;
date_struct.tm_yday = date->day_of_year;
date_struct.tm_year = date->year;
return_size = strftime(str,
size,
(const char *)format,
&date_struct);
}
return return_size;
}
/***************************************************************************//**
* Sets time zone offset.
*
* @param offset Time zone offset, in seconds.
******************************************************************************/
void sl_sleeptimer_set_tz(sl_sleeptimer_time_zone_offset_t offset)
{
CORE_DECLARE_IRQ_STATE;
CORE_ENTER_ATOMIC();
tz_offset = offset;
CORE_EXIT_ATOMIC();
}
/***************************************************************************//**
* Gets time zone offset.
*
* @return Time zone offset, in seconds.
******************************************************************************/
sl_sleeptimer_time_zone_offset_t sl_sleeptimer_get_tz(void)
{
sl_sleeptimer_time_zone_offset_t offset;
CORE_DECLARE_IRQ_STATE;
CORE_ENTER_ATOMIC();
offset = tz_offset;
CORE_EXIT_ATOMIC();
return offset;
}
/***************************************************************************//**
* Converts Unix 32 timestamp into NTP timestamp.
******************************************************************************/
sl_status_t sl_sleeptimer_convert_unix_time_to_ntp(sl_sleeptimer_timestamp_t time,
uint32_t *ntp_time)
{
if (time > TIME_UNIX_TO_NTP_MAX) {
// Maximum Unix timestamp that can be converted to NTP is 2085978495
return SL_STATUS_INVALID_PARAMETER;
}
uint32_t temp_ntp_time;
temp_ntp_time = time + TIME_NTP_EPOCH_OFFSET_SEC;
if (!is_valid_time(temp_ntp_time, TIME_FORMAT_NTP, 0u)) {
return SL_STATUS_INVALID_PARAMETER;
} else {
*ntp_time = temp_ntp_time;
return SL_STATUS_OK;
}
}
/***************************************************************************//**
* Converts NTP timestamp into Unix timestamp.
******************************************************************************/
sl_status_t sl_sleeptimer_convert_ntp_time_to_unix(uint32_t ntp_time,
sl_sleeptimer_timestamp_t *time)
{
uint32_t temp_time;
temp_time = ntp_time - TIME_NTP_EPOCH_OFFSET_SEC;
if (!is_valid_time(temp_time, TIME_FORMAT_UNIX, 0u)) {
return SL_STATUS_INVALID_PARAMETER;
} else {
*time = temp_time;
return SL_STATUS_OK;
}
}
/***************************************************************************//**
* Converts Unix timestamp into Zigbee timestamp.
******************************************************************************/
sl_status_t sl_sleeptimer_convert_unix_time_to_zigbee(sl_sleeptimer_timestamp_t time,
uint32_t *zigbee_time)
{
uint32_t temp_zigbee_time;
temp_zigbee_time = time - TIME_ZIGBEE_EPOCH_OFFSET_SEC;
if (!is_valid_time(temp_zigbee_time, TIME_FORMAT_ZIGBEE_CLUSTER, 0u)) {
return SL_STATUS_INVALID_PARAMETER;
} else {
*zigbee_time = temp_zigbee_time;
return SL_STATUS_OK;
}
}
/***************************************************************************//**
* Converts Zigbee timestamp into Unix timestamp.
******************************************************************************/
sl_status_t sl_sleeptimer_convert_zigbee_time_to_unix(uint32_t zigbee_time,
sl_sleeptimer_timestamp_t *time)
{
uint32_t temp_time;
temp_time = zigbee_time + TIME_ZIGBEE_EPOCH_OFFSET_SEC;
if (!is_valid_time(temp_time, TIME_FORMAT_UNIX, 0u)) {
return SL_STATUS_INVALID_PARAMETER;
} else {
*time = temp_time;
return SL_STATUS_OK;
}
}
#endif // SL_SLEEPTIMER_WALLCLOCK_CONFIG
/*******************************************************************************
* Active delay of 'time_ms' milliseconds.
******************************************************************************/
void sl_sleeptimer_delay_millisecond(uint16_t time_ms)
{
volatile bool wait = true;
sl_status_t error_code;
sl_sleeptimer_timer_handle_t delay_timer;
uint32_t delay = sl_sleeptimer_ms_to_tick(time_ms);
error_code = sl_sleeptimer_start_timer(&delay_timer,
delay,
delay_callback,
(void *)&wait,
0,
0);
if (error_code == SL_STATUS_OK) {
while (wait) { // Active delay loop.
}
}
}
/*******************************************************************************
* Converts milliseconds in ticks.
******************************************************************************/
uint32_t sl_sleeptimer_ms_to_tick(uint16_t time_ms)
{
return (uint32_t)((((uint64_t)time_ms * timer_frequency) + 999) / 1000);
}
/*******************************************************************************
* Converts 32-bits milliseconds in ticks.
******************************************************************************/
sl_status_t sl_sleeptimer_ms32_to_tick(uint32_t time_ms,
uint32_t *tick)
{
if (time_ms <= max_millisecond_conversion) {
*tick = (uint32_t)((((uint64_t)time_ms * timer_frequency) + 999) / 1000u);
return SL_STATUS_OK;
} else {
return SL_STATUS_INVALID_PARAMETER;
}
}
/***************************************************************************//**
* Gets the maximum value that can be passed to the functions that have a
* 32-bits time or timeout argument expressed in milliseconds.
******************************************************************************/
uint32_t sl_sleeptimer_get_max_ms32_conversion(void)
{
return max_millisecond_conversion;
}
/*******************************************************************************
* Converts ticks in milliseconds.
******************************************************************************/
uint32_t sl_sleeptimer_tick_to_ms(uint32_t tick)
{
uint32_t time_ms;
time_ms = 0;
if (timer_frequency != 0u) {
if (is_power_of_2(timer_frequency)) {
time_ms = (uint32_t)(((uint64_t)tick * (uint64_t)1000u) >> div_to_log2(timer_frequency));
} else {
time_ms = (uint32_t)(((uint64_t)tick * (uint64_t)1000u) / timer_frequency);
}
}
return time_ms;
}
/*******************************************************************************
* Converts 64-bits ticks in milliseconds.
******************************************************************************/
sl_status_t sl_sleeptimer_tick64_to_ms(uint64_t tick,
uint64_t *ms)
{
if ((tick <= UINT64_MAX / 1000)
&& (timer_frequency != 0u)) {
if (is_power_of_2(timer_frequency)) {
*ms = (tick * (uint64_t)1000u) >> div_to_log2(timer_frequency);
return SL_STATUS_OK;
} else {
*ms = (tick * (uint64_t)1000u) / timer_frequency;
return SL_STATUS_OK;
}
} else {
return SL_STATUS_INVALID_PARAMETER;
}
}
/*******************************************************************************
* Process timer interrupt.
*
* @param local_flag Flag indicating the type of timer interrupt.
******************************************************************************/
void process_timer_irq(uint8_t local_flag)
{
CORE_DECLARE_IRQ_STATE;
if (local_flag & SLEEPTIMER_EVENT_OF) {
#if SL_SLEEPTIMER_WALLCLOCK_CONFIG
uint32_t timer_freq = sl_sleeptimer_get_timer_frequency();
overflow_tick_rest += calculated_tick_rest;
if (overflow_tick_rest >= timer_freq) {
second_count++;
overflow_tick_rest -= timer_freq;
}
second_count = second_count + calculated_sec_count;
#endif
overflow_counter++;
update_delta_list();
set_comparator_for_next_timer();
}
if (local_flag & SLEEPTIMER_EVENT_COMP) {
sl_sleeptimer_timer_handle_t *current = NULL;
uint32_t nb_timer_expire = 0u;
uint16_t option_flags = 0;
CORE_ENTER_ATOMIC();
// Make sure the timers list is up to date with the time elapsed since the last update
update_delta_list();
// Process all timers that have expired.
while (timer_head && (timer_head->delta == 0)) {
sl_sleeptimer_timer_handle_t *temp = timer_head;
current = timer_head;
// Process timers with higher priority first
while ((temp != NULL) && (temp->delta == 0)) {
if (current->priority > temp->priority) {
current = temp;
}
temp = temp->next;
}
CORE_EXIT_ATOMIC();
process_expired_timer(current);
// Save current option flag and the number of timers that expired.
option_flags = current->option_flags;
nb_timer_expire++;
CORE_ENTER_ATOMIC();
// Re-update the list to account for delays during timer's callback.
update_delta_list();
}
// If the only timer expired is the internal Power Manager one,
// from the Sleeptimer perspective, the system can go back to sleep after the ISR handling.
sleep_on_isr_exit = false;
if ((nb_timer_expire == 1u) && (option_flags & SLI_SLEEPTIMER_POWER_MANAGER_EARLY_WAKEUP_TIMER_FLAG)) {
sleep_on_isr_exit = true;
}
sl_status_t error = set_comparator_for_next_timer();
if (error == SL_STATUS_NULL_POINTER) {
sleeptimer_hal_disable_int(SLEEPTIMER_EVENT_COMP);
}
CORE_EXIT_ATOMIC();
}
}
/*******************************************************************************
* Timer expiration callback for the delay function.
*
* @param handle Pointer to handle to timer.
* @param data Pointer to delay flag.
******************************************************************************/
static void delay_callback(sl_sleeptimer_timer_handle_t *handle,
void *data)
{
volatile bool *wait_flag = (bool *)data;
(void)handle; // Unused parameter.
*wait_flag = false;
}
/*******************************************************************************
* Inserts a timer in the delta list.
*
* @param handle Pointer to handle to timer.
* @param timeout Timer timeout, in ticks.
******************************************************************************/
static void delta_list_insert_timer(sl_sleeptimer_timer_handle_t *handle,
sl_sleeptimer_tick_count_t timeout)
{
sl_sleeptimer_tick_count_t local_handle_delta = timeout;
#ifdef SL_CATALOG_POWER_MANAGER_PRESENT
// If Power Manager is present, it's possible that a clock restore is needed right away
// if we are in the context of a deepsleep and the timeout value is smaller than the restore time.
// If it's the case, the restore will be started and the timeout value will be updated to match
// the restore delay.
if (handle->option_flags == 0) {
uint32_t wakeup_delay = sli_power_manager_get_restore_delay();
if (local_handle_delta < wakeup_delay) {
local_handle_delta = wakeup_delay;
sli_power_manager_initiate_restore();
}
}
#endif
handle->delta = local_handle_delta;
if (timer_head != NULL) {
sl_sleeptimer_timer_handle_t *prev = NULL;
sl_sleeptimer_timer_handle_t *current = timer_head;
// Find timer position taking into accounts the deltas and priority.
while (current != NULL
&& (local_handle_delta >= current->delta || current->delta == 0u
|| (((local_handle_delta - current->delta) == 0) && (handle->priority > current->priority)))) {
local_handle_delta -= current->delta;
handle->delta = local_handle_delta;
prev = current;
current = current->next;
}
// Insert timer in middle of delta list.
if (prev != NULL) {
prev->next = handle;
} else {
timer_head = handle;
}
handle->next = current;
if (current != NULL) {
current->delta -= local_handle_delta;
}
} else {
timer_head = handle;
handle->next = NULL;
}
}
/*******************************************************************************
* Removes a timer from delta list.
*
* @param handle Pointer to handle to timer.
*
* @return 0 if successful. Error code otherwise.
******************************************************************************/
static sl_status_t delta_list_remove_timer(sl_sleeptimer_timer_handle_t *handle)
{
sl_sleeptimer_timer_handle_t *prev = NULL;
sl_sleeptimer_timer_handle_t *current = timer_head;
if (handle == NULL) {
return SL_STATUS_NULL_POINTER;
}
// Retrieve timer in delta list.
while (current != NULL && current != handle) {
prev = current;
current = current->next;
}
if (current != handle) {
return SL_STATUS_INVALID_STATE;
}
if (prev != NULL) {
prev->next = handle->next;
} else {
timer_head = handle->next;
}
// Update delta of next timer
if (handle->next != NULL) {
handle->next->delta += handle->delta;
}
return SL_STATUS_OK;
}
/*******************************************************************************
* Sets comparator for next timer.
******************************************************************************/
static sl_status_t set_comparator_for_next_timer(void)
{
if (timer_head) {
if (timer_head->delta > 0) {
sl_sleeptimer_tick_count_t compare_value;
compare_value = last_delta_update_count + timer_head->delta;
sleeptimer_hal_enable_int(SLEEPTIMER_EVENT_COMP);
sleeptimer_hal_set_compare(compare_value);
} else {
// In case timer has already expire, don't attempt to set comparator. Just
// trigger compare match interrupt.
sleeptimer_hal_enable_int(SLEEPTIMER_EVENT_COMP);
sleeptimer_hal_set_int(SLEEPTIMER_EVENT_COMP);
}
update_next_timer_to_expire_is_power_manager();
return SL_STATUS_OK;
}
return SL_STATUS_NULL_POINTER;
}
/*******************************************************************************
* Updates timer list's deltas.
******************************************************************************/
static void update_delta_list(void)
{
sl_sleeptimer_tick_count_t current_cnt = sleeptimer_hal_get_counter();
sl_sleeptimer_timer_handle_t *timer_handle = timer_head;
sl_sleeptimer_tick_count_t time_diff = current_cnt - last_delta_update_count;
// Go through the delta timer list and update every necessary deltas
// according to the time elapsed since the last update.
while (timer_handle != NULL && time_diff > 0) {
if (timer_handle->delta >= time_diff) {
timer_handle->delta -= time_diff;
time_diff = 0;
} else {
time_diff -= timer_handle->delta;
timer_handle->delta = 0;
}
timer_handle = timer_handle->next;
}
last_delta_update_count = current_cnt;
}
/*******************************************************************************
* Creates and start a 32 bits timer.
*
* @param handle Pointer to handle to timer.
* @param timeout_initial Initial timeout, in timer ticks.
* @param timeout_periodic Periodic timeout, in timer ticks. This timeout
* applies once timeoutInitial expires. Can be set to 0 for a one
* shot timer.
* @param callback Callback function that will be called when
* initial/periodic timeout expires.
* @param callback_data Pointer to user data that will be passed to callback.
* @param priority Priority of callback. Useful in case multiple timer expire
* at the same time. 0 = highest priority.
*
* @return 0 if successful. Error code otherwise.
******************************************************************************/
static sl_status_t create_timer(sl_sleeptimer_timer_handle_t *handle,
sl_sleeptimer_tick_count_t timeout_initial,
sl_sleeptimer_tick_count_t timeout_periodic,
sl_sleeptimer_timer_callback_t callback,
void *callback_data,
uint8_t priority,
uint16_t option_flags)
{
CORE_DECLARE_IRQ_STATE;
handle->priority = priority;
handle->callback_data = callback_data;
handle->next = NULL;
handle->timeout_periodic = timeout_periodic;
handle->callback = callback;
handle->option_flags = option_flags;
if (timeout_periodic == 0) {
handle->timeout_expected_tc = sleeptimer_hal_get_counter() + timeout_initial;
} else {
handle->timeout_expected_tc = sleeptimer_hal_get_counter() + timeout_periodic;
}
if (timeout_initial == 0) {
handle->delta = 0;
if (handle->callback != NULL) {
handle->callback(handle, handle->callback_data);
}
if (timeout_periodic != 0) {
timeout_initial = timeout_periodic;
} else {
return SL_STATUS_OK;
}
}
#if ((SL_SLEEPTIMER_PERIPHERAL == SL_SLEEPTIMER_PERIPHERAL_SYSRTC) \
&& defined(SL_CATALOG_POWER_MANAGER_PRESENT) \
&& !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT))
if (option_flags == (SLI_SLEEPTIMER_POWER_MANAGER_EARLY_WAKEUP_TIMER_FLAG | SLI_SLEEPTIMER_POWER_MANAGER_HF_ACCURACY_CLK_FLAG)) {
HFXO0->CTRL |= HFXO_CTRL_EM23ONDEMAND;
sleeptimer_hal_set_compare_prs_hfxo_startup(timeout_initial);
return SL_STATUS_OK;
}
#endif
CORE_ENTER_CRITICAL();
update_delta_list();
delta_list_insert_timer(handle, timeout_initial);
// If first timer, update timer comparator.
if (timer_head == handle) {
set_comparator_for_next_timer();
}
CORE_EXIT_CRITICAL();
return SL_STATUS_OK;
}
/***************************************************************************//**
* Processes an expired timer.
*
* @param timer Expired timer to process.
******************************************************************************/
static void process_expired_timer(sl_sleeptimer_timer_handle_t *timer)
{
CORE_DECLARE_IRQ_STATE;
int32_t periodic_correction = 0u;
int64_t timeout_temp = 0;
bool skip_remove = false;
// Check if periodic timer was delayed more than its actual timeout value
// and keep it at the head of the timers list if it's the case so that the
// callback function can be called the number of required time.
if (timer->timeout_periodic != 0u) {
timeout_temp = timer->timeout_periodic;
periodic_correction = sleeptimer_hal_get_counter() - timer->timeout_expected_tc;
if (periodic_correction >= timeout_temp) {
skip_remove = true;
timer->timeout_expected_tc += timer->timeout_periodic;
}
}
// Remove timer from list except if the timer is a periodic timer that was
// intentionally kept at the head of the timers list.
if (skip_remove != true) {
CORE_ENTER_ATOMIC();
delta_list_remove_timer(timer);
CORE_EXIT_ATOMIC();
}
// Re-insert periodic timer that was previsouly removed from the list
// and compensate for any deviation from the periodic timer frequency.
if (timer->timeout_periodic != 0u && skip_remove != true) {
timeout_temp -= periodic_correction;
EFM_ASSERT(timeout_temp > 0);
// Compensate for drift caused by ms to ticks conversion
if (timer->conversion_error > 0) {
// Increment accumulated error by the ms to ticks conversion error
timer->accumulated_error += timer->conversion_error;
// If the accumulated error exceeds a tick, subtract that tick from the next
// periodic timer's timeout value.
if (timer->accumulated_error >= 1000) {
timer->accumulated_error -= 1000;
timeout_temp -= 1;
timer->timeout_expected_tc -= 1;
}
}
CORE_ENTER_ATOMIC();
delta_list_insert_timer(timer, (sl_sleeptimer_tick_count_t)timeout_temp);
timer->timeout_expected_tc += timer->timeout_periodic;
CORE_EXIT_ATOMIC();
}
// Call timer callback function if any.
if (timer->callback != NULL) {
timer->callback(timer, timer->callback_data);
}
}
/*******************************************************************************
* Updates internal flag that indicates if next timer to expire is the power
* manager's one.
******************************************************************************/
static void update_next_timer_to_expire_is_power_manager(void)
{
sl_sleeptimer_timer_handle_t *current = timer_head;
uint32_t delta_diff_with_first = 0;
next_timer_to_expire_is_power_manager = false;
while ((delta_diff_with_first <= 1) && (current != NULL)) {
if (current->option_flags & SLI_SLEEPTIMER_POWER_MANAGER_EARLY_WAKEUP_TIMER_FLAG) {
next_timer_to_expire_is_power_manager = true;
break;
}
current = current->next;
if (current != NULL) {
delta_diff_with_first += current->delta;
}
}
}
/**************************************************************************//**
* Determines if the power manager's early wakeup expired during the last ISR
* and it was the only timer to expire in that period.
*
* @return true if power manager sleep can return to sleep,
* false otherwise.
*****************************************************************************/
bool sl_sleeptimer_is_power_manager_early_restore_timer_latest_to_expire(void)
{
CORE_DECLARE_IRQ_STATE;
bool sleep;
CORE_ENTER_ATOMIC();
sleep = sleep_on_isr_exit;
CORE_EXIT_ATOMIC();
return sleep;
}
/*******************************************************************************
* Convert dividend to logarithmic value. It only works for even
* numbers equal to 2^n.
*
* @param div An unscaled dividend.
*
* @return Logarithm of 2.
******************************************************************************/
__STATIC_INLINE uint32_t div_to_log2(uint32_t div)
{
return 31UL - __CLZ(div); // Count leading zeroes and "reverse" result.
}
/*******************************************************************************
* Determines if a number is a power of two.
*
* @param nbr Input value.
*
* @return True if the number is a power of two.
******************************************************************************/
__STATIC_INLINE bool is_power_of_2(uint32_t nbr)
{
if ((nbr != 0u) && ((nbr & (nbr - 1u)) == 0u)) {
return true;
} else {
return false;
}
}
#if SL_SLEEPTIMER_WALLCLOCK_CONFIG
/*******************************************************************************
* Compute the day of the week.
*
* @param day Days since January 1st of 1970.
*
* @return the day of the week.
******************************************************************************/
static sl_sleeptimer_weekDay_t compute_day_of_week(uint32_t day)
{
return (sl_sleeptimer_weekDay_t)((day + 4) % 7); // January 1st was a Thursday(4) in 1970
}
/*******************************************************************************
* Compute the day of the week.
*
* @param day Days since January 1st of 1900.
*
* @return the day of the week.
******************************************************************************/
static sl_sleeptimer_weekDay_t compute_day_of_week_64(uint64_t day)
{
return (sl_sleeptimer_weekDay_t)((day + 1) % 7); // January 1st was a Monday(1) in 1900
}
/*******************************************************************************
* Compute the day of the year. This function assumes that the inputs are properly
* sanitized.
*
* @param month Number of months since January.
* @param day Day of the month
* @param is_leap_year Specifies if the year computed against is a leap year.
*
* @return the number of days since the beginning of the year
******************************************************************************/
static uint16_t compute_day_of_year(sl_sleeptimer_month_t month, uint8_t day, bool is_leap_year)
{
uint8_t i;
uint16_t dayOfYear = 0;
for (i = 0; i < month; ++i) {
dayOfYear += days_in_month[is_leap_year][i];
}
dayOfYear += day;
return dayOfYear;
}
/*******************************************************************************
* Checks if the year is a leap year.
*
* @param year Year to check.
*
* @return true if the year is a leap year. False otherwise.
******************************************************************************/
static bool is_leap_year(uint16_t year)
{
// 1900 is not a leap year but 0 % anything is 0.
if (year == 0) {
return false;
}
bool leap_year;
leap_year = (((year % 4u) == 0u)
&& (((year % 100u) != 0u) || ((year % 400u) == 0u))) ? true : false;
return (leap_year);
}
/*******************************************************************************
* Checks if the time stamp, format and time zone are
* within the supported range.
*
* @param base_year Year to start from to compute leap days.
* @param current_year Year end at for computing leap days.
*
* @return leap_days Days number of leap days between base_year and current_year.
******************************************************************************/
static uint16_t number_of_leap_days(uint32_t base_year, uint32_t current_year)
{
// Regular leap years
uint16_t lo_reg = (base_year - 0) / 4;
uint16_t hi_reg = (current_year - 1) / 4;
uint16_t leap_days = hi_reg - lo_reg;
// Account for non leap years
uint16_t lo_century = (base_year - 0) / 100;
uint16_t hi_century = (current_year - 1) / 100;
leap_days -= hi_century - lo_century;
// Account for quad century leap years
uint16_t lo_quad = (base_year - 0) / 400;
uint16_t hi_quad = (current_year - 1) / 400;
leap_days += hi_quad - lo_quad;
return (leap_days);
}
/*******************************************************************************
* Checks if the time stamp, format and time zone are
* within the supported range.
*
* @param time Time stamp to check.
* @param format Format of the time.
* @param time_zone Time zone offset in second.
*
* @return true if the time is valid. False otherwise.
******************************************************************************/
static bool is_valid_time(sl_sleeptimer_timestamp_t time,
sl_sleeptimer_time_format_t format,
sl_sleeptimer_time_zone_offset_t time_zone)
{
bool valid_time = false;
// Check for overflow.
if ((time_zone < 0 && time > (uint32_t)abs(time_zone)) \
|| (time_zone >= 0 && (time <= UINT32_MAX - time_zone))) {
valid_time = true;
}
if (format == TIME_FORMAT_UNIX) {
if (time > TIME_UNIX_TIMESTAMP_MAX) { // Check if Unix time stamp is an unsigned 31 bits.
valid_time = false;
}
} else {
if ((format == TIME_FORMAT_NTP) && (time >= TIME_NTP_EPOCH_OFFSET_SEC)) {
valid_time &= true;
} else if ((format == TIME_FORMAT_ZIGBEE_CLUSTER) && (time <= TIME_UNIX_TIMESTAMP_MAX - TIME_ZIGBEE_EPOCH_OFFSET_SEC)) {
valid_time &= true;
} else {
valid_time = false;
}
}
return valid_time;
}
/*******************************************************************************
* Checks if the time stamp, format and time zone are
* within the supported range.
*
* @param time Time stamp to check.
* @param format Format of the time.
* @param time_zone Time zone offset in second.
*
* @return true if the time is valid. False otherwise.
******************************************************************************/
static bool is_valid_time_64(sl_sleeptimer_timestamp_64_t time,
sl_sleeptimer_time_format_t format,
sl_sleeptimer_time_zone_offset_t time_zone)
{
bool valid_time = false;
// Check for overflow.
if ((time_zone < 0 && time > (uint64_t)abs(time_zone))
|| (time_zone >= 0 && (time <= UINT64_MAX - time_zone))) {
valid_time = true;
}
if (format == TIME_FORMAT_UNIX_64_BIT) {
if (time > TIME_64_BIT_UNIX_TIMESTAMP_MAX) { // Check if time stamp is an unsigned 64 bits.
valid_time = false;
}
}
return valid_time;
}
/*******************************************************************************
* Checks if the date is valid.
*
* @param date Date to check.
*
* @return true if the date is valid. False otherwise.
******************************************************************************/
static bool is_valid_date(sl_sleeptimer_date_t *date)
{
if ((date == NULL)
|| (date->year > TIME_UNIX_YEAR_MAX)
|| (date->month > MONTH_DECEMBER)
|| (date->month_day == 0 || date->month_day > days_in_month[is_leap_year(date->year)][date->month])
|| (date->hour > 23)
|| (date->min > 59)
|| (date->sec > 59)) {
return false;
}
// Unix is valid until the 19th of January 2038 at 03:14:07
if (date->year == TIME_UNIX_YEAR_MAX) {
if ((uint8_t)date->month > (uint8_t)MONTH_JANUARY) {
return false;
} else if (date->month_day > 19) {
return false;
} else if (date->hour > 3) {
return false;
} else if (date->min > 14) {
return false;
} else if (date->sec > 7) {
return false;
}
}
return true;
}
/*******************************************************************************
* Checks if the date is valid.
*
* @param date Date to check.
*
* @return true if the date is valid. False otherwise.
******************************************************************************/
static bool is_valid_date_64(sl_sleeptimer_date_t *date)
{
if ((date == NULL)
|| (date->year > TIME_64_BIT_YEAR_MAX)
|| (date->month > MONTH_DECEMBER)
|| (date->month_day == 0 || date->month_day > days_in_month[is_leap_year(date->year)][date->month])
|| (date->hour > 23)
|| (date->min > 59)
|| (date->sec > 59)) {
return false;
}
return true;
}
#endif
/*******************************************************************************
* @brief
* Gets the precision (in PPM) of the sleeptimer's clock.
*
* @return
* Clock accuracy, in PPM.
*
******************************************************************************/
uint16_t sl_sleeptimer_get_clock_accuracy(void)
{
return sleeptimer_hal_get_clock_accuracy();
}
/***************************************************************************//**
* @brief
* Update sleep_on_isr_exit flag.
*
* @param flag Value update_sleep_on_isr_exit will be set to.
******************************************************************************/
void sli_sleeptimer_update_sleep_on_isr_exit(bool flag)
{
sleep_on_isr_exit = flag;
}
/*******************************************************************************
* Gets the associated peripheral capture channel current value.
******************************************************************************/
uint32_t sli_sleeptimer_get_capture(void)
{
return sleeptimer_hal_get_capture();
}
/*******************************************************************************
* Resets the PRS signal triggered by the associated peripheral.
******************************************************************************/
void sli_sleeptimer_reset_prs_signal(void)
{
sleeptimer_hal_reset_prs_signal();
}