/* * 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 * @{ * * Implementation of CC26xx low-power operation functionality * * @{ * * \file * Driver for CC26xx's low-power operation */ /*---------------------------------------------------------------------------*/ #include "prcm.h" #include "contiki-conf.h" #include "ti-lib.h" #include "lpm.h" #include "sys/energest.h" #include "lib/list.h" #include "dev/leds.h" #include "dev/watchdog.h" #include "dev/cc26xx-rtc.h" /*---------------------------------------------------------------------------*/ #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); /*---------------------------------------------------------------------------*/ /* Control what power domains we are allow to run under what mode */ LIST(power_domain_locks_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 */ #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_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; for(module = list_head(modules_list); module != NULL; module = module->next) { if(module->shutdown) { module->shutdown(LPM_MODE_SHUTDOWN); } } leds_off(LEDS_ALL); for(i = 0; i < 5; i++) { t0 = RTIMER_NOW(); leds_toggle(LEDS_ALL); while(RTIMER_CLOCK_LT(RTIMER_NOW(), (t0 + (RTIMER_SECOND >> 3)))); } leds_off(LEDS_ALL); ti_lib_gpio_dir_mode_set((1 << pin), GPIO_DIR_MODE_IN); ti_lib_ioc_port_configure_set(pin, IOC_PORT_GPIO, io_cfg); ti_lib_pwr_ctrl_state_set(LPM_MODE_SHUTDOWN); } /*---------------------------------------------------------------------------*/ /* * We'll get called on three occasions: * - While running * - While sleeping * - While deep sleeping * * For the former two, we don't need to do anything. For the latter, we * notify all modules that we're back on and rely on them to restore clocks * and power domains as required. */ void lpm_wake_up() { lpm_registered_module_t *module; /* Remember IRQ energest for next pass */ ENERGEST_IRQ_SAVE(irq_energest); ENERGEST_ON(ENERGEST_TYPE_CPU); ENERGEST_OFF(ENERGEST_TYPE_LPM); /* 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); ti_lib_prcm_retention_enable(PRCM_DOMAIN_VIMS); 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); /* 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(); } } } /*---------------------------------------------------------------------------*/ 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(); return; } /* Collect max allowed PM permission from interested modules */ for(module = list_head(modules_list); module != NULL; module = module->next) { if(module->request_max_pm) { module_pm = module->request_max_pm(); if(module_pm < max_pm) { max_pm = module_pm; } } } /* Check if any events fired during this process. Last chance to abort */ if(process_nevents()) { return; } /* Drop */ if(max_pm == LPM_MODE_SLEEP) { lpm_sleep(); } else { /* 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. */ 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; } /* 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); } /* 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); /* Enable retention on the CPU domain */ ti_lib_prcm_retention_enable(PRCM_DOMAIN_CPU); /* Disable retention of AUX RAM */ 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; /* * 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() & AONWUC_AUX_POWER_ON); /* Configure the recharge controller */ 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 * 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_OFF(ENERGEST_TYPE_CPU); ENERGEST_ON(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_retention_disable(PRCM_DOMAIN_VIMS); 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 */ lpm_wake_up(); ti_lib_int_master_enable(); } } /*---------------------------------------------------------------------------*/ void lpm_sleep(void) { ENERGEST_OFF(ENERGEST_TYPE_CPU); ENERGEST_ON(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_ON(ENERGEST_TYPE_CPU); ENERGEST_OFF(ENERGEST_TYPE_LPM); } /*---------------------------------------------------------------------------*/ void lpm_register_module(lpm_registered_module_t *module) { list_add(modules_list, module); } /*---------------------------------------------------------------------------*/ void lpm_init() { list_init(modules_list); list_init(power_domain_locks_list); } /*---------------------------------------------------------------------------*/ /** * @} * @} */