qemu/hw/misc/xilinx_zynqmp_apu_ctrl.c
<<
>>
Prefs
   1/*
   2 * QEMU model of ZynqMP APU Core Functionality
   3 *
   4 * For the most part, a dummy device model.
   5 *
   6 * Copyright (c) 2013 Peter Xilinx Inc
   7 * Copyright (c) 2013 Peter Crosthwaite <peter.crosthwaite@xilinx.com>
   8 *
   9 * Permission is hereby granted, free of charge, to any person obtaining a copy
  10 * of this software and associated documentation files (the "Software"), to deal
  11 * in the Software without restriction, including without limitation the rights
  12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13 * copies of the Software, and to permit persons to whom the Software is
  14 * furnished to do so, subject to the following conditions:
  15 *
  16 * The above copyright notice and this permission notice shall be included in
  17 * all copies or substantial portions of the Software.
  18 *
  19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  22 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25 * THE SOFTWARE.
  26 */
  27
  28#include "qemu/osdep.h"
  29#include "qapi/error.h"
  30#include "hw/sysbus.h"
  31#include "qemu/log.h"
  32#include "cpu.h"
  33
  34#include "qemu/bitops.h"
  35#include "qapi/qmp/qerror.h"
  36#include "hw/register.h"
  37#include "hw/fdt_generic_util.h"
  38
  39#ifndef ZYNQMP_APU_ERR_DEBUG
  40#define ZYNQMP_APU_ERR_DEBUG 0
  41#endif
  42
  43#define TYPE_ZYNQMP_APU "xlnx.apu"
  44
  45#define ZYNQMP_APU(obj) \
  46     OBJECT_CHECK(ZynqMPAPU, (obj), TYPE_ZYNQMP_APU)
  47
  48#ifndef XILINX_ZYNQMP_APU_ERR_DEBUG
  49#define XILINX_ZYNQMP_APU_ERR_DEBUG 0
  50#endif
  51
  52#define DB_PRINT_L(lvl, fmt, args...) do {\
  53    if (XILINX_ZYNQMP_APU_ERR_DEBUG >= lvl) {\
  54        qemu_log(TYPE_ZYNQMP_APU ": %s:" fmt, __func__, ## args);\
  55    } \
  56} while (0);
  57
  58#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args)
  59
  60REG32(RVBARADDR0L, 0x40)
  61REG32(RVBARADDR0H, 0x44)
  62REG32(RVBARADDR1L, 0x48)
  63REG32(RVBARADDR1H, 0x4c)
  64REG32(RVBARADDR2L, 0x50)
  65REG32(RVBARADDR2H, 0x54)
  66REG32(RVBARADDR3L, 0x58)
  67REG32(RVBARADDR3H, 0x5c)
  68REG32(PWRCTL, 0x90)
  69    FIELD(PWRCTL, CPUPWRDWNREQ, 3, 0)
  70
  71#define R_MAX ((R_PWRCTL) + 1)
  72
  73#define NUM_CPUS 4
  74
  75typedef struct ZynqMPAPU ZynqMPAPU;
  76
  77struct ZynqMPAPU {
  78    SysBusDevice busdev;
  79    MemoryRegion iomem;
  80
  81    ARMCPU *cpus[NUM_CPUS];
  82    /* WFIs towards PMU. */
  83    qemu_irq wfi_out[4];
  84    /* CPU Power status towards INTC Redirect. */
  85    qemu_irq cpu_power_status[4];
  86
  87    uint8_t cpu_pwrdwn_req;
  88    uint8_t cpu_in_wfi;
  89
  90    uint32_t regs[R_MAX];
  91    RegisterInfo regs_info[R_MAX];
  92};
  93
  94static void update_wfi_out(void *opaque)
  95{
  96    ZynqMPAPU *s = ZYNQMP_APU(opaque);
  97    unsigned int i, wfi_pending;
  98
  99    wfi_pending = s->cpu_pwrdwn_req & s->cpu_in_wfi;
 100    for (i = 0; i < NUM_CPUS; i++) {
 101        qemu_set_irq(s->wfi_out[i], !!(wfi_pending & (1 << i)));
 102    }
 103}
 104
 105static void zynqmp_apu_reset(DeviceState *dev)
 106{
 107    ZynqMPAPU *s = ZYNQMP_APU(dev);
 108    int i;
 109 
 110    for (i = 0; i < R_MAX; ++i) {
 111        register_reset(&s->regs_info[i]);
 112    }
 113
 114    s->cpu_pwrdwn_req = 0;
 115    s->cpu_in_wfi = 0;
 116    update_wfi_out(s);
 117}
 118
 119static void zynqmp_apu_rvbar_post_write(RegisterInfo *reg, uint64_t val)
 120{
 121    ZynqMPAPU *s = ZYNQMP_APU(reg->opaque);
 122    int i;
 123
 124    for (i = 0; i < NUM_CPUS; ++i) {
 125        uint64_t rvbar = s->regs[R_RVBARADDR0L + 2 * i] +
 126                         ((uint64_t)s->regs[R_RVBARADDR0H + 2 * i] << 32);
 127        object_property_set_int(OBJECT(s->cpus[i]), rvbar, "rvbar",
 128                                &error_abort);
 129        DB_PRINT("Set RVBAR %d to %" PRIx64 "\n", i, rvbar);
 130    }
 131}
 132
 133static void zynqmp_apu_pwrctl_post_write(RegisterInfo *reg, uint64_t val)
 134{
 135    ZynqMPAPU *s = ZYNQMP_APU(reg->opaque);
 136    unsigned int i, new;
 137
 138    for (i = 0; i < NUM_CPUS; i++) {
 139        new = val & (1 << i);
 140        /* Check if CPU's CPUPWRDNREQ has changed. If yes, update GPIOs. */
 141        if (new != (s->cpu_pwrdwn_req & (1 << i))) {
 142            qemu_set_irq(s->cpu_power_status[i], !!new);
 143        }
 144        s->cpu_pwrdwn_req &= ~(1 << i);
 145        s->cpu_pwrdwn_req |= new;
 146    }
 147    update_wfi_out(s);
 148}
 149
 150static const RegisterAccessInfo zynqmp_apu_regs_info[] = {
 151#define RVBAR_REGDEF(n) \
 152    {   .name = "RVBAR CPU " #n " Low",  .decode.addr = A_RVBARADDR ## n ## L, \
 153            .reset = 0xffff0000ul,                                             \
 154            .post_write = zynqmp_apu_rvbar_post_write,                        \
 155    },{ .name = "RVBAR CPU " #n " High", .decode.addr = A_RVBARADDR ## n ## H, \
 156            .post_write = zynqmp_apu_rvbar_post_write,                        \
 157    }
 158    RVBAR_REGDEF(0),
 159    RVBAR_REGDEF(1),
 160    RVBAR_REGDEF(2),
 161    RVBAR_REGDEF(3), { .name = "PWRCTL",  .decode.addr = A_PWRCTL,
 162        .post_write = zynqmp_apu_pwrctl_post_write,
 163    }
 164};
 165
 166static const MemoryRegionOps zynqmp_apu_ops = {
 167    .read = register_read_memory_le,
 168    .write = register_write_memory_le,
 169    .endianness = DEVICE_LITTLE_ENDIAN,
 170    .valid = {
 171        .min_access_size = 4,
 172        .max_access_size = 4,
 173    }
 174};
 175
 176static void zynqmp_apu_handle_wfi(void *opaque, int irq, int level)
 177{
 178    ZynqMPAPU *s = ZYNQMP_APU(opaque);
 179
 180    s->cpu_in_wfi = deposit32(s->cpu_in_wfi, irq, 1, level);
 181    update_wfi_out(s);
 182}
 183
 184static void zynqmp_apu_realize(DeviceState *dev, Error **errp)
 185{
 186    ZynqMPAPU *s = ZYNQMP_APU(dev);
 187    const char *prefix = object_get_canonical_path(OBJECT(dev));
 188    int i;
 189
 190    for (i = 0; i < ARRAY_SIZE(zynqmp_apu_regs_info); ++i) {
 191        RegisterInfo *r = &s->regs_info[i];
 192
 193        *r = (RegisterInfo) {
 194            .data = (uint8_t *)&s->regs[
 195                    zynqmp_apu_regs_info[i].decode.addr/4],
 196            .data_size = sizeof(uint32_t),
 197            .access = &zynqmp_apu_regs_info[i],
 198            .debug = ZYNQMP_APU_ERR_DEBUG,
 199            .prefix = prefix,
 200            .opaque = s,
 201        };
 202        memory_region_init_io(&r->mem, OBJECT(dev), &zynqmp_apu_ops, r,
 203                              r->access->name, 4);
 204        memory_region_add_subregion(&s->iomem, r->access->decode.addr, &r->mem);
 205    }
 206    return;
 207}
 208
 209static void zynqmp_apu_init(Object *obj)
 210{
 211    ZynqMPAPU *s = ZYNQMP_APU(obj);
 212    int i;
 213
 214    memory_region_init(&s->iomem, obj, "MMIO", R_MAX * 4);
 215    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
 216
 217    for (i = 0; i < NUM_CPUS; ++i) {
 218        char *prop_name = g_strdup_printf("cpu%d", i);
 219        object_property_add_link(obj, prop_name, TYPE_ARM_CPU,
 220                                 (Object **)&s->cpus[i],
 221                                 qdev_prop_allow_set_link_before_realize,
 222                                 OBJ_PROP_LINK_UNREF_ON_RELEASE,
 223                                 &error_abort);
 224        g_free(prop_name);
 225    }
 226
 227    /* wfi_out is used to connect to PMU GPIs. */
 228    qdev_init_gpio_out_named(DEVICE(obj), s->wfi_out, "wfi_out", 4);
 229    /* CPU_POWER_STATUS is used to connect to INTC redirect. */
 230    qdev_init_gpio_out_named(DEVICE(obj), s->cpu_power_status,
 231                             "CPU_POWER_STATUS", 4);
 232    /* wfi_in is used as input from CPUs as wfi request. */
 233    qdev_init_gpio_in_named(DEVICE(obj), zynqmp_apu_handle_wfi, "wfi_in", 4);
 234}
 235
 236static const VMStateDescription vmstate_zynqmp_apu = {
 237    .name = "zynqmp_apu",
 238    .version_id = 1,
 239    .minimum_version_id = 1,
 240    .minimum_version_id_old = 1,
 241    .fields = (VMStateField[]) {
 242        VMSTATE_UINT32_ARRAY(regs, ZynqMPAPU, R_MAX),
 243        VMSTATE_END_OF_LIST(),
 244    }
 245};
 246
 247static const FDTGenericGPIOSet zynqmp_apu_controller_gpios[] = {
 248    {
 249        .names = &fdt_generic_gpio_name_set_gpio,
 250        .gpios = (FDTGenericGPIOConnection[])  {
 251            { .name = "wfi_in", .fdt_index = 0, .range = 4 },
 252            { .name = "CPU_POWER_STATUS", .fdt_index = 4, .range = 4 },
 253            { },
 254        },
 255    },
 256    { },
 257};
 258
 259static const FDTGenericGPIOSet zynqmp_apu_client_gpios[] = {
 260    {
 261        .names = &fdt_generic_gpio_name_set_gpio,
 262        .gpios = (FDTGenericGPIOConnection[])  {
 263            { .name = "wfi_out",          .fdt_index = 0, .range = 4 },
 264            { },
 265        },
 266    },
 267    { },
 268};
 269
 270static void zynqmp_apu_class_init(ObjectClass *klass, void *data)
 271{
 272    DeviceClass *dc = DEVICE_CLASS(klass);
 273    FDTGenericGPIOClass *fggc = FDT_GENERIC_GPIO_CLASS(klass);
 274
 275    dc->reset = zynqmp_apu_reset;
 276    dc->realize = zynqmp_apu_realize;
 277    dc->vmsd = &vmstate_zynqmp_apu;
 278    fggc->controller_gpios = zynqmp_apu_controller_gpios;
 279    fggc->client_gpios = zynqmp_apu_client_gpios;
 280}
 281
 282static const TypeInfo zynqmp_apu_info = {
 283    .name          = TYPE_ZYNQMP_APU,
 284    .parent        = TYPE_SYS_BUS_DEVICE,
 285    .instance_size = sizeof(ZynqMPAPU),
 286    .class_init    = zynqmp_apu_class_init,
 287    .instance_init = zynqmp_apu_init,
 288    .interfaces    = (InterfaceInfo[]) {
 289        { TYPE_FDT_GENERIC_GPIO },
 290        { }
 291    },
 292};
 293
 294static void zynqmp_apu_register_types(void)
 295{
 296    type_register_static(&zynqmp_apu_info);
 297}
 298
 299type_init(zynqmp_apu_register_types)
 300