From 786aa19cbd35918f41b78d73b8e4c75241b8a7b1 Mon Sep 17 00:00:00 2001 From: Billy Kozak Date: Tue, 23 Feb 2016 10:41:01 -0700 Subject: [PATCH] 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. --- cpu/cc26xx-cc13xx/lpm.c | 364 +++++++++++++++++++++++----------------- cpu/cc26xx-cc13xx/lpm.h | 1 + 2 files changed, 213 insertions(+), 152 deletions(-) diff --git a/cpu/cc26xx-cc13xx/lpm.c b/cpu/cc26xx-cc13xx/lpm.c index 6af58e899..eec8ed66d 100644 --- a/cpu/cc26xx-cc13xx/lpm.c +++ b/cpu/cc26xx-cc13xx/lpm.c @@ -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 diff --git a/cpu/cc26xx-cc13xx/lpm.h b/cpu/cc26xx-cc13xx/lpm.h index cc144809b..2d6f56d00 100644 --- a/cpu/cc26xx-cc13xx/lpm.h +++ b/cpu/cc26xx-cc13xx/lpm.h @@ -49,6 +49,7 @@ #include /*---------------------------------------------------------------------------*/ +#define LPM_MODE_AWAKE 0 #define LPM_MODE_SLEEP 1 #define LPM_MODE_DEEP_SLEEP 2 #define LPM_MODE_SHUTDOWN 3