Contiki-NG
Loading...
Searching...
No Matches
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
67rtimer_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() do {} while (0)
74#define LPM_STATS_ADD(stat, val) do {} while (0)
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 */
81static rtimer_clock_t sleep_enter_time;
82
83void clock_adjust(void);
84/*---------------------------------------------------------------------------*/
85/* Stores the currently specified MAX allowed PM */
86static 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
95static lpm_periph_permit_pm1_func_t
96periph_permit_pm1_funcs[LPM_PERIPH_PERMIT_PM1_FUNCS_MAX];
97/*---------------------------------------------------------------------------*/
98static bool
99periph_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 */
116static void
117enter_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/*---------------------------------------------------------------------------*/
134static void
135select_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/*---------------------------------------------------------------------------*/
157static void
158select_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/*---------------------------------------------------------------------------*/
186void
187lpm_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
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/*---------------------------------------------------------------------------*/
225void
226lpm_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 */
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 */
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/*---------------------------------------------------------------------------*/
334void
335lpm_set_max_pm(uint8_t pm)
336{
337 max_pm = pm > LPM_CONF_MAX_PM ? LPM_CONF_MAX_PM : pm;
338}
339/*---------------------------------------------------------------------------*/
340void
341lpm_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/*---------------------------------------------------------------------------*/
355void
356lpm_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/** @} */
CMSIS Cortex-M3 core peripheral access layer header file for CC2538.
Header file with register, macro and function declarations for the cc2538 low power module.
Header file for the cc2538 rtimer driver.
Header file for the energy estimation mechanism.
void clock_adjust(void)
Adjust the clock following missed SysTick ISRs.
Definition clock.c:224
#define RFCORE_XREG_FSMSTAT0
Radio status register.
Definition rfcore-xreg.h:62
#define RFCORE_XREG_FSMSTAT0_FSM_FFCTRL_STATE
FIFO and FFCTRL status.
rtimer_clock_t rtimer_arch_next_trigger()
Get the time of the next scheduled rtimer trigger.
Definition rtimer-arch.c:99
#define SYS_CTRL_PMCTL_PM2
PM2.
Definition sys-ctrl.h:261
#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_PMCTL_PM0
PM0.
Definition sys-ctrl.h:263
#define SYS_CTRL_CLOCK_STA
Clock status register.
Definition sys-ctrl.h:66
#define SYS_CTRL_PMCTL
Power Mode Control.
Definition sys-ctrl.h:87
#define SYS_CTRL_CLOCK_CTRL
Clock control register.
Definition sys-ctrl.h:65
#define LPM_CONF_MAX_PM
Maximum PM.
#define LPM_CONF_STATS
Set to 1 to enable LPM-related stats.
int process_nevents(void)
Number of events waiting to be processed.
Definition process.c:319
#define RTIMER_NOW()
Get the current clock time.
Definition rtimer.h:187
Header file for the Contiki process interface.
Header file with register manipulation macro definitions.
Header with declarations of the RF Core XREGs.
Header file for the cc2538 System Control driver.