mirror of
https://github.com/oliverschmidt/contiki.git
synced 2024-08-14 09:28:59 +00:00
When returning from PM1/2, the sleep timer value (used by RTIMER_NOW()) is not up-to-date until a positive edge on the 32-kHz clock has been detected after the system clock restarted. To ensure an updated value is read, wait for a positive transition on the 32-kHz clock by polling the SYS_CTRL_CLOCK_STA.SYNC_32K bit, before reading the sleep timer value. Because of this RTIMER_NOW() fixup, lpm_exit() has to be called at the very beginning of ISRs waking up the SoC. This also ensures that all clocks and timers are enabled at the correct frequency and updated before using them following wake-up. Without this fix, etimers could sometimes (randomly, depending on timings) become ultra slow (observed from 10x to 40x slower than normal) if the system exited PM1/2 very often. This issue occurred more often with PM1. Signed-off-by: Benoît Thébaudeau <benoit.thebaudeau@advansee.com>
223 lines
8.5 KiB
C
223 lines
8.5 KiB
C
/*
|
|
* Copyright (c) 2013, 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 cc2538
|
|
* @{
|
|
*
|
|
* \defgroup cc2538-lpm cc2538 Low Power Modes
|
|
*
|
|
* Driver for the cc2538 power modes
|
|
* @{
|
|
*
|
|
* \file
|
|
* Header file with register, macro and function declarations for the cc2538
|
|
* low power module
|
|
*/
|
|
#ifndef LPM_H_
|
|
#define LPM_H_
|
|
|
|
#include "contiki-conf.h"
|
|
#include "rtimer.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* \name LPM stats
|
|
*
|
|
* Maintains a record of how many rtimer ticks spent in each Power Mode.
|
|
* Mainly used for debugging the module
|
|
* @{
|
|
*/
|
|
#if LPM_CONF_STATS
|
|
extern rtimer_clock_t lpm_stats[3];
|
|
|
|
/**
|
|
* \brief Read the time spent in a PM in rtimer ticks
|
|
* \param pm The pm as a value in [0,2]
|
|
*/
|
|
#define LPM_STATS_GET(pm) lpm_stats[pm]
|
|
#else
|
|
#define LPM_STATS_GET(pm)
|
|
#endif
|
|
/** @} */
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* \name Constants to be used as arguments to lpm_set_max_pm()
|
|
* @{
|
|
*/
|
|
#define LPM_PM0 0
|
|
#define LPM_PM1 1
|
|
#define LPM_PM2 2
|
|
/** @} */
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* \brief Initialise the LPM module
|
|
*/
|
|
void lpm_init(void);
|
|
|
|
/**
|
|
* \brief Drop to Deep Sleep
|
|
*
|
|
* This function triggers a sequence to enter Deep Sleep. The sequence involves
|
|
* determining the most suitable PM and switching the system clock source to
|
|
* the 16MHz if required. If the energest module is enabled, the sequence also
|
|
* performs some simple energest calculations.
|
|
*
|
|
* Broadly speaking, this function will be called from the main loop when all
|
|
* events have been serviced. This functions aims to be clever enough in order
|
|
* to be able to choose between PMs 0/1/2 depending on chip status and
|
|
* anticipated sleep duration. This choice is made subject to configuration
|
|
* restrictions and subject to restrictions imposed by calls to
|
|
* lpm_set_max_pm().
|
|
*
|
|
* This PM selection heuristic has the following primary criteria:
|
|
* - Is the RF off?
|
|
* - Are all registered peripherals permitting PM1+?
|
|
* - Is the Sleep Timer scheduled to fire an interrupt?
|
|
*
|
|
* If the answer to any of those questions is no, we will drop to PM0 and
|
|
* will wake up to any interrupt. Best case scenario (if nothing else happens),
|
|
* we will idle until the next SysTick in no more than 1000/CLOCK_SECOND ms
|
|
* (7.8125ms).
|
|
*
|
|
* If all can be answered with 'yes', we can drop to PM1/2 knowing that the
|
|
* Sleep Timer will wake us up. Depending on the estimated deep sleep duration
|
|
* and the max PM allowed by user configuration, we select the most efficient
|
|
* Power Mode to drop to. If the duration is too short, we simply IDLE in PM0.
|
|
*
|
|
* Dropping to PM1/2 requires a switch to the 16MHz OSC. We have the option of
|
|
* letting the SoC do this for us automatically. However, if an interrupt fires
|
|
* during this automatic switch, we will need to re-assert WFI. To avoid this
|
|
* complexity, we perform the switch to the 16MHz OSC manually in software and
|
|
* we assert WFI after the transition has been completed. This gives us a
|
|
* chance to bail out if an interrupt fires or an event is raised during the
|
|
* transition. If nothing happens, dropping to PM1+ is un-interruptible and
|
|
* with a deterministic duration. When we wake up, we switch back to the 32MHz
|
|
* OSC manually before handing control back to main. This is implemented in
|
|
* lpm_exit(), which will always be called from within the Sleep Timer ISR
|
|
* context.
|
|
*
|
|
* \note Dropping to PM2 means that data in the SRAM non-retention area will
|
|
* be lost. It is recommended to disable PM2 if the total RAM footprint is
|
|
* larger than what will fit in the retention area.
|
|
* .nrdata* sections can be used to place uninitialized data in the SRAM
|
|
* non-retention area.
|
|
*
|
|
* \sa main(), rtimer_arch_next_trigger(), lpm_exit(), lpm_set_max_pm()
|
|
*/
|
|
void lpm_enter(void);
|
|
|
|
/**
|
|
* \brief Perform an 'Exit Deep Sleep' sequence
|
|
*
|
|
* This routine is called from within the context of the ISR that caused us to
|
|
* come out of PM1/2. It performs a wake up sequence to make sure the 32MHz OSC
|
|
* is back on and the system clock is sourced on it.
|
|
*
|
|
* While in PMs 1 and 2, the system clock stops ticking. This functions adjusts
|
|
* it when we wake up.
|
|
*
|
|
* We always exit PM1/2 as a result of a scheduled rtimer task or a GPIO
|
|
* interrupt. This may lead to other parts of the code trying to use the RF,
|
|
* so we need to switch the clock source \e before said code gets executed.
|
|
*
|
|
* This function also makes sure that the sleep timer value is up-to-date
|
|
* following wake-up from PM1/2 so that RTIMER_NOW() works.
|
|
*
|
|
* \note This function should be called at the very beginning of ISRs waking up
|
|
* the SoC in order to restore all clocks and timers.
|
|
*
|
|
* \sa lpm_enter(), rtimer_isr()
|
|
*/
|
|
void lpm_exit(void);
|
|
|
|
/**
|
|
* \brief Prevent the SoC from dropping to a PM higher than \e max_pm
|
|
* \param pm The highest PM we are allowed to enter, specified as a
|
|
* number in [0, 2]
|
|
*
|
|
* Defines for the \e pm argument are LPM_PMx.
|
|
*
|
|
* This function can be used by software in situations where some power
|
|
* modes are undesirable. If, for example, an application needs to avoid PM2,
|
|
* it would call lpm_set_max_pm(LPM_PM1).
|
|
* If an application wants to avoid PM1 as well, it would call
|
|
* lpm_set_max_pm(LPM_PM0)
|
|
*
|
|
* PM0 can not be disabled at runtime. Use LPM_CONF_ENABLE to disable LPM
|
|
* support altogether
|
|
*
|
|
* \note If the value of argument \e pm is greater than the value of the
|
|
* LPM_CONF_MAX_PM configuration directive, LPM_CONF_MAX_PM is used. Thus
|
|
* if LPM_CONF_MAX_PM==1, calling lpm_set_max_pm(LPM_PM2) would
|
|
* result in a maximum PM set to 1 and all subsequent Deep Sleeps would
|
|
* be limited to either PM0 or PM1.
|
|
*
|
|
* \sa lpm_enter()
|
|
*/
|
|
void lpm_set_max_pm(uint8_t pm);
|
|
/*---------------------------------------------------------------------------*/
|
|
typedef bool (*lpm_periph_permit_pm1_func_t)(void);
|
|
|
|
/**
|
|
* \brief Register a peripheral function which will get called by the LPM
|
|
* module to get 'permission' to drop to PM1+
|
|
* \param permit_pm1_func Pointer to the function
|
|
*
|
|
* Some peripherals are sensitive to PM changes. For instance, we don't want to
|
|
* drop to PM1+ if the USB PLL is active or if the UART TX FIFO is not clear.
|
|
*
|
|
* When changing power modes, the LPM driver will call all FPs registered with
|
|
* this function. The peripheral's function will return true or false to permit
|
|
* / prohibit PM1+ respectively. If at least one peripheral returns false, the
|
|
* SoC will drop to PM0 Deep Sleep instead.
|
|
*
|
|
* Registering several times the same function makes the LPM module behave as if
|
|
* the function had been registered once.
|
|
*/
|
|
void lpm_register_peripheral(lpm_periph_permit_pm1_func_t permit_pm1_func);
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Disable the entire module if required */
|
|
#if LPM_CONF_ENABLE==0
|
|
#define lpm_init()
|
|
#define lpm_enter()
|
|
#define lpm_exit()
|
|
#define lpm_set_max_pm(...)
|
|
#endif
|
|
|
|
#endif /* LPM_H_ */
|
|
|
|
/**
|
|
* @}
|
|
* @}
|
|
*/
|