QEMU main repository: Please see https://www.qemu.org/docs/master/devel/submitting-a-patch.html for how to submit changes to QEMU. Pull Requests are ignored. Please only use release tarballs from the QEMU website. http://www.qemu.org
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
7.7 KiB
258 lines
7.7 KiB
/*
|
|
* ASPEED I3C Controller
|
|
*
|
|
* Copyright (C) 2021 ASPEED Technology Inc.
|
|
* Copyright (C) 2025 Google, LLC.
|
|
*
|
|
* 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 "qemu/error-report.h"
|
|
#include "hw/i3c/aspeed_i3c.h"
|
|
#include "hw/core/registerfields.h"
|
|
#include "hw/core/qdev-properties.h"
|
|
#include "qapi/error.h"
|
|
#include "migration/vmstate.h"
|
|
#include "trace.h"
|
|
|
|
/* I3C Controller Registers */
|
|
REG32(I3C1_REG0, 0x10)
|
|
REG32(I3C1_REG1, 0x14)
|
|
FIELD(I3C1_REG1, I2C_MODE, 0, 1)
|
|
FIELD(I3C1_REG1, SLV_TEST_MODE, 1, 1)
|
|
FIELD(I3C1_REG1, ACT_MODE, 2, 2)
|
|
FIELD(I3C1_REG1, PENDING_INT, 4, 4)
|
|
FIELD(I3C1_REG1, SA, 8, 7)
|
|
FIELD(I3C1_REG1, SA_EN, 15, 1)
|
|
FIELD(I3C1_REG1, INST_ID, 16, 4)
|
|
REG32(I3C2_REG0, 0x20)
|
|
REG32(I3C2_REG1, 0x24)
|
|
FIELD(I3C2_REG1, I2C_MODE, 0, 1)
|
|
FIELD(I3C2_REG1, SLV_TEST_MODE, 1, 1)
|
|
FIELD(I3C2_REG1, ACT_MODE, 2, 2)
|
|
FIELD(I3C2_REG1, PENDING_INT, 4, 4)
|
|
FIELD(I3C2_REG1, SA, 8, 7)
|
|
FIELD(I3C2_REG1, SA_EN, 15, 1)
|
|
FIELD(I3C2_REG1, INST_ID, 16, 4)
|
|
REG32(I3C3_REG0, 0x30)
|
|
REG32(I3C3_REG1, 0x34)
|
|
FIELD(I3C3_REG1, I2C_MODE, 0, 1)
|
|
FIELD(I3C3_REG1, SLV_TEST_MODE, 1, 1)
|
|
FIELD(I3C3_REG1, ACT_MODE, 2, 2)
|
|
FIELD(I3C3_REG1, PENDING_INT, 4, 4)
|
|
FIELD(I3C3_REG1, SA, 8, 7)
|
|
FIELD(I3C3_REG1, SA_EN, 15, 1)
|
|
FIELD(I3C3_REG1, INST_ID, 16, 4)
|
|
REG32(I3C4_REG0, 0x40)
|
|
REG32(I3C4_REG1, 0x44)
|
|
FIELD(I3C4_REG1, I2C_MODE, 0, 1)
|
|
FIELD(I3C4_REG1, SLV_TEST_MODE, 1, 1)
|
|
FIELD(I3C4_REG1, ACT_MODE, 2, 2)
|
|
FIELD(I3C4_REG1, PENDING_INT, 4, 4)
|
|
FIELD(I3C4_REG1, SA, 8, 7)
|
|
FIELD(I3C4_REG1, SA_EN, 15, 1)
|
|
FIELD(I3C4_REG1, INST_ID, 16, 4)
|
|
REG32(I3C5_REG0, 0x50)
|
|
REG32(I3C5_REG1, 0x54)
|
|
FIELD(I3C5_REG1, I2C_MODE, 0, 1)
|
|
FIELD(I3C5_REG1, SLV_TEST_MODE, 1, 1)
|
|
FIELD(I3C5_REG1, ACT_MODE, 2, 2)
|
|
FIELD(I3C5_REG1, PENDING_INT, 4, 4)
|
|
FIELD(I3C5_REG1, SA, 8, 7)
|
|
FIELD(I3C5_REG1, SA_EN, 15, 1)
|
|
FIELD(I3C5_REG1, INST_ID, 16, 4)
|
|
REG32(I3C6_REG0, 0x60)
|
|
REG32(I3C6_REG1, 0x64)
|
|
FIELD(I3C6_REG1, I2C_MODE, 0, 1)
|
|
FIELD(I3C6_REG1, SLV_TEST_MODE, 1, 1)
|
|
FIELD(I3C6_REG1, ACT_MODE, 2, 2)
|
|
FIELD(I3C6_REG1, PENDING_INT, 4, 4)
|
|
FIELD(I3C6_REG1, SA, 8, 7)
|
|
FIELD(I3C6_REG1, SA_EN, 15, 1)
|
|
FIELD(I3C6_REG1, INST_ID, 16, 4)
|
|
|
|
static const uint32_t ast2600_i3c_controller_ro[ASPEED_I3C_NR_REGS] = {
|
|
[R_I3C1_REG0] = 0xcc000000,
|
|
[R_I3C1_REG1] = 0xfff00000,
|
|
[R_I3C2_REG0] = 0xcc000000,
|
|
[R_I3C2_REG1] = 0xfff00000,
|
|
[R_I3C3_REG0] = 0xcc000000,
|
|
[R_I3C3_REG1] = 0xfff00000,
|
|
[R_I3C4_REG0] = 0xcc000000,
|
|
[R_I3C4_REG1] = 0xfff00000,
|
|
[R_I3C5_REG0] = 0xcc000000,
|
|
[R_I3C5_REG1] = 0xfff00000,
|
|
[R_I3C6_REG0] = 0xcc000000,
|
|
[R_I3C6_REG1] = 0xfff00000,
|
|
};
|
|
|
|
static uint64_t aspeed_i3c_read(void *opaque, hwaddr addr, unsigned int size)
|
|
{
|
|
AspeedI3CState *s = ASPEED_I3C(opaque);
|
|
uint64_t val = 0;
|
|
|
|
val = s->regs[addr >> 2];
|
|
|
|
trace_aspeed_i3c_read(addr, val);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void aspeed_i3c_write(void *opaque,
|
|
hwaddr addr,
|
|
uint64_t data,
|
|
unsigned int size)
|
|
{
|
|
AspeedI3CState *s = ASPEED_I3C(opaque);
|
|
|
|
trace_aspeed_i3c_write(addr, data);
|
|
|
|
addr >>= 2;
|
|
|
|
data &= ~ast2600_i3c_controller_ro[addr];
|
|
/* I3C controller register */
|
|
switch (addr) {
|
|
case R_I3C1_REG1:
|
|
case R_I3C2_REG1:
|
|
case R_I3C3_REG1:
|
|
case R_I3C4_REG1:
|
|
case R_I3C5_REG1:
|
|
case R_I3C6_REG1:
|
|
if (data & R_I3C1_REG1_I2C_MODE_MASK) {
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"%s: Unsupported I2C mode [0x%08" HWADDR_PRIx
|
|
"]=%08" PRIx64 "\n",
|
|
__func__, addr << 2, data);
|
|
break;
|
|
}
|
|
if (data & R_I3C1_REG1_SA_EN_MASK) {
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"%s: Unsupported slave mode [%08" HWADDR_PRIx
|
|
"]=0x%08" PRIx64 "\n",
|
|
__func__, addr << 2, data);
|
|
break;
|
|
}
|
|
s->regs[addr] = data;
|
|
break;
|
|
default:
|
|
s->regs[addr] = data;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps aspeed_i3c_ops = {
|
|
.read = aspeed_i3c_read,
|
|
.write = aspeed_i3c_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 1,
|
|
.max_access_size = 4,
|
|
}
|
|
};
|
|
|
|
I3CBus *aspeed_i3c_get_bus(AspeedI3CState *s, uint8_t bus_num)
|
|
{
|
|
if (bus_num < ARRAY_SIZE(s->devices)) {
|
|
return s->devices[bus_num].bus;
|
|
}
|
|
/* Developer error, fail fast. */
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
static void aspeed_i3c_reset(DeviceState *dev)
|
|
{
|
|
AspeedI3CState *s = ASPEED_I3C(dev);
|
|
memset(s->regs, 0, sizeof(s->regs));
|
|
}
|
|
|
|
static void aspeed_i3c_instance_init(Object *obj)
|
|
{
|
|
AspeedI3CState *s = ASPEED_I3C(obj);
|
|
int i;
|
|
|
|
for (i = 0; i < ASPEED_I3C_NR_DEVICES; ++i) {
|
|
object_initialize_child(obj, "device[*]", &s->devices[i],
|
|
TYPE_DW_I3C);
|
|
}
|
|
}
|
|
|
|
static void aspeed_i3c_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
int i;
|
|
AspeedI3CState *s = ASPEED_I3C(dev);
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
|
|
|
memory_region_init(&s->iomem_container, OBJECT(s),
|
|
TYPE_ASPEED_I3C ".container", 0x8000);
|
|
|
|
sysbus_init_mmio(sbd, &s->iomem_container);
|
|
|
|
memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_i3c_ops, s,
|
|
TYPE_ASPEED_I3C ".regs", ASPEED_I3C_NR_REGS << 2);
|
|
|
|
memory_region_add_subregion(&s->iomem_container, 0x0, &s->iomem);
|
|
|
|
for (i = 0; i < ASPEED_I3C_NR_DEVICES; ++i) {
|
|
Object *i3c_dev = OBJECT(&s->devices[i]);
|
|
|
|
if (!object_property_set_uint(i3c_dev, "device-id", i, errp)) {
|
|
return;
|
|
}
|
|
|
|
if (!sysbus_realize(SYS_BUS_DEVICE(i3c_dev), errp)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Register Address of I3CX Device =
|
|
* (Base Address of Global Register) + (Offset of I3CX) + Offset
|
|
* X = 0, 1, 2, 3, 4, 5
|
|
* Offset of I3C0 = 0x2000
|
|
* Offset of I3C1 = 0x3000
|
|
* Offset of I3C2 = 0x4000
|
|
* Offset of I3C3 = 0x5000
|
|
* Offset of I3C4 = 0x6000
|
|
* Offset of I3C5 = 0x7000
|
|
*/
|
|
memory_region_add_subregion(&s->iomem_container,
|
|
0x2000 + i * 0x1000, &s->devices[i].mr);
|
|
}
|
|
|
|
}
|
|
|
|
static const VMStateDescription vmstate_aspeed_i3c = {
|
|
.name = TYPE_ASPEED_I3C,
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (const VMStateField[]) {
|
|
VMSTATE_UINT32_ARRAY(regs, AspeedI3CState, ASPEED_I3C_NR_REGS),
|
|
VMSTATE_STRUCT_ARRAY(devices, AspeedI3CState, ASPEED_I3C_NR_DEVICES, 1,
|
|
vmstate_dw_i3c, DWI3C),
|
|
VMSTATE_END_OF_LIST(),
|
|
}
|
|
};
|
|
|
|
static void aspeed_i3c_class_init(ObjectClass *klass, const void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->realize = aspeed_i3c_realize;
|
|
device_class_set_legacy_reset(dc, aspeed_i3c_reset);
|
|
dc->desc = "Aspeed I3C Controller";
|
|
dc->vmsd = &vmstate_aspeed_i3c;
|
|
}
|
|
|
|
static const TypeInfo aspeed_i3c_types[] = {
|
|
{
|
|
.name = TYPE_ASPEED_I3C,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_init = aspeed_i3c_instance_init,
|
|
.instance_size = sizeof(AspeedI3CState),
|
|
.class_init = aspeed_i3c_class_init,
|
|
},
|
|
};
|
|
|
|
DEFINE_TYPES(aspeed_i3c_types)
|
|
|