1 /*
2  * Copyright (c) 2024 STMicroelectronics
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /**
8  * @brief Driver for STM32WB0 GPIO interrupt controller
9  *
10  * In this file, "EXTI" should be understood as "GPIO interrupt controller".
11  * There is no "External interrupt/event controller (EXTI)" in STM32WB0 MCUs.
12  */
13 
14 #define DT_DRV_COMPAT st_stm32wb0_gpio_intc
15 
16 #include <errno.h>
17 
18 #include <soc.h>
19 #include <stm32_ll_system.h>
20 
21 #include <zephyr/irq.h>
22 #include <zephyr/device.h>
23 #include <zephyr/sys/util.h>
24 #include <zephyr/sys/__assert.h>
25 #include <zephyr/drivers/interrupt_controller/gpio_intc_stm32.h>
26 #include <zephyr/dt-bindings/pinctrl/stm32-pinctrl-common.h>	/* For PORTA/PORTB defines */
27 
28 #define INTC_NODE DT_DRV_INST(0)
29 
30 #define NUM_GPIO_PORTS		(2)
31 #define NUM_PINS_PER_GPIO_PORT	(16)
32 
33 #define GPIO_PORT_TABLE_INDEX(port)	\
34 	DT_PROP_BY_IDX(INTC_NODE, line_ranges, UTIL_X2(port))
35 
36 /* For good measure only */
37 #define _NUM_GPIOS_ON_PORT_X(x)	\
38 	DT_PROP_BY_IDX(INTC_NODE, line_ranges, UTIL_INC(UTIL_X2(x)))
39 BUILD_ASSERT(DT_PROP_LEN(INTC_NODE, line_ranges) == (2 * NUM_GPIO_PORTS));
40 BUILD_ASSERT(_NUM_GPIOS_ON_PORT_X(STM32_PORTA) == NUM_PINS_PER_GPIO_PORT);
41 BUILD_ASSERT(_NUM_GPIOS_ON_PORT_X(STM32_PORTB) == NUM_PINS_PER_GPIO_PORT);
42 BUILD_ASSERT(GPIO_PORT_TABLE_INDEX(STM32_PORTB) == NUM_PINS_PER_GPIO_PORT);
43 #undef _NUM_GPIOS_ON_PORT_X
44 
45 /* wrapper for user callback */
46 struct gpio_irq_cb_wrp {
47 	stm32_gpio_irq_cb_t fn;
48 	void *data;
49 };
50 
51 /* wrapper for ISR argument block */
52 struct wb0_gpio_isr_argblock {
53 	/* LL define for first line on GPIO port
54 	 * (= least significant bit of the port's defines)
55 	 */
56 	uint32_t port_first_line;
57 	/* Pointer to first element of irq_callbacks_table
58 	 * array that corresponds to this GPIO line
59 	 */
60 	struct gpio_irq_cb_wrp *cb_table;
61 };
62 
63 /* driver data */
64 struct stm32wb0_gpio_intc_data {
65 	/* per-port user callbacks */
66 	struct gpio_irq_cb_wrp irq_cb_table[
67 		NUM_GPIO_PORTS * NUM_PINS_PER_GPIO_PORT];
68 };
69 
70 /**
71  * @returns the LL_EXTI_LINE_Pxy define for pin @p pin on GPIO port @p port
72  */
portpin_to_ll_exti_line(uint32_t port,gpio_pin_t pin)73 static inline stm32_gpio_irq_line_t portpin_to_ll_exti_line(uint32_t port, gpio_pin_t pin)
74 {
75 	stm32_gpio_irq_line_t line = (1U << pin);
76 
77 	if (port == STM32_PORTB) {
78 		line <<= SYSCFG_IO_DTR_PB0_DT_Pos;
79 	} else if (port == STM32_PORTA) {
80 		line <<= SYSCFG_IO_DTR_PA0_DT_Pos;
81 	} else {
82 		__ASSERT_NO_MSG(0);
83 	}
84 
85 	return line;
86 }
87 
88 /**
89  * @returns a 32-bit value contaning:
90  *	- <5:5> port number (0 = PORTA, 1 = PORTB)
91  *	- <4:0> pin number (0~15)
92  *
93  * The returned value is always between 0~31.
94  */
ll_exti_line_to_portpin(stm32_gpio_irq_line_t line)95 static inline uint32_t ll_exti_line_to_portpin(stm32_gpio_irq_line_t line)
96 {
97 	return LOG2(line);
98 }
99 
100 /**
101  * @brief Retrieves the user callback block for a given line
102  */
irq_cb_wrp_for_line(stm32_gpio_irq_line_t line)103 static struct gpio_irq_cb_wrp *irq_cb_wrp_for_line(stm32_gpio_irq_line_t line)
104 {
105 	const struct device *const dev = DEVICE_DT_GET(INTC_NODE);
106 	struct stm32wb0_gpio_intc_data *const data = dev->data;
107 	const uint32_t index = ll_exti_line_to_portpin(line);
108 
109 	return data->irq_cb_table + index;
110 }
111 
112 /* Interrupt subroutines */
stm32wb0_gpio_isr(const void * userdata)113 static void stm32wb0_gpio_isr(const void *userdata)
114 {
115 	const struct wb0_gpio_isr_argblock *arg = userdata;
116 	const struct gpio_irq_cb_wrp *cb_table = arg->cb_table;
117 
118 	uint32_t line = arg->port_first_line;
119 
120 	for (uint32_t i = 0; i < NUM_PINS_PER_GPIO_PORT; i++, line <<= 1) {
121 		if (LL_EXTI_IsActiveFlag(line) != 0) {
122 			/* clear pending interrupt */
123 			LL_EXTI_ClearFlag(line);
124 
125 			/* execute user callback if registered */
126 			if (cb_table[i].fn != NULL) {
127 				const gpio_port_pins_t pin = (1U << i);
128 
129 				cb_table[i].fn(pin, cb_table[i].data);
130 			}
131 		}
132 	}
133 }
134 
135 /**
136  * Define the driver data early so that the macro that follows can
137  * refer to it directly instead of indirecting through drv->data.
138  */
139 static struct stm32wb0_gpio_intc_data gpio_intc_data;
140 
141  /**
142   * This macro creates the ISR argument block for the @p pidx GPIO port,
143   * connects the ISR to the interrupt line and enable IRQ at NVIC level.
144   *
145   * @param node	GPIO INTC device tree node
146   * @param pidx	GPIO port index
147   * @param plin	LL define of first line on GPIO port
148   */
149 #define INIT_INTC_PORT_INNER(node, pidx, plin)	\
150 	static const struct wb0_gpio_isr_argblock			\
151 		port ##pidx ##_argblock = {				\
152 			.port_first_line = plin,			\
153 			.cb_table = gpio_intc_data.irq_cb_table +	\
154 				GPIO_PORT_TABLE_INDEX(pidx)		\
155 		};							\
156 									\
157 	IRQ_CONNECT(DT_IRQN_BY_IDX(node, pidx),				\
158 		DT_IRQ_BY_IDX(node, pidx, priority),			\
159 		stm32wb0_gpio_isr, &port ##pidx ##_argblock, 0);	\
160 									\
161 	irq_enable(DT_IRQN_BY_IDX(node, pidx))
162 
163 #define STM32WB0_INIT_INTC_FOR_PORT(_PORT)				\
164 	INIT_INTC_PORT_INNER(INTC_NODE,					\
165 		STM32_PORT ##_PORT, LL_EXTI_LINE_P ##_PORT ## 0)
166 
167 /**
168  * @brief Initializes the GPIO interrupt controller driver
169  */
stm32wb0_gpio_intc_init(const struct device * dev)170 static int stm32wb0_gpio_intc_init(const struct device *dev)
171 {
172 	ARG_UNUSED(dev);
173 
174 	STM32WB0_INIT_INTC_FOR_PORT(A);
175 
176 	STM32WB0_INIT_INTC_FOR_PORT(B);
177 
178 	return 0;
179 }
180 
181 DEVICE_DT_DEFINE(INTC_NODE, &stm32wb0_gpio_intc_init,
182 		 NULL, &gpio_intc_data, NULL, PRE_KERNEL_1,
183 		 CONFIG_INTC_INIT_PRIORITY, NULL);
184 
185 /**
186  * @brief STM32 GPIO interrupt controller API implementation
187  */
188 
189 /**
190  * @internal
191  * STM32WB0 GPIO interrupt controller driver:
192  * The type @ref stm32_gpio_irq_line_t is used to hold the LL_EXTI_LINE_Pxy
193  * defines that corresponds to the specified pin. Note that these defines
194  * also contain the target GPIO port.
195  * @endinternal
196  */
stm32_gpio_intc_get_pin_irq_line(uint32_t port,gpio_pin_t pin)197 stm32_gpio_irq_line_t stm32_gpio_intc_get_pin_irq_line(uint32_t port, gpio_pin_t pin)
198 {
199 	return portpin_to_ll_exti_line(port, pin);
200 }
201 
stm32_gpio_intc_enable_line(stm32_gpio_irq_line_t line)202 void stm32_gpio_intc_enable_line(stm32_gpio_irq_line_t line)
203 {
204 	/* Enable line interrupt at INTC level */
205 	LL_EXTI_EnableIT(line);
206 
207 	/**
208 	 * Nothing else to do; INTC interrupt line
209 	 * is enabled at NVIC level during init.
210 	 */
211 }
212 
stm32_gpio_intc_disable_line(stm32_gpio_irq_line_t line)213 void stm32_gpio_intc_disable_line(stm32_gpio_irq_line_t line)
214 {
215 	/* Disable line interrupt at INTC level */
216 	LL_EXTI_DisableIT(line);
217 }
218 
stm32_gpio_intc_select_line_trigger(stm32_gpio_irq_line_t line,uint32_t trg)219 void stm32_gpio_intc_select_line_trigger(stm32_gpio_irq_line_t line, uint32_t trg)
220 {
221 	switch (trg) {
222 	case STM32_GPIO_IRQ_TRIG_NONE:
223 		/**
224 		 * There is no NONE trigger on STM32WB0.
225 		 * We could disable the line interrupts here, but it isn't
226 		 * really necessary: the GPIO driver already does it by
227 		 * calling @ref stm32_gpio_intc_disable_line before calling
228 		 * us with @p trigger = STM32_EXTI_TRIG_NONE.
229 		 */
230 		break;
231 	case STM32_GPIO_IRQ_TRIG_RISING:
232 		LL_EXTI_EnableEdgeDetection(line);
233 		LL_EXTI_DisableBothEdgeTrig(line);
234 		LL_EXTI_EnableRisingTrig(line);
235 		break;
236 	case STM32_GPIO_IRQ_TRIG_FALLING:
237 		LL_EXTI_EnableEdgeDetection(line);
238 		LL_EXTI_DisableBothEdgeTrig(line);
239 		LL_EXTI_DisableRisingTrig(line);
240 		break;
241 	case STM32_GPIO_IRQ_TRIG_BOTH:
242 		LL_EXTI_EnableEdgeDetection(line);
243 		LL_EXTI_EnableBothEdgeTrig(line);
244 		break;
245 	case STM32_GPIO_IRQ_TRIG_HIGH_LEVEL:
246 		LL_EXTI_DisableEdgeDetection(line);
247 		LL_EXTI_EnableRisingTrig(line);
248 		break;
249 	case STM32_GPIO_IRQ_TRIG_LOW_LEVEL:
250 		LL_EXTI_DisableEdgeDetection(line);
251 		LL_EXTI_DisableRisingTrig(line);
252 		break;
253 	default:
254 		__ASSERT_NO_MSG(0);
255 		break;
256 	}
257 
258 	/* Since it is not possible to disable triggers on STM32WB0,
259 	 * unlike in other STM32 series, activity on GPIO pin may have
260 	 * set the "event occurred" bit spuriously.
261 	 *
262 	 * Clear the bit now after reconfiguration to make sure that no
263 	 * spurious interrupt is delivered. (This works because interrupts
264 	 * are enabled *after* trigger selection by the GPIO driver, which
265 	 * is the only sensical order to do things in)
266 	 */
267 	LL_EXTI_ClearFlag(line);
268 }
269 
stm32_gpio_intc_set_irq_callback(stm32_gpio_irq_line_t line,stm32_gpio_irq_cb_t cb,void * user)270 int stm32_gpio_intc_set_irq_callback(stm32_gpio_irq_line_t line,
271 					stm32_gpio_irq_cb_t cb, void *user)
272 {
273 	struct gpio_irq_cb_wrp *cb_wrp = irq_cb_wrp_for_line(line);
274 
275 	if ((cb_wrp->fn == cb) && (cb_wrp->data == user)) {
276 		return 0;
277 	}
278 
279 	/* If line already has a callback, return EBUSY */
280 	if (cb_wrp->fn != NULL) {
281 		return -EBUSY;
282 	}
283 
284 	cb_wrp->fn = cb;
285 	cb_wrp->data = user;
286 
287 	return 0;
288 }
289 
stm32_gpio_intc_remove_irq_callback(uint32_t line)290 void stm32_gpio_intc_remove_irq_callback(uint32_t line)
291 {
292 	struct gpio_irq_cb_wrp *cb_wrp = irq_cb_wrp_for_line(line);
293 
294 	cb_wrp->fn = cb_wrp->data = NULL;
295 }
296