Add the two Octeon TWSI controllers and a synthesized DDR4 SPD
EEPROM. Derive the SPD geometry and timing from the configured RAM size
and DDR clock, then wire both controller windows into the machine.
---
hw/i2c/meson.build | 5 +
hw/i2c/octeon_twsi.c | 553 ++++++++++++++++++++++++++++++++++++++
hw/mips/octeon.c | 8 +-
hw/mips/octeon_internal.h | 11 +
4 files changed, 576 insertions(+), 1 deletion(-)
create mode 100644 hw/i2c/octeon_twsi.c
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
index 88aea35662..1c744768e4 100644
--- a/hw/i2c/meson.build
+++ b/hw/i2c/meson.build
@@ -20,3 +20,8 @@ i2c_ss.add(when: 'CONFIG_PCA954X', if_true:
files('i2c_mux_pca954x.c'))
i2c_ss.add(when: 'CONFIG_PMBUS', if_true: files('pmbus_device.c'))
i2c_ss.add(when: 'CONFIG_BCM2835_I2C', if_true: files('bcm2835_i2c.c'))
system_ss.add_all(when: 'CONFIG_I2C', if_true: i2c_ss)
+
+if 'CONFIG_TCG' in config_all_accel
+ mips_ss.add(when: 'CONFIG_MIPS_OCTEON',
+ if_true: files('octeon_twsi.c'))
+endif
diff --git a/hw/i2c/octeon_twsi.c b/hw/i2c/octeon_twsi.c
new file mode 100644
index 0000000000..3bc8bf7bcd
--- /dev/null
+++ b/hw/i2c/octeon_twsi.c
@@ -0,0 +1,553 @@
+/*
+ * QEMU Cavium Octeon TWSI controller model.
+ *
+ * Copyright (c) 2026 Kirill A. Korinsky
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu/host-utils.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "hw/core/boards.h"
+#include "hw/core/resettable.h"
+#include "hw/core/sysbus.h"
+#include "hw/mips/octeon_internal.h"
+#include "qemu/module.h"
+
+#define OCTEON_TWSI_SIZE 0x200
+#define OCTEON_TWSI0_BASE 0x1180000001000ULL
+#define OCTEON_TWSI1_BASE 0x1180000001200ULL
+#define OCTEON_TWSI_SW_TWSI 0x00
+#define OCTEON_TWSI_INT 0x10
+#define OCTEON_TWSI_SW_DATA_MASK 0xffffffffULL
+#define OCTEON_TWSI_SW_EOP_IA_SHIFT 32
+#define OCTEON_TWSI_SW_R (1ULL << 56)
+#define OCTEON_TWSI_SW_OP_SHIFT 57
+#define OCTEON_TWSI_SW_V (1ULL << 63)
+#define OCTEON_TWSI_OP_EOP_IA 6
+#define OCTEON_TWSI_EOP_DATA 1
+#define OCTEON_TWSI_EOP_CTL 2
+#define OCTEON_TWSI_EOP_STAT 3
+#define OCTEON_TWSI_CTL_AAK 0x04
+#define OCTEON_TWSI_CTL_IFLG 0x08
+#define OCTEON_TWSI_CTL_STP 0x10
+#define OCTEON_TWSI_CTL_STA 0x20
+#define OCTEON_TWSI_STAT_START 0x08
+#define OCTEON_TWSI_STAT_TXADDR_ACK 0x18
+#define OCTEON_TWSI_STAT_TXADDR_NAK 0x20
+#define OCTEON_TWSI_STAT_TXDATA_ACK 0x28
+#define OCTEON_TWSI_STAT_TXDATA_NAK 0x30
+#define OCTEON_TWSI_STAT_RXADDR_ACK 0x40
+#define OCTEON_TWSI_STAT_RXADDR_NAK 0x48
+#define OCTEON_TWSI_STAT_RXDATA_ACK 0x50
+#define OCTEON_TWSI_STAT_RXDATA_NAK 0x58
+#define OCTEON_SPD_BUS 1
+#define OCTEON_SPD_ADDR 0x50
+#define OCTEON_SPD_MIN_ROW_BITS 12
+#define OCTEON_SPD_MAX_ROW_BITS 18
+#define OCTEON_SPD_MIN_COL_BITS 9
+#define OCTEON_SPD_MAX_COL_BITS 10
+#define OCTEON_SPD_BANK_BITS 4
+#define OCTEON_SPD_MIN_RAM_SIZE \
+ (1ULL << (OCTEON_SPD_MIN_ROW_BITS + OCTEON_SPD_MIN_COL_BITS + \
+ OCTEON_SPD_BANK_BITS + 3))
+#define OCTEON_SPD_MAX_RAM_SIZE \
+ (1ULL << (OCTEON_SPD_MAX_ROW_BITS + OCTEON_SPD_MAX_COL_BITS + \
+ OCTEON_SPD_BANK_BITS + 3))
+#define OCTEON_SPD_DDR4_TCKMIN 18
+#define OCTEON_SPD_DDR4_FINE_TCKMIN 125
+#define OCTEON_SPD_DDR4_MTB_PS 125
+#define OCTEON_SPD_DDR4_FTB_PS 1
+
+#define TYPE_OCTEON_TWSI "octeon-twsi"
+OBJECT_DECLARE_TYPE(OcteonTWSIState, OcteonTWSIClass, OCTEON_TWSI)
+
+#define TYPE_OCTEON_SPD_EEPROM "octeon-spd-eeprom"
+OBJECT_DECLARE_TYPE(OcteonSpdEepromState, OcteonSpdEepromClass,
+ OCTEON_SPD_EEPROM)
+
+struct OcteonTWSIState {
+ SysBusDevice parent_obj;
+ OcteonState *board;
+ MemoryRegion mmio;
+ unsigned int bus;
+ uint64_t sw_twsi;
+ uint64_t int_reg;
+ uint8_t ctl;
+ uint8_t stat;
+ uint8_t data;
+ uint8_t slave_addr;
+ uint8_t write_buf[2];
+ unsigned int write_len;
+ bool have_slave;
+ bool read_transfer;
+ bool addr_phase;
+};
+
+struct OcteonTWSIClass {
+ SysBusDeviceClass parent_class;
+ ResettablePhases parent_phases;
+};
+
+struct OcteonSpdEepromState {
+ SysBusDevice parent_obj;
+ unsigned int bus;
+ uint8_t addr;
+ uint16_t offset;
+ uint8_t data[OCTEON_SPD_EEPROM_SIZE];
+};
+
+struct OcteonSpdEepromClass {
+ SysBusDeviceClass parent_class;
+ ResettablePhases parent_phases;
+};
+
+static const uint8_t octeon_spd_eeprom_template[OCTEON_SPD_EEPROM_SIZE] = {
+ [0] = 0x23, [1] = 0x11, [2] = 0x0c, [3] = 0x02,
+ [4] = 0x82, [5] = 0x19, [11] = 0x03, [12] = 0x01,
+ [13] = 0x03, [18] = 0x0a, [19] = 0x20, [20] = 0x78,
+ [24] = 0x6e, [25] = 0x6e, [26] = 0x6e, [27] = 0x11,
+ [29] = 0x6e, [31] = 0x05, [32] = 0x70, [33] = 0x03,
+ [34] = 0xd0, [35] = 0x02, [37] = 0xa8, [38] = 0x20,
+ [39] = 0x27, [40] = 0x28, [126] = 0x16, [127] = 0x2f,
+};
+
+static uint16_t octeon_spd_crc16(const uint8_t *ptr, int count)
+{
+ int crc = 0;
+ int i;
+
+ while (--count >= 0) {
+ crc ^= *ptr++ << 8;
+ for (i = 0; i < 8; i++) {
+ if (crc & 0x8000) {
+ crc = (crc << 1) ^ 0x1021;
+ } else {
+ crc <<= 1;
+ }
+ }
+ }
+
+ return crc & 0xffff;
+}
+
+static bool octeon_spd_geometry(uint64_t ram_size, int *row_bits,
+ int *col_bits, int *density_code)
+{
+ uint64_t ram_mb;
+ int pbank_lsb;
+ int row_col_bits;
+ int col;
+ int row;
+ int density;
+
+ if (ram_size < OCTEON_SPD_MIN_RAM_SIZE ||
+ ram_size > OCTEON_SPD_MAX_RAM_SIZE ||
+ !is_power_of_2(ram_size)) {
+ return false;
+ }
+
+ ram_mb = ram_size / MiB;
+ pbank_lsb = ctz64(ram_size);
+ row_col_bits = pbank_lsb - 3 - OCTEON_SPD_BANK_BITS;
+
+ col = MIN(OCTEON_SPD_MAX_COL_BITS,
+ row_col_bits - OCTEON_SPD_MIN_ROW_BITS);
+ col = MAX(OCTEON_SPD_MIN_COL_BITS, col);
+ row = row_col_bits - col;
+ density = ctz64(ram_mb) - 8;
+
+ if (row < OCTEON_SPD_MIN_ROW_BITS ||
+ row > OCTEON_SPD_MAX_ROW_BITS ||
+ density < 0 || density > 7) {
+ return false;
+ }
+
+ if (row_bits) {
+ *row_bits = row;
+ }
+ if (col_bits) {
+ *col_bits = col;
+ }
+ if (density_code) {
+ *density_code = density;
+ }
+ return true;
+}
+
+void octeon_validate_ram_size(uint64_t ram_size)
+{
+ if (!octeon_spd_geometry(ram_size, NULL, NULL, NULL)) {
+ error_report("octeon3 RAM size must be a power of two from "
+ "%" PRIu64 " MB to %" PRIu64 " GB",
+ (uint64_t)OCTEON_SPD_MIN_RAM_SIZE / MiB,
+ (uint64_t)OCTEON_SPD_MAX_RAM_SIZE / GiB);
+ exit(1);
+ }
+}
+
+static void octeon_spd_set_tckmin(OcteonState *s, uint8_t *spd)
+{
+ uint64_t tck_ps = (1000000000000ULL + s->ddr_hz - 1) / s->ddr_hz;
+ uint64_t mtb = tck_ps / OCTEON_SPD_DDR4_MTB_PS;
+ uint64_t fine = tck_ps % OCTEON_SPD_DDR4_MTB_PS;
+
+ if (mtb == 0 || mtb > UINT8_MAX || fine > INT8_MAX) {
+ error_report("octeon3 DDR clock cannot be represented in DDR4 SPD");
+ exit(1);
+ }
+
+ spd[OCTEON_SPD_DDR4_TCKMIN] = mtb;
+ spd[OCTEON_SPD_DDR4_FINE_TCKMIN] = fine / OCTEON_SPD_DDR4_FTB_PS;
+}
+
+void octeon_init_spd(OcteonState *s)
+{
+ DeviceState *dev = qdev_new(TYPE_OCTEON_SPD_EEPROM);
+ OcteonSpdEepromState *eeprom = OCTEON_SPD_EEPROM(dev);
+ uint64_t ram_size = s->machine->ram_size;
+ int col_bits;
+ int row_bits;
+ int density_code;
+ uint16_t crc;
+
+ s->spd[0] = eeprom;
+ eeprom->bus = OCTEON_SPD_BUS;
+ eeprom->addr = OCTEON_SPD_ADDR;
+ memcpy(eeprom->data, octeon_spd_eeprom_template,
+ sizeof(eeprom->data));
+
+ g_assert(octeon_spd_geometry(ram_size, &row_bits, &col_bits,
+ &density_code));
+
+ eeprom->data[4] = 0x80 | density_code;
+ eeprom->data[5] = ((row_bits - OCTEON_SPD_MIN_ROW_BITS) << 3) |
+ (col_bits - OCTEON_SPD_MIN_COL_BITS);
+ octeon_spd_set_tckmin(s, eeprom->data);
+
+ crc = octeon_spd_crc16(eeprom->data,
+ (eeprom->data[0] & 0x80) ? 117 : 126);
+ eeprom->data[126] = crc & 0xff;
+ eeprom->data[127] = crc >> 8;
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+}
+
+static OcteonSpdEepromState *octeon_twsi_find_spd(OcteonTWSIState *t)
+{
+ OcteonState *s = t->board;
+ unsigned int i;
+
+ for (i = 0; i < OCTEON_SPD_EEPROM_COUNT; i++) {
+ if (s->spd[i]->bus == t->bus &&
+ s->spd[i]->addr == t->slave_addr) {
+ return s->spd[i];
+ }
+ }
+
+ return NULL;
+}
+
+static uint8_t octeon_spd_eeprom_read(OcteonSpdEepromState *eeprom)
+{
+ uint8_t value = 0xff;
+
+ if (eeprom->offset < sizeof(eeprom->data)) {
+ value = eeprom->data[eeprom->offset];
+ }
+ eeprom->offset++;
+ return value;
+}
+
+static void octeon_twsi_write_ctl(OcteonTWSIState *t, uint8_t value)
+{
+ OcteonSpdEepromState *eeprom;
+
+ t->ctl = value | OCTEON_TWSI_CTL_IFLG;
+
+ if (value & OCTEON_TWSI_CTL_STA) {
+ t->have_slave = false;
+ t->addr_phase = false;
+ t->write_len = 0;
+ t->stat = OCTEON_TWSI_STAT_START;
+ } else if (value & OCTEON_TWSI_CTL_STP) {
+ t->have_slave = false;
+ t->addr_phase = false;
+ t->stat = OCTEON_TWSI_STAT_IDLE;
+ } else if (t->addr_phase) {
+ eeprom = octeon_twsi_find_spd(t);
+ t->addr_phase = false;
+ if (eeprom) {
+ t->stat = t->read_transfer ? OCTEON_TWSI_STAT_RXADDR_ACK :
+ OCTEON_TWSI_STAT_TXADDR_ACK;
+ } else {
+ t->stat = t->read_transfer ? OCTEON_TWSI_STAT_RXADDR_NAK :
+ OCTEON_TWSI_STAT_TXADDR_NAK;
+ }
+ } else if (t->read_transfer) {
+ t->stat = (value & OCTEON_TWSI_CTL_AAK) ?
+ OCTEON_TWSI_STAT_RXDATA_ACK : OCTEON_TWSI_STAT_RXDATA_NAK;
+ } else {
+ t->stat = octeon_twsi_find_spd(t) ? OCTEON_TWSI_STAT_TXDATA_ACK :
+ OCTEON_TWSI_STAT_TXDATA_NAK;
+ }
+}
+
+static void octeon_twsi_write_data(OcteonTWSIState *t, uint8_t value)
+{
+ OcteonSpdEepromState *eeprom;
+
+ t->data = value;
+
+ if (!t->have_slave) {
+ t->slave_addr = value >> 1;
+ t->read_transfer = value & 1;
+ t->have_slave = true;
+ t->addr_phase = true;
+ t->write_len = 0;
+ return;
+ }
+
+ if (!t->read_transfer && t->write_len < ARRAY_SIZE(t->write_buf)) {
+ t->write_buf[t->write_len++] = value;
+ eeprom = octeon_twsi_find_spd(t);
+ if (eeprom) {
+ if (t->write_len == 1) {
+ eeprom->offset = value;
+ } else if (t->write_len == ARRAY_SIZE(t->write_buf)) {
+ eeprom->offset = (t->write_buf[0] << 8) | t->write_buf[1];
+ }
+ }
+ }
+}
+
+static uint8_t octeon_twsi_read_reg(OcteonTWSIState *t, unsigned int reg)
+{
+ switch (reg) {
+ case OCTEON_TWSI_EOP_DATA:
+ if (t->read_transfer) {
+ OcteonSpdEepromState *eeprom = octeon_twsi_find_spd(t);
+
+ t->data = eeprom ? octeon_spd_eeprom_read(eeprom) : 0xff;
+ }
+ return t->data;
+ case OCTEON_TWSI_EOP_CTL:
+ return t->ctl;
+ case OCTEON_TWSI_EOP_STAT:
+ return t->stat;
+ default:
+ return 0;
+ }
+}
+
+static void octeon_twsi_write_reg(OcteonTWSIState *t, unsigned int reg,
+ uint8_t value)
+{
+ switch (reg) {
+ case OCTEON_TWSI_EOP_DATA:
+ octeon_twsi_write_data(t, value);
+ break;
+ case OCTEON_TWSI_EOP_CTL:
+ octeon_twsi_write_ctl(t, value);
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t octeon_twsi_exec(OcteonTWSIState *t, uint64_t value)
+{
+ unsigned int op = (value >> OCTEON_TWSI_SW_OP_SHIFT) & 0xf;
+ unsigned int reg = (value >> OCTEON_TWSI_SW_EOP_IA_SHIFT) & 0x7;
+
+ if (!(value & OCTEON_TWSI_SW_V)) {
+ return value;
+ }
+
+ if (op == OCTEON_TWSI_OP_EOP_IA) {
+ if (value & OCTEON_TWSI_SW_R) {
+ value &= ~OCTEON_TWSI_SW_DATA_MASK;
+ value |= octeon_twsi_read_reg(t, reg);
+ } else {
+ octeon_twsi_write_reg(t, reg, value & 0xff);
+ }
+ }
+
+ return value & ~OCTEON_TWSI_SW_V;
+}
+
+static uint64_t octeon_twsi_read(void *opaque, hwaddr addr, unsigned size)
+{
+ OcteonTWSIState *t = opaque;
+ hwaddr reg = addr & ~7ULL;
+
+ switch (reg) {
+ case OCTEON_TWSI_SW_TWSI:
+ return octeon_read64(t->sw_twsi, addr, size);
+ case OCTEON_TWSI_INT:
+ return octeon_read64(t->int_reg, addr, size);
+ default:
+ return 0;
+ }
+}
+
+static void octeon_twsi_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ OcteonTWSIState *t = opaque;
+ hwaddr reg = addr & ~7ULL;
+ uint64_t old = 0;
+
+ switch (reg) {
+ case OCTEON_TWSI_SW_TWSI:
+ old = t->sw_twsi;
+ value = octeon_write64(old, addr, value, size);
+ if (size == 4 && !(addr & 4)) {
+ t->sw_twsi = value;
+ } else {
+ t->sw_twsi = octeon_twsi_exec(t, value);
+ }
+ break;
+ case OCTEON_TWSI_INT:
+ old = t->int_reg;
+ t->int_reg = octeon_write64(old, addr, value, size);
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps octeon_twsi_ops = {
+ .read = octeon_twsi_read,
+ .write = octeon_twsi_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+};
+
+static void octeon_twsi_reset_hold(Object *obj, ResetType type)
+{
+ OcteonTWSIState *t = OCTEON_TWSI(obj);
+ OcteonTWSIClass *otc = OCTEON_TWSI_GET_CLASS(obj);
+
+ if (otc->parent_phases.hold) {
+ otc->parent_phases.hold(obj, type);
+ }
+
+ t->sw_twsi = 0;
+ t->int_reg = 0;
+ t->ctl = 0;
+ t->stat = OCTEON_TWSI_STAT_IDLE;
+ t->data = 0;
+ t->slave_addr = 0;
+ t->write_len = 0;
+ t->have_slave = false;
+ t->read_transfer = false;
+ t->addr_phase = false;
+ memset(t->write_buf, 0, sizeof(t->write_buf));
+}
+
+static void octeon_spd_eeprom_reset_hold(Object *obj, ResetType type)
+{
+ OcteonSpdEepromState *eeprom = OCTEON_SPD_EEPROM(obj);
+ OcteonSpdEepromClass *osec = OCTEON_SPD_EEPROM_GET_CLASS(obj);
+
+ if (osec->parent_phases.hold) {
+ osec->parent_phases.hold(obj, type);
+ }
+
+ eeprom->offset = 0;
+}
+
+static void octeon_twsi_realize(DeviceState *dev, Error **errp)
+{
+ static const char *const name[OCTEON_TWSI_COUNT] = {
+ "octeon.twsi0",
+ "octeon.twsi1",
+ };
+ OcteonTWSIState *t = OCTEON_TWSI(dev);
+
+ memory_region_init_io(&t->mmio, OBJECT(dev), &octeon_twsi_ops, t,
+ name[t->bus], OCTEON_TWSI_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &t->mmio);
+}
+
+static void octeon_twsi_init(Object *obj)
+{
+ OcteonTWSIState *t = OCTEON_TWSI(obj);
+
+ t->stat = OCTEON_TWSI_STAT_IDLE;
+}
+
+static void octeon_twsi_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ OcteonTWSIClass *otc = OCTEON_TWSI_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = octeon_twsi_realize;
+ dc->user_creatable = false;
+ resettable_class_set_parent_phases(rc, NULL, octeon_twsi_reset_hold, NULL,
+ &otc->parent_phases);
+}
+
+static void octeon_spd_eeprom_class_init(ObjectClass *klass,
+ const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ OcteonSpdEepromClass *osec = OCTEON_SPD_EEPROM_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->user_creatable = false;
+ resettable_class_set_parent_phases(rc, NULL,
+ octeon_spd_eeprom_reset_hold, NULL,
+ &osec->parent_phases);
+}
+
+static const TypeInfo octeon_twsi_type_info = {
+ .name = TYPE_OCTEON_TWSI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(OcteonTWSIState),
+ .instance_init = octeon_twsi_init,
+ .class_size = sizeof(OcteonTWSIClass),
+ .class_init = octeon_twsi_class_init,
+};
+
+static const TypeInfo octeon_spd_eeprom_type_info = {
+ .name = TYPE_OCTEON_SPD_EEPROM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(OcteonSpdEepromState),
+ .class_size = sizeof(OcteonSpdEepromClass),
+ .class_init = octeon_spd_eeprom_class_init,
+};
+
+static void octeon_twsi_register_types(void)
+{
+ type_register_static(&octeon_twsi_type_info);
+ type_register_static(&octeon_spd_eeprom_type_info);
+}
+
+type_init(octeon_twsi_register_types)
+
+void octeon_init_twsi(OcteonState *s)
+{
+ static const uint64_t base[OCTEON_TWSI_COUNT] = {
+ OCTEON_TWSI0_BASE,
+ OCTEON_TWSI1_BASE,
+ };
+ unsigned int i;
+
+ for (i = 0; i < OCTEON_TWSI_COUNT; i++) {
+ DeviceState *dev = qdev_new(TYPE_OCTEON_TWSI);
+ OcteonTWSIState *t = OCTEON_TWSI(dev);
+
+ t->board = s;
+ t->bus = i;
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base[i]);
+ }
+}
diff --git a/hw/mips/octeon.c b/hw/mips/octeon.c
index 133b3636a0..9388bc005b 100644
--- a/hw/mips/octeon.c
+++ b/hw/mips/octeon.c
@@ -64,7 +64,9 @@ OBJECT_DECLARE_SIMPLE_TYPE(OcteonCsrBankState,
OCTEON_CSR_BANK)
/*
* The physical layout follows U-Boot's generic Octeon III
- * OCTEON_MAX_PHY_MEM_SIZE value. Keep 1 GiB as the tested default.
+ * OCTEON_MAX_PHY_MEM_SIZE value. The synthesized DDR4 SPD below accepts
+ * power-of-two RAM sizes from 256 MiB through 32 GiB. Keep 1 GiB as the
+ * tested default.
*/
#define OCTEON_DEFAULT_RAM_SIZE (1 * GiB)
#define OCTEON_MAX_PHY_MEM_SIZE (512 * GiB)
@@ -1884,6 +1886,8 @@ static void mips_octeon_init(MachineState *machine)
exit(1);
}
+ octeon_validate_ram_size(machine->ram_size);
+ octeon_init_spd(s);
octeon_map_ram(s);
octeon_init_flash(s);
octeon_init_mio_boot_loc(s);
@@ -1902,6 +1906,8 @@ static void mips_octeon_init(MachineState *machine)
&s->intc->ciu3);
octeon_init_uart(s);
+ octeon_init_twsi(s);
+
memory_region_init_io(&s->csr_bank->csr, OBJECT(s->csr_bank),
&octeon_csr_ops, s,
"octeon.csr", OCTEON_CSR_SIZE);
diff --git a/hw/mips/octeon_internal.h b/hw/mips/octeon_internal.h
index fbe4875d66..e9826f1713 100644
--- a/hw/mips/octeon_internal.h
+++ b/hw/mips/octeon_internal.h
@@ -20,6 +20,10 @@
#define OCTEON_CIU3_IDT_COUNT (OCTEON_MAX_CPUS * 4)
#define OCTEON_MIO_BOOT_REG_CFG_COUNT 8
#define OCTEON_MIO_BOOT_LOC_SIZE 0x100
+#define OCTEON_TWSI_COUNT 2
+#define OCTEON_TWSI_STAT_IDLE 0xf8
+#define OCTEON_SPD_EEPROM_SIZE 139
+#define OCTEON_SPD_EEPROM_COUNT 1
typedef enum OcteonIRQ {
OCTEON_IRQ_UART,
@@ -44,6 +48,8 @@ typedef struct OcteonCPUState {
bool boot_cpu;
} OcteonCPUState;
+typedef struct OcteonSpdEepromState OcteonSpdEepromState;
+
struct OcteonState {
MachineState *machine;
Clock *cpuclk;
@@ -65,6 +71,7 @@ struct OcteonState {
OcteonRstState *rst;
OcteonIntcState *intc;
OcteonCsrBankState *csr_bank;
+ OcteonSpdEepromState *spd[OCTEON_SPD_EEPROM_COUNT];
SerialMM *uart;
MemoryRegion uart_alias;
MemoryRegion uart_tx;
@@ -81,4 +88,8 @@ void octeon_reg_store(GHashTable *regs, uint64_t reg,
uint64_t value);
bool octeon_csr_lookup(OcteonState *s, uint64_t reg, uint64_t *value);
void octeon_irq_set(void *opaque, int irq, int level);
+void octeon_validate_ram_size(uint64_t ram_size);
+void octeon_init_spd(OcteonState *s);
+void octeon_init_twsi(OcteonState *s);
+
#endif
--
2.54.0