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:
parent
a6227e1e3e
commit
d35732505b
|
@ -43,8 +43,10 @@
|
||||||
#include "dev/rfcore-xreg.h"
|
#include "dev/rfcore-xreg.h"
|
||||||
#include "dev/usb-regs.h"
|
#include "dev/usb-regs.h"
|
||||||
#include "rtimer-arch.h"
|
#include "rtimer-arch.h"
|
||||||
|
#include "lpm.h"
|
||||||
#include "reg.h"
|
#include "reg.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
|
@ -97,6 +99,30 @@ void clock_adjust(clock_time_t ticks);
|
||||||
/* Stores the currently specified MAX allowed PM */
|
/* Stores the currently specified MAX allowed PM */
|
||||||
static uint8_t max_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
|
* Routine to put is in PM0. We also need to do some housekeeping if the stats
|
||||||
* or the energest module is enabled
|
* or the energest module is enabled
|
||||||
|
@ -191,15 +217,15 @@ lpm_enter()
|
||||||
rtimer_clock_t duration;
|
rtimer_clock_t duration;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If either the RF or the USB is on, dropping to PM1/2 would equal pulling
|
* If either the RF, the USB or the registered peripherals are on, dropping to
|
||||||
* the rug (32MHz XOSC) from under their feet. Thus, we only drop to PM0.
|
* PM1/2 would equal pulling the rug (32MHz XOSC) from under their feet. Thus,
|
||||||
* PM0 is also used if max_pm==0
|
* 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
|
* Note: USB Suspend/Resume/Remote Wake-Up are not supported. Once the PLL is
|
||||||
* on, it stays on.
|
* on, it stays on.
|
||||||
*/
|
*/
|
||||||
if((REG(RFCORE_XREG_FSMSTAT0) & RFCORE_XREG_FSMSTAT0_FSM_FFCTRL_STATE) != 0
|
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();
|
enter_pm0();
|
||||||
|
|
||||||
/* We reach here when the interrupt context that woke us up has returned */
|
/* 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
|
* 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.
|
* task, a Sleep Timer interrupt will fire and will wake us up.
|
||||||
* Choose the most suitable PM based on anticipated deep sleep duration
|
* 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
|
/* 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();
|
select_16_mhz_rcosc();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -299,6 +327,21 @@ lpm_set_max_pm(uint8_t pm)
|
||||||
}
|
}
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
void
|
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()
|
lpm_init()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
#include "contiki-conf.h"
|
#include "contiki-conf.h"
|
||||||
#include "rtimer.h"
|
#include "rtimer.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
/*---------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------*/
|
||||||
/**
|
/**
|
||||||
|
@ -101,6 +102,7 @@ void lpm_init(void);
|
||||||
* This PM selection heuristic has the following primary criteria:
|
* This PM selection heuristic has the following primary criteria:
|
||||||
* - Is the RF off?
|
* - Is the RF off?
|
||||||
* - Is the USB PLL off?
|
* - Is the USB PLL off?
|
||||||
|
* - Are all registered peripherals permitting PM1+?
|
||||||
* - Is the Sleep Timer scheduled to fire an interrupt?
|
* - 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
|
* 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);
|
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 */
|
/* Disable the entire module if required */
|
||||||
#if LPM_CONF_ENABLE==0
|
#if LPM_CONF_ENABLE==0
|
||||||
#define lpm_init()
|
#define lpm_init()
|
||||||
|
|
|
@ -389,6 +389,7 @@ In a nutshell, the algorithm first answers the following questions:
|
||||||
|
|
||||||
* Is the RF off?
|
* Is the RF off?
|
||||||
* Is the USB PLL off?
|
* Is the USB PLL off?
|
||||||
|
* Are all registered peripherals permitting PM1+?
|
||||||
* Is the Sleep Timer scheduled to fire an interrupt?
|
* 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.
|
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.
|
||||||
|
|
Loading…
Reference in a new issue