296 lines
7.8 KiB
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;
|
|
}
|