qemu/hw/misc/mps2-fpgaio.c
<<
>>
Prefs
   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);
 356