Contiki 3.x
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-conf.h"
39 #include "sys/energest.h"
40 #include "sys/process.h"
41 #include "dev/sys-ctrl.h"
42 #include "dev/scb.h"
43 #include "dev/rfcore-xreg.h"
44 #include "rtimer-arch.h"
45 #include "lpm.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 #if ENERGEST_CONF_ON
55 static unsigned long irq_energest = 0;
56 
57 #define ENERGEST_IRQ_SAVE(a) do { \
58  a = energest_type_time(ENERGEST_TYPE_IRQ); } while(0)
59 #define ENERGEST_IRQ_RESTORE(a) do { \
60  energest_type_set(ENERGEST_TYPE_IRQ, a); } while(0)
61 #else
62 #define ENERGEST_IRQ_SAVE(a) do {} while(0)
63 #define ENERGEST_IRQ_RESTORE(a) do {} while(0)
64 #endif
65 /*---------------------------------------------------------------------------*/
66 /*
67  * Deep Sleep thresholds in rtimer ticks (~30.5 usec)
68  *
69  * If Deep Sleep duration < DEEP_SLEEP_PM1_THRESHOLD, simply enter PM0
70  * If duration < DEEP_SLEEP_PM2_THRESHOLD drop to PM1
71  * else PM2.
72  */
73 #define DEEP_SLEEP_PM1_THRESHOLD 10
74 #define DEEP_SLEEP_PM2_THRESHOLD 100
75 /*---------------------------------------------------------------------------*/
76 #define assert_wfi() do { asm("wfi"::); } while(0)
77 /*---------------------------------------------------------------------------*/
78 #if LPM_CONF_STATS
79 rtimer_clock_t lpm_stats[3];
80 
81 #define LPM_STATS_INIT() do { memset(lpm_stats, 0, sizeof(lpm_stats)); \
82  } while(0)
83 #define LPM_STATS_ADD(pm, val) do { lpm_stats[pm] += val; } while(0)
84 #else
85 #define LPM_STATS_INIT()
86 #define LPM_STATS_ADD(stat, val)
87 #endif
88 /*---------------------------------------------------------------------------*/
89 /*
90  * Remembers what time it was when went to deep sleep
91  * This is used when coming out of PM0/1/2 to keep stats
92  */
93 static rtimer_clock_t sleep_enter_time;
94 
95 void clock_adjust(void);
96 /*---------------------------------------------------------------------------*/
97 /* Stores the currently specified MAX allowed PM */
98 static uint8_t max_pm;
99 /*---------------------------------------------------------------------------*/
100 /* Buffer to store peripheral PM1+ permission FPs */
101 #ifdef LPM_CONF_PERIPH_PERMIT_PM1_FUNCS_MAX
102 #define LPM_PERIPH_PERMIT_PM1_FUNCS_MAX LPM_CONF_PERIPH_PERMIT_PM1_FUNCS_MAX
103 #else
104 #define LPM_PERIPH_PERMIT_PM1_FUNCS_MAX 2
105 #endif
106 
107 static lpm_periph_permit_pm1_func_t
108 periph_permit_pm1_funcs[LPM_PERIPH_PERMIT_PM1_FUNCS_MAX];
109 /*---------------------------------------------------------------------------*/
110 static bool
111 periph_permit_pm1(void)
112 {
113  int i;
114 
115  for(i = 0; i < LPM_PERIPH_PERMIT_PM1_FUNCS_MAX &&
116  periph_permit_pm1_funcs[i] != NULL; i++) {
117  if(!periph_permit_pm1_funcs[i]()) {
118  return false;
119  }
120  }
121  return true;
122 }
123 /*---------------------------------------------------------------------------*/
124 /*
125  * Routine to put is in PM0. We also need to do some housekeeping if the stats
126  * or the energest module is enabled
127  */
128 static void
129 enter_pm0(void)
130 {
131  ENERGEST_OFF(ENERGEST_TYPE_CPU);
132  ENERGEST_ON(ENERGEST_TYPE_LPM);
133 
134  /* We are only interested in IRQ energest while idle or in LPM */
135  ENERGEST_IRQ_RESTORE(irq_energest);
136 
137  /* Remember the current time so we can keep stats when we wake up */
138  if(LPM_CONF_STATS) {
139  sleep_enter_time = RTIMER_NOW();
140  }
141 
142  assert_wfi();
143 
144  /* We reach here when the interrupt context that woke us up has returned */
145  LPM_STATS_ADD(0, RTIMER_NOW() - sleep_enter_time);
146 
147  /* Remember IRQ energest for next pass */
148  ENERGEST_IRQ_SAVE(irq_energest);
149 
150  ENERGEST_ON(ENERGEST_TYPE_CPU);
151  ENERGEST_OFF(ENERGEST_TYPE_LPM);
152 }
153 /*---------------------------------------------------------------------------*/
154 static void
155 select_32_mhz_xosc(void)
156 {
157  /*First, make sure there is no ongoing clock source change */
158  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SOURCE_CHANGE) != 0);
159 
160  /* Turn on the 32 MHz XOSC and source the system clock on it. */
161  REG(SYS_CTRL_CLOCK_CTRL) &= ~SYS_CTRL_CLOCK_CTRL_OSC;
162 
163  /* Wait for the switch to take place */
164  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_OSC) != 0);
165 
166  /* Power down the unused oscillator. */
167  REG(SYS_CTRL_CLOCK_CTRL) |= SYS_CTRL_CLOCK_CTRL_OSC_PD;
168 }
169 /*---------------------------------------------------------------------------*/
170 static void
171 select_16_mhz_rcosc(void)
172 {
173  /*
174  * Power up both oscillators in order to speed up the transition to the 32-MHz
175  * XOSC after wake up.
176  */
177  REG(SYS_CTRL_CLOCK_CTRL) &= ~SYS_CTRL_CLOCK_CTRL_OSC_PD;
178 
179  /*First, make sure there is no ongoing clock source change */
180  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SOURCE_CHANGE) != 0);
181 
182  /* Set the System Clock to use the 16MHz RC OSC */
183  REG(SYS_CTRL_CLOCK_CTRL) |= SYS_CTRL_CLOCK_CTRL_OSC;
184 
185  /* Wait till it's happened */
186  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_OSC) == 0);
187 }
188 /*---------------------------------------------------------------------------*/
189 void
190 lpm_exit()
191 {
193  /* We either just exited PM0 or we were not sleeping in the first place.
194  * We don't need to do anything clever */
195  return;
196  }
197 
198  /*
199  * When returning from PM1/2, the sleep timer value (used by RTIMER_NOW()) is
200  * not up-to-date until a positive edge on the 32-kHz clock has been detected
201  * after the system clock restarted. To ensure an updated value is read, wait
202  * for a positive transition on the 32-kHz clock by polling the
203  * SYS_CTRL_CLOCK_STA.SYNC_32K bit, before reading the sleep timer value.
204  */
205  while(REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SYNC_32K);
206  while(!(REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SYNC_32K));
207 
208  LPM_STATS_ADD(REG(SYS_CTRL_PMCTL) & SYS_CTRL_PMCTL_PM3,
209  RTIMER_NOW() - sleep_enter_time);
210 
211  /* Adjust the system clock, since it was not counting while we were sleeping
212  * We need to convert sleep duration from rtimer ticks to clock ticks */
213  clock_adjust();
214 
215  /* Restore system clock to the 32 MHz XOSC */
216  select_32_mhz_xosc();
217 
218  /* Restore PMCTL to PM0 for next pass */
220 
221  /* Remember IRQ energest for next pass */
222  ENERGEST_IRQ_SAVE(irq_energest);
223 
224  ENERGEST_ON(ENERGEST_TYPE_CPU);
225  ENERGEST_OFF(ENERGEST_TYPE_LPM);
226 }
227 /*---------------------------------------------------------------------------*/
228 void
229 lpm_enter()
230 {
231  rtimer_clock_t lpm_exit_time;
232  rtimer_clock_t duration;
233 
234  /*
235  * If either the RF or the registered peripherals are on, dropping to PM1/2
236  * would equal pulling the rug (32MHz XOSC) from under their feet. Thus, we
237  * only drop to PM0. PM0 is also used if max_pm==0.
238  */
240  || !periph_permit_pm1() || max_pm == 0) {
241  enter_pm0();
242 
243  /* We reach here when the interrupt context that woke us up has returned */
244  return;
245  }
246 
247  /*
248  * Registered peripherals were off. Radio was off: Some Duty Cycling in place.
249  * rtimers run on the Sleep Timer. Thus, if we have a scheduled rtimer
250  * task, a Sleep Timer interrupt will fire and will wake us up.
251  * Choose the most suitable PM based on anticipated deep sleep duration
252  */
253  lpm_exit_time = rtimer_arch_next_trigger();
254  duration = lpm_exit_time - RTIMER_NOW();
255 
256  if(duration < DEEP_SLEEP_PM1_THRESHOLD || lpm_exit_time == 0) {
257  /* Anticipated duration too short or no scheduled rtimer task. Use PM0 */
258  enter_pm0();
259 
260  /* We reach here when the interrupt context that woke us up has returned */
261  return;
262  }
263 
264  /* If we reach here, we -may- (but may as well not) be dropping to PM1+. We
265  * know the registered peripherals and RF are off so we can switch to the
266  * 16MHz RCOSC. */
267  select_16_mhz_rcosc();
268 
269  /*
270  * Switching the System Clock from the 32MHz XOSC to the 16MHz RC OSC may
271  * have taken a while. Re-estimate sleep duration.
272  */
273  duration = lpm_exit_time - RTIMER_NOW();
274 
275  if(duration < DEEP_SLEEP_PM1_THRESHOLD) {
276  /*
277  * oops... The clock switch took some time and now the remaining sleep
278  * duration is too short. Restore the clock source to the 32MHz XOSC and
279  * abort the LPM attempt altogether. We can't drop to PM0,
280  * we need to yield to main() since we may have events to service now.
281  */
282  select_32_mhz_xosc();
283 
284  return;
285  } else if(duration >= DEEP_SLEEP_PM2_THRESHOLD && max_pm == 2) {
286  /* Long sleep duration and PM2 is allowed. Use it */
288  } else {
289  /*
290  * Anticipated duration too short for PM2 but long enough for PM1 and we
291  * are allowed to use PM1
292  */
294  }
295 
296  /* We are only interested in IRQ energest while idle or in LPM */
297  ENERGEST_IRQ_RESTORE(irq_energest);
298  ENERGEST_OFF(ENERGEST_TYPE_CPU);
299  ENERGEST_ON(ENERGEST_TYPE_LPM);
300 
301  /* Remember the current time so we can keep stats when we wake up */
302  if(LPM_CONF_STATS) {
303  sleep_enter_time = RTIMER_NOW();
304  }
305 
306  /*
307  * Last chance to abort entering Deep Sleep.
308  *
309  * - There is the slight off-chance that a SysTick interrupt fired while we
310  * were trying to make up our mind. This may have raised an event.
311  * - The Sleep Timer may have fired
312  *
313  * Check if there is still a scheduled rtimer task and check for pending
314  * events before going to Deep Sleep
315  */
316  if(process_nevents() || rtimer_arch_next_trigger() == 0) {
317  /* Event flag raised or rtimer inactive.
318  * Turn on the 32MHz XOSC, restore PMCTL and abort */
319  select_32_mhz_xosc();
320 
322 
323  /* Remember IRQ energest for next pass */
324  ENERGEST_IRQ_SAVE(irq_energest);
325  ENERGEST_ON(ENERGEST_TYPE_CPU);
326  ENERGEST_OFF(ENERGEST_TYPE_LPM);
327  } else {
328  /* All clear. Assert WFI and drop to PM1/2. This is now un-interruptible */
329  assert_wfi();
330  }
331 
332  /*
333  * We reach here after coming back from PM1/2. The interrupt context that
334  * woke us up has returned. lpm_exit() has run, it has switched the system
335  * clock source back to the 32MHz XOSC, it has adjusted the system clock,
336  * it has restored PMCTL and it has done energest housekeeping
337  */
338  return;
339 }
340 /*---------------------------------------------------------------------------*/
341 void
342 lpm_set_max_pm(uint8_t pm)
343 {
344  max_pm = pm > LPM_CONF_MAX_PM ? LPM_CONF_MAX_PM : pm;
345 }
346 /*---------------------------------------------------------------------------*/
347 void
348 lpm_register_peripheral(lpm_periph_permit_pm1_func_t permit_pm1_func)
349 {
350  int i;
351 
352  for(i = 0; i < LPM_PERIPH_PERMIT_PM1_FUNCS_MAX; i++) {
353  if(periph_permit_pm1_funcs[i] == permit_pm1_func) {
354  break;
355  } else if(periph_permit_pm1_funcs[i] == NULL) {
356  periph_permit_pm1_funcs[i] = permit_pm1_func;
357  break;
358  }
359  }
360 }
361 /*---------------------------------------------------------------------------*/
362 void
363 lpm_init()
364 {
365  /*
366  * The main loop calls lpm_enter() when we have no more events to service.
367  * By default, we will enter PM0 unless lpm_enter() decides otherwise
368  */
371 
372  max_pm = LPM_CONF_MAX_PM;
373 
374  LPM_STATS_INIT();
375 }
376 /*---------------------------------------------------------------------------*/
377 #endif /* LPM_CONF_ENABLE != 0 */
378 /** @} */
Header with declarations of the RF Core XREGs.
int process_nevents(void)
Number of events waiting to be processed.
Definition: process.c:316
#define SYS_CTRL_PMCTL
Power Mode Control.
Definition: sys-ctrl.h:85
#define SCB_SYSCTRL
System Control.
Definition: scb.h:48
#define SYS_CTRL_CLOCK_STA
Clock status register.
Definition: sys-ctrl.h:64
#define LPM_CONF_MAX_PM
Maximum PM.
Definition: contiki-conf.h:341
Header file for the System Control Block (SCB)
#define lpm_enter()
Drop to Deep Sleep.
Definition: lpm.h:212
#define NULL
The null pointer.
#define SYS_CTRL_PMCTL_PM2
PM2.
Definition: sys-ctrl.h:226
#define lpm_exit()
Perform an &#39;Exit Deep Sleep&#39; sequence.
Definition: lpm.h:213
#define lpm_init()
Initialise the LPM module.
Definition: lpm.h:211
#define RFCORE_XREG_FSMSTAT0_FSM_FFCTRL_STATE
FIFO and FFCTRL status.
Definition: rfcore-xreg.h:275
Header file with register manipulation macro definitions.
#define SYS_CTRL_PMCTL_PM0
PM0.
Definition: sys-ctrl.h:228
Header file for the energy estimation mechanism
void clock_adjust(void)
Adjust the clock following missed SysTick ISRs.
Definition: clock.c:220
#define SYS_CTRL_CLOCK_CTRL
Clock control register.
Definition: sys-ctrl.h:63
Header file for the cc2538 System Control driver.
#define SCB_SYSCTRL_SLEEPDEEP
Deep sleep enable.
Definition: scb.h:71
#define SYS_CTRL_PMCTL_PM1
PM1.
Definition: sys-ctrl.h:227
void lpm_register_peripheral(lpm_periph_permit_pm1_func_t permit_pm1_func)
Register a peripheral function which will get called by the LPM module to get &#39;permission&#39; to drop to...
#define RTIMER_NOW()
Get the current clock time.
Definition: rtimer.h:133
rtimer_clock_t rtimer_arch_next_trigger()
Get the time of the next scheduled rtimer trigger.
Definition: rtimer-arch.c:106
#define SYS_CTRL_PMCTL_PM3
PM3.
Definition: sys-ctrl.h:225
Header file for the Contiki process interface.
void lpm_set_max_pm(uint8_t pm)
Prevent the SoC from dropping to a PM higher than max_pm.
#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: contiki-conf.h:345