/* * Copyright (c) 2024 STMicroelectronics * * SPDX-License-Identifier: Apache-2.0 */ /** * @brief Driver for STM32WB0 GPIO interrupt controller * * In this file, "EXTI" should be understood as "GPIO interrupt controller". * There is no "External interrupt/event controller (EXTI)" in STM32WB0 MCUs. */ #define DT_DRV_COMPAT st_stm32wb0_gpio_intc #include #include #include #include #include #include #include #include #include /* For PORTA/PORTB defines */ #define INTC_NODE DT_DRV_INST(0) #define NUM_GPIO_PORTS (2) #define NUM_PINS_PER_GPIO_PORT (16) #define GPIO_PORT_TABLE_INDEX(port) \ DT_PROP_BY_IDX(INTC_NODE, line_ranges, UTIL_X2(port)) /* For good measure only */ #define _NUM_GPIOS_ON_PORT_X(x) \ DT_PROP_BY_IDX(INTC_NODE, line_ranges, UTIL_INC(UTIL_X2(x))) BUILD_ASSERT(DT_PROP_LEN(INTC_NODE, line_ranges) == (2 * NUM_GPIO_PORTS)); BUILD_ASSERT(_NUM_GPIOS_ON_PORT_X(STM32_PORTA) == NUM_PINS_PER_GPIO_PORT); BUILD_ASSERT(_NUM_GPIOS_ON_PORT_X(STM32_PORTB) == NUM_PINS_PER_GPIO_PORT); BUILD_ASSERT(GPIO_PORT_TABLE_INDEX(STM32_PORTB) == NUM_PINS_PER_GPIO_PORT); #undef _NUM_GPIOS_ON_PORT_X /* wrapper for user callback */ struct gpio_irq_cb_wrp { stm32_gpio_irq_cb_t fn; void *data; }; /* wrapper for ISR argument block */ struct wb0_gpio_isr_argblock { /* LL define for first line on GPIO port * (= least significant bit of the port's defines) */ uint32_t port_first_line; /* Pointer to first element of irq_callbacks_table * array that corresponds to this GPIO line */ struct gpio_irq_cb_wrp *cb_table; }; /* driver data */ struct stm32wb0_gpio_intc_data { /* per-port user callbacks */ struct gpio_irq_cb_wrp irq_cb_table[ NUM_GPIO_PORTS * NUM_PINS_PER_GPIO_PORT]; }; /** * @returns the LL_EXTI_LINE_Pxy define for pin @p pin on GPIO port @p port */ static inline stm32_gpio_irq_line_t portpin_to_ll_exti_line(uint32_t port, gpio_pin_t pin) { stm32_gpio_irq_line_t line = (1U << pin); if (port == STM32_PORTB) { line <<= SYSCFG_IO_DTR_PB0_DT_Pos; } else if (port == STM32_PORTA) { line <<= SYSCFG_IO_DTR_PA0_DT_Pos; } else { __ASSERT_NO_MSG(0); } return line; } /** * @returns a 32-bit value contaning: * - <5:5> port number (0 = PORTA, 1 = PORTB) * - <4:0> pin number (0~15) * * The returned value is always between 0~31. */ static inline uint32_t ll_exti_line_to_portpin(stm32_gpio_irq_line_t line) { return LOG2(line); } /** * @brief Retrieves the user callback block for a given line */ static struct gpio_irq_cb_wrp *irq_cb_wrp_for_line(stm32_gpio_irq_line_t line) { const struct device *const dev = DEVICE_DT_GET(INTC_NODE); struct stm32wb0_gpio_intc_data *const data = dev->data; const uint32_t index = ll_exti_line_to_portpin(line); return data->irq_cb_table + index; } /* Interrupt subroutines */ static void stm32wb0_gpio_isr(const void *userdata) { const struct wb0_gpio_isr_argblock *arg = userdata; const struct gpio_irq_cb_wrp *cb_table = arg->cb_table; uint32_t line = arg->port_first_line; for (uint32_t i = 0; i < NUM_PINS_PER_GPIO_PORT; i++, line <<= 1) { if (LL_EXTI_IsActiveFlag(line) != 0) { /* clear pending interrupt */ LL_EXTI_ClearFlag(line); /* execute user callback if registered */ if (cb_table[i].fn != NULL) { const gpio_port_pins_t pin = (1U << i); cb_table[i].fn(pin, cb_table[i].data); } } } } /** * Define the driver data early so that the macro that follows can * refer to it directly instead of indirecting through drv->data. */ static struct stm32wb0_gpio_intc_data gpio_intc_data; /** * This macro creates the ISR argument block for the @p pidx GPIO port, * connects the ISR to the interrupt line and enable IRQ at NVIC level. * * @param node GPIO INTC device tree node * @param pidx GPIO port index * @param plin LL define of first line on GPIO port */ #define INIT_INTC_PORT_INNER(node, pidx, plin) \ static const struct wb0_gpio_isr_argblock \ port ##pidx ##_argblock = { \ .port_first_line = plin, \ .cb_table = gpio_intc_data.irq_cb_table + \ GPIO_PORT_TABLE_INDEX(pidx) \ }; \ \ IRQ_CONNECT(DT_IRQN_BY_IDX(node, pidx), \ DT_IRQ_BY_IDX(node, pidx, priority), \ stm32wb0_gpio_isr, &port ##pidx ##_argblock, 0); \ \ irq_enable(DT_IRQN_BY_IDX(node, pidx)) #define STM32WB0_INIT_INTC_FOR_PORT(_PORT) \ INIT_INTC_PORT_INNER(INTC_NODE, \ STM32_PORT ##_PORT, LL_EXTI_LINE_P ##_PORT ## 0) /** * @brief Initializes the GPIO interrupt controller driver */ static int stm32wb0_gpio_intc_init(const struct device *dev) { ARG_UNUSED(dev); STM32WB0_INIT_INTC_FOR_PORT(A); STM32WB0_INIT_INTC_FOR_PORT(B); return 0; } DEVICE_DT_DEFINE(INTC_NODE, &stm32wb0_gpio_intc_init, NULL, &gpio_intc_data, NULL, PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, NULL); /** * @brief STM32 GPIO interrupt controller API implementation */ /** * @internal * STM32WB0 GPIO interrupt controller driver: * The type @ref stm32_gpio_irq_line_t is used to hold the LL_EXTI_LINE_Pxy * defines that corresponds to the specified pin. Note that these defines * also contain the target GPIO port. * @endinternal */ stm32_gpio_irq_line_t stm32_gpio_intc_get_pin_irq_line(uint32_t port, gpio_pin_t pin) { return portpin_to_ll_exti_line(port, pin); } void stm32_gpio_intc_enable_line(stm32_gpio_irq_line_t line) { /* Enable line interrupt at INTC level */ LL_EXTI_EnableIT(line); /** * Nothing else to do; INTC interrupt line * is enabled at NVIC level during init. */ } void stm32_gpio_intc_disable_line(stm32_gpio_irq_line_t line) { /* Disable line interrupt at INTC level */ LL_EXTI_DisableIT(line); } void stm32_gpio_intc_select_line_trigger(stm32_gpio_irq_line_t line, uint32_t trg) { switch (trg) { case STM32_GPIO_IRQ_TRIG_NONE: /** * There is no NONE trigger on STM32WB0. * We could disable the line interrupts here, but it isn't * really necessary: the GPIO driver already does it by * calling @ref stm32_gpio_intc_disable_line before calling * us with @p trigger = STM32_EXTI_TRIG_NONE. */ break; case STM32_GPIO_IRQ_TRIG_RISING: LL_EXTI_EnableEdgeDetection(line); LL_EXTI_DisableBothEdgeTrig(line); LL_EXTI_EnableRisingTrig(line); break; case STM32_GPIO_IRQ_TRIG_FALLING: LL_EXTI_EnableEdgeDetection(line); LL_EXTI_DisableBothEdgeTrig(line); LL_EXTI_DisableRisingTrig(line); break; case STM32_GPIO_IRQ_TRIG_BOTH: LL_EXTI_EnableEdgeDetection(line); LL_EXTI_EnableBothEdgeTrig(line); break; case STM32_GPIO_IRQ_TRIG_HIGH_LEVEL: LL_EXTI_DisableEdgeDetection(line); LL_EXTI_EnableRisingTrig(line); break; case STM32_GPIO_IRQ_TRIG_LOW_LEVEL: LL_EXTI_DisableEdgeDetection(line); LL_EXTI_DisableRisingTrig(line); break; default: __ASSERT_NO_MSG(0); break; } /* Since it is not possible to disable triggers on STM32WB0, * unlike in other STM32 series, activity on GPIO pin may have * set the "event occurred" bit spuriously. * * Clear the bit now after reconfiguration to make sure that no * spurious interrupt is delivered. (This works because interrupts * are enabled *after* trigger selection by the GPIO driver, which * is the only sensical order to do things in) */ LL_EXTI_ClearFlag(line); } int stm32_gpio_intc_set_irq_callback(stm32_gpio_irq_line_t line, stm32_gpio_irq_cb_t cb, void *user) { struct gpio_irq_cb_wrp *cb_wrp = irq_cb_wrp_for_line(line); if ((cb_wrp->fn == cb) && (cb_wrp->data == user)) { return 0; } /* If line already has a callback, return EBUSY */ if (cb_wrp->fn != NULL) { return -EBUSY; } cb_wrp->fn = cb; cb_wrp->data = user; return 0; } void stm32_gpio_intc_remove_irq_callback(uint32_t line) { struct gpio_irq_cb_wrp *cb_wrp = irq_cb_wrp_for_line(line); cb_wrp->fn = cb_wrp->data = NULL; }