1 /*
2  * Copyright (c) 2021 NXP
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Note: this file is linked to RAM. Any functions called while preparing for
7  * sleep mode must be defined within this file, or linked to RAM.
8  */
9 #include <zephyr/kernel.h>
10 #include <zephyr/init.h>
11 #include <zephyr/pm/pm.h>
12 #include <fsl_dcdc.h>
13 #include <fsl_pmu.h>
14 #include <fsl_gpc.h>
15 #include <fsl_clock.h>
16 #include <zephyr/logging/log.h>
17 #include <zephyr/sys/barrier.h>
18 
19 #include "power_rt10xx.h"
20 
21 LOG_MODULE_REGISTER(soc_power, CONFIG_SOC_LOG_LEVEL);
22 
23 
24 static struct clock_callbacks lpm_clock_hooks;
25 
26 /*
27  * Boards with RT10XX SOCs can register callbacks to set their clocks into
28  * normal/full speed mode, low speed mode, and low power mode.
29  * If callbacks are present, the low power subsystem will disable
30  * PLLs for power savings when entering low power states.
31  */
imxrt_clock_pm_callbacks_register(struct clock_callbacks * callbacks)32 void imxrt_clock_pm_callbacks_register(struct clock_callbacks *callbacks)
33 {
34 	/* If run callback is set, low power must be as well. */
35 	__ASSERT_NO_MSG(callbacks && callbacks->clock_set_run && callbacks->clock_set_low_power);
36 	lpm_clock_hooks.clock_set_run = callbacks->clock_set_run;
37 	lpm_clock_hooks.clock_set_low_power = callbacks->clock_set_low_power;
38 	if (callbacks->clock_lpm_init) {
39 		lpm_clock_hooks.clock_lpm_init = callbacks->clock_lpm_init;
40 	}
41 }
42 
lpm_set_sleep_mode_config(clock_mode_t mode)43 static void lpm_set_sleep_mode_config(clock_mode_t mode)
44 {
45 	uint32_t clpcr;
46 
47 	/* Set GPC wakeup config to GPT timer interrupt */
48 	GPC_EnableIRQ(GPC, DT_IRQN(DT_INST(0, nxp_gpt_hw_timer)));
49 	/*
50 	 * ERR050143: CCM: When improper low-power sequence is used,
51 	 * the SoC enters low power mode before the ARM core executes WFI.
52 	 *
53 	 * Software workaround:
54 	 * 1) Software should trigger IRQ #41 (GPR_IRQ) to be always pending
55 	 *      by setting IOMUXC_GPR_GPR1_GINT.
56 	 * 2) Software should then unmask IRQ #41 in GPC before setting CCM
57 	 *      Low-Power mode.
58 	 * 3) Software should mask IRQ #41 right after CCM Low-Power mode
59 	 *      is set (set bits 0-1 of CCM_CLPCR).
60 	 */
61 	GPC_EnableIRQ(GPC, GPR_IRQ_IRQn);
62 	clpcr = CCM->CLPCR & (~(CCM_CLPCR_LPM_MASK | CCM_CLPCR_ARM_CLK_DIS_ON_LPM_MASK));
63 	/* Note: if CCM_CLPCR_ARM_CLK_DIS_ON_LPM_MASK is set,
64 	 * debugger will not connect in sleep mode
65 	 */
66 	/* Set clock control module to transfer system to idle mode */
67 	clpcr |= CCM_CLPCR_LPM(mode) | CCM_CLPCR_MASK_SCU_IDLE_MASK |
68 		     CCM_CLPCR_MASK_L2CC_IDLE_MASK |
69 		     CCM_CLPCR_STBY_COUNT_MASK |
70 		     CCM_CLPCR_ARM_CLK_DIS_ON_LPM_MASK;
71 #ifndef CONFIG_SOC_MIMXRT1011
72 	/* RT1011 does not include handshake bits */
73 	clpcr |= CCM_CLPCR_BYPASS_LPM_HS0_MASK | CCM_CLPCR_BYPASS_LPM_HS1_MASK;
74 #endif
75 	CCM->CLPCR = clpcr;
76 	GPC_DisableIRQ(GPC, GPR_IRQ_IRQn);
77 }
78 
lpm_enter_soft_off_mode(void)79 static void lpm_enter_soft_off_mode(void)
80 {
81 	/* Enable the SNVS RTC as a wakeup source from soft-off mode, in case an RTC alarm
82 	 * was set.
83 	 */
84 	GPC_EnableIRQ(GPC, DT_IRQN(DT_INST(0, nxp_imx_snvs_rtc)));
85 	SNVS->LPCR |= SNVS_LPCR_TOP_MASK;
86 }
87 
lpm_enter_sleep_mode(clock_mode_t mode)88 static void lpm_enter_sleep_mode(clock_mode_t mode)
89 {
90 	/* FIXME: When this function is entered the Kernel has disabled
91 	 * interrupts using BASEPRI register. This is incorrect as it prevents
92 	 * waking up from any interrupt which priority is not 0. Work around the
93 	 * issue and disable interrupts using PRIMASK register as recommended
94 	 * by ARM.
95 	 */
96 
97 	/* Set PRIMASK */
98 	__disable_irq();
99 	/* Set BASEPRI to 0 */
100 	irq_unlock(0);
101 	barrier_dsync_fence_full();
102 	barrier_isync_fence_full();
103 
104 	if (mode == kCLOCK_ModeWait) {
105 		/* Clear the SLEEPDEEP bit to go into sleep mode (WAIT) */
106 		SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
107 	} else {
108 		/* Set the SLEEPDEEP bit to enable deep sleep mode (STOP) */
109 		SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
110 	}
111 	/* WFI instruction will start entry into WAIT/STOP mode */
112 	__WFI();
113 
114 }
115 
lpm_set_run_mode_config(void)116 static void lpm_set_run_mode_config(void)
117 {
118 	/* Clear GPC wakeup source */
119 	GPC_DisableIRQ(GPC, DT_IRQN(DT_INST(0, nxp_gpt_hw_timer)));
120 	CCM->CLPCR &= ~(CCM_CLPCR_LPM_MASK | CCM_CLPCR_ARM_CLK_DIS_ON_LPM_MASK);
121 }
122 
123 /* Toggle the analog bandgap reference circuitry on and off */
bandgap_set(bool on)124 static void bandgap_set(bool on)
125 {
126 	if (on) {
127 		/* Enable bandgap in PMU */
128 		PMU->MISC0_CLR = PMU_MISC0_REFTOP_PWD_MASK;
129 		/* Wait for it to stabilize */
130 		while ((PMU->MISC0 & PMU_MISC0_REFTOP_VBGUP_MASK) == 0) {
131 
132 		}
133 		/* Disable low power bandgap */
134 		XTALOSC24M->LOWPWR_CTRL_CLR = XTALOSC24M_LOWPWR_CTRL_LPBG_SEL_MASK;
135 	} else {
136 		/* Disable bandgap in PMU and switch to low power one */
137 		XTALOSC24M->LOWPWR_CTRL_SET = XTALOSC24M_LOWPWR_CTRL_LPBG_SEL_MASK;
138 		PMU->MISC0_SET = PMU_MISC0_REFTOP_PWD_MASK;
139 	}
140 }
141 
142 /* Should only be used if core clocks have been reduced- drops SOC voltage */
lpm_drop_voltage(void)143 static void lpm_drop_voltage(void)
144 {
145 	/* Move to the internal RC oscillator, since we are using low power clocks */
146 	CLOCK_InitRcOsc24M();
147 	/* Switch to internal RC oscillator */
148 	CLOCK_SwitchOsc(kCLOCK_RcOsc);
149 	CLOCK_DeinitExternalClk();
150 	/*
151 	 * Change to 1.075V SOC voltage. If you are experiencing issues with
152 	 * low power mode stability, try raising this voltage value.
153 	 */
154 	DCDC_AdjustRunTargetVoltage(DCDC, 0xB);
155 	/* Enable 2.5 and 1.1V weak regulators */
156 	PMU_2P5EnableWeakRegulator(PMU, true);
157 	PMU_1P1EnableWeakRegulator(PMU, true);
158 	/* Disable normal regulators */
159 	PMU_2P5EnableOutput(PMU, false);
160 	PMU_1P1EnableOutput(PMU, false);
161 	/* Disable analog bandgap */
162 	bandgap_set(false);
163 }
164 
165 /* Undo the changes made by lpm_drop_voltage so clocks can be raised */
lpm_raise_voltage(void)166 static void lpm_raise_voltage(void)
167 {
168 	/* Enable analog bandgap */
169 	bandgap_set(true);
170 	/* Enable regulator LDOs */
171 	PMU_2P5EnableOutput(PMU, true);
172 	PMU_1P1EnableOutput(PMU, true);
173 	/* Disable weak LDOs */
174 	PMU_2P5EnableWeakRegulator(PMU, false);
175 	PMU_1P1EnableWeakRegulator(PMU, false);
176 	/* Change to 1.275V SOC voltage */
177 	DCDC_AdjustRunTargetVoltage(DCDC, 0x13);
178 	/* Move to the external RC oscillator */
179 	CLOCK_InitExternalClk(0);
180 	/* Switch clock source to external OSC. */
181 	CLOCK_SwitchOsc(kCLOCK_XtalOsc);
182 }
183 
184 
185 /* Sets device into low power mode */
pm_state_set(enum pm_state state,uint8_t substate_id)186 void pm_state_set(enum pm_state state, uint8_t substate_id)
187 {
188 	ARG_UNUSED(substate_id);
189 
190 	switch (state) {
191 	case PM_STATE_RUNTIME_IDLE:
192 		LOG_DBG("entering PM state runtime idle");
193 		lpm_set_sleep_mode_config(kCLOCK_ModeWait);
194 		lpm_enter_sleep_mode(kCLOCK_ModeWait);
195 		break;
196 	case PM_STATE_SUSPEND_TO_IDLE:
197 		LOG_DBG("entering PM state suspend to idle");
198 		if (lpm_clock_hooks.clock_set_low_power) {
199 			/* Drop the SOC clocks to low power mode, and decrease core voltage */
200 			lpm_clock_hooks.clock_set_low_power();
201 			lpm_drop_voltage();
202 		}
203 		lpm_set_sleep_mode_config(kCLOCK_ModeWait);
204 		lpm_enter_sleep_mode(kCLOCK_ModeWait);
205 		break;
206 	case PM_STATE_SOFT_OFF:
207 		LOG_DBG("Entering PM state soft off");
208 		lpm_enter_soft_off_mode();
209 		break;
210 	default:
211 		return;
212 	}
213 }
214 
215 /* Handle SOC specific activity after Low Power Mode Exit */
pm_state_exit_post_ops(enum pm_state state,uint8_t substate_id)216 void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id)
217 {
218 	ARG_UNUSED(substate_id);
219 
220 	/* Set run mode config after wakeup */
221 	switch (state) {
222 	case PM_STATE_RUNTIME_IDLE:
223 		lpm_set_run_mode_config();
224 		LOG_DBG("exited PM state runtime idle");
225 		break;
226 	case PM_STATE_SUSPEND_TO_IDLE:
227 		lpm_set_run_mode_config();
228 		if (lpm_clock_hooks.clock_set_run) {
229 			/* Raise core voltage and restore SOC clocks */
230 			lpm_raise_voltage();
231 			lpm_clock_hooks.clock_set_run();
232 		}
233 		LOG_DBG("exited PM state suspend to idle");
234 		break;
235 	default:
236 		break;
237 	}
238 	/* Clear PRIMASK after wakeup */
239 	__enable_irq();
240 }
241 
242 /* Initialize power system */
rt10xx_power_init(void)243 static int rt10xx_power_init(void)
244 {
245 	dcdc_internal_regulator_config_t reg_config;
246 
247 
248 	/* Ensure clocks to ARM core memory will not be gated in low power mode
249 	 * if interrupt is pending
250 	 */
251 	CCM->CGPR |= CCM_CGPR_INT_MEM_CLK_LPM_MASK;
252 
253 	if (lpm_clock_hooks.clock_lpm_init) {
254 		lpm_clock_hooks.clock_lpm_init();
255 	}
256 
257 	/* Errata ERR050143 */
258 	IOMUXC_GPR->GPR1 |= IOMUXC_GPR_GPR1_GINT_MASK;
259 
260 	/* Configure DCDC */
261 	DCDC_BootIntoDCM(DCDC);
262 	/* Set target voltage for low power mode to 0.925V*/
263 	DCDC_AdjustLowPowerTargetVoltage(DCDC, 0x1);
264 	/* Reconfigure DCDC to disable internal load resistor */
265 	reg_config.enableLoadResistor = false;
266 	reg_config.feedbackPoint = 0x1; /* 1.0V with 1.3V reference voltage */
267 	DCDC_SetInternalRegulatorConfig(DCDC, &reg_config);
268 
269 	/* Enable high gate drive on power FETs to reduce leakage current */
270 	PMU_CoreEnableIncreaseGateDrive(PMU, true);
271 
272 
273 	return 0;
274 }
275 
276 SYS_INIT(rt10xx_power_init, PRE_KERNEL_2, 0);
277