| /* |
| * 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 <errno.h> |
| |
| #include <soc.h> |
| #include <stm32_ll_system.h> |
| |
| #include <zephyr/irq.h> |
| #include <zephyr/device.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/sys/__assert.h> |
| #include <zephyr/drivers/interrupt_controller/gpio_intc_stm32.h> |
| #include <zephyr/dt-bindings/pinctrl/stm32-pinctrl-common.h> /* 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; |
| } |