1 /***************************************************************************//**
2 * @file
3 * @brief Power Manager EM4 API implementation.
4 *******************************************************************************
5 * # License
6 * <b>Copyright 2024 Silicon Laboratories Inc. www.silabs.com</b>
7 *******************************************************************************
8 *
9 * SPDX-License-Identifier: Zlib
10 *
11 * The licensor of this software is Silicon Laboratories Inc.
12 *
13 * This software is provided 'as-is', without any express or implied
14 * warranty. In no event will the authors be held liable for any damages
15 * arising from the use of this software.
16 *
17 * Permission is granted to anyone to use this software for any purpose,
18 * including commercial applications, and to alter it and redistribute it
19 * freely, subject to the following restrictions:
20 *
21 * 1. The origin of this software must not be misrepresented; you must not
22 * claim that you wrote the original software. If you use this software
23 * in a product, an acknowledgment in the product documentation would be
24 * appreciated but is not required.
25 * 2. Altered source versions must be plainly marked as such, and must not be
26 * misrepresented as being the original software.
27 * 3. This notice may not be removed or altered from any source distribution.
28 *
29 ******************************************************************************/
30
31 #include "sl_power_manager.h"
32 #include "sl_power_manager_config.h"
33 #include "sli_power_manager.h"
34
35 #if defined(SL_COMPONENT_CATALOG_PRESENT)
36 #include "sl_component_catalog.h"
37 #endif
38
39 #include "em_device.h"
40 #if defined(_SILICON_LABS_32B_SERIES_2)
41 #include "em_emu.h"
42 #include "em_cmu.h"
43 #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2)
44 #include "em_iadc.h"
45 #endif
46 #endif
47
48 #include <stdlib.h>
49 #include <stdint.h>
50
51 /*******************************************************************************
52 ****************************** DEFINES ************************************
53 ******************************************************************************/
54
55 #if defined(WDOG_PRESENT)
56 // Macros to determine if WDOG instances are clocked or not
57
58 #if defined(CMU_CLKEN0_WDOG0)
59 #define WDOG0_CLOCK_ENABLED_BIT (CMU->CLKEN0 & CMU_CLKEN0_WDOG0)
60 #else
61 // There's no CMU->CLKEN1 so assume the WDOG0 is clocked
62 #define WDOG0_CLOCK_ENABLED_BIT 1
63 #endif
64
65 #if defined(CMU_CLKEN1_WDOG1)
66 #define WDOG1_CLOCK_ENABLED_BIT (CMU->CLKEN1 & CMU_CLKEN1_WDOG1)
67 #else
68 // There's no CMU->CLKEN1 so assume the WDOG1 is clocked
69 #define WDOG1_CLOCK_ENABLED_BIT 1
70 #endif
71
72 #endif
73
74 /*******************************************************************************
75 ************************** LOCAL FUNCTIONS ********************************
76 ******************************************************************************/
77
78 static bool is_em4_blocked(void);
79
80 #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2) && (SL_POWER_MANAGER_RAMP_DVDD_EN == 1)
81 static void ramp_dvdd_and_switch_to_dcdc_bypass_mode(void);
82 #endif
83
84 /*******************************************************************************
85 ************************** GLOBAL FUNCTIONS *******************************
86 ******************************************************************************/
87
88 /***************************************************************************//**
89 * update energy mode 4 configurations.
90 ******************************************************************************/
sli_power_manager_init_em4(void)91 void sli_power_manager_init_em4(void)
92 {
93 #if !defined(_SILICON_LABS_32B_SERIES_2)
94 EMU->EM4CTRL = (EMU->EM4CTRL & ~_EMU_EM4CTRL_EM4IORETMODE_MASK)
95 | (uint32_t)SL_POWER_MANAGER_INIT_EMU_EM4_PIN_RETENTION_MODE;
96 #else
97 EMU_EM4Init_TypeDef em4_init = EMU_EM4INIT_DEFAULT;
98 em4_init.pinRetentionMode = (EMU_EM4PinRetention_TypeDef)SL_POWER_MANAGER_INIT_EMU_EM4_PIN_RETENTION_MODE;
99 EMU_EM4Init(&em4_init);
100 #endif
101 }
102
103 /******************************************************************************
104 * Event called before entering EM4 sleep.
105 *****************************************************************************/
sl_power_manager_em4_presleep_hook(void)106 SL_WEAK void sl_power_manager_em4_presleep_hook(void)
107 {
108 // This implementation is empty, but this function can be redefined as it's a weak implementation.
109 }
110
111 /***************************************************************************//**
112 * Enter energy mode 4 (EM4).
113 *
114 * @note You should not expect to return from this function. Once the device
115 * enters EM4, only a power on reset or external reset pin can wake the
116 * device.
117 *
118 * @note On xG22 devices, this function re-configures the IADC if EM4 entry
119 * is possible.
120 ******************************************************************************/
sl_power_manager_enter_em4(void)121 __NO_RETURN void sl_power_manager_enter_em4(void)
122 {
123 /* Device with Boost DC-DC cannot enter EM4 because Boost DC-DC module does not
124 * have BYPASS switch so DC-DC converter can not be set to bypass mode. */
125 #if (defined(_SILICON_LABS_DCDC_FEATURE) \
126 && (_SILICON_LABS_DCDC_FEATURE == _SILICON_LABS_DCDC_FEATURE_DCDC_BOOST))
127 EFM_ASSERT(false);
128 #endif
129
130 // Make sure that we are not interrupted while we are entering em4
131 CORE_CRITICAL_IRQ_DISABLE();
132
133 EFM_ASSERT(is_em4_blocked() == false);
134
135 #if defined(SL_CATALOG_METRIC_EM4_WAKE_PRESENT)
136 sli_metric_em4_wake_init();
137 #endif
138
139 uint32_t em4seq2 = (EMU->EM4CTRL & ~_EMU_EM4CTRL_EM4ENTRY_MASK)
140 | (2U << _EMU_EM4CTRL_EM4ENTRY_SHIFT);
141 uint32_t em4seq3 = (EMU->EM4CTRL & ~_EMU_EM4CTRL_EM4ENTRY_MASK)
142 | (3U << _EMU_EM4CTRL_EM4ENTRY_SHIFT);
143
144 // Make sure that the register write lock is disabled.
145 EMU->LOCK = EMU_LOCK_LOCKKEY_UNLOCK;
146
147 #if defined(_DCDC_IF_EM4ERR_MASK)
148 // Workaround for bug that may cause a Hard Fault on EM4 entry
149 CMU_CLOCK_SELECT_SET(SYSCLK, FSRCO);
150 // The buck DC-DC is available in all energy modes except for EM4.
151 // The DC-DC converter must first be turned off and switched over to bypass mode.
152 #if (defined(EMU_SERIES2_DCDC_BUCK_PRESENT) \
153 || defined(EMU_SERIES2_DCDC_BOOST_PRESENT))
154 #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2) && (SL_POWER_MANAGER_RAMP_DVDD_EN == 1)
155 ramp_dvdd_and_switch_to_dcdc_bypass_mode();
156 #else
157 EMU_DCDCModeSet(emuDcdcMode_Bypass);
158 #endif
159 #endif
160 #endif
161
162 sl_power_manager_em4_presleep_hook();
163
164 for (uint8_t i = 0; i < 4; i++) {
165 EMU->EM4CTRL = em4seq2;
166 EMU->EM4CTRL = em4seq3;
167 }
168 EMU->EM4CTRL = em4seq2;
169 __WFI();
170
171 for (;; ) {
172 // __NO_RETURN
173 }
174 }
175
176 /***************************************************************************//**
177 * When EM4 pin retention is set to power_manager_pin_retention_latch,
178 * then pins are retained through EM4 entry and wakeup. The pin state is
179 * released by calling this function. The feature allows peripherals or
180 * GPIO to be re-initialized after EM4 exit (reset), and when
181 * initialization is done, this function can release pins and return
182 * control to the peripherals or GPIO.
183 ******************************************************************************/
sl_power_manager_em4_unlatch_pin_retention(void)184 void sl_power_manager_em4_unlatch_pin_retention(void)
185 {
186 #if defined(_EMU_EM4CTRL_EM4IORETMODE_MASK)
187 EMU->CMD = EMU_CMD_EM4UNLATCH;
188 #endif
189 }
190
191 /***************************************************************************//**
192 * Returns true if em4 entry is blocked by a watchdog peripheral.
193 ******************************************************************************/
is_em4_blocked(void)194 static bool is_em4_blocked(void)
195 {
196 #if defined(WDOG_PRESENT)
197 #if WDOG_COUNT > 0
198 if ( WDOG0_CLOCK_ENABLED_BIT && (WDOG0->CFG & WDOG_CFG_EM4BLOCK) && (WDOG0->EN & WDOG_EN_EN) ) {
199 return true;
200 }
201 #endif
202 #if WDOG_COUNT > 1
203 if ( WDOG1_CLOCK_ENABLED_BIT && (WDOG1->CFG & WDOG_CFG_EM4BLOCK) && (WDOG1->EN & WDOG_EN_EN) ) {
204 return true;
205 }
206 #endif
207 #endif
208 return false;
209 }
210
211 #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2) && (SL_POWER_MANAGER_RAMP_DVDD_EN == 1)
212
213 /* The following section provides an optimization to improve peak current
214 * consumption on xG22 devices.
215 */
216
217 // ADC clock frequency (source and after prescale)
218 #define CLK_SRC_ADC_FREQ 9600000 // CLK_SRC_ADC
219 #define CLK_ADC_FREQ 1000000 // CLK_ADC
220
221 extern void sli_delay_loop(uint32_t cycles);
222
223 uint32_t * dcdc_test_addr = (uint32_t *)(DCDC_NS_BASE + 0x80);
224 uint32_t ipkval = 7;
225 uint32_t ipktimeout = 1;
226
227 /* Pulse generation sequence TOCTRIG (bit 3) TOCMODE (bit 2)*/
228 uint32_t cmd[2] = { (1 << 2) | (1 << 3), (1 << 2) };
229
230 /***************************************************************************//**
231 * The voltage of Dvdd is ramped up to VMCU by sending pulses to a DCDC test
232 * register. These pulses are delayed, and ipkval and ipktimeout are tuned
233 * such that the voltage at Dvdd increases gradually to the voltage level of
234 * VMCU. Using the IADC, once Dvdd has gotten sufficiently close to VMCU,
235 * the DCDC peripheral is then switched into bypass mode. The IADC is used to
236 * detect this by sampling the voltage of Dvdd periodically, and calculating the
237 * difference between samples, when the difference is within some margin of zero
238 * then we know that the ramp sequence has reached a plateau.
239 ******************************************************************************/
ramp_dvdd_and_switch_to_dcdc_bypass_mode(void)240 static void ramp_dvdd_and_switch_to_dcdc_bypass_mode(void)
241 {
242 // Initialize the IADC for the purposes of detecting when the Dvdd ramp
243 // reaches a plateau.
244 IADC_Init_t init = IADC_INIT_DEFAULT;
245 IADC_AllConfigs_t initAllConfigs = IADC_ALLCONFIGS_DEFAULT;
246 IADC_InitSingle_t initSingle = IADC_INITSINGLE_DEFAULT;
247 IADC_SingleInput_t initSingleInput = IADC_SINGLEINPUT_DEFAULT;
248 CMU_ClockEnable(cmuClock_IADC0, true);
249
250 init.srcClkPrescale = IADC_calcSrcClkPrescale(IADC0, CLK_SRC_ADC_FREQ, 0);
251 initAllConfigs.configs[0].reference = iadcCfgReferenceInt1V2;
252 initAllConfigs.configs[0].vRef = 1210;
253 initAllConfigs.configs[0].analogGain = iadcCfgAnalogGain1x;
254 initAllConfigs.configs[0].digAvg = iadcDigitalAverage1;
255 initAllConfigs.configs[0].adcClkPrescale =
256 IADC_calcAdcClkPrescale(IADC0,
257 CLK_ADC_FREQ,
258 0,
259 iadcCfgModeNormal,
260 init.srcClkPrescale);
261 init.warmup = iadcWarmupKeepWarm;
262
263 IADC_reset(IADC0);
264 CMU_ClockSelectSet(cmuClock_IADCCLK, cmuSelect_EM01GRPACLK);
265 initSingle.triggerAction = iadcTriggerActionContinuous;
266 initSingle.alignment = iadcAlignRight12;
267 initSingleInput.compare = false; // Disable Window CMP
268 initSingleInput.posInput = iadcPosInputDvdd;
269 IADC_init(IADC0, &init, &initAllConfigs);
270 IADC_initSingle(IADC0, &initSingle, &initSingleInput);
271
272 // Start capturing
273 IADC_command(IADC0, iadcCmdStartSingle);
274
275 // Initialize DCDC peak current value and timeout to reach peak current value
276 DCDC->EM01CTRL0 = (DCDC->EM01CTRL0 & ~_DCDC_EM01CTRL0_IPKVAL_MASK) | (ipkval << 0);
277 DCDC->CTRL = (DCDC->CTRL & ~_DCDC_CTRL_IPKTMAXCTRL_MASK) | (ipktimeout << 4);
278
279 /* Generate pulses */
280 uint32_t iter = 1U;
281 IADC_Result_t prev_result;
282 volatile IADC_Result_t current_result = IADC_readSingleResult(IADC0);
283 while (true) {
284 // If the algorithm doesn't converge after 500 pulses, switch to dcdc
285 // bypass anyways.
286 if (iter >= 500) {
287 DCDC->CTRL_CLR = DCDC_CTRL_MODE;
288 EFM_ASSERT(false);
289 return;
290 }
291
292 /* Pulse generation sequence TOCTRIG (bit 3) TOCMODE (bit 2)*/
293 *dcdc_test_addr = cmd[0];
294 *dcdc_test_addr = cmd[1];
295
296 // In DCDC mode, MCU input voltage VREGVDD cannot be directly measured, so
297 // we can't know what the target DVDD voltage is. Instead, since DVDD
298 // ramp-up should follow a RC charge curve, measure DVDD and keep charging
299 // until the delta between measures is smaller than the set tolerance.
300 if (iter % 20U == 0U) {
301 prev_result = current_result;
302 current_result = IADC_readSingleResult(IADC0);
303 if ( abs((int32_t)(current_result.data - prev_result.data)) < SL_POWER_MANAGER_RAMP_DVDD_TOLERANCE ) {
304 DCDC->CTRL_CLR = DCDC_CTRL_MODE;
305 return;
306 }
307 }
308
309 if (DCDC->IF & DCDC_IF_TMAX) {
310 if (ipkval) {
311 ipkval--; // DCDC peak current value
312 }
313
314 if (ipktimeout < 7) {
315 ipktimeout++; // Timeout to reach peak current value
316 }
317
318 DCDC->EM01CTRL0 = (DCDC->EM01CTRL0 & ~_DCDC_EM01CTRL0_IPKVAL_MASK) | (ipkval << 0);
319 DCDC->CTRL = (DCDC->CTRL & ~_DCDC_CTRL_IPKTMAXCTRL_MASK) | (ipktimeout << 4);
320
321 DCDC->IF_CLR = DCDC_IF_TMAX;
322 }
323
324 /* delay for 8 clock cycles */
325 sli_delay_loop(8);
326 iter++;
327 }
328 }
329
330 #endif // defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2) && (SL_POWER_MANAGER_RAMP_DVDD_EN == 1)
331