Skip to content

Commit

Permalink
hw/fsi: Aspeed APB2OPB & On-chip peripheral bus
Browse files Browse the repository at this point in the history
This is a part of patchset where IBM's Flexible Service Interface is
introduced.

An APB-to-OPB bridge enabling access to the OPB from the ARM core in
the AST2600. Hardware limitations prevent the OPB from being directly
mapped into APB, so all accesses are indirect through the bridge.

The On-Chip Peripheral Bus (OPB): A low-speed bus typically found in
POWER processors. This now makes an appearance in the ASPEED SoC due
to tight integration of the FSI master IP with the OPB, mainly the
existence of an MMIO-mapping of the CFAM address straight onto a
sub-region of the OPB address space.

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Ninad Palsule <ninad@linux.ibm.com>
Reviewed-by: Cédric Le Goater <clg@kaod.org>
[ clg: - moved FSIMasterState under AspeedAPB2OPBState
       - modified fsi_opb_fsi_master_address() and
         fsi_opb_opb2fsi_address()
       - instroduced fsi_aspeed_apb2opb_init()
       - reworked fsi_aspeed_apb2opb_realize()
       - removed FSIMasterState object and fsi_opb_realize()
       - simplified OPBus ]
Signed-off-by: Cédric Le Goater <clg@kaod.org>
  • Loading branch information
ninadpalsule authored and legoater committed Jan 26, 2024
1 parent 7d3122b commit 59b3572
Show file tree
Hide file tree
Showing 6 changed files with 384 additions and 0 deletions.
1 change: 1 addition & 0 deletions hw/arm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ config ASPEED_SOC
select LED
select PMBUS
select MAX31785
select FSI_APB2OPB_ASPEED

config MPS2
bool
Expand Down
5 changes: 5 additions & 0 deletions hw/fsi/Kconfig
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
config FSI_APB2OPB_ASPEED
bool
depends on ASPEED_SOC
select FSI

config FSI
bool
329 changes: 329 additions & 0 deletions hw/fsi/aspeed_apb2opb.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
/*
* 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 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 = address_space_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);
1 change: 1 addition & 0 deletions hw/fsi/meson.build
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
system_ss.add(when: 'CONFIG_FSI', if_true: files('lbus.c','fsi.c','cfam.c','fsi-master.c'))
system_ss.add(when: 'CONFIG_FSI_APB2OPB_ASPEED', if_true: files('aspeed_apb2opb.c'))
2 changes: 2 additions & 0 deletions hw/fsi/trace-events
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ fsi_cfam_unimplemented_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%"
fsi_cfam_config_write_noaddr(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size=%d value=0x%"PRIx64
fsi_master_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size=%d"
fsi_master_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size=%d value=0x%"PRIx64
fsi_aspeed_apb2opb_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size=%d"
fsi_aspeed_apb2opb_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size=%d value=0x%"PRIx64

0 comments on commit 59b3572

Please sign in to comment.