diff options
Diffstat (limited to 'utility/low_power/tx_low_power.c')
| -rw-r--r-- | utility/low_power/tx_low_power.c | 550 |
1 files changed, 550 insertions, 0 deletions
diff --git a/utility/low_power/tx_low_power.c b/utility/low_power/tx_low_power.c new file mode 100644 index 00000000..18f2d08b --- /dev/null +++ b/utility/low_power/tx_low_power.c @@ -0,0 +1,550 @@ +/**************************************************************************/ +/* */ +/* Copyright (c) Microsoft Corporation. All rights reserved. */ +/* */ +/* This software is licensed under the Microsoft Software License */ +/* Terms for Microsoft Azure RTOS. Full text of the license can be */ +/* found in the LICENSE file at https://aka.ms/AzureRTOS_EULA */ +/* and in the root directory of this software. */ +/* */ +/**************************************************************************/ + + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** ThreadX Component */ +/** */ +/** Low Power Timer Management */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + +#define TX_SOURCE_CODE + + +/* Include necessary system files. */ + +#include "tx_api.h" +#include "tx_timer.h" +#include "tx_low_power.h" + + +/* Define low power global variables. */ + +/* Flag to determine if we've entered low power mode or not. */ +UINT tx_low_power_entered; + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* tx_low_power_enter PORTABLE C */ +/* 6.1.5 */ +/* AUTHOR */ +/* */ +/* William E. Lamie, Microsoft Corporation */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function is the low power entry function. This function is */ +/* assumed to be called from the idle loop of tx_thread_schedule. It */ +/* is important to note that if an interrupt managed by ThreadX occurs */ +/* anywhere where interrupts are enabled in this function, the entire */ +/* processing of this function is discarded and the function won't be */ +/* re-entered until the idle loop in tx_thread_schedule is executed */ +/* again. */ +/* */ +/* INPUT */ +/* */ +/* None */ +/* */ +/* OUTPUT */ +/* */ +/* None */ +/* */ +/* CALLS */ +/* */ +/* tx_timer_get_next Get next timer expiration */ +/* */ +/* CALLED BY */ +/* */ +/* _tx_thread_schedule Thread scheduling loop */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 03-02-2021 William E. Lamie Initial Version 6.1.5 */ +/* */ +/**************************************************************************/ +VOID tx_low_power_enter(VOID) +{ + +TX_INTERRUPT_SAVE_AREA +ULONG tx_low_power_next_expiration; /* The next timer experation (units of ThreadX timer ticks). */ +ULONG timers_active; + + + /* Disable interrupts while we prepare for low power mode. */ + TX_DISABLE + + /* TX_LOW_POWER_TIMER_SETUP is a macro to a routine that sets up a low power + clock. If such routine does not exist, we can skip the logic that computes + the next expiration time. */ +#ifdef TX_LOW_POWER_TIMER_SETUP + + /* At this point, we want to enter low power mode, since nothing + meaningful is going on in the system. However, in order to keep + the ThreadX timer services accurate, we must first determine the + next ThreadX timer expiration in terms of ticks. This is + accomplished via the tx_timer_get_next API. */ + timers_active = tx_timer_get_next(&tx_low_power_next_expiration); + + /* There are two possibilities: + 1: A ThreadX timer is active. tx_timer_get_next returns TX_TRUE. + Program the hardware timer source such that the next timer + interrupt is equal to: tx_low_power_next_expiration*tick_frequency. + In most applications, the tick_frequency is 10ms, but this is + completely application specific in ThreadX, typically set up + in tx_low_level_initialize. Note that in this situation, a low + power clock must be used in order to wake up the CPU for the next timeout + event. Therefore an alternative clock must be programmed. + 2: There are no ThreadX timers active. tx_timer_get_next returns TX_FALSE. + 2.a: application may choose not to keep the ThreadX internal + tick count updated (define TX_LOW_POWER_TICKLESS), therefore no need + to set up a low power clock. + 2.b: Application still needs to keep ThreadX tick up-to-date. In this case + a low power clock needs to be set up. + */ + +#ifndef TX_LOW_POWER_TICKLESS + /* We still want to keep track of time in low power mode. */ + if (timers_active == TX_FALSE) + { + /* Set the next expiration to 0xFFFFFFF, an indication that the timer sleeps for + maximum amount of time the HW supports.*/ + tx_low_power_next_expiration = 0xFFFFFFFF; + timers_active = TX_TRUE; + } +#endif /* TX_LOW_POWER_TICKLESS */ + + if (timers_active == TX_TRUE) + { + /* A ThreadX timer is active or we simply want to keep track of time. */ + TX_LOW_POWER_TIMER_SETUP(tx_low_power_next_expiration); + } +#endif /* TX_LOW_POWER_TIMER_SETUP */ + + + /* Set the flag indicating that low power has been entered. This + flag is checked in tx_low_power_exit to determine if the logic + used to adjust the ThreadX time is required. */ + tx_low_power_entered = TX_TRUE; + + /* Re-enable interrupts before low power mode is entered. */ + TX_RESTORE + + /* User code to enter low power mode. This allows the application to power down + peripherals and put the processor in sleep mode. + */ +#ifdef TX_LOW_POWER_USER_ENTER + TX_LOW_POWER_USER_ENTER; +#endif + + /* If the low power code returns, this routine returns to the tx_thread_schedule loop. */ +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* tx_low_power_exit PORTABLE C */ +/* 6.1.5 */ +/* AUTHOR */ +/* */ +/* William E. Lamie, Microsoft Corporation */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function is the low power exit function. This function must */ +/* be called from any interrupt that can wakeup the processor from */ +/* low power mode. If nothing needs to be done, this function simply */ +/* returns. */ +/* */ +/* INPUT */ +/* */ +/* None */ +/* */ +/* OUTPUT */ +/* */ +/* None */ +/* */ +/* CALLS */ +/* */ +/* tx_time_increment Update the ThreadX timer */ +/* */ +/* CALLED BY */ +/* */ +/* ISRs Front-end of Interrupt */ +/* Service Routines */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 03-02-2021 William E. Lamie Initial Version 6.1.5 */ +/* */ +/**************************************************************************/ +VOID tx_low_power_exit(VOID) +{ + +/* How many ticks to adjust ThreadX timers after exiting low power mode. */ +ULONG tx_low_power_adjust_ticks; + + + /* Determine if the interrupt occurred in low power mode. */ + if (tx_low_power_entered) + { + /* Yes, low power mode was interrupted. */ + + /* Clear the low power entered flag. */ + tx_low_power_entered = TX_FALSE; + + /* User code to exit low power mode and reprogram the + timer to the desired interrupt frequency. */ +#ifdef TX_LOW_POWER_USER_EXIT + TX_LOW_POWER_USER_EXIT; +#endif + +#ifdef TX_LOW_POWER_USER_TIMER_ADJUST + /* Call the user's low-power timer code to obtain the amount of time (in ticks) + the system has been in low power mode. */ + tx_low_power_adjust_ticks = TX_LOW_POWER_USER_TIMER_ADJUST; +#else + tx_low_power_adjust_ticks = (ULONG) 0; +#endif + + /* Determine if the ThreadX timer(s) needs incrementing. */ + if (tx_low_power_adjust_ticks) + { + /* Yes, the ThreadX timer(s) must be incremented. */ + tx_time_increment(tx_low_power_adjust_ticks); + } + } +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* tx_timer_get_next PORTABLE C */ +/* 6.1.5 */ +/* AUTHOR */ +/* */ +/* William E. Lamie, Microsoft Corporation */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function calculates the next expiration time minus 1 tick for */ +/* the currently active ThreadX timers. If no timer is active, this */ +/* routine will return a value of TX_FALSE and the next ticks value */ +/* will be set to zero. */ +/* */ +/* INPUT */ +/* */ +/* next_timer_tick_ptr Pointer to destination for next */ +/* timer expiration value */ +/* */ +/* OUTPUT */ +/* */ +/* TX_TRUE (1) At least one timer is active */ +/* TX_FALSE (0) No timers are currently active */ +/* */ +/* CALLS */ +/* */ +/* None */ +/* */ +/* CALLED BY */ +/* */ +/* tx_low_power_enter */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 03-02-2021 William E. Lamie Initial Version 6.1.5 */ +/* */ +/**************************************************************************/ +ULONG tx_timer_get_next(ULONG *next_timer_tick_ptr) +{ + +TX_INTERRUPT_SAVE_AREA + +TX_TIMER_INTERNAL **timer_list_head; +TX_TIMER_INTERNAL *next_timer; +UINT i; +ULONG calculated_time; +ULONG expiration_time = (ULONG) 0xFFFFFFFF; + + + /* Disable interrupts. */ + TX_DISABLE + + /* Look at the next timer entry. */ + timer_list_head = _tx_timer_current_ptr; + + /* Loop through the timer list, looking for the first non-NULL + value to signal an active timer. */ + for (i = (UINT)0; i < TX_TIMER_ENTRIES; i++) + { + /* Now determine if there is an active timer in this slot. */ + if (*timer_list_head) + { + /* Setup the pointer to the expiration list. */ + next_timer = *timer_list_head; + + /* Loop through the timers active for this relative time slot (determined by i). */ + do + { + /* Determine if the remaining time is larger than the list. */ + if (next_timer -> tx_timer_internal_remaining_ticks > TX_TIMER_ENTRIES) + { + /* Calculate the expiration time. */ + calculated_time = next_timer -> tx_timer_internal_remaining_ticks - (TX_TIMER_ENTRIES - i); + } + else + { + /* Calculate the expiration time, which is simply the number of entries in this case. */ + calculated_time = i; + } + + /* Determine if a new minimum expiration time is present. */ + if (expiration_time > calculated_time) + { + /* Yes, a new minimum expiration time is present - remember it! */ + expiration_time = calculated_time; + } + + /* Move to the next entry in the timer list. */ + next_timer = next_timer -> tx_timer_internal_active_next; + + } while (next_timer != *timer_list_head); + } + + /* This timer entry is NULL, so just move to the next one. */ + timer_list_head++; + + /* Check for timer list wrap condition. */ + if (timer_list_head >= _tx_timer_list_end) + { + /* Wrap to the beginning of the list. */ + timer_list_head = _tx_timer_list_start; + } + } + + /* Restore interrupts. */ + TX_RESTORE + + /* Determine if an active timer was found. */ + if (expiration_time != (ULONG) 0xFFFFFFFF) + { + /* Yes, an active timer was found. */ + *next_timer_tick_ptr = expiration_time; + return(TX_TRUE); + } + else + { + /* No active timer was found. */ + *next_timer_tick_ptr = 0; + return(TX_FALSE); + } +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* tx_time_increment PORTABLE C */ +/* 6.1.5 */ +/* AUTHOR */ +/* */ +/* William E. Lamie, Microsoft Corporation */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function increments the current time by a specified value. */ +/* The value was derived by the application by calling the */ +/* tx_timer_get_next function prior to this call, which was right */ +/* before the processor was put in sleep mode. */ +/* */ +/* INPUT */ +/* */ +/* time_increment The amount of time to catch up on */ +/* */ +/* OUTPUT */ +/* */ +/* None */ +/* */ +/* CALLS */ +/* */ +/* _tx_timer_system_activate Timer activate service */ +/* */ +/* CALLED BY */ +/* */ +/* tx_low_power_exit */ +/* */ +/* RELEASE HISTORY */ +/* */ +/* DATE NAME DESCRIPTION */ +/* */ +/* 03-02-2021 William E. Lamie Initial Version 6.1.5 */ +/* */ +/**************************************************************************/ +VOID tx_time_increment(ULONG time_increment) +{ + +TX_INTERRUPT_SAVE_AREA +UINT i; +TX_TIMER_INTERNAL **timer_list_head; +TX_TIMER_INTERNAL *next_timer; +TX_TIMER_INTERNAL *temp_list_head; + + + /* Determine if there is any time increment. */ + if (time_increment == 0) + { + /* Nothing to do, just return. */ + return; + } + + /* Disable interrupts. */ + TX_DISABLE + + /* Adjust the system clock. */ + _tx_timer_system_clock = _tx_timer_system_clock + time_increment; + + /* Adjust the time slice variable. */ + if (_tx_timer_time_slice) + { + /* Decrement the time-slice variable. */ + if (_tx_timer_time_slice > time_increment) + { + _tx_timer_time_slice = _tx_timer_time_slice - time_increment; + } + else + { + _tx_timer_time_slice = 1; + } + } + + /* Calculate the proper place to position the timer. */ + timer_list_head = _tx_timer_current_ptr; + + /* Setup the temporary list pointer. */ + temp_list_head = TX_NULL; + + /* Loop to pull all timers off the timer structure and put on the temporary list head. */ + for (i = 0; i < TX_TIMER_ENTRIES; i++) + { + /* Determine if there is a timer list in this entry. */ + if (*timer_list_head) + { + /* Walk the list and update all the relative times to actual times. */ + + /* Setup the pointer to the expiration list. */ + next_timer = *timer_list_head; + + /* Loop through the timers active for this relative time slot (determined by i). */ + do + { + /* Determine if the remaining time is larger than the list. */ + if (next_timer -> tx_timer_internal_remaining_ticks > TX_TIMER_ENTRIES) + { + /* Calculate the actual expiration time. */ + next_timer -> tx_timer_internal_remaining_ticks = + next_timer -> tx_timer_internal_remaining_ticks - (TX_TIMER_ENTRIES - i) + 1; + } + else + { + /* Calculate the expiration time, which is simply the number of entries in this case. */ + next_timer -> tx_timer_internal_remaining_ticks = i + 1; + } + + /* Move to the next entry in the timer list. */ + next_timer = next_timer -> tx_timer_internal_active_next; + + } while (next_timer != *timer_list_head); + + /* NULL terminate the current timer list. */ + ((*timer_list_head) -> tx_timer_internal_active_previous) -> tx_timer_internal_active_next = TX_NULL; + + /* Yes, determine if the temporary list is NULL. */ + if (temp_list_head == TX_NULL) + { + /* First item on the list. Move the entire linked list. */ + temp_list_head = *timer_list_head; + } + else + { + /* No, the temp list already has timers on it. Link the next timer list to the end. */ + (temp_list_head -> tx_timer_internal_active_previous) -> tx_timer_internal_active_next = *timer_list_head; + + /* Now update the previous to the new list's previous timer pointer. */ + temp_list_head -> tx_timer_internal_active_previous = (*timer_list_head) -> tx_timer_internal_active_previous; + } + + /* Now clear the current timer head pointer. */ + *timer_list_head = TX_NULL; + } + + /* Move to next timer entry. */ + timer_list_head++; + + /* Determine if a wrap around condition has occurred. */ + if (timer_list_head >= _tx_timer_list_end) + { + /* Wrap from the beginning of the list. */ + timer_list_head = _tx_timer_list_start; + } + } + + /* Set the current timer pointer to the beginning of the list. */ + _tx_timer_current_ptr = _tx_timer_list_start; + + /* Loop to update and reinsert all the timers in the list. */ + while (temp_list_head) + { + /* Pickup the next timer to update and reinsert. */ + next_timer = temp_list_head; + + /* Move the temp list head pointer to the next pointer. */ + temp_list_head = next_timer -> tx_timer_internal_active_next; + + /* Determine if the remaining time is greater than the time increment + value - this is the normal case. */ + if (next_timer -> tx_timer_internal_remaining_ticks > time_increment) + { + /* Decrement the elapsed time. */ + next_timer -> tx_timer_internal_remaining_ticks = next_timer -> tx_timer_internal_remaining_ticks - time_increment; + } + else + { + /* Simply set the expiration value to expire on the next tick. */ + next_timer -> tx_timer_internal_remaining_ticks = 1; + } + + /* Now clear the timer list head pointer for the timer activate function to work properly. */ + next_timer -> tx_timer_internal_list_head = TX_NULL; + + /* Now re-insert the timer into the list. */ + _tx_timer_system_activate(next_timer); + } + + /* Restore interrupts. */ + TX_RESTORE +} |
