diff --git a/drivers/interrupt_controller/CMakeLists.txt b/drivers/interrupt_controller/CMakeLists.txt index e9b7488f05b..edc0d23a633 100644 --- a/drivers/interrupt_controller/CMakeLists.txt +++ b/drivers/interrupt_controller/CMakeLists.txt @@ -12,6 +12,7 @@ zephyr_library_sources_ifdef(CONFIG_GIC_V1 intc_gic.c) zephyr_library_sources_ifdef(CONFIG_GIC_V2 intc_gic.c) zephyr_library_sources_ifdef(CONFIG_GIC_V3 intc_gicv3.c) zephyr_library_sources_ifdef(CONFIG_GIC_V3_ITS intc_gicv3_its.c) +zephyr_library_sources_ifdef(CONFIG_GPIO_INTC_STM32WB0 intc_gpio_stm32wb0.c) zephyr_library_sources_ifdef(CONFIG_INTEL_VTD_ICTL intc_intel_vtd.c) zephyr_library_sources_ifdef(CONFIG_IOAPIC intc_ioapic.c) zephyr_library_sources_ifdef(CONFIG_ITE_IT8XXX2_INTC intc_ite_it8xxx2.c) diff --git a/drivers/interrupt_controller/Kconfig.stm32 b/drivers/interrupt_controller/Kconfig.stm32 index d7672a8ee40..27e0841f89e 100644 --- a/drivers/interrupt_controller/Kconfig.stm32 +++ b/drivers/interrupt_controller/Kconfig.stm32 @@ -12,4 +12,11 @@ config EXTI_STM32 help Enable EXTI driver for STM32 line of MCUs +config GPIO_INTC_STM32WB0 + bool "GPIO Interrupt Controller Driver for STM32WB0 series" + default y + depends on DT_HAS_ST_STM32WB0_GPIO_INTC_ENABLED + help + Enable GPIO interrupt controller driver for STM32WB0 series + endif # SOC_FAMILY_STM32 diff --git a/drivers/interrupt_controller/intc_gpio_stm32wb0.c b/drivers/interrupt_controller/intc_gpio_stm32wb0.c new file mode 100644 index 00000000000..bcc7407d834 --- /dev/null +++ b/drivers/interrupt_controller/intc_gpio_stm32wb0.c @@ -0,0 +1,295 @@ +/* + * 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 *data) +{ + struct gpio_irq_cb_wrp *cb_wrp = irq_cb_wrp_for_line(line); + + if ((cb_wrp->fn == cb) && (cb_wrp->data == data)) { + return 0; + } + + /* If line already has a callback, return EBUSY */ + if (cb_wrp->fn != NULL) { + return -EBUSY; + } + + cb_wrp->fn = cb; + cb_wrp->data = data; + + 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; +} diff --git a/include/zephyr/drivers/interrupt_controller/gpio_intc_stm32.h b/include/zephyr/drivers/interrupt_controller/gpio_intc_stm32.h index 9199b8bad94..cd3c5ca810a 100644 --- a/include/zephyr/drivers/interrupt_controller/gpio_intc_stm32.h +++ b/include/zephyr/drivers/interrupt_controller/gpio_intc_stm32.h @@ -55,6 +55,10 @@ enum stm32_gpio_irq_trigger { STM32_GPIO_IRQ_TRIG_FALLING = 0x2, /* Trigger on both rising and falling edge */ STM32_GPIO_IRQ_TRIG_BOTH = 0x3, + /* Trigger on high level */ + STM32_GPIO_IRQ_TRIG_HIGH_LEVEL = 0x4, + /* Trigger on low level */ + STM32_GPIO_IRQ_TRIG_LOW_LEVEL = 0x5 }; /**