155c57023SPeter Delevoryas /* 255c57023SPeter Delevoryas * Aspeed PECI Controller 355c57023SPeter Delevoryas * 455c57023SPeter Delevoryas * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com) 555c57023SPeter Delevoryas * 655c57023SPeter Delevoryas * This code is licensed under the GPL version 2 or later. See the COPYING 755c57023SPeter Delevoryas * file in the top-level directory. 855c57023SPeter Delevoryas */ 955c57023SPeter Delevoryas 1055c57023SPeter Delevoryas #include "qemu/osdep.h" 1155c57023SPeter Delevoryas #include "qemu/log.h" 1255c57023SPeter Delevoryas #include "hw/irq.h" 1355c57023SPeter Delevoryas #include "hw/misc/aspeed_peci.h" 1455c57023SPeter Delevoryas #include "hw/registerfields.h" 1555c57023SPeter Delevoryas #include "trace.h" 1655c57023SPeter Delevoryas 1755c57023SPeter Delevoryas #define ASPEED_PECI_CC_RSP_SUCCESS (0x40U) 1855c57023SPeter Delevoryas 1955c57023SPeter Delevoryas /* Command Register */ 2055c57023SPeter Delevoryas REG32(PECI_CMD, 0x08) 2155c57023SPeter Delevoryas FIELD(PECI_CMD, FIRE, 0, 1) 2255c57023SPeter Delevoryas 2355c57023SPeter Delevoryas /* Interrupt Control Register */ 2455c57023SPeter Delevoryas REG32(PECI_INT_CTRL, 0x18) 2555c57023SPeter Delevoryas 2655c57023SPeter Delevoryas /* Interrupt Status Register */ 2755c57023SPeter Delevoryas REG32(PECI_INT_STS, 0x1C) 2855c57023SPeter Delevoryas FIELD(PECI_INT_STS, CMD_DONE, 0, 1) 2955c57023SPeter Delevoryas 3055c57023SPeter Delevoryas /* Rx/Tx Data Buffer Registers */ 3155c57023SPeter Delevoryas REG32(PECI_WR_DATA0, 0x20) 3255c57023SPeter Delevoryas REG32(PECI_RD_DATA0, 0x30) 3355c57023SPeter Delevoryas 3455c57023SPeter Delevoryas static void aspeed_peci_raise_interrupt(AspeedPECIState *s, uint32_t status) 3555c57023SPeter Delevoryas { 3655c57023SPeter Delevoryas trace_aspeed_peci_raise_interrupt(s->regs[R_PECI_INT_CTRL], status); 3755c57023SPeter Delevoryas 3855c57023SPeter Delevoryas s->regs[R_PECI_INT_STS] = s->regs[R_PECI_INT_CTRL] & status; 3955c57023SPeter Delevoryas if (!s->regs[R_PECI_INT_STS]) { 4055c57023SPeter Delevoryas return; 4155c57023SPeter Delevoryas } 4255c57023SPeter Delevoryas qemu_irq_raise(s->irq); 4355c57023SPeter Delevoryas } 4455c57023SPeter Delevoryas 4555c57023SPeter Delevoryas static uint64_t aspeed_peci_read(void *opaque, hwaddr offset, unsigned size) 4655c57023SPeter Delevoryas { 4755c57023SPeter Delevoryas AspeedPECIState *s = ASPEED_PECI(opaque); 4855c57023SPeter Delevoryas uint64_t data; 4955c57023SPeter Delevoryas 5055c57023SPeter Delevoryas if (offset >= ASPEED_PECI_NR_REGS << 2) { 5155c57023SPeter Delevoryas qemu_log_mask(LOG_GUEST_ERROR, 5255c57023SPeter Delevoryas "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n", 5355c57023SPeter Delevoryas __func__, offset); 5455c57023SPeter Delevoryas return 0; 5555c57023SPeter Delevoryas } 5655c57023SPeter Delevoryas data = s->regs[offset >> 2]; 5755c57023SPeter Delevoryas 5855c57023SPeter Delevoryas trace_aspeed_peci_read(offset, data); 5955c57023SPeter Delevoryas return data; 6055c57023SPeter Delevoryas } 6155c57023SPeter Delevoryas 6255c57023SPeter Delevoryas static void aspeed_peci_write(void *opaque, hwaddr offset, uint64_t data, 6355c57023SPeter Delevoryas unsigned size) 6455c57023SPeter Delevoryas { 6555c57023SPeter Delevoryas AspeedPECIState *s = ASPEED_PECI(opaque); 6655c57023SPeter Delevoryas 6755c57023SPeter Delevoryas trace_aspeed_peci_write(offset, data); 6855c57023SPeter Delevoryas 6955c57023SPeter Delevoryas if (offset >= ASPEED_PECI_NR_REGS << 2) { 7055c57023SPeter Delevoryas qemu_log_mask(LOG_GUEST_ERROR, 7155c57023SPeter Delevoryas "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n", 7255c57023SPeter Delevoryas __func__, offset); 7355c57023SPeter Delevoryas return; 7455c57023SPeter Delevoryas } 7555c57023SPeter Delevoryas 7655c57023SPeter Delevoryas switch (offset) { 7755c57023SPeter Delevoryas case A_PECI_INT_STS: 7855c57023SPeter Delevoryas s->regs[R_PECI_INT_STS] &= ~data; 7955c57023SPeter Delevoryas if (!s->regs[R_PECI_INT_STS]) { 8055c57023SPeter Delevoryas qemu_irq_lower(s->irq); 8155c57023SPeter Delevoryas } 8255c57023SPeter Delevoryas break; 8355c57023SPeter Delevoryas case A_PECI_CMD: 8455c57023SPeter Delevoryas /* 8555c57023SPeter Delevoryas * Only the FIRE bit is writable. Once the command is complete, it 8655c57023SPeter Delevoryas * should be cleared. Since we complete the command immediately, the 8755c57023SPeter Delevoryas * value is not stored in the register array. 8855c57023SPeter Delevoryas */ 8955c57023SPeter Delevoryas if (!FIELD_EX32(data, PECI_CMD, FIRE)) { 9055c57023SPeter Delevoryas break; 9155c57023SPeter Delevoryas } 9255c57023SPeter Delevoryas if (s->regs[R_PECI_INT_STS]) { 9355c57023SPeter Delevoryas qemu_log_mask(LOG_GUEST_ERROR, "%s: Interrupt status must be " 9455c57023SPeter Delevoryas "cleared before firing another command: 0x%08x\n", 9555c57023SPeter Delevoryas __func__, s->regs[R_PECI_INT_STS]); 9655c57023SPeter Delevoryas break; 9755c57023SPeter Delevoryas } 9855c57023SPeter Delevoryas s->regs[R_PECI_RD_DATA0] = ASPEED_PECI_CC_RSP_SUCCESS; 9955c57023SPeter Delevoryas s->regs[R_PECI_WR_DATA0] = ASPEED_PECI_CC_RSP_SUCCESS; 10055c57023SPeter Delevoryas aspeed_peci_raise_interrupt(s, 10155c57023SPeter Delevoryas FIELD_DP32(0, PECI_INT_STS, CMD_DONE, 1)); 10255c57023SPeter Delevoryas break; 10355c57023SPeter Delevoryas default: 10455c57023SPeter Delevoryas s->regs[offset / sizeof(s->regs[0])] = data; 10555c57023SPeter Delevoryas break; 10655c57023SPeter Delevoryas } 10755c57023SPeter Delevoryas } 10855c57023SPeter Delevoryas 10955c57023SPeter Delevoryas static const MemoryRegionOps aspeed_peci_ops = { 11055c57023SPeter Delevoryas .read = aspeed_peci_read, 11155c57023SPeter Delevoryas .write = aspeed_peci_write, 11255c57023SPeter Delevoryas .endianness = DEVICE_LITTLE_ENDIAN, 11355c57023SPeter Delevoryas }; 11455c57023SPeter Delevoryas 11555c57023SPeter Delevoryas static void aspeed_peci_realize(DeviceState *dev, Error **errp) 11655c57023SPeter Delevoryas { 11755c57023SPeter Delevoryas AspeedPECIState *s = ASPEED_PECI(dev); 11855c57023SPeter Delevoryas SysBusDevice *sbd = SYS_BUS_DEVICE(dev); 11955c57023SPeter Delevoryas 12055c57023SPeter Delevoryas memory_region_init_io(&s->mmio, OBJECT(s), &aspeed_peci_ops, s, 12155c57023SPeter Delevoryas TYPE_ASPEED_PECI, 0x1000); 12255c57023SPeter Delevoryas sysbus_init_mmio(sbd, &s->mmio); 12355c57023SPeter Delevoryas sysbus_init_irq(sbd, &s->irq); 12455c57023SPeter Delevoryas } 12555c57023SPeter Delevoryas 12655c57023SPeter Delevoryas static void aspeed_peci_reset(DeviceState *dev) 12755c57023SPeter Delevoryas { 12855c57023SPeter Delevoryas AspeedPECIState *s = ASPEED_PECI(dev); 12955c57023SPeter Delevoryas 13055c57023SPeter Delevoryas memset(s->regs, 0, sizeof(s->regs)); 13155c57023SPeter Delevoryas } 13255c57023SPeter Delevoryas 13355c57023SPeter Delevoryas static void aspeed_peci_class_init(ObjectClass *klass, void *data) 13455c57023SPeter Delevoryas { 13555c57023SPeter Delevoryas DeviceClass *dc = DEVICE_CLASS(klass); 13655c57023SPeter Delevoryas 13755c57023SPeter Delevoryas dc->realize = aspeed_peci_realize; 138*e3d08143SPeter Maydell device_class_set_legacy_reset(dc, aspeed_peci_reset); 13955c57023SPeter Delevoryas dc->desc = "Aspeed PECI Controller"; 14055c57023SPeter Delevoryas } 14155c57023SPeter Delevoryas 14255c57023SPeter Delevoryas static const TypeInfo aspeed_peci_types[] = { 14355c57023SPeter Delevoryas { 14455c57023SPeter Delevoryas .name = TYPE_ASPEED_PECI, 14555c57023SPeter Delevoryas .parent = TYPE_SYS_BUS_DEVICE, 14655c57023SPeter Delevoryas .instance_size = sizeof(AspeedPECIState), 14755c57023SPeter Delevoryas .class_init = aspeed_peci_class_init, 14855c57023SPeter Delevoryas .abstract = false, 14955c57023SPeter Delevoryas }, 15055c57023SPeter Delevoryas }; 15155c57023SPeter Delevoryas 15255c57023SPeter Delevoryas DEFINE_TYPES(aspeed_peci_types); 153