From 34f52ed08ef99de80c9cdc806c19f12717a65c1a Mon Sep 17 00:00:00 2001 From: George Oikonomou Date: Fri, 1 May 2015 16:56:32 +0100 Subject: [PATCH] Improve the LPM module: * Implement new style of PD locks * Use our own shutdown sequence rather than the one provided by cc26xxware * Shutdown from within the interrupt that requested it. This allows shutdown to take place even if the code is stuck in a loop somewhere else * Improve DCDC/GLDO/uLDO switching logic * Explicitly handle oscillators and retentions --- cpu/cc26xx/lpm.c | 196 +++++++++++++++++++++++++++++------------------ 1 file changed, 122 insertions(+), 74 deletions(-) diff --git a/cpu/cc26xx/lpm.c b/cpu/cc26xx/lpm.c index 320ccdd03..f9d309f74 100644 --- a/cpu/cc26xx/lpm.c +++ b/cpu/cc26xx/lpm.c @@ -49,6 +49,7 @@ #include "dev/leds.h" #include "dev/watchdog.h" #include "dev/cc26xx-rtc.h" +#include "dev/oscillators.h" /*---------------------------------------------------------------------------*/ #if ENERGEST_CONF_ON static unsigned long irq_energest = 0; @@ -73,49 +74,48 @@ LIST(modules_list); */ #define STANDBY_MIN_DURATION (RTIMER_SECOND >> 8) /*---------------------------------------------------------------------------*/ -/* Variables used by the power on/off (Power mode: SHUTDOWN) functionality */ -static uint8_t shutdown_requested; -static uint32_t pin; -/*---------------------------------------------------------------------------*/ void -lpm_pd_lock_obtain(lpm_power_domain_lock_t *lock, uint32_t domains) -{ - /* We only accept locks for specific PDs */ - domains &= LOCKABLE_DOMAINS; - - if(domains == 0) { - return; - } - - lock->domains = domains; - - list_add(power_domain_locks_list, lock); -} -/*---------------------------------------------------------------------------*/ -void -lpm_pd_lock_release(lpm_power_domain_lock_t *lock) -{ - lock->domains = 0; - - list_remove(power_domain_locks_list, lock); -} -/*---------------------------------------------------------------------------*/ -void -lpm_shutdown(uint32_t wakeup_pin) -{ - shutdown_requested = 1; - pin = wakeup_pin; -} -/*---------------------------------------------------------------------------*/ -static void -shutdown_now(void) +lpm_shutdown(uint32_t wakeup_pin, uint32_t io_pull, uint32_t wake_on) { lpm_registered_module_t *module; - int i; - rtimer_clock_t t0; - uint32_t io_cfg = (IOC_STD_INPUT & ~IOC_IOPULL_M) | IOC_IOPULL_UP | - IOC_WAKE_ON_LOW; + int i, j; + uint32_t io_cfg = (IOC_STD_INPUT & ~IOC_IOPULL_M) | io_pull | + wake_on; + /* This procedure may not be interrupted */ + ti_lib_int_master_disable(); + + /* Disable the RTC */ + ti_lib_aon_rtc_disable(); + ti_lib_aon_rtc_event_clear(AON_RTC_CH0); + ti_lib_aon_rtc_event_clear(AON_RTC_CH1); + ti_lib_aon_rtc_event_clear(AON_RTC_CH2); + + /* Reset AON even fabric to default wakeup sources */ + for(i = AON_EVENT_MCU_WU0; i <= AON_EVENT_MCU_WU3; i++) { + ti_lib_aon_event_mcu_wake_up_set(i, AON_EVENT_NULL); + } + for(i = AON_EVENT_AUX_WU0; i <= AON_EVENT_AUX_WU2; i++) { + ti_lib_aon_event_aux_wake_up_set(i, AON_EVENT_NULL); + } + + ti_lib_sys_ctrl_aon_sync(); + + watchdog_periodic(); + + /* fade away....... */ + j = 1000; + + for(i = j; i > 0; --i) { + leds_on(LEDS_ALL); + clock_delay_usec(i); + leds_off(LEDS_ALL); + clock_delay_usec(j - i); + } + + leds_off(LEDS_ALL); + + /* Notify all modules that we're shutting down */ for(module = list_head(modules_list); module != NULL; module = module->next) { if(module->shutdown) { @@ -123,20 +123,68 @@ shutdown_now(void) } } - leds_off(LEDS_ALL); + /* Configure the wakeup trigger */ + ti_lib_gpio_dir_mode_set((1 << wakeup_pin), GPIO_DIR_MODE_IN); + ti_lib_ioc_port_configure_set(wakeup_pin, IOC_PORT_GPIO, io_cfg); - for(i = 0; i < 5; i++) { - t0 = RTIMER_NOW(); - leds_toggle(LEDS_ALL); - while(RTIMER_CLOCK_LT(RTIMER_NOW(), (t0 + (RTIMER_SECOND >> 3)))); - } + /* Freeze I/O latches in AON */ + ti_lib_aon_ioc_freeze_enable(); - leds_off(LEDS_ALL); + /* Turn off RFCORE, SERIAL and PERIPH PDs. This will happen immediately */ + ti_lib_prcm_power_domain_off(PRCM_DOMAIN_RFCORE | PRCM_DOMAIN_SERIAL | + PRCM_DOMAIN_PERIPH); - ti_lib_gpio_dir_mode_set((1 << pin), GPIO_DIR_MODE_IN); - ti_lib_ioc_port_configure_set(pin, IOC_PORT_GPIO, io_cfg); + oscillators_switch_to_hf_rc(); + oscillators_select_lf_rcosc(); - ti_lib_pwr_ctrl_state_set(LPM_MODE_SHUTDOWN); + /* 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); + + /* Disable retentions: SRAM, CPU, AUX, RFCORE - possibly not required */ + ti_lib_aon_wuc_mcu_sram_config(0); + ti_lib_prcm_retention_disable(PRCM_DOMAIN_CPU); + ti_lib_aon_wuc_aux_sram_config(false); + ti_lib_prcm_retention_disable(PRCM_DOMAIN_RFCORE); + + /* + * Request CPU, SYSBYS and VIMS PD off. + * This will only happen when the CM3 enters deep sleep + */ + ti_lib_prcm_power_domain_off(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() & AONWUC_AUX_POWER_ON); + + /* + * Request MCU VD power off. + * This will only happen when the CM3 enters deep sleep + */ + ti_lib_prcm_mcu_power_off(); + + /* Set MCU wakeup to immediate and disable virtual power off */ + ti_lib_aon_wuc_mcu_wake_up_config(MCU_IMM_WAKE_UP); + ti_lib_aon_wuc_mcu_power_off_config(MCU_VIRT_PWOFF_DISABLE); + + /* Latch the IOs in the padring and enable I/O pad sleep mode */ + ti_lib_pwr_ctrl_io_freeze_enable(); + + /* Turn off VIMS cache, CRAM and TRAM - possibly not required */ + ti_lib_prcm_retention_disable(PRCM_DOMAIN_VIMS); + ti_lib_vims_mode_set(VIMS_BASE, VIMS_MODE_OFF); + + /* Enable shutdown and sync AON */ + ti_lib_aon_wuc_shut_down_enable(); + ti_lib_sys_ctrl_aon_sync(); + + /* Deep Sleep */ + ti_lib_prcm_deep_sleep(); } /*---------------------------------------------------------------------------*/ /* @@ -173,8 +221,8 @@ wake_up(void) ti_lib_aon_ioc_freeze_disable(); ti_lib_sys_ctrl_aon_sync(); - /* Power up AUX and allow it to go to sleep */ - ti_lib_aon_wuc_aux_wakeup_event(AONWUC_AUX_ALLOW_SLEEP); + /* Check operating conditions, optimally choose DCDC versus GLDO */ + ti_lib_sys_ctrl_dcdc_voltage_conditional_control(); /* Notify all registered modules that we've just woken up */ for(module = list_head(modules_list); module != NULL; @@ -189,16 +237,11 @@ void lpm_drop() { lpm_registered_module_t *module; - lpm_power_domain_lock_t *lock; uint8_t max_pm = LPM_MODE_MAX_SUPPORTED; uint8_t module_pm; uint32_t domains = LOCKABLE_DOMAINS; - if(shutdown_requested) { - shutdown_now(); - } - if(RTIMER_CLOCK_LT(cc26xx_rtc_get_next_trigger(), RTIMER_NOW() + STANDBY_MIN_DURATION)) { lpm_sleep(); @@ -235,26 +278,21 @@ lpm_drop() * 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); } - } - /* - * Iterate PD locks to see what we can and cannot turn off. - * - * 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(lock = list_head(power_domain_locks_list); lock != NULL; - lock = lock->next) { /* Clear the bits specified in the lock */ - domains &= ~lock->domains; + domains &= ~module->domain_lock; } /* Pat the dog: We don't want it to shout right after we wake up */ @@ -280,6 +318,20 @@ lpm_drop() 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); @@ -295,11 +347,7 @@ lpm_drop() ti_lib_aon_wuc_aux_sram_config(false); /* Disable retention in the RFCORE RAM */ - HWREG(PRCM_BASE + PRCM_O_RAMRETEN) &= ~PRCM_RAMRETEN_RFC; - - /* Disable retention of VIMS RAM (TRAM and CRAM) */ - //TODO: This can probably be removed, we are calling ti_lib_prcm_retention_disable(PRCM_DOMAIN_VIMS); further down - HWREG(PRCM_BASE + PRCM_O_RAMRETEN) &= ~PRCM_RAMRETEN_VIMS_M; + ti_lib_prcm_retention_disable(PRCM_DOMAIN_RFCORE); /* * Always turn off RFCORE, CPU, SYSBUS and VIMS. RFCORE should be off @@ -320,7 +368,7 @@ lpm_drop() ti_lib_sys_ctrl_set_recharge_before_power_down(false); /* - * If both PERIPH and SERIAL PDs are off, request the uLDO for as the power + * If both PERIPH and SERIAL PDs are off, request the uLDO as the power * source while in deep sleep. */ if(domains == LOCKABLE_DOMAINS) {