/
intc_xmc4xxx.c
227 lines (192 loc) · 7.18 KB
/
intc_xmc4xxx.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
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);