1 /******************************************************************************
2 *
3 * Copyright (C) 2017-2018 Texas Instruments Incorporated - http://www.ti.com/
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 *
9 *  Redistributions of source code must retain the above copyright
10 *  notice, this list of conditions and the following disclaimer.
11 *
12 *  Redistributions in binary form must reproduce the above copyright
13 *  notice, this list of conditions and the following disclaimer in the
14 *  documentation and/or other materials provided with the
15 *  distribution.
16 *
17 *  Neither the name of Texas Instruments Incorporated nor the names of
18 *  its contributors may be used to endorse or promote products derived
19 *  from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 *
33 ******************************************************************************/
34 
35 #include <stdint.h>
36 #include <stdbool.h>
37 #include "driverlib.h"
38 
39 /*
40  * Wrapper function for the CPSID instruction.
41  * Returns the state of PRIMASK on entry.
42  */
cpu_cpsid(void)43 uint32_t __attribute__((naked)) cpu_cpsid(void)
44 {
45 	uint32_t ret;
46 
47 	/* Read PRIMASK and disable interrupts. */
48 	__asm("    mrs     r0, PRIMASK\n"
49 		  "    cpsid   i\n"
50 		  "    bx      lr\n"
51 			: "=r" (ret));
52 
53 	/*
54 	 * The return is handled in the inline assembly, but the compiler will
55 	 * still complain if there is not an explicit return here (despite the fact
56 	 * that this does not result in any code being produced because of the
57 	 * naked attribute).
58 	 */
59 	return ret;
60 }
61 
62 /* Wrapper function for the CPUWFI instruction. */
cpu_wfi(void)63 void __attribute__((naked)) cpu_wfi(void)
64 {
65 	/* Wait for the next interrupt. */
66 	__asm("    wfi\n"
67 		  "    bx      lr\n");
68 }
69 
70 /* Power Control Module APIs */
71 #if defined(PCM)
72 
__pcm_set_core_voltage_level_advanced(uint_fast8_t voltage_level,uint32_t time_out,bool blocking)73 static bool __pcm_set_core_voltage_level_advanced(uint_fast8_t voltage_level,
74 	uint32_t time_out, bool blocking)
75 {
76 	uint8_t power_mode;
77 	uint8_t current_voltage_level;
78 	uint32_t reg_value;
79 	bool bool_timeout;
80 
81 	/* Getting current power mode and level */
82 	power_mode = pcm_get_power_mode();
83 	current_voltage_level = pcm_get_core_voltage_level();
84 
85 	bool_timeout = time_out > 0 ? true : false;
86 
87 	/* If we are already at the power mode they requested, return */
88 	if (current_voltage_level == voltage_level)
89 		return true;
90 
91 	while (current_voltage_level != voltage_level) {
92 
93 		reg_value = PCM->CTL0;
94 
95 		switch (pcm_get_power_state()) {
96 			case PCM_AM_LF_VCORE1:
97 			case PCM_AM_DCDC_VCORE1:
98 			case PCM_AM_LDO_VCORE0:
99 				PCM->CTL0 = (PCM_KEY | (PCM_AM_LDO_VCORE1)
100 					| (reg_value & ~(PCM_CTL0_KEY_MASK | PCM_CTL0_AMR_MASK)));
101 				break;
102 			case PCM_AM_LF_VCORE0:
103 			case PCM_AM_DCDC_VCORE0:
104 			case PCM_AM_LDO_VCORE1:
105 				PCM->CTL0 = (PCM_KEY | (PCM_AM_LDO_VCORE0)
106 					| (reg_value & ~(PCM_CTL0_KEY_MASK | PCM_CTL0_AMR_MASK)));
107 				break;
108 			default:
109 				break;
110 		}
111 
112 		if (blocking) {
113 			while (BITBAND_PERI(PCM->CTL1, PCM_CTL1_PMR_BUSY_OFS)) {
114 				if (bool_timeout && !(--time_out))
115 					return false;
116 			}
117 		} else
118 			return true;
119 
120 		current_voltage_level = pcm_get_core_voltage_level();
121 	}
122 
123 	/* Changing the power mode if we are stuck in LDO mode */
124 	if (power_mode != pcm_get_power_mode()) {
125 		if (power_mode == PCM_DCDC_MODE)
126 			return pcm_set_power_mode(PCM_DCDC_MODE);
127 		else
128 			return pcm_set_power_mode(PCM_LF_MODE);
129 	}
130 
131 	return true;
132 }
133 
pcm_set_core_voltage_level(uint_fast8_t voltage_level)134 bool pcm_set_core_voltage_level(uint_fast8_t voltage_level)
135 {
136 	return __pcm_set_core_voltage_level_advanced(voltage_level, 0, true);
137 }
138 
pcm_get_power_mode(void)139 uint8_t pcm_get_power_mode(void)
140 {
141 	uint8_t current_power_state;
142 
143 	current_power_state = pcm_get_power_state();
144 
145 	switch (current_power_state) {
146 		case PCM_AM_LDO_VCORE0:
147 		case PCM_AM_LDO_VCORE1:
148 		case PCM_LPM0_LDO_VCORE0:
149 		case PCM_LPM0_LDO_VCORE1:
150 		default:
151 			return PCM_LDO_MODE;
152 		case PCM_AM_DCDC_VCORE0:
153 		case PCM_AM_DCDC_VCORE1:
154 		case PCM_LPM0_DCDC_VCORE0:
155 		case PCM_LPM0_DCDC_VCORE1:
156 			return PCM_DCDC_MODE;
157 		case PCM_LPM0_LF_VCORE0:
158 		case PCM_LPM0_LF_VCORE1:
159 		case PCM_AM_LF_VCORE1:
160 		case PCM_AM_LF_VCORE0:
161 			return PCM_LF_MODE;
162 	}
163 }
164 
pcm_get_core_voltage_level(void)165 uint8_t pcm_get_core_voltage_level(void)
166 {
167 	uint8_t current_power_state = pcm_get_power_state();
168 
169 	switch (current_power_state) {
170 		case PCM_AM_LDO_VCORE0:
171 		case PCM_AM_DCDC_VCORE0:
172 		case PCM_AM_LF_VCORE0:
173 		case PCM_LPM0_LDO_VCORE0:
174 		case PCM_LPM0_DCDC_VCORE0:
175 		case PCM_LPM0_LF_VCORE0:
176 		default:
177 			return PCM_VCORE0;
178 		case PCM_AM_LDO_VCORE1:
179 		case PCM_AM_DCDC_VCORE1:
180 		case PCM_AM_LF_VCORE1:
181 		case PCM_LPM0_LDO_VCORE1:
182 		case PCM_LPM0_DCDC_VCORE1:
183 		case PCM_LPM0_LF_VCORE1:
184 			return PCM_VCORE1;
185 		case PCM_LPM3:
186 			return PCM_VCORELPM3;
187 	}
188 }
189 
__pcm_set_power_mode_advanced(uint_fast8_t power_mode,uint32_t time_out,bool blocking)190 static bool __pcm_set_power_mode_advanced(uint_fast8_t power_mode,
191 	uint32_t time_out, bool blocking)
192 {
193 	uint8_t current_power_mode;
194 	uint8_t current_power_state;
195 	uint32_t reg_value;
196 	bool bool_timeout;
197 
198 	/* Getting Current Power Mode */
199 	current_power_mode = pcm_get_power_mode();
200 
201 	/* If the power mode being set it the same as the current mode, return */
202 	if (power_mode == current_power_mode)
203 		return true;
204 
205 	current_power_state = pcm_get_power_state();
206 
207 	bool_timeout = time_out > 0 ? true : false;
208 
209 	/* Go through the while loop while we haven't achieved the power mode */
210 	while (current_power_mode != power_mode) {
211 
212 		reg_value = PCM->CTL0;
213 
214 		switch (current_power_state) {
215 			case PCM_AM_DCDC_VCORE0:
216 			case PCM_AM_LF_VCORE0:
217 				PCM->CTL0 = (PCM_KEY | PCM_AM_LDO_VCORE0
218 					| (reg_value & ~(PCM_CTL0_KEY_MASK | PCM_CTL0_AMR_MASK)));
219 				break;
220 			case PCM_AM_LF_VCORE1:
221 			case PCM_AM_DCDC_VCORE1:
222 				PCM->CTL0 = (PCM_KEY | PCM_AM_LDO_VCORE1
223 					| (reg_value & ~(PCM_CTL0_KEY_MASK | PCM_CTL0_AMR_MASK)));
224 				break;
225 			case PCM_AM_LDO_VCORE1: {
226 				if (power_mode == PCM_DCDC_MODE) {
227 					PCM->CTL0 = (PCM_KEY | PCM_AM_DCDC_VCORE1
228 						| (reg_value & ~(PCM_CTL0_KEY_MASK
229 						| PCM_CTL0_AMR_MASK)));
230 				} else if (power_mode == PCM_LF_MODE) {
231 					PCM->CTL0 = (PCM_KEY | PCM_AM_LF_VCORE1
232 						| (reg_value & ~(PCM_CTL0_KEY_MASK
233 						| PCM_CTL0_AMR_MASK)));
234 				} else
235 					return false;
236 				break;
237 			}
238 			case PCM_AM_LDO_VCORE0: {
239 				if (power_mode == PCM_DCDC_MODE) {
240 					PCM->CTL0 = (PCM_KEY | PCM_AM_DCDC_VCORE0
241 						| (reg_value & ~(PCM_CTL0_KEY_MASK
242 						| PCM_CTL0_AMR_MASK)));
243 				} else if (power_mode == PCM_LF_MODE) {
244 					PCM->CTL0 = (PCM_KEY | PCM_AM_LF_VCORE0
245 						| (reg_value & ~(PCM_CTL0_KEY_MASK
246 						| PCM_CTL0_AMR_MASK)));
247 				} else
248 					return false;
249 				break;
250 			}
251 			default:
252 				break;
253 		}
254 
255 		if (blocking) {
256 			while (BITBAND_PERI(PCM->CTL1, PCM_CTL1_PMR_BUSY_OFS)) {
257 				if (bool_timeout && !(--time_out))
258 					return false;
259 			}
260 		} else
261 			return true;
262 
263 		current_power_mode = pcm_get_power_mode();
264 		current_power_state = pcm_get_power_state();
265 	}
266 
267 	return true;
268 }
269 
pcm_set_power_mode(uint_fast8_t power_mode)270 bool pcm_set_power_mode(uint_fast8_t power_mode)
271 {
272 	return __pcm_set_power_mode_advanced(power_mode, 0, true);
273 }
274 
__pcm_set_power_state_advanced(uint_fast8_t power_state,uint32_t timeout,bool blocking)275 static bool __pcm_set_power_state_advanced(uint_fast8_t power_state,
276 	uint32_t timeout, bool blocking)
277 {
278 	uint8_t current_power_state;
279 	current_power_state = pcm_get_power_state();
280 
281 	if (current_power_state == power_state)
282 		return true;
283 
284 	switch (power_state) {
285 		case PCM_AM_LDO_VCORE0:
286 			return __pcm_set_core_voltage_level_advanced(PCM_VCORE0, timeout,
287 					blocking) && __pcm_set_power_mode_advanced(PCM_LDO_MODE,
288 					timeout, blocking);
289 		case PCM_AM_LDO_VCORE1:
290 			return __pcm_set_core_voltage_level_advanced(PCM_VCORE1, timeout,
291 					blocking) && __pcm_set_power_mode_advanced(PCM_LDO_MODE,
292 					timeout, blocking);
293 		case PCM_AM_DCDC_VCORE0:
294 			return __pcm_set_core_voltage_level_advanced(PCM_VCORE0, timeout,
295 					blocking) && __pcm_set_power_mode_advanced(PCM_DCDC_MODE,
296 					timeout, blocking);
297 		case PCM_AM_DCDC_VCORE1:
298 			return __pcm_set_core_voltage_level_advanced(PCM_VCORE1, timeout,
299 					blocking) && __pcm_set_power_mode_advanced(PCM_DCDC_MODE,
300 					timeout, blocking);
301 		case PCM_AM_LF_VCORE0:
302 			return __pcm_set_core_voltage_level_advanced(PCM_VCORE0, timeout,
303 					blocking) && __pcm_set_power_mode_advanced(PCM_LF_MODE,
304 					timeout, blocking);
305 		case PCM_AM_LF_VCORE1:
306 			return __pcm_set_core_voltage_level_advanced(PCM_VCORE1, timeout,
307 					blocking) && __pcm_set_power_mode_advanced(PCM_LF_MODE,
308 					timeout, blocking);
309 		case PCM_LPM0_LDO_VCORE0:
310 			if (!__pcm_set_core_voltage_level_advanced(PCM_VCORE0, timeout,
311 				blocking) || !__pcm_set_power_mode_advanced(PCM_LDO_MODE,
312 				timeout, blocking))
313 				break;
314 			return pcm_goto_lpm0();
315 		case PCM_LPM0_LDO_VCORE1:
316 			if (!__pcm_set_core_voltage_level_advanced(PCM_VCORE1, timeout,
317 				blocking) || !__pcm_set_power_mode_advanced(PCM_LDO_MODE,
318 				timeout, blocking))
319 				break;
320 			return pcm_goto_lpm0();
321 		case PCM_LPM0_DCDC_VCORE0:
322 			if (!__pcm_set_core_voltage_level_advanced(PCM_VCORE0, timeout,
323 				blocking) || !__pcm_set_power_mode_advanced(PCM_DCDC_MODE,
324 				timeout, blocking))
325 				break;
326 			return pcm_goto_lpm0();
327 		case PCM_LPM0_DCDC_VCORE1:
328 			if (!__pcm_set_core_voltage_level_advanced(PCM_VCORE1, timeout,
329 				blocking) || !__pcm_set_power_mode_advanced(PCM_DCDC_MODE,
330 				timeout, blocking))
331 				break;
332 			return pcm_goto_lpm0();
333 		case PCM_LPM0_LF_VCORE0:
334 			if (!__pcm_set_core_voltage_level_advanced(PCM_VCORE0, timeout,
335 				blocking) || !__pcm_set_power_mode_advanced(PCM_LF_MODE,
336 				timeout, blocking))
337 				break;
338 			return pcm_goto_lpm0();
339 		case PCM_LPM0_LF_VCORE1:
340 			if (!__pcm_set_core_voltage_level_advanced(PCM_VCORE1, timeout,
341 				blocking) || !__pcm_set_power_mode_advanced(PCM_LF_MODE,
342 				timeout, blocking))
343 				break;
344 			return pcm_goto_lpm0();
345 		case PCM_LPM3:
346 			return pcm_goto_lpm3();
347 		case PCM_LPM4:
348 			return pcm_goto_lpm4();
349 		case PCM_LPM45:
350 			return pcm_shutdown_device(PCM_LPM45);
351 		case PCM_LPM35_VCORE0:
352 			return pcm_shutdown_device(PCM_LPM35_VCORE0);
353 		default:
354 			return false;
355 	}
356 
357 	return false;
358 }
359 
pcm_set_power_state(uint_fast8_t power_state)360 bool pcm_set_power_state(uint_fast8_t power_state)
361 {
362 	return __pcm_set_power_state_advanced(power_state, 0, true);
363 }
364 
pcm_shutdown_device(uint32_t shutdown_mode)365 bool pcm_shutdown_device(uint32_t shutdown_mode)
366 {
367 	uint32_t shutdown_mode_bits = (shutdown_mode == PCM_LPM45) ?
368 		PCM_CTL0_LPMR_12 : PCM_CTL0_LPMR_10;
369 
370 	/* If a power transition is occurring, return false */
371 	if (BITBAND_PERI(PCM->CTL1, PCM_CTL1_PMR_BUSY_OFS))
372 		return false;
373 
374 	/* Initiating the shutdown */
375 	SCB->SCR |= SCB_SCR_SLEEPDEEP_MSK;
376 
377 	PCM->CTL0 = (PCM_KEY | shutdown_mode_bits
378 		| (PCM->CTL0 & ~(PCM_CTL0_KEY_MASK | PCM_CTL0_LPMR_MASK)));
379 
380 	cpu_wfi();
381 
382 	return true;
383 }
384 
pcm_goto_lpm4(void)385 bool pcm_goto_lpm4(void)
386 {
387 	/* Disabling RTC_C and WDT_A */
388 	wdt_a_hold_timer();
389 	rtc_c_hold_clock();
390 
391 	/* LPM4 is just LPM3 with WDT_A/RTC_C disabled... */
392 	return pcm_goto_lpm3();
393 }
394 
pcm_goto_lpm0(void)395 bool pcm_goto_lpm0(void)
396 {
397 	/* If we are in the middle of a state transition, return false */
398 	if (BITBAND_PERI(PCM->CTL1, PCM_CTL1_PMR_BUSY_OFS))
399 		return false;
400 
401 	SCB->SCR &= ~SCB_SCR_SLEEPDEEP_MSK;
402 
403 	cpu_wfi();
404 
405 	return true;
406 }
407 
pcm_goto_lpm3(void)408 bool pcm_goto_lpm3(void)
409 {
410 	uint_fast8_t current_power_state;
411 	uint_fast8_t current_power_mode;
412 
413 	/* If we are in the middle of a state transition, return false */
414 	if (BITBAND_PERI(PCM->CTL1, PCM_CTL1_PMR_BUSY_OFS))
415 		return false;
416 
417 	/* If we are in the middle of a shutdown, return false */
418 	if ((PCM->CTL0 & PCM_CTL0_LPMR_MASK) == PCM_CTL0_LPMR_10
419 		|| (PCM->CTL0 & PCM_CTL0_LPMR_MASK) == PCM_CTL0_LPMR_12)
420 		return false;
421 
422 	current_power_mode = pcm_get_power_mode();
423 	current_power_state = pcm_get_power_state();
424 
425 	if (current_power_mode == PCM_DCDC_MODE)
426 		pcm_set_power_mode(PCM_LDO_MODE);
427 
428 	/* Clearing the SDR */
429 	PCM->CTL0 =
430 		(PCM->CTL0 & ~(PCM_CTL0_KEY_MASK | PCM_CTL0_LPMR_MASK)) | PCM_KEY;
431 
432 	/* Setting the sleep deep bit */
433 	SCB->SCR |= SCB_SCR_SLEEPDEEP_MSK;
434 
435 	cpu_wfi();
436 
437 	SCB->SCR &= ~SCB_SCR_SLEEPDEEP_MSK;
438 
439 	return pcm_set_power_state(current_power_state);
440 }
441 
pcm_get_power_state(void)442 uint8_t pcm_get_power_state(void)
443 {
444 	return (PCM->CTL0 & PCM_CTL0_CPM_MASK) >> PCM_CTL0_CPM_OFS;
445 }
446 
447 #endif
448 
449 /* Real Time Clock APIs */
450 #if defined(RTC_C)
451 
rtc_c_hold_clock(void)452 void rtc_c_hold_clock(void)
453 {
454 	RTC_C->CTL0 = (RTC_C->CTL0 & ~RTC_C_CTL0_KEY_MASK) | RTC_C_KEY;
455 	BITBAND_PERI(RTC_C->CTL13, RTC_C_CTL13_HOLD_OFS) = 1;
456 	BITBAND_PERI(RTC_C->CTL0, RTC_C_CTL0_KEY_OFS) = 0;
457 }
458 
459 #endif
460 
461 /* Watch Dog Timer APIs */
462 #if defined(WDT_A)
463 
wdt_a_hold_timer(void)464 void wdt_a_hold_timer(void)
465 {
466 	/* Set Hold bit */
467 	uint8_t new_wdt_status = (WDT_A->CTL | WDT_A_CTL_HOLD);
468 
469 	WDT_A->CTL = WDT_A_CTL_PW + new_wdt_status;
470 }
471 
472 #endif
473