Fixed numeruous bus in CC26xx-CC13xx lpm
Bug fixes include: - keep interrupts disabled during lpm_sleep() so that we don't miss any interrupts we may have been expecting - check that the pending etimer isn't already expired (and don't sleep at all if it is) - check that the about-to-be scheduled rtimer wakeup is neither too far into the future nor too close into the future (or even in the past) before actually setting the interrupt (should fix #1509); If the time is out of bounds we use a default min or max value instead. - Correctly handle LPM_MODE_MAX_SUPPORTED set to zero (and added a macro for the zero value) so that sleeping can be disabled altogether - If no etimer is set, we specify a wakeup time which is reasonably far into the future instead of setting none at all (this will save on power consumption whenever no etimers are set). Also did a bit of refactoring in that some long functions were broken into multiple functions.
This commit is contained in:
parent
287092db42
commit
786aa19cbd
@ -75,7 +75,12 @@ LIST(modules_list);
|
||||
* Don't consider standby mode if the next AON RTC event is scheduled to fire
|
||||
* in less than STANDBY_MIN_DURATION rtimer ticks
|
||||
*/
|
||||
#define STANDBY_MIN_DURATION (RTIMER_SECOND >> 11)
|
||||
#define STANDBY_MIN_DURATION (RTIMER_SECOND >> 11)
|
||||
#define MINIMAL_SAFE_SCHEDUAL 8u
|
||||
#define MAX_SLEEP_TIME RTIMER_SECOND
|
||||
#define DEFAULT_SLEEP_TIME RTIMER_SECOND
|
||||
/*---------------------------------------------------------------------------*/
|
||||
#define CLK_TO_RT(c) ((c) * (RTIMER_SECOND / CLOCK_SECOND))
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Prototype of a function in clock.c. Called every time we come out of DS */
|
||||
void clock_update(void);
|
||||
@ -234,30 +239,195 @@ wake_up(void)
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void
|
||||
lpm_drop()
|
||||
static void
|
||||
deep_sleep(void)
|
||||
{
|
||||
lpm_registered_module_t *module;
|
||||
uint8_t max_pm = LPM_MODE_MAX_SUPPORTED;
|
||||
uint8_t module_pm;
|
||||
clock_time_t next_event;
|
||||
|
||||
uint32_t domains = LOCKABLE_DOMAINS;
|
||||
lpm_registered_module_t *module;
|
||||
|
||||
/* Critical. Don't get interrupted! */
|
||||
ti_lib_int_master_disable();
|
||||
/*
|
||||
* Notify all registered modules that we are dropping to mode X. We do not
|
||||
* need to do this for simple sleep.
|
||||
*
|
||||
* This is a chance for modules to delay us a little bit until an ongoing
|
||||
* operation has finished (e.g. uart TX) or to configure themselves for
|
||||
* deep sleep.
|
||||
*
|
||||
* At this stage, we also collect power domain locks, if any.
|
||||
* The argument to PRCMPowerDomainOff() is a bitwise OR, so every time
|
||||
* we encounter a lock we just clear the respective bits in the 'domains'
|
||||
* variable as required by the lock. In the end the domains variable will
|
||||
* just hold whatever has not been cleared
|
||||
*/
|
||||
for(module = list_head(modules_list); module != NULL;
|
||||
module = module->next) {
|
||||
if(module->shutdown) {
|
||||
module->shutdown(LPM_MODE_DEEP_SLEEP);
|
||||
}
|
||||
|
||||
/* Check if any events fired before we turned interrupts off. If so, abort */
|
||||
if(process_nevents()) {
|
||||
ti_lib_int_master_enable();
|
||||
return;
|
||||
/* Clear the bits specified in the lock */
|
||||
domains &= ~module->domain_lock;
|
||||
}
|
||||
|
||||
if(RTIMER_CLOCK_LT(soc_rtc_get_next_trigger(),
|
||||
RTIMER_NOW() + STANDBY_MIN_DURATION)) {
|
||||
ti_lib_int_master_enable();
|
||||
lpm_sleep();
|
||||
return;
|
||||
/* Pat the dog: We don't want it to shout right after we wake up */
|
||||
watchdog_periodic();
|
||||
|
||||
/* Clear unacceptable bits, just in case a lock provided a bad value */
|
||||
domains &= LOCKABLE_DOMAINS;
|
||||
|
||||
/*
|
||||
* Freeze the IOs on the boundary between MCU and AON. We only do this if
|
||||
* PERIPH is not needed
|
||||
*/
|
||||
if(domains & PRCM_DOMAIN_PERIPH) {
|
||||
ti_lib_aon_ioc_freeze_enable();
|
||||
}
|
||||
|
||||
/*
|
||||
* Among LOCKABLE_DOMAINS, turn off those that are not locked
|
||||
*
|
||||
* If domains is != 0, pass it as-is
|
||||
*/
|
||||
if(domains) {
|
||||
ti_lib_prcm_power_domain_off(domains);
|
||||
}
|
||||
|
||||
/*
|
||||
* Before entering Deep Sleep, we must switch off the HF XOSC. The HF XOSC
|
||||
* is predominantly controlled by the RF driver. In a build with radio
|
||||
* cycling (e.g. ContikiMAC), the RF driver will request the XOSC before
|
||||
* using the Freq. Synth, and switch back to the RC when it is about to
|
||||
* turn back off.
|
||||
*
|
||||
* If the radio is on, we won't even reach here, and if it's off the HF
|
||||
* clock source should already be the HF RC.
|
||||
*
|
||||
* Nevertheless, request the switch to the HF RC explicitly here.
|
||||
*/
|
||||
oscillators_switch_to_hf_rc();
|
||||
|
||||
/* Configure clock sources for MCU and AUX: No clock */
|
||||
ti_lib_aon_wuc_mcu_power_down_config(AONWUC_NO_CLOCK);
|
||||
ti_lib_aon_wuc_aux_power_down_config(AONWUC_NO_CLOCK);
|
||||
|
||||
/* Full RAM retention. */
|
||||
ti_lib_aon_wuc_mcu_sram_config(MCU_RAM0_RETENTION | MCU_RAM1_RETENTION |
|
||||
MCU_RAM2_RETENTION | MCU_RAM3_RETENTION);
|
||||
|
||||
/* Disable retention of AUX RAM */
|
||||
ti_lib_aon_wuc_aux_sram_config(false);
|
||||
|
||||
/*
|
||||
* Always turn off RFCORE, CPU, SYSBUS and VIMS. RFCORE should be off
|
||||
* already
|
||||
*/
|
||||
ti_lib_prcm_power_domain_off(PRCM_DOMAIN_RFCORE | PRCM_DOMAIN_CPU |
|
||||
PRCM_DOMAIN_VIMS | PRCM_DOMAIN_SYSBUS);
|
||||
|
||||
/* Request JTAG domain power off */
|
||||
ti_lib_aon_wuc_jtag_power_off();
|
||||
|
||||
/* Turn off AUX */
|
||||
ti_lib_aux_wuc_power_ctrl(AUX_WUC_POWER_OFF);
|
||||
ti_lib_aon_wuc_domain_power_down_enable();
|
||||
while(ti_lib_aon_wuc_power_status_get() & AONWUC_AUX_POWER_ON);
|
||||
|
||||
/* Configure the recharge controller */
|
||||
ti_lib_sys_ctrl_set_recharge_before_power_down(XOSC_IN_HIGH_POWER_MODE);
|
||||
|
||||
/*
|
||||
* If both PERIPH and SERIAL PDs are off, request the uLDO as the power
|
||||
* source while in deep sleep.
|
||||
*/
|
||||
if(domains == LOCKABLE_DOMAINS) {
|
||||
ti_lib_pwr_ctrl_source_set(PWRCTRL_PWRSRC_ULDO);
|
||||
}
|
||||
|
||||
/* We are only interested in IRQ energest while idle or in LPM */
|
||||
ENERGEST_IRQ_RESTORE(irq_energest);
|
||||
ENERGEST_SWITCH(ENERGEST_TYPE_CPU, ENERGEST_TYPE_LPM);
|
||||
|
||||
/* Sync the AON interface to ensure all writes have gone through. */
|
||||
ti_lib_sys_ctrl_aon_sync();
|
||||
|
||||
/*
|
||||
* Explicitly turn off VIMS cache, CRAM and TRAM. Needed because of
|
||||
* retention mismatch between VIMS logic and cache. We wait to do this
|
||||
* until right before deep sleep to be able to use the cache for as long
|
||||
* as possible.
|
||||
*/
|
||||
ti_lib_prcm_cache_retention_disable();
|
||||
ti_lib_vims_mode_set(VIMS_BASE, VIMS_MODE_OFF);
|
||||
|
||||
/* Deep Sleep */
|
||||
ti_lib_prcm_deep_sleep();
|
||||
|
||||
/*
|
||||
* When we reach here, some interrupt woke us up. The global interrupt
|
||||
* flag is off, hence we have a chance to run things here. We will wake up
|
||||
* the chip properly, and then we will enable the global interrupt without
|
||||
* unpending events so the handlers can fire
|
||||
*/
|
||||
wake_up();
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
static void
|
||||
safe_schedule_rtimer(rtimer_clock_t time, rtimer_clock_t now, int pm)
|
||||
{
|
||||
rtimer_clock_t min_sleep;
|
||||
rtimer_clock_t max_sleep;
|
||||
|
||||
min_sleep = now + MINIMAL_SAFE_SCHEDUAL;
|
||||
max_sleep = now + MAX_SLEEP_TIME;
|
||||
|
||||
if(RTIMER_CLOCK_LT(time, min_sleep)) {
|
||||
/* ensure that we schedule sleep a minimal number of ticks into the
|
||||
future */
|
||||
soc_rtc_schedule_one_shot(AON_RTC_CH1, min_sleep);
|
||||
} else if((pm == LPM_MODE_SLEEP) && RTIMER_CLOCK_LT(max_sleep, time)) {
|
||||
/* if max_pm is LPM_MODE_SLEEP, we could trigger the watchdog if we slept
|
||||
for too long. */
|
||||
soc_rtc_schedule_one_shot(AON_RTC_CH1, max_sleep);
|
||||
} else {
|
||||
soc_rtc_schedule_one_shot(AON_RTC_CH1, time);
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
static int
|
||||
setup_sleep_mode(void)
|
||||
{
|
||||
rtimer_clock_t et_distance = 0;
|
||||
|
||||
lpm_registered_module_t *module;
|
||||
int max_pm;
|
||||
int module_pm;
|
||||
int etimer_is_pending;
|
||||
rtimer_clock_t now;
|
||||
rtimer_clock_t et_time;
|
||||
rtimer_clock_t next_trig;
|
||||
|
||||
max_pm = LPM_MODE_MAX_SUPPORTED;
|
||||
now = RTIMER_NOW();
|
||||
|
||||
if((LPM_MODE_MAX_SUPPORTED == LPM_MODE_AWAKE) || process_nevents()) {
|
||||
return LPM_MODE_AWAKE;
|
||||
}
|
||||
|
||||
etimer_is_pending = etimer_pending();
|
||||
|
||||
if(etimer_is_pending) {
|
||||
et_distance = CLK_TO_RT(etimer_next_expiration_time() - clock_time());
|
||||
|
||||
if(RTIMER_CLOCK_LT(et_distance, 1)) {
|
||||
/* there is an etimer which is already expired; we shouldn't go to
|
||||
sleep at all */
|
||||
return LPM_MODE_AWAKE;
|
||||
}
|
||||
}
|
||||
|
||||
next_trig = soc_rtc_get_next_trigger();
|
||||
if(RTIMER_CLOCK_LT(next_trig, now + STANDBY_MIN_DURATION)) {
|
||||
return LPM_MODE_SLEEP;
|
||||
}
|
||||
|
||||
/* Collect max allowed PM permission from interested modules */
|
||||
@ -272,146 +442,36 @@ lpm_drop()
|
||||
}
|
||||
|
||||
/* Reschedule AON RTC CH1 to fire just in time for the next etimer event */
|
||||
next_event = etimer_next_expiration_time();
|
||||
if(etimer_is_pending) {
|
||||
et_time = soc_rtc_last_isr_time() + et_distance;
|
||||
|
||||
if(etimer_pending()) {
|
||||
next_event = next_event - clock_time();
|
||||
soc_rtc_schedule_one_shot(AON_RTC_CH1, soc_rtc_last_isr_time() +
|
||||
(next_event * (RTIMER_SECOND / CLOCK_SECOND)));
|
||||
safe_schedule_rtimer(et_time, now, max_pm);
|
||||
} else {
|
||||
/* set a maximal sleep period if no etimers are queued */
|
||||
soc_rtc_schedule_one_shot(AON_RTC_CH1, now + DEFAULT_SLEEP_TIME);
|
||||
}
|
||||
|
||||
return max_pm;
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void
|
||||
lpm_drop()
|
||||
{
|
||||
uint8_t max_pm;
|
||||
|
||||
/* Critical. Don't get interrupted! */
|
||||
ti_lib_int_master_disable();
|
||||
|
||||
max_pm = setup_sleep_mode();
|
||||
|
||||
/* Drop */
|
||||
if(max_pm == LPM_MODE_SLEEP) {
|
||||
ti_lib_int_master_enable();
|
||||
lpm_sleep();
|
||||
} else {
|
||||
/*
|
||||
* Notify all registered modules that we are dropping to mode X. We do not
|
||||
* need to do this for simple sleep.
|
||||
*
|
||||
* This is a chance for modules to delay us a little bit until an ongoing
|
||||
* operation has finished (e.g. uart TX) or to configure themselves for
|
||||
* deep sleep.
|
||||
*
|
||||
* At this stage, we also collect power domain locks, if any.
|
||||
* The argument to PRCMPowerDomainOff() is a bitwise OR, so every time
|
||||
* we encounter a lock we just clear the respective bits in the 'domains'
|
||||
* variable as required by the lock. In the end the domains variable will
|
||||
* just hold whatever has not been cleared
|
||||
*/
|
||||
for(module = list_head(modules_list); module != NULL;
|
||||
module = module->next) {
|
||||
if(module->shutdown) {
|
||||
module->shutdown(max_pm);
|
||||
}
|
||||
|
||||
/* Clear the bits specified in the lock */
|
||||
domains &= ~module->domain_lock;
|
||||
}
|
||||
|
||||
/* Pat the dog: We don't want it to shout right after we wake up */
|
||||
watchdog_periodic();
|
||||
|
||||
/* Clear unacceptable bits, just in case a lock provided a bad value */
|
||||
domains &= LOCKABLE_DOMAINS;
|
||||
|
||||
/*
|
||||
* Freeze the IOs on the boundary between MCU and AON. We only do this if
|
||||
* PERIPH is not needed
|
||||
*/
|
||||
if(domains & PRCM_DOMAIN_PERIPH) {
|
||||
ti_lib_aon_ioc_freeze_enable();
|
||||
}
|
||||
|
||||
/*
|
||||
* Among LOCKABLE_DOMAINS, turn off those that are not locked
|
||||
*
|
||||
* If domains is != 0, pass it as-is
|
||||
*/
|
||||
if(domains) {
|
||||
ti_lib_prcm_power_domain_off(domains);
|
||||
}
|
||||
|
||||
/*
|
||||
* Before entering Deep Sleep, we must switch off the HF XOSC. The HF XOSC
|
||||
* is predominantly controlled by the RF driver. In a build with radio
|
||||
* cycling (e.g. ContikiMAC), the RF driver will request the XOSC before
|
||||
* using the Freq. Synth, and switch back to the RC when it is about to
|
||||
* turn back off.
|
||||
*
|
||||
* If the radio is on, we won't even reach here, and if it's off the HF
|
||||
* clock source should already be the HF RC.
|
||||
*
|
||||
* Nevertheless, request the switch to the HF RC explicitly here.
|
||||
*/
|
||||
oscillators_switch_to_hf_rc();
|
||||
|
||||
/* Configure clock sources for MCU and AUX: No clock */
|
||||
ti_lib_aon_wuc_mcu_power_down_config(AONWUC_NO_CLOCK);
|
||||
ti_lib_aon_wuc_aux_power_down_config(AONWUC_NO_CLOCK);
|
||||
|
||||
/* Full RAM retention. */
|
||||
ti_lib_aon_wuc_mcu_sram_config(MCU_RAM0_RETENTION | MCU_RAM1_RETENTION |
|
||||
MCU_RAM2_RETENTION | MCU_RAM3_RETENTION);
|
||||
|
||||
/* Disable retention of AUX RAM */
|
||||
ti_lib_aon_wuc_aux_sram_config(false);
|
||||
|
||||
/*
|
||||
* Always turn off RFCORE, CPU, SYSBUS and VIMS. RFCORE should be off
|
||||
* already
|
||||
*/
|
||||
ti_lib_prcm_power_domain_off(PRCM_DOMAIN_RFCORE | PRCM_DOMAIN_CPU |
|
||||
PRCM_DOMAIN_VIMS | PRCM_DOMAIN_SYSBUS);
|
||||
|
||||
/* Request JTAG domain power off */
|
||||
ti_lib_aon_wuc_jtag_power_off();
|
||||
|
||||
/* Turn off AUX */
|
||||
ti_lib_aux_wuc_power_ctrl(AUX_WUC_POWER_OFF);
|
||||
ti_lib_aon_wuc_domain_power_down_enable();
|
||||
while(ti_lib_aon_wuc_power_status_get() & AONWUC_AUX_POWER_ON);
|
||||
|
||||
/* Configure the recharge controller */
|
||||
ti_lib_sys_ctrl_set_recharge_before_power_down(XOSC_IN_HIGH_POWER_MODE);
|
||||
|
||||
/*
|
||||
* If both PERIPH and SERIAL PDs are off, request the uLDO as the power
|
||||
* source while in deep sleep.
|
||||
*/
|
||||
if(domains == LOCKABLE_DOMAINS) {
|
||||
ti_lib_pwr_ctrl_source_set(PWRCTRL_PWRSRC_ULDO);
|
||||
}
|
||||
|
||||
/* We are only interested in IRQ energest while idle or in LPM */
|
||||
ENERGEST_IRQ_RESTORE(irq_energest);
|
||||
ENERGEST_SWITCH(ENERGEST_TYPE_CPU, ENERGEST_TYPE_LPM);
|
||||
|
||||
/* Sync the AON interface to ensure all writes have gone through. */
|
||||
ti_lib_sys_ctrl_aon_sync();
|
||||
|
||||
/*
|
||||
* Explicitly turn off VIMS cache, CRAM and TRAM. Needed because of
|
||||
* retention mismatch between VIMS logic and cache. We wait to do this
|
||||
* until right before deep sleep to be able to use the cache for as long
|
||||
* as possible.
|
||||
*/
|
||||
ti_lib_prcm_cache_retention_disable();
|
||||
ti_lib_vims_mode_set(VIMS_BASE, VIMS_MODE_OFF);
|
||||
|
||||
/* Deep Sleep */
|
||||
ti_lib_prcm_deep_sleep();
|
||||
|
||||
/*
|
||||
* When we reach here, some interrupt woke us up. The global interrupt
|
||||
* flag is off, hence we have a chance to run things here. We will wake up
|
||||
* the chip properly, and then we will enable the global interrupt without
|
||||
* unpending events so the handlers can fire
|
||||
*/
|
||||
wake_up();
|
||||
|
||||
ti_lib_int_master_enable();
|
||||
} else if(max_pm == LPM_MODE_DEEP_SLEEP) {
|
||||
deep_sleep();
|
||||
}
|
||||
|
||||
ti_lib_int_master_enable();
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void
|
||||
|
@ -49,6 +49,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
/*---------------------------------------------------------------------------*/
|
||||
#define LPM_MODE_AWAKE 0
|
||||
#define LPM_MODE_SLEEP 1
|
||||
#define LPM_MODE_DEEP_SLEEP 2
|
||||
#define LPM_MODE_SHUTDOWN 3
|
||||
|
Loading…
Reference in New Issue
Block a user