153 lines
4.2 KiB
C
153 lines
4.2 KiB
C
|
/*
|
||
|
* Aspeed PECI Controller
|
||
|
*
|
||
|
* Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com)
|
||
|
*
|
||
|
* This code is licensed under the GPL version 2 or later. See the COPYING
|
||
|
* file in the top-level directory.
|
||
|
*/
|
||
|
|
||
|
#include "qemu/osdep.h"
|
||
|
#include "qemu/log.h"
|
||
|
#include "hw/irq.h"
|
||
|
#include "hw/misc/aspeed_peci.h"
|
||
|
#include "hw/registerfields.h"
|
||
|
#include "trace.h"
|
||
|
|
||
|
#define ASPEED_PECI_CC_RSP_SUCCESS (0x40U)
|
||
|
|
||
|
/* Command Register */
|
||
|
REG32(PECI_CMD, 0x08)
|
||
|
FIELD(PECI_CMD, FIRE, 0, 1)
|
||
|
|
||
|
/* Interrupt Control Register */
|
||
|
REG32(PECI_INT_CTRL, 0x18)
|
||
|
|
||
|
/* Interrupt Status Register */
|
||
|
REG32(PECI_INT_STS, 0x1C)
|
||
|
FIELD(PECI_INT_STS, CMD_DONE, 0, 1)
|
||
|
|
||
|
/* Rx/Tx Data Buffer Registers */
|
||
|
REG32(PECI_WR_DATA0, 0x20)
|
||
|
REG32(PECI_RD_DATA0, 0x30)
|
||
|
|
||
|
static void aspeed_peci_raise_interrupt(AspeedPECIState *s, uint32_t status)
|
||
|
{
|
||
|
trace_aspeed_peci_raise_interrupt(s->regs[R_PECI_INT_CTRL], status);
|
||
|
|
||
|
s->regs[R_PECI_INT_STS] = s->regs[R_PECI_INT_CTRL] & status;
|
||
|
if (!s->regs[R_PECI_INT_STS]) {
|
||
|
return;
|
||
|
}
|
||
|
qemu_irq_raise(s->irq);
|
||
|
}
|
||
|
|
||
|
static uint64_t aspeed_peci_read(void *opaque, hwaddr offset, unsigned size)
|
||
|
{
|
||
|
AspeedPECIState *s = ASPEED_PECI(opaque);
|
||
|
uint64_t data;
|
||
|
|
||
|
if (offset >= ASPEED_PECI_NR_REGS << 2) {
|
||
|
qemu_log_mask(LOG_GUEST_ERROR,
|
||
|
"%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
|
||
|
__func__, offset);
|
||
|
return 0;
|
||
|
}
|
||
|
data = s->regs[offset >> 2];
|
||
|
|
||
|
trace_aspeed_peci_read(offset, data);
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
static void aspeed_peci_write(void *opaque, hwaddr offset, uint64_t data,
|
||
|
unsigned size)
|
||
|
{
|
||
|
AspeedPECIState *s = ASPEED_PECI(opaque);
|
||
|
|
||
|
trace_aspeed_peci_write(offset, data);
|
||
|
|
||
|
if (offset >= ASPEED_PECI_NR_REGS << 2) {
|
||
|
qemu_log_mask(LOG_GUEST_ERROR,
|
||
|
"%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
|
||
|
__func__, offset);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch (offset) {
|
||
|
case A_PECI_INT_STS:
|
||
|
s->regs[R_PECI_INT_STS] &= ~data;
|
||
|
if (!s->regs[R_PECI_INT_STS]) {
|
||
|
qemu_irq_lower(s->irq);
|
||
|
}
|
||
|
break;
|
||
|
case A_PECI_CMD:
|
||
|
/*
|
||
|
* Only the FIRE bit is writable. Once the command is complete, it
|
||
|
* should be cleared. Since we complete the command immediately, the
|
||
|
* value is not stored in the register array.
|
||
|
*/
|
||
|
if (!FIELD_EX32(data, PECI_CMD, FIRE)) {
|
||
|
break;
|
||
|
}
|
||
|
if (s->regs[R_PECI_INT_STS]) {
|
||
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Interrupt status must be "
|
||
|
"cleared before firing another command: 0x%08x\n",
|
||
|
__func__, s->regs[R_PECI_INT_STS]);
|
||
|
break;
|
||
|
}
|
||
|
s->regs[R_PECI_RD_DATA0] = ASPEED_PECI_CC_RSP_SUCCESS;
|
||
|
s->regs[R_PECI_WR_DATA0] = ASPEED_PECI_CC_RSP_SUCCESS;
|
||
|
aspeed_peci_raise_interrupt(s,
|
||
|
FIELD_DP32(0, PECI_INT_STS, CMD_DONE, 1));
|
||
|
break;
|
||
|
default:
|
||
|
s->regs[offset / sizeof(s->regs[0])] = data;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static const MemoryRegionOps aspeed_peci_ops = {
|
||
|
.read = aspeed_peci_read,
|
||
|
.write = aspeed_peci_write,
|
||
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
||
|
};
|
||
|
|
||
|
static void aspeed_peci_realize(DeviceState *dev, Error **errp)
|
||
|
{
|
||
|
AspeedPECIState *s = ASPEED_PECI(dev);
|
||
|
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
||
|
|
||
|
memory_region_init_io(&s->mmio, OBJECT(s), &aspeed_peci_ops, s,
|
||
|
TYPE_ASPEED_PECI, 0x1000);
|
||
|
sysbus_init_mmio(sbd, &s->mmio);
|
||
|
sysbus_init_irq(sbd, &s->irq);
|
||
|
}
|
||
|
|
||
|
static void aspeed_peci_reset(DeviceState *dev)
|
||
|
{
|
||
|
AspeedPECIState *s = ASPEED_PECI(dev);
|
||
|
|
||
|
memset(s->regs, 0, sizeof(s->regs));
|
||
|
}
|
||
|
|
||
|
static void aspeed_peci_class_init(ObjectClass *klass, void *data)
|
||
|
{
|
||
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
||
|
|
||
|
dc->realize = aspeed_peci_realize;
|
||
|
dc->reset = aspeed_peci_reset;
|
||
|
dc->desc = "Aspeed PECI Controller";
|
||
|
}
|
||
|
|
||
|
static const TypeInfo aspeed_peci_types[] = {
|
||
|
{
|
||
|
.name = TYPE_ASPEED_PECI,
|
||
|
.parent = TYPE_SYS_BUS_DEVICE,
|
||
|
.instance_size = sizeof(AspeedPECIState),
|
||
|
.class_init = aspeed_peci_class_init,
|
||
|
.abstract = false,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
DEFINE_TYPES(aspeed_peci_types);
|