277 lines
8.1 KiB
C
277 lines
8.1 KiB
C
/*
|
|
* Copyright (c) 2021 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Note: this file is linked to RAM. Any functions called while preparing for
|
|
* sleep mode must be defined within this file, or linked to RAM.
|
|
*/
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/pm/pm.h>
|
|
#include <fsl_dcdc.h>
|
|
#include <fsl_pmu.h>
|
|
#include <fsl_gpc.h>
|
|
#include <fsl_clock.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys/barrier.h>
|
|
|
|
#include "power.h"
|
|
|
|
LOG_MODULE_REGISTER(soc_power, CONFIG_SOC_LOG_LEVEL);
|
|
|
|
|
|
static struct clock_callbacks lpm_clock_hooks;
|
|
|
|
/*
|
|
* Boards with RT10XX SOCs can register callbacks to set their clocks into
|
|
* normal/full speed mode, low speed mode, and low power mode.
|
|
* If callbacks are present, the low power subsystem will disable
|
|
* PLLs for power savings when entering low power states.
|
|
*/
|
|
void imxrt_clock_pm_callbacks_register(struct clock_callbacks *callbacks)
|
|
{
|
|
/* If run callback is set, low power must be as well. */
|
|
__ASSERT_NO_MSG(callbacks && callbacks->clock_set_run && callbacks->clock_set_low_power);
|
|
lpm_clock_hooks.clock_set_run = callbacks->clock_set_run;
|
|
lpm_clock_hooks.clock_set_low_power = callbacks->clock_set_low_power;
|
|
if (callbacks->clock_lpm_init) {
|
|
lpm_clock_hooks.clock_lpm_init = callbacks->clock_lpm_init;
|
|
}
|
|
}
|
|
|
|
static void lpm_set_sleep_mode_config(clock_mode_t mode)
|
|
{
|
|
uint32_t clpcr;
|
|
|
|
/* Set GPC wakeup config to GPT timer interrupt */
|
|
GPC_EnableIRQ(GPC, DT_IRQN(DT_INST(0, nxp_gpt_hw_timer)));
|
|
/*
|
|
* ERR050143: CCM: When improper low-power sequence is used,
|
|
* the SoC enters low power mode before the ARM core executes WFI.
|
|
*
|
|
* Software workaround:
|
|
* 1) Software should trigger IRQ #41 (GPR_IRQ) to be always pending
|
|
* by setting IOMUXC_GPR_GPR1_GINT.
|
|
* 2) Software should then unmask IRQ #41 in GPC before setting CCM
|
|
* Low-Power mode.
|
|
* 3) Software should mask IRQ #41 right after CCM Low-Power mode
|
|
* is set (set bits 0-1 of CCM_CLPCR).
|
|
*/
|
|
GPC_EnableIRQ(GPC, GPR_IRQ_IRQn);
|
|
clpcr = CCM->CLPCR & (~(CCM_CLPCR_LPM_MASK | CCM_CLPCR_ARM_CLK_DIS_ON_LPM_MASK));
|
|
/* Note: if CCM_CLPCR_ARM_CLK_DIS_ON_LPM_MASK is set,
|
|
* debugger will not connect in sleep mode
|
|
*/
|
|
/* Set clock control module to transfer system to idle mode */
|
|
clpcr |= CCM_CLPCR_LPM(mode) | CCM_CLPCR_MASK_SCU_IDLE_MASK |
|
|
CCM_CLPCR_MASK_L2CC_IDLE_MASK |
|
|
CCM_CLPCR_STBY_COUNT_MASK |
|
|
CCM_CLPCR_ARM_CLK_DIS_ON_LPM_MASK;
|
|
#ifndef CONFIG_SOC_MIMXRT1011
|
|
/* RT1011 does not include handshake bits */
|
|
clpcr |= CCM_CLPCR_BYPASS_LPM_HS0_MASK | CCM_CLPCR_BYPASS_LPM_HS1_MASK;
|
|
#endif
|
|
CCM->CLPCR = clpcr;
|
|
GPC_DisableIRQ(GPC, GPR_IRQ_IRQn);
|
|
}
|
|
|
|
static void lpm_enter_soft_off_mode(void)
|
|
{
|
|
/* Enable the SNVS RTC as a wakeup source from soft-off mode, in case an RTC alarm
|
|
* was set.
|
|
*/
|
|
GPC_EnableIRQ(GPC, DT_IRQN(DT_INST(0, nxp_imx_snvs_rtc)));
|
|
SNVS->LPCR |= SNVS_LPCR_TOP_MASK;
|
|
}
|
|
|
|
static void lpm_enter_sleep_mode(clock_mode_t mode)
|
|
{
|
|
/* FIXME: When this function is entered the Kernel has disabled
|
|
* interrupts using BASEPRI register. This is incorrect as it prevents
|
|
* waking up from any interrupt which priority is not 0. Work around the
|
|
* issue and disable interrupts using PRIMASK register as recommended
|
|
* by ARM.
|
|
*/
|
|
|
|
/* Set PRIMASK */
|
|
__disable_irq();
|
|
/* Set BASEPRI to 0 */
|
|
irq_unlock(0);
|
|
barrier_dsync_fence_full();
|
|
barrier_isync_fence_full();
|
|
|
|
if (mode == kCLOCK_ModeWait) {
|
|
/* Clear the SLEEPDEEP bit to go into sleep mode (WAIT) */
|
|
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
|
|
} else {
|
|
/* Set the SLEEPDEEP bit to enable deep sleep mode (STOP) */
|
|
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
|
|
}
|
|
/* WFI instruction will start entry into WAIT/STOP mode */
|
|
__WFI();
|
|
|
|
}
|
|
|
|
static void lpm_set_run_mode_config(void)
|
|
{
|
|
/* Clear GPC wakeup source */
|
|
GPC_DisableIRQ(GPC, DT_IRQN(DT_INST(0, nxp_gpt_hw_timer)));
|
|
CCM->CLPCR &= ~(CCM_CLPCR_LPM_MASK | CCM_CLPCR_ARM_CLK_DIS_ON_LPM_MASK);
|
|
}
|
|
|
|
/* Toggle the analog bandgap reference circuitry on and off */
|
|
static void bandgap_set(bool on)
|
|
{
|
|
if (on) {
|
|
/* Enable bandgap in PMU */
|
|
PMU->MISC0_CLR = PMU_MISC0_REFTOP_PWD_MASK;
|
|
/* Wait for it to stabilize */
|
|
while ((PMU->MISC0 & PMU_MISC0_REFTOP_VBGUP_MASK) == 0) {
|
|
|
|
}
|
|
/* Disable low power bandgap */
|
|
XTALOSC24M->LOWPWR_CTRL_CLR = XTALOSC24M_LOWPWR_CTRL_LPBG_SEL_MASK;
|
|
} else {
|
|
/* Disable bandgap in PMU and switch to low power one */
|
|
XTALOSC24M->LOWPWR_CTRL_SET = XTALOSC24M_LOWPWR_CTRL_LPBG_SEL_MASK;
|
|
PMU->MISC0_SET = PMU_MISC0_REFTOP_PWD_MASK;
|
|
}
|
|
}
|
|
|
|
/* Should only be used if core clocks have been reduced- drops SOC voltage */
|
|
static void lpm_drop_voltage(void)
|
|
{
|
|
/* Move to the internal RC oscillator, since we are using low power clocks */
|
|
CLOCK_InitRcOsc24M();
|
|
/* Switch to internal RC oscillator */
|
|
CLOCK_SwitchOsc(kCLOCK_RcOsc);
|
|
CLOCK_DeinitExternalClk();
|
|
/*
|
|
* Change to 1.075V SOC voltage. If you are experiencing issues with
|
|
* low power mode stability, try raising this voltage value.
|
|
*/
|
|
DCDC_AdjustRunTargetVoltage(DCDC, 0xB);
|
|
/* Enable 2.5 and 1.1V weak regulators */
|
|
PMU_2P5EnableWeakRegulator(PMU, true);
|
|
PMU_1P1EnableWeakRegulator(PMU, true);
|
|
/* Disable normal regulators */
|
|
PMU_2P5EnableOutput(PMU, false);
|
|
PMU_1P1EnableOutput(PMU, false);
|
|
/* Disable analog bandgap */
|
|
bandgap_set(false);
|
|
}
|
|
|
|
/* Undo the changes made by lpm_drop_voltage so clocks can be raised */
|
|
static void lpm_raise_voltage(void)
|
|
{
|
|
/* Enable analog bandgap */
|
|
bandgap_set(true);
|
|
/* Enable regulator LDOs */
|
|
PMU_2P5EnableOutput(PMU, true);
|
|
PMU_1P1EnableOutput(PMU, true);
|
|
/* Disable weak LDOs */
|
|
PMU_2P5EnableWeakRegulator(PMU, false);
|
|
PMU_1P1EnableWeakRegulator(PMU, false);
|
|
/* Change to 1.275V SOC voltage */
|
|
DCDC_AdjustRunTargetVoltage(DCDC, 0x13);
|
|
/* Move to the external RC oscillator */
|
|
CLOCK_InitExternalClk(0);
|
|
/* Switch clock source to external OSC. */
|
|
CLOCK_SwitchOsc(kCLOCK_XtalOsc);
|
|
}
|
|
|
|
|
|
/* Sets device into low power mode */
|
|
void pm_state_set(enum pm_state state, uint8_t substate_id)
|
|
{
|
|
ARG_UNUSED(substate_id);
|
|
|
|
switch (state) {
|
|
case PM_STATE_RUNTIME_IDLE:
|
|
LOG_DBG("entering PM state runtime idle");
|
|
lpm_set_sleep_mode_config(kCLOCK_ModeWait);
|
|
lpm_enter_sleep_mode(kCLOCK_ModeWait);
|
|
break;
|
|
case PM_STATE_SUSPEND_TO_IDLE:
|
|
LOG_DBG("entering PM state suspend to idle");
|
|
if (lpm_clock_hooks.clock_set_low_power) {
|
|
/* Drop the SOC clocks to low power mode, and decrease core voltage */
|
|
lpm_clock_hooks.clock_set_low_power();
|
|
lpm_drop_voltage();
|
|
}
|
|
lpm_set_sleep_mode_config(kCLOCK_ModeWait);
|
|
lpm_enter_sleep_mode(kCLOCK_ModeWait);
|
|
break;
|
|
case PM_STATE_SOFT_OFF:
|
|
LOG_DBG("Entering PM state soft off");
|
|
lpm_enter_soft_off_mode();
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Handle SOC specific activity after Low Power Mode Exit */
|
|
void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id)
|
|
{
|
|
ARG_UNUSED(substate_id);
|
|
|
|
/* Set run mode config after wakeup */
|
|
switch (state) {
|
|
case PM_STATE_RUNTIME_IDLE:
|
|
lpm_set_run_mode_config();
|
|
LOG_DBG("exited PM state runtime idle");
|
|
break;
|
|
case PM_STATE_SUSPEND_TO_IDLE:
|
|
lpm_set_run_mode_config();
|
|
if (lpm_clock_hooks.clock_set_run) {
|
|
/* Raise core voltage and restore SOC clocks */
|
|
lpm_raise_voltage();
|
|
lpm_clock_hooks.clock_set_run();
|
|
}
|
|
LOG_DBG("exited PM state suspend to idle");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
/* Clear PRIMASK after wakeup */
|
|
__enable_irq();
|
|
}
|
|
|
|
/* Initialize power system */
|
|
static int rt10xx_power_init(void)
|
|
{
|
|
dcdc_internal_regulator_config_t reg_config;
|
|
|
|
|
|
/* Ensure clocks to ARM core memory will not be gated in low power mode
|
|
* if interrupt is pending
|
|
*/
|
|
CCM->CGPR |= CCM_CGPR_INT_MEM_CLK_LPM_MASK;
|
|
|
|
if (lpm_clock_hooks.clock_lpm_init) {
|
|
lpm_clock_hooks.clock_lpm_init();
|
|
}
|
|
|
|
/* Errata ERR050143 */
|
|
IOMUXC_GPR->GPR1 |= IOMUXC_GPR_GPR1_GINT_MASK;
|
|
|
|
/* Configure DCDC */
|
|
DCDC_BootIntoDCM(DCDC);
|
|
/* Set target voltage for low power mode to 0.925V*/
|
|
DCDC_AdjustLowPowerTargetVoltage(DCDC, 0x1);
|
|
/* Reconfigure DCDC to disable internal load resistor */
|
|
reg_config.enableLoadResistor = false;
|
|
reg_config.feedbackPoint = 0x1; /* 1.0V with 1.3V reference voltage */
|
|
DCDC_SetInternalRegulatorConfig(DCDC, ®_config);
|
|
|
|
/* Enable high gate drive on power FETs to reduce leakage current */
|
|
PMU_CoreEnableIncreaseGateDrive(PMU, true);
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
SYS_INIT(rt10xx_power_init, PRE_KERNEL_2, 0);
|