zephyr/drivers/interrupt_controller/intc_exti_stm32.c
Youssef Zini 38428c6f52 drivers: interrupt_controller: add stm32mp2 exti
Add the mp2 exti2 dts to the dtsi file.
Add mp2 exti hal and ll function calls with EXTI2 instance. We use the
EXTI2 instance because it contains the GPIO interrupts in the non-secure
context. (We are trying to build the blinky sample as a first milestone)

Signed-off-by: Youssef Zini <youssef.zini@savoirfairelinux.com>
2025-06-17 08:20:33 +02:00

441 lines
12 KiB
C

/*
* Copyright (c) 2016 Open-RnD Sp. z o.o.
* Copyright (c) 2017 RnDity Sp. z o.o.
* Copyright (c) 2019-23 Linaro Limited
* Copyright (C) 2025 Savoir-faire Linux, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief Driver for External interrupt/event controller in STM32 MCUs
*/
#define EXTI_NODE DT_INST(0, st_stm32_exti)
#include <zephyr/device.h>
#include <soc.h>
#include <stm32_ll_bus.h>
#include <stm32_ll_gpio.h> /* For STM32F1 series */
#include <stm32_ll_exti.h>
#include <stm32_ll_system.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/util.h>
#include <zephyr/dt-bindings/pinctrl/stm32-pinctrl-common.h> /* For STM32L0 series */
#include <zephyr/drivers/interrupt_controller/gpio_intc_stm32.h>
#include <zephyr/drivers/clock_control/stm32_clock_control.h>
#include <zephyr/irq.h>
#include "stm32_hsem.h"
/** @brief EXTI lines range mapped to a single interrupt line */
struct stm32_exti_range {
/** Start of the range */
uint8_t start;
/** Range length */
uint8_t len;
};
#define NUM_EXTI_LINES DT_PROP(EXTI_NODE, num_lines)
static IRQn_Type exti_irq_table[NUM_EXTI_LINES] = {[0 ... NUM_EXTI_LINES - 1] = 0xFF};
/* User callback wrapper */
struct __exti_cb {
stm32_gpio_irq_cb_t cb;
void *data;
};
/* EXTI driver data */
struct stm32_exti_data {
/* per-line callbacks */
struct __exti_cb cb[NUM_EXTI_LINES];
};
/**
* @returns the LL_<PPP>_EXTI_LINE_xxx define that corresponds to specified @p linenum
* This value can be used with the LL EXTI source configuration functions.
*/
static inline uint32_t stm32_exti_linenum_to_src_cfg_line(gpio_pin_t linenum)
{
#if defined(CONFIG_SOC_SERIES_STM32L0X) || \
defined(CONFIG_SOC_SERIES_STM32F0X)
return ((linenum % 4 * 4) << 16) | (linenum / 4);
#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti) || \
defined(CONFIG_SOC_SERIES_STM32MP2X)
return ((linenum & 0x3) << (16 + 3)) | (linenum >> 2);
#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7rs_exti)
/* Gives the LL_SBS_EXTI_LINEn corresponding to the line number */
return (((linenum % 4 * 4) << LL_SBS_REGISTER_PINPOS_SHFT) | (linenum / 4));
#else
return (0xF << ((linenum % 4 * 4) + 16)) | (linenum / 4);
#endif
}
/**
* @brief Checks interrupt pending bit for specified EXTI line
*
* @param line EXTI line number
*/
static inline int stm32_exti_is_pending(stm32_gpio_irq_line_t line)
{
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
return (LL_EXTI_IsActiveRisingFlag_0_31(line) ||
LL_EXTI_IsActiveFallingFlag_0_31(line));
#elif defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
return LL_C2_EXTI_IsActiveFlag_0_31(line);
#elif defined(CONFIG_SOC_SERIES_STM32MP2X)
return LL_EXTI_IsActiveRisingFlag_0_31(EXTI2, line) ||
LL_EXTI_IsActiveFallingFlag_0_31(EXTI2, line);
#else
return LL_EXTI_IsActiveFlag_0_31(line);
#endif
}
/**
* @brief Clears interrupt pending bit for specified EXTI line
*
* @param line EXTI line number
*/
static inline void stm32_exti_clear_pending(stm32_gpio_irq_line_t line)
{
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
LL_EXTI_ClearRisingFlag_0_31(line);
LL_EXTI_ClearFallingFlag_0_31(line);
#elif defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
LL_C2_EXTI_ClearFlag_0_31(line);
#elif defined(CONFIG_SOC_SERIES_STM32MP2X)
LL_EXTI_ClearRisingFlag_0_31(EXTI2, line);
LL_EXTI_ClearFallingFlag_0_31(EXTI2, line);
#else
LL_EXTI_ClearFlag_0_31(line);
#endif
}
/**
* @returns the LL_EXTI_LINE_n define for EXTI line number @p linenum
*/
static inline stm32_gpio_irq_line_t linenum_to_ll_exti_line(gpio_pin_t linenum)
{
return BIT(linenum);
}
/**
* @returns EXTI line number for LL_EXTI_LINE_n define
*/
static inline gpio_pin_t ll_exti_line_to_linenum(stm32_gpio_irq_line_t line)
{
return LOG2(line);
}
/**
* @brief EXTI ISR handler
*
* Check EXTI lines in exti_range for pending interrupts
*
* @param exti_range Pointer to a exti_range structure
*/
static void stm32_exti_isr(const void *exti_range)
{
const struct device *dev = DEVICE_DT_GET(EXTI_NODE);
struct stm32_exti_data *data = dev->data;
const struct stm32_exti_range *range = exti_range;
stm32_gpio_irq_line_t line;
uint32_t line_num;
/* see which bits are set */
for (uint8_t i = 0; i <= range->len; i++) {
line_num = range->start + i;
line = linenum_to_ll_exti_line(line_num);
/* check if interrupt is pending */
if (stm32_exti_is_pending(line) != 0) {
/* clear pending interrupt */
stm32_exti_clear_pending(line);
/* run callback only if one is registered */
if (!data->cb[line_num].cb) {
continue;
}
/* `line` can be passed as-is because LL_EXTI_LINE_n is (1 << n) */
data->cb[line_num].cb(line, data->cb[line_num].data);
}
}
}
/** Enables the peripheral clock required to access EXTI registers */
static int stm32_exti_enable_registers(void)
{
/* Initialize to 0 for series where there is nothing to do. */
int ret = 0;
#if defined(CONFIG_SOC_SERIES_STM32F2X) || \
defined(CONFIG_SOC_SERIES_STM32F3X) || \
defined(CONFIG_SOC_SERIES_STM32F4X) || \
defined(CONFIG_SOC_SERIES_STM32F7X) || \
defined(CONFIG_SOC_SERIES_STM32H7X) || \
defined(CONFIG_SOC_SERIES_STM32H7RSX) || \
defined(CONFIG_SOC_SERIES_STM32L1X) || \
defined(CONFIG_SOC_SERIES_STM32L4X) || \
defined(CONFIG_SOC_SERIES_STM32G4X)
const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
struct stm32_pclken pclken = {
#if defined(CONFIG_SOC_SERIES_STM32H7X)
.bus = STM32_CLOCK_BUS_APB4,
.enr = LL_APB4_GRP1_PERIPH_SYSCFG
#elif defined(CONFIG_SOC_SERIES_STM32H7RSX)
.bus = STM32_CLOCK_BUS_APB4,
.enr = LL_APB4_GRP1_PERIPH_SBS
#else
.bus = STM32_CLOCK_BUS_APB2,
.enr = LL_APB2_GRP1_PERIPH_SYSCFG
#endif /* CONFIG_SOC_SERIES_STM32H7X */
};
ret = clock_control_on(clk, (clock_control_subsys_t) &pclken);
#endif
return ret;
}
static void stm32_fill_irq_table(int8_t start, int8_t len, int32_t irqn)
{
for (int i = 0; i < len; i++) {
exti_irq_table[start + i] = irqn;
}
}
/* This macro:
* - populates line_range_x from line_range dt property
* - fill exti_irq_table through stm32_fill_irq_table()
* - calls IRQ_CONNECT for each interrupt and matching line_range
*/
#define STM32_EXTI_INIT_LINE_RANGE(node_id, interrupts, idx) \
static const struct stm32_exti_range line_range_##idx = { \
DT_PROP_BY_IDX(node_id, line_ranges, UTIL_X2(idx)), \
DT_PROP_BY_IDX(node_id, line_ranges, UTIL_INC(UTIL_X2(idx))) \
}; \
stm32_fill_irq_table(line_range_##idx.start, \
line_range_##idx.len, \
DT_IRQ_BY_IDX(node_id, idx, irq)); \
IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, idx, irq), \
DT_IRQ_BY_IDX(node_id, idx, priority), \
stm32_exti_isr, &line_range_##idx, 0);
/**
* @brief Initializes the EXTI GPIO interrupt controller driver
*/
static int stm32_exti_init(const struct device *dev)
{
ARG_UNUSED(dev);
DT_FOREACH_PROP_ELEM(EXTI_NODE,
interrupt_names,
STM32_EXTI_INIT_LINE_RANGE);
return stm32_exti_enable_registers();
}
static struct stm32_exti_data exti_data;
DEVICE_DT_DEFINE(EXTI_NODE, &stm32_exti_init,
NULL,
&exti_data, NULL,
PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY,
NULL);
/**
* @brief EXTI GPIO interrupt controller API implementation
*/
/**
* @internal
* STM32 EXTI driver:
* The type @ref stm32_gpio_irq_line_t is used to hold the LL_EXTI_LINE_xxx
* defines of the LL EXTI API that corresponds to the provided pin.
*
* The port is not part of these definitions because port configuration
* is done via different APIs, which use the LL_<PPP>_EXTI_LINE_xxx defines
* returned by @ref stm32_exti_linenum_to_src_cfg_line instead.
* @endinternal
*/
stm32_gpio_irq_line_t stm32_gpio_intc_get_pin_irq_line(uint32_t port, gpio_pin_t pin)
{
ARG_UNUSED(port);
return linenum_to_ll_exti_line(pin);
}
void stm32_gpio_intc_enable_line(stm32_gpio_irq_line_t line)
{
unsigned int irqnum;
uint32_t line_num = ll_exti_line_to_linenum(line);
__ASSERT_NO_MSG(line_num < NUM_EXTI_LINES);
/* Get matching exti irq provided line thanks to irq_table */
irqnum = exti_irq_table[line_num];
__ASSERT_NO_MSG(irqnum != 0xFF);
/* Enable requested line interrupt */
#if defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
LL_C2_EXTI_EnableIT_0_31(line);
#elif defined(CONFIG_SOC_SERIES_STM32MP2X)
LL_C2_EXTI_EnableIT_0_31(EXTI2, line);
#else
LL_EXTI_EnableIT_0_31(line);
#endif
/* Enable exti irq interrupt */
irq_enable(irqnum);
}
void stm32_gpio_intc_disable_line(stm32_gpio_irq_line_t line)
{
#if defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4)
LL_C2_EXTI_DisableIT_0_31(line);
#elif defined(CONFIG_SOC_SERIES_STM32MP2X)
LL_C2_EXTI_DisableIT_0_31(EXTI2, line);
#else
LL_EXTI_DisableIT_0_31(line);
#endif
}
void stm32_gpio_intc_select_line_trigger(stm32_gpio_irq_line_t line, uint32_t trg)
{
z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
switch (trg) {
#if defined(CONFIG_SOC_SERIES_STM32MP2X)
case STM32_GPIO_IRQ_TRIG_NONE:
LL_EXTI_DisableRisingTrig_0_31(EXTI2, line);
LL_EXTI_DisableFallingTrig_0_31(EXTI2, line);
break;
case STM32_GPIO_IRQ_TRIG_RISING:
LL_EXTI_EnableRisingTrig_0_31(EXTI2, line);
LL_EXTI_DisableFallingTrig_0_31(EXTI2, line);
break;
case STM32_GPIO_IRQ_TRIG_FALLING:
LL_EXTI_EnableFallingTrig_0_31(EXTI2, line);
LL_EXTI_DisableRisingTrig_0_31(EXTI2, line);
break;
case STM32_GPIO_IRQ_TRIG_BOTH:
LL_EXTI_EnableRisingTrig_0_31(EXTI2, line);
LL_EXTI_EnableFallingTrig_0_31(EXTI2, line);
break;
#else /* CONFIG_SOC_SERIES_STM32MP2X */
case STM32_GPIO_IRQ_TRIG_NONE:
LL_EXTI_DisableRisingTrig_0_31(line);
LL_EXTI_DisableFallingTrig_0_31(line);
break;
case STM32_GPIO_IRQ_TRIG_RISING:
LL_EXTI_EnableRisingTrig_0_31(line);
LL_EXTI_DisableFallingTrig_0_31(line);
break;
case STM32_GPIO_IRQ_TRIG_FALLING:
LL_EXTI_EnableFallingTrig_0_31(line);
LL_EXTI_DisableRisingTrig_0_31(line);
break;
case STM32_GPIO_IRQ_TRIG_BOTH:
LL_EXTI_EnableRisingTrig_0_31(line);
LL_EXTI_EnableFallingTrig_0_31(line);
break;
#endif /* CONFIG_SOC_SERIES_STM32MP2X */
default:
__ASSERT_NO_MSG(0);
break;
}
z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
}
int stm32_gpio_intc_set_irq_callback(stm32_gpio_irq_line_t line, stm32_gpio_irq_cb_t cb, void *user)
{
const struct device *const dev = DEVICE_DT_GET(EXTI_NODE);
struct stm32_exti_data *data = dev->data;
uint32_t line_num = ll_exti_line_to_linenum(line);
if ((data->cb[line_num].cb == cb) && (data->cb[line_num].data == user)) {
return 0;
}
/* if callback already exists/maybe-running return busy */
if (data->cb[line_num].cb != NULL) {
return -EBUSY;
}
data->cb[line_num].cb = cb;
data->cb[line_num].data = user;
return 0;
}
void stm32_gpio_intc_remove_irq_callback(stm32_gpio_irq_line_t line)
{
const struct device *const dev = DEVICE_DT_GET(EXTI_NODE);
struct stm32_exti_data *data = dev->data;
uint32_t line_num = ll_exti_line_to_linenum(line);
data->cb[line_num].cb = NULL;
data->cb[line_num].data = NULL;
}
void stm32_exti_set_line_src_port(gpio_pin_t line, uint32_t port)
{
uint32_t ll_line = stm32_exti_linenum_to_src_cfg_line(line);
#if defined(CONFIG_SOC_SERIES_STM32L0X) && defined(LL_SYSCFG_EXTI_PORTH)
/*
* Ports F and G are not present on some STM32L0 parts, so
* for these parts port H external interrupt should be enabled
* by writing value 0x5 instead of 0x7.
*/
if (port == STM32_PORTH) {
port = LL_SYSCFG_EXTI_PORTH;
}
#endif
z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY);
#ifdef CONFIG_SOC_SERIES_STM32F1X
LL_GPIO_AF_SetEXTISource(port, ll_line);
#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
LL_EXTI_SetEXTISource(port, ll_line);
#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7rs_exti)
LL_SBS_SetEXTISource(port, ll_line);
#elif defined(CONFIG_SOC_SERIES_STM32MP2X)
LL_EXTI_SetEXTISource(EXTI2, port, ll_line);
#else
LL_SYSCFG_SetEXTISource(port, ll_line);
#endif
z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID);
}
uint32_t stm32_exti_get_line_src_port(gpio_pin_t line)
{
uint32_t ll_line = stm32_exti_linenum_to_src_cfg_line(line);
uint32_t port;
#ifdef CONFIG_SOC_SERIES_STM32F1X
port = LL_GPIO_AF_GetEXTISource(ll_line);
#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti)
port = LL_EXTI_GetEXTISource(ll_line);
#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7rs_exti)
port = LL_SBS_GetEXTISource(ll_line);
#elif defined(CONFIG_SOC_SERIES_STM32MP2X)
port = LL_EXTI_GetEXTISource(EXTI2, ll_line);
#else
port = LL_SYSCFG_GetEXTISource(ll_line);
#endif
#if defined(CONFIG_SOC_SERIES_STM32L0X) && defined(LL_SYSCFG_EXTI_PORTH)
/*
* Ports F and G are not present on some STM32L0 parts, so
* for these parts port H external interrupt is enabled
* by writing value 0x5 instead of 0x7.
*/
if (port == LL_SYSCFG_EXTI_PORTH) {
port = STM32_PORTH;
}
#endif
return port;
}