Merge pull request #1531 from bkozak-scanimetrics/cc26xx_cc13xx_lpm_fixes

Fixed numerous bugs in CC26xx-CC13xx lpm
This commit is contained in:
George Oikonomou 2016-05-24 16:45:20 +01:00
commit 3a34b1f695
2 changed files with 213 additions and 152 deletions

View file

@ -75,7 +75,12 @@ LIST(modules_list);
* Don't consider standby mode if the next AON RTC event is scheduled to fire * Don't consider standby mode if the next AON RTC event is scheduled to fire
* in less than STANDBY_MIN_DURATION rtimer ticks * 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 */ /* Prototype of a function in clock.c. Called every time we come out of DS */
void clock_update(void); void clock_update(void);
@ -234,30 +239,195 @@ wake_up(void)
} }
} }
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
void static void
lpm_drop() 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; 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 */ /* Clear the bits specified in the lock */
if(process_nevents()) { domains &= ~module->domain_lock;
ti_lib_int_master_enable();
return;
} }
if(RTIMER_CLOCK_LT(soc_rtc_get_next_trigger(), /* Pat the dog: We don't want it to shout right after we wake up */
RTIMER_NOW() + STANDBY_MIN_DURATION)) { watchdog_periodic();
ti_lib_int_master_enable();
lpm_sleep(); /* Clear unacceptable bits, just in case a lock provided a bad value */
return; 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 */ /* 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 */ /* 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()) { safe_schedule_rtimer(et_time, now, max_pm);
next_event = next_event - clock_time(); } else {
soc_rtc_schedule_one_shot(AON_RTC_CH1, soc_rtc_last_isr_time() + /* set a maximal sleep period if no etimers are queued */
(next_event * (RTIMER_SECOND / CLOCK_SECOND))); 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 */ /* Drop */
if(max_pm == LPM_MODE_SLEEP) { if(max_pm == LPM_MODE_SLEEP) {
ti_lib_int_master_enable();
lpm_sleep(); lpm_sleep();
} else { } else if(max_pm == LPM_MODE_DEEP_SLEEP) {
/* deep_sleep();
* 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();
} }
ti_lib_int_master_enable();
} }
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
void void

View file

@ -49,6 +49,7 @@
#include <stdint.h> #include <stdint.h>
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
#define LPM_MODE_AWAKE 0
#define LPM_MODE_SLEEP 1 #define LPM_MODE_SLEEP 1
#define LPM_MODE_DEEP_SLEEP 2 #define LPM_MODE_DEEP_SLEEP 2
#define LPM_MODE_SHUTDOWN 3 #define LPM_MODE_SHUTDOWN 3