Merge pull request #408 from ADVANSEE/cc2538-uart

cc2538: lpm: Add registration mechanism for peripherals and use it
This commit is contained in:
George Oikonomou 2013-11-15 12:57:03 -08:00
commit 98a1f2dfa9
5 changed files with 97 additions and 12 deletions

View file

@ -41,8 +41,10 @@
#include "dev/ioc.h"
#include "dev/gpio.h"
#include "dev/uart.h"
#include "lpm.h"
#include "reg.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
@ -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;

View file

@ -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 <stdbool.h>
#include <stdint.h>
#include <string.h>
/*---------------------------------------------------------------------------*/
@ -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()
{
/*

View file

@ -47,6 +47,7 @@
#include "contiki-conf.h"
#include "rtimer.h"
#include <stdbool.h>
#include <stdint.h>
/*---------------------------------------------------------------------------*/
/**
@ -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()

View file

@ -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 <stdbool.h>
#include <stdint.h>
/*---------------------------------------------------------------------------*/
/* 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;

View file

@ -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.