1 /*
2  * Copyright (c) 2024 STMicroelectronics
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/kernel.h>
8 #include <zephyr/device.h>
9 #include <zephyr/init.h>
10 #include <zephyr/types.h>
11 #include <soc.h>
12 #include <zephyr/arch/cpu.h>
13 #include <zephyr/drivers/gpio.h>
14 #include <zephyr/devicetree.h>
15 
16 #include <stm32_ll_system.h>
17 #include <stm32_ll_pwr.h>
18 
19 #include <zephyr/dt-bindings/power/stm32_pwr.h>
20 
21 #include "stm32_wkup_pins.h"
22 
23 #include <zephyr/logging/log.h>
24 
25 LOG_MODULE_DECLARE(soc, CONFIG_SOC_LOG_LEVEL);
26 
27 #define DT_DRV_COMPAT st_stm32_pwr
28 
29 #define STM32_PWR_NODE DT_NODELABEL(pwr)
30 
31 #define PWR_STM32_MAX_NB_WKUP_PINS DT_INST_PROP(0, wkup_pins_nb)
32 
33 #define PWR_STM32_WKUP_PIN_SRCS_NB DT_INST_PROP_OR(0, wkup_pin_srcs, 1)
34 
35 #define PWR_STM32_WKUP_PINS_POLARITY DT_INST_PROP_OR(0, wkup_pins_pol, 0)
36 
37 #define PWR_STM32_WKUP_PINS_PUPD_CFG DT_INST_PROP_OR(0, wkup_pins_pupd, 0)
38 
39 /** @cond INTERNAL_HIDDEN */
40 
41 /**
42  * @brief flags for wake-up pin polarity configuration
43  * @{
44  */
45 
46 /* detection of wake-up event on the high level : rising edge */
47 #define STM32_PWR_WKUP_PIN_P_RISING	0
48 /* detection of wake-up event on the low level : falling edge */
49 #define STM32_PWR_WKUP_PIN_P_FALLING	1
50 
51 /** @} */
52 
53 /**
54  * @brief flags for configuration of pull-ups & pull-downs of GPIO ports
55  * that are associated with wake-up pins
56  * @{
57  */
58 
59 #define STM32_PWR_WKUP_PIN_NOPULL	0
60 #define STM32_PWR_WKUP_PIN_PULLUP	1
61 #define STM32_PWR_WKUP_PIN_PULLDOWN	(1 << 2)
62 
63 /** @} */
64 
65 /**
66  * @brief Structure for storing the devicetree configuration of a wake-up pin.
67  */
68 struct wkup_pin_dt_cfg_t {
69 	/* starts from 1 */
70 	uint32_t wkup_pin_id;
71 	/* GPIO pin(s) associated with wake-up pin */
72 	struct gpio_dt_spec gpios_cfgs[PWR_STM32_WKUP_PIN_SRCS_NB];
73 };
74 
75 #define WKUP_PIN_NODE_LABEL(i) wkup_pin_##i
76 
77 #define WKUP_PIN_NODE_ID_BY_IDX(idx) DT_CHILD(STM32_PWR_NODE, WKUP_PIN_NODE_LABEL(idx))
78 
79 static const struct gpio_dt_spec empty_gpio = {.port = NULL, .pin = 0, .dt_flags = 0};
80 
81 /* cell_idx starts from 0 */
82 #define WKUP_PIN_GPIOS_CFG_DT_BY_IDX(cell_idx, node_id)                                            \
83 	GPIO_DT_SPEC_GET_BY_IDX_OR(node_id, wkup_gpios, cell_idx, empty_gpio)
84 
85 #define NB_DEF(i, _) i
86 
87 /**
88  * @brief Get wake-up pin configuration from a given devicetree node.
89  *
90  * This returns a static initializer for a <tt>struct wkup_pin_dt_cfg_t</tt>
91  * filled with data from a given devicetree node.
92  *
93  * @param node_id Devicetree node identifier.
94  *
95  * @return Static initializer for a wkup_pin_dt_cfg_t structure.
96  */
97 #define WKUP_PIN_CFG_DT(node_id)                                                                   \
98 	{                                                                                          \
99 		.wkup_pin_id = DT_REG_ADDR(node_id),                                               \
100 		.gpios_cfgs =                                                                      \
101 		{                                                                                  \
102 		FOR_EACH_FIXED_ARG(WKUP_PIN_GPIOS_CFG_DT_BY_IDX, (,), node_id,                     \
103 			LISTIFY(PWR_STM32_WKUP_PIN_SRCS_NB, NB_DEF, (,)))                          \
104 		}                                                                                  \
105 	}
106 
107 /* wkup_pin idx starts from 1 */
108 #define WKUP_PIN_CFG_DT_BY_IDX(idx) WKUP_PIN_CFG_DT(WKUP_PIN_NODE_ID_BY_IDX(idx))
109 
110 #define PWR_STM32_WKUP_PIN_LOOKUP_MEMBER(i, _) CONCAT(LL_PWR_WAKEUP_PIN, UTIL_INC(i))
111 
112 #define WKUP_PIN_CFG_DT_COMMA(wkup_pin_id) WKUP_PIN_CFG_DT(wkup_pin_id),
113 
114 /** @endcond */
115 
116 /**
117  * @brief Structure for passing the runtime configuration of a given wake-up pin.
118  */
119 struct wkup_pin_cfg_t {
120 	uint32_t wkup_pin_id;
121 #if PWR_STM32_WKUP_PINS_POLARITY
122 	bool polarity; /* True if detection on the low level : falling edge */
123 #endif                 /* PWR_STM32_WKUP_PINS_POLARITY */
124 #if PWR_STM32_WKUP_PINS_PUPD_CFG
125 	uint32_t pupd_cfg;	/* pull-up/down config of GPIO port associated w/ this wkup pin */
126 	uint32_t ll_gpio_port;	/* GPIO port associated with this wake-up pin */
127 	uint32_t ll_gpio_pin;	/* GPIO pin associated with this wake-up pin */
128 #endif /* PWR_STM32_WKUP_PINS_PUPD_CFG */
129 	uint32_t src_selection; /* The source signal to use with this wake-up pin */
130 };
131 
132 /**
133  * @brief LookUp Table to store LL_PWR_WAKEUP_PINx for each wake-up pin.
134  */
135 static const uint32_t table_wakeup_pins[PWR_STM32_MAX_NB_WKUP_PINS + 1] = {
136 	0,
137 	LISTIFY(PWR_STM32_MAX_NB_WKUP_PINS, PWR_STM32_WKUP_PIN_LOOKUP_MEMBER, (,)),
138 };
139 
140 static struct wkup_pin_dt_cfg_t wkup_pins_cfgs[] = {
141 	DT_FOREACH_CHILD(STM32_PWR_NODE, WKUP_PIN_CFG_DT_COMMA)};
142 
143 #if PWR_STM32_WKUP_PINS_PUPD_CFG
144 
145 /**
146  * @brief Array containing pointers to each GPIO port.
147  *
148  * Entry will be NULL if the GPIO port is not enabled.
149  * Also used to get GPIO port index from device ptr, so having
150  * NULL entries for unused GPIO ports is desired.
151  */
152 static const struct device *const gpio_ports[] = {
153 	DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpioa)),
154 	DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpiob)),
155 	DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpioc)),
156 	DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpiod)),
157 	DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpioe)),
158 	DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpiof)),
159 	DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpiog)),
160 	DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpioh)),
161 	DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpioi)),
162 	DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpioj)),
163 	DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpiok)),
164 };
165 
166 /* Number of GPIO ports. */
167 static const size_t gpio_ports_cnt = ARRAY_SIZE(gpio_ports);
168 
169 /**
170  * @brief LookUp Table to store LL_PWR_GPIO_x of each GPIO port.
171  */
172 static const uint32_t table_ll_pwr_gpio_ports[] = {
173 #ifdef CONFIG_SOC_SERIES_STM32U5X
174 	(uint32_t)LL_PWR_GPIO_PORTA, (uint32_t)LL_PWR_GPIO_PORTB, (uint32_t)LL_PWR_GPIO_PORTC,
175 	(uint32_t)LL_PWR_GPIO_PORTD, (uint32_t)LL_PWR_GPIO_PORTE, (uint32_t)LL_PWR_GPIO_PORTF,
176 	(uint32_t)LL_PWR_GPIO_PORTG, (uint32_t)LL_PWR_GPIO_PORTH, (uint32_t)LL_PWR_GPIO_PORTI,
177 	(uint32_t)LL_PWR_GPIO_PORTJ
178 #else
179 	LL_PWR_GPIO_A, LL_PWR_GPIO_B, LL_PWR_GPIO_C, LL_PWR_GPIO_D, LL_PWR_GPIO_E,
180 	LL_PWR_GPIO_F, LL_PWR_GPIO_G, LL_PWR_GPIO_H, LL_PWR_GPIO_I, LL_PWR_GPIO_J
181 #endif /* CONFIG_SOC_SERIES_STM32U5X */
182 };
183 
184 static const size_t pwr_ll_gpio_ports_cnt = ARRAY_SIZE(table_ll_pwr_gpio_ports);
185 
186 #endif /* PWR_STM32_WKUP_PINS_PUPD_CFG */
187 
188 /**
189  * @brief Configure & enable a wake-up pin.
190  *
191  * @param wakeup_pin_cfg wake-up pin runtime configuration.
192  *
193  */
wkup_pin_setup(const struct wkup_pin_cfg_t * wakeup_pin_cfg)194 static void wkup_pin_setup(const struct wkup_pin_cfg_t *wakeup_pin_cfg)
195 {
196 	uint32_t wkup_pin_index = wakeup_pin_cfg->wkup_pin_id;
197 
198 #if PWR_STM32_WKUP_PINS_POLARITY
199 	/* Set wake-up pin polarity */
200 	if (wakeup_pin_cfg->polarity == STM32_PWR_WKUP_PIN_P_FALLING) {
201 		LL_PWR_SetWakeUpPinPolarityLow(table_wakeup_pins[wkup_pin_index]);
202 	} else {
203 		LL_PWR_SetWakeUpPinPolarityHigh(table_wakeup_pins[wkup_pin_index]);
204 	}
205 #endif /* PWR_STM32_WKUP_PINS_POLARITY */
206 
207 #if PWR_STM32_WKUP_PINS_PUPD_CFG
208 	if (wakeup_pin_cfg->pupd_cfg == STM32_PWR_WKUP_PIN_NOPULL) {
209 		LL_PWR_DisableGPIOPullUp(wakeup_pin_cfg->ll_gpio_port,
210 					(1u << wakeup_pin_cfg->ll_gpio_pin));
211 		LL_PWR_DisableGPIOPullDown(wakeup_pin_cfg->ll_gpio_port,
212 					(1u << wakeup_pin_cfg->ll_gpio_pin));
213 
214 	} else if ((wakeup_pin_cfg->pupd_cfg == STM32_PWR_WKUP_PIN_PULLUP) &&
215 						wakeup_pin_cfg->ll_gpio_port) {
216 		LL_PWR_EnableGPIOPullUp(wakeup_pin_cfg->ll_gpio_port,
217 					(1u << wakeup_pin_cfg->ll_gpio_pin));
218 
219 	} else if ((wakeup_pin_cfg->pupd_cfg == STM32_PWR_WKUP_PIN_PULLDOWN) &&
220 						wakeup_pin_cfg->ll_gpio_port) {
221 		LL_PWR_EnableGPIOPullDown(wakeup_pin_cfg->ll_gpio_port,
222 					  (1u << wakeup_pin_cfg->ll_gpio_pin));
223 	}
224 #endif /* PWR_STM32_WKUP_PINS_PUPD_CFG */
225 
226 #if defined(CONFIG_SOC_SERIES_STM32U5X) || defined(CONFIG_SOC_SERIES_STM32WBAX)
227 	/* Select the proper wake-up signal source */
228 	if (wakeup_pin_cfg->src_selection & STM32_PWR_WKUP_PIN_SRC_0) {
229 		LL_PWR_SetWakeUpPinSignal0Selection(table_wakeup_pins[wkup_pin_index]);
230 	} else if (wakeup_pin_cfg->src_selection & STM32_PWR_WKUP_PIN_SRC_1) {
231 		LL_PWR_SetWakeUpPinSignal1Selection(table_wakeup_pins[wkup_pin_index]);
232 	} else if (wakeup_pin_cfg->src_selection & STM32_PWR_WKUP_PIN_SRC_2) {
233 		LL_PWR_SetWakeUpPinSignal2Selection(table_wakeup_pins[wkup_pin_index]);
234 	} else {
235 		LL_PWR_SetWakeUpPinSignal3Selection(table_wakeup_pins[wkup_pin_index]);
236 	}
237 #endif /* CONFIG_SOC_SERIES_STM32U5X or CONFIG_SOC_SERIES_STM32WBAX */
238 
239 	LL_PWR_EnableWakeUpPin(table_wakeup_pins[wkup_pin_index]);
240 }
241 
242 /**
243  * @brief Exported function to configure a given GPIO pin as a source for wake-up pins
244  *
245  * @param gpio Container for GPIO pin information specified in devicetree
246  *
247  * @return 0 on success, -EINVAL if said GPIO pin is not associated with any wake-up pin.
248  */
stm32_pwr_wkup_pin_cfg_gpio(const struct gpio_dt_spec * gpio)249 int stm32_pwr_wkup_pin_cfg_gpio(const struct gpio_dt_spec *gpio)
250 {
251 	struct wkup_pin_cfg_t wakeup_pin_cfg;
252 	struct wkup_pin_dt_cfg_t *wkup_pin_dt_cfg;
253 	struct gpio_dt_spec *wkup_pin_gpio_cfg;
254 	bool found_gpio = false;
255 	int i;
256 	int j;
257 
258 	UNUSED(empty_gpio);
259 
260 	/* Look for a wake-up pin that has this GPIO pin as a source, if any */
261 	for (i = 0; i < PWR_STM32_MAX_NB_WKUP_PINS; i++) {
262 		wkup_pin_dt_cfg = &(wkup_pins_cfgs[i]);
263 		for (j = 0; j < PWR_STM32_WKUP_PIN_SRCS_NB; j++) {
264 			wkup_pin_gpio_cfg = &(wkup_pin_dt_cfg->gpios_cfgs[j]);
265 			if (wkup_pin_gpio_cfg->port == gpio->port) {
266 				if (wkup_pin_gpio_cfg->pin == gpio->pin) {
267 					found_gpio = true;
268 					break;
269 				}
270 			}
271 		}
272 		if (found_gpio) {
273 			break;
274 		};
275 	}
276 
277 	if (!found_gpio) {
278 		LOG_DBG("Couldn't find a wake-up event correspending to GPIO %s pin %d\n",
279 			gpio->port->name, gpio->pin);
280 		LOG_DBG("=> It cannot be used as a wake-up source\n");
281 		return -EINVAL;
282 	}
283 
284 	wakeup_pin_cfg.wkup_pin_id = wkup_pin_dt_cfg->wkup_pin_id;
285 
286 /* Each wake-up pin on STM32U5 is associated with 4 wkup srcs, 3 of them correspond to GPIOs. */
287 #if defined(CONFIG_SOC_SERIES_STM32U5X) || defined(CONFIG_SOC_SERIES_STM32WBAX)
288 	wakeup_pin_cfg.src_selection = wkup_pin_gpio_cfg->dt_flags &
289 					(STM32_PWR_WKUP_PIN_SRC_0 |
290 					STM32_PWR_WKUP_PIN_SRC_1 |
291 					STM32_PWR_WKUP_PIN_SRC_2);
292 #else
293 	wakeup_pin_cfg.src_selection = 0;
294 #endif /* CONFIG_SOC_SERIES_STM32U5X or CONFIG_SOC_SERIES_STM32WBAX */
295 
296 #if PWR_STM32_WKUP_PINS_POLARITY
297 	/*
298 	 * get polarity config from GPIO flags specified in device "gpios" property
299 	 */
300 	if (gpio->dt_flags & GPIO_ACTIVE_LOW) {
301 		wakeup_pin_cfg.polarity = STM32_PWR_WKUP_PIN_P_FALLING;
302 	} else {
303 		wakeup_pin_cfg.polarity = STM32_PWR_WKUP_PIN_P_RISING;
304 	}
305 #endif /* PWR_STM32_WKUP_PINS_POLARITY */
306 
307 #if PWR_STM32_WKUP_PINS_PUPD_CFG
308 	wakeup_pin_cfg.ll_gpio_port = 0;
309 	for (i = 0; i < gpio_ports_cnt; i++) {
310 		if ((gpio_ports[i] == gpio->port) && (i < pwr_ll_gpio_ports_cnt)) {
311 			wakeup_pin_cfg.ll_gpio_port = table_ll_pwr_gpio_ports[i];
312 			break;
313 		}
314 	}
315 
316 	wakeup_pin_cfg.ll_gpio_pin = gpio->pin;
317 
318 	/*
319 	 * Get pull-up/down config, if any, from GPIO flags specified in device "gpios" property
320 	 */
321 	if (gpio->dt_flags & GPIO_PULL_UP) {
322 		wakeup_pin_cfg.pupd_cfg = STM32_PWR_WKUP_PIN_PULLUP;
323 	} else if (gpio->dt_flags & GPIO_PULL_DOWN) {
324 		wakeup_pin_cfg.pupd_cfg = STM32_PWR_WKUP_PIN_PULLDOWN;
325 	} else {
326 		wakeup_pin_cfg.pupd_cfg = STM32_PWR_WKUP_PIN_NOPULL;
327 	}
328 #endif /* PWR_STM32_WKUP_PINS_PUPD_CFG */
329 
330 	wkup_pin_setup(&wakeup_pin_cfg);
331 
332 	return 0;
333 }
334 
335 /**
336  * @brief Exported function to activate pull-ups/pull-downs configuration of GPIO ports
337  * associated with wake-up pins.
338  */
stm32_pwr_wkup_pin_cfg_pupd(void)339 void stm32_pwr_wkup_pin_cfg_pupd(void)
340 {
341 #if PWR_STM32_WKUP_PINS_PUPD_CFG
342 #ifdef CONFIG_SOC_SERIES_STM32U5X
343 	LL_PWR_EnablePUPDConfig();
344 #else
345 	LL_PWR_EnablePUPDCfg();
346 #endif /* CONFIG_SOC_SERIES_STM32U5X */
347 #else
348 	return;
349 #endif /* PWR_STM32_WKUP_PINS_PUPD_CFG */
350 }
351