zephyr/drivers/interrupt_controller/intc_gpio_stm32wb0.c

296 lines
7.8 KiB
C

/*
* 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;
}