]>
Commit | Line | Data |
---|---|---|
30efbf33 | 1 | /* |
2e30ccb4 | 2 | * SiFive System-on-Chip general purpose input/output register definition |
30efbf33 FC |
3 | * |
4 | * Copyright 2019 AdaCore | |
5 | * | |
6 | * Base on nrf51_gpio.c: | |
7 | * | |
8 | * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de> | |
9 | * | |
10 | * This code is licensed under the GPL version 2 or later. See | |
11 | * the COPYING file in the top-level directory. | |
12 | */ | |
13 | ||
14 | #include "qemu/osdep.h" | |
15 | #include "qemu/log.h" | |
64552b6b | 16 | #include "hw/irq.h" |
4bb216f6 | 17 | #include "hw/qdev-properties.h" |
30efbf33 | 18 | #include "hw/riscv/sifive_gpio.h" |
d6454270 | 19 | #include "migration/vmstate.h" |
30efbf33 FC |
20 | #include "trace.h" |
21 | ||
22 | static void update_output_irq(SIFIVEGPIOState *s) | |
23 | { | |
30efbf33 FC |
24 | uint32_t pending; |
25 | uint32_t pin; | |
26 | ||
27 | pending = s->high_ip & s->high_ie; | |
28 | pending |= s->low_ip & s->low_ie; | |
29 | pending |= s->rise_ip & s->rise_ie; | |
30 | pending |= s->fall_ip & s->fall_ie; | |
31 | ||
4bb216f6 | 32 | for (int i = 0; i < s->ngpio; i++) { |
30efbf33 FC |
33 | pin = 1 << i; |
34 | qemu_set_irq(s->irq[i], (pending & pin) != 0); | |
35 | trace_sifive_gpio_update_output_irq(i, (pending & pin) != 0); | |
36 | } | |
37 | } | |
38 | ||
39 | static void update_state(SIFIVEGPIOState *s) | |
40 | { | |
41 | size_t i; | |
42 | bool prev_ival, in, in_mask, port, out_xor, pull, output_en, input_en, | |
43 | rise_ip, fall_ip, low_ip, high_ip, oval, actual_value, ival; | |
44 | ||
4bb216f6 | 45 | for (i = 0; i < s->ngpio; i++) { |
30efbf33 FC |
46 | |
47 | prev_ival = extract32(s->value, i, 1); | |
48 | in = extract32(s->in, i, 1); | |
49 | in_mask = extract32(s->in_mask, i, 1); | |
50 | port = extract32(s->port, i, 1); | |
51 | out_xor = extract32(s->out_xor, i, 1); | |
52 | pull = extract32(s->pue, i, 1); | |
53 | output_en = extract32(s->output_en, i, 1); | |
54 | input_en = extract32(s->input_en, i, 1); | |
55 | rise_ip = extract32(s->rise_ip, i, 1); | |
56 | fall_ip = extract32(s->fall_ip, i, 1); | |
57 | low_ip = extract32(s->low_ip, i, 1); | |
58 | high_ip = extract32(s->high_ip, i, 1); | |
59 | ||
60 | /* Output value (IOF not supported) */ | |
61 | oval = output_en && (port ^ out_xor); | |
62 | ||
63 | /* Pin both driven externally and internally */ | |
64 | if (output_en && in_mask) { | |
65 | qemu_log_mask(LOG_GUEST_ERROR, "GPIO pin %zu short circuited\n", i); | |
66 | } | |
67 | ||
68 | if (in_mask) { | |
69 | /* The pin is driven by external device */ | |
70 | actual_value = in; | |
71 | } else if (output_en) { | |
72 | /* The pin is driven by internal circuit */ | |
73 | actual_value = oval; | |
74 | } else { | |
75 | /* Floating? Apply pull-up resistor */ | |
76 | actual_value = pull; | |
77 | } | |
78 | ||
621c1006 BM |
79 | if (output_en) { |
80 | qemu_set_irq(s->output[i], actual_value); | |
81 | } | |
30efbf33 FC |
82 | |
83 | /* Input value */ | |
84 | ival = input_en && actual_value; | |
85 | ||
86 | /* Interrupts */ | |
87 | high_ip = high_ip || ival; | |
88 | s->high_ip = deposit32(s->high_ip, i, 1, high_ip); | |
89 | ||
90 | low_ip = low_ip || !ival; | |
91 | s->low_ip = deposit32(s->low_ip, i, 1, low_ip); | |
92 | ||
93 | rise_ip = rise_ip || (ival && !prev_ival); | |
94 | s->rise_ip = deposit32(s->rise_ip, i, 1, rise_ip); | |
95 | ||
96 | fall_ip = fall_ip || (!ival && prev_ival); | |
97 | s->fall_ip = deposit32(s->fall_ip, i, 1, fall_ip); | |
98 | ||
99 | /* Update value */ | |
100 | s->value = deposit32(s->value, i, 1, ival); | |
101 | } | |
102 | update_output_irq(s); | |
103 | } | |
104 | ||
105 | static uint64_t sifive_gpio_read(void *opaque, hwaddr offset, unsigned int size) | |
106 | { | |
107 | SIFIVEGPIOState *s = SIFIVE_GPIO(opaque); | |
108 | uint64_t r = 0; | |
109 | ||
110 | switch (offset) { | |
111 | case SIFIVE_GPIO_REG_VALUE: | |
112 | r = s->value; | |
113 | break; | |
114 | ||
115 | case SIFIVE_GPIO_REG_INPUT_EN: | |
116 | r = s->input_en; | |
117 | break; | |
118 | ||
119 | case SIFIVE_GPIO_REG_OUTPUT_EN: | |
120 | r = s->output_en; | |
121 | break; | |
122 | ||
123 | case SIFIVE_GPIO_REG_PORT: | |
124 | r = s->port; | |
125 | break; | |
126 | ||
127 | case SIFIVE_GPIO_REG_PUE: | |
128 | r = s->pue; | |
129 | break; | |
130 | ||
131 | case SIFIVE_GPIO_REG_DS: | |
132 | r = s->ds; | |
133 | break; | |
134 | ||
135 | case SIFIVE_GPIO_REG_RISE_IE: | |
136 | r = s->rise_ie; | |
137 | break; | |
138 | ||
139 | case SIFIVE_GPIO_REG_RISE_IP: | |
140 | r = s->rise_ip; | |
141 | break; | |
142 | ||
143 | case SIFIVE_GPIO_REG_FALL_IE: | |
144 | r = s->fall_ie; | |
145 | break; | |
146 | ||
147 | case SIFIVE_GPIO_REG_FALL_IP: | |
148 | r = s->fall_ip; | |
149 | break; | |
150 | ||
151 | case SIFIVE_GPIO_REG_HIGH_IE: | |
152 | r = s->high_ie; | |
153 | break; | |
154 | ||
155 | case SIFIVE_GPIO_REG_HIGH_IP: | |
156 | r = s->high_ip; | |
157 | break; | |
158 | ||
159 | case SIFIVE_GPIO_REG_LOW_IE: | |
160 | r = s->low_ie; | |
161 | break; | |
162 | ||
163 | case SIFIVE_GPIO_REG_LOW_IP: | |
164 | r = s->low_ip; | |
165 | break; | |
166 | ||
167 | case SIFIVE_GPIO_REG_IOF_EN: | |
168 | r = s->iof_en; | |
169 | break; | |
170 | ||
171 | case SIFIVE_GPIO_REG_IOF_SEL: | |
172 | r = s->iof_sel; | |
173 | break; | |
174 | ||
175 | case SIFIVE_GPIO_REG_OUT_XOR: | |
176 | r = s->out_xor; | |
177 | break; | |
178 | ||
179 | default: | |
180 | qemu_log_mask(LOG_GUEST_ERROR, | |
181 | "%s: bad read offset 0x%" HWADDR_PRIx "\n", | |
182 | __func__, offset); | |
183 | } | |
184 | ||
185 | trace_sifive_gpio_read(offset, r); | |
186 | ||
187 | return r; | |
188 | } | |
189 | ||
190 | static void sifive_gpio_write(void *opaque, hwaddr offset, | |
2e30ccb4 | 191 | uint64_t value, unsigned int size) |
30efbf33 FC |
192 | { |
193 | SIFIVEGPIOState *s = SIFIVE_GPIO(opaque); | |
194 | ||
195 | trace_sifive_gpio_write(offset, value); | |
196 | ||
197 | switch (offset) { | |
198 | ||
199 | case SIFIVE_GPIO_REG_INPUT_EN: | |
200 | s->input_en = value; | |
201 | break; | |
202 | ||
203 | case SIFIVE_GPIO_REG_OUTPUT_EN: | |
204 | s->output_en = value; | |
205 | break; | |
206 | ||
207 | case SIFIVE_GPIO_REG_PORT: | |
208 | s->port = value; | |
209 | break; | |
210 | ||
211 | case SIFIVE_GPIO_REG_PUE: | |
212 | s->pue = value; | |
213 | break; | |
214 | ||
215 | case SIFIVE_GPIO_REG_DS: | |
216 | s->ds = value; | |
217 | break; | |
218 | ||
219 | case SIFIVE_GPIO_REG_RISE_IE: | |
220 | s->rise_ie = value; | |
221 | break; | |
222 | ||
223 | case SIFIVE_GPIO_REG_RISE_IP: | |
224 | /* Write 1 to clear */ | |
225 | s->rise_ip &= ~value; | |
226 | break; | |
227 | ||
228 | case SIFIVE_GPIO_REG_FALL_IE: | |
229 | s->fall_ie = value; | |
230 | break; | |
231 | ||
232 | case SIFIVE_GPIO_REG_FALL_IP: | |
233 | /* Write 1 to clear */ | |
234 | s->fall_ip &= ~value; | |
235 | break; | |
236 | ||
237 | case SIFIVE_GPIO_REG_HIGH_IE: | |
238 | s->high_ie = value; | |
239 | break; | |
240 | ||
241 | case SIFIVE_GPIO_REG_HIGH_IP: | |
242 | /* Write 1 to clear */ | |
243 | s->high_ip &= ~value; | |
244 | break; | |
245 | ||
246 | case SIFIVE_GPIO_REG_LOW_IE: | |
247 | s->low_ie = value; | |
248 | break; | |
249 | ||
250 | case SIFIVE_GPIO_REG_LOW_IP: | |
251 | /* Write 1 to clear */ | |
252 | s->low_ip &= ~value; | |
253 | break; | |
254 | ||
255 | case SIFIVE_GPIO_REG_IOF_EN: | |
256 | s->iof_en = value; | |
257 | break; | |
258 | ||
259 | case SIFIVE_GPIO_REG_IOF_SEL: | |
260 | s->iof_sel = value; | |
261 | break; | |
262 | ||
263 | case SIFIVE_GPIO_REG_OUT_XOR: | |
264 | s->out_xor = value; | |
265 | break; | |
266 | ||
267 | default: | |
268 | qemu_log_mask(LOG_GUEST_ERROR, | |
269 | "%s: bad write offset 0x%" HWADDR_PRIx "\n", | |
270 | __func__, offset); | |
271 | } | |
272 | ||
273 | update_state(s); | |
274 | } | |
275 | ||
276 | static const MemoryRegionOps gpio_ops = { | |
277 | .read = sifive_gpio_read, | |
278 | .write = sifive_gpio_write, | |
279 | .endianness = DEVICE_LITTLE_ENDIAN, | |
280 | .impl.min_access_size = 4, | |
281 | .impl.max_access_size = 4, | |
282 | }; | |
283 | ||
284 | static void sifive_gpio_set(void *opaque, int line, int value) | |
285 | { | |
286 | SIFIVEGPIOState *s = SIFIVE_GPIO(opaque); | |
287 | ||
288 | trace_sifive_gpio_set(line, value); | |
289 | ||
290 | assert(line >= 0 && line < SIFIVE_GPIO_PINS); | |
291 | ||
292 | s->in_mask = deposit32(s->in_mask, line, 1, value >= 0); | |
293 | if (value >= 0) { | |
294 | s->in = deposit32(s->in, line, 1, value != 0); | |
295 | } | |
296 | ||
297 | update_state(s); | |
298 | } | |
299 | ||
300 | static void sifive_gpio_reset(DeviceState *dev) | |
301 | { | |
302 | SIFIVEGPIOState *s = SIFIVE_GPIO(dev); | |
303 | ||
304 | s->value = 0; | |
305 | s->input_en = 0; | |
306 | s->output_en = 0; | |
307 | s->port = 0; | |
308 | s->pue = 0; | |
309 | s->ds = 0; | |
310 | s->rise_ie = 0; | |
311 | s->rise_ip = 0; | |
312 | s->fall_ie = 0; | |
313 | s->fall_ip = 0; | |
314 | s->high_ie = 0; | |
315 | s->high_ip = 0; | |
316 | s->low_ie = 0; | |
317 | s->low_ip = 0; | |
318 | s->iof_en = 0; | |
319 | s->iof_sel = 0; | |
320 | s->out_xor = 0; | |
321 | s->in = 0; | |
322 | s->in_mask = 0; | |
30efbf33 FC |
323 | } |
324 | ||
325 | static const VMStateDescription vmstate_sifive_gpio = { | |
326 | .name = TYPE_SIFIVE_GPIO, | |
327 | .version_id = 1, | |
328 | .minimum_version_id = 1, | |
329 | .fields = (VMStateField[]) { | |
330 | VMSTATE_UINT32(value, SIFIVEGPIOState), | |
331 | VMSTATE_UINT32(input_en, SIFIVEGPIOState), | |
332 | VMSTATE_UINT32(output_en, SIFIVEGPIOState), | |
333 | VMSTATE_UINT32(port, SIFIVEGPIOState), | |
334 | VMSTATE_UINT32(pue, SIFIVEGPIOState), | |
335 | VMSTATE_UINT32(rise_ie, SIFIVEGPIOState), | |
336 | VMSTATE_UINT32(rise_ip, SIFIVEGPIOState), | |
337 | VMSTATE_UINT32(fall_ie, SIFIVEGPIOState), | |
338 | VMSTATE_UINT32(fall_ip, SIFIVEGPIOState), | |
339 | VMSTATE_UINT32(high_ie, SIFIVEGPIOState), | |
340 | VMSTATE_UINT32(high_ip, SIFIVEGPIOState), | |
341 | VMSTATE_UINT32(low_ie, SIFIVEGPIOState), | |
342 | VMSTATE_UINT32(low_ip, SIFIVEGPIOState), | |
343 | VMSTATE_UINT32(iof_en, SIFIVEGPIOState), | |
344 | VMSTATE_UINT32(iof_sel, SIFIVEGPIOState), | |
345 | VMSTATE_UINT32(out_xor, SIFIVEGPIOState), | |
2e30ccb4 BM |
346 | VMSTATE_UINT32(in, SIFIVEGPIOState), |
347 | VMSTATE_UINT32(in_mask, SIFIVEGPIOState), | |
30efbf33 FC |
348 | VMSTATE_END_OF_LIST() |
349 | } | |
350 | }; | |
351 | ||
4bb216f6 BM |
352 | static Property sifive_gpio_properties[] = { |
353 | DEFINE_PROP_UINT32("ngpio", SIFIVEGPIOState, ngpio, SIFIVE_GPIO_PINS), | |
354 | DEFINE_PROP_END_OF_LIST(), | |
355 | }; | |
356 | ||
357 | static void sifive_gpio_realize(DeviceState *dev, Error **errp) | |
30efbf33 | 358 | { |
4bb216f6 | 359 | SIFIVEGPIOState *s = SIFIVE_GPIO(dev); |
30efbf33 | 360 | |
4bb216f6 | 361 | memory_region_init_io(&s->mmio, OBJECT(dev), &gpio_ops, s, |
30efbf33 | 362 | TYPE_SIFIVE_GPIO, SIFIVE_GPIO_SIZE); |
30efbf33 | 363 | |
4bb216f6 BM |
364 | sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); |
365 | ||
366 | for (int i = 0; i < s->ngpio; i++) { | |
367 | sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq[i]); | |
30efbf33 FC |
368 | } |
369 | ||
4bb216f6 BM |
370 | qdev_init_gpio_in(DEVICE(s), sifive_gpio_set, s->ngpio); |
371 | qdev_init_gpio_out(DEVICE(s), s->output, s->ngpio); | |
30efbf33 FC |
372 | } |
373 | ||
374 | static void sifive_gpio_class_init(ObjectClass *klass, void *data) | |
375 | { | |
376 | DeviceClass *dc = DEVICE_CLASS(klass); | |
377 | ||
4bb216f6 | 378 | device_class_set_props(dc, sifive_gpio_properties); |
30efbf33 | 379 | dc->vmsd = &vmstate_sifive_gpio; |
4bb216f6 | 380 | dc->realize = sifive_gpio_realize; |
30efbf33 | 381 | dc->reset = sifive_gpio_reset; |
2e30ccb4 | 382 | dc->desc = "SiFive GPIO"; |
30efbf33 FC |
383 | } |
384 | ||
385 | static const TypeInfo sifive_gpio_info = { | |
386 | .name = TYPE_SIFIVE_GPIO, | |
387 | .parent = TYPE_SYS_BUS_DEVICE, | |
388 | .instance_size = sizeof(SIFIVEGPIOState), | |
30efbf33 FC |
389 | .class_init = sifive_gpio_class_init |
390 | }; | |
391 | ||
392 | static void sifive_gpio_register_types(void) | |
393 | { | |
394 | type_register_static(&sifive_gpio_info); | |
395 | } | |
396 | ||
397 | type_init(sifive_gpio_register_types) |