@samitouri / QOSamiQemu / commits / cc57a945fe

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 */