/* * Copyright (c) 2022 Schlumberger * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT infineon_xmc4xxx_intc #include #include #include #include #include #include /* 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);