368 lines
12 KiB
C
368 lines
12 KiB
C
|
/*
|
||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
* Copyright (C) 2024 IBM Corp.
|
||
|
*
|
||
|
* ASPEED APB-OPB FSI interface
|
||
|
* IBM On-chip Peripheral Bus
|
||
|
*/
|
||
|
|
||
|
#include "qemu/osdep.h"
|
||
|
#include "qemu/log.h"
|
||
|
#include "qom/object.h"
|
||
|
#include "qapi/error.h"
|
||
|
#include "trace.h"
|
||
|
|
||
|
#include "hw/fsi/aspeed_apb2opb.h"
|
||
|
#include "hw/qdev-core.h"
|
||
|
|
||
|
#define TO_REG(x) (x >> 2)
|
||
|
|
||
|
#define APB2OPB_VERSION TO_REG(0x00)
|
||
|
#define APB2OPB_TRIGGER TO_REG(0x04)
|
||
|
|
||
|
#define APB2OPB_CONTROL TO_REG(0x08)
|
||
|
#define APB2OPB_CONTROL_OFF BE_GENMASK(31, 13)
|
||
|
|
||
|
#define APB2OPB_OPB2FSI TO_REG(0x0c)
|
||
|
#define APB2OPB_OPB2FSI_OFF BE_GENMASK(31, 22)
|
||
|
|
||
|
#define APB2OPB_OPB0_SEL TO_REG(0x10)
|
||
|
#define APB2OPB_OPB1_SEL TO_REG(0x28)
|
||
|
#define APB2OPB_OPB_SEL_EN BIT(0)
|
||
|
|
||
|
#define APB2OPB_OPB0_MODE TO_REG(0x14)
|
||
|
#define APB2OPB_OPB1_MODE TO_REG(0x2c)
|
||
|
#define APB2OPB_OPB_MODE_RD BIT(0)
|
||
|
|
||
|
#define APB2OPB_OPB0_XFER TO_REG(0x18)
|
||
|
#define APB2OPB_OPB1_XFER TO_REG(0x30)
|
||
|
#define APB2OPB_OPB_XFER_FULL BIT(1)
|
||
|
#define APB2OPB_OPB_XFER_HALF BIT(0)
|
||
|
|
||
|
#define APB2OPB_OPB0_ADDR TO_REG(0x1c)
|
||
|
#define APB2OPB_OPB0_WRITE_DATA TO_REG(0x20)
|
||
|
|
||
|
#define APB2OPB_OPB1_ADDR TO_REG(0x34)
|
||
|
#define APB2OPB_OPB1_WRITE_DATA TO_REG(0x38)
|
||
|
|
||
|
#define APB2OPB_IRQ_STS TO_REG(0x48)
|
||
|
#define APB2OPB_IRQ_STS_OPB1_TX_ACK BIT(17)
|
||
|
#define APB2OPB_IRQ_STS_OPB0_TX_ACK BIT(16)
|
||
|
|
||
|
#define APB2OPB_OPB0_WRITE_WORD_ENDIAN TO_REG(0x4c)
|
||
|
#define APB2OPB_OPB0_WRITE_WORD_ENDIAN_BE 0x0011101b
|
||
|
#define APB2OPB_OPB0_WRITE_BYTE_ENDIAN TO_REG(0x50)
|
||
|
#define APB2OPB_OPB0_WRITE_BYTE_ENDIAN_BE 0x0c330f3f
|
||
|
#define APB2OPB_OPB1_WRITE_WORD_ENDIAN TO_REG(0x54)
|
||
|
#define APB2OPB_OPB1_WRITE_BYTE_ENDIAN TO_REG(0x58)
|
||
|
#define APB2OPB_OPB0_READ_BYTE_ENDIAN TO_REG(0x5c)
|
||
|
#define APB2OPB_OPB1_READ_BYTE_ENDIAN TO_REG(0x60)
|
||
|
#define APB2OPB_OPB0_READ_WORD_ENDIAN_BE 0x00030b1b
|
||
|
|
||
|
#define APB2OPB_OPB0_READ_DATA TO_REG(0x84)
|
||
|
#define APB2OPB_OPB1_READ_DATA TO_REG(0x90)
|
||
|
|
||
|
/*
|
||
|
* The following magic values came from AST2600 data sheet
|
||
|
* The register values are defined under section "FSI controller"
|
||
|
* as initial values.
|
||
|
*/
|
||
|
static const uint32_t aspeed_apb2opb_reset[ASPEED_APB2OPB_NR_REGS] = {
|
||
|
[APB2OPB_VERSION] = 0x000000a1,
|
||
|
[APB2OPB_OPB0_WRITE_WORD_ENDIAN] = 0x0044eee4,
|
||
|
[APB2OPB_OPB0_WRITE_BYTE_ENDIAN] = 0x0055aaff,
|
||
|
[APB2OPB_OPB1_WRITE_WORD_ENDIAN] = 0x00117717,
|
||
|
[APB2OPB_OPB1_WRITE_BYTE_ENDIAN] = 0xffaa5500,
|
||
|
[APB2OPB_OPB0_READ_BYTE_ENDIAN] = 0x0044eee4,
|
||
|
[APB2OPB_OPB1_READ_BYTE_ENDIAN] = 0x00117717
|
||
|
};
|
||
|
|
||
|
static void fsi_opb_fsi_master_address(FSIMasterState *fsi, hwaddr addr)
|
||
|
{
|
||
|
memory_region_transaction_begin();
|
||
|
memory_region_set_address(&fsi->iomem, addr);
|
||
|
memory_region_transaction_commit();
|
||
|
}
|
||
|
|
||
|
static void fsi_opb_opb2fsi_address(FSIMasterState *fsi, hwaddr addr)
|
||
|
{
|
||
|
memory_region_transaction_begin();
|
||
|
memory_region_set_address(&fsi->opb2fsi, addr);
|
||
|
memory_region_transaction_commit();
|
||
|
}
|
||
|
|
||
|
static uint64_t fsi_aspeed_apb2opb_read(void *opaque, hwaddr addr,
|
||
|
unsigned size)
|
||
|
{
|
||
|
AspeedAPB2OPBState *s = ASPEED_APB2OPB(opaque);
|
||
|
unsigned int reg = TO_REG(addr);
|
||
|
|
||
|
trace_fsi_aspeed_apb2opb_read(addr, size);
|
||
|
|
||
|
if (reg >= ASPEED_APB2OPB_NR_REGS) {
|
||
|
qemu_log_mask(LOG_GUEST_ERROR,
|
||
|
"%s: Out of bounds read: 0x%"HWADDR_PRIx" for %u\n",
|
||
|
__func__, addr, size);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return s->regs[reg];
|
||
|
}
|
||
|
|
||
|
static MemTxResult fsi_aspeed_apb2opb_rw(AddressSpace *as, hwaddr addr,
|
||
|
MemTxAttrs attrs, uint32_t *data,
|
||
|
uint32_t size, bool is_write)
|
||
|
{
|
||
|
MemTxResult res;
|
||
|
|
||
|
if (is_write) {
|
||
|
switch (size) {
|
||
|
case 4:
|
||
|
address_space_stl_le(as, addr, *data, attrs, &res);
|
||
|
break;
|
||
|
case 2:
|
||
|
address_space_stw_le(as, addr, *data, attrs, &res);
|
||
|
break;
|
||
|
case 1:
|
||
|
address_space_stb(as, addr, *data, attrs, &res);
|
||
|
break;
|
||
|
default:
|
||
|
g_assert_not_reached();
|
||
|
}
|
||
|
} else {
|
||
|
switch (size) {
|
||
|
case 4:
|
||
|
*data = address_space_ldl_le(as, addr, attrs, &res);
|
||
|
break;
|
||
|
case 2:
|
||
|
*data = address_space_lduw_le(as, addr, attrs, &res);
|
||
|
break;
|
||
|
case 1:
|
||
|
*data = address_space_ldub(as, addr, attrs, &res);
|
||
|
break;
|
||
|
default:
|
||
|
g_assert_not_reached();
|
||
|
}
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static void fsi_aspeed_apb2opb_write(void *opaque, hwaddr addr, uint64_t data,
|
||
|
unsigned size)
|
||
|
{
|
||
|
AspeedAPB2OPBState *s = ASPEED_APB2OPB(opaque);
|
||
|
unsigned int reg = TO_REG(addr);
|
||
|
|
||
|
trace_fsi_aspeed_apb2opb_write(addr, size, data);
|
||
|
|
||
|
if (reg >= ASPEED_APB2OPB_NR_REGS) {
|
||
|
qemu_log_mask(LOG_GUEST_ERROR,
|
||
|
"%s: Out of bounds write: %"HWADDR_PRIx" for %u\n",
|
||
|
__func__, addr, size);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch (reg) {
|
||
|
case APB2OPB_CONTROL:
|
||
|
fsi_opb_fsi_master_address(&s->fsi[0],
|
||
|
data & APB2OPB_CONTROL_OFF);
|
||
|
break;
|
||
|
case APB2OPB_OPB2FSI:
|
||
|
fsi_opb_opb2fsi_address(&s->fsi[0],
|
||
|
data & APB2OPB_OPB2FSI_OFF);
|
||
|
break;
|
||
|
case APB2OPB_OPB0_WRITE_WORD_ENDIAN:
|
||
|
if (data != APB2OPB_OPB0_WRITE_WORD_ENDIAN_BE) {
|
||
|
qemu_log_mask(LOG_GUEST_ERROR,
|
||
|
"%s: Bridge needs to be driven as BE (0x%x)\n",
|
||
|
__func__, APB2OPB_OPB0_WRITE_WORD_ENDIAN_BE);
|
||
|
}
|
||
|
break;
|
||
|
case APB2OPB_OPB0_WRITE_BYTE_ENDIAN:
|
||
|
if (data != APB2OPB_OPB0_WRITE_BYTE_ENDIAN_BE) {
|
||
|
qemu_log_mask(LOG_GUEST_ERROR,
|
||
|
"%s: Bridge needs to be driven as BE (0x%x)\n",
|
||
|
__func__, APB2OPB_OPB0_WRITE_BYTE_ENDIAN_BE);
|
||
|
}
|
||
|
break;
|
||
|
case APB2OPB_OPB0_READ_BYTE_ENDIAN:
|
||
|
if (data != APB2OPB_OPB0_READ_WORD_ENDIAN_BE) {
|
||
|
qemu_log_mask(LOG_GUEST_ERROR,
|
||
|
"%s: Bridge needs to be driven as BE (0x%x)\n",
|
||
|
__func__, APB2OPB_OPB0_READ_WORD_ENDIAN_BE);
|
||
|
}
|
||
|
break;
|
||
|
case APB2OPB_TRIGGER:
|
||
|
{
|
||
|
uint32_t opb, op_mode, op_size, op_addr, op_data;
|
||
|
MemTxResult result;
|
||
|
bool is_write;
|
||
|
int index;
|
||
|
AddressSpace *as;
|
||
|
|
||
|
assert((s->regs[APB2OPB_OPB0_SEL] & APB2OPB_OPB_SEL_EN) ^
|
||
|
(s->regs[APB2OPB_OPB1_SEL] & APB2OPB_OPB_SEL_EN));
|
||
|
|
||
|
if (s->regs[APB2OPB_OPB0_SEL] & APB2OPB_OPB_SEL_EN) {
|
||
|
opb = 0;
|
||
|
op_mode = s->regs[APB2OPB_OPB0_MODE];
|
||
|
op_size = s->regs[APB2OPB_OPB0_XFER];
|
||
|
op_addr = s->regs[APB2OPB_OPB0_ADDR];
|
||
|
op_data = s->regs[APB2OPB_OPB0_WRITE_DATA];
|
||
|
} else if (s->regs[APB2OPB_OPB1_SEL] & APB2OPB_OPB_SEL_EN) {
|
||
|
opb = 1;
|
||
|
op_mode = s->regs[APB2OPB_OPB1_MODE];
|
||
|
op_size = s->regs[APB2OPB_OPB1_XFER];
|
||
|
op_addr = s->regs[APB2OPB_OPB1_ADDR];
|
||
|
op_data = s->regs[APB2OPB_OPB1_WRITE_DATA];
|
||
|
} else {
|
||
|
qemu_log_mask(LOG_GUEST_ERROR,
|
||
|
"%s: Invalid operation: 0x%"HWADDR_PRIx" for %u\n",
|
||
|
__func__, addr, size);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (op_size & ~(APB2OPB_OPB_XFER_HALF | APB2OPB_OPB_XFER_FULL)) {
|
||
|
qemu_log_mask(LOG_GUEST_ERROR,
|
||
|
"OPB transaction failed: Unrecognized access width: %d\n",
|
||
|
op_size);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
op_size += 1;
|
||
|
is_write = !(op_mode & APB2OPB_OPB_MODE_RD);
|
||
|
index = opb ? APB2OPB_OPB1_READ_DATA : APB2OPB_OPB0_READ_DATA;
|
||
|
as = &s->opb[opb].as;
|
||
|
|
||
|
result = fsi_aspeed_apb2opb_rw(as, op_addr, MEMTXATTRS_UNSPECIFIED,
|
||
|
&op_data, op_size, is_write);
|
||
|
if (result != MEMTX_OK) {
|
||
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: OPB %s failed @%08x\n",
|
||
|
__func__, is_write ? "write" : "read", op_addr);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!is_write) {
|
||
|
s->regs[index] = op_data;
|
||
|
}
|
||
|
|
||
|
s->regs[APB2OPB_IRQ_STS] |= opb ? APB2OPB_IRQ_STS_OPB1_TX_ACK
|
||
|
: APB2OPB_IRQ_STS_OPB0_TX_ACK;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
s->regs[reg] = data;
|
||
|
}
|
||
|
|
||
|
static const struct MemoryRegionOps aspeed_apb2opb_ops = {
|
||
|
.read = fsi_aspeed_apb2opb_read,
|
||
|
.write = fsi_aspeed_apb2opb_write,
|
||
|
.valid.max_access_size = 4,
|
||
|
.valid.min_access_size = 4,
|
||
|
.impl.max_access_size = 4,
|
||
|
.impl.min_access_size = 4,
|
||
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
||
|
};
|
||
|
|
||
|
static void fsi_aspeed_apb2opb_init(Object *o)
|
||
|
{
|
||
|
AspeedAPB2OPBState *s = ASPEED_APB2OPB(o);
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < ASPEED_FSI_NUM; i++) {
|
||
|
object_initialize_child(o, "fsi-master[*]", &s->fsi[i],
|
||
|
TYPE_FSI_MASTER);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void fsi_aspeed_apb2opb_realize(DeviceState *dev, Error **errp)
|
||
|
{
|
||
|
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
||
|
AspeedAPB2OPBState *s = ASPEED_APB2OPB(dev);
|
||
|
int i;
|
||
|
|
||
|
/*
|
||
|
* TODO: The OPBus model initializes the OPB address space in
|
||
|
* the .instance_init handler and this is problematic for test
|
||
|
* device-introspect-test. To avoid a memory corruption and a QEMU
|
||
|
* crash, qbus_init() should be called from realize(). Something to
|
||
|
* improve. Possibly, OPBus could also be removed.
|
||
|
*/
|
||
|
for (i = 0; i < ASPEED_FSI_NUM; i++) {
|
||
|
qbus_init(&s->opb[i], sizeof(s->opb[i]), TYPE_OP_BUS, DEVICE(s),
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
sysbus_init_irq(sbd, &s->irq);
|
||
|
|
||
|
memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_apb2opb_ops, s,
|
||
|
TYPE_ASPEED_APB2OPB, 0x1000);
|
||
|
sysbus_init_mmio(sbd, &s->iomem);
|
||
|
|
||
|
for (i = 0; i < ASPEED_FSI_NUM; i++) {
|
||
|
if (!qdev_realize(DEVICE(&s->fsi[i]), BUS(&s->opb[i]), errp)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
memory_region_add_subregion(&s->opb[i].mr, 0x80000000,
|
||
|
&s->fsi[i].iomem);
|
||
|
|
||
|
memory_region_add_subregion(&s->opb[i].mr, 0xa0000000,
|
||
|
&s->fsi[i].opb2fsi);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void fsi_aspeed_apb2opb_reset(DeviceState *dev)
|
||
|
{
|
||
|
AspeedAPB2OPBState *s = ASPEED_APB2OPB(dev);
|
||
|
|
||
|
memcpy(s->regs, aspeed_apb2opb_reset, ASPEED_APB2OPB_NR_REGS);
|
||
|
}
|
||
|
|
||
|
static void fsi_aspeed_apb2opb_class_init(ObjectClass *klass, void *data)
|
||
|
{
|
||
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
||
|
|
||
|
dc->desc = "ASPEED APB2OPB Bridge";
|
||
|
dc->realize = fsi_aspeed_apb2opb_realize;
|
||
|
dc->reset = fsi_aspeed_apb2opb_reset;
|
||
|
}
|
||
|
|
||
|
static const TypeInfo aspeed_apb2opb_info = {
|
||
|
.name = TYPE_ASPEED_APB2OPB,
|
||
|
.parent = TYPE_SYS_BUS_DEVICE,
|
||
|
.instance_init = fsi_aspeed_apb2opb_init,
|
||
|
.instance_size = sizeof(AspeedAPB2OPBState),
|
||
|
.class_init = fsi_aspeed_apb2opb_class_init,
|
||
|
};
|
||
|
|
||
|
static void aspeed_apb2opb_register_types(void)
|
||
|
{
|
||
|
type_register_static(&aspeed_apb2opb_info);
|
||
|
}
|
||
|
|
||
|
type_init(aspeed_apb2opb_register_types);
|
||
|
|
||
|
static void fsi_opb_init(Object *o)
|
||
|
{
|
||
|
OPBus *opb = OP_BUS(o);
|
||
|
|
||
|
memory_region_init(&opb->mr, 0, TYPE_FSI_OPB, UINT32_MAX);
|
||
|
address_space_init(&opb->as, &opb->mr, TYPE_FSI_OPB);
|
||
|
}
|
||
|
|
||
|
static const TypeInfo opb_info = {
|
||
|
.name = TYPE_OP_BUS,
|
||
|
.parent = TYPE_BUS,
|
||
|
.instance_init = fsi_opb_init,
|
||
|
.instance_size = sizeof(OPBus),
|
||
|
};
|
||
|
|
||
|
static void fsi_opb_register_types(void)
|
||
|
{
|
||
|
type_register_static(&opb_info);
|
||
|
}
|
||
|
|
||
|
type_init(fsi_opb_register_types);
|