/*
 * AMD/Xilinx Versal family SoC model.
 *
 * Copyright (c) 2018 Xilinx Inc.
 * Copyright (c) 2025 Advanced Micro Devices, Inc.
 * Written by Edgar E. Iglesias
 *
 * 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.
 */

#include "qemu/osdep.h"
#include "qemu/units.h"
#include "qapi/error.h"
#include "qobject/qlist.h"
#include "qemu/module.h"
#include "hw/core/sysbus.h"
#include "net/net.h"
#include "system/system.h"
#include "hw/misc/unimp.h"
#include "hw/arm/xlnx-versal.h"
#include "qemu/log.h"
#include "target/arm/cpu-qom.h"
#include "target/arm/gtimer.h"
#include "system/device_tree.h"
#include "hw/arm/fdt.h"
#include "hw/char/pl011.h"
#include "hw/net/xlnx-versal-canfd.h"
#include "hw/sd/sdhci.h"
#include "hw/net/cadence_gem.h"
#include "hw/dma/xlnx-zdma.h"
#include "hw/misc/xlnx-versal-xramc.h"
#include "hw/usb/xlnx-usb-subsystem.h"
#include "hw/nvram/xlnx-versal-efuse.h"
#include "hw/ssi/xlnx-versal-ospi.h"
#include "hw/misc/xlnx-versal-pmc-iou-slcr.h"
#include "hw/nvram/xlnx-bbram.h"
#include "hw/misc/xlnx-versal-trng.h"
#include "hw/rtc/xlnx-zynqmp-rtc.h"
#include "hw/misc/xlnx-versal-cfu.h"
#include "hw/misc/xlnx-versal-cframe-reg.h"
#include "hw/core/or-irq.h"
#include "hw/misc/xlnx-versal-crl.h"
#include "hw/intc/arm_gicv3_common.h"
#include "hw/intc/arm_gicv3_its_common.h"
#include "hw/intc/arm_gic.h"
#include "hw/core/split-irq.h"
#include "target/arm/cpu.h"
#include "hw/cpu/cluster.h"
#include "hw/arm/bsa.h"

/*
 * IRQ descriptor to catch the following cases:
 *   - An IRQ can either connect to the GICs, to the PPU1 intc, or the the EAM
 *   - Multiple devices can connect to the same IRQ. They are OR'ed together.
 */
FIELD(VERSAL_IRQ, IRQ, 0, 16)
FIELD(VERSAL_IRQ, TARGET, 16, 2)
FIELD(VERSAL_IRQ, ORED, 18, 1)
FIELD(VERSAL_IRQ, OR_IDX, 19, 4) /* input index on the IRQ OR gate */

typedef enum VersalIrqTarget {
    IRQ_TARGET_GIC,
    IRQ_TARGET_PPU1,
    IRQ_TARGET_EAM,
} VersalIrqTarget;

#define PPU1_IRQ(irq) ((IRQ_TARGET_PPU1 << R_VERSAL_IRQ_TARGET_SHIFT) | (irq))
#define EAM_IRQ(irq) ((IRQ_TARGET_EAM << R_VERSAL_IRQ_TARGET_SHIFT) | (irq))
#define OR_IRQ(irq, or_idx) \
    (R_VERSAL_IRQ_ORED_MASK | ((or_idx) << R_VERSAL_IRQ_OR_IDX_SHIFT) | (irq))
#define PPU1_OR_IRQ(irq, or_idx) \
    ((IRQ_TARGET_PPU1 << R_VERSAL_IRQ_TARGET_SHIFT) | OR_IRQ(irq, or_idx))

typedef struct VersalSimplePeriphMap {
    uint64_t addr;
    int irq;
} VersalSimplePeriphMap;

typedef struct VersalMemMap {
    uint64_t addr;
    uint64_t size;
} VersalMemMap;

typedef struct VersalGicMap {
    int version;
    uint64_t dist;
    uint64_t redist;
    uint64_t cpu_iface;
    uint64_t its;
    size_t num_irq;
    bool has_its;
} VersalGicMap;

enum StartPoweredOffMode {
    SPO_SECONDARIES,
    SPO_ALL,
};

typedef struct VersalCpuClusterMap {
    VersalGicMap gic;
    /*
     * true: one GIC per cluster.
     * false: one GIC for all CPUs
     */
    bool per_cluster_gic;

    const char *name;
    const char *cpu_model;
    size_t num_core;
    size_t num_cluster;
    uint32_t qemu_cluster_id;
    bool dtb_expose;

    struct {
        uint64_t base;
        uint64_t core_shift;
        uint64_t cluster_shift;
    } mp_affinity;

    enum StartPoweredOffMode start_powered_off;
} VersalCpuClusterMap;

typedef struct VersalMap {
    VersalMemMap ocm;

    struct VersalDDRMap {
        VersalMemMap chan[4];
        size_t num_chan;
    } ddr;

    VersalCpuClusterMap apu;
    VersalCpuClusterMap rpu;

    VersalSimplePeriphMap uart[2];
    size_t num_uart;

    VersalSimplePeriphMap canfd[4];
    size_t num_canfd;

    VersalSimplePeriphMap sdhci[2];
    size_t num_sdhci;

    struct VersalGemMap {
        VersalSimplePeriphMap map;
        size_t num_prio_queue;
        const char *phy_mode;
        const uint32_t speed;
    } gem[3];
    size_t num_gem;

    struct VersalZDMAMap {
        const char *name;
        VersalSimplePeriphMap map;
        size_t num_chan;
        uint64_t chan_stride;
        int irq_stride;
    } zdma[2];
    size_t num_zdma;

    struct VersalXramMap {
        uint64_t mem;
        uint64_t mem_stride;
        uint64_t ctrl;
        uint64_t ctrl_stride;
        int irq;
        size_t num;
    } xram;

    struct VersalUsbMap {
        uint64_t xhci;
        uint64_t ctrl;
        int irq;
    } usb[2];
    size_t num_usb;

    struct VersalEfuseMap {
        uint64_t ctrl;
        uint64_t cache;
        int irq;
    } efuse;

    struct VersalOspiMap {
        uint64_t ctrl;
        uint64_t dac;
        uint64_t dac_sz;
        uint64_t dma_src;
        uint64_t dma_dst;
        int irq;
    } ospi;

    VersalSimplePeriphMap pmc_iou_slcr;
    VersalSimplePeriphMap bbram;
    VersalSimplePeriphMap trng;

    struct VersalRtcMap {
        VersalSimplePeriphMap map;
        int alarm_irq;
        int second_irq;
    } rtc;

    struct VersalCfuMap {
        uint64_t cframe_base;
        uint64_t cframe_stride;
        uint64_t cfu_fdro;
        uint64_t cframe_bcast_reg;
        uint64_t cframe_bcast_fdri;
        uint64_t cfu_apb;
        uint64_t cfu_stream;
        uint64_t cfu_stream_2;
        uint64_t cfu_sfr;
        int cfu_apb_irq;
        int cframe_irq;
        size_t num_cframe;
        struct VersalCfuCframeCfg {
            uint32_t blktype_frames[7];
        } cframe_cfg[15];
    } cfu;

    VersalSimplePeriphMap crl;

    /* reserved MMIO/IRQ space that can safely be used for virtio devices */
    struct VersalReserved {
        uint64_t mmio_start;
        int irq_start;
        int irq_num;
    } reserved;
} VersalMap;

static const VersalMap VERSAL_MAP = {
    .ocm = {
        .addr = 0xfffc0000,
        .size = 0x40000,
    },

    .ddr = {
        .chan[0] = { .addr = 0x0, .size = 2 * GiB },
        .chan[1] = { .addr = 0x800000000ull, .size = 32 * GiB },
        .chan[2] = { .addr = 0xc00000000ull, .size = 256 * GiB },
        .chan[3] = { .addr = 0x10000000000ull, .size = 734 * GiB },
        .num_chan = 4,
    },

    .apu = {
        .name = "apu",
        .cpu_model = ARM_CPU_TYPE_NAME("cortex-a72"),
        .num_cluster = 1,
        .num_core = 2,
        .qemu_cluster_id = 0,
        .mp_affinity = {
            .core_shift = ARM_AFF0_SHIFT,
            .cluster_shift = ARM_AFF1_SHIFT,
        },
        .start_powered_off = SPO_SECONDARIES,
        .dtb_expose = true,
        .gic = {
            .version = 3,
            .dist = 0xf9000000,
            .redist = 0xf9080000,
            .num_irq = 192,
            .has_its = true,
            .its = 0xf9020000,
        },
    },

    .rpu = {
        .name = "rpu",
        .cpu_model = ARM_CPU_TYPE_NAME("cortex-r5f"),
        .num_cluster = 1,
        .num_core = 2,
        .qemu_cluster_id = 1,
        .mp_affinity = {
            .base = 0x100,
            .core_shift = ARM_AFF0_SHIFT,
            .cluster_shift = ARM_AFF1_SHIFT,
        },
        .start_powered_off = SPO_ALL,
        .dtb_expose = false,
        .gic = {
            .version = 2,
            .dist = 0xf9000000,
            .cpu_iface = 0xf9001000,
            .num_irq = 192,
        },
    },

    .uart[0] = { 0xff000000, 18 },
    .uart[1] = { 0xff010000, 19 },
    .num_uart = 2,

    .canfd[0] = { 0xff060000, 20 },
    .canfd[1] = { 0xff070000, 21 },
    .num_canfd = 2,

    .sdhci[0] = { 0xf1040000, 126 },
    .sdhci[1] = { 0xf1050000, 128 },
    .num_sdhci = 2,

    .gem[0] = { { 0xff0c0000, 56 }, 2, "rgmii-id", 1000 },
    .gem[1] = { { 0xff0d0000, 58 }, 2, "rgmii-id", 1000 },
    .num_gem = 2,

    .zdma[0] = { "adma", { 0xffa80000, 60 }, 8, 0x10000, 1 },
    .num_zdma = 1,

    .xram = {
        .num = 4,
        .mem = 0xfe800000, .mem_stride = 1 * MiB,
        .ctrl = 0xff8e0000, .ctrl_stride = 0x10000,
        .irq = 79,
    },

    .usb[0] = { .xhci = 0xfe200000, .ctrl = 0xff9d0000, .irq = 22 },
    .num_usb = 1,

    .efuse = { .ctrl = 0xf1240000, .cache = 0xf1250000, .irq = 139 },

    .ospi = {
        .ctrl = 0xf1010000,
        .dac = 0xc0000000, .dac_sz = 0x20000000,
        .dma_src = 0xf1011000, .dma_dst = 0xf1011800,
        .irq = 124,
    },

    .pmc_iou_slcr = { 0xf1060000, OR_IRQ(121, 0) },
    .bbram = { 0xf11f0000, OR_IRQ(121, 1) },
    .trng = { 0xf1230000, 141 },
    .rtc = {
        { 0xf12a0000, OR_IRQ(121, 2) },
        .alarm_irq = 142, .second_irq = 143
    },

    .cfu = {
        .cframe_base = 0xf12d0000, .cframe_stride = 0x1000,
        .cframe_bcast_reg = 0xf12ee000, .cframe_bcast_fdri = 0xf12ef000,
        .cfu_apb = 0xf12b0000, .cfu_sfr = 0xf12c1000,
        .cfu_stream = 0xf12c0000, .cfu_stream_2 = 0xf1f80000,
        .cfu_fdro = 0xf12c2000,
        .cfu_apb_irq = 120, .cframe_irq = OR_IRQ(121, 3),
        .num_cframe = 15,
        .cframe_cfg = {
            { { 34111, 3528, 12800, 11, 5, 1, 1 } },
            { { 38498, 3841, 15361, 13, 7, 3, 1 } },
            { { 38498, 3841, 15361, 13, 7, 3, 1 } },
            { { 38498, 3841, 15361, 13, 7, 3, 1 } },
        },
    },

    .crl = { 0xff5e0000, 10 },

    .reserved = { 0xa0000000, 111, 8 },
};

static const VersalMap VERSAL2_MAP = {
    .ocm = {
        .addr = 0xbbe00000,
        .size = 2 * MiB,
    },

    .ddr = {
        .chan[0] = { .addr = 0x0, .size = 2046 * MiB },
        .chan[1] = { .addr = 0x800000000ull, .size = 32 * GiB },
        .chan[2] = { .addr = 0xc00000000ull, .size = 256 * GiB },
        .chan[3] = { .addr = 0x10000000000ull, .size = 734 * GiB },
        .num_chan = 4,
    },

    .apu = {
        .name = "apu",
        .cpu_model = ARM_CPU_TYPE_NAME("cortex-a78ae"),
        .num_cluster = 4,
        .num_core = 2,
        .qemu_cluster_id = 0,
        .mp_affinity = {
            .base = 0x0, /* TODO: the MT bit should be set */
            .core_shift = ARM_AFF1_SHIFT,
            .cluster_shift = ARM_AFF2_SHIFT,
        },
        .start_powered_off = SPO_SECONDARIES,
        .dtb_expose = true,
        .gic = {
            .version = 3,
            .dist = 0xe2000000,
            .redist = 0xe2060000,
            .num_irq = 544,
            .has_its = true,
            .its = 0xe2040000,
        },
    },

    .rpu = {
        .name = "rpu",
        .cpu_model = ARM_CPU_TYPE_NAME("cortex-r52"),
        .num_cluster = 5,
        .num_core = 2,
        .qemu_cluster_id = 1,
        .mp_affinity = {
            .core_shift = ARM_AFF0_SHIFT,
            .cluster_shift = ARM_AFF1_SHIFT,
        },
        .start_powered_off = SPO_ALL,
        .dtb_expose = false,
        .per_cluster_gic = true,
        .gic = {
            .version = 3,
            .dist = 0x0,
            .redist = 0x100000,
            .num_irq = 288,
        },
    },

    .uart[0] = { 0xf1920000, 25 },
    .uart[1] = { 0xf1930000, 26 },
    .num_uart = 2,

    .canfd[0] = { 0xf19e0000, 27 },
    .canfd[1] = { 0xf19f0000, 28 },
    .canfd[2] = { 0xf1a00000, 95 },
    .canfd[3] = { 0xf1a10000, 96 },
    .num_canfd = 4,

    .gem[0] = { { 0xf1a60000, 39 }, 2, "rgmii-id", 1000 },
    .gem[1] = { { 0xf1a70000, 41 }, 2, "rgmii-id", 1000 },
    .gem[2] = { { 0xed920000, 164 }, 4, "usxgmii", 10000 }, /* MMI 10Gb GEM */
    .num_gem = 3,

    .zdma[0] = { "adma", { 0xebd00000, 72 }, 8, 0x10000, 1 },
    .zdma[1] = { "sdma", { 0xebd80000, 112 }, 8, 0x10000, 1 },
    .num_zdma = 2,

    .usb[0] = { .xhci = 0xf1b00000, .ctrl = 0xf1ee0000, .irq = 29 },
    .usb[1] = { .xhci = 0xf1c00000, .ctrl = 0xf1ef0000, .irq = 34 },
    .num_usb = 2,

    .efuse = { .ctrl = 0xf1240000, .cache = 0xf1250000, .irq = 230 },

    .ospi = {
        .ctrl = 0xf1010000,
        .dac = 0xc0000000, .dac_sz = 0x20000000,
        .dma_src = 0xf1011000, .dma_dst = 0xf1011800,
        .irq = 216,
    },

    .sdhci[0] = { 0xf1040000, 218 },
    .sdhci[1] = { 0xf1050000, 220 }, /* eMMC */
    .num_sdhci = 2,

    .pmc_iou_slcr = { 0xf1060000, 222 },
    .bbram = { 0xf11f0000, PPU1_OR_IRQ(18, 0) },
    .crl = { 0xeb5e0000 },
    .trng = { 0xf1230000, 233 },
    .rtc = {
        { 0xf12a0000, PPU1_OR_IRQ(18, 1) },
        .alarm_irq = 200, .second_irq = 201
    },

    .cfu = {
        .cframe_base = 0xf12d0000, .cframe_stride = 0x1000,
        .cframe_bcast_reg = 0xf12ee000, .cframe_bcast_fdri = 0xf12ef000,
        .cfu_apb = 0xf12b0000, .cfu_sfr = 0xf12c1000,
        .cfu_stream = 0xf12c0000, .cfu_stream_2 = 0xf1f80000,
        .cfu_fdro = 0xf12c2000,
        .cfu_apb_irq = 235, .cframe_irq = EAM_IRQ(7),
    },

    .reserved = { 0xf5e00000, 270, 8 },
};

static const VersalMap *VERSION_TO_MAP[] = {
    [VERSAL_VER_VERSAL] = &VERSAL_MAP,
    [VERSAL_VER_VERSAL2] = &VERSAL2_MAP,
};

static inline VersalVersion versal_get_version(Versal *s)
{
    return XLNX_VERSAL_BASE_GET_CLASS(s)->version;
}

static inline const VersalMap *versal_get_map(Versal *s)
{
    return VERSION_TO_MAP[versal_get_version(s)];
}

static inline Object *versal_get_child(Versal *s, const char *child)
{
    return object_resolve_path_at(OBJECT(s), child);
}

static inline Object *versal_get_child_idx(Versal *s, const char *child,
                                           size_t idx)
{
    g_autofree char *n = g_strdup_printf("%s[%zu]", child, idx);

    return versal_get_child(s, n);
}

/*
 * The SoC embeds multiple GICs. They all receives the same IRQ lines at the
 * same index. This function creates a TYPE_SPLIT_IRQ device to fan out the
 * given IRQ input to all the GICs.
 *
 * The TYPE_SPLIT_IRQ devices lie in the /soc/irq-splits QOM container
 */
static qemu_irq versal_get_gic_irq(Versal *s, int irq_idx)
{
    DeviceState *split;
    Object *container = versal_get_child(s, "irq-splits");
    int idx = FIELD_EX32(irq_idx, VERSAL_IRQ, IRQ);
    g_autofree char *name = g_strdup_printf("irq[%d]", idx);

    split = DEVICE(object_resolve_path_at(container, name));

    if (split == NULL) {
        size_t i;

        split = qdev_new(TYPE_SPLIT_IRQ);
        qdev_prop_set_uint16(split, "num-lines", s->intc->len);
        object_property_add_child(container, name, OBJECT(split));
        qdev_realize_and_unref(split, NULL, &error_abort);

        for (i = 0; i < s->intc->len; i++) {
            DeviceState *gic;

            gic = g_array_index(s->intc, DeviceState *, i);
            qdev_connect_gpio_out(split, i, qdev_get_gpio_in(gic, idx));
        }
    } else {
        g_assert(FIELD_EX32(irq_idx, VERSAL_IRQ, ORED));
    }

    return qdev_get_gpio_in(split, 0);
}

/*
 * When the R_VERSAL_IRQ_ORED flag is set on an IRQ descriptor, this function is
 * used to return the corresponding or gate input IRQ. The or gate is created if
 * not already existant.
 *
 * Or gates are placed under the /soc/irq-or-gates QOM container.
 */
static qemu_irq versal_get_irq_or_gate_in(Versal *s, int irq_idx,
                                          qemu_irq target_irq)
{
    static const char *TARGET_STR[] = {
        [IRQ_TARGET_GIC] = "gic",
        [IRQ_TARGET_PPU1] = "ppu1",
        [IRQ_TARGET_EAM] = "eam",
    };

    VersalIrqTarget target;
    Object *container = versal_get_child(s, "irq-or-gates");
    DeviceState *dev;
    g_autofree char *name;
    int idx, or_idx;

    idx = FIELD_EX32(irq_idx, VERSAL_IRQ, IRQ);
    or_idx = FIELD_EX32(irq_idx, VERSAL_IRQ, OR_IDX);
    target = FIELD_EX32(irq_idx, VERSAL_IRQ, TARGET);

    name = g_strdup_printf("%s-irq[%d]", TARGET_STR[target], idx);
    dev = DEVICE(object_resolve_path_at(container, name));

    if (dev == NULL) {
        dev = qdev_new(TYPE_OR_IRQ);
        object_property_add_child(container, name, OBJECT(dev));
        qdev_prop_set_uint16(dev, "num-lines", 1 << R_VERSAL_IRQ_OR_IDX_LENGTH);
        qdev_realize_and_unref(dev, NULL, &error_abort);
        qdev_connect_gpio_out(dev, 0, target_irq);
    }

    return qdev_get_gpio_in(dev, or_idx);
}

static qemu_irq versal_get_irq(Versal *s, int irq_idx)
{
    VersalIrqTarget target;
    qemu_irq irq;
    bool ored;

    target = FIELD_EX32(irq_idx, VERSAL_IRQ, TARGET);
    ored = FIELD_EX32(irq_idx, VERSAL_IRQ, ORED);

    switch (target) {
    case IRQ_TARGET_EAM:
        /* EAM not implemented */
        return NULL;

    case IRQ_TARGET_PPU1:
        /* PPU1 CPU not implemented */
        return NULL;

    case IRQ_TARGET_GIC:
        irq = versal_get_gic_irq(s, irq_idx);
        break;

    default:
        g_assert_not_reached();
    }

    if (ored) {
        irq = versal_get_irq_or_gate_in(s, irq_idx, irq);
    }

    return irq;
}

static void versal_sysbus_connect_irq(Versal *s, SysBusDevice *sbd,
                                      int sbd_idx, int irq_idx)
{
    qemu_irq irq = versal_get_irq(s, irq_idx);

    if (irq == NULL) {
        return;
    }

    sysbus_connect_irq(sbd, sbd_idx, irq);
}

static void versal_qdev_connect_gpio_out(Versal *s, DeviceState *dev,
                                         int dev_idx, int irq_idx)
{
    qemu_irq irq = versal_get_irq(s, irq_idx);

    if (irq == NULL) {
        return;
    }

    qdev_connect_gpio_out(dev, dev_idx, irq);
}

static inline char *versal_fdt_add_subnode(Versal *s, const char *path,
                                           uint64_t at, const char *compat,
                                           size_t compat_sz)
{
    char *p;

    p = g_strdup_printf("%s@%" PRIx64, path, at);
    qemu_fdt_add_subnode(s->cfg.fdt, p);

    if (!strncmp(compat, "memory", compat_sz)) {
        qemu_fdt_setprop(s->cfg.fdt, p, "device_type", compat, compat_sz);
    } else {
        qemu_fdt_setprop(s->cfg.fdt, p, "compatible", compat, compat_sz);
    }

    return p;
}

static inline char *versal_fdt_add_simple_subnode(Versal *s, const char *path,
                                                  uint64_t addr, uint64_t len,
                                                  const char *compat,
                                                  size_t compat_sz)
{
    char *p = versal_fdt_add_subnode(s, path, addr, compat, compat_sz);

    qemu_fdt_setprop_sized_cells(s->cfg.fdt, p, "reg", 2, addr, 2, len);
    return p;
}

static inline DeviceState *create_or_gate(Versal *s, Object *parent,
                                          const char *name, uint16_t num_lines,
                                          int irq_idx)
{
    DeviceState *or;

    or = qdev_new(TYPE_OR_IRQ);
    qdev_prop_set_uint16(or, "num-lines", num_lines);
    object_property_add_child(parent, name, OBJECT(or));
    qdev_realize_and_unref(or, NULL, &error_abort);
    versal_qdev_connect_gpio_out(s, or, 0, irq_idx);

    return or;
}

static MemoryRegion *create_cpu_mr(Versal *s, DeviceState *cluster,
                                   const VersalCpuClusterMap *map)
{
    MemoryRegion *mr, *root_alias;
    char *name;

    mr = g_new(MemoryRegion, 1);
    name = g_strdup_printf("%s-mr", map->name);
    memory_region_init(mr, OBJECT(cluster), name, UINT64_MAX);
    g_free(name);

    root_alias = g_new(MemoryRegion, 1);
    name = g_strdup_printf("ps-alias-for-%s", map->name);
    memory_region_init_alias(root_alias, OBJECT(cluster), name,
                             &s->mr_ps, 0, UINT64_MAX);
    g_free(name);
    memory_region_add_subregion(mr, 0, root_alias);

    return mr;
}

static void versal_create_gic_its(Versal *s,
                                  const VersalCpuClusterMap *map,
                                  DeviceState *gic,
                                  MemoryRegion *mr,
                                  char *gic_node)
{
    DeviceState *dev;
    SysBusDevice *sbd;
    g_autofree char *node_pat = NULL, *node = NULL;
    const char compatible[] = "arm,gic-v3-its";

    if (map->gic.version != 3) {
        return;
    }

    if (!map->gic.has_its) {
        return;
    }

    dev = qdev_new(TYPE_ARM_GICV3_ITS);
    sbd = SYS_BUS_DEVICE(dev);

    object_property_add_child(OBJECT(gic), "its", OBJECT(dev));
    object_property_set_link(OBJECT(dev), "parent-gicv3", OBJECT(gic),
                             &error_abort);

    sysbus_realize_and_unref(sbd, &error_abort);

    memory_region_add_subregion(mr, map->gic.its,
                                sysbus_mmio_get_region(sbd, 0));

    if (!map->dtb_expose) {
        return;
    }

    qemu_fdt_setprop(s->cfg.fdt, gic_node, "ranges", NULL, 0);
    qemu_fdt_setprop_cell(s->cfg.fdt, gic_node, "#address-cells", 2);
    qemu_fdt_setprop_cell(s->cfg.fdt, gic_node, "#size-cells", 2);

    node_pat = g_strdup_printf("%s/its", gic_node);
    node = versal_fdt_add_simple_subnode(s, node_pat, map->gic.its, 0x20000,
                                         compatible, sizeof(compatible));
    qemu_fdt_setprop(s->cfg.fdt, node, "msi-controller", NULL, 0);
    qemu_fdt_setprop_cell(s->cfg.fdt, node, "#msi-cells", 1);
}

static DeviceState *versal_create_gic(Versal *s,
                                      const VersalCpuClusterMap *map,
                                      MemoryRegion *mr,
                                      int first_cpu_idx,
                                      size_t num_cpu)
{
    DeviceState *dev;
    SysBusDevice *sbd;
    g_autofree char *node = NULL;
    g_autofree char *name = NULL;
    const char gicv3_compat[] = "arm,gic-v3";
    const char gicv2_compat[] = "arm,cortex-a15-gic";

    switch (map->gic.version) {
    case 2:
        dev = qdev_new(gic_class_name());
        break;

    case 3:
        dev = qdev_new(gicv3_class_name());
        break;

    default:
        g_assert_not_reached();
    }

    name = g_strdup_printf("%s-gic[*]", map->name);
    object_property_add_child(OBJECT(s), name, OBJECT(dev));
    sbd = SYS_BUS_DEVICE(dev);
    qdev_prop_set_uint32(dev, "revision", map->gic.version);
    qdev_prop_set_uint32(dev, "num-cpu", num_cpu);
    qdev_prop_set_uint32(dev, "num-irq", map->gic.num_irq + 32);
    qdev_prop_set_bit(dev, "has-security-extensions", true);
    qdev_prop_set_uint32(dev, "first-cpu-index", first_cpu_idx);

    if (map->gic.version == 3) {
        QList *redist_region_count;

        redist_region_count = qlist_new();
        qlist_append_int(redist_region_count, num_cpu);
        qdev_prop_set_array(dev, "redist-region-count", redist_region_count);
        qdev_prop_set_bit(dev, "has-lpi", map->gic.has_its);
        object_property_set_link(OBJECT(dev), "sysmem", OBJECT(mr),
                                 &error_abort);

    }

    sysbus_realize_and_unref(sbd, &error_fatal);

    memory_region_add_subregion(mr, map->gic.dist,
                                sysbus_mmio_get_region(sbd, 0));

    if (map->gic.version == 3) {
        memory_region_add_subregion(mr, map->gic.redist,
                                    sysbus_mmio_get_region(sbd, 1));
    } else {
        memory_region_add_subregion(mr, map->gic.cpu_iface,
                                    sysbus_mmio_get_region(sbd, 1));
    }

    if (map->dtb_expose) {
        if (map->gic.version == 3) {
            node = versal_fdt_add_subnode(s, "/gic", map->gic.dist,
                                          gicv3_compat,
                                          sizeof(gicv3_compat));
            qemu_fdt_setprop_sized_cells(s->cfg.fdt, node, "reg",
                                         2, map->gic.dist,
                                         2, 0x10000,
                                         2, map->gic.redist,
                                         2, GICV3_REDIST_SIZE * num_cpu);
        } else {
            node = versal_fdt_add_subnode(s, "/gic", map->gic.dist,
                                          gicv2_compat,
                                          sizeof(gicv2_compat));
            qemu_fdt_setprop_sized_cells(s->cfg.fdt, node, "reg",
                                         2, map->gic.dist,
                                         2, 0x1000,
                                         2, map->gic.cpu_iface,
                                         2, 0x1000);
        }

        qemu_fdt_setprop_cell(s->cfg.fdt, node, "phandle", s->phandle.gic);
        qemu_fdt_setprop_cell(s->cfg.fdt, node, "#interrupt-cells", 3);
        qemu_fdt_setprop_cells(s->cfg.fdt, node, "interrupts",
                               GIC_FDT_IRQ_TYPE_PPI,
                               INTID_TO_PPI(ARCH_GIC_MAINT_IRQ),
                               GIC_FDT_IRQ_FLAGS_LEVEL_HI);
        qemu_fdt_setprop(s->cfg.fdt, node, "interrupt-controller", NULL, 0);
    }

    versal_create_gic_its(s, map, dev, mr, node);

    g_array_append_val(s->intc, dev);

    return dev;
}

static void connect_gic_to_cpu(const VersalCpuClusterMap *map,
                               DeviceState *gic, DeviceState *cpu, size_t idx,
                               size_t num_cpu)
{
    SysBusDevice *sbd = SYS_BUS_DEVICE(gic);
    int ppibase = map->gic.num_irq + idx * GIC_INTERNAL + GIC_NR_SGIS;
    int ti;
    bool has_gtimer;
    /*
     * Mapping from the output timer irq lines from the CPU to the
     * GIC PPI inputs.
     */
    const int timer_irq[] = {
        [GTIMER_PHYS] = INTID_TO_PPI(ARCH_TIMER_NS_EL1_IRQ),
        [GTIMER_VIRT] = INTID_TO_PPI(ARCH_TIMER_VIRT_IRQ),
        [GTIMER_HYP]  = INTID_TO_PPI(ARCH_TIMER_NS_EL2_IRQ),
        [GTIMER_SEC]  = INTID_TO_PPI(ARCH_TIMER_S_EL1_IRQ),
    };

    has_gtimer = arm_feature(&ARM_CPU(cpu)->env, ARM_FEATURE_GENERIC_TIMER);

    if (has_gtimer) {
        for (ti = 0; ti < ARRAY_SIZE(timer_irq); ti++) {
            qdev_connect_gpio_out(cpu, ti,
                                  qdev_get_gpio_in(gic,
                                                   ppibase + timer_irq[ti]));
        }
    }

    if (map->gic.version == 3) {
        qemu_irq maint_irq;
        int maint_idx = ppibase + INTID_TO_PPI(ARCH_GIC_MAINT_IRQ);

        maint_irq = qdev_get_gpio_in(gic, maint_idx);
        qdev_connect_gpio_out_named(cpu, "gicv3-maintenance-interrupt",
                                    0, maint_irq);
    }

    sysbus_connect_irq(sbd, idx, qdev_get_gpio_in(cpu, ARM_CPU_IRQ));
    sysbus_connect_irq(sbd, idx + num_cpu,
                       qdev_get_gpio_in(cpu, ARM_CPU_FIQ));
    sysbus_connect_irq(sbd, idx + 2 * num_cpu,
                       qdev_get_gpio_in(cpu, ARM_CPU_VIRQ));
    sysbus_connect_irq(sbd, idx + 3 * num_cpu,
                       qdev_get_gpio_in(cpu, ARM_CPU_VFIQ));
}

static inline void versal_create_and_connect_gic(Versal *s,
                                                 const VersalCpuClusterMap *map,
                                                 MemoryRegion *mr,
                                                 DeviceState **cpus,
                                                 size_t num_cpu)
{
    DeviceState *gic;
    int first_cpu_idx;
    size_t i;

    first_cpu_idx = CPU(cpus[0])->cpu_index;
    gic = versal_create_gic(s, map, mr, first_cpu_idx, num_cpu);

    for (i = 0; i < num_cpu; i++) {
        connect_gic_to_cpu(map, gic, cpus[i], i, num_cpu);
    }
}

static DeviceState *versal_create_cpu(Versal *s,
                                      const VersalCpuClusterMap *map,
                                      DeviceState *qemu_cluster,
                                      MemoryRegion *cpu_mr,
                                      size_t cluster_idx,
                                      size_t core_idx)
{
    DeviceState *cpu = qdev_new(map->cpu_model);
    ARMCPU *arm_cpu = ARM_CPU(cpu);
    Object *obj = OBJECT(cpu);
    uint64_t affinity;
    bool start_off;
    size_t idx = cluster_idx * map->num_core + core_idx;
    g_autofree char *name;
    g_autofree char *node = NULL;

    affinity = map->mp_affinity.base;
    affinity |= (cluster_idx & 0xff) << map->mp_affinity.cluster_shift;
    affinity |= (core_idx & 0xff) << map->mp_affinity.core_shift;

    start_off = map->start_powered_off == SPO_ALL
        || ((map->start_powered_off == SPO_SECONDARIES)
            && (cluster_idx || core_idx));

    name = g_strdup_printf("%s[*]", map->name);
    object_property_add_child(OBJECT(qemu_cluster), name, obj);
    object_property_set_bool(obj, "start-powered-off", start_off,
                             &error_abort);
    qdev_prop_set_uint64(cpu, "mp-affinity", affinity);
    qdev_prop_set_int32(cpu, "core-count",  map->num_core);
    object_property_set_link(obj, "memory", OBJECT(cpu_mr), &error_abort);
    qdev_realize_and_unref(cpu, NULL, &error_fatal);

    if (!map->dtb_expose) {
        return cpu;
    }

    node = versal_fdt_add_subnode(s, "/cpus/cpu", idx,
                                  arm_cpu->dtb_compatible,
                                  strlen(arm_cpu->dtb_compatible) + 1);
    qemu_fdt_setprop_cell(s->cfg.fdt, node, "reg",
                          arm_cpu_mp_affinity(arm_cpu) & ARM64_AFFINITY_MASK);
    qemu_fdt_setprop_string(s->cfg.fdt, node, "device_type", "cpu");
    qemu_fdt_setprop_string(s->cfg.fdt, node, "enable-method", "psci");

    return cpu;
}

static void versal_create_cpu_cluster(Versal *s, const VersalCpuClusterMap *map)
{
    size_t i, j;
    DeviceState *cluster;
    MemoryRegion *mr;
    char *name;
    g_autofree DeviceState **cpus;
    const char compatible[] = "arm,armv8-timer";
    bool has_gtimer;

    cluster = qdev_new(TYPE_CPU_CLUSTER);
    name = g_strdup_printf("%s-cluster", map->name);
    object_property_add_child(OBJECT(s), name, OBJECT(cluster));
    g_free(name);
    qdev_prop_set_uint32(cluster, "cluster-id", map->qemu_cluster_id);

    mr = create_cpu_mr(s, cluster, map);

    cpus = g_new(DeviceState *, map->num_cluster * map->num_core);

    if (map->dtb_expose) {
        qemu_fdt_add_subnode(s->cfg.fdt, "/cpus");
        qemu_fdt_setprop_cell(s->cfg.fdt, "/cpus", "#size-cells", 0);
        qemu_fdt_setprop_cell(s->cfg.fdt, "/cpus", "#address-cells", 1);
    }

    for (i = 0; i < map->num_cluster; i++) {
        for (j = 0; j < map->num_core; j++) {
            DeviceState *cpu = versal_create_cpu(s, map, cluster, mr, i, j);

            cpus[i * map->num_core + j] = cpu;
        }

        if (map->per_cluster_gic) {
            versal_create_and_connect_gic(s, map, mr, &cpus[i * map->num_core],
                                          map->num_core);
        }
    }

    qdev_realize_and_unref(cluster, NULL, &error_fatal);

    if (!map->per_cluster_gic) {
        versal_create_and_connect_gic(s, map, mr, cpus,
                                      map->num_cluster * map->num_core);
    }

    has_gtimer = arm_feature(&ARM_CPU(cpus[0])->env, ARM_FEATURE_GENERIC_TIMER);
    if (map->dtb_expose && has_gtimer) {
        qemu_fdt_add_subnode(s->cfg.fdt, "/timer");
        qemu_fdt_setprop_cells(s->cfg.fdt, "/timer", "interrupts",
                               GIC_FDT_IRQ_TYPE_PPI,
                               INTID_TO_PPI(ARCH_TIMER_S_EL1_IRQ),
                               GIC_FDT_IRQ_FLAGS_LEVEL_HI,
                               GIC_FDT_IRQ_TYPE_PPI,
                               INTID_TO_PPI(ARCH_TIMER_NS_EL1_IRQ),
                               GIC_FDT_IRQ_FLAGS_LEVEL_HI,
                               GIC_FDT_IRQ_TYPE_PPI,
                               INTID_TO_PPI(ARCH_TIMER_VIRT_IRQ),
                               GIC_FDT_IRQ_FLAGS_LEVEL_HI,
                               GIC_FDT_IRQ_TYPE_PPI,
                               INTID_TO_PPI(ARCH_TIMER_NS_EL2_IRQ),
                               GIC_FDT_IRQ_FLAGS_LEVEL_HI);
        qemu_fdt_setprop(s->cfg.fdt, "/timer", "compatible",
                         compatible, sizeof(compatible));
    }
}

static void versal_create_uart(Versal *s,
                               const VersalSimplePeriphMap *map,
                               int chardev_idx)
{
    DeviceState *dev;
    MemoryRegion *mr;
    g_autofree char *node;
    g_autofree char *alias;
    const char compatible[] = "arm,pl011\0arm,sbsa-uart";
    const char clocknames[] = "uartclk\0apb_pclk";

    dev = qdev_new(TYPE_PL011);
    object_property_add_child(OBJECT(s), "uart[*]", OBJECT(dev));
    qdev_prop_set_chr(dev, "chardev", serial_hd(chardev_idx));
    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);

    mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
    memory_region_add_subregion(&s->mr_ps, map->addr, mr);

    versal_sysbus_connect_irq(s, SYS_BUS_DEVICE(dev), 0, map->irq);

    node = versal_fdt_add_simple_subnode(s, "/uart", map->addr, 0x1000,
                                         compatible, sizeof(compatible));
    qemu_fdt_setprop_cell(s->cfg.fdt, node, "current-speed", 115200);
    qemu_fdt_setprop_cells(s->cfg.fdt, node, "clocks",
                           s->phandle.clk_125mhz, s->phandle.clk_125mhz);
    qemu_fdt_setprop(s->cfg.fdt, node, "clock-names", clocknames,
                     sizeof(clocknames));
    qemu_fdt_setprop_cells(s->cfg.fdt, node, "interrupts",
                           GIC_FDT_IRQ_TYPE_SPI, map->irq,
                           GIC_FDT_IRQ_FLAGS_LEVEL_HI);
    qemu_fdt_setprop(s->cfg.fdt, node, "u-boot,dm-pre-reloc", NULL, 0);

    alias = g_strdup_printf("serial%d", chardev_idx);
    qemu_fdt_setprop_string(s->cfg.fdt, "/aliases", alias, node);

    if (chardev_idx == 0) {
        qemu_fdt_setprop_string(s->cfg.fdt, "/chosen", "stdout-path", node);
    }
}

static void versal_create_canfd(Versal *s, const VersalSimplePeriphMap *map,
                                CanBusState *bus)
{
    SysBusDevice *sbd;
    MemoryRegion *mr;
    g_autofree char *node;
    const char compatible[] = "xlnx,canfd-2.0";
    const char clocknames[] = "can_clk\0s_axi_aclk";

    sbd = SYS_BUS_DEVICE(qdev_new(TYPE_XILINX_CANFD));
    object_property_add_child(OBJECT(s), "canfd[*]", OBJECT(sbd));

    object_property_set_int(OBJECT(sbd), "ext_clk_freq",
                            25 * 1000 * 1000 , &error_abort);

    object_property_set_link(OBJECT(sbd), "canfdbus", OBJECT(bus),
                             &error_abort);

    sysbus_realize_and_unref(sbd, &error_fatal);

    mr = sysbus_mmio_get_region(sbd, 0);
    memory_region_add_subregion(&s->mr_ps, map->addr, mr);

    versal_sysbus_connect_irq(s, sbd, 0, map->irq);

    node = versal_fdt_add_simple_subnode(s, "/canfd", map->addr, 0x10000,
                                         compatible, sizeof(compatible));
    qemu_fdt_setprop_cell(s->cfg.fdt, node, "rx-fifo-depth", 0x40);
    qemu_fdt_setprop_cell(s->cfg.fdt, node, "tx-mailbox-count", 0x20);
    qemu_fdt_setprop_cells(s->cfg.fdt, node, "clocks",
                           s->phandle.clk_25mhz, s->phandle.clk_25mhz);
    qemu_fdt_setprop(s->cfg.fdt, node, "clock-names",
                     clocknames, sizeof(clocknames));
    qemu_fdt_setprop_cells(s->cfg.fdt, node, "interrupts",
                           GIC_FDT_IRQ_TYPE_SPI, map->irq,
                           GIC_FDT_IRQ_FLAGS_LEVEL_HI);
}

static void versal_create_usb(Versal *s,
                              const struct VersalUsbMap *map)
{
    DeviceState *dev;
    MemoryRegion *mr;
    g_autofree char *node, *subnode;
    const char clocknames[] = "bus_clk\0ref_clk";
    const char irq_name[] = "dwc_usb3";
    const char compat_versal_dwc3[] = "xlnx,versal-dwc3";
    const char compat_dwc3[] = "snps,dwc3";

    dev = qdev_new(TYPE_XILINX_VERSAL_USB2);
    object_property_add_child(OBJECT(s), "usb[*]", OBJECT(dev));

    object_property_set_link(OBJECT(dev), "dma", OBJECT(&s->mr_ps),
                             &error_abort);
    qdev_prop_set_uint32(dev, "intrs", 1);
    qdev_prop_set_uint32(dev, "slots", 2);

    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);

    mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
    memory_region_add_subregion(&s->mr_ps, map->xhci, mr);

    versal_sysbus_connect_irq(s, SYS_BUS_DEVICE(dev), 0, map->irq);

    mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1);
    memory_region_add_subregion(&s->mr_ps, map->ctrl, mr);

    node = versal_fdt_add_simple_subnode(s, "/usb", map->ctrl, 0x10000,
                                         compat_versal_dwc3,
                                         sizeof(compat_versal_dwc3));
    qemu_fdt_setprop(s->cfg.fdt, node, "clock-names",
                         clocknames, sizeof(clocknames));
    qemu_fdt_setprop_cells(s->cfg.fdt, node, "clocks",
                               s->phandle.clk_25mhz, s->phandle.clk_125mhz);
    qemu_fdt_setprop(s->cfg.fdt, node, "ranges", NULL, 0);
    qemu_fdt_setprop_cell(s->cfg.fdt, node, "#address-cells", 2);
    qemu_fdt_setprop_cell(s->cfg.fdt, node, "#size-cells", 2);

    subnode = g_strdup_printf("/%s/dwc3", node);
    g_free(node);

    node = versal_fdt_add_simple_subnode(s, subnode, map->xhci, 0x10000,
                                         compat_dwc3,
                                         sizeof(compat_dwc3));
    qemu_fdt_setprop(s->cfg.fdt, node, "interrupt-names",
                     irq_name, sizeof(irq_name));
    qemu_fdt_setprop_cells(s->cfg.fdt, node, "interrupts",
                               GIC_FDT_IRQ_TYPE_SPI, map->irq,
                               GIC_FDT_IRQ_FLAGS_LEVEL_HI);
    qemu_fdt_setprop_cell(s->cfg.fdt, node,
                          "snps,quirk-frame-length-adjustment", 0x20);
    qemu_fdt_setprop_cells(s->cfg.fdt, node, "#stream-id-cells", 1);
    qemu_fdt_setprop_string(s->cfg.fdt, node, "dr_mode", "host");
    qemu_fdt_setprop_string(s->cfg.fdt, node, "phy-names", "usb3-phy");
    qemu_fdt_setprop(s->cfg.fdt, node, "snps,dis_u2_susphy_quirk", NULL, 0);
    qemu_fdt_setprop(s->cfg.fdt, node, "snps,dis_u3_susphy_quirk", NULL, 0);
    qemu_fdt_setprop(s->cfg.fdt, node, "snps,refclk_fladj", NULL, 0);
    qemu_fdt_setprop(s->cfg.fdt, node, "snps,mask_phy_reset", NULL, 0);
    qemu_fdt_setprop_string(s->cfg.fdt, node, "maximum-speed", "high-speed");
}

static void versal_create_gem(Versal *s,
                              const struct VersalGemMap *map)
{
    DeviceState *dev;
    MemoryRegion *mr;
    DeviceState *or;
    int i;

    dev = qdev_new(TYPE_CADENCE_GEM);
    object_property_add_child(OBJECT(s), "gem[*]", OBJECT(dev));

    qemu_configure_nic_device(dev, true, NULL);
    object_property_set_int(OBJECT(dev), "phy-addr", 23, &error_abort);
    object_property_set_int(OBJECT(dev), "num-priority-queues",
                            map->num_prio_queue, &error_abort);

    object_property_set_link(OBJECT(dev), "dma", OBJECT(&s->mr_ps),
                             &error_abort);
    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);

    mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
    memory_region_add_subregion(&s->mr_ps, map->map.addr, mr);

    /*
     * The GEM controller exposes one IRQ line per priority queue. In Versal
     * family devices, those are OR'ed together.
     */
    or = create_or_gate(s, OBJECT(dev), "irq-orgate",
                        map->num_prio_queue, map->map.irq);

    for (i = 0; i < map->num_prio_queue; i++) {
        sysbus_connect_irq(SYS_BUS_DEVICE(dev), i, qdev_get_gpio_in(or, i));
    }
}

static void versal_create_gem_fdt(Versal *s,
                                  const struct VersalGemMap *map)
{
    int i;
    g_autofree char *node;
    g_autofree char *phy_node;
    int phy_phandle;
    const char compatible[] = "cdns,zynqmp-gem\0cdns,gem";
    const char clocknames[] = "pclk\0hclk\0tx_clk\0rx_clk";
    g_autofree uint32_t *irq_prop;

    node = versal_fdt_add_simple_subnode(s, "/ethernet", map->map.addr, 0x1000,
                                         compatible, sizeof(compatible));
    phy_node = g_strdup_printf("%s/fixed-link", node);
    phy_phandle = qemu_fdt_alloc_phandle(s->cfg.fdt);

    /* Fixed link PHY node */
    qemu_fdt_add_subnode(s->cfg.fdt, phy_node);
    qemu_fdt_setprop_cell(s->cfg.fdt, phy_node, "phandle", phy_phandle);
    qemu_fdt_setprop(s->cfg.fdt, phy_node, "full-duplex", NULL, 0);
    qemu_fdt_setprop_cell(s->cfg.fdt, phy_node, "speed", map->speed);

    qemu_fdt_setprop_string(s->cfg.fdt, node, "phy-mode", map->phy_mode);
    qemu_fdt_setprop_cell(s->cfg.fdt, node, "phy-handle", phy_phandle);
    qemu_fdt_setprop_cells(s->cfg.fdt, node, "clocks",
                           s->phandle.clk_25mhz, s->phandle.clk_25mhz,
                           s->phandle.clk_125mhz, s->phandle.clk_125mhz);
    qemu_fdt_setprop(s->cfg.fdt, node, "clock-names",
                     clocknames, sizeof(clocknames));

    irq_prop = g_new(uint32_t, map->num_prio_queue * 3);
    for (i = 0; i < map->num_prio_queue; i++) {
        irq_prop[3 * i] = cpu_to_be32(GIC_FDT_IRQ_TYPE_SPI);
        irq_prop[3 * i + 1] = cpu_to_be32(map->map.irq);
        irq_prop[3 * i + 2] = cpu_to_be32(GIC_FDT_IRQ_FLAGS_LEVEL_HI);
    }
    qemu_fdt_setprop(s->cfg.fdt, node, "interrupts", irq_prop,
                     sizeof(uint32_t) * map->num_prio_queue * 3);
}

static void versal_create_zdma(Versal *s,
                               const struct VersalZDMAMap *map)
{
    DeviceState *dev;
    MemoryRegion *mr;
    g_autofree char *name;
    const char compatible[] = "xlnx,zynqmp-dma-1.0";
    const char clocknames[] = "clk_main\0clk_apb";
    size_t i;

    name = g_strdup_printf("%s[*]", map->name);

    for (i = 0; i < map->num_chan; i++) {
        uint64_t addr = map->map.addr + map->chan_stride * i;
        int irq = map->map.irq + map->irq_stride * i;
        g_autofree char *node;

        dev = qdev_new(TYPE_XLNX_ZDMA);
        object_property_add_child(OBJECT(s), name, OBJECT(dev));
        object_property_set_int(OBJECT(dev), "bus-width", 128, &error_abort);
        object_property_set_link(OBJECT(dev), "dma",
                                 OBJECT(get_system_memory()), &error_fatal);
        sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);

        mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
        memory_region_add_subregion(&s->mr_ps, addr, mr);

        versal_sysbus_connect_irq(s, SYS_BUS_DEVICE(dev), 0, irq);

        node = versal_fdt_add_simple_subnode(s, "/dma", addr, 0x1000,
                                             compatible, sizeof(compatible));
        qemu_fdt_setprop_cell(s->cfg.fdt, node, "xlnx,bus-width", 64);
        qemu_fdt_setprop_cells(s->cfg.fdt, node, "clocks",
                               s->phandle.clk_25mhz, s->phandle.clk_25mhz);
        qemu_fdt_setprop(s->cfg.fdt, node, "clock-names",
                         clocknames, sizeof(clocknames));
        qemu_fdt_setprop_cells(s->cfg.fdt, node, "interrupts",
                               GIC_FDT_IRQ_TYPE_SPI, irq,
                               GIC_FDT_IRQ_FLAGS_LEVEL_HI);
    }
}

#define SDHCI_CAPABILITIES  0x280737ec6481 /* Same as on ZynqMP.  */
static void versal_create_sdhci(Versal *s,
                                const VersalSimplePeriphMap *map)
{
    DeviceState *dev;
    MemoryRegion *mr;
    g_autofree char *node;
    const char compatible[] = "arasan,sdhci-8.9a";
    const char clocknames[] = "clk_xin\0clk_ahb";

    dev = qdev_new(TYPE_SYSBUS_SDHCI);
    object_property_add_child(OBJECT(s), "sdhci[*]", OBJECT(dev));

    object_property_set_uint(OBJECT(dev), "sd-spec-version", 3,
                             &error_fatal);
    object_property_set_uint(OBJECT(dev), "capareg", SDHCI_CAPABILITIES,
                             &error_fatal);
    object_property_set_uint(OBJECT(dev), "uhs", UHS_I, &error_fatal);
    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);

    mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
    memory_region_add_subregion(&s->mr_ps, map->addr, mr);

    versal_sysbus_connect_irq(s, SYS_BUS_DEVICE(dev), 0, map->irq);

    node = versal_fdt_add_simple_subnode(s, "/sdhci", map->addr, 0x10000,
                                         compatible, sizeof(compatible));
    qemu_fdt_setprop_cells(s->cfg.fdt, node, "clocks",
                           s->phandle.clk_25mhz, s->phandle.clk_25mhz);
    qemu_fdt_setprop(s->cfg.fdt, node, "clock-names",
                     clocknames, sizeof(clocknames));
    qemu_fdt_setprop_cells(s->cfg.fdt, node, "interrupts",
                           GIC_FDT_IRQ_TYPE_SPI, map->irq,
                           GIC_FDT_IRQ_FLAGS_LEVEL_HI);
}

static void versal_create_rtc(Versal *s, const struct VersalRtcMap *map)
{
    SysBusDevice *sbd;
    MemoryRegion *mr;
    g_autofree char *node;
    const char compatible[] = "xlnx,zynqmp-rtc";
    const char interrupt_names[] = "alarm\0sec";

    sbd = SYS_BUS_DEVICE(qdev_new(TYPE_XLNX_ZYNQMP_RTC));
    object_property_add_child(OBJECT(s), "rtc", OBJECT(sbd));
    sysbus_realize_and_unref(sbd, &error_abort);

    mr = sysbus_mmio_get_region(sbd, 0);
    memory_region_add_subregion(&s->mr_ps, map->map.addr, mr);

    /*
     * TODO: Connect the ALARM and SECONDS interrupts once our RTC model
     * supports them.
     */
    versal_sysbus_connect_irq(s, sbd, 0, map->map.irq);

    node = versal_fdt_add_simple_subnode(s, "/rtc", map->map.addr, 0x10000,
                                         compatible, sizeof(compatible));
    qemu_fdt_setprop_cells(s->cfg.fdt, node, "interrupts",
                           GIC_FDT_IRQ_TYPE_SPI, map->alarm_irq,
                           GIC_FDT_IRQ_FLAGS_LEVEL_HI,
                           GIC_FDT_IRQ_TYPE_SPI, map->second_irq,
                           GIC_FDT_IRQ_FLAGS_LEVEL_HI);
    qemu_fdt_setprop(s->cfg.fdt, node, "interrupt-names",
                     interrupt_names, sizeof(interrupt_names));
}

static void versal_create_trng(Versal *s, const VersalSimplePeriphMap *map)
{
    SysBusDevice *sbd;
    MemoryRegion *mr;

    sbd = SYS_BUS_DEVICE(qdev_new(TYPE_XLNX_VERSAL_TRNG));
    object_property_add_child(OBJECT(s), "trng", OBJECT(sbd));
    sysbus_realize_and_unref(sbd, &error_abort);

    mr = sysbus_mmio_get_region(sbd, 0);
    memory_region_add_subregion(&s->mr_ps, map->addr, mr);
    versal_sysbus_connect_irq(s, sbd, 0, map->irq);
}

static void versal_create_xrams(Versal *s, const struct VersalXramMap *map)
{
    SysBusDevice *sbd;
    MemoryRegion *mr;
    DeviceState *or;
    size_t i;

    or = create_or_gate(s, OBJECT(s), "xram-orgate", map->num, map->irq);

    for (i = 0; i < map->num; i++) {
        hwaddr ctrl, mem;

        sbd = SYS_BUS_DEVICE(qdev_new(TYPE_XLNX_XRAM_CTRL));
        object_property_add_child(OBJECT(s), "xram[*]", OBJECT(sbd));
        sysbus_realize_and_unref(sbd, &error_fatal);

        ctrl = map->ctrl + map->ctrl_stride * i;
        mem = map->mem + map->mem_stride * i;

        mr = sysbus_mmio_get_region(sbd, 0);
        memory_region_add_subregion(&s->mr_ps, ctrl, mr);
        mr = sysbus_mmio_get_region(sbd, 1);
        memory_region_add_subregion(&s->mr_ps, mem, mr);

        sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(or, i));
    }
}

static void versal_create_bbram(Versal *s,
                                const VersalSimplePeriphMap *map)
{
    DeviceState *dev;
    SysBusDevice *sbd;

    dev = qdev_new(TYPE_XLNX_BBRAM);
    sbd = SYS_BUS_DEVICE(dev);

    object_property_add_child(OBJECT(s), "bbram", OBJECT(dev));
    qdev_prop_set_uint32(dev, "crc-zpads", 0);
    sysbus_realize_and_unref(sbd, &error_abort);
    memory_region_add_subregion(&s->mr_ps, map->addr,
                                sysbus_mmio_get_region(sbd, 0));
    versal_sysbus_connect_irq(s, sbd, 0, map->irq);
}

static void versal_create_efuse(Versal *s,
                                const struct VersalEfuseMap *map)
{
    DeviceState *bits;
    DeviceState *ctrl;
    DeviceState *cache;

    if (versal_get_version(s) != VERSAL_VER_VERSAL) {
        /* TODO for versal2 */
        return;
    }

    ctrl = qdev_new(TYPE_XLNX_VERSAL_EFUSE_CTRL);
    cache = qdev_new(TYPE_XLNX_VERSAL_EFUSE_CACHE);
    bits = qdev_new(TYPE_XLNX_EFUSE);

    qdev_prop_set_uint32(bits, "efuse-nr", 3);
    qdev_prop_set_uint32(bits, "efuse-size", 8192);

    object_property_add_child(OBJECT(s), "efuse", OBJECT(bits));
    qdev_realize_and_unref(bits, NULL, &error_abort);

    object_property_set_link(OBJECT(ctrl), "efuse", OBJECT(bits), &error_abort);

    object_property_set_link(OBJECT(cache), "efuse", OBJECT(bits),
                             &error_abort);

    object_property_add_child(OBJECT(s), "efuse-cache", OBJECT(cache));
    sysbus_realize_and_unref(SYS_BUS_DEVICE(cache), &error_abort);

    object_property_add_child(OBJECT(s), "efuse-ctrl", OBJECT(ctrl));
    sysbus_realize_and_unref(SYS_BUS_DEVICE(ctrl), &error_abort);

    memory_region_add_subregion(&s->mr_ps, map->ctrl,
                                sysbus_mmio_get_region(SYS_BUS_DEVICE(ctrl),
                                                       0));
    memory_region_add_subregion(&s->mr_ps, map->cache,
                                sysbus_mmio_get_region(SYS_BUS_DEVICE(cache),
                                                       0));
    versal_sysbus_connect_irq(s, SYS_BUS_DEVICE(ctrl), 0, map->irq);
}

static DeviceState *versal_create_pmc_iou_slcr(Versal *s,
                                               const VersalSimplePeriphMap *map)
{
    SysBusDevice *sbd;
    DeviceState *dev;

    dev = qdev_new(TYPE_XILINX_VERSAL_PMC_IOU_SLCR);
    object_property_add_child(OBJECT(s), "pmc-iou-slcr", OBJECT(dev));

    sbd = SYS_BUS_DEVICE(dev);
    sysbus_realize_and_unref(sbd, &error_fatal);

    memory_region_add_subregion(&s->mr_ps, map->addr,
                                sysbus_mmio_get_region(sbd, 0));

    versal_sysbus_connect_irq(s, sbd, 0, map->irq);

    return dev;
}

static DeviceState *versal_create_ospi(Versal *s,
                                       const struct VersalOspiMap *map)
{
    SysBusDevice *sbd;
    MemoryRegion *mr_dac;
    DeviceState *dev, *dma_dst, *dma_src, *orgate;
    MemoryRegion *linear_mr = g_new(MemoryRegion, 1);

    dev = qdev_new(TYPE_XILINX_VERSAL_OSPI);
    object_property_add_child(OBJECT(s), "ospi", OBJECT(dev));

    memory_region_init(linear_mr, OBJECT(dev), "linear-mr", map->dac_sz);

    mr_dac = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1);
    memory_region_add_subregion(linear_mr, 0x0, mr_dac);

    /* Create the OSPI destination DMA */
    dma_dst = qdev_new(TYPE_XLNX_CSU_DMA);
    object_property_add_child(OBJECT(dev), "dma-dst-dev", OBJECT(dma_dst));
    object_property_set_link(OBJECT(dma_dst), "dma",
                             OBJECT(get_system_memory()), &error_abort);

    sbd = SYS_BUS_DEVICE(dma_dst);
    sysbus_realize_and_unref(sbd, &error_fatal);

    memory_region_add_subregion(&s->mr_ps, map->dma_dst,
                                sysbus_mmio_get_region(sbd, 0));

    /* Create the OSPI source DMA */
    dma_src = qdev_new(TYPE_XLNX_CSU_DMA);
    object_property_add_child(OBJECT(dev), "dma-src-dev", OBJECT(dma_src));

    object_property_set_bool(OBJECT(dma_src), "is-dst", false, &error_abort);

    object_property_set_link(OBJECT(dma_src), "dma", OBJECT(mr_dac),
                             &error_abort);

    object_property_set_link(OBJECT(dma_src), "stream-connected-dma",
                             OBJECT(dma_dst), &error_abort);

    sbd = SYS_BUS_DEVICE(dma_src);
    sysbus_realize_and_unref(sbd, &error_fatal);

    memory_region_add_subregion(&s->mr_ps, map->dma_src,
                                sysbus_mmio_get_region(sbd, 0));

    /* Realize the OSPI */
    object_property_set_link(OBJECT(dev), "dma-src",
                             OBJECT(dma_src), &error_abort);

    sbd = SYS_BUS_DEVICE(dev);
    sysbus_realize_and_unref(sbd, &error_fatal);

    memory_region_add_subregion(&s->mr_ps, map->ctrl,
                                sysbus_mmio_get_region(sbd, 0));

    memory_region_add_subregion(&s->mr_ps, map->dac,
                                linear_mr);

    /* OSPI irq */
    orgate = create_or_gate(s, OBJECT(dev), "irq-orgate", 3,
                            map->irq);

    sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, qdev_get_gpio_in(orgate, 0));
    sysbus_connect_irq(SYS_BUS_DEVICE(dma_src), 0, qdev_get_gpio_in(orgate, 1));
    sysbus_connect_irq(SYS_BUS_DEVICE(dma_dst), 0, qdev_get_gpio_in(orgate, 2));

    return dev;
}

static void versal_create_cfu(Versal *s, const struct VersalCfuMap *map)
{
    SysBusDevice *sbd;
    Object *container;
    DeviceState *cfu_fdro, *cfu_apb, *cfu_sfr, *cframe_bcast;
    DeviceState *cframe_irq_or;
    int i;

    container = object_new(TYPE_CONTAINER);
    object_property_add_child(OBJECT(s), "cfu", container);
    object_unref(container);

    /* CFU FDRO */
    cfu_fdro = qdev_new(TYPE_XLNX_VERSAL_CFU_FDRO);
    object_property_add_child(container, "cfu-fdro", OBJECT(cfu_fdro));
    sbd = SYS_BUS_DEVICE(cfu_fdro);

    sysbus_realize_and_unref(sbd, &error_fatal);
    memory_region_add_subregion(&s->mr_ps, map->cfu_fdro,
                                sysbus_mmio_get_region(sbd, 0));

    /* cframe bcast */
    cframe_bcast = qdev_new(TYPE_XLNX_VERSAL_CFRAME_BCAST_REG);
    object_property_add_child(container, "cframe-bcast", OBJECT(cframe_bcast));

    /* CFU APB */
    cfu_apb = qdev_new(TYPE_XLNX_VERSAL_CFU_APB);
    object_property_add_child(container, "cfu-apb", OBJECT(cfu_apb));

    /* IRQ or gate for cframes */
    cframe_irq_or = qdev_new(TYPE_OR_IRQ);
    object_property_add_child(container, "cframe-irq-or-gate",
                              OBJECT(cframe_irq_or));
    qdev_prop_set_uint16(cframe_irq_or, "num-lines", map->num_cframe);
    qdev_realize_and_unref(cframe_irq_or, NULL, &error_abort);
    versal_qdev_connect_gpio_out(s, cframe_irq_or, 0, map->cframe_irq);

    /* cframe reg */
    for (i = 0; i < map->num_cframe; i++) {
        uint64_t reg_base;
        uint64_t fdri_base;
        DeviceState *dev;
        g_autofree char *prop_name;
        size_t j;

        dev = qdev_new(TYPE_XLNX_VERSAL_CFRAME_REG);
        object_property_add_child(container, "cframe[*]", OBJECT(dev));

        sbd = SYS_BUS_DEVICE(dev);

        for (j = 0; j < ARRAY_SIZE(map->cframe_cfg[i].blktype_frames); j++) {
            g_autofree char *blktype_prop_name;

            blktype_prop_name = g_strdup_printf("blktype%zu-frames", j);
            object_property_set_int(OBJECT(dev), blktype_prop_name,
                                    map->cframe_cfg[i].blktype_frames[j],
                                    &error_abort);
        }

        object_property_set_link(OBJECT(dev), "cfu-fdro",
                                 OBJECT(cfu_fdro), &error_abort);

        sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_abort);

        reg_base = map->cframe_base + i * map->cframe_stride * 2;
        fdri_base = reg_base + map->cframe_stride;
        memory_region_add_subregion(&s->mr_ps, reg_base,
                                    sysbus_mmio_get_region(sbd, 0));
        memory_region_add_subregion(&s->mr_ps, fdri_base,
                                    sysbus_mmio_get_region(sbd, 1));
        sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(cframe_irq_or, i));

        prop_name = g_strdup_printf("cframe%d", i);
        object_property_set_link(OBJECT(cframe_bcast), prop_name,
                                 OBJECT(dev), &error_abort);
        object_property_set_link(OBJECT(cfu_apb), prop_name,
                                 OBJECT(dev), &error_abort);
    }

    sbd = SYS_BUS_DEVICE(cframe_bcast);
    sysbus_realize_and_unref(sbd, &error_abort);
    memory_region_add_subregion(&s->mr_ps, map->cframe_bcast_reg,
                                sysbus_mmio_get_region(sbd, 0));
    memory_region_add_subregion(&s->mr_ps, map->cframe_bcast_fdri,
                                sysbus_mmio_get_region(sbd, 1));

    sbd = SYS_BUS_DEVICE(cfu_apb);
    sysbus_realize_and_unref(sbd, &error_fatal);
    memory_region_add_subregion(&s->mr_ps, map->cfu_apb,
                                sysbus_mmio_get_region(sbd, 0));
    memory_region_add_subregion(&s->mr_ps, map->cfu_stream,
                                sysbus_mmio_get_region(sbd, 1));
    memory_region_add_subregion(&s->mr_ps, map->cfu_stream_2,
                                sysbus_mmio_get_region(sbd, 2));
    versal_sysbus_connect_irq(s, sbd, 0, map->cfu_apb_irq);

    /* CFU SFR */
    cfu_sfr = qdev_new(TYPE_XLNX_VERSAL_CFU_SFR);
    object_property_add_child(container, "cfu-sfr", OBJECT(cfu_sfr));
    sbd = SYS_BUS_DEVICE(cfu_sfr);

    object_property_set_link(OBJECT(cfu_sfr),
                            "cfu", OBJECT(cfu_apb), &error_abort);
    sysbus_realize_and_unref(sbd, &error_fatal);
    memory_region_add_subregion(&s->mr_ps, map->cfu_sfr,
                                sysbus_mmio_get_region(sbd, 0));
}

static inline void crl_connect_dev(Object *crl, Object *dev)
{
    const char *prop = object_get_canonical_path_component(dev);

    /* The component part of the device path matches the CRL property name */
    object_property_set_link(crl, prop, dev, &error_abort);
}

static inline void crl_connect_dev_by_name(Versal *s, Object *crl,
                                           const char *name, size_t num)
{
    size_t i;

    for (i = 0; i < num; i++) {
        Object *dev = versal_get_child_idx(s, name, i);

        crl_connect_dev(crl, dev);
    }
}

static inline void versal_create_crl(Versal *s)
{
    const VersalMap *map;
    VersalVersion ver;
    const char *crl_class;
    DeviceState *dev;
    size_t num_gem;
    Object *obj;

    map = versal_get_map(s);
    ver = versal_get_version(s);

    crl_class = xlnx_versal_crl_class_name(ver);
    dev = qdev_new(crl_class);
    obj = OBJECT(dev);
    object_property_add_child(OBJECT(s), "crl", obj);

    /*
     * The 3rd GEM controller on versal2 is in the MMI subsystem.
     * Its reset line is not connected to the CRL. Consider only the first two
     * ones.
     */
    num_gem = ver == VERSAL_VER_VERSAL2 ? 2 : map->num_gem;

    crl_connect_dev_by_name(s, obj, "rpu-cluster/rpu",
                            map->rpu.num_cluster * map->rpu.num_core);
    crl_connect_dev_by_name(s, obj, map->zdma[0].name, map->zdma[0].num_chan);
    crl_connect_dev_by_name(s, obj, "uart", map->num_uart);
    crl_connect_dev_by_name(s, obj, "gem", num_gem);
    crl_connect_dev_by_name(s, obj, "usb", map->num_usb);

    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_abort);

    memory_region_add_subregion(&s->mr_ps, map->crl.addr,
                                sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0));

    if (ver == VERSAL_VER_VERSAL) {
        /* CRL IRQ line has been removed in versal2 */
        versal_sysbus_connect_irq(s, SYS_BUS_DEVICE(dev), 0, map->crl.irq);
    }
}

/*
 * This takes the board allocated linear DDR memory and creates aliases
 * for each split DDR range/aperture on the Versal address map.
 */
static void versal_map_ddr(Versal *s, const struct VersalDDRMap *map)
{
    uint64_t size = memory_region_size(s->cfg.mr_ddr);
    uint64_t offset = 0;
    int i;

    for (i = 0; i < map->num_chan && size; i++) {
        uint64_t mapsize;
        MemoryRegion *alias;

        mapsize = MIN(size, map->chan[i].size);

        /* Create the MR alias.  */
        alias = g_new(MemoryRegion, 1);
        memory_region_init_alias(alias, OBJECT(s), "noc-ddr-range",
                                 s->cfg.mr_ddr, offset, mapsize);

        /* Map it onto the NoC MR.  */
        memory_region_add_subregion(&s->mr_ps, map->chan[i].addr, alias);
        offset += mapsize;
        size -= mapsize;
    }
}

void versal_fdt_add_memory_nodes(Versal *s, uint64_t size)
{
    const struct VersalDDRMap *map = &versal_get_map(s)->ddr;
    g_autofree char *node;
    g_autofree uint64_t *reg;
    int i;

    reg = g_new(uint64_t, map->num_chan * 2);

    for (i = 0; i < map->num_chan && size; i++) {
        uint64_t mapsize;

        mapsize = MIN(size, map->chan[i].size);

        reg[i * 2] = cpu_to_be64(map->chan[i].addr);
        reg[i * 2 + 1] = cpu_to_be64(mapsize);

        size -= mapsize;
    }

    node = versal_fdt_add_subnode(s, "/memory", 0, "memory", sizeof("memory"));
    qemu_fdt_setprop(s->cfg.fdt, node, "reg", reg, sizeof(uint64_t) * i * 2);
}

static void versal_unimp_area(Versal *s, const char *name,
                                MemoryRegion *mr,
                                hwaddr base, hwaddr size)
{
    DeviceState *dev = qdev_new(TYPE_UNIMPLEMENTED_DEVICE);
    MemoryRegion *mr_dev;

    qdev_prop_set_string(dev, "name", name);
    qdev_prop_set_uint64(dev, "size", size);
    object_property_add_child(OBJECT(s), name, OBJECT(dev));
    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);

    mr_dev = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
    memory_region_add_subregion(mr, base, mr_dev);
}

static void versal_unimp_sd_emmc_sel(void *opaque, int n, int level)
{
    qemu_log_mask(LOG_UNIMP,
                  "Selecting between enabling SD mode or eMMC mode on "
                  "controller %d is not yet implemented\n", n);
}

static void versal_unimp_qspi_ospi_mux_sel(void *opaque, int n, int level)
{
    qemu_log_mask(LOG_UNIMP,
                  "Selecting between enabling the QSPI or OSPI linear address "
                  "region is not yet implemented\n");
}

static void versal_unimp_irq_parity_imr(void *opaque, int n, int level)
{
    qemu_log_mask(LOG_UNIMP,
                  "PMC SLCR parity interrupt behaviour "
                  "is not yet implemented\n");
}

static void versal_unimp_common(Versal *s)
{
    DeviceState *slcr;
    qemu_irq gpio_in;

    versal_unimp_area(s, "crp", &s->mr_ps, 0xf1260000, 0x10000);

    qdev_init_gpio_in_named(DEVICE(s), versal_unimp_sd_emmc_sel,
                            "sd-emmc-sel-dummy", 2);
    qdev_init_gpio_in_named(DEVICE(s), versal_unimp_qspi_ospi_mux_sel,
                            "qspi-ospi-mux-sel-dummy", 1);
    qdev_init_gpio_in_named(DEVICE(s), versal_unimp_irq_parity_imr,
                            "irq-parity-imr-dummy", 1);

    slcr = DEVICE(versal_get_child(s, "pmc-iou-slcr"));
    gpio_in = qdev_get_gpio_in_named(DEVICE(s), "sd-emmc-sel-dummy", 0);
    qdev_connect_gpio_out_named(slcr, "sd-emmc-sel", 0, gpio_in);

    gpio_in = qdev_get_gpio_in_named(DEVICE(s), "sd-emmc-sel-dummy", 1);
    qdev_connect_gpio_out_named(slcr, "sd-emmc-sel", 1, gpio_in);

    gpio_in = qdev_get_gpio_in_named(DEVICE(s), "qspi-ospi-mux-sel-dummy", 0);
    qdev_connect_gpio_out_named(slcr, "qspi-ospi-mux-sel", 0, gpio_in);

    gpio_in = qdev_get_gpio_in_named(DEVICE(s), "irq-parity-imr-dummy", 0);
    qdev_connect_gpio_out_named(slcr, SYSBUS_DEVICE_GPIO_IRQ, 0, gpio_in);
}

static void versal_unimp(Versal *s)
{
    versal_unimp_area(s, "psm", &s->mr_ps, 0xffc80000, 0x70000);
    versal_unimp_area(s, "crf", &s->mr_ps, 0xfd1a0000, 0x140000);
    versal_unimp_area(s, "apu", &s->mr_ps, 0xfd5c0000, 0x100);
    versal_unimp_area(s, "iou-scntr", &s->mr_ps, 0xff130000, 0x10000);
    versal_unimp_area(s, "iou-scntr-secure", &s->mr_ps, 0xff140000, 0x10000);

    versal_unimp_common(s);
}

static void versal2_unimp(Versal *s)
{
    versal_unimp_area(s, "fpd-systmr-ctrl", &s->mr_ps, 0xec920000, 0x1000);
    versal_unimp_area(s, "crf", &s->mr_ps, 0xec200000, 0x100000);

    versal_unimp_common(s);
}

static uint32_t fdt_add_clk_node(Versal *s, const char *name,
                                 unsigned int freq_hz)
{
    uint32_t phandle;

    phandle = qemu_fdt_alloc_phandle(s->cfg.fdt);

    qemu_fdt_add_subnode(s->cfg.fdt, name);
    qemu_fdt_setprop_cell(s->cfg.fdt, name, "phandle", phandle);
    qemu_fdt_setprop_cell(s->cfg.fdt, name, "clock-frequency", freq_hz);
    qemu_fdt_setprop_cell(s->cfg.fdt, name, "#clock-cells", 0x0);
    qemu_fdt_setprop_string(s->cfg.fdt, name, "compatible", "fixed-clock");
    qemu_fdt_setprop(s->cfg.fdt, name, "u-boot,dm-pre-reloc", NULL, 0);

    return phandle;
}

static void versal_realize_common(Versal *s)
{
    DeviceState *slcr, *ospi;
    MemoryRegion *ocm;
    Object *container;
    const VersalMap *map = versal_get_map(s);
    size_t i;

    g_assert(s->cfg.fdt != NULL);

    s->phandle.clk_25mhz = fdt_add_clk_node(s, "/clk25", 25 * 1000 * 1000);
    s->phandle.clk_125mhz = fdt_add_clk_node(s, "/clk125", 125 * 1000 * 1000);
    s->phandle.gic = qemu_fdt_alloc_phandle(s->cfg.fdt);

    container = object_new(TYPE_CONTAINER);
    object_property_add_child(OBJECT(s), "irq-splits", container);
    object_unref(container);

    container = object_new(TYPE_CONTAINER);
    object_property_add_child(OBJECT(s), "irq-or-gates", container);
    object_unref(container);

    qemu_fdt_setprop_cell(s->cfg.fdt, "/", "interrupt-parent", s->phandle.gic);
    qemu_fdt_setprop_cell(s->cfg.fdt, "/", "#size-cells", 0x2);
    qemu_fdt_setprop_cell(s->cfg.fdt, "/", "#address-cells", 0x2);

    versal_create_cpu_cluster(s, &map->apu);
    versal_create_cpu_cluster(s, &map->rpu);

    for (i = 0; i < map->num_uart; i++) {
        versal_create_uart(s, &map->uart[i], i);
    }

    for (i = 0; i < map->num_canfd; i++) {
        versal_create_canfd(s, &map->canfd[i], s->cfg.canbus[i]);
    }

    for (i = 0; i < map->num_sdhci; i++) {
        versal_create_sdhci(s, &map->sdhci[i]);
    }

    for (i = 0; i < map->num_gem; i++) {
        versal_create_gem(s, &map->gem[i]);
        /*
         * Create fdt node in reverse order to keep backward compatibility with
         * previous versions of the generated FDT. This affects Linux kernel
         * interface naming order when persistent naming scheme is not in use.
         */
        versal_create_gem_fdt(s, &map->gem[map->num_gem - 1 - i]);
    }

    for (i = 0; i < map->num_zdma; i++) {
        versal_create_zdma(s, &map->zdma[i]);
    }

    versal_create_xrams(s, &map->xram);

    for (i = 0; i < map->num_usb; i++) {
        versal_create_usb(s, &map->usb[i]);
    }

    versal_create_efuse(s, &map->efuse);
    ospi = versal_create_ospi(s, &map->ospi);
    slcr = versal_create_pmc_iou_slcr(s, &map->pmc_iou_slcr);

    qdev_connect_gpio_out_named(slcr, "ospi-mux-sel", 0,
                                qdev_get_gpio_in_named(ospi,
                                                       "ospi-mux-sel", 0));

    versal_create_bbram(s, &map->bbram);
    versal_create_trng(s, &map->trng);
    versal_create_rtc(s, &map->rtc);
    versal_create_cfu(s, &map->cfu);
    versal_create_crl(s);

    versal_map_ddr(s, &map->ddr);

    /* Create the On Chip Memory (OCM).  */
    ocm = g_new(MemoryRegion, 1);
    memory_region_init_ram(ocm, OBJECT(s), "ocm", map->ocm.size, &error_fatal);
    memory_region_add_subregion_overlap(&s->mr_ps, map->ocm.addr, ocm, 0);
}

static void versal_realize(DeviceState *dev, Error **errp)
{
    Versal *s = XLNX_VERSAL_BASE(dev);

    versal_realize_common(s);
    versal_unimp(s);
}

static void versal2_realize(DeviceState *dev, Error **errp)
{
    Versal *s = XLNX_VERSAL_BASE(dev);

    versal_realize_common(s);
    versal2_unimp(s);
}

DeviceState *versal_get_boot_cpu(Versal *s)
{
    return DEVICE(versal_get_child_idx(s, "apu-cluster/apu", 0));
}

void versal_sdhci_plug_card(Versal *s, int sd_idx, BlockBackend *blk)
{
    DeviceState *sdhci, *card;

    sdhci = DEVICE(versal_get_child_idx(s, "sdhci", sd_idx));

    if (sdhci == NULL) {
        return;
    }

    card = qdev_new(TYPE_SD_CARD);
    object_property_add_child(OBJECT(sdhci), "card[*]", OBJECT(card));
    qdev_prop_set_drive_err(card, "drive", blk, &error_fatal);
    qdev_realize_and_unref(card, qdev_get_child_bus(DEVICE(sdhci), "sd-bus"),
                           &error_fatal);
}

void versal_efuse_attach_drive(Versal *s, BlockBackend *blk)
{
    DeviceState *efuse;

    efuse = DEVICE(versal_get_child(s, "efuse"));

    if (efuse == NULL) {
        return;
    }

    qdev_prop_set_drive(efuse, "drive", blk);
}

void versal_bbram_attach_drive(Versal *s, BlockBackend *blk)
{
    DeviceState *bbram;

    bbram = DEVICE(versal_get_child(s, "bbram"));

    if (bbram == NULL) {
        return;
    }

    qdev_prop_set_drive(bbram, "drive", blk);
}

void versal_ospi_create_flash(Versal *s, int flash_idx, const char *flash_mdl,
                              BlockBackend *blk)
{
    BusState *spi_bus;
    DeviceState *flash, *ospi;
    qemu_irq cs_line;

    ospi = DEVICE(versal_get_child(s, "ospi"));
    spi_bus = qdev_get_child_bus(ospi, "spi0");

    flash = qdev_new(flash_mdl);

    if (blk) {
        qdev_prop_set_drive_err(flash, "drive", blk, &error_fatal);
    }
    qdev_prop_set_uint8(flash, "cs", flash_idx);
    qdev_realize_and_unref(flash, spi_bus, &error_fatal);

    cs_line = qdev_get_gpio_in_named(flash, SSI_GPIO_CS, 0);

    sysbus_connect_irq(SYS_BUS_DEVICE(ospi),
                       flash_idx + 1, cs_line);
}

qemu_irq versal_get_reserved_irq(Versal *s, int idx, int *dtb_idx)
{
    const VersalMap *map = versal_get_map(s);

    g_assert(idx < map->reserved.irq_num);

    *dtb_idx = map->reserved.irq_start + idx;
    return versal_get_irq(s, *dtb_idx);
}

hwaddr versal_get_reserved_mmio_addr(Versal *s)
{
    const VersalMap *map = versal_get_map(s);

    return map->reserved.mmio_start;
}

int versal_get_num_cpu(VersalVersion version)
{
    const VersalMap *map = VERSION_TO_MAP[version];

    return map->apu.num_cluster * map->apu.num_core
        + map->rpu.num_cluster * map->rpu.num_core;
}

int versal_get_num_can(VersalVersion version)
{
    const VersalMap *map = VERSION_TO_MAP[version];

    return map->num_canfd;
}

int versal_get_num_sdhci(VersalVersion version)
{
    const VersalMap *map = VERSION_TO_MAP[version];

    return map->num_sdhci;
}

static void versal_base_init(Object *obj)
{
    Versal *s = XLNX_VERSAL_BASE(obj);
    size_t i, num_can;

    memory_region_init(&s->mr_ps, obj, "mr-ps-switch", UINT64_MAX);
    s->intc = g_array_new(false, false, sizeof(DeviceState *));

    num_can = versal_get_map(s)->num_canfd;
    s->cfg.canbus = g_new0(CanBusState *, num_can);

    for (i = 0; i < num_can; i++) {
        g_autofree char *prop_name = g_strdup_printf("canbus%zu", i);

        object_property_add_link(obj, prop_name, TYPE_CAN_BUS,
                                 (Object **) &s->cfg.canbus[i],
                                 object_property_allow_set_link, 0);
    }
}

static void versal_base_finalize(Object *obj)
{
    Versal *s = XLNX_VERSAL_BASE(obj);

    g_array_free(s->intc, true);
    g_free(s->cfg.canbus);
}

static const Property versal_properties[] = {
    DEFINE_PROP_LINK("ddr", Versal, cfg.mr_ddr, TYPE_MEMORY_REGION,
                     MemoryRegion *),
};

static void versal_base_class_init(ObjectClass *klass, const void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    device_class_set_props(dc, versal_properties);
    /* No VMSD since we haven't got any top-level SoC state to save.  */
}

static void versal_class_init(ObjectClass *klass, const void *data)
{
    VersalClass *vc = XLNX_VERSAL_BASE_CLASS(klass);
    DeviceClass *dc = DEVICE_CLASS(klass);

    vc->version = VERSAL_VER_VERSAL;
    dc->realize = versal_realize;
}

static void versal2_class_init(ObjectClass *klass, const void *data)
{
    VersalClass *vc = XLNX_VERSAL_BASE_CLASS(klass);
    DeviceClass *dc = DEVICE_CLASS(klass);

    vc->version = VERSAL_VER_VERSAL2;
    dc->realize = versal2_realize;
}

static const TypeInfo versal_base_info = {
    .name = TYPE_XLNX_VERSAL_BASE,
    .parent = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(Versal),
    .instance_init = versal_base_init,
    .instance_finalize = versal_base_finalize,
    .class_init = versal_base_class_init,
    .class_size = sizeof(VersalClass),
    .abstract = true,
};

static const TypeInfo versal_info = {
    .name = TYPE_XLNX_VERSAL,
    .parent = TYPE_XLNX_VERSAL_BASE,
    .class_init = versal_class_init,
};

static const TypeInfo versal2_info = {
    .name = TYPE_XLNX_VERSAL2,
    .parent = TYPE_XLNX_VERSAL_BASE,
    .class_init = versal2_class_init,
};

static void versal_register_types(void)
{
    type_register_static(&versal_base_info);
    type_register_static(&versal_info);
    type_register_static(&versal2_info);
}

type_init(versal_register_types);
