1 /*
2  * Copyright (c) 2019 STMicroelectronics.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 #include <zephyr/kernel.h>
7 #include <zephyr/pm/pm.h>
8 #include <soc.h>
9 #include <zephyr/init.h>
10 #include <zephyr/drivers/clock_control/stm32_clock_control.h>
11 
12 #include <stm32wbxx_ll_utils.h>
13 #include <stm32wbxx_ll_bus.h>
14 #include <stm32wbxx_ll_cortex.h>
15 #include <stm32wbxx_ll_pwr.h>
16 #include <stm32wbxx_ll_rcc.h>
17 #include <clock_control/clock_stm32_ll_common.h>
18 #include "stm32_hsem.h"
19 
20 #include <zephyr/logging/log.h>
21 LOG_MODULE_DECLARE(soc, CONFIG_SOC_LOG_LEVEL);
22 
23 /*
24  * @brief Switch the system clock on HSI
25  * @param none
26  * @retval none
27  */
switch_on_hsi(void)28 static void switch_on_hsi(void)
29 {
30 	LL_RCC_HSI_Enable();
31 	while (!LL_RCC_HSI_IsReady()) {
32 	}
33 
34 	LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSI);
35 	LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI);
36 	while (LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSI) {
37 	}
38 }
39 
lpm_hsem_lock(void)40 static void lpm_hsem_lock(void)
41 {
42 	/* Implementation of STM32 AN5289 algorithm to enter/exit lowpower */
43 	z_stm32_hsem_lock(CFG_HW_RCC_SEMID, HSEM_LOCK_WAIT_FOREVER);
44 
45 	if (!LL_HSEM_1StepLock(HSEM, CFG_HW_ENTRY_STOP_MODE_SEMID)) {
46 		if (LL_PWR_IsActiveFlag_C2DS()) {
47 			/* Release ENTRY_STOP_MODE semaphore */
48 			LL_HSEM_ReleaseLock(HSEM, CFG_HW_ENTRY_STOP_MODE_SEMID, 0);
49 
50 			/* The switch on HSI before entering Stop Mode is required */
51 			switch_on_hsi();
52 		}
53 	} else {
54 		/* The switch on HSI before entering Stop Mode is required */
55 		switch_on_hsi();
56 	}
57 }
58 
59 /* Invoke Low Power/System Off specific Tasks */
pm_state_set(enum pm_state state,uint8_t substate_id)60 void pm_state_set(enum pm_state state, uint8_t substate_id)
61 {
62 	if (state == PM_STATE_SUSPEND_TO_IDLE) {
63 
64 		lpm_hsem_lock();
65 
66 		/* ensure HSI is the wake-up system clock */
67 		LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_HSI);
68 
69 		switch (substate_id) {
70 		case 1:
71 			/* enter STOP0 mode */
72 			LL_PWR_SetPowerMode(LL_PWR_MODE_STOP0);
73 			break;
74 		case 2:
75 			/* enter STOP1 mode */
76 			LL_PWR_SetPowerMode(LL_PWR_MODE_STOP1);
77 			break;
78 		case 3:
79 			/* enter STOP2 mode */
80 			LL_PWR_SetPowerMode(LL_PWR_MODE_STOP2);
81 			break;
82 		default:
83 			/* Release RCC semaphore */
84 			z_stm32_hsem_unlock(CFG_HW_RCC_SEMID);
85 			LOG_DBG("Unsupported power substate-id %u", substate_id);
86 			return;
87 		}
88 
89 		if (IS_ENABLED(STM32_HSI48_ENABLED)) {
90 			/*
91 			 * Release CLK48 semaphore to make sure M0 core can enable/disable
92 			 * it as needed (shared between RNG and USB peripheral, M0 uses RNG
93 			 * during BLE advertisement phase). It seems like if left locked M0
94 			 * can enable the clock if needed but is not able (allowed) to stop
95 			 * it, with increased power consumption as a result.
96 			 */
97 			z_stm32_hsem_unlock(CFG_HW_CLK48_CONFIG_SEMID);
98 		}
99 
100 		/* Release RCC semaphore */
101 		z_stm32_hsem_unlock(CFG_HW_RCC_SEMID);
102 
103 		LL_LPM_EnableDeepSleep();
104 
105 		/* enter SLEEP mode : WFE or WFI */
106 		k_cpu_idle();
107 	} else {
108 		LOG_DBG("Unsupported power state %u", state);
109 		return;
110 	}
111 }
112 
113 /* Handle SOC specific activity after Low Power Mode Exit */
pm_state_exit_post_ops(enum pm_state state,uint8_t substate_id)114 void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id)
115 {
116 	/* Implementation of STM32 AN5289 algorithm to enter/exit lowpower */
117 	/* Release ENTRY_STOP_MODE semaphore */
118 	LL_HSEM_ReleaseLock(HSEM, CFG_HW_ENTRY_STOP_MODE_SEMID, 0);
119 	z_stm32_hsem_lock(CFG_HW_RCC_SEMID, HSEM_LOCK_WAIT_FOREVER);
120 
121 	if (state != PM_STATE_SUSPEND_TO_IDLE) {
122 		LOG_DBG("Unsupported power state %u", state);
123 	} else {
124 		switch (substate_id) {
125 		case 1:	/* STOP0 */
126 			__fallthrough;
127 		case 2:	/* STOP1 */
128 			__fallthrough;
129 		case 3:	/* STOP2 */
130 			LL_LPM_DisableSleepOnExit();
131 			LL_LPM_EnableSleep();
132 			break;
133 		default:
134 			LOG_DBG("Unsupported power substate-id %u",
135 				substate_id);
136 			break;
137 		}
138 		/* need to restore the clock */
139 		stm32_clock_control_init(NULL);
140 	}
141 
142 	/* Release RCC semaphore */
143 	z_stm32_hsem_unlock(CFG_HW_RCC_SEMID);
144 
145 	/*
146 	 * System is now in active mode.
147 	 * Reenable interrupts which were disabled
148 	 * when OS started idling code.
149 	 */
150 	irq_unlock(0);
151 }
152 
153 /* Initialize STM32 Power */
stm32_pm_init(void)154 void stm32_pm_init(void)
155 {
156 }
157