hw/gpio: Add Cadence GPIO controller
This patch add a new model for Cadence GPIO controller which supports 32 pins and interrupts for level-triggered/edge-triggered type on input pins. Also define new trace functions for analysis purpose and new configuration to enable this model. Signed-off-by: Kuan-Jui Chiu <kchiu@axiado.com> Message-id: 20260713073033.3883619-8-kchiu@axiado.com [PMM: drop unnecessary <private> and <public> marker comments] Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Kuan-Jui Chiu committed
Aug 11, 2026 at 20:14 UTC
cc57a945feb0906af6b56591c790f60bfd65945c
5 files changed
+354
hw/gpio/Kconfig
+3
@@ -30,3 +30,6 @@ config PCF8574
30
31
config ZAURUS_SCOOP
32
bool
33
+
34
+config CADENCE_GPIO
35
+ bool
hw/gpio/cadence_gpio.c
new
+292
@@ -0,0 +1,292 @@
1
+/*
2
+ * Cadence GPIO emulation.
3
+ *
4
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
5
+ *
6
+ * SPDX-License-Identifier: GPL-2.0-or-later
7
+ */
8
+
9
+#include "qemu/osdep.h"
10
+#include "hw/gpio/cadence_gpio.h"
11
+#include "hw/core/irq.h"
12
+#include "migration/vmstate.h"
13
+#include "qemu/log.h"
14
+#include "trace.h"
15
+
16
+static void cdns_gpio_update_irq(CadenceGPIOState *s)
17
+{
18
+ qemu_set_irq(s->irq, s->isr ? 1 : 0);
19
+}
20
+
21
+static void cdns_gpio_update_isr_with_inpvr(CadenceGPIOState *s, uint32_t new)
22
+{
23
+ uint32_t new_isr = 0;
24
+ uint32_t any_edges, rising_edges, falling_edges, deassert_mask;
25
+
26
+ /*
27
+ * If ITR is set, this is level triggered:
28
+ * set corresponding ISR bits when IVR matches new inpvr value.
29
+ */
30
+ new_isr |= s->itr & ~(s->ivr ^ new);
31
+
32
+ /*
33
+ * If ITR is not set, this is edge-triggered:
34
+ * If IOAR bit is set, trigger on any edge;
35
+ * otherwise trigger on rising edge if IVR is set,
36
+ * trigger on falling edge if IVR bit is 0.
37
+ */
38
+ any_edges = s->ioar & (s->inpvr ^ new);
39
+ rising_edges = s->ivr & ~s->inpvr & new;
40
+ falling_edges = ~s->ivr & s->inpvr & ~new;
41
+ new_isr |= ~s->itr & (any_edges | rising_edges | falling_edges);
42
+
43
+ /*
44
+ * In bypass mode or if this isn't an input pin, the corresponding ISR
45
+ * bit is forced to zero.
46
+ */
47
+ deassert_mask = s->bmr | ~s->dmr | s->imr;
48
+
49
+ new_isr &= ~deassert_mask;
50
+ s->isr = new_isr;
51
+
52
+ cdns_gpio_update_irq(s);
53
+}
54
+
55
+static void cdns_gpio_update_isr(CadenceGPIOState *s)
56
+{
57
+ cdns_gpio_update_isr_with_inpvr(s, s->inpvr);
58
+}
59
+
60
+static void cdns_gpio_set(void *opaque, int line, int level)
61
+{
62
+ CadenceGPIOState *s = CADENCE_GPIO(opaque);
63
+ uint32_t new_inpvr = deposit32(s->inpvr, line, 1, level ? 1 : 0);
64
+
65
+ trace_cdns_gpio_set(DEVICE(s)->canonical_path, line, level);
66
+
67
+ cdns_gpio_update_isr_with_inpvr(s, new_inpvr);
68
+
69
+ /* Sync INPVR with new value */
70
+ s->inpvr = new_inpvr;
71
+}
72
+
73
+static inline void cdns_gpio_update_output_irq(CadenceGPIOState *s)
74
+{
75
+ uint32_t is_output = ~s->bmr & ~s->dmr & s->oer;
76
+
77
+ for (int i = 0; i < CDNS_GPIO_NUM; i++) {
78
+ if (extract32(is_output, i, 1)) {
79
+ /* Forward the output value to corresponding irq */
80
+ qemu_set_irq(s->output[i], extract32(s->ovr, i, 1));
81
+ }
82
+ }
83
+}
84
+
85
+static uint64_t cdns_gpio_read(void *opaque, hwaddr offset, unsigned size)
86
+{
87
+ CadenceGPIOState *s = CADENCE_GPIO(opaque);
88
+ uint32_t reg_value = 0x0;
89
+
90
+ switch (offset) {
91
+ case CDNS_GPIO_BYPASS_MODE:
92
+ reg_value = s->bmr;
93
+ break;
94
+
95
+ case CDNS_GPIO_DIRECTION_MODE:
96
+ reg_value = s->dmr;
97
+ break;
98
+
99
+ case CDNS_GPIO_OUTPUT_EN:
100
+ reg_value = s->oer;
101
+ break;
102
+
103
+ case CDNS_GPIO_OUTPUT_VALUE:
104
+ reg_value = s->ovr;
105
+ break;
106
+
107
+ case CDNS_GPIO_INPUT_VALUE:
108
+ reg_value = s->inpvr;
109
+ break;
110
+
111
+ case CDNS_GPIO_IRQ_MASK:
112
+ reg_value = s->imr;
113
+ break;
114
+
115
+ case CDNS_GPIO_IRQ_STATUS:
116
+ reg_value = s->isr;
117
+ break;
118
+
119
+ case CDNS_GPIO_IRQ_TYPE:
120
+ reg_value = s->itr;
121
+ break;
122
+
123
+ case CDNS_GPIO_IRQ_VALUE:
124
+ reg_value = s->ivr;
125
+ break;
126
+
127
+ case CDNS_GPIO_IRQ_ANY_EDGE:
128
+ reg_value = s->ioar;
129
+ break;
130
+
131
+ default:
132
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
133
+ HWADDR_PRIx "\n", TYPE_CADENCE_GPIO, __func__, offset);
134
+ break;
135
+ }
136
+
137
+ trace_cdns_gpio_read(DEVICE(s)->canonical_path, offset, reg_value);
138
+
139
+ return reg_value;
140
+}
141
+
142
+static void cdns_gpio_write(void *opaque, hwaddr offset, uint64_t value,
143
+ unsigned size)
144
+{
145
+ CadenceGPIOState *s = CADENCE_GPIO(opaque);
146
+
147
+ trace_cdns_gpio_write(DEVICE(s)->canonical_path, offset, value);
148
+
149
+ switch (offset) {
150
+ case CDNS_GPIO_BYPASS_MODE:
151
+ s->bmr = value;
152
+ cdns_gpio_update_output_irq(s);
153
+ cdns_gpio_update_isr(s);
154
+ break;
155
+
156
+ case CDNS_GPIO_DIRECTION_MODE:
157
+ s->dmr = value;
158
+ cdns_gpio_update_output_irq(s);
159
+ cdns_gpio_update_isr(s);
160
+ break;
161
+
162
+ case CDNS_GPIO_OUTPUT_EN:
163
+ s->oer = value;
164
+ cdns_gpio_update_output_irq(s);
165
+ break;
166
+
167
+ case CDNS_GPIO_OUTPUT_VALUE:
168
+ s->ovr = value;
169
+ cdns_gpio_update_output_irq(s);
170
+ break;
171
+
172
+ case CDNS_GPIO_IRQ_EN:
173
+ s->imr &= ~value;
174
+ cdns_gpio_update_isr(s);
175
+ break;
176
+
177
+ case CDNS_GPIO_IRQ_DIS:
178
+ s->imr |= value;
179
+ cdns_gpio_update_isr(s);
180
+ break;
181
+
182
+ case CDNS_GPIO_IRQ_TYPE:
183
+ s->itr = value;
184
+ break;
185
+
186
+ case CDNS_GPIO_IRQ_VALUE:
187
+ s->ivr = value;
188
+ break;
189
+
190
+ case CDNS_GPIO_IRQ_ANY_EDGE:
191
+ s->ioar = value;
192
+ break;
193
+
194
+ case CDNS_GPIO_INPUT_VALUE:
195
+ case CDNS_GPIO_IRQ_MASK:
196
+ case CDNS_GPIO_IRQ_STATUS:
197
+ /* Read-Only */
198
+ break;
199
+
200
+ default:
201
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
202
+ HWADDR_PRIx "\n", TYPE_CADENCE_GPIO, __func__, offset);
203
+ break;
204
+ }
205
+}
206
+
207
+static const MemoryRegionOps cdns_gpio_ops = {
208
+ .read = cdns_gpio_read,
209
+ .write = cdns_gpio_write,
210
+ .endianness = DEVICE_LITTLE_ENDIAN,
211
+ .impl = {
212
+ .min_access_size = 4,
213
+ .max_access_size = 4,
214
+ },
215
+ .valid = {
216
+ .min_access_size = 4,
217
+ .max_access_size = 4,
218
+ }
219
+};
220
+
221
+static const VMStateDescription vmstate_cdns_gpio = {
222
+ .name = TYPE_CADENCE_GPIO,
223
+ .version_id = 1,
224
+ .minimum_version_id = 1,
225
+ .fields = (const VMStateField[]) {
226
+ VMSTATE_UINT32(bmr, CadenceGPIOState),
227
+ VMSTATE_UINT32(dmr, CadenceGPIOState),
228
+ VMSTATE_UINT32(oer, CadenceGPIOState),
229
+ VMSTATE_UINT32(ovr, CadenceGPIOState),
230
+ VMSTATE_UINT32(inpvr, CadenceGPIOState),
231
+ VMSTATE_UINT32(imr, CadenceGPIOState),
232
+ VMSTATE_UINT32(isr, CadenceGPIOState),
233
+ VMSTATE_UINT32(itr, CadenceGPIOState),
234
+ VMSTATE_UINT32(ivr, CadenceGPIOState),
235
+ VMSTATE_UINT32(ioar, CadenceGPIOState),
236
+ VMSTATE_END_OF_LIST()
237
+ }
238
+};
239
+
240
+static void cdns_gpio_reset(DeviceState *dev)
241
+{
242
+ CadenceGPIOState *s = CADENCE_GPIO(dev);
243
+
244
+ s->bmr = 0;
245
+ s->dmr = 0;
246
+ s->oer = 0;
247
+ s->ovr = 0;
248
+ s->inpvr = 0;
249
+ s->imr = 0xffffffff;
250
+ s->isr = 0;
251
+ s->itr = 0;
252
+ s->ivr = 0;
253
+ s->ioar = 0;
254
+}
255
+
256
+static void cdns_gpio_init(Object *obj)
257
+{
258
+ CadenceGPIOState *s = CADENCE_GPIO(obj);
259
+
260
+ memory_region_init_io(&s->iomem, obj, &cdns_gpio_ops, s,
261
+ TYPE_CADENCE_GPIO, CDNS_GPIO_REG_SIZE);
262
+
263
+ qdev_init_gpio_in(DEVICE(s), cdns_gpio_set, CDNS_GPIO_NUM);
264
+ qdev_init_gpio_out(DEVICE(s), s->output, CDNS_GPIO_NUM);
265
+
266
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
267
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
268
+}
269
+
270
+static void cdns_gpio_class_init(ObjectClass *klass, const void *data)
271
+{
272
+ DeviceClass *dc = DEVICE_CLASS(klass);
273
+
274
+ device_class_set_legacy_reset(dc, cdns_gpio_reset);
275
+ dc->vmsd = &vmstate_cdns_gpio;
276
+ dc->desc = "Cadence GPIO controller";
277
+}
278
+
279
+static const TypeInfo cdns_gpio_info = {
280
+ .name = TYPE_CADENCE_GPIO,
281
+ .parent = TYPE_SYS_BUS_DEVICE,
282
+ .instance_size = sizeof(CadenceGPIOState),
283
+ .instance_init = cdns_gpio_init,
284
+ .class_init = cdns_gpio_class_init,
285
+};
286
+
287
+static void cdns_gpio_register_types(void)
288
+{
289
+ type_register_static(&cdns_gpio_info);
290
+}
291
+
292
+type_init(cdns_gpio_register_types)
hw/gpio/meson.build
+1
@@ -19,3 +19,4 @@ system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_gpio.c'))
19
system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_sgpio.c'))
20
system_ss.add(when: 'CONFIG_SIFIVE_GPIO', if_true: files('sifive_gpio.c'))
21
system_ss.add(when: 'CONFIG_PCF8574', if_true: files('pcf8574.c'))
22
+system_ss.add(when: 'CONFIG_CADENCE_GPIO', if_true: files('cadence_gpio.c'))
hw/gpio/trace-events
+5
@@ -46,3 +46,8 @@ stm32l4x5_gpio_read(char *gpio, uint64_t addr) "GPIO%s addr: 0x%" PRIx64 " "
46
stm32l4x5_gpio_write(char *gpio, uint64_t addr, uint64_t data) "GPIO%s addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
47
stm32l4x5_gpio_update_idr(char *gpio, uint32_t old_idr, uint32_t new_idr) "GPIO%s from: 0x%x to: 0x%x"
48
stm32l4x5_gpio_pins(char *gpio, uint16_t disconnected, uint16_t high) "GPIO%s disconnected pins: 0x%x levels: 0x%x"
49
+
50
+# cadence_gpio.c
51
+cdns_gpio_read(const char *path, uint64_t offset, uint32_t value) "%s:reg[0x%04" PRIx64 "] -> 0x%" PRIx32
52
+cdns_gpio_write(const char *path, uint64_t offset, uint64_t value) "%s:reg[0x%04" PRIx64 "] <- 0x%04" PRIx64
53
+cdns_gpio_set(const char *path, int line, int level) "%s:[%d] <- %d"
include/hw/gpio/cadence_gpio.h
new
+53
@@ -0,0 +1,53 @@
1
+/*
2
+ * Cadence GPIO registers definition.
3
+ *
4
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
5
+ *
6
+ * SPDX-License-Identifier: GPL-2.0-or-later
7
+ */
8
+
9
+#ifndef CADENCE_GPIO_H
10
+#define CADENCE_GPIO_H
11
+
12
+#include "hw/core/sysbus.h"
13
+#include "qom/object.h"
14
+
15
+#define TYPE_CADENCE_GPIO "cadence_gpio"
16
+OBJECT_DECLARE_SIMPLE_TYPE(CadenceGPIOState, CADENCE_GPIO)
17
+
18
+#define CDNS_GPIO_REG_SIZE 0x400
19
+#define CDNS_GPIO_NUM 32
20
+
21
+#define CDNS_GPIO_BYPASS_MODE 0x00
22
+#define CDNS_GPIO_DIRECTION_MODE 0x04
23
+#define CDNS_GPIO_OUTPUT_EN 0x08
24
+#define CDNS_GPIO_OUTPUT_VALUE 0x0c
25
+#define CDNS_GPIO_INPUT_VALUE 0x10
26
+#define CDNS_GPIO_IRQ_MASK 0x14
27
+#define CDNS_GPIO_IRQ_EN 0x18
28
+#define CDNS_GPIO_IRQ_DIS 0x1c
29
+#define CDNS_GPIO_IRQ_STATUS 0x20
30
+#define CDNS_GPIO_IRQ_TYPE 0x24
31
+#define CDNS_GPIO_IRQ_VALUE 0x28
32
+#define CDNS_GPIO_IRQ_ANY_EDGE 0x2c
33
+
34
+struct CadenceGPIOState {
35
+ SysBusDevice parent_obj;
36
+
37
+ MemoryRegion iomem;
38
+
39
+ uint32_t bmr;
40
+ uint32_t dmr;
41
+ uint32_t oer;
42
+ uint32_t ovr;
43
+ uint32_t inpvr;
44
+ uint32_t imr;
45
+ uint32_t isr;
46
+ uint32_t itr;
47
+ uint32_t ivr;
48
+ uint32_t ioar;
49
+ qemu_irq irq;
50
+ qemu_irq output[CDNS_GPIO_NUM];
51
+};
52
+
53
+#endif /* CADENCE_GPIO_H */