2015-02-25 13:09:56 +01:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
* 3. Neither the name of the copyright holder nor the names of its
|
|
|
|
* contributors may be used to endorse or promote products derived
|
|
|
|
* from this software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
|
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
|
|
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
|
|
* \addtogroup cc26xx-lpm
|
|
|
|
* @{
|
|
|
|
*
|
2015-08-16 17:30:04 +02:00
|
|
|
* Implementation of CC13xx/CC26xx low-power operation functionality
|
2015-02-25 13:09:56 +01:00
|
|
|
*
|
|
|
|
* @{
|
|
|
|
*
|
|
|
|
* \file
|
2015-08-16 17:30:04 +02:00
|
|
|
* Driver for CC13xx/CC26xx low-power operation
|
2015-02-25 13:09:56 +01:00
|
|
|
*/
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
#include "prcm.h"
|
|
|
|
#include "contiki-conf.h"
|
|
|
|
#include "ti-lib.h"
|
|
|
|
#include "lpm.h"
|
|
|
|
#include "sys/energest.h"
|
|
|
|
#include "lib/list.h"
|
2016-04-20 17:19:54 +02:00
|
|
|
#include "dev/aux-ctrl.h"
|
2015-02-25 13:09:56 +01:00
|
|
|
#include "dev/leds.h"
|
|
|
|
#include "dev/watchdog.h"
|
2015-08-16 17:30:04 +02:00
|
|
|
#include "dev/soc-rtc.h"
|
2015-05-01 17:56:32 +02:00
|
|
|
#include "dev/oscillators.h"
|
2015-08-16 17:30:04 +02:00
|
|
|
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <string.h>
|
2016-04-20 17:19:54 +02:00
|
|
|
#include <stdbool.h>
|
2015-02-25 13:09:56 +01:00
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
#if ENERGEST_CONF_ON
|
|
|
|
static unsigned long irq_energest = 0;
|
|
|
|
|
|
|
|
#define ENERGEST_IRQ_SAVE(a) do { \
|
|
|
|
a = energest_type_time(ENERGEST_TYPE_IRQ); } while(0)
|
|
|
|
#define ENERGEST_IRQ_RESTORE(a) do { \
|
|
|
|
energest_type_set(ENERGEST_TYPE_IRQ, a); } while(0)
|
|
|
|
#else
|
|
|
|
#define ENERGEST_IRQ_SAVE(a) do {} while(0)
|
|
|
|
#define ENERGEST_IRQ_RESTORE(a) do {} while(0)
|
|
|
|
#endif
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
LIST(modules_list);
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* PDs that may stay on in deep sleep */
|
|
|
|
#define LOCKABLE_DOMAINS ((uint32_t)(PRCM_DOMAIN_SERIAL | PRCM_DOMAIN_PERIPH))
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
|
|
* Don't consider standby mode if the next AON RTC event is scheduled to fire
|
|
|
|
* in less than STANDBY_MIN_DURATION rtimer ticks
|
|
|
|
*/
|
2016-04-25 17:08:34 +02:00
|
|
|
#define STANDBY_MIN_DURATION (RTIMER_SECOND / 100) /* 10.0 ms */
|
|
|
|
|
|
|
|
/* Wake up this much time earlier before the next rtimer */
|
|
|
|
#define SLEEP_GUARD_TIME (RTIMER_SECOND / 1000) /* 1.0 ms */
|
|
|
|
|
2016-02-23 18:41:01 +01:00
|
|
|
#define MAX_SLEEP_TIME RTIMER_SECOND
|
2016-06-27 18:48:15 +02:00
|
|
|
#define MIN_SAFE_SCHEDULE 8u
|
2015-08-16 17:30:04 +02:00
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Prototype of a function in clock.c. Called every time we come out of DS */
|
|
|
|
void clock_update(void);
|
2015-02-25 13:09:56 +01:00
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
void
|
2015-05-01 17:56:32 +02:00
|
|
|
lpm_shutdown(uint32_t wakeup_pin, uint32_t io_pull, uint32_t wake_on)
|
2015-02-25 13:09:56 +01:00
|
|
|
{
|
2015-05-01 17:56:32 +02:00
|
|
|
lpm_registered_module_t *module;
|
2015-10-28 10:51:37 +01:00
|
|
|
int i;
|
2016-04-20 17:19:54 +02:00
|
|
|
uint32_t io_cfg = (IOC_STD_INPUT & ~IOC_IOPULL_M) | io_pull | wake_on;
|
|
|
|
aux_consumer_module_t aux = { .clocks = AUX_WUC_OSCCTRL_CLOCK };
|
2015-05-01 17:56:32 +02:00
|
|
|
|
|
|
|
/* 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++) {
|
2015-08-16 17:30:04 +02:00
|
|
|
ti_lib_aon_event_mcu_wake_up_set(i, AON_EVENT_NONE);
|
2015-05-01 17:56:32 +02:00
|
|
|
}
|
|
|
|
for(i = AON_EVENT_AUX_WU0; i <= AON_EVENT_AUX_WU2; i++) {
|
2015-08-16 17:30:04 +02:00
|
|
|
ti_lib_aon_event_aux_wake_up_set(i, AON_EVENT_NONE);
|
2015-02-25 13:09:56 +01:00
|
|
|
}
|
|
|
|
|
2015-05-01 17:56:32 +02:00
|
|
|
ti_lib_sys_ctrl_aon_sync();
|
2015-02-25 13:09:56 +01:00
|
|
|
|
2015-05-01 17:56:32 +02:00
|
|
|
watchdog_periodic();
|
2015-02-25 13:09:56 +01:00
|
|
|
|
2015-05-01 17:56:32 +02:00
|
|
|
/* Notify all modules that we're shutting down */
|
2015-02-25 13:09:56 +01:00
|
|
|
for(module = list_head(modules_list); module != NULL;
|
|
|
|
module = module->next) {
|
|
|
|
if(module->shutdown) {
|
|
|
|
module->shutdown(LPM_MODE_SHUTDOWN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-01 17:56:32 +02:00
|
|
|
/* Configure the wakeup trigger */
|
2015-10-28 11:06:26 +01:00
|
|
|
if(wakeup_pin != IOID_UNUSED) {
|
|
|
|
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);
|
|
|
|
}
|
2015-02-25 13:09:56 +01:00
|
|
|
|
2015-05-01 17:56:32 +02:00
|
|
|
/* Freeze I/O latches in AON */
|
|
|
|
ti_lib_aon_ioc_freeze_enable();
|
2015-02-25 13:09:56 +01:00
|
|
|
|
2015-05-01 17:56:32 +02:00
|
|
|
/* 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);
|
|
|
|
|
2016-04-20 17:19:54 +02:00
|
|
|
/* Register an aux-ctrl consumer to avoid powercycling AUX twice in a row */
|
|
|
|
aux_ctrl_register_consumer(&aux);
|
2015-05-01 17:56:32 +02:00
|
|
|
oscillators_switch_to_hf_rc();
|
|
|
|
oscillators_select_lf_rcosc();
|
|
|
|
|
2016-04-20 17:19:54 +02:00
|
|
|
/* Configure clock sources for MCU: No clock */
|
2015-05-01 17:56:32 +02:00
|
|
|
ti_lib_aon_wuc_mcu_power_down_config(AONWUC_NO_CLOCK);
|
|
|
|
|
2016-04-20 17:19:54 +02:00
|
|
|
/* Disable SRAM retention */
|
2015-05-01 17:56:32 +02:00
|
|
|
ti_lib_aon_wuc_mcu_sram_config(0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 */
|
2016-04-20 17:19:54 +02:00
|
|
|
aux_ctrl_power_down(true);
|
2015-05-01 17:56:32 +02:00
|
|
|
ti_lib_aon_wuc_domain_power_down_enable();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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);
|
2015-02-25 13:09:56 +01:00
|
|
|
|
2015-05-01 17:56:32 +02:00
|
|
|
/* Latch the IOs in the padring and enable I/O pad sleep mode */
|
|
|
|
ti_lib_pwr_ctrl_io_freeze_enable();
|
2015-02-25 13:09:56 +01:00
|
|
|
|
2015-05-01 17:56:32 +02:00
|
|
|
/* Turn off VIMS cache, CRAM and TRAM - possibly not required */
|
2015-04-20 16:26:56 +02:00
|
|
|
ti_lib_prcm_cache_retention_disable();
|
2015-05-01 17:56:32 +02:00
|
|
|
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();
|
2015-02-25 13:09:56 +01:00
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/*
|
2015-05-01 17:09:50 +02:00
|
|
|
* Notify all modules that we're back on and rely on them to restore clocks
|
2015-02-25 13:09:56 +01:00
|
|
|
* and power domains as required.
|
|
|
|
*/
|
2015-05-01 17:09:50 +02:00
|
|
|
static void
|
|
|
|
wake_up(void)
|
2015-02-25 13:09:56 +01:00
|
|
|
{
|
|
|
|
lpm_registered_module_t *module;
|
|
|
|
|
|
|
|
/* Remember IRQ energest for next pass */
|
|
|
|
ENERGEST_IRQ_SAVE(irq_energest);
|
2015-09-03 16:06:46 +02:00
|
|
|
ENERGEST_SWITCH(ENERGEST_TYPE_LPM, ENERGEST_TYPE_CPU);
|
2015-02-25 13:09:56 +01:00
|
|
|
|
|
|
|
/* Sync so that we get the latest values before adjusting recharge settings */
|
|
|
|
ti_lib_sys_ctrl_aon_sync();
|
|
|
|
|
|
|
|
/* Adjust recharge settings */
|
|
|
|
ti_lib_sys_ctrl_adjust_recharge_after_power_down();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Release the request to the uLDO
|
|
|
|
* This is likely not required, since the switch to GLDO/DCDC is automatic
|
|
|
|
* when coming back from deep sleep
|
|
|
|
*/
|
|
|
|
ti_lib_prcm_mcu_uldo_configure(false);
|
|
|
|
|
|
|
|
/* Turn on cache again */
|
|
|
|
ti_lib_vims_mode_set(VIMS_BASE, VIMS_MODE_ENABLED);
|
2015-04-20 16:26:56 +02:00
|
|
|
ti_lib_prcm_cache_retention_enable();
|
2015-02-25 13:09:56 +01:00
|
|
|
|
|
|
|
ti_lib_aon_ioc_freeze_disable();
|
|
|
|
ti_lib_sys_ctrl_aon_sync();
|
|
|
|
|
2015-05-01 17:56:32 +02:00
|
|
|
/* Check operating conditions, optimally choose DCDC versus GLDO */
|
|
|
|
ti_lib_sys_ctrl_dcdc_voltage_conditional_control();
|
2015-02-25 13:09:56 +01:00
|
|
|
|
2016-04-20 17:19:54 +02:00
|
|
|
/* Fire up AUX is the user has requested this */
|
|
|
|
aux_ctrl_power_up();
|
|
|
|
|
2015-08-16 17:30:04 +02:00
|
|
|
/*
|
|
|
|
* We may or may not have been woken up by an AON RTC tick. If not, we need
|
|
|
|
* to adjust our software tick counter
|
|
|
|
*/
|
|
|
|
clock_update();
|
|
|
|
|
|
|
|
watchdog_periodic();
|
|
|
|
|
2015-02-25 13:09:56 +01:00
|
|
|
/* Notify all registered modules that we've just woken up */
|
|
|
|
for(module = list_head(modules_list); module != NULL;
|
|
|
|
module = module->next) {
|
|
|
|
if(module->wakeup) {
|
|
|
|
module->wakeup();
|
|
|
|
}
|
|
|
|
}
|
2016-04-25 17:08:34 +02:00
|
|
|
|
|
|
|
#if CC2650_FAST_RADIO_STARTUP
|
|
|
|
/*
|
|
|
|
* Trigger a switch to the XOSC, so that we can subsequently use the RF FS
|
|
|
|
*/
|
|
|
|
oscillators_request_hf_xosc();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
2016-06-27 18:48:15 +02:00
|
|
|
static uint8_t
|
|
|
|
check_next_rtimer(rtimer_clock_t now, rtimer_clock_t *next_rtimer, bool *next_rtimer_set)
|
2016-04-25 17:08:34 +02:00
|
|
|
{
|
|
|
|
uint8_t max_pm = LPM_MODE_MAX_SUPPORTED;
|
|
|
|
|
|
|
|
if(ti_lib_aon_rtc_channel_active(AON_RTC_CH0)) {
|
2016-06-27 18:48:15 +02:00
|
|
|
*next_rtimer_set = true;
|
|
|
|
|
2016-04-25 17:08:34 +02:00
|
|
|
/* find out the timer of the next rtimer interrupt */
|
2016-06-27 18:48:15 +02:00
|
|
|
*next_rtimer = ti_lib_aon_rtc_compare_value_get(AON_RTC_CH0);
|
|
|
|
|
|
|
|
if(RTIMER_CLOCK_LT(*next_rtimer, now + 2)) {
|
|
|
|
max_pm = MIN(max_pm, LPM_MODE_AWAKE);
|
|
|
|
} else if(RTIMER_CLOCK_LT(*next_rtimer, now + STANDBY_MIN_DURATION)) {
|
|
|
|
max_pm = MIN(max_pm, LPM_MODE_SLEEP);
|
2016-04-25 17:08:34 +02:00
|
|
|
}
|
2016-06-27 18:48:15 +02:00
|
|
|
} else {
|
|
|
|
*next_rtimer_set = false;
|
2016-04-25 17:08:34 +02:00
|
|
|
}
|
|
|
|
|
2016-06-27 18:48:15 +02:00
|
|
|
return max_pm;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
static uint8_t
|
|
|
|
check_next_etimer(rtimer_clock_t now, rtimer_clock_t *next_etimer, bool *next_etimer_set)
|
|
|
|
{
|
|
|
|
uint8_t max_pm = LPM_MODE_MAX_SUPPORTED;
|
|
|
|
|
|
|
|
*next_etimer_set = false;
|
2016-04-25 17:08:34 +02:00
|
|
|
|
2016-06-27 18:48:15 +02:00
|
|
|
/* Find out the time of the next etimer */
|
|
|
|
if(etimer_pending()) {
|
|
|
|
int32_t until_next_etimer = (int32_t)etimer_next_expiration_time() - (int32_t)clock_time();
|
2016-04-25 17:08:34 +02:00
|
|
|
if(until_next_etimer < 1) {
|
2016-06-27 18:48:15 +02:00
|
|
|
max_pm = MIN(max_pm, LPM_MODE_AWAKE);
|
|
|
|
} else {
|
|
|
|
*next_etimer_set = true;
|
|
|
|
*next_etimer = soc_rtc_last_isr_time() + (until_next_etimer * (RTIMER_SECOND / CLOCK_SECOND));
|
|
|
|
if(RTIMER_CLOCK_LT(*next_etimer, now + STANDBY_MIN_DURATION)) {
|
|
|
|
max_pm = MIN(max_pm, LPM_MODE_SLEEP);
|
|
|
|
}
|
2016-04-25 17:08:34 +02:00
|
|
|
}
|
2016-06-27 18:48:15 +02:00
|
|
|
}
|
2016-04-25 17:08:34 +02:00
|
|
|
|
2016-06-27 18:48:15 +02:00
|
|
|
return max_pm;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
static uint8_t
|
|
|
|
setup_sleep_mode(void)
|
|
|
|
{
|
|
|
|
lpm_registered_module_t *module;
|
|
|
|
uint8_t max_pm = LPM_MODE_MAX_SUPPORTED;
|
|
|
|
uint8_t pm;
|
2016-04-25 17:08:34 +02:00
|
|
|
|
2016-06-27 18:48:15 +02:00
|
|
|
rtimer_clock_t now;
|
|
|
|
rtimer_clock_t next_rtimer = 0;
|
|
|
|
rtimer_clock_t next_etimer = 0;
|
|
|
|
bool next_rtimer_set = false;
|
|
|
|
bool next_etimer_set = false;
|
2016-04-25 17:08:34 +02:00
|
|
|
|
2016-06-27 18:48:15 +02:00
|
|
|
/* Check if any events fired before we turned interrupts off. If so, abort */
|
|
|
|
if(LPM_MODE_MAX_SUPPORTED == LPM_MODE_AWAKE || process_nevents()) {
|
|
|
|
return LPM_MODE_AWAKE;
|
2016-04-25 17:08:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Collect max allowed PM permission from interested modules */
|
|
|
|
for(module = list_head(modules_list); module != NULL;
|
|
|
|
module = module->next) {
|
|
|
|
if(module->request_max_pm) {
|
|
|
|
uint8_t module_pm = module->request_max_pm();
|
|
|
|
if(module_pm < max_pm) {
|
|
|
|
max_pm = module_pm;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-27 18:48:15 +02:00
|
|
|
now = RTIMER_NOW();
|
|
|
|
|
|
|
|
pm = check_next_rtimer(now, &next_rtimer, &next_rtimer_set);
|
|
|
|
if(pm < max_pm) {
|
|
|
|
max_pm = pm;
|
|
|
|
}
|
|
|
|
pm = check_next_etimer(now, &next_etimer, &next_etimer_set);
|
|
|
|
if(pm < max_pm) {
|
|
|
|
max_pm = pm;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(max_pm == LPM_MODE_SLEEP) {
|
|
|
|
if(next_etimer_set) {
|
|
|
|
/* Schedule the next system wakeup due to etimer */
|
|
|
|
if(RTIMER_CLOCK_LT(next_etimer, now + MIN_SAFE_SCHEDULE)) {
|
|
|
|
/* Too soon in future, use this minimal interval instead */
|
|
|
|
next_etimer = now + MIN_SAFE_SCHEDULE;
|
|
|
|
} else if(RTIMER_CLOCK_LT(now + MAX_SLEEP_TIME, next_etimer)) {
|
|
|
|
/* Too far in future, use MAX_SLEEP_TIME instead */
|
|
|
|
next_etimer = now + MAX_SLEEP_TIME;
|
|
|
|
}
|
|
|
|
soc_rtc_schedule_one_shot(AON_RTC_CH1, next_etimer);
|
|
|
|
} else {
|
|
|
|
/* No etimers set. Since by default the CH1 RTC fires once every clock tick,
|
|
|
|
* need to explicitly schedule a wakeup in the future to save energy.
|
|
|
|
* But do not stay in this mode for too long, otherwise watchdog will be trigerred. */
|
|
|
|
soc_rtc_schedule_one_shot(AON_RTC_CH1, now + MAX_SLEEP_TIME);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if(max_pm == LPM_MODE_DEEP_SLEEP) {
|
|
|
|
/* Watchdog is not enabled, so deep sleep can continue an arbitrary long time.
|
|
|
|
* On the other hand, if `CC2650_FAST_RADIO_STARTUP` is defined,
|
|
|
|
* early wakeup before the next rtimer should be scheduled. */
|
|
|
|
|
|
|
|
#if CC2650_FAST_RADIO_STARTUP
|
|
|
|
if(next_rtimer_set) {
|
|
|
|
if(!next_etimer_set || RTIMER_CLOCK_LT(next_rtimer - SLEEP_GUARD_TIME, next_etimer)) {
|
|
|
|
/* schedule a wakeup briefly before the next rtimer to wake up the system */
|
|
|
|
soc_rtc_schedule_one_shot(AON_RTC_CH2, next_rtimer - SLEEP_GUARD_TIME);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if(next_etimer_set) {
|
|
|
|
/* Schedule the next system wakeup due to etimer.
|
|
|
|
* No need to compare the `next_etimer` to `now` here as this branch
|
|
|
|
* is only entered when there's sufficient time for deep sleeping. */
|
|
|
|
soc_rtc_schedule_one_shot(AON_RTC_CH1, next_etimer);
|
|
|
|
} else {
|
|
|
|
/* Use the farthest possible wakeup time */
|
|
|
|
soc_rtc_schedule_one_shot(AON_RTC_CH1, now - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-25 17:08:34 +02:00
|
|
|
return max_pm;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
void
|
|
|
|
lpm_sleep(void)
|
|
|
|
{
|
|
|
|
ENERGEST_SWITCH(ENERGEST_TYPE_CPU, ENERGEST_TYPE_LPM);
|
|
|
|
|
|
|
|
/* We are only interested in IRQ energest while idle or in LPM */
|
|
|
|
ENERGEST_IRQ_RESTORE(irq_energest);
|
|
|
|
|
|
|
|
/* Just to be on the safe side, explicitly disable Deep Sleep */
|
|
|
|
HWREG(NVIC_SYS_CTRL) &= ~(NVIC_SYS_CTRL_SLEEPDEEP);
|
|
|
|
|
|
|
|
ti_lib_prcm_sleep();
|
|
|
|
|
|
|
|
/* Remember IRQ energest for next pass */
|
|
|
|
ENERGEST_IRQ_SAVE(irq_energest);
|
|
|
|
|
|
|
|
ENERGEST_SWITCH(ENERGEST_TYPE_LPM, ENERGEST_TYPE_CPU);
|
2015-02-25 13:09:56 +01:00
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
2016-02-23 18:41:01 +01:00
|
|
|
static void
|
2016-06-27 18:48:15 +02:00
|
|
|
deep_sleep(void)
|
2015-02-25 13:09:56 +01:00
|
|
|
{
|
2016-02-23 18:41:01 +01:00
|
|
|
uint32_t domains = LOCKABLE_DOMAINS;
|
2015-02-25 13:09:56 +01:00
|
|
|
lpm_registered_module_t *module;
|
|
|
|
|
2016-02-23 18:41:01 +01:00
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
}
|
2015-02-25 13:09:56 +01:00
|
|
|
|
2016-02-23 18:41:01 +01:00
|
|
|
/* 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;
|
2015-11-09 14:36:11 +01:00
|
|
|
|
2016-02-23 18:41:01 +01:00
|
|
|
/*
|
|
|
|
* 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();
|
2015-11-09 14:36:11 +01:00
|
|
|
}
|
|
|
|
|
2016-02-23 18:41:01 +01:00
|
|
|
/*
|
|
|
|
* 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
|
2016-04-25 17:08:34 +02:00
|
|
|
* clock source should already be the HF RC, unless CC2650_FAST_RADIO_STARTUP
|
|
|
|
* is defined.
|
2016-02-23 18:41:01 +01:00
|
|
|
*
|
|
|
|
* Nevertheless, request the switch to the HF RC explicitly here.
|
|
|
|
*/
|
|
|
|
oscillators_switch_to_hf_rc();
|
|
|
|
|
2016-04-20 17:19:54 +02:00
|
|
|
/* Shut Down the AUX if the user application is not using it */
|
|
|
|
aux_ctrl_power_down(false);
|
|
|
|
|
|
|
|
/* Configure clock sources for MCU: No clock */
|
2016-02-23 18:41:01 +01:00
|
|
|
ti_lib_aon_wuc_mcu_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);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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();
|
|
|
|
|
2016-04-20 17:19:54 +02:00
|
|
|
/* Allow MCU and AUX powerdown */
|
2016-02-23 18:41:01 +01:00
|
|
|
ti_lib_aon_wuc_domain_power_down_enable();
|
|
|
|
|
|
|
|
/* 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();
|
2015-02-25 13:09:56 +01:00
|
|
|
|
2016-04-25 17:08:34 +02:00
|
|
|
ti_lib_int_master_enable();
|
2016-02-23 18:41:01 +01:00
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
void
|
|
|
|
lpm_drop()
|
|
|
|
{
|
|
|
|
uint8_t max_pm;
|
2015-02-25 13:09:56 +01:00
|
|
|
|
2016-02-23 18:41:01 +01:00
|
|
|
/* Critical. Don't get interrupted! */
|
|
|
|
ti_lib_int_master_disable();
|
2015-02-25 13:09:56 +01:00
|
|
|
|
2016-06-27 18:48:15 +02:00
|
|
|
max_pm = setup_sleep_mode();
|
2015-02-25 13:09:56 +01:00
|
|
|
|
2016-02-23 18:41:01 +01:00
|
|
|
/* Drop */
|
|
|
|
if(max_pm == LPM_MODE_SLEEP) {
|
|
|
|
lpm_sleep();
|
|
|
|
} else if(max_pm == LPM_MODE_DEEP_SLEEP) {
|
2016-06-27 18:48:15 +02:00
|
|
|
deep_sleep();
|
2015-02-25 13:09:56 +01:00
|
|
|
}
|
2016-02-23 18:41:01 +01:00
|
|
|
|
|
|
|
ti_lib_int_master_enable();
|
2015-02-25 13:09:56 +01:00
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
void
|
|
|
|
lpm_register_module(lpm_registered_module_t *module)
|
|
|
|
{
|
|
|
|
list_add(modules_list, module);
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
void
|
2015-05-01 17:38:07 +02:00
|
|
|
lpm_unregister_module(lpm_registered_module_t *module)
|
|
|
|
{
|
|
|
|
list_remove(modules_list, module);
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
void
|
2015-02-25 13:09:56 +01:00
|
|
|
lpm_init()
|
|
|
|
{
|
|
|
|
list_init(modules_list);
|
2015-08-16 17:30:04 +02:00
|
|
|
|
|
|
|
/* Always wake up on any DIO edge detection */
|
2016-04-25 17:08:34 +02:00
|
|
|
ti_lib_aon_event_mcu_wake_up_set(AON_EVENT_MCU_WU3, AON_EVENT_IO);
|
2015-02-25 13:09:56 +01:00
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
2015-05-01 17:10:45 +02:00
|
|
|
void
|
|
|
|
lpm_pin_set_default_state(uint32_t ioid)
|
|
|
|
{
|
|
|
|
if(ioid == IOID_UNUSED) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ti_lib_ioc_port_configure_set(ioid, IOC_PORT_GPIO, IOC_STD_OUTPUT);
|
|
|
|
ti_lib_gpio_dir_mode_set((1 << ioid), GPIO_DIR_MODE_IN);
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
2015-02-25 13:09:56 +01:00
|
|
|
/**
|
|
|
|
* @}
|
|
|
|
* @}
|
|
|
|
*/
|