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