From d35732505b956555b46cfb38f2ded22441e271e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Th=C3=A9baudeau?= Date: Fri, 15 Nov 2013 16:21:34 +0100 Subject: [PATCH] cc2538: lpm: Add registration mechanism for peripherals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- cpu/cc2538/lpm.c | 55 +++++++++++++++++++++++++++++++++---- cpu/cc2538/lpm.h | 21 ++++++++++++++ platform/cc2538dk/README.md | 1 + 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/cpu/cc2538/lpm.c b/cpu/cc2538/lpm.c index 4c0ec18ee..5642ef023 100644 --- a/cpu/cc2538/lpm.c +++ b/cpu/cc2538/lpm.c @@ -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 #include #include /*---------------------------------------------------------------------------*/ @@ -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() { /* diff --git a/cpu/cc2538/lpm.h b/cpu/cc2538/lpm.h index 08bcf6427..b33322ad9 100644 --- a/cpu/cc2538/lpm.h +++ b/cpu/cc2538/lpm.h @@ -47,6 +47,7 @@ #include "contiki-conf.h" #include "rtimer.h" +#include #include /*---------------------------------------------------------------------------*/ /** @@ -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() diff --git a/platform/cc2538dk/README.md b/platform/cc2538dk/README.md index 32fb2bbc4..2d8e19d5f 100644 --- a/platform/cc2538dk/README.md +++ b/platform/cc2538dk/README.md @@ -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.