qemu/hw/misc/mps2-scc.c
Peter Maydell 2a5ee4e18d hw/misc/mps2-scc: Make changes needed for AN536 FPGA image
The MPS2 SCC device is broadly the same for all FPGA images, but has
minor differences in the behaviour of the CFG registers depending on
the image. In many cases we don't really care about the functionality
controlled by these registers and a reads-as-written or similar
behaviour is sufficient for the moment.

For the AN536 the required behaviour is:

 * A_CFG0 has CPU reset and halt bits
    - implement as reads-as-written for the moment
 * A_CFG1 has flash or ATCM address 0 remap handling
    - QEMU doesn't model this; implement as reads-as-written
 * A_CFG2 has QSPI select (like AN524)
    - implemented (no behaviour, as with AN524)
 * A_CFG3 is MCC_MSB_ADDR "additional MCC addressing bits"
    - QEMU doesn't care about these, so use the existing
      RAZ behaviour for convenience
 * A_CFG4 is board rev (like all other images)
    - no change needed
 * A_CFG5 is ACLK frq in hz (like AN524)
    - implemented as reads-as-written, as for other boards
 * A_CFG6 is core 0 vector table base address
    - implemented as reads-as-written for the moment
 * A_CFG7 is core 1 vector table base address
    - implemented as reads-as-written for the moment

Make the changes necessary for this; leave TODO comments where
appropriate to indicate where we might want to come back and
implement things like CPU reset.

The other aspects of the device specific to this FPGA image (like the
values of the board ID and similar registers) will be set via the
device's qdev properties.

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Message-id: 20240206132931.38376-8-peter.maydell@linaro.org
2024-02-15 14:32:38 +00:00

503 lines
14 KiB
C

/*
* ARM MPS2 SCC emulation
*
* Copyright (c) 2017 Linaro Limited
* Written by Peter Maydell
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or
* (at your option) any later version.
*/
/* This is a model of the SCC (Serial Communication Controller)
* found in the FPGA images of MPS2 development boards.
*
* Documentation of it can be found in the MPS2 TRM:
* https://developer.arm.com/documentation/100112/latest/
* and also in the Application Notes documenting individual FPGA images.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qemu/bitops.h"
#include "trace.h"
#include "hw/sysbus.h"
#include "hw/irq.h"
#include "migration/vmstate.h"
#include "hw/registerfields.h"
#include "hw/misc/mps2-scc.h"
#include "hw/misc/led.h"
#include "hw/qdev-properties.h"
REG32(CFG0, 0)
REG32(CFG1, 4)
REG32(CFG2, 8)
REG32(CFG3, 0xc)
REG32(CFG4, 0x10)
REG32(CFG5, 0x14)
REG32(CFG6, 0x18)
REG32(CFG7, 0x1c)
REG32(CFGDATA_RTN, 0xa0)
REG32(CFGDATA_OUT, 0xa4)
REG32(CFGCTRL, 0xa8)
FIELD(CFGCTRL, DEVICE, 0, 12)
FIELD(CFGCTRL, RES1, 12, 8)
FIELD(CFGCTRL, FUNCTION, 20, 6)
FIELD(CFGCTRL, RES2, 26, 4)
FIELD(CFGCTRL, WRITE, 30, 1)
FIELD(CFGCTRL, START, 31, 1)
REG32(CFGSTAT, 0xac)
FIELD(CFGSTAT, DONE, 0, 1)
FIELD(CFGSTAT, ERROR, 1, 1)
REG32(DLL, 0x100)
REG32(AID, 0xFF8)
REG32(ID, 0xFFC)
static int scc_partno(MPS2SCC *s)
{
/* Return the partno field of the SCC_ID (0x524, 0x511, etc) */
return extract32(s->id, 4, 8);
}
/* Is CFG_REG2 present? */
static bool have_cfg2(MPS2SCC *s)
{
return scc_partno(s) == 0x524 || scc_partno(s) == 0x547 ||
scc_partno(s) == 0x536;
}
/* Is CFG_REG3 present? */
static bool have_cfg3(MPS2SCC *s)
{
return scc_partno(s) != 0x524 && scc_partno(s) != 0x547 &&
scc_partno(s) != 0x536;
}
/* Is CFG_REG5 present? */
static bool have_cfg5(MPS2SCC *s)
{
return scc_partno(s) == 0x524 || scc_partno(s) == 0x547 ||
scc_partno(s) == 0x536;
}
/* Is CFG_REG6 present? */
static bool have_cfg6(MPS2SCC *s)
{
return scc_partno(s) == 0x524 || scc_partno(s) == 0x536;
}
/* Is CFG_REG7 present? */
static bool have_cfg7(MPS2SCC *s)
{
return scc_partno(s) == 0x536;
}
/* Does CFG_REG0 drive the 'remap' GPIO output? */
static bool cfg0_is_remap(MPS2SCC *s)
{
return scc_partno(s) != 0x536;
}
/* Is CFG_REG1 driving a set of LEDs? */
static bool cfg1_is_leds(MPS2SCC *s)
{
return scc_partno(s) != 0x536;
}
/* Handle a write via the SYS_CFG channel to the specified function/device.
* Return false on error (reported to guest via SYS_CFGCTRL ERROR bit).
*/
static bool scc_cfg_write(MPS2SCC *s, unsigned function,
unsigned device, uint32_t value)
{
trace_mps2_scc_cfg_write(function, device, value);
if (function != 1 || device >= s->num_oscclk) {
qemu_log_mask(LOG_GUEST_ERROR,
"MPS2 SCC config write: bad function %d device %d\n",
function, device);
return false;
}
s->oscclk[device] = value;
return true;
}
/* Handle a read via the SYS_CFG channel to the specified function/device.
* Return false on error (reported to guest via SYS_CFGCTRL ERROR bit),
* or set *value on success.
*/
static bool scc_cfg_read(MPS2SCC *s, unsigned function,
unsigned device, uint32_t *value)
{
if (function != 1 || device >= s->num_oscclk) {
qemu_log_mask(LOG_GUEST_ERROR,
"MPS2 SCC config read: bad function %d device %d\n",
function, device);
return false;
}
*value = s->oscclk[device];
trace_mps2_scc_cfg_read(function, device, *value);
return true;
}
static uint64_t mps2_scc_read(void *opaque, hwaddr offset, unsigned size)
{
MPS2SCC *s = MPS2_SCC(opaque);
uint64_t r;
switch (offset) {
case A_CFG0:
r = s->cfg0;
break;
case A_CFG1:
r = s->cfg1;
break;
case A_CFG2:
if (!have_cfg2(s)) {
goto bad_offset;
}
r = s->cfg2;
break;
case A_CFG3:
if (!have_cfg3(s)) {
goto bad_offset;
}
/*
* These are user-settable DIP switches on the board. We don't
* model that, so just return zeroes.
*
* TODO: for AN536 this is MCC_MSB_ADDR "additional MCC addressing
* bits". These change which part of the DDR4 the motherboard
* configuration controller can see in its memory map (see the
* appnote section 2.4). QEMU doesn't model the MCC at all, so these
* bits are not interesting to us; read-as-zero is as good as anything
* else.
*/
r = 0;
break;
case A_CFG4:
r = s->cfg4;
break;
case A_CFG5:
if (!have_cfg5(s)) {
goto bad_offset;
}
r = s->cfg5;
break;
case A_CFG6:
if (!have_cfg6(s)) {
goto bad_offset;
}
r = s->cfg6;
break;
case A_CFG7:
if (!have_cfg7(s)) {
goto bad_offset;
}
r = s->cfg7;
break;
case A_CFGDATA_RTN:
r = s->cfgdata_rtn;
break;
case A_CFGDATA_OUT:
r = s->cfgdata_out;
break;
case A_CFGCTRL:
r = s->cfgctrl;
break;
case A_CFGSTAT:
r = s->cfgstat;
break;
case A_DLL:
r = s->dll;
break;
case A_AID:
r = s->aid;
break;
case A_ID:
r = s->id;
break;
default:
bad_offset:
qemu_log_mask(LOG_GUEST_ERROR,
"MPS2 SCC read: bad offset %x\n", (int) offset);
r = 0;
break;
}
trace_mps2_scc_read(offset, r, size);
return r;
}
static void mps2_scc_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
MPS2SCC *s = MPS2_SCC(opaque);
trace_mps2_scc_write(offset, value, size);
switch (offset) {
case A_CFG0:
/*
* On some boards bit 0 controls board-specific remapping;
* we always reflect bit 0 in the 'remap' GPIO output line,
* and let the board wire it up or not as it chooses.
* TODO on some boards bit 1 is CPU_WAIT.
*
* TODO: on the AN536 this register controls reset and halt
* for both CPUs. For the moment we don't implement this, so the
* register just reads as written.
*/
s->cfg0 = value;
if (cfg0_is_remap(s)) {
qemu_set_irq(s->remap, s->cfg0 & 1);
}
break;
case A_CFG1:
s->cfg1 = value;
/*
* On most boards this register drives LEDs.
*
* TODO: for AN536 this controls whether flash and ATCM are
* enabled or disabled on reset. QEMU doesn't model this, and
* always wires up RAM in the ATCM area and ROM in the flash area.
*/
if (cfg1_is_leds(s)) {
for (size_t i = 0; i < ARRAY_SIZE(s->led); i++) {
led_set_state(s->led[i], extract32(value, i, 1));
}
}
break;
case A_CFG2:
if (!have_cfg2(s)) {
goto bad_offset;
}
/* AN524, AN536: QSPI Select signal */
s->cfg2 = value;
break;
case A_CFG5:
if (!have_cfg5(s)) {
goto bad_offset;
}
/* AN524, AN536: ACLK frequency in Hz */
s->cfg5 = value;
break;
case A_CFG6:
if (!have_cfg6(s)) {
goto bad_offset;
}
/* AN524: Clock divider for BRAM */
/* AN536: Core 0 vector table base address */
s->cfg6 = value;
break;
case A_CFG7:
if (!have_cfg7(s)) {
goto bad_offset;
}
/* AN536: Core 1 vector table base address */
s->cfg6 = value;
break;
case A_CFGDATA_OUT:
s->cfgdata_out = value;
break;
case A_CFGCTRL:
/* Writing to CFGCTRL clears SYS_CFGSTAT */
s->cfgstat = 0;
s->cfgctrl = value & ~(R_CFGCTRL_RES1_MASK |
R_CFGCTRL_RES2_MASK |
R_CFGCTRL_START_MASK);
if (value & R_CFGCTRL_START_MASK) {
/* Start bit set -- do a read or write (instantaneously) */
int device = extract32(s->cfgctrl, R_CFGCTRL_DEVICE_SHIFT,
R_CFGCTRL_DEVICE_LENGTH);
int function = extract32(s->cfgctrl, R_CFGCTRL_FUNCTION_SHIFT,
R_CFGCTRL_FUNCTION_LENGTH);
s->cfgstat = R_CFGSTAT_DONE_MASK;
if (s->cfgctrl & R_CFGCTRL_WRITE_MASK) {
if (!scc_cfg_write(s, function, device, s->cfgdata_out)) {
s->cfgstat |= R_CFGSTAT_ERROR_MASK;
}
} else {
uint32_t result;
if (!scc_cfg_read(s, function, device, &result)) {
s->cfgstat |= R_CFGSTAT_ERROR_MASK;
} else {
s->cfgdata_rtn = result;
}
}
}
break;
case A_DLL:
/* DLL stands for Digital Locked Loop.
* Bits [31:24] (DLL_LOCK_MASK) are writable, and indicate a
* mask of which of the DLL_LOCKED bits [16:23] should be ORed
* together to determine the ALL_UNMASKED_DLLS_LOCKED bit [0].
* For QEMU, our DLLs are always locked, so we can leave bit 0
* as 1 always and don't need to recalculate it.
*/
s->dll = deposit32(s->dll, 24, 8, extract32(value, 24, 8));
break;
default:
bad_offset:
qemu_log_mask(LOG_GUEST_ERROR,
"MPS2 SCC write: bad offset 0x%x\n", (int) offset);
break;
}
}
static const MemoryRegionOps mps2_scc_ops = {
.read = mps2_scc_read,
.write = mps2_scc_write,
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void mps2_scc_reset(DeviceState *dev)
{
MPS2SCC *s = MPS2_SCC(dev);
int i;
trace_mps2_scc_reset();
s->cfg0 = s->cfg0_reset;
s->cfg1 = 0;
s->cfg2 = 0;
s->cfg5 = 0;
s->cfg6 = 0;
s->cfgdata_rtn = 0;
s->cfgdata_out = 0;
s->cfgctrl = 0x100000;
s->cfgstat = 0;
s->dll = 0xffff0001;
for (i = 0; i < s->num_oscclk; i++) {
s->oscclk[i] = s->oscclk_reset[i];
}
for (i = 0; i < ARRAY_SIZE(s->led); i++) {
device_cold_reset(DEVICE(s->led[i]));
}
}
static void mps2_scc_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
MPS2SCC *s = MPS2_SCC(obj);
memory_region_init_io(&s->iomem, obj, &mps2_scc_ops, s, "mps2-scc", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
qdev_init_gpio_out_named(DEVICE(obj), &s->remap, "remap", 1);
}
static void mps2_scc_realize(DeviceState *dev, Error **errp)
{
MPS2SCC *s = MPS2_SCC(dev);
for (size_t i = 0; i < ARRAY_SIZE(s->led); i++) {
char *name = g_strdup_printf("SCC LED%zu", i);
s->led[i] = led_create_simple(OBJECT(dev), GPIO_POLARITY_ACTIVE_HIGH,
LED_COLOR_GREEN, name);
g_free(name);
}
s->oscclk = g_new0(uint32_t, s->num_oscclk);
}
static void mps2_scc_finalize(Object *obj)
{
MPS2SCC *s = MPS2_SCC(obj);
g_free(s->oscclk_reset);
}
static bool cfg7_needed(void *opaque)
{
MPS2SCC *s = opaque;
return have_cfg7(s);
}
static const VMStateDescription vmstate_cfg7 = {
.name = "mps2-scc/cfg7",
.version_id = 1,
.minimum_version_id = 1,
.needed = cfg7_needed,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(cfg7, MPS2SCC),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription mps2_scc_vmstate = {
.name = "mps2-scc",
.version_id = 3,
.minimum_version_id = 3,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(cfg0, MPS2SCC),
VMSTATE_UINT32(cfg1, MPS2SCC),
VMSTATE_UINT32(cfg2, MPS2SCC),
/* cfg3, cfg4 are read-only so need not be migrated */
VMSTATE_UINT32(cfg5, MPS2SCC),
VMSTATE_UINT32(cfg6, MPS2SCC),
VMSTATE_UINT32(cfgdata_rtn, MPS2SCC),
VMSTATE_UINT32(cfgdata_out, MPS2SCC),
VMSTATE_UINT32(cfgctrl, MPS2SCC),
VMSTATE_UINT32(cfgstat, MPS2SCC),
VMSTATE_UINT32(dll, MPS2SCC),
VMSTATE_VARRAY_UINT32(oscclk, MPS2SCC, num_oscclk,
0, vmstate_info_uint32, uint32_t),
VMSTATE_END_OF_LIST()
},
.subsections = (const VMStateDescription * const []) {
&vmstate_cfg7,
NULL
}
};
static Property mps2_scc_properties[] = {
/* Values for various read-only ID registers (which are specific
* to the board model or FPGA image)
*/
DEFINE_PROP_UINT32("scc-cfg4", MPS2SCC, cfg4, 0),
DEFINE_PROP_UINT32("scc-aid", MPS2SCC, aid, 0),
DEFINE_PROP_UINT32("scc-id", MPS2SCC, id, 0),
/* Reset value for CFG0 register */
DEFINE_PROP_UINT32("scc-cfg0", MPS2SCC, cfg0_reset, 0),
/*
* These are the initial settings for the source clocks on the board.
* In hardware they can be configured via a config file read by the
* motherboard configuration controller to suit the FPGA image.
*/
DEFINE_PROP_ARRAY("oscclk", MPS2SCC, num_oscclk, oscclk_reset,
qdev_prop_uint32, uint32_t),
DEFINE_PROP_END_OF_LIST(),
};
static void mps2_scc_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = mps2_scc_realize;
dc->vmsd = &mps2_scc_vmstate;
dc->reset = mps2_scc_reset;
device_class_set_props(dc, mps2_scc_properties);
}
static const TypeInfo mps2_scc_info = {
.name = TYPE_MPS2_SCC,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(MPS2SCC),
.instance_init = mps2_scc_init,
.instance_finalize = mps2_scc_finalize,
.class_init = mps2_scc_class_init,
};
static void mps2_scc_register_types(void)
{
type_register_static(&mps2_scc_info);
}
type_init(mps2_scc_register_types);