/* * Copyright (c) 2016 Open-RnD Sp. z o.o. * Copyright (c) 2017 RnDity Sp. z o.o. * Copyright (c) 2019-23 Linaro Limited * * 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 #include #include #include #include #include #include #include "stm32_hsem.h" /** @brief EXTI line ranges hold by a single ISR */ struct stm32_exti_range { /** Start of the range */ uint8_t start; /** Range length */ uint8_t len; }; #define NUM_EXTI_LINES DT_PROP(DT_NODELABEL(exti), num_lines) static IRQn_Type exti_irq_table[NUM_EXTI_LINES] = {[0 ... NUM_EXTI_LINES - 1] = 0xFF}; /* wrapper for user callback */ struct __exti_cb { stm32_exti_callback_t cb; void *data; }; /* driver data */ struct stm32_exti_data { /* per-line callbacks */ struct __exti_cb cb[NUM_EXTI_LINES]; }; void stm32_exti_enable(int line) { int irqnum = 0; if (line >= NUM_EXTI_LINES) { __ASSERT_NO_MSG(line); } /* Get matching exti irq provided line thanks to irq_table */ irqnum = exti_irq_table[line]; if (irqnum == 0xFF) { __ASSERT_NO_MSG(line); } /* Enable requested line interrupt */ #if defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4) LL_C2_EXTI_EnableIT_0_31(BIT((uint32_t)line)); #else LL_EXTI_EnableIT_0_31(BIT((uint32_t)line)); #endif /* Enable exti irq interrupt */ irq_enable(irqnum); } void stm32_exti_disable(int line) { if (line < 32) { #if defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4) LL_C2_EXTI_DisableIT_0_31(BIT((uint32_t)line)); #else LL_EXTI_DisableIT_0_31(BIT((uint32_t)line)); #endif } else { __ASSERT_NO_MSG(line); } } /** * @brief check if interrupt is pending * * @param line line number */ static inline int stm32_exti_is_pending(int line) { if (line < 32) { #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti) return (LL_EXTI_IsActiveRisingFlag_0_31(BIT((uint32_t)line)) || LL_EXTI_IsActiveFallingFlag_0_31(BIT((uint32_t)line))); #elif defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4) return LL_C2_EXTI_IsActiveFlag_0_31(BIT((uint32_t)line)); #else return LL_EXTI_IsActiveFlag_0_31(BIT((uint32_t)line)); #endif } else { __ASSERT_NO_MSG(line); return 0; } } /** * @brief clear pending interrupt bit * * @param line line number */ static inline void stm32_exti_clear_pending(int line) { if (line < 32) { #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32g0_exti) LL_EXTI_ClearRisingFlag_0_31(BIT((uint32_t)line)); LL_EXTI_ClearFallingFlag_0_31(BIT((uint32_t)line)); #elif defined(CONFIG_SOC_SERIES_STM32H7X) && defined(CONFIG_CPU_CORTEX_M4) LL_C2_EXTI_ClearFlag_0_31(BIT((uint32_t)line)); #else LL_EXTI_ClearFlag_0_31(BIT((uint32_t)line)); #endif } else { __ASSERT_NO_MSG(line); } } void stm32_exti_trigger(int line, int trigger) { if (line >= 32) { __ASSERT_NO_MSG(line); } z_stm32_hsem_lock(CFG_HW_EXTI_SEMID, HSEM_LOCK_DEFAULT_RETRY); switch (trigger) { case STM32_EXTI_TRIG_NONE: LL_EXTI_DisableRisingTrig_0_31(BIT((uint32_t)line)); LL_EXTI_DisableFallingTrig_0_31(BIT((uint32_t)line)); break; case STM32_EXTI_TRIG_RISING: LL_EXTI_EnableRisingTrig_0_31(BIT((uint32_t)line)); LL_EXTI_DisableFallingTrig_0_31(BIT((uint32_t)line)); break; case STM32_EXTI_TRIG_FALLING: LL_EXTI_EnableFallingTrig_0_31(BIT((uint32_t)line)); LL_EXTI_DisableRisingTrig_0_31(BIT((uint32_t)line)); break; case STM32_EXTI_TRIG_BOTH: LL_EXTI_EnableRisingTrig_0_31(BIT((uint32_t)line)); LL_EXTI_EnableFallingTrig_0_31(BIT((uint32_t)line)); break; default: __ASSERT_NO_MSG(trigger); break; } z_stm32_hsem_unlock(CFG_HW_EXTI_SEMID); } /** * @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; int line; /* see which bits are set */ for (uint8_t i = 0; i <= range->len; i++) { line = range->start + i; /* 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].cb) { continue; } data->cb[line].cb(line, data->cb[line].data); } } } 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 irq & matching line_range */ #define STM32_EXTI_INIT(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 initialize EXTI device driver */ static int stm32_exti_init(const struct device *dev) { ARG_UNUSED(dev); DT_FOREACH_PROP_ELEM(DT_NODELABEL(exti), interrupt_names, STM32_EXTI_INIT); return 0; } 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 set & unset for the interrupt callbacks */ int stm32_exti_set_callback(int line, stm32_exti_callback_t cb, void *arg) { const struct device *const dev = DEVICE_DT_GET(EXTI_NODE); struct stm32_exti_data *data = dev->data; if ((data->cb[line].cb == cb) && (data->cb[line].data == arg)) { return 0; } /* if callback already exists/maybe-running return busy */ if (data->cb[line].cb != NULL) { return -EBUSY; } data->cb[line].cb = cb; data->cb[line].data = arg; return 0; } void stm32_exti_unset_callback(int line) { const struct device *const dev = DEVICE_DT_GET(EXTI_NODE); struct stm32_exti_data *data = dev->data; data->cb[line].cb = NULL; data->cb[line].data = NULL; }