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() \
82  do { memset(lpm_stats, 0, sizeof(lpm_stats)); } 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 5
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_SWITCH(ENERGEST_TYPE_CPU, ENERGEST_TYPE_LPM);
132 
133  /* We are only interested in IRQ energest while idle or in LPM */
134  ENERGEST_IRQ_RESTORE(irq_energest);
135 
136  /* Remember the current time so we can keep stats when we wake up */
137  if(LPM_CONF_STATS) {
138  sleep_enter_time = RTIMER_NOW();
139  }
140 
141  assert_wfi();
142 
143  /* We reach here when the interrupt context that woke us up has returned */
144  LPM_STATS_ADD(0, RTIMER_NOW() - sleep_enter_time);
145 
146  /* Remember IRQ energest for next pass */
147  ENERGEST_IRQ_SAVE(irq_energest);
148 
149  ENERGEST_SWITCH(ENERGEST_TYPE_LPM, ENERGEST_TYPE_CPU);
150 }
151 /*---------------------------------------------------------------------------*/
152 static void
153 select_32_mhz_xosc(void)
154 {
155  /* First, make sure there is no ongoing clock source change */
156  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SOURCE_CHANGE) != 0);
157 
158  /* Turn on the 32 MHz XOSC and source the system clock on it. */
159  REG(SYS_CTRL_CLOCK_CTRL) &= ~SYS_CTRL_CLOCK_CTRL_OSC;
160 
161  /* Wait for the switch to take place */
162  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_OSC) != 0);
163 
164  /* Power down the unused oscillator and restore divisors (silicon errata) */
166 #if SYS_CTRL_SYS_DIV == SYS_CTRL_CLOCK_CTRL_SYS_DIV_32MHZ
167  & ~SYS_CTRL_CLOCK_CTRL_SYS_DIV
168 #endif
169 #if SYS_CTRL_IO_DIV == SYS_CTRL_CLOCK_CTRL_IO_DIV_32MHZ
170  & ~SYS_CTRL_CLOCK_CTRL_IO_DIV
171 #endif
172  ) | SYS_CTRL_CLOCK_CTRL_OSC_PD;
173 }
174 /*---------------------------------------------------------------------------*/
175 static void
176 select_16_mhz_rcosc(void)
177 {
178  /*
179  * Power up both oscillators in order to speed up the transition to the 32-MHz
180  * XOSC after wake up. In addition, consider CC2538 silicon errata:
181  * "Possible Incorrect Value of Clock Dividers after PM2 and PM3" and
182  * set system clock divisor / I/O clock divisor to 16 MHz in case they run
183  * at full speed (=32 MHz)
184  */
186 #if SYS_CTRL_SYS_DIV == SYS_CTRL_CLOCK_CTRL_SYS_DIV_32MHZ
187  | SYS_CTRL_CLOCK_CTRL_SYS_DIV_16MHZ
188 #endif
189 #if SYS_CTRL_IO_DIV == SYS_CTRL_CLOCK_CTRL_IO_DIV_32MHZ
190  | SYS_CTRL_CLOCK_CTRL_IO_DIV_16MHZ
191 #endif
192  ) & ~SYS_CTRL_CLOCK_CTRL_OSC_PD;
193 
194  /*First, make sure there is no ongoing clock source change */
195  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SOURCE_CHANGE) != 0);
196 
197  /* Set the System Clock to use the 16MHz RC OSC */
198  REG(SYS_CTRL_CLOCK_CTRL) |= SYS_CTRL_CLOCK_CTRL_OSC;
199 
200  /* Wait till it's happened */
201  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_OSC) == 0);
202 }
203 /*---------------------------------------------------------------------------*/
204 void
205 lpm_exit()
206 {
208  /* We either just exited PM0 or we were not sleeping in the first place.
209  * We don't need to do anything clever */
210  return;
211  }
212 
213  /*
214  * When returning from PM1/2, the sleep timer value (used by RTIMER_NOW()) is
215  * not up-to-date until a positive edge on the 32-kHz clock has been detected
216  * after the system clock restarted. To ensure an updated value is read, wait
217  * for a positive transition on the 32-kHz clock by polling the
218  * SYS_CTRL_CLOCK_STA.SYNC_32K bit, before reading the sleep timer value.
219  */
220  while(REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SYNC_32K);
221  while(!(REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SYNC_32K));
222 
223  LPM_STATS_ADD(REG(SYS_CTRL_PMCTL) & SYS_CTRL_PMCTL_PM3,
224  RTIMER_NOW() - sleep_enter_time);
225 
226  /* Adjust the system clock, since it was not counting while we were sleeping
227  * We need to convert sleep duration from rtimer ticks to clock ticks */
228  clock_adjust();
229 
230  /* Restore system clock to the 32 MHz XOSC */
231  select_32_mhz_xosc();
232 
233  /* Restore PMCTL to PM0 for next pass */
235 
236  /* Remember IRQ energest for next pass */
237  ENERGEST_IRQ_SAVE(irq_energest);
238 
239  ENERGEST_SWITCH(ENERGEST_TYPE_LPM, ENERGEST_TYPE_CPU);
240 }
241 /*---------------------------------------------------------------------------*/
242 void
243 lpm_enter()
244 {
245  rtimer_clock_t lpm_exit_time;
246  rtimer_clock_t duration;
247 
248  /*
249  * If either the RF or the registered peripherals are on, dropping to PM1/2
250  * would equal pulling the rug (32MHz XOSC) from under their feet. Thus, we
251  * only drop to PM0. PM0 is also used if max_pm==0.
252  */
254  || !periph_permit_pm1() || max_pm == 0) {
255  enter_pm0();
256 
257  /* We reach here when the interrupt context that woke us up has returned */
258  return;
259  }
260 
261  /*
262  * Registered peripherals were off. Radio was off: Some Duty Cycling in place.
263  * rtimers run on the Sleep Timer. Thus, if we have a scheduled rtimer
264  * task, a Sleep Timer interrupt will fire and will wake us up.
265  * Choose the most suitable PM based on anticipated deep sleep duration
266  */
267  lpm_exit_time = rtimer_arch_next_trigger();
268  duration = lpm_exit_time - RTIMER_NOW();
269 
270  if(duration < DEEP_SLEEP_PM1_THRESHOLD || lpm_exit_time == 0) {
271  /* Anticipated duration too short or no scheduled rtimer task. Use PM0 */
272  enter_pm0();
273 
274  /* We reach here when the interrupt context that woke us up has returned */
275  return;
276  }
277 
278  /* If we reach here, we -may- (but may as well not) be dropping to PM1+. We
279  * know the registered peripherals and RF are off so we can switch to the
280  * 16MHz RCOSC. */
281  select_16_mhz_rcosc();
282 
283  /*
284  * Switching the System Clock from the 32MHz XOSC to the 16MHz RC OSC may
285  * have taken a while. Re-estimate sleep duration.
286  */
287  duration = lpm_exit_time - RTIMER_NOW();
288 
289  if(duration < DEEP_SLEEP_PM1_THRESHOLD) {
290  /*
291  * oops... The clock switch took some time and now the remaining sleep
292  * duration is too short. Restore the clock source to the 32MHz XOSC and
293  * abort the LPM attempt altogether. We can't drop to PM0,
294  * we need to yield to main() since we may have events to service now.
295  */
296  select_32_mhz_xosc();
297 
298  return;
299  } else if(duration >= DEEP_SLEEP_PM2_THRESHOLD && max_pm == 2) {
300  /* Long sleep duration and PM2 is allowed. Use it */
302  } else {
303  /*
304  * Anticipated duration too short for PM2 but long enough for PM1 and we
305  * are allowed to use PM1
306  */
308  }
309 
310  /* We are only interested in IRQ energest while idle or in LPM */
311  ENERGEST_IRQ_RESTORE(irq_energest);
312  ENERGEST_SWITCH(ENERGEST_TYPE_CPU, ENERGEST_TYPE_LPM);
313 
314  /* Remember the current time so we can keep stats when we wake up */
315  if(LPM_CONF_STATS) {
316  sleep_enter_time = RTIMER_NOW();
317  }
318 
319  /*
320  * Last chance to abort entering Deep Sleep.
321  *
322  * - There is the slight off-chance that a SysTick interrupt fired while we
323  * were trying to make up our mind. This may have raised an event.
324  * - The Sleep Timer may have fired
325  *
326  * Check if there is still a scheduled rtimer task and check for pending
327  * events before going to Deep Sleep
328  */
329  if(process_nevents() || rtimer_arch_next_trigger() == 0) {
330  /* Event flag raised or rtimer inactive.
331  * Turn on the 32MHz XOSC, restore PMCTL and abort */
332  select_32_mhz_xosc();
333 
335 
336  /* Remember IRQ energest for next pass */
337  ENERGEST_IRQ_SAVE(irq_energest);
338  ENERGEST_SWITCH(ENERGEST_TYPE_LPM, ENERGEST_TYPE_CPU);
339  } else {
340  /* All clear. Assert WFI and drop to PM1/2. This is now un-interruptible */
341  assert_wfi();
342  }
343 
344  /*
345  * We reach here after coming back from PM1/2. The interrupt context that
346  * woke us up has returned. lpm_exit() has run, it has switched the system
347  * clock source back to the 32MHz XOSC, it has adjusted the system clock,
348  * it has restored PMCTL and it has done energest housekeeping
349  */
350  return;
351 }
352 /*---------------------------------------------------------------------------*/
353 void
354 lpm_set_max_pm(uint8_t pm)
355 {
356  max_pm = pm > LPM_CONF_MAX_PM ? LPM_CONF_MAX_PM : pm;
357 }
358 /*---------------------------------------------------------------------------*/
359 void
360 lpm_register_peripheral(lpm_periph_permit_pm1_func_t permit_pm1_func)
361 {
362  int i;
363 
364  for(i = 0; i < LPM_PERIPH_PERMIT_PM1_FUNCS_MAX; i++) {
365  if(periph_permit_pm1_funcs[i] == permit_pm1_func) {
366  break;
367  } else if(periph_permit_pm1_funcs[i] == NULL) {
368  periph_permit_pm1_funcs[i] = permit_pm1_func;
369  break;
370  }
371  }
372 }
373 /*---------------------------------------------------------------------------*/
374 void
375 lpm_init()
376 {
377  /*
378  * The main loop calls lpm_enter() when we have no more events to service.
379  * By default, we will enter PM0 unless lpm_enter() decides otherwise
380  */
383 
384  max_pm = LPM_CONF_MAX_PM;
385 
386  LPM_STATS_INIT();
387 }
388 /*---------------------------------------------------------------------------*/
389 #endif /* LPM_CONF_ENABLE != 0 */
390 /** @} */
Header file with register, macro and function declarations for the cc2538 low power module...
Header file with register manipulation macro definitions.
#define LPM_CONF_STATS
Set to 1 to enable LPM-related stats.
Definition: contiki-conf.h:342
Header with declarations of the RF Core XREGs.
void lpm_set_max_pm(uint8_t pm)
Prevent the SoC from dropping to a PM higher than max_pm.
#define RTIMER_NOW()
Get the current clock time.
Definition: rtimer.h:135
#define RFCORE_XREG_FSMSTAT0
Radio status register.
Definition: rfcore-xreg.h:62
#define SYS_CTRL_PMCTL_PM0
PM0.
Definition: sys-ctrl.h:263
void lpm_init()
Initialise the low-power mode management module.
Definition: lpm.c:510
#define SYS_CTRL_PMCTL
Power Mode Control.
Definition: sys-ctrl.h:87
Header file for the energy estimation mechanism
Header file for the System Control Block (SCB)
#define SCB_SYSCTRL
System Control.
Definition: scb.h:50
#define RFCORE_XREG_FSMSTAT0_FSM_FFCTRL_STATE
FIFO and FFCTRL status.
Definition: rfcore-xreg.h:275
void clock_adjust(void)
Adjust the clock following missed SysTick ISRs.
Definition: clock.c:230
#define SYS_CTRL_PMCTL_PM2
PM2.
Definition: sys-ctrl.h:261
Header file for the cc2538 rtimer driver.
#define SYS_CTRL_PMCTL_PM3
PM3.
Definition: sys-ctrl.h:260
#define SYS_CTRL_PMCTL_PM1
PM1.
Definition: sys-ctrl.h:262
#define SYS_CTRL_CLOCK_STA
Clock status register.
Definition: sys-ctrl.h:66
#define NULL
The null pointer.
Header file for the cc2538 System Control driver.
#define SYS_CTRL_CLOCK_CTRL
Clock control register.
Definition: sys-ctrl.h:65
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 'permission' to drop to...
Header file for the Contiki process interface.
int process_nevents(void)
Number of events waiting to be processed.
Definition: process.c:316
#define LPM_CONF_MAX_PM
Maximum PM.
Definition: contiki-conf.h:338
#define lpm_enter()
Drop to Deep Sleep.
Definition: lpm.h:212
#define SCB_SYSCTRL_SLEEPDEEP
Deep sleep enable.
Definition: scb.h:73
#define lpm_exit()
Perform an 'Exit Deep Sleep' sequence.
Definition: lpm.h:213
rtimer_clock_t rtimer_arch_next_trigger(void)
Get the time of the next scheduled rtimer trigger.
Definition: rtimer-arch.c:56