mps2-fpgaio.c (10842B)
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 * https://developer.arm.com/documentation/dai0505/latest/ 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 "hw/misc/led.h" 28#include "hw/qdev-properties.h" 29#include "qemu/timer.h" 30 31REG32(LED0, 0) 32REG32(DBGCTRL, 4) 33REG32(BUTTON, 8) 34REG32(CLK1HZ, 0x10) 35REG32(CLK100HZ, 0x14) 36REG32(COUNTER, 0x18) 37REG32(PRESCALE, 0x1c) 38REG32(PSCNTR, 0x20) 39REG32(SWITCH, 0x28) 40REG32(MISC, 0x4c) 41 42static uint32_t counter_from_tickoff(int64_t now, int64_t tick_offset, int frq) 43{ 44 return muldiv64(now - tick_offset, frq, NANOSECONDS_PER_SECOND); 45} 46 47static int64_t tickoff_from_counter(int64_t now, uint32_t count, int frq) 48{ 49 return now - muldiv64(count, NANOSECONDS_PER_SECOND, frq); 50} 51 52static void resync_counter(MPS2FPGAIO *s) 53{ 54 /* 55 * Update s->counter and s->pscntr to their true current values 56 * by calculating how many times PSCNTR has ticked since the 57 * last time we did a resync. 58 */ 59 int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 60 int64_t elapsed = now - s->pscntr_sync_ticks; 61 62 /* 63 * Round elapsed down to a whole number of PSCNTR ticks, so we don't 64 * lose time if we do multiple resyncs in a single tick. 65 */ 66 uint64_t ticks = muldiv64(elapsed, s->prescale_clk, NANOSECONDS_PER_SECOND); 67 68 /* 69 * Work out what PSCNTR and COUNTER have moved to. We assume that 70 * PSCNTR reloads from PRESCALE one tick-period after it hits zero, 71 * and that COUNTER increments at the same moment. 72 */ 73 if (ticks == 0) { 74 /* We haven't ticked since the last time we were asked */ 75 return; 76 } else if (ticks < s->pscntr) { 77 /* We haven't yet reached zero, just reduce the PSCNTR */ 78 s->pscntr -= ticks; 79 } else { 80 if (s->prescale == 0) { 81 /* 82 * If the reload value is zero then the PSCNTR will stick 83 * at zero once it reaches it, and so we will increment 84 * COUNTER every tick after that. 85 */ 86 s->counter += ticks - s->pscntr; 87 s->pscntr = 0; 88 } else { 89 /* 90 * This is the complicated bit. This ASCII art diagram gives an 91 * example with PRESCALE==5 PSCNTR==7: 92 * 93 * ticks 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 94 * PSCNTR 7 6 5 4 3 2 1 0 5 4 3 2 1 0 5 95 * cinc 1 2 96 * y 0 1 2 3 4 5 6 7 8 9 10 11 12 97 * x 0 1 2 3 4 5 0 1 2 3 4 5 0 98 * 99 * where x = y % (s->prescale + 1) 100 * and so PSCNTR = s->prescale - x 101 * and COUNTER is incremented by y / (s->prescale + 1) 102 * 103 * The case where PSCNTR < PRESCALE works out the same, 104 * though we must be careful to calculate y as 64-bit unsigned 105 * for all parts of the expression. 106 * y < 0 is not possible because that implies ticks < s->pscntr. 107 */ 108 uint64_t y = ticks - s->pscntr + s->prescale; 109 s->pscntr = s->prescale - (y % (s->prescale + 1)); 110 s->counter += y / (s->prescale + 1); 111 } 112 } 113 114 /* 115 * Only advance the sync time to the timestamp of the last PSCNTR tick, 116 * not all the way to 'now', so we don't lose time if we do multiple 117 * resyncs in a single tick. 118 */ 119 s->pscntr_sync_ticks += muldiv64(ticks, NANOSECONDS_PER_SECOND, 120 s->prescale_clk); 121} 122 123static uint64_t mps2_fpgaio_read(void *opaque, hwaddr offset, unsigned size) 124{ 125 MPS2FPGAIO *s = MPS2_FPGAIO(opaque); 126 uint64_t r; 127 int64_t now; 128 129 switch (offset) { 130 case A_LED0: 131 r = s->led0; 132 break; 133 case A_DBGCTRL: 134 if (!s->has_dbgctrl) { 135 goto bad_offset; 136 } 137 r = s->dbgctrl; 138 break; 139 case A_BUTTON: 140 /* User-pressable board buttons. We don't model that, so just return 141 * zeroes. 142 */ 143 r = 0; 144 break; 145 case A_PRESCALE: 146 r = s->prescale; 147 break; 148 case A_MISC: 149 r = s->misc; 150 break; 151 case A_CLK1HZ: 152 now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 153 r = counter_from_tickoff(now, s->clk1hz_tick_offset, 1); 154 break; 155 case A_CLK100HZ: 156 now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 157 r = counter_from_tickoff(now, s->clk100hz_tick_offset, 100); 158 break; 159 case A_COUNTER: 160 resync_counter(s); 161 r = s->counter; 162 break; 163 case A_PSCNTR: 164 resync_counter(s); 165 r = s->pscntr; 166 break; 167 case A_SWITCH: 168 if (!s->has_switches) { 169 goto bad_offset; 170 } 171 /* User-togglable board switches. We don't model that, so report 0. */ 172 r = 0; 173 break; 174 default: 175 bad_offset: 176 qemu_log_mask(LOG_GUEST_ERROR, 177 "MPS2 FPGAIO read: bad offset %x\n", (int) offset); 178 r = 0; 179 break; 180 } 181 182 trace_mps2_fpgaio_read(offset, r, size); 183 return r; 184} 185 186static void mps2_fpgaio_write(void *opaque, hwaddr offset, uint64_t value, 187 unsigned size) 188{ 189 MPS2FPGAIO *s = MPS2_FPGAIO(opaque); 190 int64_t now; 191 192 trace_mps2_fpgaio_write(offset, value, size); 193 194 switch (offset) { 195 case A_LED0: 196 if (s->num_leds != 0) { 197 uint32_t i; 198 199 s->led0 = value & MAKE_64BIT_MASK(0, s->num_leds); 200 for (i = 0; i < s->num_leds; i++) { 201 led_set_state(s->led[i], value & (1 << i)); 202 } 203 } 204 break; 205 case A_DBGCTRL: 206 if (!s->has_dbgctrl) { 207 goto bad_offset; 208 } 209 qemu_log_mask(LOG_UNIMP, 210 "MPS2 FPGAIO: DBGCTRL unimplemented\n"); 211 s->dbgctrl = value; 212 break; 213 case A_PRESCALE: 214 resync_counter(s); 215 s->prescale = value; 216 break; 217 case A_MISC: 218 /* These are control bits for some of the other devices on the 219 * board (SPI, CLCD, etc). We don't implement that yet, so just 220 * make the bits read as written. 221 */ 222 qemu_log_mask(LOG_UNIMP, 223 "MPS2 FPGAIO: MISC control bits unimplemented\n"); 224 s->misc = value; 225 break; 226 case A_CLK1HZ: 227 now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 228 s->clk1hz_tick_offset = tickoff_from_counter(now, value, 1); 229 break; 230 case A_CLK100HZ: 231 now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 232 s->clk100hz_tick_offset = tickoff_from_counter(now, value, 100); 233 break; 234 case A_COUNTER: 235 resync_counter(s); 236 s->counter = value; 237 break; 238 case A_PSCNTR: 239 resync_counter(s); 240 s->pscntr = value; 241 break; 242 default: 243 bad_offset: 244 qemu_log_mask(LOG_GUEST_ERROR, 245 "MPS2 FPGAIO write: bad offset 0x%x\n", (int) offset); 246 break; 247 } 248} 249 250static const MemoryRegionOps mps2_fpgaio_ops = { 251 .read = mps2_fpgaio_read, 252 .write = mps2_fpgaio_write, 253 .endianness = DEVICE_LITTLE_ENDIAN, 254}; 255 256static void mps2_fpgaio_reset(DeviceState *dev) 257{ 258 MPS2FPGAIO *s = MPS2_FPGAIO(dev); 259 int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 260 261 trace_mps2_fpgaio_reset(); 262 s->led0 = 0; 263 s->prescale = 0; 264 s->misc = 0; 265 s->clk1hz_tick_offset = tickoff_from_counter(now, 0, 1); 266 s->clk100hz_tick_offset = tickoff_from_counter(now, 0, 100); 267 s->counter = 0; 268 s->pscntr = 0; 269 s->pscntr_sync_ticks = now; 270 271 for (size_t i = 0; i < s->num_leds; i++) { 272 device_cold_reset(DEVICE(s->led[i])); 273 } 274} 275 276static void mps2_fpgaio_init(Object *obj) 277{ 278 SysBusDevice *sbd = SYS_BUS_DEVICE(obj); 279 MPS2FPGAIO *s = MPS2_FPGAIO(obj); 280 281 memory_region_init_io(&s->iomem, obj, &mps2_fpgaio_ops, s, 282 "mps2-fpgaio", 0x1000); 283 sysbus_init_mmio(sbd, &s->iomem); 284} 285 286static void mps2_fpgaio_realize(DeviceState *dev, Error **errp) 287{ 288 MPS2FPGAIO *s = MPS2_FPGAIO(dev); 289 uint32_t i; 290 291 if (s->num_leds > MPS2FPGAIO_MAX_LEDS) { 292 error_setg(errp, "num-leds cannot be greater than %d", 293 MPS2FPGAIO_MAX_LEDS); 294 return; 295 } 296 297 for (i = 0; i < s->num_leds; i++) { 298 g_autofree char *ledname = g_strdup_printf("USERLED%d", i); 299 s->led[i] = led_create_simple(OBJECT(dev), GPIO_POLARITY_ACTIVE_HIGH, 300 LED_COLOR_GREEN, ledname); 301 } 302} 303 304static const VMStateDescription mps2_fpgaio_vmstate = { 305 .name = "mps2-fpgaio", 306 .version_id = 3, 307 .minimum_version_id = 3, 308 .fields = (VMStateField[]) { 309 VMSTATE_UINT32(led0, MPS2FPGAIO), 310 VMSTATE_UINT32(prescale, MPS2FPGAIO), 311 VMSTATE_UINT32(misc, MPS2FPGAIO), 312 VMSTATE_UINT32(dbgctrl, MPS2FPGAIO), 313 VMSTATE_INT64(clk1hz_tick_offset, MPS2FPGAIO), 314 VMSTATE_INT64(clk100hz_tick_offset, MPS2FPGAIO), 315 VMSTATE_UINT32(counter, MPS2FPGAIO), 316 VMSTATE_UINT32(pscntr, MPS2FPGAIO), 317 VMSTATE_INT64(pscntr_sync_ticks, MPS2FPGAIO), 318 VMSTATE_END_OF_LIST() 319 }, 320}; 321 322static Property mps2_fpgaio_properties[] = { 323 /* Frequency of the prescale counter */ 324 DEFINE_PROP_UINT32("prescale-clk", MPS2FPGAIO, prescale_clk, 20000000), 325 /* Number of LEDs controlled by LED0 register */ 326 DEFINE_PROP_UINT32("num-leds", MPS2FPGAIO, num_leds, 2), 327 DEFINE_PROP_BOOL("has-switches", MPS2FPGAIO, has_switches, false), 328 DEFINE_PROP_BOOL("has-dbgctrl", MPS2FPGAIO, has_dbgctrl, false), 329 DEFINE_PROP_END_OF_LIST(), 330}; 331 332static void mps2_fpgaio_class_init(ObjectClass *klass, void *data) 333{ 334 DeviceClass *dc = DEVICE_CLASS(klass); 335 336 dc->vmsd = &mps2_fpgaio_vmstate; 337 dc->realize = mps2_fpgaio_realize; 338 dc->reset = mps2_fpgaio_reset; 339 device_class_set_props(dc, mps2_fpgaio_properties); 340} 341 342static const TypeInfo mps2_fpgaio_info = { 343 .name = TYPE_MPS2_FPGAIO, 344 .parent = TYPE_SYS_BUS_DEVICE, 345 .instance_size = sizeof(MPS2FPGAIO), 346 .instance_init = mps2_fpgaio_init, 347 .class_init = mps2_fpgaio_class_init, 348}; 349 350static void mps2_fpgaio_register_types(void) 351{ 352 type_register_static(&mps2_fpgaio_info); 353} 354 355type_init(mps2_fpgaio_register_types);