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_exti.h>
18 #include <zephyr/sys/__assert.h>
19 #include <zephyr/sys/util.h>
20 #include <zephyr/drivers/interrupt_controller/exti_stm32.h>
21 #include <zephyr/irq.h>
22 
23 #include "stm32_hsem.h"
24 
25 /** @brief EXTI line ranges hold by a single ISR */
26 struct stm32_exti_range {
27 	/** Start of the range */
28 	uint8_t start;
29 	/** Range length */
30 	uint8_t len;
31 };
32 
33 #define NUM_EXTI_LINES DT_PROP(DT_NODELABEL(exti), num_lines)
34 
35 static IRQn_Type exti_irq_table[NUM_EXTI_LINES] = {[0 ... NUM_EXTI_LINES - 1] = 0xFF};
36 
37 /* wrapper for user callback */
38 struct __exti_cb {
39 	stm32_exti_callback_t cb;
40 	void *data;
41 };
42 
43 /* driver data */
44 struct stm32_exti_data {
45 	/* per-line callbacks */
46 	struct __exti_cb cb[NUM_EXTI_LINES];
47 };
48 
stm32_exti_enable(int line)49 void stm32_exti_enable(int line)
50 {
51 	int irqnum = 0;
52 
53 	if (line >= NUM_EXTI_LINES) {
54 		__ASSERT_NO_MSG(line);
55 	}
56 
57 	/* Get matching exti irq provided line thanks to irq_table */
58 	irqnum = exti_irq_table[line];
59 	if (irqnum == 0xFF) {
60 		__ASSERT_NO_MSG(line);
61 	}
62 
63 	/* Enable requested line interrupt */
64 #if defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
65 	LL_C2_EXTI_EnableIT_0_31(BIT((uint32_t)line));
66 #else
67 	LL_EXTI_EnableIT_0_31(BIT((uint32_t)line));
68 #endif
69 
70 	/* Enable exti irq interrupt */
71 	irq_enable(irqnum);
72 }
73 
stm32_exti_disable(int line)74 void stm32_exti_disable(int line)
75 {
76 	if (line < 32) {
77 #if defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
78 		LL_C2_EXTI_DisableIT_0_31(BIT((uint32_t)line));
79 #else
80 		LL_EXTI_DisableIT_0_31(BIT((uint32_t)line));
81 #endif
82 	} else {
83 		__ASSERT_NO_MSG(line);
84 	}
85 }
86 
87 /**
88  * @brief check if interrupt is pending
89  *
90  * @param line line number
91  */
stm32_exti_is_pending(int line)92 static inline int stm32_exti_is_pending(int line)
93 {
94 	if (line < 32) {
95 #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
96 		return (LL_EXTI_IsActiveRisingFlag_0_31(BIT((uint32_t)line)) ||
97 			LL_EXTI_IsActiveFallingFlag_0_31(BIT((uint32_t)line)));
98 #elif defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
99 		return LL_C2_EXTI_IsActiveFlag_0_31(BIT((uint32_t)line));
100 #else
101 		return LL_EXTI_IsActiveFlag_0_31(BIT((uint32_t)line));
102 #endif
103 	} else {
104 		__ASSERT_NO_MSG(line);
105 		return 0;
106 	}
107 }
108 
109 /**
110  * @brief clear pending interrupt bit
111  *
112  * @param line line number
113  */
stm32_exti_clear_pending(int line)114 static inline void stm32_exti_clear_pending(int line)
115 {
116 	if (line < 32) {
117 #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
118 		LL_EXTI_ClearRisingFlag_0_31(BIT((uint32_t)line));
119 		LL_EXTI_ClearFallingFlag_0_31(BIT((uint32_t)line));
120 #elif defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
121 		LL_C2_EXTI_ClearFlag_0_31(BIT((uint32_t)line));
122 #else
123 		LL_EXTI_ClearFlag_0_31(BIT((uint32_t)line));
124 #endif
125 	} else {
126 		__ASSERT_NO_MSG(line);
127 	}
128 }
129 
stm32_exti_trigger(int line,int trigger)130 void stm32_exti_trigger(int line, int trigger)
131 {
132 
133 	if (line >= 32) {
134 		__ASSERT_NO_MSG(line);
135 	}
136 
137 	z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
138 
139 	switch (trigger) {
140 	case STM32_EXTI_TRIG_NONE:
141 		LL_EXTI_DisableRisingTrig_0_31(BIT((uint32_t)line));
142 		LL_EXTI_DisableFallingTrig_0_31(BIT((uint32_t)line));
143 		break;
144 	case STM32_EXTI_TRIG_RISING:
145 		LL_EXTI_EnableRisingTrig_0_31(BIT((uint32_t)line));
146 		LL_EXTI_DisableFallingTrig_0_31(BIT((uint32_t)line));
147 		break;
148 	case STM32_EXTI_TRIG_FALLING:
149 		LL_EXTI_EnableFallingTrig_0_31(BIT((uint32_t)line));
150 		LL_EXTI_DisableRisingTrig_0_31(BIT((uint32_t)line));
151 		break;
152 	case STM32_EXTI_TRIG_BOTH:
153 		LL_EXTI_EnableRisingTrig_0_31(BIT((uint32_t)line));
154 		LL_EXTI_EnableFallingTrig_0_31(BIT((uint32_t)line));
155 		break;
156 	default:
157 		__ASSERT_NO_MSG(trigger);
158 		break;
159 	}
160 	z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
161 }
162 
163 /**
164  * @brief EXTI ISR handler
165  *
166  * Check EXTI lines in exti_range for pending interrupts
167  *
168  * @param exti_range Pointer to a exti_range structure
169  */
stm32_exti_isr(const void * exti_range)170 static void stm32_exti_isr(const void *exti_range)
171 {
172 	const struct device *dev = DEVICE_DT_GET(EXTI_NODE);
173 	struct stm32_exti_data *data = dev->data;
174 	const struct stm32_exti_range *range = exti_range;
175 	int line;
176 
177 	/* see which bits are set */
178 	for (uint8_t i = 0; i <= range->len; i++) {
179 		line = range->start + i;
180 		/* check if interrupt is pending */
181 		if (stm32_exti_is_pending(line) != 0) {
182 			/* clear pending interrupt */
183 			stm32_exti_clear_pending(line);
184 
185 			/* run callback only if one is registered */
186 			if (!data->cb[line].cb) {
187 				continue;
188 			}
189 
190 			data->cb[line].cb(line, data->cb[line].data);
191 		}
192 	}
193 }
194 
stm32_fill_irq_table(int8_t start,int8_t len,int32_t irqn)195 static void stm32_fill_irq_table(int8_t start, int8_t len, int32_t irqn)
196 {
197 	for (int i = 0; i < len; i++) {
198 		exti_irq_table[start + i] = irqn;
199 	}
200 }
201 
202 /* This macro:
203  * - populates line_range_x from line_range dt property
204  * - fill exti_irq_table through stm32_fill_irq_table()
205  * - calls IRQ_CONNECT for each irq & matching line_range
206  */
207 
208 #define STM32_EXTI_INIT(node_id, interrupts, idx)			\
209 	static const struct stm32_exti_range line_range_##idx = {	\
210 		DT_PROP_BY_IDX(node_id, line_ranges, UTIL_X2(idx)),	      \
211 		DT_PROP_BY_IDX(node_id, line_ranges, UTIL_INC(UTIL_X2(idx))) \
212 	};								\
213 	stm32_fill_irq_table(line_range_##idx.start,			\
214 			     line_range_##idx.len,			\
215 			     DT_IRQ_BY_IDX(node_id, idx, irq));		\
216 	IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, idx, irq),			\
217 		DT_IRQ_BY_IDX(node_id, idx, priority),			\
218 		stm32_exti_isr, &line_range_##idx,			\
219 		0);
220 
221 /**
222  * @brief initialize EXTI device driver
223  */
stm32_exti_init(const struct device * dev)224 static int stm32_exti_init(const struct device *dev)
225 {
226 	ARG_UNUSED(dev);
227 
228 	DT_FOREACH_PROP_ELEM(DT_NODELABEL(exti),
229 			     interrupt_names,
230 			     STM32_EXTI_INIT);
231 
232 	return 0;
233 }
234 
235 static struct stm32_exti_data exti_data;
236 DEVICE_DT_DEFINE(EXTI_NODE, &stm32_exti_init,
237 		 NULL,
238 		 &exti_data, NULL,
239 		 PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY,
240 		 NULL);
241 
242 /**
243  * @brief set & unset for the interrupt callbacks
244  */
stm32_exti_set_callback(int line,stm32_exti_callback_t cb,void * arg)245 int stm32_exti_set_callback(int line, stm32_exti_callback_t cb, void *arg)
246 {
247 	const struct device *const dev = DEVICE_DT_GET(EXTI_NODE);
248 	struct stm32_exti_data *data = dev->data;
249 
250 	if ((data->cb[line].cb == cb) && (data->cb[line].data == arg)) {
251 		return 0;
252 	}
253 
254 	/* if callback already exists/maybe-running return busy */
255 	if (data->cb[line].cb != NULL) {
256 		return -EBUSY;
257 	}
258 
259 	data->cb[line].cb = cb;
260 	data->cb[line].data = arg;
261 
262 	return 0;
263 }
264 
stm32_exti_unset_callback(int line)265 void stm32_exti_unset_callback(int line)
266 {
267 	const struct device *const dev = DEVICE_DT_GET(EXTI_NODE);
268 	struct stm32_exti_data *data = dev->data;
269 
270 	data->cb[line].cb = NULL;
271 	data->cb[line].data = NULL;
272 }
273