1 /*
2  * Copyright (c) 2016 Open-RnD Sp. z o.o.
3  * Copyright (c) 2017 RnDity Sp. z o.o.
4  * Copyright (c) 2019-23 Linaro Limited
5  * Copyright (C) 2025 Savoir-faire Linux, Inc.
6  * Copyright (c) 2025 Alexander Kozhinov <ak.alexander.kozhinov@gmail.com>
7  *
8  * SPDX-License-Identifier: Apache-2.0
9  */
10 
11 /**
12  * @brief Driver for STM32 External interrupt/event controller
13  */
14 
15 #include <zephyr/device.h>
16 #include <soc.h>
17 #include <stm32_ll_bus.h>
18 #include <stm32_ll_gpio.h> /* For STM32F1 series */
19 #include <stm32_ll_exti.h>
20 #include <stm32_ll_system.h>
21 #include <zephyr/sys/__assert.h>
22 #include <zephyr/sys/util.h>
23 #include <zephyr/dt-bindings/pinctrl/stm32-pinctrl-common.h> /* For STM32L0 series */
24 #include <zephyr/drivers/interrupt_controller/gpio_intc_stm32.h>
25 #include <zephyr/drivers/interrupt_controller/intc_exti_stm32.h>
26 #include <zephyr/drivers/clock_control/stm32_clock_control.h>
27 #include <zephyr/irq.h>
28 
29 #include "stm32_hsem.h"
30 #include "intc_exti_stm32_priv.h"
31 
32 /** @brief EXTI lines range mapped to a single interrupt line */
33 struct stm32_exti_range {
34 	/* Start of the range */
35 	uint8_t start;
36 	/* Range length */
37 	uint8_t len;
38 };
39 
40 #define EXTI_NUM_LINES_TOTAL	DT_PROP(EXTI_NODE, num_lines)
41 #define NUM_EXTI_LINES		DT_PROP(EXTI_NODE, num_gpio_lines)
42 
43 BUILD_ASSERT(EXTI_NUM_LINES_TOTAL >= NUM_EXTI_LINES,
44 	"The total number of EXTI lines must be greater or equal than the number of GPIO lines");
45 
46 static IRQn_Type exti_irq_table[NUM_EXTI_LINES] = {[0 ... NUM_EXTI_LINES - 1] = 0xFF};
47 
48 /* User callback wrapper */
49 struct __exti_cb {
50 	stm32_gpio_irq_cb_t cb;
51 	void *data;
52 };
53 
54 /* EXTI driver data */
55 struct stm32_intc_gpio_data {
56 	/* per-line callbacks */
57 	struct __exti_cb cb[NUM_EXTI_LINES];
58 };
59 
60 static struct stm32_intc_gpio_data intc_gpio_data;
61 
62 /**
63  * @returns the LL_<PPP>_EXTI_LINE_xxx define that corresponds to specified @p linenum
64  * This value can be used with the LL EXTI source configuration functions.
65  */
stm32_exti_linenum_to_src_cfg_line(gpio_pin_t linenum)66 static inline uint32_t stm32_exti_linenum_to_src_cfg_line(gpio_pin_t linenum)
67 {
68 #if defined(CONFIG_SOC_SERIES_STM32L0X) || \
69 	defined(CONFIG_SOC_SERIES_STM32F0X)
70 	return ((linenum % 4 * 4) << 16) | (linenum / 4);
71 #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti) || \
72 	defined(CONFIG_SOC_SERIES_STM32MP2X)
73 	return ((linenum & 0x3) << (16 + 3)) | (linenum >> 2);
74 #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7rs_exti)
75 	/* Gives the LL_SBS_EXTI_LINEn corresponding to the line number */
76 	return (((linenum % 4 * 4) << LL_SBS_REGISTER_PINPOS_SHFT) | (linenum / 4));
77 #else
78 	return (0xF << ((linenum % 4 * 4) + 16)) | (linenum / 4);
79 #endif
80 }
81 
82 /**
83  * @returns EXTI line number for LL_EXTI_LINE_n define
84  */
ll_exti_line_to_linenum(stm32_gpio_irq_line_t line)85 static inline gpio_pin_t ll_exti_line_to_linenum(stm32_gpio_irq_line_t line)
86 {
87 	return LOG2(line);
88 }
89 
90 /**
91  * @brief EXTI ISR handler
92  *
93  * Check EXTI lines in exti_range for pending interrupts
94  *
95  * @param exti_range Pointer to a exti_range structure
96  */
stm32_intc_gpio_isr(const void * exti_range)97 static void stm32_intc_gpio_isr(const void *exti_range)
98 {
99 	struct stm32_intc_gpio_data *data = &intc_gpio_data;
100 	const struct stm32_exti_range *range = exti_range;
101 	stm32_gpio_irq_line_t line;
102 	uint32_t line_num;
103 
104 	/* see which bits are set */
105 	for (uint8_t i = 0; i <= range->len; i++) {
106 		line_num = range->start + i;
107 
108 		/* check if interrupt is pending */
109 		if (stm32_exti_is_pending(line_num)) {
110 			/* clear pending interrupt */
111 			stm32_exti_clear_pending(line_num);
112 
113 			/* run callback only if one is registered */
114 			if (!data->cb[line_num].cb) {
115 				continue;
116 			}
117 
118 			/* `line` can be passed as-is because LL_EXTI_LINE_n is (1 << n) */
119 			line = exti_linenum_to_ll_exti_line(line_num);
120 			data->cb[line_num].cb(line, data->cb[line_num].data);
121 		}
122 	}
123 }
124 
stm32_fill_irq_table(int8_t start,int8_t len,int32_t irqn)125 static void stm32_fill_irq_table(int8_t start, int8_t len, int32_t irqn)
126 {
127 	for (int i = 0; i < len; i++) {
128 		exti_irq_table[start + i] = irqn;
129 	}
130 }
131 
132 /* This macro:
133  * - populates line_range_x from line_range dt property
134  * - fills exti_irq_table through stm32_fill_irq_table()
135  * - calls IRQ_CONNECT for each interrupt and matching line_range
136  */
137 #define STM32_EXTI_INIT_LINE_RANGE(node_id, interrupts, idx)			\
138 	static const struct stm32_exti_range line_range_##idx = {		\
139 		DT_PROP_BY_IDX(node_id, line_ranges, UTIL_X2(idx)),		\
140 		DT_PROP_BY_IDX(node_id, line_ranges, UTIL_INC(UTIL_X2(idx)))	\
141 	};									\
142 	stm32_fill_irq_table(line_range_##idx.start,				\
143 			     line_range_##idx.len,				\
144 			     DT_IRQ_BY_IDX(node_id, idx, irq));			\
145 	IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, idx, irq),				\
146 		DT_IRQ_BY_IDX(node_id, idx, priority),				\
147 		stm32_intc_gpio_isr, &line_range_##idx, 0);
148 
149 /**
150  * @brief Initializes the EXTI GPIO interrupt controller driver
151  */
stm32_exti_gpio_intc_init(void)152 static int stm32_exti_gpio_intc_init(void)
153 {
154 	DT_FOREACH_PROP_ELEM(EXTI_NODE,
155 			     interrupt_names,
156 			     STM32_EXTI_INIT_LINE_RANGE);
157 
158 	return 0;
159 }
160 
161 SYS_INIT(stm32_exti_gpio_intc_init, PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY);
162 
163 /**
164  * @brief EXTI GPIO interrupt controller API implementation
165  */
166 
167 /**
168  * @internal
169  * STM32 EXTI driver:
170  * The type @ref stm32_gpio_irq_line_t is used to hold the LL_EXTI_LINE_xxx
171  * defines of the LL EXTI API that corresponds to the provided pin.
172  *
173  * The port is not part of these definitions because port configuration
174  * is done via different APIs, which use the LL_<PPP>_EXTI_LINE_xxx defines
175  * returned by @ref stm32_exti_linenum_to_src_cfg_line instead.
176  * @endinternal
177  */
stm32_gpio_intc_get_pin_irq_line(uint32_t port,gpio_pin_t pin)178 stm32_gpio_irq_line_t stm32_gpio_intc_get_pin_irq_line(uint32_t port, gpio_pin_t pin)
179 {
180 	ARG_UNUSED(port);
181 	return exti_linenum_to_ll_exti_line(pin);
182 }
183 
stm32_gpio_intc_enable_line(stm32_gpio_irq_line_t line)184 void stm32_gpio_intc_enable_line(stm32_gpio_irq_line_t line)
185 {
186 	unsigned int irqnum;
187 	uint32_t line_num = ll_exti_line_to_linenum(line);
188 
189 	__ASSERT_NO_MSG(line_num < NUM_EXTI_LINES);
190 
191 	/* Get matching exti irq provided line thanks to irq_table */
192 	irqnum = exti_irq_table[line_num];
193 	__ASSERT_NO_MSG(irqnum != 0xFF);
194 
195 	/* Enable requested line interrupt */
196 	EXTI_ENABLE_IT(0_31, line);
197 
198 	/* Enable exti irq interrupt */
199 	irq_enable(irqnum);
200 }
201 
stm32_gpio_intc_disable_line(stm32_gpio_irq_line_t line)202 void stm32_gpio_intc_disable_line(stm32_gpio_irq_line_t line)
203 {
204 	EXTI_DISABLE_IT(0_31, line);
205 }
206 
stm32_gpio_intc_select_line_trigger(stm32_gpio_irq_line_t line,uint32_t trg)207 void stm32_gpio_intc_select_line_trigger(stm32_gpio_irq_line_t line, uint32_t trg)
208 {
209 	z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
210 
211 	switch (trg) {
212 #if defined(CONFIG_SOC_SERIES_STM32MP2X)
213 	case STM32_GPIO_IRQ_TRIG_NONE:
214 		LL_EXTI_DisableRisingTrig_0_31(EXTI2, line);
215 		LL_EXTI_DisableFallingTrig_0_31(EXTI2, line);
216 		break;
217 	case STM32_GPIO_IRQ_TRIG_RISING:
218 		LL_EXTI_EnableRisingTrig_0_31(EXTI2, line);
219 		LL_EXTI_DisableFallingTrig_0_31(EXTI2, line);
220 		break;
221 	case STM32_GPIO_IRQ_TRIG_FALLING:
222 		LL_EXTI_EnableFallingTrig_0_31(EXTI2, line);
223 		LL_EXTI_DisableRisingTrig_0_31(EXTI2, line);
224 		break;
225 	case STM32_GPIO_IRQ_TRIG_BOTH:
226 		LL_EXTI_EnableRisingTrig_0_31(EXTI2, line);
227 		LL_EXTI_EnableFallingTrig_0_31(EXTI2, line);
228 		break;
229 #else /* CONFIG_SOC_SERIES_STM32MP2X */
230 	case STM32_GPIO_IRQ_TRIG_NONE:
231 		EXTI_DISABLE_RISING_TRIG(0_31, line);
232 		EXTI_DISABLE_FALLING_TRIG(0_31, line);
233 		break;
234 	case STM32_GPIO_IRQ_TRIG_RISING:
235 		EXTI_ENABLE_RISING_TRIG(0_31, line);
236 		EXTI_DISABLE_FALLING_TRIG(0_31, line);
237 		break;
238 	case STM32_GPIO_IRQ_TRIG_FALLING:
239 		EXTI_ENABLE_FALLING_TRIG(0_31, line);
240 		EXTI_DISABLE_RISING_TRIG(0_31, line);
241 		break;
242 	case STM32_GPIO_IRQ_TRIG_BOTH:
243 		EXTI_ENABLE_RISING_TRIG(0_31, line);
244 		EXTI_ENABLE_FALLING_TRIG(0_31, line);
245 		break;
246 #endif /* CONFIG_SOC_SERIES_STM32MP2X */
247 	default:
248 		__ASSERT_NO_MSG(0);
249 		break;
250 	}
251 	z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
252 }
253 
stm32_gpio_intc_set_irq_callback(stm32_gpio_irq_line_t line,stm32_gpio_irq_cb_t cb,void * user)254 int stm32_gpio_intc_set_irq_callback(stm32_gpio_irq_line_t line, stm32_gpio_irq_cb_t cb, void *user)
255 {
256 	struct stm32_intc_gpio_data *data = &intc_gpio_data;
257 	uint32_t line_num = ll_exti_line_to_linenum(line);
258 
259 	if ((data->cb[line_num].cb == cb) && (data->cb[line_num].data == user)) {
260 		return 0;
261 	}
262 
263 	/* if callback already exists/is running, return busy */
264 	if (data->cb[line_num].cb != NULL) {
265 		return -EBUSY;
266 	}
267 
268 	data->cb[line_num].cb = cb;
269 	data->cb[line_num].data = user;
270 
271 	return 0;
272 }
273 
stm32_gpio_intc_remove_irq_callback(stm32_gpio_irq_line_t line)274 void stm32_gpio_intc_remove_irq_callback(stm32_gpio_irq_line_t line)
275 {
276 	struct stm32_intc_gpio_data *data = &intc_gpio_data;
277 	uint32_t line_num = ll_exti_line_to_linenum(line);
278 
279 	data->cb[line_num].cb = NULL;
280 	data->cb[line_num].data = NULL;
281 }
282 
stm32_exti_set_line_src_port(gpio_pin_t line,uint32_t port)283 void stm32_exti_set_line_src_port(gpio_pin_t line, uint32_t port)
284 {
285 	uint32_t ll_line = stm32_exti_linenum_to_src_cfg_line(line);
286 
287 #if defined(CONFIG_SOC_SERIES_STM32L0X) && defined(LL_SYSCFG_EXTI_PORTH)
288 	/*
289 	 * Ports F and G are not present on some STM32L0 parts, so
290 	 * for these parts port H external interrupt should be enabled
291 	 * by writing value 0x5 instead of 0x7.
292 	 */
293 	if (port == STM32_PORTH) {
294 		port = LL_SYSCFG_EXTI_PORTH;
295 	}
296 #endif
297 
298 	z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
299 
300 #ifdef CONFIG_SOC_SERIES_STM32F1X
301 	LL_GPIO_AF_SetEXTISource(port, ll_line);
302 
303 #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
304 	LL_EXTI_SetEXTISource(port, ll_line);
305 #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7rs_exti)
306 	LL_SBS_SetEXTISource(port, ll_line);
307 #elif defined(CONFIG_SOC_SERIES_STM32MP2X)
308 	LL_EXTI_SetEXTISource(EXTI2, port, ll_line);
309 #else
310 	LL_SYSCFG_SetEXTISource(port, ll_line);
311 #endif
312 	z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
313 }
314 
stm32_exti_get_line_src_port(gpio_pin_t line)315 uint32_t stm32_exti_get_line_src_port(gpio_pin_t line)
316 {
317 	uint32_t ll_line = stm32_exti_linenum_to_src_cfg_line(line);
318 	uint32_t port;
319 
320 #ifdef CONFIG_SOC_SERIES_STM32F1X
321 	port = LL_GPIO_AF_GetEXTISource(ll_line);
322 #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
323 	port = LL_EXTI_GetEXTISource(ll_line);
324 #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7rs_exti)
325 	port = LL_SBS_GetEXTISource(ll_line);
326 #elif defined(CONFIG_SOC_SERIES_STM32MP2X)
327 	port = LL_EXTI_GetEXTISource(EXTI2, ll_line);
328 #else
329 	port = LL_SYSCFG_GetEXTISource(ll_line);
330 #endif
331 
332 #if defined(CONFIG_SOC_SERIES_STM32L0X) && defined(LL_SYSCFG_EXTI_PORTH)
333 	/*
334 	 * Ports F and G are not present on some STM32L0 parts, so
335 	 * for these parts port H external interrupt is enabled
336 	 * by writing value 0x5 instead of 0x7.
337 	 */
338 	if (port == LL_SYSCFG_EXTI_PORTH) {
339 		port = STM32_PORTH;
340 	}
341 #endif
342 
343 	return port;
344 }
345