drivers: interrupt_controller: initial support for GD32 EXTI
Add initial support for the GigaDevice External Interrupt Controller.
This driver is required to manage GPIO interrupts. Only EXTI lines 0 to
15 are supported for now (no LVD, RTC, etc.). Driver can be extended in
the future to add support for extra EXTI lines.
Signed-off-by: Gerard Marull-Paretas <gerard@teslabs.com>
diff --git a/drivers/interrupt_controller/CMakeLists.txt b/drivers/interrupt_controller/CMakeLists.txt
index ca292c2..5cf84b8 100644
--- a/drivers/interrupt_controller/CMakeLists.txt
+++ b/drivers/interrupt_controller/CMakeLists.txt
@@ -6,6 +6,7 @@
zephyr_library_sources_ifdef(CONFIG_CAVS_ICTL intc_cavs.c)
zephyr_library_sources_ifdef(CONFIG_DW_ICTL intc_dw.c)
zephyr_library_sources_ifdef(CONFIG_EXTI_STM32 intc_exti_stm32.c)
+zephyr_library_sources_ifdef(CONFIG_GD32_EXTI intc_gd32_exti.c)
zephyr_library_sources_ifdef(CONFIG_GIC_V1 intc_gic.c)
zephyr_library_sources_ifdef(CONFIG_GIC_V2 intc_gic.c)
zephyr_library_sources_ifdef(CONFIG_GIC_V3 intc_gicv3.c)
diff --git a/drivers/interrupt_controller/Kconfig b/drivers/interrupt_controller/Kconfig
index 706aaa2..bbabc35 100644
--- a/drivers/interrupt_controller/Kconfig
+++ b/drivers/interrupt_controller/Kconfig
@@ -75,4 +75,6 @@
source "drivers/interrupt_controller/Kconfig.eclic"
+source "drivers/interrupt_controller/Kconfig.gd32_exti"
+
endmenu
diff --git a/drivers/interrupt_controller/Kconfig.gd32_exti b/drivers/interrupt_controller/Kconfig.gd32_exti
new file mode 100644
index 0000000..71fd689
--- /dev/null
+++ b/drivers/interrupt_controller/Kconfig.gd32_exti
@@ -0,0 +1,12 @@
+# Copyright (c) 2021 Teslabs Engineering S.L.
+# SPDX-License-Identifier: Apache-2.0
+
+DT_COMPAT_GD_GD32_EXTI := gd,gd32-exti
+
+config GD32_EXTI
+ bool "GD32 Extended Interrupts and Events (EXTI) Controller"
+ depends on SOC_FAMILY_GD32
+ default $(dt_compat_enabled,$(DT_COMPAT_GD_GD32_EXTI))
+ help
+ Enable the GigaDevice GD32 Extended Interrupts and Events (EXTI)
+ controller driver.
diff --git a/drivers/interrupt_controller/intc_gd32_exti.c b/drivers/interrupt_controller/intc_gd32_exti.c
new file mode 100644
index 0000000..b6d0446
--- /dev/null
+++ b/drivers/interrupt_controller/intc_gd32_exti.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2021 Teslabs Engineering S.L.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define DT_DRV_COMPAT gd_gd32_exti
+
+#include <device.h>
+#include <drivers/interrupt_controller/gd32_exti.h>
+#include <soc.h>
+#include <sys/__assert.h>
+#include <sys/util_macro.h>
+
+/** Unsupported line indicator */
+#define EXTI_NOTSUP 0xFFU
+
+/** Number of EXTI lines. */
+#define NUM_EXTI_LINES DT_INST_PROP(0, num_lines)
+
+/** @brief EXTI line ranges hold by a single ISR */
+struct gd32_exti_range {
+ /** Start of the range */
+ uint8_t min;
+ /** End of the range */
+ uint8_t max;
+};
+
+/** @brief EXTI line interrupt callback. */
+struct gd32_cb_data {
+ /** Callback function */
+ gd32_exti_cb_t cb;
+ /** User data. */
+ void *user;
+};
+
+/** EXTI driver data. */
+struct gd32_exti_data {
+ /** Array of callbacks. */
+ struct gd32_cb_data cbs[NUM_EXTI_LINES];
+};
+
+#ifdef CONFIG_GPIO_GD32
+static const struct gd32_exti_range line0_range = {0U, 0U};
+static const struct gd32_exti_range line1_range = {1U, 1U};
+static const struct gd32_exti_range line2_range = {2U, 2U};
+static const struct gd32_exti_range line3_range = {3U, 3U};
+static const struct gd32_exti_range line4_range = {4U, 4U};
+static const struct gd32_exti_range line5_9_range = {5U, 9U};
+static const struct gd32_exti_range line10_15_range = {10U, 15U};
+#endif /* CONFIG_GPIO_GD32 */
+
+/** @brief Obtain line IRQ number if enabled. */
+#define EXTI_LINE_IRQ_COND(enabled, line) \
+ COND_CODE_1(enabled, (DT_INST_IRQ_BY_NAME(0, line, irq)), (EXTI_NOTSUP))
+
+static const uint8_t line2irq[NUM_EXTI_LINES] = {
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line0),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line1),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line2),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line3),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line4),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line5_9),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line5_9),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line5_9),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line5_9),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line5_9),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line10_15),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line10_15),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line10_15),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line10_15),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line10_15),
+ EXTI_LINE_IRQ_COND(CONFIG_GPIO_GD32, line10_15),
+ EXTI_NOTSUP,
+ EXTI_NOTSUP,
+ EXTI_NOTSUP,
+#ifdef CONFIG_SOC_SERIES_GD32F4XX
+ EXTI_NOTSUP,
+ EXTI_NOTSUP,
+ EXTI_NOTSUP,
+ EXTI_NOTSUP,
+#endif /* CONFIG_SOC_SERIES_GD32F4XX */
+};
+
+static void gd32_exti_isr(void *isr_data)
+{
+ const struct device *dev = DEVICE_DT_INST_GET(0);
+ struct gd32_exti_data *data = dev->data;
+ const struct gd32_exti_range *range = isr_data;
+
+ for (uint8_t i = range->min; i <= range->max; i++) {
+ if ((EXTI_PD & BIT(i)) != 0U) {
+ EXTI_PD = BIT(i);
+
+ if (data->cbs[i].cb != NULL) {
+ data->cbs[i].cb(i, data->cbs[i].user);
+ }
+ }
+ }
+}
+
+void gd32_exti_enable(uint8_t line)
+{
+ __ASSERT_NO_MSG(line < NUM_EXTI_LINES);
+ __ASSERT_NO_MSG(line2irq[line] != EXTI_NOTSUP);
+
+ EXTI_INTEN |= BIT(line);
+
+ irq_enable(line2irq[line]);
+}
+
+void gd32_exti_disable(uint8_t line)
+{
+ __ASSERT_NO_MSG(line < NUM_EXTI_LINES);
+ __ASSERT_NO_MSG(line2irq[line] != EXTI_NOTSUP);
+
+ EXTI_INTEN &= ~BIT(line);
+}
+
+void gd32_exti_trigger(uint8_t line, uint8_t trigger)
+{
+ __ASSERT_NO_MSG(line < NUM_EXTI_LINES);
+ __ASSERT_NO_MSG(line2irq[line] != EXTI_NOTSUP);
+
+ if ((trigger & GD32_EXTI_TRIG_RISING) != 0U) {
+ EXTI_RTEN |= BIT(line);
+ } else {
+ EXTI_RTEN &= ~BIT(line);
+ }
+
+ if ((trigger & GD32_EXTI_TRIG_FALLING) != 0U) {
+ EXTI_FTEN |= BIT(line);
+ } else {
+ EXTI_FTEN &= ~BIT(line);
+ }
+}
+
+int gd32_exti_configure(uint8_t line, gd32_exti_cb_t cb, void *user)
+{
+ const struct device *dev = DEVICE_DT_INST_GET(0);
+ struct gd32_exti_data *data = dev->data;
+
+ __ASSERT_NO_MSG(line < NUM_EXTI_LINES);
+ __ASSERT_NO_MSG(line2irq[line] != EXTI_NOTSUP);
+
+ if ((data->cbs[line].cb != NULL) && (cb != NULL)) {
+ return -EALREADY;
+ }
+
+ data->cbs[line].cb = cb;
+ data->cbs[line].user = user;
+
+ return 0;
+}
+
+static int gd32_exti_init(const struct device *dev)
+{
+#ifdef CONFIG_GPIO_GD32
+ IRQ_CONNECT(DT_INST_IRQ_BY_NAME(0, line0, irq),
+ DT_INST_IRQ_BY_NAME(0, line0, priority),
+ gd32_exti_isr, &line0_range, 0);
+
+ IRQ_CONNECT(DT_INST_IRQ_BY_NAME(0, line1, irq),
+ DT_INST_IRQ_BY_NAME(0, line1, priority),
+ gd32_exti_isr, &line1_range, 0);
+
+ IRQ_CONNECT(DT_INST_IRQ_BY_NAME(0, line2, irq),
+ DT_INST_IRQ_BY_NAME(0, line2, priority),
+ gd32_exti_isr, &line2_range, 0);
+
+ IRQ_CONNECT(DT_INST_IRQ_BY_NAME(0, line3, irq),
+ DT_INST_IRQ_BY_NAME(0, line3, priority),
+ gd32_exti_isr, &line3_range, 0);
+
+ IRQ_CONNECT(DT_INST_IRQ_BY_NAME(0, line4, irq),
+ DT_INST_IRQ_BY_NAME(0, line4, priority),
+ gd32_exti_isr, &line4_range, 0);
+
+ IRQ_CONNECT(DT_INST_IRQ_BY_NAME(0, line5_9, irq),
+ DT_INST_IRQ_BY_NAME(0, line5_9, priority),
+ gd32_exti_isr, &line5_9_range, 0);
+
+ IRQ_CONNECT(DT_INST_IRQ_BY_NAME(0, line10_15, irq),
+ DT_INST_IRQ_BY_NAME(0, line10_15, priority),
+ gd32_exti_isr, &line10_15_range, 0);
+#endif /* CONFIG_GPIO_GD32 */
+
+ return 0;
+}
+
+static struct gd32_exti_data data;
+
+DEVICE_DT_INST_DEFINE(0, gd32_exti_init, NULL, &data, NULL, PRE_KERNEL_1,
+ CONFIG_KERNEL_INIT_PRIORITY_DEVICE, NULL);