cc2538: lpm: Add registration mechanism for peripherals

Some peripherals have their clocks automatically gated in PM1+ modes, so they
cannot operate. This new mechanism gives peripherals a way to prohibit PM1+
modes so that they can properly complete their current operations before
entering PM1+.

This mechanism is implemented with peripheral functions registered to the LPM
module. These functions return whether the associated peripheral permits or not
PM1+ modes. They are called by the LPM module each time PM1+ might be possible.
If any of the peripherals wants to block PM1+, then the system is only dropped
to PM0.

Partly from: George Oikonomou
Signed-off-by: Benoît Thébaudeau <benoit.thebaudeau@advansee.com>
This commit is contained in:
Benoît Thébaudeau 2013-11-15 16:21:34 +01:00
parent a6227e1e3e
commit d35732505b
3 changed files with 71 additions and 6 deletions

View file

@ -43,8 +43,10 @@
#include "dev/rfcore-xreg.h"
#include "dev/usb-regs.h"
#include "rtimer-arch.h"
#include "lpm.h"
#include "reg.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
/*---------------------------------------------------------------------------*/
@ -97,6 +99,30 @@ void clock_adjust(clock_time_t ticks);
/* Stores the currently specified MAX allowed PM */
static uint8_t max_pm;
/*---------------------------------------------------------------------------*/
/* Buffer to store peripheral PM1+ permission FPs */
#ifdef LPM_CONF_PERIPH_PERMIT_PM1_FUNCS_MAX
#define LPM_PERIPH_PERMIT_PM1_FUNCS_MAX LPM_CONF_PERIPH_PERMIT_PM1_FUNCS_MAX
#else
#define LPM_PERIPH_PERMIT_PM1_FUNCS_MAX 0
#endif
lpm_periph_permit_pm1_func_t
periph_permit_pm1_funcs[LPM_PERIPH_PERMIT_PM1_FUNCS_MAX];
/*---------------------------------------------------------------------------*/
static bool
periph_permit_pm1(void)
{
int i;
for(i = 0; i < LPM_PERIPH_PERMIT_PM1_FUNCS_MAX &&
periph_permit_pm1_funcs[i] != NULL; i++) {
if(!periph_permit_pm1_funcs[i]()) {
return false;
}
}
return true;
}
/*---------------------------------------------------------------------------*/
/*
* Routine to put is in PM0. We also need to do some housekeeping if the stats
* or the energest module is enabled
@ -191,15 +217,15 @@ lpm_enter()
rtimer_clock_t duration;
/*
* If either the RF or the USB is on, dropping to PM1/2 would equal pulling
* the rug (32MHz XOSC) from under their feet. Thus, we only drop to PM0.
* PM0 is also used if max_pm==0
* If either the RF, the USB or the registered peripherals are on, dropping to
* PM1/2 would equal pulling the rug (32MHz XOSC) from under their feet. Thus,
* we only drop to PM0. PM0 is also used if max_pm==0.
*
* Note: USB Suspend/Resume/Remote Wake-Up are not supported. Once the PLL is
* on, it stays on.
*/
if((REG(RFCORE_XREG_FSMSTAT0) & RFCORE_XREG_FSMSTAT0_FSM_FFCTRL_STATE) != 0
|| REG(USB_CTRL) != 0 || max_pm == 0) {
|| REG(USB_CTRL) != 0 || !periph_permit_pm1() || max_pm == 0) {
enter_pm0();
/* We reach here when the interrupt context that woke us up has returned */
@ -207,7 +233,8 @@ lpm_enter()
}
/*
* USB PLL was off. Radio was off: Some Duty Cycling in place.
* Registered peripherals were off. USB PLL was off. Radio was off: Some Duty
* Cycling in place.
* rtimers run on the Sleep Timer. Thus, if we have a scheduled rtimer
* task, a Sleep Timer interrupt will fire and will wake us up.
* Choose the most suitable PM based on anticipated deep sleep duration
@ -224,7 +251,8 @@ lpm_enter()
}
/* If we reach here, we -may- (but may as well not) be dropping to PM1+. We
* know the USB and RF are off so we can switch to the 16MHz RCOSC. */
* know the registered peripherals, USB and RF are off so we can switch to the
* 16MHz RCOSC. */
select_16_mhz_rcosc();
/*
@ -299,6 +327,21 @@ lpm_set_max_pm(uint8_t pm)
}
/*---------------------------------------------------------------------------*/
void
lpm_register_peripheral(lpm_periph_permit_pm1_func_t permit_pm1_func)
{
int i;
for(i = 0; i < LPM_PERIPH_PERMIT_PM1_FUNCS_MAX; i++) {
if(periph_permit_pm1_funcs[i] == permit_pm1_func) {
break;
} else if(periph_permit_pm1_funcs[i] == NULL) {
periph_permit_pm1_funcs[i] = permit_pm1_func;
break;
}
}
}
/*---------------------------------------------------------------------------*/
void
lpm_init()
{
/*

View file

@ -47,6 +47,7 @@
#include "contiki-conf.h"
#include "rtimer.h"
#include <stdbool.h>
#include <stdint.h>
/*---------------------------------------------------------------------------*/
/**
@ -101,6 +102,7 @@ void lpm_init(void);
* This PM selection heuristic has the following primary criteria:
* - Is the RF off?
* - Is the USB PLL 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
@ -173,6 +175,25 @@ void lpm_exit(void);
*/
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.
*
* 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()

View file

@ -389,6 +389,7 @@ In a nutshell, the algorithm first answers the following questions:
* Is the RF off?
* Is the USB PLL off?
* Are all registered peripherals permitting PM1+?
* Is the Sleep Timer scheduled to fire an interrupt?
If the answer to any of the above question is "No", the SoC will enter PM0. If the answer to all questions is "Yes", the SoC will enter one of PMs 0/1/2 depending on the expected Deep Sleep duration and subject to user configuration and application requirements.