/* * 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. * Copyright (c) 2025 Alexander Kozhinov * * SPDX-License-Identifier: Apache-2.0 */ /** * @brief STM32 External Interrupt/Event Controller (EXTI) Driver */ #include #include #include #include #include #include #include "stm32_hsem.h" #include "intc_exti_stm32_priv.h" LOG_MODULE_REGISTER(exti_stm32, CONFIG_INTC_LOG_LEVEL); #define IS_VALID_EXTI_LINE_NUM(line_num) ((line_num) < STM32_EXTI_TOTAL_LINES_NUM) /* * The boilerplate for COND_CODE_x is needed because the values are not 0/1 */ #if STM32_EXTI_TOTAL_LINES_NUM > 32 #define HAS_LINES_32_63 1 #if STM32_EXTI_TOTAL_LINES_NUM > 64 #define HAS_LINES_64_95 1 #endif /* STM32_EXTI_TOTAL_LINES_NUM > 64 */ #endif /* STM32_EXTI_TOTAL_LINES_NUM > 32 */ #define EXTI_FN_HANDLER(_fn, line_num, line) \ if (line_num < 32U) { \ _fn(0_31, line); \ IF_ENABLED(HAS_LINES_32_63, ( \ } else if (line_num < 64U) { \ _fn(32_63, line); \ )) \ IF_ENABLED(HAS_LINES_64_95, ( \ } else if (line_num < 96U) { \ _fn(64_95, line); \ )) \ } else { \ LOG_ERR("Invalid line number %u", line_num); \ __ASSERT_NO_MSG(0); \ } #define EXTI_FN_RET_HANDLER(_fn, ret, line_num, line) \ if (line_num < 32U) { \ *ret = _fn(0_31, line); \ IF_ENABLED(HAS_LINES_32_63, ( \ } else if (line_num < 64U) { \ *ret = _fn(32_63, line); \ )) \ IF_ENABLED(HAS_LINES_64_95, ( \ } else if (line_num < 96U) { \ *ret = _fn(64_95, line); \ )) \ } else { \ LOG_ERR("Invalid line number %u", line_num); \ __ASSERT_NO_MSG(0); \ } bool stm32_exti_is_pending(uint32_t line_num) { bool ret = false; const uint32_t line = exti_linenum_to_ll_exti_line(line_num); if (!IS_VALID_EXTI_LINE_NUM(line_num)) { LOG_ERR("Invalid line number %u", line_num); return false; } z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY); /* * Note: we can't use EXTI_FN_HANDLER here because we care * about the return value of EXTI_IS_ACTIVE_FLAG. */ EXTI_FN_RET_HANDLER(EXTI_IS_ACTIVE_FLAG, &ret, line_num, line); z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID); return ret; } int stm32_exti_clear_pending(uint32_t line_num) { const uint32_t line = exti_linenum_to_ll_exti_line(line_num); if (!IS_VALID_EXTI_LINE_NUM(line_num)) { LOG_ERR("Invalid line number %u", line_num); return -EINVAL; } z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY); EXTI_FN_HANDLER(EXTI_CLEAR_FLAG, line_num, line); z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID); return 0; } int stm32_exti_sw_interrupt(uint32_t line_num) { const uint32_t line = exti_linenum_to_ll_exti_line(line_num); if (!IS_VALID_EXTI_LINE_NUM(line_num)) { LOG_ERR("Invalid line number %u", line_num); return -EINVAL; } z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY); EXTI_FN_HANDLER(EXTI_GENERATE_SWI, line_num, line); z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID); return 0; } /** Enables the peripheral clock required to access EXTI registers */ static int stm32_exti_enable_clocks(void) { /* Initialize to 0 for series where there is nothing to do. */ int ret = 0; #if DT_NODE_HAS_PROP(EXTI_NODE, clocks) const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); if (!device_is_ready(clk)) { LOG_ERR("Clock control device not ready"); return -ENODEV; } const struct stm32_pclken pclken = { .bus = DT_CLOCKS_CELL(EXTI_NODE, bus), .enr = DT_CLOCKS_CELL(EXTI_NODE, bits) }; ret = clock_control_on(clk, (clock_control_subsys_t) &pclken); #endif return ret; } /** * @brief Initializes the EXTI interrupt controller driver */ static int stm32_exti_init(const struct device *dev) { ARG_UNUSED(dev); return stm32_exti_enable_clocks(); } /** * @brief Enable EXTI interrupts. * * @param line_num EXTI line number * @param line LL EXTI line */ static void stm32_exti_enable_it(uint32_t line_num, uint32_t line) { EXTI_FN_HANDLER(EXTI_ENABLE_IT, line_num, line); } /** * @brief Disable EXTI interrupts. * * @param line_num EXTI line number * @param line LL EXTI line */ static void stm32_exti_disable_it(uint32_t line_num, uint32_t line) { EXTI_FN_HANDLER(EXTI_DISABLE_IT, line_num, line); } /** * @brief Enables rising trigger for specified EXTI line * * @param line_num EXTI line number * @param line LL EXTI line */ static void stm32_exti_enable_rising_trig(uint32_t line_num, uint32_t line) { EXTI_FN_HANDLER(EXTI_ENABLE_RISING_TRIG, line_num, line); } /** * @brief Disables rising trigger for specified EXTI line * * @param line_num EXTI line number * @param line LL EXTI line */ static void stm32_exti_disable_rising_trig(uint32_t line_num, uint32_t line) { EXTI_FN_HANDLER(EXTI_DISABLE_RISING_TRIG, line_num, line); } /** * @brief Enables falling trigger for specified EXTI line * * @param line_num EXTI line number * @param line LL EXTI line */ static void stm32_exti_enable_falling_trig(uint32_t line_num, uint32_t line) { EXTI_FN_HANDLER(EXTI_ENABLE_FALLING_TRIG, line_num, line); } /** * @brief Disables falling trigger for specified EXTI line * * @param line_num EXTI line number * @param line LL EXTI line */ static void stm32_exti_disable_falling_trig(uint32_t line_num, uint32_t line) { EXTI_FN_HANDLER(EXTI_DISABLE_FALLING_TRIG, line_num, line); } /** * @brief Selects EXTI trigger mode * * @param line_num EXTI line number * @param line LL EXTI line * @param mode EXTI mode */ static void stm32_exti_select_line_trigger(uint32_t line_num, uint32_t line, uint32_t trg) { switch (trg) { case STM32_EXTI_TRIG_NONE: stm32_exti_disable_rising_trig(line_num, line); stm32_exti_disable_falling_trig(line_num, line); break; case STM32_EXTI_TRIG_RISING: stm32_exti_enable_rising_trig(line_num, line); stm32_exti_disable_falling_trig(line_num, line); break; case STM32_EXTI_TRIG_FALLING: stm32_exti_enable_falling_trig(line_num, line); stm32_exti_disable_rising_trig(line_num, line); break; case STM32_EXTI_TRIG_BOTH: stm32_exti_enable_rising_trig(line_num, line); stm32_exti_enable_falling_trig(line_num, line); break; default: LOG_ERR("Unsupported EXTI trigger 0x%X", trg); break; } } /** * @brief Enable EXTI event. * * @param line_num EXTI line number * @param line LL EXTI line */ static void stm32_exti_enable_event(uint32_t line_num, uint32_t line) { EXTI_FN_HANDLER(EXTI_ENABLE_EVENT, line_num, line); } /** * @brief Disable EXTI interrupts. * * @param line_num EXTI line number * @param line LL EXTI line */ static void stm32_exti_disable_event(uint32_t line_num, uint32_t line) { EXTI_FN_HANDLER(EXTI_DISABLE_EVENT, line_num, line); } /** * @brief Enables external interrupt/event for specified EXTI line * * @param line_num EXTI line number * @param line LL EXTI line * @param mode EXTI mode */ static void stm32_exti_set_mode(uint32_t line_num, uint32_t line, stm32_exti_mode mode) { switch (mode) { case STM32_EXTI_MODE_NONE: stm32_exti_disable_event(line_num, line); stm32_exti_disable_it(line_num, line); break; case STM32_EXTI_MODE_IT: stm32_exti_disable_event(line_num, line); stm32_exti_enable_it(line_num, line); break; case STM32_EXTI_MODE_EVENT: stm32_exti_disable_it(line_num, line); stm32_exti_enable_event(line_num, line); break; case STM32_EXTI_MODE_BOTH: stm32_exti_enable_it(line_num, line); stm32_exti_enable_event(line_num, line); break; default: LOG_ERR("Unsupported EXTI mode %u", mode); break; } } int stm32_exti_enable(uint32_t line_num, stm32_exti_trigger_type trigger, stm32_exti_mode mode) { const uint32_t line = exti_linenum_to_ll_exti_line(line_num); if (!IS_VALID_EXTI_LINE_NUM(line_num)) { LOG_ERR("Invalid line number %u", line_num); return -EINVAL; } z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY); stm32_exti_select_line_trigger(line_num, line, trigger); stm32_exti_set_mode(line_num, line, mode); z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID); return 0; } int stm32_exti_disable(uint32_t line_num) { const uint32_t line = exti_linenum_to_ll_exti_line(line_num); if (!IS_VALID_EXTI_LINE_NUM(line_num)) { LOG_ERR("Invalid line number %u", line_num); return -EINVAL; } z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY); stm32_exti_set_mode(line_num, line, STM32_EXTI_MODE_NONE); stm32_exti_select_line_trigger(line_num, line, STM32_EXTI_TRIG_NONE); z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID); return 0; } DEVICE_DT_DEFINE(EXTI_NODE, &stm32_exti_init, NULL, NULL, NULL, PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, NULL);