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 *
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9 /**
10 * @brief Driver for External interrupt/event controller in STM32 MCUs
11 */
12
13 #define EXTI_NODE DT_INST(0, st_stm32_exti)
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/clock_control/stm32_clock_control.h>
26 #include <zephyr/irq.h>
27
28 #include "stm32_hsem.h"
29
30 /** @brief EXTI lines range mapped to a single interrupt line */
31 struct stm32_exti_range {
32 /** Start of the range */
33 uint8_t start;
34 /** Range length */
35 uint8_t len;
36 };
37
38 #define NUM_EXTI_LINES DT_PROP(DT_NODELABEL(exti), num_lines)
39
40 static IRQn_Type exti_irq_table[NUM_EXTI_LINES] = {[0 ... NUM_EXTI_LINES - 1] = 0xFF};
41
42 /* User callback wrapper */
43 struct __exti_cb {
44 stm32_gpio_irq_cb_t cb;
45 void *data;
46 };
47
48 /* EXTI driver data */
49 struct stm32_exti_data {
50 /* per-line callbacks */
51 struct __exti_cb cb[NUM_EXTI_LINES];
52 };
53
54 /**
55 * @returns the LL_<PPP>_EXTI_LINE_xxx define that corresponds to specified @p linenum
56 * This value can be used with the LL EXTI source configuration functions.
57 */
stm32_exti_linenum_to_src_cfg_line(gpio_pin_t linenum)58 static inline uint32_t stm32_exti_linenum_to_src_cfg_line(gpio_pin_t linenum)
59 {
60 #if defined(CONFIG_SOC_SERIES_STM32L0X) || \
61 defined(CONFIG_SOC_SERIES_STM32F0X)
62 return ((linenum % 4 * 4) << 16) | (linenum / 4);
63 #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
64 return ((linenum & 0x3) << (16 + 3)) | (linenum >> 2);
65 #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7rs_exti)
66 /* Gives the LL_SBS_EXTI_LINEn corresponding to the line number */
67 return (((linenum % 4 * 4) << LL_SBS_REGISTER_PINPOS_SHFT) | (linenum / 4));
68 #else
69 return (0xF << ((linenum % 4 * 4) + 16)) | (linenum / 4);
70 #endif
71 }
72
73 /**
74 * @brief Checks interrupt pending bit for specified EXTI line
75 *
76 * @param line EXTI line number
77 */
stm32_exti_is_pending(stm32_gpio_irq_line_t line)78 static inline int stm32_exti_is_pending(stm32_gpio_irq_line_t line)
79 {
80 #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
81 return (LL_EXTI_IsActiveRisingFlag_0_31(line) ||
82 LL_EXTI_IsActiveFallingFlag_0_31(line));
83 #elif defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
84 return LL_C2_EXTI_IsActiveFlag_0_31(line);
85 #else
86 return LL_EXTI_IsActiveFlag_0_31(line);
87 #endif
88 }
89
90 /**
91 * @brief Clears interrupt pending bit for specified EXTI line
92 *
93 * @param line EXTI line number
94 */
stm32_exti_clear_pending(stm32_gpio_irq_line_t line)95 static inline void stm32_exti_clear_pending(stm32_gpio_irq_line_t line)
96 {
97 #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
98 LL_EXTI_ClearRisingFlag_0_31(line);
99 LL_EXTI_ClearFallingFlag_0_31(line);
100 #elif defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
101 LL_C2_EXTI_ClearFlag_0_31(line);
102 #else
103 LL_EXTI_ClearFlag_0_31(line);
104 #endif
105 }
106
107 /**
108 * @returns the LL_EXTI_LINE_n define for EXTI line number @p linenum
109 */
linenum_to_ll_exti_line(gpio_pin_t linenum)110 static inline stm32_gpio_irq_line_t linenum_to_ll_exti_line(gpio_pin_t linenum)
111 {
112 return BIT(linenum);
113 }
114
115 /**
116 * @returns EXTI line number for LL_EXTI_LINE_n define
117 */
ll_exti_line_to_linenum(stm32_gpio_irq_line_t line)118 static inline gpio_pin_t ll_exti_line_to_linenum(stm32_gpio_irq_line_t line)
119 {
120 return LOG2(line);
121 }
122
123 /**
124 * @brief EXTI ISR handler
125 *
126 * Check EXTI lines in exti_range for pending interrupts
127 *
128 * @param exti_range Pointer to a exti_range structure
129 */
stm32_exti_isr(const void * exti_range)130 static void stm32_exti_isr(const void *exti_range)
131 {
132 const struct device *dev = DEVICE_DT_GET(EXTI_NODE);
133 struct stm32_exti_data *data = dev->data;
134 const struct stm32_exti_range *range = exti_range;
135 stm32_gpio_irq_line_t line;
136 uint32_t line_num;
137
138 /* see which bits are set */
139 for (uint8_t i = 0; i <= range->len; i++) {
140 line_num = range->start + i;
141 line = linenum_to_ll_exti_line(line_num);
142
143 /* check if interrupt is pending */
144 if (stm32_exti_is_pending(line) != 0) {
145 /* clear pending interrupt */
146 stm32_exti_clear_pending(line);
147
148 /* run callback only if one is registered */
149 if (!data->cb[line_num].cb) {
150 continue;
151 }
152
153 /* `line` can be passed as-is because LL_EXTI_LINE_n is (1 << n) */
154 data->cb[line_num].cb(line, data->cb[line_num].data);
155 }
156 }
157 }
158
159 /** Enables the peripheral clock required to access EXTI registers */
stm32_exti_enable_registers(void)160 static int stm32_exti_enable_registers(void)
161 {
162 /* Initialize to 0 for series where there is nothing to do. */
163 int ret = 0;
164 #if defined(CONFIG_SOC_SERIES_STM32F2X) || \
165 defined(CONFIG_SOC_SERIES_STM32F3X) || \
166 defined(CONFIG_SOC_SERIES_STM32F4X) || \
167 defined(CONFIG_SOC_SERIES_STM32F7X) || \
168 defined(CONFIG_SOC_SERIES_STM32H7X) || \
169 defined(CONFIG_SOC_SERIES_STM32H7RSX) || \
170 defined(CONFIG_SOC_SERIES_STM32L1X) || \
171 defined(CONFIG_SOC_SERIES_STM32L4X) || \
172 defined(CONFIG_SOC_SERIES_STM32G4X)
173 const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
174 struct stm32_pclken pclken = {
175 #if defined(CONFIG_SOC_SERIES_STM32H7X)
176 .bus = STM32_CLOCK_BUS_APB4,
177 .enr = LL_APB4_GRP1_PERIPH_SYSCFG
178 #elif defined(CONFIG_SOC_SERIES_STM32H7RSX)
179 .bus = STM32_CLOCK_BUS_APB4,
180 .enr = LL_APB4_GRP1_PERIPH_SBS
181 #else
182 .bus = STM32_CLOCK_BUS_APB2,
183 .enr = LL_APB2_GRP1_PERIPH_SYSCFG
184 #endif /* CONFIG_SOC_SERIES_STM32H7X */
185 };
186
187 ret = clock_control_on(clk, (clock_control_subsys_t) &pclken);
188 #endif
189 return ret;
190 }
191
stm32_fill_irq_table(int8_t start,int8_t len,int32_t irqn)192 static void stm32_fill_irq_table(int8_t start, int8_t len, int32_t irqn)
193 {
194 for (int i = 0; i < len; i++) {
195 exti_irq_table[start + i] = irqn;
196 }
197 }
198
199 /* This macro:
200 * - populates line_range_x from line_range dt property
201 * - fill exti_irq_table through stm32_fill_irq_table()
202 * - calls IRQ_CONNECT for each interrupt and matching line_range
203 */
204 #define STM32_EXTI_INIT_LINE_RANGE(node_id, interrupts, idx) \
205 static const struct stm32_exti_range line_range_##idx = { \
206 DT_PROP_BY_IDX(node_id, line_ranges, UTIL_X2(idx)), \
207 DT_PROP_BY_IDX(node_id, line_ranges, UTIL_INC(UTIL_X2(idx))) \
208 }; \
209 stm32_fill_irq_table(line_range_##idx.start, \
210 line_range_##idx.len, \
211 DT_IRQ_BY_IDX(node_id, idx, irq)); \
212 IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, idx, irq), \
213 DT_IRQ_BY_IDX(node_id, idx, priority), \
214 stm32_exti_isr, &line_range_##idx, 0);
215
216 /**
217 * @brief Initializes the EXTI GPIO interrupt controller driver
218 */
stm32_exti_init(const struct device * dev)219 static int stm32_exti_init(const struct device *dev)
220 {
221 ARG_UNUSED(dev);
222
223 DT_FOREACH_PROP_ELEM(DT_NODELABEL(exti),
224 interrupt_names,
225 STM32_EXTI_INIT_LINE_RANGE);
226
227 return stm32_exti_enable_registers();
228 }
229
230 static struct stm32_exti_data exti_data;
231 DEVICE_DT_DEFINE(EXTI_NODE, &stm32_exti_init,
232 NULL,
233 &exti_data, NULL,
234 PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY,
235 NULL);
236
237 /**
238 * @brief EXTI GPIO interrupt controller API implementation
239 */
240
241 /**
242 * @internal
243 * STM32 EXTI driver:
244 * The type @ref stm32_gpio_irq_line_t is used to hold the LL_EXTI_LINE_xxx
245 * defines of the LL EXTI API that corresponds to the provided pin.
246 *
247 * The port is not part of these definitions because port configuration
248 * is done via different APIs, which use the LL_<PPP>_EXTI_LINE_xxx defines
249 * returned by @ref stm32_exti_linenum_to_src_cfg_line instead.
250 * @endinternal
251 */
stm32_gpio_intc_get_pin_irq_line(uint32_t port,gpio_pin_t pin)252 stm32_gpio_irq_line_t stm32_gpio_intc_get_pin_irq_line(uint32_t port, gpio_pin_t pin)
253 {
254 ARG_UNUSED(port);
255 return linenum_to_ll_exti_line(pin);
256 }
257
stm32_gpio_intc_enable_line(stm32_gpio_irq_line_t line)258 void stm32_gpio_intc_enable_line(stm32_gpio_irq_line_t line)
259 {
260 unsigned int irqnum;
261 uint32_t line_num = ll_exti_line_to_linenum(line);
262
263 __ASSERT_NO_MSG(line_num < NUM_EXTI_LINES);
264
265 /* Get matching exti irq provided line thanks to irq_table */
266 irqnum = exti_irq_table[line_num];
267 __ASSERT_NO_MSG(irqnum != 0xFF);
268
269 /* Enable requested line interrupt */
270 #if defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
271 LL_C2_EXTI_EnableIT_0_31(line);
272 #else
273 LL_EXTI_EnableIT_0_31(line);
274 #endif
275
276 /* Enable exti irq interrupt */
277 irq_enable(irqnum);
278 }
279
stm32_gpio_intc_disable_line(stm32_gpio_irq_line_t line)280 void stm32_gpio_intc_disable_line(stm32_gpio_irq_line_t line)
281 {
282 #if defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
283 LL_C2_EXTI_DisableIT_0_31(line);
284 #else
285 LL_EXTI_DisableIT_0_31(line);
286 #endif
287 }
288
stm32_gpio_intc_select_line_trigger(stm32_gpio_irq_line_t line,uint32_t trg)289 void stm32_gpio_intc_select_line_trigger(stm32_gpio_irq_line_t line, uint32_t trg)
290 {
291 z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
292
293 switch (trg) {
294 case STM32_GPIO_IRQ_TRIG_NONE:
295 LL_EXTI_DisableRisingTrig_0_31(line);
296 LL_EXTI_DisableFallingTrig_0_31(line);
297 break;
298 case STM32_GPIO_IRQ_TRIG_RISING:
299 LL_EXTI_EnableRisingTrig_0_31(line);
300 LL_EXTI_DisableFallingTrig_0_31(line);
301 break;
302 case STM32_GPIO_IRQ_TRIG_FALLING:
303 LL_EXTI_EnableFallingTrig_0_31(line);
304 LL_EXTI_DisableRisingTrig_0_31(line);
305 break;
306 case STM32_GPIO_IRQ_TRIG_BOTH:
307 LL_EXTI_EnableRisingTrig_0_31(line);
308 LL_EXTI_EnableFallingTrig_0_31(line);
309 break;
310 default:
311 __ASSERT_NO_MSG(0);
312 break;
313 }
314 z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
315 }
316
stm32_gpio_intc_set_irq_callback(stm32_gpio_irq_line_t line,stm32_gpio_irq_cb_t cb,void * user)317 int stm32_gpio_intc_set_irq_callback(stm32_gpio_irq_line_t line, stm32_gpio_irq_cb_t cb, void *user)
318 {
319 const struct device *const dev = DEVICE_DT_GET(EXTI_NODE);
320 struct stm32_exti_data *data = dev->data;
321 uint32_t line_num = ll_exti_line_to_linenum(line);
322
323 if ((data->cb[line_num].cb == cb) && (data->cb[line_num].data == user)) {
324 return 0;
325 }
326
327 /* if callback already exists/maybe-running return busy */
328 if (data->cb[line_num].cb != NULL) {
329 return -EBUSY;
330 }
331
332 data->cb[line_num].cb = cb;
333 data->cb[line_num].data = user;
334
335 return 0;
336 }
337
stm32_gpio_intc_remove_irq_callback(stm32_gpio_irq_line_t line)338 void stm32_gpio_intc_remove_irq_callback(stm32_gpio_irq_line_t line)
339 {
340 const struct device *const dev = DEVICE_DT_GET(EXTI_NODE);
341 struct stm32_exti_data *data = dev->data;
342 uint32_t line_num = ll_exti_line_to_linenum(line);
343
344 data->cb[line_num].cb = NULL;
345 data->cb[line_num].data = NULL;
346 }
347
stm32_exti_set_line_src_port(gpio_pin_t line,uint32_t port)348 void stm32_exti_set_line_src_port(gpio_pin_t line, uint32_t port)
349 {
350 uint32_t ll_line = stm32_exti_linenum_to_src_cfg_line(line);
351
352 #if defined(CONFIG_SOC_SERIES_STM32L0X) && defined(LL_SYSCFG_EXTI_PORTH)
353 /*
354 * Ports F and G are not present on some STM32L0 parts, so
355 * for these parts port H external interrupt should be enabled
356 * by writing value 0x5 instead of 0x7.
357 */
358 if (port == STM32_PORTH) {
359 port = LL_SYSCFG_EXTI_PORTH;
360 }
361 #endif
362
363 z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
364
365 #ifdef CONFIG_SOC_SERIES_STM32F1X
366 LL_GPIO_AF_SetEXTISource(port, ll_line);
367
368 #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
369 LL_EXTI_SetEXTISource(port, ll_line);
370 #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7rs_exti)
371 LL_SBS_SetEXTISource(port, ll_line);
372 #else
373 LL_SYSCFG_SetEXTISource(port, ll_line);
374 #endif
375 z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
376 }
377
stm32_exti_get_line_src_port(gpio_pin_t line)378 uint32_t stm32_exti_get_line_src_port(gpio_pin_t line)
379 {
380 uint32_t ll_line = stm32_exti_linenum_to_src_cfg_line(line);
381 uint32_t port;
382
383 #ifdef CONFIG_SOC_SERIES_STM32F1X
384 port = LL_GPIO_AF_GetEXTISource(ll_line);
385 #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
386 port = LL_EXTI_GetEXTISource(ll_line);
387 #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7rs_exti)
388 port = LL_SBS_GetEXTISource(ll_line);
389 #else
390 port = LL_SYSCFG_GetEXTISource(ll_line);
391 #endif
392
393 #if defined(CONFIG_SOC_SERIES_STM32L0X) && defined(LL_SYSCFG_EXTI_PORTH)
394 /*
395 * Ports F and G are not present on some STM32L0 parts, so
396 * for these parts port H external interrupt is enabled
397 * by writing value 0x5 instead of 0x7.
398 */
399 if (port == LL_SYSCFG_EXTI_PORTH) {
400 port = STM32_PORTH;
401 }
402 #endif
403
404 return port;
405 }
406