Contiki-Inga 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 PM1/2 to adjust the system clock, which
92  * stops ticking while in those PMs
93  */
94 static rtimer_clock_t sleep_enter_time;
95 
96 #define RTIMER_CLOCK_TICK_RATIO (RTIMER_SECOND / CLOCK_SECOND)
97 
98 void clock_adjust(clock_time_t ticks);
99 /*---------------------------------------------------------------------------*/
100 /* Stores the currently specified MAX allowed PM */
101 static uint8_t max_pm;
102 /*---------------------------------------------------------------------------*/
103 /* Buffer to store peripheral PM1+ permission FPs */
104 #ifdef LPM_CONF_PERIPH_PERMIT_PM1_FUNCS_MAX
105 #define LPM_PERIPH_PERMIT_PM1_FUNCS_MAX LPM_CONF_PERIPH_PERMIT_PM1_FUNCS_MAX
106 #else
107 #define LPM_PERIPH_PERMIT_PM1_FUNCS_MAX 2
108 #endif
109 
110 static lpm_periph_permit_pm1_func_t
111 periph_permit_pm1_funcs[LPM_PERIPH_PERMIT_PM1_FUNCS_MAX];
112 /*---------------------------------------------------------------------------*/
113 static bool
114 periph_permit_pm1(void)
115 {
116  int i;
117 
118  for(i = 0; i < LPM_PERIPH_PERMIT_PM1_FUNCS_MAX &&
119  periph_permit_pm1_funcs[i] != NULL; i++) {
120  if(!periph_permit_pm1_funcs[i]()) {
121  return false;
122  }
123  }
124  return true;
125 }
126 /*---------------------------------------------------------------------------*/
127 /*
128  * Routine to put is in PM0. We also need to do some housekeeping if the stats
129  * or the energest module is enabled
130  */
131 static void
132 enter_pm0(void)
133 {
134  ENERGEST_OFF(ENERGEST_TYPE_CPU);
135  ENERGEST_ON(ENERGEST_TYPE_LPM);
136 
137  /* We are only interested in IRQ energest while idle or in LPM */
138  ENERGEST_IRQ_RESTORE(irq_energest);
139 
140  /*
141  * After PM0 we don't need to adjust the system clock. Thus, saving the time
142  * we enter Deep Sleep is only required if we are keeping stats.
143  */
144  if(LPM_CONF_STATS) {
145  sleep_enter_time = RTIMER_NOW();
146  }
147 
148  assert_wfi();
149 
150  /* We reach here when the interrupt context that woke us up has returned */
151  LPM_STATS_ADD(0, RTIMER_NOW() - sleep_enter_time);
152 
153  /* Remember IRQ energest for next pass */
154  ENERGEST_IRQ_SAVE(irq_energest);
155 
156  ENERGEST_ON(ENERGEST_TYPE_CPU);
157  ENERGEST_OFF(ENERGEST_TYPE_LPM);
158 }
159 /*---------------------------------------------------------------------------*/
160 static void
161 select_32_mhz_xosc(void)
162 {
163  /*First, make sure there is no ongoing clock source change */
164  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SOURCE_CHANGE) != 0);
165 
166  /* Turn on the 32 MHz XOSC and source the system clock on it. */
167  REG(SYS_CTRL_CLOCK_CTRL) &= ~SYS_CTRL_CLOCK_CTRL_OSC;
168 
169  /* Wait for the switch to take place */
170  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_OSC) != 0);
171 
172  /* Power down the unused oscillator. */
173  REG(SYS_CTRL_CLOCK_CTRL) |= SYS_CTRL_CLOCK_CTRL_OSC_PD;
174 }
175 /*---------------------------------------------------------------------------*/
176 static void
177 select_16_mhz_rcosc(void)
178 {
179  /*
180  * Power up both oscillators in order to speed up the transition to the 32-MHz
181  * XOSC after wake up.
182  */
183  REG(SYS_CTRL_CLOCK_CTRL) &= ~SYS_CTRL_CLOCK_CTRL_OSC_PD;
184 
185  /*First, make sure there is no ongoing clock source change */
186  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SOURCE_CHANGE) != 0);
187 
188  /* Set the System Clock to use the 16MHz RC OSC */
189  REG(SYS_CTRL_CLOCK_CTRL) |= SYS_CTRL_CLOCK_CTRL_OSC;
190 
191  /* Wait till it's happened */
192  while((REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_OSC) == 0);
193 }
194 /*---------------------------------------------------------------------------*/
195 void
196 lpm_exit()
197 {
199  /* We either just exited PM0 or we were not sleeping in the first place.
200  * We don't need to do anything clever */
201  return;
202  }
203 
204  /*
205  * When returning from PM1/2, the sleep timer value (used by RTIMER_NOW()) is
206  * not up-to-date until a positive edge on the 32-kHz clock has been detected
207  * after the system clock restarted. To ensure an updated value is read, wait
208  * for a positive transition on the 32-kHz clock by polling the
209  * SYS_CTRL_CLOCK_STA.SYNC_32K bit, before reading the sleep timer value.
210  */
211  while(REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SYNC_32K);
212  while(!(REG(SYS_CTRL_CLOCK_STA) & SYS_CTRL_CLOCK_STA_SYNC_32K));
213 
214  LPM_STATS_ADD(REG(SYS_CTRL_PMCTL) & SYS_CTRL_PMCTL_PM3,
215  RTIMER_NOW() - sleep_enter_time);
216 
217  /* Adjust the system clock, since it was not counting while we were sleeping
218  * We need to convert sleep duration from rtimer ticks to clock ticks and
219  * this will cost us some accuracy */
220  clock_adjust((clock_time_t)
221  ((RTIMER_NOW() - sleep_enter_time) / RTIMER_CLOCK_TICK_RATIO));
222 
223  /* Restore system clock to the 32 MHz XOSC */
224  select_32_mhz_xosc();
225 
226  /* Restore PMCTL to PM0 for next pass */
228 
229  /* Remember IRQ energest for next pass */
230  ENERGEST_IRQ_SAVE(irq_energest);
231 
232  ENERGEST_ON(ENERGEST_TYPE_CPU);
233  ENERGEST_OFF(ENERGEST_TYPE_LPM);
234 }
235 /*---------------------------------------------------------------------------*/
236 void
237 lpm_enter()
238 {
239  rtimer_clock_t lpm_exit_time;
240  rtimer_clock_t duration;
241 
242  /*
243  * If either the RF or the registered peripherals are on, dropping to PM1/2
244  * would equal pulling the rug (32MHz XOSC) from under their feet. Thus, we
245  * only drop to PM0. PM0 is also used if max_pm==0.
246  */
248  || !periph_permit_pm1() || max_pm == 0) {
249  enter_pm0();
250 
251  /* We reach here when the interrupt context that woke us up has returned */
252  return;
253  }
254 
255  /*
256  * Registered peripherals were off. Radio was off: Some Duty Cycling in place.
257  * rtimers run on the Sleep Timer. Thus, if we have a scheduled rtimer
258  * task, a Sleep Timer interrupt will fire and will wake us up.
259  * Choose the most suitable PM based on anticipated deep sleep duration
260  */
261  lpm_exit_time = rtimer_arch_next_trigger();
262  duration = lpm_exit_time - RTIMER_NOW();
263 
264  if(duration < DEEP_SLEEP_PM1_THRESHOLD || lpm_exit_time == 0) {
265  /* Anticipated duration too short or no scheduled rtimer task. Use PM0 */
266  enter_pm0();
267 
268  /* We reach here when the interrupt context that woke us up has returned */
269  return;
270  }
271 
272  /* If we reach here, we -may- (but may as well not) be dropping to PM1+. We
273  * know the registered peripherals and RF are off so we can switch to the
274  * 16MHz RCOSC. */
275  select_16_mhz_rcosc();
276 
277  /*
278  * Switching the System Clock from the 32MHz XOSC to the 16MHz RC OSC may
279  * have taken a while. Re-estimate sleep duration.
280  */
281  duration = lpm_exit_time - RTIMER_NOW();
282 
283  if(duration < DEEP_SLEEP_PM1_THRESHOLD) {
284  /*
285  * oops... The clock switch took some time and now the remaining sleep
286  * duration is too short. Restore the clock source to the 32MHz XOSC and
287  * abort the LPM attempt altogether. We can't drop to PM0,
288  * we need to yield to main() since we may have events to service now.
289  */
290  select_32_mhz_xosc();
291 
292  return;
293  } else if(duration >= DEEP_SLEEP_PM2_THRESHOLD && max_pm == 2) {
294  /* Long sleep duration and PM2 is allowed. Use it */
296  } else {
297  /*
298  * Anticipated duration too short for PM2 but long enough for PM1 and we
299  * are allowed to use PM1
300  */
302  }
303 
304  /* We are only interested in IRQ energest while idle or in LPM */
305  ENERGEST_IRQ_RESTORE(irq_energest);
306  ENERGEST_OFF(ENERGEST_TYPE_CPU);
307  ENERGEST_ON(ENERGEST_TYPE_LPM);
308 
309  /* Remember the current time so we can adjust the clock when we wake up */
310  sleep_enter_time = RTIMER_NOW();
311 
312  /*
313  * Last chance to abort entering Deep Sleep.
314  *
315  * - There is the slight off-chance that a SysTick interrupt fired while we
316  * were trying to make up our mind. This may have raised an event.
317  * - The Sleep Timer may have fired
318  *
319  * Check if there is still a scheduled rtimer task and check for pending
320  * events before going to Deep Sleep
321  */
322  if(process_nevents() || rtimer_arch_next_trigger() == 0) {
323  /* Event flag raised or rtimer inactive.
324  * Turn on the 32MHz XOSC, restore PMCTL and abort */
325  select_32_mhz_xosc();
326 
328 
329  /* Remember IRQ energest for next pass */
330  ENERGEST_IRQ_SAVE(irq_energest);
331  ENERGEST_ON(ENERGEST_TYPE_CPU);
332  ENERGEST_OFF(ENERGEST_TYPE_LPM);
333  } else {
334  /* All clear. Assert WFI and drop to PM1/2. This is now un-interruptible */
335  assert_wfi();
336  }
337 
338  /*
339  * We reach here after coming back from PM1/2. The interrupt context that
340  * woke us up has returned. lpm_exit() has run, it has switched the system
341  * clock source back to the 32MHz XOSC, it has adjusted the system clock,
342  * it has restored PMCTL and it has done energest housekeeping
343  */
344  return;
345 }
346 /*---------------------------------------------------------------------------*/
347 void
348 lpm_set_max_pm(uint8_t pm)
349 {
350  max_pm = pm > LPM_CONF_MAX_PM ? LPM_CONF_MAX_PM : pm;
351 }
352 /*---------------------------------------------------------------------------*/
353 void
354 lpm_register_peripheral(lpm_periph_permit_pm1_func_t permit_pm1_func)
355 {
356  int i;
357 
358  for(i = 0; i < LPM_PERIPH_PERMIT_PM1_FUNCS_MAX; i++) {
359  if(periph_permit_pm1_funcs[i] == permit_pm1_func) {
360  break;
361  } else if(periph_permit_pm1_funcs[i] == NULL) {
362  periph_permit_pm1_funcs[i] = permit_pm1_func;
363  break;
364  }
365  }
366 }
367 /*---------------------------------------------------------------------------*/
368 void
369 lpm_init()
370 {
371  /*
372  * The main loop calls lpm_enter() when we have no more events to service.
373  * By default, we will enter PM0 unless lpm_enter() decides otherwise
374  */
377 
378  max_pm = LPM_CONF_MAX_PM;
379 
380  LPM_STATS_INIT();
381 }
382 /*---------------------------------------------------------------------------*/
383 #endif /* LPM_CONF_ENABLE != 0 */
384 /** @} */