228 lines
7.2 KiB
C
228 lines
7.2 KiB
C
/*
|
|
* 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);
|