Contiki-NG
lpm.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2013, Texas Instruments Incorporated - http://www.ti.com/
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  *
14  * 3. Neither the name of the copyright holder nor the names of its
15  * contributors may be used to endorse or promote products derived
16  * from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29  * OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 /**
32  * \addtogroup cc2538-lpm
33  * @{
34  *
35  * \file
36  * Implementation of low power modes ofr the cc2538
37  */
38 #include "contiki.h"
39 #include "sys/energest.h"
40 #include "sys/process.h"
41 #include "dev/sys-ctrl.h"
42 #include "dev/rfcore-xreg.h"
43 #include "rtimer-arch.h"
44 #include "lpm.h"
45 #include "cc2538_cm3.h"
46 #include "reg.h"
47 
48 #include <stdbool.h>
49 #include <stdint.h>
50 #include <string.h>
51 
52 #if LPM_CONF_ENABLE != 0
53 /*---------------------------------------------------------------------------*/
54 /*
55  * Deep Sleep thresholds in rtimer ticks (~30.5 usec)
56  *
57  * If Deep Sleep duration < DEEP_SLEEP_PM1_THRESHOLD, simply enter PM0
58  * If duration < DEEP_SLEEP_PM2_THRESHOLD drop to PM1
59  * else PM2.
60  */
61 #define DEEP_SLEEP_PM1_THRESHOLD 10
62 #define DEEP_SLEEP_PM2_THRESHOLD 100
63 /*---------------------------------------------------------------------------*/
64 #define assert_wfi() do { __asm("wfi"::); } while(0)
65 /*---------------------------------------------------------------------------*/
66 #if LPM_CONF_STATS
67 rtimer_clock_t lpm_stats[3];
68 
69 #define LPM_STATS_INIT() \
70  do { memset(lpm_stats, 0, sizeof(lpm_stats)); } while(0)
71 #define LPM_STATS_ADD(pm, val) do { lpm_stats[pm] += val; } while(0)
72 #else
73 #define LPM_STATS_INIT()
74 #define LPM_STATS_ADD(stat, val)
75 #endif
76 /*---------------------------------------------------------------------------*/
77 /*
78  * Remembers what time it was when went to deep sleep
79  * This is used when coming out of PM0/1/2 to keep stats
80  */
81 static rtimer_clock_t sleep_enter_time;
82 
83 void clock_adjust(void);
84 /*---------------------------------------------------------------------------*/
85 /* Stores the currently specified MAX allowed PM */
86 static uint8_t max_pm;
87 /*---------------------------------------------------------------------------*/
88 /* Buffer to store peripheral PM1+ permission FPs */
89 #ifdef LPM_CONF_PERIPH_PERMIT_PM1_FUNCS_MAX
90 #define LPM_PERIPH_PERMIT_PM1_FUNCS_MAX LPM_CONF_PERIPH_PERMIT_PM1_FUNCS_MAX
91 #else
92 #define LPM_PERIPH_PERMIT_PM1_FUNCS_MAX 5
93 #endif
94 
95 static lpm_periph_permit_pm1_func_t
96 periph_permit_pm1_funcs[LPM_PERIPH_PERMIT_PM1_FUNCS_MAX];
97 /*---------------------------------------------------------------------------*/
98 static bool
99 periph_permit_pm1(void)
100 {
101  int i;
102 
103  for(i = 0; i < LPM_PERIPH_PERMIT_PM1_FUNCS_MAX &&
104  periph_permit_pm1_funcs[i] != NULL; i++) {
105  if(!periph_permit_pm1_funcs[i]()) {
106  return false;
107  }
108  }
109  return true;
110 }
111 /*---------------------------------------------------------------------------*/
112 /*
113  * Routine to put is in PM0. We also need to do some housekeeping if the stats
114  * or the energest module is enabled
115  */
116 static void
117 enter_pm0(void)
118 {
119  ENERGEST_SWITCH(ENERGEST_TYPE_CPU, ENERGEST_TYPE_LPM);
120 
121  /* Remember the current time so we can keep stats when we wake up */
122  if(LPM_CONF_STATS) {
123  sleep_enter_time = RTIMER_NOW();
124  }
125 
126  assert_wfi();
127 
128  /* We reach here when the interrupt context that woke us up has returned */
129  LPM_STATS_ADD(0, RTIMER_NOW() - sleep_enter_time);
130 
131  ENERGEST_SWITCH(ENERGEST_TYPE_LPM, ENERGEST_TYPE_CPU);
132 }
133 /*---------------------------------------------------------------------------*/
134 static void
135 select_32_mhz_xosc(void)
136 {
137  /* First, make sure there is no ongoing clock source change */
138  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SOURCE_CHANGE) != 0);
139 
140  /* Turn on the 32 MHz XOSC and source the system clock on it. */
141  REG(SYS_CTRL_CLOCK_CTRL) &= ~SYS_CTRL_CLOCK_CTRL_OSC;
142 
143  /* Wait for the switch to take place */
144  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_OSC) != 0);
145 
146  /* Power down the unused oscillator and restore divisors (silicon errata) */
148 #if SYS_CTRL_SYS_DIV == SYS_CTRL_CLOCK_CTRL_SYS_DIV_32MHZ
149  & ~SYS_CTRL_CLOCK_CTRL_SYS_DIV
150 #endif
151 #if SYS_CTRL_IO_DIV == SYS_CTRL_CLOCK_CTRL_IO_DIV_32MHZ
152  & ~SYS_CTRL_CLOCK_CTRL_IO_DIV
153 #endif
154  ) | SYS_CTRL_CLOCK_CTRL_OSC_PD;
155 }
156 /*---------------------------------------------------------------------------*/
157 static void
158 select_16_mhz_rcosc(void)
159 {
160  /*
161  * Power up both oscillators in order to speed up the transition to the 32-MHz
162  * XOSC after wake up. In addition, consider CC2538 silicon errata:
163  * "Possible Incorrect Value of Clock Dividers after PM2 and PM3" and
164  * set system clock divisor / I/O clock divisor to 16 MHz in case they run
165  * at full speed (=32 MHz)
166  */
168 #if SYS_CTRL_SYS_DIV == SYS_CTRL_CLOCK_CTRL_SYS_DIV_32MHZ
169  | SYS_CTRL_CLOCK_CTRL_SYS_DIV_16MHZ
170 #endif
171 #if SYS_CTRL_IO_DIV == SYS_CTRL_CLOCK_CTRL_IO_DIV_32MHZ
172  | SYS_CTRL_CLOCK_CTRL_IO_DIV_16MHZ
173 #endif
174  ) & ~SYS_CTRL_CLOCK_CTRL_OSC_PD;
175 
176  /*First, make sure there is no ongoing clock source change */
177  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SOURCE_CHANGE) != 0);
178 
179  /* Set the System Clock to use the 16MHz RC OSC */
180  REG(SYS_CTRL_CLOCK_CTRL) |= SYS_CTRL_CLOCK_CTRL_OSC;
181 
182  /* Wait till it's happened */
183  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_OSC) == 0);
184 }
185 /*---------------------------------------------------------------------------*/
186 void
187 lpm_exit()
188 {
190  /* We either just exited PM0 or we were not sleeping in the first place.
191  * We don't need to do anything clever */
192  return;
193  }
194 
195  /*
196  * When returning from PM1/2, the sleep timer value (used by RTIMER_NOW()) is
197  * not up-to-date until a positive edge on the 32-kHz clock has been detected
198  * after the system clock restarted. To ensure an updated value is read, wait
199  * for a positive transition on the 32-kHz clock by polling the
200  * SYS_CTRL_CLOCK_STA.SYNC_32K bit, before reading the sleep timer value.
201  */
202  while(REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SYNC_32K);
203  while(!(REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SYNC_32K));
204 
205  LPM_STATS_ADD(REG(SYS_CTRL_PMCTL) & SYS_CTRL_PMCTL_PM3,
206  RTIMER_NOW() - sleep_enter_time);
207 
208  /* Adjust the system clock, since it was not counting while we were sleeping
209  * We need to convert sleep duration from rtimer ticks to clock ticks */
210  clock_adjust();
211 
212  /* Restore system clock to the 32 MHz XOSC */
213  select_32_mhz_xosc();
214 
215  if((REG(SYS_CTRL_PMCTL) & SYS_CTRL_PMCTL_PM3) == SYS_CTRL_PMCTL_PM1) {
216  ENERGEST_SWITCH(ENERGEST_TYPE_LPM, ENERGEST_TYPE_CPU);
217  } else {
218  ENERGEST_SWITCH(ENERGEST_TYPE_DEEP_LPM, ENERGEST_TYPE_CPU);
219  }
220 
221  /* Restore PMCTL to PM0 for next pass */
223 }
224 /*---------------------------------------------------------------------------*/
225 void
226 lpm_enter()
227 {
228  rtimer_clock_t lpm_exit_time;
229  rtimer_clock_t duration;
230 
231  /*
232  * If either the RF or the registered peripherals are on, dropping to PM1/2
233  * would equal pulling the rug (32MHz XOSC) from under their feet. Thus, we
234  * only drop to PM0. PM0 is also used if max_pm==0.
235  */
237  || !periph_permit_pm1() || max_pm == 0) {
238  enter_pm0();
239 
240  /* We reach here when the interrupt context that woke us up has returned */
241  return;
242  }
243 
244  /*
245  * Registered peripherals were off. Radio was off: Some Duty Cycling in place.
246  * rtimers run on the Sleep Timer. Thus, if we have a scheduled rtimer
247  * task, a Sleep Timer interrupt will fire and will wake us up.
248  * Choose the most suitable PM based on anticipated deep sleep duration
249  */
250  lpm_exit_time = rtimer_arch_next_trigger();
251  duration = lpm_exit_time - RTIMER_NOW();
252 
253  if(duration < DEEP_SLEEP_PM1_THRESHOLD || lpm_exit_time == 0) {
254  /* Anticipated duration too short or no scheduled rtimer task. Use PM0 */
255  enter_pm0();
256 
257  /* We reach here when the interrupt context that woke us up has returned */
258  return;
259  }
260 
261  /* If we reach here, we -may- (but may as well not) be dropping to PM1+. We
262  * know the registered peripherals and RF are off so we can switch to the
263  * 16MHz RCOSC. */
264  select_16_mhz_rcosc();
265 
266  /*
267  * Switching the System Clock from the 32MHz XOSC to the 16MHz RC OSC may
268  * have taken a while. Re-estimate sleep duration.
269  */
270  duration = lpm_exit_time - RTIMER_NOW();
271 
272  if(duration < DEEP_SLEEP_PM1_THRESHOLD) {
273  /*
274  * oops... The clock switch took some time and now the remaining sleep
275  * duration is too short. Restore the clock source to the 32MHz XOSC and
276  * abort the LPM attempt altogether. We can't drop to PM0,
277  * we need to yield to main() since we may have events to service now.
278  */
279  select_32_mhz_xosc();
280 
281  return;
282  } else if(duration >= DEEP_SLEEP_PM2_THRESHOLD && max_pm == 2) {
283  /* Long sleep duration and PM2 is allowed. Use it */
285  } else {
286  /*
287  * Anticipated duration too short for PM2 but long enough for PM1 and we
288  * are allowed to use PM1
289  */
291  }
292 
293  /* Remember the current time so we can keep stats when we wake up */
294  if(LPM_CONF_STATS) {
295  sleep_enter_time = RTIMER_NOW();
296  }
297 
298  /*
299  * Last chance to abort entering Deep Sleep.
300  *
301  * - There is the slight off-chance that a SysTick interrupt fired while we
302  * were trying to make up our mind. This may have raised an event.
303  * - The Sleep Timer may have fired
304  *
305  * Check if there is still a scheduled rtimer task and check for pending
306  * events before going to Deep Sleep
307  */
308  if(process_nevents() || rtimer_arch_next_trigger() == 0) {
309  /* Event flag raised or rtimer inactive.
310  * Turn on the 32MHz XOSC, restore PMCTL and abort */
311  select_32_mhz_xosc();
312 
314 
315  } else {
316  /* All clear. Assert WFI and drop to PM1/2. This is now un-interruptible */
317  if((REG(SYS_CTRL_PMCTL) & SYS_CTRL_PMCTL_PM3) == SYS_CTRL_PMCTL_PM1) {
318  ENERGEST_SWITCH(ENERGEST_TYPE_CPU, ENERGEST_TYPE_LPM);
319  } else {
320  ENERGEST_SWITCH(ENERGEST_TYPE_CPU, ENERGEST_TYPE_DEEP_LPM);
321  }
322  assert_wfi();
323  }
324 
325  /*
326  * We reach here after coming back from PM1/2. The interrupt context that
327  * woke us up has returned. lpm_exit() has run, it has switched the system
328  * clock source back to the 32MHz XOSC, it has adjusted the system clock,
329  * it has restored PMCTL and it has done energest housekeeping
330  */
331  return;
332 }
333 /*---------------------------------------------------------------------------*/
334 void
335 lpm_set_max_pm(uint8_t pm)
336 {
337  max_pm = pm > LPM_CONF_MAX_PM ? LPM_CONF_MAX_PM : pm;
338 }
339 /*---------------------------------------------------------------------------*/
340 void
341 lpm_register_peripheral(lpm_periph_permit_pm1_func_t permit_pm1_func)
342 {
343  int i;
344 
345  for(i = 0; i < LPM_PERIPH_PERMIT_PM1_FUNCS_MAX; i++) {
346  if(periph_permit_pm1_funcs[i] == permit_pm1_func) {
347  break;
348  } else if(periph_permit_pm1_funcs[i] == NULL) {
349  periph_permit_pm1_funcs[i] = permit_pm1_func;
350  break;
351  }
352  }
353 }
354 /*---------------------------------------------------------------------------*/
355 void
356 lpm_init()
357 {
358  /*
359  * The main loop calls lpm_enter() when we have no more events to service.
360  * By default, we will enter PM0 unless lpm_enter() decides otherwise
361  */
363  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
364 
365  max_pm = LPM_CONF_MAX_PM;
366 
367  LPM_STATS_INIT();
368 }
369 /*---------------------------------------------------------------------------*/
370 #endif /* LPM_CONF_ENABLE != 0 */
371 /** @} */
Header file for the cc2538 System Control driver.
#define RFCORE_XREG_FSMSTAT0
Radio status register.
Definition: rfcore-xreg.h:62
#define LPM_CONF_STATS
Set to 1 to enable LPM-related stats.
Definition: cc2538-conf.h:255
Header file for the energy estimation mechanism
#define SYS_CTRL_PMCTL_PM0
PM0.
Definition: sys-ctrl.h:263
CMSIS Cortex-M3 core peripheral access layer header file for CC2538.
#define SYS_CTRL_PMCTL_PM1
PM1.
Definition: sys-ctrl.h:262
Header file with register manipulation macro definitions.
#define SYS_CTRL_PMCTL_PM3
PM3.
Definition: sys-ctrl.h:260
#define RTIMER_NOW()
Get the current clock time.
Definition: rtimer.h:185
#define RFCORE_XREG_FSMSTAT0_FSM_FFCTRL_STATE
FIFO and FFCTRL status.
Definition: rfcore-xreg.h:275
Header file for the cc2538 rtimer driver.
rtimer_clock_t rtimer_arch_next_trigger()
Get the time of the next scheduled rtimer trigger.
Definition: rtimer-arch.c:105
#define SYS_CTRL_PMCTL_PM2
PM2.
Definition: sys-ctrl.h:261
#define SYS_CTRL_CLOCK_STA
Clock status register.
Definition: sys-ctrl.h:66
Header file for the Contiki process interface.
#define SYS_CTRL_PMCTL
Power Mode Control.
Definition: sys-ctrl.h:87
int process_nevents(void)
Number of events waiting to be processed.
Definition: process.c:316
void clock_adjust(void)
Adjust the clock following missed SysTick ISRs.
Definition: clock.c:224
void lpm_init()
Initialise the low-power mode management module.
Definition: lpm.c:557
#define SYS_CTRL_CLOCK_CTRL
Clock control register.
Definition: sys-ctrl.h:65
Header with declarations of the RF Core XREGs.
Header file with register, macro and function declarations for the cc2538 low power module...
#define LPM_CONF_MAX_PM
Maximum PM.
Definition: cc2538-conf.h:251