/*
 * QEMU PowerPC nest pervasive common chiplet model
 *
 * Copyright (c) 2023, IBM Corporation.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "qemu/osdep.h"
#include "qemu/log.h"
#include "hw/qdev-properties.h"
#include "hw/ppc/pnv.h"
#include "hw/ppc/pnv_xscom.h"
#include "hw/ppc/pnv_nest_pervasive.h"

/*
 * Status, configuration, and control units in POWER chips is provided
 * by the pervasive subsystem, which connects registers to the SCOM bus,
 * which can be programmed by processor cores, other units on the chip,
 * BMCs, or other POWER chips.
 *
 * A POWER10 chip is divided into logical units called chiplets. Chiplets
 * are broadly divided into "core chiplets" (with the processor cores) and
 * "nest chiplets" (with everything else). Each chiplet has an attachment
 * to the pervasive bus (PIB) and with chiplet-specific registers.
 * All nest chiplets have a common basic set of registers.
 *
 * This model will provide the registers functionality for common registers of
 * nest unit (PB Chiplet, PCI Chiplets, MC Chiplet, PAU Chiplets)
 *
 * Currently this model provide the read/write functionality of chiplet control
 * scom registers.
 */

#define CPLT_CONF0               0x08
#define CPLT_CONF0_OR            0x18
#define CPLT_CONF0_CLEAR         0x28
#define CPLT_CONF1               0x09
#define CPLT_CONF1_OR            0x19
#define CPLT_CONF1_CLEAR         0x29
#define CPLT_STAT0               0x100
#define CPLT_MASK0               0x101
#define CPLT_PROTECT_MODE        0x3FE
#define CPLT_ATOMIC_CLOCK        0x3FF

static uint64_t pnv_chiplet_ctrl_read(void *opaque, hwaddr addr, unsigned size)
{
    PnvNestChipletPervasive *nest_pervasive = PNV_NEST_CHIPLET_PERVASIVE(
                                              opaque);
    uint32_t reg = addr >> 3;
    uint64_t val = ~0ull;

    /* CPLT_CTRL0 to CPLT_CTRL5 */
    for (int i = 0; i < PNV_CPLT_CTRL_SIZE; i++) {
        if (reg == i) {
            return nest_pervasive->control_regs.cplt_ctrl[i];
        } else if ((reg == (i + 0x10)) || (reg == (i + 0x20))) {
            qemu_log_mask(LOG_GUEST_ERROR, "%s: Write only register, ignoring "
                                           "xscom read at 0x%" PRIx32 "\n",
                                           __func__, reg);
            return val;
        }
    }

    switch (reg) {
    case CPLT_CONF0:
        val = nest_pervasive->control_regs.cplt_cfg0;
        break;
    case CPLT_CONF0_OR:
    case CPLT_CONF0_CLEAR:
        qemu_log_mask(LOG_GUEST_ERROR, "%s: Write only register, ignoring "
                                   "xscom read at 0x%" PRIx32 "\n",
                                   __func__, reg);
        break;
    case CPLT_CONF1:
        val = nest_pervasive->control_regs.cplt_cfg1;
        break;
    case CPLT_CONF1_OR:
    case CPLT_CONF1_CLEAR:
        qemu_log_mask(LOG_GUEST_ERROR, "%s: Write only register, ignoring "
                                   "xscom read at 0x%" PRIx32 "\n",
                                   __func__, reg);
        break;
    case CPLT_STAT0:
        val = nest_pervasive->control_regs.cplt_stat0;
        break;
    case CPLT_MASK0:
        val = nest_pervasive->control_regs.cplt_mask0;
        break;
    case CPLT_PROTECT_MODE:
        val = nest_pervasive->control_regs.ctrl_protect_mode;
        break;
    case CPLT_ATOMIC_CLOCK:
        val = nest_pervasive->control_regs.ctrl_atomic_lock;
        break;
    default:
        qemu_log_mask(LOG_UNIMP, "%s: Chiplet_control_regs: Invalid xscom "
                 "read at 0x%" PRIx32 "\n", __func__, reg);
    }
    return val;
}

static void pnv_chiplet_ctrl_write(void *opaque, hwaddr addr,
                                 uint64_t val, unsigned size)
{
    PnvNestChipletPervasive *nest_pervasive = PNV_NEST_CHIPLET_PERVASIVE(
                                              opaque);
    uint32_t reg = addr >> 3;

    /* CPLT_CTRL0 to CPLT_CTRL5 */
    for (int i = 0; i < PNV_CPLT_CTRL_SIZE; i++) {
        if (reg == i) {
            nest_pervasive->control_regs.cplt_ctrl[i] = val;
            return;
        } else if (reg == (i + 0x10)) {
            nest_pervasive->control_regs.cplt_ctrl[i] |= val;
            return;
        } else if (reg == (i + 0x20)) {
            nest_pervasive->control_regs.cplt_ctrl[i] &= ~val;
            return;
        }
    }

    switch (reg) {
    case CPLT_CONF0:
        nest_pervasive->control_regs.cplt_cfg0 = val;
        break;
    case CPLT_CONF0_OR:
        nest_pervasive->control_regs.cplt_cfg0 |= val;
        break;
    case CPLT_CONF0_CLEAR:
        nest_pervasive->control_regs.cplt_cfg0 &= ~val;
        break;
    case CPLT_CONF1:
        nest_pervasive->control_regs.cplt_cfg1 = val;
        break;
    case CPLT_CONF1_OR:
        nest_pervasive->control_regs.cplt_cfg1 |= val;
        break;
    case CPLT_CONF1_CLEAR:
        nest_pervasive->control_regs.cplt_cfg1 &= ~val;
        break;
    case CPLT_STAT0:
        nest_pervasive->control_regs.cplt_stat0 = val;
        break;
    case CPLT_MASK0:
        nest_pervasive->control_regs.cplt_mask0 = val;
        break;
    case CPLT_PROTECT_MODE:
        nest_pervasive->control_regs.ctrl_protect_mode = val;
        break;
    case CPLT_ATOMIC_CLOCK:
        nest_pervasive->control_regs.ctrl_atomic_lock = val;
        break;
    default:
        qemu_log_mask(LOG_UNIMP, "%s: Chiplet_control_regs: Invalid xscom "
                                 "write at 0x%" PRIx32 "\n",
                                 __func__, reg);
    }
}

static const MemoryRegionOps pnv_nest_pervasive_control_xscom_ops = {
    .read = pnv_chiplet_ctrl_read,
    .write = pnv_chiplet_ctrl_write,
    .valid.min_access_size = 8,
    .valid.max_access_size = 8,
    .impl.min_access_size = 8,
    .impl.max_access_size = 8,
    .endianness = DEVICE_BIG_ENDIAN,
};

static void pnv_nest_pervasive_realize(DeviceState *dev, Error **errp)
{
    PnvNestChipletPervasive *nest_pervasive = PNV_NEST_CHIPLET_PERVASIVE(dev);

    /* Chiplet control scoms */
    pnv_xscom_region_init(&nest_pervasive->xscom_ctrl_regs_mr,
                          OBJECT(nest_pervasive),
                          &pnv_nest_pervasive_control_xscom_ops,
                          nest_pervasive, "pervasive-control",
                          PNV10_XSCOM_CHIPLET_CTRL_REGS_SIZE);
}

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

    dc->desc = "PowerNV nest pervasive chiplet";
    dc->realize = pnv_nest_pervasive_realize;
}

static const TypeInfo pnv_nest_pervasive_info = {
    .name          = TYPE_PNV_NEST_CHIPLET_PERVASIVE,
    .parent        = TYPE_DEVICE,
    .instance_size = sizeof(PnvNestChipletPervasive),
    .class_init    = pnv_nest_pervasive_class_init,
    .interfaces    = (InterfaceInfo[]) {
        { TYPE_PNV_XSCOM_INTERFACE },
        { }
    }
};

static void pnv_nest_pervasive_register_types(void)
{
    type_register_static(&pnv_nest_pervasive_info);
}

type_init(pnv_nest_pervasive_register_types);