473 lines
13 KiB
C
473 lines
13 KiB
C
/******************************************************************************
|
|
*
|
|
* Copyright (C) 2017-2018 Texas Instruments Incorporated - http://www.ti.com/
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the
|
|
* distribution.
|
|
*
|
|
* Neither the name of Texas Instruments Incorporated nor the names of
|
|
* its contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
******************************************************************************/
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include "driverlib.h"
|
|
|
|
/*
|
|
* Wrapper function for the CPSID instruction.
|
|
* Returns the state of PRIMASK on entry.
|
|
*/
|
|
uint32_t __attribute__((naked)) cpu_cpsid(void)
|
|
{
|
|
uint32_t ret;
|
|
|
|
/* Read PRIMASK and disable interrupts. */
|
|
__asm(" mrs r0, PRIMASK\n"
|
|
" cpsid i\n"
|
|
" bx lr\n"
|
|
: "=r" (ret));
|
|
|
|
/*
|
|
* The return is handled in the inline assembly, but the compiler will
|
|
* still complain if there is not an explicit return here (despite the fact
|
|
* that this does not result in any code being produced because of the
|
|
* naked attribute).
|
|
*/
|
|
return ret;
|
|
}
|
|
|
|
/* Wrapper function for the CPUWFI instruction. */
|
|
void __attribute__((naked)) cpu_wfi(void)
|
|
{
|
|
/* Wait for the next interrupt. */
|
|
__asm(" wfi\n"
|
|
" bx lr\n");
|
|
}
|
|
|
|
/* Power Control Module APIs */
|
|
#if defined(PCM)
|
|
|
|
static bool __pcm_set_core_voltage_level_advanced(uint_fast8_t voltage_level,
|
|
uint32_t time_out, bool blocking)
|
|
{
|
|
uint8_t power_mode;
|
|
uint8_t current_voltage_level;
|
|
uint32_t reg_value;
|
|
bool bool_timeout;
|
|
|
|
/* Getting current power mode and level */
|
|
power_mode = pcm_get_power_mode();
|
|
current_voltage_level = pcm_get_core_voltage_level();
|
|
|
|
bool_timeout = time_out > 0 ? true : false;
|
|
|
|
/* If we are already at the power mode they requested, return */
|
|
if (current_voltage_level == voltage_level)
|
|
return true;
|
|
|
|
while (current_voltage_level != voltage_level) {
|
|
|
|
reg_value = PCM->CTL0;
|
|
|
|
switch (pcm_get_power_state()) {
|
|
case PCM_AM_LF_VCORE1:
|
|
case PCM_AM_DCDC_VCORE1:
|
|
case PCM_AM_LDO_VCORE0:
|
|
PCM->CTL0 = (PCM_KEY | (PCM_AM_LDO_VCORE1)
|
|
| (reg_value & ~(PCM_CTL0_KEY_MASK | PCM_CTL0_AMR_MASK)));
|
|
break;
|
|
case PCM_AM_LF_VCORE0:
|
|
case PCM_AM_DCDC_VCORE0:
|
|
case PCM_AM_LDO_VCORE1:
|
|
PCM->CTL0 = (PCM_KEY | (PCM_AM_LDO_VCORE0)
|
|
| (reg_value & ~(PCM_CTL0_KEY_MASK | PCM_CTL0_AMR_MASK)));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (blocking) {
|
|
while (BITBAND_PERI(PCM->CTL1, PCM_CTL1_PMR_BUSY_OFS)) {
|
|
if (bool_timeout && !(--time_out))
|
|
return false;
|
|
}
|
|
} else
|
|
return true;
|
|
|
|
current_voltage_level = pcm_get_core_voltage_level();
|
|
}
|
|
|
|
/* Changing the power mode if we are stuck in LDO mode */
|
|
if (power_mode != pcm_get_power_mode()) {
|
|
if (power_mode == PCM_DCDC_MODE)
|
|
return pcm_set_power_mode(PCM_DCDC_MODE);
|
|
else
|
|
return pcm_set_power_mode(PCM_LF_MODE);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool pcm_set_core_voltage_level(uint_fast8_t voltage_level)
|
|
{
|
|
return __pcm_set_core_voltage_level_advanced(voltage_level, 0, true);
|
|
}
|
|
|
|
uint8_t pcm_get_power_mode(void)
|
|
{
|
|
uint8_t current_power_state;
|
|
|
|
current_power_state = pcm_get_power_state();
|
|
|
|
switch (current_power_state) {
|
|
case PCM_AM_LDO_VCORE0:
|
|
case PCM_AM_LDO_VCORE1:
|
|
case PCM_LPM0_LDO_VCORE0:
|
|
case PCM_LPM0_LDO_VCORE1:
|
|
default:
|
|
return PCM_LDO_MODE;
|
|
case PCM_AM_DCDC_VCORE0:
|
|
case PCM_AM_DCDC_VCORE1:
|
|
case PCM_LPM0_DCDC_VCORE0:
|
|
case PCM_LPM0_DCDC_VCORE1:
|
|
return PCM_DCDC_MODE;
|
|
case PCM_LPM0_LF_VCORE0:
|
|
case PCM_LPM0_LF_VCORE1:
|
|
case PCM_AM_LF_VCORE1:
|
|
case PCM_AM_LF_VCORE0:
|
|
return PCM_LF_MODE;
|
|
}
|
|
}
|
|
|
|
uint8_t pcm_get_core_voltage_level(void)
|
|
{
|
|
uint8_t current_power_state = pcm_get_power_state();
|
|
|
|
switch (current_power_state) {
|
|
case PCM_AM_LDO_VCORE0:
|
|
case PCM_AM_DCDC_VCORE0:
|
|
case PCM_AM_LF_VCORE0:
|
|
case PCM_LPM0_LDO_VCORE0:
|
|
case PCM_LPM0_DCDC_VCORE0:
|
|
case PCM_LPM0_LF_VCORE0:
|
|
default:
|
|
return PCM_VCORE0;
|
|
case PCM_AM_LDO_VCORE1:
|
|
case PCM_AM_DCDC_VCORE1:
|
|
case PCM_AM_LF_VCORE1:
|
|
case PCM_LPM0_LDO_VCORE1:
|
|
case PCM_LPM0_DCDC_VCORE1:
|
|
case PCM_LPM0_LF_VCORE1:
|
|
return PCM_VCORE1;
|
|
case PCM_LPM3:
|
|
return PCM_VCORELPM3;
|
|
}
|
|
}
|
|
|
|
static bool __pcm_set_power_mode_advanced(uint_fast8_t power_mode,
|
|
uint32_t time_out, bool blocking)
|
|
{
|
|
uint8_t current_power_mode;
|
|
uint8_t current_power_state;
|
|
uint32_t reg_value;
|
|
bool bool_timeout;
|
|
|
|
/* Getting Current Power Mode */
|
|
current_power_mode = pcm_get_power_mode();
|
|
|
|
/* If the power mode being set it the same as the current mode, return */
|
|
if (power_mode == current_power_mode)
|
|
return true;
|
|
|
|
current_power_state = pcm_get_power_state();
|
|
|
|
bool_timeout = time_out > 0 ? true : false;
|
|
|
|
/* Go through the while loop while we haven't achieved the power mode */
|
|
while (current_power_mode != power_mode) {
|
|
|
|
reg_value = PCM->CTL0;
|
|
|
|
switch (current_power_state) {
|
|
case PCM_AM_DCDC_VCORE0:
|
|
case PCM_AM_LF_VCORE0:
|
|
PCM->CTL0 = (PCM_KEY | PCM_AM_LDO_VCORE0
|
|
| (reg_value & ~(PCM_CTL0_KEY_MASK | PCM_CTL0_AMR_MASK)));
|
|
break;
|
|
case PCM_AM_LF_VCORE1:
|
|
case PCM_AM_DCDC_VCORE1:
|
|
PCM->CTL0 = (PCM_KEY | PCM_AM_LDO_VCORE1
|
|
| (reg_value & ~(PCM_CTL0_KEY_MASK | PCM_CTL0_AMR_MASK)));
|
|
break;
|
|
case PCM_AM_LDO_VCORE1: {
|
|
if (power_mode == PCM_DCDC_MODE) {
|
|
PCM->CTL0 = (PCM_KEY | PCM_AM_DCDC_VCORE1
|
|
| (reg_value & ~(PCM_CTL0_KEY_MASK
|
|
| PCM_CTL0_AMR_MASK)));
|
|
} else if (power_mode == PCM_LF_MODE) {
|
|
PCM->CTL0 = (PCM_KEY | PCM_AM_LF_VCORE1
|
|
| (reg_value & ~(PCM_CTL0_KEY_MASK
|
|
| PCM_CTL0_AMR_MASK)));
|
|
} else
|
|
return false;
|
|
break;
|
|
}
|
|
case PCM_AM_LDO_VCORE0: {
|
|
if (power_mode == PCM_DCDC_MODE) {
|
|
PCM->CTL0 = (PCM_KEY | PCM_AM_DCDC_VCORE0
|
|
| (reg_value & ~(PCM_CTL0_KEY_MASK
|
|
| PCM_CTL0_AMR_MASK)));
|
|
} else if (power_mode == PCM_LF_MODE) {
|
|
PCM->CTL0 = (PCM_KEY | PCM_AM_LF_VCORE0
|
|
| (reg_value & ~(PCM_CTL0_KEY_MASK
|
|
| PCM_CTL0_AMR_MASK)));
|
|
} else
|
|
return false;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (blocking) {
|
|
while (BITBAND_PERI(PCM->CTL1, PCM_CTL1_PMR_BUSY_OFS)) {
|
|
if (bool_timeout && !(--time_out))
|
|
return false;
|
|
}
|
|
} else
|
|
return true;
|
|
|
|
current_power_mode = pcm_get_power_mode();
|
|
current_power_state = pcm_get_power_state();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool pcm_set_power_mode(uint_fast8_t power_mode)
|
|
{
|
|
return __pcm_set_power_mode_advanced(power_mode, 0, true);
|
|
}
|
|
|
|
static bool __pcm_set_power_state_advanced(uint_fast8_t power_state,
|
|
uint32_t timeout, bool blocking)
|
|
{
|
|
uint8_t current_power_state;
|
|
current_power_state = pcm_get_power_state();
|
|
|
|
if (current_power_state == power_state)
|
|
return true;
|
|
|
|
switch (power_state) {
|
|
case PCM_AM_LDO_VCORE0:
|
|
return __pcm_set_core_voltage_level_advanced(PCM_VCORE0, timeout,
|
|
blocking) && __pcm_set_power_mode_advanced(PCM_LDO_MODE,
|
|
timeout, blocking);
|
|
case PCM_AM_LDO_VCORE1:
|
|
return __pcm_set_core_voltage_level_advanced(PCM_VCORE1, timeout,
|
|
blocking) && __pcm_set_power_mode_advanced(PCM_LDO_MODE,
|
|
timeout, blocking);
|
|
case PCM_AM_DCDC_VCORE0:
|
|
return __pcm_set_core_voltage_level_advanced(PCM_VCORE0, timeout,
|
|
blocking) && __pcm_set_power_mode_advanced(PCM_DCDC_MODE,
|
|
timeout, blocking);
|
|
case PCM_AM_DCDC_VCORE1:
|
|
return __pcm_set_core_voltage_level_advanced(PCM_VCORE1, timeout,
|
|
blocking) && __pcm_set_power_mode_advanced(PCM_DCDC_MODE,
|
|
timeout, blocking);
|
|
case PCM_AM_LF_VCORE0:
|
|
return __pcm_set_core_voltage_level_advanced(PCM_VCORE0, timeout,
|
|
blocking) && __pcm_set_power_mode_advanced(PCM_LF_MODE,
|
|
timeout, blocking);
|
|
case PCM_AM_LF_VCORE1:
|
|
return __pcm_set_core_voltage_level_advanced(PCM_VCORE1, timeout,
|
|
blocking) && __pcm_set_power_mode_advanced(PCM_LF_MODE,
|
|
timeout, blocking);
|
|
case PCM_LPM0_LDO_VCORE0:
|
|
if (!__pcm_set_core_voltage_level_advanced(PCM_VCORE0, timeout,
|
|
blocking) || !__pcm_set_power_mode_advanced(PCM_LDO_MODE,
|
|
timeout, blocking))
|
|
break;
|
|
return pcm_goto_lpm0();
|
|
case PCM_LPM0_LDO_VCORE1:
|
|
if (!__pcm_set_core_voltage_level_advanced(PCM_VCORE1, timeout,
|
|
blocking) || !__pcm_set_power_mode_advanced(PCM_LDO_MODE,
|
|
timeout, blocking))
|
|
break;
|
|
return pcm_goto_lpm0();
|
|
case PCM_LPM0_DCDC_VCORE0:
|
|
if (!__pcm_set_core_voltage_level_advanced(PCM_VCORE0, timeout,
|
|
blocking) || !__pcm_set_power_mode_advanced(PCM_DCDC_MODE,
|
|
timeout, blocking))
|
|
break;
|
|
return pcm_goto_lpm0();
|
|
case PCM_LPM0_DCDC_VCORE1:
|
|
if (!__pcm_set_core_voltage_level_advanced(PCM_VCORE1, timeout,
|
|
blocking) || !__pcm_set_power_mode_advanced(PCM_DCDC_MODE,
|
|
timeout, blocking))
|
|
break;
|
|
return pcm_goto_lpm0();
|
|
case PCM_LPM0_LF_VCORE0:
|
|
if (!__pcm_set_core_voltage_level_advanced(PCM_VCORE0, timeout,
|
|
blocking) || !__pcm_set_power_mode_advanced(PCM_LF_MODE,
|
|
timeout, blocking))
|
|
break;
|
|
return pcm_goto_lpm0();
|
|
case PCM_LPM0_LF_VCORE1:
|
|
if (!__pcm_set_core_voltage_level_advanced(PCM_VCORE1, timeout,
|
|
blocking) || !__pcm_set_power_mode_advanced(PCM_LF_MODE,
|
|
timeout, blocking))
|
|
break;
|
|
return pcm_goto_lpm0();
|
|
case PCM_LPM3:
|
|
return pcm_goto_lpm3();
|
|
case PCM_LPM4:
|
|
return pcm_goto_lpm4();
|
|
case PCM_LPM45:
|
|
return pcm_shutdown_device(PCM_LPM45);
|
|
case PCM_LPM35_VCORE0:
|
|
return pcm_shutdown_device(PCM_LPM35_VCORE0);
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool pcm_set_power_state(uint_fast8_t power_state)
|
|
{
|
|
return __pcm_set_power_state_advanced(power_state, 0, true);
|
|
}
|
|
|
|
bool pcm_shutdown_device(uint32_t shutdown_mode)
|
|
{
|
|
uint32_t shutdown_mode_bits = (shutdown_mode == PCM_LPM45) ?
|
|
PCM_CTL0_LPMR_12 : PCM_CTL0_LPMR_10;
|
|
|
|
/* If a power transition is occuring, return false */
|
|
if (BITBAND_PERI(PCM->CTL1, PCM_CTL1_PMR_BUSY_OFS))
|
|
return false;
|
|
|
|
/* Initiating the shutdown */
|
|
SCB->SCR |= SCB_SCR_SLEEPDEEP_MSK;
|
|
|
|
PCM->CTL0 = (PCM_KEY | shutdown_mode_bits
|
|
| (PCM->CTL0 & ~(PCM_CTL0_KEY_MASK | PCM_CTL0_LPMR_MASK)));
|
|
|
|
cpu_wfi();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool pcm_goto_lpm4(void)
|
|
{
|
|
/* Disabling RTC_C and WDT_A */
|
|
wdt_a_hold_timer();
|
|
rtc_c_hold_clock();
|
|
|
|
/* LPM4 is just LPM3 with WDT_A/RTC_C disabled... */
|
|
return pcm_goto_lpm3();
|
|
}
|
|
|
|
bool pcm_goto_lpm0(void)
|
|
{
|
|
/* If we are in the middle of a state transition, return false */
|
|
if (BITBAND_PERI(PCM->CTL1, PCM_CTL1_PMR_BUSY_OFS))
|
|
return false;
|
|
|
|
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_MSK;
|
|
|
|
cpu_wfi();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool pcm_goto_lpm3(void)
|
|
{
|
|
uint_fast8_t current_power_state;
|
|
uint_fast8_t current_power_mode;
|
|
|
|
/* If we are in the middle of a state transition, return false */
|
|
if (BITBAND_PERI(PCM->CTL1, PCM_CTL1_PMR_BUSY_OFS))
|
|
return false;
|
|
|
|
/* If we are in the middle of a shutdown, return false */
|
|
if ((PCM->CTL0 & PCM_CTL0_LPMR_MASK) == PCM_CTL0_LPMR_10
|
|
|| (PCM->CTL0 & PCM_CTL0_LPMR_MASK) == PCM_CTL0_LPMR_12)
|
|
return false;
|
|
|
|
current_power_mode = pcm_get_power_mode();
|
|
current_power_state = pcm_get_power_state();
|
|
|
|
if (current_power_mode == PCM_DCDC_MODE)
|
|
pcm_set_power_mode(PCM_LDO_MODE);
|
|
|
|
/* Clearing the SDR */
|
|
PCM->CTL0 =
|
|
(PCM->CTL0 & ~(PCM_CTL0_KEY_MASK | PCM_CTL0_LPMR_MASK)) | PCM_KEY;
|
|
|
|
/* Setting the sleep deep bit */
|
|
SCB->SCR |= SCB_SCR_SLEEPDEEP_MSK;
|
|
|
|
cpu_wfi();
|
|
|
|
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_MSK;
|
|
|
|
return pcm_set_power_state(current_power_state);
|
|
}
|
|
|
|
uint8_t pcm_get_power_state(void)
|
|
{
|
|
return (PCM->CTL0 & PCM_CTL0_CPM_MASK) >> PCM_CTL0_CPM_OFS;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* Real Time Clock APIs */
|
|
#if defined(RTC_C)
|
|
|
|
void rtc_c_hold_clock(void)
|
|
{
|
|
RTC_C->CTL0 = (RTC_C->CTL0 & ~RTC_C_CTL0_KEY_MASK) | RTC_C_KEY;
|
|
BITBAND_PERI(RTC_C->CTL13, RTC_C_CTL13_HOLD_OFS) = 1;
|
|
BITBAND_PERI(RTC_C->CTL0, RTC_C_CTL0_KEY_OFS) = 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* Watch Dog Timer APIs */
|
|
#if defined(WDT_A)
|
|
|
|
void wdt_a_hold_timer(void)
|
|
{
|
|
/* Set Hold bit */
|
|
uint8_t new_wdt_status = (WDT_A->CTL | WDT_A_CTL_HOLD);
|
|
|
|
WDT_A->CTL = WDT_A_CTL_PW + new_wdt_status;
|
|
}
|
|
|
|
#endif
|