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 STM32 External Interrupt/Event Controller (EXTI) Driver
13  */
14 
15 #include <soc.h>
16 #include <zephyr/device.h>
17 #include <zephyr/sys/util.h>
18 #include <zephyr/logging/log.h>
19 #include <zephyr/drivers/interrupt_controller/intc_exti_stm32.h>
20 #include <zephyr/drivers/clock_control/stm32_clock_control.h>
21 
22 #include "stm32_hsem.h"
23 #include "intc_exti_stm32_priv.h"
24 
25 LOG_MODULE_REGISTER(exti_stm32, CONFIG_INTC_LOG_LEVEL);
26 
27 #define IS_VALID_EXTI_LINE_NUM(line_num) ((line_num) < STM32_EXTI_TOTAL_LINES_NUM)
28 
29 /*
30  * The boilerplate for COND_CODE_x is needed because the values are not 0/1
31  */
32 #if STM32_EXTI_TOTAL_LINES_NUM > 32
33 #define HAS_LINES_32_63	1
34 #if STM32_EXTI_TOTAL_LINES_NUM > 64
35 #define HAS_LINES_64_95	1
36 #endif /* STM32_EXTI_TOTAL_LINES_NUM > 64 */
37 #endif /* STM32_EXTI_TOTAL_LINES_NUM > 32 */
38 
39 #define EXTI_FN_HANDLER(_fn, line_num, line)					\
40 	if (line_num < 32U) {							\
41 		_fn(0_31, line);						\
42 IF_ENABLED(HAS_LINES_32_63, (							\
43 	} else if (line_num < 64U) {						\
44 		_fn(32_63, line);						\
45 ))										\
46 IF_ENABLED(HAS_LINES_64_95, (							\
47 	} else if (line_num < 96U) {						\
48 		_fn(64_95, line);						\
49 ))										\
50 	} else {								\
51 		LOG_ERR("Invalid line number %u", line_num);			\
52 		__ASSERT_NO_MSG(0);						\
53 	}
54 
55 #define EXTI_FN_RET_HANDLER(_fn, ret, line_num, line)				\
56 	if (line_num < 32U) {							\
57 		*ret = _fn(0_31, line);						\
58 IF_ENABLED(HAS_LINES_32_63, (							\
59 	} else if (line_num < 64U) {						\
60 		*ret = _fn(32_63, line);					\
61 ))										\
62 IF_ENABLED(HAS_LINES_64_95, (							\
63 	} else if (line_num < 96U) {						\
64 		*ret = _fn(64_95, line);					\
65 ))										\
66 	} else {								\
67 		LOG_ERR("Invalid line number %u", line_num);			\
68 		__ASSERT_NO_MSG(0);						\
69 	}
70 
71 
stm32_exti_is_pending(uint32_t line_num)72 bool stm32_exti_is_pending(uint32_t line_num)
73 {
74 	bool ret = false;
75 	const uint32_t line = exti_linenum_to_ll_exti_line(line_num);
76 
77 	if (!IS_VALID_EXTI_LINE_NUM(line_num)) {
78 		LOG_ERR("Invalid line number %u", line_num);
79 		return false;
80 	}
81 
82 	z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
83 
84 	/*
85 	 * Note: we can't use EXTI_FN_HANDLER here because we care
86 	 * about the return value of EXTI_IS_ACTIVE_FLAG.
87 	 */
88 	EXTI_FN_RET_HANDLER(EXTI_IS_ACTIVE_FLAG, &ret, line_num, line);
89 
90 	z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
91 
92 	return ret;
93 }
94 
stm32_exti_clear_pending(uint32_t line_num)95 int stm32_exti_clear_pending(uint32_t line_num)
96 {
97 	const uint32_t line = exti_linenum_to_ll_exti_line(line_num);
98 
99 	if (!IS_VALID_EXTI_LINE_NUM(line_num)) {
100 		LOG_ERR("Invalid line number %u", line_num);
101 		return -EINVAL;
102 	}
103 
104 	z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
105 
106 	EXTI_FN_HANDLER(EXTI_CLEAR_FLAG, line_num, line);
107 
108 	z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
109 
110 	return 0;
111 }
112 
stm32_exti_sw_interrupt(uint32_t line_num)113 int stm32_exti_sw_interrupt(uint32_t line_num)
114 {
115 	const uint32_t line = exti_linenum_to_ll_exti_line(line_num);
116 
117 	if (!IS_VALID_EXTI_LINE_NUM(line_num)) {
118 		LOG_ERR("Invalid line number %u", line_num);
119 		return -EINVAL;
120 	}
121 
122 	z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
123 
124 	EXTI_FN_HANDLER(EXTI_GENERATE_SWI, line_num, line);
125 
126 	z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
127 
128 	return 0;
129 }
130 
131 /** Enables the peripheral clock required to access EXTI registers */
stm32_exti_enable_clocks(void)132 static int stm32_exti_enable_clocks(void)
133 {
134 	/* Initialize to 0 for series where there is nothing to do. */
135 	int ret = 0;
136 
137 #if DT_NODE_HAS_PROP(EXTI_NODE, clocks)
138 	const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
139 
140 	if (!device_is_ready(clk)) {
141 		LOG_ERR("Clock control device not ready");
142 		return -ENODEV;
143 	}
144 
145 	const struct stm32_pclken pclken = {
146 		.bus = DT_CLOCKS_CELL(EXTI_NODE, bus),
147 		.enr = DT_CLOCKS_CELL(EXTI_NODE, bits)
148 	};
149 
150 	ret = clock_control_on(clk, (clock_control_subsys_t) &pclken);
151 #endif
152 	return ret;
153 }
154 
155 /**
156  * @brief Initializes the EXTI interrupt controller driver
157  */
stm32_exti_init(const struct device * dev)158 static int stm32_exti_init(const struct device *dev)
159 {
160 	ARG_UNUSED(dev);
161 
162 	return stm32_exti_enable_clocks();
163 }
164 
165 /**
166  * @brief Enable EXTI interrupts.
167  *
168  * @param line_num	EXTI line number
169  * @param line	LL EXTI line
170  */
stm32_exti_enable_it(uint32_t line_num,uint32_t line)171 static void stm32_exti_enable_it(uint32_t line_num, uint32_t line)
172 {
173 	EXTI_FN_HANDLER(EXTI_ENABLE_IT, line_num, line);
174 }
175 
176 /**
177  * @brief Disable EXTI interrupts.
178  *
179  * @param line_num	EXTI line number
180  * @param line	LL EXTI line
181  */
stm32_exti_disable_it(uint32_t line_num,uint32_t line)182 static void stm32_exti_disable_it(uint32_t line_num, uint32_t line)
183 {
184 	EXTI_FN_HANDLER(EXTI_DISABLE_IT, line_num, line);
185 }
186 
187 /**
188  * @brief Enables rising trigger for specified EXTI line
189  *
190  * @param line_num	EXTI line number
191  * @param line	LL EXTI line
192  */
stm32_exti_enable_rising_trig(uint32_t line_num,uint32_t line)193 static void stm32_exti_enable_rising_trig(uint32_t line_num, uint32_t line)
194 {
195 	EXTI_FN_HANDLER(EXTI_ENABLE_RISING_TRIG, line_num, line);
196 }
197 
198 /**
199  * @brief Disables rising trigger for specified EXTI line
200  *
201  * @param line_num	EXTI line number
202  * @param line	LL EXTI line
203  */
stm32_exti_disable_rising_trig(uint32_t line_num,uint32_t line)204 static void stm32_exti_disable_rising_trig(uint32_t line_num, uint32_t line)
205 {
206 	EXTI_FN_HANDLER(EXTI_DISABLE_RISING_TRIG, line_num, line);
207 }
208 
209 /**
210  * @brief Enables falling trigger for specified EXTI line
211  *
212  * @param line_num	EXTI line number
213  * @param line	LL EXTI line
214  */
stm32_exti_enable_falling_trig(uint32_t line_num,uint32_t line)215 static void  stm32_exti_enable_falling_trig(uint32_t line_num, uint32_t line)
216 {
217 	EXTI_FN_HANDLER(EXTI_ENABLE_FALLING_TRIG, line_num, line);
218 }
219 
220 /**
221  * @brief Disables falling trigger for specified EXTI line
222  *
223  * @param line_num	EXTI line number
224  * @param line	LL EXTI line
225  */
stm32_exti_disable_falling_trig(uint32_t line_num,uint32_t line)226 static void stm32_exti_disable_falling_trig(uint32_t line_num, uint32_t line)
227 {
228 	EXTI_FN_HANDLER(EXTI_DISABLE_FALLING_TRIG, line_num, line);
229 }
230 
231 /**
232  * @brief Selects EXTI trigger mode
233  *
234  * @param line_num	EXTI line number
235  * @param line	LL EXTI line
236  * @param mode	EXTI mode
237  */
stm32_exti_select_line_trigger(uint32_t line_num,uint32_t line,uint32_t trg)238 static void stm32_exti_select_line_trigger(uint32_t line_num, uint32_t line,
239 					   uint32_t trg)
240 {
241 	switch (trg) {
242 	case STM32_EXTI_TRIG_NONE:
243 		stm32_exti_disable_rising_trig(line_num, line);
244 		stm32_exti_disable_falling_trig(line_num, line);
245 		break;
246 	case STM32_EXTI_TRIG_RISING:
247 		stm32_exti_enable_rising_trig(line_num, line);
248 		stm32_exti_disable_falling_trig(line_num, line);
249 		break;
250 	case STM32_EXTI_TRIG_FALLING:
251 		stm32_exti_enable_falling_trig(line_num, line);
252 		stm32_exti_disable_rising_trig(line_num, line);
253 		break;
254 	case STM32_EXTI_TRIG_BOTH:
255 		stm32_exti_enable_rising_trig(line_num, line);
256 		stm32_exti_enable_falling_trig(line_num, line);
257 		break;
258 	default:
259 		LOG_ERR("Unsupported EXTI trigger 0x%X", trg);
260 		break;
261 	}
262 }
263 
264 /**
265  * @brief Enable EXTI event.
266  *
267  * @param line_num	EXTI line number
268  * @param line	LL EXTI line
269  */
stm32_exti_enable_event(uint32_t line_num,uint32_t line)270 static void stm32_exti_enable_event(uint32_t line_num, uint32_t line)
271 {
272 	EXTI_FN_HANDLER(EXTI_ENABLE_EVENT, line_num, line);
273 }
274 
275 /**
276  * @brief Disable EXTI interrupts.
277  *
278  * @param line_num	EXTI line number
279  * @param line	LL EXTI line
280  */
stm32_exti_disable_event(uint32_t line_num,uint32_t line)281 static void stm32_exti_disable_event(uint32_t line_num, uint32_t line)
282 {
283 	EXTI_FN_HANDLER(EXTI_DISABLE_EVENT, line_num, line);
284 }
285 
286 /**
287  * @brief Enables external interrupt/event for specified EXTI line
288  *
289  * @param line_num	EXTI line number
290  * @param line	LL EXTI line
291  * @param mode	EXTI mode
292  */
stm32_exti_set_mode(uint32_t line_num,uint32_t line,stm32_exti_mode mode)293 static void stm32_exti_set_mode(uint32_t line_num, uint32_t line,
294 				stm32_exti_mode mode)
295 {
296 	switch (mode) {
297 	case STM32_EXTI_MODE_NONE:
298 		stm32_exti_disable_event(line_num, line);
299 		stm32_exti_disable_it(line_num, line);
300 		break;
301 	case STM32_EXTI_MODE_IT:
302 		stm32_exti_disable_event(line_num, line);
303 		stm32_exti_enable_it(line_num, line);
304 		break;
305 	case STM32_EXTI_MODE_EVENT:
306 		stm32_exti_disable_it(line_num, line);
307 		stm32_exti_enable_event(line_num, line);
308 		break;
309 	case STM32_EXTI_MODE_BOTH:
310 		stm32_exti_enable_it(line_num, line);
311 		stm32_exti_enable_event(line_num, line);
312 		break;
313 	default:
314 		LOG_ERR("Unsupported EXTI mode %u", mode);
315 		break;
316 	}
317 }
318 
stm32_exti_enable(uint32_t line_num,stm32_exti_trigger_type trigger,stm32_exti_mode mode)319 int stm32_exti_enable(uint32_t line_num, stm32_exti_trigger_type trigger,
320 		      stm32_exti_mode mode)
321 {
322 	const uint32_t line = exti_linenum_to_ll_exti_line(line_num);
323 
324 	if (!IS_VALID_EXTI_LINE_NUM(line_num)) {
325 		LOG_ERR("Invalid line number %u", line_num);
326 		return -EINVAL;
327 	}
328 
329 	z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
330 
331 	stm32_exti_select_line_trigger(line_num, line, trigger);
332 	stm32_exti_set_mode(line_num, line, mode);
333 
334 	z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
335 
336 	return 0;
337 }
338 
stm32_exti_disable(uint32_t line_num)339 int stm32_exti_disable(uint32_t line_num)
340 {
341 	const uint32_t line = exti_linenum_to_ll_exti_line(line_num);
342 
343 	if (!IS_VALID_EXTI_LINE_NUM(line_num)) {
344 		LOG_ERR("Invalid line number %u", line_num);
345 		return -EINVAL;
346 	}
347 
348 	z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
349 
350 	stm32_exti_set_mode(line_num, line, STM32_EXTI_MODE_NONE);
351 	stm32_exti_select_line_trigger(line_num, line, STM32_EXTI_TRIG_NONE);
352 
353 	z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
354 
355 	return 0;
356 }
357 
358 DEVICE_DT_DEFINE(EXTI_NODE, &stm32_exti_init,
359 	NULL, NULL, NULL,
360 	PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY,
361 	NULL);
362