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