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);