drivers: interrupt_controller: Add XMC4XXX ERU driver
In Infineon XMC4XXX SoCs, gpio interrupts are triggered via an
Event Request Unit (ERU) module. A subset of the gpios are
connected to the ERU. The ERU monitors edge triggers and creates
a SR.
This driver configures the ERU for a target port/pin combination
for rising/falling edge events. Note that the ERU module does
not generate SR based on the gpio level. Internally the ERU
tracks the *status* of an event. The status is set on a positive
edge and unset on a negative edge (or vice-versa depending on
the configuration). The value of the status is used to implement
a level triggered interrupt; The ISR checks the status flag and
calls the callback function if the status is set.
The ERU configurations for supported port/pin combinations are
stored in a devicetree file dts/arm/infineon/xmc4xxx_x_x-intc.dtsi.
The configurations are stored in the opaque array
uint16 port_line_mapping[].
Signed-off-by: Andriy Gelman <andriy.gelman@gmail.com>
diff --git a/drivers/interrupt_controller/CMakeLists.txt b/drivers/interrupt_controller/CMakeLists.txt
index 5c09490..8c4fa09 100644
--- a/drivers/interrupt_controller/CMakeLists.txt
+++ b/drivers/interrupt_controller/CMakeLists.txt
@@ -31,6 +31,7 @@
zephyr_library_sources_ifdef(CONFIG_VEXRISCV_LITEX_IRQ intc_vexriscv_litex.c)
zephyr_library_sources_ifdef(CONFIG_NUCLEI_ECLIC intc_nuclei_eclic.c)
zephyr_library_sources_ifdef(CONFIG_NXP_S32_EIRQ intc_eirq_nxp_s32.c)
+zephyr_library_sources_ifdef(CONFIG_XMC4XXX_INTC intc_xmc4xxx.c)
if(CONFIG_INTEL_VTD_ICTL)
zephyr_library_include_directories(${ZEPHYR_BASE}/arch/x86/include)
diff --git a/drivers/interrupt_controller/Kconfig b/drivers/interrupt_controller/Kconfig
index fd2bd20..d436bb8 100644
--- a/drivers/interrupt_controller/Kconfig
+++ b/drivers/interrupt_controller/Kconfig
@@ -83,4 +83,6 @@
source "drivers/interrupt_controller/Kconfig.nxp_s32"
+source "drivers/interrupt_controller/Kconfig.xmc4xxx"
+
endmenu
diff --git a/drivers/interrupt_controller/Kconfig.xmc4xxx b/drivers/interrupt_controller/Kconfig.xmc4xxx
new file mode 100644
index 0000000..e76b5c9
--- /dev/null
+++ b/drivers/interrupt_controller/Kconfig.xmc4xxx
@@ -0,0 +1,12 @@
+# XMC4XXX INTC configuration
+
+# Copyright (c) Schlumberger
+# SPDX-License-Identifier: Apache-2.0
+
+config XMC4XXX_INTC
+ bool "Interrupt Controller Driver for XMC4XXX series devices"
+ default y
+ depends on DT_HAS_INFINEON_XMC4XXX_INTC_ENABLED
+ help
+ Enable interrupt controller driver for XMC4XXX series of devices. This is required for
+ GPIO interrupt support.
diff --git a/drivers/interrupt_controller/intc_xmc4xxx.c b/drivers/interrupt_controller/intc_xmc4xxx.c
new file mode 100644
index 0000000..b5de41c
--- /dev/null
+++ b/drivers/interrupt_controller/intc_xmc4xxx.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2022 Schlumberger
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define DT_DRV_COMPAT infineon_xmc4xxx_intc
+
+#include <zephyr/device.h>
+#include <zephyr/devicetree.h>
+#include <zephyr/drivers/gpio.h>
+#include <zephyr/dt-bindings/interrupt-controller/infineon-xmc4xxx-intc.h>
+#include <zephyr/irq.h>
+
+#include <xmc_eru.h>
+
+/* In Infineon XMC4XXX SoCs, gpio interrupts are triggered via an Event Request Unit (ERU) */
+/* module. A subset of the GPIOs are connected to the ERU. The ERU monitors edge triggers */
+/* and creates a SR. */
+
+/* This driver configures the ERU for a target port/pin combination for rising/falling */
+/* edge events. Note that the ERU module does not generate SR based on the gpio level. */
+/* Internally the ERU tracks the *status* of an event. The status is set on a positive edge and */
+/* unset on a negative edge (or vice-versa depending on the configuration). The value of */
+/* the status is used to implement a level triggered interrupt; The ISR checks the status */
+/* flag and calls the callback function if the status is set. */
+
+/* The ERU configurations for supported port/pin combinations are stored in a devicetree file */
+/* dts/arm/infineon/xmc4xxx_x_x-intc.dtsi. The configurations are stored in the opaque array */
+/* uint16 port_line_mapping[]. The bitfields for the opaque entries are defined in */
+/* dt-bindings/interrupt-controller/infineon-xmc4xxx-intc.h. */
+
+struct isr_cb {
+ /* if fn is NULL it implies the interrupt line has not been allocated */
+ void (*fn)(const struct device *dev, int pin);
+ void *data;
+ enum gpio_int_mode mode;
+ uint8_t port_id;
+ uint8_t pin;
+};
+
+#define MAX_ISR_NUM 8
+struct intc_xmc4xxx_data {
+ struct isr_cb cb[MAX_ISR_NUM];
+};
+
+#define NUM_ERUS 2
+struct intc_xmc4xxx_config {
+ XMC_ERU_t *eru_regs[NUM_ERUS];
+};
+
+static const uint16_t port_line_mapping[DT_INST_PROP_LEN(0, port_line_mapping)] =
+ DT_INST_PROP(0, port_line_mapping);
+
+int intc_xmc4xxx_gpio_enable_interrupt(int port_id, int pin, enum gpio_int_mode mode,
+ enum gpio_int_trig trig,
+ void (*fn)(const struct device *, int), void *user_data)
+{
+ const struct device *dev = DEVICE_DT_INST_GET(0);
+ struct intc_xmc4xxx_data *data = dev->data;
+ const struct intc_xmc4xxx_config *config = dev->config;
+ int ret = -ENOTSUP;
+
+ for (int i = 0; i < ARRAY_SIZE(port_line_mapping); i++) {
+ XMC_ERU_ETL_CONFIG_t etl_config = {0};
+ XMC_ERU_OGU_CONFIG_t isr_config = {0};
+ XMC_ERU_ETL_EDGE_DETECTION_t trig_xmc;
+ XMC_ERU_t *eru;
+ int port_map, pin_map, line, eru_src, eru_ch;
+ struct isr_cb *cb;
+
+ port_map = XMC4XXX_INTC_GET_PORT(port_line_mapping[i]);
+ pin_map = XMC4XXX_INTC_GET_PIN(port_line_mapping[i]);
+
+ if (port_map != port_id || pin_map != pin) {
+ continue;
+ }
+
+ line = XMC4XXX_INTC_GET_LINE(port_line_mapping[i]);
+ cb = &data->cb[line];
+ if (cb->fn) {
+ /* It's already used. Continue search for available line */
+ /* with same port/pin */
+ ret = -EBUSY;
+ continue;
+ }
+
+ eru_src = XMC4XXX_INTC_GET_ERU_SRC(port_line_mapping[i]);
+ eru_ch = line & 0x3;
+
+ if (trig == GPIO_INT_TRIG_HIGH) {
+ trig_xmc = XMC_ERU_ETL_EDGE_DETECTION_RISING;
+ } else if (trig == GPIO_INT_TRIG_LOW) {
+ trig_xmc = XMC_ERU_ETL_EDGE_DETECTION_FALLING;
+ } else if (trig == GPIO_INT_TRIG_BOTH) {
+ trig_xmc = XMC_ERU_ETL_EDGE_DETECTION_BOTH;
+ } else {
+ return -EINVAL;
+ }
+
+ cb->port_id = port_id;
+ cb->pin = pin;
+ cb->mode = mode;
+ cb->fn = fn;
+ cb->data = user_data;
+
+ /* setup the eru */
+ etl_config.edge_detection = trig_xmc;
+ etl_config.input_a = eru_src;
+ etl_config.input_b = eru_src;
+ etl_config.source = eru_src >> 2;
+ etl_config.status_flag_mode = XMC_ERU_ETL_STATUS_FLAG_MODE_HWCTRL;
+ etl_config.enable_output_trigger = 1;
+ etl_config.output_trigger_channel = eru_ch;
+
+ eru = config->eru_regs[line >> 2];
+
+ XMC_ERU_ETL_Init(eru, eru_ch, &etl_config);
+
+ isr_config.service_request = XMC_ERU_OGU_SERVICE_REQUEST_ON_TRIGGER;
+ XMC_ERU_OGU_Init(eru, eru_ch, &isr_config);
+
+ /* if the gpio level is already set then we must manually set the interrupt to */
+ /* pending */
+ if (mode == GPIO_INT_MODE_LEVEL) {
+ ret = gpio_pin_get_raw(user_data, pin);
+ if (ret < 0) {
+ return ret;
+ }
+#define NVIC_ISPR_BASE 0xe000e200u
+ if ((ret == 0 && trig == GPIO_INT_TRIG_LOW) ||
+ (ret == 1 && trig == GPIO_INT_TRIG_HIGH)) {
+ eru->EXICON_b[eru_ch].FL = 1;
+ /* put interrupt into pending state */
+ *(uint32_t *)(NVIC_ISPR_BASE) |= BIT(line + 1);
+ }
+ }
+
+ return 0;
+ }
+ return ret;
+}
+
+int intc_xmc4xxx_gpio_disable_interrupt(int port_id, int pin)
+{
+ const struct device *dev = DEVICE_DT_INST_GET(0);
+ const struct intc_xmc4xxx_config *config = dev->config;
+ struct intc_xmc4xxx_data *data = dev->data;
+ int eru_ch;
+
+ for (int line = 0; line < ARRAY_SIZE(data->cb); line++) {
+ struct isr_cb *cb;
+
+ cb = &data->cb[line];
+ eru_ch = line & 0x3;
+ if (cb->fn && cb->port_id == port_id && cb->pin == pin) {
+ XMC_ERU_t *eru = config->eru_regs[line >> 2];
+
+ cb->fn = NULL;
+ /* disable the SR */
+ eru->EXICON_b[eru_ch].PE = 0;
+ /* unset the status flag */
+ eru->EXICON_b[eru_ch].FL = 0;
+ /* no need to clear other variables in cb*/
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static void intc_xmc4xxx_isr(void *arg)
+{
+ int line = (int)arg;
+ const struct device *dev = DEVICE_DT_INST_GET(0);
+ struct intc_xmc4xxx_data *data = dev->data;
+ const struct intc_xmc4xxx_config *config = dev->config;
+ struct isr_cb *cb = &data->cb[line];
+ XMC_ERU_t *eru = config->eru_regs[line >> 2];
+ int eru_ch = line & 0x3;
+
+ /* The callback function may actually disable the interrupt and set cb->fn = NULL */
+ /* as is done in tests/drivers/gpio/gpio_api_1pin. Assume that the callback function */
+ /* will NOT disable the interrupt and then enable another port/pin */
+ /* in the same callback which could potentially set cb->fn again. */
+ while (cb->fn) {
+ cb->fn(cb->data, cb->pin);
+ /* for level triggered interrupts we have to manually check the status. */
+ if (cb->mode == GPIO_INT_MODE_LEVEL && eru->EXICON_b[eru_ch].FL == 1) {
+ continue;
+ }
+ /* break for edge triggered interrupts */
+ break;
+ }
+}
+
+#define INTC_IRQ_CONNECT_ENABLE(name, line_number) \
+ COND_CODE_1(DT_INST_IRQ_HAS_NAME(0, name), \
+ (IRQ_CONNECT(DT_INST_IRQ_BY_NAME(0, name, irq), \
+ DT_INST_IRQ_BY_NAME(0, name, priority), intc_xmc4xxx_isr, (void *)line_number, 0); \
+ irq_enable(DT_INST_IRQ_BY_NAME(0, name, irq));), ())
+
+static int intc_xmc4xxx_init(const struct device *dev)
+{
+ /* connect irqs only if they defined by name in the dts */
+ INTC_IRQ_CONNECT_ENABLE(eru0sr0, 0);
+ INTC_IRQ_CONNECT_ENABLE(eru0sr1, 1);
+ INTC_IRQ_CONNECT_ENABLE(eru0sr2, 2);
+ INTC_IRQ_CONNECT_ENABLE(eru0sr3, 3);
+ INTC_IRQ_CONNECT_ENABLE(eru1sr0, 4);
+ INTC_IRQ_CONNECT_ENABLE(eru1sr1, 5);
+ INTC_IRQ_CONNECT_ENABLE(eru1sr2, 6);
+ INTC_IRQ_CONNECT_ENABLE(eru1sr3, 7);
+ return 0;
+}
+
+struct intc_xmc4xxx_data intc_xmc4xxx_data0;
+
+struct intc_xmc4xxx_config intc_xmc4xxx_config0 = {
+ .eru_regs = {
+ (XMC_ERU_t *)DT_INST_REG_ADDR_BY_NAME(0, eru0),
+ (XMC_ERU_t *)DT_INST_REG_ADDR_BY_NAME(0, eru1),
+ },
+};
+
+DEVICE_DT_INST_DEFINE(0, intc_xmc4xxx_init, NULL,
+ &intc_xmc4xxx_data0, &intc_xmc4xxx_config0, PRE_KERNEL_1,
+ CONFIG_INTC_INIT_PRIORITY, NULL);