xref: /openbmc/qemu/hw/misc/mps2-fpgaio.c (revision 650d103d3ea959212f826acb9d3fe80cf30e347b)
1 /*
2  * ARM MPS2 AN505 FPGAIO emulation
3  *
4  * Copyright (c) 2018 Linaro Limited
5  * Written by Peter Maydell
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 or
9  *  (at your option) any later version.
10  */
11 
12 /* This is a model of the "FPGA system control and I/O" block found
13  * in the AN505 FPGA image for the MPS2 devboard.
14  * It is documented in AN505:
15  * http://infocenter.arm.com/help/topic/com.arm.doc.dai0505b/index.html
16  */
17 
18 #include "qemu/osdep.h"
19 #include "qemu/log.h"
20 #include "qemu/module.h"
21 #include "qapi/error.h"
22 #include "trace.h"
23 #include "hw/sysbus.h"
24 #include "migration/vmstate.h"
25 #include "hw/registerfields.h"
26 #include "hw/misc/mps2-fpgaio.h"
27 #include "qemu/timer.h"
28 
29 REG32(LED0, 0)
30 REG32(BUTTON, 8)
31 REG32(CLK1HZ, 0x10)
32 REG32(CLK100HZ, 0x14)
33 REG32(COUNTER, 0x18)
34 REG32(PRESCALE, 0x1c)
35 REG32(PSCNTR, 0x20)
36 REG32(MISC, 0x4c)
37 
38 static uint32_t counter_from_tickoff(int64_t now, int64_t tick_offset, int frq)
39 {
40     return muldiv64(now - tick_offset, frq, NANOSECONDS_PER_SECOND);
41 }
42 
43 static int64_t tickoff_from_counter(int64_t now, uint32_t count, int frq)
44 {
45     return now - muldiv64(count, NANOSECONDS_PER_SECOND, frq);
46 }
47 
48 static void resync_counter(MPS2FPGAIO *s)
49 {
50     /*
51      * Update s->counter and s->pscntr to their true current values
52      * by calculating how many times PSCNTR has ticked since the
53      * last time we did a resync.
54      */
55     int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
56     int64_t elapsed = now - s->pscntr_sync_ticks;
57 
58     /*
59      * Round elapsed down to a whole number of PSCNTR ticks, so we don't
60      * lose time if we do multiple resyncs in a single tick.
61      */
62     uint64_t ticks = muldiv64(elapsed, s->prescale_clk, NANOSECONDS_PER_SECOND);
63 
64     /*
65      * Work out what PSCNTR and COUNTER have moved to. We assume that
66      * PSCNTR reloads from PRESCALE one tick-period after it hits zero,
67      * and that COUNTER increments at the same moment.
68      */
69     if (ticks == 0) {
70         /* We haven't ticked since the last time we were asked */
71         return;
72     } else if (ticks < s->pscntr) {
73         /* We haven't yet reached zero, just reduce the PSCNTR */
74         s->pscntr -= ticks;
75     } else {
76         if (s->prescale == 0) {
77             /*
78              * If the reload value is zero then the PSCNTR will stick
79              * at zero once it reaches it, and so we will increment
80              * COUNTER every tick after that.
81              */
82             s->counter += ticks - s->pscntr;
83             s->pscntr = 0;
84         } else {
85             /*
86              * This is the complicated bit. This ASCII art diagram gives an
87              * example with PRESCALE==5 PSCNTR==7:
88              *
89              * ticks  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14
90              * PSCNTR 7  6  5  4  3  2  1  0  5  4  3  2  1  0  5
91              * cinc                           1                 2
92              * y            0  1  2  3  4  5  6  7  8  9 10 11 12
93              * x            0  1  2  3  4  5  0  1  2  3  4  5  0
94              *
95              * where x = y % (s->prescale + 1)
96              * and so PSCNTR = s->prescale - x
97              * and COUNTER is incremented by y / (s->prescale + 1)
98              *
99              * The case where PSCNTR < PRESCALE works out the same,
100              * though we must be careful to calculate y as 64-bit unsigned
101              * for all parts of the expression.
102              * y < 0 is not possible because that implies ticks < s->pscntr.
103              */
104             uint64_t y = ticks - s->pscntr + s->prescale;
105             s->pscntr = s->prescale - (y % (s->prescale + 1));
106             s->counter += y / (s->prescale + 1);
107         }
108     }
109 
110     /*
111      * Only advance the sync time to the timestamp of the last PSCNTR tick,
112      * not all the way to 'now', so we don't lose time if we do multiple
113      * resyncs in a single tick.
114      */
115     s->pscntr_sync_ticks += muldiv64(ticks, NANOSECONDS_PER_SECOND,
116                                      s->prescale_clk);
117 }
118 
119 static uint64_t mps2_fpgaio_read(void *opaque, hwaddr offset, unsigned size)
120 {
121     MPS2FPGAIO *s = MPS2_FPGAIO(opaque);
122     uint64_t r;
123     int64_t now;
124 
125     switch (offset) {
126     case A_LED0:
127         r = s->led0;
128         break;
129     case A_BUTTON:
130         /* User-pressable board buttons. We don't model that, so just return
131          * zeroes.
132          */
133         r = 0;
134         break;
135     case A_PRESCALE:
136         r = s->prescale;
137         break;
138     case A_MISC:
139         r = s->misc;
140         break;
141     case A_CLK1HZ:
142         now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
143         r = counter_from_tickoff(now, s->clk1hz_tick_offset, 1);
144         break;
145     case A_CLK100HZ:
146         now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
147         r = counter_from_tickoff(now, s->clk100hz_tick_offset, 100);
148         break;
149     case A_COUNTER:
150         resync_counter(s);
151         r = s->counter;
152         break;
153     case A_PSCNTR:
154         resync_counter(s);
155         r = s->pscntr;
156         break;
157     default:
158         qemu_log_mask(LOG_GUEST_ERROR,
159                       "MPS2 FPGAIO read: bad offset %x\n", (int) offset);
160         r = 0;
161         break;
162     }
163 
164     trace_mps2_fpgaio_read(offset, r, size);
165     return r;
166 }
167 
168 static void mps2_fpgaio_write(void *opaque, hwaddr offset, uint64_t value,
169                               unsigned size)
170 {
171     MPS2FPGAIO *s = MPS2_FPGAIO(opaque);
172     int64_t now;
173 
174     trace_mps2_fpgaio_write(offset, value, size);
175 
176     switch (offset) {
177     case A_LED0:
178         /* LED bits [1:0] control board LEDs. We don't currently have
179          * a mechanism for displaying this graphically, so use a trace event.
180          */
181         trace_mps2_fpgaio_leds(value & 0x02 ? '*' : '.',
182                                value & 0x01 ? '*' : '.');
183         s->led0 = value & 0x3;
184         break;
185     case A_PRESCALE:
186         resync_counter(s);
187         s->prescale = value;
188         break;
189     case A_MISC:
190         /* These are control bits for some of the other devices on the
191          * board (SPI, CLCD, etc). We don't implement that yet, so just
192          * make the bits read as written.
193          */
194         qemu_log_mask(LOG_UNIMP,
195                       "MPS2 FPGAIO: MISC control bits unimplemented\n");
196         s->misc = value;
197         break;
198     case A_CLK1HZ:
199         now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
200         s->clk1hz_tick_offset = tickoff_from_counter(now, value, 1);
201         break;
202     case A_CLK100HZ:
203         now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
204         s->clk100hz_tick_offset = tickoff_from_counter(now, value, 100);
205         break;
206     case A_COUNTER:
207         resync_counter(s);
208         s->counter = value;
209         break;
210     case A_PSCNTR:
211         resync_counter(s);
212         s->pscntr = value;
213         break;
214     default:
215         qemu_log_mask(LOG_GUEST_ERROR,
216                       "MPS2 FPGAIO write: bad offset 0x%x\n", (int) offset);
217         break;
218     }
219 }
220 
221 static const MemoryRegionOps mps2_fpgaio_ops = {
222     .read = mps2_fpgaio_read,
223     .write = mps2_fpgaio_write,
224     .endianness = DEVICE_LITTLE_ENDIAN,
225 };
226 
227 static void mps2_fpgaio_reset(DeviceState *dev)
228 {
229     MPS2FPGAIO *s = MPS2_FPGAIO(dev);
230     int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
231 
232     trace_mps2_fpgaio_reset();
233     s->led0 = 0;
234     s->prescale = 0;
235     s->misc = 0;
236     s->clk1hz_tick_offset = tickoff_from_counter(now, 0, 1);
237     s->clk100hz_tick_offset = tickoff_from_counter(now, 0, 100);
238     s->counter = 0;
239     s->pscntr = 0;
240     s->pscntr_sync_ticks = now;
241 }
242 
243 static void mps2_fpgaio_init(Object *obj)
244 {
245     SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
246     MPS2FPGAIO *s = MPS2_FPGAIO(obj);
247 
248     memory_region_init_io(&s->iomem, obj, &mps2_fpgaio_ops, s,
249                           "mps2-fpgaio", 0x1000);
250     sysbus_init_mmio(sbd, &s->iomem);
251 }
252 
253 static bool mps2_fpgaio_counters_needed(void *opaque)
254 {
255     /* Currently vmstate.c insists all subsections have a 'needed' function */
256     return true;
257 }
258 
259 static const VMStateDescription mps2_fpgaio_counters_vmstate = {
260     .name = "mps2-fpgaio/counters",
261     .version_id = 2,
262     .minimum_version_id = 2,
263     .needed = mps2_fpgaio_counters_needed,
264     .fields = (VMStateField[]) {
265         VMSTATE_INT64(clk1hz_tick_offset, MPS2FPGAIO),
266         VMSTATE_INT64(clk100hz_tick_offset, MPS2FPGAIO),
267         VMSTATE_UINT32(counter, MPS2FPGAIO),
268         VMSTATE_UINT32(pscntr, MPS2FPGAIO),
269         VMSTATE_INT64(pscntr_sync_ticks, MPS2FPGAIO),
270         VMSTATE_END_OF_LIST()
271     }
272 };
273 
274 static const VMStateDescription mps2_fpgaio_vmstate = {
275     .name = "mps2-fpgaio",
276     .version_id = 1,
277     .minimum_version_id = 1,
278     .fields = (VMStateField[]) {
279         VMSTATE_UINT32(led0, MPS2FPGAIO),
280         VMSTATE_UINT32(prescale, MPS2FPGAIO),
281         VMSTATE_UINT32(misc, MPS2FPGAIO),
282         VMSTATE_END_OF_LIST()
283     },
284     .subsections = (const VMStateDescription*[]) {
285         &mps2_fpgaio_counters_vmstate,
286         NULL
287     }
288 };
289 
290 static Property mps2_fpgaio_properties[] = {
291     /* Frequency of the prescale counter */
292     DEFINE_PROP_UINT32("prescale-clk", MPS2FPGAIO, prescale_clk, 20000000),
293     DEFINE_PROP_END_OF_LIST(),
294 };
295 
296 static void mps2_fpgaio_class_init(ObjectClass *klass, void *data)
297 {
298     DeviceClass *dc = DEVICE_CLASS(klass);
299 
300     dc->vmsd = &mps2_fpgaio_vmstate;
301     dc->reset = mps2_fpgaio_reset;
302     dc->props = mps2_fpgaio_properties;
303 }
304 
305 static const TypeInfo mps2_fpgaio_info = {
306     .name = TYPE_MPS2_FPGAIO,
307     .parent = TYPE_SYS_BUS_DEVICE,
308     .instance_size = sizeof(MPS2FPGAIO),
309     .instance_init = mps2_fpgaio_init,
310     .class_init = mps2_fpgaio_class_init,
311 };
312 
313 static void mps2_fpgaio_register_types(void)
314 {
315     type_register_static(&mps2_fpgaio_info);
316 }
317 
318 type_init(mps2_fpgaio_register_types);
319