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
This commit is contained in:
George Oikonomou 2015-05-01 16:56:32 +01:00
parent 6e7d52b5df
commit 34f52ed08e

View file

@ -49,6 +49,7 @@
#include "dev/leds.h" #include "dev/leds.h"
#include "dev/watchdog.h" #include "dev/watchdog.h"
#include "dev/cc26xx-rtc.h" #include "dev/cc26xx-rtc.h"
#include "dev/oscillators.h"
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
#if ENERGEST_CONF_ON #if ENERGEST_CONF_ON
static unsigned long irq_energest = 0; static unsigned long irq_energest = 0;
@ -73,49 +74,48 @@ LIST(modules_list);
*/ */
#define STANDBY_MIN_DURATION (RTIMER_SECOND >> 8) #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 void
lpm_pd_lock_obtain(lpm_power_domain_lock_t *lock, uint32_t domains) lpm_shutdown(uint32_t wakeup_pin, uint32_t io_pull, uint32_t wake_on)
{
/* 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_registered_module_t *module; lpm_registered_module_t *module;
int i; int i, j;
rtimer_clock_t t0; uint32_t io_cfg = (IOC_STD_INPUT & ~IOC_IOPULL_M) | io_pull |
uint32_t io_cfg = (IOC_STD_INPUT & ~IOC_IOPULL_M) | IOC_IOPULL_UP | wake_on;
IOC_WAKE_ON_LOW;
/* 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; for(module = list_head(modules_list); module != NULL;
module = module->next) { module = module->next) {
if(module->shutdown) { 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++) { /* Freeze I/O latches in AON */
t0 = RTIMER_NOW(); ti_lib_aon_ioc_freeze_enable();
leds_toggle(LEDS_ALL);
while(RTIMER_CLOCK_LT(RTIMER_NOW(), (t0 + (RTIMER_SECOND >> 3))));
}
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); oscillators_switch_to_hf_rc();
ti_lib_ioc_port_configure_set(pin, IOC_PORT_GPIO, io_cfg); 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_aon_ioc_freeze_disable();
ti_lib_sys_ctrl_aon_sync(); ti_lib_sys_ctrl_aon_sync();
/* Power up AUX and allow it to go to sleep */ /* Check operating conditions, optimally choose DCDC versus GLDO */
ti_lib_aon_wuc_aux_wakeup_event(AONWUC_AUX_ALLOW_SLEEP); ti_lib_sys_ctrl_dcdc_voltage_conditional_control();
/* Notify all registered modules that we've just woken up */ /* Notify all registered modules that we've just woken up */
for(module = list_head(modules_list); module != NULL; for(module = list_head(modules_list); module != NULL;
@ -189,16 +237,11 @@ void
lpm_drop() lpm_drop()
{ {
lpm_registered_module_t *module; lpm_registered_module_t *module;
lpm_power_domain_lock_t *lock;
uint8_t max_pm = LPM_MODE_MAX_SUPPORTED; uint8_t max_pm = LPM_MODE_MAX_SUPPORTED;
uint8_t module_pm; uint8_t module_pm;
uint32_t domains = LOCKABLE_DOMAINS; uint32_t domains = LOCKABLE_DOMAINS;
if(shutdown_requested) {
shutdown_now();
}
if(RTIMER_CLOCK_LT(cc26xx_rtc_get_next_trigger(), if(RTIMER_CLOCK_LT(cc26xx_rtc_get_next_trigger(),
RTIMER_NOW() + STANDBY_MIN_DURATION)) { RTIMER_NOW() + STANDBY_MIN_DURATION)) {
lpm_sleep(); lpm_sleep();
@ -235,26 +278,21 @@ lpm_drop()
* This is a chance for modules to delay us a little bit until an ongoing * 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 * operation has finished (e.g. uart TX) or to configure themselves for
* deep sleep. * 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; for(module = list_head(modules_list); module != NULL;
module = module->next) { module = module->next) {
if(module->shutdown) { if(module->shutdown) {
module->shutdown(max_pm); 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 */ /* 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 */ /* 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); 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 */ /* Configure clock sources for MCU and AUX: No clock */
ti_lib_aon_wuc_mcu_power_down_config(AONWUC_NO_CLOCK); ti_lib_aon_wuc_mcu_power_down_config(AONWUC_NO_CLOCK);
ti_lib_aon_wuc_aux_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); ti_lib_aon_wuc_aux_sram_config(false);
/* Disable retention in the RFCORE RAM */ /* Disable retention in the RFCORE RAM */
HWREG(PRCM_BASE + PRCM_O_RAMRETEN) &= ~PRCM_RAMRETEN_RFC; ti_lib_prcm_retention_disable(PRCM_DOMAIN_RFCORE);
/* 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;
/* /*
* Always turn off RFCORE, CPU, SYSBUS and VIMS. RFCORE should be off * 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); 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. * source while in deep sleep.
*/ */
if(domains == LOCKABLE_DOMAINS) { if(domains == LOCKABLE_DOMAINS) {