/*
 * QEMU model of the CRF APB control registers for clock controller.
 *
 * Copyright (c) 2018 Xilinx Inc.
 *
 * Autogenerated by xregqemu.py 2018-08-16.
 * Written by Edgar E. Iglesias <edgar.iglesias@xilinx.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "qemu/osdep.h"
#include "hw/sysbus.h"
#include "hw/register.h"
#include "qemu/bitops.h"
#include "qemu/log.h"
#include "migration/vmstate.h"
#include "hw/qdev-properties.h"
#include "hw/arm/linux-boot-if.h"

#include "hw/fdt_generic_util.h"

#ifndef XILINX_CRF_ERR_DEBUG
#define XILINX_CRF_ERR_DEBUG 0
#endif

#define TYPE_XILINX_CRF "xlnx.versal-crf"

#define XILINX_CRF(obj) \
     OBJECT_CHECK(XlnxZynq3Crf, (obj), TYPE_XILINX_CRF)

REG32(ERR_CTRL, 0x0)
    FIELD(ERR_CTRL, SLVERR_ENABLE, 0, 1)
REG32(IR_STATUS, 0x4)
    FIELD(IR_STATUS, ADDR_DECODE_ERR, 0, 1)
REG32(IR_MASK, 0x8)
    FIELD(IR_MASK, ADDR_DECODE_ERR, 0, 1)
REG32(IR_ENABLE, 0xc)
    FIELD(IR_ENABLE, ADDR_DECODE_ERR, 0, 1)
REG32(IR_DISABLE, 0x10)
    FIELD(IR_DISABLE, ADDR_DECODE_ERR, 0, 1)
REG32(WPROT, 0x1c)
    FIELD(WPROT, ACTIVE, 0, 1)
REG32(APLL_CTRL, 0x40)
    FIELD(APLL_CTRL, POST_SRC, 24, 3)
    FIELD(APLL_CTRL, PRE_SRC, 20, 3)
    FIELD(APLL_CTRL, CLKOUTDIV, 16, 2)
    FIELD(APLL_CTRL, FBDIV, 8, 8)
    FIELD(APLL_CTRL, BYPASS, 3, 1)
    FIELD(APLL_CTRL, RESET, 0, 1)
REG32(APLL_CFG, 0x44)
    FIELD(APLL_CFG, LOCK_DLY, 25, 7)
    FIELD(APLL_CFG, LOCK_CNT, 13, 10)
    FIELD(APLL_CFG, LFHF, 10, 2)
    FIELD(APLL_CFG, CP, 5, 4)
    FIELD(APLL_CFG, RES, 0, 4)
REG32(APLL_FRAC_CFG, 0x48)
    FIELD(APLL_FRAC_CFG, ENABLED, 31, 1)
    FIELD(APLL_FRAC_CFG, SEED, 22, 3)
    FIELD(APLL_FRAC_CFG, ALGRTHM, 19, 1)
    FIELD(APLL_FRAC_CFG, ORDER, 18, 1)
    FIELD(APLL_FRAC_CFG, DATA, 0, 16)
REG32(PLL_STATUS, 0x50)
    FIELD(PLL_STATUS, APLL_STABLE, 2, 1)
    FIELD(PLL_STATUS, APLL_LOCK, 0, 1)
REG32(APLL_TO_XPD_CTRL, 0x100)
    FIELD(APLL_TO_XPD_CTRL, CLKACT, 25, 1)
    FIELD(APLL_TO_XPD_CTRL, DIVISOR0, 8, 10)
REG32(FPD_TOP_SWITCH_CTRL, 0x104)
    FIELD(FPD_TOP_SWITCH_CTRL, CLKACT, 25, 1)
    FIELD(FPD_TOP_SWITCH_CTRL, DIVISOR0, 8, 10)
    FIELD(FPD_TOP_SWITCH_CTRL, SRCSEL, 0, 3)
REG32(FPD_LSBUS_CTRL, 0x108)
    FIELD(FPD_LSBUS_CTRL, CLKACT, 25, 1)
    FIELD(FPD_LSBUS_CTRL, DIVISOR0, 8, 10)
    FIELD(FPD_LSBUS_CTRL, SRCSEL, 0, 3)
REG32(ACPU_CTRL, 0x10c)
    FIELD(ACPU_CTRL, CLKACT, 25, 1)
    FIELD(ACPU_CTRL, DIVISOR0, 8, 10)
    FIELD(ACPU_CTRL, SRCSEL, 0, 3)
REG32(DBG_TRACE_CTRL, 0x110)
    FIELD(DBG_TRACE_CTRL, CLKACT, 25, 1)
    FIELD(DBG_TRACE_CTRL, DIVISOR0, 8, 10)
    FIELD(DBG_TRACE_CTRL, SRCSEL, 0, 3)
REG32(DBG_FPD_CTRL, 0x114)
    FIELD(DBG_FPD_CTRL, CLKACT, 25, 1)
    FIELD(DBG_FPD_CTRL, DIVISOR0, 8, 10)
    FIELD(DBG_FPD_CTRL, SRCSEL, 0, 3)
REG32(SAFETY_CHK, 0x150)
REG32(RST_APU, 0x300)
    FIELD(RST_APU, ACPU3_PWRON, 13, 1)
    FIELD(RST_APU, ACPU2_PWRON, 12, 1)
    FIELD(RST_APU, ACPU1_PWRON, 11, 1)
    FIELD(RST_APU, ACPU0_PWRON, 10, 1)
    FIELD(RST_APU, ACPU_L2_RESET, 8, 1)
    FIELD(RST_APU, ACPU_GIC_RESET, 7, 1)
    FIELD(RST_APU, ACPU3, 3, 1)
    FIELD(RST_APU, ACPU2, 2, 1)
    FIELD(RST_APU, ACPU1, 1, 1)
    FIELD(RST_APU, ACPU0, 0, 1)
REG32(RST_DBG_FPD, 0x30c)
    FIELD(RST_DBG_FPD, RESET, 0, 1)
REG32(RST_FPD_SWDT, 0x314)
    FIELD(RST_FPD_SWDT, RESET, 0, 1)
REG32(RST_SYSMON, 0x318)
    FIELD(RST_SYSMON, SEQ_RST, 1, 1)
    FIELD(RST_SYSMON, CFG_RST, 0, 1)

#define CRF_R_MAX (R_RST_SYSMON + 1)

typedef struct XlnxZynq3Crf {
    SysBusDevice parent_obj;
    MemoryRegion iomem;
    qemu_irq irq_ir;

    qemu_irq rst_acpu[4];
    qemu_irq rst_acpu_gic;
    qemu_irq rst_acpu_l2;
    qemu_irq rst_acpu_pwron[4];
    qemu_irq rst_dbg_fpd;
    qemu_irq rst_fpd_swdt;
    qemu_irq rst_sysmon_cfg;
    qemu_irq rst_sysmon_seq;

    bool linux_direct_boot;
    uint32_t regs[CRF_R_MAX];
    RegisterInfo regs_info[CRF_R_MAX];
} XlnxZynq3Crf;

#define PROPAGATE_GPIO(reg, f, irq) { \
    bool val = ARRAY_FIELD_EX32(s->regs, reg, f); \
    qemu_set_irq(irq, val); \
}

static void ir_update_irq(XlnxZynq3Crf *s)
{
    bool pending = s->regs[R_IR_STATUS] & ~s->regs[R_IR_MASK];
    qemu_set_irq(s->irq_ir, pending);
}

static void ir_status_postw(RegisterInfo *reg, uint64_t val64)
{
    XlnxZynq3Crf *s = XILINX_CRF(reg->opaque);
    ir_update_irq(s);
}

static uint64_t ir_enable_prew(RegisterInfo *reg, uint64_t val64)
{
    XlnxZynq3Crf *s = XILINX_CRF(reg->opaque);
    uint32_t val = val64;

    s->regs[R_IR_MASK] &= ~val;
    ir_update_irq(s);
    return 0;
}

static uint64_t ir_disable_prew(RegisterInfo *reg, uint64_t val64)
{
    XlnxZynq3Crf *s = XILINX_CRF(reg->opaque);
    uint32_t val = val64;

    s->regs[R_IR_MASK] |= val;
    ir_update_irq(s);
    return 0;
}

static void crf_update_gpios(XlnxZynq3Crf *s)
{
    if (!s->linux_direct_boot) {
        PROPAGATE_GPIO(RST_APU, ACPU0, s->rst_acpu[0]);
        PROPAGATE_GPIO(RST_APU, ACPU1, s->rst_acpu[1]);
        PROPAGATE_GPIO(RST_APU, ACPU2, s->rst_acpu[2]);
        PROPAGATE_GPIO(RST_APU, ACPU3, s->rst_acpu[3]);
        PROPAGATE_GPIO(RST_APU, ACPU_GIC_RESET, s->rst_acpu_gic);
        PROPAGATE_GPIO(RST_APU, ACPU_L2_RESET, s->rst_acpu_l2);
        PROPAGATE_GPIO(RST_APU, ACPU0_PWRON, s->rst_acpu_pwron[0]);
        PROPAGATE_GPIO(RST_APU, ACPU1_PWRON, s->rst_acpu_pwron[1]);
        PROPAGATE_GPIO(RST_APU, ACPU2_PWRON, s->rst_acpu_pwron[2]);
        PROPAGATE_GPIO(RST_APU, ACPU3_PWRON, s->rst_acpu_pwron[3]);
    }
    PROPAGATE_GPIO(RST_DBG_FPD, RESET, s->rst_dbg_fpd);
    PROPAGATE_GPIO(RST_FPD_SWDT, RESET, s->rst_fpd_swdt);
    PROPAGATE_GPIO(RST_SYSMON, CFG_RST, s->rst_sysmon_cfg);
    PROPAGATE_GPIO(RST_SYSMON, SEQ_RST, s->rst_sysmon_seq);
}

static void crf_update_gpios_pw(RegisterInfo *reg, uint64_t val64)
{
    XlnxZynq3Crf *s = XILINX_CRF(reg->opaque);
    crf_update_gpios(s);
}

static const RegisterAccessInfo crf_regs_info[] = {
    {   .name = "ERR_CTRL",  .addr = A_ERR_CTRL,
    },{ .name = "IR_STATUS",  .addr = A_IR_STATUS,
        .w1c = 0x1,
        .post_write = ir_status_postw,
    },{ .name = "IR_MASK",  .addr = A_IR_MASK,
        .reset = 0x1,
        .ro = 0x1,
    },{ .name = "IR_ENABLE",  .addr = A_IR_ENABLE,
        .pre_write = ir_enable_prew,
    },{ .name = "IR_DISABLE",  .addr = A_IR_DISABLE,
        .pre_write = ir_disable_prew,
    },{ .name = "WPROT",  .addr = A_WPROT,
    },{ .name = "APLL_CTRL",  .addr = A_APLL_CTRL,
        .reset = 0x24809,
        .rsvd = 0xf88c00f6,
    },{ .name = "APLL_CFG",  .addr = A_APLL_CFG,
        .reset = 0x2000000,
        .rsvd = 0x1801210,
    },{ .name = "APLL_FRAC_CFG",  .addr = A_APLL_FRAC_CFG,
        .rsvd = 0x7e330000,
    },{ .name = "PLL_STATUS",  .addr = A_PLL_STATUS,
        .reset = R_PLL_STATUS_APLL_LOCK_MASK |
                 R_PLL_STATUS_APLL_STABLE_MASK,
        .rsvd = 0xfa,
        .ro = 0x5,
    },{ .name = "APLL_TO_XPD_CTRL",  .addr = A_APLL_TO_XPD_CTRL,
        .reset = 0x2000100,
        .rsvd = 0xfdfc00ff,
    },{ .name = "FPD_TOP_SWITCH_CTRL",  .addr = A_FPD_TOP_SWITCH_CTRL,
        .reset = 0x2000200,
        .rsvd = 0xfdfc00f8,
    },{ .name = "FPD_LSBUS_CTRL",  .addr = A_FPD_LSBUS_CTRL,
        .reset = 0x2000800,
        .rsvd = 0xfdfc00f8,
    },{ .name = "ACPU_CTRL",  .addr = A_ACPU_CTRL,
        .reset = 0x2000200,
        .rsvd = 0xfdfc00f8,
    },{ .name = "DBG_TRACE_CTRL",  .addr = A_DBG_TRACE_CTRL,
        .reset = 0x500,
        .rsvd = 0xfdfc00f8,
    },{ .name = "DBG_FPD_CTRL",  .addr = A_DBG_FPD_CTRL,
        .reset = 0x2000300,
        .rsvd = 0xfdfc00f8,
    },{ .name = "SAFETY_CHK",  .addr = A_SAFETY_CHK,
    },{ .name = "RST_APU",  .addr = A_RST_APU,
        .reset = 0x3d0f,
        .rsvd = 0xc270,
        .post_write = crf_update_gpios_pw,
    },{ .name = "RST_DBG_FPD",  .addr = A_RST_DBG_FPD,
        .reset = 0x1,
        .post_write = crf_update_gpios_pw,
    },{ .name = "RST_FPD_SWDT",  .addr = A_RST_FPD_SWDT,
        .reset = 0x1,
        .post_write = crf_update_gpios_pw,
    },{ .name = "RST_SYSMON",  .addr = A_RST_SYSMON,
        .post_write = crf_update_gpios_pw,
    }
};

static void crf_reset(DeviceState *dev)
{
    XlnxZynq3Crf *s = XILINX_CRF(dev);
    unsigned int i;

    for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) {
        register_reset(&s->regs_info[i]);
    }
    ir_update_irq(s);
    crf_update_gpios(s);
}

static void crf_linux_boot_if_init(ARMLinuxBootIf *obj, bool secure_boot)
{
    XlnxZynq3Crf *s = XILINX_CRF(obj);
    int i;

    s->linux_direct_boot = true;
    for (i = 0; i < 4; i++) {
        qemu_set_irq(s->rst_acpu[i], false);
    }
}

static const MemoryRegionOps crf_ops = {
    .read = register_read_memory,
    .write_with_attrs = register_write_memory_with_attrs,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = {
        .min_access_size = 4,
        .max_access_size = 4,
    },
};

static void crf_realize(DeviceState *dev, Error **errp)
{
    /* Delete this if you don't need it */
}

static void crf_init(Object *obj)
{
    XlnxZynq3Crf *s = XILINX_CRF(obj);
    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
    RegisterInfoArray *reg_array;

    memory_region_init(&s->iomem, obj, TYPE_XILINX_CRF, CRF_R_MAX * 4);
    reg_array =
        register_init_block32(DEVICE(obj), crf_regs_info,
                              ARRAY_SIZE(crf_regs_info),
                              s->regs_info, s->regs,
                              &crf_ops,
                              XILINX_CRF_ERR_DEBUG,
                              CRF_R_MAX * 4);
    memory_region_add_subregion(&s->iomem,
                                0x0,
                                &reg_array->mem);
    sysbus_init_mmio(sbd, &s->iomem);
    sysbus_init_irq(sbd, &s->irq_ir);

    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_acpu[0], "rst-acpu0", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_acpu[1], "rst-acpu1", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_acpu[2], "rst-acpu2", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_acpu[3], "rst-acpu3", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_acpu_gic, "rst-acpu-gic", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_acpu_l2, "rst-acpu-l2", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_acpu_pwron[0],
                             "rst-acpu0-pwron", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_acpu_pwron[1],
                             "rst-acpu1-pwron", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_acpu_pwron[2],
                             "rst-acpu2-pwron", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_acpu_pwron[3],
                             "rst-acpu3-pwron", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_dbg_fpd, "rst-dbg-fpd", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_fpd_swdt, "rst-fpd-swdt", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_sysmon_cfg,
                             "rst-sysmon-cfg", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->rst_sysmon_seq,
                             "rst-sysmon-seq", 1);
}

static const VMStateDescription vmstate_crf = {
    .name = TYPE_XILINX_CRF,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT32_ARRAY(regs, XlnxZynq3Crf, CRF_R_MAX),
        VMSTATE_END_OF_LIST(),
    }
};

static const FDTGenericGPIOSet crf_gpios[] = {
    {
      .names = &fdt_generic_gpio_name_set_gpio,
      .gpios = (FDTGenericGPIOConnection[]) {
        { .name = "rst-acpu0", .fdt_index = 0, .range = 1 },
        { .name = "rst-acpu1", .fdt_index = 1, .range = 1 },
        { .name = "rst-acpu2", .fdt_index = 2, .range = 1 },
        { .name = "rst-acpu3", .fdt_index = 3, .range = 1 },
        { .name = "rst-acpu-gic", .fdt_index = 16, .range = 1 },
        { .name = "rst-acpu-l2", .fdt_index = 17, .range = 1 },
        { .name = "rst-acpu0-pwron", .fdt_index = 18, .range = 1 },
        { .name = "rst-acpu1-pwron", .fdt_index = 19, .range = 1 },
        { .name = "rst-acpu2-pwron", .fdt_index = 20, .range = 1 },
        { .name = "rst-acpu3-pwron", .fdt_index = 21, .range = 1 },
        { .name = "rst-dbg-fpd", .fdt_index = 26, .range = 1 },
        { .name = "rst-fpd-swdt", .fdt_index = 27, .range = 1 },
        { .name = "rst-sysmon-cfg", .fdt_index = 28, .range = 1 },
        { .name = "rst-sysmon-seq", .fdt_index = 29, .range = 1 },
        { },
      },
    },
    { },
};

static void crf_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    FDTGenericGPIOClass *fggc = FDT_GENERIC_GPIO_CLASS(klass);
    ARMLinuxBootIfClass *albifc = ARM_LINUX_BOOT_IF_CLASS(klass);

    dc->reset = crf_reset;
    dc->realize = crf_realize;
    dc->vmsd = &vmstate_crf;
    fggc->controller_gpios = crf_gpios;
    albifc->arm_linux_init = crf_linux_boot_if_init;
}

static const TypeInfo crf_info = {
    .name          = TYPE_XILINX_CRF,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(XlnxZynq3Crf),
    .class_init    = crf_class_init,
    .instance_init = crf_init,
    .interfaces = (InterfaceInfo[]) {
        { TYPE_FDT_GENERIC_GPIO },
        { TYPE_ARM_LINUX_BOOT_IF },
        { }
    },
};

static void crf_register_types(void)
{
    type_register_static(&crf_info);
}

type_init(crf_register_types)
