/***************************************************************************//** * @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(); }