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