/* | |
* FreeRTOS Kernel V10.3.0 | |
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy of | |
* this software and associated documentation files (the "Software"), to deal in | |
* the Software without restriction, including without limitation the rights to | |
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | |
* the Software, and to permit persons to whom the Software is furnished to do so, | |
* subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | |
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | |
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
* | |
* http://www.FreeRTOS.org | |
* http://aws.amazon.com/freertos | |
* | |
* 1 tab == 4 spaces! | |
*/ | |
/* Standard includes. */ | |
#include "limits.h" | |
/* FreeRTOS includes. */ | |
#include "FreeRTOS.h" | |
#include "task.h" | |
/* Library includes. */ | |
#include <asf.h> | |
/* | |
* When configCREATE_LOW_POWER_DEMO is set to 1 then the tick interrupt | |
* is generated by the AST. The AST configuration and handling functions are | |
* defined in this file. | |
* | |
* When configCREATE_LOW_POWER_DEMO is set to 0 the tick interrupt is | |
* generated by the standard FreeRTOS Cortex-M port layer, which uses the | |
* SysTick timer. | |
*/ | |
#if configCREATE_LOW_POWER_DEMO == 1 | |
/* Constants required to pend a PendSV interrupt from the tick ISR if the | |
preemptive scheduler is being used. These are just standard bits and registers | |
within the Cortex-M core itself. */ | |
#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL ) | |
/* The alarm used to generate interrupts in the asynchronous timer. */ | |
#define portAST_ALARM_CHANNEL 0 | |
/*-----------------------------------------------------------*/ | |
/* | |
* The tick interrupt is generated by the asynchronous timer. The default tick | |
* interrupt handler cannot be used (even with the AST being handled from the | |
* tick hook function) because the default tick interrupt accesses the SysTick | |
* registers when configUSE_TICKLESS_IDLE set to 1. AST_ALARM_Handler() is the | |
* default name for the AST alarm interrupt. This definition overrides the | |
* default implementation that is weakly defined in the interrupt vector table | |
* file. | |
*/ | |
void AST_ALARM_Handler(void); | |
/* | |
* Functions that disable and enable the AST respectively, not returning until | |
* the operation is known to have taken effect. | |
*/ | |
static void prvDisableAST( void ); | |
static void prvEnableAST( void ); | |
/*-----------------------------------------------------------*/ | |
/* Calculate how many clock increments make up a single tick period. */ | |
static const uint32_t ulAlarmValueForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ); | |
/* Holds the maximum number of ticks that can be suppressed - which is | |
basically how far into the future an interrupt can be generated. Set | |
during initialisation. */ | |
static TickType_t xMaximumPossibleSuppressedTicks = 0; | |
/* Flag set from the tick interrupt to allow the sleep processing to know if | |
sleep mode was exited because of an AST interrupt or a different interrupt. */ | |
static volatile uint32_t ulTickFlag = pdFALSE; | |
/* The AST counter is stopped temporarily each time it is re-programmed. The | |
following variable offsets the AST counter alarm value by the number of AST | |
counts that would typically be missed while the counter was stopped to compensate | |
for the lost time. _RB_ Value needs calculating correctly. */ | |
static uint32_t ulStoppedTimerCompensation = 2 / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ ); | |
/*-----------------------------------------------------------*/ | |
/* The tick interrupt handler. This is always the same other than the part that | |
clears the interrupt, which is specific to the clock being used to generate the | |
tick. */ | |
void AST_ALARM_Handler(void) | |
{ | |
/* Protect incrementing the tick with an interrupt safe critical section. */ | |
( void ) portSET_INTERRUPT_MASK_FROM_ISR(); | |
{ | |
if( xTaskIncrementTick() != pdFALSE ) | |
{ | |
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; | |
} | |
/* Just completely clear the interrupt mask on exit by passing 0 because | |
it is known that this interrupt will only ever execute with the lowest | |
possible interrupt priority. */ | |
} | |
portCLEAR_INTERRUPT_MASK_FROM_ISR( 0 ); | |
/* The CPU woke because of a tick. */ | |
ulTickFlag = pdTRUE; | |
/* If this is the first tick since exiting tickless mode then the AST needs | |
to be reconfigured to generate interrupts at the defined tick frequency. */ | |
ast_write_alarm0_value( AST, ulAlarmValueForOneTick ); | |
/* Ensure the interrupt is clear before exiting. */ | |
ast_clear_interrupt_flag( AST, AST_INTERRUPT_ALARM ); | |
} | |
/*-----------------------------------------------------------*/ | |
/* Override the default definition of vPortSetupTimerInterrupt() that is weakly | |
defined in the FreeRTOS Cortex-M3 port layer with a version that configures the | |
asynchronous timer (AST) to generate the tick interrupt. */ | |
void vPortSetupTimerInterrupt( void ) | |
{ | |
struct ast_config ast_conf; | |
/* Ensure the AST can bring the CPU out of sleep mode. */ | |
sleepmgr_lock_mode( SLEEPMGR_RET ); | |
/* Ensure the 32KHz oscillator is enabled. */ | |
if( osc_is_ready( OSC_ID_OSC32 ) == pdFALSE ) | |
{ | |
osc_enable( OSC_ID_OSC32 ); | |
osc_wait_ready( OSC_ID_OSC32 ); | |
} | |
/* Enable the AST itself. */ | |
ast_enable( AST ); | |
ast_conf.mode = AST_COUNTER_MODE; /* Simple up counter. */ | |
ast_conf.osc_type = AST_OSC_32KHZ; | |
ast_conf.psel = 0; /* No prescale so the actual frequency is 32KHz/2. */ | |
ast_conf.counter = 0; | |
ast_set_config( AST, &ast_conf ); | |
/* The AST alarm interrupt is used as the tick interrupt. Ensure the alarm | |
status starts clear. */ | |
ast_clear_interrupt_flag( AST, AST_INTERRUPT_ALARM ); | |
/* Enable wakeup from alarm 0 in the AST and power manager. */ | |
ast_enable_wakeup( AST, AST_WAKEUP_ALARM ); | |
bpm_enable_wakeup_source( BPM, ( 1 << BPM_BKUPWEN_AST ) ); | |
/* Tick interrupt MUST execute at the lowest interrupt priority. */ | |
NVIC_SetPriority( AST_ALARM_IRQn, configLIBRARY_LOWEST_INTERRUPT_PRIORITY); | |
ast_enable_interrupt( AST, AST_INTERRUPT_ALARM ); | |
NVIC_ClearPendingIRQ( AST_ALARM_IRQn ); | |
NVIC_EnableIRQ( AST_ALARM_IRQn ); | |
/* Automatically clear the counter on interrupt. */ | |
ast_enable_counter_clear_on_alarm( AST, portAST_ALARM_CHANNEL ); | |
/* Start with the tick active and generating a tick with regular period. */ | |
ast_write_alarm0_value( AST, ulAlarmValueForOneTick ); | |
ast_write_counter_value( AST, 0 ); | |
/* See the comments where xMaximumPossibleSuppressedTicks is declared. */ | |
xMaximumPossibleSuppressedTicks = ULONG_MAX / ulAlarmValueForOneTick; | |
} | |
/*-----------------------------------------------------------*/ | |
static void prvDisableAST( void ) | |
{ | |
while( ast_is_busy( AST ) ) | |
{ | |
/* Nothing to do here, just waiting. */ | |
} | |
AST->AST_CR &= ~( AST_CR_EN ); | |
while( ast_is_busy( AST ) ) | |
{ | |
/* Nothing to do here, just waiting. */ | |
} | |
} | |
/*-----------------------------------------------------------*/ | |
static void prvEnableAST( void ) | |
{ | |
while( ast_is_busy( AST ) ) | |
{ | |
/* Nothing to do here, just waiting. */ | |
} | |
AST->AST_CR |= AST_CR_EN; | |
while( ast_is_busy( AST ) ) | |
{ | |
/* Nothing to do here, just waiting. */ | |
} | |
} | |
/*-----------------------------------------------------------*/ | |
/* Override the default definition of vPortSuppressTicksAndSleep() that is weakly | |
defined in the FreeRTOS Cortex-M3 port layer with a version that manages the | |
asynchronous timer (AST), as the tick is generated from the low power AST and | |
not the SysTick as would normally be the case on a Cortex-M. */ | |
void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) | |
{ | |
uint32_t ulAlarmValue, ulCompleteTickPeriods, ulInterruptStatus; | |
eSleepModeStatus eSleepAction; | |
TickType_t xModifiableIdleTime; | |
enum sleepmgr_mode xSleepMode; | |
/* THIS FUNCTION IS CALLED WITH THE SCHEDULER SUSPENDED. */ | |
/* Make sure the AST reload value does not overflow the counter. */ | |
if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ) | |
{ | |
xExpectedIdleTime = xMaximumPossibleSuppressedTicks; | |
} | |
/* Calculate the reload value required to wait xExpectedIdleTime tick | |
periods. */ | |
ulAlarmValue = ulAlarmValueForOneTick * xExpectedIdleTime; | |
if( ulAlarmValue > ulStoppedTimerCompensation ) | |
{ | |
/* Compensate for the fact that the AST is going to be stopped | |
momentarily. */ | |
ulAlarmValue -= ulStoppedTimerCompensation; | |
} | |
/* Stop the AST momentarily. The time the AST is stopped for is accounted | |
for as best it can be, but using the tickless mode will inevitably result in | |
some tiny drift of the time maintained by the kernel with respect to | |
calendar time. */ | |
prvDisableAST(); | |
/* Enter a critical section but don't use the taskENTER_CRITICAL() method as | |
that will mask interrupts that should exit sleep mode. */ | |
ulInterruptStatus = cpu_irq_save(); | |
/* The tick flag is set to false before sleeping. If it is true when sleep | |
mode is exited then sleep mode was probably exited because the tick was | |
suppressed for the entire xExpectedIdleTime period. */ | |
ulTickFlag = pdFALSE; | |
/* If a context switch is pending then abandon the low power entry as | |
the context switch might have been pended by an external interrupt that | |
requires processing. */ | |
eSleepAction = eTaskConfirmSleepModeStatus(); | |
if( eSleepAction == eAbortSleep ) | |
{ | |
/* Restart tick. */ | |
prvEnableAST(); | |
/* Re-enable interrupts - see comments above the cpsid instruction() | |
above. */ | |
cpu_irq_restore( ulInterruptStatus ); | |
} | |
else | |
{ | |
/* Adjust the alarm value to take into account that the current time | |
slice is already partially complete. */ | |
ulAlarmValue -= ast_read_counter_value( AST ); | |
ast_write_alarm0_value( AST, ulAlarmValue ); | |
/* Restart the AST. */ | |
prvEnableAST(); | |
/* Allow the application to define some pre-sleep processing. */ | |
xModifiableIdleTime = xExpectedIdleTime; | |
configPRE_SLEEP_PROCESSING( xModifiableIdleTime ); | |
/* xExpectedIdleTime being set to 0 by configPRE_SLEEP_PROCESSING() | |
means the application defined code has already executed the WAIT | |
instruction. */ | |
if( xModifiableIdleTime > 0 ) | |
{ | |
/* Find the deepest allowable sleep mode. */ | |
xSleepMode = sleepmgr_get_sleep_mode(); | |
if( xSleepMode != SLEEPMGR_ACTIVE ) | |
{ | |
/* Sleep until something happens. */ | |
bpm_sleep( BPM, xSleepMode ); | |
} | |
} | |
/* Allow the application to define some post sleep processing. */ | |
configPOST_SLEEP_PROCESSING( xModifiableIdleTime ); | |
/* Stop AST. Again, the time the SysTick is stopped for is accounted | |
for as best it can be, but using the tickless mode will inevitably | |
result in some tiny drift of the time maintained by the kernel with | |
respect to calendar time. */ | |
prvDisableAST(); | |
/* Re-enable interrupts - see comments above the cpsid instruction() | |
above. */ | |
cpu_irq_restore( ulInterruptStatus ); | |
if( ulTickFlag != pdFALSE ) | |
{ | |
/* The tick interrupt has already executed, although because this | |
function is called with the scheduler suspended the actual tick | |
processing will not occur until after this function has exited. | |
Reset the alarm value with whatever remains of this tick period. */ | |
ulAlarmValue = ulAlarmValueForOneTick - ast_read_counter_value( AST ); | |
ast_write_alarm0_value( AST, ulAlarmValue ); | |
/* The tick interrupt handler will already have pended the tick | |
processing in the kernel. As the pending tick will be processed as | |
soon as this function exits, the tick value maintained by the tick | |
is stepped forward by one less than the time spent sleeping. The | |
actual stepping of the tick appears later in this function. */ | |
ulCompleteTickPeriods = xExpectedIdleTime - 1UL; | |
} | |
else | |
{ | |
/* Something other than the tick interrupt ended the sleep. How | |
many complete tick periods passed while the processor was | |
sleeping? */ | |
ulCompleteTickPeriods = ast_read_counter_value( AST ) / ulAlarmValueForOneTick; | |
/* The alarm value is set to whatever fraction of a single tick | |
period remains. */ | |
ulAlarmValue = ast_read_counter_value( AST ) - ( ulCompleteTickPeriods * ulAlarmValueForOneTick ); | |
if( ulAlarmValue == 0 ) | |
{ | |
/* There is no fraction remaining. */ | |
ulAlarmValue = ulAlarmValueForOneTick; | |
ulCompleteTickPeriods++; | |
} | |
ast_write_counter_value( AST, 0 ); | |
ast_write_alarm0_value( AST, ulAlarmValue ); | |
} | |
/* Restart the AST so it runs up to the alarm value. The alarm value | |
will get set to the value required to generate exactly one tick period | |
the next time the AST interrupt executes. */ | |
prvEnableAST(); | |
/* Wind the tick forward by the number of tick periods that the CPU | |
remained in a low power state. */ | |
vTaskStepTick( ulCompleteTickPeriods ); | |
} | |
} | |
#endif /* configCREATE_LOW_POWER_DEMO == 1 */ | |