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 4c0ec18ee..709665e5b 100644 --- a/cpu/cc2538/lpm.c +++ b/cpu/cc2538/lpm.c @@ -41,10 +41,11 @@ #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" +#include #include #include /*---------------------------------------------------------------------------*/ @@ -97,6 +98,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 2 +#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 +216,12 @@ 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 - * - * 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 || max_pm == 0) { + || !periph_permit_pm1() || max_pm == 0) { enter_pm0(); /* We reach here when the interrupt context that woke us up has returned */ @@ -207,7 +229,7 @@ lpm_enter() } /* - * 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 @@ -224,7 +246,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 and RF are off so we can switch to the + * 16MHz RCOSC. */ select_16_mhz_rcosc(); /* @@ -299,6 +322,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..22b4d4958 100644 --- a/cpu/cc2538/lpm.h +++ b/cpu/cc2538/lpm.h @@ -47,6 +47,7 @@ #include "contiki-conf.h" #include "rtimer.h" +#include #include /*---------------------------------------------------------------------------*/ /** @@ -100,7 +101,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 +174,26 @@ 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. 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() 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 32fb2bbc4..ada90abd1 100644 --- a/platform/cc2538dk/README.md +++ b/platform/cc2538dk/README.md @@ -388,7 +388,7 @@ 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? 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.