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 1/3] 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. From 0692ee251d023313bd2b0bb61d150726ad3d42ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Th=C3=A9baudeau?= Date: Fri, 15 Nov 2013 17:14:45 +0100 Subject: [PATCH 2/3] cc2538: usb: Use the new LPM peripheral registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Benoît Thébaudeau --- cpu/cc2538/lpm.c | 19 +++++++------------ cpu/cc2538/lpm.h | 4 ++-- cpu/cc2538/usb/usb-arch.c | 14 ++++++++++++++ platform/cc2538dk/README.md | 1 - 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/cpu/cc2538/lpm.c b/cpu/cc2538/lpm.c index 5642ef023..70dca181e 100644 --- a/cpu/cc2538/lpm.c +++ b/cpu/cc2538/lpm.c @@ -41,7 +41,6 @@ #include "dev/sys-ctrl.h" #include "dev/scb.h" #include "dev/rfcore-xreg.h" -#include "dev/usb-regs.h" #include "rtimer-arch.h" #include "lpm.h" #include "reg.h" @@ -103,7 +102,7 @@ static uint8_t max_pm; #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 +#define LPM_PERIPH_PERMIT_PM1_FUNCS_MAX 1 #endif lpm_periph_permit_pm1_func_t @@ -217,15 +216,12 @@ lpm_enter() rtimer_clock_t duration; /* - * 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 either the RF 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. */ if((REG(RFCORE_XREG_FSMSTAT0) & RFCORE_XREG_FSMSTAT0_FSM_FFCTRL_STATE) != 0 - || REG(USB_CTRL) != 0 || !periph_permit_pm1() || max_pm == 0) { + || !periph_permit_pm1() || max_pm == 0) { enter_pm0(); /* We reach here when the interrupt context that woke us up has returned */ @@ -233,8 +229,7 @@ lpm_enter() } /* - * Registered peripherals were off. USB PLL was off. Radio was off: Some Duty - * Cycling in place. + * Registered peripherals were 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 @@ -251,7 +246,7 @@ lpm_enter() } /* If we reach here, we -may- (but may as well not) be dropping to PM1+. We - * know the registered peripherals, USB and RF are off so we can switch to the + * know the registered peripherals and RF are off so we can switch to the * 16MHz RCOSC. */ select_16_mhz_rcosc(); diff --git a/cpu/cc2538/lpm.h b/cpu/cc2538/lpm.h index b33322ad9..cd8b2b510 100644 --- a/cpu/cc2538/lpm.h +++ b/cpu/cc2538/lpm.h @@ -101,7 +101,6 @@ 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? * @@ -182,7 +181,8 @@ typedef bool (*lpm_periph_permit_pm1_func_t)(void); * module to get 'permission' to drop to PM1+ * \param permit_pm1_func Pointer to the function * - * Some peripherals are sensitive to PM changes. + * Some peripherals are sensitive to PM changes. For instance, we don't want to + * drop to PM1+ if the USB PLL is active. * * 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 diff --git a/cpu/cc2538/usb/usb-arch.c b/cpu/cc2538/usb/usb-arch.c index 5ed1eee8e..315f6f0cb 100644 --- a/cpu/cc2538/usb/usb-arch.c +++ b/cpu/cc2538/usb/usb-arch.c @@ -46,10 +46,12 @@ #include "dev/ioc.h" #include "dev/udma.h" #include "sys/clock.h" +#include "lpm.h" #include "reg.h" #include "dev/watchdog.h" +#include #include /*---------------------------------------------------------------------------*/ /* EP max FIFO sizes without double buffering */ @@ -303,12 +305,24 @@ reset(void) usb_arch_setup_control_endpoint(0); } /*---------------------------------------------------------------------------*/ +static bool +permit_pm1(void) +{ + /* + * Note: USB Suspend/Resume/Remote Wake-Up are not supported. Once the PLL is + * on, it stays on. + */ + return REG(USB_CTRL) == 0; +} +/*---------------------------------------------------------------------------*/ /* Init USB */ void usb_arch_setup(void) { uint8_t i; + lpm_register_peripheral(permit_pm1); + /* Switch on USB PLL & USB module */ REG(USB_CTRL) = USB_CTRL_USB_EN | USB_CTRL_PLL_EN; diff --git a/platform/cc2538dk/README.md b/platform/cc2538dk/README.md index 2d8e19d5f..ada90abd1 100644 --- a/platform/cc2538dk/README.md +++ b/platform/cc2538dk/README.md @@ -388,7 +388,6 @@ The Low-Power module uses a simple heuristic to determine the best power mode, d 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? From b8b54a033c67e2dbb98dabda6d1b4c9e64d5db90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Th=C3=A9baudeau?= Date: Fri, 15 Nov 2013 17:24:26 +0100 Subject: [PATCH 3/3] cc2538: uart: Fix crippled output occurring upon lpm_enter() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lpm_enter() must not enter PM1+ if the UART TX FIFO is not empty. Otherwise, the UART clock gets disabled, and its TX is broken. Signed-off-by: Benoît Thébaudeau --- cpu/cc2538/dev/uart.c | 12 ++++++++++++ cpu/cc2538/lpm.c | 2 +- cpu/cc2538/lpm.h | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/cpu/cc2538/dev/uart.c b/cpu/cc2538/dev/uart.c index 8545cf4e2..304f717db 100644 --- a/cpu/cc2538/dev/uart.c +++ b/cpu/cc2538/dev/uart.c @@ -41,8 +41,10 @@ #include "dev/ioc.h" #include "dev/gpio.h" #include "dev/uart.h" +#include "lpm.h" #include "reg.h" +#include #include #include @@ -96,9 +98,19 @@ reset(void) REG(UART_BASE | UART_CTL) |= UART_CTL_UARTEN; } /*---------------------------------------------------------------------------*/ +static bool +permit_pm1(void) +{ + /* Note: UART_FR.TXFE reads 0 if the UART clock is gated. */ + return (REG(SYS_CTRL_RCGCUART) & SYS_CTRL_RCGCUART_UART) == 0 || + (REG(UART_BASE | UART_FR) & UART_FR_TXFE) != 0; +} +/*---------------------------------------------------------------------------*/ void uart_init(void) { + lpm_register_peripheral(permit_pm1); + /* Enable clock for the UART while Running, in Sleep and Deep Sleep */ REG(SYS_CTRL_RCGCUART) |= SYS_CTRL_RCGCUART_UART; REG(SYS_CTRL_SCGCUART) |= SYS_CTRL_SCGCUART_UART; diff --git a/cpu/cc2538/lpm.c b/cpu/cc2538/lpm.c index 70dca181e..709665e5b 100644 --- a/cpu/cc2538/lpm.c +++ b/cpu/cc2538/lpm.c @@ -102,7 +102,7 @@ static uint8_t max_pm; #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 1 +#define LPM_PERIPH_PERMIT_PM1_FUNCS_MAX 2 #endif lpm_periph_permit_pm1_func_t diff --git a/cpu/cc2538/lpm.h b/cpu/cc2538/lpm.h index cd8b2b510..22b4d4958 100644 --- a/cpu/cc2538/lpm.h +++ b/cpu/cc2538/lpm.h @@ -182,7 +182,7 @@ typedef bool (*lpm_periph_permit_pm1_func_t)(void); * \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. + * 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